├── .github ├── actions │ └── setup │ │ └── action.yaml ├── dependabot.yaml └── workflows │ ├── bench.yaml │ ├── dependabot.yaml │ ├── document.yaml │ ├── lint.yaml │ ├── release.yaml │ └── test.yaml ├── .gitignore ├── .goreleaser.yaml ├── .ruby-version ├── Cargo.lock ├── Cargo.toml ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── aa-tree.scm ├── bench ├── Cargo.toml ├── benches │ ├── embed.rs │ └── main.rs ├── build.rs └── src │ ├── add │ ├── main.lua │ ├── main.py │ ├── main.rb │ └── main.scm │ ├── empty │ ├── main.lua │ ├── main.py │ ├── main.rb │ └── main.scm │ ├── eval │ ├── main.lua │ ├── main.py │ ├── main.rb │ └── main.scm │ ├── fibonacci │ ├── main.lua │ ├── main.py │ ├── main.rb │ └── main.scm │ ├── hello │ ├── main.lua │ ├── main.py │ ├── main.rb │ └── main.scm │ ├── lib.rs │ ├── read │ └── main.scm │ ├── sum │ ├── main.lua │ ├── main.py │ ├── main.rb │ └── main.scm │ ├── tak │ ├── main.lua │ ├── main.py │ ├── main.rb │ └── main.scm │ └── write │ └── main.scm ├── build ├── Cargo.toml └── src │ ├── error.rs │ ├── lib.rs │ └── prelude.scm ├── cmd ├── compile │ ├── Cargo.toml │ └── src │ │ ├── main.rs │ │ └── main.scm ├── decode │ ├── Cargo.toml │ └── src │ │ ├── main.rs │ │ └── main.scm ├── interpret │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── minify │ ├── Cargo.toml │ └── src │ │ ├── main.rs │ │ └── main.scm ├── minimal │ ├── Cargo.lock │ ├── Cargo.toml │ ├── interpret │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ └── run │ │ ├── Cargo.toml │ │ └── src │ │ ├── main.rs │ │ └── main.scm ├── profile │ ├── Cargo.toml │ └── src │ │ └── main.rs └── repl │ ├── Cargo.toml │ └── src │ ├── main.rs │ └── main.scm ├── compile.scm ├── compiler ├── Cargo.toml ├── build.rs └── src │ ├── compile.scm │ ├── error.rs │ ├── lib.rs │ └── prelude.scm ├── configuration ├── Cargo.toml └── src │ └── lib.rs ├── cspell.json ├── device ├── Cargo.toml └── src │ ├── device.rs │ ├── device │ ├── buffer_error.rs │ ├── fixed_buffer.rs │ ├── libc.rs │ ├── libc │ │ ├── buffer.rs │ │ ├── device.rs │ │ ├── error.rs │ │ ├── read.rs │ │ ├── stdio.rs │ │ └── write.rs │ ├── read_write.rs │ ├── stdio.rs │ └── void.rs │ ├── lib.rs │ ├── primitive_set.rs │ └── primitive_set │ ├── error.rs │ └── primitive.rs ├── doc ├── .gitignore ├── astro.config.ts ├── biome.json ├── eslint.config.js ├── package-lock.json ├── package.json ├── public │ ├── .gitignore │ └── manifest.json ├── src │ ├── application │ │ ├── compile.ts │ │ ├── compile │ │ │ └── worker.ts │ │ ├── handle-worker-message.ts │ │ ├── interpret.ts │ │ ├── interpret │ │ │ └── worker.ts │ │ ├── repl.ts │ │ ├── repl │ │ │ └── worker.ts │ │ ├── result.ts │ │ ├── run-worker.ts │ │ ├── run.ts │ │ └── run │ │ │ └── worker.ts │ ├── components │ │ ├── Button.module.css │ │ ├── Button.tsx │ │ ├── ButtonGroup.module.css │ │ ├── ButtonGroup.tsx │ │ ├── CodeEditor.tsx │ │ ├── CompilerDemo.astro │ │ ├── CompilerDemo │ │ │ ├── DemoForm.module.css │ │ │ ├── DemoForm.tsx │ │ │ ├── DemoIo.module.css │ │ │ └── DemoIo.tsx │ │ ├── Demo.astro │ │ ├── Demo.module.css │ │ ├── ErrorMessage.module.css │ │ ├── ErrorMessage.tsx │ │ ├── Field.module.css │ │ ├── Field.tsx │ │ ├── InterpreterDemo.astro │ │ ├── InterpreterDemo │ │ │ ├── DemoForm.module.css │ │ │ ├── DemoForm.tsx │ │ │ ├── DemoOutput.module.css │ │ │ └── DemoOutput.tsx │ │ ├── Label.module.css │ │ ├── Label.tsx │ │ ├── Link.module.css │ │ ├── Link.tsx │ │ ├── ReplDemo.astro │ │ ├── ReplDemo │ │ │ ├── DemoForm.module.css │ │ │ └── DemoForm.tsx │ │ ├── TextArea.module.css │ │ └── TextArea.tsx │ ├── content.config.ts │ ├── content │ │ └── docs │ │ │ ├── demo │ │ │ ├── compiler.mdx │ │ │ └── interpreter.mdx │ │ │ ├── embedding-scripts.md │ │ │ ├── examples │ │ │ └── .gitignore │ │ │ ├── hot-reload.md │ │ │ ├── index.mdx │ │ │ ├── install.md │ │ │ ├── limitations.md │ │ │ ├── no-std-no-alloc.md │ │ │ └── writing-scheme-subset.md │ ├── env.d.ts │ ├── index.css │ ├── stores │ │ ├── compiler.ts │ │ ├── interpreter.ts │ │ └── repl.ts │ └── system.module.css ├── tools │ └── build.sh └── tsconfig.json ├── dynamic ├── Cargo.toml └── src │ ├── error.rs │ ├── lib.rs │ ├── primitive_set.rs │ └── scheme_value.rs ├── engine ├── Cargo.toml ├── build.rs └── src │ ├── engine.rs │ ├── error.rs │ ├── lib.rs │ └── primitive_set.rs ├── examples ├── README.md ├── custom-vm │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ └── src │ │ ├── bar.scm │ │ ├── foo.scm │ │ └── main.rs ├── embedded-script │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ └── src │ │ ├── fight.scm │ │ └── main.rs ├── fibonacci.scm ├── hot-reload │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ └── src │ │ ├── handler.scm │ │ └── main.rs └── no-std-no-alloc │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ └── src │ ├── fibonacci.scm │ └── lib.rs ├── features ├── continuation.feature ├── dynamic-wind.feature ├── equal.feature ├── evaluation.feature ├── exception.feature ├── file.feature ├── instrinsics.feature ├── lazy.feature ├── library.feature ├── macro.feature ├── multiple-values.feature ├── parameter.feature ├── process-context │ ├── command-line.feature │ ├── environment-variable.feature │ └── exit.feature ├── quasi-quote.feature ├── quote.feature ├── r5rs.feature ├── read.feature ├── smoke.feature ├── support │ └── aruba.rb ├── syntax │ ├── and.feature │ ├── begin.feature │ ├── case-lambda.feature │ ├── case.feature │ ├── cond-expand.feature │ ├── cond.feature │ ├── define.feature │ ├── do.feature │ ├── if.feature │ ├── let-star.feature │ ├── let-syntax.feature │ ├── let.feature │ ├── letrec-star.feature │ ├── letrec-syntax.feature │ ├── letrec.feature │ ├── or.feature │ ├── set.feature │ ├── unless.feature │ └── when.feature ├── time.feature ├── types │ ├── aa-tree.feature │ ├── boolean.feature │ ├── bytevector.feature │ ├── character.feature │ ├── eof.feature │ ├── list.feature │ ├── number.feature │ ├── port.feature │ ├── procedure.feature │ ├── record.feature │ ├── string.feature │ ├── symbol.feature │ └── vector.feature └── write.feature ├── file ├── Cargo.toml └── src │ ├── file_system.rs │ ├── file_system │ ├── error.rs │ ├── libc.rs │ ├── memory.rs │ ├── os.rs │ ├── utility.rs │ └── void.rs │ ├── lib.rs │ ├── primitive_set.rs │ └── primitive_set │ ├── error.rs │ └── primitive.rs ├── inexact ├── Cargo.toml └── src │ ├── lib.rs │ ├── primitive.rs │ └── primitive_set.rs ├── libc ├── Cargo.toml └── src │ ├── heap.rs │ ├── lib.rs │ └── mmap.rs ├── macro-util ├── Cargo.toml └── src │ └── lib.rs ├── macro ├── Cargo.toml ├── src │ ├── foo.scm │ └── lib.rs └── tests │ ├── empty.scm │ └── main.rs ├── minifier ├── Cargo.toml └── src │ ├── lib.rs │ └── minify.scm ├── minifier_macro ├── Cargo.toml ├── src │ ├── foo.scm │ └── lib.rs └── tests │ ├── foo.scm │ └── main.rs ├── minify.scm ├── module ├── Cargo.toml └── src │ ├── guard.rs │ ├── hot_reload.rs │ ├── lib.rs │ ├── module.rs │ ├── static.rs │ └── universal.rs ├── native ├── Cargo.toml └── src │ ├── equal.rs │ ├── lib.rs │ ├── list.rs │ └── type_check.rs ├── prelude.scm ├── process_context ├── Cargo.toml └── src │ ├── lib.rs │ ├── primitive_set.rs │ ├── primitive_set │ └── primitive.rs │ ├── process_context.rs │ └── process_context │ ├── libc.rs │ ├── memory.rs │ ├── os.rs │ └── void.rs ├── profiler ├── Cargo.toml └── src │ ├── collapse.rs │ ├── duration.rs │ ├── error.rs │ ├── flamegraph.rs │ ├── lib.rs │ ├── read.rs │ ├── record.rs │ ├── record │ ├── duration_record.rs │ ├── procedure_operation.rs │ ├── procedure_record.rs │ ├── stack.rs │ └── stacked_record.rs │ ├── reverse.rs │ ├── stack_profiler.rs │ └── write.rs ├── r7rs ├── Cargo.toml └── src │ ├── lib.rs │ ├── small.rs │ └── small │ ├── error.rs │ └── primitive.rs ├── repl.scm ├── root ├── Cargo.toml ├── README.md ├── build.rs └── src │ ├── fibonacci.scm │ ├── fight.scm │ ├── hello.scm │ ├── lib.rs │ ├── main.rs │ └── main.scm ├── run.scm ├── rust-toolchain.toml ├── sac ├── Cargo.toml └── src │ ├── lib.rs │ └── main.scm ├── snapshots ├── aa-tree.md ├── bench │ └── src │ │ ├── add │ │ └── main.md │ │ ├── empty │ │ └── main.md │ │ ├── eval │ │ └── main.md │ │ ├── fibonacci │ │ └── main.md │ │ ├── hello │ │ └── main.md │ │ ├── read │ │ └── main.md │ │ ├── sum │ │ └── main.md │ │ ├── tak │ │ └── main.md │ │ └── write │ │ └── main.md ├── cmd │ └── decode │ │ └── src │ │ └── main.md ├── compile.md ├── examples │ ├── custom-vm │ │ └── src │ │ │ ├── bar.md │ │ │ └── foo.md │ ├── embedded-script │ │ └── src │ │ │ └── fight.md │ ├── fibonacci.md │ └── hot-reload │ │ └── src │ │ └── handler.md ├── macro │ ├── src │ │ └── foo.md │ └── tests │ │ └── empty.md ├── minifier_macro │ ├── src │ │ └── foo.md │ └── tests │ │ └── foo.md ├── minify.md ├── repl.md ├── run.md └── sac │ └── src │ └── main.md ├── time ├── Cargo.toml └── src │ ├── clock.rs │ ├── clock │ ├── libc.rs │ ├── os.rs │ └── void.rs │ ├── lib.rs │ ├── primitive_set.rs │ └── primitive_set │ ├── error.rs │ └── primitive.rs ├── tools ├── bench.sh ├── bytecode_size_bench.sh ├── ci │ ├── release.sh │ └── setup.sh ├── coverage.sh ├── cucumber.sh ├── decode_test.sh ├── document.sh ├── integration_test.sh ├── lint.sh ├── memory_bench.sh ├── profile_test.sh ├── r7rs_compatible_compiler_test.sh ├── rust_loc_bench.sh ├── scheme │ ├── chibi │ │ └── stak │ ├── gauche │ │ └── stak │ ├── guile │ │ └── stak │ ├── mstak-tools │ │ ├── stak │ │ ├── stak-compile │ │ └── stak-interpret │ ├── mstak │ │ └── stak │ ├── stak-tools │ │ ├── stak │ │ ├── stak-compile │ │ └── stak-interpret │ └── stak │ │ └── stak ├── self_host_test.sh ├── utility.sh └── version.sh ├── util ├── Cargo.toml └── src │ └── lib.rs ├── vm ├── Cargo.toml └── src │ ├── code.rs │ ├── cons.rs │ ├── error.rs │ ├── exception.rs │ ├── instruction.rs │ ├── lib.rs │ ├── memory.rs │ ├── number.rs │ ├── primitive_set.rs │ ├── profiler.rs │ ├── snapshots │ ├── stak_vm__memory__tests__create.snap │ ├── stak_vm__memory__tests__create_list-2.snap │ ├── stak_vm__memory__tests__create_list-3.snap │ ├── stak_vm__memory__tests__create_list.snap │ ├── stak_vm__memory__tests__garbage_collection__collect_cons.snap │ ├── stak_vm__memory__tests__garbage_collection__collect_cycle.snap │ ├── stak_vm__memory__tests__garbage_collection__collect_deep_stack.snap │ └── stak_vm__memory__tests__garbage_collection__collect_stack.snap │ ├── stack_slot.rs │ ├── type.rs │ ├── value.rs │ ├── value_inner.rs │ ├── value_inner │ ├── float62.rs │ ├── float64.rs │ └── integer63.rs │ └── vm.rs └── wasm ├── .gitignore ├── Cargo.toml ├── build.rs ├── package.json ├── src ├── lib.rs ├── repl.rs ├── repl.scm └── run.scm └── tests └── web.rs /.github/actions/setup/action.yaml: -------------------------------------------------------------------------------- 1 | name: stak/setup 2 | description: Sets up a build environment for Stak Scheme 3 | inputs: {} 4 | outputs: {} 5 | runs: 6 | using: composite 7 | steps: 8 | - uses: raviqqe/cargo-cache@v1 9 | - uses: ruby/setup-ruby@v1 10 | - uses: homebrew/actions/setup-homebrew@master 11 | with: 12 | stable: true 13 | - run: tools/ci/setup.sh 14 | shell: bash 15 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: daily 7 | - package-ecosystem: github-actions 8 | directory: /.github/actions/setup 9 | schedule: 10 | interval: daily 11 | - package-ecosystem: cargo 12 | directory: / 13 | schedule: 14 | interval: daily 15 | - package-ecosystem: cargo 16 | directory: /cmd/minimal 17 | schedule: 18 | interval: daily 19 | - package-ecosystem: bundler 20 | directory: / 21 | schedule: 22 | interval: daily 23 | - package-ecosystem: npm 24 | directory: /doc 25 | groups: 26 | astro: 27 | patterns: 28 | - astro 29 | - "@astrojs/*" 30 | schedule: 31 | interval: daily 32 | -------------------------------------------------------------------------------- /.github/workflows/dependabot.yaml: -------------------------------------------------------------------------------- 1 | name: dependabot 2 | on: pull_request 3 | permissions: 4 | contents: write 5 | pull-requests: write 6 | jobs: 7 | merge: 8 | runs-on: ubuntu-latest 9 | if: github.actor == 'dependabot[bot]' 10 | steps: 11 | - run: gh pr merge --auto --squash ${{ github.event.pull_request.html_url }} 12 | env: 13 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.bc 2 | *.info 3 | .cargo_cache 4 | target 5 | tmp 6 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | builds: 3 | - id: stak 4 | binary: stak 5 | builder: rust 6 | flags: --release -p=stak 7 | - id: stak-compile 8 | binary: stak-compile 9 | builder: rust 10 | flags: --release -p=stak-compile 11 | - id: stak-interpret 12 | binary: stak-interpret 13 | builder: rust 14 | flags: --release -p=stak-interpret 15 | - id: stak-repl 16 | binary: stak-repl 17 | builder: rust 18 | flags: --release -p=stak-repl 19 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | gem "aruba", "~> 2.3.0" 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Yota Toyama, Samuel Yvon, Léonard Oest O'Leary, Mathis Laroche, Marc Feeley, Alex Shinn, the R7RS authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /bench/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stak-bench" 3 | version.workspace = true 4 | publish = false 5 | edition.workspace = true 6 | 7 | [dependencies] 8 | mlua = { version = "0.10.5", features = ["lua54"] } 9 | stak = { version = "0.10.24", path = "../root" } 10 | stak-compiler = { version = "0.10.24", path = "../compiler" } 11 | 12 | [dev-dependencies] 13 | criterion = { package = "codspeed-criterion-compat", version = "2.7.2" } 14 | 15 | [build-dependencies] 16 | stak-build = { version = "0.10.24", path = "../build" } 17 | 18 | [lints] 19 | workspace = true 20 | 21 | [[bench]] 22 | name = "main" 23 | harness = false 24 | 25 | [[bench]] 26 | name = "embed" 27 | harness = false 28 | -------------------------------------------------------------------------------- /bench/build.rs: -------------------------------------------------------------------------------- 1 | //! A build script. 2 | 3 | use stak_build::{BuildError, build_r7rs}; 4 | 5 | fn main() -> Result<(), BuildError> { 6 | build_r7rs() 7 | } 8 | -------------------------------------------------------------------------------- /bench/src/add/main.lua: -------------------------------------------------------------------------------- 1 | local x = 1 + 2 + 3 2 | -------------------------------------------------------------------------------- /bench/src/add/main.py: -------------------------------------------------------------------------------- 1 | x = 1 + 2 + 3 2 | -------------------------------------------------------------------------------- /bench/src/add/main.rb: -------------------------------------------------------------------------------- 1 | x = 1 + 2 + 3 2 | -------------------------------------------------------------------------------- /bench/src/add/main.scm: -------------------------------------------------------------------------------- 1 | (import (scheme base)) 2 | 3 | (+ 1 2 3) 4 | -------------------------------------------------------------------------------- /bench/src/empty/main.lua: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /bench/src/empty/main.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /bench/src/empty/main.rb: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /bench/src/empty/main.scm: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /bench/src/eval/main.lua: -------------------------------------------------------------------------------- 1 | load([[ 2 | local y = 0 3 | 4 | for x = 0, 10000000 do 5 | y = x + y 6 | end 7 | 8 | print(y) 9 | ]])() 10 | -------------------------------------------------------------------------------- /bench/src/eval/main.py: -------------------------------------------------------------------------------- 1 | exec(""" 2 | def sum(x): 3 | z = 0 4 | 5 | for y in range(0, x + 1): 6 | z += y 7 | 8 | return z 9 | 10 | print(sum(10000000)) 11 | """) 12 | -------------------------------------------------------------------------------- /bench/src/eval/main.rb: -------------------------------------------------------------------------------- 1 | eval(" 2 | y = 0 3 | 4 | (0..10000000).each do |x| 5 | y += x 6 | end 7 | 8 | print(y) 9 | ") 10 | -------------------------------------------------------------------------------- /bench/src/eval/main.scm: -------------------------------------------------------------------------------- 1 | (import (scheme base) (scheme eval) (scheme write)) 2 | 3 | (define sum 4 | (eval 5 | '(lambda (x) 6 | (let loop ((x x) (y 0)) 7 | (if (zero? x) 8 | y 9 | (loop (- x 1) (+ x y))))) 10 | (environment '(scheme base)))) 11 | 12 | (write (sum 10000000)) 13 | -------------------------------------------------------------------------------- /bench/src/fibonacci/main.lua: -------------------------------------------------------------------------------- 1 | function fibonacci(x) 2 | if x < 2 then 3 | return x 4 | end 5 | 6 | return fibonacci(x - 1) + fibonacci(x - 2) 7 | end 8 | 9 | print(fibonacci(32)) 10 | -------------------------------------------------------------------------------- /bench/src/fibonacci/main.py: -------------------------------------------------------------------------------- 1 | def fibonacci(x): 2 | return x if x < 2 else fibonacci(x - 1) + fibonacci(x - 2) 3 | 4 | 5 | print(fibonacci(32)) 6 | -------------------------------------------------------------------------------- /bench/src/fibonacci/main.rb: -------------------------------------------------------------------------------- 1 | def fibonacci(x) 2 | x < 2 ? x : fibonacci(x - 1) + fibonacci(x - 2) 3 | end 4 | 5 | p(fibonacci(32)) 6 | -------------------------------------------------------------------------------- /bench/src/fibonacci/main.scm: -------------------------------------------------------------------------------- 1 | (import (scheme base) (scheme write)) 2 | 3 | (define (fibonacci x) 4 | (if (< x 2) 5 | x 6 | (+ 7 | (fibonacci (- x 1)) 8 | (fibonacci (- x 2))))) 9 | 10 | (write (fibonacci 32)) 11 | -------------------------------------------------------------------------------- /bench/src/hello/main.lua: -------------------------------------------------------------------------------- 1 | print("Hello, world!") 2 | -------------------------------------------------------------------------------- /bench/src/hello/main.py: -------------------------------------------------------------------------------- 1 | print("Hello, world!") 2 | -------------------------------------------------------------------------------- /bench/src/hello/main.rb: -------------------------------------------------------------------------------- 1 | puts("Hello, world!") 2 | -------------------------------------------------------------------------------- /bench/src/hello/main.scm: -------------------------------------------------------------------------------- 1 | (import (scheme base)) 2 | 3 | (write-string "Hello, world!\n") 4 | -------------------------------------------------------------------------------- /bench/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Benchmarks for Stak Scheme. 2 | -------------------------------------------------------------------------------- /bench/src/read/main.scm: -------------------------------------------------------------------------------- 1 | (import (scheme base) (scheme read)) 2 | 3 | (define (read-all) 4 | (let ((x (read))) 5 | (if (eof-object? x) 6 | '() 7 | (cons x (read-all))))) 8 | 9 | (read-all) 10 | -------------------------------------------------------------------------------- /bench/src/sum/main.lua: -------------------------------------------------------------------------------- 1 | local y = 0 2 | 3 | for x = 0, 10000000 do 4 | y = x + y 5 | end 6 | 7 | print(y) 8 | -------------------------------------------------------------------------------- /bench/src/sum/main.py: -------------------------------------------------------------------------------- 1 | def sum(x): 2 | z = 0 3 | 4 | for y in range(0, x + 1): 5 | z += y 6 | 7 | return z 8 | 9 | 10 | print(sum(10000000)) 11 | -------------------------------------------------------------------------------- /bench/src/sum/main.rb: -------------------------------------------------------------------------------- 1 | y = 0 2 | 3 | (0..10000000).each do |x| 4 | y += x 5 | end 6 | 7 | print(y) 8 | -------------------------------------------------------------------------------- /bench/src/sum/main.scm: -------------------------------------------------------------------------------- 1 | (import (scheme base) (scheme write)) 2 | 3 | (define (sum x) 4 | (let loop ((x x) (y 0)) 5 | (if (zero? x) 6 | y 7 | (loop (- x 1) (+ x y))))) 8 | 9 | (write (sum 10000000)) 10 | -------------------------------------------------------------------------------- /bench/src/tak/main.lua: -------------------------------------------------------------------------------- 1 | function tak(x, y, z) 2 | if y < x then 3 | return tak(tak(x - 1, y, z), tak(y - 1, z, x), tak(z - 1, x, y)) 4 | end 5 | 6 | return z 7 | end 8 | 9 | print(tak(16, 8, 0)) 10 | -------------------------------------------------------------------------------- /bench/src/tak/main.py: -------------------------------------------------------------------------------- 1 | def tak(x, y, z): 2 | if y < x: 3 | return tak(tak(x - 1, y, z), tak(y - 1, z, x), tak(z - 1, x, y)) 4 | else: 5 | return z 6 | 7 | 8 | print(tak(16, 8, 0)) 9 | -------------------------------------------------------------------------------- /bench/src/tak/main.rb: -------------------------------------------------------------------------------- 1 | def tak(x, y, z) 2 | if y < x 3 | tak(tak(x - 1, y, z), tak(y - 1, z, x), tak(z - 1, x, y)) 4 | else 5 | z 6 | end 7 | end 8 | 9 | p(tak(16, 8, 0)) 10 | -------------------------------------------------------------------------------- /bench/src/tak/main.scm: -------------------------------------------------------------------------------- 1 | (import (scheme base) (scheme write)) 2 | 3 | (define (tak x y z) 4 | (if (< y x) 5 | (tak 6 | (tak (- x 1) y z) 7 | (tak (- y 1) z x) 8 | (tak (- z 1) x y)) 9 | z)) 10 | 11 | (write (tak 16 8 0)) 12 | -------------------------------------------------------------------------------- /bench/src/write/main.scm: -------------------------------------------------------------------------------- 1 | (import (scheme base) (scheme read) (scheme write)) 2 | 3 | (define (read-all) 4 | (let ((x (read))) 5 | (if (eof-object? x) 6 | '() 7 | (cons x (read-all))))) 8 | 9 | (write (read-all)) 10 | -------------------------------------------------------------------------------- /build/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stak-build" 3 | description = "Build scripts for Stak Scheme" 4 | categories.workspace = true 5 | edition.workspace = true 6 | keywords.workspace = true 7 | license-file.workspace = true 8 | readme.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [dependencies] 13 | glob = "0.3.2" 14 | stak-compiler = { version = "0.10.24", path = "../compiler" } 15 | tokio = { version = "1.45.1", features = [ 16 | "fs", 17 | "io-util", 18 | "process", 19 | "rt-multi-thread", 20 | ] } 21 | which = "7.0.3" 22 | 23 | [lints] 24 | workspace = true 25 | -------------------------------------------------------------------------------- /build/src/prelude.scm: -------------------------------------------------------------------------------- 1 | ../../prelude.scm -------------------------------------------------------------------------------- /cmd/compile/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stak-compile" 3 | description = "Stak Scheme bytecode compiler" 4 | categories.workspace = true 5 | edition.workspace = true 6 | keywords.workspace = true 7 | license-file.workspace = true 8 | readme.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [features] 13 | default = ["float"] 14 | float = ["stak-sac/float"] 15 | 16 | [dependencies] 17 | stak-sac = { version = "0.10.24", path = "../../sac", features = ["std"] } 18 | 19 | [lints] 20 | workspace = true 21 | -------------------------------------------------------------------------------- /cmd/compile/src/main.rs: -------------------------------------------------------------------------------- 1 | //! A command to compile source codes in Scheme into bytecodes for a virtual 2 | //! machine. 3 | //! 4 | //! # Usage 5 | //! 6 | //! ```sh 7 | //! stak-compile < foo.scm > foo.bc 8 | //! ``` 9 | 10 | stak_sac::main!("main.scm"); 11 | -------------------------------------------------------------------------------- /cmd/compile/src/main.scm: -------------------------------------------------------------------------------- 1 | ../../../compile.scm -------------------------------------------------------------------------------- /cmd/decode/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stak-decode" 3 | description = "Stak Scheme bytecode decoder" 4 | categories.workspace = true 5 | edition.workspace = true 6 | keywords.workspace = true 7 | license-file.workspace = true 8 | readme.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [dependencies] 13 | stak-sac = { version = "0.10.24", path = "../../sac", features = [ 14 | "float", 15 | "std", 16 | ] } 17 | 18 | [lints] 19 | workspace = true 20 | -------------------------------------------------------------------------------- /cmd/decode/src/main.rs: -------------------------------------------------------------------------------- 1 | //! A command to decode a bytecode file. 2 | //! 3 | //! It decodes a program in bytecodes and prints it in Markdown. This command is 4 | //! primarily for debugging. 5 | //! 6 | //! # Usage 7 | //! 8 | //! ```sh 9 | //! stak-decode < foo.bc 10 | //! ``` 11 | 12 | stak_sac::main!("main.scm"); 13 | -------------------------------------------------------------------------------- /cmd/interpret/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stak-interpret" 3 | description = "Stak Scheme bytecode interpreter" 4 | categories.workspace = true 5 | edition.workspace = true 6 | keywords.workspace = true 7 | license-file.workspace = true 8 | readme.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [features] 13 | default = ["float"] 14 | float = ["stak-vm/float"] 15 | float62 = ["stak-vm/float62"] 16 | gc_always = ["stak-vm/gc_always"] 17 | trace_instruction = ["stak-vm/trace_instruction"] 18 | trace_memory = ["stak-vm/trace_memory"] 19 | 20 | [dependencies] 21 | clap = { version = "4.5.39", features = ["derive"] } 22 | main_error = "0.1.2" 23 | stak-configuration = { version = "0.10.24", path = "../../configuration" } 24 | stak-device = { version = "0.10.24", path = "../../device", features = ["std"] } 25 | stak-file = { version = "0.10.24", path = "../../file", features = ["std"] } 26 | stak-process-context = { version = "0.10.24", path = "../../process_context", features = [ 27 | "std", 28 | ] } 29 | stak-r7rs = { version = "0.10.24", path = "../../r7rs" } 30 | stak-time = { version = "0.10.24", path = "../../time", features = ["std"] } 31 | stak-vm = { version = "0.10.24", path = "../../vm" } 32 | 33 | [lints] 34 | workspace = true 35 | -------------------------------------------------------------------------------- /cmd/interpret/src/main.rs: -------------------------------------------------------------------------------- 1 | //! A command to interpret a bytecode file. 2 | //! 3 | //! # Usage 4 | //! 5 | //! ```sh 6 | //! stak-interpret foo.bc 7 | //! ``` 8 | 9 | use clap::Parser; 10 | use main_error::MainError; 11 | use stak_configuration::DEFAULT_HEAP_SIZE; 12 | use stak_device::StdioDevice; 13 | use stak_file::OsFileSystem; 14 | use stak_process_context::OsProcessContext; 15 | use stak_r7rs::SmallPrimitiveSet; 16 | use stak_time::OsClock; 17 | use stak_vm::Vm; 18 | use std::{fs::read, path::PathBuf}; 19 | 20 | #[derive(clap::Parser)] 21 | #[command(about, version)] 22 | struct Arguments { 23 | #[arg(required(true))] 24 | file: PathBuf, 25 | #[arg()] 26 | arguments: Vec, 27 | #[arg(short = 's', long, default_value_t = DEFAULT_HEAP_SIZE)] 28 | heap_size: usize, 29 | } 30 | 31 | fn main() -> Result<(), MainError> { 32 | let arguments = Arguments::parse(); 33 | 34 | let mut heap = vec![Default::default(); arguments.heap_size]; 35 | let mut vm = Vm::new( 36 | &mut heap, 37 | SmallPrimitiveSet::new( 38 | StdioDevice::new(), 39 | OsFileSystem::new(), 40 | OsProcessContext::new(), 41 | OsClock::new(), 42 | ), 43 | )?; 44 | 45 | vm.initialize(read(&arguments.file)?)?; 46 | 47 | Ok(vm.run()?) 48 | } 49 | -------------------------------------------------------------------------------- /cmd/minify/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stak-minify" 3 | description = "Stak Scheme source code minifier" 4 | categories.workspace = true 5 | edition.workspace = true 6 | keywords.workspace = true 7 | license-file.workspace = true 8 | readme.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [features] 13 | default = ["float"] 14 | float = ["stak-sac/float"] 15 | 16 | [dependencies] 17 | stak-sac = { version = "0.10.24", path = "../../sac", features = ["std"] } 18 | 19 | [lints] 20 | workspace = true 21 | -------------------------------------------------------------------------------- /cmd/minify/src/main.rs: -------------------------------------------------------------------------------- 1 | //! A command to minify source codes in Scheme. 2 | //! 3 | //! # Usage 4 | //! 5 | //! ```sh 6 | //! stak-minify < foo.scm > bar.scm 7 | //! ``` 8 | 9 | stak_sac::main!("main.scm"); 10 | -------------------------------------------------------------------------------- /cmd/minify/src/main.scm: -------------------------------------------------------------------------------- 1 | ../../../minify.scm -------------------------------------------------------------------------------- /cmd/minimal/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = ["interpret", "run"] 4 | 5 | [workspace.package] 6 | categories = ["compilers", "no-std", "no-std::no-alloc", "wasm"] 7 | edition = "2024" 8 | keywords = ["interpreter", "language", "scheme"] 9 | license-file = "../../LICENSE" 10 | readme = "../../README.md" 11 | repository = "https://github.com/raviqqe/stak" 12 | version = "0.5.24" 13 | 14 | [workspace.lints.rust] 15 | missing_docs = "deny" 16 | warnings = "deny" 17 | 18 | [workspace.lints.clippy] 19 | cargo = "deny" 20 | complexity = "deny" 21 | correctness = "deny" 22 | perf = "deny" 23 | style = "deny" 24 | suspicious = "deny" 25 | 26 | alloc_instead_of_core = "deny" 27 | dbg_macro = "deny" 28 | derive_partial_eq_without_eq = "deny" 29 | equatable_if_let = "deny" 30 | explicit_deref_methods = "deny" 31 | if_not_else = "deny" 32 | manual_let_else = "deny" 33 | missing_const_for_fn = "deny" 34 | missing_panics_doc = "deny" 35 | multiple_crate_versions = { level = "allow", priority = 1 } 36 | option_if_let_else = "deny" 37 | std_instead_of_alloc = "deny" 38 | std_instead_of_core = "deny" 39 | todo = "deny" 40 | undocumented_unsafe_blocks = "deny" 41 | unimplemented = "deny" 42 | uninlined_format_args = "deny" 43 | unnecessary_safety_comment = "deny" 44 | unused_self = "deny" 45 | use_self = "deny" 46 | 47 | [profile.dev] 48 | lto = true 49 | opt-level = 3 50 | panic = "abort" 51 | 52 | [profile.release] 53 | codegen-units = 1 54 | lto = true 55 | panic = "abort" 56 | strip = true 57 | 58 | [profile.dev.build-override] 59 | opt-level = 3 60 | debug-assertions = false 61 | overflow-checks = false 62 | 63 | [profile.release.build-override] 64 | opt-level = 3 65 | debug-assertions = false 66 | overflow-checks = false 67 | -------------------------------------------------------------------------------- /cmd/minimal/interpret/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mstak-interpret" 3 | description = "Minimal Stak Scheme bytecode interpreter" 4 | categories.workspace = true 5 | edition.workspace = true 6 | keywords.workspace = true 7 | license-file.workspace = true 8 | readme.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [dependencies] 13 | libc = { version = "0.2.172", default-features = false } 14 | stak-device = { version = "0.10.0", path = "../../../device", features = [ 15 | "libc", 16 | ] } 17 | stak-file = { version = "0.10.0", path = "../../../file", features = ["libc"] } 18 | stak-process-context = { version = "0.10.0", path = "../../../process_context", features = [ 19 | "libc", 20 | ] } 21 | stak-r7rs = { version = "0.10.0", path = "../../../r7rs" } 22 | stak-time = { version = "0.10.0", path = "../../../time", features = ["libc"] } 23 | stak-libc = { version = "0.10.0", path = "../../../libc" } 24 | stak-vm = { version = "0.10.0", path = "../../../vm" } 25 | 26 | [lints] 27 | workspace = true 28 | -------------------------------------------------------------------------------- /cmd/minimal/run/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mstak" 3 | description = "Minimal Stak Scheme interpreter" 4 | categories.workspace = true 5 | edition.workspace = true 6 | keywords.workspace = true 7 | license-file.workspace = true 8 | readme.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [dependencies] 13 | stak-sac = { version = "0.10.0", path = "../../../sac", features = ["libc"] } 14 | 15 | [lints] 16 | workspace = true 17 | -------------------------------------------------------------------------------- /cmd/minimal/run/src/main.rs: -------------------------------------------------------------------------------- 1 | //! A minimal Stak Scheme interpreter. 2 | //! 3 | //! # Usage 4 | //! 5 | //! ```sh 6 | //! mstak foo.scm 7 | //! ``` 8 | 9 | #![no_std] 10 | #![cfg_attr(not(test), no_main)] 11 | 12 | stak_sac::libc_main!("main.scm"); 13 | -------------------------------------------------------------------------------- /cmd/minimal/run/src/main.scm: -------------------------------------------------------------------------------- 1 | ../../../../run.scm -------------------------------------------------------------------------------- /cmd/profile/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stak-profile" 3 | description = "Stak Scheme profiler" 4 | categories.workspace = true 5 | edition.workspace = true 6 | keywords.workspace = true 7 | license-file.workspace = true 8 | readme.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [features] 13 | default = ["float"] 14 | float = ["stak-vm/float"] 15 | 16 | [dependencies] 17 | clap = { version = "4.5.39", features = ["derive"] } 18 | main_error = "0.1.2" 19 | stak-configuration = { version = "0.10.24", path = "../../configuration" } 20 | stak-device = { version = "0.10.24", path = "../../device", features = ["std"] } 21 | stak-file = { version = "0.10.24", path = "../../file", features = ["std"] } 22 | stak-process-context = { version = "0.10.24", path = "../../process_context", features = [ 23 | "std", 24 | ] } 25 | stak-profiler = { version = "0.10.24", path = "../../profiler" } 26 | stak-r7rs = { version = "0.10.24", path = "../../r7rs" } 27 | stak-time = { version = "0.10.24", path = "../../time", features = ["std"] } 28 | stak-vm = { version = "0.10.24", path = "../../vm", features = ["profile"] } 29 | 30 | [lints] 31 | workspace = true 32 | -------------------------------------------------------------------------------- /cmd/repl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stak-repl" 3 | description = "Stak Scheme REPL" 4 | categories.workspace = true 5 | edition.workspace = true 6 | keywords.workspace = true 7 | license-file.workspace = true 8 | readme.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [features] 13 | default = ["float"] 14 | float = ["stak-sac/float"] 15 | 16 | [dependencies] 17 | stak-sac = { version = "0.10.24", path = "../../sac", features = ["std"] } 18 | 19 | [lints] 20 | workspace = true 21 | -------------------------------------------------------------------------------- /cmd/repl/src/main.rs: -------------------------------------------------------------------------------- 1 | //! Stak Scheme REPL. 2 | //! 3 | //! # Usage 4 | //! 5 | //! ```sh 6 | //! stak-repl 7 | //! ``` 8 | 9 | stak_sac::main!("main.scm"); 10 | -------------------------------------------------------------------------------- /cmd/repl/src/main.scm: -------------------------------------------------------------------------------- 1 | ../../../repl.scm -------------------------------------------------------------------------------- /compiler/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stak-compiler" 3 | description = "Stak Scheme bytecode compiler" 4 | categories.workspace = true 5 | edition.workspace = true 6 | keywords.workspace = true 7 | license-file.workspace = true 8 | readme.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [features] 13 | async = [ 14 | "stak-device/async", 15 | "stak-file/async", 16 | "stak-process-context/async", 17 | "stak-r7rs/async", 18 | "stak-time/async", 19 | "stak-vm/async", 20 | ] 21 | 22 | [dependencies] 23 | stak-configuration = { version = "0.10.24", path = "../configuration" } 24 | stak-device = { version = "0.10.24", path = "../device", features = ["std"] } 25 | stak-file = { version = "0.10.24", path = "../file" } 26 | stak-process-context = { version = "0.10.24", path = "../process_context" } 27 | stak-r7rs = { version = "0.10.24", path = "../r7rs" } 28 | stak-time = { version = "0.10.24", path = "../time" } 29 | stak-vm = { version = "0.10.24", path = "../vm" } 30 | 31 | [dev-dependencies] 32 | indoc = "2.0.6" 33 | 34 | [lints] 35 | workspace = true 36 | -------------------------------------------------------------------------------- /compiler/src/compile.scm: -------------------------------------------------------------------------------- 1 | ../../compile.scm -------------------------------------------------------------------------------- /compiler/src/error.rs: -------------------------------------------------------------------------------- 1 | use core::{ 2 | error, 3 | fmt::{self, Display, Formatter}, 4 | }; 5 | use stak_r7rs::SmallError; 6 | 7 | /// A compile error. 8 | #[derive(Clone, Debug, Eq, PartialEq)] 9 | pub enum CompileError { 10 | /// A run error. 11 | Run(SmallError), 12 | /// A user error. 13 | User(String), 14 | /// A virtual machine error. 15 | Vm(stak_vm::Error), 16 | } 17 | 18 | impl error::Error for CompileError {} 19 | 20 | impl Display for CompileError { 21 | fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { 22 | match self { 23 | Self::Run(error) => write!(formatter, "{error}"), 24 | Self::User(error) => write!(formatter, "{error}"), 25 | Self::Vm(error) => write!(formatter, "{error}"), 26 | } 27 | } 28 | } 29 | 30 | impl From for CompileError { 31 | fn from(error: SmallError) -> Self { 32 | Self::Run(error) 33 | } 34 | } 35 | 36 | impl From for CompileError { 37 | fn from(error: stak_vm::Error) -> Self { 38 | Self::Vm(error) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /compiler/src/prelude.scm: -------------------------------------------------------------------------------- 1 | ../../prelude.scm -------------------------------------------------------------------------------- /configuration/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stak-configuration" 3 | description = "Stak Scheme configuration" 4 | categories.workspace = true 5 | edition.workspace = true 6 | keywords.workspace = true 7 | license-file.workspace = true 8 | readme.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | -------------------------------------------------------------------------------- /configuration/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Stak Scheme configurations. 2 | 3 | #![no_std] 4 | 5 | /// A default heap size. 6 | // TODO Decrease this. 😭 7 | pub const DEFAULT_HEAP_SIZE: usize = 1 << 22; 8 | -------------------------------------------------------------------------------- /cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignorePaths": ["LICENSE"], 3 | "ignoreRegExpList": ["cxr"], 4 | "patterns": [ 5 | { 6 | "name": "cxr", 7 | "pattern": "/c[ad]+r/" 8 | } 9 | ], 10 | "useGitignore": true, 11 | "words": [ 12 | "alist", 13 | "alloc", 14 | "arity", 15 | "assq", 16 | "assv", 17 | "astro", 18 | "astrojs", 19 | "bindgen", 20 | "bitvec", 21 | "bytecodes", 22 | "bytevector", 23 | "cdylib", 24 | "chibi", 25 | "clippy", 26 | "codecov", 27 | "codegen", 28 | "codspeed", 29 | "comby", 30 | "cond", 31 | "coreutils", 32 | "ctypes", 33 | "doctest", 34 | "elif", 35 | "embeddable", 36 | "errno", 37 | "expt", 38 | "fclose", 39 | "findutils", 40 | "flamegraph", 41 | "fopen", 42 | "fread", 43 | "fseek", 44 | "fstat", 45 | "ftell", 46 | "gettime", 47 | "heapless", 48 | "imag", 49 | "indoc", 50 | "insta", 51 | "isize", 52 | "kloc", 53 | "lcov", 54 | "letrec", 55 | "libc", 56 | "libm", 57 | "memq", 58 | "memv", 59 | "micropython", 60 | "minifier", 61 | "mlua", 62 | "mmap", 63 | "mmaps", 64 | "mruby", 65 | "mrun", 66 | "mstak", 67 | "munmap", 68 | "nanos", 69 | "nanostores", 70 | "nonbox", 71 | "nproc", 72 | "println", 73 | "quasiquote", 74 | "raviqqe", 75 | "repr", 76 | "ribbit", 77 | "rlib", 78 | "rposition", 79 | "rustc", 80 | "rustfmt", 81 | "rustix", 82 | "rustup", 83 | "stak", 84 | "tokei", 85 | "uninlined", 86 | "uninterned", 87 | "unresolve", 88 | "unsync", 89 | "usize", 90 | "uutils", 91 | "valgrind", 92 | "varint" 93 | ] 94 | } 95 | -------------------------------------------------------------------------------- /device/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stak-device" 3 | description = "Devices for Stak Scheme" 4 | categories.workspace = true 5 | edition.workspace = true 6 | keywords.workspace = true 7 | license-file.workspace = true 8 | readme.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [features] 13 | async = ["stak-vm/async", "winter-maybe-async/async"] 14 | libc = ["dep:rustix"] 15 | std = [] 16 | 17 | [dependencies] 18 | rustix = { version = "1.0.7", default-features = false, features = [ 19 | "stdio", 20 | ], optional = true } 21 | stak-vm = { version = "0.10.24", path = "../vm" } 22 | winter-maybe-async = "0.12.0" 23 | 24 | [dev-dependencies] 25 | stak-util = { path = "../util" } 26 | 27 | [lints] 28 | workspace = true 29 | -------------------------------------------------------------------------------- /device/src/device.rs: -------------------------------------------------------------------------------- 1 | mod buffer_error; 2 | mod fixed_buffer; 3 | #[cfg(feature = "libc")] 4 | pub mod libc; 5 | #[cfg(feature = "std")] 6 | mod read_write; 7 | #[cfg(feature = "std")] 8 | mod stdio; 9 | mod void; 10 | 11 | pub use buffer_error::BufferError; 12 | use core::error::Error; 13 | pub use fixed_buffer::FixedBufferDevice; 14 | #[cfg(feature = "std")] 15 | pub use read_write::ReadWriteDevice; 16 | #[cfg(feature = "std")] 17 | pub use stdio::StdioDevice; 18 | pub use void::*; 19 | use winter_maybe_async::{maybe_async, maybe_await}; 20 | 21 | /// A device. 22 | pub trait Device { 23 | /// An error. 24 | type Error: Error; 25 | 26 | /// Reads from standard input. 27 | #[maybe_async] 28 | fn read(&mut self) -> Result, Self::Error>; 29 | 30 | /// Writes to standard output. 31 | #[maybe_async] 32 | fn write(&mut self, byte: u8) -> Result<(), Self::Error>; 33 | 34 | /// Writes to standard error. 35 | #[maybe_async] 36 | fn write_error(&mut self, byte: u8) -> Result<(), Self::Error>; 37 | } 38 | 39 | impl Device for &mut T { 40 | type Error = T::Error; 41 | 42 | #[maybe_async] 43 | fn read(&mut self) -> Result, Self::Error> { 44 | maybe_await!((**self).read()) 45 | } 46 | 47 | #[maybe_async] 48 | fn write(&mut self, byte: u8) -> Result<(), Self::Error> { 49 | maybe_await!((**self).write(byte)) 50 | } 51 | 52 | #[maybe_async] 53 | fn write_error(&mut self, byte: u8) -> Result<(), Self::Error> { 54 | maybe_await!((**self).write_error(byte)) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /device/src/device/buffer_error.rs: -------------------------------------------------------------------------------- 1 | use core::{ 2 | error, 3 | fmt::{self, Display, Formatter}, 4 | }; 5 | 6 | /// A buffer error. 7 | #[derive(Clone, Debug, Eq, PartialEq)] 8 | pub enum BufferError { 9 | /// A read error. 10 | Read, 11 | /// A write error. 12 | Write, 13 | } 14 | 15 | impl error::Error for BufferError {} 16 | 17 | impl Display for BufferError { 18 | fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { 19 | match self { 20 | Self::Read => write!(formatter, "failed to read buffer"), 21 | Self::Write => write!(formatter, "failed to write buffer"), 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /device/src/device/libc.rs: -------------------------------------------------------------------------------- 1 | //! Devices based on libc. 2 | 3 | mod buffer; 4 | mod device; 5 | mod error; 6 | mod read; 7 | mod stdio; 8 | mod write; 9 | 10 | pub use buffer::{Buffer, BufferMut}; 11 | pub use device::ReadWriteDevice; 12 | pub use error::LibcError; 13 | pub use read::Read; 14 | pub use stdio::{Stderr, Stdin, Stdout}; 15 | pub use write::Write; 16 | -------------------------------------------------------------------------------- /device/src/device/libc/device.rs: -------------------------------------------------------------------------------- 1 | use super::{Read, Write, error::LibcError}; 2 | use crate::Device; 3 | use winter_maybe_async::maybe_async; 4 | 5 | /// A device composed of objects implementing `Read` and `Write` traits. 6 | pub struct ReadWriteDevice { 7 | stdin: I, 8 | stdout: O, 9 | stderr: E, 10 | } 11 | 12 | impl ReadWriteDevice { 13 | /// Creates a device. 14 | pub const fn new(stdin: I, stdout: O, stderr: E) -> Self { 15 | Self { 16 | stdin, 17 | stdout, 18 | stderr, 19 | } 20 | } 21 | 22 | /// Returns a stdin. 23 | pub const fn stdin(&self) -> &I { 24 | &self.stdin 25 | } 26 | 27 | /// Returns a stdout. 28 | pub const fn stdout(&self) -> &O { 29 | &self.stdout 30 | } 31 | 32 | /// Returns a stderr. 33 | pub const fn stderr(&self) -> &E { 34 | &self.stderr 35 | } 36 | } 37 | 38 | impl Device for ReadWriteDevice { 39 | type Error = LibcError; 40 | 41 | #[maybe_async] 42 | fn read(&mut self) -> Result, Self::Error> { 43 | self.stdin.read().map_err(|_| LibcError::Stdin) 44 | } 45 | 46 | #[maybe_async] 47 | fn write(&mut self, byte: u8) -> Result<(), Self::Error> { 48 | self.stdout.write(byte).map_err(|_| LibcError::Stdout) 49 | } 50 | 51 | #[maybe_async] 52 | fn write_error(&mut self, byte: u8) -> Result<(), Self::Error> { 53 | self.stderr.write(byte).map_err(|_| LibcError::Stderr) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /device/src/device/libc/error.rs: -------------------------------------------------------------------------------- 1 | use core::{ 2 | error, 3 | fmt::{self, Display, Formatter}, 4 | }; 5 | 6 | /// An error. 7 | #[derive(Clone, Copy, Debug)] 8 | pub enum LibcError { 9 | /// A stdin error. 10 | Stdin, 11 | /// A stdout error. 12 | Stdout, 13 | /// A stderr error. 14 | Stderr, 15 | } 16 | 17 | impl error::Error for LibcError {} 18 | 19 | impl Display for LibcError { 20 | fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { 21 | match self { 22 | Self::Stdin => write!(formatter, "failed to read stdin"), 23 | Self::Stdout => write!(formatter, "failed to write stdout"), 24 | Self::Stderr => write!(formatter, "failed to write stderr"), 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /device/src/device/libc/read.rs: -------------------------------------------------------------------------------- 1 | use core::error::Error; 2 | 3 | /// A trait that reads a byte. 4 | pub trait Read { 5 | /// An error. 6 | type Error: Error; 7 | 8 | /// Reads a byte. 9 | fn read(&mut self) -> Result, Self::Error>; 10 | } 11 | 12 | impl Read for &mut T { 13 | type Error = T::Error; 14 | 15 | fn read(&mut self) -> Result, Self::Error> { 16 | (**self).read() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /device/src/device/libc/write.rs: -------------------------------------------------------------------------------- 1 | use core::error::Error; 2 | 3 | /// A trait that writes a byte. 4 | pub trait Write { 5 | /// An error. 6 | type Error: Error; 7 | 8 | /// Writes a byte. 9 | fn write(&mut self, byte: u8) -> Result<(), Self::Error>; 10 | } 11 | 12 | impl Write for &mut T { 13 | type Error = T::Error; 14 | 15 | fn write(&mut self, byte: u8) -> Result<(), Self::Error> { 16 | (**self).write(byte) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /device/src/device/stdio.rs: -------------------------------------------------------------------------------- 1 | use crate::{Device, ReadWriteDevice}; 2 | use std::io::{Error, Stderr, Stdin, Stdout, stderr, stdin, stdout}; 3 | use winter_maybe_async::{maybe_async, maybe_await}; 4 | 5 | /// A standard I/O device of a current process. 6 | #[derive(Debug)] 7 | pub struct StdioDevice { 8 | device: ReadWriteDevice, 9 | } 10 | 11 | impl StdioDevice { 12 | /// Creates a device. 13 | pub fn new() -> Self { 14 | Self { 15 | device: ReadWriteDevice::new(stdin(), stdout(), stderr()), 16 | } 17 | } 18 | } 19 | 20 | impl Device for StdioDevice { 21 | type Error = Error; 22 | 23 | #[maybe_async] 24 | fn read(&mut self) -> Result, Self::Error> { 25 | maybe_await!(self.device.read()) 26 | } 27 | 28 | #[maybe_async] 29 | fn write(&mut self, byte: u8) -> Result<(), Self::Error> { 30 | maybe_await!(self.device.write(byte)) 31 | } 32 | 33 | #[maybe_async] 34 | fn write_error(&mut self, byte: u8) -> Result<(), Self::Error> { 35 | maybe_await!(self.device.write_error(byte)) 36 | } 37 | } 38 | 39 | impl Default for StdioDevice { 40 | fn default() -> Self { 41 | Self::new() 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /device/src/device/void.rs: -------------------------------------------------------------------------------- 1 | use crate::{BufferError, Device}; 2 | use winter_maybe_async::maybe_async; 3 | 4 | /// A void device where all I/O operations succeed with no side effect. 5 | #[derive(Debug, Default)] 6 | pub struct VoidDevice {} 7 | 8 | impl VoidDevice { 9 | /// Creates a device. 10 | pub fn new() -> Self { 11 | Self::default() 12 | } 13 | } 14 | 15 | impl Device for VoidDevice { 16 | type Error = BufferError; 17 | 18 | #[maybe_async] 19 | fn read(&mut self) -> Result, Self::Error> { 20 | Ok(None) 21 | } 22 | 23 | #[maybe_async] 24 | fn write(&mut self, _byte: u8) -> Result<(), Self::Error> { 25 | Ok(()) 26 | } 27 | 28 | #[maybe_async] 29 | fn write_error(&mut self, _byte: u8) -> Result<(), Self::Error> { 30 | Ok(()) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /device/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Devices to handle I/O. 2 | 3 | #![cfg_attr(all(doc, not(doctest)), feature(doc_auto_cfg))] 4 | #![no_std] 5 | 6 | #[cfg(test)] 7 | extern crate alloc; 8 | #[cfg(feature = "std")] 9 | extern crate std; 10 | 11 | mod device; 12 | mod primitive_set; 13 | 14 | pub use device::*; 15 | pub use primitive_set::{DevicePrimitiveSet, Primitive, PrimitiveError}; 16 | -------------------------------------------------------------------------------- /device/src/primitive_set/error.rs: -------------------------------------------------------------------------------- 1 | use core::{ 2 | error, 3 | fmt::{self, Display, Formatter}, 4 | }; 5 | use stak_vm::Exception; 6 | 7 | /// An error of primitives. 8 | #[derive(Clone, Debug, Eq, PartialEq)] 9 | pub enum PrimitiveError { 10 | /// A failure to read from standard input. 11 | ReadInput, 12 | /// A virtual machine error. 13 | Vm(stak_vm::Error), 14 | /// A failure to write to standard error. 15 | WriteError, 16 | /// A failure to write to standard output. 17 | WriteOutput, 18 | } 19 | 20 | impl Exception for PrimitiveError { 21 | fn is_critical(&self) -> bool { 22 | match self { 23 | Self::ReadInput | Self::WriteError | Self::WriteOutput => false, 24 | Self::Vm(error) => error.is_critical(), 25 | } 26 | } 27 | } 28 | 29 | impl error::Error for PrimitiveError {} 30 | 31 | impl Display for PrimitiveError { 32 | fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { 33 | match self { 34 | Self::ReadInput => write!(formatter, "failed to read input"), 35 | Self::Vm(error) => write!(formatter, "{error}"), 36 | Self::WriteError => write!(formatter, "failed to write error"), 37 | Self::WriteOutput => write!(formatter, "failed to write output"), 38 | } 39 | } 40 | } 41 | 42 | impl From for PrimitiveError { 43 | fn from(error: stak_vm::Error) -> Self { 44 | Self::Vm(error) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /device/src/primitive_set/primitive.rs: -------------------------------------------------------------------------------- 1 | /// A primitive of a device. 2 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 3 | pub enum Primitive { 4 | /// Read from a device. 5 | Read, 6 | /// Write to a device. 7 | Write, 8 | /// Write error to a device. 9 | WriteError, 10 | } 11 | 12 | impl Primitive { 13 | pub(super) const READ: usize = Self::Read as _; 14 | pub(super) const WRITE: usize = Self::Write as _; 15 | pub(super) const WRITE_ERROR: usize = Self::WriteError as _; 16 | } 17 | -------------------------------------------------------------------------------- /doc/.gitignore: -------------------------------------------------------------------------------- 1 | .astro 2 | dist 3 | node_modules 4 | -------------------------------------------------------------------------------- /doc/biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["./node_modules/@raviqqe/biome-config/biome.json"], 3 | "css": { 4 | "formatter": { "enabled": false }, 5 | "linter": { "enabled": false } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /doc/eslint.config.js: -------------------------------------------------------------------------------- 1 | import configurations from "@raviqqe/eslint-config"; 2 | import solid from "eslint-plugin-solid/configs/typescript"; 3 | 4 | export default [ 5 | ...configurations, 6 | { 7 | rules: { 8 | "@typescript-eslint/triple-slash-reference": "off", 9 | "import-x/order": "off", 10 | "perfectionist/sort-named-imports": "off", 11 | "react/jsx-no-useless-fragment": "off", 12 | "react/no-unknown-property": "off", 13 | }, 14 | }, 15 | solid, 16 | ]; 17 | -------------------------------------------------------------------------------- /doc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "doc", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "scripts": { 6 | "dev": "astro dev", 7 | "start": "astro dev", 8 | "build": "tools/build.sh", 9 | "preview": "astro preview", 10 | "lint": "npm run lint:biome && eslint --cache src", 11 | "lint:biome": "biome ci", 12 | "format": "biome format --write ." 13 | }, 14 | "dependencies": { 15 | "@astrojs/sitemap": "^3.4.0", 16 | "@astrojs/solid-js": "^5.1.0", 17 | "@astrojs/starlight": "^0.34.3", 18 | "@biomejs/biome": "^1.9.4", 19 | "@nanostores/solid": "^1.0.0", 20 | "@raviqqe/stak": "^0.10.24", 21 | "astro": "^5.8.1", 22 | "classnames": "^2.5.1", 23 | "es-toolkit": "^1.38.0", 24 | "lucide-solid": "^0.511.0", 25 | "monaco-editor": "^0.52.2", 26 | "nanostores": "^1.0.1", 27 | "solid-js": "^1.9.7", 28 | "vite-plugin-wasm": "^3.4.1" 29 | }, 30 | "devDependencies": { 31 | "@raviqqe/biome-config": "^1.0.2", 32 | "@raviqqe/eslint-config": "^4.1.16", 33 | "@types/node": "^22.15.27", 34 | "eslint": "^9.27.0", 35 | "eslint-plugin-solid": "^0.14.5", 36 | "typescript": "^5.8.3" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /doc/public/.gitignore: -------------------------------------------------------------------------------- 1 | *.svg 2 | -------------------------------------------------------------------------------- /doc/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Stak", 3 | "name": "Stak Scheme", 4 | "icons": [{ "src": "icon.svg", "sizes": "any" }], 5 | "start_url": ".", 6 | "display": "standalone", 7 | "theme_color": "steelblue", 8 | "background_color": "snow" 9 | } 10 | -------------------------------------------------------------------------------- /doc/src/application/compile.ts: -------------------------------------------------------------------------------- 1 | import { runWorker } from "../application/run-worker.js"; 2 | import Worker from "./compile/worker.js?worker"; 3 | 4 | export const compile = async ( 5 | source: string, 6 | ): Promise> => runWorker(() => new Worker(), source); 7 | -------------------------------------------------------------------------------- /doc/src/application/compile/worker.ts: -------------------------------------------------------------------------------- 1 | import init, { compile } from "@raviqqe/stak"; 2 | import { handleWorkerMessage } from "../handle-worker-message"; 3 | 4 | handleWorkerMessage(init, compile); 5 | -------------------------------------------------------------------------------- /doc/src/application/handle-worker-message.ts: -------------------------------------------------------------------------------- 1 | import type { Result } from "./result"; 2 | 3 | export const handleWorkerMessage = ( 4 | init: () => Promise, 5 | handle: (input: T) => S, 6 | ): void => { 7 | const promise = init(); 8 | 9 | // eslint-disable-next-line @typescript-eslint/no-misused-promises 10 | addEventListener("message", async (event: MessageEvent) => { 11 | await promise; 12 | 13 | let result: Result; 14 | 15 | try { 16 | result = { value: handle(event.data) }; 17 | } catch (error) { 18 | result = { error: (error as Error).message }; 19 | } 20 | 21 | postMessage(result); 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /doc/src/application/interpret.ts: -------------------------------------------------------------------------------- 1 | import { runWorker } from "../application/run-worker.js"; 2 | import Worker from "./interpret/worker.js?worker"; 3 | 4 | export const interpret = async ( 5 | program: Uint8Array, 6 | input: Uint8Array, 7 | ): Promise> => 8 | runWorker(() => new Worker(), { input, program }); 9 | -------------------------------------------------------------------------------- /doc/src/application/interpret/worker.ts: -------------------------------------------------------------------------------- 1 | import init, { interpret } from "@raviqqe/stak"; 2 | import { handleWorkerMessage } from "../handle-worker-message"; 3 | 4 | handleWorkerMessage( 5 | init, 6 | ({ input, program }: { input: Uint8Array; program: Uint8Array }) => 7 | interpret(program, input, 2 ** 22), 8 | ); 9 | -------------------------------------------------------------------------------- /doc/src/application/repl.ts: -------------------------------------------------------------------------------- 1 | import { runStreamWorker } from "../application/run-worker.js"; 2 | import Worker from "./repl/worker.js?worker"; 3 | 4 | export const runRepl = ( 5 | input: ReadableStream, 6 | ): ReadableStream => runStreamWorker(() => new Worker(), input); 7 | -------------------------------------------------------------------------------- /doc/src/application/repl/worker.ts: -------------------------------------------------------------------------------- 1 | import init, { repl } from "@raviqqe/stak"; 2 | 3 | const input = new ReadableStream({ 4 | start: (controller) => 5 | addEventListener("message", (event: MessageEvent) => { 6 | for (const byte of event.data) { 7 | controller.enqueue(byte); 8 | } 9 | }), 10 | }); 11 | const reader = input.getReader(); 12 | 13 | const global = self as unknown as { 14 | // eslint-disable-next-line @typescript-eslint/naming-convention 15 | read_stdin: () => Promise; 16 | // eslint-disable-next-line @typescript-eslint/naming-convention 17 | write_stderr: (byte: number) => Promise; 18 | // eslint-disable-next-line @typescript-eslint/naming-convention 19 | write_stdout: (byte: number) => Promise; 20 | }; 21 | 22 | global.read_stdin = async () => { 23 | const result = await reader.read(); 24 | 25 | if (result.done) { 26 | throw new Error("Input stream closed"); 27 | } 28 | 29 | return result.value; 30 | }; 31 | global.write_stdout = global.write_stderr = (byte: number) => 32 | Promise.resolve(postMessage(new Uint8Array([byte]))); 33 | 34 | await init(); 35 | await repl(2 ** 22); 36 | -------------------------------------------------------------------------------- /doc/src/application/result.ts: -------------------------------------------------------------------------------- 1 | export interface ErrorResult { 2 | error: string; 3 | } 4 | 5 | export type Result = ErrorResult | SuccessResult; 6 | 7 | export interface SuccessResult { 8 | error?: undefined; 9 | value: T; 10 | } 11 | -------------------------------------------------------------------------------- /doc/src/application/run-worker.ts: -------------------------------------------------------------------------------- 1 | import type { Result } from "./result"; 2 | 3 | export const runWorker = async ( 4 | createWorker: () => Worker, 5 | input: T, 6 | ): Promise => { 7 | const worker = createWorker(); 8 | 9 | const promise = new Promise>((resolve) => 10 | worker.addEventListener("message", (event: MessageEvent>) => 11 | resolve(event.data), 12 | ), 13 | ); 14 | 15 | worker.postMessage(input); 16 | const result = await promise; 17 | worker.terminate(); 18 | 19 | if (typeof result.error === "string") { 20 | throw new Error(result.error); 21 | } 22 | 23 | return result.value; 24 | }; 25 | 26 | export const runStreamWorker = ( 27 | createWorker: () => Worker, 28 | input: ReadableStream, 29 | ): ReadableStream => { 30 | const worker = createWorker(); 31 | 32 | const output = new ReadableStream({ 33 | start: (controller) => { 34 | worker.addEventListener("message", (event: MessageEvent) => 35 | controller.enqueue(event.data), 36 | ); 37 | }, 38 | }); 39 | 40 | void (async () => { 41 | for await (const message of input) { 42 | worker.postMessage(message); 43 | } 44 | })(); 45 | 46 | return output; 47 | }; 48 | -------------------------------------------------------------------------------- /doc/src/application/run.ts: -------------------------------------------------------------------------------- 1 | import { runWorker } from "../application/run-worker.js"; 2 | import Worker from "./run/worker.js?worker"; 3 | 4 | export const run = async (source: string): Promise> => 5 | runWorker(() => new Worker(), source); 6 | -------------------------------------------------------------------------------- /doc/src/application/run/worker.ts: -------------------------------------------------------------------------------- 1 | import init, { run } from "@raviqqe/stak"; 2 | import { handleWorkerMessage } from "../handle-worker-message"; 3 | 4 | const heapSize: number = 2 ** 20; 5 | 6 | handleWorkerMessage(init, (input: string) => 7 | run(input, new Uint8Array(), heapSize), 8 | ); 9 | -------------------------------------------------------------------------------- /doc/src/components/Button.module.css: -------------------------------------------------------------------------------- 1 | .root { 2 | composes: boxShadow from "../system.module.css"; 3 | display: flex; 4 | align-items: center; 5 | gap: 0.5rem; 6 | font-size: inherit; 7 | background-color: var(--sl-color-text-accent); 8 | color: var(--sl-color-text-invert); 9 | border-radius: 0.5rem; 10 | border: none; 11 | padding: 0.7rem 1rem; 12 | transition: transform 0.1s; 13 | 14 | &[disabled] { 15 | color: white; 16 | background-color: dimgrey; 17 | } 18 | 19 | &:active { 20 | transform: translateY(0.1rem); 21 | 22 | &[disabled] { 23 | transform: none; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /doc/src/components/Button.tsx: -------------------------------------------------------------------------------- 1 | import type { JSX } from "solid-js"; 2 | import styles from "./Button.module.css"; 3 | 4 | interface Props { 5 | children: JSX.Element; 6 | disabled?: boolean; 7 | icon?: JSX.Element; 8 | onClick: () => void; 9 | } 10 | 11 | export const Button = (props: Props): JSX.Element => ( 12 | 21 | ); 22 | -------------------------------------------------------------------------------- /doc/src/components/ButtonGroup.module.css: -------------------------------------------------------------------------------- 1 | .root { 2 | display: flex; 3 | gap: 1rem; 4 | } 5 | -------------------------------------------------------------------------------- /doc/src/components/ButtonGroup.tsx: -------------------------------------------------------------------------------- 1 | import type { JSX } from "solid-js"; 2 | import styles from "./ButtonGroup.module.css"; 3 | 4 | interface Props { 5 | children: JSX.Element; 6 | } 7 | 8 | export const ButtonGroup = (props: Props): JSX.Element => ( 9 |
{props.children}
10 | ); 11 | -------------------------------------------------------------------------------- /doc/src/components/CodeEditor.tsx: -------------------------------------------------------------------------------- 1 | import { editor } from "monaco-editor"; 2 | import { 3 | type JSX, 4 | createEffect, 5 | createMemo, 6 | createUniqueId, 7 | onMount, 8 | } from "solid-js"; 9 | 10 | interface Props { 11 | autoBrackets?: boolean; 12 | class?: string; 13 | id?: string; 14 | onChange: (text: string) => void; 15 | value?: string; 16 | } 17 | 18 | export const CodeEditor = (props: Props): JSX.Element => { 19 | let instance: ReturnType | undefined; 20 | 21 | const id = createMemo(() => props.id ?? createUniqueId()); 22 | 23 | onMount(() => { 24 | const element = document.getElementById(id()); 25 | 26 | if (!element) { 27 | throw new Error("Editor element not found"); 28 | } 29 | 30 | instance = editor.create(element, { 31 | autoClosingBrackets: props.autoBrackets === false ? "never" : undefined, 32 | automaticLayout: true, 33 | language: "scheme", 34 | lineNumbers: "off", 35 | minimap: { enabled: false }, 36 | theme: "vs-dark", 37 | value: props.value, 38 | }); 39 | const model = instance.getModel(); 40 | const onChange = props.onChange; 41 | 42 | model?.onDidChangeContent(() => onChange(model.getValue())); 43 | }); 44 | 45 | createEffect(() => { 46 | if (props.value) { 47 | instance?.setValue(props.value); 48 | instance?.setPosition({ 49 | column: Number.POSITIVE_INFINITY, 50 | lineNumber: Number.POSITIVE_INFINITY, 51 | }); 52 | } 53 | }); 54 | 55 | return
; 56 | }; 57 | -------------------------------------------------------------------------------- /doc/src/components/CompilerDemo.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { DemoForm } from "./CompilerDemo/DemoForm.jsx"; 3 | import { DemoIo } from "./CompilerDemo/DemoIo.jsx"; 4 | import Demo from "./Demo.astro"; 5 | --- 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /doc/src/components/CompilerDemo/DemoForm.module.css: -------------------------------------------------------------------------------- 1 | .root { 2 | display: flex; 3 | flex-direction: column; 4 | gap: 1.5rem; 5 | align-items: stretch; 6 | } 7 | 8 | .program { 9 | flex: 1; 10 | min-height: 20rem; 11 | } 12 | -------------------------------------------------------------------------------- /doc/src/components/CompilerDemo/DemoForm.tsx: -------------------------------------------------------------------------------- 1 | import { useStore } from "@nanostores/solid"; 2 | import { Boxes, CirclePlay } from "lucide-solid"; 3 | import type { JSX } from "solid-js"; 4 | import * as store from "../../stores/compiler.js"; 5 | import { Button } from "../Button.jsx"; 6 | import { ButtonGroup } from "../ButtonGroup.jsx"; 7 | import { CodeEditor } from "../CodeEditor.js"; 8 | import { ErrorMessage } from "../ErrorMessage.jsx"; 9 | import { Field } from "../Field.jsx"; 10 | import { Label } from "../Label.jsx"; 11 | import styles from "./DemoForm.module.css"; 12 | 13 | export const DemoForm = (): JSX.Element => { 14 | const source = useStore(store.source); 15 | const bytecodesReady = useStore(store.bytecodesReady); 16 | const compiling = useStore(store.compiling); 17 | const interpreting = useStore(store.interpretingStore); 18 | const error = useStore(store.compilerError); 19 | 20 | return ( 21 |
22 | 23 | 24 | store.source.set(source)} 28 | value={source()} 29 | /> 30 | {error()} 31 | 32 | 33 | 36 | 43 | 44 |
45 | ); 46 | }; 47 | -------------------------------------------------------------------------------- /doc/src/components/CompilerDemo/DemoIo.module.css: -------------------------------------------------------------------------------- 1 | .root { 2 | display: flex; 3 | flex-direction: column; 4 | gap: 1.5rem; 5 | min-width: 0; 6 | } 7 | 8 | .input { 9 | min-height: 4rem; 10 | } 11 | 12 | .output { 13 | composes: input; 14 | composes: textArea from "../../system.module.css"; 15 | overflow: auto; 16 | flex: 1; 17 | margin: 0; 18 | 19 | & > pre { 20 | margin: 0; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /doc/src/components/CompilerDemo/DemoIo.tsx: -------------------------------------------------------------------------------- 1 | import { useStore } from "@nanostores/solid"; 2 | import type { JSX } from "solid-js"; 3 | import * as store from "../../stores/compiler.js"; 4 | import { ErrorMessage } from "../ErrorMessage.jsx"; 5 | import { Field } from "../Field.jsx"; 6 | import { Label } from "../Label.jsx"; 7 | import { Link } from "../Link.jsx"; 8 | import { TextArea } from "../TextArea.jsx"; 9 | import styles from "./DemoIo.module.css"; 10 | 11 | export const DemoIo = (): JSX.Element => { 12 | const input = useStore(store.input); 13 | const output = useStore(store.textOutput); 14 | const outputUrl = useStore(store.outputUrlStore); 15 | const error = useStore(store.interpreterError); 16 | 17 | return ( 18 |
19 | 20 | 21 | 23 | ); 24 | -------------------------------------------------------------------------------- /doc/src/content.config.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import-x/no-unresolved 2 | import { defineCollection } from "astro:content"; 3 | import { docsLoader } from "@astrojs/starlight/loaders"; 4 | import { docsSchema } from "@astrojs/starlight/schema"; 5 | 6 | export const collections = { 7 | docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }), 8 | }; 9 | -------------------------------------------------------------------------------- /doc/src/content/docs/demo/compiler.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Compiler demo 3 | description: Stak Scheme compiler demo running on web browsers. 4 | --- 5 | 6 | import CompilerDemo from "../../../components/CompilerDemo.astro"; 7 | 8 | Stak Scheme can run on many platforms including web browsers. The following is the demo of a bytecode compiler for Stak Scheme running on a web page. 9 | 10 | In the demo, the bytecode compiler is written in Scheme. A Stak Scheme virtual machine is running the compiler itself compiled into bytecodes ahead. 11 | 12 | 13 | -------------------------------------------------------------------------------- /doc/src/content/docs/demo/interpreter.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Interpreter demo 3 | description: Stak Scheme interpreter demo running on web browsers. 4 | --- 5 | 6 | import InterpreterDemo from "../../../components/InterpreterDemo.astro"; 7 | 8 | Stak Scheme can run on many platforms including web browsers. The following is the demo of Stak Scheme built in WASM running on a web page. 9 | 10 | 11 | -------------------------------------------------------------------------------- /doc/src/content/docs/examples/.gitignore: -------------------------------------------------------------------------------- 1 | *.md 2 | -------------------------------------------------------------------------------- /doc/src/content/docs/hot-reload.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Hot reloading of Scheme scripts in Rust 3 | description: How to reload Scheme scripts dynamically while Rust programs keep running 4 | --- 5 | 6 | This page explains [hot reloading][hot-reload] of Stak Scheme scripts to change behavior of Rust programs dynamically. By reading this page, you will learn: 7 | 8 | - How to enable [hot reloading][hot-reload] of Scheme scripts in Rust projects. 9 | - How to keep updating Scheme scripts in Rust programs during hot reloading. 10 | 11 | > WIP 12 | 13 | ## References 14 | 15 | - [`examples/hot-reload` directory on GitHub](https://github.com/raviqqe/stak/tree/main/examples/hot-reload) 16 | 17 | [hot-reload]: https://en.wikipedia.org/wiki/Hot_swapping#Software_development 18 | -------------------------------------------------------------------------------- /doc/src/content/docs/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Miniature, embeddable Scheme 3 | description: The no-std and no-alloc Scheme implementation in Rust. 4 | template: splash 5 | hero: 6 | image: 7 | alt: Icon 8 | file: ../../../public/icon.svg 9 | tagline: The no-std and no-alloc Scheme implementation in Rust. 10 | actions: 11 | - text: Get started 12 | link: /stak/install 13 | icon: right-arrow 14 | variant: primary 15 | - text: GitHub 16 | link: https://github.com/raviqqe/stak 17 | icon: github 18 | variant: secondary 19 | - text: Rust integration 20 | link: https://docs.rs/stak/ 21 | icon: seti:rust 22 | variant: secondary 23 | --- 24 | 25 | import Readme from "../../../../README.md"; 26 | import ReplDemo from "../../components/ReplDemo.astro"; 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /doc/src/content/docs/limitations.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Limitations in the current implementation 3 | description: Limitations in the current implementation of Stak Scheme 4 | --- 5 | 6 | This page describes the limitations on the current implementation of Stak Scheme. These limitations are considered transient although some of them do not have any concrete resolution plans right now; we hopefully remove these limitations in the future versions of Stak Scheme without incurring too much complexity. 7 | 8 | ## Number representation 9 | 10 | Number representation in Stak Scheme is either [a 64-bit floating point number (IEEE 754)](https://en.wikipedia.org/wiki/IEEE_754) or a 63-bit integer. 11 | 12 | The other "full" implementations of [the R7RS-small standard](https://small.r7rs.org/), such as [Chibi Scheme](https://github.com/ashinn/chibi-scheme), [Gauche](https://github.com/shirok/Gauche), and [Guile](https://www.gnu.org/software/guile/), often come with [the full numeric tower](https://en.wikipedia.org/wiki/Numerical_tower). 13 | 14 | ## String and character representation 15 | 16 | Stak Scheme supports only ASCII characters but not multi-byte characters in Unicode in strings and characters. 17 | -------------------------------------------------------------------------------- /doc/src/content/docs/writing-scheme-subset.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Writing your own Scheme subset 3 | description: How to write your own Scheme subset and use it on a Stak Scheme VM for smaller memory footprints 4 | --- 5 | 6 | > WIP 7 | -------------------------------------------------------------------------------- /doc/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /doc/src/index.css: -------------------------------------------------------------------------------- 1 | .sl-markdown-content img { 2 | display: initial !important; 3 | } 4 | -------------------------------------------------------------------------------- /doc/src/stores/interpreter.ts: -------------------------------------------------------------------------------- 1 | import { atom, computed, task } from "nanostores"; 2 | import { run as runProgram } from "../application/run.js"; 3 | 4 | export const source = atom( 5 | ` 6 | (import (scheme base) (scheme write)) 7 | 8 | (define (fibonacci x) 9 | (if (< x 2) 10 | x 11 | (+ 12 | (fibonacci (- x 1)) 13 | (fibonacci (- x 2))))) 14 | 15 | (display "Answer: ") 16 | (write (fibonacci 10)) 17 | (newline) 18 | `.trim(), 19 | ); 20 | 21 | const run = computed(source, (source) => 22 | task(async () => { 23 | try { 24 | return await runProgram(source); 25 | } catch (error) { 26 | return error as Error; 27 | } 28 | }), 29 | ); 30 | 31 | export const output = computed(run, (output) => 32 | output instanceof Error ? null : new TextDecoder().decode(output), 33 | ); 34 | 35 | export const error = computed(run, (error) => 36 | error instanceof Error ? error : null, 37 | ); 38 | -------------------------------------------------------------------------------- /doc/src/stores/repl.ts: -------------------------------------------------------------------------------- 1 | import { atom, computed } from "nanostores"; 2 | import { runRepl } from "../application/repl.js"; 3 | 4 | export const input = atom>(new ReadableStream()); 5 | 6 | export const output = computed(input, (input) => runRepl(input)); 7 | -------------------------------------------------------------------------------- /doc/src/system.module.css: -------------------------------------------------------------------------------- 1 | .boxShadow { 2 | box-shadow: 0 0.1rem 0.2rem rgba(0, 0, 0, 0.2); 3 | } 4 | 5 | .textArea { 6 | composes: boxShadow; 7 | font-family: monospace; 8 | padding: 1rem; 9 | box-sizing: border-box; 10 | overflow: auto; 11 | border: 0.1rem solid gray; 12 | border-radius: 0.5rem; 13 | color: var(--primary-text-color); 14 | background-color: var(--secondary-background-color); 15 | } 16 | -------------------------------------------------------------------------------- /doc/tools/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | mkdir -p public 6 | curl -fsSL https://stak-lang.s3.amazonaws.com/icon.svg >public/icon.svg 7 | 8 | astro build "$@" 9 | -------------------------------------------------------------------------------- /doc/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strict", 3 | "compilerOptions": { 4 | "jsx": "react-jsx", 5 | "jsxImportSource": "solid-js", 6 | "skipLibCheck": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /dynamic/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stak-dynamic" 3 | description = "Dynamic primitives for Stak Scheme" 4 | categories.workspace = true 5 | edition.workspace = true 6 | keywords.workspace = true 7 | license-file.workspace = true 8 | readme.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [features] 13 | async = ["stak-vm/async", "winter-maybe-async/async"] 14 | 15 | [dependencies] 16 | any-fn = { version = "0.6.1" } 17 | bitvec = "1.0.1" 18 | heapless = { version = "0.8.0", default-features = false } 19 | stak-vm = { version = "0.10.24", path = "../vm" } 20 | winter-maybe-async = { version = "0.12.0" } 21 | 22 | [lints] 23 | workspace = true 24 | 25 | [dev-dependencies] 26 | tokio = { version = "1.45.1", features = ["macros", "rt-multi-thread"] } 27 | -------------------------------------------------------------------------------- /dynamic/src/error.rs: -------------------------------------------------------------------------------- 1 | use any_fn::AnyFnError; 2 | use core::{ 3 | error::Error, 4 | fmt::{self, Display, Formatter}, 5 | }; 6 | use stak_vm::Exception; 7 | 8 | /// An error. 9 | #[derive(Debug)] 10 | pub enum DynamicError { 11 | /// An `AnyFn` error. 12 | AnyFn(AnyFnError), 13 | /// A foreign value expected. 14 | ForeignValueExpected, 15 | /// A value index error. 16 | ValueIndex, 17 | /// A virtual machine error. 18 | Vm(stak_vm::Error), 19 | } 20 | 21 | impl From for DynamicError { 22 | fn from(error: AnyFnError) -> Self { 23 | Self::AnyFn(error) 24 | } 25 | } 26 | 27 | impl From for DynamicError { 28 | fn from(error: stak_vm::Error) -> Self { 29 | Self::Vm(error) 30 | } 31 | } 32 | 33 | impl Exception for DynamicError { 34 | fn is_critical(&self) -> bool { 35 | match self { 36 | Self::AnyFn(_) | Self::ForeignValueExpected | Self::ValueIndex => true, 37 | Self::Vm(error) => error.is_critical(), 38 | } 39 | } 40 | } 41 | 42 | impl Error for DynamicError {} 43 | 44 | impl Display for DynamicError { 45 | fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { 46 | match self { 47 | Self::AnyFn(error) => write!(formatter, "{error}"), 48 | Self::ForeignValueExpected => write!(formatter, "foreign value expected"), 49 | Self::ValueIndex => write!(formatter, "invalid value index"), 50 | Self::Vm(error) => write!(formatter, "{error}"), 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /dynamic/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Stak Scheme primitive sets for dynamically-defined primitives. 2 | 3 | #![no_std] 4 | 5 | extern crate alloc; 6 | 7 | mod error; 8 | mod primitive_set; 9 | mod scheme_value; 10 | 11 | pub use error::*; 12 | pub use primitive_set::*; 13 | pub use scheme_value::*; 14 | -------------------------------------------------------------------------------- /engine/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stak-engine" 3 | description = "Stak Scheme scripting engine for Rust" 4 | categories.workspace = true 5 | edition.workspace = true 6 | keywords.workspace = true 7 | license-file.workspace = true 8 | readme.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [features] 13 | async = [ 14 | "stak-device/async", 15 | "stak-file/async", 16 | "stak-time/async", 17 | "stak-process-context/async", 18 | "stak-r7rs/async", 19 | "stak-vm/async", 20 | "winter-maybe-async/async", 21 | ] 22 | libc = ["stak-device/libc"] 23 | std = ["stak-device/std"] 24 | 25 | [dependencies] 26 | any-fn = "0.6.1" 27 | cfg-elif = "0.6.3" 28 | stak-device = { version = "0.10.24", path = "../device" } 29 | stak-dynamic = { version = "0.10.24", path = "../dynamic" } 30 | stak-file = { version = "0.10.24", path = "../file" } 31 | stak-module = { version = "0.10.24", path = "../module" } 32 | stak-process-context = { version = "0.10.24", path = "../process_context" } 33 | stak-r7rs = { version = "0.10.24", path = "../r7rs" } 34 | stak-time = { version = "0.10.24", path = "../time" } 35 | stak-util = { version = "0.10.24", path = "../util" } 36 | stak-vm = { version = "0.10.24", path = "../vm" } 37 | winter-maybe-async = "0.12.0" 38 | 39 | [dev-dependencies] 40 | rand = "0.9.1" 41 | stak = { path = "../root" } 42 | 43 | [build-dependencies] 44 | stak-build = { version = "0.10.24", path = "../build" } 45 | 46 | [lints] 47 | workspace = true 48 | -------------------------------------------------------------------------------- /engine/build.rs: -------------------------------------------------------------------------------- 1 | //! A build script. 2 | 3 | use stak_build::{BuildError, build_r7rs}; 4 | 5 | fn main() -> Result<(), BuildError> { 6 | build_r7rs() 7 | } 8 | -------------------------------------------------------------------------------- /engine/src/error.rs: -------------------------------------------------------------------------------- 1 | use core::{ 2 | error::Error, 3 | fmt::{self, Display, Formatter}, 4 | }; 5 | use stak_dynamic::DynamicError; 6 | use stak_r7rs::SmallError; 7 | use stak_vm::Exception; 8 | 9 | /// An engine error 10 | #[derive(Debug)] 11 | pub enum EngineError { 12 | /// A dynamic primitive error. 13 | Dynamic(DynamicError), 14 | /// An R7RS-small error. 15 | Small(SmallError), 16 | /// A virtual machine error. 17 | Vm(stak_vm::Error), 18 | } 19 | 20 | impl From for EngineError { 21 | fn from(error: DynamicError) -> Self { 22 | Self::Dynamic(error) 23 | } 24 | } 25 | 26 | impl From for EngineError { 27 | fn from(error: SmallError) -> Self { 28 | Self::Small(error) 29 | } 30 | } 31 | 32 | impl From for EngineError { 33 | fn from(error: stak_vm::Error) -> Self { 34 | Self::Vm(error) 35 | } 36 | } 37 | 38 | impl Exception for EngineError { 39 | fn is_critical(&self) -> bool { 40 | match self { 41 | Self::Dynamic(error) => error.is_critical(), 42 | Self::Small(error) => error.is_critical(), 43 | Self::Vm(error) => error.is_critical(), 44 | } 45 | } 46 | } 47 | 48 | impl Error for EngineError {} 49 | 50 | impl Display for EngineError { 51 | fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { 52 | match self { 53 | Self::Dynamic(error) => write!(formatter, "{error}"), 54 | Self::Small(error) => write!(formatter, "{error}"), 55 | Self::Vm(error) => write!(formatter, "{error}"), 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /engine/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Stak Scheme scripting engine for Rust. 2 | 3 | #![no_std] 4 | 5 | mod engine; 6 | mod error; 7 | mod primitive_set; 8 | 9 | pub use engine::*; 10 | pub use error::*; 11 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | These are examples of how to integrate Stak Scheme in Rust programs. The examples include: 4 | 5 | - [Embedding Scheme scripts in Rust](embedded-script) 6 | - [Hot reloading of Scheme scripts](hot-reload) 7 | - [Customized virtual machine](custom-vm) 8 | - [Running Scheme without `std` and `alloc` crates](no-std-no-alloc) 9 | -------------------------------------------------------------------------------- /examples/custom-vm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "custom-vm" 3 | version.workspace = true 4 | publish = false 5 | edition.workspace = true 6 | 7 | [dependencies] 8 | stak = { version = "0.10.24", path = "../../root" } 9 | 10 | [build-dependencies] 11 | stak-build = { version = "0.10.24", path = "../../build" } 12 | 13 | [lints] 14 | workspace = true 15 | -------------------------------------------------------------------------------- /examples/custom-vm/README.md: -------------------------------------------------------------------------------- 1 | # Customized virtual machines 2 | 3 | This example shows how to run customized virtual machines of Stak Scheme embedded into Rust programs. 4 | 5 | ## Usage 6 | 7 | ```sh 8 | cargo run 9 | ``` 10 | -------------------------------------------------------------------------------- /examples/custom-vm/build.rs: -------------------------------------------------------------------------------- 1 | //! A build script. 2 | 3 | use stak_build::{BuildError, build_r7rs}; 4 | 5 | fn main() -> Result<(), BuildError> { 6 | build_r7rs() 7 | } 8 | -------------------------------------------------------------------------------- /examples/custom-vm/src/bar.scm: -------------------------------------------------------------------------------- 1 | (import (scheme base)) 2 | 3 | (write-string "Hello, bar\n") 4 | -------------------------------------------------------------------------------- /examples/custom-vm/src/foo.scm: -------------------------------------------------------------------------------- 1 | (import (scheme base)) 2 | 3 | (write-string "Hello, foo\n") 4 | -------------------------------------------------------------------------------- /examples/custom-vm/src/main.rs: -------------------------------------------------------------------------------- 1 | //! An example of customized virtual machines. 2 | 3 | use core::error::Error; 4 | use stak::{ 5 | device::StdioDevice, 6 | file::VoidFileSystem, 7 | include_module, 8 | module::{Module, UniversalModule}, 9 | process_context::VoidProcessContext, 10 | r7rs::{SmallError, SmallPrimitiveSet}, 11 | time::VoidClock, 12 | vm::Vm, 13 | }; 14 | 15 | const HEAP_SIZE: usize = 1 << 16; 16 | 17 | static FOO_MODULE: UniversalModule = include_module!("foo.scm"); 18 | static BAR_MODULE: UniversalModule = include_module!("bar.scm"); 19 | 20 | fn main() -> Result<(), Box> { 21 | run(&FOO_MODULE.bytecode())?; 22 | run(&BAR_MODULE.bytecode())?; 23 | 24 | Ok(()) 25 | } 26 | 27 | fn run(bytecodes: &[u8]) -> Result<(), SmallError> { 28 | let mut heap = vec![Default::default(); HEAP_SIZE]; 29 | let mut vm = Vm::new( 30 | &mut heap, 31 | SmallPrimitiveSet::new( 32 | StdioDevice::new(), 33 | VoidFileSystem::new(), 34 | VoidProcessContext::new(), 35 | VoidClock::new(), 36 | ), 37 | )?; 38 | 39 | vm.initialize(bytecodes.iter().copied())?; 40 | vm.run() 41 | } 42 | 43 | #[cfg(test)] 44 | mod tests { 45 | use super::*; 46 | 47 | #[test] 48 | fn run() { 49 | main().unwrap() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /examples/embedded-script/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "embedded-script" 3 | version.workspace = true 4 | publish = false 5 | edition.workspace = true 6 | 7 | [dependencies] 8 | any-fn = "0.6.1" 9 | rand = "0.9.1" 10 | stak = { version = "0.10.24", path = "../../root" } 11 | 12 | [build-dependencies] 13 | stak-build = { version = "0.10.24", path = "../../build" } 14 | 15 | [lints] 16 | workspace = true 17 | -------------------------------------------------------------------------------- /examples/embedded-script/README.md: -------------------------------------------------------------------------------- 1 | # Embedding scripts 2 | 3 | This example shows how to embed Scheme scripts into Rust programs. 4 | 5 | ## Usage 6 | 7 | ```sh 8 | cargo run 9 | ``` 10 | -------------------------------------------------------------------------------- /examples/embedded-script/build.rs: -------------------------------------------------------------------------------- 1 | //! A build script. 2 | 3 | use stak_build::{BuildError, build_r7rs}; 4 | 5 | fn main() -> Result<(), BuildError> { 6 | build_r7rs() 7 | } 8 | -------------------------------------------------------------------------------- /examples/embedded-script/src/fight.scm: -------------------------------------------------------------------------------- 1 | ; Import a base library and the library named `(stak rust)` for Rust integration. 2 | (import (scheme base) (stak rust)) 3 | 4 | ; Make two people with a number of pies they have and their dodge rates. 5 | (define me (make-person 4 0.2)) 6 | (define friend (make-person 2 0.6)) 7 | 8 | ; The fight begins. Let's throw pies to each other! 9 | (do () 10 | ((or 11 | (person-wasted me) 12 | (person-wasted friend) 13 | (and 14 | (zero? (person-pies me)) 15 | (zero? (person-pies friend))))) 16 | (person-throw-pie me friend) 17 | (person-throw-pie friend me)) 18 | 19 | ; Output the winner. 20 | (write-string 21 | (cond 22 | ((person-wasted friend) 23 | "You won!") 24 | ((person-wasted me) 25 | "You lost...") 26 | (else 27 | "Draw..."))) 28 | -------------------------------------------------------------------------------- /examples/fibonacci.scm: -------------------------------------------------------------------------------- 1 | (import 2 | (scheme base) 3 | (scheme read) 4 | (scheme write)) 5 | 6 | (define (fibonacci x) 7 | (if (< x 2) 8 | x 9 | (+ 10 | (fibonacci (- x 1)) 11 | (fibonacci (- x 2))))) 12 | 13 | (write (fibonacci (read))) 14 | -------------------------------------------------------------------------------- /examples/hot-reload/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hot-reload" 3 | version.workspace = true 4 | publish = false 5 | edition.workspace = true 6 | 7 | [features] 8 | default = ["hot-reload"] 9 | hot-reload = ["stak/hot-reload"] 10 | 11 | [dependencies] 12 | axum = "0.8.4" 13 | stak = { version = "0.10.24", path = "../../root" } 14 | tokio = { version = "1.45.1", features = ["rt-multi-thread"] } 15 | 16 | [build-dependencies] 17 | stak-build = { version = "0.10.24", path = "../../build" } 18 | 19 | [lints] 20 | workspace = true 21 | -------------------------------------------------------------------------------- /examples/hot-reload/README.md: -------------------------------------------------------------------------------- 1 | # Hot reloading 2 | 3 | This example shows how to use the feature of hot reloading to change behavior of Rust programs dynamically embedding Scheme scripts. 4 | 5 | ## Usage 6 | 7 | ```sh 8 | # Start an HTTP server. 9 | cargo run 10 | # In another terminal, send an HTTP request to the server. 11 | curl -f -X POST --data '(1 2 3 4 5)' http://localhost:3000/calculate # -> 15 12 | ``` 13 | 14 | Then, you can modify the Scheme script at `src/handler.scm` and run `cargo build`. It should change the behavior of the HTTP server while it keeps running. 15 | -------------------------------------------------------------------------------- /examples/hot-reload/build.rs: -------------------------------------------------------------------------------- 1 | //! A build script. 2 | 3 | use stak_build::{BuildError, build_r7rs}; 4 | 5 | fn main() -> Result<(), BuildError> { 6 | build_r7rs() 7 | } 8 | -------------------------------------------------------------------------------- /examples/hot-reload/src/handler.scm: -------------------------------------------------------------------------------- 1 | (import 2 | (scheme base) 3 | (scheme read) 4 | (scheme write)) 5 | 6 | (write (apply + (read))) 7 | -------------------------------------------------------------------------------- /examples/no-std-no-alloc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "no-std-no-alloc" 3 | version.workspace = true 4 | publish = false 5 | edition.workspace = true 6 | keywords.workspace = true 7 | license-file.workspace = true 8 | readme.workspace = true 9 | repository.workspace = true 10 | 11 | [dependencies] 12 | heapless = "0.8.0" 13 | stak = { version = "0.10.24", path = "../../root", default-features = false } 14 | 15 | [build-dependencies] 16 | stak-build = { version = "0.10.24", path = "../../build" } 17 | 18 | [lints] 19 | workspace = true 20 | -------------------------------------------------------------------------------- /examples/no-std-no-alloc/README.md: -------------------------------------------------------------------------------- 1 | # No-`std` nad no-`alloc` environment 2 | 3 | This example shows how to embed and run Stak Scheme in a crate without `std` and `alloc` crates in Rust. 4 | 5 | ## Usage 6 | 7 | ```sh 8 | cargo build 9 | cargo test 10 | ``` 11 | -------------------------------------------------------------------------------- /examples/no-std-no-alloc/build.rs: -------------------------------------------------------------------------------- 1 | //! A build script. 2 | 3 | use stak_build::{BuildError, build_r7rs}; 4 | 5 | fn main() -> Result<(), BuildError> { 6 | build_r7rs() 7 | } 8 | -------------------------------------------------------------------------------- /examples/no-std-no-alloc/src/fibonacci.scm: -------------------------------------------------------------------------------- 1 | ../../fibonacci.scm -------------------------------------------------------------------------------- /features/instrinsics.feature: -------------------------------------------------------------------------------- 1 | Feature: Intrinsics 2 | @stak 3 | Scenario: Get a tag of a pair with a non-cons `cdr` 4 | Given a file named "main.scm" with: 5 | """scheme 6 | (import (scheme base)) 7 | 8 | (rib-tag (cons 1 2)) 9 | """ 10 | When I successfully run `stak main.scm` 11 | Then the exit status should be 0 12 | 13 | @stak 14 | Scenario: Preserve a tag when a `cdr` is set 15 | Given a file named "main.scm" with: 16 | """scheme 17 | (import (scheme base) (scheme write)) 18 | 19 | (define x (rib 0 #f #f 7)) 20 | 21 | (write-u8 (+ 48 (rib-tag x))) 22 | (write-u8 (if (cdr x) 65 66)) 23 | 24 | (set-cdr! x #t) 25 | 26 | (write-u8 (+ 48 (rib-tag x))) 27 | (write-u8 (if (cdr x) 65 66)) 28 | """ 29 | When I successfully run `stak main.scm` 30 | Then the stdout should contain exactly "7B7A" 31 | -------------------------------------------------------------------------------- /features/process-context/command-line.feature: -------------------------------------------------------------------------------- 1 | @command-line 2 | Feature: Command line 3 | Scenario: Get an argument 4 | Given a file named "main.scm" with: 5 | """scheme 6 | (import (scheme base) (scheme process-context)) 7 | 8 | (map write-string (command-line)) 9 | """ 10 | When I successfully run `stak main.scm hello` 11 | Then the stdout should contain "hello" 12 | 13 | Scenario: Get two arguments 14 | Given a file named "main.scm" with: 15 | """scheme 16 | (import (scheme base) (scheme process-context)) 17 | 18 | (map write-string (command-line)) 19 | """ 20 | When I successfully run `stak main.scm hello world` 21 | Then the stdout should contain "hello" 22 | And the stdout should contain "world" 23 | -------------------------------------------------------------------------------- /features/process-context/environment-variable.feature: -------------------------------------------------------------------------------- 1 | @environment-variable 2 | Feature: Environment variables 3 | Scenario: Get an environment variable 4 | Given a file named "main.scm" with: 5 | """scheme 6 | (import (scheme base) (scheme process-context)) 7 | 8 | (write-string (get-environment-variable "FOO")) 9 | """ 10 | And I set the environment variable "FOO" to "bar" 11 | When I successfully run `stak main.scm` 12 | Then the stdout should contain exactly "bar" 13 | 14 | Scenario: Get environment variables 15 | Given a file named "main.scm" with: 16 | """scheme 17 | (import (scheme base) (scheme process-context)) 18 | 19 | (for-each 20 | (lambda (pair) 21 | (write-string (car pair)) 22 | (write-char #\=) 23 | (write-string (cdr pair)) 24 | (newline)) 25 | (get-environment-variables)) 26 | """ 27 | And I set the environment variable "FOO" to "bar" 28 | And I set the environment variable "BAZ" to "qux" 29 | When I successfully run `stak main.scm` 30 | Then the stdout should contain "FOO=bar" 31 | And the stdout should contain "BAZ=qux" 32 | -------------------------------------------------------------------------------- /features/quote.feature: -------------------------------------------------------------------------------- 1 | Feature: Quote 2 | Scenario Outline: Quote a scalar value 3 | Given a file named "main.scm" with: 4 | """scheme 5 | (import (scheme base)) 6 | 7 | (write-u8 (if (eq? ') 65 66)) 8 | """ 9 | When I successfully run `stak main.scm` 10 | Then the stdout should contain exactly "A" 11 | 12 | Examples: 13 | | value | 14 | | #f | 15 | | #t | 16 | | 0 | 17 | | 1 | 18 | | 2 | 19 | | 42 | 20 | | -1 | 21 | | -2 | 22 | | -42 | 23 | 24 | Scenario: Quote a list 25 | Given a file named "main.scm" with: 26 | """scheme 27 | (import (scheme base)) 28 | 29 | (for-each write-u8 '(65 66 67)) 30 | """ 31 | When I successfully run `stak main.scm` 32 | Then the stdout should contain exactly "ABC" 33 | -------------------------------------------------------------------------------- /features/support/aruba.rb: -------------------------------------------------------------------------------- 1 | require "aruba/cucumber" 2 | 3 | Aruba.configure do |config| 4 | config.exit_timeout = 1200 5 | end 6 | -------------------------------------------------------------------------------- /features/syntax/and.feature: -------------------------------------------------------------------------------- 1 | Feature: and 2 | Scenario Outline: Use an `and` operator 3 | Given a file named "main.scm" with: 4 | """scheme 5 | (import (scheme base)) 6 | 7 | (write-u8 (if (and ) 65 66)) 8 | """ 9 | When I successfully run `stak main.scm` 10 | Then the stdout should contain exactly "" 11 | 12 | Examples: 13 | | values | output | 14 | | | A | 15 | | #t | A | 16 | | #f | B | 17 | | #t #t | A | 18 | | #t #f | B | 19 | -------------------------------------------------------------------------------- /features/syntax/begin.feature: -------------------------------------------------------------------------------- 1 | Feature: begin 2 | Scenario: Use a `begin` expression 3 | Given a file named "main.scm" with: 4 | """scheme 5 | (import (scheme base)) 6 | 7 | (write-u8 (begin 65)) 8 | (write-u8 (begin 65 66)) 9 | (write-u8 (begin 65 66 67)) 10 | """ 11 | When I successfully run `stak main.scm` 12 | Then the stdout should contain exactly "ABC" 13 | 14 | Scenario: Use a `begin` expression with no value 15 | Given a file named "main.scm" with: 16 | """scheme 17 | (import (scheme base)) 18 | 19 | (write-u8 (begin)) 20 | """ 21 | When I run `stak main.scm` 22 | Then the exit status should not be 0 23 | -------------------------------------------------------------------------------- /features/syntax/case-lambda.feature: -------------------------------------------------------------------------------- 1 | Feature: case-lambda 2 | Scenario: Evaluate the first clause 3 | Given a file named "main.scm" with: 4 | """scheme 5 | (import (scheme base) (scheme case-lambda)) 6 | 7 | (define foo 8 | (case-lambda 9 | ((x) x) 10 | ((x y) (+ x y)))) 11 | 12 | (write-u8 (foo 65)) 13 | """ 14 | When I successfully run `stak main.scm` 15 | Then the stdout should contain exactly "A" 16 | 17 | Scenario: Evaluate the second clause 18 | Given a file named "main.scm" with: 19 | """scheme 20 | (import (scheme base) (scheme case-lambda)) 21 | 22 | (define foo 23 | (case-lambda 24 | ((x) x) 25 | ((x y) (+ x y)))) 26 | 27 | (write-u8 (foo 65 1)) 28 | """ 29 | When I successfully run `stak main.scm` 30 | Then the stdout should contain exactly "B" 31 | 32 | Scenario: Evaluate a clause with rest arguments 33 | Given a file named "main.scm" with: 34 | """scheme 35 | (import (scheme base) (scheme case-lambda)) 36 | 37 | (define foo 38 | (case-lambda 39 | ((x) x) 40 | ((x . xs) (apply + x xs)))) 41 | 42 | (write-u8 (foo 65 1 2 3)) 43 | """ 44 | When I successfully run `stak main.scm` 45 | Then the stdout should contain exactly "G" 46 | -------------------------------------------------------------------------------- /features/syntax/case.feature: -------------------------------------------------------------------------------- 1 | Feature: case 2 | Scenario: Evaluate the first clause 3 | Given a file named "main.scm" with: 4 | """scheme 5 | (import (scheme base)) 6 | 7 | (write-u8 8 | (case 2 9 | ((1 2 3) 10 | 65) 11 | (else 12 | 66))) 13 | """ 14 | When I successfully run `stak main.scm` 15 | Then the stdout should contain exactly "A" 16 | 17 | Scenario: Evaluate the second clause 18 | Given a file named "main.scm" with: 19 | """scheme 20 | (import (scheme base)) 21 | 22 | (write-u8 23 | (case 5 24 | ((1 2 3) 25 | 65) 26 | ((4 5 6) 27 | 66) 28 | (else 29 | 67))) 30 | """ 31 | When I successfully run `stak main.scm` 32 | Then the stdout should contain exactly "B" 33 | 34 | Scenario: Evaluate an `else` clause 35 | Given a file named "main.scm" with: 36 | """scheme 37 | (import (scheme base)) 38 | 39 | (write-u8 40 | (case 7 41 | ((1 2 3) 42 | 65) 43 | ((4 5 6) 44 | 66) 45 | (else 46 | 67))) 47 | """ 48 | When I successfully run `stak main.scm` 49 | Then the stdout should contain exactly "C" 50 | -------------------------------------------------------------------------------- /features/syntax/cond.feature: -------------------------------------------------------------------------------- 1 | Feature: cond 2 | Scenario: Evaluate the first clause 3 | Given a file named "main.scm" with: 4 | """scheme 5 | (import (scheme base)) 6 | 7 | (write-u8 8 | (cond 9 | (#t 10 | 65) 11 | (else 12 | 66))) 13 | """ 14 | When I successfully run `stak main.scm` 15 | Then the stdout should contain exactly "A" 16 | 17 | Scenario: Evaluate the second clause 18 | Given a file named "main.scm" with: 19 | """scheme 20 | (import (scheme base)) 21 | 22 | (write-u8 23 | (cond 24 | (#f 25 | 65) 26 | (#t 27 | 66) 28 | (else 29 | 67))) 30 | """ 31 | When I successfully run `stak main.scm` 32 | Then the stdout should contain exactly "B" 33 | 34 | Scenario: Evaluate an `else` clause 35 | Given a file named "main.scm" with: 36 | """scheme 37 | (import (scheme base)) 38 | 39 | (write-u8 40 | (cond 41 | (#f 42 | 65) 43 | (#f 44 | 66) 45 | (else 46 | 67))) 47 | """ 48 | When I successfully run `stak main.scm` 49 | Then the stdout should contain exactly "C" 50 | -------------------------------------------------------------------------------- /features/syntax/define.feature: -------------------------------------------------------------------------------- 1 | Feature: define 2 | Scenario: Define a recursive procedure 3 | Given a file named "main.scm" with: 4 | """scheme 5 | (import (scheme base)) 6 | 7 | (define (sum x) 8 | (if (eq? x 0) 0 (+ x (sum (- x 1))))) 9 | 10 | (write-u8 (sum 11)) 11 | """ 12 | When I successfully run `stak main.scm` 13 | Then the stdout should contain exactly "B" 14 | 15 | Scenario: Use a local variable in a definition 16 | Given a file named "main.scm" with: 17 | """scheme 18 | (import (scheme base)) 19 | 20 | (define (f x) 21 | (let ((y x)) 22 | (define z y) 23 | z)) 24 | 25 | (write-u8 (f 65)) 26 | """ 27 | When I successfully run `stak main.scm` 28 | Then the stdout should contain exactly "A" 29 | -------------------------------------------------------------------------------- /features/syntax/do.feature: -------------------------------------------------------------------------------- 1 | Feature: do 2 | Scenario: Use a `do` syntax with steps 3 | Given a file named "main.scm" with: 4 | """scheme 5 | (import (scheme base)) 6 | 7 | (define xs '(1 2 3 4 5 6 7 8 9 10 11)) 8 | 9 | (do ((xs xs (cdr xs)) 10 | (y 0 (+ y (car xs)))) 11 | ((null? xs) 12 | (write-u8 y))) 13 | """ 14 | When I successfully run `stak main.scm` 15 | Then the stdout should contain exactly "B" 16 | 17 | Scenario: Use a `do` syntax without a step 18 | Given a file named "main.scm" with: 19 | """scheme 20 | (import (scheme base)) 21 | 22 | (define y 0) 23 | 24 | (do ((xs #(1 2 3 4 5 6 7 8 9 10 11)) 25 | (i 0 (+ i 1))) 26 | ((= i (vector-length xs)) 27 | (write-u8 y)) 28 | (set! y (+ y (vector-ref xs i)))) 29 | """ 30 | When I successfully run `stak main.scm` 31 | Then the stdout should contain exactly "B" 32 | -------------------------------------------------------------------------------- /features/syntax/let-star.feature: -------------------------------------------------------------------------------- 1 | Feature: let* 2 | Scenario: Bind a variable 3 | Given a file named "main.scm" with: 4 | """scheme 5 | (import (scheme base)) 6 | 7 | (write-u8 (let* ((x 65)) x)) 8 | """ 9 | When I successfully run `stak main.scm` 10 | Then the stdout should contain exactly "A" 11 | 12 | Scenario: Bind two variables 13 | Given a file named "main.scm" with: 14 | """scheme 15 | (import (scheme base)) 16 | 17 | (write-u8 (let* ((x 65) (y x)) y)) 18 | """ 19 | When I successfully run `stak main.scm` 20 | Then the stdout should contain exactly "A" 21 | 22 | Scenario: Bind three variables 23 | Given a file named "main.scm" with: 24 | """scheme 25 | (import (scheme base)) 26 | 27 | (write-u8 (let* ((x 65) (y x) (z y)) z)) 28 | """ 29 | When I successfully run `stak main.scm` 30 | Then the stdout should contain exactly "A" 31 | -------------------------------------------------------------------------------- /features/syntax/letrec-star.feature: -------------------------------------------------------------------------------- 1 | Feature: letrec* 2 | Scenario: Bind a variable 3 | Given a file named "main.scm" with: 4 | """scheme 5 | (import (scheme base)) 6 | 7 | (define (f x) 8 | (letrec* ( 9 | (f 10 | (lambda (x) 11 | (if (eqv? x 65) 12 | x 13 | (f (+ x 1)))))) 14 | (f x))) 15 | 16 | (write-u8 (f 0)) 17 | """ 18 | When I successfully run `stak main.scm` 19 | Then the stdout should contain exactly "A" 20 | 21 | Scenario: Bind two variables 22 | Given a file named "main.scm" with: 23 | """scheme 24 | (import (scheme base)) 25 | 26 | (define (f x) 27 | (letrec* ( 28 | (f (lambda (x) (if (eqv? x 65) x (g (+ x 1))))) 29 | (g (lambda (x) (f x)))) 30 | (f x))) 31 | 32 | (write-u8 (f 0)) 33 | """ 34 | When I successfully run `stak main.scm` 35 | Then the stdout should contain exactly "A" 36 | -------------------------------------------------------------------------------- /features/syntax/letrec-syntax.feature: -------------------------------------------------------------------------------- 1 | Feature: letrec-syntax 2 | Scenario: Define a recursive local macro 3 | Given a file named "main.scm" with: 4 | """scheme 5 | (import (scheme base)) 6 | 7 | (letrec-syntax 8 | ((foo 9 | (syntax-rules () 10 | ((_ x) 11 | x) 12 | ((_ x y) 13 | (foo y))))) 14 | (write-u8 (foo 65 66))) 15 | """ 16 | When I successfully run `stak main.scm` 17 | Then the stdout should contain exactly "B" 18 | 19 | Scenario: Define a mutually recursive local macro 20 | Given a file named "main.scm" with: 21 | """scheme 22 | (import (scheme base)) 23 | 24 | (letrec-syntax ( 25 | (foo 26 | (syntax-rules () 27 | ((_ x) 28 | x) 29 | ((_ x ... y) 30 | (bar x ...)))) 31 | (bar 32 | (syntax-rules () 33 | ((_ x) 34 | x) 35 | ((_ x ... y) 36 | (foo x ...))))) 37 | (write-u8 (foo 65 66 67))) 38 | """ 39 | When I successfully run `stak main.scm` 40 | Then the stdout should contain exactly "A" 41 | 42 | Scenario: Put a sequence in a body of `letrec-syntax` 43 | Given a file named "main.scm" with: 44 | """scheme 45 | (import (scheme base)) 46 | 47 | (letrec-syntax () 48 | (write-u8 65) 49 | (write-u8 66)) 50 | """ 51 | When I successfully run `stak main.scm` 52 | Then the stdout should contain exactly "AB" 53 | -------------------------------------------------------------------------------- /features/syntax/letrec.feature: -------------------------------------------------------------------------------- 1 | Feature: letrec 2 | Scenario: Bind a variable 3 | Given a file named "main.scm" with: 4 | """scheme 5 | (import (scheme base)) 6 | 7 | (define (f x) 8 | (letrec ( 9 | (f 10 | (lambda (x) 11 | (if (eqv? x 65) 12 | x 13 | (f (+ x 1)))))) 14 | (f x))) 15 | 16 | (write-u8 (f 0)) 17 | """ 18 | When I successfully run `stak main.scm` 19 | Then the stdout should contain exactly "A" 20 | 21 | Scenario: Bind two variables 22 | Given a file named "main.scm" with: 23 | """scheme 24 | (import (scheme base)) 25 | 26 | (define (f x) 27 | (letrec ( 28 | (f (lambda (x) (if (eqv? x 65) x (g (+ x 1))))) 29 | (g (lambda (x) (f x)))) 30 | (f x))) 31 | 32 | (write-u8 (f 0)) 33 | """ 34 | When I successfully run `stak main.scm` 35 | Then the stdout should contain exactly "A" 36 | -------------------------------------------------------------------------------- /features/syntax/or.feature: -------------------------------------------------------------------------------- 1 | Feature: or 2 | Scenario Outline: Use an `or` operator 3 | Given a file named "main.scm" with: 4 | """scheme 5 | (import (scheme base)) 6 | 7 | (write-u8 (if (or ) 65 66)) 8 | """ 9 | When I successfully run `stak main.scm` 10 | Then the stdout should contain exactly "" 11 | 12 | Examples: 13 | | values | output | 14 | | | B | 15 | | #f | B | 16 | | #t | A | 17 | | #f #f | B | 18 | | #f #t | A | 19 | 20 | Scenario Outline: Return the first true value 21 | Given a file named "main.scm" with: 22 | """scheme 23 | (import (scheme base)) 24 | 25 | (write-u8 (or )) 26 | """ 27 | When I successfully run `stak main.scm` 28 | Then the stdout should contain exactly "A" 29 | 30 | Examples: 31 | | values | 32 | | 65 | 33 | | 65 #f | 34 | | #f 65 | 35 | -------------------------------------------------------------------------------- /features/syntax/set.feature: -------------------------------------------------------------------------------- 1 | Feature: set! 2 | Scenario: Set a global variable 3 | Given a file named "main.scm" with: 4 | """scheme 5 | (import (scheme base)) 6 | 7 | (define x #f) 8 | (set! x 65) 9 | (write-u8 x) 10 | """ 11 | When I successfully run `stak main.scm` 12 | Then the stdout should contain exactly "A" 13 | 14 | Scenario: Set a local variable 15 | Given a file named "main.scm" with: 16 | """scheme 17 | (import (scheme base)) 18 | 19 | (let ((x #f)) 20 | (set! x 65) 21 | (write-u8 x)) 22 | """ 23 | When I successfully run `stak main.scm` 24 | Then the stdout should contain exactly "A" 25 | -------------------------------------------------------------------------------- /features/syntax/unless.feature: -------------------------------------------------------------------------------- 1 | Feature: unless 2 | Scenario: Evaluate a clause 3 | Given a file named "main.scm" with: 4 | """scheme 5 | (import (scheme base)) 6 | 7 | (unless #f (write-u8 65)) 8 | """ 9 | When I successfully run `stak main.scm` 10 | Then the stdout should contain exactly "A" 11 | 12 | Scenario: Do not evaluate a clause 13 | Given a file named "main.scm" with: 14 | """scheme 15 | (import (scheme base)) 16 | 17 | (unless #t (write-u8 65)) 18 | """ 19 | When I successfully run `stak main.scm` 20 | Then the stdout should contain exactly "" 21 | -------------------------------------------------------------------------------- /features/syntax/when.feature: -------------------------------------------------------------------------------- 1 | Feature: when 2 | Scenario: Evaluate a clause 3 | Given a file named "main.scm" with: 4 | """scheme 5 | (import (scheme base)) 6 | 7 | (when #t (write-u8 65)) 8 | """ 9 | When I successfully run `stak main.scm` 10 | Then the stdout should contain exactly "A" 11 | 12 | Scenario: Do not evaluate a clause 13 | Given a file named "main.scm" with: 14 | """scheme 15 | (import (scheme base)) 16 | 17 | (when #f (write-u8 65)) 18 | """ 19 | When I successfully run `stak main.scm` 20 | Then the stdout should contain exactly "" 21 | -------------------------------------------------------------------------------- /features/time.feature: -------------------------------------------------------------------------------- 1 | Feature: Time 2 | Scenario: Get a current second 3 | Given a file named "main.scm" with: 4 | """scheme 5 | (import (scheme base) (scheme time)) 6 | 7 | (write-u8 (if (> (current-second) 1727447593) 65 66)) 8 | """ 9 | When I successfully run `stak main.scm` 10 | Then the stdout should contain exactly "A" 11 | 12 | @chibi @stak 13 | Scenario: Get a current jiffy 14 | Given a file named "main.scm" with: 15 | """scheme 16 | (import (scheme base) (scheme time)) 17 | 18 | (define seconds (current-second)) 19 | 20 | (write-u8 (if (>= (current-jiffy) (* (- seconds 1) (jiffies-per-second))) 65 66)) 21 | """ 22 | When I successfully run `stak main.scm` 23 | Then the stdout should contain exactly "A" 24 | -------------------------------------------------------------------------------- /features/types/boolean.feature: -------------------------------------------------------------------------------- 1 | Feature: Boolean 2 | Scenario Outline: Check if a value is a boolean 3 | Given a file named "main.scm" with: 4 | """scheme 5 | (import (scheme base)) 6 | 7 | (write-u8 (if (boolean? ) 65 66)) 8 | """ 9 | When I successfully run `stak main.scm` 10 | Then the stdout should contain exactly "" 11 | 12 | Examples: 13 | | value | output | 14 | | #f | A | 15 | | #t | A | 16 | | '() | B | 17 | 18 | Scenario Outline: Use a `not` operator 19 | Given a file named "main.scm" with: 20 | """scheme 21 | (import (scheme base)) 22 | 23 | (write-u8 (if (not ) 65 66)) 24 | """ 25 | When I successfully run `stak main.scm` 26 | Then the stdout should contain exactly "" 27 | 28 | Examples: 29 | | value | output | 30 | | #f | A | 31 | | #t | B | 32 | -------------------------------------------------------------------------------- /features/types/bytevector.feature: -------------------------------------------------------------------------------- 1 | Feature: Bytevector 2 | Scenario: Write a bytevector 3 | Given a file named "main.scm" with: 4 | """scheme 5 | (import (scheme base)) 6 | 7 | (write-bytevector #u8(65 66 67)) 8 | """ 9 | When I successfully run `stak main.scm` 10 | Then the stdout should contain exactly "ABC" 11 | 12 | Scenario Outline: Get a length of a bytevector 13 | Given a file named "main.scm" with: 14 | """scheme 15 | (import (scheme base)) 16 | 17 | (write-u8 (if (= (bytevector-length ) ) 65 66)) 18 | """ 19 | When I successfully run `stak main.scm` 20 | Then the stdout should contain exactly "A" 21 | 22 | Examples: 23 | | value | length | 24 | | #u8() | 0 | 25 | | #u8(0) | 1 | 26 | | #u8(0 0) | 2 | 27 | | #u8(0 0 0) | 3 | 28 | 29 | Scenario Outline: Get a length of a bytevector 30 | Given a file named "main.scm" with: 31 | """scheme 32 | (import (scheme base)) 33 | 34 | (write-u8 (bytevector-u8-ref )) 35 | """ 36 | When I successfully run `stak main.scm` 37 | Then the stdout should contain exactly "A" 38 | 39 | Examples: 40 | | vector | index | 41 | | #u8(65) | 0 | 42 | | #u8(65 66) | 0 | 43 | | #u8(66 65) | 1 | 44 | | #u8(65 66 66) | 0 | 45 | | #u8(66 65 66) | 1 | 46 | | #u8(66 66 65) | 2 | 47 | -------------------------------------------------------------------------------- /features/types/eof.feature: -------------------------------------------------------------------------------- 1 | Feature: EOF object 2 | Scenario: Check if a value is an EOF object 3 | Given a file named "main.scm" with: 4 | """scheme 5 | (import (scheme base)) 6 | 7 | (write-u8 (if (eof-object? (eof-object)) 65 66)) 8 | """ 9 | When I successfully run `stak main.scm` 10 | Then the stdout should contain exactly "A" 11 | -------------------------------------------------------------------------------- /features/types/port.feature: -------------------------------------------------------------------------------- 1 | Feature: Port 2 | Scenario Outline: Check if a value is a port 3 | Given a file named "main.scm" with: 4 | """scheme 5 | (import (scheme base)) 6 | 7 | (write-u8 (if (port? ) 65 66)) 8 | """ 9 | When I successfully run `stak main.scm` 10 | Then the stdout should contain exactly "A" 11 | 12 | Examples: 13 | | expression | 14 | | (current-input-port) | 15 | | (current-output-port) | 16 | 17 | Scenario: Check if a value is an input port 18 | Given a file named "main.scm" with: 19 | """scheme 20 | (import (scheme base)) 21 | 22 | (write-u8 (if (input-port? (current-input-port)) 65 66)) 23 | """ 24 | When I successfully run `stak main.scm` 25 | Then the stdout should contain exactly "A" 26 | 27 | Scenario Outline: Check if a value is an output port 28 | Given a file named "main.scm" with: 29 | """scheme 30 | (import (scheme base)) 31 | 32 | (write-u8 (if (output-port? ) 65 66)) 33 | """ 34 | When I successfully run `stak main.scm` 35 | Then the stdout should contain exactly "A" 36 | 37 | Examples: 38 | | expression | 39 | | (current-output-port) | 40 | | (current-error-port) | 41 | -------------------------------------------------------------------------------- /features/types/symbol.feature: -------------------------------------------------------------------------------- 1 | Feature: Symbol 2 | Scenario: Write a symbol 3 | Given a file named "main.scm" with: 4 | """scheme 5 | (import (scheme base)) 6 | 7 | (write-string (symbol->string 'foo)) 8 | """ 9 | When I successfully run `stak main.scm` 10 | Then the stdout should contain exactly "foo" 11 | 12 | Scenario: Convert a string to an existing symbol 13 | Given a file named "main.scm" with: 14 | """scheme 15 | (import (scheme base)) 16 | 17 | (write-u8 (if (eq? (string->symbol "foo") 'foo) 65 66)) 18 | """ 19 | When I successfully run `stak main.scm` 20 | Then the stdout should contain exactly "A" 21 | 22 | Scenario: Convert a string to a new symbol 23 | Given a file named "main.scm" with: 24 | """scheme 25 | (import (scheme base)) 26 | 27 | (write-u8 (if (eq? (string->symbol "foo") (string->symbol "foo")) 65 66)) 28 | """ 29 | When I successfully run `stak main.scm` 30 | Then the stdout should contain exactly "A" 31 | -------------------------------------------------------------------------------- /file/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stak-file" 3 | description = "File system for Stak Scheme" 4 | categories.workspace = true 5 | edition.workspace = true 6 | keywords.workspace = true 7 | license-file.workspace = true 8 | readme.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [features] 13 | async = ["stak-vm/async", "winter-maybe-async/async"] 14 | libc = ["dep:rustix"] 15 | std = [] 16 | 17 | [dependencies] 18 | heapless = { version = "0.8.0", default-features = false } 19 | rustix = { version = "1.0.7", default-features = false, features = [ 20 | "fs", 21 | ], optional = true } 22 | stak-vm = { version = "0.10.24", path = "../vm" } 23 | winter-maybe-async = "0.12.0" 24 | 25 | [dev-dependencies] 26 | tempfile = "3.20.0" 27 | 28 | [lints] 29 | workspace = true 30 | -------------------------------------------------------------------------------- /file/src/file_system.rs: -------------------------------------------------------------------------------- 1 | mod error; 2 | #[cfg(feature = "libc")] 3 | mod libc; 4 | mod memory; 5 | #[cfg(feature = "std")] 6 | mod os; 7 | mod utility; 8 | mod void; 9 | 10 | pub use self::error::FileError; 11 | use core::error::Error; 12 | #[cfg(feature = "libc")] 13 | pub use libc::LibcFileSystem; 14 | pub use memory::MemoryFileSystem; 15 | #[cfg(feature = "std")] 16 | pub use os::OsFileSystem; 17 | use stak_vm::{Memory, Value}; 18 | pub use void::VoidFileSystem; 19 | 20 | /// A file descriptor. 21 | pub type FileDescriptor = usize; 22 | 23 | /// A file system. 24 | pub trait FileSystem { 25 | /// A path. 26 | type Path: ?Sized; 27 | 28 | /// A path buffer. 29 | type PathBuf: AsRef; 30 | 31 | /// An error. 32 | type Error: Error; 33 | 34 | /// Opens a file and returns its descriptor. 35 | fn open(&mut self, path: &Self::Path, output: bool) -> Result; 36 | 37 | /// Closes a file. 38 | fn close(&mut self, descriptor: FileDescriptor) -> Result<(), Self::Error>; 39 | 40 | /// Reads a file. 41 | fn read(&mut self, descriptor: FileDescriptor) -> Result; 42 | 43 | /// Writes a file. 44 | fn write(&mut self, descriptor: FileDescriptor, byte: u8) -> Result<(), Self::Error>; 45 | 46 | /// Deletes a file. 47 | fn delete(&mut self, path: &Self::Path) -> Result<(), Self::Error>; 48 | 49 | /// Checks if a file exists. 50 | fn exists(&self, path: &Self::Path) -> Result; 51 | 52 | /// Decodes a path. 53 | fn decode_path(memory: &Memory, list: Value) -> Result; 54 | } 55 | -------------------------------------------------------------------------------- /file/src/file_system/error.rs: -------------------------------------------------------------------------------- 1 | use core::{ 2 | error, 3 | fmt::{self, Display, Formatter}, 4 | }; 5 | 6 | /// An error. 7 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 8 | pub enum FileError { 9 | /// An open failure. 10 | Open, 11 | /// A close failure. 12 | Close, 13 | /// An invalid file descriptor. 14 | InvalidFileDescriptor, 15 | /// A read failure. 16 | Read, 17 | /// A write failure. 18 | Write, 19 | /// A deletion failure. 20 | Delete, 21 | /// A existence check failure. 22 | Exists, 23 | /// A path decode failure. 24 | PathDecode, 25 | } 26 | 27 | impl error::Error for FileError {} 28 | 29 | impl Display for FileError { 30 | fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { 31 | match self { 32 | Self::Open => write!(formatter, "cannot open file"), 33 | Self::Close => write!(formatter, "cannot close file"), 34 | Self::InvalidFileDescriptor => write!(formatter, "invalid file descriptor"), 35 | Self::Read => write!(formatter, "cannot read file"), 36 | Self::Write => write!(formatter, "cannot write file"), 37 | Self::Delete => write!(formatter, "cannot delete file"), 38 | Self::Exists => write!(formatter, "cannot check file existence"), 39 | Self::PathDecode => write!(formatter, "cannot decode path"), 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /file/src/file_system/utility.rs: -------------------------------------------------------------------------------- 1 | use heapless::Vec; 2 | use stak_vm::{Memory, Value}; 3 | 4 | pub fn decode_path(memory: &Memory, mut list: Value) -> Option> { 5 | let mut path = Vec::new(); 6 | 7 | while list.assume_cons() != memory.null() { 8 | path.push(memory.car_value(list).assume_number().to_i64() as u8) 9 | .ok()?; 10 | list = memory.cdr_value(list); 11 | } 12 | 13 | Some(path) 14 | } 15 | -------------------------------------------------------------------------------- /file/src/file_system/void.rs: -------------------------------------------------------------------------------- 1 | use crate::{FileDescriptor, FileError, FileSystem}; 2 | use stak_vm::{Memory, Value}; 3 | 4 | /// A file system that does nothing and fails every operation. 5 | #[derive(Debug)] 6 | pub struct VoidFileSystem {} 7 | 8 | impl VoidFileSystem { 9 | /// Creates a file system. 10 | pub const fn new() -> Self { 11 | Self {} 12 | } 13 | } 14 | 15 | impl FileSystem for VoidFileSystem { 16 | type Path = [u8]; 17 | type PathBuf = [u8; 0]; 18 | type Error = FileError; 19 | 20 | fn open(&mut self, _: &Self::Path, _: bool) -> Result { 21 | Err(FileError::Open) 22 | } 23 | 24 | fn close(&mut self, _: FileDescriptor) -> Result<(), Self::Error> { 25 | Err(FileError::Close) 26 | } 27 | 28 | fn read(&mut self, _: FileDescriptor) -> Result { 29 | Err(FileError::Read) 30 | } 31 | 32 | fn write(&mut self, _: FileDescriptor, _: u8) -> Result<(), Self::Error> { 33 | Err(FileError::Write) 34 | } 35 | 36 | fn delete(&mut self, _: &Self::Path) -> Result<(), Self::Error> { 37 | Err(FileError::Delete) 38 | } 39 | 40 | fn exists(&self, _: &Self::Path) -> Result { 41 | Err(FileError::Exists) 42 | } 43 | 44 | fn decode_path(_: &Memory, _: Value) -> Result { 45 | Err(FileError::PathDecode) 46 | } 47 | } 48 | 49 | impl Default for VoidFileSystem { 50 | fn default() -> Self { 51 | Self::new() 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /file/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! File systems. 2 | 3 | #![cfg_attr(all(doc, not(doctest)), feature(doc_auto_cfg))] 4 | #![no_std] 5 | 6 | #[cfg(test)] 7 | extern crate alloc; 8 | #[cfg(any(feature = "std", test))] 9 | extern crate std; 10 | 11 | mod file_system; 12 | mod primitive_set; 13 | 14 | pub use file_system::*; 15 | pub use primitive_set::{FilePrimitiveSet, Primitive, PrimitiveError}; 16 | -------------------------------------------------------------------------------- /file/src/primitive_set/error.rs: -------------------------------------------------------------------------------- 1 | use crate::FileError; 2 | use core::{ 3 | error, 4 | fmt::{self, Debug, Display, Formatter}, 5 | }; 6 | use stak_vm::Exception; 7 | 8 | /// An error of primitives. 9 | #[derive(Clone, Debug, Eq, PartialEq)] 10 | pub enum PrimitiveError { 11 | /// A file system error. 12 | File(FileError), 13 | /// A virtual machine error. 14 | Vm(stak_vm::Error), 15 | } 16 | 17 | impl Exception for PrimitiveError { 18 | fn is_critical(&self) -> bool { 19 | match self { 20 | Self::File(_) => false, 21 | Self::Vm(error) => error.is_critical(), 22 | } 23 | } 24 | } 25 | 26 | impl error::Error for PrimitiveError {} 27 | 28 | impl Display for PrimitiveError { 29 | fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { 30 | match self { 31 | Self::File(error) => write!(formatter, "{error}"), 32 | Self::Vm(error) => write!(formatter, "{error}"), 33 | } 34 | } 35 | } 36 | 37 | impl From for PrimitiveError { 38 | fn from(error: FileError) -> Self { 39 | Self::File(error) 40 | } 41 | } 42 | 43 | impl From for PrimitiveError { 44 | fn from(error: stak_vm::Error) -> Self { 45 | Self::Vm(error) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /file/src/primitive_set/primitive.rs: -------------------------------------------------------------------------------- 1 | /// A primitive of a file system. 2 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 3 | pub enum Primitive { 4 | /// Open a file. 5 | OpenFile, 6 | /// Close a file. 7 | CloseFile, 8 | /// Read a file. 9 | ReadFile, 10 | /// Write a file. 11 | WriteFile, 12 | /// Delete a file. 13 | DeleteFile, 14 | /// Check if a file exists. 15 | ExistsFile, 16 | } 17 | 18 | impl Primitive { 19 | pub(super) const OPEN_FILE: usize = Self::OpenFile as _; 20 | pub(super) const CLOSE_FILE: usize = Self::CloseFile as _; 21 | pub(super) const READ_FILE: usize = Self::ReadFile as _; 22 | pub(super) const WRITE_FILE: usize = Self::WriteFile as _; 23 | pub(super) const DELETE_FILE: usize = Self::DeleteFile as _; 24 | pub(super) const EXISTS_FILE: usize = Self::ExistsFile as _; 25 | } 26 | -------------------------------------------------------------------------------- /inexact/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stak-inexact" 3 | description = "Inexact number operations for Stak Scheme" 4 | categories.workspace = true 5 | edition.workspace = true 6 | keywords.workspace = true 7 | license-file.workspace = true 8 | readme.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [features] 13 | async = ["stak-vm/async", "winter-maybe-async/async"] 14 | 15 | [dependencies] 16 | libm = { version = "0.2.15", default-features = false } 17 | stak-vm = { version = "0.10.24", path = "../vm" } 18 | winter-maybe-async = "0.12.0" 19 | 20 | [lints] 21 | workspace = true 22 | -------------------------------------------------------------------------------- /inexact/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Inexact number operations. 2 | 3 | #![no_std] 4 | 5 | mod primitive; 6 | mod primitive_set; 7 | 8 | pub use primitive_set::*; 9 | -------------------------------------------------------------------------------- /inexact/src/primitive.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 2 | pub(crate) enum Primitive { 3 | Exponentiation, 4 | Logarithm, 5 | } 6 | 7 | impl Primitive { 8 | pub const EXPONENTIATION: usize = Self::Exponentiation as _; 9 | pub const LOGARITHM: usize = Self::Logarithm as _; 10 | } 11 | -------------------------------------------------------------------------------- /inexact/src/primitive_set.rs: -------------------------------------------------------------------------------- 1 | use crate::primitive::Primitive; 2 | use libm::{exp, log}; 3 | use stak_vm::{Error, Memory, Number, PrimitiveSet}; 4 | use winter_maybe_async::maybe_async; 5 | 6 | /// A primitive set for inexact number operations. 7 | #[derive(Debug, Default)] 8 | pub struct InexactPrimitiveSet {} 9 | 10 | impl InexactPrimitiveSet { 11 | /// Creates a primitive set. 12 | pub fn new() -> Self { 13 | Self::default() 14 | } 15 | } 16 | 17 | impl PrimitiveSet for InexactPrimitiveSet { 18 | type Error = Error; 19 | 20 | #[maybe_async] 21 | fn operate(&mut self, memory: &mut Memory<'_>, primitive: usize) -> Result<(), Self::Error> { 22 | match primitive { 23 | Primitive::EXPONENTIATION => { 24 | memory.operate_unary(|x| Number::from_f64(exp(x.to_f64())))? 25 | } 26 | Primitive::LOGARITHM => memory.operate_unary(|x| Number::from_f64(log(x.to_f64())))?, 27 | _ => return Err(Error::IllegalPrimitive), 28 | } 29 | 30 | Ok(()) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /libc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stak-libc" 3 | description = "Stak Scheme libc utilities" 4 | categories.workspace = true 5 | edition.workspace = true 6 | keywords.workspace = true 7 | license-file.workspace = true 8 | readme.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [dependencies] 13 | libc = { version = "0.2.172", default-features = false } 14 | rustix = { version = "1.0.7", default-features = false, features = [ 15 | "fs", 16 | "mm", 17 | ] } 18 | 19 | [lints] 20 | workspace = true 21 | -------------------------------------------------------------------------------- /libc/src/heap.rs: -------------------------------------------------------------------------------- 1 | use core::{mem::align_of, ptr::write, slice}; 2 | 3 | /// A memory block on a heap. 4 | pub struct Heap { 5 | ptr: *mut T, 6 | len: usize, 7 | } 8 | 9 | impl Heap { 10 | /// Creates a heap. 11 | pub fn new(len: usize, default: impl Fn() -> T) -> Self { 12 | let mut this = Self { 13 | // SAFETY: We allow memory access only within `len`. 14 | ptr: unsafe { libc::malloc(len * align_of::()) } as _, 15 | len, 16 | }; 17 | 18 | for x in this.as_slice_mut() { 19 | // SAFETY: `x` is not initialized yet. 20 | unsafe { write(x, default()) }; 21 | } 22 | 23 | this 24 | } 25 | 26 | /// Returns a slice. 27 | pub const fn as_slice(&mut self) -> &[T] { 28 | // SAFETY: `self.ptr` has the length of `self.len`. 29 | unsafe { slice::from_raw_parts(self.ptr as _, self.len) } 30 | } 31 | 32 | /// Returns a mutable slice. 33 | pub const fn as_slice_mut(&mut self) -> &mut [T] { 34 | // SAFETY: `self.ptr` has the length of `self.len`. 35 | unsafe { slice::from_raw_parts_mut(self.ptr as _, self.len) } 36 | } 37 | } 38 | 39 | impl Drop for Heap { 40 | fn drop(&mut self) { 41 | // SAFETY: The previous `malloc` call is guaranteed to have succeeded. 42 | unsafe { libc::free(self.ptr as _) } 43 | } 44 | } 45 | 46 | #[cfg(test)] 47 | mod tests { 48 | use super::*; 49 | 50 | #[test] 51 | fn new() { 52 | Heap::::new(42, || 42); 53 | } 54 | 55 | #[test] 56 | fn new_zero_sized() { 57 | Heap::::new(0, || 42); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /libc/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Utilities around `libc` for Stak Scheme. 2 | 3 | #![no_std] 4 | 5 | mod heap; 6 | mod mmap; 7 | 8 | pub use heap::Heap; 9 | pub use mmap::Mmap; 10 | -------------------------------------------------------------------------------- /libc/src/mmap.rs: -------------------------------------------------------------------------------- 1 | use core::{ffi::CStr, ptr::null_mut, slice}; 2 | use rustix::{ 3 | fs::{self, Mode, OFlags}, 4 | io, 5 | mm::{MapFlags, ProtFlags, mmap, munmap}, 6 | }; 7 | 8 | /// A mmap. 9 | pub struct Mmap { 10 | ptr: *mut u8, 11 | len: usize, 12 | } 13 | 14 | impl Mmap { 15 | /// Creates a mmap opening a file at a path. 16 | pub fn new(path: &CStr) -> io::Result { 17 | let len = fs::stat(path)?.st_size as _; 18 | // spell-checker: disable-next-line 19 | let descriptor = fs::open(path, OFlags::RDONLY, Mode::RUSR)?; 20 | 21 | Ok(Self { 22 | // SAFETY: The passed pointer is null. 23 | ptr: unsafe { 24 | mmap( 25 | null_mut(), 26 | len, 27 | ProtFlags::READ, 28 | MapFlags::PRIVATE, 29 | descriptor, 30 | 0, 31 | )? 32 | } as _, 33 | len, 34 | }) 35 | } 36 | 37 | /// Returns a slice of bytes. 38 | pub const fn as_slice(&self) -> &[u8] { 39 | // SAFETY: `self.ptr` is guaranteed to have the length of `self.len`. 40 | unsafe { slice::from_raw_parts(self.ptr, self.len) } 41 | } 42 | } 43 | 44 | impl Drop for Mmap { 45 | fn drop(&mut self) { 46 | // SAFETY: We ensure that the `mmap` call succeeds. 47 | unsafe { munmap(self.ptr as _, self.len).unwrap() } 48 | } 49 | } 50 | 51 | #[cfg(test)] 52 | mod tests { 53 | use super::*; 54 | 55 | #[test] 56 | fn read_file() { 57 | Mmap::new(c"src/lib.rs").unwrap(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /macro-util/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stak-macro-util" 3 | description = "Macro utilities for Stak Scheme" 4 | categories.workspace = true 5 | edition.workspace = true 6 | keywords.workspace = true 7 | license-file.workspace = true 8 | readme.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [dependencies] 13 | proc-macro2 = "1.0.95" 14 | quote = "1.0.40" 15 | syn = "2.0.101" 16 | 17 | [lints] 18 | workspace = true 19 | -------------------------------------------------------------------------------- /macro-util/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Macro utilities. 2 | 3 | use core::error::Error; 4 | use proc_macro2::TokenStream; 5 | use quote::quote; 6 | use std::{env, fs::read_to_string, path::Path}; 7 | use syn::LitStr; 8 | 9 | /// Reads a source file. 10 | pub fn read_source_file(path: LitStr) -> Result> { 11 | Ok(read_to_string( 12 | Path::new(&env::var("CARGO_MANIFEST_DIR")?) 13 | .join("src") 14 | .join(path.value()), 15 | )?) 16 | } 17 | 18 | /// Converts a macro result into a token stream. 19 | pub fn convert_result(result: Result>) -> TokenStream { 20 | result.unwrap_or_else(|error| { 21 | let message = error.to_string(); 22 | 23 | quote! { compile_error!(#message) } 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stak-macro" 3 | description = "Macros to integrate Stak Scheme" 4 | categories.workspace = true 5 | edition.workspace = true 6 | keywords.workspace = true 7 | license-file.workspace = true 8 | readme.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [lib] 13 | proc-macro = true 14 | 15 | [features] 16 | async = [ 17 | "stak-compiler/async", 18 | "stak-device/async", 19 | "stak-file/async", 20 | "stak-process-context/async", 21 | "stak-r7rs/async", 22 | "stak-time/async", 23 | "stak-vm/async", 24 | ] 25 | hot-reload = [] 26 | 27 | [dependencies] 28 | cfg-elif = "0.6.3" 29 | proc-macro2 = { version = "1.0.95", features = ["proc-macro"] } 30 | quote = "1.0.40" 31 | stak-compiler = { version = "0.10.24", path = "../compiler" } 32 | stak-macro-util = { version = "0.10.24", path = "../macro-util" } 33 | syn = "2.0.101" 34 | 35 | [dev-dependencies] 36 | stak-device = { path = "../device" } 37 | stak-file = { path = "../file" } 38 | stak-process-context = { path = "../process_context" } 39 | stak-r7rs = { path = "../r7rs" } 40 | stak-time = { path = "../time" } 41 | stak-vm = { path = "../vm" } 42 | 43 | [lints] 44 | workspace = true 45 | -------------------------------------------------------------------------------- /macro/src/foo.scm: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /macro/tests/empty.scm: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /minifier/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stak-minifier" 3 | description = "Stak Scheme source code minifier" 4 | categories.workspace = true 5 | edition.workspace = true 6 | keywords.workspace = true 7 | license-file.workspace = true 8 | readme.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [dependencies] 13 | stak-configuration = { version = "0.10.24", path = "../configuration" } 14 | stak-device = { version = "0.10.24", path = "../device", features = ["std"] } 15 | stak-file = { version = "0.10.24", path = "../file" } 16 | stak-macro = { version = "0.10.24", path = "../macro" } 17 | stak-process-context = { version = "0.10.24", path = "../process_context" } 18 | stak-r7rs = { version = "0.10.24", path = "../r7rs" } 19 | stak-time = { version = "0.10.24", path = "../time" } 20 | stak-vm = { version = "0.10.24", path = "../vm" } 21 | 22 | [dev-dependencies] 23 | pretty_assertions = "1.4.1" 24 | 25 | [lints] 26 | workspace = true 27 | -------------------------------------------------------------------------------- /minifier/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Scheme source code minifier. 2 | 3 | use stak_configuration::DEFAULT_HEAP_SIZE; 4 | use stak_device::ReadWriteDevice; 5 | use stak_file::VoidFileSystem; 6 | use stak_macro::include_r7rs; 7 | use stak_process_context::VoidProcessContext; 8 | use stak_r7rs::{SmallError, SmallPrimitiveSet}; 9 | use stak_time::VoidClock; 10 | use stak_vm::Vm; 11 | use std::io::{Read, Write, empty}; 12 | 13 | /// Minifies given source codes. 14 | pub fn minify(reader: impl Read, writer: impl Write) -> Result<(), SmallError> { 15 | const BYTECODE: &[u8] = include_r7rs!("minify.scm"); 16 | 17 | let mut heap = vec![Default::default(); DEFAULT_HEAP_SIZE]; 18 | let mut vm = Vm::new( 19 | &mut heap, 20 | SmallPrimitiveSet::new( 21 | ReadWriteDevice::new(reader, writer, empty()), 22 | VoidFileSystem::new(), 23 | VoidProcessContext::new(), 24 | VoidClock::new(), 25 | ), 26 | )?; 27 | 28 | vm.initialize(BYTECODE.iter().copied())?; 29 | vm.run()?; 30 | 31 | Ok(()) 32 | } 33 | 34 | #[cfg(test)] 35 | mod tests { 36 | use super::*; 37 | use pretty_assertions::assert_eq; 38 | 39 | #[test] 40 | fn minify_expression() { 41 | let source = "(foo bar)"; 42 | let mut target = vec![]; 43 | 44 | minify(source.as_bytes(), &mut target).unwrap(); 45 | 46 | assert_eq!(target, b"(foo bar)\n"); 47 | } 48 | 49 | #[test] 50 | fn minify_expressions() { 51 | let source = "(foo bar)\n\n( baz blah )"; 52 | let mut target = vec![]; 53 | 54 | minify(source.as_bytes(), &mut target).unwrap(); 55 | 56 | assert_eq!(target, b"(foo bar)\n(baz blah)\n"); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /minifier/src/minify.scm: -------------------------------------------------------------------------------- 1 | ../../minify.scm -------------------------------------------------------------------------------- /minifier_macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stak-minifier-macro" 3 | description = "Macros to minify Stak Scheme source codes" 4 | categories.workspace = true 5 | edition.workspace = true 6 | keywords.workspace = true 7 | license-file.workspace = true 8 | readme.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [lib] 13 | proc-macro = true 14 | 15 | [dependencies] 16 | proc-macro2 = { version = "1.0.95", features = ["proc-macro"] } 17 | quote = "1.0.40" 18 | syn = "2.0.101" 19 | stak-minifier = { version = "0.10.24", path = "../minifier" } 20 | stak-macro-util = { version = "0.10.24", path = "../macro-util" } 21 | 22 | [lints] 23 | workspace = true 24 | -------------------------------------------------------------------------------- /minifier_macro/src/foo.scm: -------------------------------------------------------------------------------- 1 | (foo bar) 2 | 3 | (baz) 4 | -------------------------------------------------------------------------------- /minifier_macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Macros to minify Scheme source codes. 2 | 3 | extern crate alloc; 4 | 5 | use alloc::str; 6 | use core::error::Error; 7 | use proc_macro::TokenStream; 8 | use proc_macro2::Literal; 9 | use quote::quote; 10 | use stak_macro_util::{convert_result, read_source_file}; 11 | use syn::{LitStr, parse_macro_input}; 12 | 13 | /// Minifies source codes in Scheme. 14 | /// 15 | /// # Examples 16 | /// 17 | /// ```rust 18 | /// const SCRIPT: &str = stak_minifier_macro::minify!("( foo bar )\n\n(baz)"); 19 | /// 20 | /// assert_eq!(SCRIPT, "(foo bar)\n(baz)\n"); 21 | /// ``` 22 | #[proc_macro] 23 | pub fn minify(input: TokenStream) -> TokenStream { 24 | let input = parse_macro_input!(input as LitStr); 25 | 26 | convert_result(minify_source(&input.value())).into() 27 | } 28 | 29 | /// Includes and minifies source codes in Scheme in a file. 30 | /// 31 | /// # Examples 32 | /// 33 | /// ```rust 34 | /// const SCRIPT: &str = stak_minifier_macro::include_minified!("foo.scm"); 35 | /// 36 | /// assert_eq!(SCRIPT, "(foo bar)\n(baz)\n"); 37 | /// ``` 38 | #[proc_macro] 39 | pub fn include_minified(input: TokenStream) -> TokenStream { 40 | let input = parse_macro_input!(input as LitStr); 41 | 42 | convert_result((|| minify_source(&read_source_file(input)?))()).into() 43 | } 44 | 45 | fn minify_source(source: &str) -> Result> { 46 | let mut buffer = vec![]; 47 | 48 | stak_minifier::minify(source.as_bytes(), &mut buffer)?; 49 | 50 | let target = Literal::string(str::from_utf8(&buffer)?); 51 | 52 | Ok(quote! { #target }) 53 | } 54 | -------------------------------------------------------------------------------- /minifier_macro/tests/foo.scm: -------------------------------------------------------------------------------- 1 | (foo bar) 2 | 3 | (baz) 4 | -------------------------------------------------------------------------------- /minifier_macro/tests/main.rs: -------------------------------------------------------------------------------- 1 | #![expect(missing_docs)] 2 | #![no_std] 3 | 4 | use stak_minifier_macro::{include_minified, minify}; 5 | 6 | #[test] 7 | fn minify_expressions() { 8 | const SCRIPT: &str = minify!("( foo bar )\n\n(baz)"); 9 | 10 | assert_eq!(SCRIPT, "(foo bar)\n(baz)\n"); 11 | } 12 | 13 | #[test] 14 | fn include_minified_expressions() { 15 | const SCRIPT: &str = include_minified!("../tests/foo.scm"); 16 | 17 | assert_eq!(SCRIPT, "(foo bar)\n(baz)\n"); 18 | } 19 | -------------------------------------------------------------------------------- /minify.scm: -------------------------------------------------------------------------------- 1 | (import (scheme base) (scheme read) (scheme write)) 2 | 3 | (define (read-all) 4 | (let ((x (read))) 5 | (if (eof-object? x) 6 | '() 7 | (cons x (read-all))))) 8 | 9 | (define (write-all xs) 10 | (for-each 11 | (lambda (x) 12 | (begin 13 | (write x) 14 | (newline))) 15 | xs)) 16 | 17 | (write-all (read-all)) 18 | -------------------------------------------------------------------------------- /module/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stak-module" 3 | description = "Modules in Stak Scheme" 4 | categories.workspace = true 5 | edition.workspace = true 6 | keywords.workspace = true 7 | license-file.workspace = true 8 | readme.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [dependencies] 13 | hmr = "0.2.3" 14 | 15 | [lints] 16 | workspace = true 17 | -------------------------------------------------------------------------------- /module/src/guard.rs: -------------------------------------------------------------------------------- 1 | //! Guards against modules. 2 | 3 | use core::ops::Deref; 4 | 5 | /// A read guard against a module. 6 | pub trait Guard: Deref {} 7 | -------------------------------------------------------------------------------- /module/src/hot_reload.rs: -------------------------------------------------------------------------------- 1 | //! Hot-reloaded modules. 2 | 3 | use crate::{Guard, Module}; 4 | use core::ops::Deref; 5 | 6 | /// A hot-reloaded module. 7 | pub struct HotReloadModule { 8 | module: hmr::Module, 9 | } 10 | 11 | impl HotReloadModule { 12 | /// Creates a hot-reloaded module. 13 | pub const fn new(path: &'static str) -> Self { 14 | Self { 15 | module: hmr::Module::new(path), 16 | } 17 | } 18 | } 19 | 20 | impl Module<'static> for HotReloadModule { 21 | type Guard = HotReloadGuard; 22 | 23 | fn bytecode(&'static self) -> Self::Guard { 24 | HotReloadGuard(self.module.load()) 25 | } 26 | } 27 | 28 | /// A read guard against a hot-reloaded module. 29 | pub struct HotReloadGuard(hmr::Guard); 30 | 31 | impl Deref for HotReloadGuard { 32 | type Target = [u8]; 33 | 34 | fn deref(&self) -> &Self::Target { 35 | &self.0 36 | } 37 | } 38 | 39 | impl Guard for HotReloadGuard {} 40 | -------------------------------------------------------------------------------- /module/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Modules in Stak Scheme. 2 | 3 | mod guard; 4 | mod hot_reload; 5 | mod module; 6 | mod r#static; 7 | mod universal; 8 | 9 | pub use guard::Guard; 10 | pub use hot_reload::*; 11 | pub use module::Module; 12 | pub use r#static::*; 13 | pub use universal::*; 14 | -------------------------------------------------------------------------------- /module/src/module.rs: -------------------------------------------------------------------------------- 1 | //! Modules. 2 | 3 | use crate::Guard; 4 | 5 | /// A module. 6 | pub trait Module<'a> { 7 | /// A read guard against a module. 8 | type Guard: Guard; 9 | 10 | /// Returns bytecodes. 11 | fn bytecode(&'a self) -> Self::Guard; 12 | } 13 | -------------------------------------------------------------------------------- /module/src/static.rs: -------------------------------------------------------------------------------- 1 | //! Static modules. 2 | 3 | use crate::{Guard, Module}; 4 | use core::ops::Deref; 5 | 6 | /// A static module. 7 | #[derive(Debug)] 8 | pub struct StaticModule { 9 | bytecode: &'static [u8], 10 | } 11 | 12 | impl StaticModule { 13 | /// Creates a static module. 14 | pub const fn new(bytecode: &'static [u8]) -> Self { 15 | Self { bytecode } 16 | } 17 | } 18 | 19 | impl<'a> Module<'a> for StaticModule { 20 | type Guard = StaticGuard; 21 | 22 | fn bytecode(&'a self) -> Self::Guard { 23 | StaticGuard(self.bytecode) 24 | } 25 | } 26 | 27 | /// A read guard against a static module. 28 | pub struct StaticGuard(&'static [u8]); 29 | 30 | impl Deref for StaticGuard { 31 | type Target = [u8]; 32 | 33 | fn deref(&self) -> &Self::Target { 34 | self.0 35 | } 36 | } 37 | 38 | impl Guard for StaticGuard {} 39 | -------------------------------------------------------------------------------- /module/src/universal.rs: -------------------------------------------------------------------------------- 1 | use crate::{Guard, HotReloadGuard, HotReloadModule, Module, StaticGuard, StaticModule}; 2 | use core::ops::Deref; 3 | 4 | /// A universal module. 5 | pub enum UniversalModule { 6 | /// A hot-reloaded module. 7 | HotReload(&'static HotReloadModule), 8 | /// A static module. 9 | Static(StaticModule), 10 | } 11 | 12 | impl<'a> Module<'a> for UniversalModule { 13 | type Guard = UniversalGuard; 14 | 15 | fn bytecode(&'a self) -> Self::Guard { 16 | match self { 17 | Self::HotReload(module) => UniversalGuard::HotReload(module.bytecode()), 18 | Self::Static(module) => UniversalGuard::Static(module.bytecode()), 19 | } 20 | } 21 | } 22 | 23 | /// A read guard against a universal module. 24 | pub enum UniversalGuard { 25 | /// A guard for a hot-reloaded module. 26 | HotReload(HotReloadGuard), 27 | /// A guard for a static module. 28 | Static(StaticGuard), 29 | } 30 | 31 | impl Deref for UniversalGuard { 32 | type Target = [u8]; 33 | 34 | fn deref(&self) -> &Self::Target { 35 | match self { 36 | Self::HotReload(guard) => guard, 37 | Self::Static(guard) => guard, 38 | } 39 | } 40 | } 41 | 42 | impl Guard for UniversalGuard {} 43 | -------------------------------------------------------------------------------- /native/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stak-native" 3 | description = "Optimized primitives of native functions for Stak Scheme" 4 | categories.workspace = true 5 | edition.workspace = true 6 | keywords.workspace = true 7 | license-file.workspace = true 8 | readme.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [features] 13 | async = ["stak-vm/async", "winter-maybe-async/async"] 14 | 15 | [dependencies] 16 | stak-vm = { version = "0.10.24", path = "../vm" } 17 | winter-maybe-async = "0.12.0" 18 | 19 | [lints] 20 | workspace = true 21 | -------------------------------------------------------------------------------- /native/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Stak Scheme primitive sets for optimized primitives of native functions. 2 | 3 | #![no_std] 4 | 5 | mod equal; 6 | mod list; 7 | mod type_check; 8 | 9 | pub use equal::*; 10 | pub use list::*; 11 | pub use type_check::*; 12 | -------------------------------------------------------------------------------- /native/src/type_check.rs: -------------------------------------------------------------------------------- 1 | use stak_vm::{Error, Memory, PrimitiveSet, Type}; 2 | use winter_maybe_async::maybe_async; 3 | 4 | /// A type check primitive. 5 | pub enum TypeCheckPrimitive { 6 | /// A null type check. 7 | Null, 8 | /// A pair type check. 9 | Pair, 10 | } 11 | 12 | impl TypeCheckPrimitive { 13 | const NULL: usize = Self::Null as _; 14 | const PAIR: usize = Self::Pair as _; 15 | } 16 | 17 | /// A type check primitive set. 18 | #[derive(Debug, Default)] 19 | pub struct TypeCheckPrimitiveSet {} 20 | 21 | impl TypeCheckPrimitiveSet { 22 | /// Creates a primitive set. 23 | pub fn new() -> Self { 24 | Self::default() 25 | } 26 | } 27 | 28 | impl PrimitiveSet for TypeCheckPrimitiveSet { 29 | type Error = Error; 30 | 31 | #[maybe_async] 32 | fn operate(&mut self, memory: &mut Memory<'_>, primitive: usize) -> Result<(), Self::Error> { 33 | match primitive { 34 | TypeCheckPrimitive::NULL => memory.operate_top(|memory, value| { 35 | memory.boolean(value == memory.null().into()).into() 36 | })?, 37 | TypeCheckPrimitive::PAIR => memory.operate_top(|memory, value| { 38 | memory 39 | .boolean( 40 | value 41 | .to_cons() 42 | .map(|cons| memory.cdr(cons).tag() == Type::Pair as _) 43 | .unwrap_or_default(), 44 | ) 45 | .into() 46 | })?, 47 | _ => return Err(Error::IllegalPrimitive), 48 | } 49 | 50 | Ok(()) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /process_context/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stak-process-context" 3 | description = "Process context for Stak Scheme" 4 | categories.workspace = true 5 | edition.workspace = true 6 | keywords.workspace = true 7 | license-file.workspace = true 8 | readme.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [features] 13 | async = ["stak-vm/async", "winter-maybe-async/async"] 14 | libc = [] 15 | std = [] 16 | 17 | [lints] 18 | workspace = true 19 | 20 | [dependencies] 21 | stak-vm = { version = "0.10.24", path = "../vm" } 22 | winter-maybe-async = "0.12.0" 23 | -------------------------------------------------------------------------------- /process_context/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A process context for Stak Scheme. 2 | 3 | #![cfg_attr(all(doc, not(doctest)), feature(doc_auto_cfg))] 4 | #![no_std] 5 | 6 | #[cfg(feature = "std")] 7 | extern crate alloc; 8 | 9 | #[cfg(feature = "std")] 10 | extern crate std; 11 | 12 | mod primitive_set; 13 | mod process_context; 14 | 15 | pub use primitive_set::{Primitive, ProcessContextPrimitiveSet}; 16 | pub use process_context::*; 17 | -------------------------------------------------------------------------------- /process_context/src/primitive_set/primitive.rs: -------------------------------------------------------------------------------- 1 | /// A primitive of process context. 2 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 3 | pub enum Primitive { 4 | /// A command line. 5 | CommandLine, 6 | /// Environment variables. 7 | EnvironmentVariables, 8 | } 9 | 10 | impl Primitive { 11 | pub(super) const COMMAND_LINE: usize = Self::CommandLine as _; 12 | pub(super) const ENVIRONMENT_VARIABLES: usize = Self::EnvironmentVariables as _; 13 | } 14 | -------------------------------------------------------------------------------- /process_context/src/process_context.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "libc")] 2 | mod libc; 3 | mod memory; 4 | #[cfg(feature = "std")] 5 | mod os; 6 | mod void; 7 | 8 | #[cfg(feature = "libc")] 9 | pub use libc::LibcProcessContext; 10 | pub use memory::MemoryProcessContext; 11 | #[cfg(feature = "std")] 12 | pub use os::OsProcessContext; 13 | pub use void::VoidProcessContext; 14 | 15 | /// A process context. 16 | pub trait ProcessContext { 17 | /// Returns a command name and its arguments in a reverse order. 18 | fn command_line_rev(&self) -> impl IntoIterator; 19 | 20 | /// Returns environment variables. 21 | fn environment_variables(&self) -> impl IntoIterator; 22 | } 23 | -------------------------------------------------------------------------------- /process_context/src/process_context/libc.rs: -------------------------------------------------------------------------------- 1 | use crate::ProcessContext; 2 | use core::{ffi::CStr, slice}; 3 | 4 | /// A process context based on libc. 5 | #[derive(Debug)] 6 | pub struct LibcProcessContext { 7 | arguments: &'static [*const i8], 8 | } 9 | 10 | impl LibcProcessContext { 11 | /// Creates a process context. 12 | /// 13 | /// # Safety 14 | /// 15 | /// The `argc` and `argv` arguments should be the ones passed down as 16 | /// arguments to the `main` function in C. 17 | pub const unsafe fn new(argc: isize, argv: *const *const i8) -> Self { 18 | Self { 19 | // SAFETY: Operating systems guarantee the length of `argv` to be `argc`. 20 | arguments: unsafe { slice::from_raw_parts(argv, argc as _) }, 21 | } 22 | } 23 | } 24 | 25 | impl ProcessContext for LibcProcessContext { 26 | fn command_line_rev(&self) -> impl IntoIterator { 27 | self.arguments.iter().rev().map(|&argument| { 28 | // SAFETY: Operating systems guarantee elements in `argv` to be C strings. 29 | unsafe { CStr::from_ptr(argument as _) }.to_str().unwrap() 30 | }) 31 | } 32 | 33 | fn environment_variables(&self) -> impl IntoIterator { 34 | [] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /process_context/src/process_context/memory.rs: -------------------------------------------------------------------------------- 1 | use crate::ProcessContext; 2 | 3 | /// A in-memory process context. 4 | #[derive(Debug)] 5 | pub struct MemoryProcessContext<'a> { 6 | arguments: &'a [&'a str], 7 | environment_variables: &'a [(&'a str, &'a str)], 8 | } 9 | 10 | impl<'a> MemoryProcessContext<'a> { 11 | /// Creates a process context. 12 | pub const fn new( 13 | arguments: &'a [&'a str], 14 | environment_variables: &'a [(&'a str, &'a str)], 15 | ) -> Self { 16 | Self { 17 | arguments, 18 | environment_variables, 19 | } 20 | } 21 | } 22 | 23 | impl ProcessContext for MemoryProcessContext<'_> { 24 | fn command_line_rev(&self) -> impl IntoIterator { 25 | self.arguments.iter().rev().copied() 26 | } 27 | 28 | fn environment_variables(&self) -> impl IntoIterator { 29 | self.environment_variables.iter().copied() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /process_context/src/process_context/os.rs: -------------------------------------------------------------------------------- 1 | use crate::ProcessContext; 2 | use alloc::{string::String, vec::Vec}; 3 | use std::{ 4 | env::{args, vars}, 5 | sync::LazyLock, 6 | }; 7 | 8 | /// A process context provided by an operating system. 9 | pub struct OsProcessContext { 10 | arguments: LazyLock>, 11 | environment_variables: LazyLock>, 12 | } 13 | 14 | impl OsProcessContext { 15 | /// Creates a process context. 16 | pub fn new() -> Self { 17 | Self { 18 | arguments: LazyLock::new(|| args().collect()), 19 | environment_variables: LazyLock::new(|| vars().collect()), 20 | } 21 | } 22 | } 23 | 24 | impl ProcessContext for OsProcessContext { 25 | fn command_line_rev(&self) -> impl IntoIterator { 26 | (*self.arguments).iter().map(AsRef::as_ref).rev() 27 | } 28 | 29 | fn environment_variables(&self) -> impl IntoIterator { 30 | (*self.environment_variables) 31 | .iter() 32 | .map(|(key, value)| (key.as_ref(), value.as_ref())) 33 | } 34 | } 35 | 36 | impl Default for OsProcessContext { 37 | fn default() -> Self { 38 | Self::new() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /process_context/src/process_context/void.rs: -------------------------------------------------------------------------------- 1 | use crate::ProcessContext; 2 | 3 | /// A void process context that provides no context information. 4 | #[derive(Debug, Default)] 5 | pub struct VoidProcessContext {} 6 | 7 | impl VoidProcessContext { 8 | /// Creates a process context. 9 | pub const fn new() -> Self { 10 | Self {} 11 | } 12 | } 13 | 14 | impl ProcessContext for VoidProcessContext { 15 | fn command_line_rev(&self) -> impl IntoIterator { 16 | [] 17 | } 18 | 19 | fn environment_variables(&self) -> impl IntoIterator { 20 | [] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /profiler/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stak-profiler" 3 | description = "Profiling for Stak Scheme" 4 | categories.workspace = true 5 | edition.workspace = true 6 | keywords.workspace = true 7 | license-file.workspace = true 8 | readme.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [dependencies] 13 | stak-vm = { version = "0.10.24", path = "../vm" } 14 | 15 | [dev-dependencies] 16 | indoc = "2.0.6" 17 | pretty_assertions = "1.4.1" 18 | 19 | [lints] 20 | workspace = true 21 | -------------------------------------------------------------------------------- /profiler/src/error.rs: -------------------------------------------------------------------------------- 1 | use core::{ 2 | fmt::{self, Display, Formatter}, 3 | num::ParseIntError, 4 | }; 5 | use std::{error, io}; 6 | 7 | /// An error. 8 | #[derive(Debug, Clone, Eq, PartialEq)] 9 | pub enum Error { 10 | /// I/O failure. 11 | Io(String), 12 | /// A missing call record. 13 | MissingCallRecord, 14 | /// A missing record type. 15 | MissingProcedureOperation, 16 | /// A missing stack. 17 | MissingStack, 18 | /// A missing time. 19 | MissingTime, 20 | /// Integer parse failure. 21 | ParseInt(ParseIntError), 22 | /// An unknown record type. 23 | UnknownRecordType, 24 | } 25 | 26 | impl Display for Error { 27 | fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { 28 | match self { 29 | Self::Io(error) => write!(formatter, "{error}"), 30 | Self::MissingCallRecord => write!(formatter, "missing call record"), 31 | Self::MissingProcedureOperation => write!(formatter, "missing record type"), 32 | Self::MissingStack => write!(formatter, "missing stack"), 33 | Self::MissingTime => write!(formatter, "missing time"), 34 | Self::ParseInt(error) => write!(formatter, "{error}"), 35 | Self::UnknownRecordType => write!(formatter, "unknown record type"), 36 | } 37 | } 38 | } 39 | 40 | impl error::Error for Error {} 41 | 42 | impl From for Error { 43 | fn from(error: io::Error) -> Self { 44 | Self::Io(error.to_string()) 45 | } 46 | } 47 | 48 | impl From for Error { 49 | fn from(error: ParseIntError) -> Self { 50 | Self::ParseInt(error) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /profiler/src/record.rs: -------------------------------------------------------------------------------- 1 | mod duration_record; 2 | mod procedure_operation; 3 | mod procedure_record; 4 | mod stack; 5 | mod stacked_record; 6 | 7 | use core::{fmt::Display, str::FromStr}; 8 | pub use duration_record::DurationRecord; 9 | pub use procedure_operation::ProcedureOperation; 10 | pub use procedure_record::ProcedureRecord; 11 | pub use stack::Stack; 12 | pub use stacked_record::StackedRecord; 13 | 14 | /// A profile record. 15 | pub trait Record: Display + FromStr {} 16 | -------------------------------------------------------------------------------- /profiler/src/record/stacked_record.rs: -------------------------------------------------------------------------------- 1 | use super::Record; 2 | use crate::Stack; 3 | 4 | /// A profile record with stack trace information. 5 | pub trait StackedRecord: Record { 6 | /// Returns a stack. 7 | fn stack(&self) -> &Stack; 8 | 9 | /// Returns a mutable stack. 10 | fn stack_mut(&mut self) -> &mut Stack; 11 | } 12 | -------------------------------------------------------------------------------- /profiler/src/reverse.rs: -------------------------------------------------------------------------------- 1 | use crate::{Error, StackedRecord}; 2 | 3 | /// Reverses stack frames in profile records. 4 | pub fn reverse_stacks( 5 | records: impl IntoIterator>, 6 | ) -> impl IntoIterator> { 7 | records.into_iter().map(|record| { 8 | record.map(|mut record| { 9 | record.stack_mut().reverse_frames(); 10 | record 11 | }) 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /r7rs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stak-r7rs" 3 | description = "Stak Scheme primitives for R7RS" 4 | categories.workspace = true 5 | edition.workspace = true 6 | keywords.workspace = true 7 | license-file.workspace = true 8 | readme.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [features] 13 | async = [ 14 | "stak-device/async", 15 | "stak-file/async", 16 | "stak-inexact/async", 17 | "stak-native/async", 18 | "stak-process-context/async", 19 | "stak-time/async", 20 | "stak-vm/async", 21 | "winter-maybe-async/async", 22 | ] 23 | 24 | [dependencies] 25 | stak-device = { version = "0.10.24", path = "../device" } 26 | stak-file = { version = "0.10.24", path = "../file" } 27 | stak-inexact = { version = "0.10.24", path = "../inexact" } 28 | stak-native = { version = "0.10.24", path = "../native" } 29 | stak-process-context = { version = "0.10.24", path = "../process_context" } 30 | stak-time = { version = "0.10.24", path = "../time" } 31 | stak-vm = { version = "0.10.24", path = "../vm" } 32 | winter-maybe-async = "0.12.0" 33 | -------------------------------------------------------------------------------- /r7rs/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Stak Scheme primitive sets for [R7RS](https://r7rs.org/). 2 | //! 3 | //! This crate provides [`PrimitiveSet`](stak_vm::PrimitiveSet)s that covers 4 | //! R7RS. 5 | 6 | #![no_std] 7 | 8 | mod small; 9 | 10 | pub use small::{Error as SmallError, SmallPrimitiveSet}; 11 | -------------------------------------------------------------------------------- /repl.scm: -------------------------------------------------------------------------------- 1 | (import 2 | (scheme base) 3 | (scheme char) 4 | (only (scheme cxr)) 5 | (scheme eval) 6 | (only (scheme file)) 7 | (only (scheme inexact)) 8 | (only (scheme lazy)) 9 | (scheme process-context) 10 | (scheme read) 11 | (scheme repl) 12 | (only (scheme time)) 13 | (scheme write)) 14 | 15 | (define (write-value value) 16 | (if (error-object? value) 17 | (begin 18 | (display "ERROR: ") 19 | (display (error-object-message value))) 20 | (write value))) 21 | 22 | (define (main) 23 | (display "> " (current-error-port)) 24 | 25 | (let loop () 26 | (let ((char (peek-char))) 27 | (cond 28 | ((char-whitespace? char) 29 | (read-char) 30 | (loop)) 31 | 32 | ((or (eof-object? char) (eqv? char (integer->char 4))) 33 | #f) 34 | 35 | (else 36 | (write-value 37 | (guard (error (#t error)) 38 | (eval (read) (interaction-environment)))) 39 | (newline) 40 | (main)))))) 41 | 42 | (let ((arguments (command-line))) 43 | (when (or 44 | (member "-h" arguments) 45 | (member "--help" arguments)) 46 | (write-string "The Stak Scheme REPL interpreter.\n\n") 47 | (write-string "Usage: stak-repl\n") 48 | (exit))) 49 | 50 | (main) 51 | -------------------------------------------------------------------------------- /root/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /root/build.rs: -------------------------------------------------------------------------------- 1 | //! A build script. 2 | 3 | use stak_build::{BuildError, build_r7rs}; 4 | 5 | fn main() -> Result<(), BuildError> { 6 | build_r7rs() 7 | } 8 | -------------------------------------------------------------------------------- /root/src/fibonacci.scm: -------------------------------------------------------------------------------- 1 | ../../examples/fibonacci.scm -------------------------------------------------------------------------------- /root/src/fight.scm: -------------------------------------------------------------------------------- 1 | ../../examples/embedded-script/src/fight.scm -------------------------------------------------------------------------------- /root/src/hello.scm: -------------------------------------------------------------------------------- 1 | ../../bench/src/hello/main.scm -------------------------------------------------------------------------------- /root/src/main.rs: -------------------------------------------------------------------------------- 1 | //! A Stak Scheme interpreter. 2 | //! 3 | //! # Usage 4 | //! 5 | //! ```sh 6 | //! stak foo.scm 7 | //! ``` 8 | 9 | stak_sac::main!("main.scm"); 10 | -------------------------------------------------------------------------------- /root/src/main.scm: -------------------------------------------------------------------------------- 1 | ../../run.scm -------------------------------------------------------------------------------- /run.scm: -------------------------------------------------------------------------------- 1 | ; We import all libraries here because the compiler strips any unimported libraries. 2 | (import 3 | (scheme base) 4 | (only (scheme case-lambda)) 5 | (scheme char) 6 | (only (scheme cxr)) 7 | (only (scheme eval) eval) 8 | (scheme file) 9 | (only (scheme inexact)) 10 | (only (scheme lazy)) 11 | (scheme process-context) 12 | (only (scheme r5rs)) 13 | (scheme read) 14 | (scheme repl) 15 | (only (scheme time)) 16 | (only (scheme write))) 17 | 18 | (define (run path) 19 | (define file (open-input-file path)) 20 | 21 | (do () 22 | ((eof-object? (peek-char file)) 23 | #f) 24 | (if (char-whitespace? (peek-char file)) 25 | (read-char file) 26 | (eval (read file) (interaction-environment))))) 27 | 28 | (define (main) 29 | (do ((arguments (cdr (command-line)) (cddr arguments))) 30 | ((not (equal? (car arguments) "-l")) 31 | (run (car arguments))) 32 | (run (cadr arguments)))) 33 | 34 | (let ((arguments (command-line))) 35 | (when (or 36 | (member "-h" arguments) 37 | (member "--help" arguments)) 38 | (write-string "The Stak Scheme interpreter.\n\n") 39 | (write-string "Usage: stak [-l LIBRARY_FILE] SCRIPT_FILE ARGUMENT...\n") 40 | (exit))) 41 | 42 | (main) 43 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | components = ["clippy", "rustfmt", "rust-analyzer", "rust-src"] 4 | -------------------------------------------------------------------------------- /sac/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stak-sac" 3 | description = "Stand-Alone Complex for Stak Scheme" 4 | categories.workspace = true 5 | edition.workspace = true 6 | keywords.workspace = true 7 | license-file.workspace = true 8 | readme.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [features] 13 | float = ["stak-vm/float"] 14 | float62 = ["stak-vm/float62"] 15 | libc = [ 16 | "dep:libc", 17 | "dep:stak-libc", 18 | "stak-device/libc", 19 | "stak-file/libc", 20 | "stak-process-context/libc", 21 | "stak-time/libc", 22 | ] 23 | std = [ 24 | "dep:clap", 25 | "dep:main_error", 26 | "stak-device/std", 27 | "stak-file/std", 28 | "stak-process-context/std", 29 | "stak-time/std", 30 | ] 31 | 32 | [dependencies] 33 | clap = { version = "4.5.39", features = ["derive"], optional = true } 34 | libc = { version = "0.2.172", default-features = false, optional = true } 35 | main_error = { version = "0.1.2", optional = true } 36 | stak-configuration = { version = "0.10.24", path = "../configuration" } 37 | stak-device = { version = "0.10.24", path = "../device" } 38 | stak-file = { version = "0.10.24", path = "../file" } 39 | stak-libc = { version = "0.10.24", path = "../libc", optional = true } 40 | stak-macro = { version = "0.10.24", path = "../macro" } 41 | stak-process-context = { version = "0.10.24", path = "../process_context" } 42 | stak-r7rs = { version = "0.10.24", path = "../r7rs" } 43 | stak-time = { version = "0.10.24", path = "../time" } 44 | stak-vm = { version = "0.10.24", path = "../vm" } 45 | 46 | [dev-dependencies] 47 | stak = { path = "../root" } 48 | 49 | [lints] 50 | workspace = true 51 | -------------------------------------------------------------------------------- /sac/src/main.scm: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /snapshots/aa-tree.md: -------------------------------------------------------------------------------- 1 | - constant primitive 0 2 | - set || 3 | - constant primitive 1 4 | - set || 5 | - constant primitive 9 6 | - set || 7 | - constant primitive 10 8 | - set || 9 | - constant primitive 11 10 | - set || 11 | - constant primitive 12 12 | - set || 13 | - constant primitive 13 14 | - set || 15 | - constant #f 16 | -------------------------------------------------------------------------------- /snapshots/bench/src/empty/main.md: -------------------------------------------------------------------------------- 1 | - constant primitive 0 2 | - set || 3 | - constant primitive 1 4 | - set || 5 | - constant primitive 9 6 | - set || 7 | - constant primitive 10 8 | - set || 9 | - constant primitive 11 10 | - set || 11 | - constant primitive 12 12 | - set || 13 | - constant primitive 13 14 | - set || 15 | - constant #f 16 | -------------------------------------------------------------------------------- /snapshots/macro/src/foo.md: -------------------------------------------------------------------------------- 1 | - constant primitive 0 2 | - set || 3 | - constant primitive 1 4 | - set || 5 | - constant primitive 9 6 | - set || 7 | - constant primitive 10 8 | - set || 9 | - constant primitive 11 10 | - set || 11 | - constant primitive 12 12 | - set || 13 | - constant primitive 13 14 | - set || 15 | - constant #f 16 | -------------------------------------------------------------------------------- /snapshots/macro/tests/empty.md: -------------------------------------------------------------------------------- 1 | - constant primitive 0 2 | - set || 3 | - constant primitive 1 4 | - set || 5 | - constant primitive 9 6 | - set || 7 | - constant primitive 10 8 | - set || 9 | - constant primitive 11 10 | - set || 11 | - constant primitive 12 12 | - set || 13 | - constant primitive 13 14 | - set || 15 | - constant #f 16 | -------------------------------------------------------------------------------- /snapshots/minifier_macro/src/foo.md: -------------------------------------------------------------------------------- 1 | - constant primitive 0 2 | - set || 3 | - constant primitive 1 4 | - set || 5 | - constant primitive 9 6 | - set || 7 | - constant primitive 10 8 | - set || 9 | - constant primitive 11 10 | - set || 11 | - constant primitive 12 12 | - set || 13 | - constant primitive 13 14 | - set || 15 | - get || 16 | - call 1 #f || 17 | - set 0 18 | - call 0 #f || 19 | -------------------------------------------------------------------------------- /snapshots/minifier_macro/tests/foo.md: -------------------------------------------------------------------------------- 1 | - constant primitive 0 2 | - set || 3 | - constant primitive 1 4 | - set || 5 | - constant primitive 9 6 | - set || 7 | - constant primitive 10 8 | - set || 9 | - constant primitive 11 10 | - set || 11 | - constant primitive 12 12 | - set || 13 | - constant primitive 13 14 | - set || 15 | - get || 16 | - call 1 #f || 17 | - set 0 18 | - call 0 #f || 19 | -------------------------------------------------------------------------------- /snapshots/sac/src/main.md: -------------------------------------------------------------------------------- 1 | - constant primitive 0 2 | - set || 3 | - constant primitive 1 4 | - set || 5 | - constant primitive 9 6 | - set || 7 | - constant primitive 10 8 | - set || 9 | - constant primitive 11 10 | - set || 11 | - constant primitive 12 12 | - set || 13 | - constant primitive 13 14 | - set || 15 | - constant #f 16 | -------------------------------------------------------------------------------- /time/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stak-time" 3 | description = "Time library for Stak Scheme" 4 | categories.workspace = true 5 | edition.workspace = true 6 | keywords.workspace = true 7 | license-file.workspace = true 8 | readme.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [features] 13 | async = ["stak-vm/async", "winter-maybe-async/async"] 14 | libc = ["dep:rustix"] 15 | std = [] 16 | 17 | [dependencies] 18 | rustix = { version = "1.0.7", default-features = false, features = [ 19 | "time", 20 | ], optional = true } 21 | stak-vm = { version = "0.10.24", path = "../vm" } 22 | winter-maybe-async = "0.12.0" 23 | 24 | [lints] 25 | workspace = true 26 | -------------------------------------------------------------------------------- /time/src/clock.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "libc")] 2 | mod libc; 3 | #[cfg(feature = "std")] 4 | mod os; 5 | mod void; 6 | 7 | use core::error::Error; 8 | #[cfg(feature = "libc")] 9 | pub use libc::LibcClock; 10 | #[cfg(feature = "std")] 11 | pub use os::OsClock; 12 | pub use void::VoidClock; 13 | 14 | /// A clock. 15 | pub trait Clock { 16 | /// An error. 17 | type Error: Error; 18 | 19 | /// Returns a current jiffy. 20 | fn current_jiffy(&self) -> Result; 21 | } 22 | -------------------------------------------------------------------------------- /time/src/clock/libc.rs: -------------------------------------------------------------------------------- 1 | use super::Clock; 2 | use core::convert::Infallible; 3 | use rustix::time::{ClockId, clock_gettime}; 4 | 5 | /// A clock based on libc. 6 | #[derive(Debug, Default)] 7 | pub struct LibcClock {} 8 | 9 | impl LibcClock { 10 | /// Creates a clock. 11 | pub const fn new() -> Self { 12 | Self {} 13 | } 14 | } 15 | 16 | impl Clock for LibcClock { 17 | type Error = Infallible; 18 | 19 | fn current_jiffy(&self) -> Result { 20 | let time = clock_gettime(ClockId::Realtime); 21 | 22 | // spell-checker: disable-next-line 23 | Ok((time.tv_sec * 1_000_000_000 + time.tv_nsec) as _) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /time/src/clock/os.rs: -------------------------------------------------------------------------------- 1 | use super::Clock; 2 | use std::time::{SystemTime, SystemTimeError, UNIX_EPOCH}; 3 | 4 | /// A clock provided by an operating system. 5 | #[derive(Debug, Default)] 6 | pub struct OsClock {} 7 | 8 | impl OsClock { 9 | /// Creates a time. 10 | pub const fn new() -> Self { 11 | Self {} 12 | } 13 | } 14 | 15 | impl Clock for OsClock { 16 | type Error = SystemTimeError; 17 | 18 | fn current_jiffy(&self) -> Result { 19 | Ok(SystemTime::now().duration_since(UNIX_EPOCH)?.as_nanos() as _) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /time/src/clock/void.rs: -------------------------------------------------------------------------------- 1 | use crate::Clock; 2 | use core::convert::Infallible; 3 | 4 | /// A void clock stopped a fixed time. 5 | #[derive(Debug, Default)] 6 | pub struct VoidClock {} 7 | 8 | impl VoidClock { 9 | /// Creates a clock. 10 | pub const fn new() -> Self { 11 | Self {} 12 | } 13 | } 14 | 15 | impl Clock for VoidClock { 16 | type Error = Infallible; 17 | 18 | fn current_jiffy(&self) -> Result { 19 | Ok(Default::default()) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /time/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Time for Stak Scheme. 2 | 3 | #![cfg_attr(all(doc, not(doctest)), feature(doc_auto_cfg))] 4 | #![no_std] 5 | 6 | #[cfg(feature = "std")] 7 | extern crate std; 8 | 9 | mod clock; 10 | mod primitive_set; 11 | 12 | pub use clock::*; 13 | pub use primitive_set::{Primitive, PrimitiveError, TimePrimitiveSet}; 14 | -------------------------------------------------------------------------------- /time/src/primitive_set.rs: -------------------------------------------------------------------------------- 1 | mod error; 2 | mod primitive; 3 | 4 | pub use self::{error::PrimitiveError, primitive::Primitive}; 5 | use crate::Clock; 6 | use stak_vm::{Error, Memory, Number, PrimitiveSet}; 7 | use winter_maybe_async::maybe_async; 8 | 9 | /// A primitive set for time. 10 | pub struct TimePrimitiveSet { 11 | clock: T, 12 | } 13 | 14 | impl TimePrimitiveSet { 15 | /// Creates a primitive set. 16 | pub const fn new(clock: T) -> Self { 17 | Self { clock } 18 | } 19 | } 20 | 21 | impl PrimitiveSet for TimePrimitiveSet { 22 | type Error = PrimitiveError; 23 | 24 | #[maybe_async] 25 | fn operate(&mut self, memory: &mut Memory<'_>, primitive: usize) -> Result<(), Self::Error> { 26 | match primitive { 27 | Primitive::CURRENT_JIFFY => { 28 | memory.push( 29 | Number::from_i64( 30 | self.clock 31 | .current_jiffy() 32 | .map_err(|_| PrimitiveError::CurrentJiffy)? 33 | as _, 34 | ) 35 | .into(), 36 | )?; 37 | } 38 | _ => return Err(Error::IllegalPrimitive.into()), 39 | } 40 | 41 | Ok(()) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /time/src/primitive_set/error.rs: -------------------------------------------------------------------------------- 1 | use core::{ 2 | error, 3 | fmt::{self, Debug, Display, Formatter}, 4 | }; 5 | use stak_vm::Exception; 6 | 7 | /// An error of primitives. 8 | #[derive(Clone, Debug, Eq, PartialEq)] 9 | pub enum PrimitiveError { 10 | /// A current jiffy error. 11 | CurrentJiffy, 12 | /// A virtual machine error. 13 | Vm(stak_vm::Error), 14 | } 15 | 16 | impl Exception for PrimitiveError { 17 | fn is_critical(&self) -> bool { 18 | match self { 19 | Self::CurrentJiffy => false, 20 | Self::Vm(error) => error.is_critical(), 21 | } 22 | } 23 | } 24 | 25 | impl error::Error for PrimitiveError {} 26 | 27 | impl Display for PrimitiveError { 28 | fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { 29 | match self { 30 | Self::CurrentJiffy => write!(formatter, "failed to get current jiffy"), 31 | Self::Vm(error) => write!(formatter, "{error}"), 32 | } 33 | } 34 | } 35 | 36 | impl From for PrimitiveError { 37 | fn from(error: stak_vm::Error) -> Self { 38 | Self::Vm(error) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /time/src/primitive_set/primitive.rs: -------------------------------------------------------------------------------- 1 | /// A primitive of time. 2 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 3 | pub enum Primitive { 4 | /// A current jiffy. 5 | CurrentJiffy, 6 | } 7 | 8 | impl Primitive { 9 | pub(super) const CURRENT_JIFFY: usize = Self::CurrentJiffy as _; 10 | } 11 | -------------------------------------------------------------------------------- /tools/bench.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -ex 4 | 5 | feature= 6 | 7 | while getopts f: option; do 8 | case $option in 9 | f) 10 | feature=$OPTARG 11 | ;; 12 | esac 13 | done 14 | 15 | shift $(expr $OPTIND - 1) 16 | 17 | filter=. 18 | 19 | if [ $# -gt 0 ]; then 20 | filter="$@" 21 | fi 22 | 23 | cd $(dirname $0)/.. 24 | 25 | . tools/utility.sh 26 | 27 | setup_bench $feature 28 | 29 | export PATH=$PWD/target/release:$PWD/cmd/minimal/target/release:$PATH 30 | 31 | output_directory=$PWD/tmp/bench/time 32 | mkdir -p $output_directory 33 | 34 | cd bench/src 35 | 36 | for file in $(ls */main.scm | sort | grep $filter); do 37 | base=${file%.scm} 38 | 39 | scripts="stak $file,mstak $file,stak-interpret $base.bc,mstak-interpret $base.bc,chibi-scheme $file,gosh $file,guile $file" 40 | reference= 41 | 42 | if [ $(dirname $base) != eval ]; then 43 | scripts="$scripts,gsi $file" 44 | fi 45 | 46 | if [ -r $base.py ]; then 47 | reference="python3 $base.py" 48 | scripts="micropython $base.py,ruby $base.rb,mruby $base.rb,lua $base.lua,$scripts" 49 | fi 50 | 51 | hyperfine \ 52 | --shell none \ 53 | --warmup 5 \ 54 | --export-markdown $output_directory/$(dirname $base).md \ 55 | --export-json $output_directory/$(dirname $base).json \ 56 | --input ../../compile.scm \ 57 | ${reference:+--reference "$reference"} \ 58 | -L script "$scripts" \ 59 | "{script}" 60 | done 61 | -------------------------------------------------------------------------------- /tools/bytecode_size_bench.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | . $(dirname $0)/utility.sh 6 | 7 | cd $(dirname $0)/.. 8 | 9 | cargo build --release 10 | 11 | export PATH=$PWD/target/release:$PATH 12 | 13 | for file in $(list_scheme_files); do 14 | cat prelude.scm $file | stak-compile >main.bc 15 | echo '>>>' "$file:\t$(wc -c $bytecode_file 9 | 10 | git add -f $bytecode_file 11 | } 12 | 13 | update_cargo_toml() { 14 | for main_file in $(git ls-files '*/src/main.rs'); do 15 | for profile in dev release; do 16 | cat <>$(dirname $main_file)/../Cargo.toml 30 | done 31 | 32 | git add . 33 | } 34 | 35 | cargo install cargo-workspaces 36 | 37 | update_bytecode 38 | update_cargo_toml 39 | 40 | git config user.email action@github.com 41 | git config user.name 'GitHub Action' 42 | git commit -m release 43 | 44 | for directory in . cmd/minimal; do 45 | ( 46 | cd $directory 47 | cargo workspaces publish -y --from-git "$@" 48 | ) 49 | done 50 | -------------------------------------------------------------------------------- /tools/ci/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | brew install lua@5.4 pkgconf uutils-coreutils uutils-findutils 6 | cargo install stak 7 | 8 | echo LD_LIBRARY_PATH=$(brew --prefix lua@5.4)/lib:$LD_LIBRARY_PATH >>$GITHUB_ENV 9 | -------------------------------------------------------------------------------- /tools/coverage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | rustup component add llvm-tools-preview 6 | 7 | cargo install cargo-llvm-cov 8 | cargo llvm-cov --workspace --profile release --lcov --output-path lcov.info 9 | -------------------------------------------------------------------------------- /tools/cucumber.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | while getopts t: option; do 6 | case $option in 7 | t) 8 | tags=$OPTARG 9 | ;; 10 | esac 11 | done 12 | 13 | shift $(expr $OPTIND - 1) 14 | 15 | if [ $# -ne 1 ]; then 16 | exit 1 17 | fi 18 | 19 | file=$1 20 | 21 | cd $(dirname $0)/.. 22 | 23 | directory=tmp/$(basename ${file%.*}) 24 | 25 | mkdir -p $directory 26 | cd $directory 27 | 28 | env -i \ 29 | PATH=$PATH \ 30 | $(env | grep STAK_) \ 31 | bundler exec cucumber \ 32 | --publish-quiet \ 33 | --strict-undefined \ 34 | --require ../../features \ 35 | ${tags:+--tags "$tags"} \ 36 | ../../$file 37 | -------------------------------------------------------------------------------- /tools/decode_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | . $(dirname $0)/utility.sh 6 | 7 | cd $(dirname $0)/.. 8 | 9 | cargo build --release 10 | 11 | export PATH=$PWD/target/release:$PATH 12 | 13 | for file in $(list_scheme_files); do 14 | echo '>>>' $file 15 | snapshot_file=snapshots/${file%.scm}.md 16 | mkdir -p $(dirname $snapshot_file) 17 | cat prelude.scm $file | stak-compile >main.bc 18 | stak-decode $snapshot_file 19 | done 20 | 21 | npx prettier --write snapshots 22 | git diff --exit-code 23 | -------------------------------------------------------------------------------- /tools/document.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -ex 4 | 5 | while getopts l option; do 6 | case $option in 7 | l) 8 | localhost=true 9 | ;; 10 | esac 11 | done 12 | 13 | shift $(expr $OPTIND - 1) 14 | 15 | [ $# -eq 0 ] 16 | 17 | cd $(dirname $0)/.. 18 | 19 | directory=doc/src/content/docs/examples 20 | 21 | rm -rf $directory/* 22 | go run github.com/raviqqe/gherkin2markdown@latest features $directory 23 | 24 | rm $(find $directory -name '*smoke*') 25 | 26 | for file in $(find $directory -name '*.md'); do 27 | new_file=$(dirname $file)/$(basename $file).tmp 28 | 29 | ( 30 | echo --- 31 | echo title: $(grep -o '^# \(.*\)$' $file | sed 's/# *//') 32 | echo --- 33 | cat $file | grep -v '^# ' 34 | ) >$new_file 35 | mv $new_file $file 36 | done 37 | 38 | cd doc 39 | 40 | npm ci 41 | npm run build -- ${localhost:+--site http://localhost:4321/stak} 42 | -------------------------------------------------------------------------------- /tools/integration_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -ex 4 | 5 | epoch() { 6 | date +%s 7 | } 8 | 9 | features=, 10 | interpreter=stak 11 | 12 | while getopts f:i:t: option; do 13 | case $option in 14 | f) 15 | features=$OPTARG 16 | ;; 17 | i) 18 | interpreter=$OPTARG 19 | ;; 20 | t) 21 | tags=$OPTARG 22 | ;; 23 | esac 24 | done 25 | 26 | shift $(expr $OPTIND - 1) 27 | 28 | cd $(dirname $0)/.. 29 | 30 | brew install chibi-scheme gauche guile parallel 31 | bundler install 32 | 33 | cargo build --profile release_test --features $features 34 | ( 35 | cd cmd/minimal 36 | cargo build --release 37 | ) 38 | 39 | export STAK_ROOT=$PWD 40 | export PATH=$PWD/tools/scheme/$interpreter:$PATH 41 | 42 | start=$(epoch) 43 | 44 | if [ $# -eq 0 ]; then 45 | git ls-files '**/*.feature' | xargs ls -S | parallel -q tools/cucumber.sh ${tags:+-t "$tags"} 46 | else 47 | bundler exec cucumber --publish-quiet --strict-undefined "$@" 48 | fi 49 | 50 | echo Duration: $(expr $(epoch) - $start)s 51 | -------------------------------------------------------------------------------- /tools/lint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | cargo clippy "$@" 6 | cargo clippy --all-features "$@" 7 | -------------------------------------------------------------------------------- /tools/memory_bench.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -ex 4 | 5 | profile() ( 6 | base=$1 7 | shift 1 8 | 9 | mkdir -p $(dirname $base) 10 | out_file=$base.out 11 | 12 | valgrind --tool=massif --massif-out-file=$out_file "$@" 13 | ms_print $out_file | tee ${out_file%.out}.txt 14 | ) 15 | 16 | heap_size() ( 17 | bytecode_file=$1 18 | size=8192 19 | 20 | for _ in $(seq 8); do 21 | if stak-interpret --heap-size $size $bytecode_file >/dev/null; then 22 | echo $size 23 | return 24 | fi 25 | 26 | size=$(expr $size '*' 2) 27 | done 28 | 29 | return 1 30 | ) 31 | 32 | [ $# -eq 0 -a $(uname) = Linux ] 33 | 34 | cd $(dirname $0)/.. 35 | 36 | . tools/utility.sh 37 | 38 | brew install valgrind 39 | 40 | setup_bench 41 | 42 | export PATH=$PWD/target/release:$PWD/cmd/minimal/target/release:$PATH 43 | 44 | output_directory=$PWD/tmp/bench/memory 45 | mkdir -p $output_directory 46 | 47 | cd bench/src 48 | 49 | for file in $(ls */main.scm | sort | grep -v eval); do 50 | base=${file%.scm} 51 | directory=$output_directory/$(dirname $base) 52 | 53 | profile $directory/stak-interpret stak-interpret --heap-size $(heap_size $base.bc) $base.bc 54 | 55 | for command in chibi-scheme gosh guile; do 56 | profile $directory/$command $command $file 57 | done 58 | 59 | if [ -r $base.py ]; then 60 | for command in python3 micropython; do 61 | profile $directory/$command $command $base.py 62 | done 63 | 64 | for command in ruby mruby; do 65 | profile $directory/$command $command $base.rb 66 | done 67 | 68 | profile $directory/lua lua $base.lua 69 | fi 70 | done 71 | -------------------------------------------------------------------------------- /tools/profile_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -ex 4 | 5 | cd $(dirname $0)/.. 6 | 7 | export PATH=$PWD/target/release:$PATH 8 | cargo build --release 9 | 10 | mkdir -p tmp 11 | cat <tmp/main.scm 12 | (import (scheme base) (scheme write)) 13 | 14 | (define (foo x) 15 | (display x)) 16 | 17 | (foo 42) 18 | EOF 19 | 20 | cat prelude.scm tmp/main.scm | stak-compile >tmp/main.bc 21 | stak-profile run --profile tmp/profile.txt tmp/main.bc 22 | file --mime-type tmp/profile.txt | grep text/plain 23 | stak-profile analyze duration tmp/flamegraph.txt 27 | -------------------------------------------------------------------------------- /tools/r7rs_compatible_compiler_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | . $(dirname $0)/utility.sh 6 | 7 | cd $(dirname $0)/.. 8 | 9 | brew install chibi-scheme gauche guile 10 | interpreters='chibi-scheme gosh guile' 11 | 12 | cargo build --release 13 | export PATH=$PWD/target/release:$PATH 14 | 15 | for file in $(list_scheme_files); do 16 | echo '>>>' $file 17 | 18 | for interpreter in $interpreters stak; do 19 | cat prelude.scm $file | log $interpreter compile.scm >$interpreter.bc 20 | done 21 | 22 | for interpreter in $interpreters; do 23 | log diff stak.bc $interpreter.bc 24 | done 25 | done 26 | -------------------------------------------------------------------------------- /tools/rust_loc_bench.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | [ -n "$CI" ] 6 | 7 | . $(dirname $0)/utility.sh 8 | 9 | cargo install ast-grep tokei 10 | 11 | for pattern in 'mod tests { $$$ }' '#[cfg(test)]'; do 12 | sg -Ul rs -p "$pattern" -r '' 13 | done 14 | 15 | cargo fmt --all 16 | 17 | for cargo_file in $(find . -name Cargo.toml | sort); do 18 | log tokei $(dirname $cargo_file) 19 | done 20 | -------------------------------------------------------------------------------- /tools/scheme/chibi/stak: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | while getopts l: option; do 6 | case $option in 7 | l) 8 | cp $OPTARG ${OPTARG%.scm}.sld 9 | ;; 10 | esac 11 | done 12 | 13 | shift $(expr $OPTIND - 1) 14 | 15 | chibi-scheme "$@" 16 | -------------------------------------------------------------------------------- /tools/scheme/gauche/stak: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | while getopts l: option; do 6 | case $option in 7 | l) 8 | libraries="$libraries $OPTARG" 9 | ;; 10 | esac 11 | done 12 | 13 | shift $(expr $OPTIND - 1) 14 | 15 | main_file=$1 16 | shift 1 17 | 18 | cat $libraries $main_file >gosh.scm 19 | 20 | gosh gosh.scm "$@" 21 | -------------------------------------------------------------------------------- /tools/scheme/guile/stak: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | while getopts l: option; do 6 | case $option in 7 | l) 8 | cp $OPTARG ${OPTARG%.scm}.sld 9 | ;; 10 | esac 11 | done 12 | 13 | shift $(expr $OPTIND - 1) 14 | 15 | guile -L . "$@" 16 | -------------------------------------------------------------------------------- /tools/scheme/mstak-tools/stak: -------------------------------------------------------------------------------- 1 | ../stak-tools/stak -------------------------------------------------------------------------------- /tools/scheme/mstak-tools/stak-compile: -------------------------------------------------------------------------------- 1 | ../stak-tools/stak-compile -------------------------------------------------------------------------------- /tools/scheme/mstak-tools/stak-interpret: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | $(dirname $0)/../../../cmd/minimal/target/release/mstak-interpret "$@" 6 | -------------------------------------------------------------------------------- /tools/scheme/mstak/stak: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | $(dirname $0)/../../../cmd/minimal/target/release/mstak "$@" 6 | -------------------------------------------------------------------------------- /tools/scheme/stak-tools/stak: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | directory=$(dirname $0)/../../.. 6 | 7 | while getopts l: option; do 8 | case $option in 9 | l) 10 | libraries="$libraries $OPTARG" 11 | ;; 12 | esac 13 | done 14 | 15 | shift $(expr $OPTIND - 1) 16 | 17 | script=$1 18 | shift 1 19 | 20 | cat $directory/prelude.scm $libraries $script | stak-compile >main.bc 21 | stak-interpret main.bc "$@" 22 | -------------------------------------------------------------------------------- /tools/scheme/stak-tools/stak-compile: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | directory=$(dirname $0)/../../.. 6 | 7 | if [ -n "$STAK_HOST_INTERPRETER" ]; then 8 | $STAK_HOST_INTERPRETER $directory/compile.scm 9 | else 10 | $directory/target/release_test/stak-compile 11 | fi 12 | -------------------------------------------------------------------------------- /tools/scheme/stak-tools/stak-interpret: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | $(dirname $0)/../../../target/release_test/stak-interpret "$@" 6 | -------------------------------------------------------------------------------- /tools/scheme/stak/stak: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | $(dirname $0)/../../../target/release_test/stak "$@" 6 | -------------------------------------------------------------------------------- /tools/self_host_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | stage_count=3 6 | 7 | . $(dirname $0)/utility.sh 8 | 9 | profile=release_test 10 | target=target/$profile 11 | 12 | run_stage() { 13 | if [ $1 -eq 0 ]; then 14 | log stak compile.scm 15 | else 16 | log $target/stak-interpret stage$1.bc 17 | fi 18 | } 19 | 20 | artifact_path() { 21 | echo tmp/stage$1.$2 22 | } 23 | 24 | cd $(dirname $0)/.. 25 | 26 | mkdir -p tmp 27 | cargo build --profile $profile 28 | 29 | for stage in $(seq 0 $(expr $stage_count - 1)); do 30 | cat prelude.scm compile.scm | run_stage $stage >stage$(expr $stage + 1).bc 31 | done 32 | 33 | for file in $(list_scheme_files); do 34 | echo '>>>' $file 35 | 36 | for stage in $(seq 0 $stage_count); do 37 | bytecode_file=$(artifact_path $stage bc) 38 | 39 | cat prelude.scm $file | run_stage $stage >$bytecode_file 40 | done 41 | 42 | for stage in $(seq 0 $(expr $stage_count - 1)); do 43 | for extension in bc; do 44 | log diff $(artifact_path $stage $extension) $(artifact_path $(expr $stage + 1) $extension) 45 | done 46 | done 47 | done 48 | -------------------------------------------------------------------------------- /tools/utility.sh: -------------------------------------------------------------------------------- 1 | log() ( 2 | echo '>>>' "$@" >&2 3 | "$@" 4 | ) 5 | 6 | list_scheme_files() ( 7 | [ $# -eq 0 ] 8 | 9 | for file in $(git ls-files '*.scm' | grep -v prelude); do 10 | if [ -L $file ]; then 11 | continue 12 | fi 13 | 14 | echo $file 15 | done 16 | ) 17 | 18 | build_binary() ( 19 | cd $1 20 | shift 1 21 | 22 | cargo build --release 23 | cargo build --release "$@" 24 | ) 25 | 26 | setup_bench() ( 27 | [ $# -le 1 ] 28 | 29 | feature=$1 30 | 31 | brew install chibi-scheme gambit-scheme gauche guile lua micropython mruby ruby 32 | cargo install hyperfine 33 | 34 | case $feature in 35 | i63) 36 | build_options='--no-default-features --features std' 37 | ;; 38 | f62) 39 | build_options='--no-default-features --features std,float62' 40 | ;; 41 | esac 42 | 43 | build_binary . -p stak -p stak-interpret $build_options 44 | build_binary cmd/minimal -p mstak -p mstak-interpret 45 | 46 | export PATH=$PWD/target/release:$PATH 47 | 48 | for file in bench/src/*/main.scm; do 49 | cat prelude.scm $file | stak-compile >${file%.scm}.bc 50 | done 51 | ) 52 | -------------------------------------------------------------------------------- /tools/version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | type=${1:-patch} 6 | 7 | if [ $# -gt 0 ]; then 8 | shift 1 9 | fi 10 | 11 | options="$@" 12 | 13 | cd $(dirname $0)/.. 14 | 15 | for directory in . cmd/minimal; do 16 | ( 17 | cd $directory 18 | cargo release version $type --execute --no-confirm --allow-branch '*' $options 19 | ) 20 | done 21 | -------------------------------------------------------------------------------- /util/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stak-util" 3 | description = "Stak Scheme utilities" 4 | categories.workspace = true 5 | edition.workspace = true 6 | keywords.workspace = true 7 | license-file.workspace = true 8 | readme.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [dependencies] 13 | cfg-elif = "0.6.3" 14 | noop-executor = "0.1.0" 15 | 16 | [lints] 17 | workspace = true 18 | -------------------------------------------------------------------------------- /util/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Utilities around `libc`. 2 | 3 | #![no_std] 4 | 5 | #[doc(hidden)] 6 | pub mod __private { 7 | pub use cfg_elif; 8 | pub use noop_executor; 9 | } 10 | 11 | /// Blocks on a future if an `async` feature in on, or returns a given value as 12 | /// it is otherwise. 13 | #[macro_export] 14 | macro_rules! block_on { 15 | ($value:expr) => { 16 | $crate::__private::cfg_elif::expr::feature!(if ("async") { 17 | $crate::__private::noop_executor::block_on($value) 18 | } else { 19 | $value 20 | }) 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /vm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stak-vm" 3 | description = "Virtual machines for Stak Scheme" 4 | categories.workspace = true 5 | edition.workspace = true 6 | keywords.workspace = true 7 | license-file.workspace = true 8 | readme.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [features] 13 | async = [ 14 | "stak-device/async", 15 | "stak-file/async", 16 | "stak-macro/async", 17 | "stak-process-context/async", 18 | "stak-r7rs/async", 19 | "stak-time/async", 20 | "winter-maybe-async/async", 21 | ] 22 | float = ["dep:nonbox"] 23 | float62 = ["float"] 24 | gc_always = [] 25 | profile = [] 26 | trace_instruction = [] 27 | trace_memory = [] 28 | 29 | [dependencies] 30 | cfg-elif = "0.6.3" 31 | nonbox = { version = "0.5.10", optional = true } 32 | stak-util = { version = "0.10.24", path = "../util" } 33 | winter-maybe-async = "0.12.0" 34 | 35 | [dev-dependencies] 36 | insta = "1.43.1" 37 | stak-device = { path = "../device" } 38 | stak-file = { path = "../file" } 39 | stak-macro = { path = "../macro" } 40 | stak-process-context = { path = "../process_context" } 41 | stak-r7rs = { path = "../r7rs" } 42 | stak-time = { path = "../time" } 43 | 44 | [lints] 45 | workspace = true 46 | -------------------------------------------------------------------------------- /vm/src/code.rs: -------------------------------------------------------------------------------- 1 | pub const INTEGER_BASE: u128 = 1 << 7; 2 | pub const NUMBER_BASE: u128 = 1 << 4; 3 | pub const TAG_BASE: u128 = 1 << 4; 4 | pub const SHARE_BASE: u128 = (1 << 5) - 1; 5 | -------------------------------------------------------------------------------- /vm/src/exception.rs: -------------------------------------------------------------------------------- 1 | use crate::Error; 2 | use core::error; 3 | 4 | /// An exception. 5 | pub trait Exception: From + error::Error { 6 | /// Returns `true` if an error is critical and not recoverable. 7 | fn is_critical(&self) -> bool; 8 | } 9 | -------------------------------------------------------------------------------- /vm/src/instruction.rs: -------------------------------------------------------------------------------- 1 | use crate::Tag; 2 | 3 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 4 | pub enum Instruction { 5 | Constant, 6 | Get, 7 | Set, 8 | If, 9 | Call, 10 | } 11 | 12 | impl Instruction { 13 | pub const CONSTANT: Tag = Self::Constant as _; 14 | pub const GET: Tag = Self::Get as _; 15 | pub const SET: Tag = Self::Set as _; 16 | pub const IF: Tag = Self::If as _; 17 | pub const CALL: Tag = Self::Call as _; 18 | } 19 | -------------------------------------------------------------------------------- /vm/src/primitive_set.rs: -------------------------------------------------------------------------------- 1 | use crate::{Exception, memory::Memory}; 2 | use winter_maybe_async::maybe_async; 3 | 4 | /// A primitive set. 5 | /// 6 | /// [`PrimitiveSet`](PrimitiveSet)s provide primitive functionalities, such as 7 | /// arithmetic and I/O, to [`Vm`](super::Vm)s. Each primitive has its own 8 | /// identifier. 9 | pub trait PrimitiveSet: Sized { 10 | /// An error. 11 | type Error: Exception; 12 | 13 | /// Runs a primitive on a virtual machine. 14 | #[maybe_async] 15 | fn operate(&mut self, memory: &mut Memory, primitive: usize) -> Result<(), Self::Error>; 16 | } 17 | -------------------------------------------------------------------------------- /vm/src/profiler.rs: -------------------------------------------------------------------------------- 1 | use crate::{Cons, Memory}; 2 | 3 | /// A profiler. 4 | pub trait Profiler { 5 | /// Profiles a call. 6 | fn profile_call(&mut self, memory: &Memory, call_code: Cons, r#return: bool); 7 | 8 | /// Profiles a return. 9 | fn profile_return(&mut self, memory: &Memory); 10 | 11 | /// Profiles a call. 12 | fn profile_event(&mut self, name: &str); 13 | } 14 | -------------------------------------------------------------------------------- /vm/src/snapshots/stak_vm__memory__tests__create.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: vm/src/memory.rs 3 | expression: memory 4 | snapshot_kind: text 5 | --- 6 | code: ! 7 | stack: ! 8 | 00: n0 n0 9 | 02: c0 c0 10 | -------------------------------------------------------------------------------- /vm/src/snapshots/stak_vm__memory__tests__create_list-2.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: vm/src/memory.rs 3 | expression: memory 4 | snapshot_kind: text 5 | --- 6 | code: ! 7 | stack: ! 8 | 00: n0 n0 9 | 02: c0 c0 10 | 04: n1 c0 11 | 06: n2 c4 12 | -------------------------------------------------------------------------------- /vm/src/snapshots/stak_vm__memory__tests__create_list-3.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: vm/src/memory.rs 3 | expression: memory 4 | snapshot_kind: text 5 | --- 6 | code: ! 7 | stack: ! 8 | 00: n0 n0 9 | 02: c0 c0 10 | 04: n1 c0 11 | 06: n2 c4 12 | 08: n3 c6 13 | -------------------------------------------------------------------------------- /vm/src/snapshots/stak_vm__memory__tests__create_list.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: vm/src/memory.rs 3 | expression: memory 4 | snapshot_kind: text 5 | --- 6 | code: ! 7 | stack: ! 8 | 00: n0 n0 9 | 02: c0 c0 10 | 04: n1 c0 11 | -------------------------------------------------------------------------------- /vm/src/snapshots/stak_vm__memory__tests__garbage_collection__collect_cons.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: vm/src/memory.rs 3 | expression: memory 4 | snapshot_kind: text 5 | --- 6 | code: ! 7 | stack: ! 8 | 100: c102 c102 9 | 102: n0 n0 10 | -------------------------------------------------------------------------------- /vm/src/snapshots/stak_vm__memory__tests__garbage_collection__collect_cycle.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: vm/src/memory.rs 3 | expression: memory 4 | snapshot_kind: text 5 | --- 6 | code: ! 7 | stack: ! 8 | 100: c102 c102 9 | 102: n0 n0 10 | -------------------------------------------------------------------------------- /vm/src/snapshots/stak_vm__memory__tests__garbage_collection__collect_deep_stack.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: vm/src/memory.rs 3 | expression: memory 4 | snapshot_kind: text 5 | --- 6 | code: ! 7 | stack: c100 8 | 100: n2 c104 <- stack 9 | 102: c106 c106 10 | 104: n1 c106 11 | 106: n0 n0 12 | -------------------------------------------------------------------------------- /vm/src/snapshots/stak_vm__memory__tests__garbage_collection__collect_stack.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: vm/src/memory.rs 3 | expression: memory 4 | snapshot_kind: text 5 | --- 6 | code: ! 7 | stack: c100 8 | 100: n42 c104 <- stack 9 | 102: c104 c104 10 | 104: n0 n0 11 | -------------------------------------------------------------------------------- /vm/src/stack_slot.rs: -------------------------------------------------------------------------------- 1 | /// A tag of a stack slot. 2 | pub enum StackSlot { 3 | /// A value. 4 | Value, 5 | /// A procedure frame. 6 | Frame, 7 | } 8 | -------------------------------------------------------------------------------- /vm/src/type.rs: -------------------------------------------------------------------------------- 1 | use crate::Tag; 2 | 3 | /// A type in Scheme. 4 | #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] 5 | pub enum Type { 6 | /// A pair. 7 | #[default] 8 | Pair, 9 | /// A null. 10 | Null, 11 | /// A boolean. 12 | Boolean, 13 | /// A procedure. 14 | Procedure, 15 | /// A symbol. 16 | Symbol, 17 | /// A string. 18 | String, 19 | /// A character. 20 | Character, 21 | /// A vector. 22 | Vector, 23 | /// A byte vector. 24 | ByteVector, 25 | /// A record. 26 | Record, 27 | /// A foreign object 28 | Foreign = Tag::MAX as _, 29 | } 30 | -------------------------------------------------------------------------------- /vm/src/value_inner.rs: -------------------------------------------------------------------------------- 1 | use cfg_elif::item; 2 | 3 | item::feature!(if ("float62") { 4 | mod float62; 5 | } else if ("float") { 6 | mod float64; 7 | } else { 8 | mod integer63; 9 | }); 10 | 11 | item::feature!(if ("float62") { 12 | pub(crate) use float62::*; 13 | } else if ("float") { 14 | pub(crate) use float64::*; 15 | } else { 16 | pub(crate) use integer63::*; 17 | }); 18 | 19 | #[cfg(test)] 20 | mod tests { 21 | use super::*; 22 | 23 | const MAXIMUM_CONS: u64 = 424242; 24 | const MINIMUM_INTEGER: i64 = -424242; 25 | const MAXIMUM_INTEGER: i64 = 424242; 26 | 27 | #[test] 28 | fn cons() { 29 | for cons in 0..MAXIMUM_CONS { 30 | assert_eq!(unbox_cons(box_cons(cons)), cons); 31 | } 32 | } 33 | 34 | #[test] 35 | fn check_cons() { 36 | for cons in 0..MAXIMUM_CONS { 37 | assert!(is_cons(box_cons(cons))); 38 | } 39 | } 40 | 41 | #[test] 42 | fn integer() { 43 | for integer in MINIMUM_INTEGER..MAXIMUM_INTEGER { 44 | assert_eq!(to_i64(from_i64(integer)), integer); 45 | } 46 | } 47 | 48 | #[test] 49 | fn float() { 50 | for integer in MINIMUM_INTEGER..MAXIMUM_INTEGER { 51 | let float = integer as f64 / 100.0; 52 | 53 | assert_eq!(to_i64(from_f64(float)), float as i64); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /vm/src/value_inner/float62.rs: -------------------------------------------------------------------------------- 1 | use nonbox::f62::{Float62, box_payload, is_payload, unbox_payload_unchecked}; 2 | 3 | pub type NumberInner = Float62; 4 | 5 | #[inline] 6 | pub const fn box_cons(cons: u64) -> u64 { 7 | box_payload(cons) 8 | } 9 | 10 | #[inline] 11 | pub const fn unbox_cons(cons: u64) -> u64 { 12 | unbox_payload_unchecked(cons) 13 | } 14 | 15 | #[inline] 16 | pub const fn is_cons(value: u64) -> bool { 17 | is_payload(value) 18 | } 19 | 20 | #[inline] 21 | pub const fn from_number(number: NumberInner) -> NumberInner { 22 | number 23 | } 24 | 25 | #[inline] 26 | pub const fn to_number(number: NumberInner) -> NumberInner { 27 | number 28 | } 29 | 30 | #[inline] 31 | pub const fn from_i64(number: i64) -> NumberInner { 32 | Float62::from_integer(number) 33 | } 34 | 35 | #[inline] 36 | pub const fn to_i64(number: NumberInner) -> i64 { 37 | let Some(integer) = number.to_integer() else { 38 | // Unlikely 39 | return number.to_float_unchecked() as _; 40 | }; 41 | 42 | integer 43 | } 44 | 45 | #[inline] 46 | pub const fn from_f64(number: f64) -> NumberInner { 47 | Float62::from_float(number) 48 | } 49 | 50 | #[inline] 51 | pub const fn to_f64(number: NumberInner) -> f64 { 52 | let Some(integer) = number.to_integer() else { 53 | // Unlikely 54 | return number.to_float_unchecked(); 55 | }; 56 | 57 | integer as _ 58 | } 59 | 60 | #[inline] 61 | pub const fn from_raw(raw: u64) -> NumberInner { 62 | NumberInner::from_bits(raw) 63 | } 64 | 65 | #[inline] 66 | pub const fn to_raw(number: NumberInner) -> u64 { 67 | number.to_bits() 68 | } 69 | -------------------------------------------------------------------------------- /vm/src/value_inner/float64.rs: -------------------------------------------------------------------------------- 1 | use nonbox::f64::{box_unsigned, is_boxed, unbox_unsigned_unchecked}; 2 | 3 | pub type NumberInner = f64; 4 | 5 | #[inline] 6 | pub const fn box_cons(cons: u64) -> u64 { 7 | box_unsigned(cons) 8 | } 9 | 10 | #[inline] 11 | pub const fn unbox_cons(cons: u64) -> u64 { 12 | unbox_unsigned_unchecked(cons) 13 | } 14 | 15 | #[inline] 16 | pub const fn is_cons(value: u64) -> bool { 17 | is_boxed(value) 18 | } 19 | 20 | #[inline] 21 | pub const fn from_number(number: NumberInner) -> NumberInner { 22 | number 23 | } 24 | 25 | #[inline] 26 | pub const fn to_number(number: NumberInner) -> NumberInner { 27 | number 28 | } 29 | 30 | #[inline] 31 | pub const fn from_i64(number: i64) -> NumberInner { 32 | number as _ 33 | } 34 | 35 | #[inline] 36 | pub const fn to_i64(number: NumberInner) -> i64 { 37 | number as _ 38 | } 39 | 40 | #[inline] 41 | pub const fn from_f64(number: f64) -> NumberInner { 42 | number 43 | } 44 | 45 | #[inline] 46 | pub const fn to_f64(number: NumberInner) -> f64 { 47 | number 48 | } 49 | 50 | #[inline] 51 | pub const fn from_raw(raw: u64) -> NumberInner { 52 | NumberInner::from_bits(raw) 53 | } 54 | 55 | #[inline] 56 | pub const fn to_raw(number: NumberInner) -> u64 { 57 | number.to_bits() 58 | } 59 | -------------------------------------------------------------------------------- /vm/src/value_inner/integer63.rs: -------------------------------------------------------------------------------- 1 | pub type NumberInner = i64; 2 | 3 | #[inline] 4 | pub const fn box_cons(cons: u64) -> u64 { 5 | cons << 1 6 | } 7 | 8 | #[inline] 9 | pub const fn unbox_cons(cons: u64) -> u64 { 10 | cons >> 1 11 | } 12 | 13 | #[inline] 14 | pub const fn is_cons(value: u64) -> bool { 15 | value & 1 == 0 16 | } 17 | 18 | #[inline] 19 | pub const fn from_number(number: NumberInner) -> NumberInner { 20 | (number << 1) | 1 21 | } 22 | 23 | #[inline] 24 | pub const fn to_number(number: NumberInner) -> NumberInner { 25 | number >> 1 26 | } 27 | 28 | #[inline] 29 | pub const fn from_i64(number: i64) -> NumberInner { 30 | from_number(number) 31 | } 32 | 33 | #[inline] 34 | pub const fn to_i64(number: NumberInner) -> i64 { 35 | to_number(number) 36 | } 37 | 38 | #[inline] 39 | pub const fn from_f64(number: f64) -> NumberInner { 40 | from_number(number as _) 41 | } 42 | 43 | #[inline] 44 | pub const fn to_f64(number: NumberInner) -> f64 { 45 | to_number(number) as _ 46 | } 47 | 48 | #[inline] 49 | pub const fn from_raw(raw: u64) -> NumberInner { 50 | raw as _ 51 | } 52 | 53 | #[inline] 54 | pub const fn to_raw(number: NumberInner) -> u64 { 55 | number as _ 56 | } 57 | -------------------------------------------------------------------------------- /wasm/.gitignore: -------------------------------------------------------------------------------- 1 | LICENSE 2 | pkg/ 3 | -------------------------------------------------------------------------------- /wasm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stak-wasm" 3 | description = "Stak Scheme in WebAssembly" 4 | categories.workspace = true 5 | edition.workspace = true 6 | keywords.workspace = true 7 | license-file.workspace = true 8 | readme.workspace = true 9 | repository.workspace = true 10 | version.workspace = true 11 | 12 | [package.metadata.cargo-machete] 13 | ignored = ["wasm-bindgen-futures"] 14 | 15 | [features] 16 | async = [ 17 | "stak-compiler/async", 18 | "stak-device/async", 19 | "stak-file/async", 20 | "stak-macro/async", 21 | "stak-process-context/async", 22 | "stak-r7rs/async", 23 | "stak-time/async", 24 | "stak-vm/async", 25 | ] 26 | 27 | [lib] 28 | crate-type = ["cdylib", "rlib"] 29 | 30 | [dependencies] 31 | cfg-elif = "0.6.3" 32 | stak-compiler = { version = "0.10.24", path = "../compiler" } 33 | stak-configuration = { version = "0.10.24", path = "../configuration" } 34 | stak-device = { version = "0.10.24", path = "../device", features = ["std"] } 35 | stak-file = { version = "0.10.24", path = "../file" } 36 | stak-macro = { version = "0.10.24", path = "../macro" } 37 | stak-module = { version = "0.10.24", path = "../module" } 38 | stak-process-context = { version = "0.10.24", path = "../process_context" } 39 | stak-r7rs = { version = "0.10.24", path = "../r7rs" } 40 | stak-time = { version = "0.10.24", path = "../time" } 41 | stak-vm = { version = "0.10.24", path = "../vm", features = ["float"] } 42 | wasm-bindgen = "0.2.97" 43 | wasm-bindgen-futures = "0.4.50" 44 | winter-maybe-async = "0.12.0" 45 | 46 | [dev-dependencies] 47 | stak-configuration = { path = "../configuration" } 48 | stak-macro = { path = "../macro" } 49 | wasm-bindgen-test = "0.3.47" 50 | 51 | [build-dependencies] 52 | stak-build = { version = "0.10.24", path = "../build" } 53 | 54 | [lints] 55 | workspace = true 56 | -------------------------------------------------------------------------------- /wasm/build.rs: -------------------------------------------------------------------------------- 1 | //! A build script. 2 | 3 | use stak_build::{BuildError, build_r7rs}; 4 | 5 | fn main() -> Result<(), BuildError> { 6 | build_r7rs() 7 | } 8 | -------------------------------------------------------------------------------- /wasm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@raviqqe/stak", 3 | "publishConfig": { 4 | "provenance": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /wasm/src/repl.scm: -------------------------------------------------------------------------------- 1 | ../../repl.scm -------------------------------------------------------------------------------- /wasm/src/run.scm: -------------------------------------------------------------------------------- 1 | ../../run.scm -------------------------------------------------------------------------------- /wasm/tests/web.rs: -------------------------------------------------------------------------------- 1 | #![expect(missing_docs)] 2 | #![cfg(target_arch = "wasm32")] 3 | 4 | use stak_configuration::DEFAULT_HEAP_SIZE; 5 | use stak_macro::compile_r7rs; 6 | use stak_wasm::{compile, interpret, run}; 7 | use wasm_bindgen_test::wasm_bindgen_test; 8 | 9 | const SOURCE: &str = r#" 10 | (import (scheme write)) 11 | 12 | (display "Hello, World!") 13 | "#; 14 | 15 | #[wasm_bindgen_test] 16 | fn compile_source() { 17 | compile(SOURCE).unwrap(); 18 | } 19 | 20 | #[wasm_bindgen_test] 21 | fn run_bytecodes() { 22 | const BYTECODE: &[u8] = compile_r7rs!( 23 | r#" 24 | (import (scheme write)) 25 | 26 | (display "Hello, world!") 27 | "# 28 | ); 29 | 30 | assert_eq!( 31 | interpret(BYTECODE, &[], DEFAULT_HEAP_SIZE).unwrap(), 32 | b"Hello, world!" 33 | ); 34 | } 35 | 36 | #[wasm_bindgen_test] 37 | fn run_script() { 38 | const SCRIPT: &str = r#" 39 | (import (scheme write)) 40 | 41 | (display "Hello, world!") 42 | "#; 43 | 44 | assert_eq!( 45 | run(SCRIPT, &[], DEFAULT_HEAP_SIZE).unwrap(), 46 | b"Hello, world!" 47 | ); 48 | } 49 | 50 | #[wasm_bindgen_test] 51 | fn emit_error() { 52 | const SCRIPT: &str = r#" 53 | (import (scheme base)) 54 | 55 | (error "Oh, no!") 56 | "#; 57 | 58 | assert!(format!("{:?}", run(SCRIPT, &[], DEFAULT_HEAP_SIZE).unwrap_err()).contains("Oh, no!")); 59 | } 60 | --------------------------------------------------------------------------------