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 flake {
47 pub use sui_compat::flake::*;
48}
49
50pub use eval::eval;
52pub use value::{EvalError, Value};
54
55pub trait Evaluator {
60 fn eval_expr(&self, input: &str) -> Result<Value, EvalError>;
62
63 fn eval_file(&self, path: &std::path::Path) -> Result<Value, EvalError>;
65}
66
67pub struct TreeWalkEvaluator;
69
70impl Evaluator for TreeWalkEvaluator {
71 fn eval_expr(&self, input: &str) -> Result<Value, EvalError> {
72 eval(input)
73 }
74
75 fn eval_file(&self, path: &std::path::Path) -> Result<Value, EvalError> {
76 let source = std::fs::read_to_string(path)
77 .map_err(|e| EvalError::IoError {
78 context: format!("eval_file: {}", path.display()),
79 message: e.to_string(),
80 })?;
81 let path_buf = path.to_path_buf();
82 let _guard = eval::push_eval_file(path_buf.clone());
83 eval::eval_with_file(&source, Some(path_buf))
84 }
85}
86
87pub struct BytecodeEvaluator;
93
94impl BytecodeEvaluator {
95 fn eval_with_flake_resolver(input: &str) -> Result<Value, EvalError> {
102 let _flake_guard = sui_bytecode::set_flake_resolver(Box::new(|flake_ref: &str| {
104 let flake_dir = if flake_ref.starts_with('/') || flake_ref.starts_with('.') {
105 std::path::PathBuf::from(flake_ref)
106 } else if let Some(path) = flake_ref.strip_prefix("path:") {
107 std::path::PathBuf::from(path)
108 } else {
109 return Err(format!("unsupported flake reference: {flake_ref}"));
110 };
111
112 let result = builtins::evaluate_flake(&flake_dir)
113 .map_err(|e| e.to_string())?;
114
115 Ok(eval_to_string_keyed(&result))
117 }));
118
119 let _bridge_guard = sui_bytecode::set_builtin_bridge(Box::new(
121 |name: &str, args: Vec<sui_bytecode::StringKeyedValue>| {
122 if name == "__import" {
125 let path_str = match &args[0] {
126 sui_bytecode::StringKeyedValue::Path(p)
127 | sui_bytecode::StringKeyedValue::String(p) => p.clone(),
128 _ => return Err("__import: expected path or string argument".to_string()),
129 };
130 let path = std::path::Path::new(&path_str);
131 let source = std::fs::read_to_string(path)
132 .map_err(|e| format!("__import: {}: {e}", path.display()))?;
133 let path_buf = path.to_path_buf();
134 let _guard = eval::push_eval_file(path_buf.clone());
135 let result = eval::eval_with_file(&source, Some(path_buf))
136 .map_err(|e| e.to_string())?;
137 let forced = eval::force_value(&result)
141 .map_err(|e| e.to_string())?;
142 return Ok(eval_to_string_keyed(&forced));
143 }
144
145 let eval_args: Vec<Value> = args
147 .iter()
148 .map(|a| convert::string_keyed_to_eval(a))
149 .collect();
150
151 let result = builtins::call_builtin_by_name(name, &eval_args)
153 .map_err(|e| e.to_string())?;
154
155 let forced = eval::force_value(&result)
158 .map_err(|e| e.to_string())?;
159
160 Ok(eval_to_string_keyed(&forced))
162 },
163 ));
164
165 match sui_bytecode::eval_full(input) {
166 Ok(result) => Ok(convert::string_keyed_to_eval(&result.to_string_keyed())),
167 Err(sui_bytecode::EvalError::Compile(c)) => {
168 eprintln!("[sui-vm] top-level compile fallback: {c}");
170 eval::eval(input)
171 }
172 Err(sui_bytecode::EvalError::Runtime(r)) => {
173 eprintln!("[sui-vm] top-level runtime fallback: {r}");
177 eval::eval(input)
178 }
179 }
180 }
181}
182
183pub fn eval_to_string_keyed(val: &Value) -> sui_bytecode::StringKeyedValue {
196 match val {
197 Value::Null => sui_bytecode::StringKeyedValue::Null,
198 Value::Bool(b) => sui_bytecode::StringKeyedValue::Bool(*b),
199 Value::Int(n) => sui_bytecode::StringKeyedValue::Int(*n),
200 Value::Float(f) => sui_bytecode::StringKeyedValue::Float(*f),
201 Value::String(s) => sui_bytecode::StringKeyedValue::String(s.chars.to_string()),
202 Value::Path(p) => sui_bytecode::StringKeyedValue::Path(p.to_string()),
203 Value::List(items) => {
204 sui_bytecode::StringKeyedValue::List(
205 items.iter().map(eval_to_string_keyed).collect(),
206 )
207 }
208 Value::Attrs(attrs) => {
209 let mut map = std::collections::BTreeMap::new();
210 for (k, v) in attrs.iter() {
211 map.insert(k.clone(), eval_to_string_keyed(v));
212 }
213 sui_bytecode::StringKeyedValue::Attrs(map)
214 }
215 Value::Lambda(closure) => {
216 let closure_rc = std::rc::Rc::new((**closure).clone());
222 sui_bytecode::StringKeyedValue::Callable(std::rc::Rc::new(move |arg| {
223 let eval_arg = convert::string_keyed_to_eval(&arg);
224 let func = Value::Lambda(Rc::new((*closure_rc).clone()));
225 let result = eval::apply(func, eval_arg)
226 .map_err(|e| e.to_string())?;
227 let forced = eval::force_value(&result)
228 .map_err(|e| e.to_string())?;
229 Ok(eval_to_string_keyed(&forced))
230 }))
231 }
232 Value::Builtin(bf) => {
233 let bf_rc = std::rc::Rc::new((**bf).clone());
237 sui_bytecode::StringKeyedValue::Callable(std::rc::Rc::new(move |arg| {
238 let eval_arg = convert::string_keyed_to_eval(&arg);
239 let func = Value::Builtin(Box::new((*bf_rc).clone()));
240 let result = eval::apply(func, eval_arg)
241 .map_err(|e| e.to_string())?;
242 let forced = eval::force_value(&result)
243 .map_err(|e| e.to_string())?;
244 Ok(eval_to_string_keyed(&forced))
245 }))
246 }
247 Value::Thunk(t) => {
248 if t.is_evaluated() {
251 match t.force(&|e, env| eval::eval_expr(e, env)) {
252 Ok(v) => eval_to_string_keyed(&v),
253 Err(_) => sui_bytecode::StringKeyedValue::Null,
254 }
255 } else {
256 let thunk_clone = t.clone();
261 sui_bytecode::StringKeyedValue::Thunk(std::rc::Rc::new(move || {
262 let forced = thunk_clone
263 .force(&|e, env| eval::eval_expr(e, env))
264 .map_err(|e| e.to_string())?;
265 Ok(eval_to_string_keyed(&forced))
266 }))
267 }
268 }
269 }
270}
271
272impl Evaluator for BytecodeEvaluator {
273 fn eval_expr(&self, input: &str) -> Result<Value, EvalError> {
274 Self::eval_with_flake_resolver(input)
275 }
276
277 fn eval_file(&self, path: &std::path::Path) -> Result<Value, EvalError> {
278 let source = std::fs::read_to_string(path)
279 .map_err(|e| EvalError::IoError {
280 context: format!("eval_file: {}", path.display()),
281 message: e.to_string(),
282 })?;
283 self.eval_expr(&source)
284 }
285}
286
287#[cfg(test)]
288mod tests {
289 use super::*;
290
291 struct MockEvaluator(Result<Value, EvalError>);
292 impl Evaluator for MockEvaluator {
293 fn eval_expr(&self, _: &str) -> Result<Value, EvalError> {
294 match &self.0 { Ok(v) => Ok(v.clone()), Err(_) => Err(EvalError::NotImplemented("mock".into())) }
295 }
296 fn eval_file(&self, _: &std::path::Path) -> Result<Value, EvalError> {
297 self.eval_expr("")
298 }
299 }
300
301 #[test]
302 fn mock_evaluator_ok() {
303 let e = MockEvaluator(Ok(Value::Int(42)));
304 assert_eq!(e.eval_expr("anything").unwrap(), Value::Int(42));
305 }
306
307 #[test]
308 fn mock_evaluator_err() {
309 let e = MockEvaluator(Err(EvalError::NotImplemented("x".into())));
310 assert!(e.eval_expr("anything").is_err());
311 }
312
313 #[test]
314 fn tree_walk_evaluator() {
315 let e = TreeWalkEvaluator;
316 assert_eq!(e.eval_expr("1 + 2").unwrap(), Value::Int(3));
317 }
318
319 #[test]
320 fn evaluator_trait_object_safe() {
321 fn _assert(_: &dyn Evaluator) {}
322 }
323
324 #[test]
327 fn tree_walk_eval_integer_arithmetic() {
328 let e: &dyn Evaluator = &TreeWalkEvaluator;
329 assert_eq!(e.eval_expr("2 + 3").unwrap(), Value::Int(5));
330 }
331
332 #[test]
333 fn tree_walk_eval_string_literal() {
334 let e: &dyn Evaluator = &TreeWalkEvaluator;
335 assert_eq!(
336 e.eval_expr(r#""hello world""#).unwrap(),
337 Value::string("hello world"),
338 );
339 }
340
341 #[test]
342 fn tree_walk_eval_boolean() {
343 let e: &dyn Evaluator = &TreeWalkEvaluator;
344 assert_eq!(e.eval_expr("true && false").unwrap(), Value::Bool(false));
345 }
346
347 #[test]
348 fn tree_walk_eval_if_else() {
349 let e: &dyn Evaluator = &TreeWalkEvaluator;
350 assert_eq!(
351 e.eval_expr("if true then 42 else 0").unwrap(),
352 Value::Int(42),
353 );
354 }
355
356 #[test]
357 fn tree_walk_eval_let_binding() {
358 let e: &dyn Evaluator = &TreeWalkEvaluator;
359 assert_eq!(
360 e.eval_expr("let x = 10; in x * 2").unwrap(),
361 Value::Int(20),
362 );
363 }
364
365 #[test]
366 fn tree_walk_eval_attrset() {
367 let e: &dyn Evaluator = &TreeWalkEvaluator;
368 let val = e.eval_expr("{ a = 1; b = 2; }.a").unwrap();
369 assert_eq!(val, Value::Int(1));
370 }
371
372 #[test]
373 fn tree_walk_eval_list() {
374 let e: &dyn Evaluator = &TreeWalkEvaluator;
375 let val = e.eval_expr("[1 2 3]").unwrap();
376 assert_eq!(
377 val,
378 Value::list(vec![Value::Int(1), Value::Int(2), Value::Int(3)]),
379 );
380 }
381
382 #[test]
383 fn tree_walk_eval_lambda_application() {
384 let e: &dyn Evaluator = &TreeWalkEvaluator;
385 assert_eq!(
386 e.eval_expr("(x: x + 1) 5").unwrap(),
387 Value::Int(6),
388 );
389 }
390
391 #[test]
392 fn tree_walk_eval_builtin_via_trait() {
393 let e: &dyn Evaluator = &TreeWalkEvaluator;
394 assert_eq!(
395 e.eval_expr("builtins.length [1 2 3]").unwrap(),
396 Value::Int(3),
397 );
398 }
399
400 #[test]
401 fn tree_walk_eval_parse_error_via_trait() {
402 let e: &dyn Evaluator = &TreeWalkEvaluator;
403 let result = e.eval_expr("let in");
404 assert!(result.is_err());
405 }
406
407 #[test]
408 fn tree_walk_eval_null_via_trait() {
409 let e: &dyn Evaluator = &TreeWalkEvaluator;
410 assert_eq!(e.eval_expr("null").unwrap(), Value::Null);
411 }
412
413 #[test]
414 fn tree_walk_eval_file_missing() {
415 let e: &dyn Evaluator = &TreeWalkEvaluator;
416 let result = e.eval_file(std::path::Path::new("/nonexistent/file.nix"));
417 assert!(result.is_err());
418 }
419
420 #[test]
421 fn tree_walk_eval_string_interpolation_via_trait() {
422 let e: &dyn Evaluator = &TreeWalkEvaluator;
423 assert_eq!(
424 e.eval_expr(r#"let name = "world"; in "hello ${name}""#).unwrap(),
425 Value::string("hello world"),
426 );
427 }
428
429 #[test]
430 fn tree_walk_eval_comparison_via_trait() {
431 let e: &dyn Evaluator = &TreeWalkEvaluator;
432 assert_eq!(e.eval_expr("3 > 2").unwrap(), Value::Bool(true));
433 assert_eq!(e.eval_expr("1 == 1").unwrap(), Value::Bool(true));
434 }
435
436 #[test]
437 fn tree_walk_eval_recursive_attrset_via_trait() {
438 let e: &dyn Evaluator = &TreeWalkEvaluator;
439 assert_eq!(
440 e.eval_expr("rec { x = 1; y = x + 1; }.y").unwrap(),
441 Value::Int(2),
442 );
443 }
444
445 #[test]
448 fn re_export_eval_function_works() {
449 assert_eq!(eval("1 + 1").unwrap(), Value::Int(2));
452 }
453
454 #[test]
455 fn re_export_value_and_error_types_constructible() {
456 let v: Value = Value::Int(7);
457 let e: EvalError = EvalError::UndefinedVar("x".into());
458 assert_eq!(v.type_name(), "int");
459 assert!(e.to_string().contains("undefined"));
460 }
461
462 #[test]
465 fn flake_module_re_exports_compat_types() {
466 #[allow(unused_imports)]
471 use crate::flake::*;
472 }
474
475 #[test]
478 fn tree_walk_eval_file_with_real_temp_file() {
479 let dir = std::env::temp_dir().join("sui-eval-test-tree-walk");
480 let _ = std::fs::create_dir_all(&dir);
481 let path = dir.join("simple.nix");
482 std::fs::write(&path, "1 + 2").unwrap();
483 let e: &dyn Evaluator = &TreeWalkEvaluator;
484 let result = e.eval_file(&path).unwrap();
485 assert_eq!(result, Value::Int(3));
486 let _ = std::fs::remove_file(&path);
487 let _ = std::fs::remove_dir(&dir);
488 }
489
490 #[test]
491 fn tree_walk_eval_file_propagates_io_error_kind() {
492 let e: &dyn Evaluator = &TreeWalkEvaluator;
493 let result = e.eval_file(std::path::Path::new("/nonexistent/never/exists.nix"));
494 match result {
495 Err(EvalError::IoError { context, .. }) => {
496 assert!(context.contains("eval_file"));
497 }
498 other => panic!("expected IoError, got {other:?}"),
499 }
500 }
501
502 #[test]
503 fn tree_walk_eval_file_parse_error_propagates() {
504 let dir = std::env::temp_dir().join("sui-eval-test-tw-parse");
505 let _ = std::fs::create_dir_all(&dir);
506 let path = dir.join("bad.nix");
507 std::fs::write(&path, "let in").unwrap();
508 let e: &dyn Evaluator = &TreeWalkEvaluator;
509 let result = e.eval_file(&path);
510 assert!(result.is_err());
511 let _ = std::fs::remove_file(&path);
512 let _ = std::fs::remove_dir(&dir);
513 }
514
515 #[test]
518 fn mock_evaluator_dispatched_via_trait_object() {
519 let m: Box<dyn Evaluator> = Box::new(MockEvaluator(Ok(Value::Bool(true))));
520 let r = m.eval_expr("anything").unwrap();
521 assert_eq!(r, Value::Bool(true));
522 }
523
524 #[test]
525 fn mock_evaluator_eval_file_routes_through_eval_expr() {
526 let m = MockEvaluator(Ok(Value::Int(1)));
527 let r = m.eval_file(std::path::Path::new("/dev/null"));
528 assert_eq!(r.unwrap(), Value::Int(1));
529 }
530
531 #[test]
534 fn tree_walk_eval_function_with_default_args() {
535 let e: &dyn Evaluator = &TreeWalkEvaluator;
536 assert_eq!(
537 e.eval_expr("({a, b ? 10}: a + b) {a = 5;}").unwrap(),
538 Value::Int(15),
539 );
540 }
541
542 #[test]
543 fn tree_walk_eval_with_throws_propagated() {
544 let e: &dyn Evaluator = &TreeWalkEvaluator;
545 let result = e.eval_expr(r#"builtins.throw "boom""#);
546 assert!(result.is_err());
547 let err = result.unwrap_err();
548 assert!(err.is_throw());
549 }
550
551 #[test]
552 fn tree_walk_eval_assert_failure() {
553 let e: &dyn Evaluator = &TreeWalkEvaluator;
554 let result = e.eval_expr("assert false; 42");
555 assert!(matches!(result, Err(EvalError::AssertionFailed(_))));
556 }
557
558 #[test]
559 fn tree_walk_eval_division_by_zero() {
560 let e: &dyn Evaluator = &TreeWalkEvaluator;
561 let result = e.eval_expr("1 / 0");
562 assert!(matches!(result, Err(EvalError::DivisionByZero)));
563 }
564
565 #[test]
566 fn tree_walk_eval_undefined_variable() {
567 let e: &dyn Evaluator = &TreeWalkEvaluator;
568 let result = e.eval_expr("nonexistent_xyz");
569 assert!(matches!(result, Err(EvalError::UndefinedVar(_))));
570 }
571
572 #[test]
573 fn tree_walk_eval_path_literal() {
574 let e: &dyn Evaluator = &TreeWalkEvaluator;
575 let v = e.eval_expr("/tmp/x").unwrap();
576 assert!(matches!(v, Value::Path(_)));
577 }
578
579 #[test]
580 fn tree_walk_eval_float_literal() {
581 let e: &dyn Evaluator = &TreeWalkEvaluator;
582 assert_eq!(e.eval_expr("3.14").unwrap(), Value::Float(3.14));
583 }
584
585 #[test]
586 fn tree_walk_eval_lambda_returns_lambda() {
587 let e: &dyn Evaluator = &TreeWalkEvaluator;
588 let v = e.eval_expr("x: x").unwrap();
589 assert!(matches!(v, Value::Lambda(_)));
590 }
591}