1use std::cell::{Cell, RefCell};
18use std::collections::{BTreeMap, HashMap};
19use std::path::PathBuf;
20use std::rc::Rc;
21use std::sync::atomic::{AtomicU64, Ordering};
22static VM_FALLBACK_COUNT: AtomicU64 = AtomicU64::new(0);
24pub fn vm_fallback_count() -> u64 {
26 VM_FALLBACK_COUNT.load(Ordering::Relaxed)
27}
28use crate::builtins::BuiltinRegistry;
29use crate::chunk::Chunk;
30use crate::compiler::Compiler;
31use crate::error::VMError;
32use crate::intern::{Interner, Symbol};
33use crate::nanbox::NanBox;
34use crate::opcode::OpCode;
35use crate::value::{HigherOrderBuiltin, HigherOrderOp, ThunkState, VMThunk, VMValue};
36const MAX_CALL_DEPTH: usize = 1024;
38const MAX_THUNK_CHAIN_DEPTH: u32 = 2000;
41fn deferred_apply_chunk() -> Rc<Chunk> {
46 thread_local! {
47 static CHUNK: Rc<Chunk> = {
48 let mut c = Chunk::new();
49 c.write_op(OpCode::GetUpvalue, 0);
51 c.write_byte(0, 0); c.write_byte(0, 0); c.write_op(OpCode::GetUpvalue, 0);
55 c.write_byte(1, 0); c.write_byte(0, 0); c.write_op(OpCode::Call, 0);
59 c.write_op(OpCode::Return, 0);
61 Rc::new(c)
62 };
63 }
64 CHUNK.with(|c| c.clone())
65}
66pub type FlakeResolverFn = dyn Fn(&str) -> Result<crate::value::StringKeyedValue, String>;
79thread_local! {
80 static FLAKE_RESOLVER: RefCell<Option<Box<FlakeResolverFn>>> = const { RefCell::new(None) };
81}
82pub fn set_flake_resolver(
88 resolver: Box<FlakeResolverFn>,
89) -> FlakeResolverGuard {
90 let prev = FLAKE_RESOLVER.with(|r| r.borrow_mut().replace(resolver));
91 FlakeResolverGuard { _prev: prev }
92}
93pub struct FlakeResolverGuard {
95 _prev: Option<Box<FlakeResolverFn>>,
96}
97impl Drop for FlakeResolverGuard {
98 fn drop(&mut self) {
99 let prev = self._prev.take();
100 FLAKE_RESOLVER.with(|r| *r.borrow_mut() = prev);
101 }
102}
103#[derive(Clone)]
105struct CallFrame {
106 chunk: Rc<Chunk>,
108 ip: usize,
110 stack_base: usize,
112 upvalues: Vec<NanBox>,
114}
115pub struct VM<'a> {
122 stack: Vec<NanBox>,
124 frames: Vec<CallFrame>,
126 interner: &'a mut Interner,
128 with_stack: Vec<NanBox>,
130 builtins: BuiltinRegistry,
132 import_cache: Rc<RefCell<HashMap<String, VMValue>>>,
134 compile_cache: HashMap<PathBuf, Rc<Chunk>>,
138}
139impl<'a> VM<'a> {
140 pub fn execute(chunk: Chunk, interner: &'a mut Interner) -> Result<VMValue, VMError> {
142 let mut vm = Self {
143 stack: Vec::with_capacity(256),
144 frames: Vec::with_capacity(64),
145 interner,
146 with_stack: Vec::new(),
147 builtins: BuiltinRegistry::new(),
148 import_cache: Rc::new(RefCell::new(HashMap::new())),
149 compile_cache: HashMap::new(),
150 };
151 vm.frames.push(CallFrame {
152 chunk: Rc::new(chunk),
153 ip: 0,
154 stack_base: 0,
155 upvalues: Vec::new(),
156 });
157 let result = vm.run()?;
158 let result = vm.force_value(result)?;
160 let result = vm.deep_force(result)?;
163 Ok(result.to_vmvalue())
164 }
165 fn run(&mut self) -> Result<NanBox, VMError> {
167 self.run_until(0)
168 }
169 fn run_until(&mut self, stop_depth: usize) -> Result<NanBox, VMError> {
175 let mut op_count: u64 = 0;
176 loop {
177 op_count += 1;
178 if std::env::var("SUI_VM_TRACE").is_ok() && op_count % 1_000_000 == 0 {
179 eprintln!(
180 "[sui-vm] {}M ops, depth {}, chunk: {}",
181 op_count / 1_000_000,
182 self.frames.len(),
183 self.current_chunk_name(),
184 );
185 }
186 let op_byte = self.read_byte()?;
187 let op = OpCode::from_byte(op_byte).ok_or(VMError::InvalidOpcode(op_byte))?;
188 match op {
189 OpCode::Add | OpCode::Sub | OpCode::Mul | OpCode::Div | OpCode::Negate => {
191 self.dispatch_arithmetic(op)?;
192 }
193 OpCode::Equal | OpCode::NotEqual | OpCode::Less | OpCode::Greater |
195 OpCode::LessEqual | OpCode::GreaterEqual => {
196 self.dispatch_comparison(op)?;
197 }
198 OpCode::Not | OpCode::And | OpCode::Or | OpCode::Implication => {
200 self.dispatch_logic(op)?;
201 }
202 OpCode::Constant | OpCode::Null | OpCode::True | OpCode::False => {
204 self.dispatch_constant(op)?;
205 }
206 OpCode::GetLocal | OpCode::SetLocal | OpCode::GetUpvalue | OpCode::SetUpvalue => {
208 self.dispatch_variable(op)?;
209 }
210 OpCode::MakeAttrs | OpCode::GetAttr | OpCode::HasAttr | OpCode::UpdateAttrs |
212 OpCode::SelectOrDefault | OpCode::DynGetAttr | OpCode::DynHasAttr |
213 OpCode::DynSelectOrDefault => {
214 self.dispatch_attrset(op)?;
215 }
216 OpCode::MakeList | OpCode::Concat => {
218 self.dispatch_list(op)?;
219 }
220 OpCode::Jump | OpCode::JumpIfFalse | OpCode::JumpIfTrue | OpCode::Assert | OpCode::Throw => {
222 self.dispatch_control(op)?;
223 }
224 OpCode::MakeClosure | OpCode::Call | OpCode::TailCall => {
226 self.dispatch_function(op)?;
227 }
228 OpCode::Return => {
229 let result = self.pop()?;
230 let frame = self.frames.pop().ok_or(VMError::Internal(
231 "return with empty call stack".to_string(),
232 ))?;
233 if self.frames.len() <= stop_depth {
234 return Ok(result);
235 }
236 self.stack.truncate(frame.stack_base);
237 self.push(result);
238 }
239 OpCode::MakeThunk | OpCode::MakeLazyThunk | OpCode::Force |
241 OpCode::PatchThunkUpvalues => {
242 self.dispatch_thunk(op)?;
243 }
244 OpCode::PushWith | OpCode::PopWith | OpCode::LookupWith |
246 OpCode::PushBuiltins => {
247 self.dispatch_scope(op)?;
248 }
249 OpCode::Import | OpCode::CallBuiltin => {
251 self.dispatch_import(op)?;
252 }
253 OpCode::GetLocalAttr | OpCode::GetLocalCall => {
255 self.dispatch_super(op)?;
256 }
257 OpCode::Pop | OpCode::Dup | OpCode::Interpolate => {
259 self.dispatch_stack(op)?;
260 }
261 }
262 }
263 }
264 fn dispatch_constant(&mut self, op: OpCode) -> Result<(), VMError> {
266 match op {
267 OpCode::Constant => {
268 let idx = self.read_u16()?;
269 let value = &self.current_chunk().constants[idx as usize];
270 let boxed = NanBox::from_vmvalue(value);
271 self.push(boxed);
272 }
273 OpCode::Null => self.push(NanBox::null()),
274 OpCode::True => self.push(NanBox::bool(true)),
275 OpCode::False => self.push(NanBox::bool(false)),
276 _ => unreachable!(),
277 }
278 Ok(())
279 }
280 fn dispatch_arithmetic(&mut self, op: OpCode) -> Result<(), VMError> {
281 match op {
282 OpCode::Add => {
283 let b = self.pop_forced()?;
284 let a = self.pop_forced()?;
285 self.push(self.add(&a, &b)?);
286 }
287 OpCode::Sub => {
288 let b = self.pop_forced()?;
289 let a = self.pop_forced()?;
290 self.push(self.num_op(&a, &b, |x, y| x - y, |x, y| x - y, "subtraction")?);
291 }
292 OpCode::Mul => {
293 let b = self.pop_forced()?;
294 let a = self.pop_forced()?;
295 self.push(self.num_op(&a, &b, |x, y| x * y, |x, y| x * y, "multiplication")?);
296 }
297 OpCode::Div => {
298 let b = self.pop_forced()?;
299 let a = self.pop_forced()?;
300 if a.is_int() && b.as_int() == Some(0) {
301 return Err(VMError::DivisionByZero);
302 }
303 self.push(self.num_op(&a, &b, |x, y| x / y, |x, y| x / y, "division")?);
304 }
305 OpCode::Negate => {
306 let val = self.pop_forced()?;
307 if let Some(n) = val.as_int() {
308 self.push(NanBox::int(-n));
309 } else if let Some(f) = val.as_float() {
310 self.push(NanBox::float(-f));
311 } else {
312 return Err(VMError::TypeError {
313 expected: "int or float",
314 got: val.type_name(),
315 context: "negation".to_string(),
316 });
317 }
318 }
319 _ => unreachable!(),
320 }
321 Ok(())
322 }
323 fn dispatch_logic(&mut self, op: OpCode) -> Result<(), VMError> {
324 match op {
325 OpCode::Not => {
326 let val = self.pop_forced()?;
327 let b = val.is_truthy()?;
328 self.push(NanBox::bool(!b));
329 }
330 OpCode::And => {
331 let b = self.pop_forced()?;
332 let a = self.pop_forced()?;
333 self.push(NanBox::bool(a.is_truthy()? && b.is_truthy()?));
334 }
335 OpCode::Or => {
336 let b = self.pop_forced()?;
337 let a = self.pop_forced()?;
338 self.push(NanBox::bool(a.is_truthy()? || b.is_truthy()?));
339 }
340 OpCode::Implication => {
341 let b = self.pop_forced()?;
342 let a = self.pop_forced()?;
343 self.push(NanBox::bool(!a.is_truthy()? || b.is_truthy()?));
344 }
345 _ => unreachable!(),
346 }
347 Ok(())
348 }
349 fn dispatch_comparison(&mut self, op: OpCode) -> Result<(), VMError> {
350 match op {
351 OpCode::Equal => {
352 let b = self.pop_forced()?;
353 let a = self.pop_forced()?;
354 let eq = self.deep_eq(&a, &b)?;
355 self.push(NanBox::bool(eq));
356 }
357 OpCode::NotEqual => {
358 let b = self.pop_forced()?;
359 let a = self.pop_forced()?;
360 let eq = self.deep_eq(&a, &b)?;
361 self.push(NanBox::bool(!eq));
362 }
363 OpCode::Less => {
364 let b = self.pop_forced()?;
365 let a = self.pop_forced()?;
366 self.push(NanBox::bool(self.compare(&a, &b)? == std::cmp::Ordering::Less));
367 }
368 OpCode::Greater => {
369 let b = self.pop_forced()?;
370 let a = self.pop_forced()?;
371 self.push(NanBox::bool(self.compare(&a, &b)? == std::cmp::Ordering::Greater));
372 }
373 OpCode::LessEqual => {
374 let b = self.pop_forced()?;
375 let a = self.pop_forced()?;
376 self.push(NanBox::bool(self.compare(&a, &b)? != std::cmp::Ordering::Greater));
377 }
378 OpCode::GreaterEqual => {
379 let b = self.pop_forced()?;
380 let a = self.pop_forced()?;
381 self.push(NanBox::bool(self.compare(&a, &b)? != std::cmp::Ordering::Less));
382 }
383 _ => unreachable!(),
384 }
385 Ok(())
386 }
387 fn dispatch_variable(&mut self, op: OpCode) -> Result<(), VMError> {
388 match op {
389 OpCode::GetLocal => {
390 let slot = self.read_u16()? as usize;
391 let abs_slot = self.current_frame().stack_base + slot;
392 if abs_slot >= self.stack.len() {
393 let frame = self.current_frame();
394 let chunk = &frame.chunk;
395 let failing_ip = frame.ip.saturating_sub(3);
396 let frame_info: Vec<String> = self.frames.iter().enumerate()
397 .map(|(i, f)| format!("frame[{i}]: base={}, ip={}", f.stack_base, f.ip))
398 .collect();
399 let bytecode_context = Self::disassemble_around(chunk, failing_ip, 10);
400 return Err(VMError::Internal(format!(
401 "GetLocal: slot {slot} (abs {abs_slot}) out of bounds \
402 (stack len {}, base {}, depth {})\n \
403 {}\n bytecode around ip={failing_ip}:\n{}",
404 self.stack.len(),
405 self.current_frame().stack_base,
406 self.frames.len(),
407 frame_info.join("\n "),
408 bytecode_context,
409 )));
410 }
411 let value = self.stack[abs_slot].clone();
412 self.push(value);
413 }
414 OpCode::SetLocal => {
415 let slot = self.read_u16()? as usize;
416 let abs_slot = self.current_frame().stack_base + slot;
417 if abs_slot >= self.stack.len() {
418 return Err(VMError::Internal(format!(
419 "SetLocal: slot {slot} (abs {abs_slot}) out of bounds \
420 (stack len {}, base {})",
421 self.stack.len(),
422 self.current_frame().stack_base,
423 )));
424 }
425 let value = self.peek()?.clone();
426 self.stack[abs_slot] = value;
427 }
428 OpCode::GetUpvalue => {
429 let idx = self.read_u16()? as usize;
430 let upvalues = &self.current_frame().upvalues;
431 if idx >= upvalues.len() {
432 eprintln!(
435 "[sui-vm] GetUpvalue: index {} out of bounds (len {})",
436 idx, upvalues.len()
437 );
438 self.push(NanBox::null());
439 } else {
440 let value = upvalues[idx].clone();
441 self.push(value);
442 }
443 }
444 OpCode::SetUpvalue => {
445 let idx = self.read_u16()? as usize;
446 let value = self.peek()?.clone();
447 self.current_frame_mut().upvalues[idx] = value;
448 }
449 _ => unreachable!(),
450 }
451 Ok(())
452 }
453 fn dispatch_scope(&mut self, op: OpCode) -> Result<(), VMError> {
454 match op {
455 OpCode::PushWith => {
456 let scope = self.pop_forced()?;
457 self.with_stack.push(scope);
458 }
459 OpCode::PopWith => {
460 self.with_stack.pop().ok_or_else(|| {
461 VMError::Internal("PopWith: empty with-stack".to_string())
462 })?;
463 }
464 OpCode::LookupWith => {
465 let name_idx = self.read_u16()?;
466 let name_string = match &self.current_chunk().constants[name_idx as usize] {
467 VMValue::String(s) => s.clone(),
468 _ => {
469 return Err(VMError::Internal(
470 "LookupWith: constant not a string".to_string(),
471 ));
472 }
473 };
474 let sym = self.interner.intern(&name_string);
475 let mut found = None;
476 for scope in self.with_stack.iter().rev() {
477 if let Some(attrs) = scope.as_attrs() {
478 if let Some(val) = attrs.get(&sym) {
479 found = Some(val.clone());
480 break;
481 }
482 }
483 }
484 match found {
485 Some(val) => self.push(val),
486 None => {
487 return Err(VMError::UndefinedVariable(name_string));
488 }
489 }
490 }
491 OpCode::PushBuiltins => {
492 let builtins_val = self.builtins.make_builtins_attrset(self.interner);
493 self.push(NanBox::from_vmvalue(&builtins_val));
494 }
495 _ => unreachable!(),
496 }
497 Ok(())
498 }
499 fn dispatch_attrset(&mut self, op: OpCode) -> Result<(), VMError> {
500 match op {
501 OpCode::MakeAttrs => {
502 let count = self.read_u16()? as usize;
503 let mut attrs: BTreeMap<Symbol, NanBox> = BTreeMap::new();
504 for _ in 0..count {
505 let key = self.pop()?;
506 let value = self.pop()?;
507 let key_sym = if let Some(s) = key.as_string() {
508 self.interner.intern(s)
509 } else {
510 return Err(VMError::TypeError {
511 expected: "string",
512 got: key.type_name(),
513 context: "attrset key".to_string(),
514 });
515 };
516 attrs.insert(key_sym, value);
517 }
518 self.push(NanBox::attrs(attrs));
519 }
520 OpCode::GetAttr => {
521 let key_idx = self.read_u16()?;
522 let key_sym = self.resolve_key_constant(key_idx)?;
523 let attrset = self.pop_forced()?;
524 if let Some(attrs) = attrset.as_attrs() {
525 if let Some(val) = attrs.get(&key_sym) {
526 let forced = if val.is_thunk() {
527 self.force_value(val.clone())?
528 } else {
529 val.clone()
530 };
531 self.push(forced);
532 } else {
533 let key_str = self.interner.resolve(key_sym).to_string();
534 return Err(VMError::AttrNotFound(key_str));
535 }
536 } else {
537 let key_str = self.interner.resolve(key_sym).to_string();
538 return Err(VMError::TypeError {
539 expected: "set",
540 got: attrset.type_name(),
541 context: format!("attribute selection '.{key_str}'"),
542 });
543 }
544 }
545 OpCode::HasAttr => {
546 let key_idx = self.read_u16()?;
547 let key_sym = self.resolve_key_constant(key_idx)?;
548 let attrset = self.pop_forced()?;
549 let result = if let Some(attrs) = attrset.as_attrs() {
550 attrs.contains_key(&key_sym)
551 } else {
552 false
553 };
554 self.push(NanBox::bool(result));
555 }
556 OpCode::UpdateAttrs => {
557 let b = self.pop_forced()?;
558 let a = self.pop_forced()?;
559 let b_vmval = b.to_vmvalue();
560 let a_vmval = a.to_vmvalue();
561 match (a_vmval, b_vmval) {
562 (VMValue::Attrs(mut left), VMValue::Attrs(right)) => {
563 for (k, v) in right {
564 left.insert(k, v);
565 }
566 self.push(NanBox::from_vmvalue(&VMValue::Attrs(left)));
567 }
568 (VMValue::Attrs(_), other) => {
569 return Err(VMError::TypeError {
570 expected: "set",
571 got: other.type_name(),
572 context: "// (right)".to_string(),
573 });
574 }
575 (other, _) => {
576 return Err(VMError::TypeError {
577 expected: "set",
578 got: other.type_name(),
579 context: "// (left)".to_string(),
580 });
581 }
582 }
583 }
584 OpCode::SelectOrDefault => {
585 let key_idx = self.read_u16()?;
586 let key_sym = self.resolve_key_constant(key_idx)?;
587 let default = self.pop()?;
588 let attrset = self.pop_forced()?;
589 if let Some(attrs) = attrset.as_attrs() {
590 if let Some(val) = attrs.get(&key_sym) {
591 let forced = if val.is_thunk() {
592 self.force_value(val.clone())?
593 } else {
594 val.clone()
595 };
596 self.push(forced);
597 } else {
598 self.push(default);
599 }
600 } else {
601 self.push(default);
602 }
603 }
604 OpCode::DynGetAttr => {
605 let key_val = self.pop_forced()?;
606 let attrset = self.pop_forced()?;
607 let key_str = key_val
608 .as_string()
609 .ok_or_else(|| VMError::TypeError {
610 expected: "string",
611 got: key_val.type_name(),
612 context: "dynamic attribute key".to_string(),
613 })?
614 .to_string();
615 let key_sym = self.interner.intern(&key_str);
616 if let Some(attrs) = attrset.as_attrs() {
617 if let Some(val) = attrs.get(&key_sym) {
618 let forced = if val.is_thunk() {
619 self.force_value(val.clone())?
620 } else {
621 val.clone()
622 };
623 self.push(forced);
624 } else {
625 return Err(VMError::AttrNotFound(key_str));
626 }
627 } else {
628 return Err(VMError::TypeError {
629 expected: "set",
630 got: attrset.type_name(),
631 context: format!("dynamic select .${{{key_str}}}"),
632 });
633 }
634 }
635 OpCode::DynHasAttr => {
636 let key_val = self.pop_forced()?;
637 let attrset = self.pop_forced()?;
638 let key_str = key_val
639 .as_string()
640 .ok_or_else(|| VMError::TypeError {
641 expected: "string",
642 got: key_val.type_name(),
643 context: "dynamic hasattr key".to_string(),
644 })?
645 .to_string();
646 let key_sym = self.interner.intern(&key_str);
647 let result = attrset.as_attrs().map_or(false, |attrs| attrs.contains_key(&key_sym));
648 self.push(NanBox::bool(result));
649 }
650 OpCode::DynSelectOrDefault => {
651 let default = self.pop()?;
652 let key_val = self.pop_forced()?;
653 let attrset = self.pop_forced()?;
654 let key_str = key_val
655 .as_string()
656 .ok_or_else(|| VMError::TypeError {
657 expected: "string",
658 got: key_val.type_name(),
659 context: "dynamic select-or-default key".to_string(),
660 })?
661 .to_string();
662 let key_sym = self.interner.intern(&key_str);
663 if let Some(attrs) = attrset.as_attrs() {
664 if let Some(val) = attrs.get(&key_sym) {
665 let forced = if val.is_thunk() {
666 self.force_value(val.clone())?
667 } else {
668 val.clone()
669 };
670 self.push(forced);
671 } else {
672 self.push(default);
673 }
674 } else {
675 self.push(default);
676 }
677 }
678 _ => unreachable!(),
679 }
680 Ok(())
681 }
682 fn dispatch_list(&mut self, op: OpCode) -> Result<(), VMError> {
683 match op {
684 OpCode::MakeList => {
685 let count = self.read_u16()? as usize;
686 let start = self.stack.len() - count;
687 let items: Vec<NanBox> = self.stack.drain(start..).collect();
688 self.push(NanBox::list(items));
689 }
690 OpCode::Concat => {
691 let b = self.pop_forced()?;
692 let a = self.pop_forced()?;
693 let a_vmval = a.to_vmvalue();
694 let b_vmval = b.to_vmvalue();
695 match (a_vmval, b_vmval) {
696 (VMValue::List(mut left), VMValue::List(right)) => {
697 left.extend(right);
698 self.push(NanBox::from_vmvalue(&VMValue::List(left)));
699 }
700 (VMValue::List(_), other) => {
701 return Err(VMError::TypeError {
702 expected: "list",
703 got: other.type_name(),
704 context: "++ (right)".to_string(),
705 });
706 }
707 (other, _) => {
708 return Err(VMError::TypeError {
709 expected: "list",
710 got: other.type_name(),
711 context: "++ (left)".to_string(),
712 });
713 }
714 }
715 }
716 _ => unreachable!(),
717 }
718 Ok(())
719 }
720 fn dispatch_control(&mut self, op: OpCode) -> Result<(), VMError> {
721 match op {
722 OpCode::Jump => {
723 let target = self.read_u16()? as usize;
724 self.current_frame_mut().ip = target;
725 }
726 OpCode::JumpIfFalse => {
727 let target = self.read_u16()? as usize;
728 let cond = self.pop_forced()?;
729 match cond.is_truthy() {
730 Ok(false) => { self.current_frame_mut().ip = target; }
731 Ok(true) => {}
732 Err(e) => {
733 if std::env::var("SUI_VM_TRACE").is_ok() {
735 let keys_preview = if let Some(attrs) = cond.as_attrs() {
736 let keys: Vec<_> = attrs.keys().take(5)
737 .map(|k| self.interner.resolve(*k).to_string())
738 .collect();
739 format!("{{{}}}", keys.join(", "))
740 } else {
741 cond.type_name().to_string()
742 };
743 eprintln!(
744 "[sui-vm] condition type error: got {} ({}) at depth {}, chunk: {}",
745 cond.type_name(), keys_preview,
746 self.frames.len(), self.current_chunk_name(),
747 );
748 }
749 return Err(e);
750 }
751 }
752 }
753 OpCode::JumpIfTrue => {
754 let target = self.read_u16()? as usize;
755 let cond = self.pop_forced()?;
756 match cond.is_truthy() {
757 Ok(true) => { self.current_frame_mut().ip = target; }
758 Ok(false) => {}
759 Err(e) => {
760 if std::env::var("SUI_VM_TRACE").is_ok() {
762 let keys_preview = if let Some(attrs) = cond.as_attrs() {
763 let keys: Vec<_> = attrs.keys().take(5)
764 .map(|k| self.interner.resolve(*k).to_string())
765 .collect();
766 format!("{{{}}}", keys.join(", "))
767 } else {
768 cond.type_name().to_string()
769 };
770 eprintln!(
771 "[sui-vm] condition type error: got {} ({}) at depth {}, chunk: {}",
772 cond.type_name(), keys_preview,
773 self.frames.len(), self.current_chunk_name(),
774 );
775 }
776 return Err(e);
777 }
778 }
779 }
780 OpCode::Assert => {
781 let cond = self.pop_forced()?;
782 if !cond.is_truthy()? {
783 return Err(VMError::AssertionFailed);
784 }
785 }
786 OpCode::Throw => {
787 let msg = self.pop_forced()?;
788 let msg_str = match msg.to_vmvalue() {
789 VMValue::String(s) => s,
790 other => format!("{other:?}"),
791 };
792 return Err(VMError::Throw(msg_str));
793 }
794 _ => unreachable!(),
795 }
796 Ok(())
797 }
798 fn dispatch_function(&mut self, op: OpCode) -> Result<(), VMError> {
799 match op {
800 OpCode::MakeClosure => {
801 let idx = self.read_u16()?;
802 let upvalue_count = self.read_u16()? as usize;
803 let closure_template = self.current_chunk().constants[idx as usize].clone();
804 if let VMValue::Closure(mut closure) = closure_template {
805 let mut upvalues = Vec::with_capacity(upvalue_count);
806 for _ in 0..upvalue_count {
807 let is_local = self.read_byte()? != 0;
808 let uv_index = self.read_u16()? as usize;
809 if is_local {
810 let abs_slot = self.current_frame().stack_base + uv_index;
811 upvalues.push(self.stack[abs_slot].clone());
812 } else {
813 let val = self.current_frame().upvalues[uv_index].clone();
814 upvalues.push(val);
815 }
816 }
817 closure.upvalues = upvalues;
818 self.push(NanBox::closure(closure));
819 } else {
820 return Err(VMError::Internal(
821 "MakeClosure: constant is not a closure".to_string(),
822 ));
823 }
824 }
825 OpCode::Call => {
826 let arg = self.pop()?;
827 let func = self.pop_forced()?;
828 if let Some(closure) = func.as_closure() {
829 let is_tail = self.peek_next_is_return();
830 let chunk = closure.chunk.clone();
831 let upvalues = closure.upvalues.clone();
832 if is_tail && self.frames.len() > 1 {
833 let base = self.current_frame().stack_base;
834 self.stack.truncate(base);
835 self.push(arg);
836 let frame = self.current_frame_mut();
837 frame.chunk = chunk;
838 frame.ip = 0;
839 frame.upvalues = upvalues;
840 } else {
841 if self.frames.len() >= MAX_CALL_DEPTH {
842 return Err(VMError::StackOverflow);
843 }
844 let stack_base = self.stack.len();
845 self.push(arg);
846 self.frames.push(CallFrame {
847 chunk,
848 ip: 0,
849 stack_base,
850 upvalues,
851 });
852 }
853 } else if func.is_higher_order_builtin() {
854 let hob = func.as_higher_order_builtin().unwrap().clone();
855 let forced_arg = self.force_value(arg)?;
856 let result = self.call_higher_order_builtin(&hob, forced_arg)?;
857 self.push(result);
858 } else if let Some(builtin) = func.as_builtin() {
859 if builtin.name == "tryEval" {
861 if let Some(result) = self.try_vm_builtin("tryEval", &arg)? {
862 self.push(result);
863 }
864 } else {
865 let forced_arg = self.force_value(arg)?;
866 if let Some(result) = self.try_vm_builtin(builtin.name, &forced_arg)? {
867 self.push(result);
868 } else {
869 let mut arg_vmval = forced_arg.to_vmvalue();
870 arg_vmval = self.shallow_force_list(arg_vmval)?;
871 let builtin_func = builtin.func.clone();
872 let result = self.call_builtin_with_scoped_import_dispatch(
873 builtin_func, arg_vmval,
874 )?;
875 self.push(result);
876 }
877 }
878 } else {
879 return Err(VMError::NotCallable(func.type_name().to_string()));
880 }
881 }
882 OpCode::TailCall => {
883 let arg = self.pop()?;
887 let func = self.pop_forced()?;
888 if let Some(closure) = func.as_closure() {
889 let chunk = closure.chunk.clone();
890 let upvalues = closure.upvalues.clone();
891 if self.frames.len() > 1 {
892 let base = self.current_frame().stack_base;
894 self.stack.truncate(base);
895 self.push(arg);
896 let frame = self.current_frame_mut();
897 frame.chunk = chunk;
898 frame.ip = 0;
899 frame.upvalues = upvalues;
900 } else {
901 if self.frames.len() >= MAX_CALL_DEPTH {
903 return Err(VMError::StackOverflow);
904 }
905 let stack_base = self.stack.len();
906 self.push(arg);
907 self.frames.push(CallFrame {
908 chunk,
909 ip: 0,
910 stack_base,
911 upvalues,
912 });
913 }
914 } else if func.is_higher_order_builtin() {
915 let hob = func.as_higher_order_builtin().unwrap().clone();
916 let forced_arg = self.force_value(arg)?;
917 let result = self.call_higher_order_builtin(&hob, forced_arg)?;
918 self.push(result);
919 } else if let Some(builtin) = func.as_builtin() {
920 if builtin.name == "tryEval" {
921 if let Some(result) = self.try_vm_builtin("tryEval", &arg)? {
922 self.push(result);
923 }
924 } else {
925 let forced_arg = self.force_value(arg)?;
926 if let Some(result) = self.try_vm_builtin(builtin.name, &forced_arg)? {
927 self.push(result);
928 } else {
929 let mut arg_vmval = forced_arg.to_vmvalue();
930 arg_vmval = self.shallow_force_list(arg_vmval)?;
931 let builtin_func = builtin.func.clone();
932 let result = self.call_builtin_with_scoped_import_dispatch(
933 builtin_func, arg_vmval,
934 )?;
935 self.push(result);
936 }
937 }
938 } else {
939 return Err(VMError::NotCallable(func.type_name().to_string()));
940 }
941 }
942 _ => unreachable!(),
943 }
944 Ok(())
945 }
946 fn dispatch_thunk(&mut self, op: OpCode) -> Result<(), VMError> {
947 match op {
948 OpCode::MakeThunk => {
949 let chunk_idx = self.read_u16()?;
950 let upvalue_count = self.read_u16()? as usize;
951 let thunk_chunk =
952 match &self.current_chunk().constants[chunk_idx as usize] {
953 VMValue::Closure(c) => c.chunk.clone(),
954 _ => {
955 return Err(VMError::Internal(
956 "MakeThunk: constant is not a closure".to_string(),
957 ))
958 }
959 };
960 let mut upvalues = Vec::with_capacity(upvalue_count);
961 for _ in 0..upvalue_count {
962 let is_local = self.read_byte()? != 0;
963 let uv_index = self.read_u16()? as usize;
964 if is_local {
965 let abs_slot = self.current_frame().stack_base + uv_index;
966 upvalues.push(self.stack[abs_slot].clone());
967 } else {
968 let val = self.current_frame().upvalues[uv_index].clone();
969 upvalues.push(val);
970 }
971 }
972 let thunk = crate::value::VMThunk::new(thunk_chunk, upvalues);
973 self.push(NanBox::thunk(thunk));
974 }
975 OpCode::Force => {
976 let val = self.pop()?;
977 let forced = self.force_value(val)?;
978 self.push(forced);
979 }
980 OpCode::PatchThunkUpvalues => {
981 let patch_slot = self.read_u16()? as usize;
982 let patch_uv_count = self.read_u16()? as usize;
983 let patch_abs = self.current_frame().stack_base + patch_slot;
984 let mut patch_uvs: Vec<NanBox> = Vec::with_capacity(patch_uv_count);
985 for _ in 0..patch_uv_count {
986 let il = self.read_byte()? != 0;
987 let ui = self.read_u16()? as usize;
988 if il {
989 let a = self.current_frame().stack_base + ui;
990 if a >= self.stack.len() {
991 patch_uvs.push(NanBox::null());
993 continue;
994 }
995 patch_uvs.push(self.stack[a].clone());
996 } else {
997 if ui >= self.current_frame().upvalues.len() {
998 patch_uvs.push(NanBox::null());
999 continue;
1000 }
1001 patch_uvs.push(self.current_frame().upvalues[ui].clone());
1002 }
1003 }
1004 if patch_abs < self.stack.len() {
1005 let patch_nb = self.stack[patch_abs].clone();
1006 let patch_vm = patch_nb.to_vmvalue();
1007 if let VMValue::Thunk(ref t) = patch_vm {
1008 let s = t.state.take();
1009 if let Some(ThunkState::Pending { chunk: c, .. }) = s {
1010 t.state.set(Some(ThunkState::Pending { chunk: c, upvalues: patch_uvs }));
1011 } else {
1012 t.state.set(s);
1013 }
1014 }
1015 }
1016 }
1017 OpCode::MakeLazyThunk => {
1018 let src_idx = self.read_u16()? as usize;
1019 let offset = self.read_u32()? as usize;
1020 let length = self.read_u32()? as usize;
1021 let dir_idx = self.read_u16()? as usize;
1022 let upvalue_count = self.read_u16()? as usize;
1023 let source_text = match &self.current_chunk().constants[src_idx] {
1024 VMValue::String(s) => Rc::new(s.clone()),
1025 _ => return Err(VMError::Internal(
1026 "MakeLazyThunk: source constant not a string".to_string(),
1027 )),
1028 };
1029 let base_dir_str = match &self.current_chunk().constants[dir_idx] {
1030 VMValue::String(s) => s.clone(),
1031 _ => return Err(VMError::Internal(
1032 "MakeLazyThunk: base_dir constant not a string".to_string(),
1033 )),
1034 };
1035 let base_dir = PathBuf::from(base_dir_str);
1036 let mut upvalues = Vec::with_capacity(upvalue_count);
1037 for _ in 0..upvalue_count {
1038 let is_local = self.read_byte()? != 0;
1039 let uv_index = self.read_u16()? as usize;
1040 if is_local {
1041 let abs_slot = self.current_frame().stack_base + uv_index;
1042 upvalues.push(self.stack[abs_slot].clone());
1043 } else {
1044 let val = self.current_frame().upvalues[uv_index].clone();
1045 upvalues.push(val);
1046 }
1047 }
1048 let thunk = crate::value::VMThunk {
1049 state: Rc::new(std::cell::Cell::new(Some(ThunkState::LazySource {
1050 source: source_text,
1051 offset,
1052 length,
1053 base_dir,
1054 upvalues,
1055 }))),
1056 };
1057 self.push(NanBox::thunk(thunk));
1058 }
1059 _ => unreachable!(),
1060 }
1061 Ok(())
1062 }
1063 fn dispatch_import(&mut self, op: OpCode) -> Result<(), VMError> {
1064 match op {
1065 OpCode::Import => {
1066 let path_val = self.pop()?;
1067 let path_val = self.force_value(path_val)?; let path = if let Some(p) = path_val.as_path() {
1069 p.to_string()
1070 } else if let Some(s) = path_val.as_string() {
1071 s.to_string()
1072 } else {
1073 return Err(VMError::TypeError {
1074 expected: "path or string",
1075 got: path_val.type_name(),
1076 context: "import".to_string(),
1077 });
1078 };
1079 let result = self.import_file(&path)?;
1080 self.push(result);
1081 }
1082 OpCode::CallBuiltin => {
1083 let builtin_idx = self.read_u16()?;
1084 let arg_count = self.read_u16()? as usize;
1085 let start = self.stack.len() - arg_count;
1086 let raw_args: Vec<NanBox> = self.stack.drain(start..).collect();
1087 let mut args = Vec::with_capacity(raw_args.len());
1088 for raw in raw_args {
1089 let forced = self.force_value(raw)?;
1090 let mut vm_val = forced.to_vmvalue();
1091 vm_val = self.shallow_force_list(vm_val)?;
1092 args.push(vm_val);
1093 }
1094 let result = self.builtins.call(builtin_idx, args)?;
1095 self.push(NanBox::from_vmvalue(&result));
1096 }
1097 _ => unreachable!(),
1098 }
1099 Ok(())
1100 }
1101 fn dispatch_super(&mut self, op: OpCode) -> Result<(), VMError> {
1102 match op {
1103 OpCode::GetLocalAttr => {
1104 let slot = self.read_u16()? as usize;
1105 let key_idx = self.read_u16()?;
1106 let key_sym = self.resolve_key_constant(key_idx)?;
1107 let abs_slot = self.current_frame().stack_base + slot;
1108 let local = self.stack[abs_slot].clone();
1109 let local = self.force_value(local)?;
1110 if let Some(attrs) = local.as_attrs() {
1111 if let Some(val) = attrs.get(&key_sym) {
1112 let forced = if val.is_thunk() {
1113 self.force_value(val.clone())?
1114 } else {
1115 val.clone()
1116 };
1117 self.push(forced);
1118 } else {
1119 let key_str = self.interner.resolve(key_sym).to_string();
1120 return Err(VMError::AttrNotFound(key_str));
1121 }
1122 } else {
1123 let key_str = self.interner.resolve(key_sym).to_string();
1124 return Err(VMError::TypeError {
1125 expected: "set",
1126 got: local.type_name(),
1127 context: format!("attribute selection '.{key_str}'"),
1128 });
1129 }
1130 }
1131 OpCode::GetLocalCall => {
1132 let slot = self.read_u16()? as usize;
1133 let abs_slot = self.current_frame().stack_base + slot;
1134 let func = self.stack[abs_slot].clone();
1135 let func = self.force_value(func)?;
1136 let arg = self.pop()?;
1137 if let Some(closure) = func.as_closure() {
1138 if self.frames.len() >= MAX_CALL_DEPTH {
1139 return Err(VMError::StackOverflow);
1140 }
1141 let upvalues = closure.upvalues.clone();
1142 let chunk = closure.chunk.clone();
1143 let stack_base = self.stack.len();
1144 self.push(arg);
1145 self.frames.push(CallFrame {
1146 chunk,
1147 ip: 0,
1148 stack_base,
1149 upvalues,
1150 });
1151 } else if func.is_higher_order_builtin() {
1152 let hob = func.as_higher_order_builtin().unwrap().clone();
1153 let forced_arg = self.force_value(arg)?;
1156 let result = self.call_higher_order_builtin(&hob, forced_arg)?;
1157 self.push(result);
1158 } else if let Some(builtin) = func.as_builtin() {
1159 let forced_arg = self.force_value(arg)?;
1162 if let Some(result) = self.try_vm_builtin(builtin.name, &forced_arg)? {
1163 self.push(result);
1164 } else {
1165 let mut arg_vmval = forced_arg.to_vmvalue();
1171 arg_vmval = self.shallow_force_list(arg_vmval)?;
1173 let builtin_func = builtin.func.clone();
1174 let result = self.call_builtin_with_scoped_import_dispatch(
1175 builtin_func, arg_vmval,
1176 )?;
1177 self.push(result);
1178 }
1179 } else {
1180 return Err(VMError::NotCallable(func.type_name().to_string()));
1181 }
1182 }
1183 _ => unreachable!(),
1184 }
1185 Ok(())
1186 }
1187 fn dispatch_stack(&mut self, op: OpCode) -> Result<(), VMError> {
1188 match op {
1189 OpCode::Pop => {
1190 self.pop()?;
1191 }
1192 OpCode::Dup => {
1193 let top = self.stack.last().ok_or(VMError::StackUnderflow)?.clone();
1194 self.push(top);
1195 }
1196 OpCode::Interpolate => {
1197 let count = self.read_u16()? as usize;
1198 let start = self.stack.len() - count;
1199 let mut parts: Vec<NanBox> = self.stack.drain(start..).collect();
1201 for part in &mut parts {
1202 if part.is_thunk() {
1203 *part = self.force_value(part.clone())?;
1204 }
1205 }
1206 let mut result = String::new();
1207 for v in &parts {
1208 if let Some(s) = v.as_string() {
1209 result.push_str(s);
1210 } else if let Some(n) = v.as_int() {
1211 result.push_str(&n.to_string());
1212 } else if let Some(f) = v.as_float() {
1213 result.push_str(&format!("{f}"));
1214 } else if let Some(p) = v.as_path() {
1215 result.push_str(p);
1216 } else if let Some(attrs) = v.as_attrs() {
1217 let to_str_sym = sui_intern::intern("__toString");
1219 if let Some(to_str_fn) = attrs.get(&to_str_sym) {
1220 let func_nb = self.force_value(to_str_fn.clone())?;
1221 let call_result = self.call_callable(&func_nb, v.clone())?;
1222 let forced = self.force_value(call_result)?;
1223 if let Some(s) = forced.as_string() {
1224 result.push_str(s);
1225 } else {
1226 return Err(VMError::TypeError {
1227 expected: "string",
1228 got: forced.type_name(),
1229 context: "__toString result in string interpolation".to_string(),
1230 });
1231 }
1232 } else {
1233 let out_path_sym = sui_intern::intern("outPath");
1234 if let Some(out_path) = attrs.get(&out_path_sym) {
1235 let forced = self.force_value(out_path.clone())?;
1236 if let Some(s) = forced.as_string() {
1237 result.push_str(s);
1238 } else if let Some(p) = forced.as_path() {
1239 result.push_str(p);
1240 } else {
1241 return Err(VMError::TypeError {
1242 expected: "string or path",
1243 got: forced.type_name(),
1244 context: "outPath in string interpolation".to_string(),
1245 });
1246 }
1247 } else {
1248 return Err(VMError::TypeError {
1249 expected: "string, int, float, or path",
1250 got: "set (no __toString or outPath)",
1251 context: "string interpolation".to_string(),
1252 });
1253 }
1254 }
1255 } else if v.is_bool() {
1256 let b = v.as_bool().unwrap();
1257 return Err(VMError::TypeError {
1258 expected: "string, int, float, or path",
1259 got: if b { "bool (true)" } else { "bool (false)" },
1260 context: "string interpolation".to_string(),
1261 });
1262 } else {
1263 return Err(VMError::TypeError {
1264 expected: "string, int, float, or path",
1265 got: v.type_name(),
1266 context: "string interpolation".to_string(),
1267 });
1268 }
1269 }
1270 self.push(NanBox::string(result));
1272 }
1273 _ => unreachable!(),
1274 }
1275 Ok(())
1276 }
1277 fn deep_eq(&mut self, a: &NanBox, b: &NanBox) -> Result<bool, VMError> {
1285 let a = if a.is_thunk() { self.force_value(a.clone())? } else { a.clone() };
1287 let b = if b.is_thunk() { self.force_value(b.clone())? } else { b.clone() };
1288 if a.is_null() || a.is_bool() || a.is_int() || a.is_float() {
1290 return Ok(a == b);
1291 }
1292 if a.is_string() || a.is_path() {
1293 return Ok(a == b);
1294 }
1295 if let (Some(a_items), Some(b_items)) = (a.as_list(), b.as_list()) {
1297 if a_items.len() != b_items.len() {
1298 return Ok(false);
1299 }
1300 for (ai, bi) in a_items.iter().zip(b_items.iter()) {
1301 if !self.deep_eq(ai, bi)? {
1302 return Ok(false);
1303 }
1304 }
1305 return Ok(true);
1306 }
1307 if let (Some(a_attrs), Some(b_attrs)) = (a.as_attrs(), b.as_attrs()) {
1309 if a_attrs.len() != b_attrs.len() {
1310 return Ok(false);
1311 }
1312 let a_entries: Vec<_> = a_attrs.iter().collect();
1314 let b_entries: Vec<_> = b_attrs.iter().collect();
1315 for ((ak, av), (bk, bv)) in a_entries.iter().zip(b_entries.iter()) {
1316 if ak != bk {
1317 return Ok(false);
1318 }
1319 if !self.deep_eq(av, bv)? {
1320 return Ok(false);
1321 }
1322 }
1323 return Ok(true);
1324 }
1325 if a.is_closure() || a.is_builtin() || a.is_higher_order_builtin() {
1327 return Ok(false);
1328 }
1329 Ok(a == b)
1331 }
1332 fn push(&mut self, value: NanBox) {
1334 self.stack.push(value);
1335 }
1336 fn pop(&mut self) -> Result<NanBox, VMError> {
1337 self.stack.pop().ok_or(VMError::StackUnderflow)
1338 }
1339 fn pop_forced(&mut self) -> Result<NanBox, VMError> {
1342 let val = self.pop()?;
1343 self.force_value(val)
1344 }
1345 fn peek(&self) -> Result<&NanBox, VMError> {
1346 self.stack.last().ok_or(VMError::StackUnderflow)
1347 }
1348 fn current_frame(&self) -> &CallFrame {
1350 self.frames.last().expect("no active frame")
1351 }
1352 fn current_frame_mut(&mut self) -> &mut CallFrame {
1353 self.frames.last_mut().expect("no active frame")
1354 }
1355 fn current_chunk(&self) -> &Chunk {
1356 &self.current_frame().chunk
1357 }
1358 fn current_chunk_name(&self) -> String {
1359 self.current_chunk()
1360 .source_file
1361 .clone()
1362 .unwrap_or_else(|| "<inline>".to_string())
1363 }
1364 fn read_byte(&mut self) -> Result<u8, VMError> {
1365 let frame = self.current_frame();
1366 if frame.ip >= frame.chunk.code.len() {
1367 return Err(VMError::Internal("unexpected end of bytecode".to_string()));
1368 }
1369 let byte = frame.chunk.code[frame.ip];
1370 self.current_frame_mut().ip += 1;
1371 Ok(byte)
1372 }
1373 fn read_u16(&mut self) -> Result<u16, VMError> {
1374 let lo = self.read_byte()?;
1375 let hi = self.read_byte()?;
1376 Ok(u16::from_le_bytes([lo, hi]))
1377 }
1378 fn read_u32(&mut self) -> Result<u32, VMError> {
1379 let b0 = self.read_byte()?;
1380 let b1 = self.read_byte()?;
1381 let b2 = self.read_byte()?;
1382 let b3 = self.read_byte()?;
1383 Ok(u32::from_le_bytes([b0, b1, b2, b3]))
1384 }
1385 fn peek_next_is_return(&self) -> bool {
1388 let frame = self.current_frame();
1389 if frame.ip < frame.chunk.code.len() {
1390 frame.chunk.code[frame.ip] == OpCode::Return as u8
1391 } else {
1392 false
1393 }
1394 }
1395 fn resolve_key_constant(&mut self, idx: u16) -> Result<Symbol, VMError> {
1398 let idx_usize = idx as usize;
1399 let chunk = self.current_frame().chunk.clone();
1400 if let Some(Some(sym)) = chunk.key_symbols.get(idx_usize) {
1401 return Ok(*sym);
1402 }
1403 let key_string = match &chunk.constants[idx_usize] {
1404 VMValue::String(s) => s.clone(),
1405 _ => return Err(VMError::Internal("attr key constant not a string".to_string())),
1406 };
1407 Ok(self.interner.intern(&key_string))
1408 }
1409 fn add(&self, a: &NanBox, b: &NanBox) -> Result<NanBox, VMError> {
1411 if let (Some(x), Some(y)) = (a.as_int(), b.as_int()) {
1413 return Ok(NanBox::int(x + y));
1414 }
1415 if let (Some(x), Some(y)) = (a.as_float(), b.as_float()) {
1416 return Ok(NanBox::float(x + y));
1417 }
1418 if let (Some(x), Some(y)) = (a.as_int(), b.as_float()) {
1419 return Ok(NanBox::float(x as f64 + y));
1420 }
1421 if let (Some(x), Some(y)) = (a.as_float(), b.as_int()) {
1422 return Ok(NanBox::float(x + y as f64));
1423 }
1424 if let (Some(x), Some(y)) = (a.as_string(), b.as_string()) {
1426 return Ok(NanBox::string(format!("{x}{y}")));
1427 }
1428 if let (Some(x), Some(y)) = (a.as_path(), b.as_string()) {
1429 return Ok(NanBox::path(format!("{x}{y}")));
1430 }
1431 if let (Some(x), Some(y)) = (a.as_path(), b.as_path()) {
1432 return Ok(NanBox::path(format!("{x}/{y}")));
1433 }
1434 Err(VMError::TypeError {
1435 expected: "numbers or strings",
1436 got: a.type_name(),
1437 context: format!("addition ({} + {})", a.type_name(), b.type_name()),
1438 })
1439 }
1440 fn num_op(
1441 &self,
1442 a: &NanBox,
1443 b: &NanBox,
1444 int_op: impl Fn(i64, i64) -> i64,
1445 float_op: impl Fn(f64, f64) -> f64,
1446 context: &str,
1447 ) -> Result<NanBox, VMError> {
1448 if let (Some(x), Some(y)) = (a.as_int(), b.as_int()) {
1449 return Ok(NanBox::int(int_op(x, y)));
1450 }
1451 if let (Some(x), Some(y)) = (a.as_float(), b.as_float()) {
1452 return Ok(NanBox::float(float_op(x, y)));
1453 }
1454 if let (Some(x), Some(y)) = (a.as_int(), b.as_float()) {
1455 return Ok(NanBox::float(float_op(x as f64, y)));
1456 }
1457 if let (Some(x), Some(y)) = (a.as_float(), b.as_int()) {
1458 return Ok(NanBox::float(float_op(x, y as f64)));
1459 }
1460 Err(VMError::TypeError {
1461 expected: "numbers",
1462 got: a.type_name(),
1463 context: context.to_string(),
1464 })
1465 }
1466 fn compare(&self, a: &NanBox, b: &NanBox) -> Result<std::cmp::Ordering, VMError> {
1467 if let (Some(x), Some(y)) = (a.as_int(), b.as_int()) {
1468 return Ok(x.cmp(&y));
1469 }
1470 if let (Some(x), Some(y)) = (a.as_float(), b.as_float()) {
1471 return Ok(x.partial_cmp(&y).unwrap_or(std::cmp::Ordering::Equal));
1472 }
1473 if let (Some(x), Some(y)) = (a.as_int(), b.as_float()) {
1474 return Ok((x as f64)
1475 .partial_cmp(&y)
1476 .unwrap_or(std::cmp::Ordering::Equal));
1477 }
1478 if let (Some(x), Some(y)) = (a.as_float(), b.as_int()) {
1479 return Ok(x
1480 .partial_cmp(&(y as f64))
1481 .unwrap_or(std::cmp::Ordering::Equal));
1482 }
1483 if let (Some(x), Some(y)) = (a.as_string(), b.as_string()) {
1484 return Ok(x.cmp(y));
1485 }
1486 Err(VMError::TypeError {
1487 expected: "comparable types",
1488 got: a.type_name(),
1489 context: "comparison".to_string(),
1490 })
1491 }
1492 fn json_value_to_vm(&mut self, v: &serde_json::Value) -> VMValue {
1500 use std::collections::BTreeMap;
1501 match v {
1502 serde_json::Value::Null => VMValue::Null,
1503 serde_json::Value::Bool(b) => VMValue::Bool(*b),
1504 serde_json::Value::Number(n) => {
1505 if let Some(i) = n.as_i64() {
1506 VMValue::Int(i)
1507 } else {
1508 VMValue::Float(n.as_f64().unwrap_or(0.0))
1509 }
1510 }
1511 serde_json::Value::String(s) => VMValue::String(s.clone()),
1512 serde_json::Value::Array(arr) => {
1513 VMValue::List(arr.iter().map(|v| self.json_value_to_vm(v)).collect())
1514 }
1515 serde_json::Value::Object(map) => {
1516 let mut attrs: BTreeMap<Symbol, VMValue> = BTreeMap::new();
1517 for (k, val) in map {
1518 let sym = self.interner.intern(k);
1519 attrs.insert(sym, self.json_value_to_vm(val));
1520 }
1521 VMValue::Attrs(attrs)
1522 }
1523 }
1524 }
1525
1526 fn json_value_to_nanbox(&mut self, v: &serde_json::Value) -> NanBox {
1528 NanBox::from_vmvalue(&self.json_value_to_vm(v))
1529 }
1530
1531 fn force_value(&mut self, val: NanBox) -> Result<NanBox, VMError> {
1532 if !val.is_thunk() {
1533 return Ok(val);
1534 }
1535 let vmval = val.to_vmvalue();
1537 match vmval {
1538 VMValue::Thunk(ref thunk) => {
1539 let state = thunk.state.take();
1540 match state {
1541 Some(ThunkState::Done(boxed)) => {
1542 thunk.state.set(Some(ThunkState::Done(boxed.clone())));
1543 Ok(NanBox::from_vmvalue(&*boxed))
1544 }
1545 Some(ThunkState::Evaluating) => {
1546 thunk.state.set(Some(ThunkState::Evaluating));
1561 if std::env::var("SUI_VM_TRACE").is_ok() {
1562 eprintln!(
1563 "[sui-vm] fixpoint re-access at depth {}, returning placeholder",
1564 self.frames.len(),
1565 );
1566 }
1567 Ok(NanBox::attrs(BTreeMap::new()))
1568 }
1569 Some(ThunkState::Pending { chunk, upvalues }) => {
1570 thunk.state.set(Some(ThunkState::Evaluating));
1571 if self.frames.len() >= MAX_CALL_DEPTH {
1572 thunk.state.set(Some(ThunkState::Pending {
1573 chunk,
1574 upvalues,
1575 }));
1576 return Err(VMError::StackOverflow);
1577 }
1578 let return_depth = self.frames.len();
1579 let stack_base = self.stack.len();
1580 let frame_upvalues: Vec<NanBox> = upvalues.clone();
1584 let upvalues_for_restore = upvalues;
1585 self.frames.push(CallFrame {
1586 chunk: chunk.clone(),
1587 ip: 0,
1588 stack_base,
1589 upvalues: frame_upvalues,
1590 });
1591 let result = self.run_until(return_depth);
1592 self.stack.truncate(stack_base);
1596 match result {
1597 Ok(value) => {
1598 let partial_vmval = value.to_vmvalue();
1605 thunk.state.set(Some(ThunkState::Done(
1606 Box::new(partial_vmval),
1607 )));
1608 let mut forced = value;
1610 let mut depth = 0u32;
1611 while forced.is_thunk() {
1612 depth += 1;
1613 if depth > MAX_THUNK_CHAIN_DEPTH {
1614 if std::env::var("SUI_VM_TRACE").is_ok() {
1615 eprintln!("[sui-vm] thunk chain depth {} exceeded at chunk: {}", depth, self.current_chunk_name());
1616 }
1617 return Err(VMError::InfiniteRecursion);
1618 }
1619 forced = self.force_value(forced)?;
1620 }
1621 let forced_vmval = forced.to_vmvalue();
1623 thunk.state.set(Some(ThunkState::Done(
1624 Box::new(forced_vmval),
1625 )));
1626 Ok(forced)
1627 }
1628 Err(e) => {
1629 thunk.state.set(Some(ThunkState::Pending {
1630 chunk,
1631 upvalues: upvalues_for_restore,
1632 }));
1633 Err(e)
1634 }
1635 }
1636 }
1637 Some(ThunkState::LazySource { source, offset, length, base_dir, upvalues }) => {
1638 thunk.state.set(Some(ThunkState::Evaluating));
1639 let expr_text = &source[offset..offset + length];
1641 let shared_interner = Rc::new(RefCell::new(std::mem::take(self.interner)));
1642 let compiled = Compiler::compile_expression(
1643 expr_text,
1644 &base_dir,
1645 shared_interner.clone(),
1646 ).map_err(|e| {
1647 *self.interner = match Rc::try_unwrap(shared_interner.clone()) {
1649 Ok(cell) => cell.into_inner(),
1650 Err(rc) => rc.borrow().clone(),
1651 };
1652 thunk.state.set(Some(ThunkState::LazySource {
1653 source: source.clone(),
1654 offset,
1655 length,
1656 base_dir: base_dir.clone(),
1657 upvalues: upvalues.clone(),
1658 }));
1659 VMError::ImportError(format!("lazy thunk compile: {e}"))
1660 })?;
1661 *self.interner = match Rc::try_unwrap(shared_interner) {
1662 Ok(cell) => cell.into_inner(),
1663 Err(rc) => rc.borrow().clone(),
1664 };
1665 let chunk = Rc::new(compiled);
1666 if self.frames.len() >= MAX_CALL_DEPTH {
1667 thunk.state.set(Some(ThunkState::LazySource {
1668 source, offset, length, base_dir, upvalues,
1669 }));
1670 return Err(VMError::StackOverflow);
1671 }
1672 let return_depth = self.frames.len();
1673 let stack_base = self.stack.len();
1674 let frame_upvalues: Vec<NanBox> = upvalues.clone();
1677 self.frames.push(CallFrame {
1678 chunk: chunk.clone(),
1679 ip: 0,
1680 stack_base,
1681 upvalues: frame_upvalues,
1682 });
1683 let result = self.run_until(return_depth);
1684 self.stack.truncate(stack_base);
1685 match result {
1686 Ok(value) => {
1687 let partial_vmval = value.to_vmvalue();
1689 thunk.state.set(Some(ThunkState::Done(
1690 Box::new(partial_vmval),
1691 )));
1692 let mut forced = value;
1693 let mut depth = 0u32;
1694 while forced.is_thunk() {
1695 depth += 1;
1696 if depth > MAX_THUNK_CHAIN_DEPTH {
1697 if std::env::var("SUI_VM_TRACE").is_ok() {
1698 eprintln!("[sui-vm] thunk chain depth {} exceeded at chunk: {}", depth, self.current_chunk_name());
1699 }
1700 return Err(VMError::InfiniteRecursion);
1701 }
1702 forced = self.force_value(forced)?;
1703 }
1704 let forced_vmval = forced.to_vmvalue();
1705 thunk.state.set(Some(ThunkState::Done(
1706 Box::new(forced_vmval),
1707 )));
1708 Ok(forced)
1709 }
1710 Err(e) => {
1711 thunk.state.set(Some(ThunkState::Pending {
1712 chunk,
1713 upvalues,
1714 }));
1715 Err(e)
1716 }
1717 }
1718 }
1719 Some(ThunkState::NativeCallback(cb)) => {
1720 thunk.state.set(Some(ThunkState::Evaluating));
1721 match cb() {
1722 Ok(sk_val) => {
1723 let nb = self.string_keyed_to_nanbox(&sk_val);
1724 let partial_vmval = nb.to_vmvalue();
1726 thunk.state.set(Some(ThunkState::Done(
1727 Box::new(partial_vmval),
1728 )));
1729 let mut forced = nb;
1730 let mut depth = 0u32;
1731 while forced.is_thunk() {
1732 depth += 1;
1733 if depth > MAX_THUNK_CHAIN_DEPTH {
1734 if std::env::var("SUI_VM_TRACE").is_ok() {
1735 eprintln!("[sui-vm] thunk chain depth {} exceeded at chunk: {}", depth, self.current_chunk_name());
1736 }
1737 return Err(VMError::InfiniteRecursion);
1738 }
1739 forced = self.force_value(forced)?;
1740 }
1741 let forced_vmval = forced.to_vmvalue();
1742 thunk.state.set(Some(ThunkState::Done(
1743 Box::new(forced_vmval),
1744 )));
1745 Ok(forced)
1746 }
1747 Err(e) => {
1748 thunk.state.set(Some(ThunkState::NativeCallback(cb)));
1750 Err(VMError::Throw(format!("native thunk: {e}")))
1751 }
1752 }
1753 }
1754 None => Err(VMError::Internal("thunk state is None".to_string())),
1755 }
1756 }
1757 _ => Ok(NanBox::from_vmvalue(&vmval)),
1758 }
1759 }
1760 fn shallow_force_list(&mut self, val: VMValue) -> Result<VMValue, VMError> {
1766 match val {
1767 VMValue::List(items) => {
1768 let mut forced_items = Vec::with_capacity(items.len());
1769 for item in items {
1770 let nb = NanBox::from_vmvalue(&item);
1771 if nb.is_thunk() {
1772 let forced = self.force_value(nb)?;
1773 forced_items.push(forced.to_vmvalue());
1774 } else {
1775 forced_items.push(item);
1776 }
1777 }
1778 Ok(VMValue::List(forced_items))
1779 }
1780 other => Ok(other),
1784 }
1785 }
1786 fn deep_force(&mut self, val: NanBox) -> Result<NanBox, VMError> {
1789 let forced = self.force_value(val)?;
1790 if let Some(attrs) = forced.as_attrs() {
1791 let mut new_attrs: BTreeMap<Symbol, NanBox> = BTreeMap::new();
1792 for (k, v) in attrs {
1793 let forced_v = self.deep_force(v.clone())?;
1794 new_attrs.insert(*k, forced_v);
1795 }
1796 Ok(NanBox::attrs(new_attrs))
1797 } else if forced.is_list() {
1798 let vmval = forced.to_vmvalue();
1799 if let VMValue::List(items) = vmval {
1800 let mut new_items = Vec::with_capacity(items.len());
1801 for item in &items {
1802 let item_nb = NanBox::from_vmvalue(item);
1803 let forced_item = self.deep_force(item_nb)?;
1804 new_items.push(forced_item);
1805 }
1806 Ok(NanBox::list(new_items))
1807 } else {
1808 Ok(forced)
1809 }
1810 } else {
1811 Ok(forced)
1812 }
1813 }
1814 fn try_vm_builtin(
1820 &mut self,
1821 name: &str,
1822 arg: &NanBox,
1823 ) -> Result<Option<NanBox>, VMError> {
1824 match name {
1825 "tryEval" => {
1826
1827 let success_sym = self.interner.intern("success");
1831 let value_sym = self.interner.intern("value");
1832 match self.force_value(arg.clone()) {
1833 Ok(forced) => {
1834 let mut attrs = BTreeMap::new();
1835 attrs.insert(success_sym, NanBox::bool(true));
1836 attrs.insert(value_sym, forced);
1837 Ok(Some(NanBox::attrs(attrs)))
1838 }
1839 Err(_) => {
1840 let mut attrs = BTreeMap::new();
1841 attrs.insert(success_sym, NanBox::bool(false));
1842 attrs.insert(value_sym, NanBox::bool(false));
1843 Ok(Some(NanBox::attrs(attrs)))
1844 }
1845 }
1846 }
1847 "derivation" | "derivationStrict" => {
1848 let forced = self.force_value(arg.clone())?;
1849 let result = self.vm_build_derivation(forced)?;
1850 Ok(Some(result))
1851 }
1852 "import" => {
1853 let forced = self.force_value(arg.clone())?;
1855 let path = if let Some(p) = forced.as_path() {
1856 p.to_string()
1857 } else if let Some(s) = forced.as_string() {
1858 s.to_string()
1859 } else {
1860 return Err(VMError::TypeError {
1861 expected: "path or string",
1862 got: forced.type_name(),
1863 context: "import".to_string(),
1864 });
1865 };
1866 let result = self.import_file(&path)?;
1867 Ok(Some(result))
1868 }
1869 "attrNames" => {
1870 let forced = self.force_value(arg.clone())?;
1871 if let Some(attrs) = forced.as_attrs() {
1872 let mut name_strs: Vec<String> = attrs
1874 .keys()
1875 .map(|k| self.interner.resolve(*k).to_string())
1876 .collect();
1877 name_strs.sort();
1878 let names: Vec<NanBox> = name_strs
1879 .into_iter()
1880 .map(NanBox::string)
1881 .collect();
1882 Ok(Some(NanBox::list(names)))
1883 } else {
1884 Err(VMError::TypeError {
1885 expected: "set",
1886 got: forced.type_name(),
1887 context: "attrNames".to_string(),
1888 })
1889 }
1890 }
1891 "attrValues" => {
1892 let forced = self.force_value(arg.clone())?;
1898 if let Some(attrs) = forced.as_attrs() {
1899 let mut pairs: Vec<(String, &NanBox)> = attrs
1900 .iter()
1901 .map(|(k, v)| (self.interner.resolve(*k).to_string(), v))
1902 .collect();
1903 pairs.sort_by(|(a, _), (b, _)| a.cmp(b));
1904 let values: Vec<NanBox> =
1905 pairs.into_iter().map(|(_, v)| v.clone()).collect();
1906 Ok(Some(NanBox::list(values)))
1907 } else {
1908 Err(VMError::TypeError {
1909 expected: "set",
1910 got: forced.type_name(),
1911 context: "attrValues".to_string(),
1912 })
1913 }
1914 }
1915 "functionArgs" => {
1916 let forced = self.force_value(arg.clone())?;
1926 let vmval = forced.to_vmvalue();
1927 match vmval {
1928 VMValue::Closure(closure) => {
1929 let mut result = std::collections::BTreeMap::new();
1930 for (name, has_default) in &closure.formals {
1931 let sym = self.interner.intern(name);
1932 result.insert(sym, VMValue::Bool(*has_default));
1933 }
1934 Ok(Some(NanBox::from_vmvalue(&VMValue::Attrs(result))))
1935 }
1936 VMValue::Builtin(_) | VMValue::HigherOrderBuiltin(_) => {
1937 Ok(Some(NanBox::from_vmvalue(&VMValue::Attrs(
1938 std::collections::BTreeMap::new(),
1939 ))))
1940 }
1941 other => Err(VMError::TypeError {
1942 expected: "lambda",
1943 got: other.type_name(),
1944 context: "functionArgs".to_string(),
1945 }),
1946 }
1947 }
1948 "fromJSON" => {
1949 let forced = self.force_value(arg.clone())?;
1959 let s = match forced.as_string() {
1960 Some(s) => s.to_string(),
1961 None => {
1962 return Err(VMError::TypeError {
1963 expected: "string",
1964 got: forced.type_name(),
1965 context: "fromJSON".to_string(),
1966 });
1967 }
1968 };
1969 let parsed: serde_json::Value = serde_json::from_str(&s)
1970 .map_err(|e| VMError::Throw(format!("fromJSON: {e}")))?;
1971 Ok(Some(self.json_value_to_nanbox(&parsed)))
1972 }
1973 "listToAttrs" => {
1974 let forced = self.force_value(arg.clone())?;
1975 let vmval = forced.to_vmvalue();
1976 let list = match &vmval {
1977 VMValue::List(l) => l,
1978 other => {
1979 return Err(VMError::TypeError {
1980 expected: "list",
1981 got: other.type_name(),
1982 context: "listToAttrs".to_string(),
1983 });
1984 }
1985 };
1986 let name_sym = self.interner.intern("name");
1987 let value_sym = self.interner.intern("value");
1988 let mut result: BTreeMap<Symbol, NanBox> = BTreeMap::new();
1989 for item in list {
1990 if let VMValue::Attrs(a) = item {
1991 let name_val = a.get(&name_sym).ok_or_else(|| {
1992 VMError::Throw(
1993 "listToAttrs: element missing 'name'".to_string(),
1994 )
1995 })?;
1996 let value_val = a.get(&value_sym).ok_or_else(|| {
1997 VMError::Throw(
1998 "listToAttrs: element missing 'value'".to_string(),
1999 )
2000 })?;
2001 let key_str = match name_val {
2002 VMValue::String(s) => s.clone(),
2003 _ => {
2004 return Err(VMError::TypeError {
2005 expected: "string",
2006 got: name_val.type_name(),
2007 context: "listToAttrs name".to_string(),
2008 });
2009 }
2010 };
2011 let key_sym = self.interner.intern(&key_str);
2012 result
2017 .entry(key_sym)
2018 .or_insert_with(|| NanBox::from_vmvalue(value_val));
2019 } else {
2020 return Err(VMError::TypeError {
2021 expected: "set",
2022 got: item.type_name(),
2023 context: "listToAttrs element".to_string(),
2024 });
2025 }
2026 }
2027 Ok(Some(NanBox::attrs(result)))
2028 }
2029 "removeAttrs" => {
2030 let forced = self.force_value(arg.clone())?;
2032 if let Some(attrs) = forced.as_attrs() {
2033 let attrs_vm: BTreeMap<Symbol, VMValue> = attrs
2035 .iter()
2036 .map(|(k, v)| (*k, v.to_vmvalue()))
2037 .collect();
2038 let interner_names: Vec<(Symbol, String)> = attrs
2039 .keys()
2040 .map(|k| (*k, self.interner.resolve(*k).to_string()))
2041 .collect();
2042 let result = VMValue::Builtin(crate::value::VMBuiltin {
2043 name: "removeAttrs<partial>",
2044 func: Rc::new(move |args2| {
2045 let to_remove = match &args2[0] {
2046 VMValue::List(l) => l,
2047 other => {
2048 return Err(VMError::TypeError {
2049 expected: "list",
2050 got: other.type_name(),
2051 context: "removeAttrs".to_string(),
2052 });
2053 }
2054 };
2055 let remove_names: std::collections::HashSet<String> = to_remove
2056 .iter()
2057 .filter_map(|v| {
2058 if let VMValue::String(s) = v {
2059 Some(s.clone())
2060 } else {
2061 None
2062 }
2063 })
2064 .collect();
2065 let mut result = BTreeMap::new();
2066 for &(sym, ref name) in &interner_names {
2067 if !remove_names.contains(name) {
2068 if let Some(v) = attrs_vm.get(&sym) {
2069 result.insert(sym, v.clone());
2070 }
2071 }
2072 }
2073 Ok(VMValue::Attrs(result))
2074 }),
2075 arity: 1,
2076 });
2077 Ok(Some(NanBox::from_vmvalue(&result)))
2078 } else {
2079 Err(VMError::TypeError {
2080 expected: "set",
2081 got: forced.type_name(),
2082 context: "removeAttrs".to_string(),
2083 })
2084 }
2085 }
2086 "hasAttr" => {
2087 let forced = self.force_value(arg.clone())?;
2089 let name_str = match forced.to_vmvalue() {
2090 VMValue::String(s) => s,
2091 other => {
2092 return Err(VMError::TypeError {
2093 expected: "string",
2094 got: other.type_name(),
2095 context: "hasAttr".to_string(),
2096 });
2097 }
2098 };
2099 let sym = self.interner.intern(&name_str);
2100 Ok(Some(NanBox::from_vmvalue(&VMValue::Builtin(
2101 crate::value::VMBuiltin {
2102 name: "hasAttr<partial>",
2103 func: Rc::new(move |args2| {
2104 let attrs = match &args2[0] {
2105 VMValue::Attrs(a) => a,
2106 other => {
2107 return Err(VMError::TypeError {
2108 expected: "set",
2109 got: other.type_name(),
2110 context: "hasAttr".to_string(),
2111 });
2112 }
2113 };
2114 Ok(VMValue::Bool(attrs.contains_key(&sym)))
2115 }),
2116 arity: 1,
2117 },
2118 ))))
2119 }
2120 "getAttr" => {
2121 let forced = self.force_value(arg.clone())?;
2122 let name_str = match forced.to_vmvalue() {
2123 VMValue::String(s) => s,
2124 other => {
2125 return Err(VMError::TypeError {
2126 expected: "string",
2127 got: other.type_name(),
2128 context: "getAttr".to_string(),
2129 });
2130 }
2131 };
2132 let sym = self.interner.intern(&name_str);
2133 let name_for_err = name_str.clone();
2134 Ok(Some(NanBox::from_vmvalue(&VMValue::Builtin(
2135 crate::value::VMBuiltin {
2136 name: "getAttr<partial>",
2137 func: Rc::new(move |args2| {
2138 let attrs = match &args2[0] {
2139 VMValue::Attrs(a) => a,
2140 other => {
2141 return Err(VMError::TypeError {
2142 expected: "set",
2143 got: other.type_name(),
2144 context: "getAttr".to_string(),
2145 });
2146 }
2147 };
2148 attrs.get(&sym).cloned().ok_or_else(|| {
2149 VMError::AttrNotFound(name_for_err.clone())
2150 })
2151 }),
2152 arity: 1,
2153 },
2154 ))))
2155 }
2156 "getFlake" => {
2157 let forced = self.force_value(arg.clone())?;
2158 let flake_ref = match forced.to_vmvalue() {
2159 VMValue::String(s) => s,
2160 other => {
2161 return Err(VMError::TypeError {
2162 expected: "string",
2163 got: other.type_name(),
2164 context: "getFlake".to_string(),
2165 });
2166 }
2167 };
2168 let result = self.vm_get_flake(&flake_ref)?;
2169 Ok(Some(result))
2170 }
2171 "scopedImport" => {
2172 let forced = self.force_value(arg.clone())?;
2174 let scope_vmval = forced.to_vmvalue();
2175 match scope_vmval {
2176 VMValue::Attrs(_) => {}
2177 ref other => {
2178 return Err(VMError::TypeError {
2179 expected: "set",
2180 got: other.type_name(),
2181 context: "scopedImport".to_string(),
2182 });
2183 }
2184 }
2185 let scope_str = if let Some(attrs) = forced.as_attrs() {
2187 let mut parts = String::from("{");
2188 for (k, v) in attrs {
2189 let key = self.interner.resolve(*k).to_string();
2190 let val_vm = v.to_vmvalue();
2191 let rhs = match &val_vm {
2192 VMValue::Int(n) => n.to_string(),
2193 VMValue::Float(f) => format!("{f}"),
2194 VMValue::Bool(true) => "true".to_string(),
2195 VMValue::Bool(false) => "false".to_string(),
2196 VMValue::Null => "null".to_string(),
2197 VMValue::String(s) => {
2198 let escaped = s
2199 .replace('\\', "\\\\")
2200 .replace('"', "\\\"")
2201 .replace('$', "\\$");
2202 format!("\"{escaped}\"")
2203 }
2204 VMValue::Path(p) => format!("\"{p}\""),
2205 _ => {
2206 return Err(VMError::Throw(format!(
2207 "scopedImport: cannot render scope value of type {}",
2208 val_vm.type_name()
2209 )));
2210 }
2211 };
2212 parts.push_str(&format!(" {key} = {rhs};"));
2213 }
2214 parts.push_str(" }");
2215 parts
2216 } else {
2217 "{}".to_string()
2218 };
2219 let result = VMValue::Builtin(crate::value::VMBuiltin {
2221 name: "scopedImport<partial>",
2222 func: Rc::new(move |args2| {
2223 let path = match &args2[0] {
2224 VMValue::String(s) => s.clone(),
2225 VMValue::Path(p) => p.clone(),
2226 other => {
2227 return Err(VMError::TypeError {
2228 expected: "path or string",
2229 got: other.type_name(),
2230 context: "scopedImport".to_string(),
2231 });
2232 }
2233 };
2234 Err(VMError::Throw(format!(
2237 "__scopedImport_dispatch__:{}:{}",
2238 scope_str, path
2239 )))
2240 }),
2241 arity: 1,
2242 });
2243 Ok(Some(NanBox::from_vmvalue(&result)))
2244 }
2245 "scopedImport<partial>" => {
2246 let forced = self.force_value(arg.clone())?;
2248 let path = match forced.to_vmvalue() {
2249 VMValue::String(s) => s,
2250 VMValue::Path(p) => p,
2251 other => {
2252 return Err(VMError::TypeError {
2253 expected: "path or string",
2254 got: other.type_name(),
2255 context: "scopedImport".to_string(),
2256 });
2257 }
2258 };
2259 let _ = path;
2263 Ok(None)
2264 }
2265 "catAttrs" => {
2266 let forced = self.force_value(arg.clone())?;
2267 let name_str = match forced.to_vmvalue() {
2268 VMValue::String(s) => s,
2269 other => {
2270 return Err(VMError::TypeError {
2271 expected: "string",
2272 got: other.type_name(),
2273 context: "catAttrs".to_string(),
2274 });
2275 }
2276 };
2277 let sym = self.interner.intern(&name_str);
2278 Ok(Some(NanBox::from_vmvalue(&VMValue::Builtin(
2279 crate::value::VMBuiltin {
2280 name: "catAttrs<partial>",
2281 func: Rc::new(move |args2| {
2282 let list = match &args2[0] {
2283 VMValue::List(l) => l,
2284 other => {
2285 return Err(VMError::TypeError {
2286 expected: "list",
2287 got: other.type_name(),
2288 context: "catAttrs".to_string(),
2289 });
2290 }
2291 };
2292 let mut result = Vec::new();
2293 for item in list {
2294 if let VMValue::Attrs(a) = item {
2295 if let Some(v) = a.get(&sym) {
2296 result.push(v.clone());
2297 }
2298 }
2299 }
2300 Ok(VMValue::List(result))
2301 }),
2302 arity: 1,
2303 },
2304 ))))
2305 }
2306 "readDir" | "parseDrvName" | "fromTOML" | "genericClosure"
2312 | "zipAttrsWith" | "getContext" | "toXML"
2313 | "convertHash" | "path" | "filterSource" | "parseFlakeRef"
2314 | "flakeRefToString" | "toFile" | "currentTime" | "hashFile"
2315 | "findFile" => {
2316 let shallow = self.force_value(arg.clone())?;
2319 let forced = self.deep_force(shallow)?;
2320 let vmval = forced.to_vmvalue();
2321 let sk = vmval.to_string_keyed(self.interner);
2322 let _ = crate::fallback::record(crate::fallback::Layer::Builtin, name);
2330 match crate::bridge::call_builtin_bridge(name, vec![sk]) {
2331 Ok(Some(result)) => {
2332 let vm_result = crate::builtins::string_keyed_to_vmvalue(
2333 &result,
2334 self.interner,
2335 );
2336 Ok(Some(NanBox::from_vmvalue(&vm_result)))
2337 }
2338 Ok(None) => {
2339 Ok(None)
2342 }
2343 Err(e) => Err(VMError::Internal(format!("bridge error in '{name}': {e}"))),
2344 }
2345 }
2346 "match" | "split" => {
2349 let forced = self.force_value(arg.clone())?;
2350 let pattern = match forced.to_vmvalue() {
2351 VMValue::String(s) => s,
2352 other => {
2353 return Err(VMError::TypeError {
2354 expected: "string",
2355 got: other.type_name(),
2356 context: name.to_string(),
2357 });
2358 }
2359 };
2360 let builtin_name = name.to_string();
2361 Ok(Some(NanBox::from_vmvalue(&VMValue::Builtin(
2362 crate::value::VMBuiltin {
2363 name: if name == "match" {
2364 "match<partial>"
2365 } else {
2366 "split<partial>"
2367 },
2368 func: Rc::new(move |args2| {
2369 let input = match &args2[0] {
2370 VMValue::String(s) => s.clone(),
2371 other => {
2372 return Err(VMError::TypeError {
2373 expected: "string",
2374 got: other.type_name(),
2375 context: builtin_name.clone(),
2376 });
2377 }
2378 };
2379 let sk_args = vec![
2381 crate::value::StringKeyedValue::String(pattern.clone()),
2382 crate::value::StringKeyedValue::String(input),
2383 ];
2384 let _ = crate::fallback::record(
2388 crate::fallback::Layer::Builtin,
2389 &builtin_name,
2390 );
2391 match crate::bridge::call_builtin_bridge(&builtin_name, sk_args) {
2392 Ok(Some(result)) => {
2393 let mut tmp = crate::intern::Interner::new();
2394 Ok(crate::builtins::string_keyed_to_vmvalue(&result, &mut tmp))
2395 }
2396 Ok(None) => Err(VMError::Throw(format!(
2397 "{builtin_name}: requires bridge but no bridge is set"
2398 ))),
2399 Err(e) => Err(VMError::Internal(format!("bridge error in '{builtin_name}': {e}"))),
2400 }
2401 }),
2402 arity: 1,
2403 },
2404 ))))
2405 }
2406 _ => Ok(None),
2407 }
2408 }
2409 fn coerce_drv_env_value(&mut self, v: &VMValue) -> Option<String> {
2429 match v {
2430 VMValue::String(s) => Some(s.clone()),
2431 VMValue::Path(p) => Some(p.clone()),
2432 VMValue::Int(n) => Some(n.to_string()),
2433 VMValue::Float(f) => Some(format!("{f:.6}")),
2436 VMValue::Bool(true) => Some("1".to_string()),
2437 VMValue::Bool(false) => Some(String::new()),
2438 VMValue::Null => Some(String::new()),
2439 VMValue::List(items) => {
2440 let mut parts = Vec::with_capacity(items.len());
2441 for item in items {
2442 let forced = self
2444 .force_value(NanBox::from_vmvalue(item))
2445 .ok()?
2446 .to_vmvalue();
2447 parts.push(self.coerce_drv_env_value(&forced)?);
2448 }
2449 Some(parts.join(" "))
2450 }
2451 VMValue::Attrs(map) => {
2452 let to_string_sym = self.interner.intern("__toString");
2456 if map.contains_key(&to_string_sym) {
2457 return None;
2462 }
2463 let out_path_sym = self.interner.intern("outPath");
2464 let out_path = map.get(&out_path_sym)?;
2465 let forced = self
2466 .force_value(NanBox::from_vmvalue(out_path))
2467 .ok()?
2468 .to_vmvalue();
2469 self.coerce_drv_env_value(&forced)
2470 }
2471 _ => None,
2472 }
2473 }
2474 fn vm_build_derivation(&mut self, arg: NanBox) -> Result<NanBox, VMError> {
2476 use sui_compat::derivation::{Derivation, DerivationOutput};
2477 let attrs = match arg.as_attrs() {
2478 Some(a) => a.clone(),
2479 None => {
2480 return Err(VMError::TypeError {
2481 expected: "set",
2482 got: arg.type_name(),
2483 context: "derivation".to_string(),
2484 });
2485 }
2486 };
2487 let get_str = |attrs: &BTreeMap<Symbol, NanBox>,
2489 interner: &mut Interner,
2490 key: &str|
2491 -> Result<String, VMError> {
2492 let sym = interner.intern(key);
2493 let val = attrs.get(&sym).ok_or_else(|| {
2494 VMError::AttrNotFound(key.to_string())
2495 })?;
2496 match val.to_vmvalue() {
2497 VMValue::String(s) => Ok(s),
2498 other => Err(VMError::TypeError {
2499 expected: "string",
2500 got: other.type_name(),
2501 context: format!("derivation attr '{key}'"),
2502 }),
2503 }
2504 };
2505 let get_str_opt = |attrs: &BTreeMap<Symbol, NanBox>,
2506 interner: &mut Interner,
2507 key: &str|
2508 -> Result<Option<String>, VMError> {
2509 let sym = interner.intern(key);
2510 match attrs.get(&sym) {
2511 None => Ok(None),
2512 Some(val) => match val.to_vmvalue() {
2513 VMValue::String(s) => Ok(Some(s)),
2514 other => Err(VMError::TypeError {
2515 expected: "string",
2516 got: other.type_name(),
2517 context: format!("derivation attr '{key}'"),
2518 }),
2519 },
2520 }
2521 };
2522 let name = get_str(&attrs, self.interner, "name")?;
2523 let system = get_str(&attrs, self.interner, "system")?;
2524 let builder = get_str(&attrs, self.interner, "builder")?;
2525 let args_sym = self.interner.intern("args");
2534 let args_list: Vec<String> = if let Some(a) = attrs.get(&args_sym) {
2535 let forced_a = self.force_value(a.clone())?;
2536 let vmval = forced_a.to_vmvalue();
2537 match vmval {
2538 VMValue::List(l) => {
2539 let mut out = Vec::with_capacity(l.len());
2540 for item in &l {
2541 let forced = self.force_value(NanBox::from_vmvalue(item))?;
2543 match forced.to_vmvalue() {
2544 VMValue::String(s) => out.push(s.clone()),
2545 VMValue::Int(n) => out.push(n.to_string()),
2546 VMValue::Float(f) => out.push(format!("{f:.6}")),
2547 VMValue::Bool(true) => out.push("1".to_string()),
2548 VMValue::Bool(false) => out.push(String::new()),
2549 VMValue::Null => out.push(String::new()),
2550 VMValue::Path(p) => out.push(p.clone()),
2551 _ => out.push(String::new()),
2552 }
2553 }
2554 out
2555 }
2556 _ => Vec::new(),
2557 }
2558 } else {
2559 Vec::new()
2560 };
2561 let outputs_sym = self.interner.intern("outputs");
2570 let outputs: Vec<String> = if let Some(o) = attrs.get(&outputs_sym) {
2571 let forced_o = self.force_value(o.clone())?;
2572 match forced_o.to_vmvalue() {
2573 VMValue::List(l) => {
2574 let mut out = Vec::with_capacity(l.len());
2575 for item in &l {
2576 let forced = self.force_value(NanBox::from_vmvalue(item))?;
2577 if let VMValue::String(s) = forced.to_vmvalue() {
2578 out.push(s);
2579 }
2580 }
2581 if out.is_empty() {
2582 vec!["out".to_string()]
2583 } else {
2584 out
2585 }
2586 }
2587 _ => vec!["out".to_string()],
2588 }
2589 } else {
2590 vec!["out".to_string()]
2591 };
2592 let ignore_nulls_sym = self.interner.intern("__ignoreNulls");
2598 let ignore_nulls = attrs
2599 .get(&ignore_nulls_sym)
2600 .map(|v| self.force_value(v.clone()))
2601 .transpose()?
2602 .map(|v| matches!(v.to_vmvalue(), VMValue::Bool(true)))
2603 .unwrap_or(false);
2604
2605 let special = [
2614 "name", "system", "builder", "args",
2615 "__ignoreNulls", "__impure", "__contentAddressed",
2616 ];
2617 let special_syms: Vec<Symbol> = special
2618 .iter()
2619 .map(|s| self.interner.intern(s))
2620 .collect();
2621 let mut env_vars: BTreeMap<String, String> = BTreeMap::new();
2622 let env_keys: Vec<(Symbol, String)> = attrs
2625 .iter()
2626 .filter(|(k, _)| !special_syms.contains(k))
2627 .map(|(k, _)| (*k, self.interner.resolve(*k).to_string()))
2628 .collect();
2629 for (k, key_str) in env_keys {
2630 let Some(v) = attrs.get(&k) else { continue };
2631 let forced = self.force_value(v.clone())?;
2636 let fv = forced.to_vmvalue();
2637 if ignore_nulls && matches!(fv, VMValue::Null) {
2639 continue;
2640 }
2641 match self.coerce_drv_env_value(&fv) {
2647 Some(s) => {
2648 env_vars.insert(key_str, s);
2649 }
2650 None => continue,
2651 }
2652 }
2653 env_vars.insert("name".to_string(), name.clone());
2654 env_vars.insert("system".to_string(), system.clone());
2655 env_vars.insert("builder".to_string(), builder.clone());
2656 let output_hash_sym = self.interner.intern("outputHash");
2658 let is_fod = attrs.contains_key(&output_hash_sym);
2659 let mut drv = Derivation {
2660 outputs: BTreeMap::new(),
2661 input_derivations: BTreeMap::new(),
2662 input_sources: Vec::new(),
2663 system,
2664 builder,
2665 args: args_list,
2666 env: env_vars,
2667 };
2668 let (drv_path, out_paths, mut drv) = if is_fod {
2669 let raw_output_hash = get_str(&attrs, self.interner, "outputHash")?;
2670 let raw_algo = get_str_opt(&attrs, self.interner, "outputHashAlgo")?
2671 .unwrap_or_default();
2672 let output_hash_mode = get_str_opt(&attrs, self.interner, "outputHashMode")?
2673 .unwrap_or_else(|| "flat".to_string());
2674 let is_recursive =
2675 output_hash_mode == "recursive" || output_hash_mode == "nar";
2676 let output_hash_algo = if raw_algo.is_empty() {
2679 ["sha256", "sha512", "sha1", "md5"].iter()
2680 .find(|a| raw_output_hash.starts_with(&format!("{a}-")))
2681 .map(|s| (*s).to_string())
2682 .unwrap_or_else(|| "sha256".to_string())
2683 } else {
2684 raw_algo
2685 };
2686 let algo = sui_compat::hash::HashAlgorithm::from_nix_str(&output_hash_algo)
2690 .map_err(|e| VMError::Internal(format!(
2691 "derivation: invalid outputHashAlgo {output_hash_algo:?}: {e}",
2692 )))?;
2693 let parsed = sui_compat::hash::NixHash::parse_any(algo, &raw_output_hash)
2694 .map_err(|e| VMError::Internal(format!(
2695 "derivation: invalid outputHash {raw_output_hash:?}: {e}",
2696 )))?;
2697 let output_hash_hex = parsed.to_hex();
2698 let out_path = sui_compat::store_path::compute_fixed_output_hash(
2699 &output_hash_algo,
2700 &output_hash_hex,
2701 is_recursive,
2702 &name,
2703 );
2704 drv.outputs.insert(
2705 "out".to_string(),
2706 DerivationOutput {
2707 path: out_path.clone(),
2708 hash_algo: if is_recursive {
2709 format!("r:{output_hash_algo}")
2710 } else {
2711 output_hash_algo.clone()
2712 },
2713 hash: output_hash_hex,
2714 },
2715 );
2716 drv.env.insert("out".to_string(), out_path.clone());
2722
2723 let drv_content = drv.serialize();
2724 let drv_refs: Vec<String> = drv.input_derivations.keys().cloned()
2735 .chain(drv.input_sources.iter().cloned())
2736 .collect();
2737 let drv_path = sui_compat::store_path::compute_drv_path_with_refs(
2738 drv_content.as_bytes(), &name, &drv_refs);
2739
2740 if let Ok(dir) = std::env::var("SUI_EMIT_DRV") {
2744 if !dir.is_empty() {
2745 let base = drv_path.rsplit('/').next().unwrap_or(&drv_path);
2746 let _ = std::fs::create_dir_all(&dir);
2747 let _ = std::fs::write(
2748 std::path::Path::new(&dir).join(base),
2749 drv_content.as_bytes(),
2750 );
2751 }
2752 }
2753
2754 let out_output = drv.outputs.get("out");
2762 let method_algo = out_output
2763 .map(|o| o.hash_algo.clone())
2764 .unwrap_or_default();
2765 let output_hash_hex = out_output
2766 .map(|o| o.hash.clone())
2767 .unwrap_or_default();
2768 let modulo_preimage =
2769 format!("fixed:out:{method_algo}:{output_hash_hex}:{out_path}");
2770 let modulo_hex: String = {
2771 use sha2::{Digest, Sha256};
2772 Sha256::digest(modulo_preimage.as_bytes())
2773 .iter()
2774 .map(|b| format!("{b:02x}"))
2775 .collect()
2776 };
2777 sui_spec::derivation::remember_modulo_hash(&drv_path, &modulo_hex);
2778
2779 let mut out_paths = BTreeMap::new();
2780 out_paths.insert("out".to_string(), out_path);
2781 (drv_path, out_paths, drv)
2782 } else {
2783 let algo = sui_spec::derivation::load_canonical().map_err(|e| {
2791 VMError::TypeError {
2792 expected: "valid derivation algorithm spec",
2793 got: "load error",
2794 context: format!("sui-spec: {e}"),
2795 }
2796 })?;
2797 let (drv_path, out_paths, drv_final) =
2798 sui_spec::derivation::apply(&algo, drv, outputs.clone(), &name)
2799 .map_err(|e| VMError::TypeError {
2800 expected: "derivation interpreter success",
2801 got: "interp error",
2802 context: format!("sui-spec: {e}"),
2803 })?;
2804 (drv_path, out_paths, drv_final)
2805 };
2806 for (output_name, output_path) in &out_paths {
2808 if let Some(output) = drv.outputs.get_mut(output_name) {
2809 if output.path.is_empty() {
2810 output.path.clone_from(output_path);
2811 }
2812 }
2813 drv.env.insert(output_name.clone(), output_path.clone());
2814 }
2815 let drv_content_final = drv.serialize();
2816 let store_dir = std::env::var("SUI_STORE_DIR")
2817 .unwrap_or_else(|_| "/nix/store".to_string());
2818 let disk_path = if store_dir != "/nix/store" {
2819 drv_path.replacen("/nix/store", &store_dir, 1)
2820 } else {
2821 drv_path.clone()
2822 };
2823 let drv_file = std::path::Path::new(&disk_path);
2824 if !drv_file.exists() {
2825 if let Some(parent) = drv_file.parent() {
2826 std::fs::create_dir_all(parent).ok();
2827 }
2828 match std::fs::write(drv_file, drv_content_final.as_bytes()) {
2829 Ok(()) => {}
2830 Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => {
2831 let fallback_dir = std::env::temp_dir().join("sui-drv-cache");
2832 std::fs::create_dir_all(&fallback_dir).ok();
2833 let fallback_path = fallback_dir.join(
2834 drv_file.file_name().unwrap_or_default(),
2835 );
2836 let _ = std::fs::write(&fallback_path, drv_content_final.as_bytes());
2837 }
2838 Err(e) => {
2839 return Err(VMError::Throw(format!(
2840 "derivation: failed to write {drv_path}: {e}"
2841 )));
2842 }
2843 }
2844 }
2845 let mut result: BTreeMap<Symbol, NanBox> = attrs.clone();
2847 let type_sym = self.interner.intern("type");
2848 result.insert(type_sym, NanBox::string("derivation".to_string()));
2849 let drv_path_sym = self.interner.intern("drvPath");
2850 result.insert(drv_path_sym, NanBox::string(drv_path.clone()));
2851 let drv_attrs_sym = self.interner.intern("drvAttrs");
2853 result.insert(drv_attrs_sym, NanBox::attrs(attrs));
2854 let primary_out = out_paths
2855 .get("out")
2856 .cloned()
2857 .or_else(|| out_paths.values().next().cloned())
2858 .unwrap_or_default();
2859 let out_path_sym = self.interner.intern("outPath");
2860 result.insert(out_path_sym, NanBox::string(primary_out));
2861 let output_name_sym = self.interner.intern("outputName");
2863 let primary_output_name = if out_paths.contains_key("out") { "out" }
2864 else { out_paths.keys().next().map(|s| s.as_str()).unwrap_or("out") };
2865 result.insert(output_name_sym, NanBox::string(primary_output_name.to_string()));
2866 let mut all_outputs: Vec<NanBox> = Vec::new();
2867 for (output_name, output_path) in &out_paths {
2868 let mut out_attrs: BTreeMap<Symbol, NanBox> = BTreeMap::new();
2869 out_attrs.insert(out_path_sym, NanBox::string(output_path.clone()));
2870 out_attrs.insert(drv_path_sym, NanBox::string(drv_path.clone()));
2871 out_attrs.insert(type_sym, NanBox::string("derivation".to_string()));
2872 out_attrs.insert(output_name_sym, NanBox::string(output_name.clone()));
2873 let name_sym = self.interner.intern("name");
2874 out_attrs.insert(name_sym, NanBox::string(name.clone()));
2875 let out_val = NanBox::attrs(out_attrs);
2876 all_outputs.push(out_val.clone());
2877 let out_sym = self.interner.intern(output_name);
2878 result.insert(out_sym, out_val);
2879 }
2880 let all_sym = self.interner.intern("all");
2882 result.insert(all_sym, NanBox::list(all_outputs));
2883 Ok(NanBox::attrs(result))
2884 }
2885 fn call_builtin_with_scoped_import_dispatch(
2887 &mut self,
2888 func: Rc<dyn Fn(Vec<VMValue>) -> Result<VMValue, VMError>>,
2889 arg: VMValue,
2890 ) -> Result<NanBox, VMError> {
2891 let arg = if let VMValue::Thunk(ref thunk) = arg {
2893 let nb = NanBox::from_vmvalue(&arg);
2894 self.force_value(nb)?.to_vmvalue()
2895 } else {
2896 arg
2897 };
2898 match func(vec![arg]) {
2899 Ok(result) => Ok(NanBox::from_vmvalue(&result)),
2900 Err(VMError::Throw(ref msg))
2901 if msg.starts_with("__scopedImport_dispatch__:") =>
2902 {
2903 let rest = &msg["__scopedImport_dispatch__:".len()..];
2904 if let Some(colon_pos) = rest.rfind(':') {
2905 let scope_nix = &rest[..colon_pos];
2906 let path = &rest[colon_pos + 1..];
2907 self.vm_scoped_import(scope_nix, path)
2908 } else {
2909 Err(VMError::Throw(msg.clone()))
2910 }
2911 }
2912 Err(e) => Err(e),
2913 }
2914 }
2915 fn vm_get_flake(&mut self, flake_ref: &str) -> Result<NanBox, VMError> {
2923 let resolved = FLAKE_RESOLVER.with(|r| {
2925 let borrow = r.borrow();
2926 if let Some(ref resolver) = *borrow {
2927 Some(resolver(flake_ref))
2928 } else {
2929 None
2930 }
2931 });
2932 if let Some(result) = resolved {
2933 let sk = result.map_err(|e| VMError::Throw(format!("getFlake: {e}")))?;
2934 return Ok(self.string_keyed_to_nanbox(&sk));
2935 }
2936 self.vm_get_flake_native(flake_ref)
2938 }
2939 fn string_keyed_to_nanbox(&mut self, sk: &crate::value::StringKeyedValue) -> NanBox {
2946 match sk {
2947 crate::value::StringKeyedValue::Null => NanBox::null(),
2948 crate::value::StringKeyedValue::Bool(b) => NanBox::bool(*b),
2949 crate::value::StringKeyedValue::Int(n) => NanBox::int(*n),
2950 crate::value::StringKeyedValue::Float(f) => NanBox::float(*f),
2951 crate::value::StringKeyedValue::String(s) => NanBox::string(s.clone()),
2952 crate::value::StringKeyedValue::Path(p) => NanBox::from_vmvalue(&VMValue::Path(p.clone())),
2953 crate::value::StringKeyedValue::List(items) => {
2954 let nb_items: Vec<NanBox> = items.iter().map(|v| self.string_keyed_to_nanbox(v)).collect();
2955 NanBox::list(nb_items)
2956 }
2957 crate::value::StringKeyedValue::Attrs(map) => {
2958 let mut nb_map: BTreeMap<Symbol, NanBox> = BTreeMap::new();
2959 for (k, v) in map {
2960 let sym = self.interner.intern(k);
2961 nb_map.insert(sym, self.string_keyed_to_nanbox(v));
2962 }
2963 NanBox::attrs(nb_map)
2964 }
2965 crate::value::StringKeyedValue::Lambda => NanBox::null(),
2966 crate::value::StringKeyedValue::Callable(cb) => {
2967 let cb_clone = Rc::clone(cb);
2968 let builtin = crate::value::VMBuiltin {
2969 name: "<bridge-fn>",
2970 arity: 1,
2971 func: Rc::new(move |args: Vec<VMValue>| {
2972 let interner = crate::intern::Interner::new();
2973 let sk_arg = args.into_iter().next()
2974 .unwrap_or(VMValue::Null)
2975 .to_string_keyed(&interner);
2976 let sk_result = cb_clone(sk_arg)
2977 .map_err(|e| crate::error::VMError::Throw(e))?;
2978 let mut tmp_interner = crate::intern::Interner::new();
2979 Ok(crate::builtins::string_keyed_to_vmvalue(&sk_result, &mut tmp_interner))
2980 }),
2981 };
2982 NanBox::builtin(builtin)
2983 }
2984 crate::value::StringKeyedValue::Thunk(cb) => {
2985 let thunk = VMThunk {
2989 state: Rc::new(Cell::new(Some(ThunkState::NativeCallback(Rc::clone(cb))))),
2990 };
2991 NanBox::thunk(thunk)
2992 }
2993 }
2994 }
2995 fn vm_get_flake_native(&mut self, flake_ref: &str) -> Result<NanBox, VMError> {
2997 let flake_dir = if flake_ref.starts_with('/') || flake_ref.starts_with('.') {
2998 std::path::PathBuf::from(flake_ref)
2999 } else if let Some(path) = flake_ref.strip_prefix("path:") {
3000 std::path::PathBuf::from(path)
3001 } else {
3002 return Err(VMError::Throw(format!(
3003 "getFlake: unsupported flake reference: {flake_ref} (only path: refs supported in VM)"
3004 )));
3005 };
3006 let flake_nix = flake_dir.join("flake.nix");
3007 if !flake_nix.exists() {
3008 return Err(VMError::Throw(format!(
3009 "getFlake: flake.nix not found in {}",
3010 flake_dir.display()
3011 )));
3012 }
3013 let flake_nix_str = flake_nix.to_string_lossy().to_string();
3015 let flake_attrs = self.import_file(&flake_nix_str)?;
3016 let flake_attrs = self.force_value(flake_attrs)?;
3017 let self_sym = self.interner.intern("self");
3019 let out_path_sym = self.interner.intern("outPath");
3020 let flake_dir_str = flake_dir.to_string_lossy().to_string();
3021 let mut self_attrs: BTreeMap<Symbol, NanBox> = BTreeMap::new();
3022 self_attrs.insert(out_path_sym, NanBox::string(flake_dir_str.clone()));
3023 let mut inputs: BTreeMap<Symbol, NanBox> = BTreeMap::new();
3024 inputs.insert(self_sym, NanBox::attrs(self_attrs));
3025 let lock_path = flake_dir.join("flake.lock");
3027 if lock_path.exists() {
3028 if let Ok(lock_str) = std::fs::read_to_string(&lock_path) {
3029 if let Ok(lock_json) = serde_json::from_str::<serde_json::Value>(&lock_str) {
3030 self.resolve_flake_lock_inputs(&lock_json, &flake_dir, &mut inputs);
3031 }
3032 }
3033 }
3034 let outputs_sym = self.interner.intern("outputs");
3036 if let Some(attrs) = flake_attrs.as_attrs() {
3037 if let Some(outputs_func) = attrs.get(&outputs_sym) {
3038 let outputs_func = outputs_func.clone();
3039 let outputs_func = self.force_value(outputs_func)?;
3040 let inputs_nb = NanBox::attrs(inputs);
3041 let result = self.call_callable(&outputs_func, inputs_nb)?;
3042 let mut result_forced = self.force_value(result)?;
3043 let desc_sym = self.interner.intern("description");
3045 if let Some(desc) = attrs.get(&desc_sym) {
3046 if let Some(result_attrs) = result_forced.as_attrs() {
3047 let mut merged = result_attrs.clone();
3048 merged.insert(desc_sym, desc.clone());
3049 result_forced = NanBox::attrs(merged);
3050 }
3051 }
3052 return Ok(result_forced);
3053 }
3054 }
3055 Ok(flake_attrs)
3057 }
3058 fn resolve_flake_lock_inputs(
3060 &mut self,
3061 lock: &serde_json::Value,
3062 flake_dir: &std::path::Path,
3063 inputs: &mut BTreeMap<Symbol, NanBox>,
3064 ) {
3065 let nodes = match lock.get("nodes").and_then(|n| n.as_object()) {
3066 Some(n) => n,
3067 None => return,
3068 };
3069 let root_node = match lock.get("root").and_then(|r| r.as_str()) {
3070 Some(r) => r.to_string(),
3071 None => "root".to_string(),
3072 };
3073 let root_inputs = match nodes
3074 .get(&root_node)
3075 .and_then(|n| n.get("inputs"))
3076 .and_then(|i| i.as_object())
3077 {
3078 Some(i) => i,
3079 None => return,
3080 };
3081 for (input_name, node_ref) in root_inputs {
3082 let node_key = match node_ref.as_str() {
3083 Some(s) => s.to_string(),
3084 None => {
3085 if let Some(arr) = node_ref.as_array() {
3086 if let Some(s) = arr.first().and_then(|v| v.as_str()) {
3087 s.to_string()
3088 } else {
3089 continue;
3090 }
3091 } else {
3092 continue;
3093 }
3094 }
3095 };
3096 if let Some(node) = nodes.get(&node_key) {
3097 if let Some(locked) = node.get("locked") {
3098 let locked_type = locked.get("type").and_then(|t| t.as_str()).unwrap_or("");
3099 let out_path = match locked_type {
3100 "path" => {
3101 if let Some(p) = locked.get("path").and_then(|p| p.as_str()) {
3102 let path = if p.starts_with('/') {
3103 std::path::PathBuf::from(p)
3104 } else {
3105 flake_dir.join(p)
3106 };
3107 path.to_string_lossy().to_string()
3108 } else {
3109 continue;
3110 }
3111 }
3112 _ => continue, };
3114 let input_sym = self.interner.intern(input_name);
3115 let out_path_sym = self.interner.intern("outPath");
3116 let mut input_attrs: BTreeMap<Symbol, NanBox> = BTreeMap::new();
3117 input_attrs.insert(out_path_sym, NanBox::string(out_path));
3118 inputs.insert(input_sym, NanBox::attrs(input_attrs));
3119 }
3120 }
3121 }
3122 }
3123 fn vm_scoped_import(
3127 &mut self,
3128 scope_nix: &str,
3129 path: &str,
3130 ) -> Result<NanBox, VMError> {
3131 let read_path = crate::bridge::materialize(path);
3134 let resolved = if std::path::Path::new(&read_path).is_dir() {
3136 format!("{read_path}/default.nix")
3137 } else {
3138 read_path
3139 };
3140 let source = std::fs::read_to_string(&resolved)
3141 .map_err(|e| VMError::ImportError(format!("{path}: {e}")))?;
3142 let wrapped = format!("with {scope_nix}; {source}");
3144 let file_dir = std::path::Path::new(&resolved)
3145 .parent()
3146 .map(|p| p.to_path_buf())
3147 .unwrap_or_default();
3148 let shared_interner = Rc::new(RefCell::new(std::mem::take(self.interner)));
3150 let chunk = Compiler::compile_with_shared_interner(&wrapped, file_dir, shared_interner.clone())
3151 .map_err(|e| VMError::ImportError(format!("{path}: {e}")))?;
3152 *self.interner = match Rc::try_unwrap(shared_interner) {
3153 Ok(cell) => cell.into_inner(),
3154 Err(rc) => rc.borrow().clone(),
3155 };
3156 if self.frames.len() >= MAX_CALL_DEPTH {
3157 return Err(VMError::StackOverflow);
3158 }
3159 let return_depth = self.frames.len();
3160 let stack_base = self.stack.len();
3161 self.frames.push(CallFrame {
3162 chunk: Rc::new(chunk),
3163 ip: 0,
3164 stack_base,
3165 upvalues: Vec::new(),
3166 });
3167 self.run_until(return_depth)
3168 }
3169 fn call_callable(&mut self, func: &NanBox, arg: NanBox) -> Result<NanBox, VMError> {
3171 if let Some(closure) = func.as_closure() {
3172 if self.frames.len() >= MAX_CALL_DEPTH {
3173 return Err(VMError::StackOverflow);
3174 }
3175 let upvalues = closure.upvalues.clone();
3176 let chunk = closure.chunk.clone();
3177 let return_depth = self.frames.len();
3178 let stack_base = self.stack.len();
3179 self.push(arg);
3180 self.frames.push(CallFrame {
3181 chunk,
3182 ip: 0,
3183 stack_base,
3184 upvalues,
3185 });
3186 let result = self.run_until(return_depth)?;
3187 self.stack.truncate(stack_base);
3188 self.force_value(result)
3191 } else if func.is_higher_order_builtin() {
3192 let hob = func.as_higher_order_builtin().unwrap().clone();
3193 self.call_higher_order_builtin(&hob, arg)
3194 } else if let Some(builtin) = func.as_builtin() {
3195 let arg = self.force_value(arg)?;
3197 if let Some(result) = self.try_vm_builtin(builtin.name, &arg)? {
3198 Ok(result)
3199 } else {
3200 let deep = self.deep_force(arg)?;
3202 let arg_vmval = deep.to_vmvalue();
3203 let builtin_func = builtin.func.clone();
3204 let result = self.call_builtin_with_scoped_import_dispatch(
3205 builtin_func, arg_vmval,
3206 )?;
3207 Ok(result)
3208 }
3209 } else {
3210 Err(VMError::NotCallable(func.type_name().to_string()))
3211 }
3212 }
3213 #[allow(clippy::too_many_lines)]
3214 fn call_higher_order_builtin(
3215 &mut self,
3216 hob: &HigherOrderBuiltin,
3217 arg: NanBox,
3218 ) -> Result<NanBox, VMError> {
3219 use HigherOrderOp::*;
3220 let arg = self.force_value(arg)?;
3223 match hob.op {
3224 Map => {
3225 let list_val = arg.to_vmvalue();
3226 let list = match &list_val {
3227 VMValue::List(l) => l,
3228 other => return Err(VMError::TypeError {
3229 expected: "list", got: other.type_name(),
3230 context: "builtins.map".to_string(),
3231 }),
3232 };
3233 let func_nb = NanBox::from_vmvalue(&hob.func);
3234 let mut results = Vec::with_capacity(list.len());
3235 for item in list {
3236 let r = self.call_callable(&func_nb, NanBox::from_vmvalue(item))?;
3237 results.push(r);
3238 }
3239 Ok(NanBox::list(results))
3240 }
3241 Filter => {
3242 let list_val = arg.to_vmvalue();
3243 let list = match &list_val {
3244 VMValue::List(l) => l,
3245 other => return Err(VMError::TypeError {
3246 expected: "list", got: other.type_name(),
3247 context: "builtins.filter".to_string(),
3248 }),
3249 };
3250 let func_nb = NanBox::from_vmvalue(&hob.func);
3251 let mut results = Vec::new();
3252 for item in list {
3253 let item_nb = NanBox::from_vmvalue(item);
3254 let r = self.call_callable(&func_nb, item_nb.clone())?;
3255
3256 if r.is_truthy()? { results.push(item_nb); }
3257 }
3258 Ok(NanBox::list(results))
3259 }
3260 FoldlP1 => {
3261 let init_vmval = arg.to_vmvalue();
3262 Ok(NanBox::from_vmvalue(&VMValue::HigherOrderBuiltin(
3263 HigherOrderBuiltin {
3264 op: FoldlP2,
3265 func: hob.func.clone(),
3266 extra_args: vec![init_vmval],
3267 },
3268 )))
3269 }
3270 FoldlP2 => {
3271 let list_val = arg.to_vmvalue();
3272 let list = match &list_val {
3273 VMValue::List(l) => l,
3274 other => return Err(VMError::TypeError {
3275 expected: "list", got: other.type_name(),
3276 context: "builtins.foldl'".to_string(),
3277 }),
3278 };
3279 let func_nb = NanBox::from_vmvalue(&hob.func);
3280 let mut acc = NanBox::from_vmvalue(&hob.extra_args[0]);
3281 for item in list {
3282 let partial = self.call_callable(&func_nb, acc)?;
3283 acc = self.call_callable(&partial, NanBox::from_vmvalue(item))?;
3284 }
3285 Ok(acc)
3286 }
3287 Sort => {
3288 let list_val = arg.to_vmvalue();
3289 let list = match &list_val {
3290 VMValue::List(l) => l.clone(),
3291 other => return Err(VMError::TypeError {
3292 expected: "list", got: other.type_name(),
3293 context: "builtins.sort".to_string(),
3294 }),
3295 };
3296 if list.len() <= 1 {
3297 return Ok(NanBox::from_vmvalue(&VMValue::List(list)));
3298 }
3299 let func_nb = NanBox::from_vmvalue(&hob.func);
3300 let mut sorted: Vec<VMValue> = Vec::with_capacity(list.len());
3301 for item in &list {
3302 let item_nb = NanBox::from_vmvalue(item);
3303 let mut pos = sorted.len();
3304 for (i, existing) in sorted.iter().enumerate() {
3305 let existing_nb = NanBox::from_vmvalue(existing);
3306 let partial = self.call_callable(&func_nb, item_nb.clone())?;
3307 let cmp_result = self.call_callable(&partial, existing_nb)?;
3308 if cmp_result.is_truthy()? { pos = i; break; }
3309 }
3310 sorted.insert(pos, item.clone());
3311 }
3312 Ok(NanBox::from_vmvalue(&VMValue::List(sorted)))
3313 }
3314 GenList => {
3315 let n = match arg.to_vmvalue() {
3316 VMValue::Int(n) => n,
3317 other => return Err(VMError::TypeError {
3318 expected: "int", got: other.type_name(),
3319 context: "builtins.genList".to_string(),
3320 }),
3321 };
3322 if n < 0 { return Err(VMError::Throw("genList: negative length".to_string())); }
3323 let func_nb = NanBox::from_vmvalue(&hob.func);
3324 let mut results = Vec::with_capacity(n as usize);
3325 for i in 0..n {
3326 results.push(self.call_callable(&func_nb, NanBox::int(i))?);
3327 }
3328 Ok(NanBox::list(results))
3329 }
3330 ConcatMap => {
3331 let list_val = arg.to_vmvalue();
3332 let list = match &list_val {
3333 VMValue::List(l) => l,
3334 other => return Err(VMError::TypeError {
3335 expected: "list", got: other.type_name(),
3336 context: "builtins.concatMap".to_string(),
3337 }),
3338 };
3339 let func_nb = NanBox::from_vmvalue(&hob.func);
3340 let mut results = Vec::new();
3341 for item in list {
3342 let mapped = self.call_callable(&func_nb, NanBox::from_vmvalue(item))?;
3343 match mapped.to_vmvalue() {
3344 VMValue::List(inner) => {
3345 for v in &inner { results.push(NanBox::from_vmvalue(v)); }
3346 }
3347 other => return Err(VMError::TypeError {
3348 expected: "list", got: other.type_name(),
3349 context: "builtins.concatMap result".to_string(),
3350 }),
3351 }
3352 }
3353 Ok(NanBox::list(results))
3354 }
3355 Any => {
3356 let list_val = arg.to_vmvalue();
3357 let list = match &list_val {
3358 VMValue::List(l) => l,
3359 other => return Err(VMError::TypeError {
3360 expected: "list", got: other.type_name(),
3361 context: "builtins.any".to_string(),
3362 }),
3363 };
3364 let func_nb = NanBox::from_vmvalue(&hob.func);
3365 for item in list {
3366 if self.call_callable(&func_nb, NanBox::from_vmvalue(item))?.is_truthy()? {
3367 return Ok(NanBox::bool(true));
3368 }
3369 }
3370 Ok(NanBox::bool(false))
3371 }
3372 All => {
3373 let list_val = arg.to_vmvalue();
3374 let list = match &list_val {
3375 VMValue::List(l) => l,
3376 other => return Err(VMError::TypeError {
3377 expected: "list", got: other.type_name(),
3378 context: "builtins.all".to_string(),
3379 }),
3380 };
3381 let func_nb = NanBox::from_vmvalue(&hob.func);
3382 for item in list {
3383 if !self.call_callable(&func_nb, NanBox::from_vmvalue(item))?.is_truthy()? {
3384 return Ok(NanBox::bool(false));
3385 }
3386 }
3387 Ok(NanBox::bool(true))
3388 }
3389 Partition => {
3390 let list_val = arg.to_vmvalue();
3391 let list = match &list_val {
3392 VMValue::List(l) => l,
3393 other => return Err(VMError::TypeError {
3394 expected: "list", got: other.type_name(),
3395 context: "builtins.partition".to_string(),
3396 }),
3397 };
3398 let func_nb = NanBox::from_vmvalue(&hob.func);
3399 let (mut right, mut wrong) = (Vec::new(), Vec::new());
3400 for item in list {
3401 let item_nb = NanBox::from_vmvalue(item);
3402 if self.call_callable(&func_nb, item_nb.clone())?.is_truthy()? {
3403 right.push(item_nb);
3404 } else {
3405 wrong.push(item_nb);
3406 }
3407 }
3408 let rs = self.interner.intern("right");
3409 let ws = self.interner.intern("wrong");
3410 let mut attrs = BTreeMap::new();
3411 attrs.insert(rs, NanBox::list(right));
3412 attrs.insert(ws, NanBox::list(wrong));
3413 Ok(NanBox::attrs(attrs))
3414 }
3415 GroupBy => {
3416 let list_val = arg.to_vmvalue();
3417 let list = match &list_val {
3418 VMValue::List(l) => l,
3419 other => return Err(VMError::TypeError {
3420 expected: "list", got: other.type_name(),
3421 context: "builtins.groupBy".to_string(),
3422 }),
3423 };
3424 let func_nb = NanBox::from_vmvalue(&hob.func);
3425 let mut groups: BTreeMap<String, Vec<NanBox>> = BTreeMap::new();
3426 for item in list {
3427 let item_nb = NanBox::from_vmvalue(item);
3428 let kr = self.call_callable(&func_nb, item_nb.clone())?;
3429 let ks = kr.as_string().ok_or_else(|| VMError::TypeError {
3430 expected: "string", got: kr.type_name(),
3431 context: "builtins.groupBy key".to_string(),
3432 })?.to_string();
3433 groups.entry(ks).or_default().push(item_nb);
3434 }
3435 let mut attrs = BTreeMap::new();
3436 for (k, vs) in groups {
3437 attrs.insert(self.interner.intern(&k), NanBox::list(vs));
3438 }
3439 Ok(NanBox::attrs(attrs))
3440 }
3441 MapAttrs => {
3442 let attrs_val = arg.to_vmvalue();
3443 let attrs = match &attrs_val {
3444 VMValue::Attrs(a) => a,
3445 other => return Err(VMError::TypeError {
3446 expected: "set", got: other.type_name(),
3447 context: "builtins.mapAttrs".to_string(),
3448 }),
3449 };
3450 let func_nb = NanBox::from_vmvalue(&hob.func);
3451 let entries: Vec<_> = attrs.iter().map(|(k, v)| (*k, v.clone())).collect();
3452 let chunk = deferred_apply_chunk();
3453 let mut result = BTreeMap::new();
3454 for (sym, val) in entries {
3455 let key_str = self.interner.resolve(sym).to_string();
3456 let partial = self.call_callable(&func_nb, NanBox::string(key_str))?;
3459 let thunk = VMThunk::new(
3466 chunk.clone(),
3467 vec![partial, NanBox::from_vmvalue(&val)],
3468 );
3469 result.insert(sym, NanBox::thunk(thunk));
3470 }
3471 Ok(NanBox::attrs(result))
3472 }
3473 Elem => {
3474 let needle = NanBox::from_vmvalue(&hob.func);
3479 let forced_needle = self.force_value(needle)?;
3480 let list = if let Some(items) = arg.as_list() {
3481 items.to_vec()
3482 } else {
3483 let forced = self.force_value(arg)?;
3484 if let Some(items) = forced.as_list() {
3485 items.to_vec()
3486 } else {
3487 return Err(VMError::TypeError {
3488 expected: "list",
3489 got: forced.type_name(),
3490 context: "builtins.elem".to_string(),
3491 });
3492 }
3493 };
3494 for item in &list {
3495 let forced_item = self.force_value(item.clone())?;
3496 if self.deep_eq(&forced_needle, &forced_item)? {
3497 return Ok(NanBox::bool(true));
3498 }
3499 }
3500 Ok(NanBox::bool(false))
3501 }
3502 }
3503 }
3504 fn import_file(&mut self, path: &str) -> Result<NanBox, VMError> {
3510 let read_path = crate::bridge::materialize(path);
3517 let resolved = std::fs::canonicalize(&read_path)
3518 .map_err(|e| VMError::ImportError(format!("{path}: {e}")))?;
3519 let resolved = if resolved.is_dir() {
3521 resolved.join("default.nix")
3522 } else {
3523 resolved
3524 };
3525 let canonical = resolved.to_string_lossy().to_string();
3526 if let Some(cached) = self.import_cache.borrow().get(&canonical) {
3528 return Ok(NanBox::from_vmvalue(cached));
3529 }
3530 let chunk = self.try_compile_import(&resolved, &canonical)?;
3532 let chunk = match chunk {
3533 Some(c) => c,
3534 None => {
3535 return self.import_via_bridge(&canonical);
3537 }
3538 };
3539 if self.frames.len() >= MAX_CALL_DEPTH {
3540 return Err(VMError::StackOverflow);
3541 }
3542 let return_depth = self.frames.len();
3543 let stack_base = self.stack.len();
3544 self.frames.push(CallFrame {
3545 chunk,
3546 ip: 0,
3547 stack_base,
3548 upvalues: Vec::new(),
3549 });
3550 let result = match self.run_until(return_depth) {
3551 Ok(r) => r,
3552 Err(e @ VMError::Throw(_)) => {
3553 self.stack.truncate(stack_base);
3555 if self.frames.len() > return_depth {
3556 self.frames.truncate(return_depth);
3557 }
3558 return Err(e);
3559 }
3560 Err(e) => {
3561 crate::fallback::record(
3569 crate::fallback::Layer::ImportedFile,
3570 &format!("runtime error importing {canonical}: {e}"),
3571 )
3572 .map_err(VMError::Throw)?;
3573 eprintln!("[sui-vm] runtime fallback for {canonical}: {e}");
3574 use std::sync::atomic::Ordering;
3575 crate::vm::VM_FALLBACK_COUNT.fetch_add(1, Ordering::Relaxed);
3576 self.stack.truncate(stack_base);
3577 if self.frames.len() > return_depth {
3578 self.frames.truncate(return_depth);
3579 }
3580 return self.import_via_bridge(&canonical);
3581 }
3582 };
3583 self.stack.truncate(stack_base);
3586 let result_vmval = result.to_vmvalue();
3588 self.import_cache
3589 .borrow_mut()
3590 .insert(canonical, result_vmval);
3591 Ok(result)
3592 }
3593 fn try_compile_import(
3597 &mut self,
3598 resolved: &std::path::Path,
3599 canonical: &str,
3600 ) -> Result<Option<Rc<Chunk>>, VMError> {
3601 if let Some(cached_chunk) = self.compile_cache.get(resolved) {
3603 return Ok(Some(cached_chunk.clone()));
3604 }
3605 let source = std::fs::read_to_string(canonical)
3607 .map_err(|e| VMError::ImportError(format!("{canonical}: {e}")))?;
3608 let file_dir = resolved
3609 .parent()
3610 .map(|p| p.to_path_buf())
3611 .unwrap_or_default();
3612 let shared_interner = Rc::new(RefCell::new(std::mem::take(self.interner)));
3615 let compile_result =
3616 Compiler::compile_with_shared_interner(&source, file_dir, shared_interner.clone());
3617 *self.interner = match Rc::try_unwrap(shared_interner) {
3618 Ok(cell) => cell.into_inner(),
3619 Err(rc) => rc.borrow().clone(),
3620 };
3621 match compile_result {
3622 Ok(mut compiled) => {
3623 Self::set_source_file_recursive(&mut compiled, canonical);
3624 let chunk = Rc::new(compiled);
3625 self.compile_cache
3626 .insert(resolved.to_path_buf(), chunk.clone());
3627 Ok(Some(chunk))
3628 }
3629 Err(compile_error) => {
3630 crate::fallback::record(
3636 crate::fallback::Layer::ImportedFile,
3637 &format!("cannot compile {canonical}: {compile_error}"),
3638 )
3639 .map_err(VMError::Throw)?;
3640 VM_FALLBACK_COUNT.fetch_add(1, Ordering::Relaxed);
3641 eprintln!("[sui-vm] fallback to tree-walker for {canonical}: {compile_error}");
3642 Ok(None)
3643 }
3644 }
3645 }
3646 fn import_via_bridge(&mut self, canonical: &str) -> Result<NanBox, VMError> {
3650 match crate::bridge::call_builtin_bridge(
3651 "__import",
3652 vec![crate::value::StringKeyedValue::Path(canonical.to_string())],
3653 ) {
3654 Ok(Some(result)) => {
3655 let nanbox = self.string_keyed_to_nanbox(&result);
3656 let nanbox = if nanbox.is_thunk() {
3660 self.force_value(nanbox)?
3661 } else {
3662 nanbox
3663 };
3664 let result_vmval = nanbox.to_vmvalue();
3666 self.import_cache
3667 .borrow_mut()
3668 .insert(canonical.to_string(), result_vmval);
3669 Ok(nanbox)
3670 }
3671 Ok(None) => Err(VMError::ImportError(format!(
3672 "compilation failed and no bridge installed for '{canonical}'"
3673 ))),
3674 Err(e) => Err(VMError::ImportError(format!(
3675 "bridge fallback error for '{canonical}': {e}"
3676 ))),
3677 }
3678 }
3679 fn set_source_file_recursive(chunk: &mut Chunk, file: &str) {
3681 chunk.source_file = Some(file.to_string());
3682 for constant in &mut chunk.constants {
3683 if let VMValue::Closure(closure) = constant {
3684 if let Some(inner_chunk) = Rc::get_mut(&mut closure.chunk) {
3685 Self::set_source_file_recursive(inner_chunk, file);
3686 }
3687 }
3688 }
3689 }
3690 fn disassemble_around(chunk: &Chunk, center_ip: usize, window: usize) -> String {
3694 let code = &chunk.code;
3695 let mut lines: Vec<String> = Vec::new();
3696 let mut boundaries: Vec<usize> = Vec::new();
3698 let mut pos = 0;
3699 while pos < code.len() {
3700 boundaries.push(pos);
3701 pos += Self::instruction_width(code, pos);
3702 }
3703 let center_idx = boundaries.iter().position(|&b| b >= center_ip).unwrap_or(0);
3705 let start_idx = center_idx.saturating_sub(window);
3706 let end_idx = (center_idx + window + 1).min(boundaries.len());
3707 for idx in start_idx..end_idx {
3708 let ip = boundaries[idx];
3709 let marker = if ip == center_ip { ">>>" } else { " " };
3710 let line = chunk.lines.get(ip).copied().unwrap_or(0);
3711 if let Some(op) = OpCode::from_byte(code[ip]) {
3712 let operands = Self::format_operands(code, ip, op);
3713 lines.push(format!(" {marker} {ip:4}: {op:?}{operands} (line {line})"));
3714 } else {
3715 lines.push(format!(" {marker} {ip:4}: <unknown {}> (line {line})", code[ip]));
3716 }
3717 }
3718 lines.join("\n")
3719 }
3720 fn instruction_width(code: &[u8], pos: usize) -> usize {
3722 let byte = code[pos];
3723 match OpCode::from_byte(byte) {
3724 Some(op) => match op {
3725 OpCode::Null | OpCode::True | OpCode::False
3727 | OpCode::Add | OpCode::Sub | OpCode::Mul | OpCode::Div | OpCode::Negate
3728 | OpCode::Not | OpCode::And | OpCode::Or | OpCode::Implication
3729 | OpCode::Equal | OpCode::NotEqual | OpCode::Less | OpCode::Greater
3730 | OpCode::LessEqual | OpCode::GreaterEqual
3731 | OpCode::UpdateAttrs | OpCode::Concat
3732 | OpCode::Call | OpCode::TailCall | OpCode::Return
3733 | OpCode::Assert | OpCode::Throw | OpCode::Pop | OpCode::Dup | OpCode::PushWith | OpCode::PopWith
3734 | OpCode::PushBuiltins | OpCode::Force | OpCode::Import
3735 | OpCode::DynGetAttr | OpCode::DynHasAttr
3736 | OpCode::DynSelectOrDefault | OpCode::Dup => 1,
3737 OpCode::Constant | OpCode::GetLocal | OpCode::SetLocal
3739 | OpCode::GetUpvalue | OpCode::SetUpvalue | OpCode::LookupWith
3740 | OpCode::GetAttr | OpCode::HasAttr | OpCode::MakeAttrs
3741 | OpCode::SelectOrDefault | OpCode::MakeList
3742 | OpCode::Jump | OpCode::JumpIfFalse | OpCode::JumpIfTrue
3743 | OpCode::Interpolate => 3,
3744 OpCode::GetLocalAttr | OpCode::GetLocalCall | OpCode::CallBuiltin => 5,
3746 OpCode::MakeClosure => {
3748 if pos + 5 <= code.len() {
3749 let uv_count = u16::from_le_bytes([code[pos + 3], code[pos + 4]]) as usize;
3750 5 + uv_count * 3
3751 } else {
3752 3 }
3754 }
3755 OpCode::MakeThunk => {
3757 if pos + 5 <= code.len() {
3758 let uv_count = u16::from_le_bytes([code[pos + 3], code[pos + 4]]) as usize;
3759 5 + uv_count * 3
3760 } else {
3761 3
3762 }
3763 }
3764 OpCode::PatchThunkUpvalues => {
3766 if pos + 5 <= code.len() {
3767 let uv_count = u16::from_le_bytes([code[pos + 3], code[pos + 4]]) as usize;
3768 5 + uv_count * 3
3769 } else {
3770 3
3771 }
3772 }
3773 OpCode::MakeLazyThunk => {
3775 if pos + 15 <= code.len() {
3776 let uv_count = u16::from_le_bytes([code[pos + 13], code[pos + 14]]) as usize;
3777 15 + uv_count * 3
3778 } else {
3779 3
3780 }
3781 }
3782 },
3783 None => 1, }
3785 }
3786 fn format_operands(code: &[u8], pos: usize, op: OpCode) -> String {
3788 let read_u16_at = |p: usize| -> Option<u16> {
3789 if p + 2 <= code.len() {
3790 Some(u16::from_le_bytes([code[p], code[p + 1]]))
3791 } else {
3792 None
3793 }
3794 };
3795 match op {
3796 OpCode::Constant | OpCode::GetLocal | OpCode::SetLocal
3797 | OpCode::GetUpvalue | OpCode::SetUpvalue | OpCode::LookupWith
3798 | OpCode::GetAttr | OpCode::HasAttr | OpCode::MakeAttrs
3799 | OpCode::SelectOrDefault | OpCode::MakeList
3800 | OpCode::Jump | OpCode::JumpIfFalse | OpCode::JumpIfTrue
3801 | OpCode::Interpolate => {
3802 read_u16_at(pos + 1).map_or(String::new(), |v| format!(" {v}"))
3803 }
3804 OpCode::GetLocalAttr => {
3805 let s = read_u16_at(pos + 1).unwrap_or(0);
3806 let k = read_u16_at(pos + 3).unwrap_or(0);
3807 format!(" slot={s} key={k}")
3808 }
3809 OpCode::GetLocalCall => {
3810 read_u16_at(pos + 1).map_or(String::new(), |v| format!(" slot={v}"))
3811 }
3812 OpCode::CallBuiltin => {
3813 let idx = read_u16_at(pos + 1).unwrap_or(0);
3814 let argc = read_u16_at(pos + 3).unwrap_or(0);
3815 format!(" idx={idx} argc={argc}")
3816 }
3817 OpCode::MakeThunk | OpCode::MakeClosure => {
3818 let ci = read_u16_at(pos + 1).unwrap_or(0);
3819 let uv = read_u16_at(pos + 3).unwrap_or(0);
3820 format!(" const={ci} upvals={uv}")
3821 }
3822 OpCode::PatchThunkUpvalues => {
3823 let s = read_u16_at(pos + 1).unwrap_or(0);
3824 let uv = read_u16_at(pos + 3).unwrap_or(0);
3825 format!(" slot={s} upvals={uv}")
3826 }
3827 _ => String::new(),
3828 }
3829 }
3830}
3831#[cfg(test)]
3832mod tests {
3833 use super::*;
3834 use crate::compiler::Compiler;
3835 use crate::value::StringKeyedValue;
3836 fn eval(input: &str) -> VMValue {
3837 let (chunk, mut interner) =
3838 Compiler::compile(input).unwrap_or_else(|e| panic!("compile '{input}': {e}"));
3839 VM::execute(chunk, &mut interner).unwrap_or_else(|e| panic!("execute '{input}': {e}"))
3840 }
3841 fn eval_full_helper(input: &str) -> crate::StringKeyedValue {
3842 let result =
3843 crate::eval_full(input).unwrap_or_else(|e| panic!("eval_full '{input}': {e}"));
3844 result.to_string_keyed()
3845 }
3846 fn eval_err(input: &str) -> VMError {
3847 let (chunk, mut interner) =
3848 Compiler::compile(input).unwrap_or_else(|e| panic!("compile '{input}': {e}"));
3849 VM::execute(chunk, &mut interner).unwrap_err()
3850 }
3851 #[test]
3869 fn vm_has_no_nixpkgs_lib_leaks() {
3870 const REMOVED_LEAKS: &[&str] = &[
3873 "concatStrings",
3874 "filterAttrs",
3875 "hasPrefix",
3876 "hasSuffix",
3877 "toLower",
3878 "toUpper",
3879 ];
3880
3881 let live = match eval("builtins.attrNames builtins") {
3882 VMValue::List(items) => items
3883 .iter()
3884 .map(|v| match v {
3885 VMValue::String(s) => s.to_string(),
3886 other => panic!("attrNames produced a non-string: {other:?}"),
3887 })
3888 .collect::<Vec<String>>(),
3889 other => panic!("attrNames must produce a list, got {other:?}"),
3890 };
3891
3892 assert!(
3896 live.len() >= 100,
3897 "the VM enumerated only {} builtins — that is not a usable subject, \
3898 so this test would pass without checking anything",
3899 live.len()
3900 );
3901
3902 let resurrected: Vec<&str> = REMOVED_LEAKS
3903 .iter()
3904 .filter(|n| live.iter().any(|l| l == *n))
3905 .copied()
3906 .collect();
3907
3908 assert_eq!(
3912 (REMOVED_LEAKS.len(), live.len() >= 100, resurrected.as_slice()),
3913 (6, true, [].as_slice()),
3914 "a removed nixpkgs `lib` leak is back in the VM: {resurrected:?} \
3915 (checked {} names against a {}-name VM registry)",
3916 REMOVED_LEAKS.len(),
3917 live.len()
3918 );
3919
3920 eprintln!("vm_has_no_nixpkgs_lib_leaks: VM registry is {} names", live.len());
3921 }
3922
3923 #[test]
3925 fn eval_integer() {
3926 assert_eq!(eval("42"), VMValue::Int(42));
3927 }
3928 #[test]
3929 fn eval_negative_integer() {
3930 assert_eq!(eval("-7"), VMValue::Int(-7));
3931 }
3932 #[test]
3933 fn eval_float() {
3934 assert_eq!(eval("3.14"), VMValue::Float(3.14));
3935 }
3936 #[test]
3937 fn eval_bool_true() {
3938 assert_eq!(eval("true"), VMValue::Bool(true));
3939 }
3940 #[test]
3941 fn eval_bool_false() {
3942 assert_eq!(eval("false"), VMValue::Bool(false));
3943 }
3944 #[test]
3945 fn eval_null() {
3946 assert_eq!(eval("null"), VMValue::Null);
3947 }
3948 #[test]
3949 fn eval_string() {
3950 assert_eq!(eval(r#""hello""#), VMValue::String("hello".to_string()));
3951 }
3952 #[test]
3954 fn eval_add_int() {
3955 assert_eq!(eval("1 + 2"), VMValue::Int(3));
3956 }
3957 #[test]
3958 fn eval_sub_int() {
3959 assert_eq!(eval("10 - 3"), VMValue::Int(7));
3960 }
3961 #[test]
3962 fn eval_mul_int() {
3963 assert_eq!(eval("3 * 4"), VMValue::Int(12));
3964 }
3965 #[test]
3966 fn eval_div_int() {
3967 assert_eq!(eval("10 / 3"), VMValue::Int(3));
3968 }
3969 #[test]
3970 fn eval_div_zero() {
3971 assert!(matches!(eval_err("1 / 0"), VMError::DivisionByZero));
3972 }
3973 #[test]
3974 fn eval_float_arithmetic() {
3975 assert_eq!(eval("1.5 + 2.5"), VMValue::Float(4.0));
3976 }
3977 #[test]
3978 fn eval_mixed_arithmetic() {
3979 assert_eq!(eval("1 + 2.0"), VMValue::Float(3.0));
3980 }
3981 #[test]
3982 fn eval_compound_arithmetic() {
3983 assert_eq!(eval("2 * 3 + 1"), VMValue::Int(7));
3984 }
3985 #[test]
3986 fn eval_negate_float() {
3987 assert_eq!(eval("-3.14"), VMValue::Float(-3.14));
3988 }
3989 #[test]
3990 fn eval_string_concat() {
3991 assert_eq!(
3992 eval(r#""hello" + " " + "world""#),
3993 VMValue::String("hello world".to_string())
3994 );
3995 }
3996 #[test]
3998 fn eval_equal() {
3999 assert_eq!(eval("1 == 1"), VMValue::Bool(true));
4000 assert_eq!(eval("1 == 2"), VMValue::Bool(false));
4001 }
4002 #[test]
4003 fn eval_not_equal() {
4004 assert_eq!(eval("1 != 2"), VMValue::Bool(true));
4005 assert_eq!(eval("1 != 1"), VMValue::Bool(false));
4006 }
4007 #[test]
4008 fn eval_less() {
4009 assert_eq!(eval("1 < 2"), VMValue::Bool(true));
4010 assert_eq!(eval("2 < 1"), VMValue::Bool(false));
4011 }
4012 #[test]
4013 fn eval_greater() {
4014 assert_eq!(eval("2 > 1"), VMValue::Bool(true));
4015 assert_eq!(eval("1 > 2"), VMValue::Bool(false));
4016 }
4017 #[test]
4018 fn eval_less_equal() {
4019 assert_eq!(eval("1 <= 1"), VMValue::Bool(true));
4020 assert_eq!(eval("1 <= 2"), VMValue::Bool(true));
4021 assert_eq!(eval("2 <= 1"), VMValue::Bool(false));
4022 }
4023 #[test]
4024 fn eval_greater_equal() {
4025 assert_eq!(eval("1 >= 1"), VMValue::Bool(true));
4026 assert_eq!(eval("2 >= 1"), VMValue::Bool(true));
4027 assert_eq!(eval("1 >= 2"), VMValue::Bool(false));
4028 }
4029 #[test]
4031 fn eval_not() {
4032 assert_eq!(eval("!true"), VMValue::Bool(false));
4033 assert_eq!(eval("!false"), VMValue::Bool(true));
4034 }
4035 #[test]
4036 fn eval_and_short_circuit() {
4037 assert_eq!(eval("true && true"), VMValue::Bool(true));
4038 assert_eq!(eval("true && false"), VMValue::Bool(false));
4039 assert_eq!(eval("false && true"), VMValue::Bool(false));
4040 }
4041 #[test]
4042 fn eval_or_short_circuit() {
4043 assert_eq!(eval("false || true"), VMValue::Bool(true));
4044 assert_eq!(eval("false || false"), VMValue::Bool(false));
4045 assert_eq!(eval("true || false"), VMValue::Bool(true));
4046 }
4047 #[test]
4048 fn eval_implication() {
4049 assert_eq!(eval("true -> true"), VMValue::Bool(true));
4050 assert_eq!(eval("true -> false"), VMValue::Bool(false));
4051 assert_eq!(eval("false -> true"), VMValue::Bool(true));
4052 assert_eq!(eval("false -> false"), VMValue::Bool(true));
4053 }
4054 #[test]
4056 fn eval_if_true() {
4057 assert_eq!(eval("if true then 1 else 2"), VMValue::Int(1));
4058 }
4059 #[test]
4060 fn eval_if_false() {
4061 assert_eq!(eval("if false then 1 else 2"), VMValue::Int(2));
4062 }
4063 #[test]
4064 fn eval_if_expression() {
4065 assert_eq!(
4066 eval("if 1 > 2 then \"yes\" else \"no\""),
4067 VMValue::String("no".to_string())
4068 );
4069 }
4070 #[test]
4071 fn eval_nested_if() {
4072 assert_eq!(
4073 eval("if true then (if false then 1 else 2) else 3"),
4074 VMValue::Int(2)
4075 );
4076 }
4077 #[test]
4079 fn eval_let_simple() {
4080 assert_eq!(eval("let x = 1; y = 2; in x + y"), VMValue::Int(3));
4081 }
4082 #[test]
4083 fn eval_let_nested() {
4084 assert_eq!(
4085 eval("let a = 10; in let b = 20; in a + b"),
4086 VMValue::Int(30)
4087 );
4088 }
4089 #[test]
4090 fn eval_let_shadow() {
4091 assert_eq!(eval("let x = 1; in let x = 2; in x"), VMValue::Int(2));
4092 }
4093 #[test]
4094 fn eval_let_with_expression() {
4095 assert_eq!(eval("let x = 2 * 3; in x + 1"), VMValue::Int(7));
4096 }
4097 #[test]
4099 fn eval_empty_list() {
4100 assert_eq!(eval("[]"), VMValue::List(vec![]));
4101 }
4102 #[test]
4103 fn eval_list() {
4104 assert_eq!(
4105 eval("[1 2 3]"),
4106 VMValue::List(vec![VMValue::Int(1), VMValue::Int(2), VMValue::Int(3)])
4107 );
4108 }
4109 #[test]
4110 fn eval_list_concat() {
4111 assert_eq!(
4112 eval("[1 2] ++ [3 4]"),
4113 VMValue::List(vec![
4114 VMValue::Int(1),
4115 VMValue::Int(2),
4116 VMValue::Int(3),
4117 VMValue::Int(4),
4118 ])
4119 );
4120 }
4121 #[test]
4122 fn eval_list_concat_with_inline_map() {
4123 assert_eq!(
4127 eval("[1] ++ builtins.map (a: a) [2 3]"),
4128 VMValue::List(vec![VMValue::Int(1), VMValue::Int(2), VMValue::Int(3)])
4129 );
4130 }
4131 #[test]
4132 fn eval_list_concat_with_inline_map_attrsets() {
4133 let result = eval(r#"[{ x = 1; }] ++ builtins.map (a: { v = a; }) ["a" "b"]"#);
4135 match result {
4136 VMValue::List(items) => assert_eq!(items.len(), 3),
4137 other => panic!("expected list, got {:?}", other.type_name()),
4138 }
4139 }
4140 #[test]
4141 fn eval_list_concat_with_inline_filter() {
4142 assert_eq!(
4144 eval("[0] ++ builtins.filter (x: x > 1) [1 2 3]"),
4145 VMValue::List(vec![VMValue::Int(0), VMValue::Int(2), VMValue::Int(3)])
4146 );
4147 }
4148 #[test]
4149 fn eval_list_mixed() {
4150 assert_eq!(
4151 eval(r#"[1 "hello" true]"#),
4152 VMValue::List(vec![
4153 VMValue::Int(1),
4154 VMValue::String("hello".to_string()),
4155 VMValue::Bool(true),
4156 ])
4157 );
4158 }
4159 #[test]
4161 fn eval_empty_attrset() {
4162 assert_eq!(eval("{ }"), VMValue::Attrs(BTreeMap::new()));
4163 }
4164 #[test]
4165 fn eval_attrset() {
4166 let result = eval_full_helper("{ a = 1; b = 2; }");
4167 let mut expected = BTreeMap::new();
4168 expected.insert("a".to_string(), crate::StringKeyedValue::Int(1));
4169 expected.insert("b".to_string(), crate::StringKeyedValue::Int(2));
4170 assert_eq!(result, crate::StringKeyedValue::Attrs(expected));
4171 }
4172 #[test]
4173 fn eval_attrset_select() {
4174 assert_eq!(eval("{ a = 1; b = 2; }.a"), VMValue::Int(1));
4175 }
4176 #[test]
4177 fn eval_attrset_update() {
4178 let result = eval_full_helper("{ a = 1; } // { b = 2; }");
4179 let mut expected = BTreeMap::new();
4180 expected.insert("a".to_string(), crate::StringKeyedValue::Int(1));
4181 expected.insert("b".to_string(), crate::StringKeyedValue::Int(2));
4182 assert_eq!(result, crate::StringKeyedValue::Attrs(expected));
4183 }
4184 #[test]
4185 fn eval_attrset_update_override() {
4186 assert_eq!(eval("({ a = 1; } // { a = 2; }).a"), VMValue::Int(2));
4187 }
4188 #[test]
4189 fn eval_has_attr_true() {
4190 assert_eq!(eval("{ a = 1; } ? a"), VMValue::Bool(true));
4191 }
4192 #[test]
4193 fn eval_has_attr_false() {
4194 assert_eq!(eval("{ a = 1; } ? b"), VMValue::Bool(false));
4195 }
4196 #[test]
4197 fn eval_select_or_default() {
4198 assert_eq!(eval("{ a = 1; }.b or 0"), VMValue::Int(0));
4199 assert_eq!(eval("{ a = 1; }.a or 0"), VMValue::Int(1));
4200 }
4201 #[test]
4202 fn eval_dyn_select_or_default_missing() {
4203 assert_eq!(
4205 eval(r#"let x = "missing"; in { a = 1; }.${ x } or 99"#),
4206 VMValue::Int(99),
4207 );
4208 }
4209 #[test]
4210 fn eval_dyn_select_or_default_found() {
4211 assert_eq!(
4213 eval(r#"let x = "a"; in { a = 42; }.${ x } or 99"#),
4214 VMValue::Int(42),
4215 );
4216 }
4217 #[test]
4218 fn eval_dyn_select_or_default_dotted_key() {
4219 assert_eq!(
4221 eval(r#"let x = "a.b"; in { "a.b" = 7; }.${ x } or 0"#),
4222 VMValue::Int(7),
4223 );
4224 }
4225 #[test]
4226 fn eval_dyn_select_or_default_special_chars() {
4227 assert_eq!(
4229 eval(r#"let x = "armv8.3-a+crypto+sha2"; in { "armv8-a" = 1; }.${ x } or 0"#),
4230 VMValue::Int(0),
4231 );
4232 }
4233 #[test]
4234 fn eval_dyn_select_or_default_non_attrset() {
4235 assert_eq!(
4237 eval(r#"let x = "a"; base = 42; in base.${ x } or 99"#),
4238 VMValue::Int(99),
4239 );
4240 }
4241 #[test]
4243 fn eval_identity_lambda() {
4244 assert_eq!(eval("(x: x) 42"), VMValue::Int(42));
4245 }
4246 #[test]
4247 fn eval_lambda_arithmetic() {
4248 assert_eq!(eval("(x: x + 1) 5"), VMValue::Int(6));
4249 }
4250 #[test]
4251 #[ignore = "requires upvalue capture (Phase 2)"]
4252 fn eval_curried_lambda() {
4253 assert_eq!(eval("(x: y: x + y) 3 4"), VMValue::Int(7));
4254 }
4255 #[test]
4256 fn eval_let_lambda() {
4257 assert_eq!(
4258 eval("let f = x: x * 2; in f 5"),
4259 VMValue::Int(10)
4260 );
4261 }
4262 #[test]
4263 fn eval_pattern_lambda() {
4264 assert_eq!(eval("({ a, b }: a + b) { a = 3; b = 4; }"), VMValue::Int(7));
4265 }
4266 #[test]
4267 fn eval_pattern_lambda_default() {
4268 assert_eq!(
4269 eval("({ a, b ? 10 }: a + b) { a = 5; }"),
4270 VMValue::Int(15)
4271 );
4272 }
4273 #[test]
4274 fn eval_lambda_with_let() {
4275 assert_eq!(
4276 eval("let inc = x: x + 1; double = x: x * 2; in double (inc 3)"),
4277 VMValue::Int(8)
4278 );
4279 }
4280 #[test]
4282 fn eval_assert_pass() {
4283 assert_eq!(eval("assert true; 42"), VMValue::Int(42));
4284 }
4285 #[test]
4286 fn eval_assert_fail() {
4287 assert!(matches!(eval_err("assert false; 42"), VMError::AssertionFailed));
4288 }
4289 #[test]
4291 fn deep_eq_attrs_with_thunked_values() {
4292 assert_eq!(
4295 eval("let a = { x = 1; }; b = { x = 1; }; in a == b"),
4296 VMValue::Bool(true)
4297 );
4298 }
4299 #[test]
4300 fn deep_eq_attrs_different_values() {
4301 assert_eq!(
4302 eval("let a = { x = 1; }; b = { x = 2; }; in a == b"),
4303 VMValue::Bool(false)
4304 );
4305 }
4306 #[test]
4307 fn deep_eq_nested_attrs() {
4308 assert_eq!(
4309 eval("let a = { x = { y = 1; }; }; b = { x = { y = 1; }; }; in a == b"),
4310 VMValue::Bool(true)
4311 );
4312 }
4313 #[test]
4314 fn deep_eq_list_with_thunked_elements() {
4315 assert_eq!(
4316 eval("let a = [ 1 2 ]; b = [ 1 2 ]; in a == b"),
4317 VMValue::Bool(true)
4318 );
4319 }
4320 #[test]
4322 fn eval_elem_thunked_attrsets() {
4323 assert_eq!(
4325 eval("let a = { x = 1; }; b = { x = 1; }; in builtins.elem a [ b ]"),
4326 VMValue::Bool(true)
4327 );
4328 }
4329 #[test]
4330 fn eval_elem_basic_int() {
4331 assert_eq!(
4332 eval("builtins.elem 2 [ 1 2 3 ]"),
4333 VMValue::Bool(true)
4334 );
4335 }
4336 #[test]
4337 fn eval_elem_missing() {
4338 assert_eq!(
4339 eval("builtins.elem 4 [ 1 2 3 ]"),
4340 VMValue::Bool(false)
4341 );
4342 }
4343 #[test]
4344 fn eval_elem_string() {
4345 assert_eq!(
4346 eval(r#"builtins.elem "b" [ "a" "b" "c" ]"#),
4347 VMValue::Bool(true)
4348 );
4349 }
4350 #[test]
4351 fn eval_elem_thunked_list_elements() {
4352 assert_eq!(
4353 eval("let x = 1; in builtins.elem 1 [ x ]"),
4354 VMValue::Bool(true)
4355 );
4356 }
4357 #[test]
4359 fn eval_string_interpolation() {
4360 assert_eq!(
4361 eval(r#"let x = "world"; in "hello ${x}""#),
4362 VMValue::String("hello world".to_string()),
4363 );
4364 }
4365 #[test]
4366 #[ignore = "requires builtins.toString (Phase 2)"]
4367 fn eval_string_interpolation_int() {
4368 assert_eq!(
4369 eval(r#"let n = 42; in "value: ${toString n}""#),
4370 VMValue::String("value: 42".to_string()),
4371 );
4372 }
4373 #[test]
4375 fn eval_absolute_path() {
4376 assert_eq!(eval("/tmp/x"), VMValue::Path("/tmp/x".to_string()));
4377 }
4378 #[test]
4380 fn eval_fibonacci_like() {
4381 assert_eq!(
4382 eval("let a = 1; b = 1; c = a + b; d = b + c; e = c + d; in e"),
4383 VMValue::Int(5)
4384 );
4385 }
4386 #[test]
4387 fn eval_nested_attrset_select() {
4388 assert_eq!(
4389 eval("{ a = { b = 42; }; }.a.b"),
4390 VMValue::Int(42)
4391 );
4392 }
4393 #[test]
4394 fn eval_let_with_attrset() {
4395 assert_eq!(
4396 eval("let set = { x = 10; y = 20; }; in set.x + set.y"),
4397 VMValue::Int(30)
4398 );
4399 }
4400 #[test]
4401 fn eval_conditional_attrset() {
4402 assert_eq!(
4403 eval("(if true then { a = 1; } else { a = 2; }).a"),
4404 VMValue::Int(1)
4405 );
4406 }
4407 #[test]
4409 fn builtin_length() {
4410 assert_eq!(eval("builtins.length [1 2 3]"), VMValue::Int(3));
4411 }
4412 #[test]
4413 fn builtin_length_empty() {
4414 assert_eq!(eval("builtins.length []"), VMValue::Int(0));
4415 }
4416 #[test]
4417 fn builtin_head() {
4418 assert_eq!(eval("builtins.head [10 20 30]"), VMValue::Int(10));
4419 }
4420 #[test]
4421 fn builtin_tail() {
4422 let result = eval_full_helper("builtins.tail [1 2 3]");
4423 assert_eq!(
4424 result,
4425 StringKeyedValue::List(vec![StringKeyedValue::Int(2), StringKeyedValue::Int(3)])
4426 );
4427 }
4428 #[test]
4429 fn builtin_type_of_int() {
4430 assert_eq!(
4431 eval("builtins.typeOf 42"),
4432 VMValue::String("int".to_string())
4433 );
4434 }
4435 #[test]
4436 fn builtin_type_of_string() {
4437 assert_eq!(
4438 eval("builtins.typeOf \"hello\""),
4439 VMValue::String("string".to_string())
4440 );
4441 }
4442 #[test]
4443 fn builtin_type_of_bool() {
4444 assert_eq!(
4445 eval("builtins.typeOf true"),
4446 VMValue::String("bool".to_string())
4447 );
4448 }
4449 #[test]
4450 fn builtin_type_of_null() {
4451 assert_eq!(
4452 eval("builtins.typeOf null"),
4453 VMValue::String("null".to_string())
4454 );
4455 }
4456 #[test]
4457 fn builtin_type_of_list() {
4458 assert_eq!(
4459 eval("builtins.typeOf [1 2]"),
4460 VMValue::String("list".to_string())
4461 );
4462 }
4463 #[test]
4464 fn builtin_type_of_set() {
4465 assert_eq!(
4466 eval("builtins.typeOf { a = 1; }"),
4467 VMValue::String("set".to_string())
4468 );
4469 }
4470 #[test]
4471 fn builtin_type_of_lambda() {
4472 assert_eq!(
4473 eval("builtins.typeOf (x: x)"),
4474 VMValue::String("lambda".to_string())
4475 );
4476 }
4477 #[test]
4478 fn builtin_is_int() {
4479 assert_eq!(eval("builtins.isInt 42"), VMValue::Bool(true));
4480 assert_eq!(
4481 eval("builtins.isInt \"hello\""),
4482 VMValue::Bool(false)
4483 );
4484 }
4485 #[test]
4486 fn builtin_is_string() {
4487 assert_eq!(eval("builtins.isString \"hi\""), VMValue::Bool(true));
4488 assert_eq!(eval("builtins.isString 42"), VMValue::Bool(false));
4489 }
4490 #[test]
4491 fn builtin_is_list() {
4492 assert_eq!(eval("builtins.isList [1]"), VMValue::Bool(true));
4493 assert_eq!(eval("builtins.isList 42"), VMValue::Bool(false));
4494 }
4495 #[test]
4496 fn builtin_is_attrs() {
4497 assert_eq!(
4498 eval("builtins.isAttrs { a = 1; }"),
4499 VMValue::Bool(true)
4500 );
4501 assert_eq!(eval("builtins.isAttrs 42"), VMValue::Bool(false));
4502 }
4503 #[test]
4504 fn builtin_is_function() {
4505 assert_eq!(
4506 eval("builtins.isFunction (x: x)"),
4507 VMValue::Bool(true)
4508 );
4509 assert_eq!(eval("builtins.isFunction 42"), VMValue::Bool(false));
4510 }
4511 #[test]
4512 fn builtin_is_bool() {
4513 assert_eq!(eval("builtins.isBool true"), VMValue::Bool(true));
4514 assert_eq!(eval("builtins.isBool 42"), VMValue::Bool(false));
4515 }
4516 #[test]
4517 fn builtin_is_null() {
4518 assert_eq!(eval("builtins.isNull null"), VMValue::Bool(true));
4519 assert_eq!(eval("builtins.isNull 42"), VMValue::Bool(false));
4520 }
4521 #[test]
4522 fn builtin_string_length() {
4523 assert_eq!(
4524 eval("builtins.stringLength \"hello\""),
4525 VMValue::Int(5)
4526 );
4527 }
4528 #[test]
4529 fn builtin_to_string_int() {
4530 assert_eq!(
4531 eval("builtins.toString 42"),
4532 VMValue::String("42".to_string())
4533 );
4534 }
4535 #[test]
4536 fn builtin_to_string_bool() {
4537 assert_eq!(
4538 eval("builtins.toString true"),
4539 VMValue::String("1".to_string())
4540 );
4541 }
4542 #[test]
4543 fn builtin_throw() {
4544 let result = eval_err("builtins.throw \"test error\"");
4545 assert!(matches!(result, VMError::Throw(_)));
4546 }
4547 #[test]
4548 fn builtin_abort() {
4549 let result = eval_err("builtins.abort \"fatal\"");
4550 assert!(matches!(result, VMError::Throw(_)));
4551 }
4552 #[test]
4553 fn builtin_add_curried() {
4554 assert_eq!(eval("builtins.add 3 4"), VMValue::Int(7));
4555 }
4556 #[test]
4557 fn builtin_sub_curried() {
4558 assert_eq!(eval("builtins.sub 10 3"), VMValue::Int(7));
4559 }
4560 #[test]
4561 fn builtin_mul_curried() {
4562 assert_eq!(eval("builtins.mul 6 7"), VMValue::Int(42));
4563 }
4564 #[test]
4565 fn builtin_div_curried() {
4566 assert_eq!(eval("builtins.div 42 6"), VMValue::Int(7));
4567 }
4568 #[test]
4569 fn builtin_elem_at() {
4570 assert_eq!(eval("builtins.elemAt [10 20 30] 1"), VMValue::Int(20));
4571 }
4572 #[test]
4573 fn builtin_elem() {
4574 assert_eq!(eval("builtins.elem 2 [1 2 3]"), VMValue::Bool(true));
4575 assert_eq!(eval("builtins.elem 5 [1 2 3]"), VMValue::Bool(false));
4576 }
4577 #[test]
4578 fn builtin_concat_lists() {
4579 let result = eval_full_helper("builtins.concatLists [[1 2] [3 4]]");
4580 assert_eq!(
4581 result,
4582 StringKeyedValue::List(vec![
4583 StringKeyedValue::Int(1),
4584 StringKeyedValue::Int(2),
4585 StringKeyedValue::Int(3),
4586 StringKeyedValue::Int(4),
4587 ])
4588 );
4589 }
4590 #[test]
4591 fn builtin_concat_strings_sep() {
4592 assert_eq!(
4593 eval("builtins.concatStringsSep \", \" [\"a\" \"b\" \"c\"]"),
4594 VMValue::String("a, b, c".to_string())
4595 );
4596 }
4597 #[test]
4598 fn builtin_from_json() {
4599 assert_eq!(
4600 eval("builtins.fromJSON \"42\""),
4601 VMValue::Int(42)
4602 );
4603 assert_eq!(
4604 eval("builtins.fromJSON \"true\""),
4605 VMValue::Bool(true)
4606 );
4607 }
4608 #[test]
4609 fn builtin_seq() {
4610 assert_eq!(eval("builtins.seq 1 42"), VMValue::Int(42));
4611 }
4612 #[test]
4613 fn builtin_deep_seq() {
4614 assert_eq!(eval("builtins.deepSeq [1 2] 42"), VMValue::Int(42));
4615 }
4616 #[test]
4617 fn builtin_trace() {
4618 assert_eq!(
4619 eval("builtins.trace \"debug\" 42"),
4620 VMValue::Int(42)
4621 );
4622 }
4623 #[test]
4624 fn builtin_ceil_floor() {
4625 assert_eq!(eval("builtins.ceil 3.2"), VMValue::Int(4));
4626 assert_eq!(eval("builtins.floor 3.8"), VMValue::Int(3));
4627 }
4628 #[test]
4629 fn builtin_bit_ops() {
4630 assert_eq!(eval("builtins.bitAnd 12 10"), VMValue::Int(8));
4631 assert_eq!(eval("builtins.bitOr 12 10"), VMValue::Int(14));
4632 assert_eq!(eval("builtins.bitXor 12 10"), VMValue::Int(6));
4633 }
4634 #[test]
4635 fn builtin_intersect_attrs() {
4636 let result =
4637 eval_full_helper("builtins.intersectAttrs { a = 1; b = 2; } { a = 10; c = 30; }");
4638 match result {
4639 StringKeyedValue::Attrs(map) => {
4640 assert_eq!(map.get("a"), Some(&StringKeyedValue::Int(10)));
4641 assert!(!map.contains_key("b"));
4642 assert!(!map.contains_key("c"));
4643 }
4644 _ => panic!("expected Attrs, got {result:?}"),
4645 }
4646 }
4647 #[test]
4648 fn builtin_attr_values() {
4649 let result = eval_full_helper("builtins.attrValues { a = 1; b = 2; }");
4650 match result {
4651 StringKeyedValue::List(items) => {
4652 assert_eq!(items.len(), 2);
4653 assert!(items.contains(&StringKeyedValue::Int(1)));
4654 assert!(items.contains(&StringKeyedValue::Int(2)));
4655 }
4656 _ => panic!("expected List, got {result:?}"),
4657 }
4658 }
4659 #[test]
4660 fn builtin_to_int() {
4661 assert_eq!(eval("builtins.toInt \"42\""), VMValue::Int(42));
4662 }
4663 #[test]
4664 fn builtin_replace_strings() {
4665 assert_eq!(
4666 eval("builtins.replaceStrings [\"o\"] [\"0\"] \"foo\""),
4667 VMValue::String("f00".to_string())
4668 );
4669 }
4670 #[test]
4671 fn builtin_substring() {
4672 assert_eq!(
4673 eval("builtins.substring 1 3 \"hello\""),
4674 VMValue::String("ell".to_string())
4675 );
4676 }
4677 #[test]
4679 fn import_basic() {
4680 let dir = tempfile::tempdir().unwrap();
4681 let file_path = dir.path().join("test.nix");
4682 std::fs::write(&file_path, "42").unwrap();
4683 let nix_expr = format!("import {}", file_path.display());
4684 assert_eq!(eval(&nix_expr), VMValue::Int(42));
4685 }
4686 #[test]
4687 fn import_cached() {
4688 let dir = tempfile::tempdir().unwrap();
4689 let file_path = dir.path().join("cached.nix");
4690 std::fs::write(&file_path, "{ x = 1; }").unwrap();
4691 let nix_expr = format!(
4692 "let a = import {}; b = import {}; in a == b",
4693 file_path.display(),
4694 file_path.display()
4695 );
4696 assert_eq!(eval(&nix_expr), VMValue::Bool(true));
4697 }
4698 #[test]
4699 fn import_attrset() {
4700 let dir = tempfile::tempdir().unwrap();
4701 let file_path = dir.path().join("attrs.nix");
4702 std::fs::write(&file_path, "{ greeting = \"hello\"; }").unwrap();
4703 let nix_expr = format!("(import {}).greeting", file_path.display());
4704 assert_eq!(eval(&nix_expr), VMValue::String("hello".to_string()));
4705 }
4706 #[test]
4707 fn import_directory_default_nix() {
4708 let dir = tempfile::tempdir().unwrap();
4710 let sub = dir.path().join("mylib");
4711 std::fs::create_dir(&sub).unwrap();
4712 std::fs::write(sub.join("default.nix"), "{ x = 42; }").unwrap();
4713 let nix_expr = format!("(import {}).x", sub.display());
4714 assert_eq!(eval(&nix_expr), VMValue::Int(42));
4715 }
4716 #[test]
4717 fn import_directory_cached() {
4718 let dir = tempfile::tempdir().unwrap();
4720 let sub = dir.path().join("lib");
4721 std::fs::create_dir(&sub).unwrap();
4722 std::fs::write(sub.join("default.nix"), "{ v = 99; }").unwrap();
4723 let nix_expr = format!(
4724 "let a = import {}; b = import {}; in a == b",
4725 sub.display(),
4726 sub.display()
4727 );
4728 assert_eq!(eval(&nix_expr), VMValue::Bool(true));
4729 }
4730 #[test]
4731 fn import_directory_nested() {
4732 let dir = tempfile::tempdir().unwrap();
4734 let lib = dir.path().join("lib");
4735 let sub = lib.join("sub");
4736 std::fs::create_dir_all(&sub).unwrap();
4737 std::fs::write(sub.join("default.nix"), "{ val = 7; }").unwrap();
4738 std::fs::write(
4739 lib.join("default.nix"),
4740 &format!("(import {}).val + 3", sub.display()),
4741 )
4742 .unwrap();
4743 let nix_expr = format!("import {}", lib.display());
4744 assert_eq!(eval(&nix_expr), VMValue::Int(10));
4745 }
4746 #[test]
4748 fn lazy_unused_throw_in_attrset() {
4749 assert_eq!(
4750 eval("let s = { a = 1; }; in s.a"),
4751 VMValue::Int(1)
4752 );
4753 }
4754 #[test]
4755 fn lazy_unused_let_binding() {
4756 assert_eq!(eval("let x = 1; y = 2; in x"), VMValue::Int(1));
4757 }
4758 #[test]
4760 fn import_forces_thunk_before_type_check() {
4761 let dir = tempfile::tempdir().unwrap();
4764 let file_path = dir.path().join("forced.nix");
4765 std::fs::write(&file_path, "99").unwrap();
4766 let nix_expr = format!(
4767 "let p = {}; in import p",
4768 file_path.display()
4769 );
4770 assert_eq!(eval(&nix_expr), VMValue::Int(99));
4771 }
4772 #[test]
4773 fn import_with_path_value_succeeds() {
4774 let dir = tempfile::tempdir().unwrap();
4775 let file_path = dir.path().join("pathval.nix");
4776 std::fs::write(&file_path, "\"from-path\"").unwrap();
4777 let nix_expr = format!("import {}", file_path.display());
4778 assert_eq!(
4779 eval(&nix_expr),
4780 VMValue::String("from-path".to_string())
4781 );
4782 }
4783 #[test]
4784 fn import_with_string_value_succeeds() {
4785 let dir = tempfile::tempdir().unwrap();
4786 let file_path = dir.path().join("strval.nix");
4787 std::fs::write(&file_path, "\"from-string\"").unwrap();
4788 let nix_expr = format!(
4789 "let s = \"{}\"; in import s",
4790 file_path.display()
4791 );
4792 assert_eq!(
4793 eval(&nix_expr),
4794 VMValue::String("from-string".to_string())
4795 );
4796 }
4797 #[test]
4799 fn tail_call_deep_recursion_via_import() {
4800 let dir = tempfile::tempdir().unwrap();
4804 let file_path = dir.path().join("countdown.nix");
4805 std::fs::write(
4806 &file_path,
4807 "{ f, n }: if n == 0 then 0 else f { inherit f; n = n - 1; }",
4808 )
4809 .unwrap();
4810 let nix_expr = format!(
4813 "let g = import {}; in g {{ f = g; n = 2000; }}",
4814 file_path.display()
4815 );
4816 assert_eq!(eval(&nix_expr), VMValue::Int(0));
4817 }
4818 #[test]
4819 fn tail_call_simple_lambda_chain() {
4820 assert_eq!(
4824 eval("let g = x: x + 1; f = x: g x; in f 41"),
4825 VMValue::Int(42)
4826 );
4827 }
4828 #[test]
4829 fn tail_call_if_branches() {
4830 assert_eq!(
4833 eval("let f = x: if x > 0 then x else x + 1; in f 10"),
4834 VMValue::Int(10)
4835 );
4836 assert_eq!(
4837 eval("let f = x: if x > 0 then x else x + 1; in f 0"),
4838 VMValue::Int(1)
4839 );
4840 }
4841 #[test]
4843 fn builtin_get_env_returns_value() {
4844 unsafe { std::env::set_var("SUI_TEST_VAR", "hello_sui") };
4847 assert_eq!(
4848 eval("builtins.getEnv \"SUI_TEST_VAR\""),
4849 VMValue::String("hello_sui".to_string())
4850 );
4851 unsafe { std::env::remove_var("SUI_TEST_VAR") };
4852 }
4853 #[test]
4854 fn builtin_get_env_missing_returns_empty() {
4855 unsafe { std::env::remove_var("SUI_NONEXISTENT_VAR_12345") };
4858 assert_eq!(
4859 eval("builtins.getEnv \"SUI_NONEXISTENT_VAR_12345\""),
4860 VMValue::String(String::new())
4861 );
4862 }
4863 #[test]
4864 fn builtin_try_eval_success() {
4865 let result = eval_full_helper("builtins.tryEval 42");
4867 match result {
4868 StringKeyedValue::Attrs(map) => {
4869 assert_eq!(
4870 map.get("success"),
4871 Some(&StringKeyedValue::Bool(true))
4872 );
4873 assert_eq!(
4874 map.get("value"),
4875 Some(&StringKeyedValue::Int(42))
4876 );
4877 }
4878 _ => panic!("expected Attrs, got {result:?}"),
4879 }
4880 }
4881 #[test]
4882 fn builtin_try_eval_with_non_throwing_expr() {
4883 let result = eval_full_helper(
4886 "builtins.tryEval (1 + 2)"
4887 );
4888 match result {
4889 StringKeyedValue::Attrs(map) => {
4890 assert_eq!(
4891 map.get("success"),
4892 Some(&StringKeyedValue::Bool(true))
4893 );
4894 assert_eq!(
4895 map.get("value"),
4896 Some(&StringKeyedValue::Int(3))
4897 );
4898 }
4899 _ => panic!("expected Attrs, got {result:?}"),
4900 }
4901 }
4902 #[test]
4903 fn builtin_try_eval_with_throw_catches() {
4904 let result = eval_full_helper(
4910 "let bad = builtins.throw \"oops\"; in builtins.tryEval bad"
4911 );
4912 match result {
4913 StringKeyedValue::Attrs(map) => {
4914 assert_eq!(
4915 map.get("success"),
4916 Some(&StringKeyedValue::Bool(false))
4917 );
4918 assert_eq!(
4919 map.get("value"),
4920 Some(&StringKeyedValue::Bool(false))
4921 );
4922 }
4923 _ => panic!("expected Attrs, got {result:?}"),
4924 }
4925 }
4926 #[test]
4928 fn if_else_in_let_body_stack_depth() {
4929 assert_eq!(
4932 eval("let a = 1; in if a == 1 then 10 else 20"),
4933 VMValue::Int(10),
4934 );
4935 }
4936 #[test]
4937 fn nested_let_with_if_else() {
4938 assert_eq!(
4940 eval(r#"
4941 let
4942 a = 1;
4943 b = if a == 1 then 2 else 3;
4944 in
4945 let c = b + 10; in c
4946 "#),
4947 VMValue::Int(12),
4948 );
4949 }
4950 #[test]
4951 fn short_circuit_and_in_let_body() {
4952 assert_eq!(
4954 eval("let x = true; in x && false"),
4955 VMValue::Bool(false),
4956 );
4957 }
4958 #[test]
4959 fn short_circuit_or_in_let_body() {
4960 assert_eq!(
4961 eval("let x = false; in x || true"),
4962 VMValue::Bool(true),
4963 );
4964 }
4965 #[test]
4966 fn short_circuit_implication_in_let_body() {
4967 assert_eq!(
4969 eval("let x = false; in x -> 42"),
4970 VMValue::Bool(true),
4971 );
4972 }
4973 #[test]
4974 fn inherit_from_in_attrset_stack_depth() {
4975 assert_eq!(
4978 eval(r#"
4979 let
4980 src = { a = 1; b = 2; };
4981 result = { inherit (src) a b; c = 3; };
4982 in result.a + result.b + result.c
4983 "#),
4984 VMValue::Int(6),
4985 );
4986 }
4987 #[test]
4988 fn inherit_from_many_fields_stack_depth() {
4989 assert_eq!(
4992 eval(r#"
4993 let
4994 s = { w = 1; x = 2; y = 3; z = 4; };
4995 r = { inherit (s) w x y z; extra = 10; };
4996 in r.w + r.x + r.y + r.z + r.extra
4997 "#),
4998 VMValue::Int(20),
4999 );
5000 }
5001 #[test]
5002 fn if_else_followed_by_let_binding() {
5003 assert_eq!(
5007 eval(r#"
5008 let
5009 a = 1;
5010 b = 2;
5011 c = 3;
5012 in
5013 let
5014 x = if a == 1 then b else c;
5015 y = x + 100;
5016 in y
5017 "#),
5018 VMValue::Int(102),
5019 );
5020 }
5021 #[test]
5022 fn multi_segment_hasattr_stack_depth() {
5023 assert_eq!(
5026 eval(r#"
5027 let
5028 s = { a = { b = 1; }; };
5029 has = s ? a.b;
5030 val = if has then 42 else 0;
5031 in val
5032 "#),
5033 VMValue::Int(42),
5034 );
5035 }
5036 #[test]
5037 fn many_let_bindings_with_if_else() {
5038 assert_eq!(
5042 eval(r#"
5043 let
5044 a = 1;
5045 b = 2;
5046 c = 3;
5047 d = 4;
5048 e = 5;
5049 f = 6;
5050 g = 7;
5051 h = 8;
5052 i = 9;
5053 j = 10;
5054 in
5055 let
5056 x = if a == 1 then b else c;
5057 y = if d == 4 then e else f;
5058 z = if g == 7 then h else i;
5059 w = j;
5060 in x + y + z + w
5061 "#),
5062 VMValue::Int(25),
5063 );
5064 }
5065 #[test]
5066 fn import_in_pattern_default_stack_depth() {
5067 assert_eq!(
5073 eval(r#"
5074 let
5075 f = { a ? 1, b ? 2, c ? 3 }:
5076 a + b + c;
5077 in f {}
5078 "#),
5079 VMValue::Int(6),
5080 );
5081 }
5082 #[test]
5083 fn pattern_lambda_many_defaults_then_let() {
5084 assert_eq!(
5090 eval(r#"
5091 let
5092 mk = { a, b ? 10, c ? 20, d ? 30, e ? 40 }:
5093 let
5094 sum = a + b + c + d + e;
5095 doubled = sum + sum;
5096 in doubled;
5097 in mk { a = 1; }
5098 "#),
5099 VMValue::Int(202),
5100 );
5101 }
5102 #[test]
5104 fn rec_dotted_lambda_captures_sibling() {
5105 let result = eval_full_helper(
5110 r#"rec { types.a = 1; types.b = 2; f = _: types; }.f 0"#,
5111 );
5112 match result {
5113 StringKeyedValue::Attrs(ref m) => {
5114 assert_eq!(m.get("a"), Some(&StringKeyedValue::Int(1)));
5115 assert_eq!(m.get("b"), Some(&StringKeyedValue::Int(2)));
5116 }
5117 other => panic!("expected attrset, got {other:?}"),
5118 }
5119 }
5120 #[test]
5121 fn rec_dotted_lambda_attr_select() {
5122 assert_eq!(
5124 eval(r#"rec { types.a = 1; types.b = 2; f = x: types.b; result = f 0; }.result"#),
5125 VMValue::Int(2),
5126 );
5127 }
5128 #[test]
5129 fn rec_dotted_lambda_assert_check() {
5130 assert_eq!(
5134 eval(r#"
5135 rec {
5136 types.parsedPlatform = { check = _: true; };
5137 mkSystem = components:
5138 assert types.parsedPlatform.check components;
5139 components;
5140 result = mkSystem 42;
5141 }.result
5142 "#),
5143 VMValue::Int(42),
5144 );
5145 }
5146 #[test]
5147 fn let_lambda_captures_rec_sibling() {
5148 assert_eq!(
5151 eval(r#"let a = 1 + 1; f = _: a; in f 0"#),
5152 VMValue::Int(2),
5153 );
5154 }
5155 #[test]
5156 fn rec_dotted_multiple_lambdas() {
5157 assert_eq!(
5159 eval(r#"
5160 rec {
5161 a.x = 10;
5162 b.y = 20;
5163 f = _: a.x + b.y;
5164 result = f 0;
5165 }.result
5166 "#),
5167 VMValue::Int(30),
5168 );
5169 }
5170}