https://github.com/gold-silver-copper/grift
grift is a no_std no_alloc no_unsafe lisp for bare-metal devices
https://github.com/gold-silver-copper/grift
arduino embedded esp32 lisp no-alloc no-std rust scheme
Last synced: 3 months ago
JSON representation
grift is a no_std no_alloc no_unsafe lisp for bare-metal devices
- Host: GitHub
- URL: https://github.com/gold-silver-copper/grift
- Owner: gold-silver-copper
- Created: 2026-01-26T06:07:33.000Z (5 months ago)
- Default Branch: pure
- Last Pushed: 2026-03-09T10:43:18.000Z (3 months ago)
- Last Synced: 2026-03-09T11:52:50.604Z (3 months ago)
- Topics: arduino, embedded, esp32, lisp, no-alloc, no-std, rust, scheme
- Language: Rust
- Homepage:
- Size: 7.47 MB
- Stars: 6
- Watchers: 0
- Forks: 3
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# grift
Grift is a small Kernel-inspired Lisp interpreter for constrained Rust
environments. It implements first-class operatives (`vau`), applicatives,
first-class environments, and tail-call optimization on top of a fixed-size
arena with no heap allocation and no `unsafe` code.
The project is aimed at embedded or resource-bounded settings where a normal
allocator-backed Lisp runtime would be a poor fit, while still preserving a
substantial part of the vau-calculus execution model.
## Highlights
- `#![no_std]`, `no_alloc`, `#![forbid(unsafe_code)]`
- Fixed-capacity arena with free-list allocation
- Mark-and-sweep garbage collection, triggered on OOM and available explicitly
via `(gc-collect)`
- Kernel-style operative/applicative model
- First-class mutable environments with lexical parent lists
- Tail-call optimization via a trampoline evaluator
- Immutable pairs and `CharPair`-based string storage
- Symbol interning
- Checked integer arithmetic
- Rust native-function registration through `Lisp::register_native`
- Lazy prelude loading from `prelude.grift`
## What It Implements
Grift is not a Scheme clone. The language model is centered on `vau`, where an
operative receives its operands unevaluated together with the caller's
environment.
That gives the runtime three important properties:
- user-defined control forms are first-class values rather than a separate macro
system
- `lambda` is derived from the operative model rather than being primitive
- environments are explicit runtime values that can be captured, passed around,
and selectively exposed
The current builtin surface includes:
- operatives such as `quote`, `if`, `define!`, `fn!`, `set!`, `begin`, `cond`,
`and`, `or`, `let`, `vau`, and `current-environment`
- applicatives such as arithmetic, list primitives, equality, `eval`, `wrap`,
`unwrap`, environment constructors, GC control, and raw read/write helpers
`apply` and `wrap` operate uniformly over first-class callables, including
builtin operatives such as `if`.
## Rust API
```rust
use grift::{Lisp, Value};
let lisp: Lisp<20_000> = Lisp::new();
assert_eq!(lisp.eval("(+ 1 2)"), Ok(Value::Number(3)));
assert_eq!(lisp.eval("(car (cons 1 2))"), Ok(Value::Number(1)));
```
The const generic parameter sets the arena capacity in slots. A larger value
gives the interpreter more space for code, data, environments, temporary
evaluation state, and garbage-collector working room.
Native Rust functions can be exposed as Lisp applicatives:
```rust
use grift::{ArenaIndex, ArenaResult, Lisp, LispOps, extract_arg};
fn double(lisp: &dyn LispOps, args: ArenaIndex) -> ArenaResult {
let (n, _rest): (isize, ArenaIndex) = extract_arg(lisp, args)?;
lisp.number(n * 2)
}
let lisp: Lisp<20_000> = Lisp::new();
lisp.register_native("double", double).unwrap();
assert_eq!(lisp.eval("(double 21)"), Ok(grift::Value::Number(42)));
```
## Lisp Example
```lisp
;; Operatives receive operands unevaluated.
(define! my-quote
(vau (x) #ignore x))
(my-quote (+ 1 2)) ; => (+ 1 2)
;; lambda can be defined in terms of vau.
(define! lambda
(vau (formals . body) e
(wrap
(eval (cons 'vau
(cons formals
(cons #ignore body)))
e))))
(define! double
(lambda (x)
(* x 2)))
(double 5) ; => 10
;; The caller environment can be captured explicitly.
(define! my-if
(vau (test then else) e
(if (eval test e)
(eval then e)
(eval else e))))
(apply if (list #t 1 2)) ; => 1
((wrap if) #t 1 2) ; => 1
;; Tail recursion runs through the trampoline evaluator.
(fn! fib (n a b)
(if (= n 0)
a
(fib (- n 1) b (+ a b))))
(fib 50 0 1) ; => 12586269025
```
## Runtime Model
Every runtime value lives inside `Arena`. The evaluator does not rely
on heap allocation, and the runtime keeps a small set of reserved singleton
slots for values such as `NIL`, booleans, `#inert`, `#ignore`, the ground
environment, the global environment, GC roots, and the symbol intern list.
Current implementation details that matter:
- strings are stored as linked `CharPair` chains rather than contiguous buffers
- empty string is represented internally as `NIL`
- user code runs in the global environment, which is a child of the builtin
ground environment
- GC is currently demand-driven: the evaluator collects when allocation hits
`OutOfMemory`
More detail is in [docs/architecture/ARCHITECTURE.md](docs/architecture/ARCHITECTURE.md).
## Build And Test
```bash
cargo build --workspace
cargo test --workspace
```
Run the benchmark example:
```bash
cargo run -p grift --example fib_bench --release
```
Run the REPL:
```bash
cargo run -p grift --features repl
```
## Toolchain
The workspace MSRV is **Rust 1.85** (edition 2024), as declared in
`Cargo.toml`.
## Further Reading
- [docs/architecture/ARCHITECTURE.md](docs/architecture/ARCHITECTURE.md): current architecture and language notes
- [docs/grift/index.md](docs/grift/index.md): generated API-oriented documentation
- [05-07.pdf](05-07.pdf): Revised-1 Report on the Kernel Programming Language
- [jshutt.pdf](jshutt.pdf): "vau: the ultimate abstraction"
## License
MIT OR Apache-2.0