8 releases (4 breaking)

0.5.2 Aug 26, 2023
0.5.1 Aug 23, 2023
0.4.0 Aug 21, 2023
0.3.0 Aug 18, 2023
0.1.0 Jun 8, 2023

#1773 in Procedural macros

Download history 4/week @ 2024-02-28 138/week @ 2024-03-20 4/week @ 2024-03-27 4/week @ 2024-04-03

146 downloads per month
Used in spaik

BSD-2-Clause

12KB
240 lines

The SPAIK LISP Programming Language

SPAIK is a dynamic extension language for Rust. It implements macros, garbage collection, iterators, continuations, async/await and wraps it up in a (hopefully) easy to use high-level Rust API.

This README contains many shorts snippets showing how SPAIK is used, while you can find complete examples in the examples directory, and the more detailed API docs can be found at docs.rs.

You can also try SPAIK directly in your browser!

Basic usage

For basic usage, all you need are the eval and exec methods (exec is just eval but it throws away the result to aid type-inference.)

let mut vm = Spaik::new();
vm.exec(r#"(println "Hello, World!")"#)?;

vm.set("f", |x: i32| x + 2); // Functions are first-class at the API boundary!
assert_eq!(vm.eval("(f 2)"), Ok(4));

// Optional linear-algebra types from glam
vm.exec("(defun funky (x y) (* x (vec3 1 y 3)))")?;
assert_eq!(vm.call("funky", (2, 4)), Ok(glam::vec3(2.0, 8.0, 6.0))); // Call a spaik function

// Define interfaces more formally
defuns!(trait MyInterface {
    fn funky(x: f32, y: f32) -> glam::Vec3;
});
// This panics if the function `funky` does not match the spec
assert_eq!(vm.funky(2.0, 4.0), glam::vec3(2.0, 8.0, 6.0));

Loading Code

You probably don't want to store all your SPAIK code as embedded strings in Rust, so you can of course load SPAIK scripts from the filesystem.

vm.add_load_path("my-spaik-programs");
vm.load::<Ignore>("stuff")?;
let result: Lambda = vm.load("other-stuff")?;
result.call(&mut vm, (1, 2, 3));

The add_load_path method adds the given string to the global sys/load-path variable, which is just a SPAIK vector. You can mutate this from SPAIK too:

(eval-when-compile (push sys/load-path "my-dependencies"))
(load dependency)

But notice that we had to use (eval-when-compile ...) when adding the new path, because (load ...) also runs during compilation.

Exporting functions to SPAIK

You can simply vm.set("name", func), or use the convenience-function vm.defun(add_to), which is equivalent to vm.set("add-to", add_to).

use spaik::prelude::*;

fn add_to(x: i32) -> i32 {
    x + 1
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut vm = Spaik::new();
    println!("Calling from Rust: {}", add_to(2));
    vm.set("other-name-for-add-to", add_to);
    vm.defun(add_to);
    vm.exec(r#"(let ((r (add-to 2))) (println "Calling Rust from SPAIK: {r}"))"#)?;
    Ok(())
}

The html macro

Because of how easy it is to create new syntax constructs in LISPs, you can use SPAIK as a rudimentary html templating engine.

(load html)
(html (p :class 'interjection "Interjection!"))
<p class="interjection">Interjection!</p>

Internal Architecture

SPAIK code is bytecode compiled and runs on a custom VM called the Rodent VM (R8VM,) which uses a moving tracing garbage collector. For more detailed information about its internals, see HACKING.md.

Dependencies

~4MB
~81K SLoC