1use std::rc::Rc;
9
10pub mod builtins;
12pub mod convert;
14pub mod eval;
16pub mod drv_cache;
18pub mod eval_cache;
20pub mod fetcher;
22pub mod flake_lock;
24pub mod git;
26pub mod path;
28pub mod pos;
30pub mod perf;
32pub mod resolve_env;
35pub mod normalize_env;
36pub mod trace;
38pub mod value;
40pub mod lazy;
42pub mod realize;
45
46pub mod render;
50
51pub mod flake {
53 pub use sui_compat::flake::*;
54}
55
56pub use eval::{eval, eval_with_file};
58pub use value::{EvalError, Value};
60
61pub trait Evaluator {
66 fn eval_expr(&self, input: &str) -> Result<Value, EvalError>;
68
69 fn eval_file(&self, path: &std::path::Path) -> Result<Value, EvalError>;
71}
72
73pub struct TreeWalkEvaluator;
75
76impl Evaluator for TreeWalkEvaluator {
77 fn eval_expr(&self, input: &str) -> Result<Value, EvalError> {
78 eval(input)
79 }
80
81 fn eval_file(&self, path: &std::path::Path) -> Result<Value, EvalError> {
82 let source = std::fs::read_to_string(path)
83 .map_err(|e| EvalError::IoError {
84 context: format!("eval_file: {}", path.display()),
85 message: e.to_string(),
86 })?;
87 let path_buf = path.to_path_buf();
88 let _guard = eval::push_eval_file(path_buf.clone());
89 eval::eval_with_file(&source, Some(path_buf))
90 }
91}
92
93pub struct BytecodeEvaluator;
99
100pub struct VmBridgeGuards {
105 _flake: sui_bytecode::FlakeResolverGuard,
106 _bridge: sui_bytecode::BuiltinBridgeGuard,
107 _path: sui_bytecode::PathMaterializerGuard,
108}
109
110#[must_use]
132pub fn install_vm_bridges() -> VmBridgeGuards {
133 let _flake_guard = sui_bytecode::set_flake_resolver(Box::new(|flake_ref: &str| {
135 let flake_dir = if flake_ref.starts_with('/') || flake_ref.starts_with('.') {
136 std::path::PathBuf::from(flake_ref)
137 } else if let Some(path) = flake_ref.strip_prefix("path:") {
138 std::path::PathBuf::from(path)
139 } else {
140 return Err(format!("unsupported flake reference: {flake_ref}"));
141 };
142
143 let result = builtins::evaluate_flake(&flake_dir)
144 .map_err(|e| e.to_string())?;
145
146 Ok(eval_to_string_keyed(&result))
148 }));
149
150 let _bridge_guard = sui_bytecode::set_builtin_bridge(Box::new(
152 |name: &str, args: Vec<sui_bytecode::StringKeyedValue>| {
153 if name == "__import" {
156 let path_str = match &args[0] {
157 sui_bytecode::StringKeyedValue::Path(p)
158 | sui_bytecode::StringKeyedValue::String(p) => p.clone(),
159 _ => return Err("__import: expected path or string argument".to_string()),
160 };
161 let path = std::path::Path::new(&path_str);
162 let source = std::fs::read_to_string(path)
163 .map_err(|e| format!("__import: {}: {e}", path.display()))?;
164 let path_buf = path.to_path_buf();
165 let _guard = eval::push_eval_file(path_buf.clone());
166 let result = eval::eval_with_file(&source, Some(path_buf))
167 .map_err(|e| e.to_string())?;
168 let forced = eval::force_value(&result)
172 .map_err(|e| e.to_string())?;
173 return Ok(eval_to_string_keyed(&forced));
174 }
175
176 let eval_args: Vec<Value> = args
178 .iter()
179 .map(|a| convert::string_keyed_to_eval(a))
180 .collect();
181
182 let result = builtins::call_builtin_by_name(name, &eval_args)
184 .map_err(|e| e.to_string())?;
185
186 let forced = eval::force_value(&result)
189 .map_err(|e| e.to_string())?;
190
191 Ok(eval_to_string_keyed(&forced))
193 },
194 ));
195
196 let _path_guard = sui_bytecode::set_path_materializer(Box::new(|p: &str| {
202 crate::path::materialize_str(p)
203 }));
204
205 VmBridgeGuards {
206 _flake: _flake_guard,
207 _bridge: _bridge_guard,
208 _path: _path_guard,
209 }
210}
211
212impl BytecodeEvaluator {
213 fn eval_with_flake_resolver(input: &str) -> Result<Value, EvalError> {
218 let _bridges = install_vm_bridges();
219
220 match sui_bytecode::eval_full(input) {
221 Ok(result) => Ok(convert::string_keyed_to_eval(&result.to_string_keyed())),
222 Err(sui_bytecode::EvalError::Compile(c)) => {
223 eprintln!("[sui-vm] top-level compile fallback: {c}");
225 eval::eval(input)
226 }
227 Err(sui_bytecode::EvalError::Runtime(r)) => {
228 eprintln!("[sui-vm] top-level runtime fallback: {r}");
232 eval::eval(input)
233 }
234 }
235 }
236}
237
238pub fn eval_to_string_keyed(val: &Value) -> sui_bytecode::StringKeyedValue {
251 match val {
252 Value::Null => sui_bytecode::StringKeyedValue::Null,
253 Value::Bool(b) => sui_bytecode::StringKeyedValue::Bool(*b),
254 Value::Int(n) => sui_bytecode::StringKeyedValue::Int(*n),
255 Value::Float(f) => sui_bytecode::StringKeyedValue::Float(*f),
256 Value::String(s) => sui_bytecode::StringKeyedValue::String(s.chars.to_string()),
257 Value::Path(p) => sui_bytecode::StringKeyedValue::Path(p.to_string()),
258 Value::List(items) => {
259 sui_bytecode::StringKeyedValue::List(
260 items.iter().map(eval_to_string_keyed).collect(),
261 )
262 }
263 Value::Attrs(attrs) => {
264 let mut map = std::collections::BTreeMap::new();
265 for (k, v) in attrs.iter() {
266 map.insert(k.clone(), eval_to_string_keyed(v));
267 }
268 sui_bytecode::StringKeyedValue::Attrs(map)
269 }
270 Value::Lambda(closure) => {
271 let closure_rc = std::rc::Rc::new((**closure).clone());
277 sui_bytecode::StringKeyedValue::Callable(std::rc::Rc::new(move |arg| {
278 let eval_arg = convert::string_keyed_to_eval(&arg);
279 let func = Value::Lambda(Rc::new((*closure_rc).clone()));
280 let result = eval::apply(func, eval_arg)
281 .map_err(|e| e.to_string())?;
282 let forced = eval::force_value(&result)
283 .map_err(|e| e.to_string())?;
284 Ok(eval_to_string_keyed(&forced))
285 }))
286 }
287 Value::Builtin(bf) => {
288 let bf_rc = std::rc::Rc::new((**bf).clone());
292 sui_bytecode::StringKeyedValue::Callable(std::rc::Rc::new(move |arg| {
293 let eval_arg = convert::string_keyed_to_eval(&arg);
294 let func = Value::Builtin(Box::new((*bf_rc).clone()));
295 let result = eval::apply(func, eval_arg)
296 .map_err(|e| e.to_string())?;
297 let forced = eval::force_value(&result)
298 .map_err(|e| e.to_string())?;
299 Ok(eval_to_string_keyed(&forced))
300 }))
301 }
302 Value::Thunk(t) => {
303 if t.is_evaluated() {
306 match t.force(&|e, env| eval::eval_expr(e, env)) {
307 Ok(v) => eval_to_string_keyed(&v),
308 Err(_) => sui_bytecode::StringKeyedValue::Null,
309 }
310 } else {
311 let thunk_clone = t.clone();
316 sui_bytecode::StringKeyedValue::Thunk(std::rc::Rc::new(move || {
317 let forced = thunk_clone
318 .force(&|e, env| eval::eval_expr(e, env))
319 .map_err(|e| e.to_string())?;
320 Ok(eval_to_string_keyed(&forced))
321 }))
322 }
323 }
324 }
325}
326
327impl Evaluator for BytecodeEvaluator {
328 fn eval_expr(&self, input: &str) -> Result<Value, EvalError> {
329 Self::eval_with_flake_resolver(input)
330 }
331
332 fn eval_file(&self, path: &std::path::Path) -> Result<Value, EvalError> {
333 let source = std::fs::read_to_string(path)
334 .map_err(|e| EvalError::IoError {
335 context: format!("eval_file: {}", path.display()),
336 message: e.to_string(),
337 })?;
338 self.eval_expr(&source)
339 }
340}
341
342#[cfg(test)]
343mod tests {
344 use super::*;
345
346 struct MockEvaluator(Result<Value, EvalError>);
347 impl Evaluator for MockEvaluator {
348 fn eval_expr(&self, _: &str) -> Result<Value, EvalError> {
349 match &self.0 { Ok(v) => Ok(v.clone()), Err(_) => Err(EvalError::NotImplemented("mock".into())) }
350 }
351 fn eval_file(&self, _: &std::path::Path) -> Result<Value, EvalError> {
352 self.eval_expr("")
353 }
354 }
355
356 #[test]
357 fn mock_evaluator_ok() {
358 let e = MockEvaluator(Ok(Value::Int(42)));
359 assert_eq!(e.eval_expr("anything").unwrap(), Value::Int(42));
360 }
361
362 #[test]
363 fn mock_evaluator_err() {
364 let e = MockEvaluator(Err(EvalError::NotImplemented("x".into())));
365 assert!(e.eval_expr("anything").is_err());
366 }
367
368 #[test]
369 fn tree_walk_evaluator() {
370 let e = TreeWalkEvaluator;
371 assert_eq!(e.eval_expr("1 + 2").unwrap(), Value::Int(3));
372 }
373
374 #[test]
375 fn evaluator_trait_object_safe() {
376 fn _assert(_: &dyn Evaluator) {}
377 }
378
379 #[test]
382 fn tree_walk_eval_integer_arithmetic() {
383 let e: &dyn Evaluator = &TreeWalkEvaluator;
384 assert_eq!(e.eval_expr("2 + 3").unwrap(), Value::Int(5));
385 }
386
387 #[test]
388 fn tree_walk_eval_string_literal() {
389 let e: &dyn Evaluator = &TreeWalkEvaluator;
390 assert_eq!(
391 e.eval_expr(r#""hello world""#).unwrap(),
392 Value::string("hello world"),
393 );
394 }
395
396 #[test]
397 fn tree_walk_eval_boolean() {
398 let e: &dyn Evaluator = &TreeWalkEvaluator;
399 assert_eq!(e.eval_expr("true && false").unwrap(), Value::Bool(false));
400 }
401
402 #[test]
403 fn tree_walk_eval_if_else() {
404 let e: &dyn Evaluator = &TreeWalkEvaluator;
405 assert_eq!(
406 e.eval_expr("if true then 42 else 0").unwrap(),
407 Value::Int(42),
408 );
409 }
410
411 #[test]
412 fn tree_walk_eval_let_binding() {
413 let e: &dyn Evaluator = &TreeWalkEvaluator;
414 assert_eq!(
415 e.eval_expr("let x = 10; in x * 2").unwrap(),
416 Value::Int(20),
417 );
418 }
419
420 #[test]
421 fn tree_walk_eval_attrset() {
422 let e: &dyn Evaluator = &TreeWalkEvaluator;
423 let val = e.eval_expr("{ a = 1; b = 2; }.a").unwrap();
424 assert_eq!(val, Value::Int(1));
425 }
426
427 #[test]
428 fn tree_walk_eval_list() {
429 let e: &dyn Evaluator = &TreeWalkEvaluator;
430 let val = e.eval_expr("[1 2 3]").unwrap();
431 assert_eq!(
432 val,
433 Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3)]),
434 );
435 }
436
437 #[test]
438 fn tree_walk_eval_lambda_application() {
439 let e: &dyn Evaluator = &TreeWalkEvaluator;
440 assert_eq!(
441 e.eval_expr("(x: x + 1) 5").unwrap(),
442 Value::Int(6),
443 );
444 }
445
446 #[test]
447 fn tree_walk_eval_builtin_via_trait() {
448 let e: &dyn Evaluator = &TreeWalkEvaluator;
449 assert_eq!(
450 e.eval_expr("builtins.length [1 2 3]").unwrap(),
451 Value::Int(3),
452 );
453 }
454
455 #[test]
456 fn tree_walk_eval_parse_error_via_trait() {
457 let e: &dyn Evaluator = &TreeWalkEvaluator;
458 let result = e.eval_expr("let in");
459 assert!(result.is_err());
460 }
461
462 #[test]
463 fn tree_walk_eval_null_via_trait() {
464 let e: &dyn Evaluator = &TreeWalkEvaluator;
465 assert_eq!(e.eval_expr("null").unwrap(), Value::Null);
466 }
467
468 #[test]
469 fn tree_walk_eval_file_missing() {
470 let e: &dyn Evaluator = &TreeWalkEvaluator;
471 let result = e.eval_file(std::path::Path::new("/nonexistent/file.nix"));
472 assert!(result.is_err());
473 }
474
475 #[test]
476 fn tree_walk_eval_string_interpolation_via_trait() {
477 let e: &dyn Evaluator = &TreeWalkEvaluator;
478 assert_eq!(
479 e.eval_expr(r#"let name = "world"; in "hello ${name}""#).unwrap(),
480 Value::string("hello world"),
481 );
482 }
483
484 #[test]
485 fn tree_walk_eval_comparison_via_trait() {
486 let e: &dyn Evaluator = &TreeWalkEvaluator;
487 assert_eq!(e.eval_expr("3 > 2").unwrap(), Value::Bool(true));
488 assert_eq!(e.eval_expr("1 == 1").unwrap(), Value::Bool(true));
489 }
490
491 #[test]
492 fn tree_walk_eval_recursive_attrset_via_trait() {
493 let e: &dyn Evaluator = &TreeWalkEvaluator;
494 assert_eq!(
495 e.eval_expr("rec { x = 1; y = x + 1; }.y").unwrap(),
496 Value::Int(2),
497 );
498 }
499
500 #[test]
503 fn re_export_eval_function_works() {
504 assert_eq!(eval("1 + 1").unwrap(), Value::Int(2));
507 }
508
509 #[test]
510 fn re_export_value_and_error_types_constructible() {
511 let v: Value = Value::Int(7);
512 let e: EvalError = EvalError::UndefinedVar("x".into());
513 assert_eq!(v.type_name(), "int");
514 assert!(e.to_string().contains("undefined"));
515 }
516
517 #[test]
520 fn flake_module_re_exports_compat_types() {
521 #[allow(unused_imports)]
526 use crate::flake::*;
527 }
529
530 #[test]
533 fn tree_walk_eval_file_with_real_temp_file() {
534 let dir = std::env::temp_dir().join("sui-eval-test-tree-walk");
535 let _ = std::fs::create_dir_all(&dir);
536 let path = dir.join("simple.nix");
537 std::fs::write(&path, "1 + 2").unwrap();
538 let e: &dyn Evaluator = &TreeWalkEvaluator;
539 let result = e.eval_file(&path).unwrap();
540 assert_eq!(result, Value::Int(3));
541 let _ = std::fs::remove_file(&path);
542 let _ = std::fs::remove_dir(&dir);
543 }
544
545 #[test]
546 fn tree_walk_eval_file_propagates_io_error_kind() {
547 let e: &dyn Evaluator = &TreeWalkEvaluator;
548 let result = e.eval_file(std::path::Path::new("/nonexistent/never/exists.nix"));
549 match result {
550 Err(EvalError::IoError { context, .. }) => {
551 assert!(context.contains("eval_file"));
552 }
553 other => panic!("expected IoError, got {other:?}"),
554 }
555 }
556
557 #[test]
558 fn tree_walk_eval_file_parse_error_propagates() {
559 let dir = std::env::temp_dir().join("sui-eval-test-tw-parse");
560 let _ = std::fs::create_dir_all(&dir);
561 let path = dir.join("bad.nix");
562 std::fs::write(&path, "let in").unwrap();
563 let e: &dyn Evaluator = &TreeWalkEvaluator;
564 let result = e.eval_file(&path);
565 assert!(result.is_err());
566 let _ = std::fs::remove_file(&path);
567 let _ = std::fs::remove_dir(&dir);
568 }
569
570 #[test]
573 fn mock_evaluator_dispatched_via_trait_object() {
574 let m: Box<dyn Evaluator> = Box::new(MockEvaluator(Ok(Value::Bool(true))));
575 let r = m.eval_expr("anything").unwrap();
576 assert_eq!(r, Value::Bool(true));
577 }
578
579 #[test]
580 fn mock_evaluator_eval_file_routes_through_eval_expr() {
581 let m = MockEvaluator(Ok(Value::Int(1)));
582 let r = m.eval_file(std::path::Path::new("/dev/null"));
583 assert_eq!(r.unwrap(), Value::Int(1));
584 }
585
586 #[test]
589 fn tree_walk_eval_function_with_default_args() {
590 let e: &dyn Evaluator = &TreeWalkEvaluator;
591 assert_eq!(
592 e.eval_expr("({a, b ? 10}: a + b) {a = 5;}").unwrap(),
593 Value::Int(15),
594 );
595 }
596
597 #[test]
598 fn tree_walk_eval_with_throws_propagated() {
599 let e: &dyn Evaluator = &TreeWalkEvaluator;
600 let result = e.eval_expr(r#"builtins.throw "boom""#);
601 assert!(result.is_err());
602 let err = result.unwrap_err();
603 assert!(err.is_throw());
604 }
605
606 #[test]
607 fn tree_walk_eval_assert_failure() {
608 let e: &dyn Evaluator = &TreeWalkEvaluator;
609 let result = e.eval_expr("assert false; 42");
610 assert!(matches!(result, Err(EvalError::AssertionFailed(_))));
611 }
612
613 #[test]
614 fn tree_walk_eval_division_by_zero() {
615 let e: &dyn Evaluator = &TreeWalkEvaluator;
616 let result = e.eval_expr("1 / 0");
617 assert!(matches!(result, Err(EvalError::DivisionByZero)));
618 }
619
620 #[test]
621 fn tree_walk_eval_undefined_variable() {
622 let e: &dyn Evaluator = &TreeWalkEvaluator;
623 let result = e.eval_expr("nonexistent_xyz");
624 assert!(matches!(result, Err(EvalError::UndefinedVar(_))));
625 }
626
627 #[test]
628 fn tree_walk_eval_path_literal() {
629 let e: &dyn Evaluator = &TreeWalkEvaluator;
630 let v = e.eval_expr("/tmp/x").unwrap();
631 assert!(matches!(v, Value::Path(_)));
632 }
633
634 #[test]
635 fn tree_walk_eval_float_literal() {
636 let e: &dyn Evaluator = &TreeWalkEvaluator;
637 assert_eq!(e.eval_expr("3.14").unwrap(), Value::Float(3.14));
638 }
639
640 #[test]
641 fn tree_walk_eval_lambda_returns_lambda() {
642 let e: &dyn Evaluator = &TreeWalkEvaluator;
643 let v = e.eval_expr("x: x").unwrap();
644 assert!(matches!(v, Value::Lambda(_)));
645 }
646}