38 releases
new 0.9.10 | Feb 10, 2025 |
---|---|
0.9.8 | Jan 30, 2025 |
0.9.1 | Dec 31, 2024 |
0.8.8 | Nov 27, 2024 |
#1360 in Programming languages
2,737 downloads per month
Used in 17 crates
(9 directly)
175KB
3.5K
SLoC
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 stak
cargo add --build stak-build
cargo 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
License
Dependencies
~1MB
~18K SLoC