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 resolved = if std::path::Path::new(path).is_dir() {
3118 format!("{path}/default.nix")
3119 } else {
3120 path.to_string()
3121 };
3122 let source = std::fs::read_to_string(&resolved)
3123 .map_err(|e| VMError::ImportError(format!("{path}: {e}")))?;
3124 let wrapped = format!("with {scope_nix}; {source}");
3126 let file_dir = std::path::Path::new(&resolved)
3127 .parent()
3128 .map(|p| p.to_path_buf())
3129 .unwrap_or_default();
3130 let shared_interner = Rc::new(RefCell::new(std::mem::take(self.interner)));
3132 let chunk = Compiler::compile_with_shared_interner(&wrapped, file_dir, shared_interner.clone())
3133 .map_err(|e| VMError::ImportError(format!("{path}: {e}")))?;
3134 *self.interner = match Rc::try_unwrap(shared_interner) {
3135 Ok(cell) => cell.into_inner(),
3136 Err(rc) => rc.borrow().clone(),
3137 };
3138 if self.frames.len() >= MAX_CALL_DEPTH {
3139 return Err(VMError::StackOverflow);
3140 }
3141 let return_depth = self.frames.len();
3142 let stack_base = self.stack.len();
3143 self.frames.push(CallFrame {
3144 chunk: Rc::new(chunk),
3145 ip: 0,
3146 stack_base,
3147 upvalues: Vec::new(),
3148 });
3149 self.run_until(return_depth)
3150 }
3151 fn call_callable(&mut self, func: &NanBox, arg: NanBox) -> Result<NanBox, VMError> {
3153 if let Some(closure) = func.as_closure() {
3154 if self.frames.len() >= MAX_CALL_DEPTH {
3155 return Err(VMError::StackOverflow);
3156 }
3157 let upvalues = closure.upvalues.clone();
3158 let chunk = closure.chunk.clone();
3159 let return_depth = self.frames.len();
3160 let stack_base = self.stack.len();
3161 self.push(arg);
3162 self.frames.push(CallFrame {
3163 chunk,
3164 ip: 0,
3165 stack_base,
3166 upvalues,
3167 });
3168 let result = self.run_until(return_depth)?;
3169 self.stack.truncate(stack_base);
3170 self.force_value(result)
3173 } else if func.is_higher_order_builtin() {
3174 let hob = func.as_higher_order_builtin().unwrap().clone();
3175 self.call_higher_order_builtin(&hob, arg)
3176 } else if let Some(builtin) = func.as_builtin() {
3177 let arg = self.force_value(arg)?;
3179 if let Some(result) = self.try_vm_builtin(builtin.name, &arg)? {
3180 Ok(result)
3181 } else {
3182 let deep = self.deep_force(arg)?;
3184 let arg_vmval = deep.to_vmvalue();
3185 let builtin_func = builtin.func.clone();
3186 let result = self.call_builtin_with_scoped_import_dispatch(
3187 builtin_func, arg_vmval,
3188 )?;
3189 Ok(result)
3190 }
3191 } else {
3192 Err(VMError::NotCallable(func.type_name().to_string()))
3193 }
3194 }
3195 #[allow(clippy::too_many_lines)]
3196 fn call_higher_order_builtin(
3197 &mut self,
3198 hob: &HigherOrderBuiltin,
3199 arg: NanBox,
3200 ) -> Result<NanBox, VMError> {
3201 use HigherOrderOp::*;
3202 let arg = self.force_value(arg)?;
3205 match hob.op {
3206 Map => {
3207 let list_val = arg.to_vmvalue();
3208 let list = match &list_val {
3209 VMValue::List(l) => l,
3210 other => return Err(VMError::TypeError {
3211 expected: "list", got: other.type_name(),
3212 context: "builtins.map".to_string(),
3213 }),
3214 };
3215 let func_nb = NanBox::from_vmvalue(&hob.func);
3216 let mut results = Vec::with_capacity(list.len());
3217 for item in list {
3218 let r = self.call_callable(&func_nb, NanBox::from_vmvalue(item))?;
3219 results.push(r);
3220 }
3221 Ok(NanBox::list(results))
3222 }
3223 Filter => {
3224 let list_val = arg.to_vmvalue();
3225 let list = match &list_val {
3226 VMValue::List(l) => l,
3227 other => return Err(VMError::TypeError {
3228 expected: "list", got: other.type_name(),
3229 context: "builtins.filter".to_string(),
3230 }),
3231 };
3232 let func_nb = NanBox::from_vmvalue(&hob.func);
3233 let mut results = Vec::new();
3234 for item in list {
3235 let item_nb = NanBox::from_vmvalue(item);
3236 let r = self.call_callable(&func_nb, item_nb.clone())?;
3237
3238 if r.is_truthy()? { results.push(item_nb); }
3239 }
3240 Ok(NanBox::list(results))
3241 }
3242 FoldlP1 => {
3243 let init_vmval = arg.to_vmvalue();
3244 Ok(NanBox::from_vmvalue(&VMValue::HigherOrderBuiltin(
3245 HigherOrderBuiltin {
3246 op: FoldlP2,
3247 func: hob.func.clone(),
3248 extra_args: vec![init_vmval],
3249 },
3250 )))
3251 }
3252 FoldlP2 => {
3253 let list_val = arg.to_vmvalue();
3254 let list = match &list_val {
3255 VMValue::List(l) => l,
3256 other => return Err(VMError::TypeError {
3257 expected: "list", got: other.type_name(),
3258 context: "builtins.foldl'".to_string(),
3259 }),
3260 };
3261 let func_nb = NanBox::from_vmvalue(&hob.func);
3262 let mut acc = NanBox::from_vmvalue(&hob.extra_args[0]);
3263 for item in list {
3264 let partial = self.call_callable(&func_nb, acc)?;
3265 acc = self.call_callable(&partial, NanBox::from_vmvalue(item))?;
3266 }
3267 Ok(acc)
3268 }
3269 Sort => {
3270 let list_val = arg.to_vmvalue();
3271 let list = match &list_val {
3272 VMValue::List(l) => l.clone(),
3273 other => return Err(VMError::TypeError {
3274 expected: "list", got: other.type_name(),
3275 context: "builtins.sort".to_string(),
3276 }),
3277 };
3278 if list.len() <= 1 {
3279 return Ok(NanBox::from_vmvalue(&VMValue::List(list)));
3280 }
3281 let func_nb = NanBox::from_vmvalue(&hob.func);
3282 let mut sorted: Vec<VMValue> = Vec::with_capacity(list.len());
3283 for item in &list {
3284 let item_nb = NanBox::from_vmvalue(item);
3285 let mut pos = sorted.len();
3286 for (i, existing) in sorted.iter().enumerate() {
3287 let existing_nb = NanBox::from_vmvalue(existing);
3288 let partial = self.call_callable(&func_nb, item_nb.clone())?;
3289 let cmp_result = self.call_callable(&partial, existing_nb)?;
3290 if cmp_result.is_truthy()? { pos = i; break; }
3291 }
3292 sorted.insert(pos, item.clone());
3293 }
3294 Ok(NanBox::from_vmvalue(&VMValue::List(sorted)))
3295 }
3296 GenList => {
3297 let n = match arg.to_vmvalue() {
3298 VMValue::Int(n) => n,
3299 other => return Err(VMError::TypeError {
3300 expected: "int", got: other.type_name(),
3301 context: "builtins.genList".to_string(),
3302 }),
3303 };
3304 if n < 0 { return Err(VMError::Throw("genList: negative length".to_string())); }
3305 let func_nb = NanBox::from_vmvalue(&hob.func);
3306 let mut results = Vec::with_capacity(n as usize);
3307 for i in 0..n {
3308 results.push(self.call_callable(&func_nb, NanBox::int(i))?);
3309 }
3310 Ok(NanBox::list(results))
3311 }
3312 ConcatMap => {
3313 let list_val = arg.to_vmvalue();
3314 let list = match &list_val {
3315 VMValue::List(l) => l,
3316 other => return Err(VMError::TypeError {
3317 expected: "list", got: other.type_name(),
3318 context: "builtins.concatMap".to_string(),
3319 }),
3320 };
3321 let func_nb = NanBox::from_vmvalue(&hob.func);
3322 let mut results = Vec::new();
3323 for item in list {
3324 let mapped = self.call_callable(&func_nb, NanBox::from_vmvalue(item))?;
3325 match mapped.to_vmvalue() {
3326 VMValue::List(inner) => {
3327 for v in &inner { results.push(NanBox::from_vmvalue(v)); }
3328 }
3329 other => return Err(VMError::TypeError {
3330 expected: "list", got: other.type_name(),
3331 context: "builtins.concatMap result".to_string(),
3332 }),
3333 }
3334 }
3335 Ok(NanBox::list(results))
3336 }
3337 Any => {
3338 let list_val = arg.to_vmvalue();
3339 let list = match &list_val {
3340 VMValue::List(l) => l,
3341 other => return Err(VMError::TypeError {
3342 expected: "list", got: other.type_name(),
3343 context: "builtins.any".to_string(),
3344 }),
3345 };
3346 let func_nb = NanBox::from_vmvalue(&hob.func);
3347 for item in list {
3348 if self.call_callable(&func_nb, NanBox::from_vmvalue(item))?.is_truthy()? {
3349 return Ok(NanBox::bool(true));
3350 }
3351 }
3352 Ok(NanBox::bool(false))
3353 }
3354 All => {
3355 let list_val = arg.to_vmvalue();
3356 let list = match &list_val {
3357 VMValue::List(l) => l,
3358 other => return Err(VMError::TypeError {
3359 expected: "list", got: other.type_name(),
3360 context: "builtins.all".to_string(),
3361 }),
3362 };
3363 let func_nb = NanBox::from_vmvalue(&hob.func);
3364 for item in list {
3365 if !self.call_callable(&func_nb, NanBox::from_vmvalue(item))?.is_truthy()? {
3366 return Ok(NanBox::bool(false));
3367 }
3368 }
3369 Ok(NanBox::bool(true))
3370 }
3371 Partition => {
3372 let list_val = arg.to_vmvalue();
3373 let list = match &list_val {
3374 VMValue::List(l) => l,
3375 other => return Err(VMError::TypeError {
3376 expected: "list", got: other.type_name(),
3377 context: "builtins.partition".to_string(),
3378 }),
3379 };
3380 let func_nb = NanBox::from_vmvalue(&hob.func);
3381 let (mut right, mut wrong) = (Vec::new(), Vec::new());
3382 for item in list {
3383 let item_nb = NanBox::from_vmvalue(item);
3384 if self.call_callable(&func_nb, item_nb.clone())?.is_truthy()? {
3385 right.push(item_nb);
3386 } else {
3387 wrong.push(item_nb);
3388 }
3389 }
3390 let rs = self.interner.intern("right");
3391 let ws = self.interner.intern("wrong");
3392 let mut attrs = BTreeMap::new();
3393 attrs.insert(rs, NanBox::list(right));
3394 attrs.insert(ws, NanBox::list(wrong));
3395 Ok(NanBox::attrs(attrs))
3396 }
3397 GroupBy => {
3398 let list_val = arg.to_vmvalue();
3399 let list = match &list_val {
3400 VMValue::List(l) => l,
3401 other => return Err(VMError::TypeError {
3402 expected: "list", got: other.type_name(),
3403 context: "builtins.groupBy".to_string(),
3404 }),
3405 };
3406 let func_nb = NanBox::from_vmvalue(&hob.func);
3407 let mut groups: BTreeMap<String, Vec<NanBox>> = BTreeMap::new();
3408 for item in list {
3409 let item_nb = NanBox::from_vmvalue(item);
3410 let kr = self.call_callable(&func_nb, item_nb.clone())?;
3411 let ks = kr.as_string().ok_or_else(|| VMError::TypeError {
3412 expected: "string", got: kr.type_name(),
3413 context: "builtins.groupBy key".to_string(),
3414 })?.to_string();
3415 groups.entry(ks).or_default().push(item_nb);
3416 }
3417 let mut attrs = BTreeMap::new();
3418 for (k, vs) in groups {
3419 attrs.insert(self.interner.intern(&k), NanBox::list(vs));
3420 }
3421 Ok(NanBox::attrs(attrs))
3422 }
3423 MapAttrs => {
3424 let attrs_val = arg.to_vmvalue();
3425 let attrs = match &attrs_val {
3426 VMValue::Attrs(a) => a,
3427 other => return Err(VMError::TypeError {
3428 expected: "set", got: other.type_name(),
3429 context: "builtins.mapAttrs".to_string(),
3430 }),
3431 };
3432 let func_nb = NanBox::from_vmvalue(&hob.func);
3433 let entries: Vec<_> = attrs.iter().map(|(k, v)| (*k, v.clone())).collect();
3434 let chunk = deferred_apply_chunk();
3435 let mut result = BTreeMap::new();
3436 for (sym, val) in entries {
3437 let key_str = self.interner.resolve(sym).to_string();
3438 let partial = self.call_callable(&func_nb, NanBox::string(key_str))?;
3441 let thunk = VMThunk::new(
3448 chunk.clone(),
3449 vec![partial, NanBox::from_vmvalue(&val)],
3450 );
3451 result.insert(sym, NanBox::thunk(thunk));
3452 }
3453 Ok(NanBox::attrs(result))
3454 }
3455 FilterAttrs => {
3456 let attrs_val = arg.to_vmvalue();
3457 let attrs = match &attrs_val {
3458 VMValue::Attrs(a) => a,
3459 other => return Err(VMError::TypeError {
3460 expected: "set", got: other.type_name(),
3461 context: "builtins.filterAttrs".to_string(),
3462 }),
3463 };
3464 let func_nb = NanBox::from_vmvalue(&hob.func);
3465 let entries: Vec<_> = attrs.iter().map(|(k, v)| (*k, v.clone())).collect();
3466 let mut result = BTreeMap::new();
3467 for (sym, val) in entries {
3468 let key_str = self.interner.resolve(sym).to_string();
3469 let partial = self.call_callable(&func_nb, NanBox::string(key_str))?;
3470 if self.call_callable(&partial, NanBox::from_vmvalue(&val))?.is_truthy()? {
3471 result.insert(sym, NanBox::from_vmvalue(&val));
3472 }
3473 }
3474 Ok(NanBox::attrs(result))
3475 }
3476 Elem => {
3477 let needle = NanBox::from_vmvalue(&hob.func);
3482 let forced_needle = self.force_value(needle)?;
3483 let list = if let Some(items) = arg.as_list() {
3484 items.to_vec()
3485 } else {
3486 let forced = self.force_value(arg)?;
3487 if let Some(items) = forced.as_list() {
3488 items.to_vec()
3489 } else {
3490 return Err(VMError::TypeError {
3491 expected: "list",
3492 got: forced.type_name(),
3493 context: "builtins.elem".to_string(),
3494 });
3495 }
3496 };
3497 for item in &list {
3498 let forced_item = self.force_value(item.clone())?;
3499 if self.deep_eq(&forced_needle, &forced_item)? {
3500 return Ok(NanBox::bool(true));
3501 }
3502 }
3503 Ok(NanBox::bool(false))
3504 }
3505 }
3506 }
3507 fn import_file(&mut self, path: &str) -> Result<NanBox, VMError> {
3513 let resolved = std::fs::canonicalize(path)
3514 .map_err(|e| VMError::ImportError(format!("{path}: {e}")))?;
3515 let resolved = if resolved.is_dir() {
3517 resolved.join("default.nix")
3518 } else {
3519 resolved
3520 };
3521 let canonical = resolved.to_string_lossy().to_string();
3522 if let Some(cached) = self.import_cache.borrow().get(&canonical) {
3524 return Ok(NanBox::from_vmvalue(cached));
3525 }
3526 let chunk = self.try_compile_import(&resolved, &canonical)?;
3528 let chunk = match chunk {
3529 Some(c) => c,
3530 None => {
3531 return self.import_via_bridge(&canonical);
3533 }
3534 };
3535 if self.frames.len() >= MAX_CALL_DEPTH {
3536 return Err(VMError::StackOverflow);
3537 }
3538 let return_depth = self.frames.len();
3539 let stack_base = self.stack.len();
3540 self.frames.push(CallFrame {
3541 chunk,
3542 ip: 0,
3543 stack_base,
3544 upvalues: Vec::new(),
3545 });
3546 let result = match self.run_until(return_depth) {
3547 Ok(r) => r,
3548 Err(e @ VMError::Throw(_)) => {
3549 self.stack.truncate(stack_base);
3551 if self.frames.len() > return_depth {
3552 self.frames.truncate(return_depth);
3553 }
3554 return Err(e);
3555 }
3556 Err(e) => {
3557 eprintln!("[sui-vm] runtime fallback for {canonical}: {e}");
3560 use std::sync::atomic::Ordering;
3561 crate::vm::VM_FALLBACK_COUNT.fetch_add(1, Ordering::Relaxed);
3562 self.stack.truncate(stack_base);
3563 if self.frames.len() > return_depth {
3564 self.frames.truncate(return_depth);
3565 }
3566 return self.import_via_bridge(&canonical);
3567 }
3568 };
3569 self.stack.truncate(stack_base);
3572 let result_vmval = result.to_vmvalue();
3574 self.import_cache
3575 .borrow_mut()
3576 .insert(canonical, result_vmval);
3577 Ok(result)
3578 }
3579 fn try_compile_import(
3583 &mut self,
3584 resolved: &std::path::Path,
3585 canonical: &str,
3586 ) -> Result<Option<Rc<Chunk>>, VMError> {
3587 if let Some(cached_chunk) = self.compile_cache.get(resolved) {
3589 return Ok(Some(cached_chunk.clone()));
3590 }
3591 let source = std::fs::read_to_string(canonical)
3593 .map_err(|e| VMError::ImportError(format!("{canonical}: {e}")))?;
3594 let file_dir = resolved
3595 .parent()
3596 .map(|p| p.to_path_buf())
3597 .unwrap_or_default();
3598 let shared_interner = Rc::new(RefCell::new(std::mem::take(self.interner)));
3601 let compile_result =
3602 Compiler::compile_with_shared_interner(&source, file_dir, shared_interner.clone());
3603 *self.interner = match Rc::try_unwrap(shared_interner) {
3604 Ok(cell) => cell.into_inner(),
3605 Err(rc) => rc.borrow().clone(),
3606 };
3607 match compile_result {
3608 Ok(mut compiled) => {
3609 Self::set_source_file_recursive(&mut compiled, canonical);
3610 let chunk = Rc::new(compiled);
3611 self.compile_cache
3612 .insert(resolved.to_path_buf(), chunk.clone());
3613 Ok(Some(chunk))
3614 }
3615 Err(compile_error) => {
3616 VM_FALLBACK_COUNT.fetch_add(1, Ordering::Relaxed);
3619 eprintln!("[sui-vm] fallback to tree-walker for {canonical}: {compile_error}");
3620 Ok(None)
3621 }
3622 }
3623 }
3624 fn import_via_bridge(&mut self, canonical: &str) -> Result<NanBox, VMError> {
3628 match crate::bridge::call_builtin_bridge(
3629 "__import",
3630 vec![crate::value::StringKeyedValue::Path(canonical.to_string())],
3631 ) {
3632 Ok(Some(result)) => {
3633 let nanbox = self.string_keyed_to_nanbox(&result);
3634 let nanbox = if nanbox.is_thunk() {
3638 self.force_value(nanbox)?
3639 } else {
3640 nanbox
3641 };
3642 let result_vmval = nanbox.to_vmvalue();
3644 self.import_cache
3645 .borrow_mut()
3646 .insert(canonical.to_string(), result_vmval);
3647 Ok(nanbox)
3648 }
3649 Ok(None) => Err(VMError::ImportError(format!(
3650 "compilation failed and no bridge installed for '{canonical}'"
3651 ))),
3652 Err(e) => Err(VMError::ImportError(format!(
3653 "bridge fallback error for '{canonical}': {e}"
3654 ))),
3655 }
3656 }
3657 fn set_source_file_recursive(chunk: &mut Chunk, file: &str) {
3659 chunk.source_file = Some(file.to_string());
3660 for constant in &mut chunk.constants {
3661 if let VMValue::Closure(closure) = constant {
3662 if let Some(inner_chunk) = Rc::get_mut(&mut closure.chunk) {
3663 Self::set_source_file_recursive(inner_chunk, file);
3664 }
3665 }
3666 }
3667 }
3668 fn disassemble_around(chunk: &Chunk, center_ip: usize, window: usize) -> String {
3672 let code = &chunk.code;
3673 let mut lines: Vec<String> = Vec::new();
3674 let mut boundaries: Vec<usize> = Vec::new();
3676 let mut pos = 0;
3677 while pos < code.len() {
3678 boundaries.push(pos);
3679 pos += Self::instruction_width(code, pos);
3680 }
3681 let center_idx = boundaries.iter().position(|&b| b >= center_ip).unwrap_or(0);
3683 let start_idx = center_idx.saturating_sub(window);
3684 let end_idx = (center_idx + window + 1).min(boundaries.len());
3685 for idx in start_idx..end_idx {
3686 let ip = boundaries[idx];
3687 let marker = if ip == center_ip { ">>>" } else { " " };
3688 let line = chunk.lines.get(ip).copied().unwrap_or(0);
3689 if let Some(op) = OpCode::from_byte(code[ip]) {
3690 let operands = Self::format_operands(code, ip, op);
3691 lines.push(format!(" {marker} {ip:4}: {op:?}{operands} (line {line})"));
3692 } else {
3693 lines.push(format!(" {marker} {ip:4}: <unknown {}> (line {line})", code[ip]));
3694 }
3695 }
3696 lines.join("\n")
3697 }
3698 fn instruction_width(code: &[u8], pos: usize) -> usize {
3700 let byte = code[pos];
3701 match OpCode::from_byte(byte) {
3702 Some(op) => match op {
3703 OpCode::Null | OpCode::True | OpCode::False
3705 | OpCode::Add | OpCode::Sub | OpCode::Mul | OpCode::Div | OpCode::Negate
3706 | OpCode::Not | OpCode::And | OpCode::Or | OpCode::Implication
3707 | OpCode::Equal | OpCode::NotEqual | OpCode::Less | OpCode::Greater
3708 | OpCode::LessEqual | OpCode::GreaterEqual
3709 | OpCode::UpdateAttrs | OpCode::Concat
3710 | OpCode::Call | OpCode::TailCall | OpCode::Return
3711 | OpCode::Assert | OpCode::Throw | OpCode::Pop | OpCode::Dup | OpCode::PushWith | OpCode::PopWith
3712 | OpCode::PushBuiltins | OpCode::Force | OpCode::Import
3713 | OpCode::DynGetAttr | OpCode::DynHasAttr
3714 | OpCode::DynSelectOrDefault | OpCode::Dup => 1,
3715 OpCode::Constant | OpCode::GetLocal | OpCode::SetLocal
3717 | OpCode::GetUpvalue | OpCode::SetUpvalue | OpCode::LookupWith
3718 | OpCode::GetAttr | OpCode::HasAttr | OpCode::MakeAttrs
3719 | OpCode::SelectOrDefault | OpCode::MakeList
3720 | OpCode::Jump | OpCode::JumpIfFalse | OpCode::JumpIfTrue
3721 | OpCode::Interpolate => 3,
3722 OpCode::GetLocalAttr | OpCode::GetLocalCall | OpCode::CallBuiltin => 5,
3724 OpCode::MakeClosure => {
3726 if pos + 5 <= code.len() {
3727 let uv_count = u16::from_le_bytes([code[pos + 3], code[pos + 4]]) as usize;
3728 5 + uv_count * 3
3729 } else {
3730 3 }
3732 }
3733 OpCode::MakeThunk => {
3735 if pos + 5 <= code.len() {
3736 let uv_count = u16::from_le_bytes([code[pos + 3], code[pos + 4]]) as usize;
3737 5 + uv_count * 3
3738 } else {
3739 3
3740 }
3741 }
3742 OpCode::PatchThunkUpvalues => {
3744 if pos + 5 <= code.len() {
3745 let uv_count = u16::from_le_bytes([code[pos + 3], code[pos + 4]]) as usize;
3746 5 + uv_count * 3
3747 } else {
3748 3
3749 }
3750 }
3751 OpCode::MakeLazyThunk => {
3753 if pos + 15 <= code.len() {
3754 let uv_count = u16::from_le_bytes([code[pos + 13], code[pos + 14]]) as usize;
3755 15 + uv_count * 3
3756 } else {
3757 3
3758 }
3759 }
3760 },
3761 None => 1, }
3763 }
3764 fn format_operands(code: &[u8], pos: usize, op: OpCode) -> String {
3766 let read_u16_at = |p: usize| -> Option<u16> {
3767 if p + 2 <= code.len() {
3768 Some(u16::from_le_bytes([code[p], code[p + 1]]))
3769 } else {
3770 None
3771 }
3772 };
3773 match op {
3774 OpCode::Constant | OpCode::GetLocal | OpCode::SetLocal
3775 | OpCode::GetUpvalue | OpCode::SetUpvalue | OpCode::LookupWith
3776 | OpCode::GetAttr | OpCode::HasAttr | OpCode::MakeAttrs
3777 | OpCode::SelectOrDefault | OpCode::MakeList
3778 | OpCode::Jump | OpCode::JumpIfFalse | OpCode::JumpIfTrue
3779 | OpCode::Interpolate => {
3780 read_u16_at(pos + 1).map_or(String::new(), |v| format!(" {v}"))
3781 }
3782 OpCode::GetLocalAttr => {
3783 let s = read_u16_at(pos + 1).unwrap_or(0);
3784 let k = read_u16_at(pos + 3).unwrap_or(0);
3785 format!(" slot={s} key={k}")
3786 }
3787 OpCode::GetLocalCall => {
3788 read_u16_at(pos + 1).map_or(String::new(), |v| format!(" slot={v}"))
3789 }
3790 OpCode::CallBuiltin => {
3791 let idx = read_u16_at(pos + 1).unwrap_or(0);
3792 let argc = read_u16_at(pos + 3).unwrap_or(0);
3793 format!(" idx={idx} argc={argc}")
3794 }
3795 OpCode::MakeThunk | OpCode::MakeClosure => {
3796 let ci = read_u16_at(pos + 1).unwrap_or(0);
3797 let uv = read_u16_at(pos + 3).unwrap_or(0);
3798 format!(" const={ci} upvals={uv}")
3799 }
3800 OpCode::PatchThunkUpvalues => {
3801 let s = read_u16_at(pos + 1).unwrap_or(0);
3802 let uv = read_u16_at(pos + 3).unwrap_or(0);
3803 format!(" slot={s} upvals={uv}")
3804 }
3805 _ => String::new(),
3806 }
3807 }
3808}
3809#[cfg(test)]
3810mod tests {
3811 use super::*;
3812 use crate::compiler::Compiler;
3813 use crate::value::StringKeyedValue;
3814 fn eval(input: &str) -> VMValue {
3815 let (chunk, mut interner) =
3816 Compiler::compile(input).unwrap_or_else(|e| panic!("compile '{input}': {e}"));
3817 VM::execute(chunk, &mut interner).unwrap_or_else(|e| panic!("execute '{input}': {e}"))
3818 }
3819 fn eval_full_helper(input: &str) -> crate::StringKeyedValue {
3820 let result =
3821 crate::eval_full(input).unwrap_or_else(|e| panic!("eval_full '{input}': {e}"));
3822 result.to_string_keyed()
3823 }
3824 fn eval_err(input: &str) -> VMError {
3825 let (chunk, mut interner) =
3826 Compiler::compile(input).unwrap_or_else(|e| panic!("compile '{input}': {e}"));
3827 VM::execute(chunk, &mut interner).unwrap_err()
3828 }
3829 #[test]
3831 fn eval_integer() {
3832 assert_eq!(eval("42"), VMValue::Int(42));
3833 }
3834 #[test]
3835 fn eval_negative_integer() {
3836 assert_eq!(eval("-7"), VMValue::Int(-7));
3837 }
3838 #[test]
3839 fn eval_float() {
3840 assert_eq!(eval("3.14"), VMValue::Float(3.14));
3841 }
3842 #[test]
3843 fn eval_bool_true() {
3844 assert_eq!(eval("true"), VMValue::Bool(true));
3845 }
3846 #[test]
3847 fn eval_bool_false() {
3848 assert_eq!(eval("false"), VMValue::Bool(false));
3849 }
3850 #[test]
3851 fn eval_null() {
3852 assert_eq!(eval("null"), VMValue::Null);
3853 }
3854 #[test]
3855 fn eval_string() {
3856 assert_eq!(eval(r#""hello""#), VMValue::String("hello".to_string()));
3857 }
3858 #[test]
3860 fn eval_add_int() {
3861 assert_eq!(eval("1 + 2"), VMValue::Int(3));
3862 }
3863 #[test]
3864 fn eval_sub_int() {
3865 assert_eq!(eval("10 - 3"), VMValue::Int(7));
3866 }
3867 #[test]
3868 fn eval_mul_int() {
3869 assert_eq!(eval("3 * 4"), VMValue::Int(12));
3870 }
3871 #[test]
3872 fn eval_div_int() {
3873 assert_eq!(eval("10 / 3"), VMValue::Int(3));
3874 }
3875 #[test]
3876 fn eval_div_zero() {
3877 assert!(matches!(eval_err("1 / 0"), VMError::DivisionByZero));
3878 }
3879 #[test]
3880 fn eval_float_arithmetic() {
3881 assert_eq!(eval("1.5 + 2.5"), VMValue::Float(4.0));
3882 }
3883 #[test]
3884 fn eval_mixed_arithmetic() {
3885 assert_eq!(eval("1 + 2.0"), VMValue::Float(3.0));
3886 }
3887 #[test]
3888 fn eval_compound_arithmetic() {
3889 assert_eq!(eval("2 * 3 + 1"), VMValue::Int(7));
3890 }
3891 #[test]
3892 fn eval_negate_float() {
3893 assert_eq!(eval("-3.14"), VMValue::Float(-3.14));
3894 }
3895 #[test]
3896 fn eval_string_concat() {
3897 assert_eq!(
3898 eval(r#""hello" + " " + "world""#),
3899 VMValue::String("hello world".to_string())
3900 );
3901 }
3902 #[test]
3904 fn eval_equal() {
3905 assert_eq!(eval("1 == 1"), VMValue::Bool(true));
3906 assert_eq!(eval("1 == 2"), VMValue::Bool(false));
3907 }
3908 #[test]
3909 fn eval_not_equal() {
3910 assert_eq!(eval("1 != 2"), VMValue::Bool(true));
3911 assert_eq!(eval("1 != 1"), VMValue::Bool(false));
3912 }
3913 #[test]
3914 fn eval_less() {
3915 assert_eq!(eval("1 < 2"), VMValue::Bool(true));
3916 assert_eq!(eval("2 < 1"), VMValue::Bool(false));
3917 }
3918 #[test]
3919 fn eval_greater() {
3920 assert_eq!(eval("2 > 1"), VMValue::Bool(true));
3921 assert_eq!(eval("1 > 2"), VMValue::Bool(false));
3922 }
3923 #[test]
3924 fn eval_less_equal() {
3925 assert_eq!(eval("1 <= 1"), VMValue::Bool(true));
3926 assert_eq!(eval("1 <= 2"), VMValue::Bool(true));
3927 assert_eq!(eval("2 <= 1"), VMValue::Bool(false));
3928 }
3929 #[test]
3930 fn eval_greater_equal() {
3931 assert_eq!(eval("1 >= 1"), VMValue::Bool(true));
3932 assert_eq!(eval("2 >= 1"), VMValue::Bool(true));
3933 assert_eq!(eval("1 >= 2"), VMValue::Bool(false));
3934 }
3935 #[test]
3937 fn eval_not() {
3938 assert_eq!(eval("!true"), VMValue::Bool(false));
3939 assert_eq!(eval("!false"), VMValue::Bool(true));
3940 }
3941 #[test]
3942 fn eval_and_short_circuit() {
3943 assert_eq!(eval("true && true"), VMValue::Bool(true));
3944 assert_eq!(eval("true && false"), VMValue::Bool(false));
3945 assert_eq!(eval("false && true"), VMValue::Bool(false));
3946 }
3947 #[test]
3948 fn eval_or_short_circuit() {
3949 assert_eq!(eval("false || true"), VMValue::Bool(true));
3950 assert_eq!(eval("false || false"), VMValue::Bool(false));
3951 assert_eq!(eval("true || false"), VMValue::Bool(true));
3952 }
3953 #[test]
3954 fn eval_implication() {
3955 assert_eq!(eval("true -> true"), VMValue::Bool(true));
3956 assert_eq!(eval("true -> false"), VMValue::Bool(false));
3957 assert_eq!(eval("false -> true"), VMValue::Bool(true));
3958 assert_eq!(eval("false -> false"), VMValue::Bool(true));
3959 }
3960 #[test]
3962 fn eval_if_true() {
3963 assert_eq!(eval("if true then 1 else 2"), VMValue::Int(1));
3964 }
3965 #[test]
3966 fn eval_if_false() {
3967 assert_eq!(eval("if false then 1 else 2"), VMValue::Int(2));
3968 }
3969 #[test]
3970 fn eval_if_expression() {
3971 assert_eq!(
3972 eval("if 1 > 2 then \"yes\" else \"no\""),
3973 VMValue::String("no".to_string())
3974 );
3975 }
3976 #[test]
3977 fn eval_nested_if() {
3978 assert_eq!(
3979 eval("if true then (if false then 1 else 2) else 3"),
3980 VMValue::Int(2)
3981 );
3982 }
3983 #[test]
3985 fn eval_let_simple() {
3986 assert_eq!(eval("let x = 1; y = 2; in x + y"), VMValue::Int(3));
3987 }
3988 #[test]
3989 fn eval_let_nested() {
3990 assert_eq!(
3991 eval("let a = 10; in let b = 20; in a + b"),
3992 VMValue::Int(30)
3993 );
3994 }
3995 #[test]
3996 fn eval_let_shadow() {
3997 assert_eq!(eval("let x = 1; in let x = 2; in x"), VMValue::Int(2));
3998 }
3999 #[test]
4000 fn eval_let_with_expression() {
4001 assert_eq!(eval("let x = 2 * 3; in x + 1"), VMValue::Int(7));
4002 }
4003 #[test]
4005 fn eval_empty_list() {
4006 assert_eq!(eval("[]"), VMValue::List(vec![]));
4007 }
4008 #[test]
4009 fn eval_list() {
4010 assert_eq!(
4011 eval("[1 2 3]"),
4012 VMValue::List(vec![VMValue::Int(1), VMValue::Int(2), VMValue::Int(3)])
4013 );
4014 }
4015 #[test]
4016 fn eval_list_concat() {
4017 assert_eq!(
4018 eval("[1 2] ++ [3 4]"),
4019 VMValue::List(vec![
4020 VMValue::Int(1),
4021 VMValue::Int(2),
4022 VMValue::Int(3),
4023 VMValue::Int(4),
4024 ])
4025 );
4026 }
4027 #[test]
4028 fn eval_list_concat_with_inline_map() {
4029 assert_eq!(
4033 eval("[1] ++ builtins.map (a: a) [2 3]"),
4034 VMValue::List(vec![VMValue::Int(1), VMValue::Int(2), VMValue::Int(3)])
4035 );
4036 }
4037 #[test]
4038 fn eval_list_concat_with_inline_map_attrsets() {
4039 let result = eval(r#"[{ x = 1; }] ++ builtins.map (a: { v = a; }) ["a" "b"]"#);
4041 match result {
4042 VMValue::List(items) => assert_eq!(items.len(), 3),
4043 other => panic!("expected list, got {:?}", other.type_name()),
4044 }
4045 }
4046 #[test]
4047 fn eval_list_concat_with_inline_filter() {
4048 assert_eq!(
4050 eval("[0] ++ builtins.filter (x: x > 1) [1 2 3]"),
4051 VMValue::List(vec![VMValue::Int(0), VMValue::Int(2), VMValue::Int(3)])
4052 );
4053 }
4054 #[test]
4055 fn eval_list_mixed() {
4056 assert_eq!(
4057 eval(r#"[1 "hello" true]"#),
4058 VMValue::List(vec![
4059 VMValue::Int(1),
4060 VMValue::String("hello".to_string()),
4061 VMValue::Bool(true),
4062 ])
4063 );
4064 }
4065 #[test]
4067 fn eval_empty_attrset() {
4068 assert_eq!(eval("{ }"), VMValue::Attrs(BTreeMap::new()));
4069 }
4070 #[test]
4071 fn eval_attrset() {
4072 let result = eval_full_helper("{ a = 1; b = 2; }");
4073 let mut expected = BTreeMap::new();
4074 expected.insert("a".to_string(), crate::StringKeyedValue::Int(1));
4075 expected.insert("b".to_string(), crate::StringKeyedValue::Int(2));
4076 assert_eq!(result, crate::StringKeyedValue::Attrs(expected));
4077 }
4078 #[test]
4079 fn eval_attrset_select() {
4080 assert_eq!(eval("{ a = 1; b = 2; }.a"), VMValue::Int(1));
4081 }
4082 #[test]
4083 fn eval_attrset_update() {
4084 let result = eval_full_helper("{ a = 1; } // { b = 2; }");
4085 let mut expected = BTreeMap::new();
4086 expected.insert("a".to_string(), crate::StringKeyedValue::Int(1));
4087 expected.insert("b".to_string(), crate::StringKeyedValue::Int(2));
4088 assert_eq!(result, crate::StringKeyedValue::Attrs(expected));
4089 }
4090 #[test]
4091 fn eval_attrset_update_override() {
4092 assert_eq!(eval("({ a = 1; } // { a = 2; }).a"), VMValue::Int(2));
4093 }
4094 #[test]
4095 fn eval_has_attr_true() {
4096 assert_eq!(eval("{ a = 1; } ? a"), VMValue::Bool(true));
4097 }
4098 #[test]
4099 fn eval_has_attr_false() {
4100 assert_eq!(eval("{ a = 1; } ? b"), VMValue::Bool(false));
4101 }
4102 #[test]
4103 fn eval_select_or_default() {
4104 assert_eq!(eval("{ a = 1; }.b or 0"), VMValue::Int(0));
4105 assert_eq!(eval("{ a = 1; }.a or 0"), VMValue::Int(1));
4106 }
4107 #[test]
4108 fn eval_dyn_select_or_default_missing() {
4109 assert_eq!(
4111 eval(r#"let x = "missing"; in { a = 1; }.${ x } or 99"#),
4112 VMValue::Int(99),
4113 );
4114 }
4115 #[test]
4116 fn eval_dyn_select_or_default_found() {
4117 assert_eq!(
4119 eval(r#"let x = "a"; in { a = 42; }.${ x } or 99"#),
4120 VMValue::Int(42),
4121 );
4122 }
4123 #[test]
4124 fn eval_dyn_select_or_default_dotted_key() {
4125 assert_eq!(
4127 eval(r#"let x = "a.b"; in { "a.b" = 7; }.${ x } or 0"#),
4128 VMValue::Int(7),
4129 );
4130 }
4131 #[test]
4132 fn eval_dyn_select_or_default_special_chars() {
4133 assert_eq!(
4135 eval(r#"let x = "armv8.3-a+crypto+sha2"; in { "armv8-a" = 1; }.${ x } or 0"#),
4136 VMValue::Int(0),
4137 );
4138 }
4139 #[test]
4140 fn eval_dyn_select_or_default_non_attrset() {
4141 assert_eq!(
4143 eval(r#"let x = "a"; base = 42; in base.${ x } or 99"#),
4144 VMValue::Int(99),
4145 );
4146 }
4147 #[test]
4149 fn eval_identity_lambda() {
4150 assert_eq!(eval("(x: x) 42"), VMValue::Int(42));
4151 }
4152 #[test]
4153 fn eval_lambda_arithmetic() {
4154 assert_eq!(eval("(x: x + 1) 5"), VMValue::Int(6));
4155 }
4156 #[test]
4157 #[ignore = "requires upvalue capture (Phase 2)"]
4158 fn eval_curried_lambda() {
4159 assert_eq!(eval("(x: y: x + y) 3 4"), VMValue::Int(7));
4160 }
4161 #[test]
4162 fn eval_let_lambda() {
4163 assert_eq!(
4164 eval("let f = x: x * 2; in f 5"),
4165 VMValue::Int(10)
4166 );
4167 }
4168 #[test]
4169 fn eval_pattern_lambda() {
4170 assert_eq!(eval("({ a, b }: a + b) { a = 3; b = 4; }"), VMValue::Int(7));
4171 }
4172 #[test]
4173 fn eval_pattern_lambda_default() {
4174 assert_eq!(
4175 eval("({ a, b ? 10 }: a + b) { a = 5; }"),
4176 VMValue::Int(15)
4177 );
4178 }
4179 #[test]
4180 fn eval_lambda_with_let() {
4181 assert_eq!(
4182 eval("let inc = x: x + 1; double = x: x * 2; in double (inc 3)"),
4183 VMValue::Int(8)
4184 );
4185 }
4186 #[test]
4188 fn eval_assert_pass() {
4189 assert_eq!(eval("assert true; 42"), VMValue::Int(42));
4190 }
4191 #[test]
4192 fn eval_assert_fail() {
4193 assert!(matches!(eval_err("assert false; 42"), VMError::AssertionFailed));
4194 }
4195 #[test]
4197 fn deep_eq_attrs_with_thunked_values() {
4198 assert_eq!(
4201 eval("let a = { x = 1; }; b = { x = 1; }; in a == b"),
4202 VMValue::Bool(true)
4203 );
4204 }
4205 #[test]
4206 fn deep_eq_attrs_different_values() {
4207 assert_eq!(
4208 eval("let a = { x = 1; }; b = { x = 2; }; in a == b"),
4209 VMValue::Bool(false)
4210 );
4211 }
4212 #[test]
4213 fn deep_eq_nested_attrs() {
4214 assert_eq!(
4215 eval("let a = { x = { y = 1; }; }; b = { x = { y = 1; }; }; in a == b"),
4216 VMValue::Bool(true)
4217 );
4218 }
4219 #[test]
4220 fn deep_eq_list_with_thunked_elements() {
4221 assert_eq!(
4222 eval("let a = [ 1 2 ]; b = [ 1 2 ]; in a == b"),
4223 VMValue::Bool(true)
4224 );
4225 }
4226 #[test]
4228 fn eval_elem_thunked_attrsets() {
4229 assert_eq!(
4231 eval("let a = { x = 1; }; b = { x = 1; }; in builtins.elem a [ b ]"),
4232 VMValue::Bool(true)
4233 );
4234 }
4235 #[test]
4236 fn eval_elem_basic_int() {
4237 assert_eq!(
4238 eval("builtins.elem 2 [ 1 2 3 ]"),
4239 VMValue::Bool(true)
4240 );
4241 }
4242 #[test]
4243 fn eval_elem_missing() {
4244 assert_eq!(
4245 eval("builtins.elem 4 [ 1 2 3 ]"),
4246 VMValue::Bool(false)
4247 );
4248 }
4249 #[test]
4250 fn eval_elem_string() {
4251 assert_eq!(
4252 eval(r#"builtins.elem "b" [ "a" "b" "c" ]"#),
4253 VMValue::Bool(true)
4254 );
4255 }
4256 #[test]
4257 fn eval_elem_thunked_list_elements() {
4258 assert_eq!(
4259 eval("let x = 1; in builtins.elem 1 [ x ]"),
4260 VMValue::Bool(true)
4261 );
4262 }
4263 #[test]
4265 fn eval_string_interpolation() {
4266 assert_eq!(
4267 eval(r#"let x = "world"; in "hello ${x}""#),
4268 VMValue::String("hello world".to_string()),
4269 );
4270 }
4271 #[test]
4272 #[ignore = "requires builtins.toString (Phase 2)"]
4273 fn eval_string_interpolation_int() {
4274 assert_eq!(
4275 eval(r#"let n = 42; in "value: ${toString n}""#),
4276 VMValue::String("value: 42".to_string()),
4277 );
4278 }
4279 #[test]
4281 fn eval_absolute_path() {
4282 assert_eq!(eval("/tmp/x"), VMValue::Path("/tmp/x".to_string()));
4283 }
4284 #[test]
4286 fn eval_fibonacci_like() {
4287 assert_eq!(
4288 eval("let a = 1; b = 1; c = a + b; d = b + c; e = c + d; in e"),
4289 VMValue::Int(5)
4290 );
4291 }
4292 #[test]
4293 fn eval_nested_attrset_select() {
4294 assert_eq!(
4295 eval("{ a = { b = 42; }; }.a.b"),
4296 VMValue::Int(42)
4297 );
4298 }
4299 #[test]
4300 fn eval_let_with_attrset() {
4301 assert_eq!(
4302 eval("let set = { x = 10; y = 20; }; in set.x + set.y"),
4303 VMValue::Int(30)
4304 );
4305 }
4306 #[test]
4307 fn eval_conditional_attrset() {
4308 assert_eq!(
4309 eval("(if true then { a = 1; } else { a = 2; }).a"),
4310 VMValue::Int(1)
4311 );
4312 }
4313 #[test]
4315 fn builtin_length() {
4316 assert_eq!(eval("builtins.length [1 2 3]"), VMValue::Int(3));
4317 }
4318 #[test]
4319 fn builtin_length_empty() {
4320 assert_eq!(eval("builtins.length []"), VMValue::Int(0));
4321 }
4322 #[test]
4323 fn builtin_head() {
4324 assert_eq!(eval("builtins.head [10 20 30]"), VMValue::Int(10));
4325 }
4326 #[test]
4327 fn builtin_tail() {
4328 let result = eval_full_helper("builtins.tail [1 2 3]");
4329 assert_eq!(
4330 result,
4331 StringKeyedValue::List(vec![StringKeyedValue::Int(2), StringKeyedValue::Int(3)])
4332 );
4333 }
4334 #[test]
4335 fn builtin_type_of_int() {
4336 assert_eq!(
4337 eval("builtins.typeOf 42"),
4338 VMValue::String("int".to_string())
4339 );
4340 }
4341 #[test]
4342 fn builtin_type_of_string() {
4343 assert_eq!(
4344 eval("builtins.typeOf \"hello\""),
4345 VMValue::String("string".to_string())
4346 );
4347 }
4348 #[test]
4349 fn builtin_type_of_bool() {
4350 assert_eq!(
4351 eval("builtins.typeOf true"),
4352 VMValue::String("bool".to_string())
4353 );
4354 }
4355 #[test]
4356 fn builtin_type_of_null() {
4357 assert_eq!(
4358 eval("builtins.typeOf null"),
4359 VMValue::String("null".to_string())
4360 );
4361 }
4362 #[test]
4363 fn builtin_type_of_list() {
4364 assert_eq!(
4365 eval("builtins.typeOf [1 2]"),
4366 VMValue::String("list".to_string())
4367 );
4368 }
4369 #[test]
4370 fn builtin_type_of_set() {
4371 assert_eq!(
4372 eval("builtins.typeOf { a = 1; }"),
4373 VMValue::String("set".to_string())
4374 );
4375 }
4376 #[test]
4377 fn builtin_type_of_lambda() {
4378 assert_eq!(
4379 eval("builtins.typeOf (x: x)"),
4380 VMValue::String("lambda".to_string())
4381 );
4382 }
4383 #[test]
4384 fn builtin_is_int() {
4385 assert_eq!(eval("builtins.isInt 42"), VMValue::Bool(true));
4386 assert_eq!(
4387 eval("builtins.isInt \"hello\""),
4388 VMValue::Bool(false)
4389 );
4390 }
4391 #[test]
4392 fn builtin_is_string() {
4393 assert_eq!(eval("builtins.isString \"hi\""), VMValue::Bool(true));
4394 assert_eq!(eval("builtins.isString 42"), VMValue::Bool(false));
4395 }
4396 #[test]
4397 fn builtin_is_list() {
4398 assert_eq!(eval("builtins.isList [1]"), VMValue::Bool(true));
4399 assert_eq!(eval("builtins.isList 42"), VMValue::Bool(false));
4400 }
4401 #[test]
4402 fn builtin_is_attrs() {
4403 assert_eq!(
4404 eval("builtins.isAttrs { a = 1; }"),
4405 VMValue::Bool(true)
4406 );
4407 assert_eq!(eval("builtins.isAttrs 42"), VMValue::Bool(false));
4408 }
4409 #[test]
4410 fn builtin_is_function() {
4411 assert_eq!(
4412 eval("builtins.isFunction (x: x)"),
4413 VMValue::Bool(true)
4414 );
4415 assert_eq!(eval("builtins.isFunction 42"), VMValue::Bool(false));
4416 }
4417 #[test]
4418 fn builtin_is_bool() {
4419 assert_eq!(eval("builtins.isBool true"), VMValue::Bool(true));
4420 assert_eq!(eval("builtins.isBool 42"), VMValue::Bool(false));
4421 }
4422 #[test]
4423 fn builtin_is_null() {
4424 assert_eq!(eval("builtins.isNull null"), VMValue::Bool(true));
4425 assert_eq!(eval("builtins.isNull 42"), VMValue::Bool(false));
4426 }
4427 #[test]
4428 fn builtin_string_length() {
4429 assert_eq!(
4430 eval("builtins.stringLength \"hello\""),
4431 VMValue::Int(5)
4432 );
4433 }
4434 #[test]
4435 fn builtin_to_string_int() {
4436 assert_eq!(
4437 eval("builtins.toString 42"),
4438 VMValue::String("42".to_string())
4439 );
4440 }
4441 #[test]
4442 fn builtin_to_string_bool() {
4443 assert_eq!(
4444 eval("builtins.toString true"),
4445 VMValue::String("1".to_string())
4446 );
4447 }
4448 #[test]
4449 fn builtin_throw() {
4450 let result = eval_err("builtins.throw \"test error\"");
4451 assert!(matches!(result, VMError::Throw(_)));
4452 }
4453 #[test]
4454 fn builtin_abort() {
4455 let result = eval_err("builtins.abort \"fatal\"");
4456 assert!(matches!(result, VMError::Throw(_)));
4457 }
4458 #[test]
4459 fn builtin_add_curried() {
4460 assert_eq!(eval("builtins.add 3 4"), VMValue::Int(7));
4461 }
4462 #[test]
4463 fn builtin_sub_curried() {
4464 assert_eq!(eval("builtins.sub 10 3"), VMValue::Int(7));
4465 }
4466 #[test]
4467 fn builtin_mul_curried() {
4468 assert_eq!(eval("builtins.mul 6 7"), VMValue::Int(42));
4469 }
4470 #[test]
4471 fn builtin_div_curried() {
4472 assert_eq!(eval("builtins.div 42 6"), VMValue::Int(7));
4473 }
4474 #[test]
4475 fn builtin_elem_at() {
4476 assert_eq!(eval("builtins.elemAt [10 20 30] 1"), VMValue::Int(20));
4477 }
4478 #[test]
4479 fn builtin_elem() {
4480 assert_eq!(eval("builtins.elem 2 [1 2 3]"), VMValue::Bool(true));
4481 assert_eq!(eval("builtins.elem 5 [1 2 3]"), VMValue::Bool(false));
4482 }
4483 #[test]
4484 fn builtin_concat_lists() {
4485 let result = eval_full_helper("builtins.concatLists [[1 2] [3 4]]");
4486 assert_eq!(
4487 result,
4488 StringKeyedValue::List(vec![
4489 StringKeyedValue::Int(1),
4490 StringKeyedValue::Int(2),
4491 StringKeyedValue::Int(3),
4492 StringKeyedValue::Int(4),
4493 ])
4494 );
4495 }
4496 #[test]
4497 fn builtin_has_prefix() {
4498 assert_eq!(
4499 eval("builtins.hasPrefix \"he\" \"hello\""),
4500 VMValue::Bool(true)
4501 );
4502 assert_eq!(
4503 eval("builtins.hasPrefix \"wo\" \"hello\""),
4504 VMValue::Bool(false)
4505 );
4506 }
4507 #[test]
4508 fn builtin_has_suffix() {
4509 assert_eq!(
4510 eval("builtins.hasSuffix \"lo\" \"hello\""),
4511 VMValue::Bool(true)
4512 );
4513 }
4514 #[test]
4515 fn builtin_concat_strings_sep() {
4516 assert_eq!(
4517 eval("builtins.concatStringsSep \", \" [\"a\" \"b\" \"c\"]"),
4518 VMValue::String("a, b, c".to_string())
4519 );
4520 }
4521 #[test]
4522 fn builtin_to_lower() {
4523 assert_eq!(
4524 eval("builtins.toLower \"Hello World\""),
4525 VMValue::String("hello world".to_string())
4526 );
4527 }
4528 #[test]
4529 fn builtin_to_upper() {
4530 assert_eq!(
4531 eval("builtins.toUpper \"hello\""),
4532 VMValue::String("HELLO".to_string())
4533 );
4534 }
4535 #[test]
4536 fn builtin_from_json() {
4537 assert_eq!(
4538 eval("builtins.fromJSON \"42\""),
4539 VMValue::Int(42)
4540 );
4541 assert_eq!(
4542 eval("builtins.fromJSON \"true\""),
4543 VMValue::Bool(true)
4544 );
4545 }
4546 #[test]
4547 fn builtin_seq() {
4548 assert_eq!(eval("builtins.seq 1 42"), VMValue::Int(42));
4549 }
4550 #[test]
4551 fn builtin_deep_seq() {
4552 assert_eq!(eval("builtins.deepSeq [1 2] 42"), VMValue::Int(42));
4553 }
4554 #[test]
4555 fn builtin_trace() {
4556 assert_eq!(
4557 eval("builtins.trace \"debug\" 42"),
4558 VMValue::Int(42)
4559 );
4560 }
4561 #[test]
4562 fn builtin_ceil_floor() {
4563 assert_eq!(eval("builtins.ceil 3.2"), VMValue::Int(4));
4564 assert_eq!(eval("builtins.floor 3.8"), VMValue::Int(3));
4565 }
4566 #[test]
4567 fn builtin_bit_ops() {
4568 assert_eq!(eval("builtins.bitAnd 12 10"), VMValue::Int(8));
4569 assert_eq!(eval("builtins.bitOr 12 10"), VMValue::Int(14));
4570 assert_eq!(eval("builtins.bitXor 12 10"), VMValue::Int(6));
4571 }
4572 #[test]
4573 fn builtin_intersect_attrs() {
4574 let result =
4575 eval_full_helper("builtins.intersectAttrs { a = 1; b = 2; } { a = 10; c = 30; }");
4576 match result {
4577 StringKeyedValue::Attrs(map) => {
4578 assert_eq!(map.get("a"), Some(&StringKeyedValue::Int(10)));
4579 assert!(!map.contains_key("b"));
4580 assert!(!map.contains_key("c"));
4581 }
4582 _ => panic!("expected Attrs, got {result:?}"),
4583 }
4584 }
4585 #[test]
4586 fn builtin_attr_values() {
4587 let result = eval_full_helper("builtins.attrValues { a = 1; b = 2; }");
4588 match result {
4589 StringKeyedValue::List(items) => {
4590 assert_eq!(items.len(), 2);
4591 assert!(items.contains(&StringKeyedValue::Int(1)));
4592 assert!(items.contains(&StringKeyedValue::Int(2)));
4593 }
4594 _ => panic!("expected List, got {result:?}"),
4595 }
4596 }
4597 #[test]
4598 fn builtin_to_int() {
4599 assert_eq!(eval("builtins.toInt \"42\""), VMValue::Int(42));
4600 }
4601 #[test]
4602 fn builtin_replace_strings() {
4603 assert_eq!(
4604 eval("builtins.replaceStrings [\"o\"] [\"0\"] \"foo\""),
4605 VMValue::String("f00".to_string())
4606 );
4607 }
4608 #[test]
4609 fn builtin_substring() {
4610 assert_eq!(
4611 eval("builtins.substring 1 3 \"hello\""),
4612 VMValue::String("ell".to_string())
4613 );
4614 }
4615 #[test]
4617 fn import_basic() {
4618 let dir = tempfile::tempdir().unwrap();
4619 let file_path = dir.path().join("test.nix");
4620 std::fs::write(&file_path, "42").unwrap();
4621 let nix_expr = format!("import {}", file_path.display());
4622 assert_eq!(eval(&nix_expr), VMValue::Int(42));
4623 }
4624 #[test]
4625 fn import_cached() {
4626 let dir = tempfile::tempdir().unwrap();
4627 let file_path = dir.path().join("cached.nix");
4628 std::fs::write(&file_path, "{ x = 1; }").unwrap();
4629 let nix_expr = format!(
4630 "let a = import {}; b = import {}; in a == b",
4631 file_path.display(),
4632 file_path.display()
4633 );
4634 assert_eq!(eval(&nix_expr), VMValue::Bool(true));
4635 }
4636 #[test]
4637 fn import_attrset() {
4638 let dir = tempfile::tempdir().unwrap();
4639 let file_path = dir.path().join("attrs.nix");
4640 std::fs::write(&file_path, "{ greeting = \"hello\"; }").unwrap();
4641 let nix_expr = format!("(import {}).greeting", file_path.display());
4642 assert_eq!(eval(&nix_expr), VMValue::String("hello".to_string()));
4643 }
4644 #[test]
4645 fn import_directory_default_nix() {
4646 let dir = tempfile::tempdir().unwrap();
4648 let sub = dir.path().join("mylib");
4649 std::fs::create_dir(&sub).unwrap();
4650 std::fs::write(sub.join("default.nix"), "{ x = 42; }").unwrap();
4651 let nix_expr = format!("(import {}).x", sub.display());
4652 assert_eq!(eval(&nix_expr), VMValue::Int(42));
4653 }
4654 #[test]
4655 fn import_directory_cached() {
4656 let dir = tempfile::tempdir().unwrap();
4658 let sub = dir.path().join("lib");
4659 std::fs::create_dir(&sub).unwrap();
4660 std::fs::write(sub.join("default.nix"), "{ v = 99; }").unwrap();
4661 let nix_expr = format!(
4662 "let a = import {}; b = import {}; in a == b",
4663 sub.display(),
4664 sub.display()
4665 );
4666 assert_eq!(eval(&nix_expr), VMValue::Bool(true));
4667 }
4668 #[test]
4669 fn import_directory_nested() {
4670 let dir = tempfile::tempdir().unwrap();
4672 let lib = dir.path().join("lib");
4673 let sub = lib.join("sub");
4674 std::fs::create_dir_all(&sub).unwrap();
4675 std::fs::write(sub.join("default.nix"), "{ val = 7; }").unwrap();
4676 std::fs::write(
4677 lib.join("default.nix"),
4678 &format!("(import {}).val + 3", sub.display()),
4679 )
4680 .unwrap();
4681 let nix_expr = format!("import {}", lib.display());
4682 assert_eq!(eval(&nix_expr), VMValue::Int(10));
4683 }
4684 #[test]
4686 fn lazy_unused_throw_in_attrset() {
4687 assert_eq!(
4688 eval("let s = { a = 1; }; in s.a"),
4689 VMValue::Int(1)
4690 );
4691 }
4692 #[test]
4693 fn lazy_unused_let_binding() {
4694 assert_eq!(eval("let x = 1; y = 2; in x"), VMValue::Int(1));
4695 }
4696 #[test]
4698 fn import_forces_thunk_before_type_check() {
4699 let dir = tempfile::tempdir().unwrap();
4702 let file_path = dir.path().join("forced.nix");
4703 std::fs::write(&file_path, "99").unwrap();
4704 let nix_expr = format!(
4705 "let p = {}; in import p",
4706 file_path.display()
4707 );
4708 assert_eq!(eval(&nix_expr), VMValue::Int(99));
4709 }
4710 #[test]
4711 fn import_with_path_value_succeeds() {
4712 let dir = tempfile::tempdir().unwrap();
4713 let file_path = dir.path().join("pathval.nix");
4714 std::fs::write(&file_path, "\"from-path\"").unwrap();
4715 let nix_expr = format!("import {}", file_path.display());
4716 assert_eq!(
4717 eval(&nix_expr),
4718 VMValue::String("from-path".to_string())
4719 );
4720 }
4721 #[test]
4722 fn import_with_string_value_succeeds() {
4723 let dir = tempfile::tempdir().unwrap();
4724 let file_path = dir.path().join("strval.nix");
4725 std::fs::write(&file_path, "\"from-string\"").unwrap();
4726 let nix_expr = format!(
4727 "let s = \"{}\"; in import s",
4728 file_path.display()
4729 );
4730 assert_eq!(
4731 eval(&nix_expr),
4732 VMValue::String("from-string".to_string())
4733 );
4734 }
4735 #[test]
4737 fn tail_call_deep_recursion_via_import() {
4738 let dir = tempfile::tempdir().unwrap();
4742 let file_path = dir.path().join("countdown.nix");
4743 std::fs::write(
4744 &file_path,
4745 "{ f, n }: if n == 0 then 0 else f { inherit f; n = n - 1; }",
4746 )
4747 .unwrap();
4748 let nix_expr = format!(
4751 "let g = import {}; in g {{ f = g; n = 2000; }}",
4752 file_path.display()
4753 );
4754 assert_eq!(eval(&nix_expr), VMValue::Int(0));
4755 }
4756 #[test]
4757 fn tail_call_simple_lambda_chain() {
4758 assert_eq!(
4762 eval("let g = x: x + 1; f = x: g x; in f 41"),
4763 VMValue::Int(42)
4764 );
4765 }
4766 #[test]
4767 fn tail_call_if_branches() {
4768 assert_eq!(
4771 eval("let f = x: if x > 0 then x else x + 1; in f 10"),
4772 VMValue::Int(10)
4773 );
4774 assert_eq!(
4775 eval("let f = x: if x > 0 then x else x + 1; in f 0"),
4776 VMValue::Int(1)
4777 );
4778 }
4779 #[test]
4781 fn builtin_get_env_returns_value() {
4782 unsafe { std::env::set_var("SUI_TEST_VAR", "hello_sui") };
4785 assert_eq!(
4786 eval("builtins.getEnv \"SUI_TEST_VAR\""),
4787 VMValue::String("hello_sui".to_string())
4788 );
4789 unsafe { std::env::remove_var("SUI_TEST_VAR") };
4790 }
4791 #[test]
4792 fn builtin_get_env_missing_returns_empty() {
4793 unsafe { std::env::remove_var("SUI_NONEXISTENT_VAR_12345") };
4796 assert_eq!(
4797 eval("builtins.getEnv \"SUI_NONEXISTENT_VAR_12345\""),
4798 VMValue::String(String::new())
4799 );
4800 }
4801 #[test]
4802 fn builtin_try_eval_success() {
4803 let result = eval_full_helper("builtins.tryEval 42");
4805 match result {
4806 StringKeyedValue::Attrs(map) => {
4807 assert_eq!(
4808 map.get("success"),
4809 Some(&StringKeyedValue::Bool(true))
4810 );
4811 assert_eq!(
4812 map.get("value"),
4813 Some(&StringKeyedValue::Int(42))
4814 );
4815 }
4816 _ => panic!("expected Attrs, got {result:?}"),
4817 }
4818 }
4819 #[test]
4820 fn builtin_try_eval_with_non_throwing_expr() {
4821 let result = eval_full_helper(
4824 "builtins.tryEval (1 + 2)"
4825 );
4826 match result {
4827 StringKeyedValue::Attrs(map) => {
4828 assert_eq!(
4829 map.get("success"),
4830 Some(&StringKeyedValue::Bool(true))
4831 );
4832 assert_eq!(
4833 map.get("value"),
4834 Some(&StringKeyedValue::Int(3))
4835 );
4836 }
4837 _ => panic!("expected Attrs, got {result:?}"),
4838 }
4839 }
4840 #[test]
4841 fn builtin_try_eval_with_throw_catches() {
4842 let result = eval_full_helper(
4848 "let bad = builtins.throw \"oops\"; in builtins.tryEval bad"
4849 );
4850 match result {
4851 StringKeyedValue::Attrs(map) => {
4852 assert_eq!(
4853 map.get("success"),
4854 Some(&StringKeyedValue::Bool(false))
4855 );
4856 assert_eq!(
4857 map.get("value"),
4858 Some(&StringKeyedValue::Bool(false))
4859 );
4860 }
4861 _ => panic!("expected Attrs, got {result:?}"),
4862 }
4863 }
4864 #[test]
4866 fn if_else_in_let_body_stack_depth() {
4867 assert_eq!(
4870 eval("let a = 1; in if a == 1 then 10 else 20"),
4871 VMValue::Int(10),
4872 );
4873 }
4874 #[test]
4875 fn nested_let_with_if_else() {
4876 assert_eq!(
4878 eval(r#"
4879 let
4880 a = 1;
4881 b = if a == 1 then 2 else 3;
4882 in
4883 let c = b + 10; in c
4884 "#),
4885 VMValue::Int(12),
4886 );
4887 }
4888 #[test]
4889 fn short_circuit_and_in_let_body() {
4890 assert_eq!(
4892 eval("let x = true; in x && false"),
4893 VMValue::Bool(false),
4894 );
4895 }
4896 #[test]
4897 fn short_circuit_or_in_let_body() {
4898 assert_eq!(
4899 eval("let x = false; in x || true"),
4900 VMValue::Bool(true),
4901 );
4902 }
4903 #[test]
4904 fn short_circuit_implication_in_let_body() {
4905 assert_eq!(
4907 eval("let x = false; in x -> 42"),
4908 VMValue::Bool(true),
4909 );
4910 }
4911 #[test]
4912 fn inherit_from_in_attrset_stack_depth() {
4913 assert_eq!(
4916 eval(r#"
4917 let
4918 src = { a = 1; b = 2; };
4919 result = { inherit (src) a b; c = 3; };
4920 in result.a + result.b + result.c
4921 "#),
4922 VMValue::Int(6),
4923 );
4924 }
4925 #[test]
4926 fn inherit_from_many_fields_stack_depth() {
4927 assert_eq!(
4930 eval(r#"
4931 let
4932 s = { w = 1; x = 2; y = 3; z = 4; };
4933 r = { inherit (s) w x y z; extra = 10; };
4934 in r.w + r.x + r.y + r.z + r.extra
4935 "#),
4936 VMValue::Int(20),
4937 );
4938 }
4939 #[test]
4940 fn if_else_followed_by_let_binding() {
4941 assert_eq!(
4945 eval(r#"
4946 let
4947 a = 1;
4948 b = 2;
4949 c = 3;
4950 in
4951 let
4952 x = if a == 1 then b else c;
4953 y = x + 100;
4954 in y
4955 "#),
4956 VMValue::Int(102),
4957 );
4958 }
4959 #[test]
4960 fn multi_segment_hasattr_stack_depth() {
4961 assert_eq!(
4964 eval(r#"
4965 let
4966 s = { a = { b = 1; }; };
4967 has = s ? a.b;
4968 val = if has then 42 else 0;
4969 in val
4970 "#),
4971 VMValue::Int(42),
4972 );
4973 }
4974 #[test]
4975 fn many_let_bindings_with_if_else() {
4976 assert_eq!(
4980 eval(r#"
4981 let
4982 a = 1;
4983 b = 2;
4984 c = 3;
4985 d = 4;
4986 e = 5;
4987 f = 6;
4988 g = 7;
4989 h = 8;
4990 i = 9;
4991 j = 10;
4992 in
4993 let
4994 x = if a == 1 then b else c;
4995 y = if d == 4 then e else f;
4996 z = if g == 7 then h else i;
4997 w = j;
4998 in x + y + z + w
4999 "#),
5000 VMValue::Int(25),
5001 );
5002 }
5003 #[test]
5004 fn import_in_pattern_default_stack_depth() {
5005 assert_eq!(
5011 eval(r#"
5012 let
5013 f = { a ? 1, b ? 2, c ? 3 }:
5014 a + b + c;
5015 in f {}
5016 "#),
5017 VMValue::Int(6),
5018 );
5019 }
5020 #[test]
5021 fn pattern_lambda_many_defaults_then_let() {
5022 assert_eq!(
5028 eval(r#"
5029 let
5030 mk = { a, b ? 10, c ? 20, d ? 30, e ? 40 }:
5031 let
5032 sum = a + b + c + d + e;
5033 doubled = sum + sum;
5034 in doubled;
5035 in mk { a = 1; }
5036 "#),
5037 VMValue::Int(202),
5038 );
5039 }
5040 #[test]
5042 fn rec_dotted_lambda_captures_sibling() {
5043 let result = eval_full_helper(
5048 r#"rec { types.a = 1; types.b = 2; f = _: types; }.f 0"#,
5049 );
5050 match result {
5051 StringKeyedValue::Attrs(ref m) => {
5052 assert_eq!(m.get("a"), Some(&StringKeyedValue::Int(1)));
5053 assert_eq!(m.get("b"), Some(&StringKeyedValue::Int(2)));
5054 }
5055 other => panic!("expected attrset, got {other:?}"),
5056 }
5057 }
5058 #[test]
5059 fn rec_dotted_lambda_attr_select() {
5060 assert_eq!(
5062 eval(r#"rec { types.a = 1; types.b = 2; f = x: types.b; result = f 0; }.result"#),
5063 VMValue::Int(2),
5064 );
5065 }
5066 #[test]
5067 fn rec_dotted_lambda_assert_check() {
5068 assert_eq!(
5072 eval(r#"
5073 rec {
5074 types.parsedPlatform = { check = _: true; };
5075 mkSystem = components:
5076 assert types.parsedPlatform.check components;
5077 components;
5078 result = mkSystem 42;
5079 }.result
5080 "#),
5081 VMValue::Int(42),
5082 );
5083 }
5084 #[test]
5085 fn let_lambda_captures_rec_sibling() {
5086 assert_eq!(
5089 eval(r#"let a = 1 + 1; f = _: a; in f 0"#),
5090 VMValue::Int(2),
5091 );
5092 }
5093 #[test]
5094 fn rec_dotted_multiple_lambdas() {
5095 assert_eq!(
5097 eval(r#"
5098 rec {
5099 a.x = 10;
5100 b.y = 20;
5101 f = _: a.x + b.y;
5102 result = f 0;
5103 }.result
5104 "#),
5105 VMValue::Int(30),
5106 );
5107 }
5108}