├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── Changelog.org ├── HACKING.md ├── LICENSE ├── README.md ├── benches ├── lisp_test.rs ├── sieve.lisp └── sieve.rs ├── examples ├── command-pattern-multi-threaded │ ├── Cargo.toml │ ├── async-example.lisp │ └── src │ │ └── main.rs ├── exported-fns │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── get-set.rs ├── hello-world.rs ├── loading-code.rs ├── resources │ └── friends.lisp └── to-be-loaded.lisp ├── justfile ├── lisp ├── bench-tools.lisp ├── boot-stage0.lisp ├── core.lisp ├── hello-world.lisp ├── html.lisp ├── self.lisp └── test.lisp ├── proc-macros ├── Cargo.toml └── src │ └── lib.rs ├── src ├── ast.rs ├── bin │ ├── minify.rs │ ├── readline-repl.rs │ ├── run-lisp-tests.rs │ └── scratch.rs ├── builtins.rs ├── chasm.rs ├── comp.rs ├── deserialize.rs ├── error.rs ├── events.rs ├── fmt.rs ├── lib.rs ├── limits.rs ├── lisp_test.rs ├── logging.rs ├── main.rs ├── math.rs ├── module.rs ├── nkgc.rs ├── nuke.rs ├── opt.rs ├── plug.rs ├── pmem.rs ├── r8vm.rs ├── records.rs ├── repl.rs ├── run_test.rs ├── scratch.rs ├── stack_gymnastics.rs ├── stak.rs ├── string_parse.rs ├── stylize.rs ├── subrs.rs ├── swym.rs ├── tok.rs ├── tokit.rs └── utils.rs ├── tests ├── html.lisp ├── test-builtins.lisp └── test-stdlib.lisp └── tools ├── gdbinit ├── measure.sh ├── sloc.sh ├── stats.awk └── wasmtime.sh /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Setup 20 | run: | 21 | sudo dpkg --add-architecture i386 22 | sudo apt-get update 23 | sudo snap install --edge just --classic 24 | sudo apt install valgrind wine gcc-mingw-w64 binutils-mingw-w64 gcc-i686-linux-gnu binutils-i686-linux-gnu 25 | rustup target add wasm32-wasip1 26 | rustup target add x86_64-pc-windows-gnu 27 | rustup target add i686-unknown-linux-gnu 28 | cargo install cargo-valgrind 29 | sudo apt-get install wine32 30 | - uses: actions/checkout@v3 31 | - name: Install Miri 32 | run: | 33 | rustup toolchain install nightly --component miri 34 | cargo +nightly miri setup 35 | - name: Build 36 | run: cargo build --verbose 37 | - name: Run tests on Linux 38 | run: cargo test --verbose 39 | # - name: Run tests on 32-bit Linux 40 | # run: cargo test --verbose --target i686-unknown-linux-gnu 41 | - name: Run tests on 32-bit WebAssembly 42 | run: | 43 | curl https://wasmtime.dev/install.sh -sSf | bash 44 | just test-wasm 45 | # - name: Run tests on WINE 46 | # run: cargo test --verbose --target x86_64-pc-windows-gnu 47 | # env: 48 | # CARGO_TARGET_X86_64_PC_WINDOWS_GNU_RUNNER: "wine" 49 | # - name: Run tests with Valgrind 50 | # run: | 51 | # export PATH="$PATH:$HOME/.cargo/bin" 52 | # cargo valgrind test 53 | # - name: Run tests with Valgrind 32-bit 54 | # run: | 55 | # export PATH="$PATH:$HOME/.cargo/bin" 56 | # cargo valgrind test --target i686-unknown-linux-gnu 57 | - name: Run tests on Miri 58 | run: cargo +nightly miri test 59 | env: 60 | MIRIFLAGS: "-Zmiri-disable-isolation -Zmiri-tree-borrows" 61 | # - name: Run tests on Miri 32-Bit 62 | # run: cargo +nightly miri test --target i686-unknown-linux-gnu 63 | # env: 64 | # MIRIFLAGS: "-Zmiri-disable-isolation -Zmiri-tree-borrows" 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | examples/*/target 3 | /proc-macros/target 4 | /logs/ 5 | **/*.rs.bk 6 | *.lock 7 | *.rmeta 8 | *.rlib 9 | .auctex-auto/* 10 | .ccls-cache/* 11 | .ccls-cache 12 | .projectile 13 | *_scrot.png 14 | TODO.pdf 15 | TODO.tex 16 | stats/* 17 | liblisp/* 18 | vgcores/* 19 | vgcore.* 20 | perf.data* 21 | perf.hist* 22 | massif.out.* 23 | TODO.org 24 | asm/* 25 | .dir-locals.el 26 | js/node_modules 27 | js/dist 28 | js/bin 29 | package-lock.json 30 | cachegrind.out* 31 | /flamegraph.svg 32 | heaptrack.* 33 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "spaik" 3 | version = "0.4.0-alpha" 4 | authors = ["Jonas Møller "] 5 | description = "The SPAIK Programming Language" 6 | edition = "2021" 7 | repository = "https://github.com/snyball/spaik" 8 | homepage = "https://github.com/snyball/spaik" 9 | license = "BSD-2-Clause" 10 | default-run = "repl" 11 | rust-version = "1.75" 12 | 13 | [profile.release] 14 | panic = "abort" 15 | codegen-units = 1 16 | debug = 2 17 | lto = "fat" 18 | 19 | [profile.wasm] 20 | debug = 0 21 | inherits = "release" 22 | panic = "abort" 23 | lto = "fat" 24 | opt-level = "z" 25 | codegen-units = 1 26 | 27 | [features] 28 | default = ["math", "extra", "readline", "derive", "modules", "freeze", "pretty_env_logger"] 29 | extra = ["comfy-table", "owo-colors"] 30 | readline = ["rustyline", "dirs"] 31 | wasm = ["wasm-bindgen"] 32 | math = ["glam", "mint"] 33 | modules = ["bincode", "serde"] 34 | derive = ["spaik-proc-macros"] 35 | freeze = ["bincode", "serde"] 36 | no-threading = [] 37 | serde = ["dep:serde"] 38 | shipyard = ["dep:shipyard"] 39 | cleanup-vtables = [] 40 | rapier2d = ["dep:rapier2d"] 41 | 42 | [dependencies] 43 | log = "0.4" 44 | ahash = "0.8" 45 | serde = { version = "1.0.143", features = ["derive"], optional = true } 46 | 47 | spaik-proc-macros = { path = "proc-macros", optional = true } 48 | #spaik-proc-macros = { version = "0.5.1", optional = true } 49 | bincode = { version = "1.3.3", optional = true } 50 | comfy-table = { version = "5.0.0", optional = true, default-features = false, features = [] } 51 | dirs = { version = "4.0.0", optional = true } 52 | rustyline = { version = "6.2", optional = true } 53 | glam = { version = "0.25", features = ["mint", "serde"], optional = true } 54 | owo-colors = { version = "3.5.0", optional = true, features = ["supports-colors"] } 55 | mint = { version = "0.5.9", optional = true } 56 | sptr = "0.3.2" 57 | shipyard = { version = "0.6.2", optional = true } 58 | pretty_env_logger = { version = "0.5.0", optional = true } 59 | rapier2d = { version = "0.18.0", optional = true } 60 | 61 | [target.'cfg(all(target_env = "musl", target_pointer_width = "64"))'.dependencies.jemallocator] 62 | version = "0.3.2" 63 | 64 | [dev-dependencies] 65 | iai = { version = "0.1.1", features = ["macro"] } 66 | pretty_env_logger = "0.5.0" 67 | 68 | [build-dependencies] 69 | wasm-bindgen = { version = "0.2.79", optional = true } 70 | 71 | [lib] 72 | name = "spaik" 73 | #doctest = false 74 | path = "src/lib.rs" 75 | 76 | [[bin]] 77 | name = "repl" 78 | path = "src/bin/readline-repl.rs" 79 | required-features = ["readline", "pretty_env_logger"] 80 | 81 | [[bin]] 82 | name = "main" 83 | path = "src/main.rs" 84 | 85 | [[bin]] 86 | name = "lisp-test" 87 | path = "src/bin/run-lisp-tests.rs" 88 | 89 | [[bin]] 90 | name = "scratch" 91 | path = "src/bin/scratch.rs" 92 | 93 | [[bench]] 94 | name = "lisp_test" 95 | harness = false 96 | 97 | [[bench]] 98 | name = "sieve" 99 | harness = false 100 | -------------------------------------------------------------------------------- /Changelog.org: -------------------------------------------------------------------------------- 1 | #+title: Changelog 2 | 3 | * Unreleased 4 | - Added ~vm.fork()~ for using SPAIK in an asynchronous fashion, using a 5 | message-api. 6 | - Added serde deserializer for arbitrary runtime SPAIK values 7 | - Added line-number reporting on runtime errors 8 | - Made ~(pow :number :number)~ a builtin 9 | - Added ~(join :intoiter)~ for joining strings inside collections 10 | - Added ~(sym-id :symbol)~ 11 | - Added ~(instant)~ for timing 12 | - The ~(len)~ function now accepts strings 13 | - Re-added ~(disassemble-all)~ 14 | - Added ~(dis)~ macro for easily disassembling expressions 15 | - Made it possible to pass ~+ - / *~ operations as functions 16 | - Removed the ~sys::~ cruft in favor of the new ~Subr~ trait and functions 17 | implemented using it. 18 | - ~EnumCall~ and associated proc-macro derivation to make it easier to implement 19 | the command-pattern using *SPAIK*. 20 | - Derive-macro for the ~Fissile~ trait 21 | - The ~spaikfn~ macro, for making Rust functions callable from Lisp. 22 | - Use ~serde~/~bincode~ instead of ~binread~/~binwrite~ for dumping modules 23 | - Removed C code 24 | - Added continuations and an ~async~/~await~ API in ~async.lisp~ 25 | - Added ~(throw :any)~, for control flow. Currently exclusively used for control 26 | flow that bubbles up to Rust. 27 | - Added ~(error :symbol)~, for error-reporting 28 | - Added generic iteration using ~(iter obj)~ and ~(next it)~ for ~vector~, 29 | ~cons~, and ~string~. 30 | - Added ~sqrt~ function 31 | - Added the ~char~ type for ~string~ iteration. 32 | - Added public SPAIK API to make it easier to integrate SPAIK into other 33 | projects. 34 | - Remove ~stack_ref_op~ cruft 35 | -------------------------------------------------------------------------------- /HACKING.md: -------------------------------------------------------------------------------- 1 | # Hacking SPAIK 2 | 3 | ### Important internal types 4 | 5 | - `PV` Primitive Value 6 | - `*mut NkAtom` Atom Pointer 7 | 8 | `PV` is the Lisp primitive value, it can be a float, integer, symbol, etc. or 9 | it can refer to GC memory via a `PV::Ref(*mut NkAtom)` pointer. Interacting with 10 | a `PV` is always `unsafe`, and occasionally highly precarious. SPAIK uses a 11 | moving garbage collector, which means that even if the referenced memory is a 12 | part of the [root set][1] a garbage collection cycle could invalidate the pointer. 13 | In practice, you have to maintain the following invariant: 14 | 15 | > A `PV` value taken from the SPAIK root-set cannot be dereferenced after 16 | > `Arena::collect` runs. 17 | 18 | [1]: https://en.wikipedia.org/wiki/Tracing_garbage_collection 19 | 20 | So if you acquire a `PV`, that value is valid until some manner of `eval` 21 | happens, because any evaluation of code can cause a garbage collection sweep. 22 | 23 | ### Interacting with primitive values 24 | 25 | In order to ergonomically dereference a `PV`, you can make use of the `with_ref!` and `with_ref_mut!` macros. 26 | 27 | ```rust 28 | let it = self.mem.pop()?; 29 | with_ref!(it, Cons(p) => { 30 | self.mem.push((*p).car); 31 | Ok(()) 32 | }).map_err(|e| e.bop(Builtin::Car))? 33 | ``` 34 | 35 | The `with_ref!` macro typechecks and constructs appropriate error messages. 36 | 37 | > Type Error: Expected one of (cons) in car, but got integer [x.lisp 408:3] 38 | 39 | Note: Information about what operation was performed (in this case car,) is 40 | added with the `e.bop(Builtin::Op)` call (bop, for built-in-operation.) 41 | 42 | ### What if I need to keep a PV around? 43 | 44 | There are 3 main options: 45 | 46 | 1. Just keep it in the VM, and never store it permanently somewhere on the Rust 47 | stack/heap. 48 | 2. Use `SPV`, call `Arena::make_extref(pv)` and receive a Safe Primitive Value, 49 | a thread-safe way to refer to a SPAIK GC object. 50 | - `SpaikPlug` uses this to implement async/await. 51 | 3. If performance is a concern, you can push/pop the value to/from the VM stack. 52 | Whenever you do anything that leads to eval do `vm.mem.push(pv)` and then 53 | `pv = vm.mem.pop().unwrap()` after evaluation finishes. 54 | - Some of the hairiest bits of the VM, like `R8VM::macroexpand_pv` are 55 | examples of this approach. 56 | - SPAIK uses the convention `invalid!(x, y, z) // ` for marking variables that become invalidated. 57 | - **Do not rely on all invalidated variables being documented** 58 | 59 | ### NkAtom layout 60 | 61 | An `NkAtom` looks like this: 62 | 63 | ```rust 64 | #[repr(C)] 65 | pub struct NkAtom { 66 | next: *mut NkAtom, 67 | sz: NkSz, 68 | meta: AtomMeta, 69 | } 70 | ``` 71 | 72 | Following `next` leads to the next object in the GC memory, `sz` is the size of 73 | the allocated object, and `meta` holds the type and [color][2] of the object. 74 | 75 | [2]: https://en.wikipedia.org/wiki/Tracing_garbage_collection#Tri-color_marking 76 | 77 | Strangely missing however is the object itself, which is actually stored 78 | directly after the `NkAtom`. The definition neglects to mention that, because 79 | there is no safe way to express a "`dyn` DST" in Rust. This is how we can turn 80 | an `NkAtom` into the `T` it contains 81 | 82 | ```rust 83 | pub unsafe fn fastcast_mut(atom: *mut NkAtom) -> *mut T { 84 | const DELTA: isize = mem::size_of::() as isize; 85 | let p = (atom as *mut u8).offset(DELTA); 86 | align_mut(p, align_of::() as isize) as *mut T 87 | } 88 | ``` 89 | 90 | When an object is allocated in the SPAIK GC, it gets allocated with an `NkAtom` 91 | header followed by any padding, and then the object itself. 92 | 93 | However `fastcast_mut` is essentially just as unsafe as `mem::transmute`, in 94 | order to do this safely we use the wrapper `cast_mut`: 95 | 96 | ```rust 97 | pub unsafe fn cast_mut(atom: *mut NkAtom) -> Option<*mut T> { 98 | let ty = T::type_of(); 99 | let got = mem::transmute((*atom).meta.typ()); 100 | (ty == got).then(|| fastcast_mut::(atom)) 101 | } 102 | ``` 103 | 104 | The `Fissile` trait is a marker for any objects that can be stored directly in 105 | the SPAIK GC, it looks like this: 106 | 107 | ```rust 108 | pub unsafe trait Fissile: LispFmt + Debug + Clone + Traceable + Any + 'static { 109 | fn type_of() -> NkT; 110 | } 111 | ``` 112 | 113 | Adding a new internal type to SPAIK means making it Fissile, by putting it in 114 | the `fissile_types!` invocation in `nuke.rs`. 115 | 116 | ```rust 117 | fissile_types! { 118 | (/* Given name */ Void, /* Symbol */ Builtin::Void.sym(), /* Type */ crate::nuke::Void), 119 | // ... 120 | } 121 | ``` 122 | 123 | If you would like to dereference a `PV` without `with_ref!`, this is what 124 | `with_ref!` does internally: 125 | 126 | ```rust 127 | match pv { 128 | PV::Ref(p) => match to_fissile_ref(p) { 129 | NkRef::Cons(p) => { 130 | println!("pointer to Cons: {p:?}") 131 | } 132 | NkRef::Vector(p) => { 133 | println!("pointer to Vec: {p:?}") 134 | } 135 | _ => Err(...) 136 | } 137 | _ => Err(...) 138 | } 139 | ``` 140 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020-2023 Jonas Møller 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 17 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 19 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 20 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 21 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 22 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | The SPAIK LISP Programming Language 2 | =================================== 3 | 4 | SPAIK is a dynamic extension language for Rust. It implements macros, garbage 5 | collection, iterators, continuations, async/await and wraps it up in a 6 | (hopefully) easy to use high-level Rust API. 7 | 8 | This README contains many shorts snippets showing how SPAIK is used, while you 9 | can find complete examples in the [examples](examples) directory, and the more 10 | detailed API docs can be found at [docs.rs](https://docs.rs/spaik/latest/spaik/). 11 | 12 | [You can also try SPAIK directly in your 13 | browser!](https://snyball.github.io/spaik-site/) 14 | 15 | ### Basic usage 16 | 17 | For basic usage, all you need are the `eval` and `exec` methods (`exec` is just 18 | `eval` but it throws away the result to aid type-inference.) 19 | 20 | ``` rust 21 | let mut vm = Spaik::new(); 22 | vm.exec(r#"(println "Hello, World!")"#)?; 23 | 24 | vm.set("f", |x: i32| x + 2); // Functions are first-class at the API boundary! 25 | assert_eq!(vm.eval("(f 2)"), Ok(4)); 26 | 27 | // Optional linear-algebra types from glam 28 | vm.exec("(defun funky (x y) (* x (vec3 1 y 3)))")?; 29 | assert_eq!(vm.call("funky", (2, 4)), Ok(glam::vec3(2.0, 8.0, 6.0))); // Call a spaik function 30 | 31 | // Define interfaces more formally 32 | defuns!(trait MyInterface { 33 | fn funky(x: f32, y: f32) -> glam::Vec3; 34 | }); 35 | // This panics if the function `funky` does not match the spec 36 | assert_eq!(vm.funky(2.0, 4.0), glam::vec3(2.0, 8.0, 6.0)); 37 | ``` 38 | 39 | ### Loading Code 40 | 41 | You probably don't want to store all your SPAIK code as embedded strings in Rust, 42 | so you can of course load SPAIK scripts from the filesystem. 43 | 44 | ``` rust 45 | vm.add_load_path("my-spaik-programs"); 46 | vm.load::("stuff")?; 47 | let result: Lambda = vm.load("other-stuff")?; 48 | result.call(&mut vm, (1, 2, 3)); 49 | ``` 50 | 51 | The `add_load_path` method adds the given string to the global `sys/load-path` 52 | variable, which is just a SPAIK vector. You can mutate this from SPAIK too: 53 | 54 | ``` common-lisp 55 | (eval-when-compile (push sys/load-path "my-dependencies")) 56 | (load dependency) 57 | ``` 58 | 59 | But notice that we had to use `(eval-when-compile ...)` when adding the new 60 | path, because `(load ...)` also runs during compilation. 61 | 62 | ### Exporting functions to SPAIK 63 | 64 | You can simply `vm.set("name", func)`, or use the convenience-function 65 | `vm.defun(add_to)`, which is equivalent to `vm.set("add-to", add_to)`. 66 | 67 | ``` rust 68 | use spaik::prelude::*; 69 | 70 | fn add_to(x: i32) -> i32 { 71 | x + 1 72 | } 73 | 74 | fn main() -> Result<(), Box> { 75 | let mut vm = Spaik::new(); 76 | println!("Calling from Rust: {}", add_to(2)); 77 | vm.set("other-name-for-add-to", add_to); 78 | vm.defun(add_to); 79 | vm.exec(r#"(let ((r (add-to 2))) (println "Calling Rust from SPAIK: {r}"))"#)?; 80 | Ok(()) 81 | } 82 | ``` 83 | 84 | ### The `html` macro 85 | 86 | Because of how easy it is to create new syntax constructs in LISPs, you can 87 | use SPAIK as a rudimentary html templating engine. 88 | 89 | ``` common-lisp 90 | (load html) 91 | (html (p :class 'interjection "Interjection!")) 92 | ``` 93 | 94 | ``` html 95 |

Interjection!

