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