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 if let Ok(dir) = std::env::var("SUI_EMIT_DRV") {
2729 if !dir.is_empty() {
2730 let base = drv_path.rsplit('/').next().unwrap_or(&drv_path);
2731 let _ = std::fs::create_dir_all(&dir);
2732 let _ = std::fs::write(
2733 std::path::Path::new(&dir).join(base),
2734 drv_content.as_bytes(),
2735 );
2736 }
2737 }
2738
2739 let out_output = drv.outputs.get("out");
2747 let method_algo = out_output
2748 .map(|o| o.hash_algo.clone())
2749 .unwrap_or_default();
2750 let output_hash_hex = out_output
2751 .map(|o| o.hash.clone())
2752 .unwrap_or_default();
2753 let modulo_preimage =
2754 format!("fixed:out:{method_algo}:{output_hash_hex}:{out_path}");
2755 let modulo_hex: String = {
2756 use sha2::{Digest, Sha256};
2757 Sha256::digest(modulo_preimage.as_bytes())
2758 .iter()
2759 .map(|b| format!("{b:02x}"))
2760 .collect()
2761 };
2762 sui_spec::derivation::remember_modulo_hash(&drv_path, &modulo_hex);
2763
2764 let mut out_paths = BTreeMap::new();
2765 out_paths.insert("out".to_string(), out_path);
2766 (drv_path, out_paths, drv)
2767 } else {
2768 let algo = sui_spec::derivation::load_canonical().map_err(|e| {
2776 VMError::TypeError {
2777 expected: "valid derivation algorithm spec",
2778 got: "load error",
2779 context: format!("sui-spec: {e}"),
2780 }
2781 })?;
2782 let (drv_path, out_paths, drv_final) =
2783 sui_spec::derivation::apply(&algo, drv, outputs.clone(), &name)
2784 .map_err(|e| VMError::TypeError {
2785 expected: "derivation interpreter success",
2786 got: "interp error",
2787 context: format!("sui-spec: {e}"),
2788 })?;
2789 (drv_path, out_paths, drv_final)
2790 };
2791 for (output_name, output_path) in &out_paths {
2793 if let Some(output) = drv.outputs.get_mut(output_name) {
2794 if output.path.is_empty() {
2795 output.path.clone_from(output_path);
2796 }
2797 }
2798 drv.env.insert(output_name.clone(), output_path.clone());
2799 }
2800 let drv_content_final = drv.serialize();
2801 let store_dir = std::env::var("SUI_STORE_DIR")
2802 .unwrap_or_else(|_| "/nix/store".to_string());
2803 let disk_path = if store_dir != "/nix/store" {
2804 drv_path.replacen("/nix/store", &store_dir, 1)
2805 } else {
2806 drv_path.clone()
2807 };
2808 let drv_file = std::path::Path::new(&disk_path);
2809 if !drv_file.exists() {
2810 if let Some(parent) = drv_file.parent() {
2811 std::fs::create_dir_all(parent).ok();
2812 }
2813 match std::fs::write(drv_file, drv_content_final.as_bytes()) {
2814 Ok(()) => {}
2815 Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => {
2816 let fallback_dir = std::env::temp_dir().join("sui-drv-cache");
2817 std::fs::create_dir_all(&fallback_dir).ok();
2818 let fallback_path = fallback_dir.join(
2819 drv_file.file_name().unwrap_or_default(),
2820 );
2821 let _ = std::fs::write(&fallback_path, drv_content_final.as_bytes());
2822 }
2823 Err(e) => {
2824 return Err(VMError::Throw(format!(
2825 "derivation: failed to write {drv_path}: {e}"
2826 )));
2827 }
2828 }
2829 }
2830 let mut result: BTreeMap<Symbol, NanBox> = attrs.clone();
2832 let type_sym = self.interner.intern("type");
2833 result.insert(type_sym, NanBox::string("derivation".to_string()));
2834 let drv_path_sym = self.interner.intern("drvPath");
2835 result.insert(drv_path_sym, NanBox::string(drv_path.clone()));
2836 let drv_attrs_sym = self.interner.intern("drvAttrs");
2838 result.insert(drv_attrs_sym, NanBox::attrs(attrs));
2839 let primary_out = out_paths
2840 .get("out")
2841 .cloned()
2842 .or_else(|| out_paths.values().next().cloned())
2843 .unwrap_or_default();
2844 let out_path_sym = self.interner.intern("outPath");
2845 result.insert(out_path_sym, NanBox::string(primary_out));
2846 let output_name_sym = self.interner.intern("outputName");
2848 let primary_output_name = if out_paths.contains_key("out") { "out" }
2849 else { out_paths.keys().next().map(|s| s.as_str()).unwrap_or("out") };
2850 result.insert(output_name_sym, NanBox::string(primary_output_name.to_string()));
2851 let mut all_outputs: Vec<NanBox> = Vec::new();
2852 for (output_name, output_path) in &out_paths {
2853 let mut out_attrs: BTreeMap<Symbol, NanBox> = BTreeMap::new();
2854 out_attrs.insert(out_path_sym, NanBox::string(output_path.clone()));
2855 out_attrs.insert(drv_path_sym, NanBox::string(drv_path.clone()));
2856 out_attrs.insert(type_sym, NanBox::string("derivation".to_string()));
2857 out_attrs.insert(output_name_sym, NanBox::string(output_name.clone()));
2858 let name_sym = self.interner.intern("name");
2859 out_attrs.insert(name_sym, NanBox::string(name.clone()));
2860 let out_val = NanBox::attrs(out_attrs);
2861 all_outputs.push(out_val.clone());
2862 let out_sym = self.interner.intern(output_name);
2863 result.insert(out_sym, out_val);
2864 }
2865 let all_sym = self.interner.intern("all");
2867 result.insert(all_sym, NanBox::list(all_outputs));
2868 Ok(NanBox::attrs(result))
2869 }
2870 fn call_builtin_with_scoped_import_dispatch(
2872 &mut self,
2873 func: Rc<dyn Fn(Vec<VMValue>) -> Result<VMValue, VMError>>,
2874 arg: VMValue,
2875 ) -> Result<NanBox, VMError> {
2876 let arg = if let VMValue::Thunk(ref thunk) = arg {
2878 let nb = NanBox::from_vmvalue(&arg);
2879 self.force_value(nb)?.to_vmvalue()
2880 } else {
2881 arg
2882 };
2883 match func(vec![arg]) {
2884 Ok(result) => Ok(NanBox::from_vmvalue(&result)),
2885 Err(VMError::Throw(ref msg))
2886 if msg.starts_with("__scopedImport_dispatch__:") =>
2887 {
2888 let rest = &msg["__scopedImport_dispatch__:".len()..];
2889 if let Some(colon_pos) = rest.rfind(':') {
2890 let scope_nix = &rest[..colon_pos];
2891 let path = &rest[colon_pos + 1..];
2892 self.vm_scoped_import(scope_nix, path)
2893 } else {
2894 Err(VMError::Throw(msg.clone()))
2895 }
2896 }
2897 Err(e) => Err(e),
2898 }
2899 }
2900 fn vm_get_flake(&mut self, flake_ref: &str) -> Result<NanBox, VMError> {
2908 let resolved = FLAKE_RESOLVER.with(|r| {
2910 let borrow = r.borrow();
2911 if let Some(ref resolver) = *borrow {
2912 Some(resolver(flake_ref))
2913 } else {
2914 None
2915 }
2916 });
2917 if let Some(result) = resolved {
2918 let sk = result.map_err(|e| VMError::Throw(format!("getFlake: {e}")))?;
2919 return Ok(self.string_keyed_to_nanbox(&sk));
2920 }
2921 self.vm_get_flake_native(flake_ref)
2923 }
2924 fn string_keyed_to_nanbox(&mut self, sk: &crate::value::StringKeyedValue) -> NanBox {
2931 match sk {
2932 crate::value::StringKeyedValue::Null => NanBox::null(),
2933 crate::value::StringKeyedValue::Bool(b) => NanBox::bool(*b),
2934 crate::value::StringKeyedValue::Int(n) => NanBox::int(*n),
2935 crate::value::StringKeyedValue::Float(f) => NanBox::float(*f),
2936 crate::value::StringKeyedValue::String(s) => NanBox::string(s.clone()),
2937 crate::value::StringKeyedValue::Path(p) => NanBox::from_vmvalue(&VMValue::Path(p.clone())),
2938 crate::value::StringKeyedValue::List(items) => {
2939 let nb_items: Vec<NanBox> = items.iter().map(|v| self.string_keyed_to_nanbox(v)).collect();
2940 NanBox::list(nb_items)
2941 }
2942 crate::value::StringKeyedValue::Attrs(map) => {
2943 let mut nb_map: BTreeMap<Symbol, NanBox> = BTreeMap::new();
2944 for (k, v) in map {
2945 let sym = self.interner.intern(k);
2946 nb_map.insert(sym, self.string_keyed_to_nanbox(v));
2947 }
2948 NanBox::attrs(nb_map)
2949 }
2950 crate::value::StringKeyedValue::Lambda => NanBox::null(),
2951 crate::value::StringKeyedValue::Callable(cb) => {
2952 let cb_clone = Rc::clone(cb);
2953 let builtin = crate::value::VMBuiltin {
2954 name: "<bridge-fn>",
2955 arity: 1,
2956 func: Rc::new(move |args: Vec<VMValue>| {
2957 let interner = crate::intern::Interner::new();
2958 let sk_arg = args.into_iter().next()
2959 .unwrap_or(VMValue::Null)
2960 .to_string_keyed(&interner);
2961 let sk_result = cb_clone(sk_arg)
2962 .map_err(|e| crate::error::VMError::Throw(e))?;
2963 let mut tmp_interner = crate::intern::Interner::new();
2964 Ok(crate::builtins::string_keyed_to_vmvalue(&sk_result, &mut tmp_interner))
2965 }),
2966 };
2967 NanBox::builtin(builtin)
2968 }
2969 crate::value::StringKeyedValue::Thunk(cb) => {
2970 let thunk = VMThunk {
2974 state: Rc::new(Cell::new(Some(ThunkState::NativeCallback(Rc::clone(cb))))),
2975 };
2976 NanBox::thunk(thunk)
2977 }
2978 }
2979 }
2980 fn vm_get_flake_native(&mut self, flake_ref: &str) -> Result<NanBox, VMError> {
2982 let flake_dir = if flake_ref.starts_with('/') || flake_ref.starts_with('.') {
2983 std::path::PathBuf::from(flake_ref)
2984 } else if let Some(path) = flake_ref.strip_prefix("path:") {
2985 std::path::PathBuf::from(path)
2986 } else {
2987 return Err(VMError::Throw(format!(
2988 "getFlake: unsupported flake reference: {flake_ref} (only path: refs supported in VM)"
2989 )));
2990 };
2991 let flake_nix = flake_dir.join("flake.nix");
2992 if !flake_nix.exists() {
2993 return Err(VMError::Throw(format!(
2994 "getFlake: flake.nix not found in {}",
2995 flake_dir.display()
2996 )));
2997 }
2998 let flake_nix_str = flake_nix.to_string_lossy().to_string();
3000 let flake_attrs = self.import_file(&flake_nix_str)?;
3001 let flake_attrs = self.force_value(flake_attrs)?;
3002 let self_sym = self.interner.intern("self");
3004 let out_path_sym = self.interner.intern("outPath");
3005 let flake_dir_str = flake_dir.to_string_lossy().to_string();
3006 let mut self_attrs: BTreeMap<Symbol, NanBox> = BTreeMap::new();
3007 self_attrs.insert(out_path_sym, NanBox::string(flake_dir_str.clone()));
3008 let mut inputs: BTreeMap<Symbol, NanBox> = BTreeMap::new();
3009 inputs.insert(self_sym, NanBox::attrs(self_attrs));
3010 let lock_path = flake_dir.join("flake.lock");
3012 if lock_path.exists() {
3013 if let Ok(lock_str) = std::fs::read_to_string(&lock_path) {
3014 if let Ok(lock_json) = serde_json::from_str::<serde_json::Value>(&lock_str) {
3015 self.resolve_flake_lock_inputs(&lock_json, &flake_dir, &mut inputs);
3016 }
3017 }
3018 }
3019 let outputs_sym = self.interner.intern("outputs");
3021 if let Some(attrs) = flake_attrs.as_attrs() {
3022 if let Some(outputs_func) = attrs.get(&outputs_sym) {
3023 let outputs_func = outputs_func.clone();
3024 let outputs_func = self.force_value(outputs_func)?;
3025 let inputs_nb = NanBox::attrs(inputs);
3026 let result = self.call_callable(&outputs_func, inputs_nb)?;
3027 let mut result_forced = self.force_value(result)?;
3028 let desc_sym = self.interner.intern("description");
3030 if let Some(desc) = attrs.get(&desc_sym) {
3031 if let Some(result_attrs) = result_forced.as_attrs() {
3032 let mut merged = result_attrs.clone();
3033 merged.insert(desc_sym, desc.clone());
3034 result_forced = NanBox::attrs(merged);
3035 }
3036 }
3037 return Ok(result_forced);
3038 }
3039 }
3040 Ok(flake_attrs)
3042 }
3043 fn resolve_flake_lock_inputs(
3045 &mut self,
3046 lock: &serde_json::Value,
3047 flake_dir: &std::path::Path,
3048 inputs: &mut BTreeMap<Symbol, NanBox>,
3049 ) {
3050 let nodes = match lock.get("nodes").and_then(|n| n.as_object()) {
3051 Some(n) => n,
3052 None => return,
3053 };
3054 let root_node = match lock.get("root").and_then(|r| r.as_str()) {
3055 Some(r) => r.to_string(),
3056 None => "root".to_string(),
3057 };
3058 let root_inputs = match nodes
3059 .get(&root_node)
3060 .and_then(|n| n.get("inputs"))
3061 .and_then(|i| i.as_object())
3062 {
3063 Some(i) => i,
3064 None => return,
3065 };
3066 for (input_name, node_ref) in root_inputs {
3067 let node_key = match node_ref.as_str() {
3068 Some(s) => s.to_string(),
3069 None => {
3070 if let Some(arr) = node_ref.as_array() {
3071 if let Some(s) = arr.first().and_then(|v| v.as_str()) {
3072 s.to_string()
3073 } else {
3074 continue;
3075 }
3076 } else {
3077 continue;
3078 }
3079 }
3080 };
3081 if let Some(node) = nodes.get(&node_key) {
3082 if let Some(locked) = node.get("locked") {
3083 let locked_type = locked.get("type").and_then(|t| t.as_str()).unwrap_or("");
3084 let out_path = match locked_type {
3085 "path" => {
3086 if let Some(p) = locked.get("path").and_then(|p| p.as_str()) {
3087 let path = if p.starts_with('/') {
3088 std::path::PathBuf::from(p)
3089 } else {
3090 flake_dir.join(p)
3091 };
3092 path.to_string_lossy().to_string()
3093 } else {
3094 continue;
3095 }
3096 }
3097 _ => continue, };
3099 let input_sym = self.interner.intern(input_name);
3100 let out_path_sym = self.interner.intern("outPath");
3101 let mut input_attrs: BTreeMap<Symbol, NanBox> = BTreeMap::new();
3102 input_attrs.insert(out_path_sym, NanBox::string(out_path));
3103 inputs.insert(input_sym, NanBox::attrs(input_attrs));
3104 }
3105 }
3106 }
3107 }
3108 fn vm_scoped_import(
3112 &mut self,
3113 scope_nix: &str,
3114 path: &str,
3115 ) -> Result<NanBox, VMError> {
3116 let read_path = crate::bridge::materialize(path);
3119 let resolved = if std::path::Path::new(&read_path).is_dir() {
3121 format!("{read_path}/default.nix")
3122 } else {
3123 read_path
3124 };
3125 let source = std::fs::read_to_string(&resolved)
3126 .map_err(|e| VMError::ImportError(format!("{path}: {e}")))?;
3127 let wrapped = format!("with {scope_nix}; {source}");
3129 let file_dir = std::path::Path::new(&resolved)
3130 .parent()
3131 .map(|p| p.to_path_buf())
3132 .unwrap_or_default();
3133 let shared_interner = Rc::new(RefCell::new(std::mem::take(self.interner)));
3135 let chunk = Compiler::compile_with_shared_interner(&wrapped, file_dir, shared_interner.clone())
3136 .map_err(|e| VMError::ImportError(format!("{path}: {e}")))?;
3137 *self.interner = match Rc::try_unwrap(shared_interner) {
3138 Ok(cell) => cell.into_inner(),
3139 Err(rc) => rc.borrow().clone(),
3140 };
3141 if self.frames.len() >= MAX_CALL_DEPTH {
3142 return Err(VMError::StackOverflow);
3143 }
3144 let return_depth = self.frames.len();
3145 let stack_base = self.stack.len();
3146 self.frames.push(CallFrame {
3147 chunk: Rc::new(chunk),
3148 ip: 0,
3149 stack_base,
3150 upvalues: Vec::new(),
3151 });
3152 self.run_until(return_depth)
3153 }
3154 fn call_callable(&mut self, func: &NanBox, arg: NanBox) -> Result<NanBox, VMError> {
3156 if let Some(closure) = func.as_closure() {
3157 if self.frames.len() >= MAX_CALL_DEPTH {
3158 return Err(VMError::StackOverflow);
3159 }
3160 let upvalues = closure.upvalues.clone();
3161 let chunk = closure.chunk.clone();
3162 let return_depth = self.frames.len();
3163 let stack_base = self.stack.len();
3164 self.push(arg);
3165 self.frames.push(CallFrame {
3166 chunk,
3167 ip: 0,
3168 stack_base,
3169 upvalues,
3170 });
3171 let result = self.run_until(return_depth)?;
3172 self.stack.truncate(stack_base);
3173 self.force_value(result)
3176 } else if func.is_higher_order_builtin() {
3177 let hob = func.as_higher_order_builtin().unwrap().clone();
3178 self.call_higher_order_builtin(&hob, arg)
3179 } else if let Some(builtin) = func.as_builtin() {
3180 let arg = self.force_value(arg)?;
3182 if let Some(result) = self.try_vm_builtin(builtin.name, &arg)? {
3183 Ok(result)
3184 } else {
3185 let deep = self.deep_force(arg)?;
3187 let arg_vmval = deep.to_vmvalue();
3188 let builtin_func = builtin.func.clone();
3189 let result = self.call_builtin_with_scoped_import_dispatch(
3190 builtin_func, arg_vmval,
3191 )?;
3192 Ok(result)
3193 }
3194 } else {
3195 Err(VMError::NotCallable(func.type_name().to_string()))
3196 }
3197 }
3198 #[allow(clippy::too_many_lines)]
3199 fn call_higher_order_builtin(
3200 &mut self,
3201 hob: &HigherOrderBuiltin,
3202 arg: NanBox,
3203 ) -> Result<NanBox, VMError> {
3204 use HigherOrderOp::*;
3205 let arg = self.force_value(arg)?;
3208 match hob.op {
3209 Map => {
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.map".to_string(),
3216 }),
3217 };
3218 let func_nb = NanBox::from_vmvalue(&hob.func);
3219 let mut results = Vec::with_capacity(list.len());
3220 for item in list {
3221 let r = self.call_callable(&func_nb, NanBox::from_vmvalue(item))?;
3222 results.push(r);
3223 }
3224 Ok(NanBox::list(results))
3225 }
3226 Filter => {
3227 let list_val = arg.to_vmvalue();
3228 let list = match &list_val {
3229 VMValue::List(l) => l,
3230 other => return Err(VMError::TypeError {
3231 expected: "list", got: other.type_name(),
3232 context: "builtins.filter".to_string(),
3233 }),
3234 };
3235 let func_nb = NanBox::from_vmvalue(&hob.func);
3236 let mut results = Vec::new();
3237 for item in list {
3238 let item_nb = NanBox::from_vmvalue(item);
3239 let r = self.call_callable(&func_nb, item_nb.clone())?;
3240
3241 if r.is_truthy()? { results.push(item_nb); }
3242 }
3243 Ok(NanBox::list(results))
3244 }
3245 FoldlP1 => {
3246 let init_vmval = arg.to_vmvalue();
3247 Ok(NanBox::from_vmvalue(&VMValue::HigherOrderBuiltin(
3248 HigherOrderBuiltin {
3249 op: FoldlP2,
3250 func: hob.func.clone(),
3251 extra_args: vec![init_vmval],
3252 },
3253 )))
3254 }
3255 FoldlP2 => {
3256 let list_val = arg.to_vmvalue();
3257 let list = match &list_val {
3258 VMValue::List(l) => l,
3259 other => return Err(VMError::TypeError {
3260 expected: "list", got: other.type_name(),
3261 context: "builtins.foldl'".to_string(),
3262 }),
3263 };
3264 let func_nb = NanBox::from_vmvalue(&hob.func);
3265 let mut acc = NanBox::from_vmvalue(&hob.extra_args[0]);
3266 for item in list {
3267 let partial = self.call_callable(&func_nb, acc)?;
3268 acc = self.call_callable(&partial, NanBox::from_vmvalue(item))?;
3269 }
3270 Ok(acc)
3271 }
3272 Sort => {
3273 let list_val = arg.to_vmvalue();
3274 let list = match &list_val {
3275 VMValue::List(l) => l.clone(),
3276 other => return Err(VMError::TypeError {
3277 expected: "list", got: other.type_name(),
3278 context: "builtins.sort".to_string(),
3279 }),
3280 };
3281 if list.len() <= 1 {
3282 return Ok(NanBox::from_vmvalue(&VMValue::List(list)));
3283 }
3284 let func_nb = NanBox::from_vmvalue(&hob.func);
3285 let mut sorted: Vec<VMValue> = Vec::with_capacity(list.len());
3286 for item in &list {
3287 let item_nb = NanBox::from_vmvalue(item);
3288 let mut pos = sorted.len();
3289 for (i, existing) in sorted.iter().enumerate() {
3290 let existing_nb = NanBox::from_vmvalue(existing);
3291 let partial = self.call_callable(&func_nb, item_nb.clone())?;
3292 let cmp_result = self.call_callable(&partial, existing_nb)?;
3293 if cmp_result.is_truthy()? { pos = i; break; }
3294 }
3295 sorted.insert(pos, item.clone());
3296 }
3297 Ok(NanBox::from_vmvalue(&VMValue::List(sorted)))
3298 }
3299 GenList => {
3300 let n = match arg.to_vmvalue() {
3301 VMValue::Int(n) => n,
3302 other => return Err(VMError::TypeError {
3303 expected: "int", got: other.type_name(),
3304 context: "builtins.genList".to_string(),
3305 }),
3306 };
3307 if n < 0 { return Err(VMError::Throw("genList: negative length".to_string())); }
3308 let func_nb = NanBox::from_vmvalue(&hob.func);
3309 let mut results = Vec::with_capacity(n as usize);
3310 for i in 0..n {
3311 results.push(self.call_callable(&func_nb, NanBox::int(i))?);
3312 }
3313 Ok(NanBox::list(results))
3314 }
3315 ConcatMap => {
3316 let list_val = arg.to_vmvalue();
3317 let list = match &list_val {
3318 VMValue::List(l) => l,
3319 other => return Err(VMError::TypeError {
3320 expected: "list", got: other.type_name(),
3321 context: "builtins.concatMap".to_string(),
3322 }),
3323 };
3324 let func_nb = NanBox::from_vmvalue(&hob.func);
3325 let mut results = Vec::new();
3326 for item in list {
3327 let mapped = self.call_callable(&func_nb, NanBox::from_vmvalue(item))?;
3328 match mapped.to_vmvalue() {
3329 VMValue::List(inner) => {
3330 for v in &inner { results.push(NanBox::from_vmvalue(v)); }
3331 }
3332 other => return Err(VMError::TypeError {
3333 expected: "list", got: other.type_name(),
3334 context: "builtins.concatMap result".to_string(),
3335 }),
3336 }
3337 }
3338 Ok(NanBox::list(results))
3339 }
3340 Any => {
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.any".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(true));
3353 }
3354 }
3355 Ok(NanBox::bool(false))
3356 }
3357 All => {
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.all".to_string(),
3364 }),
3365 };
3366 let func_nb = NanBox::from_vmvalue(&hob.func);
3367 for item in list {
3368 if !self.call_callable(&func_nb, NanBox::from_vmvalue(item))?.is_truthy()? {
3369 return Ok(NanBox::bool(false));
3370 }
3371 }
3372 Ok(NanBox::bool(true))
3373 }
3374 Partition => {
3375 let list_val = arg.to_vmvalue();
3376 let list = match &list_val {
3377 VMValue::List(l) => l,
3378 other => return Err(VMError::TypeError {
3379 expected: "list", got: other.type_name(),
3380 context: "builtins.partition".to_string(),
3381 }),
3382 };
3383 let func_nb = NanBox::from_vmvalue(&hob.func);
3384 let (mut right, mut wrong) = (Vec::new(), Vec::new());
3385 for item in list {
3386 let item_nb = NanBox::from_vmvalue(item);
3387 if self.call_callable(&func_nb, item_nb.clone())?.is_truthy()? {
3388 right.push(item_nb);
3389 } else {
3390 wrong.push(item_nb);
3391 }
3392 }
3393 let rs = self.interner.intern("right");
3394 let ws = self.interner.intern("wrong");
3395 let mut attrs = BTreeMap::new();
3396 attrs.insert(rs, NanBox::list(right));
3397 attrs.insert(ws, NanBox::list(wrong));
3398 Ok(NanBox::attrs(attrs))
3399 }
3400 GroupBy => {
3401 let list_val = arg.to_vmvalue();
3402 let list = match &list_val {
3403 VMValue::List(l) => l,
3404 other => return Err(VMError::TypeError {
3405 expected: "list", got: other.type_name(),
3406 context: "builtins.groupBy".to_string(),
3407 }),
3408 };
3409 let func_nb = NanBox::from_vmvalue(&hob.func);
3410 let mut groups: BTreeMap<String, Vec<NanBox>> = BTreeMap::new();
3411 for item in list {
3412 let item_nb = NanBox::from_vmvalue(item);
3413 let kr = self.call_callable(&func_nb, item_nb.clone())?;
3414 let ks = kr.as_string().ok_or_else(|| VMError::TypeError {
3415 expected: "string", got: kr.type_name(),
3416 context: "builtins.groupBy key".to_string(),
3417 })?.to_string();
3418 groups.entry(ks).or_default().push(item_nb);
3419 }
3420 let mut attrs = BTreeMap::new();
3421 for (k, vs) in groups {
3422 attrs.insert(self.interner.intern(&k), NanBox::list(vs));
3423 }
3424 Ok(NanBox::attrs(attrs))
3425 }
3426 MapAttrs => {
3427 let attrs_val = arg.to_vmvalue();
3428 let attrs = match &attrs_val {
3429 VMValue::Attrs(a) => a,
3430 other => return Err(VMError::TypeError {
3431 expected: "set", got: other.type_name(),
3432 context: "builtins.mapAttrs".to_string(),
3433 }),
3434 };
3435 let func_nb = NanBox::from_vmvalue(&hob.func);
3436 let entries: Vec<_> = attrs.iter().map(|(k, v)| (*k, v.clone())).collect();
3437 let chunk = deferred_apply_chunk();
3438 let mut result = BTreeMap::new();
3439 for (sym, val) in entries {
3440 let key_str = self.interner.resolve(sym).to_string();
3441 let partial = self.call_callable(&func_nb, NanBox::string(key_str))?;
3444 let thunk = VMThunk::new(
3451 chunk.clone(),
3452 vec![partial, NanBox::from_vmvalue(&val)],
3453 );
3454 result.insert(sym, NanBox::thunk(thunk));
3455 }
3456 Ok(NanBox::attrs(result))
3457 }
3458 FilterAttrs => {
3459 let attrs_val = arg.to_vmvalue();
3460 let attrs = match &attrs_val {
3461 VMValue::Attrs(a) => a,
3462 other => return Err(VMError::TypeError {
3463 expected: "set", got: other.type_name(),
3464 context: "builtins.filterAttrs".to_string(),
3465 }),
3466 };
3467 let func_nb = NanBox::from_vmvalue(&hob.func);
3468 let entries: Vec<_> = attrs.iter().map(|(k, v)| (*k, v.clone())).collect();
3469 let mut result = BTreeMap::new();
3470 for (sym, val) in entries {
3471 let key_str = self.interner.resolve(sym).to_string();
3472 let partial = self.call_callable(&func_nb, NanBox::string(key_str))?;
3473 if self.call_callable(&partial, NanBox::from_vmvalue(&val))?.is_truthy()? {
3474 result.insert(sym, NanBox::from_vmvalue(&val));
3475 }
3476 }
3477 Ok(NanBox::attrs(result))
3478 }
3479 Elem => {
3480 let needle = NanBox::from_vmvalue(&hob.func);
3485 let forced_needle = self.force_value(needle)?;
3486 let list = if let Some(items) = arg.as_list() {
3487 items.to_vec()
3488 } else {
3489 let forced = self.force_value(arg)?;
3490 if let Some(items) = forced.as_list() {
3491 items.to_vec()
3492 } else {
3493 return Err(VMError::TypeError {
3494 expected: "list",
3495 got: forced.type_name(),
3496 context: "builtins.elem".to_string(),
3497 });
3498 }
3499 };
3500 for item in &list {
3501 let forced_item = self.force_value(item.clone())?;
3502 if self.deep_eq(&forced_needle, &forced_item)? {
3503 return Ok(NanBox::bool(true));
3504 }
3505 }
3506 Ok(NanBox::bool(false))
3507 }
3508 }
3509 }
3510 fn import_file(&mut self, path: &str) -> Result<NanBox, VMError> {
3516 let read_path = crate::bridge::materialize(path);
3523 let resolved = std::fs::canonicalize(&read_path)
3524 .map_err(|e| VMError::ImportError(format!("{path}: {e}")))?;
3525 let resolved = if resolved.is_dir() {
3527 resolved.join("default.nix")
3528 } else {
3529 resolved
3530 };
3531 let canonical = resolved.to_string_lossy().to_string();
3532 if let Some(cached) = self.import_cache.borrow().get(&canonical) {
3534 return Ok(NanBox::from_vmvalue(cached));
3535 }
3536 let chunk = self.try_compile_import(&resolved, &canonical)?;
3538 let chunk = match chunk {
3539 Some(c) => c,
3540 None => {
3541 return self.import_via_bridge(&canonical);
3543 }
3544 };
3545 if self.frames.len() >= MAX_CALL_DEPTH {
3546 return Err(VMError::StackOverflow);
3547 }
3548 let return_depth = self.frames.len();
3549 let stack_base = self.stack.len();
3550 self.frames.push(CallFrame {
3551 chunk,
3552 ip: 0,
3553 stack_base,
3554 upvalues: Vec::new(),
3555 });
3556 let result = match self.run_until(return_depth) {
3557 Ok(r) => r,
3558 Err(e @ VMError::Throw(_)) => {
3559 self.stack.truncate(stack_base);
3561 if self.frames.len() > return_depth {
3562 self.frames.truncate(return_depth);
3563 }
3564 return Err(e);
3565 }
3566 Err(e) => {
3567 eprintln!("[sui-vm] runtime fallback for {canonical}: {e}");
3570 use std::sync::atomic::Ordering;
3571 crate::vm::VM_FALLBACK_COUNT.fetch_add(1, Ordering::Relaxed);
3572 self.stack.truncate(stack_base);
3573 if self.frames.len() > return_depth {
3574 self.frames.truncate(return_depth);
3575 }
3576 return self.import_via_bridge(&canonical);
3577 }
3578 };
3579 self.stack.truncate(stack_base);
3582 let result_vmval = result.to_vmvalue();
3584 self.import_cache
3585 .borrow_mut()
3586 .insert(canonical, result_vmval);
3587 Ok(result)
3588 }
3589 fn try_compile_import(
3593 &mut self,
3594 resolved: &std::path::Path,
3595 canonical: &str,
3596 ) -> Result<Option<Rc<Chunk>>, VMError> {
3597 if let Some(cached_chunk) = self.compile_cache.get(resolved) {
3599 return Ok(Some(cached_chunk.clone()));
3600 }
3601 let source = std::fs::read_to_string(canonical)
3603 .map_err(|e| VMError::ImportError(format!("{canonical}: {e}")))?;
3604 let file_dir = resolved
3605 .parent()
3606 .map(|p| p.to_path_buf())
3607 .unwrap_or_default();
3608 let shared_interner = Rc::new(RefCell::new(std::mem::take(self.interner)));
3611 let compile_result =
3612 Compiler::compile_with_shared_interner(&source, file_dir, shared_interner.clone());
3613 *self.interner = match Rc::try_unwrap(shared_interner) {
3614 Ok(cell) => cell.into_inner(),
3615 Err(rc) => rc.borrow().clone(),
3616 };
3617 match compile_result {
3618 Ok(mut compiled) => {
3619 Self::set_source_file_recursive(&mut compiled, canonical);
3620 let chunk = Rc::new(compiled);
3621 self.compile_cache
3622 .insert(resolved.to_path_buf(), chunk.clone());
3623 Ok(Some(chunk))
3624 }
3625 Err(compile_error) => {
3626 VM_FALLBACK_COUNT.fetch_add(1, Ordering::Relaxed);
3629 eprintln!("[sui-vm] fallback to tree-walker for {canonical}: {compile_error}");
3630 Ok(None)
3631 }
3632 }
3633 }
3634 fn import_via_bridge(&mut self, canonical: &str) -> Result<NanBox, VMError> {
3638 match crate::bridge::call_builtin_bridge(
3639 "__import",
3640 vec![crate::value::StringKeyedValue::Path(canonical.to_string())],
3641 ) {
3642 Ok(Some(result)) => {
3643 let nanbox = self.string_keyed_to_nanbox(&result);
3644 let nanbox = if nanbox.is_thunk() {
3648 self.force_value(nanbox)?
3649 } else {
3650 nanbox
3651 };
3652 let result_vmval = nanbox.to_vmvalue();
3654 self.import_cache
3655 .borrow_mut()
3656 .insert(canonical.to_string(), result_vmval);
3657 Ok(nanbox)
3658 }
3659 Ok(None) => Err(VMError::ImportError(format!(
3660 "compilation failed and no bridge installed for '{canonical}'"
3661 ))),
3662 Err(e) => Err(VMError::ImportError(format!(
3663 "bridge fallback error for '{canonical}': {e}"
3664 ))),
3665 }
3666 }
3667 fn set_source_file_recursive(chunk: &mut Chunk, file: &str) {
3669 chunk.source_file = Some(file.to_string());
3670 for constant in &mut chunk.constants {
3671 if let VMValue::Closure(closure) = constant {
3672 if let Some(inner_chunk) = Rc::get_mut(&mut closure.chunk) {
3673 Self::set_source_file_recursive(inner_chunk, file);
3674 }
3675 }
3676 }
3677 }
3678 fn disassemble_around(chunk: &Chunk, center_ip: usize, window: usize) -> String {
3682 let code = &chunk.code;
3683 let mut lines: Vec<String> = Vec::new();
3684 let mut boundaries: Vec<usize> = Vec::new();
3686 let mut pos = 0;
3687 while pos < code.len() {
3688 boundaries.push(pos);
3689 pos += Self::instruction_width(code, pos);
3690 }
3691 let center_idx = boundaries.iter().position(|&b| b >= center_ip).unwrap_or(0);
3693 let start_idx = center_idx.saturating_sub(window);
3694 let end_idx = (center_idx + window + 1).min(boundaries.len());
3695 for idx in start_idx..end_idx {
3696 let ip = boundaries[idx];
3697 let marker = if ip == center_ip { ">>>" } else { " " };
3698 let line = chunk.lines.get(ip).copied().unwrap_or(0);
3699 if let Some(op) = OpCode::from_byte(code[ip]) {
3700 let operands = Self::format_operands(code, ip, op);
3701 lines.push(format!(" {marker} {ip:4}: {op:?}{operands} (line {line})"));
3702 } else {
3703 lines.push(format!(" {marker} {ip:4}: <unknown {}> (line {line})", code[ip]));
3704 }
3705 }
3706 lines.join("\n")
3707 }
3708 fn instruction_width(code: &[u8], pos: usize) -> usize {
3710 let byte = code[pos];
3711 match OpCode::from_byte(byte) {
3712 Some(op) => match op {
3713 OpCode::Null | OpCode::True | OpCode::False
3715 | OpCode::Add | OpCode::Sub | OpCode::Mul | OpCode::Div | OpCode::Negate
3716 | OpCode::Not | OpCode::And | OpCode::Or | OpCode::Implication
3717 | OpCode::Equal | OpCode::NotEqual | OpCode::Less | OpCode::Greater
3718 | OpCode::LessEqual | OpCode::GreaterEqual
3719 | OpCode::UpdateAttrs | OpCode::Concat
3720 | OpCode::Call | OpCode::TailCall | OpCode::Return
3721 | OpCode::Assert | OpCode::Throw | OpCode::Pop | OpCode::Dup | OpCode::PushWith | OpCode::PopWith
3722 | OpCode::PushBuiltins | OpCode::Force | OpCode::Import
3723 | OpCode::DynGetAttr | OpCode::DynHasAttr
3724 | OpCode::DynSelectOrDefault | OpCode::Dup => 1,
3725 OpCode::Constant | OpCode::GetLocal | OpCode::SetLocal
3727 | OpCode::GetUpvalue | OpCode::SetUpvalue | OpCode::LookupWith
3728 | OpCode::GetAttr | OpCode::HasAttr | OpCode::MakeAttrs
3729 | OpCode::SelectOrDefault | OpCode::MakeList
3730 | OpCode::Jump | OpCode::JumpIfFalse | OpCode::JumpIfTrue
3731 | OpCode::Interpolate => 3,
3732 OpCode::GetLocalAttr | OpCode::GetLocalCall | OpCode::CallBuiltin => 5,
3734 OpCode::MakeClosure => {
3736 if pos + 5 <= code.len() {
3737 let uv_count = u16::from_le_bytes([code[pos + 3], code[pos + 4]]) as usize;
3738 5 + uv_count * 3
3739 } else {
3740 3 }
3742 }
3743 OpCode::MakeThunk => {
3745 if pos + 5 <= code.len() {
3746 let uv_count = u16::from_le_bytes([code[pos + 3], code[pos + 4]]) as usize;
3747 5 + uv_count * 3
3748 } else {
3749 3
3750 }
3751 }
3752 OpCode::PatchThunkUpvalues => {
3754 if pos + 5 <= code.len() {
3755 let uv_count = u16::from_le_bytes([code[pos + 3], code[pos + 4]]) as usize;
3756 5 + uv_count * 3
3757 } else {
3758 3
3759 }
3760 }
3761 OpCode::MakeLazyThunk => {
3763 if pos + 15 <= code.len() {
3764 let uv_count = u16::from_le_bytes([code[pos + 13], code[pos + 14]]) as usize;
3765 15 + uv_count * 3
3766 } else {
3767 3
3768 }
3769 }
3770 },
3771 None => 1, }
3773 }
3774 fn format_operands(code: &[u8], pos: usize, op: OpCode) -> String {
3776 let read_u16_at = |p: usize| -> Option<u16> {
3777 if p + 2 <= code.len() {
3778 Some(u16::from_le_bytes([code[p], code[p + 1]]))
3779 } else {
3780 None
3781 }
3782 };
3783 match op {
3784 OpCode::Constant | OpCode::GetLocal | OpCode::SetLocal
3785 | OpCode::GetUpvalue | OpCode::SetUpvalue | OpCode::LookupWith
3786 | OpCode::GetAttr | OpCode::HasAttr | OpCode::MakeAttrs
3787 | OpCode::SelectOrDefault | OpCode::MakeList
3788 | OpCode::Jump | OpCode::JumpIfFalse | OpCode::JumpIfTrue
3789 | OpCode::Interpolate => {
3790 read_u16_at(pos + 1).map_or(String::new(), |v| format!(" {v}"))
3791 }
3792 OpCode::GetLocalAttr => {
3793 let s = read_u16_at(pos + 1).unwrap_or(0);
3794 let k = read_u16_at(pos + 3).unwrap_or(0);
3795 format!(" slot={s} key={k}")
3796 }
3797 OpCode::GetLocalCall => {
3798 read_u16_at(pos + 1).map_or(String::new(), |v| format!(" slot={v}"))
3799 }
3800 OpCode::CallBuiltin => {
3801 let idx = read_u16_at(pos + 1).unwrap_or(0);
3802 let argc = read_u16_at(pos + 3).unwrap_or(0);
3803 format!(" idx={idx} argc={argc}")
3804 }
3805 OpCode::MakeThunk | OpCode::MakeClosure => {
3806 let ci = read_u16_at(pos + 1).unwrap_or(0);
3807 let uv = read_u16_at(pos + 3).unwrap_or(0);
3808 format!(" const={ci} upvals={uv}")
3809 }
3810 OpCode::PatchThunkUpvalues => {
3811 let s = read_u16_at(pos + 1).unwrap_or(0);
3812 let uv = read_u16_at(pos + 3).unwrap_or(0);
3813 format!(" slot={s} upvals={uv}")
3814 }
3815 _ => String::new(),
3816 }
3817 }
3818}
3819#[cfg(test)]
3820mod tests {
3821 use super::*;
3822 use crate::compiler::Compiler;
3823 use crate::value::StringKeyedValue;
3824 fn eval(input: &str) -> VMValue {
3825 let (chunk, mut interner) =
3826 Compiler::compile(input).unwrap_or_else(|e| panic!("compile '{input}': {e}"));
3827 VM::execute(chunk, &mut interner).unwrap_or_else(|e| panic!("execute '{input}': {e}"))
3828 }
3829 fn eval_full_helper(input: &str) -> crate::StringKeyedValue {
3830 let result =
3831 crate::eval_full(input).unwrap_or_else(|e| panic!("eval_full '{input}': {e}"));
3832 result.to_string_keyed()
3833 }
3834 fn eval_err(input: &str) -> VMError {
3835 let (chunk, mut interner) =
3836 Compiler::compile(input).unwrap_or_else(|e| panic!("compile '{input}': {e}"));
3837 VM::execute(chunk, &mut interner).unwrap_err()
3838 }
3839 #[test]
3841 fn eval_integer() {
3842 assert_eq!(eval("42"), VMValue::Int(42));
3843 }
3844 #[test]
3845 fn eval_negative_integer() {
3846 assert_eq!(eval("-7"), VMValue::Int(-7));
3847 }
3848 #[test]
3849 fn eval_float() {
3850 assert_eq!(eval("3.14"), VMValue::Float(3.14));
3851 }
3852 #[test]
3853 fn eval_bool_true() {
3854 assert_eq!(eval("true"), VMValue::Bool(true));
3855 }
3856 #[test]
3857 fn eval_bool_false() {
3858 assert_eq!(eval("false"), VMValue::Bool(false));
3859 }
3860 #[test]
3861 fn eval_null() {
3862 assert_eq!(eval("null"), VMValue::Null);
3863 }
3864 #[test]
3865 fn eval_string() {
3866 assert_eq!(eval(r#""hello""#), VMValue::String("hello".to_string()));
3867 }
3868 #[test]
3870 fn eval_add_int() {
3871 assert_eq!(eval("1 + 2"), VMValue::Int(3));
3872 }
3873 #[test]
3874 fn eval_sub_int() {
3875 assert_eq!(eval("10 - 3"), VMValue::Int(7));
3876 }
3877 #[test]
3878 fn eval_mul_int() {
3879 assert_eq!(eval("3 * 4"), VMValue::Int(12));
3880 }
3881 #[test]
3882 fn eval_div_int() {
3883 assert_eq!(eval("10 / 3"), VMValue::Int(3));
3884 }
3885 #[test]
3886 fn eval_div_zero() {
3887 assert!(matches!(eval_err("1 / 0"), VMError::DivisionByZero));
3888 }
3889 #[test]
3890 fn eval_float_arithmetic() {
3891 assert_eq!(eval("1.5 + 2.5"), VMValue::Float(4.0));
3892 }
3893 #[test]
3894 fn eval_mixed_arithmetic() {
3895 assert_eq!(eval("1 + 2.0"), VMValue::Float(3.0));
3896 }
3897 #[test]
3898 fn eval_compound_arithmetic() {
3899 assert_eq!(eval("2 * 3 + 1"), VMValue::Int(7));
3900 }
3901 #[test]
3902 fn eval_negate_float() {
3903 assert_eq!(eval("-3.14"), VMValue::Float(-3.14));
3904 }
3905 #[test]
3906 fn eval_string_concat() {
3907 assert_eq!(
3908 eval(r#""hello" + " " + "world""#),
3909 VMValue::String("hello world".to_string())
3910 );
3911 }
3912 #[test]
3914 fn eval_equal() {
3915 assert_eq!(eval("1 == 1"), VMValue::Bool(true));
3916 assert_eq!(eval("1 == 2"), VMValue::Bool(false));
3917 }
3918 #[test]
3919 fn eval_not_equal() {
3920 assert_eq!(eval("1 != 2"), VMValue::Bool(true));
3921 assert_eq!(eval("1 != 1"), VMValue::Bool(false));
3922 }
3923 #[test]
3924 fn eval_less() {
3925 assert_eq!(eval("1 < 2"), VMValue::Bool(true));
3926 assert_eq!(eval("2 < 1"), VMValue::Bool(false));
3927 }
3928 #[test]
3929 fn eval_greater() {
3930 assert_eq!(eval("2 > 1"), VMValue::Bool(true));
3931 assert_eq!(eval("1 > 2"), VMValue::Bool(false));
3932 }
3933 #[test]
3934 fn eval_less_equal() {
3935 assert_eq!(eval("1 <= 1"), VMValue::Bool(true));
3936 assert_eq!(eval("1 <= 2"), VMValue::Bool(true));
3937 assert_eq!(eval("2 <= 1"), VMValue::Bool(false));
3938 }
3939 #[test]
3940 fn eval_greater_equal() {
3941 assert_eq!(eval("1 >= 1"), VMValue::Bool(true));
3942 assert_eq!(eval("2 >= 1"), VMValue::Bool(true));
3943 assert_eq!(eval("1 >= 2"), VMValue::Bool(false));
3944 }
3945 #[test]
3947 fn eval_not() {
3948 assert_eq!(eval("!true"), VMValue::Bool(false));
3949 assert_eq!(eval("!false"), VMValue::Bool(true));
3950 }
3951 #[test]
3952 fn eval_and_short_circuit() {
3953 assert_eq!(eval("true && true"), VMValue::Bool(true));
3954 assert_eq!(eval("true && false"), VMValue::Bool(false));
3955 assert_eq!(eval("false && true"), VMValue::Bool(false));
3956 }
3957 #[test]
3958 fn eval_or_short_circuit() {
3959 assert_eq!(eval("false || true"), VMValue::Bool(true));
3960 assert_eq!(eval("false || false"), VMValue::Bool(false));
3961 assert_eq!(eval("true || false"), VMValue::Bool(true));
3962 }
3963 #[test]
3964 fn eval_implication() {
3965 assert_eq!(eval("true -> true"), VMValue::Bool(true));
3966 assert_eq!(eval("true -> false"), VMValue::Bool(false));
3967 assert_eq!(eval("false -> true"), VMValue::Bool(true));
3968 assert_eq!(eval("false -> false"), VMValue::Bool(true));
3969 }
3970 #[test]
3972 fn eval_if_true() {
3973 assert_eq!(eval("if true then 1 else 2"), VMValue::Int(1));
3974 }
3975 #[test]
3976 fn eval_if_false() {
3977 assert_eq!(eval("if false then 1 else 2"), VMValue::Int(2));
3978 }
3979 #[test]
3980 fn eval_if_expression() {
3981 assert_eq!(
3982 eval("if 1 > 2 then \"yes\" else \"no\""),
3983 VMValue::String("no".to_string())
3984 );
3985 }
3986 #[test]
3987 fn eval_nested_if() {
3988 assert_eq!(
3989 eval("if true then (if false then 1 else 2) else 3"),
3990 VMValue::Int(2)
3991 );
3992 }
3993 #[test]
3995 fn eval_let_simple() {
3996 assert_eq!(eval("let x = 1; y = 2; in x + y"), VMValue::Int(3));
3997 }
3998 #[test]
3999 fn eval_let_nested() {
4000 assert_eq!(
4001 eval("let a = 10; in let b = 20; in a + b"),
4002 VMValue::Int(30)
4003 );
4004 }
4005 #[test]
4006 fn eval_let_shadow() {
4007 assert_eq!(eval("let x = 1; in let x = 2; in x"), VMValue::Int(2));
4008 }
4009 #[test]
4010 fn eval_let_with_expression() {
4011 assert_eq!(eval("let x = 2 * 3; in x + 1"), VMValue::Int(7));
4012 }
4013 #[test]
4015 fn eval_empty_list() {
4016 assert_eq!(eval("[]"), VMValue::List(vec![]));
4017 }
4018 #[test]
4019 fn eval_list() {
4020 assert_eq!(
4021 eval("[1 2 3]"),
4022 VMValue::List(vec![VMValue::Int(1), VMValue::Int(2), VMValue::Int(3)])
4023 );
4024 }
4025 #[test]
4026 fn eval_list_concat() {
4027 assert_eq!(
4028 eval("[1 2] ++ [3 4]"),
4029 VMValue::List(vec![
4030 VMValue::Int(1),
4031 VMValue::Int(2),
4032 VMValue::Int(3),
4033 VMValue::Int(4),
4034 ])
4035 );
4036 }
4037 #[test]
4038 fn eval_list_concat_with_inline_map() {
4039 assert_eq!(
4043 eval("[1] ++ builtins.map (a: a) [2 3]"),
4044 VMValue::List(vec![VMValue::Int(1), VMValue::Int(2), VMValue::Int(3)])
4045 );
4046 }
4047 #[test]
4048 fn eval_list_concat_with_inline_map_attrsets() {
4049 let result = eval(r#"[{ x = 1; }] ++ builtins.map (a: { v = a; }) ["a" "b"]"#);
4051 match result {
4052 VMValue::List(items) => assert_eq!(items.len(), 3),
4053 other => panic!("expected list, got {:?}", other.type_name()),
4054 }
4055 }
4056 #[test]
4057 fn eval_list_concat_with_inline_filter() {
4058 assert_eq!(
4060 eval("[0] ++ builtins.filter (x: x > 1) [1 2 3]"),
4061 VMValue::List(vec![VMValue::Int(0), VMValue::Int(2), VMValue::Int(3)])
4062 );
4063 }
4064 #[test]
4065 fn eval_list_mixed() {
4066 assert_eq!(
4067 eval(r#"[1 "hello" true]"#),
4068 VMValue::List(vec![
4069 VMValue::Int(1),
4070 VMValue::String("hello".to_string()),
4071 VMValue::Bool(true),
4072 ])
4073 );
4074 }
4075 #[test]
4077 fn eval_empty_attrset() {
4078 assert_eq!(eval("{ }"), VMValue::Attrs(BTreeMap::new()));
4079 }
4080 #[test]
4081 fn eval_attrset() {
4082 let result = eval_full_helper("{ a = 1; b = 2; }");
4083 let mut expected = BTreeMap::new();
4084 expected.insert("a".to_string(), crate::StringKeyedValue::Int(1));
4085 expected.insert("b".to_string(), crate::StringKeyedValue::Int(2));
4086 assert_eq!(result, crate::StringKeyedValue::Attrs(expected));
4087 }
4088 #[test]
4089 fn eval_attrset_select() {
4090 assert_eq!(eval("{ a = 1; b = 2; }.a"), VMValue::Int(1));
4091 }
4092 #[test]
4093 fn eval_attrset_update() {
4094 let result = eval_full_helper("{ a = 1; } // { b = 2; }");
4095 let mut expected = BTreeMap::new();
4096 expected.insert("a".to_string(), crate::StringKeyedValue::Int(1));
4097 expected.insert("b".to_string(), crate::StringKeyedValue::Int(2));
4098 assert_eq!(result, crate::StringKeyedValue::Attrs(expected));
4099 }
4100 #[test]
4101 fn eval_attrset_update_override() {
4102 assert_eq!(eval("({ a = 1; } // { a = 2; }).a"), VMValue::Int(2));
4103 }
4104 #[test]
4105 fn eval_has_attr_true() {
4106 assert_eq!(eval("{ a = 1; } ? a"), VMValue::Bool(true));
4107 }
4108 #[test]
4109 fn eval_has_attr_false() {
4110 assert_eq!(eval("{ a = 1; } ? b"), VMValue::Bool(false));
4111 }
4112 #[test]
4113 fn eval_select_or_default() {
4114 assert_eq!(eval("{ a = 1; }.b or 0"), VMValue::Int(0));
4115 assert_eq!(eval("{ a = 1; }.a or 0"), VMValue::Int(1));
4116 }
4117 #[test]
4118 fn eval_dyn_select_or_default_missing() {
4119 assert_eq!(
4121 eval(r#"let x = "missing"; in { a = 1; }.${ x } or 99"#),
4122 VMValue::Int(99),
4123 );
4124 }
4125 #[test]
4126 fn eval_dyn_select_or_default_found() {
4127 assert_eq!(
4129 eval(r#"let x = "a"; in { a = 42; }.${ x } or 99"#),
4130 VMValue::Int(42),
4131 );
4132 }
4133 #[test]
4134 fn eval_dyn_select_or_default_dotted_key() {
4135 assert_eq!(
4137 eval(r#"let x = "a.b"; in { "a.b" = 7; }.${ x } or 0"#),
4138 VMValue::Int(7),
4139 );
4140 }
4141 #[test]
4142 fn eval_dyn_select_or_default_special_chars() {
4143 assert_eq!(
4145 eval(r#"let x = "armv8.3-a+crypto+sha2"; in { "armv8-a" = 1; }.${ x } or 0"#),
4146 VMValue::Int(0),
4147 );
4148 }
4149 #[test]
4150 fn eval_dyn_select_or_default_non_attrset() {
4151 assert_eq!(
4153 eval(r#"let x = "a"; base = 42; in base.${ x } or 99"#),
4154 VMValue::Int(99),
4155 );
4156 }
4157 #[test]
4159 fn eval_identity_lambda() {
4160 assert_eq!(eval("(x: x) 42"), VMValue::Int(42));
4161 }
4162 #[test]
4163 fn eval_lambda_arithmetic() {
4164 assert_eq!(eval("(x: x + 1) 5"), VMValue::Int(6));
4165 }
4166 #[test]
4167 #[ignore = "requires upvalue capture (Phase 2)"]
4168 fn eval_curried_lambda() {
4169 assert_eq!(eval("(x: y: x + y) 3 4"), VMValue::Int(7));
4170 }
4171 #[test]
4172 fn eval_let_lambda() {
4173 assert_eq!(
4174 eval("let f = x: x * 2; in f 5"),
4175 VMValue::Int(10)
4176 );
4177 }
4178 #[test]
4179 fn eval_pattern_lambda() {
4180 assert_eq!(eval("({ a, b }: a + b) { a = 3; b = 4; }"), VMValue::Int(7));
4181 }
4182 #[test]
4183 fn eval_pattern_lambda_default() {
4184 assert_eq!(
4185 eval("({ a, b ? 10 }: a + b) { a = 5; }"),
4186 VMValue::Int(15)
4187 );
4188 }
4189 #[test]
4190 fn eval_lambda_with_let() {
4191 assert_eq!(
4192 eval("let inc = x: x + 1; double = x: x * 2; in double (inc 3)"),
4193 VMValue::Int(8)
4194 );
4195 }
4196 #[test]
4198 fn eval_assert_pass() {
4199 assert_eq!(eval("assert true; 42"), VMValue::Int(42));
4200 }
4201 #[test]
4202 fn eval_assert_fail() {
4203 assert!(matches!(eval_err("assert false; 42"), VMError::AssertionFailed));
4204 }
4205 #[test]
4207 fn deep_eq_attrs_with_thunked_values() {
4208 assert_eq!(
4211 eval("let a = { x = 1; }; b = { x = 1; }; in a == b"),
4212 VMValue::Bool(true)
4213 );
4214 }
4215 #[test]
4216 fn deep_eq_attrs_different_values() {
4217 assert_eq!(
4218 eval("let a = { x = 1; }; b = { x = 2; }; in a == b"),
4219 VMValue::Bool(false)
4220 );
4221 }
4222 #[test]
4223 fn deep_eq_nested_attrs() {
4224 assert_eq!(
4225 eval("let a = { x = { y = 1; }; }; b = { x = { y = 1; }; }; in a == b"),
4226 VMValue::Bool(true)
4227 );
4228 }
4229 #[test]
4230 fn deep_eq_list_with_thunked_elements() {
4231 assert_eq!(
4232 eval("let a = [ 1 2 ]; b = [ 1 2 ]; in a == b"),
4233 VMValue::Bool(true)
4234 );
4235 }
4236 #[test]
4238 fn eval_elem_thunked_attrsets() {
4239 assert_eq!(
4241 eval("let a = { x = 1; }; b = { x = 1; }; in builtins.elem a [ b ]"),
4242 VMValue::Bool(true)
4243 );
4244 }
4245 #[test]
4246 fn eval_elem_basic_int() {
4247 assert_eq!(
4248 eval("builtins.elem 2 [ 1 2 3 ]"),
4249 VMValue::Bool(true)
4250 );
4251 }
4252 #[test]
4253 fn eval_elem_missing() {
4254 assert_eq!(
4255 eval("builtins.elem 4 [ 1 2 3 ]"),
4256 VMValue::Bool(false)
4257 );
4258 }
4259 #[test]
4260 fn eval_elem_string() {
4261 assert_eq!(
4262 eval(r#"builtins.elem "b" [ "a" "b" "c" ]"#),
4263 VMValue::Bool(true)
4264 );
4265 }
4266 #[test]
4267 fn eval_elem_thunked_list_elements() {
4268 assert_eq!(
4269 eval("let x = 1; in builtins.elem 1 [ x ]"),
4270 VMValue::Bool(true)
4271 );
4272 }
4273 #[test]
4275 fn eval_string_interpolation() {
4276 assert_eq!(
4277 eval(r#"let x = "world"; in "hello ${x}""#),
4278 VMValue::String("hello world".to_string()),
4279 );
4280 }
4281 #[test]
4282 #[ignore = "requires builtins.toString (Phase 2)"]
4283 fn eval_string_interpolation_int() {
4284 assert_eq!(
4285 eval(r#"let n = 42; in "value: ${toString n}""#),
4286 VMValue::String("value: 42".to_string()),
4287 );
4288 }
4289 #[test]
4291 fn eval_absolute_path() {
4292 assert_eq!(eval("/tmp/x"), VMValue::Path("/tmp/x".to_string()));
4293 }
4294 #[test]
4296 fn eval_fibonacci_like() {
4297 assert_eq!(
4298 eval("let a = 1; b = 1; c = a + b; d = b + c; e = c + d; in e"),
4299 VMValue::Int(5)
4300 );
4301 }
4302 #[test]
4303 fn eval_nested_attrset_select() {
4304 assert_eq!(
4305 eval("{ a = { b = 42; }; }.a.b"),
4306 VMValue::Int(42)
4307 );
4308 }
4309 #[test]
4310 fn eval_let_with_attrset() {
4311 assert_eq!(
4312 eval("let set = { x = 10; y = 20; }; in set.x + set.y"),
4313 VMValue::Int(30)
4314 );
4315 }
4316 #[test]
4317 fn eval_conditional_attrset() {
4318 assert_eq!(
4319 eval("(if true then { a = 1; } else { a = 2; }).a"),
4320 VMValue::Int(1)
4321 );
4322 }
4323 #[test]
4325 fn builtin_length() {
4326 assert_eq!(eval("builtins.length [1 2 3]"), VMValue::Int(3));
4327 }
4328 #[test]
4329 fn builtin_length_empty() {
4330 assert_eq!(eval("builtins.length []"), VMValue::Int(0));
4331 }
4332 #[test]
4333 fn builtin_head() {
4334 assert_eq!(eval("builtins.head [10 20 30]"), VMValue::Int(10));
4335 }
4336 #[test]
4337 fn builtin_tail() {
4338 let result = eval_full_helper("builtins.tail [1 2 3]");
4339 assert_eq!(
4340 result,
4341 StringKeyedValue::List(vec![StringKeyedValue::Int(2), StringKeyedValue::Int(3)])
4342 );
4343 }
4344 #[test]
4345 fn builtin_type_of_int() {
4346 assert_eq!(
4347 eval("builtins.typeOf 42"),
4348 VMValue::String("int".to_string())
4349 );
4350 }
4351 #[test]
4352 fn builtin_type_of_string() {
4353 assert_eq!(
4354 eval("builtins.typeOf \"hello\""),
4355 VMValue::String("string".to_string())
4356 );
4357 }
4358 #[test]
4359 fn builtin_type_of_bool() {
4360 assert_eq!(
4361 eval("builtins.typeOf true"),
4362 VMValue::String("bool".to_string())
4363 );
4364 }
4365 #[test]
4366 fn builtin_type_of_null() {
4367 assert_eq!(
4368 eval("builtins.typeOf null"),
4369 VMValue::String("null".to_string())
4370 );
4371 }
4372 #[test]
4373 fn builtin_type_of_list() {
4374 assert_eq!(
4375 eval("builtins.typeOf [1 2]"),
4376 VMValue::String("list".to_string())
4377 );
4378 }
4379 #[test]
4380 fn builtin_type_of_set() {
4381 assert_eq!(
4382 eval("builtins.typeOf { a = 1; }"),
4383 VMValue::String("set".to_string())
4384 );
4385 }
4386 #[test]
4387 fn builtin_type_of_lambda() {
4388 assert_eq!(
4389 eval("builtins.typeOf (x: x)"),
4390 VMValue::String("lambda".to_string())
4391 );
4392 }
4393 #[test]
4394 fn builtin_is_int() {
4395 assert_eq!(eval("builtins.isInt 42"), VMValue::Bool(true));
4396 assert_eq!(
4397 eval("builtins.isInt \"hello\""),
4398 VMValue::Bool(false)
4399 );
4400 }
4401 #[test]
4402 fn builtin_is_string() {
4403 assert_eq!(eval("builtins.isString \"hi\""), VMValue::Bool(true));
4404 assert_eq!(eval("builtins.isString 42"), VMValue::Bool(false));
4405 }
4406 #[test]
4407 fn builtin_is_list() {
4408 assert_eq!(eval("builtins.isList [1]"), VMValue::Bool(true));
4409 assert_eq!(eval("builtins.isList 42"), VMValue::Bool(false));
4410 }
4411 #[test]
4412 fn builtin_is_attrs() {
4413 assert_eq!(
4414 eval("builtins.isAttrs { a = 1; }"),
4415 VMValue::Bool(true)
4416 );
4417 assert_eq!(eval("builtins.isAttrs 42"), VMValue::Bool(false));
4418 }
4419 #[test]
4420 fn builtin_is_function() {
4421 assert_eq!(
4422 eval("builtins.isFunction (x: x)"),
4423 VMValue::Bool(true)
4424 );
4425 assert_eq!(eval("builtins.isFunction 42"), VMValue::Bool(false));
4426 }
4427 #[test]
4428 fn builtin_is_bool() {
4429 assert_eq!(eval("builtins.isBool true"), VMValue::Bool(true));
4430 assert_eq!(eval("builtins.isBool 42"), VMValue::Bool(false));
4431 }
4432 #[test]
4433 fn builtin_is_null() {
4434 assert_eq!(eval("builtins.isNull null"), VMValue::Bool(true));
4435 assert_eq!(eval("builtins.isNull 42"), VMValue::Bool(false));
4436 }
4437 #[test]
4438 fn builtin_string_length() {
4439 assert_eq!(
4440 eval("builtins.stringLength \"hello\""),
4441 VMValue::Int(5)
4442 );
4443 }
4444 #[test]
4445 fn builtin_to_string_int() {
4446 assert_eq!(
4447 eval("builtins.toString 42"),
4448 VMValue::String("42".to_string())
4449 );
4450 }
4451 #[test]
4452 fn builtin_to_string_bool() {
4453 assert_eq!(
4454 eval("builtins.toString true"),
4455 VMValue::String("1".to_string())
4456 );
4457 }
4458 #[test]
4459 fn builtin_throw() {
4460 let result = eval_err("builtins.throw \"test error\"");
4461 assert!(matches!(result, VMError::Throw(_)));
4462 }
4463 #[test]
4464 fn builtin_abort() {
4465 let result = eval_err("builtins.abort \"fatal\"");
4466 assert!(matches!(result, VMError::Throw(_)));
4467 }
4468 #[test]
4469 fn builtin_add_curried() {
4470 assert_eq!(eval("builtins.add 3 4"), VMValue::Int(7));
4471 }
4472 #[test]
4473 fn builtin_sub_curried() {
4474 assert_eq!(eval("builtins.sub 10 3"), VMValue::Int(7));
4475 }
4476 #[test]
4477 fn builtin_mul_curried() {
4478 assert_eq!(eval("builtins.mul 6 7"), VMValue::Int(42));
4479 }
4480 #[test]
4481 fn builtin_div_curried() {
4482 assert_eq!(eval("builtins.div 42 6"), VMValue::Int(7));
4483 }
4484 #[test]
4485 fn builtin_elem_at() {
4486 assert_eq!(eval("builtins.elemAt [10 20 30] 1"), VMValue::Int(20));
4487 }
4488 #[test]
4489 fn builtin_elem() {
4490 assert_eq!(eval("builtins.elem 2 [1 2 3]"), VMValue::Bool(true));
4491 assert_eq!(eval("builtins.elem 5 [1 2 3]"), VMValue::Bool(false));
4492 }
4493 #[test]
4494 fn builtin_concat_lists() {
4495 let result = eval_full_helper("builtins.concatLists [[1 2] [3 4]]");
4496 assert_eq!(
4497 result,
4498 StringKeyedValue::List(vec![
4499 StringKeyedValue::Int(1),
4500 StringKeyedValue::Int(2),
4501 StringKeyedValue::Int(3),
4502 StringKeyedValue::Int(4),
4503 ])
4504 );
4505 }
4506 #[test]
4507 fn builtin_has_prefix() {
4508 assert_eq!(
4509 eval("builtins.hasPrefix \"he\" \"hello\""),
4510 VMValue::Bool(true)
4511 );
4512 assert_eq!(
4513 eval("builtins.hasPrefix \"wo\" \"hello\""),
4514 VMValue::Bool(false)
4515 );
4516 }
4517 #[test]
4518 fn builtin_has_suffix() {
4519 assert_eq!(
4520 eval("builtins.hasSuffix \"lo\" \"hello\""),
4521 VMValue::Bool(true)
4522 );
4523 }
4524 #[test]
4525 fn builtin_concat_strings_sep() {
4526 assert_eq!(
4527 eval("builtins.concatStringsSep \", \" [\"a\" \"b\" \"c\"]"),
4528 VMValue::String("a, b, c".to_string())
4529 );
4530 }
4531 #[test]
4532 fn builtin_to_lower() {
4533 assert_eq!(
4534 eval("builtins.toLower \"Hello World\""),
4535 VMValue::String("hello world".to_string())
4536 );
4537 }
4538 #[test]
4539 fn builtin_to_upper() {
4540 assert_eq!(
4541 eval("builtins.toUpper \"hello\""),
4542 VMValue::String("HELLO".to_string())
4543 );
4544 }
4545 #[test]
4546 fn builtin_from_json() {
4547 assert_eq!(
4548 eval("builtins.fromJSON \"42\""),
4549 VMValue::Int(42)
4550 );
4551 assert_eq!(
4552 eval("builtins.fromJSON \"true\""),
4553 VMValue::Bool(true)
4554 );
4555 }
4556 #[test]
4557 fn builtin_seq() {
4558 assert_eq!(eval("builtins.seq 1 42"), VMValue::Int(42));
4559 }
4560 #[test]
4561 fn builtin_deep_seq() {
4562 assert_eq!(eval("builtins.deepSeq [1 2] 42"), VMValue::Int(42));
4563 }
4564 #[test]
4565 fn builtin_trace() {
4566 assert_eq!(
4567 eval("builtins.trace \"debug\" 42"),
4568 VMValue::Int(42)
4569 );
4570 }
4571 #[test]
4572 fn builtin_ceil_floor() {
4573 assert_eq!(eval("builtins.ceil 3.2"), VMValue::Int(4));
4574 assert_eq!(eval("builtins.floor 3.8"), VMValue::Int(3));
4575 }
4576 #[test]
4577 fn builtin_bit_ops() {
4578 assert_eq!(eval("builtins.bitAnd 12 10"), VMValue::Int(8));
4579 assert_eq!(eval("builtins.bitOr 12 10"), VMValue::Int(14));
4580 assert_eq!(eval("builtins.bitXor 12 10"), VMValue::Int(6));
4581 }
4582 #[test]
4583 fn builtin_intersect_attrs() {
4584 let result =
4585 eval_full_helper("builtins.intersectAttrs { a = 1; b = 2; } { a = 10; c = 30; }");
4586 match result {
4587 StringKeyedValue::Attrs(map) => {
4588 assert_eq!(map.get("a"), Some(&StringKeyedValue::Int(10)));
4589 assert!(!map.contains_key("b"));
4590 assert!(!map.contains_key("c"));
4591 }
4592 _ => panic!("expected Attrs, got {result:?}"),
4593 }
4594 }
4595 #[test]
4596 fn builtin_attr_values() {
4597 let result = eval_full_helper("builtins.attrValues { a = 1; b = 2; }");
4598 match result {
4599 StringKeyedValue::List(items) => {
4600 assert_eq!(items.len(), 2);
4601 assert!(items.contains(&StringKeyedValue::Int(1)));
4602 assert!(items.contains(&StringKeyedValue::Int(2)));
4603 }
4604 _ => panic!("expected List, got {result:?}"),
4605 }
4606 }
4607 #[test]
4608 fn builtin_to_int() {
4609 assert_eq!(eval("builtins.toInt \"42\""), VMValue::Int(42));
4610 }
4611 #[test]
4612 fn builtin_replace_strings() {
4613 assert_eq!(
4614 eval("builtins.replaceStrings [\"o\"] [\"0\"] \"foo\""),
4615 VMValue::String("f00".to_string())
4616 );
4617 }
4618 #[test]
4619 fn builtin_substring() {
4620 assert_eq!(
4621 eval("builtins.substring 1 3 \"hello\""),
4622 VMValue::String("ell".to_string())
4623 );
4624 }
4625 #[test]
4627 fn import_basic() {
4628 let dir = tempfile::tempdir().unwrap();
4629 let file_path = dir.path().join("test.nix");
4630 std::fs::write(&file_path, "42").unwrap();
4631 let nix_expr = format!("import {}", file_path.display());
4632 assert_eq!(eval(&nix_expr), VMValue::Int(42));
4633 }
4634 #[test]
4635 fn import_cached() {
4636 let dir = tempfile::tempdir().unwrap();
4637 let file_path = dir.path().join("cached.nix");
4638 std::fs::write(&file_path, "{ x = 1; }").unwrap();
4639 let nix_expr = format!(
4640 "let a = import {}; b = import {}; in a == b",
4641 file_path.display(),
4642 file_path.display()
4643 );
4644 assert_eq!(eval(&nix_expr), VMValue::Bool(true));
4645 }
4646 #[test]
4647 fn import_attrset() {
4648 let dir = tempfile::tempdir().unwrap();
4649 let file_path = dir.path().join("attrs.nix");
4650 std::fs::write(&file_path, "{ greeting = \"hello\"; }").unwrap();
4651 let nix_expr = format!("(import {}).greeting", file_path.display());
4652 assert_eq!(eval(&nix_expr), VMValue::String("hello".to_string()));
4653 }
4654 #[test]
4655 fn import_directory_default_nix() {
4656 let dir = tempfile::tempdir().unwrap();
4658 let sub = dir.path().join("mylib");
4659 std::fs::create_dir(&sub).unwrap();
4660 std::fs::write(sub.join("default.nix"), "{ x = 42; }").unwrap();
4661 let nix_expr = format!("(import {}).x", sub.display());
4662 assert_eq!(eval(&nix_expr), VMValue::Int(42));
4663 }
4664 #[test]
4665 fn import_directory_cached() {
4666 let dir = tempfile::tempdir().unwrap();
4668 let sub = dir.path().join("lib");
4669 std::fs::create_dir(&sub).unwrap();
4670 std::fs::write(sub.join("default.nix"), "{ v = 99; }").unwrap();
4671 let nix_expr = format!(
4672 "let a = import {}; b = import {}; in a == b",
4673 sub.display(),
4674 sub.display()
4675 );
4676 assert_eq!(eval(&nix_expr), VMValue::Bool(true));
4677 }
4678 #[test]
4679 fn import_directory_nested() {
4680 let dir = tempfile::tempdir().unwrap();
4682 let lib = dir.path().join("lib");
4683 let sub = lib.join("sub");
4684 std::fs::create_dir_all(&sub).unwrap();
4685 std::fs::write(sub.join("default.nix"), "{ val = 7; }").unwrap();
4686 std::fs::write(
4687 lib.join("default.nix"),
4688 &format!("(import {}).val + 3", sub.display()),
4689 )
4690 .unwrap();
4691 let nix_expr = format!("import {}", lib.display());
4692 assert_eq!(eval(&nix_expr), VMValue::Int(10));
4693 }
4694 #[test]
4696 fn lazy_unused_throw_in_attrset() {
4697 assert_eq!(
4698 eval("let s = { a = 1; }; in s.a"),
4699 VMValue::Int(1)
4700 );
4701 }
4702 #[test]
4703 fn lazy_unused_let_binding() {
4704 assert_eq!(eval("let x = 1; y = 2; in x"), VMValue::Int(1));
4705 }
4706 #[test]
4708 fn import_forces_thunk_before_type_check() {
4709 let dir = tempfile::tempdir().unwrap();
4712 let file_path = dir.path().join("forced.nix");
4713 std::fs::write(&file_path, "99").unwrap();
4714 let nix_expr = format!(
4715 "let p = {}; in import p",
4716 file_path.display()
4717 );
4718 assert_eq!(eval(&nix_expr), VMValue::Int(99));
4719 }
4720 #[test]
4721 fn import_with_path_value_succeeds() {
4722 let dir = tempfile::tempdir().unwrap();
4723 let file_path = dir.path().join("pathval.nix");
4724 std::fs::write(&file_path, "\"from-path\"").unwrap();
4725 let nix_expr = format!("import {}", file_path.display());
4726 assert_eq!(
4727 eval(&nix_expr),
4728 VMValue::String("from-path".to_string())
4729 );
4730 }
4731 #[test]
4732 fn import_with_string_value_succeeds() {
4733 let dir = tempfile::tempdir().unwrap();
4734 let file_path = dir.path().join("strval.nix");
4735 std::fs::write(&file_path, "\"from-string\"").unwrap();
4736 let nix_expr = format!(
4737 "let s = \"{}\"; in import s",
4738 file_path.display()
4739 );
4740 assert_eq!(
4741 eval(&nix_expr),
4742 VMValue::String("from-string".to_string())
4743 );
4744 }
4745 #[test]
4747 fn tail_call_deep_recursion_via_import() {
4748 let dir = tempfile::tempdir().unwrap();
4752 let file_path = dir.path().join("countdown.nix");
4753 std::fs::write(
4754 &file_path,
4755 "{ f, n }: if n == 0 then 0 else f { inherit f; n = n - 1; }",
4756 )
4757 .unwrap();
4758 let nix_expr = format!(
4761 "let g = import {}; in g {{ f = g; n = 2000; }}",
4762 file_path.display()
4763 );
4764 assert_eq!(eval(&nix_expr), VMValue::Int(0));
4765 }
4766 #[test]
4767 fn tail_call_simple_lambda_chain() {
4768 assert_eq!(
4772 eval("let g = x: x + 1; f = x: g x; in f 41"),
4773 VMValue::Int(42)
4774 );
4775 }
4776 #[test]
4777 fn tail_call_if_branches() {
4778 assert_eq!(
4781 eval("let f = x: if x > 0 then x else x + 1; in f 10"),
4782 VMValue::Int(10)
4783 );
4784 assert_eq!(
4785 eval("let f = x: if x > 0 then x else x + 1; in f 0"),
4786 VMValue::Int(1)
4787 );
4788 }
4789 #[test]
4791 fn builtin_get_env_returns_value() {
4792 unsafe { std::env::set_var("SUI_TEST_VAR", "hello_sui") };
4795 assert_eq!(
4796 eval("builtins.getEnv \"SUI_TEST_VAR\""),
4797 VMValue::String("hello_sui".to_string())
4798 );
4799 unsafe { std::env::remove_var("SUI_TEST_VAR") };
4800 }
4801 #[test]
4802 fn builtin_get_env_missing_returns_empty() {
4803 unsafe { std::env::remove_var("SUI_NONEXISTENT_VAR_12345") };
4806 assert_eq!(
4807 eval("builtins.getEnv \"SUI_NONEXISTENT_VAR_12345\""),
4808 VMValue::String(String::new())
4809 );
4810 }
4811 #[test]
4812 fn builtin_try_eval_success() {
4813 let result = eval_full_helper("builtins.tryEval 42");
4815 match result {
4816 StringKeyedValue::Attrs(map) => {
4817 assert_eq!(
4818 map.get("success"),
4819 Some(&StringKeyedValue::Bool(true))
4820 );
4821 assert_eq!(
4822 map.get("value"),
4823 Some(&StringKeyedValue::Int(42))
4824 );
4825 }
4826 _ => panic!("expected Attrs, got {result:?}"),
4827 }
4828 }
4829 #[test]
4830 fn builtin_try_eval_with_non_throwing_expr() {
4831 let result = eval_full_helper(
4834 "builtins.tryEval (1 + 2)"
4835 );
4836 match result {
4837 StringKeyedValue::Attrs(map) => {
4838 assert_eq!(
4839 map.get("success"),
4840 Some(&StringKeyedValue::Bool(true))
4841 );
4842 assert_eq!(
4843 map.get("value"),
4844 Some(&StringKeyedValue::Int(3))
4845 );
4846 }
4847 _ => panic!("expected Attrs, got {result:?}"),
4848 }
4849 }
4850 #[test]
4851 fn builtin_try_eval_with_throw_catches() {
4852 let result = eval_full_helper(
4858 "let bad = builtins.throw \"oops\"; in builtins.tryEval bad"
4859 );
4860 match result {
4861 StringKeyedValue::Attrs(map) => {
4862 assert_eq!(
4863 map.get("success"),
4864 Some(&StringKeyedValue::Bool(false))
4865 );
4866 assert_eq!(
4867 map.get("value"),
4868 Some(&StringKeyedValue::Bool(false))
4869 );
4870 }
4871 _ => panic!("expected Attrs, got {result:?}"),
4872 }
4873 }
4874 #[test]
4876 fn if_else_in_let_body_stack_depth() {
4877 assert_eq!(
4880 eval("let a = 1; in if a == 1 then 10 else 20"),
4881 VMValue::Int(10),
4882 );
4883 }
4884 #[test]
4885 fn nested_let_with_if_else() {
4886 assert_eq!(
4888 eval(r#"
4889 let
4890 a = 1;
4891 b = if a == 1 then 2 else 3;
4892 in
4893 let c = b + 10; in c
4894 "#),
4895 VMValue::Int(12),
4896 );
4897 }
4898 #[test]
4899 fn short_circuit_and_in_let_body() {
4900 assert_eq!(
4902 eval("let x = true; in x && false"),
4903 VMValue::Bool(false),
4904 );
4905 }
4906 #[test]
4907 fn short_circuit_or_in_let_body() {
4908 assert_eq!(
4909 eval("let x = false; in x || true"),
4910 VMValue::Bool(true),
4911 );
4912 }
4913 #[test]
4914 fn short_circuit_implication_in_let_body() {
4915 assert_eq!(
4917 eval("let x = false; in x -> 42"),
4918 VMValue::Bool(true),
4919 );
4920 }
4921 #[test]
4922 fn inherit_from_in_attrset_stack_depth() {
4923 assert_eq!(
4926 eval(r#"
4927 let
4928 src = { a = 1; b = 2; };
4929 result = { inherit (src) a b; c = 3; };
4930 in result.a + result.b + result.c
4931 "#),
4932 VMValue::Int(6),
4933 );
4934 }
4935 #[test]
4936 fn inherit_from_many_fields_stack_depth() {
4937 assert_eq!(
4940 eval(r#"
4941 let
4942 s = { w = 1; x = 2; y = 3; z = 4; };
4943 r = { inherit (s) w x y z; extra = 10; };
4944 in r.w + r.x + r.y + r.z + r.extra
4945 "#),
4946 VMValue::Int(20),
4947 );
4948 }
4949 #[test]
4950 fn if_else_followed_by_let_binding() {
4951 assert_eq!(
4955 eval(r#"
4956 let
4957 a = 1;
4958 b = 2;
4959 c = 3;
4960 in
4961 let
4962 x = if a == 1 then b else c;
4963 y = x + 100;
4964 in y
4965 "#),
4966 VMValue::Int(102),
4967 );
4968 }
4969 #[test]
4970 fn multi_segment_hasattr_stack_depth() {
4971 assert_eq!(
4974 eval(r#"
4975 let
4976 s = { a = { b = 1; }; };
4977 has = s ? a.b;
4978 val = if has then 42 else 0;
4979 in val
4980 "#),
4981 VMValue::Int(42),
4982 );
4983 }
4984 #[test]
4985 fn many_let_bindings_with_if_else() {
4986 assert_eq!(
4990 eval(r#"
4991 let
4992 a = 1;
4993 b = 2;
4994 c = 3;
4995 d = 4;
4996 e = 5;
4997 f = 6;
4998 g = 7;
4999 h = 8;
5000 i = 9;
5001 j = 10;
5002 in
5003 let
5004 x = if a == 1 then b else c;
5005 y = if d == 4 then e else f;
5006 z = if g == 7 then h else i;
5007 w = j;
5008 in x + y + z + w
5009 "#),
5010 VMValue::Int(25),
5011 );
5012 }
5013 #[test]
5014 fn import_in_pattern_default_stack_depth() {
5015 assert_eq!(
5021 eval(r#"
5022 let
5023 f = { a ? 1, b ? 2, c ? 3 }:
5024 a + b + c;
5025 in f {}
5026 "#),
5027 VMValue::Int(6),
5028 );
5029 }
5030 #[test]
5031 fn pattern_lambda_many_defaults_then_let() {
5032 assert_eq!(
5038 eval(r#"
5039 let
5040 mk = { a, b ? 10, c ? 20, d ? 30, e ? 40 }:
5041 let
5042 sum = a + b + c + d + e;
5043 doubled = sum + sum;
5044 in doubled;
5045 in mk { a = 1; }
5046 "#),
5047 VMValue::Int(202),
5048 );
5049 }
5050 #[test]
5052 fn rec_dotted_lambda_captures_sibling() {
5053 let result = eval_full_helper(
5058 r#"rec { types.a = 1; types.b = 2; f = _: types; }.f 0"#,
5059 );
5060 match result {
5061 StringKeyedValue::Attrs(ref m) => {
5062 assert_eq!(m.get("a"), Some(&StringKeyedValue::Int(1)));
5063 assert_eq!(m.get("b"), Some(&StringKeyedValue::Int(2)));
5064 }
5065 other => panic!("expected attrset, got {other:?}"),
5066 }
5067 }
5068 #[test]
5069 fn rec_dotted_lambda_attr_select() {
5070 assert_eq!(
5072 eval(r#"rec { types.a = 1; types.b = 2; f = x: types.b; result = f 0; }.result"#),
5073 VMValue::Int(2),
5074 );
5075 }
5076 #[test]
5077 fn rec_dotted_lambda_assert_check() {
5078 assert_eq!(
5082 eval(r#"
5083 rec {
5084 types.parsedPlatform = { check = _: true; };
5085 mkSystem = components:
5086 assert types.parsedPlatform.check components;
5087 components;
5088 result = mkSystem 42;
5089 }.result
5090 "#),
5091 VMValue::Int(42),
5092 );
5093 }
5094 #[test]
5095 fn let_lambda_captures_rec_sibling() {
5096 assert_eq!(
5099 eval(r#"let a = 1 + 1; f = _: a; in f 0"#),
5100 VMValue::Int(2),
5101 );
5102 }
5103 #[test]
5104 fn rec_dotted_multiple_lambdas() {
5105 assert_eq!(
5107 eval(r#"
5108 rec {
5109 a.x = 10;
5110 b.y = 20;
5111 f = _: a.x + b.y;
5112 result = f 0;
5113 }.result
5114 "#),
5115 VMValue::Int(30),
5116 );
5117 }
5118}