pub struct Lisp<const N: usize> { /* private fields */ }Expand description
A Lisp execution context wrapping an arena
§Nil Value
The Lisp singleton values (nil, true, false) are pre-allocated in reserved slots at initialization time. This avoids allocation overhead and ensures consistent identity for these commonly-used values.
§Reserved Slots
The first 4 slots of the arena are reserved for singleton values:
- Slot 0:
Value::Nil- empty list () - Slot 1:
Value::True- boolean true (#t) - Slot 2:
Value::False- boolean false (#f) - Slot 3:
Value::Cons- intern table reference cell (car = intern table root)
These slots are pre-allocated during Lisp::new() and returned as
constants from true_val() and false_val(). This optimization
avoids allocating new slots for these frequently-used values.
§Symbol Interning
All symbols are interned in an association list stored in the arena.
The intern_table_slot field points to a cons cell whose car is the alist
of (string_index . symbol_index) pairs. The cons cell is used as a
“reference cell” to allow updating the intern table without RefCell.
When creating a symbol, we first check if it already exists in the table.
This ensures that the same symbol name always returns the same index.
Implementations§
Source§impl<const N: usize> Lisp<N>
impl<const N: usize> Lisp<N>
Sourcepub fn new() -> Self
pub fn new() -> Self
Create a new Lisp context
Pre-allocates reserved slots for nil, true, false, and the intern table. These slots are never freed and provide O(1) access to common values.
§Panics
Panics if the arena capacity N < RESERVED_SLOTS.
Sourcepub fn alloc(&self, value: Value) -> ArenaResult<ArenaIndex>
pub fn alloc(&self, value: Value) -> ArenaResult<ArenaIndex>
Allocate a value
Sourcepub fn get(&self, index: ArenaIndex) -> ArenaResult<Value>
pub fn get(&self, index: ArenaIndex) -> ArenaResult<Value>
Get a value from the arena by index
Sourcepub fn set(&self, index: ArenaIndex, value: Value) -> ArenaResult<()>
pub fn set(&self, index: ArenaIndex, value: Value) -> ArenaResult<()>
Set a value
Sourcepub fn arena_index_at_offset(
&self,
base: ArenaIndex,
offset: usize,
) -> ArenaResult<ArenaIndex>
pub fn arena_index_at_offset( &self, base: ArenaIndex, offset: usize, ) -> ArenaResult<ArenaIndex>
Get an arena index at a given offset from a base index.
This is useful for accessing elements in contiguous storage (strings, arrays).
Sourcepub fn nil(&self) -> ArenaResult<ArenaIndex>
pub fn nil(&self) -> ArenaResult<ArenaIndex>
Get the pre-allocated Nil singleton (empty list)
Returns the reserved slot 0 which always contains Value::Nil.
Sourcepub fn true_val(&self) -> ArenaResult<ArenaIndex>
pub fn true_val(&self) -> ArenaResult<ArenaIndex>
Get the pre-allocated True singleton (#t)
Returns the reserved slot 1 which always contains Value::True.
Sourcepub fn false_val(&self) -> ArenaResult<ArenaIndex>
pub fn false_val(&self) -> ArenaResult<ArenaIndex>
Get the pre-allocated False singleton (#f)
Returns the reserved slot 2 which always contains Value::False.
Sourcepub fn boolean(&self, b: bool) -> ArenaResult<ArenaIndex>
pub fn boolean(&self, b: bool) -> ArenaResult<ArenaIndex>
Allocate a boolean based on a Rust bool
Sourcepub fn number(&self, n: isize) -> ArenaResult<ArenaIndex>
pub fn number(&self, n: isize) -> ArenaResult<ArenaIndex>
Allocate a number
Sourcepub fn char(&self, c: char) -> ArenaResult<ArenaIndex>
pub fn char(&self, c: char) -> ArenaResult<ArenaIndex>
Allocate a character
Sourcepub fn cons(&self, car: ArenaIndex, cdr: ArenaIndex) -> ArenaResult<ArenaIndex>
pub fn cons(&self, car: ArenaIndex, cdr: ArenaIndex) -> ArenaResult<ArenaIndex>
Allocate a cons cell
Creates a cons cell with inline car and cdr indices. No arena data slots are needed - the indices are stored directly in the Value.
Sourcepub fn car(&self, index: ArenaIndex) -> ArenaResult<ArenaIndex>
pub fn car(&self, index: ArenaIndex) -> ArenaResult<ArenaIndex>
Get car of a cons cell
In Scheme R7RS, car of an empty list is an error. O(1) access - car is stored inline.
Sourcepub fn cdr(&self, index: ArenaIndex) -> ArenaResult<ArenaIndex>
pub fn cdr(&self, index: ArenaIndex) -> ArenaResult<ArenaIndex>
Get cdr of a cons cell
In Scheme R7RS, cdr of an empty list is an error. O(1) access - cdr is stored inline.
Sourcepub fn car_cdr(
&self,
index: ArenaIndex,
) -> ArenaResult<(ArenaIndex, ArenaIndex)>
pub fn car_cdr( &self, index: ArenaIndex, ) -> ArenaResult<(ArenaIndex, ArenaIndex)>
Get both car and cdr of a cons cell in one operation
More efficient than calling car() and cdr() separately when both are needed. O(1) access - both are stored inline.
Sourcepub fn set_car(
&self,
index: ArenaIndex,
new_car: ArenaIndex,
) -> ArenaResult<ArenaIndex>
pub fn set_car( &self, index: ArenaIndex, new_car: ArenaIndex, ) -> ArenaResult<ArenaIndex>
Set car of a cons cell (mutation operation) Returns the new value on success
Sourcepub fn set_cdr(
&self,
index: ArenaIndex,
new_cdr: ArenaIndex,
) -> ArenaResult<ArenaIndex>
pub fn set_cdr( &self, index: ArenaIndex, new_cdr: ArenaIndex, ) -> ArenaResult<ArenaIndex>
Set cdr of a cons cell (mutation operation) Returns the new value on success
pub fn pack_refs1(&self, a: ArenaIndex) -> ArenaResult<ArenaIndex>
pub fn unpack_refs1(&self, data: ArenaIndex) -> ArenaResult<ArenaIndex>
pub fn pack_refs2( &self, a: ArenaIndex, b: ArenaIndex, ) -> ArenaResult<ArenaIndex>
pub fn unpack_refs2( &self, data: ArenaIndex, ) -> ArenaResult<(ArenaIndex, ArenaIndex)>
pub fn pack_refs3( &self, a: ArenaIndex, b: ArenaIndex, c: ArenaIndex, ) -> ArenaResult<ArenaIndex>
pub fn unpack_refs3( &self, data: ArenaIndex, ) -> ArenaResult<(ArenaIndex, ArenaIndex, ArenaIndex)>
pub fn pack_refs4( &self, a: ArenaIndex, b: ArenaIndex, c: ArenaIndex, d: ArenaIndex, ) -> ArenaResult<ArenaIndex>
pub fn unpack_refs4( &self, data: ArenaIndex, ) -> ArenaResult<(ArenaIndex, ArenaIndex, ArenaIndex, ArenaIndex)>
pub fn pack_refs5( &self, a: ArenaIndex, b: ArenaIndex, c: ArenaIndex, d: ArenaIndex, e: ArenaIndex, ) -> ArenaResult<ArenaIndex>
pub fn unpack_refs5( &self, data: ArenaIndex, ) -> ArenaResult<(ArenaIndex, ArenaIndex, ArenaIndex, ArenaIndex, ArenaIndex)>
pub fn pack_refs6( &self, a: ArenaIndex, b: ArenaIndex, c: ArenaIndex, d: ArenaIndex, e: ArenaIndex, f: ArenaIndex, ) -> ArenaResult<ArenaIndex>
pub fn unpack_refs6( &self, data: ArenaIndex, ) -> ArenaResult<(ArenaIndex, ArenaIndex, ArenaIndex, ArenaIndex, ArenaIndex, ArenaIndex)>
pub fn pack_refs7( &self, a: ArenaIndex, b: ArenaIndex, c: ArenaIndex, d: ArenaIndex, e: ArenaIndex, f: ArenaIndex, g: ArenaIndex, ) -> ArenaResult<ArenaIndex>
pub fn unpack_refs7( &self, data: ArenaIndex, ) -> ArenaResult<(ArenaIndex, ArenaIndex, ArenaIndex, ArenaIndex, ArenaIndex, ArenaIndex, ArenaIndex)>
Sourcepub fn intern_table(&self) -> ArenaIndex
pub fn intern_table(&self) -> ArenaIndex
Get the intern table root (for GC roots)
The intern table is stored in the car of the intern_table_slot cons cell.
Sourcepub fn symbol(&self, name: &str) -> ArenaResult<ArenaIndex>
pub fn symbol(&self, name: &str) -> ArenaResult<ArenaIndex>
Create or retrieve an interned symbol from a string slice
The symbol’s chars field points to a Value::String with the symbol name.
Symbol interning ensures the same symbol name always returns the same index.
This method is optimized to avoid allocation on cache hits by comparing the input string directly against interned symbols before allocating.
Sourcepub fn symbol_from_bytes(&self, bytes: &[u8]) -> ArenaResult<ArenaIndex>
pub fn symbol_from_bytes(&self, bytes: &[u8]) -> ArenaResult<ArenaIndex>
Create or retrieve an interned symbol from bytes (for parsing)
This method is optimized to avoid allocation on cache hits by comparing the input bytes directly against interned strings before allocating.
Sourcepub fn builtin(&self, b: Builtin) -> ArenaResult<ArenaIndex>
pub fn builtin(&self, b: Builtin) -> ArenaResult<ArenaIndex>
Allocate a builtin function
Sourcepub fn stdlib(&self, s: StdLib) -> ArenaResult<ArenaIndex>
pub fn stdlib(&self, s: StdLib) -> ArenaResult<ArenaIndex>
Allocate a stdlib function
StdLib functions are stored in static memory with on-demand parsing. The function body is parsed on each call.
Sourcepub fn native(&self, id: usize, name_hash: usize) -> ArenaResult<ArenaIndex>
pub fn native(&self, id: usize, name_hash: usize) -> ArenaResult<ArenaIndex>
Allocate a native function reference.
Native functions are Rust functions registered with the evaluator.
The id is the index in the NativeRegistry, and name_hash is
a simple hash for verification.
Both values are stored inline - no arena data slots needed.
Sourcepub fn native_id(&self, native_idx: ArenaIndex) -> ArenaResult<usize>
pub fn native_id(&self, native_idx: ArenaIndex) -> ArenaResult<usize>
Get the id from a native function O(1) access - id is stored inline.
Sourcepub fn native_name_hash(&self, native_idx: ArenaIndex) -> ArenaResult<usize>
pub fn native_name_hash(&self, native_idx: ArenaIndex) -> ArenaResult<usize>
Get the name_hash from a native function O(1) access - name_hash is stored inline.
Sourcepub fn lambda(
&self,
params: ArenaIndex,
body: ArenaIndex,
env: ArenaIndex,
) -> ArenaResult<ArenaIndex>
pub fn lambda( &self, params: ArenaIndex, body: ArenaIndex, env: ArenaIndex, ) -> ArenaResult<ArenaIndex>
Allocate a lambda
Stores lambda with inline params and body_env indices. body_env is a cons cell containing (body . env).
Sourcepub fn lambda_parts(
&self,
index: ArenaIndex,
) -> ArenaResult<(ArenaIndex, ArenaIndex, ArenaIndex)>
pub fn lambda_parts( &self, index: ArenaIndex, ) -> ArenaResult<(ArenaIndex, ArenaIndex, ArenaIndex)>
Extract parts from a lambda: (params, body, env)
Lambda has inline params and body_env, where body_env is a cons (body . env).
Sourcepub fn syntax_rules(
&self,
literals: ArenaIndex,
rules: ArenaIndex,
definition_env: ArenaIndex,
) -> ArenaResult<ArenaIndex>
pub fn syntax_rules( &self, literals: ArenaIndex, rules: ArenaIndex, definition_env: ArenaIndex, ) -> ArenaResult<ArenaIndex>
Create a syntax-rules macro transformer
Packs rules and definition_env into a cons cell to maintain the 2-index constraint (matching Lambda’s layout).
§Arguments
literals- List of literal keywords that must match exactlyrules- List of (pattern . template) pairsdefinition_env- Environment where macro was defined
Sourcepub fn syntax_rules_parts(
&self,
idx: ArenaIndex,
) -> ArenaResult<(ArenaIndex, ArenaIndex, ArenaIndex)>
pub fn syntax_rules_parts( &self, idx: ArenaIndex, ) -> ArenaResult<(ArenaIndex, ArenaIndex, ArenaIndex)>
Extract parts from a SyntaxRules value
Returns (literals, rules, definition_env) unpacked from the internal structure.
Sourcepub fn list<I: IntoIterator<Item = ArenaIndex>>(
&self,
items: I,
) -> ArenaResult<ArenaIndex>where
I::IntoIter: DoubleEndedIterator,
pub fn list<I: IntoIterator<Item = ArenaIndex>>(
&self,
items: I,
) -> ArenaResult<ArenaIndex>where
I::IntoIter: DoubleEndedIterator,
Build a list from an iterator of indices
Sourcepub fn list_len(&self, list: ArenaIndex) -> ArenaResult<usize>
pub fn list_len(&self, list: ArenaIndex) -> ArenaResult<usize>
Get the length of a list
Sourcepub fn symbol_eq(&self, a: ArenaIndex, b: ArenaIndex) -> ArenaResult<bool>
pub fn symbol_eq(&self, a: ArenaIndex, b: ArenaIndex) -> ArenaResult<bool>
Check if two symbols are equal.
Symbols are compared by their underlying string content.
Sourcepub fn eqv(&self, a: ArenaIndex, b: ArenaIndex) -> ArenaResult<bool>
pub fn eqv(&self, a: ArenaIndex, b: ArenaIndex) -> ArenaResult<bool>
Check if two values are eqv? (Scheme eqv? predicate)
Returns true if values are identical or have the same primitive value.
Sourcepub fn symbol_matches(&self, sym: ArenaIndex, name: &str) -> ArenaResult<bool>
pub fn symbol_matches(&self, sym: ArenaIndex, name: &str) -> ArenaResult<bool>
Check if a symbol matches a string.
Sourcepub fn symbol_to_bytes(
&self,
sym: ArenaIndex,
buf: &mut [u8],
) -> ArenaResult<usize>
pub fn symbol_to_bytes( &self, sym: ArenaIndex, buf: &mut [u8], ) -> ArenaResult<usize>
Extract symbol name to a fixed buffer.
Sourcepub fn symbol_len(&self, sym: ArenaIndex) -> ArenaResult<usize>
pub fn symbol_len(&self, sym: ArenaIndex) -> ArenaResult<usize>
Get the length of a symbol’s name.
Sourcepub fn symbol_char_at(
&self,
sym: ArenaIndex,
index: usize,
) -> ArenaResult<Option<char>>
pub fn symbol_char_at( &self, sym: ArenaIndex, index: usize, ) -> ArenaResult<Option<char>>
Get a character at a specific index within a symbol’s name Returns None if the index is out of bounds or if the value is not a symbol
Sourcepub fn gc(&self, roots: &[ArenaIndex]) -> GcStats
pub fn gc(&self, roots: &[ArenaIndex]) -> GcStats
Run garbage collection with intern table as an additional root
The intern table reference cell (slot 3) is always included as a GC root to prevent interned symbols from being collected. The intern table is stored as a cons cell whose car points to the alist of interned symbols.
§Panics
Panics if the number of roots exceeds the internal limit (512 roots). This limit is chosen to balance stack usage in no_std environments with typical program needs. Most Lisp programs use far fewer roots.
Sourcepub fn stats(&self) -> ArenaStats
pub fn stats(&self) -> ArenaStats
Get arena stats
Sourcepub fn string(&self, s: &str) -> ArenaResult<ArenaIndex>
pub fn string(&self, s: &str) -> ArenaResult<ArenaIndex>
Allocate a string as contiguous Char values.
Returns a Value::String with inline length and data pointing directly to the first character (no length header in arena). Empty strings have len=0 and data == NIL.
§Memory Usage
Allocates s.chars().count() slots (characters only, no header), plus 1 slot for the String value itself.
§Errors
Returns ArenaError::OutOfMemory if:
- No contiguous block is available
§Example
use grift_parser::Lisp;
let lisp = Lisp::<1000>::new();
let hello = lisp.string("hello").unwrap();
assert_eq!(lisp.string_len(hello).unwrap(), 5);
assert_eq!(lisp.string_char_at(hello, 0).unwrap(), 'h');Sourcepub fn string_from_chars(&self, chars: &[char]) -> ArenaResult<ArenaIndex>
pub fn string_from_chars(&self, chars: &[char]) -> ArenaResult<ArenaIndex>
Allocate a string from a slice of chars.
Returns an ArenaIndex pointing to a Value::String.
Sourcepub fn string_len(&self, str_idx: ArenaIndex) -> ArenaResult<usize>
pub fn string_len(&self, str_idx: ArenaIndex) -> ArenaResult<usize>
Get the length of a string.
Returns O(1) since length is stored inline in the Value. No arena access needed!
§Errors
Returns ArenaError::InvalidIndex if the index doesn’t point to
a valid string.
Sourcepub fn string_char_at(
&self,
str_idx: ArenaIndex,
char_index: usize,
) -> ArenaResult<char>
pub fn string_char_at( &self, str_idx: ArenaIndex, char_index: usize, ) -> ArenaResult<char>
Get a character at the given index within a string.
Character indices are 0-based. Returns O(1) access.
§Errors
Returns ArenaError::InvalidIndex if:
- The string index is invalid
- The character index is out of bounds
- The slot doesn’t contain a Char value
Sourcepub fn string_eq_contiguous(
&self,
a: ArenaIndex,
b: ArenaIndex,
) -> ArenaResult<bool>
pub fn string_eq_contiguous( &self, a: ArenaIndex, b: ArenaIndex, ) -> ArenaResult<bool>
Sourcepub fn string_matches(&self, str_idx: ArenaIndex, s: &str) -> ArenaResult<bool>
pub fn string_matches(&self, str_idx: ArenaIndex, s: &str) -> ArenaResult<bool>
Check if a string matches a Rust string slice.
§Errors
Returns an error if the string index is invalid.
Sourcepub fn string_matches_bytes(
&self,
str_idx: ArenaIndex,
bytes: &[u8],
) -> ArenaResult<bool>
pub fn string_matches_bytes( &self, str_idx: ArenaIndex, bytes: &[u8], ) -> ArenaResult<bool>
Check if a string matches a byte slice directly.
This is an optimization for symbol interning - allows comparing an interned string against input bytes without allocating a new string. Assumes the bytes are ASCII (valid for Lisp symbols).
§Errors
Returns an error if the string index is invalid.
Sourcepub fn string_to_bytes(
&self,
str_idx: ArenaIndex,
buf: &mut [u8],
) -> ArenaResult<usize>
pub fn string_to_bytes( &self, str_idx: ArenaIndex, buf: &mut [u8], ) -> ArenaResult<usize>
Copy a string’s contents to a byte buffer.
Returns the number of bytes written. Only ASCII characters (0-127) are copied; non-ASCII characters are skipped.
§Warning
This method is designed for ASCII strings. For strings containing non-ASCII Unicode characters, some characters will be skipped and the byte count may not match the character count.
§Errors
Returns an error if the string index is invalid.
Sourcepub fn string_free(&self, str_idx: ArenaIndex) -> ArenaResult<()>
pub fn string_free(&self, str_idx: ArenaIndex) -> ArenaResult<()>
Sourcepub fn make_array(
&self,
len: usize,
default: ArenaIndex,
) -> ArenaResult<ArenaIndex>
pub fn make_array( &self, len: usize, default: ArenaIndex, ) -> ArenaResult<ArenaIndex>
Create an array with the given length, initialized with a default value.
The array stores len values contiguously in the arena with inline length.
§Memory Usage
Allocates len slots (elements only, no header), plus 1 slot for the Array value itself.
§Example
use grift_parser::Lisp;
let lisp = Lisp::<1000>::new();
let arr = lisp.make_array(3, lisp.nil().unwrap()).unwrap();
assert_eq!(lisp.array_len(arr).unwrap(), 3);Sourcepub fn array_len(&self, arr_idx: ArenaIndex) -> ArenaResult<usize>
pub fn array_len(&self, arr_idx: ArenaIndex) -> ArenaResult<usize>
Get the length of an array.
Returns O(1) since length is stored inline in the Value. No arena access needed!
§Errors
Returns ArenaError::InvalidIndex if the index doesn’t point to an array.
Sourcepub fn array_get(
&self,
arr_idx: ArenaIndex,
index: usize,
) -> ArenaResult<ArenaIndex>
pub fn array_get( &self, arr_idx: ArenaIndex, index: usize, ) -> ArenaResult<ArenaIndex>
Get the element at the given index within an array.
Returns O(1) access via direct index calculation.
§Errors
Returns ArenaError::InvalidIndex if:
- The array index is invalid
- The element index is out of bounds
Sourcepub fn array_set(
&self,
arr_idx: ArenaIndex,
index: usize,
value: ArenaIndex,
) -> ArenaResult<()>
pub fn array_set( &self, arr_idx: ArenaIndex, index: usize, value: ArenaIndex, ) -> ArenaResult<()>
Set the element at the given index within an array.
Returns O(1) mutation via direct index calculation.
§Errors
Returns ArenaError::InvalidIndex if:
- The array index is invalid
- The element index is out of bounds