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