pub mod bridge;
pub mod builtins;
pub mod chunk;
pub mod compiler;
pub mod error;
pub mod intern;
pub mod nanbox;
pub mod opcode;
pub mod value;
pub mod vm;
pub use bridge::{
BuiltinBridgeFn, BuiltinBridgeGuard, PathMaterializerFn, PathMaterializerGuard,
call_builtin_bridge, materialize, materialize_path, set_builtin_bridge,
set_path_materializer,
};
pub use builtins::BuiltinRegistry;
pub use chunk::Chunk;
pub use compiler::Compiler;
pub use error::{CompileError, VMError};
pub use intern::{Interner, Symbol};
pub use opcode::OpCode;
pub use value::{StringKeyedValue, VMBuiltin, VMThunk, VMValue};
pub use vm::{FlakeResolverGuard, set_flake_resolver, vm_fallback_count, VM};
use std::cell::RefCell;
use std::collections::HashMap;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::rc::Rc;
struct CachedCompile {
chunk: Rc<Chunk>,
interner: Interner,
}
thread_local! {
static COMPILE_CACHE: RefCell<HashMap<u64, CachedCompile>> =
RefCell::new(HashMap::new());
}
fn hash_expr(input: &str) -> u64 {
let mut hasher = DefaultHasher::new();
input.hash(&mut hasher);
hasher.finish()
}
pub struct EvalResult {
pub value: VMValue,
pub interner: Interner,
}
impl EvalResult {
#[must_use]
pub fn to_string_keyed(&self) -> StringKeyedValue {
self.value.to_string_keyed(&self.interner)
}
}
pub fn eval(input: &str) -> Result<VMValue, EvalError> {
let result = eval_full(input)?;
Ok(result.value)
}
pub fn eval_full(input: &str) -> Result<EvalResult, EvalError> {
let key = hash_expr(input);
let cached = COMPILE_CACHE.with(|cache| {
cache.borrow().get(&key).map(|entry| {
(entry.chunk.clone(), entry.interner.clone())
})
});
let (chunk, mut interner) = if let Some((rc_chunk, interner)) = cached {
((*rc_chunk).clone(), interner)
} else {
let (chunk, interner) = Compiler::compile(input).map_err(EvalError::Compile)?;
let rc_chunk = Rc::new(chunk.clone());
COMPILE_CACHE.with(|cache| {
cache.borrow_mut().insert(key, CachedCompile {
chunk: rc_chunk,
interner: interner.clone(),
});
});
(chunk, interner)
};
let value = VM::execute(chunk, &mut interner).map_err(EvalError::Runtime)?;
Ok(EvalResult { value, interner })
}
pub fn clear_compile_cache() {
COMPILE_CACHE.with(|cache| cache.borrow_mut().clear());
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum EvalError {
#[error("compile error: {0}")]
Compile(CompileError),
#[error("runtime error: {0}")]
Runtime(VMError),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn eval_simple_addition() {
assert_eq!(eval("1 + 2").unwrap(), VMValue::Int(3));
}
#[test]
fn eval_null_literal() {
assert_eq!(eval("null").unwrap(), VMValue::Null);
}
#[test]
fn eval_bool_logic() {
assert_eq!(eval("true && false").unwrap(), VMValue::Bool(false));
assert_eq!(eval("true || false").unwrap(), VMValue::Bool(true));
}
#[test]
fn eval_let_binding() {
assert_eq!(eval("let x = 10; in x").unwrap(), VMValue::Int(10));
}
#[test]
fn eval_lambda_call() {
assert_eq!(eval("(x: x + 1) 5").unwrap(), VMValue::Int(6));
}
#[test]
fn eval_compile_error() {
let result = eval("let in");
assert!(result.is_err());
assert!(matches!(result, Err(EvalError::Compile(_))));
}
#[test]
fn eval_runtime_error_div_zero() {
let result = eval("1 / 0");
assert!(result.is_err());
assert!(matches!(
result,
Err(EvalError::Runtime(VMError::DivisionByZero))
));
}
#[test]
fn eval_lazy_let_thunk() {
assert_eq!(eval("let x = 2 * 3; in x").unwrap(), VMValue::Int(6));
}
#[test]
fn eval_lazy_let_cross_ref() {
clear_compile_cache();
assert_eq!(
eval("let f = x: x + 1; g = f 10; in g").unwrap(),
VMValue::Int(11)
);
}
#[test]
fn eval_fixpoint_via_intermediate() {
clear_compile_cache();
let result = eval(
"let fix = f: let x = f x; in x; r = fix (self: { a = 1; }); s = r.a; in s",
);
assert_eq!(result.unwrap(), VMValue::Int(1));
}
}