96 | ``` 97 | 98 | 99 | ### Internal Architecture 100 | 101 | SPAIK code is bytecode compiled and runs on a custom VM called the Rodent VM 102 | (R8VM,) which uses a moving tracing garbage collector. For more detailed 103 | information about its internals, see [HACKING.md](HACKING.md). 104 | -------------------------------------------------------------------------------- /benches/lisp_test.rs: -------------------------------------------------------------------------------- 1 | use spaik::run_tests; 2 | 3 | fn run_lisp_tests() { 4 | run_tests().unwrap(); 5 | } 6 | 7 | iai::main!(run_lisp_tests); 8 | -------------------------------------------------------------------------------- /benches/sieve.lisp: -------------------------------------------------------------------------------- 1 | (defun sieve (n) 2 | (let ((field (vec)) 3 | (primes (vec))) 4 | (range (i (0 (+ n 1))) 5 | (push field false)) 6 | (range (f (2 (+ n 1))) 7 | (unless (get field f) 8 | (push primes f) 9 | (let ((j f)) 10 | (while (<= j n) 11 | (set (get field j) true) 12 | (set j (+ j f)))))) 13 | primes)) 14 | 15 | (sieve 100000) 16 | -------------------------------------------------------------------------------- /benches/sieve.rs: -------------------------------------------------------------------------------- 1 | use iai::black_box; 2 | use spaik::Spaik; 3 | 4 | fn run_sieve() { 5 | let sieve_lisp = include_str!("sieve.lisp"); 6 | let mut vm = Spaik::new(); 7 | vm.exec(black_box(sieve_lisp)).unwrap(); 8 | } 9 | 10 | iai::main!(run_sieve); 11 | -------------------------------------------------------------------------------- /examples/command-pattern-multi-threaded/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ext-examples-command-pattern-multi-threaded" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | serde = "1.0" 10 | spaik = { path = "../.." } 11 | -------------------------------------------------------------------------------- /examples/command-pattern-multi-threaded/async-example.lisp: -------------------------------------------------------------------------------- 1 | (define *global* 0) 2 | 3 | (defun init ()) 4 | 5 | (defun add (x) 6 | (let ((res (await '(test :id 1337)))) 7 | (set *global* (+ *global* res x 1)))) 8 | -------------------------------------------------------------------------------- /examples/command-pattern-multi-threaded/src/main.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | use spaik::prelude::*; 3 | use spaik::EnumCall; 4 | 5 | #[derive(Debug, Deserialize, Clone, PartialEq, Eq)] 6 | #[serde(rename_all = "kebab-case")] 7 | enum Msg { 8 | Test { id: i32 }, 9 | } 10 | 11 | #[derive(Debug, Deserialize, Clone, PartialEq, Eq, EnumCall)] 12 | #[serde(rename_all = "kebab-case")] 13 | enum Cmd { 14 | Add(i32), 15 | Subtract(i32) 16 | } 17 | 18 | fn main() -> Result<(), Box> { 19 | let mut vm = Spaik::new(); 20 | vm.add_load_path("."); 21 | vm.load("async-example")?; 22 | let vm = vm.fork::(); 23 | 24 | vm.cmd(Cmd::Add(10)); 25 | vm.cmd(Cmd::Add(20)); 26 | 27 | // Loop until all commands have been responded to 28 | let mut recvd = 0; 29 | while recvd < 2 { 30 | while let Some(p) = vm.recv() { 31 | assert_eq!(p.get(), &Msg::Test { id: 1337 }); 32 | vm.fulfil(p, 31337); 33 | recvd += 1; 34 | } 35 | } 36 | 37 | // We can join with the VM again on the same thread 38 | let mut vm = vm.join(); 39 | let glob: i32 = vm.eval("*global*")?; 40 | assert_eq!(glob, 10 + 31337 + 1 + 20 + 31337 + 1); 41 | 42 | println!("*global*: {}", vm.get::("*global*")?); 43 | 44 | Ok(()) 45 | } 46 | -------------------------------------------------------------------------------- /examples/exported-fns/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ext-example-exported-fns" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | serde = "1.0" 10 | spaik = { path = "../.." } 11 | -------------------------------------------------------------------------------- /examples/exported-fns/src/main.rs: -------------------------------------------------------------------------------- 1 | use spaik::prelude::*; 2 | 3 | struct Fns; 4 | 5 | fn add_to(x: i32) -> i32 { 6 | x + 1 7 | } 8 | 9 | fn main() -> Result<(), Box> { 10 | let mut vm = Spaik::new(); 11 | println!("Calling from Rust: {}", add_to(2)); 12 | vm.set("add-to", add_to); 13 | vm.exec(r#"(let ((r (add-to 2))) (println "Calling Rust from SPAIK: {r}"))"#)?; 14 | Ok(()) 15 | } 16 | -------------------------------------------------------------------------------- /examples/get-set.rs: -------------------------------------------------------------------------------- 1 | use spaik::Spaik; 2 | 3 | pub fn main() -> Result<(), Box> { 4 | let mut vm = Spaik::new(); 5 | 6 | // Use exec when you don't care about the result 7 | vm.exec(r#"(println "Hello, World!")"#)?; 8 | 9 | vm.set("*global*", 3); 10 | // Use eval when you do care 11 | let res: i32 = vm.eval(r#"(+ 1 *global*)"#)?; 12 | assert_eq!(res, 4); 13 | 14 | Ok(()) 15 | } 16 | -------------------------------------------------------------------------------- /examples/hello-world.rs: -------------------------------------------------------------------------------- 1 | use spaik::Spaik; 2 | 3 | pub fn main() -> Result<(), Box> { 4 | let mut vm = Spaik::new(); 5 | 6 | // Use exec when you don't care about the result 7 | vm.exec(r#"(println "Hello, World!")"#)?; 8 | 9 | // Use eval when you do care 10 | let res: String = vm.eval(r#"(println "Hello, World. Again?")"#)?; 11 | assert_eq!(res, "Hello, World. Again?"); 12 | let res: spaik::Result = vm.eval(r#"(println "Hello, Wo-... This is getting old isn't it?")"#); 13 | assert!(res.is_err()); 14 | 15 | // vm.exec(...) is equivalent to 16 | let _: spaik::Ignore = vm.eval(r#"(println "Helloooooooooooooooo?!")"#)?; 17 | 18 | Ok(()) 19 | } 20 | -------------------------------------------------------------------------------- /examples/loading-code.rs: -------------------------------------------------------------------------------- 1 | use spaik::{Ignore, Spaik}; 2 | 3 | pub fn main() -> Result<(), Box> { 4 | let mut vm = Spaik::new(); 5 | 6 | // We need to specify a load-path first 7 | vm.add_load_path("examples"); 8 | 9 | vm.load::("to-be-loaded")?; 10 | 11 | Ok(()) 12 | } 13 | -------------------------------------------------------------------------------- /examples/resources/friends.lisp: -------------------------------------------------------------------------------- 1 | (defun friend () 2 | (println ":^)")) 3 | -------------------------------------------------------------------------------- /examples/to-be-loaded.lisp: -------------------------------------------------------------------------------- 1 | (println "Yay! I'm being loaded! I EXIIIIIIIIIIIST") 2 | 3 | ;; You can also modify the load-path from lisp, it is just a variable, although 4 | ;; since load runs during compilation, you have to modify sys/load-path during 5 | ;; compilation too! 6 | (eval-when-compile 7 | (push sys/load-path "examples/resources")) 8 | (load friends) 9 | (friend) 10 | (println ":^D") 11 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | set positional-arguments 2 | 3 | export MIRIFLAGS := "-Zmiri-disable-isolation -Zmiri-tree-borrows" 4 | export CARGO_TARGET_WASM32_WASIP1_RUNNER := "tools/wasmtime.sh" 5 | export WASMTIME_BACKTRACE_DETAILS := "1" 6 | 7 | install-tools: 8 | command -v cargo-hack &>/dev/null || cargo install cargo-hack 9 | rustup +nightly component add miri 10 | rustup target add wasm32-unknown-unknown 11 | rustup target add wasm32-wasip1 12 | rustup target add x86_64-unknown-linux-musl 13 | rustup target add x86_64-unknown-linux-gnu 14 | 15 | test: 16 | cargo test 17 | 18 | test-wasm: 19 | cargo test --target wasm32-wasip1 -- --nocapture 20 | 21 | build-wasm: 22 | cargo +nightly build --profile wasm \ 23 | --target wasm32-unknown-unknown \ 24 | --features serde \ 25 | -Z build-std=std,panic_abort \ 26 | -Z build-std-features=panic_immediate_abort \ 27 | --no-default-features \ 28 | --features wasm 29 | mkdir -p target/wasm-opt 30 | @echo "optimizing wasm files ..." 31 | fd . -d1 -ewasm target/wasm32-unknown-unknown/wasm \ 32 | -x wasm-opt -Oz -o target/wasm-opt/{/} {} 33 | @echo "optimized wasm files saved to target/wasm-opt" 34 | 35 | build-mini: 36 | cargo build --profile wasm \ 37 | --target x86_64-unknown-linux-gnu \ 38 | -Z build-std=std,panic_abort \ 39 | -Z build-std-features=panic_immediate_abort \ 40 | --no-default-features 41 | 42 | before-commit: 43 | cargo check 44 | cargo test 45 | @just test-wasm 46 | 47 | test-all: install-tools 48 | cargo test 49 | @just test-wasm 50 | @echo "testing with miri, this will take a long time" 51 | @just test-miri 52 | @just test-miri-32 53 | #cargo hack test --feature-powerset 54 | 55 | @miri *args: 56 | cargo miri "$@" 57 | 58 | test-miri: 59 | cargo +nightly miri test 60 | 61 | test-miri-32: 62 | cargo +nightly miri --target i686-unknown-linux-gnu test 63 | 64 | default: 65 | @just --list 66 | -------------------------------------------------------------------------------- /lisp/bench-tools.lisp: -------------------------------------------------------------------------------- 1 | (defmacro timeit (&body it) 2 | `(let* ((s (instant)) 3 | (res (progn ,@it))) 4 | (let ((el (- (instant) s))) 5 | (println "Executed in {el} seconds")) 6 | res)) 7 | -------------------------------------------------------------------------------- /lisp/boot-stage0.lisp: -------------------------------------------------------------------------------- 1 | ;; Stage 0 of the SPAIK bootup process, this has to be a separate compilation 2 | ;; unit from core.lisp because the set-macro function must run so that 3 | ;; set-macro! becomes available by the time core.lisp is compiled. 4 | 5 | (define (<ξ>-set-macro! macro fn) 6 | (sys/set-macro macro fn) 7 | nil) 8 | 9 | (define (<ξ>-set-macro-character! macro fn) 10 | (sys/set-macro-character macro fn) 11 | nil) 12 | 13 | (sys/set-macro 'set-macro! '<ξ>-set-macro!) 14 | 15 | (sys/set-macro 'set-macro-character! '<ξ>-set-macro-character!) 16 | -------------------------------------------------------------------------------- /lisp/core.lisp: -------------------------------------------------------------------------------- 1 | (define sys/load-path (vec)) 2 | 3 | (define (<ξ>-defun name args &body body) 4 | `(define (,name ,@args) ,@body)) 5 | (set-macro! defun <ξ>-defun) 6 | 7 | (defun <ξ>-defmacro (name args &body body) 8 | ((lambda (mac-fn-name) 9 | `(progn 10 | (define (,mac-fn-name ,@args) 11 | ,@body) 12 | (set-macro! ,name ,mac-fn-name))) 13 | (intern (concat '<ξ>- name)))) 14 | (set-macro! defmacro <ξ>-defmacro) 15 | 16 | (defun head (x) 17 | (car x)) 18 | (defun tail (x) 19 | (cdr x)) 20 | (defun caar (x) 21 | (car (car x))) 22 | (defun cadr (x) 23 | (car (cdr x))) 24 | (defun cdar (x) 25 | (cdr (car x))) 26 | (defun cddr (x) 27 | (cdr (cdr x))) 28 | (defun caaar (x) 29 | (car (car (car x)))) 30 | (defun caadr (x) 31 | (car (car (cdr x)))) 32 | (defun cadar (x) 33 | (car (cdr (car x)))) 34 | (defun caddr (x) 35 | (car (cdr (cdr x)))) 36 | (defun cdaar (x) 37 | (cdr (car (car x)))) 38 | (defun cdadr (x) 39 | (cdr (car (cdr x)))) 40 | (defun cddar (x) 41 | (cdr (cdr (car x)))) 42 | (defun cdddr (x) 43 | (cdr (cdr (cdr x)))) 44 | (defun caaaar (x) 45 | ;; khaaaaaaaaaaan 46 | (car (car (car (car x))))) 47 | (defun caaadr (x) 48 | (car (car (car (cdr x))))) 49 | (defun caadar (x) 50 | (car (car (cdr (car x))))) 51 | (defun caaddr (x) 52 | (car (car (cdr (cdr x))))) 53 | (defun cadaar (x) 54 | (car (cdr (car (car x))))) 55 | (defun cadadr (x) 56 | (car (cdr (car (cdr x))))) 57 | (defun caddar (x) 58 | (car (cdr (cdr (car x))))) 59 | (defun cadddr (x) 60 | (car (cdr (cdr (cdr x))))) 61 | (defun cdaaar (x) 62 | (cdr (car (car (car x))))) 63 | (defun cdaadr (x) 64 | (cdr (car (car (cdr x))))) 65 | (defun cdadar (x) 66 | (cdr (car (cdr (car x))))) 67 | (defun cdaddr (x) 68 | (cdr (car (cdr (cdr x))))) 69 | (defun cddaar (x) 70 | (cdr (cdr (car (car x))))) 71 | (defun cddadr (x) 72 | (cdr (cdr (car (cdr x))))) 73 | (defun cdddar (x) 74 | (cdr (cdr (cdr (car x))))) 75 | (defun cddddr (x) 76 | (cdr (cdr (cdr (cdr x))))) 77 | 78 | (defun map (f xs) 79 | (if xs (cons (f (car xs)) 80 | (map f (cdr xs))))) 81 | 82 | (defmacro let (defs &rest body) 83 | `((lambda ,(map (lambda (x) (car x)) defs) 84 | ,@body) 85 | ,@(map (lambda (x) (car (cdr x))) defs))) 86 | 87 | (defmacro while (cnd &body body) 88 | `(loop 89 | (if (not ,cnd) 90 | (break)) 91 | ,@body)) 92 | 93 | (defmacro until (cnd &body body) 94 | `(loop 95 | (if ,cnd (break)) 96 | ,@body)) 97 | 98 | (defmacro inc! (var &opt n) 99 | (let ((num (or n 1))) 100 | `(set ,var (+ ,var ,num)))) 101 | 102 | (defmacro dec! (var &opt n) 103 | (let ((num (or n 1))) 104 | `(set ,var (- ,var ,num)))) 105 | 106 | (define <β>-num 0) 107 | (defun gensym () 108 | (let ((sym (intern (concat "<β>-" <β>-num)))) 109 | (inc! <β>-num) 110 | sym)) 111 | 112 | (defmacro when (cnd &body if-true) 113 | `(if ,cnd 114 | (progn 115 | ,@if-true))) 116 | 117 | (defmacro unless (cnd &body if-false) 118 | `(if (not ,cnd) 119 | (progn 120 | ,@if-false))) 121 | 122 | (defmacro set* (to from) 123 | (let ((let-set nil) 124 | (set-set nil)) 125 | (while to 126 | (let ((tmp-sym (gensym))) 127 | (set let-set (cons `(,tmp-sym ,(car from)) let-set)) 128 | (set set-set (cons `(set ,(car to) ,tmp-sym) set-set))) 129 | (set to (cdr to)) 130 | (set from (cdr from))) 131 | `(let ,let-set 132 | ,@set-set))) 133 | 134 | (defun let*/helper (pairs body) 135 | (if pairs 136 | `(let (,(car pairs)) 137 | ,(let*/helper (cdr pairs) body)) 138 | `(progn ,@body))) 139 | 140 | (defmacro defvar (var init) 141 | `(define ,var ,init)) 142 | 143 | (defmacro let* (pairs &body body) 144 | (let*/helper pairs body)) 145 | 146 | (defmacro iter-end? (res) 147 | `(= ,res '<ζ>-iter-stop)) 148 | 149 | (defmacro dolist (cnd &body body) 150 | (let ((name (car cnd)) 151 | (init (car (cdr cnd))) 152 | (it (gensym))) 153 | `(let ((,name nil) 154 | (,it (iter ,init))) 155 | (loop (if (iter-end? (set ,name (next ,it))) 156 | (break)) 157 | ,@body)))) 158 | 159 | (defun map! (f xs) 160 | (dolist (x xs) 161 | (f x))) 162 | 163 | (defun member? (x xs) 164 | (dolist (y xs) 165 | (when (eq? x y) 166 | (break true)))) 167 | 168 | (defmacro range (cnd &body body) 169 | (let ((loop-var (car cnd)) 170 | (min (caadr cnd)) 171 | (max (cadadr cnd))) 172 | `(let ((,loop-var ,min)) 173 | (loop-with-epilogue 174 | (progn 175 | (if (not (< ,loop-var ,max)) (break)) 176 | ,@body) 177 | (inc! ,loop-var))))) 178 | 179 | (defun range-list (a b) 180 | (let ((xs nil)) 181 | (range (x (a b)) 182 | (set xs (cons x xs))) 183 | (reverse xs))) 184 | 185 | (defun filter (f xs) 186 | (when xs 187 | (let ((x (car xs))) 188 | (if (f x) 189 | (cons x (filter f (cdr xs))) 190 | (filter f (cdr xs)))))) 191 | 192 | (defun zip (xs ys) 193 | (when (and xs ys) 194 | (cons (cons (car xs) 195 | (car ys)) 196 | (zip (cdr xs) 197 | (cdr ys))))) 198 | 199 | (defun reverse (xs) 200 | (let ((ys nil)) 201 | (dolist (x xs) 202 | (set ys (cons x ys))) 203 | ys)) 204 | 205 | (defun all? (f xs) 206 | (not 207 | (dolist (x xs) 208 | (unless (f x) 209 | (break (and)))))) 210 | 211 | (defun any? (f xs) 212 | (dolist (x xs) 213 | (if (f x) 214 | (break (and))))) 215 | 216 | (defun elem? (x ys) 217 | (dolist (y ys) 218 | (when (= x y) 219 | (break (and))))) 220 | 221 | (defmacro extreme-value (ord src) 222 | (let ((m (gensym)) (xs (gensym)) (x (gensym))) 223 | `(let ((,m (car ,src)) 224 | (,xs (cdr ,src))) 225 | (dolist (,x ,xs) 226 | (when (,ord ,x ,m) 227 | (set ,m ,x))) 228 | ,m))) 229 | 230 | (defun min (xs) 231 | (extreme-value < xs)) 232 | 233 | (defun max (xs) 234 | (extreme-value > xs)) 235 | 236 | (defun sum (xs) 237 | (let ((s 0)) 238 | (dolist (x xs) 239 | (inc! s x)) 240 | s)) 241 | 242 | (defun abs (x) 243 | (if (< x 0) 244 | (- x) 245 | x)) 246 | 247 | (defun sqrt (x) 248 | (pow x 0.5)) 249 | 250 | (defun mean (xs) 251 | (/ (sum xs) (len xs))) 252 | 253 | (defun chr (s) 254 | (next (iter s))) 255 | 256 | (defmacro %chr (s) 257 | (chr s)) 258 | 259 | (defmacro m-map (m xs) 260 | (let ((p '())) 261 | (dolist (x xs) 262 | (set p (cons `(,m ,x) p))) 263 | `(progn ,@(reverse p)))) 264 | 265 | (defmacro make-tcheck (type) 266 | (let ((name (intern (concat type '?)))) 267 | `(defun ,name (x) 268 | (= (type-of x) ',type)))) 269 | 270 | (m-map make-tcheck (integer 271 | symbol 272 | unsigned-integer 273 | float 274 | bool 275 | string 276 | cons 277 | vec)) 278 | 279 | (defun keyword? (x) 280 | (and (symbol? x) 281 | (= (chr (string x)) 282 | (%chr ":")))) 283 | 284 | (defun keyword-name (x) 285 | (let ((it (iter (string x)))) 286 | (next it) 287 | (apply concat (collect it)))) 288 | 289 | (defun number? (x) 290 | (or (integer? x) 291 | (float? x))) 292 | 293 | (defun nil? (x) 294 | (= x nil)) 295 | 296 | (defmacro cond (&rest cnds) 297 | `(loop 298 | ,@(map (lambda (cnd) 299 | `(if ,(car cnd) 300 | (break ,@(cdr cnd)))) 301 | cnds) 302 | (break nil))) 303 | 304 | (defmacro case (this &rest is) 305 | `(loop 306 | ,@(map (lambda (x) 307 | (if (= (car x) '_) 308 | `(break ,@(cdr x)) 309 | `(if (eq? ,this ,(car x)) 310 | (break ,@(cdr x))))) 311 | is) 312 | (break nil))) 313 | 314 | (defmacro fmt (w &rest in) 315 | (let* ((begin (%chr "{")) 316 | (end (%chr "}")) 317 | (in-sub false) 318 | (span (vec)) 319 | (out '(concat))) 320 | (dolist (c w) 321 | (when (= c begin) 322 | (set out (cons (join span) out)) 323 | (set span (vec)) 324 | (set in-sub true) 325 | (next)) 326 | (when (= c end) 327 | (unless in-sub 328 | (error 'trailing-delimiter)) 329 | (set out (cons (intern (join span)) out)) 330 | (set span (vec)) 331 | (set in-sub false) 332 | (next)) 333 | (push span c)) 334 | (when in-sub 335 | (error 'unclosed-delimiter)) 336 | (set out (cons (join span) out)) 337 | (if (= (len out) 2) 338 | (car out) 339 | (reverse out)))) 340 | 341 | ;; Functions for builtins, these do not override the builtins when used in 342 | ;; function-position, but allow them to be passed as closures 343 | (defun cons (x y) (cons x y)) 344 | (defun car (x) (car x)) 345 | (defun cdr (x) (cdr x)) 346 | (defun pop (xs) (pop xs)) 347 | (defun push (xs y) (push xs y)) 348 | (defun get (xs i) (get xs i)) 349 | (defun len (xs) (len xs)) 350 | (defun not (x) (not x)) 351 | (defun apply (f xs) (apply f xs)) 352 | 353 | (defun _println (x) 354 | (println x)) 355 | 356 | (defmacro println (w &rest in) 357 | (if (string? w) 358 | `(_println (fmt ,w ,@in)) 359 | `(_println ,w ,@in))) 360 | 361 | (defun _print (x) 362 | (print x)) 363 | 364 | (defmacro print (w &rest in) 365 | (if (string? w) 366 | `(_print (fmt ,w ,@in)) 367 | `(_print ,w ,@in))) 368 | 369 | (defun find-first-duplicate (xs) 370 | (when xs 371 | (let ((x (car xs)) 372 | (ys (cdr xs))) 373 | (if (member? x ys) 374 | x 375 | (find-first-duplicate ys))))) 376 | 377 | (defmacro if-let (decl &body b) 378 | (let ((name (car decl)) 379 | (init (cadr decl))) 380 | `(let ((,name ,init)) 381 | (if ,name (progn ,@b))))) 382 | 383 | (defmacro eval-when-compile (&body b) 384 | (eval `(progn ,@b))) 385 | 386 | (defun _require (lib) 387 | (require lib)) 388 | 389 | (defun _load (lib) 390 | (load lib)) 391 | 392 | (defmacro load (lib) 393 | (_load lib) 394 | nil) 395 | 396 | (defmacro require (lib) 397 | (_require lib) 398 | nil) 399 | 400 | (defmacro dbg (obj) 401 | `(_println (concat ',obj ": " ,obj))) 402 | 403 | (defun collect (it) 404 | (let ((elem nil) 405 | (out (vec))) 406 | (loop (if (iter-end? (set elem (next it))) 407 | (break)) 408 | (push out elem)) 409 | out)) 410 | 411 | (defun append (a b) 412 | (append! (clone a) (clone b))) 413 | 414 | (defmacro yield (expr) 415 | (let ((k (gensym))) 416 | `(call/cc (lambda (,k) 417 | (throw 'yield (cons ,expr ,k)))))) 418 | 419 | (defmacro await (expr) 420 | (let ((k (gensym))) 421 | `(call/cc (lambda (,k) 422 | (<ζ>-send-message ,expr ,k) 423 | (throw 'yield '<ζ>-yield-await))))) 424 | 425 | (defmacro send (expr) 426 | `(<ζ>-send-message ,expr)) 427 | 428 | (defun nth (xs i &opt alt) 429 | (cond 430 | ((vec? xs) (if (< i (len xs)) 431 | (get xs i) 432 | (or alt (get xs i)))) 433 | ((= xs nil) (or alt (error 'index-error))) 434 | ((cons? xs) (let ((j 0) 435 | (o false)) 436 | (let ((elem (dolist (x xs) 437 | (when (= i j) 438 | (set o true) 439 | (break x)) 440 | (inc! j)))) 441 | (if o elem (or alt (error 'index-error)))))) 442 | (true (error 'type-error)))) 443 | -------------------------------------------------------------------------------- /lisp/hello-world.lisp: -------------------------------------------------------------------------------- 1 | (println "Hello, World!") 2 | -------------------------------------------------------------------------------- /lisp/html.lisp: -------------------------------------------------------------------------------- 1 | (defun escape (s) 2 | (or (and (or (number? s) (bool? s)) (string s)) 3 | (and (symbol? s) (escape (string s))) 4 | (let ((out (vec))) 5 | (dolist (x s) 6 | (push out 7 | (cond ((= x (chr "<")) "<") 8 | ((= x (chr ">")) ">") 9 | ((= x (chr "&")) "&") 10 | (true x)))) 11 | (apply concat out)))) 12 | 13 | (defun escape-attr (s) 14 | (or (and (or (number? s) (bool? s)) s) 15 | (and (symbol? s) s) 16 | (let ((out (vec))) 17 | (dolist (x s) 18 | (push out 19 | (cond ((= x (chr "<")) "<") 20 | ((= x (chr ">")) ">") 21 | ((= x (chr "&")) "&") 22 | ((= x (chr "\"")) """) 23 | ((= x (chr "'")) "'") 24 | (true x)))) 25 | (apply concat out)))) 26 | 27 | (defmacro html (&body b) 28 | (let ((s (vec 'vec))) 29 | (dolist (elem b) 30 | (if (cons? elem) 31 | (progn 32 | (if (symbol? (car elem)) 33 | (let ((tag (string (car elem)))) 34 | (push s "<") 35 | (push s tag) 36 | (while (keyword? (cadr elem)) 37 | (push s " ") 38 | (push s (keyword-name (cadr elem))) 39 | (set elem (cdr elem)) 40 | (push s "=\"") 41 | (if (cons? (cadr elem)) 42 | (push s (cadr elem)) 43 | (push s (escape-attr (cadr elem)))) 44 | (set elem (cdr elem)) 45 | (push s "\"")) 46 | (push s ">") 47 | (push s `(html ,@(cdr elem))) 48 | (push s "")) 51 | (push s elem))) 52 | (if (symbol? elem) 53 | (push s elem) 54 | (push s (escape elem))))) 55 | `(apply concat 56 | ,(apply (lambda (&rest r) r) s)))) 57 | -------------------------------------------------------------------------------- /lisp/self.lisp: -------------------------------------------------------------------------------- 1 | (defun atom? (x) 2 | (or (number? x) 3 | (bool? x) 4 | (string? x) 5 | (nil? x))) 6 | 7 | (defun product (xs) 8 | (let ((p 1)) 9 | (dolist (x xs) 10 | (set p (* p x))) 11 | p)) 12 | 13 | (defun evil-r (expr env) 14 | (let ((rec (lambda (e) 15 | (evil-r e env)))) 16 | (cond ((atom? expr) 17 | expr) 18 | ((symbol? expr) 19 | (env expr)) 20 | ((cons? expr) 21 | (let ((op (car expr)) 22 | (args (cdr expr))) 23 | (case op 24 | ('= (= (evil-r (car args) env) 25 | (evil-r (cadr args) env))) 26 | ('+ (sum (map rec args))) 27 | ('- (- (evil-r (car args) env) 28 | (sum (map rec (cdr args))))) 29 | ('* (product (map rec args))) 30 | ('/ (/ (evil-r (car args) env) 31 | (product (map rec (cdr args))))) 32 | ('cons (cons (evil-r (car args) env) 33 | (evil-r (cadr args) env))) 34 | ('car (car (evil-r (car args) env))) 35 | ('cdr (cdr (evil-r (car args) env))) 36 | ('quote (car args)) 37 | ('if (if (evil-r (car args) env) 38 | (evil-r (cadr args) env) 39 | (and (cddr args) 40 | (evil-r (caddr args) env)))) 41 | ('progn (let ((res nil)) 42 | (dolist (arg args) 43 | (set res (evil-r arg env))) 44 | res)) 45 | ('println (println (apply concat args))) 46 | ('lambda (let ((x (caar args)) 47 | (body `(progn ,@(cdr args)))) 48 | (lambda (arg) 49 | (evil-r body (lambda (sym) 50 | (if (= sym x) 51 | arg 52 | (env sym))))))) 53 | (_ ((evil-r op env) 54 | (evil-r (car args) env)))))) 55 | (true (progn 56 | (println "Undefined pattern: {expr}") 57 | (error 'undefined-pattern)))))) 58 | 59 | (defun evil (expr) 60 | (evil-r expr (lambda (x) 61 | (println "Undefined variable: {x}") 62 | (error 'undefined-variable)))) 63 | 64 | (defun evil-examples () 65 | (println (evil '((lambda (x) (+ x 1330)) 7))) 66 | 67 | (println (evil '((((lambda (x) 68 | (lambda (y) 69 | (lambda (z) 70 | (+ (* x (* 2 y)) z)))) 71 | 4) 5) 6))) 72 | 73 | (println (evil '((lambda (f) (f 2)) 74 | (lambda (x) (+ 10 x))))) 75 | 76 | (println (evil '((lambda (f) 77 | ((f f) 20)) 78 | (lambda (f) 79 | (lambda (n) 80 | (if (= n 0) 81 | 1 82 | (* n ((f f) (- n 1))))))))) 83 | 84 | (println (evil '((((lambda (f) 85 | (f f)) 86 | (lambda (f) 87 | (lambda (g) 88 | (lambda (xs) 89 | (if xs 90 | (cons (g (car xs)) 91 | (((f f) g) (cdr xs)))))))) 92 | (lambda (x) 93 | (* x 2))) 94 | '(2 4 6 8 10 12)))) 95 | 96 | (println (evil '(((lambda (map) 97 | (lambda (factorial) 98 | ((map factorial) '(1 2 3 4 5 6 7 8 9 10)))) 99 | ((lambda (f) 100 | (f f)) 101 | (lambda (f) 102 | (lambda (g) 103 | (lambda (xs) 104 | (if xs 105 | (cons (g (car xs)) 106 | (((f f) g) (cdr xs))))))))) 107 | ((lambda (f) 108 | (f f)) 109 | (lambda (f) 110 | (lambda (n) 111 | (if (= n 0) 112 | 1 113 | (* n ((f f) (- n 1))))))))))) 114 | 115 | (defun do-factorial () 116 | (evil '(((lambda (map) 117 | (lambda (factorial) 118 | ((map factorial) '(1 2 3 4 5 6 7 8 9 10)))) 119 | ((lambda (f) 120 | (f f)) 121 | (lambda (f) 122 | (lambda (g) 123 | (lambda (xs) 124 | (if xs 125 | (cons (g (car xs)) 126 | (((f f) g) (cdr xs))))))))) 127 | ((lambda (f) 128 | (f f)) 129 | (lambda (f) 130 | (lambda (n) 131 | (if (= n 0) 132 | 1 133 | (* n ((f f) (- n 1)))))))))) 134 | 135 | (defun do-factorial-d () 136 | (((lambda (map) 137 | (lambda (factorial) 138 | ((map factorial) '(1 2 3 4 5 6 7 8 9 10)))) 139 | ((lambda (f) 140 | (f f)) 141 | (lambda (f) 142 | (lambda (g) 143 | (lambda (xs) 144 | (if xs 145 | (cons (g (car xs)) 146 | (((f f) g) (cdr xs))))))))) 147 | ((lambda (f) 148 | (f f)) 149 | (lambda (f) 150 | (lambda (n) 151 | (if (= n 0) 152 | 1 153 | (* n ((f f) (- n 1))))))))) 154 | 155 | -------------------------------------------------------------------------------- /lisp/test.lisp: -------------------------------------------------------------------------------- 1 | (defmacro test (name &body tests) 2 | (let ((check (lambda (test) 3 | (let* ((op (car test)) 4 | (args (cdr test)) 5 | (defs (map (lambda (x) 6 | `(,(gensym) ,x)) 7 | args)) 8 | (vars (map head defs))) 9 | `(let ,defs 10 | (unless (,op ,@vars) 11 | (list :fail ',test (list ',op ,@vars)))))))) 12 | `(defun ,(intern (concat 'tests/ name)) () 13 | (or (or ,@(map check tests)) 14 | '(:pass))))) 15 | -------------------------------------------------------------------------------- /proc-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "spaik-proc-macros" 3 | version = "0.5.2" 4 | authors = ["Jonas Møller "] 5 | edition = "2021" 6 | description = "Procedural macros for SPAIK" 7 | license = "BSD-2-Clause" 8 | repository = "https://github.com/snyball/spaik" 9 | 10 | [lib] 11 | name = "spaik_proc_macros" 12 | proc-macro = true 13 | 14 | [dependencies] 15 | proc-macro2 = "1.0" 16 | quote = "1.0" 17 | syn = { version = "1.0", features = ["full"] } 18 | proc-macro-crate = "1.1.3" 19 | convert_case = "0.6.0" 20 | -------------------------------------------------------------------------------- /src/bin/minify.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, env::args, io::stdout, process::exit}; 2 | 3 | fn usage() -> ! { 4 | println!("Usage: minify "); 5 | exit(1); 6 | } 7 | 8 | fn main() -> Result<(), Box> { 9 | let mut argv = args(); 10 | let Some(_) = argv.next() else { usage() }; 11 | let Some(arg) = argv.next() else { usage() }; 12 | let code = fs::read_to_string(arg)?; 13 | spaik::minify(&code, &mut stdout().lock())?; 14 | Ok(()) 15 | } 16 | -------------------------------------------------------------------------------- /src/bin/readline-repl.rs: -------------------------------------------------------------------------------- 1 | use spaik::repl::REPL; 2 | 3 | fn main() { 4 | pretty_env_logger::init(); 5 | let mut repl = REPL::new(None); 6 | repl.readline_repl() 7 | } 8 | -------------------------------------------------------------------------------- /src/bin/run-lisp-tests.rs: -------------------------------------------------------------------------------- 1 | #[cfg(all(target_env = "musl", target_pointer_width = "64"))] 2 | use jemallocator::Jemalloc; 3 | #[cfg(all(target_env = "musl", target_pointer_width = "64"))] 4 | #[global_allocator] 5 | static GLOBAL: Jemalloc = Jemalloc; 6 | 7 | use spaik::run_tests; 8 | use std::process::exit; 9 | 10 | fn main() { 11 | exit(match run_tests() { 12 | Ok(_) => 0, 13 | Err(_) => 1 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /src/bin/scratch.rs: -------------------------------------------------------------------------------- 1 | /// Run spaik::scratch::main 2 | 3 | use spaik::scratch_main as scratch; 4 | 5 | fn main() -> Result<(), Box> { 6 | scratch() 7 | } 8 | -------------------------------------------------------------------------------- /src/builtins.rs: -------------------------------------------------------------------------------- 1 | //! Builtin Symbols 2 | 3 | use crate::nkgc::SymID; 4 | use crate::swym; 5 | use std::fmt::{Display, self, LowerHex}; 6 | use std::mem; 7 | #[cfg(feature = "freeze")] 8 | use serde::{Serialize, Deserialize}; 9 | 10 | macro_rules! builtins { 11 | ($(($sym:ident, $str:expr)),*) => { 12 | #[repr(u8)] 13 | #[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)] 14 | #[cfg_attr(feature = "freeze", derive(Serialize, Deserialize))] 15 | pub enum Builtin { $($sym),* } 16 | 17 | const NUM_BUILTINS: usize = count_args!($($sym),*); 18 | 19 | pub const BUILTIN_SYMBOLS: [&'static str; NUM_BUILTINS] = [ 20 | $($str),* 21 | ]; 22 | 23 | pub static BUILTIN_SYMS: [swym::Sym; NUM_BUILTINS] = [ 24 | $(crate::swym::Sym::from_static($str)),* 25 | ]; 26 | 27 | fn get_builtin(s: &str) -> Option { 28 | Some(match s { 29 | $($str => Builtin::$sym),*, 30 | _ => return None 31 | }) 32 | } 33 | } 34 | } 35 | 36 | builtins! { 37 | (Unknown, "?"), 38 | (ConsDot, "."), 39 | (AyyLmao, "ayy-lmao"), 40 | (SysLoadPath, "sys/load-path"), 41 | (SysLoad, "sys/load"), 42 | (LimitsMacroexpandRecursion, "limits/macroexpand-recursion"), 43 | (If, "if"), 44 | (Compile, "compile"), 45 | (LoopWithEpilogue, "loop-with-epilogue"), 46 | (ZSendMessage, "<ζ>-send-message"), 47 | (SymID, "sym-id"), 48 | (Continuation, "continuation"), 49 | (Keyword, "keyword"), 50 | (Join, "join"), 51 | (Error, "error"), 52 | (MakeSymbol, "make-symbol"), 53 | (Intern, "intern"), 54 | (Pow, "pow"), 55 | (Modulo, "%"), 56 | (And, "and"), 57 | (Or, "or"), 58 | (Set, "set"), 59 | (Quote, "quote"), 60 | (Quasi, "`"), 61 | (Unquote, ","), 62 | (USplice, ",@"), 63 | (Object, "object"), 64 | (Loop, "loop"), 65 | (Cdr, "cdr"), 66 | (Car, "car"), 67 | (Cons, "cons"), 68 | (Void, "void"), 69 | (Intr, "intr"), 70 | (List, "list"), 71 | (ArgList, "arg-list"), 72 | (Append, "append"), 73 | (AppendMut, "append!"), 74 | (Vector, "vec"), 75 | (Table, "table"), 76 | (Get, "get"), 77 | (Push, "push"), 78 | (Pop, "pop"), 79 | (Exit, "exit"), 80 | (Len, "len"), 81 | (Lambda, "lambda"), 82 | (GreekLambda, "λ"), 83 | (Apply, "apply"), 84 | (MethodCall, "method-call"), 85 | (True, "true"), 86 | (False, "false"), 87 | (Add, "+"), 88 | (Tag, "tag"), 89 | (MakeTable, "make-table"), 90 | (Sub, "-"), 91 | (Div, "/"), 92 | (Mul, "*"), 93 | (Gt, ">"), 94 | (Lt, "<"), 95 | (Lte, "<="), 96 | (Gte, ">="), 97 | (Eq, "="), 98 | (Cmp, "cmp"), 99 | (Eqp, "eq?"), 100 | (Not, "not"), 101 | (Define, "define"), 102 | (Progn, "progn"), 103 | (Catch, "catch"), 104 | (Throw, "throw"), 105 | (ArgOptional, "&opt"), 106 | (ArgBody, "&body"), 107 | (ArgRest, "&rest"), 108 | (KwPass, ":pass"), 109 | (KwFail, ":fail"), 110 | (KwOk, ":ok"), 111 | (Fail, "fail"), 112 | (Symbol, "symbol"), 113 | (Label, "label"), 114 | (Char, "char"), 115 | (Id, "id"), 116 | (RigidBody, "rigid-body"), 117 | (CallCC, "call/cc"), 118 | (Integer, "integer"), 119 | (String, "string"), 120 | (Ref, "ref"), 121 | (Next, "next"), 122 | (Break, "break"), 123 | (Number, "number"), 124 | (UnsignedInteger, "unsigned-integer"), 125 | (Float, "float"), 126 | (Bool, "bool"), 127 | (HaltFunc, "<ζ>-halt"), 128 | (IP, "<ζ>-ip"), 129 | (Frame, "<ζ>-frame"), 130 | (LambdaObject, "<ζ>-lambda-object"), 131 | (IterStop, "<ζ>-iter-stop"), 132 | (ZCore, "<ζ>-core"), 133 | (Subr, "subr"), 134 | (Nil, "nil"), 135 | (Callable, "callable"), 136 | (Iter, "iter"), 137 | (Vec2, "vec2"), 138 | (Vec3, "vec3"), 139 | (Vec4, "vec4"), 140 | (Mat2, "mat2"), 141 | (Mat3, "mat3"), 142 | (Mat4, "mat4"), 143 | (Quat, "quat"), 144 | // NOTE: The zero-length string ε, *must* be a static builtin. Static 145 | // symbols (e.g builtins) all have `sz: 0`, regardless of length. This 146 | // system for telling static and dynamically allocated strings apart fails 147 | // if ɛ can be created during runtime. 148 | (Epsilon, "") 149 | } 150 | 151 | #[allow(unused)] 152 | struct Hex(pub T) where T: LowerHex; 153 | 154 | impl Display for Hex where T: LowerHex { 155 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 156 | write!(f, "{:#x}", self.0) 157 | } 158 | } 159 | 160 | impl fmt::Debug for Hex where T: LowerHex { 161 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 162 | write!(f, "{:#x}", self.0) 163 | } 164 | } 165 | 166 | impl Builtin { 167 | pub fn from_sym(SymID(p): SymID) -> Option { 168 | // Check if the pointer `p` is inside the static `BUILTIN_SYMS` array, 169 | // get its index, and transmute that into a Builtin. 170 | let p = p as usize; 171 | let buf = &BUILTIN_SYMS[0] as *const swym::Sym; 172 | let start = buf as usize; 173 | let end = unsafe { buf.add(NUM_BUILTINS) } as usize; 174 | (p >= start && p < end).then(|| unsafe { 175 | mem::transmute(((p - start) / mem::size_of::()) as u8) 176 | }) 177 | } 178 | 179 | pub fn as_str(&self) -> &'static str { 180 | let idx: u8 = unsafe { mem::transmute(*self) }; 181 | BUILTIN_SYMBOLS[idx as usize] 182 | } 183 | 184 | pub(crate) fn sym_id(&self) -> swym::SymID { 185 | let id: u8 = unsafe { mem::transmute(*self) }; 186 | let idx: usize = id.into(); 187 | let rf: &'static swym::Sym = &BUILTIN_SYMS[idx]; 188 | swym::SymID::new(rf as *const swym::Sym as *mut swym::Sym) 189 | } 190 | 191 | pub fn sym(&self) -> swym::SymRef { 192 | let id: u8 = unsafe { mem::transmute(*self) }; 193 | let idx: usize = id.into(); 194 | (&BUILTIN_SYMS[idx]).into() 195 | } 196 | 197 | pub fn from>(s: T) -> Option { 198 | get_builtin(s.as_ref()) 199 | } 200 | } 201 | 202 | impl std::fmt::Display for Builtin { 203 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { 204 | write!(f, "{}", self.as_str()) 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/chasm.rs: -------------------------------------------------------------------------------- 1 | //! ChASM /ˈkæz(ə)m/, an assembler 2 | 3 | use crate::Builtin; 4 | use crate::nkgc::SymID; 5 | use std::io::{Read, Write, self}; 6 | use std::fmt::{self, Display}; 7 | use crate::error::{ErrorKind, Result}; 8 | use std::convert::{TryInto, TryFrom}; 9 | use crate::utils::{HMap, HSet}; 10 | 11 | pub type OpCode = u8; 12 | 13 | macro_rules! chasm_primitives { 14 | ($($t:ident),+) => { 15 | #[allow(non_camel_case_types)] 16 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 17 | pub enum ASMPV { 18 | $($t($t)),+ 19 | } 20 | 21 | impl fmt::Display for ASMPV { 22 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 23 | match self { $(ASMPV::$t(v) => write!(f, "{}", v)),+ } 24 | } 25 | } 26 | 27 | $(impl From<$t> for ASMPV { 28 | fn from(v: $t) -> ASMPV { ASMPV::$t(v) } 29 | })+ 30 | 31 | fn try_into_err(from: &'static str, 32 | to: &'static str, 33 | val: &dyn std::fmt::Display) -> $crate::error::Error { 34 | error!(ConversionError, from, to, val: format!("{}", val)) 35 | } 36 | 37 | fn try_into_asmpv + )+>(pv: ASMPV) -> Result { 38 | match pv { 39 | $(ASMPV::$t(v) => v 40 | .try_into() 41 | .map_err(|_| try_into_err(stringify!($t), 42 | std::any::type_name::(), 43 | &v).into())),+ 44 | } 45 | } 46 | 47 | impl ASMPV { 48 | pub fn add_mut(&mut self, n: isize) -> Result<()> { 49 | match self { 50 | // This is written in a really strange way to aid 51 | // type-inference. Rust seems to not be able to figure out 52 | // either `*v += n.try_into()?` or `*v = *v + 53 | // n.try_into()?`. 54 | $(ASMPV::$t(ref mut v) => { let k = *v; 55 | *v = n.try_into()?; 56 | *v += k; }),+ 57 | } 58 | Ok(()) 59 | } 60 | } 61 | 62 | $(impl From<$t> for Arg { 63 | fn from(v: $t) -> Arg { Arg::ASMPV(ASMPV::$t(v)) } 64 | })+ 65 | 66 | $(impl std::convert::TryFrom for $t { 67 | type Error = crate::error::Error; 68 | fn try_from(v: ASMPV) -> std::result::Result { 69 | try_into_asmpv::<$t>(v) 70 | } 71 | })+ 72 | } 73 | } 74 | 75 | chasm_primitives![u8, i8, 76 | u16, i16, 77 | u32, i32, 78 | usize, isize]; 79 | 80 | #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] 81 | pub struct Lbl(u32, &'static str); 82 | 83 | impl fmt::Display for Lbl { 84 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 85 | write!(f, "{}#{}", self.1, self.0) 86 | } 87 | } 88 | 89 | #[derive(Debug, Clone)] 90 | pub enum Arg { 91 | Lbl(Lbl), 92 | AbsLbl(Lbl), 93 | ASMPV(ASMPV), 94 | } 95 | 96 | impl Arg { 97 | pub fn add_mut(&mut self, v: isize) -> Result<()> { 98 | match self { 99 | Self::Lbl(_) | Self::AbsLbl(_) => bail!(TypeError { expect: Builtin::Number, 100 | got: Builtin::Label }), 101 | Self::ASMPV(pv) => pv.add_mut(v) 102 | } 103 | } 104 | } 105 | 106 | impl From for Arg { 107 | fn from(v: Lbl) -> Self { Arg::Lbl(v) } 108 | } 109 | impl From for Arg { 110 | fn from(v: SymID) -> Self { Arg::ASMPV(v.as_int().into()) } 111 | } 112 | 113 | #[derive(Debug, Clone)] 114 | pub struct ChOp { 115 | pub id: T, 116 | pub args: Vec, 117 | } 118 | 119 | impl ChOp { 120 | pub fn new(id: T, args: Vec) -> ChOp { 121 | ChOp { id, args } 122 | } 123 | } 124 | 125 | impl Display for ChOp { 126 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 127 | let args = self.args 128 | .iter() 129 | .map(|v| format!("{:?}", v)) 130 | .collect::>() 131 | .join(", "); 132 | write!(f, "{} {}", self.id, args) 133 | } 134 | } 135 | 136 | pub trait ASMOp { 137 | type OpName: ChASMOpName; 138 | 139 | fn new(op: Self::OpName, args: &[ASMPV]) -> Result 140 | where Self: std::marker::Sized; 141 | fn name(&self) -> &'static str; 142 | fn args(&self) -> Vec; 143 | fn write(&self, out: &mut dyn Write) -> io::Result; 144 | fn read(inp: &mut dyn Read) -> Result<(Self, usize)> 145 | where Self: std::marker::Sized; 146 | } 147 | 148 | #[allow(unused)] 149 | pub trait ChASMOpName { 150 | fn dialect(&self) -> &'static str; 151 | fn id(&self) -> OpCode; 152 | fn from_num(num: OpCode) -> Result 153 | where Self: std::marker::Sized; 154 | fn from_str(s: &str) -> Option 155 | where Self: std::marker::Sized; 156 | } 157 | 158 | macro_rules! chasm_def { 159 | ( $name:ident : $($en:ident($($arg:ident : $targ:ty),*)),+ ) => { 160 | pub mod $name { 161 | #[allow(unused_imports)] 162 | use super::*; 163 | #[allow(unused_imports)] 164 | use $crate::chasm::*; 165 | 166 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 167 | pub enum Op { 168 | $($en($($targ),*)),+ 169 | } 170 | 171 | #[repr(u8)] 172 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 173 | pub enum OpName { 174 | $($en),+ 175 | } 176 | 177 | impl $crate::chasm::ChASMOpName for OpName { 178 | fn dialect(&self) -> &'static str { stringify!($name) } 179 | fn id(&self) -> $crate::chasm::OpCode { 180 | unsafe { std::mem::transmute(*self) } 181 | } 182 | fn from_num(id: $crate::chasm::OpCode) -> Result { 183 | use $crate::error::ErrorKind::*; 184 | if id < count_args!($($en),+) { 185 | Ok(unsafe { std::mem::transmute(id) }) 186 | } else { 187 | Err(IDError { id: id as usize }.into()) 188 | } 189 | } 190 | fn from_str(s: &str) -> Option { 191 | match s { 192 | $(stringify!($en) => Some(OpName::$en)),+, 193 | _ => None 194 | } 195 | } 196 | } 197 | } 198 | 199 | fn op_arg_err(got: usize, expect: usize, name: &'static str) -> Error { 200 | error!(ArgError, 201 | expect: ArgSpec::normal(expect.try_into().unwrap()), 202 | got_num: got.try_into().unwrap()).sop(name) 203 | } 204 | 205 | impl $crate::chasm::ASMOp for $name::Op { 206 | type OpName = $name::OpName; 207 | 208 | fn new(op: Self::OpName, args: &[ASMPV]) -> Result<$name::Op> { 209 | use std::convert::TryInto; 210 | let len = args.len(); 211 | match op { 212 | $($name::OpName::$en => if let [$($arg),*] = args { 213 | Ok($name::Op::$en($((*$arg).try_into()?),*)) 214 | } else { 215 | Err(op_arg_err(len, 216 | count_args!($($arg),*), 217 | stringify!($en))) 218 | }),+ 219 | } 220 | } 221 | 222 | fn name(&self) -> &'static str { 223 | match self { 224 | $($name::Op::$en(..) => stringify!($en)),+ 225 | } 226 | } 227 | 228 | fn args(&self) -> Vec { 229 | match self { 230 | $($name::Op::$en($($arg),*) => { 231 | [$((*$arg).into()),*].iter() 232 | .cloned() 233 | .collect::>() 234 | }),+ 235 | } 236 | } 237 | 238 | fn write(&self, out: &mut dyn std::io::Write) -> std::io::Result { 239 | let mut sz = std::mem::size_of::<$crate::chasm::OpCode>(); 240 | let dscr: usize = unsafe { 241 | std::mem::transmute(std::mem::discriminant(self)) 242 | }; 243 | assert!(dscr < 256); 244 | let op: $crate::chasm::OpCode = dscr as u8; 245 | out.write_all(&op.to_ne_bytes())?; 246 | match self { 247 | $($name::Op::$en($($arg),*) => { 248 | $(let buf = $arg.to_ne_bytes(); 249 | out.write_all(&buf)?; 250 | sz += buf.len();)* 251 | })+ 252 | } 253 | Ok(sz) 254 | } 255 | 256 | fn read(inp: &mut dyn std::io::Read) -> Result<(Self, usize)> { 257 | let mut rd_sz = std::mem::size_of::<$crate::chasm::OpCode>(); 258 | let mut op_buf: [u8; 1] = [0]; 259 | inp.read_exact(&mut op_buf)?; 260 | let op = match $name::OpName::from_num(op_buf[0])? { 261 | $($name::OpName::$en => { 262 | $(rd_sz += std::mem::size_of::<$targ>();)* 263 | $(let mut $arg: [u8; std::mem::size_of::<$targ>()] = Default::default(); 264 | inp.read_exact(&mut $arg)?;)* 265 | $name::Op::$en($(unsafe { std::mem::transmute($arg) }),*) 266 | }),+ 267 | }; 268 | Ok((op, rd_sz)) 269 | } 270 | } 271 | 272 | impl std::fmt::Display for $name::Op { 273 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> 274 | fmt::Result 275 | { 276 | use std::convert::TryInto; 277 | match self { 278 | $( 279 | $name::Op::$en($($arg),*) => { 280 | let args_v = [$((*$arg).try_into().unwrap()),*] 281 | .iter() 282 | .map(|&s: &i64| s.to_string()) 283 | .collect::>(); 284 | write!(f, "{} {}", stringify!($en).to_ascii_lowercase(), 285 | args_v.join(", ")) 286 | } 287 | ),+ 288 | } 289 | } 290 | } 291 | } 292 | } 293 | 294 | #[derive(Debug, Clone)] 295 | pub struct ChASM { 296 | ops: Vec>, 297 | label_names: Vec<&'static str>, 298 | marks: HMap, 299 | } 300 | 301 | impl Default for ChASM { 302 | fn default() -> Self { 303 | Self { 304 | ops: Default::default(), 305 | label_names: Default::default(), 306 | marks: Default::default() 307 | } 308 | } 309 | } 310 | 311 | pub type LblMap = HMap; 312 | 313 | impl ChASM { 314 | #[allow(dead_code)] 315 | pub fn new() -> ChASM { 316 | Default::default() 317 | } 318 | 319 | pub fn link_into(self, 320 | out: &mut Vec, 321 | sz: usize, 322 | hm_out: &mut LblMap) -> Result 323 | { 324 | for (lbl, tgt) in self.marks.iter() { 325 | hm_out.insert((*tgt + sz as isize) as u32, 326 | Lbl(*lbl, self.label_names[*lbl as usize])); 327 | } 328 | let labels = self.marks; 329 | let it = 330 | self.ops 331 | .into_iter() 332 | .enumerate() 333 | .map(|(i, op)| -> Result { 334 | let link_err = |sym, count| { 335 | ErrorKind::LinkError { dst: format!("{}#{}", sym, count), 336 | src: i } 337 | }; 338 | let args = op.args 339 | .into_iter() 340 | .map(|arg| match arg { 341 | Arg::Lbl(Lbl(c, s)) => 342 | labels.get(&c) 343 | .map(|pos| ASMPV::i32(*pos as i32 - (i as i32))) 344 | .ok_or_else(|| link_err(s, c)), 345 | Arg::AbsLbl(Lbl(c, s)) => 346 | labels.get(&c) 347 | .map(|pos| ASMPV::u32(*pos as u32 + sz as u32)) 348 | .ok_or_else(|| link_err(s, c)), 349 | Arg::ASMPV(pv) => Ok(pv) 350 | }).collect::, _>>()?; 351 | T::new(op.id, &args[..]) 352 | }); 353 | let mut len = 0; 354 | for asm in it { 355 | out.push(asm?); 356 | len += 1; 357 | } 358 | Ok(len) 359 | } 360 | 361 | pub fn add(&mut self, op: T::OpName, args: &[Arg]) { 362 | self.ops.push(ChOp::new(op, Vec::from(args))) 363 | } 364 | 365 | pub fn op(&mut self, op: ChOp) -> usize { 366 | let len = self.ops.len(); 367 | self.ops.push(op); 368 | len 369 | } 370 | 371 | pub fn label(&mut self, text: &'static str) -> Lbl { 372 | let idx = self.label_names.len(); 373 | self.label_names.push(text); 374 | Lbl(idx as u32, text) 375 | } 376 | 377 | pub fn mark(&mut self, lbl: Lbl) { 378 | self.marks.insert(lbl.0, self.ops.len() as isize); 379 | } 380 | 381 | pub fn pop(&mut self) { 382 | self.ops.pop(); 383 | } 384 | 385 | pub fn len(&self) -> usize { 386 | self.ops.len() 387 | } 388 | 389 | #[allow(dead_code)] 390 | pub fn is_empty(&self) -> bool { 391 | self.len() == 0 392 | } 393 | 394 | pub fn last_mut(&mut self) -> Option<&mut ChOp> { 395 | self.ops.last_mut() 396 | } 397 | } 398 | 399 | macro_rules! chasm { 400 | ($op:ident $($arg:expr),*) => { 401 | ChOp::new($op, vec![$($arg.into()),*]) 402 | }; 403 | } 404 | 405 | #[cfg(test)] 406 | mod tests { 407 | use super::*; 408 | use crate::r8vm::{R8VM, r8c}; 409 | use std::io::Cursor; 410 | 411 | #[test] 412 | fn read_read_op() { 413 | use crate::r8vm::r8c::Op as R8C; 414 | use crate::r8vm::r8c::OpName::*; 415 | assert_eq!(R8C::new(JMP, &[123i32.into()]), Ok(R8C::JMP(123))) 416 | } 417 | 418 | #[test] 419 | fn primitive_type_conversions() { 420 | let pv_big = ASMPV::u32(260); 421 | let v_big: Result = pv_big.try_into(); 422 | assert_eq!(v_big.map_err(|e| e.kind().clone()), Err(ErrorKind::ConversionError { 423 | from: "u32", 424 | to: "u8", 425 | val: String::from("260") 426 | })); 427 | } 428 | 429 | #[test] 430 | fn read_write_asm() { 431 | let vm = R8VM::no_std(); 432 | let pmem = vm.pmem(); 433 | let mut pmem_out = Vec::::new(); 434 | for op in pmem.iter() { 435 | op.write(&mut pmem_out).unwrap(); 436 | } 437 | let len: u64 = pmem_out.len().try_into().unwrap(); 438 | let mut pmem_in = Cursor::new(pmem_out); 439 | let mut pmem_2 = Vec::new(); 440 | while pmem_in.position() < len { 441 | let (op, _) = r8c::Op::read(&mut pmem_in).unwrap(); 442 | pmem_2.push(op); 443 | } 444 | assert_eq!(pmem, &pmem_2); 445 | } 446 | } 447 | -------------------------------------------------------------------------------- /src/deserialize.rs: -------------------------------------------------------------------------------- 1 | //! Ser(de)serialization for runtime SPAIK values 2 | 3 | use std::marker::PhantomData; 4 | 5 | use serde::Deserialize; 6 | use serde::de::{ 7 | self, DeserializeSeed, EnumAccess, MapAccess, SeqAccess, 8 | VariantAccess, Visitor, 9 | }; 10 | 11 | use crate::builtins::Builtin; 12 | use crate::error::Error; 13 | 14 | pub type Result = std::result::Result; 15 | use crate::nkgc::PV; 16 | 17 | pub struct Deserializer<'de> { 18 | // This string starts with the input data and characters are truncated off 19 | // the beginning as data is parsed. 20 | input: PV, 21 | _phantom: PhantomData<&'de str> 22 | } 23 | 24 | impl<'de> Deserializer<'de> { 25 | // By convention, `Deserializer` constructors are named like `from_xyz`. 26 | // That way basic use cases are satisfied by something like 27 | // `serde_json::from_str(...)` while advanced use cases that require a 28 | // deserializer can make one with `serde_json::Deserializer::from_str(...)`. 29 | pub fn from_pv(input: PV) -> Self { 30 | Deserializer { input, _phantom: Default::default() } 31 | } 32 | } 33 | 34 | // By convention, the public API of a Serde deserializer is one or more 35 | // `from_xyz` methods such as `from_str`, `from_bytes`, or `from_reader` 36 | // depending on what Rust types the deserializer is able to consume as input. 37 | // 38 | // This basic deserializer supports only `from_str`. 39 | pub fn from_pv<'a, 'de: 'a, T>(s: PV) -> Result 40 | where 41 | T: Deserialize<'a>, 42 | { 43 | let mut deserializer = Deserializer::from_pv(s); 44 | let t = T::deserialize(&mut deserializer)?; 45 | Ok(t) 46 | } 47 | 48 | // SERDE IS NOT A PARSING LIBRARY. This impl block defines a few basic parsing 49 | // functions from scratch. More complicated formats may wish to use a dedicated 50 | // parsing library to help implement their Serde deserializer. 51 | impl<'de> Deserializer<'de> { 52 | } 53 | 54 | impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { 55 | type Error = Error; 56 | 57 | // Look at the input data to decide what Serde data model type to 58 | // deserialize as. Not all data formats are able to support this operation. 59 | // Formats that support `deserialize_any` are known as self-describing. 60 | fn deserialize_any(self, visitor: V) -> Result 61 | where 62 | V: Visitor<'de>, 63 | { 64 | match self.input { 65 | PV::Ref(_) => todo!(), 66 | PV::Sym(_) => self.deserialize_identifier(visitor), 67 | PV::Int(_) => self.deserialize_i32(visitor), 68 | PV::UInt(_) => self.deserialize_u64(visitor), 69 | PV::Real(_) => self.deserialize_f32(visitor), 70 | PV::Bool(_) => self.deserialize_bool(visitor), 71 | PV::Char(_) => self.deserialize_char(visitor), 72 | PV::Id(_) => self.deserialize_u64(visitor), 73 | PV::RigidBody(_) => self.deserialize_u64(visitor), 74 | #[cfg(feature = "math")] 75 | PV::Vec2(_) => self.deserialize_tuple(2, visitor), 76 | #[cfg(feature = "math")] 77 | PV::Vec3(_) => self.deserialize_tuple(3, visitor), 78 | PV::Nil => self.deserialize_unit(visitor), 79 | } 80 | } 81 | 82 | // Uses the `parse_bool` parsing function defined above to read the JSON 83 | // identifier `true` or `false` from the input. 84 | // 85 | // Parsing refers to looking at the input and deciding that it contains the 86 | // JSON value `true` or `false`. 87 | // 88 | // Deserialization refers to mapping that JSON value into Serde's data 89 | // model by invoking one of the `Visitor` methods. In the case of JSON and 90 | // bool that mapping is straightforward so the distinction may seem silly, 91 | // but in other cases Deserializers sometimes perform non-obvious mappings. 92 | // For example the TOML format has a Datetime type and Serde's data model 93 | // does not. In the `toml` crate, a Datetime in the input is deserialized by 94 | // mapping it to a Serde data model "struct" type with a special name and a 95 | // single field containing the Datetime represented as a string. 96 | fn deserialize_bool(self, visitor: V) -> Result 97 | where 98 | V: Visitor<'de>, 99 | { 100 | visitor.visit_bool(self.input.into()) 101 | } 102 | 103 | // The `parse_signed` function is generic over the integer type `T` so here 104 | // it is invoked with `T=i8`. The next 8 methods are similar. 105 | fn deserialize_i8(self, visitor: V) -> Result 106 | where 107 | V: Visitor<'de>, 108 | { 109 | visitor.visit_i8(self.input.try_into()?) 110 | } 111 | 112 | fn deserialize_i16(self, visitor: V) -> Result 113 | where 114 | V: Visitor<'de>, 115 | { 116 | visitor.visit_i16(self.input.try_into()?) 117 | } 118 | 119 | fn deserialize_i32(self, visitor: V) -> Result 120 | where 121 | V: Visitor<'de>, 122 | { 123 | visitor.visit_i32(self.input.try_into()?) 124 | } 125 | 126 | fn deserialize_i64(self, visitor: V) -> Result 127 | where 128 | V: Visitor<'de>, 129 | { 130 | visitor.visit_i64(self.input.try_into()?) 131 | } 132 | 133 | fn deserialize_u8(self, visitor: V) -> Result 134 | where 135 | V: Visitor<'de>, 136 | { 137 | visitor.visit_u8(self.input.try_into()?) 138 | } 139 | 140 | fn deserialize_u16(self, visitor: V) -> Result 141 | where 142 | V: Visitor<'de>, 143 | { 144 | visitor.visit_u16(self.input.try_into()?) 145 | } 146 | 147 | fn deserialize_u32(self, visitor: V) -> Result 148 | where 149 | V: Visitor<'de>, 150 | { 151 | visitor.visit_u32(self.input.try_into()?) 152 | } 153 | 154 | fn deserialize_u64(self, visitor: V) -> Result 155 | where 156 | V: Visitor<'de>, 157 | { 158 | visitor.visit_u64(self.input.try_into()?) 159 | } 160 | 161 | // Float parsing is stupidly hard. 162 | fn deserialize_f32(self, visitor: V) -> Result 163 | where 164 | V: Visitor<'de>, 165 | { 166 | visitor.visit_f32(self.input.try_into()?) 167 | } 168 | 169 | // Float parsing is stupidly hard. 170 | fn deserialize_f64(self, visitor: V) -> Result 171 | where 172 | V: Visitor<'de>, 173 | { 174 | visitor.visit_f64(self.input.try_into()?) 175 | } 176 | 177 | // The `Serializer` implementation on the previous page serialized chars as 178 | // single-character strings so handle that representation here. 179 | fn deserialize_char(self, visitor: V) -> Result 180 | where 181 | V: Visitor<'de>, 182 | { 183 | // Parse a string, check that it is one character, call `visit_char`. 184 | visitor.visit_char(self.input.try_into()?) 185 | } 186 | 187 | // Refer to the "Understanding deserializer lifetimes" page for information 188 | // about the three deserialization flavors of strings in Serde. 189 | fn deserialize_str(self, visitor: V) -> Result 190 | where 191 | V: Visitor<'de>, 192 | { 193 | with_ref!(self.input, String(s) => { visitor.visit_borrowed_str((*s).as_ref()) }) 194 | } 195 | 196 | fn deserialize_string(self, visitor: V) -> Result 197 | where 198 | V: Visitor<'de>, 199 | { 200 | self.deserialize_str(visitor) 201 | } 202 | 203 | // The `Serializer` implementation on the previous page serialized byte 204 | // arrays as JSON arrays of bytes. Handle that representation here. 205 | fn deserialize_bytes(self, _visitor: V) -> Result 206 | where 207 | V: Visitor<'de>, 208 | { 209 | unimplemented!() 210 | } 211 | 212 | fn deserialize_byte_buf(self, _visitor: V) -> Result 213 | where 214 | V: Visitor<'de>, 215 | { 216 | unimplemented!() 217 | } 218 | 219 | // An absent optional is represented as the JSON `null` and a present 220 | // optional is represented as just the contained value. 221 | // 222 | // As commented in `Serializer` implementation, this is a lossy 223 | // representation. For example the values `Some(())` and `None` both 224 | // serialize as just `null`. Unfortunately this is typically what people 225 | // expect when working with JSON. Other formats are encouraged to behave 226 | // more intelligently if possible. 227 | fn deserialize_option(self, visitor: V) -> Result 228 | where 229 | V: Visitor<'de>, 230 | { 231 | if self.input == PV::Nil { 232 | visitor.visit_none() 233 | } else { 234 | visitor.visit_some(self) 235 | } 236 | } 237 | 238 | // In Serde, unit means an anonymous value containing no data. 239 | fn deserialize_unit(self, _visitor: V) -> Result 240 | where 241 | V: Visitor<'de>, 242 | { 243 | todo!() 244 | } 245 | 246 | // Unit struct means a named value containing no data. 247 | fn deserialize_unit_struct( 248 | self, 249 | _name: &'static str, 250 | _visitor: V, 251 | ) -> Result 252 | where 253 | V: Visitor<'de>, 254 | { 255 | todo!() 256 | } 257 | 258 | // As is done here, serializers are encouraged to treat newtype structs as 259 | // insignificant wrappers around the data they contain. That means not 260 | // parsing anything other than the contained value. 261 | fn deserialize_newtype_struct( 262 | self, 263 | _name: &'static str, 264 | _visitor: V, 265 | ) -> Result 266 | where 267 | V: Visitor<'de>, 268 | { 269 | todo!() 270 | } 271 | 272 | // Deserialization of compound types like sequences and maps happens by 273 | // passing the visitor an "Access" object that gives it the ability to 274 | // iterate through the data contained in the sequence. 275 | fn deserialize_seq(self, visitor: V) -> Result 276 | where 277 | V: Visitor<'de>, 278 | { 279 | visitor.visit_seq(CommaSeparated::new(self)) 280 | } 281 | 282 | // Tuples look just like sequences in JSON. Some formats may be able to 283 | // represent tuples more efficiently. 284 | // 285 | // As indicated by the length parameter, the `Deserialize` implementation 286 | // for a tuple in the Serde data model is required to know the length of the 287 | // tuple before even looking at the input data. 288 | fn deserialize_tuple(self, _len: usize, visitor: V) -> Result 289 | where 290 | V: Visitor<'de>, 291 | { 292 | visitor.visit_seq(CommaSeparated::new(self)) 293 | } 294 | 295 | // Tuple structs look just like sequences in JSON. 296 | fn deserialize_tuple_struct( 297 | self, 298 | _name: &'static str, 299 | _len: usize, 300 | visitor: V, 301 | ) -> Result 302 | where 303 | V: Visitor<'de>, 304 | { 305 | visitor.visit_seq(CommaSeparated::new(self)) 306 | } 307 | 308 | // Much like `deserialize_seq` but calls the visitors `visit_map` method 309 | // with a `MapAccess` implementation, rather than the visitor's `visit_seq` 310 | // method with a `SeqAccess` implementation. 311 | fn deserialize_map(self, visitor: V) -> Result 312 | where 313 | V: Visitor<'de>, 314 | { 315 | visitor.visit_map(CommaSeparated::new(self)) 316 | } 317 | 318 | // Structs look just like maps in JSON. 319 | // 320 | // Notice the `fields` parameter - a "struct" in the Serde data model means 321 | // that the `Deserialize` implementation is required to know what the fields 322 | // are before even looking at the input data. Any key-value pairing in which 323 | // the fields cannot be known ahead of time is probably a map. 324 | fn deserialize_struct( 325 | self, 326 | _name: &'static str, 327 | _fields: &'static [&'static str], 328 | visitor: V, 329 | ) -> Result 330 | where 331 | V: Visitor<'de>, 332 | { 333 | visitor.visit_map(CommaSeparated::new(self)) 334 | } 335 | 336 | fn deserialize_enum( 337 | self, 338 | _name: &'static str, 339 | _variants: &'static [&'static str], 340 | visitor: V, 341 | ) -> Result 342 | where 343 | V: Visitor<'de>, 344 | { 345 | visitor.visit_enum(Enum::new(self)) 346 | } 347 | 348 | // An identifier in Serde is the type that identifies a field of a struct or 349 | // the variant of an enum. In JSON, struct fields and enum variants are 350 | // represented as strings. In other formats they may be represented as 351 | // numeric indices. 352 | fn deserialize_identifier(self, visitor: V) -> Result 353 | where 354 | V: Visitor<'de>, 355 | { 356 | if let Some(sym) = self.input.op() { 357 | visitor.visit_str(sym.as_ref()) 358 | } else if let PV::Sym(sym) = self.input { 359 | if let Some(ident) = sym.as_ref().strip_prefix(':') { 360 | visitor.visit_str(ident) 361 | } else { 362 | Err(error!(TypeError, 363 | expect: Builtin::Keyword, 364 | got: Builtin::Symbol)) 365 | } 366 | } else { 367 | Err(error!(TypeError, 368 | expect: Builtin::Symbol, 369 | got: self.input.bt_type_of())) 370 | } 371 | } 372 | 373 | // Like `deserialize_any` but indicates to the `Deserializer` that it makes 374 | // no difference which `Visitor` method is called because the data is 375 | // ignored. 376 | // 377 | // Some deserializers are able to implement this more efficiently than 378 | // `deserialize_any`, for example by rapidly skipping over matched 379 | // delimiters without paying close attention to the data in between. 380 | // 381 | // Some formats are not able to implement this at all. Formats that can 382 | // implement `deserialize_any` and `deserialize_ignored_any` are known as 383 | // self-describing. 384 | fn deserialize_ignored_any(self, visitor: V) -> Result 385 | where 386 | V: Visitor<'de>, 387 | { 388 | self.deserialize_any(visitor) 389 | } 390 | } 391 | 392 | // In order to handle commas correctly when deserializing a JSON array or map, 393 | // we need to track whether we are on the first element or past the first 394 | // element. 395 | struct CommaSeparated<'a, 'de: 'a> { 396 | de: &'a mut Deserializer<'de>, 397 | } 398 | 399 | impl<'a, 'de> CommaSeparated<'a, 'de> { 400 | fn new(de: &'a mut Deserializer<'de>) -> Self { 401 | CommaSeparated { 402 | de, 403 | } 404 | } 405 | } 406 | 407 | // `SeqAccess` is provided to the `Visitor` to give it the ability to iterate 408 | // through elements of the sequence. 409 | impl<'de, 'a> SeqAccess<'de> for CommaSeparated<'a, 'de> { 410 | type Error = Error; 411 | 412 | fn next_element_seed(&mut self, seed: T) -> Result> 413 | where 414 | T: DeserializeSeed<'de>, 415 | { 416 | // Check if there are no more elements. 417 | if self.de.input == PV::Nil { 418 | return Ok(None); 419 | } 420 | // Deserialize an array element. 421 | let mut head = Deserializer::from_pv(self.de.input.car().unwrap()); 422 | let res = seed.deserialize(&mut head).map(Some); 423 | self.de.input = self.de.input.cdr().unwrap(); 424 | res 425 | } 426 | } 427 | 428 | // `MapAccess` is provided to the `Visitor` to give it the ability to iterate 429 | // through entries of the map. 430 | impl<'de, 'a> MapAccess<'de> for CommaSeparated<'a, 'de> { 431 | type Error = Error; 432 | 433 | fn next_key_seed(&mut self, seed: K) -> Result> 434 | where 435 | K: DeserializeSeed<'de>, 436 | { 437 | // Check if there are no more elements. 438 | if self.de.input == PV::Nil { 439 | return Ok(None); 440 | } 441 | // Deserialize an array element. 442 | let mut head = Deserializer::from_pv(self.de.input.car().unwrap()); 443 | let res = seed.deserialize(&mut head).map(Some); 444 | self.de.input = self.de.input.cdr().unwrap(); 445 | res 446 | } 447 | 448 | fn next_value_seed(&mut self, seed: V) -> Result 449 | where 450 | V: DeserializeSeed<'de>, 451 | { 452 | let mut head = Deserializer::from_pv(self.de.input.car().unwrap()); 453 | let res = seed.deserialize(&mut head); 454 | self.de.input = self.de.input.cdr().unwrap(); 455 | res 456 | } 457 | } 458 | 459 | struct Enum<'a, 'de: 'a> { 460 | de: &'a mut Deserializer<'de>, 461 | } 462 | 463 | impl<'a, 'de> Enum<'a, 'de> { 464 | fn new(de: &'a mut Deserializer<'de>) -> Self { 465 | Enum { de } 466 | } 467 | } 468 | 469 | // `EnumAccess` is provided to the `Visitor` to give it the ability to determine 470 | // which variant of the enum is supposed to be deserialized. 471 | // 472 | // Note that all enum deserialization methods in Serde refer exclusively to the 473 | // "externally tagged" enum representation. 474 | impl<'de, 'a> EnumAccess<'de> for Enum<'a, 'de> { 475 | type Error = Error; 476 | type Variant = Self; 477 | 478 | fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant)> 479 | where 480 | V: DeserializeSeed<'de>, 481 | { 482 | // The `deserialize_enum` method parsed a `{` character so we are 483 | // currently inside of a map. The seed will be deserializing itself from 484 | // the key of the map. 485 | let val = seed.deserialize(&mut *self.de)?; 486 | Ok((val, self)) 487 | } 488 | } 489 | 490 | // `VariantAccess` is provided to the `Visitor` to give it the ability to see 491 | // the content of the single variant that it decided to deserialize. 492 | impl<'de, 'a> VariantAccess<'de> for Enum<'a, 'de> { 493 | type Error = Error; 494 | 495 | // If the `Visitor` expected this variant to be a unit variant, the input 496 | // should have been the plain string case handled in `deserialize_enum`. 497 | fn unit_variant(self) -> Result<()> { 498 | Ok(()) 499 | } 500 | 501 | // Newtype variants are represented in JSON as `{ NAME: VALUE }` so 502 | // deserialize the value here. 503 | fn newtype_variant_seed(self, seed: T) -> Result 504 | where 505 | T: DeserializeSeed<'de>, 506 | { 507 | seed.deserialize(self.de) 508 | } 509 | 510 | // Tuple variants are represented in JSON as `{ NAME: [DATA...] }` so 511 | // deserialize the sequence of data here. 512 | fn tuple_variant(self, _len: usize, visitor: V) -> Result 513 | where 514 | V: Visitor<'de>, 515 | { 516 | let inner = self.de.input; 517 | let mut nde = Deserializer::from_pv(inner.cdr().unwrap_or(PV::Nil)); 518 | de::Deserializer::deserialize_seq(&mut nde, visitor) 519 | } 520 | 521 | // Struct variants are represented in JSON as `{ NAME: { K: V, ... } }` so 522 | // deserialize the inner map here. 523 | fn struct_variant( 524 | self, 525 | _fields: &'static [&'static str], 526 | visitor: V, 527 | ) -> Result 528 | where 529 | V: Visitor<'de>, 530 | { 531 | let inner = self.de.input; 532 | let mut nde = Deserializer::from_pv(inner.cdr().unwrap_or(PV::Nil)); 533 | de::Deserializer::deserialize_map(&mut nde, visitor) 534 | } 535 | } 536 | 537 | #[cfg(test)] 538 | mod tests { 539 | use serde::Serialize; 540 | 541 | use crate::{r8vm::R8VM, nkgc::SymID, logging::setup_logging}; 542 | 543 | use super::*; 544 | 545 | #[test] 546 | fn deser_basic_types() { 547 | let mut vm = R8VM::no_std(); 548 | 549 | let s = vm.eval(r#" "test" "#).unwrap(); 550 | let out_s: String = from_pv(s).unwrap(); 551 | assert_eq!(out_s, "test"); 552 | 553 | let s = vm.eval(r#" 123 "#).unwrap(); 554 | let out_s: u32 = from_pv(s).unwrap(); 555 | assert_eq!(out_s, 123); 556 | 557 | let s = vm.eval(r#" -123 "#).unwrap(); 558 | let out_s = from_pv::(s); 559 | assert!(out_s.is_err()); 560 | 561 | let sigma = 0.000001; 562 | let s = vm.eval(r#" 123.0 "#).unwrap(); 563 | let out_s: f32 = from_pv(s).unwrap(); 564 | assert!(out_s - 123.0 < sigma); 565 | 566 | let sigma = 0.000001; 567 | let s = vm.eval(r#" 123.0 "#).unwrap(); 568 | let out_s: f64 = from_pv(s).unwrap(); 569 | assert!(out_s - 123.0 < sigma); 570 | } 571 | 572 | #[test] 573 | fn test_enum_type() { 574 | let mut vm = R8VM::no_std(); 575 | #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd)] 576 | #[serde(rename_all = "kebab-case")] 577 | enum U { 578 | A(u32, u32), 579 | B(String, u32), 580 | #[serde(rename_all = "kebab-case")] 581 | C { key: String, 582 | key_2: u32 }, 583 | D { sym: SymID }, 584 | } 585 | 586 | let s = vm.eval(r#" '(a 10 12) "#).unwrap(); 587 | let u = from_pv::(s).unwrap(); 588 | assert_eq!(u, U::A(10, 12)); 589 | 590 | let s = vm.eval(r#" '(b "brittany was here" 12) "#).unwrap(); 591 | let u = from_pv::(s).unwrap(); 592 | assert_eq!(u, U::B("brittany was here".to_string(), 12)); 593 | 594 | let s = vm.eval(r#" '(c :key "brittany was here" :key-2 12) "#).unwrap(); 595 | let u = from_pv::(s).unwrap(); 596 | assert_eq!(u, U::C { key: "brittany was here".to_string(), key_2: 12 }); 597 | 598 | let s = vm.eval(r#" ((lambda (x y) `(c :key ,y :key-2 ,x)) 123 "ayy lmao") "#) 599 | .unwrap(); 600 | let u = from_pv::(s).unwrap(); 601 | assert_eq!(u, U::C { key: "ayy lmao".to_string(), key_2: 123 }); 602 | } 603 | 604 | #[test] 605 | fn unit_variants() { 606 | #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd)] 607 | #[serde(rename_all = "kebab-case")] 608 | enum Abc { 609 | Qwerty, 610 | Asdfgh, 611 | } 612 | 613 | let mut vm = R8VM::no_std(); 614 | let s = vm.eval(r#" '(qwerty) "#).unwrap(); 615 | let u = from_pv::(s).unwrap(); 616 | assert_eq!(u, Abc::Qwerty); 617 | 618 | let mut vm = R8VM::no_std(); 619 | let s = vm.eval(r#" '(asdfgh) "#).unwrap(); 620 | let u = from_pv::(s).unwrap(); 621 | assert_eq!(u, Abc::Asdfgh); 622 | } 623 | 624 | #[cfg(feature="math")] 625 | #[test] 626 | fn tuples() { 627 | #[derive(Debug, Serialize, Deserialize, PartialEq)] 628 | #[serde(rename_all = "kebab-case")] 629 | enum Abc { 630 | Qwerty { k: glam::Vec2 }, 631 | Asdfgh, 632 | } 633 | 634 | let mut vm = R8VM::no_std(); 635 | let s = vm.eval(r#" '(qwerty :k (1 0)) "#).unwrap(); 636 | let u = from_pv::(s).unwrap(); 637 | assert_eq!(u, Abc::Qwerty { k: glam::vec2(1.0, 0.0) }); 638 | 639 | let mut vm = R8VM::no_std(); 640 | let s = vm.eval(r#" '(asdfgh) "#).unwrap(); 641 | let u = from_pv::(s).unwrap(); 642 | assert_eq!(u, Abc::Asdfgh); 643 | } 644 | 645 | #[test] 646 | fn min_cons_use_after_free() { 647 | let mut vm = R8VM::no_std(); 648 | vm.eval(r#"'(qwerty :k (1 0))"#).unwrap(); 649 | } 650 | } 651 | -------------------------------------------------------------------------------- /src/events.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use crate::{Spaik, Userdata, nkgc::PV}; 4 | 5 | #[macro_export] 6 | macro_rules! def_call_builder { 7 | () => { 8 | mod __spaik_call_builder { 9 | pub struct CallBuilder<'a, 'b, 'c, T> { 10 | pub fns: &'a mut T, 11 | pub vm: &'b mut $crate::Spaik, 12 | pub catch: Option>, 13 | _ph: std::marker::PhantomData<&'c ()> 14 | } 15 | 16 | impl<'q: 'c, 'a, 'b, 'c, T> CallBuilder<'a, 'b, 'c, T> { 17 | pub fn with_resource(self, global: &'q mut G) -> CallBuilder<'a, 'b, 'q, T> 18 | where G: $crate::Userdata 19 | { 20 | unsafe { self.vm.set_resource(global) }; 21 | CallBuilder { fns: self.fns, 22 | vm: self.vm, 23 | _ph: Default::default(), 24 | catch: self.catch } 25 | } 26 | 27 | pub fn catch(mut self, tag: impl $crate::AsSym) -> Self { 28 | self.catch = Some(Some(tag.as_sym_spaik(self.vm))); 29 | self 30 | } 31 | 32 | pub fn catch_all(mut self) -> Self { 33 | self.catch = Some(None); 34 | self 35 | } 36 | } 37 | 38 | pub trait IntoCallBuilder: Sized { 39 | fn on<'a, 'b>(&'a mut self, vm: &'b mut $crate::Spaik) -> CallBuilder<'a, 'b, 'b, Self>; 40 | } 41 | 42 | impl IntoCallBuilder for T where T: $crate::LinkedEvents { 43 | fn on<'a, 'b>(&'a mut self, vm: &'b mut $crate::Spaik) -> CallBuilder<'a, 'b, 'b, T> { 44 | CallBuilder { fns: self, vm, _ph: Default::default(), catch: None } 45 | } 46 | } 47 | } 48 | }; 49 | } 50 | 51 | #[macro_export] 52 | macro_rules! init { 53 | () => { 54 | $crate::def_call_builder!(); 55 | } 56 | } 57 | 58 | pub use crate::__spaik_call_builder::*; 59 | 60 | pub trait LinkedEvents { 61 | fn link_events(&mut self, vm: &mut Spaik); 62 | } 63 | 64 | #[cfg(test)] 65 | mod tests { 66 | use spaik_proc_macros::{Obj, methods}; 67 | 68 | use super::*; 69 | 70 | #[cfg(feature = "derive")] 71 | #[test] 72 | fn call_builder() { 73 | use spaik_proc_macros::hooks; 74 | 75 | #[hooks("events/")] 76 | trait Example { 77 | fn dead(id: u32); 78 | fn ready(); 79 | fn thing(x: i32) -> i32; 80 | } 81 | 82 | #[derive(Debug, Obj, Clone)] 83 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 84 | struct Test { x: i32 } 85 | const A: i32 = 1337; 86 | const B: i32 = 420; 87 | const C: i32 = 42; 88 | #[methods(())] 89 | impl Test { 90 | fn funk(&self, x: i32) -> i32 { 91 | x + A + self.x 92 | } 93 | } 94 | let mut ex = Example::default(); 95 | // let mut ex2 = Example2::default(); 96 | let mut vm = Spaik::new_no_core(); 97 | let mut test1 = Test { x: 0 }; 98 | let mut test2 = Test { x: C }; 99 | vm.defmethods::(); 100 | vm.bind_resource_fns::(None); 101 | vm.exec("(define (events/thing x) (test/funk x))").unwrap(); 102 | ex.link_events(&mut vm); 103 | let v = ex.on(&mut vm) 104 | .with_resource(&mut test1) 105 | .with_resource(&mut test2); 106 | let res = v.thing(B).unwrap(); 107 | assert_eq!(res, A + B + C); 108 | test1.x; 109 | assert!(vm.exec("(test/funk 2)").is_err()); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/fmt.rs: -------------------------------------------------------------------------------- 1 | //! Formatting for Lisp objects 2 | 3 | use crate::nkgc::{ConsElem, ConsIter}; 4 | use crate::nuke::*; 5 | use std::fmt; 6 | use crate::utils::{HMap, HSet}; 7 | use std::slice::Iter; 8 | 9 | pub type VisitSet = HSet<*const NkAtom>; 10 | 11 | pub struct FmtWrap<'a, T> where T: ?Sized { 12 | pub val: &'a T, 13 | } 14 | 15 | impl fmt::Display for FmtWrap<'_, T> where T: LispFmt + ?Sized { 16 | #[inline] 17 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 18 | let mut visited = HSet::default(); 19 | self.val.lisp_fmt(&mut visited, f) 20 | } 21 | } 22 | 23 | pub trait LispFmt { 24 | fn lisp_fmt(&self, 25 | visited: &mut VisitSet, 26 | f: &mut fmt::Formatter<'_>) -> fmt::Result; 27 | 28 | fn lisp_to_string(&self) -> String { 29 | format!("{}", FmtWrap { val: self }) 30 | } 31 | } 32 | 33 | impl fmt::Display for dyn LispFmt { 34 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 35 | write!(f, "{}", self.lisp_to_string()) 36 | } 37 | } 38 | 39 | impl LispFmt for Vec 40 | where T: LispFmt 41 | { 42 | fn lisp_fmt(&self, 43 | visited: &mut VisitSet, 44 | f: &mut fmt::Formatter<'_>) -> fmt::Result { 45 | write!(f, "(vec")?; 46 | if self.is_empty() { 47 | write!(f, ")") 48 | } else { 49 | write!(f, " ")?; 50 | self.iter().lisp_fmt(visited, f)?; 51 | write!(f, ")") 52 | } 53 | } 54 | } 55 | 56 | impl LispFmt for Iter<'_, T> 57 | where T: LispFmt 58 | { 59 | fn lisp_fmt(&self, 60 | visited: &mut VisitSet, 61 | f: &mut fmt::Formatter<'_>) -> fmt::Result { 62 | let mut it = self.clone().peekable(); 63 | while let Some(item) = it.next() { 64 | item.lisp_fmt(visited, f)?; 65 | if it.peek().is_some() { 66 | write!(f, " ")?; 67 | } 68 | } 69 | Ok(()) 70 | } 71 | } 72 | 73 | impl LispFmt for ConsIter { 74 | fn lisp_fmt(&self, 75 | visited: &mut VisitSet, 76 | f: &mut fmt::Formatter<'_>) -> fmt::Result { 77 | let mut it = self.clone().peekable(); 78 | write!(f, "(")?; 79 | while let Some(item) = it.next() { 80 | if matches!(item, ConsElem::Tail(_)) { 81 | write!(f, ". ")?; 82 | } 83 | item.get().lisp_fmt(visited, f)?; 84 | if it.peek().is_some() { 85 | write!(f, " ")?; 86 | } 87 | } 88 | write!(f, ")") 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/limits.rs: -------------------------------------------------------------------------------- 1 | pub const MACROEXPAND_RECURSION: usize = 1000; 2 | -------------------------------------------------------------------------------- /src/lisp_test.rs: -------------------------------------------------------------------------------- 1 | use crate::r8vm::R8VM; 2 | use crate::SPV; 3 | use crate::Builtin; 4 | use crate::stylize::Stylize; 5 | use std::fmt; 6 | use std::error::Error; 7 | use std::fs; 8 | 9 | enum TestResult { 10 | Pass, 11 | Fail { 12 | expect: SPV, 13 | got: SPV 14 | } 15 | } 16 | 17 | impl TestResult { 18 | pub fn new(res: SPV, vm: &mut R8VM) -> Option { 19 | Some(match res.bt_op(&vm.mem) { 20 | Some(Builtin::KwPass) => TestResult::Pass, 21 | Some(Builtin::KwFail) => { 22 | let args = res.args_vec(&mut vm.mem); 23 | match &args[..] { 24 | [expect, got] => TestResult::Fail { expect: expect.clone(), 25 | got: got.clone() }, 26 | _ => return None 27 | } 28 | } 29 | _ => return None 30 | }) 31 | } 32 | } 33 | 34 | #[derive(Debug)] 35 | pub enum TestError { 36 | WrongResult { 37 | expect: String, 38 | got: String, 39 | }, 40 | RuntimeError { 41 | origin: crate::error::Error, 42 | } 43 | } 44 | 45 | impl fmt::Display for TestError { 46 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 47 | match self { 48 | TestError::RuntimeError { origin } => write!(f, "{origin}"), 49 | TestError::WrongResult { expect, got } => { 50 | write!(f, "{expect} != {got}") 51 | } 52 | } 53 | } 54 | } 55 | 56 | impl Error for TestError { 57 | fn source(&self) -> Option<&(dyn Error + 'static)> { 58 | match self { 59 | TestError::RuntimeError { origin } => Some(origin), 60 | _ => None 61 | } 62 | } 63 | 64 | fn cause(&self) -> Option<&dyn Error> { 65 | self.source() 66 | } 67 | } 68 | 69 | /// Run SPAIK tests from the `./tests` directory and report any errors. 70 | pub fn run_tests() -> Result, Box> { 71 | let mut vm = R8VM::new(); 72 | let tests_path = "./tests"; 73 | let test = vm.sym_id("test"); 74 | vm.eval(r#"(push sys/load-path "./lisp")"#).unwrap(); 75 | 76 | if let Err(e) = vm.load_eval(test) { 77 | vmprintln!(vm, "{e}"); 78 | return Err(e.into()); 79 | } 80 | 81 | let paths = fs::read_dir(tests_path)?.map(|p| p.map(|p| p.path())) 82 | .collect::, _>>()?; 83 | for path in paths { 84 | match vm.read_compile_from(&path) { 85 | Ok(_) => (), 86 | Err(e) => { 87 | vmprintln!(vm, "Error when loading {}", path.display()); 88 | vmprintln!(vm, "{e}"); 89 | return Err(e.to_string().into()); 90 | }, 91 | } 92 | } 93 | 94 | vm.minimize(); 95 | 96 | let test_fn_prefix = "tests/"; 97 | let test_fns = vm.get_funcs_with_prefix(test_fn_prefix); 98 | let mut err_results = vec![]; 99 | 100 | for func in test_fns.iter() { 101 | let name = func.as_ref() 102 | .chars() 103 | .skip(test_fn_prefix.len()) 104 | .collect::(); 105 | match vm.call_spv(*func, ()) { 106 | Ok(res) => match TestResult::new(res, &mut vm) { 107 | Some(TestResult::Pass) => 108 | vmprintln!(vm, "test {} ... [{}]", 109 | name.style_info(), 110 | "✓".style_success()), 111 | Some(TestResult::Fail { expect, got }) => { 112 | let expect = expect.to_string(&vm.mem); 113 | let got = got.to_string(&vm.mem); 114 | 115 | vmprintln!(vm, "test {} ... [{}]", 116 | name.style_error(), 117 | "✘".style_error()); 118 | vmprintln!(vm, " Expected:"); 119 | for line in expect.lines() { 120 | vmprintln!(vm, " {}", line); 121 | } 122 | vmprintln!(vm, " Got:"); 123 | for line in got.to_string().lines() { 124 | vmprintln!(vm, " {}", line) 125 | } 126 | 127 | err_results.push(TestError::WrongResult { expect, got }); 128 | } 129 | _ => () 130 | } 131 | Err(e) => { 132 | vmprintln!(vm, "test {} [{}]", 133 | name.style_error(), 134 | "✘".style_error()); 135 | for line in e.to_string().lines() { 136 | vmprintln!(vm, " {}", line); 137 | } 138 | err_results.push(TestError::RuntimeError { origin: e }) 139 | }, 140 | } 141 | } 142 | 143 | Ok(err_results) 144 | } 145 | 146 | #[cfg(test)] 147 | mod tests { 148 | use super::*; 149 | 150 | #[test] 151 | fn lisp_tests() { 152 | let results = run_tests().unwrap(); 153 | for res in results { 154 | panic!("{res}"); 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/logging.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Once; 2 | 3 | static INIT: Once = Once::new(); 4 | 5 | pub fn setup_logging() { 6 | INIT.call_once(pretty_env_logger::init); 7 | } 8 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #[cfg(all(target_env = "musl", target_pointer_width = "64"))] 2 | use jemallocator::Jemalloc; 3 | #[cfg(all(target_env = "musl", target_pointer_width = "64"))] 4 | #[global_allocator] 5 | static GLOBAL: Jemalloc = Jemalloc; 6 | use spaik::Spaik; 7 | use std::env; 8 | use std::fs::File; 9 | use std::io::prelude::*; 10 | use std::io; 11 | 12 | fn main() -> Result<(), Box> { 13 | let args: Vec = env::args().skip(1).collect(); 14 | let mut f: Box = match &args[..] { 15 | [file] => Box::new(File::open(file)?), 16 | [] => Box::new(io::stdin()), 17 | _ => panic!("Invalid arguments: {:?}", args), 18 | }; 19 | let mut code = String::new(); 20 | if f.read_to_string(&mut code).is_err() { 21 | return Ok(()) 22 | } 23 | let mut vm = Spaik::new_no_core(); 24 | match vm.exec(&code) { 25 | Ok(_) => (), 26 | Err(e) => eprintln!("{}", e), 27 | } 28 | vm.trace_report(); 29 | 30 | Ok(()) 31 | } 32 | -------------------------------------------------------------------------------- /src/math.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use crate::fmt::{LispFmt, VisitSet}; 4 | 5 | impl LispFmt for glam::Vec2 { 6 | fn lisp_fmt(&self, 7 | _visited: &mut VisitSet, 8 | f: &mut fmt::Formatter<'_>) -> fmt::Result { 9 | write!(f, "(vec2 {} {})", self.x, self.y) 10 | } 11 | } 12 | 13 | impl LispFmt for glam::Vec3 { 14 | fn lisp_fmt(&self, 15 | _visited: &mut VisitSet, 16 | f: &mut fmt::Formatter<'_>) -> fmt::Result { 17 | write!(f, "(vec3 {} {} {})", self.x, self.y, self.z) 18 | } 19 | } 20 | 21 | impl LispFmt for glam::Vec4 { 22 | fn lisp_fmt(&self, 23 | _visited: &mut VisitSet, 24 | f: &mut fmt::Formatter<'_>) -> fmt::Result { 25 | write!(f, "(vec4 {} {} {} {})", self.x, self.y, self.z, self.w) 26 | } 27 | } 28 | 29 | impl LispFmt for glam::Mat2 { 30 | fn lisp_fmt(&self, 31 | _visited: &mut VisitSet, 32 | f: &mut fmt::Formatter<'_>) -> fmt::Result { 33 | let col0 = self.col(0); 34 | let col1 = self.col(1); 35 | write!(f, "(mat2 ({} {}) ({} {}))", col0.x, col0.y, col1.x, col1.y) 36 | } 37 | } 38 | 39 | impl LispFmt for glam::Mat3 { 40 | fn lisp_fmt(&self, 41 | _visited: &mut VisitSet, 42 | f: &mut fmt::Formatter<'_>) -> fmt::Result { 43 | let col0 = self.col(0); 44 | let col1 = self.col(1); 45 | let col2 = self.col(2); 46 | write!(f, "(mat3 ({} {} {}) ({} {} {}) ({} {} {}))", 47 | col0.x, col0.y, col0.z, 48 | col1.x, col1.y, col1.z, 49 | col2.x, col2.y, col2.z) 50 | } 51 | } 52 | impl LispFmt for glam::Mat4 { 53 | fn lisp_fmt(&self, 54 | _visited: &mut VisitSet, 55 | f: &mut fmt::Formatter<'_>) -> fmt::Result { 56 | let col0 = self.col(0); 57 | let col1 = self.col(1); 58 | let col2 = self.col(2); 59 | let col3 = self.col(3); 60 | write!(f, "(mat4 ({} {} {} {}) ({} {} {} {}) ({} {} {} {}) ({} {} {} {}))", 61 | col0.x, col0.y, col0.z, col0.w, 62 | col1.x, col1.y, col1.z, col1.w, 63 | col2.x, col2.y, col2.z, col2.w, 64 | col3.x, col3.y, col3.z, col3.w) 65 | } 66 | } 67 | 68 | impl LispFmt for glam::Quat { 69 | fn lisp_fmt(&self, 70 | _visited: &mut VisitSet, 71 | f: &mut fmt::Formatter<'_>) -> fmt::Result { 72 | write!(f, "(quat {} {} {} {})", self.x, self.y, self.z, self.w) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/module.rs: -------------------------------------------------------------------------------- 1 | use crate::{nkgc::SymID, swym::SwymDb}; 2 | use crate::chasm::ASMOp; 3 | use serde::{Serialize, Deserialize}; 4 | 5 | #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] 6 | pub struct SymEntry { 7 | name: String, 8 | sym: SymID, 9 | } 10 | 11 | #[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] 12 | pub struct Import { 13 | path: Vec, 14 | syms: Vec, 15 | } 16 | 17 | impl Import { 18 | pub fn new(path: &[SymID], imports: &[SymID]) -> Import { 19 | Import { 20 | path: path.to_vec(), 21 | syms: imports.to_vec() 22 | } 23 | } 24 | } 25 | 26 | #[derive(Serialize, Deserialize, Debug)] 27 | pub struct ModuleMeta { 28 | name_len: u32, 29 | name: Vec, 30 | spaik_ver: (u16, u16, u16), 31 | mod_ver: (u16, u16, u16), 32 | } 33 | 34 | #[derive(Deserialize, Serialize, Clone, Copy, PartialEq, Eq, Debug)] 35 | #[repr(u8)] 36 | pub enum ExportKind { 37 | Func, 38 | Macro, 39 | Var 40 | } 41 | 42 | #[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] 43 | pub struct Export { 44 | kind: ExportKind, 45 | name: SymID, 46 | pos: u32, 47 | } 48 | 49 | impl Export { 50 | pub fn new(kind: ExportKind, name: SymID, pos: u32) -> Export { 51 | Export { kind, name, pos } 52 | } 53 | } 54 | 55 | #[derive(Serialize, Deserialize, Debug)] 56 | pub enum Const { 57 | String(String), 58 | } 59 | 60 | #[derive(Serialize, Deserialize, Debug)] 61 | pub struct LispModule { 62 | symtbl: Vec, 63 | imports: Vec, 64 | exports: Vec, 65 | consts: Vec, 66 | pmem: Vec, 67 | } 68 | 69 | impl LispModule { 70 | pub fn new(_pmem_in: &[ASM], 71 | _symtbl_in: &SwymDb, 72 | _imports: Vec, 73 | _exports: Vec) -> LispModule 74 | where ASM: ASMOp 75 | { 76 | todo!() 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/opt.rs: -------------------------------------------------------------------------------- 1 | use std::mem::{take, replace}; 2 | 3 | use crate::{ast::{AST2, M, Visitor, VarDecl, Visitable}, nkgc::PV, error::Source, SymID, Builtin}; 4 | use crate::utils::HSet; 5 | 6 | #[derive(Debug, Default)] 7 | pub struct Optomat { 8 | varmod: HSet, 9 | } 10 | 11 | impl Optomat { 12 | pub fn new() -> Self { 13 | Self::default() 14 | } 15 | } 16 | 17 | struct IsModified(SymID); 18 | 19 | impl Visitor for IsModified { 20 | fn visit(&mut self, elem: &mut AST2) -> crate::Result<()> { 21 | match elem.kind { 22 | M::Set(ref mut name, _) if *name == self.0 => bail!(None), 23 | _ => elem.visit(self) 24 | } 25 | } 26 | } 27 | 28 | #[derive(Debug)] 29 | struct LowerConst(SymID, PV); 30 | 31 | impl Visitor for LowerConst { 32 | fn visit(&mut self, elem: &mut AST2) -> crate::Result<()> { 33 | match elem.kind { 34 | // Skip branches that shadow the lowered variable 35 | M::Lambda(ref args, _) if args.1.iter().any(|(x, _)| *x == self.0) => (), 36 | M::Let(ref decl, _) if decl.iter().any(|VarDecl(x, _, _)| *x == self.0) => (), 37 | 38 | M::Loop(ref mut body) => { 39 | let mut modf = IsModified(self.0); 40 | body.visit(&mut modf)?; 41 | body.visit(self)?; 42 | } 43 | M::Bt2(Builtin::LoopWithEpilogue, ref mut bod, ref mut epl) => { 44 | let mut modf = IsModified(self.0); 45 | modf.visit(bod)?; 46 | modf.visit(epl)?; 47 | self.visit(bod)?; 48 | self.visit(epl)?; 49 | } 50 | M::Set(ref mut name, _) if *name == self.0 => bail!(None), 51 | M::Var(name) if name == self.0 => *elem = AST2 { 52 | kind: self.1.into(), 53 | src: elem.src.clone() 54 | }, 55 | _ => elem.visit(self)? 56 | } 57 | Ok(()) 58 | } 59 | } 60 | 61 | impl Visitor for Optomat { 62 | fn visit(&mut self, elem: &mut AST2) -> crate::Result<()> { 63 | macro_rules! cmp_op { 64 | ($a:ident $m:ident $b:ident $($q:tt)*) => { 65 | match ($a.atom(), $b.atom()) { 66 | (Some((a, _)), Some((b, _))) => 67 | *elem = AST2 { src: elem.src.clone(), 68 | kind: M::Atom(a.$m(&b) $($q)*) }, 69 | _ => { self.visit($a)?; 70 | self.visit($b)?; } 71 | } 72 | }; 73 | } 74 | macro_rules! sum_op { 75 | ($xs:ident $m:ident) => {{ 76 | $xs.visit(self)?; 77 | let mut consts = $xs.iter_mut().filter_map(|x| x.atom()); 78 | let Some((mut s, src)) = consts.next() else { 79 | return Ok(()) 80 | }; 81 | let src = src.clone(); 82 | for (x, src) in consts { 83 | s.$m(&x).map_err(|e| e.src(src.clone()))?; 84 | } 85 | $xs.retain(|x| !x.is_atom()); 86 | let s_elem = AST2 { src, kind: M::Atom(s) }; 87 | if $xs.is_empty() { 88 | *elem = s_elem; 89 | } else { 90 | $xs.push(s_elem); 91 | } 92 | }}; 93 | } 94 | match elem.kind { 95 | // Lowering 96 | M::Let(ref mut vars, ref mut body) => { 97 | let consts = vars.iter().filter_map(|VarDecl(sym, _s, init)| { 98 | init.imm().map(|(atom, _s)| LowerConst(*sym, atom)) 99 | }); 100 | let mut dead_vars = consts.clone() 101 | .filter(|p| !self.varmod.contains(&p.0)) 102 | .map(|LowerConst(sym, _)| sym) 103 | .collect::>(); 104 | for mut lower in consts { 105 | for pt in body.iter_mut() { 106 | match lower.visit(pt) { 107 | Ok(_) => {} 108 | Err(e) if e.is_none() => { 109 | self.varmod.insert(lower.0); 110 | dead_vars.remove(&lower.0); 111 | break; 112 | } 113 | e => return e 114 | } 115 | } 116 | } 117 | vars.retain(|VarDecl(name, _, _)| !dead_vars.contains(name)); 118 | if vars.is_empty() { 119 | elem.kind = M::Progn(take(body)); 120 | } 121 | elem.visit(self)?; 122 | } 123 | M::Add(ref mut xs) => sum_op!(xs add_mut), 124 | M::Mul(ref mut xs) => sum_op!(xs mul_mut), 125 | M::If(ref mut cond, ref mut ift, ref mut ifn) => { 126 | self.visit(cond)?; 127 | if let Some((cond, src)) = cond.atom() { 128 | let nil = || Box::new(AST2 { src: src.clone(), 129 | kind: M::Atom(PV::Nil) }); 130 | if cond.into() { 131 | if let Some(ref mut ift) = ift { ift.visit(self)? } 132 | *elem = *ift.take().unwrap_or_else(nil); 133 | } else { 134 | if let Some(ref mut ifn) = ifn { ifn.visit(self)? } 135 | *elem = *ifn.take().unwrap_or_else(nil); 136 | } 137 | } else { 138 | self.visit(cond)?; 139 | if let Some(ref mut ift) = ift { self.visit(ift)? } 140 | if let Some(ref mut ifn) = ifn { self.visit(ifn)? } 141 | } 142 | } 143 | M::Gt(ref mut a, ref mut b) => cmp_op!(a gt b ?), 144 | M::Gte(ref mut a, ref mut b) => cmp_op!(a gte b ?), 145 | M::Lt(ref mut a, ref mut b) => cmp_op!(a lt b ?), 146 | M::Lte(ref mut a, ref mut b) => cmp_op!(a lte b ?), 147 | M::Eq(ref mut a, ref mut b) => cmp_op!(a eq b .into()), 148 | M::Eqp(ref mut a, ref mut b) => cmp_op!(a equalp b .into()), 149 | _ => elem.visit(self)?, 150 | } 151 | Ok(()) 152 | } 153 | } 154 | 155 | struct FindLoopBreak crate::Result<()>>(F); 156 | 157 | impl Visitor for FindLoopBreak 158 | where F: FnMut(&mut AST2) -> crate::Result<()> 159 | { 160 | fn visit(&mut self, elem: &mut AST2) -> crate::Result<()> { 161 | match elem.kind { 162 | M::Break(Some(ref mut x)) => (self.0)(x)?, 163 | M::Loop(_) => (), 164 | _ => (), 165 | } 166 | Ok(()) 167 | } 168 | } 169 | 170 | #[derive(Debug, Default)] 171 | pub struct TCOptomat { 172 | names: Vec, 173 | } 174 | 175 | impl Visitor for TCOptomat { 176 | fn visit(&mut self, elem: &mut AST2) -> crate::Result<()> { 177 | match elem.kind { 178 | M::Defun(name, _, ref mut body) => { 179 | self.names.push(name); 180 | if let Some(e) = body.last_mut() { 181 | self.visit(e)?; 182 | } 183 | self.names.pop().unwrap(); 184 | } 185 | M::Lambda(_, _) => (), 186 | M::If(_, ref mut ift, ref mut ifn) => { 187 | if let Some(e) = ift { 188 | self.visit(e)?; 189 | } 190 | if let Some(e) = ifn { 191 | self.visit(e)?; 192 | } 193 | } 194 | M::Loop(ref mut body_ref) => { 195 | let mut body = take(body_ref); 196 | let mut visitor = FindLoopBreak(|x| self.visit(x)); 197 | body.visit(&mut visitor)?; 198 | drop(replace(body_ref, body)); 199 | } 200 | M::SymApp(ref mut name, ref mut body) 201 | if Some(*name) == self.names.last().cloned() => { 202 | *elem = AST2 { src: elem.src.clone(), 203 | kind: M::TailCall(take(body)) } 204 | } 205 | _ if self.names.is_empty() => elem.visit(self)?, 206 | _ => () 207 | } 208 | Ok(()) 209 | } 210 | } 211 | 212 | impl AST2 { 213 | fn atom(&self) -> Option<(PV, &Source)> { 214 | match self.kind { 215 | M::Atom(atom) => Some((atom, &self.src)), 216 | _ => None, 217 | } 218 | } 219 | 220 | fn imm(&self) -> Option<(PV, &Source)> { 221 | match self.kind { 222 | M::Atom(atom) => match atom { 223 | PV::Ref(_) => None, 224 | pv => Some((pv, &self.src)) 225 | } 226 | _ => None, 227 | } 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /src/plug.rs: -------------------------------------------------------------------------------- 1 | use std::sync::mpsc::{Sender, Receiver, TryRecvError, RecvTimeoutError}; 2 | use std::thread::JoinHandle; 3 | use std::time::Duration; 4 | use std::ops::{Deref, DerefMut}; 5 | use std::fmt::Debug; 6 | 7 | use serde::de::DeserializeOwned; 8 | 9 | use crate::nkgc::PV; 10 | use crate::r8vm::{R8VM, ArgSpec}; 11 | use crate::{Args, AsSym, SPV, Builtin, deserialize, nkgc, FromLisp, Subr, Result, Spaik, IntoLisp}; 12 | 13 | pub enum Event { 14 | Promise { res: Box, cont: SPV }, 15 | Event { name: Box, 16 | args: Box }, 17 | Stop, 18 | } 19 | 20 | /// Promise made to `SpaikPlug` 21 | #[derive(Debug)] 22 | #[must_use = "A promise made should be a promise kept"] 23 | pub struct Promise { 24 | pub(crate) msg: Box, 25 | pub(crate) cont: Option, 26 | } 27 | 28 | impl Deref for Promise { 29 | type Target = T; 30 | fn deref(&self) -> &Self::Target { 31 | &self.msg 32 | } 33 | } 34 | 35 | impl DerefMut for Promise { 36 | fn deref_mut(&mut self) -> &mut Self::Target { 37 | &mut self.msg 38 | } 39 | } 40 | 41 | impl FromLisp> for PV where T: DeserializeOwned { 42 | fn from_lisp(self, mem: &mut nkgc::Arena) -> Result> { 43 | with_ref!(self, Cons(p) => { 44 | let msg = (*p).car; 45 | let cont = (*p).cdr; 46 | let msg = deserialize::from_pv(msg)?; 47 | if cont.bt_type_of() != Builtin::Continuation { 48 | return err!(TypeError, 49 | expect: Builtin::Continuation, 50 | got: cont.bt_type_of()); 51 | } 52 | let cont = Some(mem.make_extref(cont)); 53 | Ok(Promise { msg, cont }) 54 | }) 55 | } 56 | } 57 | 58 | impl Promise { 59 | pub fn get(&self) -> &T { 60 | &self.msg 61 | } 62 | 63 | pub fn get_mut(&mut self) -> &mut T { 64 | &mut self.msg 65 | } 66 | } 67 | 68 | /// Asynchronous SPAIK, in another thread 69 | pub struct SpaikPlug { 70 | pub(crate) promises: Receiver>, 71 | pub(crate) events: Sender, 72 | pub(crate) handle: JoinHandle, 73 | } 74 | 75 | #[derive(Clone, Debug)] 76 | #[allow(non_camel_case_types)] 77 | pub(crate) struct send_message 78 | where T: DeserializeOwned + Clone + Send 79 | { 80 | pub(crate) sender: Sender>, 81 | } 82 | 83 | unsafe impl Subr for send_message 84 | where T: DeserializeOwned + Clone + Send + 'static + Debug + Sized 85 | { 86 | fn call(&mut self, vm: &mut R8VM, args: &[PV]) -> Result { 87 | let (msg, r, cont) = match args { 88 | [x, y] => (deserialize::from_pv(*x) 89 | .map_err(|e| e.argn(1).bop(Builtin::ZSendMessage))?, 90 | *x, 91 | Some(vm.mem.make_extref(*y))), 92 | [x] => (deserialize::from_pv(*x) 93 | .map_err(|e| e.argn(1).bop(Builtin::ZSendMessage))?, 94 | *x, 95 | None), 96 | _ => ArgSpec::opt(1, 1).check(args.len() as u16) 97 | .map_err(|e| e.bop(Builtin::ZSendMessage)) 98 | .map(|_| -> ! { unreachable!() })? 99 | 100 | }; 101 | self.sender.send(Promise { msg, cont })?; 102 | Ok(r) 103 | } 104 | fn name(&self) -> &'static str { "<ζ>-send-message" } 105 | } 106 | 107 | impl SpaikPlug { 108 | #[inline] 109 | pub fn recv(&self) -> Option> 110 | where T: DeserializeOwned 111 | { 112 | match self.promises.try_recv() { 113 | Ok(e) => Some(e), 114 | Err(TryRecvError::Empty) => None, 115 | Err(TryRecvError::Disconnected) => { 116 | panic!("Spaik VM disconnected"); 117 | } 118 | } 119 | } 120 | 121 | #[inline] 122 | pub fn recv_timeout(&self, timeout: Duration) -> Option> 123 | where T: DeserializeOwned 124 | { 125 | match self.promises.recv_timeout(timeout) { 126 | Ok(e) => Some(e), 127 | Err(RecvTimeoutError::Timeout) => None, 128 | Err(RecvTimeoutError::Disconnected) => { 129 | panic!("Spaik VM disconnected"); 130 | } 131 | } 132 | } 133 | 134 | #[inline] 135 | pub fn send(&self, name: V, args: A) 136 | where V: AsSym + Send + 'static, 137 | A: Args + Send + 'static 138 | { 139 | self.events.send(Event::Event { name: Box::new(name), 140 | args: Box::new(args) }).unwrap(); 141 | } 142 | 143 | #[inline] 144 | pub fn fulfil(&self, promise: Promise, ans: R) 145 | where R: IntoLisp + Clone + Send + 'static 146 | { 147 | if let Some(cont) = promise.cont { 148 | self.events.send(Event::Promise { res: Box::new((ans,)), 149 | cont }).unwrap(); 150 | } 151 | } 152 | 153 | #[inline] 154 | pub fn join(self) -> Spaik { 155 | self.events.send(Event::Stop).unwrap(); 156 | self.handle.join().unwrap() 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/pmem.rs: -------------------------------------------------------------------------------- 1 | //! Program Memory 2 | 3 | #![allow(dead_code)] 4 | 5 | use std::{alloc::{realloc, Layout, handle_alloc_error}, mem::size_of}; 6 | 7 | use crate::{r8vm::r8c::Op, nuke::malloc}; 8 | 9 | struct PMem { 10 | mem: *mut Op, 11 | sz: usize, 12 | len: usize, 13 | ip: *mut Op, 14 | } 15 | 16 | impl PMem { 17 | pub fn new(sz: usize) -> Self { 18 | let mem = unsafe { malloc(sz) }; 19 | Self { mem, sz, len: 0, ip: mem } 20 | } 21 | 22 | pub fn fit(&mut self, fit: usize) { 23 | let byte_fit = fit * size_of::(); 24 | let new_sz = (self.sz << 1).max(self.sz + byte_fit); 25 | unsafe { 26 | let ipd = self.ip.sub(self.mem as usize) as usize; 27 | let layout = Layout::array::(self.sz).unwrap(); 28 | let mem = realloc(self.mem as *mut u8, 29 | layout, 30 | size_of::() * new_sz) as *mut Op; 31 | if mem.is_null() { 32 | handle_alloc_error(layout); 33 | } 34 | self.mem = mem; 35 | self.sz = new_sz; 36 | self.ip = self.mem.add(ipd); 37 | } 38 | } 39 | 40 | pub fn push(&mut self, from: &[Op]) { 41 | if self.len + from.len() > self.sz { 42 | self.fit(from.len()) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/records.rs: -------------------------------------------------------------------------------- 1 | use spaik_proc_macros::Obj; 2 | 3 | use crate::error::OpName; 4 | 5 | use crate::nkgc::Traceable; 6 | use crate::r8vm::{ObjMethod, ArgSpec}; 7 | use crate::{Subr, swym::SymRef, nkgc::PV, r8vm::R8VM}; 8 | use crate::{Result, Error}; 9 | 10 | #[allow(unused)] 11 | #[derive(Debug, Obj, Clone, PartialEq)] 12 | #[cfg_attr(feature = "freeze", derive(serde::Serialize, serde::Deserialize))] 13 | struct Example { 14 | x: f32, 15 | y: f32, 16 | z: String 17 | } 18 | 19 | #[allow(unused)] 20 | #[derive(Debug, Clone, Obj, PartialEq)] 21 | #[cfg_attr(feature = "freeze", derive(serde::Serialize, serde::Deserialize))] 22 | enum EnumExample { 23 | Ayy { 24 | x: f32, 25 | y: f32, 26 | z: String 27 | }, 28 | Lmao { 29 | example: Example 30 | }, 31 | Foo { 32 | x: i32, 33 | }, 34 | Bar(i32, i32), 35 | Zed, 36 | } 37 | 38 | #[derive(Clone)] 39 | pub struct MacroNew { 40 | name: &'static str, 41 | variant: &'static str, 42 | variant_maker: &'static str, 43 | key_strings: &'static [&'static str], 44 | defaults: Vec>, 45 | keys: Vec, 46 | make_fn: Option, 47 | } 48 | 49 | unsafe impl Send for MacroNew {} 50 | 51 | impl TryFrom for MacroNew { 52 | type Error = crate::Error; 53 | 54 | fn try_from(_: PV) -> std::result::Result { 55 | err!(ImmovableObject, name: OpName::OpStr("EnumMacro")) 56 | } 57 | } 58 | 59 | impl Traceable for MacroNew { 60 | fn trace(&self, gray: &mut Vec<*mut crate::__private::NkAtom>) { 61 | for df in self.defaults.iter() { 62 | df.map(|df| df.trace(gray)); 63 | } 64 | } 65 | 66 | fn update_ptrs(&mut self, reloc: &crate::__private::PtrMap) { 67 | for df in self.defaults.iter() { 68 | df.map(|mut df| df.update_ptrs(reloc)); 69 | } 70 | } 71 | } 72 | 73 | unsafe impl Subr for MacroNew { 74 | fn call(&mut self, vm: &mut R8VM, args: &[PV]) -> std::result::Result { 75 | if self.keys.is_empty() { 76 | self.keys = self.key_strings.into_iter().map(|key| { 77 | vm.sym(key) 78 | }).collect(); 79 | } 80 | let name = self.make_fn.get_or_insert_with(|| vm.sym(self.variant_maker)); 81 | let mut out = self.defaults.clone(); 82 | into_init(vm, self.name, name, args, &self.keys[..], &mut out[..]) 83 | } 84 | 85 | fn name(&self) -> &'static str { 86 | self.variant 87 | } 88 | } 89 | 90 | pub trait Enum { 91 | fn enum_macros() -> impl Iterator; 92 | fn enum_constructors() -> impl Iterator>; 93 | } 94 | 95 | pub struct MacroNewVariant { 96 | pub variant: &'static str, 97 | pub variant_maker: &'static str, 98 | pub key_strings: &'static [&'static str], 99 | } 100 | 101 | #[inline(never)] 102 | pub fn into_macro_news(parts: &'static [MacroNewVariant]) -> impl Iterator { 103 | parts.iter().map(|MacroNewVariant { variant, variant_maker, key_strings }: 104 | &MacroNewVariant| MacroNew { 105 | name: "unknown", 106 | variant, 107 | variant_maker, 108 | key_strings, 109 | defaults: vec![None; key_strings.len()], 110 | keys: Default::default(), 111 | make_fn: None, 112 | }) 113 | } 114 | 115 | #[inline(never)] 116 | pub fn into_init(vm: &mut R8VM, 117 | name: &'static str, 118 | make_fn: &SymRef, 119 | args: &[PV], 120 | keys: &[SymRef], 121 | out: &mut [Option]) -> Result 122 | { 123 | 'outer: for pair in args.chunks(2) { 124 | match *pair { 125 | [k, v] => for (i, key) in keys.iter().enumerate() { 126 | if key.eq_pv(k) { 127 | if out[i].is_some() { 128 | return err!(DuplicateField, record: name.to_string(), 129 | field: k.to_string()) 130 | } 131 | out[i] = Some(v); 132 | continue 'outer; 133 | } 134 | } 135 | // FIXME: Better error message 136 | [_] => return err!(UnclosedDelimiter, open: ":key"), 137 | _ => unreachable!(), 138 | } 139 | return err!(NoSuchField, record: name.to_string(), field: pair[0].to_string()) 140 | } 141 | vm.mem.push_symref(make_fn); 142 | for pv in out.iter() { 143 | if let Some(pv) = pv { 144 | vm.mem.push(*pv); 145 | } else { 146 | // FIXME: List the missing fields 147 | return err!(RecordMissingFields, 148 | record: name.to_string(), 149 | fields: vec![]) 150 | } 151 | } 152 | vm.mem.list((out.len() + 1) as u32); 153 | vm.mem.pop() 154 | } 155 | 156 | pub trait FieldAccess { 157 | fn field_access(&mut self, _args: &[PV]) -> crate::Result> { 158 | Ok(None) 159 | } 160 | } 161 | 162 | pub trait MethodCall { 163 | fn call_method(&mut self, _args: &[PV]) -> crate::Result> { 164 | Ok(None) 165 | } 166 | } 167 | 168 | pub unsafe trait MethodSet { 169 | fn methods() -> &'static [(&'static str, ArgSpec, ObjMethod)]; 170 | } 171 | 172 | pub trait SubrSet { 173 | fn subrs() -> impl Iterator>; 174 | } 175 | 176 | pub trait KebabTypeName { 177 | fn kebab_type_name() -> &'static str; 178 | } 179 | 180 | #[cfg(test)] 181 | mod tests { 182 | use spaik_proc_macros::methods; 183 | 184 | use crate::{Spaik, Gc}; 185 | 186 | use super::*; 187 | 188 | #[test] 189 | fn record_macro_auto() { 190 | let mut vm = Spaik::new_no_core(); 191 | vm.defobj::(Default::default()); 192 | vm.exec(r##"(define g (example :x 1 :y 2 :z "z"))"##).unwrap(); 193 | let mut gx: Gc = vm.eval("g").unwrap(); 194 | let x = gx.with(|x| x.clone()); 195 | drop(gx); 196 | let y: Example = vm.eval("g").unwrap(); 197 | assert!(vm.eval::("(void? g)").unwrap()); 198 | assert_eq!(y, Example { x: 1.0, y: 2.0, z: "z".to_string() }); 199 | assert_eq!(y, x); 200 | } 201 | 202 | #[test] 203 | fn record_macro_auto_shared_ref() { 204 | let mut vm = Spaik::new_no_core(); 205 | vm.defobj::(Default::default()); 206 | vm.exec(r##"(define g (example :x 1 :y 2 :z "z"))"##).unwrap(); 207 | let _gx: Gc = vm.eval("g").unwrap(); 208 | assert!(matches!(vm.eval::("g").map_err(|e| e.kind().clone()), 209 | Err(crate::error::ErrorKind::CannotMoveSharedReference { nref: 2, .. }))) ; 210 | } 211 | 212 | #[test] 213 | fn struct_as_enum() { 214 | #[derive(Debug, Obj, Clone, PartialEq, Eq)] 215 | #[cfg_attr(feature = "freeze", derive(serde::Serialize, serde::Deserialize))] 216 | pub struct Test1 { 217 | x: i32 218 | } 219 | #[derive(Debug, Obj, Clone, PartialEq, Eq)] 220 | #[cfg_attr(feature = "freeze", derive(serde::Serialize, serde::Deserialize))] 221 | pub struct Test2; 222 | #[derive(Debug, Obj, Clone, PartialEq, Eq)] 223 | #[cfg_attr(feature = "freeze", derive(serde::Serialize, serde::Deserialize))] 224 | pub struct Test3(i32, i32); 225 | let mut vm = Spaik::new_no_core(); 226 | vm.defobj::(Default::default()); 227 | vm.defobj::(Default::default()); 228 | vm.defobj::(Default::default()); 229 | vm.exec(r##"(define g (test-1 :x 2))"##).unwrap(); 230 | vm.exec(r##"(define g2 (test-2))"##).unwrap(); 231 | vm.exec(r##"(define g3 (test-3 1 2))"##).unwrap(); 232 | let mut g: Gc = vm.get("g").unwrap(); 233 | assert_eq!(g.with(|t| t.clone()), Test1 { x: 2 }); 234 | let mut g2: Gc = vm.get("g2").unwrap(); 235 | assert_eq!(g2.with(|t| t.clone()), Test2); 236 | let mut g3: Gc = vm.get("g3").unwrap(); 237 | assert_eq!(g3.with(|t| t.clone()), Test3(1, 2)); 238 | } 239 | 240 | #[test] 241 | fn enum_macros() { 242 | let mut vm = Spaik::new_no_core(); 243 | vm.defobj::(Default::default()); 244 | vm.defobj::(Default::default()); 245 | vm.exec(r##"(define g (enum-example/ayy :x 1 :y 2 :z "z"))"##).unwrap(); 246 | vm.exec(r##"(define z (enum-example/lmao :example (example :x 1 :y 2 :z "z")))"##).unwrap(); 247 | vm.exec(r##"(define zed (enum-example/zed))"##).unwrap(); 248 | vm.exec(r##"(define bar (enum-example/bar 1 2))"##).unwrap(); 249 | let mut g: Gc = vm.get("g").unwrap(); 250 | let mut z: Gc = vm.get("z").unwrap(); 251 | let mut zed: Gc = vm.get("zed").unwrap(); 252 | let mut bar: Gc = vm.get("bar").unwrap(); 253 | assert_eq!(g.with(|t| t.clone()), 254 | EnumExample::Ayy { x: 1.0, y: 2.0, z: "z".to_string() }); 255 | assert_eq!(z.with(|t| t.clone()), 256 | EnumExample::Lmao { example: Example { x: 1.0, y: 2.0, z: "z".to_string() } }); 257 | assert_eq!(zed.with(|t| t.clone()), EnumExample::Zed); 258 | assert_eq!(bar.with(|t| t.clone()), EnumExample::Bar(1, 2)); 259 | } 260 | 261 | #[test] 262 | fn static_and_self_methods() { 263 | #[derive(Debug, Obj, Clone, PartialEq, Eq)] 264 | #[cfg_attr(feature = "freeze", derive(serde::Serialize, serde::Deserialize))] 265 | pub struct TestStatic; 266 | #[methods(())] 267 | impl TestStatic { 268 | fn f(&self, x: i32) -> i32 { 269 | x + 1 270 | } 271 | fn s(x: i32) -> i32 { 272 | x + 1 273 | } 274 | fn s2(x: i32, y: i32) -> i32 { 275 | x + 2 + y 276 | } 277 | } 278 | let mut vm = Spaik::new(); 279 | vm.defobj::(Default::default()); 280 | vm.defmethods::(); 281 | vm.exec("(define g (test-static))").unwrap(); 282 | assert_eq!(3i32, vm.eval("(g :f 2)").unwrap()); 283 | assert_eq!(3i32, vm.eval("(test-static/s 2)").unwrap()); 284 | assert_eq!(7i32, vm.eval("(test-static/s-2 2 3)").unwrap()); 285 | } 286 | 287 | #[test] 288 | fn only_static_methods() { 289 | pub struct TestStatic; 290 | #[methods(())] 291 | impl TestStatic { 292 | fn s(x: i32) -> i32 { 293 | x + 1 294 | } 295 | fn s2(x: i32, y: i32) -> i32 { 296 | x + 2 + y 297 | } 298 | fn s3(x: i32, y: i32) -> i32 { 299 | x + 3 + y 300 | } 301 | } 302 | let mut vm = Spaik::new(); 303 | vm.defstatic::(); 304 | assert_eq!(3i32, vm.eval("(test-static/s 2)").unwrap()); 305 | assert_eq!(7i32, vm.eval("(test-static/s-2 2 3)").unwrap()); 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /src/repl.rs: -------------------------------------------------------------------------------- 1 | //! Interactive Read-Eval-Print-Loop 2 | 3 | #[cfg(feature = "readline")] 4 | use rustyline::{Editor, error::ReadlineError}; 5 | #[cfg(feature = "readline")] 6 | use std::{process, fs}; 7 | use crate::Spaik; 8 | use crate::r8vm::OutStream; 9 | use crate::nkgc::PV; 10 | use crate::fmt::LispFmt; 11 | use std::path::Path; 12 | use crate::stylize::Stylize; 13 | 14 | #[allow(unused)] 15 | fn make_intro() -> String { 16 | format!("{read} {arrow} {eval} {arrow} {print} {arrow} {loop} 17 | ┗━━━━━━━━━━━━━━━━━━━━━━┛\n", 18 | read="read".style_ret(), 19 | eval="eval".style_ret(), 20 | print="print".style_ret(), 21 | loop="loop".style_ret(), 22 | arrow="➟" 23 | ) 24 | } 25 | 26 | macro_rules! vmprint { 27 | ($vm:expr, $($fmt:expr),+) => { 28 | $vm.print_fmt(format_args!($($fmt),+)).unwrap() 29 | }; 30 | } 31 | 32 | #[allow(unused_macros)] 33 | macro_rules! vmprintln { 34 | ($vm:expr, $($fmt:expr),+) => { 35 | $vm.print_fmt(format_args!($($fmt),+)).unwrap(); 36 | $vm.println(&"").unwrap(); 37 | }; 38 | } 39 | 40 | type EBResult = Result>; 41 | 42 | pub trait LineInput { 43 | fn readline(); 44 | fn save_history + ?Sized>(&self, path: &P) -> EBResult<()>; 45 | fn load_history + ?Sized>(&mut self, path: &P) -> EBResult<()>; 46 | fn add_history_entry + Into>(&mut self, line: S) -> bool; 47 | } 48 | 49 | pub struct REPL { 50 | vm: Spaik, 51 | exit_status: Option, 52 | } 53 | 54 | impl REPL { 55 | pub fn new(out_override: Option>) -> REPL { 56 | let mut vm = Spaik::new(); 57 | if let Some(out) = out_override { 58 | vm.vm.set_stdout(out); 59 | } 60 | REPL { 61 | vm, 62 | exit_status: None, 63 | } 64 | } 65 | 66 | pub fn eval(&mut self, code: impl AsRef) -> Result, String> { 67 | let res = self.vm.vm.eval(code.as_ref()); 68 | self.vm.vm.flush_output().map_err(|e| e.to_string())?; 69 | match res { 70 | Ok(PV::Nil) => Ok(None), 71 | Ok(res) => Ok(Some(res.lisp_to_string())), 72 | Err(e) => Err(e.to_string()), 73 | } 74 | } 75 | 76 | pub fn exit_status(&self) -> Option { 77 | self.exit_status 78 | } 79 | 80 | pub fn print_intro(&mut self) { 81 | vmprint!(self.vm.vm, "{}", make_intro()); 82 | } 83 | 84 | #[cfg(not(feature = "readline"))] 85 | pub fn readline_repl(&mut self) -> ! { 86 | unimplemented!("readline not available") 87 | } 88 | 89 | #[cfg(feature = "readline")] 90 | pub fn readline_repl(&mut self) -> ! { 91 | use crate::Ignore; 92 | 93 | let mut spaik_dir = dirs::data_local_dir().unwrap(); 94 | spaik_dir.push("spaik"); 95 | if let Err(e) = fs::create_dir_all(spaik_dir) { 96 | vmprintln!(self.vm.vm, "Error: {}", e); 97 | process::exit(1); 98 | } 99 | let mut hist_path = dirs::data_local_dir().unwrap(); 100 | hist_path.push("spaik"); 101 | hist_path.push("history"); 102 | self.vm.add_load_path("."); 103 | self.vm.add_load_path("lisp"); 104 | let mut config_dir = dirs::config_dir().unwrap(); 105 | config_dir.push("spaik"); 106 | self.vm.add_load_path(config_dir.to_str().unwrap()); 107 | match self.vm.load::("init") { 108 | Ok(_) => (), 109 | Err(e) => { 110 | vmprintln!(self.vm.vm, "{}", e); 111 | } 112 | } 113 | let mut rl = Editor::<()>::new(); 114 | if rl.load_history(&hist_path).is_err() { 115 | vmprintln!(self.vm.vm, "{} {}", 116 | "Warning: No history log, will be created in".style_warning(), 117 | hist_path.to_string_lossy().style_info()); 118 | } 119 | self.print_intro(); 120 | let prompt = "λ> ".style_prompt().to_string(); 121 | while self.exit_status.is_none() { 122 | let readline = rl.readline(&prompt); 123 | match readline { 124 | Ok(line) => { 125 | rl.add_history_entry(line.as_str()); 126 | match self.eval(line.as_str()) { 127 | Ok(Some(s)) => { vmprintln!(self.vm.vm, "{s}"); }, 128 | Err(e) => { vmprintln!(self.vm.vm, "{e}"); }, 129 | _ => () 130 | } 131 | }, 132 | Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => { 133 | self.exit_status = Some(0) 134 | }, 135 | Err(err) => { 136 | vmprintln!(self.vm.vm, "Read Error: {:?}", err); 137 | self.exit_status = Some(1) 138 | } 139 | } 140 | } 141 | if let Err(e) = rl.save_history(&hist_path) { 142 | vmprintln!(self.vm.vm, "Error: {}", e); 143 | } 144 | process::exit(self.exit_status.unwrap_or_default()) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/run_test.rs: -------------------------------------------------------------------------------- 1 | use spaik::r8vm::*; 2 | 3 | fn main() { 4 | let mut vm = R8VM::new(); 5 | vm.read("(println (list 1 2 3 4 5 6))").unwrap(); 6 | let sym = vm.compile("test"); 7 | vm.dis(); 8 | let args = vec![]; 9 | vm.call(sym, args).unwrap(); 10 | } 11 | -------------------------------------------------------------------------------- /src/scratch.rs: -------------------------------------------------------------------------------- 1 | /// Rust doesn't have a good REPL, this is my substitute. Changes to this file 2 | /// should not be commited to git. 3 | 4 | pub fn main() -> Result<(), Box> { 5 | println!("Hello, World!"); 6 | Ok(()) 7 | } 8 | -------------------------------------------------------------------------------- /src/stack_gymnastics.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use crate::Spaik; 4 | 5 | #[test] 6 | fn eval_macroexpand_eval() { 7 | let mut vm = Spaik::new(); 8 | vm.exec(r#"(define n 0)"#).unwrap(); 9 | vm.exec(r#"(eval-when-compile (inc! n))"#).unwrap(); 10 | let n: i32 = vm.eval(r#"n"#).unwrap(); 11 | assert_eq!(n, 1); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/stak.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use std::alloc::{Layout, alloc, dealloc, handle_alloc_error}; 4 | 5 | struct Stak { 6 | ebp: *mut T, 7 | top: *mut T, 8 | buf: *mut T, 9 | sz: usize, 10 | } 11 | 12 | impl Stak { 13 | pub fn new(sz: usize) -> Stak { 14 | let layout = Layout::array::(sz).unwrap(); 15 | let buf = unsafe { alloc(layout) } as *mut T; 16 | if buf.is_null() { 17 | handle_alloc_error(layout); 18 | } 19 | Stak { ebp: buf, 20 | top: buf, 21 | buf, 22 | sz } 23 | } 24 | } 25 | 26 | impl Drop for Stak { 27 | fn drop(&mut self) { 28 | let layout = Layout::array::(self.sz).unwrap(); 29 | unsafe { dealloc(self.buf as *mut u8, layout) } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/string_parse.rs: -------------------------------------------------------------------------------- 1 | //! String parser 2 | 3 | use crate::Error; 4 | use crate::error::ErrorKind; 5 | use crate::Result; 6 | use crate::tok::Token; 7 | 8 | pub fn string_parse(tok: &Token) -> Result { 9 | let mut out = String::new(); 10 | let mut it = tok.text.chars(); 11 | while let Some(c) = it.next() { 12 | if c == '\\' { 13 | let c = it.next().ok_or(Error::new(ErrorKind::TrailingEscape))?; 14 | match c { 15 | '"' => out.push('"'), 16 | 'n' => out.push('\n'), 17 | 'r' => out.push('\r'), 18 | 't' => out.push('\t'), 19 | '\\' => out.push('\\'), 20 | 'z' => out.push('ζ'), 21 | 'Z' => out.push('Ζ'), 22 | 'x' => out.push('ξ'), 23 | 'X' => out.push('Ξ'), 24 | 'l' => out.push('λ'), 25 | 'L' => out.push('Λ'), 26 | 'F' => out.push('🔥'), 27 | '🐙' => out.push_str("Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn"), 28 | '🚀' => out.push_str("SEE YOU SPACE COWBOY . . ."), 29 | _ => bail!(NoSuchEscapeChar { chr: c }) 30 | } 31 | } else { 32 | out.push(c); 33 | } 34 | } 35 | Ok(out) 36 | } 37 | -------------------------------------------------------------------------------- /src/stylize.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "extra")] 2 | use owo_colors::{OwoColorize, Styled, style}; 3 | 4 | macro_rules! def_styles { 5 | ($($name:ident $init:block)*) => { 6 | #[cfg(feature = "extra")] 7 | pub trait Stylize: OwoColorize { 8 | $(fn $name(&self) -> Styled<&Self>;)* 9 | } 10 | #[cfg(feature = "extra")] 11 | impl Stylize for T where T: OwoColorize { 12 | $(fn $name(&self) -> Styled<&T> { self.style($init) })* 13 | } 14 | 15 | #[cfg(not(feature = "extra"))] 16 | pub trait Stylize { 17 | $(fn $name(&self) -> &Self;)* 18 | } 19 | #[cfg(not(feature = "extra"))] 20 | impl Stylize for T { 21 | $(#[inline(always)] 22 | fn $name(&self) -> &T { self })* 23 | } 24 | }; 25 | } 26 | 27 | def_styles! { 28 | style_ret { style().blue().bold() } 29 | style_asm_label_ref { style().yellow() } 30 | style_asm_label { style().yellow().bold() } 31 | style_asm_fn { style().cyan().bold() } 32 | style_asm_op { style().cyan().bold() } 33 | style_warning { style().yellow().bold() } 34 | style_error { style().red().bold() } 35 | style_success { style().green().bold() } 36 | style_info { style().white().bold() } 37 | style_prompt { style().white().bold() } 38 | } 39 | -------------------------------------------------------------------------------- /src/subrs.rs: -------------------------------------------------------------------------------- 1 | //! Rust Subroutines for SPAIK LISP 2 | 3 | use serde::de::DeserializeOwned; 4 | use serde::{Serialize, Deserialize}; 5 | 6 | use crate::utils::{HMap, HSet}; 7 | use crate::r8vm::{R8VM, ArgSpec}; 8 | use crate::nkgc::{PV, SPV, Arena, ObjRef}; 9 | use crate::error::{Error, ErrorKind, ExtError}; 10 | use crate::{nuke::*, SymID, Builtin, deserialize}; 11 | use crate::fmt::{LispFmt, VisitSet}; 12 | use std::any::Any; 13 | use std::collections::HashMap; 14 | use std::collections::hash_map::Entry; 15 | use std::convert::{TryInto, TryFrom}; 16 | use std::fmt; 17 | use std::io::{Read, Cursor}; 18 | use std::marker::PhantomData; 19 | use std::ops::{Deref, DerefMut}; 20 | use std::rc::Rc; 21 | use std::sync::{OnceLock, Mutex, Arc}; 22 | 23 | /// The `mem` parameter is necessary here, because some of the conversions 24 | /// may need to create an SPV reference-counter 25 | pub trait FromLisp: Sized { 26 | #[allow(clippy::wrong_self_convention)] 27 | fn from_lisp(self, mem: &mut Arena) -> Result; 28 | } 29 | 30 | /// The `mem` parameter is necessary here, because some of the conversions 31 | /// may need to do memory allocation. 32 | pub trait IntoLisp: Sized { 33 | fn into_spv(self, mem: &mut Arena) -> Result { 34 | let pv = self.into_pv(mem)?; 35 | Ok(mem.make_extref(pv)) 36 | } 37 | 38 | fn into_pv(self, mem: &mut Arena) -> Result; 39 | } 40 | 41 | pub trait RefIntoLisp { 42 | fn ref_into_spv(&self, mem: &mut Arena) -> Result { 43 | let pv = self.ref_into_pv(mem)?; 44 | Ok(mem.make_extref(pv)) 45 | } 46 | 47 | fn ref_into_pv(&self, mem: &mut Arena) -> Result; 48 | } 49 | 50 | impl RefIntoLisp for T 51 | where T: IntoLisp + Clone 52 | { 53 | fn ref_into_pv(&self, mem: &mut Arena) -> Result { 54 | self.clone().into_pv(mem) 55 | } 56 | } 57 | 58 | #[allow(unused_macros)] 59 | macro_rules! impl_glam_tryfrom { 60 | ($($from_t:tt),*) => { 61 | $(impl TryFrom for glam::$from_t { 62 | type Error = Error; 63 | fn try_from(value: PV) -> Result { 64 | with_ref!(value, $from_t(v) => { Ok(*v) }) 65 | } 66 | })* 67 | }; 68 | } 69 | 70 | #[allow(unused_macros)] 71 | macro_rules! impl_objref { 72 | ($($from_t:ty),*) => { 73 | $(impl TryFrom for ObjRef<$from_t> { 74 | type Error = Error; 75 | #[inline(always)] 76 | fn try_from(v: PV) -> Result, Self::Error> { 77 | Ok(ObjRef(v.try_into()?)) 78 | } 79 | })* 80 | }; 81 | } 82 | 83 | #[allow(unused_macros)] 84 | macro_rules! impl_objput { 85 | ($($from_t:ty),*) => { 86 | $(impl IntoLisp for $from_t { 87 | fn into_pv(self, mem: &mut Arena) -> Result { 88 | Ok(mem.put_pv(self)) 89 | } 90 | })* 91 | }; 92 | } 93 | 94 | macro_rules! pv_convert { 95 | ($pvt:ident, $($from_t:ty),*) => { 96 | $(impl IntoLisp for $from_t { 97 | fn into_pv(self, _: &mut Arena) -> Result { 98 | Ok(PV::$pvt(self.try_into()?)) 99 | } 100 | })* 101 | $(impl TryFrom for $from_t { 102 | type Error = Error; 103 | fn try_from(v: PV) -> Result<$from_t, Self::Error> { 104 | if let PV::$pvt(x) = v { 105 | Ok(x.try_into()?) 106 | } else if let PV::Real(x) = v { 107 | Ok(x as $from_t) 108 | } else if let PV::Int(x) = v { 109 | Ok(x as $from_t) 110 | } else { 111 | Err(Error::new(ErrorKind::TypeError { 112 | expect: PV::$pvt(Default::default()).bt_type_of(), 113 | got: v.bt_type_of(), 114 | })) 115 | } 116 | } 117 | } 118 | impl TryFrom for ObjRef<$from_t> { 119 | type Error = Error; 120 | #[inline(always)] 121 | fn try_from(v: PV) -> Result, Self::Error> { 122 | Ok(ObjRef(v.try_into()?)) 123 | } 124 | })* 125 | }; 126 | } 127 | 128 | pv_convert!(Int, 129 | i8, u8, 130 | i16, u16, 131 | i32, u32, 132 | i64, u64, 133 | i128, u128, 134 | isize, usize); 135 | 136 | pv_convert!(Real, 137 | f32); 138 | 139 | #[cfg(feature = "shipyard")] 140 | impl TryFrom for shipyard::EntityId { 141 | type Error = Error; 142 | 143 | fn try_from(value: PV) -> Result { 144 | if let PV::Id(v) = value { 145 | Ok(v) 146 | } else { 147 | err!(TypeError, expect: Builtin::Id, got: value.bt_type_of()) 148 | } 149 | } 150 | } 151 | 152 | #[cfg(feature = "shipyard")] 153 | impl_objref!(shipyard::EntityId); 154 | 155 | #[cfg(feature = "shipyard")] 156 | impl IntoLisp for shipyard::EntityId { 157 | fn into_pv(self, _mem: &mut Arena) -> Result { 158 | Ok(PV::Id(self)) 159 | } 160 | } 161 | 162 | #[cfg(feature = "rapier2d")] 163 | impl TryFrom for rapier2d::prelude::RigidBodyHandle { 164 | type Error = Error; 165 | 166 | fn try_from(value: PV) -> Result { 167 | if let PV::RigidBody(v) = value { 168 | Ok(v) 169 | } else { 170 | err!(TypeError, expect: Builtin::RigidBody, got: value.bt_type_of()) 171 | } 172 | } 173 | } 174 | 175 | #[cfg(feature = "rapier2d")] 176 | impl_objref!(rapier2d::prelude::RigidBodyHandle); 177 | 178 | #[cfg(feature = "rapier2d")] 179 | impl IntoLisp for rapier2d::prelude::RigidBodyHandle { 180 | fn into_pv(self, _mem: &mut Arena) -> Result { 181 | Ok(PV::RigidBody(self)) 182 | } 183 | } 184 | 185 | #[cfg(feature = "math")] 186 | impl TryFrom for glam::Vec2 { 187 | type Error = Error; 188 | 189 | fn try_from(value: PV) -> Result { 190 | if let PV::Vec2(v) = value { 191 | Ok(v) 192 | } else { 193 | err!(TypeError, expect: Builtin::Vec2, got: value.bt_type_of()) 194 | } 195 | } 196 | } 197 | 198 | #[cfg(feature = "math")] 199 | impl TryFrom for glam::Vec3 { 200 | type Error = Error; 201 | 202 | fn try_from(value: PV) -> Result { 203 | if let PV::Vec3(v) = value { 204 | Ok(v) 205 | } else { 206 | err!(TypeError, expect: Builtin::Vec3, got: value.bt_type_of()) 207 | } 208 | } 209 | } 210 | 211 | #[cfg(feature = "math")] 212 | impl_glam_tryfrom!(Vec4, Mat2, Mat3, Mat4); 213 | 214 | #[cfg(feature = "math")] 215 | impl IntoLisp for glam::Vec2 { 216 | fn into_pv(self, _mem: &mut Arena) -> Result { 217 | Ok(PV::Vec2(self)) 218 | } 219 | } 220 | 221 | #[cfg(feature = "math")] 222 | impl IntoLisp for glam::Vec3 { 223 | fn into_pv(self, _mem: &mut Arena) -> Result { 224 | Ok(PV::Vec3(self)) 225 | } 226 | } 227 | 228 | #[cfg(feature = "math")] 229 | impl_objput!(glam::Vec4, glam::Mat2, glam::Mat3, glam::Mat4); 230 | 231 | #[cfg(feature = "math")] 232 | impl_objref!(glam::Vec2, glam::Vec3, glam::Vec4); 233 | 234 | impl IntoLisp for char { 235 | fn into_pv(self,_: &mut Arena) -> Result{ 236 | Ok(PV::Char(self)) 237 | } 238 | 239 | } 240 | impl TryFromfor char { 241 | type Error = Error; 242 | fn try_from(v:PV) -> Result{ 243 | if let PV::Char(x) = v { 244 | Ok(x) 245 | } else { 246 | Err(Error::new(ErrorKind::TypeError { 247 | expect:PV::Char(Default::default()).bt_type_of(),got:v.bt_type_of(), 248 | })) 249 | } 250 | } 251 | 252 | } 253 | impl TryFromfor ObjRef{ 254 | type Error = Error; 255 | #[inline(always)] 256 | fn try_from(v:PV) -> Result,Self::Error>{ 257 | Ok(ObjRef(v.try_into()?)) 258 | } 259 | } 260 | 261 | impl IntoLisp for () { 262 | fn into_pv(self, _: &mut Arena) -> Result { 263 | Ok(PV::Nil) 264 | } 265 | } 266 | 267 | impl TryFrom for () { 268 | type Error = Error; 269 | fn try_from(v: PV) -> Result<(), Self::Error> { 270 | if let PV::Nil = v { 271 | Ok(()) 272 | } else { 273 | Err(Error::new(ErrorKind::TypeError { 274 | expect: PV::Nil.bt_type_of(), 275 | got: v.bt_type_of(), 276 | })) 277 | } 278 | } 279 | } 280 | 281 | /** 282 | * A type that all SPAIK values may be converted to. 283 | * 284 | * ```rust 285 | * use spaik::{Spaik, Ignore}; 286 | * let mut vm = Spaik::new_no_core(); 287 | * // Both of these succeed 288 | * let _: Ignore = vm.eval("1").unwrap(); 289 | * let _: Ignore = vm.eval(r#"(concat "a" "b")"#).unwrap(); 290 | * ``` 291 | */ 292 | pub struct Ignore; 293 | 294 | impl IntoLisp for Ignore { 295 | fn into_pv(self, _: &mut Arena) -> Result { 296 | Ok(PV::Nil) 297 | } 298 | } 299 | 300 | impl TryFrom for Ignore { 301 | type Error = Error; 302 | fn try_from(_v: PV) -> Result { 303 | Ok(Ignore) 304 | } 305 | } 306 | 307 | impl IntoLisp for bool { 308 | fn into_pv(self, _: &mut Arena) -> Result { 309 | Ok(PV::Bool(self)) 310 | } 311 | } 312 | 313 | impl IntoLisp for &str { 314 | fn into_pv(self, mem: &mut Arena) -> Result { 315 | Ok(mem.put_pv(self.to_string())) 316 | } 317 | } 318 | 319 | impl IntoLisp for String { 320 | fn into_pv(self, mem: &mut Arena) -> Result { 321 | Ok(mem.put_pv(self)) 322 | } 323 | } 324 | 325 | impl IntoLisp for SymID { 326 | fn into_pv(self, _mem: &mut Arena) -> Result { 327 | Ok(PV::Sym(self)) 328 | } 329 | } 330 | 331 | impl IntoLisp for Vec 332 | where T: IntoLisp 333 | { 334 | fn into_pv(self, mem: &mut Arena) -> Result { 335 | let arr = self.into_iter() 336 | .map(|v| v.into_pv(mem)) 337 | .collect::, _>>()?; 338 | Ok(mem.put_pv(arr)) 339 | } 340 | } 341 | 342 | impl TryFrom for Vec 343 | where T: TryFrom 344 | { 345 | type Error = Error; 346 | fn try_from(v: PV) -> Result, Self::Error> { 347 | with_ref!(v, Vector(v) => { 348 | (*v).iter().map(|&x| x.try_into()) 349 | .collect::>() 350 | }) 351 | } 352 | } 353 | 354 | impl TryFrom for HashMap 355 | where K: std::hash::Hash + Eq + TryFrom, 356 | V: TryFrom 357 | { 358 | type Error = Error; 359 | fn try_from(v: PV) -> Result, Self::Error> { 360 | with_ref!(v, Table(v) => { 361 | (*v).iter().map(|(&k, &v)| Ok((k.try_into()?, v.try_into()?))) 362 | .collect::>() 363 | }) 364 | } 365 | } 366 | 367 | impl IntoLisp for HashMap 368 | where K: std::hash::Hash + Eq + IntoLisp, 369 | V: IntoLisp 370 | { 371 | fn into_pv(self, mem: &mut Arena) -> Result { 372 | let arr = self.into_iter() 373 | .map(|(k, v)| -> Result<_, Error> { 374 | Ok((k.into_pv(mem)?, v.into_pv(mem)?)) 375 | }) 376 | .collect::, _>>()?; 377 | Ok(mem.put_pv(arr)) 378 | } 379 | } 380 | 381 | 382 | /// NOTE: This is safe because while the `String` is shifted around by the 383 | /// GC mark-sweep phase, the actual allocated string contents are not. 384 | /// XXX: This definition also means that strings *must* be immutable. 385 | impl<'a> TryFrom for &'a str { 386 | type Error = Error; 387 | fn try_from(v: PV) -> Result<&'a str, Self::Error> { 388 | with_ref!(v, String(s) => { Ok(&*s) }) 389 | } 390 | } 391 | 392 | impl IntoLisp for Result 393 | where T: IntoLisp, E: Into> 394 | { 395 | fn into_pv(self, mem: &mut Arena) -> Result { 396 | match self { 397 | Ok(v) => v.into_pv(mem), 398 | Err(e) => Err(ErrorKind::ExtError(ExtError(Arc::new(e.into()))).into()), 399 | } 400 | } 401 | } 402 | 403 | // pub trait CloneSubr: Subr { 404 | // fn clone_subr(&self) -> Box; 405 | // } 406 | 407 | // impl CloneSubr for T 408 | // where 409 | // T: 'static + Subr 410 | // { 411 | // fn clone_subr(&self) -> Box { 412 | // Box::new(self.clone()) 413 | // } 414 | // } 415 | 416 | #[cfg(feature = "freeze")] 417 | pub type SubrThawFn = fn(from: &mut dyn Read) -> Result, Error>; 418 | #[cfg(feature = "freeze")] 419 | static SUBR_THAW_FNS: OnceLock>> = OnceLock::new(); 420 | // #[cfg(feature = "freeze")] 421 | // static SUBRS: OnceLock>>> = OnceLock::new(); 422 | 423 | #[cfg(feature = "freeze")] 424 | pub struct Zubr { 425 | name: TypePath, 426 | data: Option>, 427 | } 428 | 429 | #[cfg(feature = "freeze")] 430 | impl Zubr { 431 | pub fn funmut() { 432 | let thaw_fns = SUBR_THAW_FNS.get_or_init(|| Mutex::new(HMap::default())); 433 | if let Entry::Vacant(e) = thaw_fns.lock().unwrap().entry(TypePath::of::()) { 434 | e.insert(|from| { 435 | use bincode::Options; 436 | let opts = bincode::DefaultOptions::new(); 437 | let obj: T = opts.deserialize_from(from).unwrap(); 438 | Ok(obj.into_subr()) 439 | }); 440 | } 441 | } 442 | 443 | pub fn fun(_f: F) 444 | where A: Send + 'static, 445 | R: Send + 'static, 446 | F: Funcable + IntoSubr + Clone + 'static 447 | { 448 | let _thaw_fns = SUBR_THAW_FNS.get_or_init(|| Mutex::new(HMap::default())); 449 | // let subrs = SUBRS.get_or_init(|| Mutex::new(HMap::default())); 450 | // if let Entry::Vacant(e) = thaw_fns.lock().unwrap().entry(TypePath::of::()) { 451 | // subrs.lock().unwrap().insert(TypePath::of::(), Box::new(RLambda::new(f))); 452 | // e.insert(|_| { 453 | // let subrs = SUBRS.get_or_init(|| Mutex::new(HMap::default())); 454 | // Ok(subrs.lock().unwrap()[&TypePath::of::()].clone_subr()) 455 | // }); 456 | // } 457 | } 458 | 459 | pub fn thaw(&self) -> Result, Error> { 460 | let thaw_fns = SUBR_THAW_FNS.get_or_init(|| Mutex::new(HMap::default())); 461 | // let mut cr = Cursor::new(&self.data); 462 | let empty = []; 463 | let mut cr = if let Some(ref data) = self.data { 464 | Cursor::new(&data[..]) 465 | } else { 466 | Cursor::new(&empty[..]) 467 | }; 468 | (thaw_fns.lock().unwrap()[&self.name])(&mut cr) 469 | } 470 | } 471 | 472 | pub unsafe trait Subr: SubrClone + Send + 'static { 473 | fn call(&mut self, vm: &mut R8VM, args: &[PV]) -> Result; 474 | fn name(&self) -> &'static str; 475 | #[cfg(feature = "freeze")] 476 | fn freeze(&self) -> Zubr { 477 | Zubr { name: TypePath::of::(), data: None } 478 | } 479 | } 480 | 481 | impl Clone for Box { 482 | fn clone(&self) -> Self { 483 | self.clone_inner_subr() 484 | } 485 | } 486 | 487 | pub trait SubrClone { 488 | fn clone_inner_subr(&self) -> Box; 489 | } 490 | 491 | impl SubrClone for T where T: Clone + Subr { 492 | fn clone_inner_subr(&self) -> Box { 493 | Box::new(self.clone()) 494 | } 495 | } 496 | 497 | pub trait BoxSubr { 498 | fn into_subr(self) -> Box; 499 | } 500 | 501 | impl BoxSubr for T where T: Subr + Sized + 'static { 502 | fn into_subr(self) -> Box { 503 | Box::new(self) 504 | } 505 | } 506 | 507 | pub trait Funcable: Send + 'static { 508 | fn call(&mut self, vm: &mut R8VM, args: &[PV]) -> Result; 509 | } 510 | 511 | macro_rules! impl_funcable { 512 | ($($x:tt),*) => { 513 | #[allow(non_snake_case, unused_parens)] 514 | impl Funcable<($($x,)*), Rt> for Funk 515 | where Funk: FnMut($($x),*) -> Rt + Send + 'static, 516 | $(PV: FromLisp>,)* 517 | Rt: IntoLisp 518 | { 519 | fn call(&mut self, vm: &mut R8VM, args: &[PV]) -> Result { 520 | let ($(ObjRef($x)),*) = match &args[..] { 521 | &[$($x),*] => ($($x.from_lisp(&mut vm.mem)?),*), 522 | _ => return err!(ArgError, 523 | expect: ArgSpec::normal(count_args!($($x),*)), 524 | got_num: args.len() as u32) 525 | }; 526 | (self)($($x),*).into_pv(&mut vm.mem) 527 | } 528 | } 529 | 530 | impl IntoSubr<($($x,)*), Rt> for Funk 531 | where Funk: Clone + FnMut($($x),*) -> Rt + Funcable<($($x,)*), Rt> + 'static, 532 | $($x: Send + 'static,)* 533 | Rt: Send + IntoLisp + 'static, 534 | { 535 | fn into_subr(self) -> Box { 536 | Box::new(RLambda::new(self)) 537 | } 538 | } 539 | 540 | impl Lispify<($($x,)*), Rt, fn()> for Funk 541 | where Funk: IntoSubr<($($x,)*), Rt> 542 | { 543 | fn lispify(self, mem: &mut Arena) -> Result { 544 | self.into_subr().into_pv(mem) 545 | } 546 | } 547 | }; 548 | } 549 | 550 | impl_funcable!(); 551 | impl_funcable!(A); 552 | impl_funcable!(A, B); 553 | impl_funcable!(A, B, C); 554 | impl_funcable!(A, B, C, D); 555 | impl_funcable!(A, B, C, D, E); 556 | impl_funcable!(A, B, C, D, E, F); 557 | impl_funcable!(A, B, C, D, E, F, G); 558 | impl_funcable!(A, B, C, D, E, F, G, H); 559 | impl_funcable!(A, B, C, D, E, F, G, H, I); 560 | impl_funcable!(A, B, C, D, E, F, G, H, I, J); 561 | impl_funcable!(A, B, C, D, E, F, G, H, I, J, K); 562 | impl_funcable!(A, B, C, D, E, F, G, H, I, J, K, L); 563 | 564 | #[derive(Serialize, Deserialize)] 565 | pub struct RLambda 566 | where A: Send + 'static, R: Send + 'static, F: Funcable 567 | { 568 | f: F, 569 | _phantom: PhantomData<(A, R)>, 570 | } 571 | 572 | impl Clone for RLambda 573 | where F: Clone, A: Send + 'static, R: Send + 'static, F: Funcable 574 | { 575 | fn clone(&self) -> Self { 576 | Self { f: self.f.clone(), 577 | _phantom: Default::default() } 578 | } 579 | } 580 | 581 | unsafe impl Subr for RLambda 582 | where A: Send, R: Send, F: Clone + Funcable + Any 583 | { 584 | fn call(&mut self, vm: &mut R8VM, args: &[PV]) -> Result { 585 | self.f.call(vm, args) 586 | } 587 | 588 | fn name(&self) -> &'static str { 589 | std::any::type_name::() 590 | } 591 | } 592 | 593 | impl RLambda 594 | where A: Send + 'static, R: Send + 'static, F: Funcable 595 | { 596 | pub fn new(f: F) -> Self { 597 | RLambda { f, _phantom: Default::default() } 598 | } 599 | } 600 | 601 | pub trait IntoSubr { 602 | fn into_subr(self) -> Box; 603 | } 604 | 605 | pub trait Lispify { 606 | fn lispify(self, mem: &mut Arena) -> Result; 607 | } 608 | 609 | impl IntoLisp for &mut T where T: Userdata { 610 | fn into_pv(self, mem: &mut Arena) -> Result { 611 | let (rf, _p) = mem.put(Object::from_ref(self)); 612 | mem.push_borrow(rf); 613 | Ok(PV::Ref(rf)) 614 | } 615 | } 616 | 617 | impl Lispify<(), (), ()> for T where T: IntoLisp { 618 | fn lispify(self, mem: &mut Arena) -> Result { 619 | self.into_pv(mem) 620 | } 621 | } 622 | 623 | impl fmt::Debug for Box { 624 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 625 | write!(f, "(subr {})", self.name()) 626 | } 627 | } 628 | 629 | impl LispFmt for Box { 630 | fn lisp_fmt(&self, 631 | _: &mut VisitSet, 632 | f: &mut fmt::Formatter<'_>) -> fmt::Result { 633 | write!(f, "(subr {})", self.name()) 634 | } 635 | } 636 | 637 | impl IntoLisp for Box { 638 | fn into_pv(self, mem: &mut Arena) -> Result { 639 | Ok(mem.put_pv(self)) 640 | } 641 | } 642 | 643 | #[derive(Debug)] 644 | pub struct PList(pub T) where T: DeserializeOwned; 645 | 646 | impl TryFrom for PList where T: DeserializeOwned { 647 | type Error = Error; 648 | fn try_from(value: PV) -> Result { 649 | Ok(PList(deserialize::from_pv(value)?)) 650 | } 651 | } 652 | 653 | impl TryFrom for ObjRef> { 654 | type Error = Error; 655 | #[inline(always)] 656 | fn try_from(v: PV) -> Result>, Self::Error> { 657 | Ok(ObjRef(v.try_into()?)) 658 | } 659 | } 660 | 661 | impl Deref for PList { 662 | type Target = T; 663 | 664 | fn deref(&self) -> &Self::Target { 665 | &self.0 666 | } 667 | } 668 | 669 | impl DerefMut for PList { 670 | fn deref_mut(&mut self) -> &mut Self::Target { 671 | &mut self.0 672 | } 673 | } 674 | 675 | /// The `mem` parameter is necessary here, because some of the conversions 676 | /// may need to create an SPV reference-counter 677 | pub trait FromLisp3: Sized { 678 | #[allow(clippy::wrong_self_convention)] 679 | fn from_lisp_3(self, mem: &mut Arena) -> Result; 680 | } 681 | 682 | impl FromLisp3, Option<()>, ()> for PV where PV: FromLisp, T: Sized { 683 | fn from_lisp_3(self, mem: &mut Arena) -> Result, Error> { 684 | match self { 685 | PV::Nil => Ok(None), 686 | x => Ok(Some(x.from_lisp(mem)?)) 687 | } 688 | } 689 | } 690 | 691 | 692 | impl FromLisp3 for PV where PV: FromLisp, T: Sized { 693 | fn from_lisp_3(self, mem: &mut Arena) -> Result { 694 | self.from_lisp(mem) 695 | } 696 | } 697 | 698 | impl Lispify<(), (), Option> for Option where A: IntoLisp { 699 | fn lispify(self, mem: &mut Arena) -> Result { 700 | match self { 701 | Some(x) => x.lispify(mem), 702 | None => Ok(PV::Nil) 703 | } 704 | } 705 | } 706 | 707 | #[cfg(test)] 708 | mod tests { 709 | use std::sync::{Arc, Mutex}; 710 | 711 | #[cfg(feature = "derive")] 712 | use spaik_proc_macros::Userdata; 713 | 714 | use crate::{Spaik, PList, logging}; 715 | 716 | use serde::{Serialize, Deserialize}; 717 | 718 | #[test] 719 | fn from_plist() { 720 | #[derive(Debug, Deserialize, Clone, Copy, PartialEq)] 721 | #[serde(rename_all = "kebab-case")] 722 | struct Lmao { 723 | x: i32, 724 | blah_blah: f32 725 | } 726 | 727 | let lmao: Arc>> = Arc::new(Mutex::new(None)); 728 | let lmao_2 = lmao.clone(); 729 | let mut vm = Spaik::new_no_core(); 730 | vm.set("f", move |x: PList| { *lmao.lock().unwrap() = Some(*x) }); 731 | vm.exec("(f '(:x 123 :blah-blah 1.2))").unwrap(); 732 | let lock = lmao_2.lock().unwrap(); 733 | assert_eq!(lock.clone(), Some(Lmao { x: 123, blah_blah: 1.2 })) 734 | } 735 | 736 | #[test] 737 | fn option_from_lisp() { 738 | let mut vm = Spaik::new_no_core(); 739 | let ans: Option = vm.eval("1").unwrap(); 740 | assert_eq!(ans, Some(1)) 741 | } 742 | 743 | #[test] 744 | fn option_to_lisp() { 745 | let mut vm = Spaik::new_no_core(); 746 | vm.exec("(define (::is-nil? w) (= w nil))").unwrap(); 747 | let ans: Option = None; 748 | let ans: bool = vm.call("::is-nil?", (ans,)).unwrap(); 749 | assert!(ans); 750 | vm.exec("(define (::id w) w)").unwrap(); 751 | let ans: Option = Some(1); 752 | let ans: i32 = vm.call("::id", (ans,)).unwrap(); 753 | assert_eq!(ans, 1); 754 | assert_eq!(ans, 1); 755 | } 756 | 757 | #[cfg(feature = "derive")] 758 | #[test] 759 | fn method_calls() { 760 | use spaik_proc_macros::{methods, Userdata}; 761 | 762 | let mut vm = Spaik::new_no_core(); 763 | 764 | #[derive(Debug, Clone, PartialEq, PartialOrd, Userdata)] 765 | #[cfg_attr(feature = "freeze", derive(Serialize, Deserialize))] 766 | struct Lmao {} 767 | 768 | #[methods(())] 769 | impl Lmao { 770 | fn foo(&self, x: i32, y: i32) -> i32 { 771 | x + y 772 | } 773 | 774 | fn bar(&self, x: i32, y: i32, z: &str) -> String { 775 | format!("answer: {} ({z})", x+y) 776 | } 777 | 778 | fn baz(&self, x: f32, y: f32, z: &str) -> String { 779 | format!("answer: {} ({z})", x+y) 780 | } 781 | } 782 | 783 | vm.set("lmao", Lmao{}); 784 | vm.defmethods::(); 785 | assert_eq!(vm.eval("(lmao :foo 1 2)"), Ok(3)); 786 | assert_eq!(vm.eval(r#"(lmao :bar 8.8 8.8 "lmao")"#), Ok("answer: 16 (lmao)")); 787 | assert_eq!(vm.eval(r#"(lmao :baz 8 8 "lmao")"#), Ok("answer: 16 (lmao)")); 788 | assert_eq!(vm.eval(r#"(lmao :baz 8.8 8.8 "lmao")"#), Ok("answer: 17.6 (lmao)")); 789 | } 790 | 791 | #[cfg(feature = "math")] 792 | #[test] 793 | fn call_with_vec() { 794 | let mut vm = Spaik::new_no_core(); 795 | vm.set("f", |v: glam::Vec2| { 3.0 * v }); 796 | assert_eq!(vm.eval("(f (vec2 1 2))"), Ok(glam::vec2(3.0, 6.0))); 797 | vm.set("f", |v: glam::Vec3| { 3.0 * v }); 798 | assert_eq!(vm.eval("(f (vec3 1 2 3))"), Ok(glam::vec3(3.0, 6.0, 9.0))); 799 | vm.set("f", |v: glam::Vec4| { 3.0 * v }); 800 | assert_eq!(vm.eval("(f (vec4 1 2 3 4))"), Ok(glam::vec4(3.0, 6.0, 9.0, 12.0))); 801 | } 802 | 803 | #[cfg(feature = "math")] 804 | #[test] 805 | fn call_with_vec4() { 806 | logging::setup_logging(); 807 | log::trace!("Making VM ..."); 808 | let mut vm = Spaik::new_no_core(); 809 | vm.set("f", |v: glam::Vec4| { 3.0 * v }); 810 | assert_eq!(vm.eval("(f (vec4 1 2 3 4))"), Ok(glam::vec4(3.0, 6.0, 9.0, 12.0))); 811 | } 812 | } 813 | -------------------------------------------------------------------------------- /src/swym.rs: -------------------------------------------------------------------------------- 1 | use core::slice; 2 | use std::collections::HashSet; 3 | use std::sync::atomic::AtomicU32; 4 | use std::{cmp, fmt}; 5 | use std::hash::{Hash, self, BuildHasher}; 6 | use std::{ptr::NonNull, mem, ptr}; 7 | use std::alloc::{Layout, alloc, dealloc, handle_alloc_error}; 8 | use std::fmt::{Debug, Display}; 9 | 10 | use crate::utils::{HMap, HSet}; 11 | use serde::{Deserialize, Serialize}; 12 | 13 | use crate::AsSym; 14 | use crate::nkgc::PV; 15 | use crate::nuke::GcRc; 16 | use crate::nuke::memcpy; 17 | use crate::r8vm::{R8VM, VMID}; 18 | 19 | pub struct Sym { 20 | rc: GcRc, 21 | ptr: NonNull, 22 | len: usize, 23 | sz: usize, 24 | // vm_id: VMID, 25 | } 26 | 27 | impl Sym { 28 | // This is for creating &'static Sym. If you use this to create Syms on the 29 | // heap like in SwymDb, you will be leaking the alloc() because the 30 | // ref-count is initialized to 2. 31 | // 32 | // Either allocate all Syms in bulk on a Vec, and then free that later, 33 | // or store the Syms in a static array. 34 | pub const fn from_static(st: &'static str) -> Sym { 35 | let len = st.len(); 36 | Sym { 37 | ptr: unsafe { NonNull::new_unchecked(st.as_ptr() as *mut u8) }, 38 | rc: GcRc::new(AtomicU32::new(2)), 39 | len, 40 | sz: 0 41 | } 42 | } 43 | } 44 | 45 | unsafe impl Send for Sym {} 46 | unsafe impl Sync for Sym {} 47 | 48 | pub struct SymRef(*mut Sym); 49 | unsafe impl Send for SymRef {} 50 | unsafe impl Sync for SymRef {} 51 | 52 | impl SymRef { 53 | pub fn eq_pv(&self, pv: PV) -> bool { 54 | pv.sym().map(|sym| sym.0 == self.0).unwrap_or_default() 55 | } 56 | 57 | pub(crate) fn inner(&self) -> *mut Sym { 58 | self.0 59 | } 60 | } 61 | 62 | impl Hash for SymRef { 63 | fn hash(&self, state: &mut H) { 64 | self.0.hash(state); 65 | } 66 | } 67 | 68 | impl From<&'static Sym> for SymRef { 69 | fn from(value: &'static Sym) -> Self { 70 | value.rc.inc(); 71 | Self(value as *const Sym as *mut Sym) 72 | } 73 | } 74 | 75 | impl SymRef { 76 | /// This is only intended for R8VM-internal use, where we need the syms to 77 | /// be Copy, and know that they will not be dropped because the SwymDb is 78 | /// live for as long as the R8VM is. 79 | pub(crate) fn id(self) -> SymID { 80 | let p = self.0; 81 | drop(self); 82 | SymID(p) 83 | } 84 | } 85 | 86 | impl AsSym for &SymRef { 87 | fn as_sym(&self, _vm: &mut R8VM) -> SymID { 88 | SymID(self.0) 89 | } 90 | } 91 | 92 | impl AsSym for SymRef { 93 | fn as_sym(&self, _vm: &mut R8VM) -> SymID { 94 | SymID(self.0) 95 | } 96 | } 97 | 98 | impl Display for SymRef { 99 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 100 | write!(f, "{}", self.as_ref()) 101 | } 102 | } 103 | 104 | impl From for String { 105 | fn from(v: SymRef) -> Self { 106 | unsafe { 107 | let p = v.0; 108 | mem::forget(v); 109 | take_inner_string(p) 110 | } 111 | } 112 | } 113 | 114 | impl AsRef for SymRef { 115 | fn as_ref(&self) -> &str { 116 | unsafe { 117 | let p = (*self.0).ptr; 118 | let slice = slice::from_raw_parts(p.as_ptr(), (*self.0).len); 119 | std::str::from_utf8_unchecked(slice) 120 | } 121 | } 122 | } 123 | 124 | impl AsRef for SymID { 125 | fn as_ref(&self) -> &str { 126 | unsafe { 127 | let p = (*self.0).ptr; 128 | let slice = slice::from_raw_parts(p.as_ptr(), (*self.0).len); 129 | std::str::from_utf8_unchecked(slice) 130 | } 131 | } 132 | } 133 | 134 | impl From for SymRef { 135 | fn from(val: SymID) -> Self { 136 | unsafe { SymRef::new(val.0) } 137 | } 138 | } 139 | 140 | impl TryFrom for SymRef { 141 | type Error = crate::Error; 142 | 143 | fn try_from(value: crate::PV) -> Result { 144 | value.sym().map(|s| s.into()) 145 | } 146 | } 147 | 148 | #[derive(Eq, PartialEq, Hash, Clone, Copy)] 149 | pub struct SymID(pub(crate) *mut Sym); 150 | 151 | impl SymID { 152 | pub fn new(sym: *mut Sym) -> Self { 153 | Self(sym) 154 | } 155 | 156 | pub fn as_int(&self) -> isize { 157 | self.0 as isize 158 | } 159 | } 160 | 161 | impl Debug for SymID { 162 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result { 163 | debug_print_sym(self.0, f) 164 | } 165 | } 166 | 167 | impl PartialOrd for SymID { 168 | fn partial_cmp(&self, other: &Self) -> Option { 169 | Some(self.cmp(other)) 170 | } 171 | } 172 | 173 | impl Ord for SymID { 174 | fn cmp(&self, other: &Self) -> cmp::Ordering { 175 | self.as_ref().cmp(other.as_ref()) 176 | } 177 | } 178 | 179 | impl<'de> Deserialize<'de> for SymID { 180 | fn deserialize(_d: D) -> Result 181 | where D: serde::Deserializer<'de> { 182 | todo!() 183 | } 184 | } 185 | 186 | impl Serialize for SymID { 187 | fn serialize(&self, _serializer: S) -> Result 188 | where 189 | S: serde::Serializer { 190 | todo!() 191 | } 192 | } 193 | 194 | fn debug_print_sym(sym: *mut Sym, f: &mut std::fmt::Formatter<'_>) -> fmt::Result { 195 | unsafe { 196 | let slice = slice::from_raw_parts((*sym).ptr.as_ptr(), (*sym).len); 197 | write!(f, "{}", std::str::from_utf8_unchecked(slice)) 198 | } 199 | } 200 | 201 | impl Debug for SymRef { 202 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result { 203 | debug_print_sym(self.0, f) 204 | } 205 | } 206 | 207 | #[derive(Clone)] 208 | pub struct SymKeyRef(SymRef); 209 | 210 | impl SymKeyRef { 211 | pub fn into_inner(self) -> SymRef { 212 | self.0 213 | } 214 | 215 | pub fn clone_inner(&self) -> SymRef { 216 | self.0.clone() 217 | } 218 | } 219 | 220 | impl Clone for SymRef { 221 | fn clone(&self) -> Self { 222 | unsafe { (*self.0).rc.inc() } 223 | Self(self.0) 224 | } 225 | } 226 | 227 | impl SymRef { 228 | unsafe fn new(from: *mut Sym) -> Self { 229 | unsafe { 230 | (*from).rc.inc(); 231 | Self(from) 232 | } 233 | } 234 | } 235 | 236 | impl Hash for SymKeyRef { 237 | fn hash(&self, state: &mut H) { 238 | unsafe { 239 | let p = (*self.0.0).ptr.as_ptr(); 240 | let len = (*self.0.0).len; 241 | for i in 0..len { 242 | (*p.add(i)).hash(state); 243 | } 244 | } 245 | } 246 | } 247 | 248 | impl PartialEq for SymRef { 249 | fn eq(&self, other: &Self) -> bool { 250 | self.0 == other.0 251 | } 252 | } 253 | 254 | impl Eq for SymRef {} 255 | 256 | impl PartialEq for SymKeyRef { 257 | fn eq(&self, other: &Self) -> bool { 258 | unsafe { 259 | let l = (*self.0.0).ptr.as_ptr(); 260 | let r = (*other.0.0).ptr.as_ptr(); 261 | let l_len = (*self.0.0).len; 262 | let r_len = (*other.0.0).len; 263 | slice::from_raw_parts(l, l_len) == slice::from_raw_parts(r, r_len) 264 | } 265 | } 266 | } 267 | 268 | impl Eq for SymKeyRef {} 269 | 270 | unsafe fn take_inner_string(p: *mut Sym) -> String { 271 | let layout = Layout::from_size_align_unchecked( 272 | mem::size_of::(), 273 | mem::align_of::(), 274 | ); 275 | if (*p).rc.is_owned() { 276 | let s = String::from_raw_parts((*p).ptr.as_ptr(), (*p).len, (*p).sz); 277 | dealloc(p as *mut u8, layout); 278 | s 279 | } else { 280 | let layout = Layout::array::((*p).len).unwrap(); 281 | let buf = alloc(layout); 282 | if buf.is_null() { 283 | handle_alloc_error(layout); 284 | } 285 | memcpy(buf, (*p).ptr.as_ptr(), (*p).len); 286 | let s = String::from_raw_parts(buf, (*p).len, (*p).len); 287 | (*p).rc.is_dropped(); 288 | s 289 | } 290 | } 291 | 292 | impl Drop for SymRef { 293 | fn drop(&mut self) { 294 | unsafe { 295 | let layout = Layout::from_size_align_unchecked( 296 | mem::size_of::(), 297 | mem::align_of::(), 298 | ); 299 | if (*self.0).rc.is_dropped() { 300 | debug_assert_ne!((*self.0).sz, 0); 301 | drop(String::from_raw_parts((*self.0).ptr.as_ptr(), 302 | (*self.0).len, 303 | (*self.0).sz)); 304 | dealloc(self.0 as *mut u8, layout) 305 | } 306 | } 307 | } 308 | } 309 | 310 | #[derive(Default)] 311 | pub struct SwymDb { 312 | map: HSet, 313 | } 314 | 315 | impl Clone for SwymDb { 316 | fn clone(&self) -> Self { 317 | let map = self.map.clone(); 318 | for sym in map.iter() { 319 | unsafe { (*sym.0.0).rc.inc() } 320 | } 321 | Self { map } 322 | } 323 | } 324 | 325 | impl Debug for SwymDb { 326 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 327 | f.debug_struct("SwymDb").field("map", &"...").finish() 328 | } 329 | } 330 | 331 | impl From for HashSet 332 | where HashSet: Default, 333 | H: BuildHasher 334 | { 335 | fn from(mut v: SwymDb) -> HashSet { 336 | let mut hm: HashSet = Default::default(); 337 | for r in v.map.drain() { 338 | hm.insert(r.into_inner().into()); 339 | } 340 | hm 341 | } 342 | } 343 | 344 | impl SwymDb { 345 | pub fn put(&mut self, s: String) -> SymRef { 346 | unsafe { 347 | let mut s = mem::ManuallyDrop::new(s); 348 | 349 | let mut sym = Sym { 350 | ptr: NonNull::new(s.as_mut_ptr()).unwrap(), 351 | len: s.len(), 352 | sz: s.capacity(), 353 | rc: GcRc::new(AtomicU32::new(0)) 354 | }; 355 | 356 | let key = mem::ManuallyDrop::new( 357 | SymKeyRef(SymRef((&mut sym) as *mut Sym)) 358 | ); 359 | if let Some(v) = self.map.get(&key) { 360 | drop(String::from_raw_parts(s.as_mut_ptr(), 361 | s.len(), 362 | s.capacity())); 363 | v.clone_inner() 364 | } else { 365 | debug_assert_ne!(s.capacity(), 0); 366 | let layout = Layout::for_value(&sym); 367 | let p = alloc(layout) as *mut Sym; 368 | if p.is_null() { 369 | handle_alloc_error(layout); 370 | } 371 | ptr::write(p, sym); 372 | let sym = SymRef::new(p); 373 | self.map.insert(SymKeyRef(sym.clone())); 374 | sym 375 | } 376 | } 377 | } 378 | 379 | pub fn put_ref(&mut self, s: &str) -> SymRef { 380 | let mut sym = Sym { 381 | ptr: NonNull::new(s.as_ptr() as *mut u8).unwrap(), 382 | len: s.len(), 383 | sz: 0, 384 | rc: GcRc::new(AtomicU32::new(0)) 385 | }; 386 | let key = mem::ManuallyDrop::new( 387 | SymKeyRef(SymRef((&mut sym) as *mut Sym)) 388 | ); 389 | if let Some(v) = self.map.get(&key) { 390 | v.clone_inner() 391 | } else { 392 | let mut s = mem::ManuallyDrop::new(s.to_string()); 393 | sym.ptr = NonNull::new(s.as_mut_ptr()).unwrap(); 394 | sym.sz = s.capacity(); 395 | let layout = Layout::for_value(&sym); 396 | unsafe { 397 | let p = alloc(layout) as *mut Sym; 398 | if p.is_null() { 399 | handle_alloc_error(layout); 400 | } 401 | ptr::write(p, sym); 402 | let sym = SymRef::new(p); 403 | self.map.insert(SymKeyRef(sym.clone())); 404 | sym 405 | } 406 | } 407 | } 408 | 409 | pub fn put_static(&mut self, sym: &'static Sym) { 410 | let key = SymKeyRef(SymRef(sym as *const Sym as *mut Sym)); 411 | self.map.insert(key); 412 | } 413 | 414 | pub fn len(&self) -> usize { 415 | self.map.len() 416 | } 417 | 418 | pub fn is_empty(&self) -> bool { 419 | self.map.is_empty() 420 | } 421 | 422 | pub fn iter(&self) -> impl Iterator { 423 | self.map.iter().map(|key| (key.0.0 as *const Sym, key.0.as_ref())) 424 | } 425 | } 426 | 427 | impl Drop for SwymDb { 428 | fn drop(&mut self) { 429 | for key in self.map.drain() { 430 | // Ignore statically allocated symbols 431 | if unsafe { (*key.0.0).sz } == 0 { 432 | mem::forget(key); 433 | } 434 | } 435 | } 436 | } 437 | 438 | #[cfg(test)] 439 | mod tests { 440 | use super::*; 441 | 442 | #[test] 443 | fn go_for_a_swym() { 444 | let mut swym = SwymDb::default(); 445 | let lmao1 = swym.put("lmao".to_string()); 446 | let ayy = swym.put("ayy".to_string()); 447 | let lmao2 = swym.put("lmao".to_string()); 448 | assert_eq!(lmao1, lmao2); 449 | assert_eq!(lmao1.0, lmao2.0); 450 | for _ in 0..1000 { 451 | let ayy_n = swym.put("ayy".to_string()); 452 | assert_eq!(ayy_n, ayy); 453 | } 454 | for _ in 0..100 { 455 | let lmao_n = swym.put("lmao".to_string()); 456 | assert_eq!(lmao1, lmao_n); 457 | } 458 | } 459 | 460 | #[test] 461 | fn go_for_a_swym_and_clone_myself_into_a_hashset() { 462 | let mut swym = SwymDb::default(); 463 | let lmao1 = swym.put("lmao".to_string()); 464 | let ayy = swym.put("ayy".to_string()); 465 | let lmao2 = swym.put("lmao".to_string()); 466 | assert_eq!(lmao1, lmao2); 467 | assert_eq!(lmao1.0, lmao2.0); 468 | for _ in 0..1000 { 469 | let ayy_n = swym.put("ayy".to_string()); 470 | assert_eq!(ayy_n, ayy); 471 | } 472 | for _ in 0..100 { 473 | let lmao_n = swym.put("lmao".to_string()); 474 | assert_eq!(lmao1, lmao_n); 475 | } 476 | 477 | let (p_ayy, p_lmao) = unsafe { ((*ayy.0).ptr.as_ptr(), 478 | (*lmao1.0).ptr.as_ptr()) }; 479 | 480 | let hm: HashSet = swym.into(); 481 | 482 | let mut hm_cmp = HashSet::default(); 483 | hm_cmp.insert(String::from("ayy")); 484 | hm_cmp.insert(String::from("lmao")); 485 | assert_eq!(hm, hm_cmp); 486 | 487 | // swym.into() should allocate new Strings, because ayy/lmao are still 488 | // referenced. 489 | assert_ne!((*hm.get("ayy").unwrap()).as_ptr(), p_ayy); 490 | assert_ne!((*hm.get("lmao").unwrap()).as_ptr(), p_lmao); 491 | } 492 | 493 | #[test] 494 | fn go_for_a_swym_and_jump_right_into_a_hashset() { 495 | let mut swym = SwymDb::default(); 496 | let (p_ayy, p_lmao) = { 497 | let lmao1 = swym.put("lmao".to_string()); 498 | let ayy = swym.put("ayy".to_string()); 499 | let lmao2 = swym.put("lmao".to_string()); 500 | assert_eq!(lmao1, lmao2); 501 | assert_eq!(lmao1.0, lmao2.0); 502 | for _ in 0..1000 { 503 | let ayy_n = swym.put("ayy".to_string()); 504 | assert_eq!(ayy_n, ayy); 505 | } 506 | for _ in 0..100 { 507 | let lmao_n = swym.put("lmao".to_string()); 508 | assert_eq!(lmao1, lmao_n); 509 | } 510 | unsafe { ((*ayy.0).ptr.as_ptr(), 511 | (*lmao1.0).ptr.as_ptr()) } 512 | }; 513 | 514 | let hm: HashSet = swym.into(); 515 | 516 | let mut hm_cmp = HashSet::default(); 517 | hm_cmp.insert(String::from("ayy")); 518 | hm_cmp.insert(String::from("lmao")); 519 | assert_eq!(hm, hm_cmp); 520 | 521 | // Confirm that we have the same exact String allocations as we started 522 | // with. 523 | assert_eq!((*hm.get("ayy").unwrap()).as_ptr(), p_ayy); 524 | assert_eq!((*hm.get("lmao").unwrap()).as_ptr(), p_lmao); 525 | } 526 | 527 | 528 | #[test] 529 | fn hopefully_dont_take_a_hike() { 530 | let mut swym = SwymDb::default(); 531 | let lmao1 = swym.put_ref("lmao"); 532 | let ayy = swym.put_ref("ayy"); 533 | let lmao2 = swym.put_ref("lmao"); 534 | assert_eq!(lmao1, lmao2); 535 | assert_eq!(lmao1.0, lmao2.0); 536 | for _ in 0..1000 { 537 | let ayy_n = swym.put_ref("ayy"); 538 | assert_eq!(ayy_n, ayy); 539 | } 540 | for _ in 0..100 { 541 | let lmao_n = swym.put_ref("lmao"); 542 | assert_eq!(lmao1, lmao_n); 543 | } 544 | } 545 | } 546 | -------------------------------------------------------------------------------- /src/tok.rs: -------------------------------------------------------------------------------- 1 | //! Token Data Structure 2 | 3 | use std::fmt; 4 | 5 | #[derive(Clone, Copy)] 6 | pub struct Token<'a> { 7 | pub line: u32, 8 | pub col: u32, 9 | pub text: &'a str, 10 | } 11 | 12 | impl<'a> fmt::Display for Token<'a> { 13 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 14 | write!(f, "<{}>", self.text.escape_default().collect::()) 15 | } 16 | } 17 | 18 | impl<'a> fmt::Debug for Token<'a> { 19 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 20 | write!(f, "<{}>", self.text.escape_default().collect::()) 21 | } 22 | } 23 | 24 | impl<'a> Token<'a> { 25 | pub fn new(line: u32, col: u32, text: &'a str) -> Token<'a> { 26 | Token { line, col, text } 27 | } 28 | 29 | pub fn inner_str(&self) -> Option { 30 | if self.text.starts_with('"') && self.text.ends_with('"') { 31 | Some(Token::new(self.line, self.col, &self.text[1..self.text.len()-1])) 32 | } else { 33 | None 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/tokit.rs: -------------------------------------------------------------------------------- 1 | //! S-Expression Tokenizer 2 | 3 | use crate::error::ErrorKind; 4 | use crate::error::LineCol; 5 | use crate::error::Meta; 6 | use crate::tok::*; 7 | use crate::error::Error; 8 | use std::collections::VecDeque; 9 | use std::io; 10 | use std::iter::Peekable; 11 | use std::str::CharIndices; 12 | 13 | #[derive(Hash, PartialEq, Eq, Debug, Clone)] 14 | pub struct Fragment { 15 | end: bool, 16 | c: char, 17 | choices: Vec 18 | } 19 | 20 | /** 21 | * The result of an advancement on a token fragment tree. 22 | * 23 | * - `Advance`: The next node to follow. 24 | * - `Valid`: Means that the tree cannot progress using the given 25 | * branch-character, *but* the current node is an end-node 26 | * so it represents a valid operator. The character given 27 | * to advance() when `Valid` is returned is *not* a part of 28 | * the operator. 29 | * - `Invalid`: The input does not match any token in the tree, this means that 30 | * you should backtrack. 31 | */ 32 | enum AdvanceResult<'a> { 33 | Advance(&'a Fragment), 34 | Valid, 35 | Invalid 36 | } 37 | 38 | /** 39 | * A fragment is a part of a operator/token lookup tree. To get a feel for how 40 | * it works, use the print_dot method on `standard_lisp_tok_tree()` with either 41 | * `&mut io::stdout()` or a file handle, then run the following command on your 42 | * generated file: 43 | * 44 | * dot -Tsvg print_dot_output.dot > tree.svg 45 | * 46 | * Then open that svg file in your favorite browser (or Emacs) to see the 47 | * structure. 48 | */ 49 | impl Fragment { 50 | pub fn root() -> Fragment { 51 | Fragment { c: 'ɛ', 52 | choices: Vec::new(), 53 | end: false } 54 | } 55 | 56 | pub fn insert(&mut self, text: &str) { 57 | if text.is_empty() { 58 | self.end = true; 59 | } else if let Some(sub) = self.find_mut(text) { 60 | sub.insert(&text[1..]); 61 | } else { 62 | self.choices.push(Fragment { c: text.chars().next().unwrap(), 63 | choices: Default::default(), 64 | end: false }); 65 | self.choices.last_mut() 66 | .unwrap() 67 | .insert(&text[1..]) 68 | } 69 | } 70 | 71 | fn find_mut(&mut self, text: &str) -> Option<&mut Fragment> { 72 | self.choices 73 | .iter_mut() 74 | .find(|Fragment { c, .. }| text.starts_with(*c)) 75 | } 76 | 77 | pub fn is_valid(&self) -> bool { 78 | self.end 79 | } 80 | 81 | /** 82 | * Advance forward to the next node in the tree, see the documentation for 83 | * AdvanceResult for more information. 84 | * 85 | * # Arguments 86 | * 87 | * - `nc` : Which character to branch on. 88 | */ 89 | fn advance(&self, nc: char) -> AdvanceResult<'_> { 90 | use AdvanceResult::*; 91 | self.choices.iter() 92 | .find(|Fragment { c, .. }| *c == nc) 93 | .map(Advance) 94 | .or(if self.end { 95 | Some(Valid) 96 | } else { 97 | None 98 | }).unwrap_or(Invalid) 99 | } 100 | 101 | /** 102 | * Print out the token fragment tree in the GraphViz dot [1] format. 103 | * 104 | * [1]: https://www.graphviz.org/documentation/ 105 | * 106 | * # Arguments 107 | * 108 | * - `stream` : Output. 109 | */ 110 | #[allow(dead_code)] 111 | pub fn print_dot(&self, stream: &mut dyn io::Write) -> io::Result<()> { 112 | let mut cnt = 0; 113 | let mut roots = vec![(cnt, self)]; 114 | writeln!(stream, "digraph tokens {{")?; 115 | writeln!(stream, r#" end [label="end", shape=box, style=filled]"#)?; 116 | while let Some((id, root)) = roots.pop() { 117 | writeln!(stream, " N{} [label={:?}];", id, 118 | &[root.c].iter().collect::())?; 119 | if root.end { 120 | writeln!(stream, " N{} -> end;", id)?; 121 | } 122 | for sub in root.choices.iter() { 123 | cnt += 1; 124 | writeln!(stream, " N{} -> N{};", id, cnt)?; 125 | roots.push((cnt, sub)); 126 | } 127 | } 128 | writeln!(stream, "}}") 129 | } 130 | } 131 | 132 | #[derive(Default, Clone, Copy, PartialEq, Eq)] 133 | struct Span { 134 | beg: usize, 135 | end: usize 136 | } 137 | 138 | impl Span { 139 | #[inline] 140 | pub fn new(beg: usize, end: usize) -> Span { 141 | Span { beg, end } 142 | } 143 | 144 | #[inline] 145 | pub fn on(beg: usize, c: char) -> Span { 146 | Span::new(beg, beg + c.len_utf8()) 147 | } 148 | 149 | #[inline] 150 | pub fn at(beg: usize) -> Span { 151 | Span { beg, end: beg } 152 | } 153 | 154 | #[inline] 155 | pub fn advance(&mut self, c: char) { 156 | self.end += c.len_utf8(); 157 | } 158 | 159 | #[inline] 160 | pub fn make_tok<'a>(&self, line: u32, col: u32, src: &'a str) -> Token<'a> { 161 | Token { line, col, text: &src[self.beg..self.end] } 162 | } 163 | 164 | #[inline] 165 | pub fn is_empty(&self) -> bool { 166 | self.beg == self.end 167 | } 168 | 169 | #[inline] 170 | pub fn join(&self, o: &Span) -> Span { 171 | Span { beg: self.beg, end: o.end } 172 | } 173 | } 174 | 175 | #[derive(Clone, Copy, PartialEq, Eq)] 176 | enum TokState { 177 | Churn { span: Span, 178 | start: LineTracker }, 179 | InLineComment, 180 | InString { span: Span, 181 | start: LineTracker, 182 | had_escape: bool }, 183 | Done, 184 | } 185 | 186 | #[derive(Clone, Copy, PartialEq, Eq)] 187 | struct LineTracker { 188 | line: u32, 189 | col: u32 190 | } 191 | 192 | impl Default for LineTracker { 193 | fn default() -> Self { 194 | Self { line: 1, 195 | col: Default::default() } 196 | } 197 | } 198 | 199 | impl LineTracker { 200 | #[inline] 201 | pub fn step(&mut self, c: char) { 202 | if c == '\n' { 203 | self.line += 1; 204 | self.col = 0; 205 | } else { 206 | self.col += 1; 207 | } 208 | } 209 | 210 | #[inline] 211 | pub fn get(&self) -> (u32, u32) { 212 | (self.line, self.col) 213 | } 214 | } 215 | 216 | pub fn standard_lisp_tok_tree() -> Fragment { 217 | const TOKS: &[&str] = &[",", ",@", "`", "(", ")", "#'", "#", "'"]; 218 | let mut tree = Fragment::root(); 219 | for tok in TOKS { 220 | tree.insert(tok); 221 | } 222 | tree 223 | } 224 | 225 | pub struct Toker<'a, 'b> { 226 | line_tracker: LineTracker, 227 | state: TokState, 228 | text: &'a str, 229 | frags: &'b Fragment, 230 | it: Peekable>, 231 | toks: VecDeque>, 232 | err: Option, 233 | } 234 | 235 | impl<'a> Toker<'a, '_> { 236 | pub fn new<'b>(text: &'a str, frags: &'b Fragment) -> Toker<'a, 'b> { 237 | let line_tracker = LineTracker::default(); 238 | Toker { line_tracker, 239 | state: TokState::Churn { span: Span::at(0), 240 | start: line_tracker }, 241 | text, 242 | frags, 243 | it: text.char_indices().peekable(), 244 | toks: Default::default(), 245 | err: None } 246 | } 247 | 248 | pub fn check_error(&mut self) -> Result<(), Error> { 249 | self.err.take().map(Err).unwrap_or(Ok(())) 250 | } 251 | 252 | fn add_tok(&mut self, start: &LineTracker, span: &Span) { 253 | if !span.is_empty() { 254 | let (line, col) = start.get(); 255 | self.toks.push_back(span.make_tok(line, col, self.text)); 256 | } 257 | } 258 | 259 | pub fn peek(&mut self) -> Option> { 260 | self.toks.front().copied().or_else(|| { 261 | self.advance(); 262 | self.toks.front().copied() 263 | }) 264 | } 265 | 266 | fn advance(&mut self) { 267 | use TokState::*; 268 | const COMMENT_CHAR: char = ';'; 269 | const QUOTE_CHAR: char = '"'; 270 | const ESCAPE_CHAR: char = '\\'; 271 | 272 | let num_toks = self.toks.len(); 273 | while num_toks == self.toks.len() { 274 | let Some((i, c)) = self.it.next() else { 275 | if self.state == Done { return } 276 | match self.state { 277 | InString { start, .. } => 278 | self.err = Some( 279 | Error::new(ErrorKind::UnterminatedString) 280 | .amend(Meta::Source(start.into())) 281 | ), 282 | Churn { span, start } => 283 | self.add_tok(&start, &span), 284 | _ => () 285 | } 286 | self.state = Done; 287 | return; 288 | }; 289 | 290 | let next_range = move || Span::at(i + c.len_utf8()); 291 | let line_tracker = self.line_tracker; 292 | let churn = move || Churn { span: next_range(), 293 | start: line_tracker }; 294 | self.line_tracker.step(c); 295 | 296 | let mut state = self.state; 297 | match state { 298 | InString { ref mut span, ref mut had_escape, start } => { 299 | span.advance(c); 300 | if *had_escape { 301 | *had_escape = false; 302 | } else if c == ESCAPE_CHAR { 303 | *had_escape = true; 304 | } else if c == QUOTE_CHAR { 305 | self.add_tok(&start, span); 306 | state = churn(); 307 | } 308 | } 309 | InLineComment if c == '\n' => state = churn(), 310 | InLineComment => (), 311 | Churn { span, start } if c == COMMENT_CHAR => { 312 | self.add_tok(&start, &span); 313 | state = InLineComment; 314 | } 315 | Churn { span, start } if c == QUOTE_CHAR => { 316 | self.add_tok(&start, &span); 317 | state = InString { span: Span::on(i, c), 318 | had_escape: false, 319 | start: self.line_tracker }; 320 | } 321 | Churn { ref mut span, ref mut start } if c.is_whitespace() => { 322 | self.add_tok(start, span); 323 | // Skip remaining whitespace 324 | *span = loop { 325 | match self.it.peek() { 326 | Some((_, nc)) if nc.is_whitespace() => 327 | self.line_tracker.step(*nc), 328 | Some((j, _)) => break Span::at(*j), 329 | // Since we're at the end, we can set span to *any* empty 330 | // Span. 331 | None => break Span::at(0), 332 | } 333 | self.it.next().unwrap(); 334 | }; 335 | *start = self.line_tracker; 336 | } 337 | Churn { ref mut span, ref start } => match self.frags.advance(c) { 338 | AdvanceResult::Invalid => span.advance(c), 339 | AdvanceResult::Valid => unreachable!(), 340 | // We've spotted what could possibly be the start of an 341 | // operator, we do 1-lookahead until either finding a valid 342 | // operator sequence, or ending with an invalid one. 343 | AdvanceResult::Advance(mut frag) => { 344 | let op_start = self.line_tracker; 345 | let mut op_span = Span::on(i, c); 346 | state = loop { 347 | if let Some((j, nc)) = self.it.peek().copied() { 348 | match frag.advance(nc) { 349 | AdvanceResult::Valid => { 350 | self.add_tok(start, span); 351 | self.add_tok(&op_start, &op_span); 352 | break Churn { span: Span::at(j), 353 | start: *start }; 354 | } 355 | // Say `-->` is an operator, and you type 356 | // thi--ng, when we reach `n` the token can no 357 | // longer be `-->`, so we continue with symbol 358 | // parsing. 359 | AdvanceResult::Invalid => 360 | break Churn { span: span.join(&op_span), 361 | start: *start }, 362 | AdvanceResult::Advance(next) => { 363 | op_span.advance(c); 364 | frag = next 365 | }, 366 | } 367 | self.line_tracker.step(nc); 368 | self.it.next().unwrap(); 369 | } else if frag.is_valid() { 370 | // Given `-->` is an operator, the input ended with 371 | // something like `this-->`, which will be 372 | // interpreted as [(this), (-->)] 373 | self.add_tok(start, span); 374 | self.add_tok(&op_start, &op_span); 375 | break Done 376 | } else { 377 | // Given `-->` is an operator, the input ended with 378 | // something like `this--`, which will be a symbol. 379 | self.add_tok(start, &span.join(&op_span)); 380 | break Done 381 | } 382 | } 383 | } 384 | } 385 | // The Done state is only reached when Churn has exhausted input. 386 | Done => unreachable!(), 387 | } 388 | self.state = state; 389 | } 390 | } 391 | } 392 | 393 | impl From for LineCol { 394 | fn from(LineTracker { line, col }: LineTracker) -> Self { 395 | LineCol { line, col } 396 | } 397 | } 398 | 399 | impl<'a, 'b> Iterator for Toker<'a, 'b> { 400 | type Item = Token<'a>; 401 | 402 | fn next(&mut self) -> Option { 403 | if let Some(tok) = self.toks.pop_front() { 404 | return Some(tok) 405 | } 406 | self.advance(); 407 | self.toks.pop_front() 408 | } 409 | } 410 | 411 | /** 412 | * Remove unneeded whitespace from SPAIK code. 413 | * 414 | * # Arguments 415 | * 416 | * - `text` : Input SPAIK code 417 | * - `io` : Output stream to write minified code to. 418 | * 419 | */ 420 | pub fn minify(text: &str, f: &mut dyn io::Write) -> Result<(), Error> { 421 | let tok_tree = standard_lisp_tok_tree(); 422 | let mut toker = Toker::new(text, &tok_tree); 423 | macro_rules! ops { 424 | () => { "(" | ")" | "," | ",@" | "'" | "`" | "#'" | "#" }; 425 | } 426 | while let Some(tok) = toker.next() { 427 | if matches!(tok.text, ops!()) { 428 | write!(f, "{}", tok.text)?; 429 | continue; 430 | } 431 | match toker.peek().map(|t| t.text) { 432 | Some(ops!()) | None => write!(f, "{}", tok.text)?, 433 | Some(_) => write!(f, "{} ", tok.text)?, 434 | } 435 | } 436 | Ok(()) 437 | } 438 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | //! Miscellaneous Utilities 2 | 3 | use std::convert::Infallible; 4 | pub use ahash::AHashMap as HMap; 5 | pub use ahash::AHashSet as HSet; 6 | 7 | pub type Success = Result<(), Infallible>; 8 | 9 | macro_rules! count_args { 10 | () => { 0 }; 11 | ( $arg:expr ) => { 1 }; 12 | ( $arg:expr, $($tail:expr),* ) => { 13 | 1 + count_args!($($tail),*) 14 | } 15 | } 16 | 17 | /// SPAIK uses the convention: 18 | /// ```ignore 19 | /// invalid!(x, y, z) // 20 | /// ``` 21 | /// for marking variables that become invalidated, and may have pointers that no 22 | /// longer point to valid data. Do not access any variable after invalid!(...) 23 | /// has marked them, even if rustc will let you! 24 | /// 25 | /// **Do not rely on all invalidated variables being documented** 26 | macro_rules! invalid { 27 | ($($pv:ident),+) => {{}}; 28 | } 29 | 30 | #[allow(unused_macros)] 31 | macro_rules! vm_assert { 32 | ($vm:expr, $ex:expr) => { 33 | assert!($vm.eval::($ex).unwrap(), $ex) 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /tests/html.lisp: -------------------------------------------------------------------------------- 1 | (load html) 2 | 3 | (test html 4 | (= (let ((img "funny")) 5 | (html (img :src (fmt "https://my.site/{img}.png") nil))) 6 | "")) 7 | -------------------------------------------------------------------------------- /tests/test-builtins.lisp: -------------------------------------------------------------------------------- 1 | ;;; 2 | ;;; Sanity-checks for builtins 3 | ;;; 4 | 5 | ;;; [ misc ]---------------------------------------- 6 | (defun tests--sum-n (n) 7 | (/ (* n (+ n 1)) 2)) 8 | 9 | ;;; ---[ math ]---------------------------------------- 10 | (defun math/fibonacci (n) 11 | (let ((a 0) (b 1)) 12 | (while (> n 0) 13 | (set* (a b) (b (+ a b))) 14 | (dec! n)) 15 | a)) 16 | 17 | (defun math/factorial (n) 18 | (let ((x 1)) 19 | (while (> n 0) 20 | (set* (x n) ((* x n) (- n 1)))) 21 | x)) 22 | 23 | (test math 24 | (= (+) 0) 25 | (= (+ 10) 10) 26 | (= (+ 10 10) 20) 27 | (= (*) 1) 28 | (= (* 10) 10) 29 | (= (* 10 10) 100) 30 | (= (- 1) -1) 31 | (= (- 1 2) -1) 32 | (= (- 1 2 3) -4) 33 | (= (/ 2.0) 0.5) 34 | (= (/ 8 2) 4) 35 | (= (/ 8 2 2) 2) 36 | (eq? (map abs (range-list -10 0)) 37 | (reverse (range-list 1 11))) 38 | (eq? (map math/fibonacci (range-list 1 21)) 39 | '(1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765)) 40 | (eq? (map math/factorial (range-list 0 13)) 41 | '(1 1 2 6 24 120 720 5040 40320 362880 3628800 39916800 479001600))) 42 | 43 | ;;; ---[ length ]-------------------------------------- 44 | (test len 45 | (= (len '(a b c)) 3) 46 | (= (len '()) 0) 47 | (= (len '(a b)) 2) 48 | (= (len (vec 1 8 10 12)) 4) 49 | (= (len (vec)) 0) 50 | (= (len "abc") 3) 51 | (= (len "") 0) 52 | (= (len (let ((tbl (make-table))) 53 | (set (get tbl :a) 1) 54 | (set (get tbl :b) 2) 55 | tbl)) 56 | 2) 57 | (= (len (make-table)) 0) 58 | (= (len (vec2 1 1)) 1.4142135) 59 | (= (len (vec2 0 0)) 0.0) 60 | (= (len (vec3 1 1 1)) 1.7320508) 61 | (= (len (vec3 0 0 0)) 0.0) 62 | (= (len (vec4 1 1 1 1)) 2.0) 63 | (= (len (vec4 0 0 0 0)) 0.0)) 64 | 65 | ;;; ---[ loops ]--------------------------------------- 66 | (test loops 67 | (= (loop (break 'ok)) 'ok) 68 | (= (let ((x (dolist (y '(abc def ghi)) 69 | (break y)))) 70 | x) 71 | 'abc) 72 | (eq? (let ((xs nil)) 73 | (loop 74 | (set xs (cons 'x xs)) 75 | (when (= (len xs) 1) 76 | (set xs (cons 'y xs)) 77 | (next)) 78 | (set xs (cons 'z xs)) 79 | (break xs))) 80 | '(z x y x))) 81 | 82 | ;;; ---[ quasi ]--------------------------------------- 83 | (test quasi-quoting 84 | (eq? (let ((x 1) (y 'abc) (z 'xyz)) 85 | `(:x ,x :y ,y :z ,z)) 86 | '(:x 1 :y abc :z xyz)) 87 | (eq? `(x y z) 88 | '(x y z)) 89 | (eq? (let ((x 1) (y 2) (z 3)) 90 | `(,x (,y ,z) ,y ,z ((,x)))) 91 | '(1 (2 3) 2 3 ((1))))) 92 | 93 | ;;; ---[ let ]----------------------------------------- 94 | (test let-bindings 95 | (= (let ((x 1)) x) 96 | 1) 97 | (= (let ((x (let ((y (let ((z 1336)) (+ z 1)))) y))) 98 | x) 99 | 1337)) 100 | 101 | ;;; ---[ lambda ]-------------------------------------- 102 | (test lambda-empty-call 103 | (= ((lambda (x) (+ x 10)) 10) 104 | 20) 105 | ;(= ((lambda (x &? y) (+ x (or y 1))) 10) 106 | ; 11) 107 | ;(= ((lambda (x &? y) (+ x (or y 1))) 10 10) 108 | ; 20) 109 | (= ((lambda (x &rest r) (* x (sum r))) 1) 110 | 0) 111 | (= ((lambda (x &rest r) (* x (sum r))) 12 1 2 3 4 5) 112 | (* 12 (tests--sum-n 5)))) 113 | 114 | (test lambda-capture-call 115 | (= ((let ((y 10)) 116 | (lambda (x) (+ x y))) 10) 117 | 20) 118 | (= ((let ((f (let ((y 1.3e3)) 119 | (lambda (x) (+ x y))))) 120 | (lambda (x) (f x))) 37) 121 | 1337.0)) 122 | 123 | ;;; ---[ static-variables ]---------------------------- 124 | (define var 0) 125 | (defun tests--static-var () 126 | (inc! var)) 127 | 128 | (test static-variables 129 | (eq? `(,(tests--static-var) 130 | ,(tests--static-var) 131 | ,(tests--static-var) 132 | ,(tests--static-var)) 133 | '(1 2 3 4))) 134 | 135 | ;;; ---[ rest-args ]----------------------------------- 136 | (defun tests--mul-sum (x &rest r) 137 | (* x (sum r))) 138 | 139 | (test rest-args 140 | (= (tests--mul-sum 1) 0) 141 | (= (tests--mul-sum 4) 0) 142 | (= (tests--mul-sum 4 1 2 3) (* 4 (tests--sum-n 3))) 143 | (= (tests--mul-sum 6 1 2 3 4 5 6 7 8 9) (* 6 (tests--sum-n 9)))) 144 | 145 | ;;; ---[ optional-args ]------------------------------- 146 | (defun tests--opt (x &opt y) 147 | (+ x (or y 2))) 148 | 149 | (defun tests--opt-2 (x &opt y z) 150 | (+ x (or y 2) (or z 2))) 151 | 152 | (test optional-args 153 | (= (tests--opt 2) 4) 154 | (= (tests--opt 3) 5) 155 | (= (tests--opt 2 3) 5) 156 | (= (tests--opt 2 5) 7) 157 | (= (tests--opt-2 2) 6) 158 | (= (tests--opt-2 3) 7) 159 | (= (tests--opt-2 2 3) 7) 160 | (= (tests--opt-2 2 5) 9) 161 | (= (tests--opt-2 2 3 3) 8) 162 | (= (tests--opt-2 2 5 3) 10)) 163 | 164 | ;;; ---[ rest-and-optional-args ]----------------------- 165 | (defun tests--mul-opt-sum (&opt x &rest r) 166 | (* (or x 1) (sum r))) 167 | 168 | (test rest-and-optional-args 169 | (= (tests--mul-opt-sum) 0) 170 | (= (tests--mul-opt-sum 1) 0) 171 | (= (tests--mul-opt-sum 4 1 2 3) (* 4 (tests--sum-n 3))) 172 | (= (tests--mul-opt-sum 6 1 2 3 4 5 6 7 8 9) (* 6 (tests--sum-n 9)))) 173 | 174 | ;;; ---[ eval ]----------------------------------------- 175 | (test eval 176 | (= (eval nil) nil) 177 | (= (eval '(+ 2 2)) 4) 178 | (= (eval '(eval '(eval '(+ 1 1)))) 2)) 179 | 180 | ;;; ---[ eval-when-compile ]----------------------------- 181 | (define static-1 0) 182 | (defun tests--inc-static-1 (n) 183 | (inc! static-1 n)) 184 | 185 | (define static-2 0) 186 | (defun tests--inc-static-2 (n) 187 | (inc! static-2 n)) 188 | ;(test eval-when-compile 189 | ; (= (progn 190 | ; (tests--inc-static-1 10) 191 | ; (tests--inc-static-1 20)) 192 | ; 30) 193 | ; (= (progn 194 | ; (tests--inc-static-2 10) 195 | ; (eval-when :compile 196 | ; (tests--inc-static-2 20))) 197 | ; 20)) 198 | 199 | ;;; ---[ and/or ]---------------------------------------- 200 | (define static-3 0) 201 | (defun tests--inc-static-3 (n) 202 | (inc! static-3 n)) 203 | 204 | (define static-4 0) 205 | (defun tests--inc-static-4 (n) 206 | (inc! static-4 n)) 207 | 208 | (test and 209 | (= (and) 210 | true) 211 | (= (and false) 212 | false) 213 | (= (and true) 214 | true) 215 | (= (and true false) 216 | false) 217 | (= (progn 218 | (and true 219 | false 220 | (tests--inc-static-3 10)) 221 | (tests--inc-static-3 10)) 222 | 10)) 223 | 224 | (test or 225 | (= (or) 226 | false) 227 | (= (or false) 228 | nil) 229 | (= (or true) 230 | true) 231 | (= (or false 'thing 10) 232 | 'thing) 233 | (= (or true false) 234 | true) 235 | (= (progn 236 | (or false 237 | true 238 | (tests--inc-static-4 10)) 239 | (tests--inc-static-4 10)) 240 | 10)) 241 | 242 | ;;; ---[ vector ]---------------------------------------- 243 | (test vector 244 | (eq? (let ((v (vec 1 2 3))) 245 | `(,(pop v) ,(pop v) ,(pop v))) 246 | '(3 2 1)) 247 | (eq? (let ((v (vec 1 2 3))) 248 | (push v 10) 249 | `(,(pop v) ,(pop v) ,(pop v))) 250 | '(10 3 2)) 251 | (eq? (let ((v (vec 1 2 3))) 252 | (push v 10) 253 | (push v 12) 254 | `(,(get v 0) ,(get v 4) ,(get v 3))) 255 | '(1 12 10)) 256 | (= (let ((v (vec))) 257 | (push v 1337)) 258 | 1337) 259 | (= (len (vec 1 2 3)) 260 | 3) 261 | (= (len (vec)) 262 | 0)) 263 | 264 | ;;; ---[ inc/dec ]--------------------------------------- 265 | (test inc 266 | (= (let ((x 0)) 267 | (inc! x)) 268 | 1) 269 | (= (let ((x 0)) 270 | (inc! x) 271 | (inc! x)) 272 | 2) 273 | (= (let ((x 0)) 274 | (inc! x) 275 | (inc! x) 276 | x) 277 | 2)) 278 | 279 | (test dec 280 | (= (let ((x 1)) 281 | (dec! x)) 282 | 0) 283 | (= (let ((x 1)) 284 | (dec! x) 285 | (dec! x)) 286 | -1) 287 | (= (let ((x 1)) 288 | (dec! x) 289 | (dec! x) 290 | x) 291 | -1)) 292 | 293 | ;;; ---[ self ]------------------------------------------ 294 | (load self) 295 | 296 | (test self 297 | (eq? (do-factorial) 298 | '(1 2 6 24 120 720 5040 40320 362880 3628800)) 299 | (eq? (do-factorial-d) 300 | '(1 2 6 24 120 720 5040 40320 362880 3628800)) 301 | (eq? (evil '((((lambda (f) 302 | (f f)) 303 | (lambda (f) 304 | (lambda (g) 305 | (lambda (xs) 306 | (if xs 307 | (cons (g (car xs)) 308 | (((f f) g) (cdr xs)))))))) 309 | (lambda (x) 310 | (* x 2))) 311 | '(2 4 6 8 10 12))) 312 | '(4 8 12 16 20 24)) 313 | (eq? (evil '((((lambda (x) 314 | (lambda (y) 315 | (lambda (z) 316 | (+ (* x (* 2 y)) z)))) 317 | 4) 5) 6)) 318 | 46)) 319 | 320 | ;;; ---[ gensym ]---------------------------------------- 321 | (defun mapwise (f xs) 322 | (= (dolist (pair (zip xs (cdr xs))) 323 | (let ((x (car pair)) 324 | (y (cadr pair))) 325 | (break `(,x ,y)))) 326 | nil)) 327 | 328 | (test gensym) 329 | 330 | ;;; ---[ continuations ]--------------------------------- 331 | (test continuations 332 | (= (catch 'a (+ (call/cc (lambda (k) (throw 'a (k 1)))) 1)) 2)) 333 | 334 | ;;; ---[ apply ]----------------------------------------- 335 | (test apply 336 | (eq? (apply (lambda (&rest xs) xs) (vec 1 2 3)) 337 | '(1 2 3)) 338 | (eq? (apply (lambda (&rest xs) xs) '(1 2 3)) 339 | '(1 2 3)) 340 | (eq? (all? (lambda (li) 341 | (eq? (apply (lambda (&rest xs) 342 | xs) 343 | li) 344 | li)) 345 | `(,(range-list 0 100) 346 | ,(range-list 10 15))) 347 | true) 348 | (= (apply + (let ((v (vec))) (range (i (0 65535)) (push v i)) v)) 349 | (let ((s 0)) 350 | (range (i (0 65535)) (set s (+ s i))) 351 | s))) 352 | 353 | ;;; ---[ unused ]---------------------------------------- 354 | (defun black-box (x) x) 355 | (define *test-global* 0) 356 | (defun shenanigans (x) 357 | (set *test-global* (+ *test-global* x))) 358 | 359 | (test dead-code 360 | (= (let ((x 0)) 361 | (+ 1 2 3 4 5 (set x 5)) 362 | (+ 1 2 3 (set x (+ x 5)) 4 5) 363 | (- 1 2 3 (set x (+ x 5)) 4 5) 364 | x) 365 | 15) 366 | (= (progn 367 | (+ 1 (shenanigans 10)) 368 | (< 1 (shenanigans 30)) 369 | *test-global*) 370 | 40)) 371 | 372 | ;;; ---[ macros ]---------------------------------------- 373 | (defmacro make-lambda (x) 374 | `(lambda () ,x)) 375 | 376 | (test macros 377 | (= ((make-lambda 1)) 1) 378 | (= ((make-lambda (+ 1 2 3))) (+ 1 2 3)) 379 | (= (eval-when-compile ((make-lambda 1))) 1) 380 | (= (read-compile "(apply (make-lambda 1) (vec))") 1) 381 | (= ((read-compile "(make-lambda 1)")) 1) 382 | (= (read-compile "((make-lambda 1))") 1)) 383 | 384 | ;;; ---[ evaaal ]---------------------------------------- 385 | 386 | (test evaaal 387 | (eq? (eval '(do-factorial-d)) (do-factorial-d))) 388 | -------------------------------------------------------------------------------- /tests/test-stdlib.lisp: -------------------------------------------------------------------------------- 1 | (test filter 2 | (eq? (filter (lambda (x) (= x 2)) 3 | '(1 2 3)) 4 | '(2)) 5 | (eq? (filter (lambda (x) (and (>= x 30) 6 | (< x 60))) 7 | (range-list 0 100)) 8 | (range-list 30 60))) 9 | 10 | (test reverse 11 | (eq? (reverse '(1 2 3 4 5)) 12 | '(5 4 3 2 1)) 13 | (eq? (reverse (reverse (range-list 0 100))) 14 | (range-list 0 100))) 15 | 16 | (test elem? 17 | (elem? 5 (range-list 0 10)) 18 | (nil? (elem? 5 (range-list 0 3)))) 19 | 20 | (test all? 21 | (all? (lambda (x) (<= 1 x)) (range-list 1 10)) 22 | (not (all? (lambda (x) (< x 10)) (range-list 1 11)))) 23 | 24 | (test any? 25 | (any? (lambda (x) (= x 9)) (range-list 0 10)) 26 | (not (any? (lambda (x) (= x 9)) (range-list 0 9)))) 27 | -------------------------------------------------------------------------------- /tools/gdbinit: -------------------------------------------------------------------------------- 1 | skip -rfu core::* 2 | skip -rfu alloc::* 3 | skip -rfu std::* 4 | set print pretty on 5 | set print array on 6 | set unwindonsignal on 7 | set debuginfod enabled on 8 | -------------------------------------------------------------------------------- /tools/measure.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | declare bin="$1" 6 | shift 7 | declare -i tries=80 8 | 9 | cargo build --release --bin "$bin" 10 | 11 | stats="$(dirname "$0")/stats.awk" 12 | 13 | function run() { 14 | local -x LC_NUMERIC=en_US.UTF-8 15 | perf stat -x',' -- "target/release/${bin}" "$@" >/dev/null 16 | } 17 | 18 | { 19 | for _ in $(seq $tries); do 20 | run "$@" 2>&1 | awk -vFS=',' '$2 == "msec" {print $1}' 21 | echo -n "·" 1>&2 22 | done 23 | echo 1>&2 24 | } | awk -v CSVOUT='' -f "$stats" 25 | -------------------------------------------------------------------------------- /tools/sloc.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | command -v cloc &>/dev/null || { echo "cloc not installed"; exit 1; } 6 | command -v jq &>/dev/null || { echo "jq not installed"; exit 1; } 7 | command -v fd &>/dev/null || { echo "fd not installed"; exit 1; } 8 | 9 | function total() { 10 | cloc --json src proc-macros lisp \ 11 | | jq '.["Rust"].code + .["C"].code + .["Lisp"].code + .["C/C++ Header"].code' 12 | } 13 | 14 | function lisp-tests() { 15 | cloc --json tests | jq '.["Lisp"].code' 16 | } 17 | 18 | function tests() { 19 | fd . -ers -x awk '/#\[cfg\(test\)\]/{a=1} a; /^}/{a=0}' '{}' \ 20 | | cloc - --force-lang=rust --json \ 21 | | jq '.["SUM"].code' 22 | } 23 | 24 | n_total="$(total)" 25 | n_lisp_tests="$(lisp-tests)" 26 | n_rust_tests="$(tests)" 27 | n_tests=$((n_lisp_tests + n_rust_tests)) 28 | echo "sloc (excluding tests):" $((n_total - n_tests)) 29 | echo "sloc (only tests):" $n_tests 30 | -------------------------------------------------------------------------------- /tools/stats.awk: -------------------------------------------------------------------------------- 1 | ## Computes average, minimum, maximum, median, and standard deviation 2 | ## from lines of numbers. 3 | 4 | BEGIN { 5 | min = 2^1024 6 | max = -min 7 | } 8 | 9 | { 10 | s += $1 11 | a[n++] = $1 12 | if ($1 < min) min = $1 13 | if ($1 > max) max = $1 14 | } 15 | 16 | END { 17 | if (CSVOUT) 18 | for (i in a) 19 | print i","a[i] > CSVOUT 20 | avg = s / n 21 | asort(a) 22 | for (i in a) 23 | s2 += (a[i] - avg)^2 24 | std = sqrt(s2 / (n - 1)) 25 | 26 | print "{" 27 | print " \"mean\": "a[int(n / 2)]"," 28 | print " \"stddev\": "std"," 29 | print " \"avg\": "avg"," 30 | print " \"min\": "min"," 31 | print " \"max\": "max 32 | print "}" 33 | } 34 | -------------------------------------------------------------------------------- /tools/wasmtime.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | WASMTIME=wasmtime 4 | if ! command -v "$WASMTIME"; then 5 | WASMTIME="$HOME/.wasmtime/bin/wasmtime" 6 | fi 7 | exec "$WASMTIME" --dir . "$@" 8 | --------------------------------------------------------------------------------