use std::rc::Rc;
pub mod builtins;
pub mod convert;
pub mod eval;
pub mod drv_cache;
pub mod eval_cache;
pub mod fetcher;
pub mod flake_lock;
pub mod git;
pub mod path;
pub mod pos;
pub mod perf;
pub mod resolve_env;
pub mod trace;
pub mod value;
pub mod lazy;
pub mod realize;
pub mod flake {
pub use sui_compat::flake::*;
}
pub use eval::eval;
pub use value::{EvalError, Value};
pub trait Evaluator {
fn eval_expr(&self, input: &str) -> Result<Value, EvalError>;
fn eval_file(&self, path: &std::path::Path) -> Result<Value, EvalError>;
}
pub struct TreeWalkEvaluator;
impl Evaluator for TreeWalkEvaluator {
fn eval_expr(&self, input: &str) -> Result<Value, EvalError> {
eval(input)
}
fn eval_file(&self, path: &std::path::Path) -> Result<Value, EvalError> {
let source = std::fs::read_to_string(path)
.map_err(|e| EvalError::IoError {
context: format!("eval_file: {}", path.display()),
message: e.to_string(),
})?;
let path_buf = path.to_path_buf();
let _guard = eval::push_eval_file(path_buf.clone());
eval::eval_with_file(&source, Some(path_buf))
}
}
pub struct BytecodeEvaluator;
impl BytecodeEvaluator {
fn eval_with_flake_resolver(input: &str) -> Result<Value, EvalError> {
let _flake_guard = sui_bytecode::set_flake_resolver(Box::new(|flake_ref: &str| {
let flake_dir = if flake_ref.starts_with('/') || flake_ref.starts_with('.') {
std::path::PathBuf::from(flake_ref)
} else if let Some(path) = flake_ref.strip_prefix("path:") {
std::path::PathBuf::from(path)
} else {
return Err(format!("unsupported flake reference: {flake_ref}"));
};
let result = builtins::evaluate_flake(&flake_dir)
.map_err(|e| e.to_string())?;
Ok(eval_to_string_keyed(&result))
}));
let _bridge_guard = sui_bytecode::set_builtin_bridge(Box::new(
|name: &str, args: Vec<sui_bytecode::StringKeyedValue>| {
if name == "__import" {
let path_str = match &args[0] {
sui_bytecode::StringKeyedValue::Path(p)
| sui_bytecode::StringKeyedValue::String(p) => p.clone(),
_ => return Err("__import: expected path or string argument".to_string()),
};
let path = std::path::Path::new(&path_str);
let source = std::fs::read_to_string(path)
.map_err(|e| format!("__import: {}: {e}", path.display()))?;
let path_buf = path.to_path_buf();
let _guard = eval::push_eval_file(path_buf.clone());
let result = eval::eval_with_file(&source, Some(path_buf))
.map_err(|e| e.to_string())?;
let forced = eval::force_value(&result)
.map_err(|e| e.to_string())?;
return Ok(eval_to_string_keyed(&forced));
}
let eval_args: Vec<Value> = args
.iter()
.map(|a| convert::string_keyed_to_eval(a))
.collect();
let result = builtins::call_builtin_by_name(name, &eval_args)
.map_err(|e| e.to_string())?;
let forced = eval::force_value(&result)
.map_err(|e| e.to_string())?;
Ok(eval_to_string_keyed(&forced))
},
));
match sui_bytecode::eval_full(input) {
Ok(result) => Ok(convert::string_keyed_to_eval(&result.to_string_keyed())),
Err(sui_bytecode::EvalError::Compile(c)) => {
eprintln!("[sui-vm] top-level compile fallback: {c}");
eval::eval(input)
}
Err(sui_bytecode::EvalError::Runtime(r)) => {
eprintln!("[sui-vm] top-level runtime fallback: {r}");
eval::eval(input)
}
}
}
}
pub fn eval_to_string_keyed(val: &Value) -> sui_bytecode::StringKeyedValue {
match val {
Value::Null => sui_bytecode::StringKeyedValue::Null,
Value::Bool(b) => sui_bytecode::StringKeyedValue::Bool(*b),
Value::Int(n) => sui_bytecode::StringKeyedValue::Int(*n),
Value::Float(f) => sui_bytecode::StringKeyedValue::Float(*f),
Value::String(s) => sui_bytecode::StringKeyedValue::String(s.chars.to_string()),
Value::Path(p) => sui_bytecode::StringKeyedValue::Path(p.to_string()),
Value::List(items) => {
sui_bytecode::StringKeyedValue::List(
items.iter().map(eval_to_string_keyed).collect(),
)
}
Value::Attrs(attrs) => {
let mut map = std::collections::BTreeMap::new();
for (k, v) in attrs.iter() {
map.insert(k.clone(), eval_to_string_keyed(v));
}
sui_bytecode::StringKeyedValue::Attrs(map)
}
Value::Lambda(closure) => {
let closure_rc = std::rc::Rc::new((**closure).clone());
sui_bytecode::StringKeyedValue::Callable(std::rc::Rc::new(move |arg| {
let eval_arg = convert::string_keyed_to_eval(&arg);
let func = Value::Lambda(Rc::new((*closure_rc).clone()));
let result = eval::apply(func, eval_arg)
.map_err(|e| e.to_string())?;
let forced = eval::force_value(&result)
.map_err(|e| e.to_string())?;
Ok(eval_to_string_keyed(&forced))
}))
}
Value::Builtin(bf) => {
let bf_rc = std::rc::Rc::new((**bf).clone());
sui_bytecode::StringKeyedValue::Callable(std::rc::Rc::new(move |arg| {
let eval_arg = convert::string_keyed_to_eval(&arg);
let func = Value::Builtin(Box::new((*bf_rc).clone()));
let result = eval::apply(func, eval_arg)
.map_err(|e| e.to_string())?;
let forced = eval::force_value(&result)
.map_err(|e| e.to_string())?;
Ok(eval_to_string_keyed(&forced))
}))
}
Value::Thunk(t) => {
if t.is_evaluated() {
match t.force(&|e, env| eval::eval_expr(e, env)) {
Ok(v) => eval_to_string_keyed(&v),
Err(_) => sui_bytecode::StringKeyedValue::Null,
}
} else {
let thunk_clone = t.clone();
sui_bytecode::StringKeyedValue::Thunk(std::rc::Rc::new(move || {
let forced = thunk_clone
.force(&|e, env| eval::eval_expr(e, env))
.map_err(|e| e.to_string())?;
Ok(eval_to_string_keyed(&forced))
}))
}
}
}
}
impl Evaluator for BytecodeEvaluator {
fn eval_expr(&self, input: &str) -> Result<Value, EvalError> {
Self::eval_with_flake_resolver(input)
}
fn eval_file(&self, path: &std::path::Path) -> Result<Value, EvalError> {
let source = std::fs::read_to_string(path)
.map_err(|e| EvalError::IoError {
context: format!("eval_file: {}", path.display()),
message: e.to_string(),
})?;
self.eval_expr(&source)
}
}
#[cfg(test)]
mod tests {
use super::*;
struct MockEvaluator(Result<Value, EvalError>);
impl Evaluator for MockEvaluator {
fn eval_expr(&self, _: &str) -> Result<Value, EvalError> {
match &self.0 { Ok(v) => Ok(v.clone()), Err(_) => Err(EvalError::NotImplemented("mock".into())) }
}
fn eval_file(&self, _: &std::path::Path) -> Result<Value, EvalError> {
self.eval_expr("")
}
}
#[test]
fn mock_evaluator_ok() {
let e = MockEvaluator(Ok(Value::Int(42)));
assert_eq!(e.eval_expr("anything").unwrap(), Value::Int(42));
}
#[test]
fn mock_evaluator_err() {
let e = MockEvaluator(Err(EvalError::NotImplemented("x".into())));
assert!(e.eval_expr("anything").is_err());
}
#[test]
fn tree_walk_evaluator() {
let e = TreeWalkEvaluator;
assert_eq!(e.eval_expr("1 + 2").unwrap(), Value::Int(3));
}
#[test]
fn evaluator_trait_object_safe() {
fn _assert(_: &dyn Evaluator) {}
}
#[test]
fn tree_walk_eval_integer_arithmetic() {
let e: &dyn Evaluator = &TreeWalkEvaluator;
assert_eq!(e.eval_expr("2 + 3").unwrap(), Value::Int(5));
}
#[test]
fn tree_walk_eval_string_literal() {
let e: &dyn Evaluator = &TreeWalkEvaluator;
assert_eq!(
e.eval_expr(r#""hello world""#).unwrap(),
Value::string("hello world"),
);
}
#[test]
fn tree_walk_eval_boolean() {
let e: &dyn Evaluator = &TreeWalkEvaluator;
assert_eq!(e.eval_expr("true && false").unwrap(), Value::Bool(false));
}
#[test]
fn tree_walk_eval_if_else() {
let e: &dyn Evaluator = &TreeWalkEvaluator;
assert_eq!(
e.eval_expr("if true then 42 else 0").unwrap(),
Value::Int(42),
);
}
#[test]
fn tree_walk_eval_let_binding() {
let e: &dyn Evaluator = &TreeWalkEvaluator;
assert_eq!(
e.eval_expr("let x = 10; in x * 2").unwrap(),
Value::Int(20),
);
}
#[test]
fn tree_walk_eval_attrset() {
let e: &dyn Evaluator = &TreeWalkEvaluator;
let val = e.eval_expr("{ a = 1; b = 2; }.a").unwrap();
assert_eq!(val, Value::Int(1));
}
#[test]
fn tree_walk_eval_list() {
let e: &dyn Evaluator = &TreeWalkEvaluator;
let val = e.eval_expr("[1 2 3]").unwrap();
assert_eq!(
val,
Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3)]),
);
}
#[test]
fn tree_walk_eval_lambda_application() {
let e: &dyn Evaluator = &TreeWalkEvaluator;
assert_eq!(
e.eval_expr("(x: x + 1) 5").unwrap(),
Value::Int(6),
);
}
#[test]
fn tree_walk_eval_builtin_via_trait() {
let e: &dyn Evaluator = &TreeWalkEvaluator;
assert_eq!(
e.eval_expr("builtins.length [1 2 3]").unwrap(),
Value::Int(3),
);
}
#[test]
fn tree_walk_eval_parse_error_via_trait() {
let e: &dyn Evaluator = &TreeWalkEvaluator;
let result = e.eval_expr("let in");
assert!(result.is_err());
}
#[test]
fn tree_walk_eval_null_via_trait() {
let e: &dyn Evaluator = &TreeWalkEvaluator;
assert_eq!(e.eval_expr("null").unwrap(), Value::Null);
}
#[test]
fn tree_walk_eval_file_missing() {
let e: &dyn Evaluator = &TreeWalkEvaluator;
let result = e.eval_file(std::path::Path::new("/nonexistent/file.nix"));
assert!(result.is_err());
}
#[test]
fn tree_walk_eval_string_interpolation_via_trait() {
let e: &dyn Evaluator = &TreeWalkEvaluator;
assert_eq!(
e.eval_expr(r#"let name = "world"; in "hello ${name}""#).unwrap(),
Value::string("hello world"),
);
}
#[test]
fn tree_walk_eval_comparison_via_trait() {
let e: &dyn Evaluator = &TreeWalkEvaluator;
assert_eq!(e.eval_expr("3 > 2").unwrap(), Value::Bool(true));
assert_eq!(e.eval_expr("1 == 1").unwrap(), Value::Bool(true));
}
#[test]
fn tree_walk_eval_recursive_attrset_via_trait() {
let e: &dyn Evaluator = &TreeWalkEvaluator;
assert_eq!(
e.eval_expr("rec { x = 1; y = x + 1; }.y").unwrap(),
Value::Int(2),
);
}
#[test]
fn re_export_eval_function_works() {
assert_eq!(eval("1 + 1").unwrap(), Value::Int(2));
}
#[test]
fn re_export_value_and_error_types_constructible() {
let v: Value = Value::Int(7);
let e: EvalError = EvalError::UndefinedVar("x".into());
assert_eq!(v.type_name(), "int");
assert!(e.to_string().contains("undefined"));
}
#[test]
fn flake_module_re_exports_compat_types() {
#[allow(unused_imports)]
use crate::flake::*;
}
#[test]
fn tree_walk_eval_file_with_real_temp_file() {
let dir = std::env::temp_dir().join("sui-eval-test-tree-walk");
let _ = std::fs::create_dir_all(&dir);
let path = dir.join("simple.nix");
std::fs::write(&path, "1 + 2").unwrap();
let e: &dyn Evaluator = &TreeWalkEvaluator;
let result = e.eval_file(&path).unwrap();
assert_eq!(result, Value::Int(3));
let _ = std::fs::remove_file(&path);
let _ = std::fs::remove_dir(&dir);
}
#[test]
fn tree_walk_eval_file_propagates_io_error_kind() {
let e: &dyn Evaluator = &TreeWalkEvaluator;
let result = e.eval_file(std::path::Path::new("/nonexistent/never/exists.nix"));
match result {
Err(EvalError::IoError { context, .. }) => {
assert!(context.contains("eval_file"));
}
other => panic!("expected IoError, got {other:?}"),
}
}
#[test]
fn tree_walk_eval_file_parse_error_propagates() {
let dir = std::env::temp_dir().join("sui-eval-test-tw-parse");
let _ = std::fs::create_dir_all(&dir);
let path = dir.join("bad.nix");
std::fs::write(&path, "let in").unwrap();
let e: &dyn Evaluator = &TreeWalkEvaluator;
let result = e.eval_file(&path);
assert!(result.is_err());
let _ = std::fs::remove_file(&path);
let _ = std::fs::remove_dir(&dir);
}
#[test]
fn mock_evaluator_dispatched_via_trait_object() {
let m: Box<dyn Evaluator> = Box::new(MockEvaluator(Ok(Value::Bool(true))));
let r = m.eval_expr("anything").unwrap();
assert_eq!(r, Value::Bool(true));
}
#[test]
fn mock_evaluator_eval_file_routes_through_eval_expr() {
let m = MockEvaluator(Ok(Value::Int(1)));
let r = m.eval_file(std::path::Path::new("/dev/null"));
assert_eq!(r.unwrap(), Value::Int(1));
}
#[test]
fn tree_walk_eval_function_with_default_args() {
let e: &dyn Evaluator = &TreeWalkEvaluator;
assert_eq!(
e.eval_expr("({a, b ? 10}: a + b) {a = 5;}").unwrap(),
Value::Int(15),
);
}
#[test]
fn tree_walk_eval_with_throws_propagated() {
let e: &dyn Evaluator = &TreeWalkEvaluator;
let result = e.eval_expr(r#"builtins.throw "boom""#);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.is_throw());
}
#[test]
fn tree_walk_eval_assert_failure() {
let e: &dyn Evaluator = &TreeWalkEvaluator;
let result = e.eval_expr("assert false; 42");
assert!(matches!(result, Err(EvalError::AssertionFailed(_))));
}
#[test]
fn tree_walk_eval_division_by_zero() {
let e: &dyn Evaluator = &TreeWalkEvaluator;
let result = e.eval_expr("1 / 0");
assert!(matches!(result, Err(EvalError::DivisionByZero)));
}
#[test]
fn tree_walk_eval_undefined_variable() {
let e: &dyn Evaluator = &TreeWalkEvaluator;
let result = e.eval_expr("nonexistent_xyz");
assert!(matches!(result, Err(EvalError::UndefinedVar(_))));
}
#[test]
fn tree_walk_eval_path_literal() {
let e: &dyn Evaluator = &TreeWalkEvaluator;
let v = e.eval_expr("/tmp/x").unwrap();
assert!(matches!(v, Value::Path(_)));
}
#[test]
fn tree_walk_eval_float_literal() {
let e: &dyn Evaluator = &TreeWalkEvaluator;
assert_eq!(e.eval_expr("3.14").unwrap(), Value::Float(3.14));
}
#[test]
fn tree_walk_eval_lambda_returns_lambda() {
let e: &dyn Evaluator = &TreeWalkEvaluator;
let v = e.eval_expr("x: x").unwrap();
assert!(matches!(v, Value::Lambda(_)));
}
}