Skip to main content

Evaluator

Struct Evaluator 

Source
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>

Source

pub fn new(lisp: &'a Lisp<N>) -> Result<Self, EvalError>

Create a new evaluator with standard environment

Source

pub fn lisp(&self) -> &Lisp<N>

Get the Lisp context

Source

pub fn global_env(&self) -> ArenaIndex

Get the global environment

Source

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));
Source

pub fn native_registry(&self) -> &NativeRegistry<N>

Get a reference to the native function registry.

Source

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));
Source

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.

Source

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.

Source

pub fn define(&mut self, name: ArenaIndex, value: ArenaIndex) -> EvalResult

Define in global environment (NOTE: only allowed at top-level)

Source

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.

Source

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

Source

pub fn eval_str(&mut self, input: &str) -> EvalResult

Evaluate a string

Source§

impl<'a, const N: usize> Evaluator<'a, N>

Source

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.

Source

pub fn gensym_simple(&mut self) -> EvalResult

Generate a simple gensym with default prefix

Source§

impl<'a, const N: usize> Evaluator<'a, N>

Source

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

Source

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 2
Source

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 identifier
  • id2 - 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))               ;; => 2
Source§

impl<'a, const N: usize> Evaluator<'a, N>

Source

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>

Source

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)       ; => 42

Unwrap syntax objects to get datums (iterative)

Uses iteration for list traversal to avoid stack overflow.

Source

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 used
  • datum - The datum to wrap
§Example
(datum->syntax #'here 'new-name)  ; Creates syntax object with 'here's context
Source

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>

Source§

fn trace_roots(&self, tracer: &mut dyn FnMut(ArenaIndex))

Call tracer once for every ArenaIndex that is a live GC root.

Auto Trait Implementations§

§

impl<'a, const N: usize> Freeze for Evaluator<'a, N>

§

impl<'a, const N: usize> !RefUnwindSafe for Evaluator<'a, N>

§

impl<'a, const N: usize> !Send for Evaluator<'a, N>

§

impl<'a, const N: usize> !Sync for Evaluator<'a, N>

§

impl<'a, const N: usize> Unpin for Evaluator<'a, N>

§

impl<'a, const N: usize> !UnwindSafe for Evaluator<'a, N>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.