Miniature, embeddable Scheme
The no-std and no-alloc Scheme implementation in Rust.
Stak Scheme
The miniature, embeddable R7RS Scheme implementation in Rust
Stak Scheme aims to be:
- An embeddable Scheme interpreter for Rust with very small memory footprint and reasonable performance
- The minimal implementation of the R7RS-small standard
- A subset of Chibi Scheme, Gauche, and Guile
- A portable scripting environment that supports even no-
std
and no-alloc
platforms
For more information and usage, visit the full documentation.
Install
Interpreter
To install the Scheme interpreter as a command, run:
cargo install stak
Libraries
To install Stak Scheme as a library in your Rust project, run:
cargo add stakcargo add --build stak-buildcargo install stak-compile
For full examples, see the examples
directory.
Examples
Dynamic scripting in Rust
First, prepare a Scheme script named src/fight.scm
:
; Import a base library and the library named `(stak rust)` for Rust integration.(import (scheme base) (stak rust))
; Use the `define-rust` procedure to import native functions written in Rust.; The order of the functions should match the ones passed into the `Engine::new()`; function in Rust.(define-rust make-person person-pies person-wasted person-throw-pie)
; Make two people with a number of pies they have and their dodge rates.(define me (make-person 4 0.2))(define friend (make-person 2 0.6))
; The fight begins. Let's throw pies to each other!(do () ((or (person-wasted me) (person-wasted friend) (and (zero? (person-pies me)) (zero? (person-pies friend))))) (person-throw-pie me friend) (person-throw-pie friend me))
; Output the winner.(write-string (cond ((person-wasted friend) "You won!") ((person-wasted me) "You lost...") (else "Draw...")))
Then, add a build script at build.rs
to build the Scheme source file
into bytecodes.
use stak_build::{build_r7rs, BuildError};
fn main() -> Result<(), BuildError> { build_r7rs()}
Finally, you can embed and run the Scheme script in a Rust program.
use any_fn::{r#fn, Ref};use core::error::Error;use rand::random;use stak::{ engine::{Engine, EngineError}, include_module, module::UniversalModule,};
const HEAP_SIZE: usize = 1 << 16;
/// A person who holds pies to throw.struct Person { pies: usize, dodge: f64, wasted: bool,}
impl Person { /// Creates a person. pub fn new(pies: usize, dodge: f64) -> Self { Self { pies, dodge, wasted: false, } }
/// Returns a number of pies the person has. pub fn pies(&self) -> usize { self.pies }
/// Returns `true` if a person is wasted. pub fn wasted(&self) -> bool { self.wasted }
/// Throws a pie to another person. pub fn throw_pie(&mut self, other: &mut Person) { if self.pies == 0 || self.wasted { return; }
self.pies -= 1;
if random::<f64>() > other.dodge { other.wasted = true; } }}
fn main() -> Result<(), Box<dyn Error>> { // Import a Scheme module of the script. static MODULE: UniversalModule = include_module!("fight.scm");
// Run the Scheme module. run_scheme(&MODULE)?;
Ok(())}
fn run_scheme(module: &'static UniversalModule) -> Result<(), EngineError> { // Initialize a heap memory for a Scheme scripting engine. let mut heap = [Default::default(); HEAP_SIZE]; // Define Rust functions to pass to the engine. let mut functions = [ r#fn(Person::new), r#fn::<(Ref<_>,), _>(Person::pies), r#fn::<(Ref<_>,), _>(Person::wasted), r#fn(Person::throw_pie), ]; // Initialize the engine. let mut engine = Engine::new(&mut heap, &mut functions)?;
// Finally, run the module! engine.run(module)}
References
- This project is based on Ribbit Scheme, the small and portable R4RS implementation.
- Scheme programming language
- The R7RS-small standard