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 match crate::bridge::call_builtin_bridge(name, vec![sk]) {
2323 Ok(Some(result)) => {
2324 let vm_result = crate::builtins::string_keyed_to_vmvalue(
2325 &result,
2326 self.interner,
2327 );
2328 Ok(Some(NanBox::from_vmvalue(&vm_result)))
2329 }
2330 Ok(None) => {
2331 Ok(None)
2334 }
2335 Err(e) => Err(VMError::Internal(format!("bridge error in '{name}': {e}"))),
2336 }
2337 }
2338 "match" | "split" => {
2341 let forced = self.force_value(arg.clone())?;
2342 let pattern = match forced.to_vmvalue() {
2343 VMValue::String(s) => s,
2344 other => {
2345 return Err(VMError::TypeError {
2346 expected: "string",
2347 got: other.type_name(),
2348 context: name.to_string(),
2349 });
2350 }
2351 };
2352 let builtin_name = name.to_string();
2353 Ok(Some(NanBox::from_vmvalue(&VMValue::Builtin(
2354 crate::value::VMBuiltin {
2355 name: if name == "match" {
2356 "match<partial>"
2357 } else {
2358 "split<partial>"
2359 },
2360 func: Rc::new(move |args2| {
2361 let input = match &args2[0] {
2362 VMValue::String(s) => s.clone(),
2363 other => {
2364 return Err(VMError::TypeError {
2365 expected: "string",
2366 got: other.type_name(),
2367 context: builtin_name.clone(),
2368 });
2369 }
2370 };
2371 let sk_args = vec![
2373 crate::value::StringKeyedValue::String(pattern.clone()),
2374 crate::value::StringKeyedValue::String(input),
2375 ];
2376 match crate::bridge::call_builtin_bridge(&builtin_name, sk_args) {
2377 Ok(Some(result)) => {
2378 let mut tmp = crate::intern::Interner::new();
2379 Ok(crate::builtins::string_keyed_to_vmvalue(&result, &mut tmp))
2380 }
2381 Ok(None) => Err(VMError::Throw(format!(
2382 "{builtin_name}: requires bridge but no bridge is set"
2383 ))),
2384 Err(e) => Err(VMError::Internal(format!("bridge error in '{builtin_name}': {e}"))),
2385 }
2386 }),
2387 arity: 1,
2388 },
2389 ))))
2390 }
2391 _ => Ok(None),
2392 }
2393 }
2394 fn coerce_drv_env_value(&mut self, v: &VMValue) -> Option<String> {
2414 match v {
2415 VMValue::String(s) => Some(s.clone()),
2416 VMValue::Path(p) => Some(p.clone()),
2417 VMValue::Int(n) => Some(n.to_string()),
2418 VMValue::Float(f) => Some(format!("{f:.6}")),
2421 VMValue::Bool(true) => Some("1".to_string()),
2422 VMValue::Bool(false) => Some(String::new()),
2423 VMValue::Null => Some(String::new()),
2424 VMValue::List(items) => {
2425 let mut parts = Vec::with_capacity(items.len());
2426 for item in items {
2427 let forced = self
2429 .force_value(NanBox::from_vmvalue(item))
2430 .ok()?
2431 .to_vmvalue();
2432 parts.push(self.coerce_drv_env_value(&forced)?);
2433 }
2434 Some(parts.join(" "))
2435 }
2436 VMValue::Attrs(map) => {
2437 let to_string_sym = self.interner.intern("__toString");
2441 if map.contains_key(&to_string_sym) {
2442 return None;
2447 }
2448 let out_path_sym = self.interner.intern("outPath");
2449 let out_path = map.get(&out_path_sym)?;
2450 let forced = self
2451 .force_value(NanBox::from_vmvalue(out_path))
2452 .ok()?
2453 .to_vmvalue();
2454 self.coerce_drv_env_value(&forced)
2455 }
2456 _ => None,
2457 }
2458 }
2459 fn vm_build_derivation(&mut self, arg: NanBox) -> Result<NanBox, VMError> {
2461 use sui_compat::derivation::{Derivation, DerivationOutput};
2462 let attrs = match arg.as_attrs() {
2463 Some(a) => a.clone(),
2464 None => {
2465 return Err(VMError::TypeError {
2466 expected: "set",
2467 got: arg.type_name(),
2468 context: "derivation".to_string(),
2469 });
2470 }
2471 };
2472 let get_str = |attrs: &BTreeMap<Symbol, NanBox>,
2474 interner: &mut Interner,
2475 key: &str|
2476 -> Result<String, VMError> {
2477 let sym = interner.intern(key);
2478 let val = attrs.get(&sym).ok_or_else(|| {
2479 VMError::AttrNotFound(key.to_string())
2480 })?;
2481 match val.to_vmvalue() {
2482 VMValue::String(s) => Ok(s),
2483 other => Err(VMError::TypeError {
2484 expected: "string",
2485 got: other.type_name(),
2486 context: format!("derivation attr '{key}'"),
2487 }),
2488 }
2489 };
2490 let get_str_opt = |attrs: &BTreeMap<Symbol, NanBox>,
2491 interner: &mut Interner,
2492 key: &str|
2493 -> Result<Option<String>, VMError> {
2494 let sym = interner.intern(key);
2495 match attrs.get(&sym) {
2496 None => Ok(None),
2497 Some(val) => match val.to_vmvalue() {
2498 VMValue::String(s) => Ok(Some(s)),
2499 other => Err(VMError::TypeError {
2500 expected: "string",
2501 got: other.type_name(),
2502 context: format!("derivation attr '{key}'"),
2503 }),
2504 },
2505 }
2506 };
2507 let name = get_str(&attrs, self.interner, "name")?;
2508 let system = get_str(&attrs, self.interner, "system")?;
2509 let builder = get_str(&attrs, self.interner, "builder")?;
2510 let args_sym = self.interner.intern("args");
2519 let args_list: Vec<String> = if let Some(a) = attrs.get(&args_sym) {
2520 let forced_a = self.force_value(a.clone())?;
2521 let vmval = forced_a.to_vmvalue();
2522 match vmval {
2523 VMValue::List(l) => {
2524 let mut out = Vec::with_capacity(l.len());
2525 for item in &l {
2526 let forced = self.force_value(NanBox::from_vmvalue(item))?;
2528 match forced.to_vmvalue() {
2529 VMValue::String(s) => out.push(s.clone()),
2530 VMValue::Int(n) => out.push(n.to_string()),
2531 VMValue::Float(f) => out.push(format!("{f:.6}")),
2532 VMValue::Bool(true) => out.push("1".to_string()),
2533 VMValue::Bool(false) => out.push(String::new()),
2534 VMValue::Null => out.push(String::new()),
2535 VMValue::Path(p) => out.push(p.clone()),
2536 _ => out.push(String::new()),
2537 }
2538 }
2539 out
2540 }
2541 _ => Vec::new(),
2542 }
2543 } else {
2544 Vec::new()
2545 };
2546 let outputs_sym = self.interner.intern("outputs");
2555 let outputs: Vec<String> = if let Some(o) = attrs.get(&outputs_sym) {
2556 let forced_o = self.force_value(o.clone())?;
2557 match forced_o.to_vmvalue() {
2558 VMValue::List(l) => {
2559 let mut out = Vec::with_capacity(l.len());
2560 for item in &l {
2561 let forced = self.force_value(NanBox::from_vmvalue(item))?;
2562 if let VMValue::String(s) = forced.to_vmvalue() {
2563 out.push(s);
2564 }
2565 }
2566 if out.is_empty() {
2567 vec!["out".to_string()]
2568 } else {
2569 out
2570 }
2571 }
2572 _ => vec!["out".to_string()],
2573 }
2574 } else {
2575 vec!["out".to_string()]
2576 };
2577 let ignore_nulls_sym = self.interner.intern("__ignoreNulls");
2583 let ignore_nulls = attrs
2584 .get(&ignore_nulls_sym)
2585 .map(|v| self.force_value(v.clone()))
2586 .transpose()?
2587 .map(|v| matches!(v.to_vmvalue(), VMValue::Bool(true)))
2588 .unwrap_or(false);
2589
2590 let special = [
2599 "name", "system", "builder", "args",
2600 "__ignoreNulls", "__impure", "__contentAddressed",
2601 ];
2602 let special_syms: Vec<Symbol> = special
2603 .iter()
2604 .map(|s| self.interner.intern(s))
2605 .collect();
2606 let mut env_vars: BTreeMap<String, String> = BTreeMap::new();
2607 let env_keys: Vec<(Symbol, String)> = attrs
2610 .iter()
2611 .filter(|(k, _)| !special_syms.contains(k))
2612 .map(|(k, _)| (*k, self.interner.resolve(*k).to_string()))
2613 .collect();
2614 for (k, key_str) in env_keys {
2615 let Some(v) = attrs.get(&k) else { continue };
2616 let forced = self.force_value(v.clone())?;
2621 let fv = forced.to_vmvalue();
2622 if ignore_nulls && matches!(fv, VMValue::Null) {
2624 continue;
2625 }
2626 match self.coerce_drv_env_value(&fv) {
2632 Some(s) => {
2633 env_vars.insert(key_str, s);
2634 }
2635 None => continue,
2636 }
2637 }
2638 env_vars.insert("name".to_string(), name.clone());
2639 env_vars.insert("system".to_string(), system.clone());
2640 env_vars.insert("builder".to_string(), builder.clone());
2641 let output_hash_sym = self.interner.intern("outputHash");
2643 let is_fod = attrs.contains_key(&output_hash_sym);
2644 let mut drv = Derivation {
2645 outputs: BTreeMap::new(),
2646 input_derivations: BTreeMap::new(),
2647 input_sources: Vec::new(),
2648 system,
2649 builder,
2650 args: args_list,
2651 env: env_vars,
2652 };
2653 let (drv_path, out_paths, mut drv) = if is_fod {
2654 let raw_output_hash = get_str(&attrs, self.interner, "outputHash")?;
2655 let raw_algo = get_str_opt(&attrs, self.interner, "outputHashAlgo")?
2656 .unwrap_or_default();
2657 let output_hash_mode = get_str_opt(&attrs, self.interner, "outputHashMode")?
2658 .unwrap_or_else(|| "flat".to_string());
2659 let is_recursive =
2660 output_hash_mode == "recursive" || output_hash_mode == "nar";
2661 let output_hash_algo = if raw_algo.is_empty() {
2664 ["sha256", "sha512", "sha1", "md5"].iter()
2665 .find(|a| raw_output_hash.starts_with(&format!("{a}-")))
2666 .map(|s| (*s).to_string())
2667 .unwrap_or_else(|| "sha256".to_string())
2668 } else {
2669 raw_algo
2670 };
2671 let algo = sui_compat::hash::HashAlgorithm::from_nix_str(&output_hash_algo)
2675 .map_err(|e| VMError::Internal(format!(
2676 "derivation: invalid outputHashAlgo {output_hash_algo:?}: {e}",
2677 )))?;
2678 let parsed = sui_compat::hash::NixHash::parse_any(algo, &raw_output_hash)
2679 .map_err(|e| VMError::Internal(format!(
2680 "derivation: invalid outputHash {raw_output_hash:?}: {e}",
2681 )))?;
2682 let output_hash_hex = parsed.to_hex();
2683 let out_path = sui_compat::store_path::compute_fixed_output_hash(
2684 &output_hash_algo,
2685 &output_hash_hex,
2686 is_recursive,
2687 &name,
2688 );
2689 drv.outputs.insert(
2690 "out".to_string(),
2691 DerivationOutput {
2692 path: out_path.clone(),
2693 hash_algo: if is_recursive {
2694 format!("r:{output_hash_algo}")
2695 } else {
2696 output_hash_algo.clone()
2697 },
2698 hash: output_hash_hex,
2699 },
2700 );
2701 drv.env.insert("out".to_string(), out_path.clone());
2707
2708 let drv_content = drv.serialize();
2709 let drv_refs: Vec<String> = drv.input_derivations.keys().cloned()
2720 .chain(drv.input_sources.iter().cloned())
2721 .collect();
2722 let drv_path = sui_compat::store_path::compute_drv_path_with_refs(
2723 drv_content.as_bytes(), &name, &drv_refs);
2724
2725 let out_output = drv.outputs.get("out");
2733 let method_algo = out_output
2734 .map(|o| o.hash_algo.clone())
2735 .unwrap_or_default();
2736 let output_hash_hex = out_output
2737 .map(|o| o.hash.clone())
2738 .unwrap_or_default();
2739 let modulo_preimage =
2740 format!("fixed:out:{method_algo}:{output_hash_hex}:{out_path}");
2741 let modulo_hex: String = {
2742 use sha2::{Digest, Sha256};
2743 Sha256::digest(modulo_preimage.as_bytes())
2744 .iter()
2745 .map(|b| format!("{b:02x}"))
2746 .collect()
2747 };
2748 sui_spec::derivation::remember_modulo_hash(&drv_path, &modulo_hex);
2749
2750 let mut out_paths = BTreeMap::new();
2751 out_paths.insert("out".to_string(), out_path);
2752 (drv_path, out_paths, drv)
2753 } else {
2754 let algo = sui_spec::derivation::load_canonical().map_err(|e| {
2762 VMError::TypeError {
2763 expected: "valid derivation algorithm spec",
2764 got: "load error",
2765 context: format!("sui-spec: {e}"),
2766 }
2767 })?;
2768 let (drv_path, out_paths, drv_final) =
2769 sui_spec::derivation::apply(&algo, drv, outputs.clone(), &name)
2770 .map_err(|e| VMError::TypeError {
2771 expected: "derivation interpreter success",
2772 got: "interp error",
2773 context: format!("sui-spec: {e}"),
2774 })?;
2775 (drv_path, out_paths, drv_final)
2776 };
2777 for (output_name, output_path) in &out_paths {
2779 if let Some(output) = drv.outputs.get_mut(output_name) {
2780 if output.path.is_empty() {
2781 output.path.clone_from(output_path);
2782 }
2783 }
2784 drv.env.insert(output_name.clone(), output_path.clone());
2785 }
2786 let drv_content_final = drv.serialize();
2787 let store_dir = std::env::var("SUI_STORE_DIR")
2788 .unwrap_or_else(|_| "/nix/store".to_string());
2789 let disk_path = if store_dir != "/nix/store" {
2790 drv_path.replacen("/nix/store", &store_dir, 1)
2791 } else {
2792 drv_path.clone()
2793 };
2794 let drv_file = std::path::Path::new(&disk_path);
2795 if !drv_file.exists() {
2796 if let Some(parent) = drv_file.parent() {
2797 std::fs::create_dir_all(parent).ok();
2798 }
2799 match std::fs::write(drv_file, drv_content_final.as_bytes()) {
2800 Ok(()) => {}
2801 Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => {
2802 let fallback_dir = std::env::temp_dir().join("sui-drv-cache");
2803 std::fs::create_dir_all(&fallback_dir).ok();
2804 let fallback_path = fallback_dir.join(
2805 drv_file.file_name().unwrap_or_default(),
2806 );
2807 let _ = std::fs::write(&fallback_path, drv_content_final.as_bytes());
2808 }
2809 Err(e) => {
2810 return Err(VMError::Throw(format!(
2811 "derivation: failed to write {drv_path}: {e}"
2812 )));
2813 }
2814 }
2815 }
2816 let mut result: BTreeMap<Symbol, NanBox> = attrs.clone();
2818 let type_sym = self.interner.intern("type");
2819 result.insert(type_sym, NanBox::string("derivation".to_string()));
2820 let drv_path_sym = self.interner.intern("drvPath");
2821 result.insert(drv_path_sym, NanBox::string(drv_path.clone()));
2822 let drv_attrs_sym = self.interner.intern("drvAttrs");
2824 result.insert(drv_attrs_sym, NanBox::attrs(attrs));
2825 let primary_out = out_paths
2826 .get("out")
2827 .cloned()
2828 .or_else(|| out_paths.values().next().cloned())
2829 .unwrap_or_default();
2830 let out_path_sym = self.interner.intern("outPath");
2831 result.insert(out_path_sym, NanBox::string(primary_out));
2832 let output_name_sym = self.interner.intern("outputName");
2834 let primary_output_name = if out_paths.contains_key("out") { "out" }
2835 else { out_paths.keys().next().map(|s| s.as_str()).unwrap_or("out") };
2836 result.insert(output_name_sym, NanBox::string(primary_output_name.to_string()));
2837 let mut all_outputs: Vec<NanBox> = Vec::new();
2838 for (output_name, output_path) in &out_paths {
2839 let mut out_attrs: BTreeMap<Symbol, NanBox> = BTreeMap::new();
2840 out_attrs.insert(out_path_sym, NanBox::string(output_path.clone()));
2841 out_attrs.insert(drv_path_sym, NanBox::string(drv_path.clone()));
2842 out_attrs.insert(type_sym, NanBox::string("derivation".to_string()));
2843 out_attrs.insert(output_name_sym, NanBox::string(output_name.clone()));
2844 let name_sym = self.interner.intern("name");
2845 out_attrs.insert(name_sym, NanBox::string(name.clone()));
2846 let out_val = NanBox::attrs(out_attrs);
2847 all_outputs.push(out_val.clone());
2848 let out_sym = self.interner.intern(output_name);
2849 result.insert(out_sym, out_val);
2850 }
2851 let all_sym = self.interner.intern("all");
2853 result.insert(all_sym, NanBox::list(all_outputs));
2854 Ok(NanBox::attrs(result))
2855 }
2856 fn call_builtin_with_scoped_import_dispatch(
2858 &mut self,
2859 func: Rc<dyn Fn(Vec<VMValue>) -> Result<VMValue, VMError>>,
2860 arg: VMValue,
2861 ) -> Result<NanBox, VMError> {
2862 let arg = if let VMValue::Thunk(ref thunk) = arg {
2864 let nb = NanBox::from_vmvalue(&arg);
2865 self.force_value(nb)?.to_vmvalue()
2866 } else {
2867 arg
2868 };
2869 match func(vec![arg]) {
2870 Ok(result) => Ok(NanBox::from_vmvalue(&result)),
2871 Err(VMError::Throw(ref msg))
2872 if msg.starts_with("__scopedImport_dispatch__:") =>
2873 {
2874 let rest = &msg["__scopedImport_dispatch__:".len()..];
2875 if let Some(colon_pos) = rest.rfind(':') {
2876 let scope_nix = &rest[..colon_pos];
2877 let path = &rest[colon_pos + 1..];
2878 self.vm_scoped_import(scope_nix, path)
2879 } else {
2880 Err(VMError::Throw(msg.clone()))
2881 }
2882 }
2883 Err(e) => Err(e),
2884 }
2885 }
2886 fn vm_get_flake(&mut self, flake_ref: &str) -> Result<NanBox, VMError> {
2894 let resolved = FLAKE_RESOLVER.with(|r| {
2896 let borrow = r.borrow();
2897 if let Some(ref resolver) = *borrow {
2898 Some(resolver(flake_ref))
2899 } else {
2900 None
2901 }
2902 });
2903 if let Some(result) = resolved {
2904 let sk = result.map_err(|e| VMError::Throw(format!("getFlake: {e}")))?;
2905 return Ok(self.string_keyed_to_nanbox(&sk));
2906 }
2907 self.vm_get_flake_native(flake_ref)
2909 }
2910 fn string_keyed_to_nanbox(&mut self, sk: &crate::value::StringKeyedValue) -> NanBox {
2917 match sk {
2918 crate::value::StringKeyedValue::Null => NanBox::null(),
2919 crate::value::StringKeyedValue::Bool(b) => NanBox::bool(*b),
2920 crate::value::StringKeyedValue::Int(n) => NanBox::int(*n),
2921 crate::value::StringKeyedValue::Float(f) => NanBox::float(*f),
2922 crate::value::StringKeyedValue::String(s) => NanBox::string(s.clone()),
2923 crate::value::StringKeyedValue::Path(p) => NanBox::from_vmvalue(&VMValue::Path(p.clone())),
2924 crate::value::StringKeyedValue::List(items) => {
2925 let nb_items: Vec<NanBox> = items.iter().map(|v| self.string_keyed_to_nanbox(v)).collect();
2926 NanBox::list(nb_items)
2927 }
2928 crate::value::StringKeyedValue::Attrs(map) => {
2929 let mut nb_map: BTreeMap<Symbol, NanBox> = BTreeMap::new();
2930 for (k, v) in map {
2931 let sym = self.interner.intern(k);
2932 nb_map.insert(sym, self.string_keyed_to_nanbox(v));
2933 }
2934 NanBox::attrs(nb_map)
2935 }
2936 crate::value::StringKeyedValue::Lambda => NanBox::null(),
2937 crate::value::StringKeyedValue::Callable(cb) => {
2938 let cb_clone = Rc::clone(cb);
2939 let builtin = crate::value::VMBuiltin {
2940 name: "<bridge-fn>",
2941 arity: 1,
2942 func: Rc::new(move |args: Vec<VMValue>| {
2943 let interner = crate::intern::Interner::new();
2944 let sk_arg = args.into_iter().next()
2945 .unwrap_or(VMValue::Null)
2946 .to_string_keyed(&interner);
2947 let sk_result = cb_clone(sk_arg)
2948 .map_err(|e| crate::error::VMError::Throw(e))?;
2949 let mut tmp_interner = crate::intern::Interner::new();
2950 Ok(crate::builtins::string_keyed_to_vmvalue(&sk_result, &mut tmp_interner))
2951 }),
2952 };
2953 NanBox::builtin(builtin)
2954 }
2955 crate::value::StringKeyedValue::Thunk(cb) => {
2956 let thunk = VMThunk {
2960 state: Rc::new(Cell::new(Some(ThunkState::NativeCallback(Rc::clone(cb))))),
2961 };
2962 NanBox::thunk(thunk)
2963 }
2964 }
2965 }
2966 fn vm_get_flake_native(&mut self, flake_ref: &str) -> Result<NanBox, VMError> {
2968 let flake_dir = if flake_ref.starts_with('/') || flake_ref.starts_with('.') {
2969 std::path::PathBuf::from(flake_ref)
2970 } else if let Some(path) = flake_ref.strip_prefix("path:") {
2971 std::path::PathBuf::from(path)
2972 } else {
2973 return Err(VMError::Throw(format!(
2974 "getFlake: unsupported flake reference: {flake_ref} (only path: refs supported in VM)"
2975 )));
2976 };
2977 let flake_nix = flake_dir.join("flake.nix");
2978 if !flake_nix.exists() {
2979 return Err(VMError::Throw(format!(
2980 "getFlake: flake.nix not found in {}",
2981 flake_dir.display()
2982 )));
2983 }
2984 let flake_nix_str = flake_nix.to_string_lossy().to_string();
2986 let flake_attrs = self.import_file(&flake_nix_str)?;
2987 let flake_attrs = self.force_value(flake_attrs)?;
2988 let self_sym = self.interner.intern("self");
2990 let out_path_sym = self.interner.intern("outPath");
2991 let flake_dir_str = flake_dir.to_string_lossy().to_string();
2992 let mut self_attrs: BTreeMap<Symbol, NanBox> = BTreeMap::new();
2993 self_attrs.insert(out_path_sym, NanBox::string(flake_dir_str.clone()));
2994 let mut inputs: BTreeMap<Symbol, NanBox> = BTreeMap::new();
2995 inputs.insert(self_sym, NanBox::attrs(self_attrs));
2996 let lock_path = flake_dir.join("flake.lock");
2998 if lock_path.exists() {
2999 if let Ok(lock_str) = std::fs::read_to_string(&lock_path) {
3000 if let Ok(lock_json) = serde_json::from_str::<serde_json::Value>(&lock_str) {
3001 self.resolve_flake_lock_inputs(&lock_json, &flake_dir, &mut inputs);
3002 }
3003 }
3004 }
3005 let outputs_sym = self.interner.intern("outputs");
3007 if let Some(attrs) = flake_attrs.as_attrs() {
3008 if let Some(outputs_func) = attrs.get(&outputs_sym) {
3009 let outputs_func = outputs_func.clone();
3010 let outputs_func = self.force_value(outputs_func)?;
3011 let inputs_nb = NanBox::attrs(inputs);
3012 let result = self.call_callable(&outputs_func, inputs_nb)?;
3013 let mut result_forced = self.force_value(result)?;
3014 let desc_sym = self.interner.intern("description");
3016 if let Some(desc) = attrs.get(&desc_sym) {
3017 if let Some(result_attrs) = result_forced.as_attrs() {
3018 let mut merged = result_attrs.clone();
3019 merged.insert(desc_sym, desc.clone());
3020 result_forced = NanBox::attrs(merged);
3021 }
3022 }
3023 return Ok(result_forced);
3024 }
3025 }
3026 Ok(flake_attrs)
3028 }
3029 fn resolve_flake_lock_inputs(
3031 &mut self,
3032 lock: &serde_json::Value,
3033 flake_dir: &std::path::Path,
3034 inputs: &mut BTreeMap<Symbol, NanBox>,
3035 ) {
3036 let nodes = match lock.get("nodes").and_then(|n| n.as_object()) {
3037 Some(n) => n,
3038 None => return,
3039 };
3040 let root_node = match lock.get("root").and_then(|r| r.as_str()) {
3041 Some(r) => r.to_string(),
3042 None => "root".to_string(),
3043 };
3044 let root_inputs = match nodes
3045 .get(&root_node)
3046 .and_then(|n| n.get("inputs"))
3047 .and_then(|i| i.as_object())
3048 {
3049 Some(i) => i,
3050 None => return,
3051 };
3052 for (input_name, node_ref) in root_inputs {
3053 let node_key = match node_ref.as_str() {
3054 Some(s) => s.to_string(),
3055 None => {
3056 if let Some(arr) = node_ref.as_array() {
3057 if let Some(s) = arr.first().and_then(|v| v.as_str()) {
3058 s.to_string()
3059 } else {
3060 continue;
3061 }
3062 } else {
3063 continue;
3064 }
3065 }
3066 };
3067 if let Some(node) = nodes.get(&node_key) {
3068 if let Some(locked) = node.get("locked") {
3069 let locked_type = locked.get("type").and_then(|t| t.as_str()).unwrap_or("");
3070 let out_path = match locked_type {
3071 "path" => {
3072 if let Some(p) = locked.get("path").and_then(|p| p.as_str()) {
3073 let path = if p.starts_with('/') {
3074 std::path::PathBuf::from(p)
3075 } else {
3076 flake_dir.join(p)
3077 };
3078 path.to_string_lossy().to_string()
3079 } else {
3080 continue;
3081 }
3082 }
3083 _ => continue, };
3085 let input_sym = self.interner.intern(input_name);
3086 let out_path_sym = self.interner.intern("outPath");
3087 let mut input_attrs: BTreeMap<Symbol, NanBox> = BTreeMap::new();
3088 input_attrs.insert(out_path_sym, NanBox::string(out_path));
3089 inputs.insert(input_sym, NanBox::attrs(input_attrs));
3090 }
3091 }
3092 }
3093 }
3094 fn vm_scoped_import(
3098 &mut self,
3099 scope_nix: &str,
3100 path: &str,
3101 ) -> Result<NanBox, VMError> {
3102 let resolved = if std::path::Path::new(path).is_dir() {
3104 format!("{path}/default.nix")
3105 } else {
3106 path.to_string()
3107 };
3108 let source = std::fs::read_to_string(&resolved)
3109 .map_err(|e| VMError::ImportError(format!("{path}: {e}")))?;
3110 let wrapped = format!("with {scope_nix}; {source}");
3112 let file_dir = std::path::Path::new(&resolved)
3113 .parent()
3114 .map(|p| p.to_path_buf())
3115 .unwrap_or_default();
3116 let shared_interner = Rc::new(RefCell::new(std::mem::take(self.interner)));
3118 let chunk = Compiler::compile_with_shared_interner(&wrapped, file_dir, shared_interner.clone())
3119 .map_err(|e| VMError::ImportError(format!("{path}: {e}")))?;
3120 *self.interner = match Rc::try_unwrap(shared_interner) {
3121 Ok(cell) => cell.into_inner(),
3122 Err(rc) => rc.borrow().clone(),
3123 };
3124 if self.frames.len() >= MAX_CALL_DEPTH {
3125 return Err(VMError::StackOverflow);
3126 }
3127 let return_depth = self.frames.len();
3128 let stack_base = self.stack.len();
3129 self.frames.push(CallFrame {
3130 chunk: Rc::new(chunk),
3131 ip: 0,
3132 stack_base,
3133 upvalues: Vec::new(),
3134 });
3135 self.run_until(return_depth)
3136 }
3137 fn call_callable(&mut self, func: &NanBox, arg: NanBox) -> Result<NanBox, VMError> {
3139 if let Some(closure) = func.as_closure() {
3140 if self.frames.len() >= MAX_CALL_DEPTH {
3141 return Err(VMError::StackOverflow);
3142 }
3143 let upvalues = closure.upvalues.clone();
3144 let chunk = closure.chunk.clone();
3145 let return_depth = self.frames.len();
3146 let stack_base = self.stack.len();
3147 self.push(arg);
3148 self.frames.push(CallFrame {
3149 chunk,
3150 ip: 0,
3151 stack_base,
3152 upvalues,
3153 });
3154 let result = self.run_until(return_depth)?;
3155 self.stack.truncate(stack_base);
3156 self.force_value(result)
3159 } else if func.is_higher_order_builtin() {
3160 let hob = func.as_higher_order_builtin().unwrap().clone();
3161 self.call_higher_order_builtin(&hob, arg)
3162 } else if let Some(builtin) = func.as_builtin() {
3163 let arg = self.force_value(arg)?;
3165 if let Some(result) = self.try_vm_builtin(builtin.name, &arg)? {
3166 Ok(result)
3167 } else {
3168 let deep = self.deep_force(arg)?;
3170 let arg_vmval = deep.to_vmvalue();
3171 let builtin_func = builtin.func.clone();
3172 let result = self.call_builtin_with_scoped_import_dispatch(
3173 builtin_func, arg_vmval,
3174 )?;
3175 Ok(result)
3176 }
3177 } else {
3178 Err(VMError::NotCallable(func.type_name().to_string()))
3179 }
3180 }
3181 #[allow(clippy::too_many_lines)]
3182 fn call_higher_order_builtin(
3183 &mut self,
3184 hob: &HigherOrderBuiltin,
3185 arg: NanBox,
3186 ) -> Result<NanBox, VMError> {
3187 use HigherOrderOp::*;
3188 let arg = self.force_value(arg)?;
3191 match hob.op {
3192 Map => {
3193 let list_val = arg.to_vmvalue();
3194 let list = match &list_val {
3195 VMValue::List(l) => l,
3196 other => return Err(VMError::TypeError {
3197 expected: "list", got: other.type_name(),
3198 context: "builtins.map".to_string(),
3199 }),
3200 };
3201 let func_nb = NanBox::from_vmvalue(&hob.func);
3202 let mut results = Vec::with_capacity(list.len());
3203 for item in list {
3204 let r = self.call_callable(&func_nb, NanBox::from_vmvalue(item))?;
3205 results.push(r);
3206 }
3207 Ok(NanBox::list(results))
3208 }
3209 Filter => {
3210 let list_val = arg.to_vmvalue();
3211 let list = match &list_val {
3212 VMValue::List(l) => l,
3213 other => return Err(VMError::TypeError {
3214 expected: "list", got: other.type_name(),
3215 context: "builtins.filter".to_string(),
3216 }),
3217 };
3218 let func_nb = NanBox::from_vmvalue(&hob.func);
3219 let mut results = Vec::new();
3220 for item in list {
3221 let item_nb = NanBox::from_vmvalue(item);
3222 let r = self.call_callable(&func_nb, item_nb.clone())?;
3223
3224 if r.is_truthy()? { results.push(item_nb); }
3225 }
3226 Ok(NanBox::list(results))
3227 }
3228 FoldlP1 => {
3229 let init_vmval = arg.to_vmvalue();
3230 Ok(NanBox::from_vmvalue(&VMValue::HigherOrderBuiltin(
3231 HigherOrderBuiltin {
3232 op: FoldlP2,
3233 func: hob.func.clone(),
3234 extra_args: vec![init_vmval],
3235 },
3236 )))
3237 }
3238 FoldlP2 => {
3239 let list_val = arg.to_vmvalue();
3240 let list = match &list_val {
3241 VMValue::List(l) => l,
3242 other => return Err(VMError::TypeError {
3243 expected: "list", got: other.type_name(),
3244 context: "builtins.foldl'".to_string(),
3245 }),
3246 };
3247 let func_nb = NanBox::from_vmvalue(&hob.func);
3248 let mut acc = NanBox::from_vmvalue(&hob.extra_args[0]);
3249 for item in list {
3250 let partial = self.call_callable(&func_nb, acc)?;
3251 acc = self.call_callable(&partial, NanBox::from_vmvalue(item))?;
3252 }
3253 Ok(acc)
3254 }
3255 Sort => {
3256 let list_val = arg.to_vmvalue();
3257 let list = match &list_val {
3258 VMValue::List(l) => l.clone(),
3259 other => return Err(VMError::TypeError {
3260 expected: "list", got: other.type_name(),
3261 context: "builtins.sort".to_string(),
3262 }),
3263 };
3264 if list.len() <= 1 {
3265 return Ok(NanBox::from_vmvalue(&VMValue::List(list)));
3266 }
3267 let func_nb = NanBox::from_vmvalue(&hob.func);
3268 let mut sorted: Vec<VMValue> = Vec::with_capacity(list.len());
3269 for item in &list {
3270 let item_nb = NanBox::from_vmvalue(item);
3271 let mut pos = sorted.len();
3272 for (i, existing) in sorted.iter().enumerate() {
3273 let existing_nb = NanBox::from_vmvalue(existing);
3274 let partial = self.call_callable(&func_nb, item_nb.clone())?;
3275 let cmp_result = self.call_callable(&partial, existing_nb)?;
3276 if cmp_result.is_truthy()? { pos = i; break; }
3277 }
3278 sorted.insert(pos, item.clone());
3279 }
3280 Ok(NanBox::from_vmvalue(&VMValue::List(sorted)))
3281 }
3282 GenList => {
3283 let n = match arg.to_vmvalue() {
3284 VMValue::Int(n) => n,
3285 other => return Err(VMError::TypeError {
3286 expected: "int", got: other.type_name(),
3287 context: "builtins.genList".to_string(),
3288 }),
3289 };
3290 if n < 0 { return Err(VMError::Throw("genList: negative length".to_string())); }
3291 let func_nb = NanBox::from_vmvalue(&hob.func);
3292 let mut results = Vec::with_capacity(n as usize);
3293 for i in 0..n {
3294 results.push(self.call_callable(&func_nb, NanBox::int(i))?);
3295 }
3296 Ok(NanBox::list(results))
3297 }
3298 ConcatMap => {
3299 let list_val = arg.to_vmvalue();
3300 let list = match &list_val {
3301 VMValue::List(l) => l,
3302 other => return Err(VMError::TypeError {
3303 expected: "list", got: other.type_name(),
3304 context: "builtins.concatMap".to_string(),
3305 }),
3306 };
3307 let func_nb = NanBox::from_vmvalue(&hob.func);
3308 let mut results = Vec::new();
3309 for item in list {
3310 let mapped = self.call_callable(&func_nb, NanBox::from_vmvalue(item))?;
3311 match mapped.to_vmvalue() {
3312 VMValue::List(inner) => {
3313 for v in &inner { results.push(NanBox::from_vmvalue(v)); }
3314 }
3315 other => return Err(VMError::TypeError {
3316 expected: "list", got: other.type_name(),
3317 context: "builtins.concatMap result".to_string(),
3318 }),
3319 }
3320 }
3321 Ok(NanBox::list(results))
3322 }
3323 Any => {
3324 let list_val = arg.to_vmvalue();
3325 let list = match &list_val {
3326 VMValue::List(l) => l,
3327 other => return Err(VMError::TypeError {
3328 expected: "list", got: other.type_name(),
3329 context: "builtins.any".to_string(),
3330 }),
3331 };
3332 let func_nb = NanBox::from_vmvalue(&hob.func);
3333 for item in list {
3334 if self.call_callable(&func_nb, NanBox::from_vmvalue(item))?.is_truthy()? {
3335 return Ok(NanBox::bool(true));
3336 }
3337 }
3338 Ok(NanBox::bool(false))
3339 }
3340 All => {
3341 let list_val = arg.to_vmvalue();
3342 let list = match &list_val {
3343 VMValue::List(l) => l,
3344 other => return Err(VMError::TypeError {
3345 expected: "list", got: other.type_name(),
3346 context: "builtins.all".to_string(),
3347 }),
3348 };
3349 let func_nb = NanBox::from_vmvalue(&hob.func);
3350 for item in list {
3351 if !self.call_callable(&func_nb, NanBox::from_vmvalue(item))?.is_truthy()? {
3352 return Ok(NanBox::bool(false));
3353 }
3354 }
3355 Ok(NanBox::bool(true))
3356 }
3357 Partition => {
3358 let list_val = arg.to_vmvalue();
3359 let list = match &list_val {
3360 VMValue::List(l) => l,
3361 other => return Err(VMError::TypeError {
3362 expected: "list", got: other.type_name(),
3363 context: "builtins.partition".to_string(),
3364 }),
3365 };
3366 let func_nb = NanBox::from_vmvalue(&hob.func);
3367 let (mut right, mut wrong) = (Vec::new(), Vec::new());
3368 for item in list {
3369 let item_nb = NanBox::from_vmvalue(item);
3370 if self.call_callable(&func_nb, item_nb.clone())?.is_truthy()? {
3371 right.push(item_nb);
3372 } else {
3373 wrong.push(item_nb);
3374 }
3375 }
3376 let rs = self.interner.intern("right");
3377 let ws = self.interner.intern("wrong");
3378 let mut attrs = BTreeMap::new();
3379 attrs.insert(rs, NanBox::list(right));
3380 attrs.insert(ws, NanBox::list(wrong));
3381 Ok(NanBox::attrs(attrs))
3382 }
3383 GroupBy => {
3384 let list_val = arg.to_vmvalue();
3385 let list = match &list_val {
3386 VMValue::List(l) => l,
3387 other => return Err(VMError::TypeError {
3388 expected: "list", got: other.type_name(),
3389 context: "builtins.groupBy".to_string(),
3390 }),
3391 };
3392 let func_nb = NanBox::from_vmvalue(&hob.func);
3393 let mut groups: BTreeMap<String, Vec<NanBox>> = BTreeMap::new();
3394 for item in list {
3395 let item_nb = NanBox::from_vmvalue(item);
3396 let kr = self.call_callable(&func_nb, item_nb.clone())?;
3397 let ks = kr.as_string().ok_or_else(|| VMError::TypeError {
3398 expected: "string", got: kr.type_name(),
3399 context: "builtins.groupBy key".to_string(),
3400 })?.to_string();
3401 groups.entry(ks).or_default().push(item_nb);
3402 }
3403 let mut attrs = BTreeMap::new();
3404 for (k, vs) in groups {
3405 attrs.insert(self.interner.intern(&k), NanBox::list(vs));
3406 }
3407 Ok(NanBox::attrs(attrs))
3408 }
3409 MapAttrs => {
3410 let attrs_val = arg.to_vmvalue();
3411 let attrs = match &attrs_val {
3412 VMValue::Attrs(a) => a,
3413 other => return Err(VMError::TypeError {
3414 expected: "set", got: other.type_name(),
3415 context: "builtins.mapAttrs".to_string(),
3416 }),
3417 };
3418 let func_nb = NanBox::from_vmvalue(&hob.func);
3419 let entries: Vec<_> = attrs.iter().map(|(k, v)| (*k, v.clone())).collect();
3420 let chunk = deferred_apply_chunk();
3421 let mut result = BTreeMap::new();
3422 for (sym, val) in entries {
3423 let key_str = self.interner.resolve(sym).to_string();
3424 let partial = self.call_callable(&func_nb, NanBox::string(key_str))?;
3427 let thunk = VMThunk::new(
3434 chunk.clone(),
3435 vec![partial, NanBox::from_vmvalue(&val)],
3436 );
3437 result.insert(sym, NanBox::thunk(thunk));
3438 }
3439 Ok(NanBox::attrs(result))
3440 }
3441 FilterAttrs => {
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.filterAttrs".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 mut result = BTreeMap::new();
3453 for (sym, val) in entries {
3454 let key_str = self.interner.resolve(sym).to_string();
3455 let partial = self.call_callable(&func_nb, NanBox::string(key_str))?;
3456 if self.call_callable(&partial, NanBox::from_vmvalue(&val))?.is_truthy()? {
3457 result.insert(sym, NanBox::from_vmvalue(&val));
3458 }
3459 }
3460 Ok(NanBox::attrs(result))
3461 }
3462 Elem => {
3463 let needle = NanBox::from_vmvalue(&hob.func);
3468 let forced_needle = self.force_value(needle)?;
3469 let list = if let Some(items) = arg.as_list() {
3470 items.to_vec()
3471 } else {
3472 let forced = self.force_value(arg)?;
3473 if let Some(items) = forced.as_list() {
3474 items.to_vec()
3475 } else {
3476 return Err(VMError::TypeError {
3477 expected: "list",
3478 got: forced.type_name(),
3479 context: "builtins.elem".to_string(),
3480 });
3481 }
3482 };
3483 for item in &list {
3484 let forced_item = self.force_value(item.clone())?;
3485 if self.deep_eq(&forced_needle, &forced_item)? {
3486 return Ok(NanBox::bool(true));
3487 }
3488 }
3489 Ok(NanBox::bool(false))
3490 }
3491 }
3492 }
3493 fn import_file(&mut self, path: &str) -> Result<NanBox, VMError> {
3499 let resolved = std::fs::canonicalize(path)
3500 .map_err(|e| VMError::ImportError(format!("{path}: {e}")))?;
3501 let resolved = if resolved.is_dir() {
3503 resolved.join("default.nix")
3504 } else {
3505 resolved
3506 };
3507 let canonical = resolved.to_string_lossy().to_string();
3508 if let Some(cached) = self.import_cache.borrow().get(&canonical) {
3510 return Ok(NanBox::from_vmvalue(cached));
3511 }
3512 let chunk = self.try_compile_import(&resolved, &canonical)?;
3514 let chunk = match chunk {
3515 Some(c) => c,
3516 None => {
3517 return self.import_via_bridge(&canonical);
3519 }
3520 };
3521 if self.frames.len() >= MAX_CALL_DEPTH {
3522 return Err(VMError::StackOverflow);
3523 }
3524 let return_depth = self.frames.len();
3525 let stack_base = self.stack.len();
3526 self.frames.push(CallFrame {
3527 chunk,
3528 ip: 0,
3529 stack_base,
3530 upvalues: Vec::new(),
3531 });
3532 let result = match self.run_until(return_depth) {
3533 Ok(r) => r,
3534 Err(e @ VMError::Throw(_)) => {
3535 self.stack.truncate(stack_base);
3537 if self.frames.len() > return_depth {
3538 self.frames.truncate(return_depth);
3539 }
3540 return Err(e);
3541 }
3542 Err(e) => {
3543 eprintln!("[sui-vm] runtime fallback for {canonical}: {e}");
3546 use std::sync::atomic::Ordering;
3547 crate::vm::VM_FALLBACK_COUNT.fetch_add(1, Ordering::Relaxed);
3548 self.stack.truncate(stack_base);
3549 if self.frames.len() > return_depth {
3550 self.frames.truncate(return_depth);
3551 }
3552 return self.import_via_bridge(&canonical);
3553 }
3554 };
3555 self.stack.truncate(stack_base);
3558 let result_vmval = result.to_vmvalue();
3560 self.import_cache
3561 .borrow_mut()
3562 .insert(canonical, result_vmval);
3563 Ok(result)
3564 }
3565 fn try_compile_import(
3569 &mut self,
3570 resolved: &std::path::Path,
3571 canonical: &str,
3572 ) -> Result<Option<Rc<Chunk>>, VMError> {
3573 if let Some(cached_chunk) = self.compile_cache.get(resolved) {
3575 return Ok(Some(cached_chunk.clone()));
3576 }
3577 let source = std::fs::read_to_string(canonical)
3579 .map_err(|e| VMError::ImportError(format!("{canonical}: {e}")))?;
3580 let file_dir = resolved
3581 .parent()
3582 .map(|p| p.to_path_buf())
3583 .unwrap_or_default();
3584 let shared_interner = Rc::new(RefCell::new(std::mem::take(self.interner)));
3587 let compile_result =
3588 Compiler::compile_with_shared_interner(&source, file_dir, shared_interner.clone());
3589 *self.interner = match Rc::try_unwrap(shared_interner) {
3590 Ok(cell) => cell.into_inner(),
3591 Err(rc) => rc.borrow().clone(),
3592 };
3593 match compile_result {
3594 Ok(mut compiled) => {
3595 Self::set_source_file_recursive(&mut compiled, canonical);
3596 let chunk = Rc::new(compiled);
3597 self.compile_cache
3598 .insert(resolved.to_path_buf(), chunk.clone());
3599 Ok(Some(chunk))
3600 }
3601 Err(compile_error) => {
3602 VM_FALLBACK_COUNT.fetch_add(1, Ordering::Relaxed);
3605 eprintln!("[sui-vm] fallback to tree-walker for {canonical}: {compile_error}");
3606 Ok(None)
3607 }
3608 }
3609 }
3610 fn import_via_bridge(&mut self, canonical: &str) -> Result<NanBox, VMError> {
3614 match crate::bridge::call_builtin_bridge(
3615 "__import",
3616 vec![crate::value::StringKeyedValue::Path(canonical.to_string())],
3617 ) {
3618 Ok(Some(result)) => {
3619 let nanbox = self.string_keyed_to_nanbox(&result);
3620 let nanbox = if nanbox.is_thunk() {
3624 self.force_value(nanbox)?
3625 } else {
3626 nanbox
3627 };
3628 let result_vmval = nanbox.to_vmvalue();
3630 self.import_cache
3631 .borrow_mut()
3632 .insert(canonical.to_string(), result_vmval);
3633 Ok(nanbox)
3634 }
3635 Ok(None) => Err(VMError::ImportError(format!(
3636 "compilation failed and no bridge installed for '{canonical}'"
3637 ))),
3638 Err(e) => Err(VMError::ImportError(format!(
3639 "bridge fallback error for '{canonical}': {e}"
3640 ))),
3641 }
3642 }
3643 fn set_source_file_recursive(chunk: &mut Chunk, file: &str) {
3645 chunk.source_file = Some(file.to_string());
3646 for constant in &mut chunk.constants {
3647 if let VMValue::Closure(closure) = constant {
3648 if let Some(inner_chunk) = Rc::get_mut(&mut closure.chunk) {
3649 Self::set_source_file_recursive(inner_chunk, file);
3650 }
3651 }
3652 }
3653 }
3654 fn disassemble_around(chunk: &Chunk, center_ip: usize, window: usize) -> String {
3658 let code = &chunk.code;
3659 let mut lines: Vec<String> = Vec::new();
3660 let mut boundaries: Vec<usize> = Vec::new();
3662 let mut pos = 0;
3663 while pos < code.len() {
3664 boundaries.push(pos);
3665 pos += Self::instruction_width(code, pos);
3666 }
3667 let center_idx = boundaries.iter().position(|&b| b >= center_ip).unwrap_or(0);
3669 let start_idx = center_idx.saturating_sub(window);
3670 let end_idx = (center_idx + window + 1).min(boundaries.len());
3671 for idx in start_idx..end_idx {
3672 let ip = boundaries[idx];
3673 let marker = if ip == center_ip { ">>>" } else { " " };
3674 let line = chunk.lines.get(ip).copied().unwrap_or(0);
3675 if let Some(op) = OpCode::from_byte(code[ip]) {
3676 let operands = Self::format_operands(code, ip, op);
3677 lines.push(format!(" {marker} {ip:4}: {op:?}{operands} (line {line})"));
3678 } else {
3679 lines.push(format!(" {marker} {ip:4}: <unknown {}> (line {line})", code[ip]));
3680 }
3681 }
3682 lines.join("\n")
3683 }
3684 fn instruction_width(code: &[u8], pos: usize) -> usize {
3686 let byte = code[pos];
3687 match OpCode::from_byte(byte) {
3688 Some(op) => match op {
3689 OpCode::Null | OpCode::True | OpCode::False
3691 | OpCode::Add | OpCode::Sub | OpCode::Mul | OpCode::Div | OpCode::Negate
3692 | OpCode::Not | OpCode::And | OpCode::Or | OpCode::Implication
3693 | OpCode::Equal | OpCode::NotEqual | OpCode::Less | OpCode::Greater
3694 | OpCode::LessEqual | OpCode::GreaterEqual
3695 | OpCode::UpdateAttrs | OpCode::Concat
3696 | OpCode::Call | OpCode::TailCall | OpCode::Return
3697 | OpCode::Assert | OpCode::Throw | OpCode::Pop | OpCode::Dup | OpCode::PushWith | OpCode::PopWith
3698 | OpCode::PushBuiltins | OpCode::Force | OpCode::Import
3699 | OpCode::DynGetAttr | OpCode::DynHasAttr
3700 | OpCode::DynSelectOrDefault | OpCode::Dup => 1,
3701 OpCode::Constant | OpCode::GetLocal | OpCode::SetLocal
3703 | OpCode::GetUpvalue | OpCode::SetUpvalue | OpCode::LookupWith
3704 | OpCode::GetAttr | OpCode::HasAttr | OpCode::MakeAttrs
3705 | OpCode::SelectOrDefault | OpCode::MakeList
3706 | OpCode::Jump | OpCode::JumpIfFalse | OpCode::JumpIfTrue
3707 | OpCode::Interpolate => 3,
3708 OpCode::GetLocalAttr | OpCode::GetLocalCall | OpCode::CallBuiltin => 5,
3710 OpCode::MakeClosure => {
3712 if pos + 5 <= code.len() {
3713 let uv_count = u16::from_le_bytes([code[pos + 3], code[pos + 4]]) as usize;
3714 5 + uv_count * 3
3715 } else {
3716 3 }
3718 }
3719 OpCode::MakeThunk => {
3721 if pos + 5 <= code.len() {
3722 let uv_count = u16::from_le_bytes([code[pos + 3], code[pos + 4]]) as usize;
3723 5 + uv_count * 3
3724 } else {
3725 3
3726 }
3727 }
3728 OpCode::PatchThunkUpvalues => {
3730 if pos + 5 <= code.len() {
3731 let uv_count = u16::from_le_bytes([code[pos + 3], code[pos + 4]]) as usize;
3732 5 + uv_count * 3
3733 } else {
3734 3
3735 }
3736 }
3737 OpCode::MakeLazyThunk => {
3739 if pos + 15 <= code.len() {
3740 let uv_count = u16::from_le_bytes([code[pos + 13], code[pos + 14]]) as usize;
3741 15 + uv_count * 3
3742 } else {
3743 3
3744 }
3745 }
3746 },
3747 None => 1, }
3749 }
3750 fn format_operands(code: &[u8], pos: usize, op: OpCode) -> String {
3752 let read_u16_at = |p: usize| -> Option<u16> {
3753 if p + 2 <= code.len() {
3754 Some(u16::from_le_bytes([code[p], code[p + 1]]))
3755 } else {
3756 None
3757 }
3758 };
3759 match op {
3760 OpCode::Constant | OpCode::GetLocal | OpCode::SetLocal
3761 | OpCode::GetUpvalue | OpCode::SetUpvalue | OpCode::LookupWith
3762 | OpCode::GetAttr | OpCode::HasAttr | OpCode::MakeAttrs
3763 | OpCode::SelectOrDefault | OpCode::MakeList
3764 | OpCode::Jump | OpCode::JumpIfFalse | OpCode::JumpIfTrue
3765 | OpCode::Interpolate => {
3766 read_u16_at(pos + 1).map_or(String::new(), |v| format!(" {v}"))
3767 }
3768 OpCode::GetLocalAttr => {
3769 let s = read_u16_at(pos + 1).unwrap_or(0);
3770 let k = read_u16_at(pos + 3).unwrap_or(0);
3771 format!(" slot={s} key={k}")
3772 }
3773 OpCode::GetLocalCall => {
3774 read_u16_at(pos + 1).map_or(String::new(), |v| format!(" slot={v}"))
3775 }
3776 OpCode::CallBuiltin => {
3777 let idx = read_u16_at(pos + 1).unwrap_or(0);
3778 let argc = read_u16_at(pos + 3).unwrap_or(0);
3779 format!(" idx={idx} argc={argc}")
3780 }
3781 OpCode::MakeThunk | OpCode::MakeClosure => {
3782 let ci = read_u16_at(pos + 1).unwrap_or(0);
3783 let uv = read_u16_at(pos + 3).unwrap_or(0);
3784 format!(" const={ci} upvals={uv}")
3785 }
3786 OpCode::PatchThunkUpvalues => {
3787 let s = read_u16_at(pos + 1).unwrap_or(0);
3788 let uv = read_u16_at(pos + 3).unwrap_or(0);
3789 format!(" slot={s} upvals={uv}")
3790 }
3791 _ => String::new(),
3792 }
3793 }
3794}
3795#[cfg(test)]
3796mod tests {
3797 use super::*;
3798 use crate::compiler::Compiler;
3799 use crate::value::StringKeyedValue;
3800 fn eval(input: &str) -> VMValue {
3801 let (chunk, mut interner) =
3802 Compiler::compile(input).unwrap_or_else(|e| panic!("compile '{input}': {e}"));
3803 VM::execute(chunk, &mut interner).unwrap_or_else(|e| panic!("execute '{input}': {e}"))
3804 }
3805 fn eval_full_helper(input: &str) -> crate::StringKeyedValue {
3806 let result =
3807 crate::eval_full(input).unwrap_or_else(|e| panic!("eval_full '{input}': {e}"));
3808 result.to_string_keyed()
3809 }
3810 fn eval_err(input: &str) -> VMError {
3811 let (chunk, mut interner) =
3812 Compiler::compile(input).unwrap_or_else(|e| panic!("compile '{input}': {e}"));
3813 VM::execute(chunk, &mut interner).unwrap_err()
3814 }
3815 #[test]
3817 fn eval_integer() {
3818 assert_eq!(eval("42"), VMValue::Int(42));
3819 }
3820 #[test]
3821 fn eval_negative_integer() {
3822 assert_eq!(eval("-7"), VMValue::Int(-7));
3823 }
3824 #[test]
3825 fn eval_float() {
3826 assert_eq!(eval("3.14"), VMValue::Float(3.14));
3827 }
3828 #[test]
3829 fn eval_bool_true() {
3830 assert_eq!(eval("true"), VMValue::Bool(true));
3831 }
3832 #[test]
3833 fn eval_bool_false() {
3834 assert_eq!(eval("false"), VMValue::Bool(false));
3835 }
3836 #[test]
3837 fn eval_null() {
3838 assert_eq!(eval("null"), VMValue::Null);
3839 }
3840 #[test]
3841 fn eval_string() {
3842 assert_eq!(eval(r#""hello""#), VMValue::String("hello".to_string()));
3843 }
3844 #[test]
3846 fn eval_add_int() {
3847 assert_eq!(eval("1 + 2"), VMValue::Int(3));
3848 }
3849 #[test]
3850 fn eval_sub_int() {
3851 assert_eq!(eval("10 - 3"), VMValue::Int(7));
3852 }
3853 #[test]
3854 fn eval_mul_int() {
3855 assert_eq!(eval("3 * 4"), VMValue::Int(12));
3856 }
3857 #[test]
3858 fn eval_div_int() {
3859 assert_eq!(eval("10 / 3"), VMValue::Int(3));
3860 }
3861 #[test]
3862 fn eval_div_zero() {
3863 assert!(matches!(eval_err("1 / 0"), VMError::DivisionByZero));
3864 }
3865 #[test]
3866 fn eval_float_arithmetic() {
3867 assert_eq!(eval("1.5 + 2.5"), VMValue::Float(4.0));
3868 }
3869 #[test]
3870 fn eval_mixed_arithmetic() {
3871 assert_eq!(eval("1 + 2.0"), VMValue::Float(3.0));
3872 }
3873 #[test]
3874 fn eval_compound_arithmetic() {
3875 assert_eq!(eval("2 * 3 + 1"), VMValue::Int(7));
3876 }
3877 #[test]
3878 fn eval_negate_float() {
3879 assert_eq!(eval("-3.14"), VMValue::Float(-3.14));
3880 }
3881 #[test]
3882 fn eval_string_concat() {
3883 assert_eq!(
3884 eval(r#""hello" + " " + "world""#),
3885 VMValue::String("hello world".to_string())
3886 );
3887 }
3888 #[test]
3890 fn eval_equal() {
3891 assert_eq!(eval("1 == 1"), VMValue::Bool(true));
3892 assert_eq!(eval("1 == 2"), VMValue::Bool(false));
3893 }
3894 #[test]
3895 fn eval_not_equal() {
3896 assert_eq!(eval("1 != 2"), VMValue::Bool(true));
3897 assert_eq!(eval("1 != 1"), VMValue::Bool(false));
3898 }
3899 #[test]
3900 fn eval_less() {
3901 assert_eq!(eval("1 < 2"), VMValue::Bool(true));
3902 assert_eq!(eval("2 < 1"), VMValue::Bool(false));
3903 }
3904 #[test]
3905 fn eval_greater() {
3906 assert_eq!(eval("2 > 1"), VMValue::Bool(true));
3907 assert_eq!(eval("1 > 2"), VMValue::Bool(false));
3908 }
3909 #[test]
3910 fn eval_less_equal() {
3911 assert_eq!(eval("1 <= 1"), VMValue::Bool(true));
3912 assert_eq!(eval("1 <= 2"), VMValue::Bool(true));
3913 assert_eq!(eval("2 <= 1"), VMValue::Bool(false));
3914 }
3915 #[test]
3916 fn eval_greater_equal() {
3917 assert_eq!(eval("1 >= 1"), VMValue::Bool(true));
3918 assert_eq!(eval("2 >= 1"), VMValue::Bool(true));
3919 assert_eq!(eval("1 >= 2"), VMValue::Bool(false));
3920 }
3921 #[test]
3923 fn eval_not() {
3924 assert_eq!(eval("!true"), VMValue::Bool(false));
3925 assert_eq!(eval("!false"), VMValue::Bool(true));
3926 }
3927 #[test]
3928 fn eval_and_short_circuit() {
3929 assert_eq!(eval("true && true"), VMValue::Bool(true));
3930 assert_eq!(eval("true && false"), VMValue::Bool(false));
3931 assert_eq!(eval("false && true"), VMValue::Bool(false));
3932 }
3933 #[test]
3934 fn eval_or_short_circuit() {
3935 assert_eq!(eval("false || true"), VMValue::Bool(true));
3936 assert_eq!(eval("false || false"), VMValue::Bool(false));
3937 assert_eq!(eval("true || false"), VMValue::Bool(true));
3938 }
3939 #[test]
3940 fn eval_implication() {
3941 assert_eq!(eval("true -> true"), VMValue::Bool(true));
3942 assert_eq!(eval("true -> false"), VMValue::Bool(false));
3943 assert_eq!(eval("false -> true"), VMValue::Bool(true));
3944 assert_eq!(eval("false -> false"), VMValue::Bool(true));
3945 }
3946 #[test]
3948 fn eval_if_true() {
3949 assert_eq!(eval("if true then 1 else 2"), VMValue::Int(1));
3950 }
3951 #[test]
3952 fn eval_if_false() {
3953 assert_eq!(eval("if false then 1 else 2"), VMValue::Int(2));
3954 }
3955 #[test]
3956 fn eval_if_expression() {
3957 assert_eq!(
3958 eval("if 1 > 2 then \"yes\" else \"no\""),
3959 VMValue::String("no".to_string())
3960 );
3961 }
3962 #[test]
3963 fn eval_nested_if() {
3964 assert_eq!(
3965 eval("if true then (if false then 1 else 2) else 3"),
3966 VMValue::Int(2)
3967 );
3968 }
3969 #[test]
3971 fn eval_let_simple() {
3972 assert_eq!(eval("let x = 1; y = 2; in x + y"), VMValue::Int(3));
3973 }
3974 #[test]
3975 fn eval_let_nested() {
3976 assert_eq!(
3977 eval("let a = 10; in let b = 20; in a + b"),
3978 VMValue::Int(30)
3979 );
3980 }
3981 #[test]
3982 fn eval_let_shadow() {
3983 assert_eq!(eval("let x = 1; in let x = 2; in x"), VMValue::Int(2));
3984 }
3985 #[test]
3986 fn eval_let_with_expression() {
3987 assert_eq!(eval("let x = 2 * 3; in x + 1"), VMValue::Int(7));
3988 }
3989 #[test]
3991 fn eval_empty_list() {
3992 assert_eq!(eval("[]"), VMValue::List(vec![]));
3993 }
3994 #[test]
3995 fn eval_list() {
3996 assert_eq!(
3997 eval("[1 2 3]"),
3998 VMValue::List(vec![VMValue::Int(1), VMValue::Int(2), VMValue::Int(3)])
3999 );
4000 }
4001 #[test]
4002 fn eval_list_concat() {
4003 assert_eq!(
4004 eval("[1 2] ++ [3 4]"),
4005 VMValue::List(vec![
4006 VMValue::Int(1),
4007 VMValue::Int(2),
4008 VMValue::Int(3),
4009 VMValue::Int(4),
4010 ])
4011 );
4012 }
4013 #[test]
4014 fn eval_list_concat_with_inline_map() {
4015 assert_eq!(
4019 eval("[1] ++ builtins.map (a: a) [2 3]"),
4020 VMValue::List(vec![VMValue::Int(1), VMValue::Int(2), VMValue::Int(3)])
4021 );
4022 }
4023 #[test]
4024 fn eval_list_concat_with_inline_map_attrsets() {
4025 let result = eval(r#"[{ x = 1; }] ++ builtins.map (a: { v = a; }) ["a" "b"]"#);
4027 match result {
4028 VMValue::List(items) => assert_eq!(items.len(), 3),
4029 other => panic!("expected list, got {:?}", other.type_name()),
4030 }
4031 }
4032 #[test]
4033 fn eval_list_concat_with_inline_filter() {
4034 assert_eq!(
4036 eval("[0] ++ builtins.filter (x: x > 1) [1 2 3]"),
4037 VMValue::List(vec![VMValue::Int(0), VMValue::Int(2), VMValue::Int(3)])
4038 );
4039 }
4040 #[test]
4041 fn eval_list_mixed() {
4042 assert_eq!(
4043 eval(r#"[1 "hello" true]"#),
4044 VMValue::List(vec![
4045 VMValue::Int(1),
4046 VMValue::String("hello".to_string()),
4047 VMValue::Bool(true),
4048 ])
4049 );
4050 }
4051 #[test]
4053 fn eval_empty_attrset() {
4054 assert_eq!(eval("{ }"), VMValue::Attrs(BTreeMap::new()));
4055 }
4056 #[test]
4057 fn eval_attrset() {
4058 let result = eval_full_helper("{ a = 1; b = 2; }");
4059 let mut expected = BTreeMap::new();
4060 expected.insert("a".to_string(), crate::StringKeyedValue::Int(1));
4061 expected.insert("b".to_string(), crate::StringKeyedValue::Int(2));
4062 assert_eq!(result, crate::StringKeyedValue::Attrs(expected));
4063 }
4064 #[test]
4065 fn eval_attrset_select() {
4066 assert_eq!(eval("{ a = 1; b = 2; }.a"), VMValue::Int(1));
4067 }
4068 #[test]
4069 fn eval_attrset_update() {
4070 let result = eval_full_helper("{ a = 1; } // { b = 2; }");
4071 let mut expected = BTreeMap::new();
4072 expected.insert("a".to_string(), crate::StringKeyedValue::Int(1));
4073 expected.insert("b".to_string(), crate::StringKeyedValue::Int(2));
4074 assert_eq!(result, crate::StringKeyedValue::Attrs(expected));
4075 }
4076 #[test]
4077 fn eval_attrset_update_override() {
4078 assert_eq!(eval("({ a = 1; } // { a = 2; }).a"), VMValue::Int(2));
4079 }
4080 #[test]
4081 fn eval_has_attr_true() {
4082 assert_eq!(eval("{ a = 1; } ? a"), VMValue::Bool(true));
4083 }
4084 #[test]
4085 fn eval_has_attr_false() {
4086 assert_eq!(eval("{ a = 1; } ? b"), VMValue::Bool(false));
4087 }
4088 #[test]
4089 fn eval_select_or_default() {
4090 assert_eq!(eval("{ a = 1; }.b or 0"), VMValue::Int(0));
4091 assert_eq!(eval("{ a = 1; }.a or 0"), VMValue::Int(1));
4092 }
4093 #[test]
4094 fn eval_dyn_select_or_default_missing() {
4095 assert_eq!(
4097 eval(r#"let x = "missing"; in { a = 1; }.${ x } or 99"#),
4098 VMValue::Int(99),
4099 );
4100 }
4101 #[test]
4102 fn eval_dyn_select_or_default_found() {
4103 assert_eq!(
4105 eval(r#"let x = "a"; in { a = 42; }.${ x } or 99"#),
4106 VMValue::Int(42),
4107 );
4108 }
4109 #[test]
4110 fn eval_dyn_select_or_default_dotted_key() {
4111 assert_eq!(
4113 eval(r#"let x = "a.b"; in { "a.b" = 7; }.${ x } or 0"#),
4114 VMValue::Int(7),
4115 );
4116 }
4117 #[test]
4118 fn eval_dyn_select_or_default_special_chars() {
4119 assert_eq!(
4121 eval(r#"let x = "armv8.3-a+crypto+sha2"; in { "armv8-a" = 1; }.${ x } or 0"#),
4122 VMValue::Int(0),
4123 );
4124 }
4125 #[test]
4126 fn eval_dyn_select_or_default_non_attrset() {
4127 assert_eq!(
4129 eval(r#"let x = "a"; base = 42; in base.${ x } or 99"#),
4130 VMValue::Int(99),
4131 );
4132 }
4133 #[test]
4135 fn eval_identity_lambda() {
4136 assert_eq!(eval("(x: x) 42"), VMValue::Int(42));
4137 }
4138 #[test]
4139 fn eval_lambda_arithmetic() {
4140 assert_eq!(eval("(x: x + 1) 5"), VMValue::Int(6));
4141 }
4142 #[test]
4143 #[ignore = "requires upvalue capture (Phase 2)"]
4144 fn eval_curried_lambda() {
4145 assert_eq!(eval("(x: y: x + y) 3 4"), VMValue::Int(7));
4146 }
4147 #[test]
4148 fn eval_let_lambda() {
4149 assert_eq!(
4150 eval("let f = x: x * 2; in f 5"),
4151 VMValue::Int(10)
4152 );
4153 }
4154 #[test]
4155 fn eval_pattern_lambda() {
4156 assert_eq!(eval("({ a, b }: a + b) { a = 3; b = 4; }"), VMValue::Int(7));
4157 }
4158 #[test]
4159 fn eval_pattern_lambda_default() {
4160 assert_eq!(
4161 eval("({ a, b ? 10 }: a + b) { a = 5; }"),
4162 VMValue::Int(15)
4163 );
4164 }
4165 #[test]
4166 fn eval_lambda_with_let() {
4167 assert_eq!(
4168 eval("let inc = x: x + 1; double = x: x * 2; in double (inc 3)"),
4169 VMValue::Int(8)
4170 );
4171 }
4172 #[test]
4174 fn eval_assert_pass() {
4175 assert_eq!(eval("assert true; 42"), VMValue::Int(42));
4176 }
4177 #[test]
4178 fn eval_assert_fail() {
4179 assert!(matches!(eval_err("assert false; 42"), VMError::AssertionFailed));
4180 }
4181 #[test]
4183 fn deep_eq_attrs_with_thunked_values() {
4184 assert_eq!(
4187 eval("let a = { x = 1; }; b = { x = 1; }; in a == b"),
4188 VMValue::Bool(true)
4189 );
4190 }
4191 #[test]
4192 fn deep_eq_attrs_different_values() {
4193 assert_eq!(
4194 eval("let a = { x = 1; }; b = { x = 2; }; in a == b"),
4195 VMValue::Bool(false)
4196 );
4197 }
4198 #[test]
4199 fn deep_eq_nested_attrs() {
4200 assert_eq!(
4201 eval("let a = { x = { y = 1; }; }; b = { x = { y = 1; }; }; in a == b"),
4202 VMValue::Bool(true)
4203 );
4204 }
4205 #[test]
4206 fn deep_eq_list_with_thunked_elements() {
4207 assert_eq!(
4208 eval("let a = [ 1 2 ]; b = [ 1 2 ]; in a == b"),
4209 VMValue::Bool(true)
4210 );
4211 }
4212 #[test]
4214 fn eval_elem_thunked_attrsets() {
4215 assert_eq!(
4217 eval("let a = { x = 1; }; b = { x = 1; }; in builtins.elem a [ b ]"),
4218 VMValue::Bool(true)
4219 );
4220 }
4221 #[test]
4222 fn eval_elem_basic_int() {
4223 assert_eq!(
4224 eval("builtins.elem 2 [ 1 2 3 ]"),
4225 VMValue::Bool(true)
4226 );
4227 }
4228 #[test]
4229 fn eval_elem_missing() {
4230 assert_eq!(
4231 eval("builtins.elem 4 [ 1 2 3 ]"),
4232 VMValue::Bool(false)
4233 );
4234 }
4235 #[test]
4236 fn eval_elem_string() {
4237 assert_eq!(
4238 eval(r#"builtins.elem "b" [ "a" "b" "c" ]"#),
4239 VMValue::Bool(true)
4240 );
4241 }
4242 #[test]
4243 fn eval_elem_thunked_list_elements() {
4244 assert_eq!(
4245 eval("let x = 1; in builtins.elem 1 [ x ]"),
4246 VMValue::Bool(true)
4247 );
4248 }
4249 #[test]
4251 fn eval_string_interpolation() {
4252 assert_eq!(
4253 eval(r#"let x = "world"; in "hello ${x}""#),
4254 VMValue::String("hello world".to_string()),
4255 );
4256 }
4257 #[test]
4258 #[ignore = "requires builtins.toString (Phase 2)"]
4259 fn eval_string_interpolation_int() {
4260 assert_eq!(
4261 eval(r#"let n = 42; in "value: ${toString n}""#),
4262 VMValue::String("value: 42".to_string()),
4263 );
4264 }
4265 #[test]
4267 fn eval_absolute_path() {
4268 assert_eq!(eval("/tmp/x"), VMValue::Path("/tmp/x".to_string()));
4269 }
4270 #[test]
4272 fn eval_fibonacci_like() {
4273 assert_eq!(
4274 eval("let a = 1; b = 1; c = a + b; d = b + c; e = c + d; in e"),
4275 VMValue::Int(5)
4276 );
4277 }
4278 #[test]
4279 fn eval_nested_attrset_select() {
4280 assert_eq!(
4281 eval("{ a = { b = 42; }; }.a.b"),
4282 VMValue::Int(42)
4283 );
4284 }
4285 #[test]
4286 fn eval_let_with_attrset() {
4287 assert_eq!(
4288 eval("let set = { x = 10; y = 20; }; in set.x + set.y"),
4289 VMValue::Int(30)
4290 );
4291 }
4292 #[test]
4293 fn eval_conditional_attrset() {
4294 assert_eq!(
4295 eval("(if true then { a = 1; } else { a = 2; }).a"),
4296 VMValue::Int(1)
4297 );
4298 }
4299 #[test]
4301 fn builtin_length() {
4302 assert_eq!(eval("builtins.length [1 2 3]"), VMValue::Int(3));
4303 }
4304 #[test]
4305 fn builtin_length_empty() {
4306 assert_eq!(eval("builtins.length []"), VMValue::Int(0));
4307 }
4308 #[test]
4309 fn builtin_head() {
4310 assert_eq!(eval("builtins.head [10 20 30]"), VMValue::Int(10));
4311 }
4312 #[test]
4313 fn builtin_tail() {
4314 let result = eval_full_helper("builtins.tail [1 2 3]");
4315 assert_eq!(
4316 result,
4317 StringKeyedValue::List(vec![StringKeyedValue::Int(2), StringKeyedValue::Int(3)])
4318 );
4319 }
4320 #[test]
4321 fn builtin_type_of_int() {
4322 assert_eq!(
4323 eval("builtins.typeOf 42"),
4324 VMValue::String("int".to_string())
4325 );
4326 }
4327 #[test]
4328 fn builtin_type_of_string() {
4329 assert_eq!(
4330 eval("builtins.typeOf \"hello\""),
4331 VMValue::String("string".to_string())
4332 );
4333 }
4334 #[test]
4335 fn builtin_type_of_bool() {
4336 assert_eq!(
4337 eval("builtins.typeOf true"),
4338 VMValue::String("bool".to_string())
4339 );
4340 }
4341 #[test]
4342 fn builtin_type_of_null() {
4343 assert_eq!(
4344 eval("builtins.typeOf null"),
4345 VMValue::String("null".to_string())
4346 );
4347 }
4348 #[test]
4349 fn builtin_type_of_list() {
4350 assert_eq!(
4351 eval("builtins.typeOf [1 2]"),
4352 VMValue::String("list".to_string())
4353 );
4354 }
4355 #[test]
4356 fn builtin_type_of_set() {
4357 assert_eq!(
4358 eval("builtins.typeOf { a = 1; }"),
4359 VMValue::String("set".to_string())
4360 );
4361 }
4362 #[test]
4363 fn builtin_type_of_lambda() {
4364 assert_eq!(
4365 eval("builtins.typeOf (x: x)"),
4366 VMValue::String("lambda".to_string())
4367 );
4368 }
4369 #[test]
4370 fn builtin_is_int() {
4371 assert_eq!(eval("builtins.isInt 42"), VMValue::Bool(true));
4372 assert_eq!(
4373 eval("builtins.isInt \"hello\""),
4374 VMValue::Bool(false)
4375 );
4376 }
4377 #[test]
4378 fn builtin_is_string() {
4379 assert_eq!(eval("builtins.isString \"hi\""), VMValue::Bool(true));
4380 assert_eq!(eval("builtins.isString 42"), VMValue::Bool(false));
4381 }
4382 #[test]
4383 fn builtin_is_list() {
4384 assert_eq!(eval("builtins.isList [1]"), VMValue::Bool(true));
4385 assert_eq!(eval("builtins.isList 42"), VMValue::Bool(false));
4386 }
4387 #[test]
4388 fn builtin_is_attrs() {
4389 assert_eq!(
4390 eval("builtins.isAttrs { a = 1; }"),
4391 VMValue::Bool(true)
4392 );
4393 assert_eq!(eval("builtins.isAttrs 42"), VMValue::Bool(false));
4394 }
4395 #[test]
4396 fn builtin_is_function() {
4397 assert_eq!(
4398 eval("builtins.isFunction (x: x)"),
4399 VMValue::Bool(true)
4400 );
4401 assert_eq!(eval("builtins.isFunction 42"), VMValue::Bool(false));
4402 }
4403 #[test]
4404 fn builtin_is_bool() {
4405 assert_eq!(eval("builtins.isBool true"), VMValue::Bool(true));
4406 assert_eq!(eval("builtins.isBool 42"), VMValue::Bool(false));
4407 }
4408 #[test]
4409 fn builtin_is_null() {
4410 assert_eq!(eval("builtins.isNull null"), VMValue::Bool(true));
4411 assert_eq!(eval("builtins.isNull 42"), VMValue::Bool(false));
4412 }
4413 #[test]
4414 fn builtin_string_length() {
4415 assert_eq!(
4416 eval("builtins.stringLength \"hello\""),
4417 VMValue::Int(5)
4418 );
4419 }
4420 #[test]
4421 fn builtin_to_string_int() {
4422 assert_eq!(
4423 eval("builtins.toString 42"),
4424 VMValue::String("42".to_string())
4425 );
4426 }
4427 #[test]
4428 fn builtin_to_string_bool() {
4429 assert_eq!(
4430 eval("builtins.toString true"),
4431 VMValue::String("1".to_string())
4432 );
4433 }
4434 #[test]
4435 fn builtin_throw() {
4436 let result = eval_err("builtins.throw \"test error\"");
4437 assert!(matches!(result, VMError::Throw(_)));
4438 }
4439 #[test]
4440 fn builtin_abort() {
4441 let result = eval_err("builtins.abort \"fatal\"");
4442 assert!(matches!(result, VMError::Throw(_)));
4443 }
4444 #[test]
4445 fn builtin_add_curried() {
4446 assert_eq!(eval("builtins.add 3 4"), VMValue::Int(7));
4447 }
4448 #[test]
4449 fn builtin_sub_curried() {
4450 assert_eq!(eval("builtins.sub 10 3"), VMValue::Int(7));
4451 }
4452 #[test]
4453 fn builtin_mul_curried() {
4454 assert_eq!(eval("builtins.mul 6 7"), VMValue::Int(42));
4455 }
4456 #[test]
4457 fn builtin_div_curried() {
4458 assert_eq!(eval("builtins.div 42 6"), VMValue::Int(7));
4459 }
4460 #[test]
4461 fn builtin_elem_at() {
4462 assert_eq!(eval("builtins.elemAt [10 20 30] 1"), VMValue::Int(20));
4463 }
4464 #[test]
4465 fn builtin_elem() {
4466 assert_eq!(eval("builtins.elem 2 [1 2 3]"), VMValue::Bool(true));
4467 assert_eq!(eval("builtins.elem 5 [1 2 3]"), VMValue::Bool(false));
4468 }
4469 #[test]
4470 fn builtin_concat_lists() {
4471 let result = eval_full_helper("builtins.concatLists [[1 2] [3 4]]");
4472 assert_eq!(
4473 result,
4474 StringKeyedValue::List(vec![
4475 StringKeyedValue::Int(1),
4476 StringKeyedValue::Int(2),
4477 StringKeyedValue::Int(3),
4478 StringKeyedValue::Int(4),
4479 ])
4480 );
4481 }
4482 #[test]
4483 fn builtin_has_prefix() {
4484 assert_eq!(
4485 eval("builtins.hasPrefix \"he\" \"hello\""),
4486 VMValue::Bool(true)
4487 );
4488 assert_eq!(
4489 eval("builtins.hasPrefix \"wo\" \"hello\""),
4490 VMValue::Bool(false)
4491 );
4492 }
4493 #[test]
4494 fn builtin_has_suffix() {
4495 assert_eq!(
4496 eval("builtins.hasSuffix \"lo\" \"hello\""),
4497 VMValue::Bool(true)
4498 );
4499 }
4500 #[test]
4501 fn builtin_concat_strings_sep() {
4502 assert_eq!(
4503 eval("builtins.concatStringsSep \", \" [\"a\" \"b\" \"c\"]"),
4504 VMValue::String("a, b, c".to_string())
4505 );
4506 }
4507 #[test]
4508 fn builtin_to_lower() {
4509 assert_eq!(
4510 eval("builtins.toLower \"Hello World\""),
4511 VMValue::String("hello world".to_string())
4512 );
4513 }
4514 #[test]
4515 fn builtin_to_upper() {
4516 assert_eq!(
4517 eval("builtins.toUpper \"hello\""),
4518 VMValue::String("HELLO".to_string())
4519 );
4520 }
4521 #[test]
4522 fn builtin_from_json() {
4523 assert_eq!(
4524 eval("builtins.fromJSON \"42\""),
4525 VMValue::Int(42)
4526 );
4527 assert_eq!(
4528 eval("builtins.fromJSON \"true\""),
4529 VMValue::Bool(true)
4530 );
4531 }
4532 #[test]
4533 fn builtin_seq() {
4534 assert_eq!(eval("builtins.seq 1 42"), VMValue::Int(42));
4535 }
4536 #[test]
4537 fn builtin_deep_seq() {
4538 assert_eq!(eval("builtins.deepSeq [1 2] 42"), VMValue::Int(42));
4539 }
4540 #[test]
4541 fn builtin_trace() {
4542 assert_eq!(
4543 eval("builtins.trace \"debug\" 42"),
4544 VMValue::Int(42)
4545 );
4546 }
4547 #[test]
4548 fn builtin_ceil_floor() {
4549 assert_eq!(eval("builtins.ceil 3.2"), VMValue::Int(4));
4550 assert_eq!(eval("builtins.floor 3.8"), VMValue::Int(3));
4551 }
4552 #[test]
4553 fn builtin_bit_ops() {
4554 assert_eq!(eval("builtins.bitAnd 12 10"), VMValue::Int(8));
4555 assert_eq!(eval("builtins.bitOr 12 10"), VMValue::Int(14));
4556 assert_eq!(eval("builtins.bitXor 12 10"), VMValue::Int(6));
4557 }
4558 #[test]
4559 fn builtin_intersect_attrs() {
4560 let result =
4561 eval_full_helper("builtins.intersectAttrs { a = 1; b = 2; } { a = 10; c = 30; }");
4562 match result {
4563 StringKeyedValue::Attrs(map) => {
4564 assert_eq!(map.get("a"), Some(&StringKeyedValue::Int(10)));
4565 assert!(!map.contains_key("b"));
4566 assert!(!map.contains_key("c"));
4567 }
4568 _ => panic!("expected Attrs, got {result:?}"),
4569 }
4570 }
4571 #[test]
4572 fn builtin_attr_values() {
4573 let result = eval_full_helper("builtins.attrValues { a = 1; b = 2; }");
4574 match result {
4575 StringKeyedValue::List(items) => {
4576 assert_eq!(items.len(), 2);
4577 assert!(items.contains(&StringKeyedValue::Int(1)));
4578 assert!(items.contains(&StringKeyedValue::Int(2)));
4579 }
4580 _ => panic!("expected List, got {result:?}"),
4581 }
4582 }
4583 #[test]
4584 fn builtin_to_int() {
4585 assert_eq!(eval("builtins.toInt \"42\""), VMValue::Int(42));
4586 }
4587 #[test]
4588 fn builtin_replace_strings() {
4589 assert_eq!(
4590 eval("builtins.replaceStrings [\"o\"] [\"0\"] \"foo\""),
4591 VMValue::String("f00".to_string())
4592 );
4593 }
4594 #[test]
4595 fn builtin_substring() {
4596 assert_eq!(
4597 eval("builtins.substring 1 3 \"hello\""),
4598 VMValue::String("ell".to_string())
4599 );
4600 }
4601 #[test]
4603 fn import_basic() {
4604 let dir = tempfile::tempdir().unwrap();
4605 let file_path = dir.path().join("test.nix");
4606 std::fs::write(&file_path, "42").unwrap();
4607 let nix_expr = format!("import {}", file_path.display());
4608 assert_eq!(eval(&nix_expr), VMValue::Int(42));
4609 }
4610 #[test]
4611 fn import_cached() {
4612 let dir = tempfile::tempdir().unwrap();
4613 let file_path = dir.path().join("cached.nix");
4614 std::fs::write(&file_path, "{ x = 1; }").unwrap();
4615 let nix_expr = format!(
4616 "let a = import {}; b = import {}; in a == b",
4617 file_path.display(),
4618 file_path.display()
4619 );
4620 assert_eq!(eval(&nix_expr), VMValue::Bool(true));
4621 }
4622 #[test]
4623 fn import_attrset() {
4624 let dir = tempfile::tempdir().unwrap();
4625 let file_path = dir.path().join("attrs.nix");
4626 std::fs::write(&file_path, "{ greeting = \"hello\"; }").unwrap();
4627 let nix_expr = format!("(import {}).greeting", file_path.display());
4628 assert_eq!(eval(&nix_expr), VMValue::String("hello".to_string()));
4629 }
4630 #[test]
4631 fn import_directory_default_nix() {
4632 let dir = tempfile::tempdir().unwrap();
4634 let sub = dir.path().join("mylib");
4635 std::fs::create_dir(&sub).unwrap();
4636 std::fs::write(sub.join("default.nix"), "{ x = 42; }").unwrap();
4637 let nix_expr = format!("(import {}).x", sub.display());
4638 assert_eq!(eval(&nix_expr), VMValue::Int(42));
4639 }
4640 #[test]
4641 fn import_directory_cached() {
4642 let dir = tempfile::tempdir().unwrap();
4644 let sub = dir.path().join("lib");
4645 std::fs::create_dir(&sub).unwrap();
4646 std::fs::write(sub.join("default.nix"), "{ v = 99; }").unwrap();
4647 let nix_expr = format!(
4648 "let a = import {}; b = import {}; in a == b",
4649 sub.display(),
4650 sub.display()
4651 );
4652 assert_eq!(eval(&nix_expr), VMValue::Bool(true));
4653 }
4654 #[test]
4655 fn import_directory_nested() {
4656 let dir = tempfile::tempdir().unwrap();
4658 let lib = dir.path().join("lib");
4659 let sub = lib.join("sub");
4660 std::fs::create_dir_all(&sub).unwrap();
4661 std::fs::write(sub.join("default.nix"), "{ val = 7; }").unwrap();
4662 std::fs::write(
4663 lib.join("default.nix"),
4664 &format!("(import {}).val + 3", sub.display()),
4665 )
4666 .unwrap();
4667 let nix_expr = format!("import {}", lib.display());
4668 assert_eq!(eval(&nix_expr), VMValue::Int(10));
4669 }
4670 #[test]
4672 fn lazy_unused_throw_in_attrset() {
4673 assert_eq!(
4674 eval("let s = { a = 1; }; in s.a"),
4675 VMValue::Int(1)
4676 );
4677 }
4678 #[test]
4679 fn lazy_unused_let_binding() {
4680 assert_eq!(eval("let x = 1; y = 2; in x"), VMValue::Int(1));
4681 }
4682 #[test]
4684 fn import_forces_thunk_before_type_check() {
4685 let dir = tempfile::tempdir().unwrap();
4688 let file_path = dir.path().join("forced.nix");
4689 std::fs::write(&file_path, "99").unwrap();
4690 let nix_expr = format!(
4691 "let p = {}; in import p",
4692 file_path.display()
4693 );
4694 assert_eq!(eval(&nix_expr), VMValue::Int(99));
4695 }
4696 #[test]
4697 fn import_with_path_value_succeeds() {
4698 let dir = tempfile::tempdir().unwrap();
4699 let file_path = dir.path().join("pathval.nix");
4700 std::fs::write(&file_path, "\"from-path\"").unwrap();
4701 let nix_expr = format!("import {}", file_path.display());
4702 assert_eq!(
4703 eval(&nix_expr),
4704 VMValue::String("from-path".to_string())
4705 );
4706 }
4707 #[test]
4708 fn import_with_string_value_succeeds() {
4709 let dir = tempfile::tempdir().unwrap();
4710 let file_path = dir.path().join("strval.nix");
4711 std::fs::write(&file_path, "\"from-string\"").unwrap();
4712 let nix_expr = format!(
4713 "let s = \"{}\"; in import s",
4714 file_path.display()
4715 );
4716 assert_eq!(
4717 eval(&nix_expr),
4718 VMValue::String("from-string".to_string())
4719 );
4720 }
4721 #[test]
4723 fn tail_call_deep_recursion_via_import() {
4724 let dir = tempfile::tempdir().unwrap();
4728 let file_path = dir.path().join("countdown.nix");
4729 std::fs::write(
4730 &file_path,
4731 "{ f, n }: if n == 0 then 0 else f { inherit f; n = n - 1; }",
4732 )
4733 .unwrap();
4734 let nix_expr = format!(
4737 "let g = import {}; in g {{ f = g; n = 2000; }}",
4738 file_path.display()
4739 );
4740 assert_eq!(eval(&nix_expr), VMValue::Int(0));
4741 }
4742 #[test]
4743 fn tail_call_simple_lambda_chain() {
4744 assert_eq!(
4748 eval("let g = x: x + 1; f = x: g x; in f 41"),
4749 VMValue::Int(42)
4750 );
4751 }
4752 #[test]
4753 fn tail_call_if_branches() {
4754 assert_eq!(
4757 eval("let f = x: if x > 0 then x else x + 1; in f 10"),
4758 VMValue::Int(10)
4759 );
4760 assert_eq!(
4761 eval("let f = x: if x > 0 then x else x + 1; in f 0"),
4762 VMValue::Int(1)
4763 );
4764 }
4765 #[test]
4767 fn builtin_get_env_returns_value() {
4768 unsafe { std::env::set_var("SUI_TEST_VAR", "hello_sui") };
4771 assert_eq!(
4772 eval("builtins.getEnv \"SUI_TEST_VAR\""),
4773 VMValue::String("hello_sui".to_string())
4774 );
4775 unsafe { std::env::remove_var("SUI_TEST_VAR") };
4776 }
4777 #[test]
4778 fn builtin_get_env_missing_returns_empty() {
4779 unsafe { std::env::remove_var("SUI_NONEXISTENT_VAR_12345") };
4782 assert_eq!(
4783 eval("builtins.getEnv \"SUI_NONEXISTENT_VAR_12345\""),
4784 VMValue::String(String::new())
4785 );
4786 }
4787 #[test]
4788 fn builtin_try_eval_success() {
4789 let result = eval_full_helper("builtins.tryEval 42");
4791 match result {
4792 StringKeyedValue::Attrs(map) => {
4793 assert_eq!(
4794 map.get("success"),
4795 Some(&StringKeyedValue::Bool(true))
4796 );
4797 assert_eq!(
4798 map.get("value"),
4799 Some(&StringKeyedValue::Int(42))
4800 );
4801 }
4802 _ => panic!("expected Attrs, got {result:?}"),
4803 }
4804 }
4805 #[test]
4806 fn builtin_try_eval_with_non_throwing_expr() {
4807 let result = eval_full_helper(
4810 "builtins.tryEval (1 + 2)"
4811 );
4812 match result {
4813 StringKeyedValue::Attrs(map) => {
4814 assert_eq!(
4815 map.get("success"),
4816 Some(&StringKeyedValue::Bool(true))
4817 );
4818 assert_eq!(
4819 map.get("value"),
4820 Some(&StringKeyedValue::Int(3))
4821 );
4822 }
4823 _ => panic!("expected Attrs, got {result:?}"),
4824 }
4825 }
4826 #[test]
4827 fn builtin_try_eval_with_throw_catches() {
4828 let result = eval_full_helper(
4834 "let bad = builtins.throw \"oops\"; in builtins.tryEval bad"
4835 );
4836 match result {
4837 StringKeyedValue::Attrs(map) => {
4838 assert_eq!(
4839 map.get("success"),
4840 Some(&StringKeyedValue::Bool(false))
4841 );
4842 assert_eq!(
4843 map.get("value"),
4844 Some(&StringKeyedValue::Bool(false))
4845 );
4846 }
4847 _ => panic!("expected Attrs, got {result:?}"),
4848 }
4849 }
4850 #[test]
4852 fn if_else_in_let_body_stack_depth() {
4853 assert_eq!(
4856 eval("let a = 1; in if a == 1 then 10 else 20"),
4857 VMValue::Int(10),
4858 );
4859 }
4860 #[test]
4861 fn nested_let_with_if_else() {
4862 assert_eq!(
4864 eval(r#"
4865 let
4866 a = 1;
4867 b = if a == 1 then 2 else 3;
4868 in
4869 let c = b + 10; in c
4870 "#),
4871 VMValue::Int(12),
4872 );
4873 }
4874 #[test]
4875 fn short_circuit_and_in_let_body() {
4876 assert_eq!(
4878 eval("let x = true; in x && false"),
4879 VMValue::Bool(false),
4880 );
4881 }
4882 #[test]
4883 fn short_circuit_or_in_let_body() {
4884 assert_eq!(
4885 eval("let x = false; in x || true"),
4886 VMValue::Bool(true),
4887 );
4888 }
4889 #[test]
4890 fn short_circuit_implication_in_let_body() {
4891 assert_eq!(
4893 eval("let x = false; in x -> 42"),
4894 VMValue::Bool(true),
4895 );
4896 }
4897 #[test]
4898 fn inherit_from_in_attrset_stack_depth() {
4899 assert_eq!(
4902 eval(r#"
4903 let
4904 src = { a = 1; b = 2; };
4905 result = { inherit (src) a b; c = 3; };
4906 in result.a + result.b + result.c
4907 "#),
4908 VMValue::Int(6),
4909 );
4910 }
4911 #[test]
4912 fn inherit_from_many_fields_stack_depth() {
4913 assert_eq!(
4916 eval(r#"
4917 let
4918 s = { w = 1; x = 2; y = 3; z = 4; };
4919 r = { inherit (s) w x y z; extra = 10; };
4920 in r.w + r.x + r.y + r.z + r.extra
4921 "#),
4922 VMValue::Int(20),
4923 );
4924 }
4925 #[test]
4926 fn if_else_followed_by_let_binding() {
4927 assert_eq!(
4931 eval(r#"
4932 let
4933 a = 1;
4934 b = 2;
4935 c = 3;
4936 in
4937 let
4938 x = if a == 1 then b else c;
4939 y = x + 100;
4940 in y
4941 "#),
4942 VMValue::Int(102),
4943 );
4944 }
4945 #[test]
4946 fn multi_segment_hasattr_stack_depth() {
4947 assert_eq!(
4950 eval(r#"
4951 let
4952 s = { a = { b = 1; }; };
4953 has = s ? a.b;
4954 val = if has then 42 else 0;
4955 in val
4956 "#),
4957 VMValue::Int(42),
4958 );
4959 }
4960 #[test]
4961 fn many_let_bindings_with_if_else() {
4962 assert_eq!(
4966 eval(r#"
4967 let
4968 a = 1;
4969 b = 2;
4970 c = 3;
4971 d = 4;
4972 e = 5;
4973 f = 6;
4974 g = 7;
4975 h = 8;
4976 i = 9;
4977 j = 10;
4978 in
4979 let
4980 x = if a == 1 then b else c;
4981 y = if d == 4 then e else f;
4982 z = if g == 7 then h else i;
4983 w = j;
4984 in x + y + z + w
4985 "#),
4986 VMValue::Int(25),
4987 );
4988 }
4989 #[test]
4990 fn import_in_pattern_default_stack_depth() {
4991 assert_eq!(
4997 eval(r#"
4998 let
4999 f = { a ? 1, b ? 2, c ? 3 }:
5000 a + b + c;
5001 in f {}
5002 "#),
5003 VMValue::Int(6),
5004 );
5005 }
5006 #[test]
5007 fn pattern_lambda_many_defaults_then_let() {
5008 assert_eq!(
5014 eval(r#"
5015 let
5016 mk = { a, b ? 10, c ? 20, d ? 30, e ? 40 }:
5017 let
5018 sum = a + b + c + d + e;
5019 doubled = sum + sum;
5020 in doubled;
5021 in mk { a = 1; }
5022 "#),
5023 VMValue::Int(202),
5024 );
5025 }
5026 #[test]
5028 fn rec_dotted_lambda_captures_sibling() {
5029 let result = eval_full_helper(
5034 r#"rec { types.a = 1; types.b = 2; f = _: types; }.f 0"#,
5035 );
5036 match result {
5037 StringKeyedValue::Attrs(ref m) => {
5038 assert_eq!(m.get("a"), Some(&StringKeyedValue::Int(1)));
5039 assert_eq!(m.get("b"), Some(&StringKeyedValue::Int(2)));
5040 }
5041 other => panic!("expected attrset, got {other:?}"),
5042 }
5043 }
5044 #[test]
5045 fn rec_dotted_lambda_attr_select() {
5046 assert_eq!(
5048 eval(r#"rec { types.a = 1; types.b = 2; f = x: types.b; result = f 0; }.result"#),
5049 VMValue::Int(2),
5050 );
5051 }
5052 #[test]
5053 fn rec_dotted_lambda_assert_check() {
5054 assert_eq!(
5058 eval(r#"
5059 rec {
5060 types.parsedPlatform = { check = _: true; };
5061 mkSystem = components:
5062 assert types.parsedPlatform.check components;
5063 components;
5064 result = mkSystem 42;
5065 }.result
5066 "#),
5067 VMValue::Int(42),
5068 );
5069 }
5070 #[test]
5071 fn let_lambda_captures_rec_sibling() {
5072 assert_eq!(
5075 eval(r#"let a = 1 + 1; f = _: a; in f 0"#),
5076 VMValue::Int(2),
5077 );
5078 }
5079 #[test]
5080 fn rec_dotted_multiple_lambdas() {
5081 assert_eq!(
5083 eval(r#"
5084 rec {
5085 a.x = 10;
5086 b.y = 20;
5087 f = _: a.x + b.y;
5088 result = f 0;
5089 }.result
5090 "#),
5091 VMValue::Int(30),
5092 );
5093 }
5094}