Rex Engine (rexlang-engine)
This crate evaluates Rex ASTs and supports host-native injection of functions and values. The API
now exposes an explicit preparation boundary: Engine builds the host environment, Compiler
prepares Rex code into CompiledProgram, and Evaluator runs prepared programs. The current
implementation still keeps engine-backed state internally, but the compile/runtime split is now a
first-class part of the public API. The runtime operates on Value and supports closures,
application, let-in, if-then-else, tuples/lists/dicts, and match expressions.
Quickstart
use Engine;
use Token;
use Parser;
use ;
async
Phase-specific errors:
CompilerreturnsCompileErrorEvaluator::runreturnsEvalError- convenience helpers like
eval_snippetreturnExecutionError
Injection API
export_value(name, value): inject a constant value (numbers, strings, arrays, etc.).export(name, handler): inject a typed native function.export_async(name, handler): inject a typed async native function.export_native/export_native_async: inject pointer-level natives with explicitScheme+ arity.adt_decl+inject_adt: declare and register ADT constructors (mirrorstypedeclarations).inject_class: register a type class (mirrorsclassdeclarations).inject_instance: register a type class instance in the checker (mirrorsinstancedeclarations).
Operator names can be injected with parentheses (e.g., "(+)"); the engine normalizes to +.
Engine is generic over host state (Engine<State>, where State: Clone + Sync + 'static).
export callbacks receive &State as the first argument and must return Result<T, EngineError>;
returning Err(...) fails evaluation.
export_async callbacks receive &State and return Future<Output = Result<T, EngineError>>;
returning Err(...) fails evaluation.
Pointer-level APIs (export_native*) receive EvaluatorRef<'_, State> so they can access heap/runtime internals.
export_native* validates Scheme/arity compatibility during registration.
Prelude
Engine::with_prelude(())? injects the standard runtime helpers. If you need host state, pass
your state value instead: Engine::with_prelude(state)?.
For explicit control, use:
-
Engine::with_options(state, EngineOptions { ... }) -
PreludeMode::{Enabled, Disabled} -
default_imports(defaults to importingPreludeweakly) -
Constructors:
Empty,Cons,Some,None,Ok,Err -
Arithmetic:
+,-,*,/,negate,zero,one -
Equality:
==,!= -
Ordering:
<,<=,>,>= -
Booleans:
&&,|| -
Collection combinators (List/Array/Option/Result):
map,fold,foldl,foldr,filter,filter_map,bind,ap,sum,mean,count,take,skip,zip,unzip,min,max,or_else -
Option/Result helpers:
is_some,is_none,is_ok,is_err
List literals are evaluated to the Empty/Cons ADT constructors (stored as Value::Adt). Arrays are host-native Value::Array values injected from Rust (e.g., by passing Vec<T> to export_value) and participate in the same collection combinators via type classes.
Type Defaults
Some expressions can leave overloaded values ambiguous (for example, one or zero in a polymorphic branch). During evaluation, the engine applies a small defaulting pass to pick a concrete type when possible:
- Prefer primitive types already observed in the expression.
- Fall back to
f32, theni32, thenstring.
Tests
Run: