pub struct Evaluator<'a, const N: usize> { /* private fields */ }Expand description
The Lisp evaluator with full trampolined TCO.
This evaluator uses continuation-passing style with an arena-based continuation chain, enabling unlimited recursion depth without Rust stack overflow.
The continuation stack is stored entirely in the arena as a linked list
of ContFrame values, enabling O(1) capture for call/cc.
Implementations§
Source§impl<'a, const N: usize> Evaluator<'a, N>
impl<'a, const N: usize> Evaluator<'a, N>
Sourcepub fn new(lisp: &'a Lisp<N>) -> Result<Self, EvalError>
pub fn new(lisp: &'a Lisp<N>) -> Result<Self, EvalError>
Create a new evaluator with standard environment
Sourcepub fn global_env(&self) -> ArenaIndex
pub fn global_env(&self) -> ArenaIndex
Get the global environment
Sourcepub fn register_native(
&mut self,
name: &'static str,
func: NativeFn<N>,
) -> Result<(), EvalError>
pub fn register_native( &mut self, name: &'static str, func: NativeFn<N>, ) -> Result<(), EvalError>
Register a native Rust function that can be called from Lisp.
The function will be bound to the given name in the global environment.
§Example
use grift_eval::{Lisp, Evaluator, ArenaIndex, ArenaResult, FromLisp, ToLisp};
fn my_double<const N: usize>(lisp: &Lisp<N>, args: ArenaIndex) -> ArenaResult<ArenaIndex> {
let n = isize::from_lisp(lisp, lisp.car(args)?)?;
(n * 2).to_lisp(lisp)
}
let lisp: Lisp<20000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
eval.register_native("my-double", my_double).unwrap();
// Now you can call (my-double 5) from Lisp to get 10
let result = eval.eval_str("(my-double 5)").unwrap();
assert_eq!(lisp.get(result).unwrap().as_number(), Some(10));Sourcepub fn native_registry(&self) -> &NativeRegistry<N>
pub fn native_registry(&self) -> &NativeRegistry<N>
Get a reference to the native function registry.
Sourcepub fn set_output_callback(&mut self, callback: Option<OutputCallback<N>>)
pub fn set_output_callback(&mut self, callback: Option<OutputCallback<N>>)
Set an output callback for display/newline operations.
When set, the display and newline builtins will call this function
to produce output. This enables side effects during macro expansion to be visible.
§Example
use grift_eval::{Lisp, Evaluator, ArenaIndex};
fn my_output_handler<const N: usize>(lisp: &Lisp<N>, val: ArenaIndex) {
// Handle output - check if val.is_nil() for newline vs display
// In a real std implementation, you would write to stdout here
}
let lisp: Lisp<20000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
// Set output callback
eval.set_output_callback(Some(my_output_handler));Sourcepub fn set_io_provider(&mut self, io: &'a mut (dyn IoProvider + 'a))
pub fn set_io_provider(&mut self, io: &'a mut (dyn IoProvider + 'a))
Set the I/O provider for port operations.
When set, port builtins (read-char, write-char, etc.) use this provider
for actual I/O. Without an I/O provider, port operations will raise errors.
Sourcepub fn gc(&self) -> GcStats
pub fn gc(&self) -> GcStats
Run GC with minimal roots (evaluator-owned roots only).
Use gc_with_state() during evaluation to also root the current expression/value.
Sourcepub fn define(&mut self, name: ArenaIndex, value: ArenaIndex) -> EvalResult
pub fn define(&mut self, name: ArenaIndex, value: ArenaIndex) -> EvalResult
Define in global environment (NOTE: only allowed at top-level)
Sourcepub fn eval(&mut self, expr: ExprRef) -> EvalResult
pub fn eval(&mut self, expr: ExprRef) -> EvalResult
Evaluate an expression (entry point)
Macros are now expanded during evaluation (not pre-processed). This enables evaluation-time macro expansion per the R7RS model.
Sourcepub fn eval_in_env(&mut self, expr: ExprRef, env: EnvRef) -> EvalResult
pub fn eval_in_env(&mut self, expr: ExprRef, env: EnvRef) -> EvalResult
Evaluate an expression in a given environment Uses full trampolining - no Rust recursion
This is public so the REPL can evaluate expressions for display
Sourcepub fn eval_str(&mut self, input: &str) -> EvalResult
pub fn eval_str(&mut self, input: &str) -> EvalResult
Evaluate a string
Source§impl<'a, const N: usize> Evaluator<'a, N>
impl<'a, const N: usize> Evaluator<'a, N>
Sourcepub fn gensym(&mut self, base: &str) -> EvalResult
pub fn gensym(&mut self, base: &str) -> EvalResult
Generate a fresh symbol guaranteed to be unique
Format: #:g{counter} or #:{base}{counter}
§Example
use grift_parser::Lisp;
use grift_eval::Evaluator;
let lisp: Lisp<20000> = Lisp::new();
let mut eval = Evaluator::new(&lisp).unwrap();
let sym1 = eval.gensym("tmp").unwrap();
let sym2 = eval.gensym("tmp").unwrap();
let sym3 = eval.gensym_simple().unwrap();
// Each symbol is unique
assert_ne!(sym1, sym2);
assert_ne!(sym2, sym3);§Memory
Each gensym allocates 1 arena slot for the symbol. Symbols are interned, so repeated calls create new unique symbols.
Sourcepub fn gensym_simple(&mut self) -> EvalResult
pub fn gensym_simple(&mut self) -> EvalResult
Generate a simple gensym with default prefix
Source§impl<'a, const N: usize> Evaluator<'a, N>
impl<'a, const N: usize> Evaluator<'a, N>
Sourcepub fn mark_syntax(&mut self, stx: ArenaIndex) -> EvalResult
pub fn mark_syntax(&mut self, stx: ArenaIndex) -> EvalResult
Apply a fresh mark to a syntax object
Marks track macro expansion scopes for hygiene. Each time a macro is expanded, a fresh mark is applied to all identifiers introduced by the macro. This allows the system to distinguish between identifiers with the same name but different binding scopes.
§Arguments
stx- A syntax object to mark
§Returns
A new syntax object with the fresh mark added to its marks list
§Memory
Allocates 2 arena slots: one for the new mark, one for the cons cell
Sourcepub fn bound_identifier_eq(
&self,
id1: ArenaIndex,
id2: ArenaIndex,
) -> Result<bool, EvalError>
pub fn bound_identifier_eq( &self, id1: ArenaIndex, id2: ArenaIndex, ) -> Result<bool, EvalError>
Check if two identifiers are bound-identifier=?
Two identifiers are bound-identifier=? if they have the same name
AND the same marks. This means they were introduced at the same
point in the macro expansion process and would bind the same variable.
§Arguments
id1- First identifier (as a syntax object)id2- Second identifier (as a syntax object)
§Returns
true if the identifiers have the same name and marks
§Example
In the following macro, x introduced by the template is different
from x provided by the user because they have different marks:
(define-syntax test
(lambda (stx)
(syntax-case stx ()
((test x) (syntax (let ((x 1)) x))))))
(let ((x 2)) (test x)) ; returns 1, not 2Sourcepub fn free_identifier_eq(
&self,
id1: ArenaIndex,
id2: ArenaIndex,
) -> Result<bool, EvalError>
pub fn free_identifier_eq( &self, id1: ArenaIndex, id2: ArenaIndex, ) -> Result<bool, EvalError>
Check if two identifiers are free-identifier=?
Two identifiers are free-identifier=? if they resolve to the same
binding in the current environment. This is used when checking if
an identifier in a pattern matches a literal keyword.
§Arguments
id1- First identifierid2- Second identifier
§Returns
true if both identifiers resolve to the same binding (or both are unbound
and have the same name)
§Example
;; Two references to the same variable are free-identifier=?
(define x 1)
;; (free-identifier=? #'x #'x) => #t
;; In a macro, an identifier from the input may refer to a different
;; binding than an identifier introduced by the template:
(let ((x 1)) ;; outer x
(let ((x 2)) ;; inner x (shadows outer)
;; references to 'x' here refer to inner x (value 2)
;; but in a macro that captured outer x, they would differ
x)) ;; => 2Source§impl<'a, const N: usize> Evaluator<'a, N>
impl<'a, const N: usize> Evaluator<'a, N>
Sourcepub fn expand(&mut self, expr: ArenaIndex) -> EvalResult
pub fn expand(&mut self, expr: ArenaIndex) -> EvalResult
Expand all macros in an expression
This is the main entry point for macro expansion. Called before evaluation.
Source§impl<'a, const N: usize> Evaluator<'a, N>
impl<'a, const N: usize> Evaluator<'a, N>
Sourcepub fn syntax_to_datum_recursive(&mut self, stx: ArenaIndex) -> EvalResult
pub fn syntax_to_datum_recursive(&mut self, stx: ArenaIndex) -> EvalResult
Recursively strip syntax wrappers to get the underlying datum.
This walks through the expression, converting:
- Syntax objects to their wrapped datum
- Pairs to pairs with recursively unwrapped car and cdr
- Atoms pass through unchanged
§Example
(syntax->datum #'(a b c)) ; => (a b c)
(syntax->datum #'42) ; => 42Unwrap syntax objects to get datums (iterative)
Uses iteration for list traversal to avoid stack overflow.
Sourcepub fn datum_to_syntax(
&mut self,
template_id: ArenaIndex,
datum: ArenaIndex,
) -> EvalResult
pub fn datum_to_syntax( &mut self, template_id: ArenaIndex, datum: ArenaIndex, ) -> EvalResult
Wrap a datum with syntax context from a template identifier.
The template-id provides the lexical context (marks and substitutions) for the resulting syntax object. This is essential for creating hygienically correct identifiers in procedural macros.
§Arguments
template_id- An identifier whose lexical context is useddatum- The datum to wrap
§Example
(datum->syntax #'here 'new-name) ; Creates syntax object with 'here's contextSourcepub fn generate_temporaries(&mut self, input: ArenaIndex) -> EvalResult
pub fn generate_temporaries(&mut self, input: ArenaIndex) -> EvalResult
Generate a list of fresh temporary identifiers.
For each element in the input list, generates a unique temporary identifier using gensym. The temporary identifiers are syntax objects with the same lexical context.
§Arguments
input- A list (length determines number of temporaries generated)
§Example
(generate-temporaries '(a b c)) ; => (#:tmp0 #:tmp1 #:tmp2)Trait Implementations§
Source§impl<'a, const N: usize> GcRoots for Evaluator<'a, N>
impl<'a, const N: usize> GcRoots for Evaluator<'a, N>
Source§fn trace_roots(&self, tracer: &mut dyn FnMut(ArenaIndex))
fn trace_roots(&self, tracer: &mut dyn FnMut(ArenaIndex))
tracer once for every ArenaIndex that is a live GC root.