91 releases

new 0.1.91 Jan 11, 2025
0.1.89 Dec 31, 2024
0.1.75 Nov 27, 2024
0.1.45 Jul 15, 2024
0.1.1 Dec 3, 2023

#1117 in Programming languages

Download history 426/week @ 2024-09-20 1762/week @ 2024-09-27 921/week @ 2024-10-04 364/week @ 2024-10-11 540/week @ 2024-10-18 571/week @ 2024-10-25 272/week @ 2024-11-01 289/week @ 2024-11-08 459/week @ 2024-11-15 603/week @ 2024-11-22 691/week @ 2024-11-29 393/week @ 2024-12-06 1264/week @ 2024-12-13 670/week @ 2024-12-20 371/week @ 2024-12-27 140/week @ 2025-01-03

2,566 downloads per month
Used in 5 crates

Custom license

180KB
3.5K SLoC

Stak Scheme

GitHub Action Crate Codecov License

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 portable scripting environment that supports even no-std and no-alloc platforms

For more information and usage, visit the full documentation.

Install

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.

Command line tools

To install the Scheme interpreter as a command, run:

cargo install stak

Examples

Embedding Scheme scripts in Rust

First, prepare a Scheme script at src/hello.scm.

(import (scheme base))

(write-string "Hello, world!\n")

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()
}

Now, you can include the Scheme script into a program in Rust using the stak::include_module macro.

use core::error::Error;
use stak::{
    device::StdioDevice,
    file::VoidFileSystem,
    include_module,
    process_context::VoidProcessContext,
    module::{Module, UniversalModule},
    r7rs::{SmallError, SmallPrimitiveSet},
    time::VoidClock,
    vm::Vm,
};

const HEAP_SIZE: usize = 1 << 16;

// Include a Scheme script in the bytecode format built by the build script above.
static MODULE: UniversalModule = include_module!("hello.scm");

fn main() -> Result<(), Box<dyn Error>> {
    run(&MODULE.bytecode())?;

    Ok(())
}

fn run(bytecodes: &[u8]) -> Result<(), SmallError> {
    // Prepare a heap memory of a virtual machine.
    let mut heap = [Default::default(); HEAP_SIZE];
    // Create a virtual machine with its heap memory primitive procedures.
    let mut vm = Vm::new(
        &mut heap,
        SmallPrimitiveSet::new(
            // Attach standard input, output, and error of this process to a virtual machine.
            StdioDevice::new(),
            // Use void system interfaces for security because we don't need them for this example.
            VoidFileSystem::new(),
            VoidProcessContext::new(),
            VoidClock::new(),
        ),
    )?;

    // Initialize a virtual machine with bytecodes.
    vm.initialize(bytecodes.iter().copied())?;
    // Run bytecodes on a virtual machine.
    vm.run()
}

Communication between Scheme and Rust

Currently, in-memory standard input (stdin) and output (stdout) to Scheme scripts are the only way to communicate information between Rust programs and Scheme scripts.

use core::{error::Error, str::{self, FromStr}};
use stak::{
    device::ReadWriteDevice,
    file::VoidFileSystem,
    include_module,
    process_context::VoidProcessContext,
    module::{Module, UniversalModule},
    r7rs::{SmallError, SmallPrimitiveSet},
    time::VoidClock,
    vm::Vm,
};

const BUFFER_SIZE: usize = 1 << 8;
const HEAP_SIZE: usize = 1 << 16;

static MODULE: UniversalModule = include_module!("fibonacci.scm");

fn main() -> Result<(), Box<dyn Error>> {
    let input = 15;
    let mut output = vec![];
    let mut error = vec![];

    run(&MODULE.bytecode(), input.to_string().as_bytes(), &mut output, &mut error)?;

    // If stderr is not empty, we assume that some error has occurred.
    if !error.is_empty() {
        return Err(str::from_utf8(&error)?.into());
    }

    // Decode and test the output.
    assert_eq!(isize::from_str(&str::from_utf8(&output)?)?, 610);

    Ok(())
}

fn run(
    bytecodes: &[u8],
    input: &[u8],
    output: &mut Vec<u8>,
    error: &mut Vec<u8>,
) -> Result<(), SmallError> {
    let mut heap = [Default::default(); HEAP_SIZE];
    let mut vm = Vm::new(
        &mut heap,
        SmallPrimitiveSet::new(
            // Create and attach an in-memory I/O device.
            ReadWriteDevice::new(input, output, error),
            VoidFileSystem::new(),
            VoidProcessContext::new(),
            VoidClock::new(),
        ),
    )?;

    vm.initialize(bytecodes.iter().copied())?;
    vm.run()
}

References

License

MIT

Dependencies

~1.2–1.8MB
~39K SLoC