1use std::cell::Cell;
12use std::collections::BTreeMap;
13use std::rc::Rc;
14
15use crate::error::VMError;
16use crate::intern::{Interner, Symbol};
17use crate::value::{HigherOrderBuiltin, HigherOrderOp, ThunkState, VMBuiltin, VMValue};
18
19pub struct BuiltinRegistry {
21 entries: Vec<BuiltinEntry>,
23}
24
25struct BuiltinEntry {
26 name: &'static str,
27 func: Rc<dyn Fn(Vec<VMValue>) -> Result<VMValue, VMError>>,
28 arity: u8,
29}
30
31impl BuiltinRegistry {
32 #[must_use]
34 pub fn new() -> Self {
35 let mut reg = Self {
36 entries: Vec::new(),
37 };
38 reg.register_all();
39 reg
40 }
41
42 #[must_use]
44 pub fn lookup(&self, name: &str) -> Option<u16> {
45 self.entries
46 .iter()
47 .position(|e| e.name == name)
48 .map(|i| i as u16)
49 }
50
51 pub fn call(&self, index: u16, args: Vec<VMValue>) -> Result<VMValue, VMError> {
53 let entry = self
54 .entries
55 .get(index as usize)
56 .ok_or_else(|| VMError::UnknownBuiltin(format!("index {index}")))?;
57 (entry.func)(args)
58 }
59
60 pub fn make_builtins_attrset(&self, interner: &mut Interner) -> VMValue {
62 let mut attrs = BTreeMap::new();
63 for (i, entry) in self.entries.iter().enumerate() {
64 let sym = interner.intern(entry.name);
65 let builtin = VMValue::Builtin(VMBuiltin {
66 name: entry.name,
67 func: Rc::clone(&entry.func),
68 arity: entry.arity,
69 });
70 let _ = i;
71 attrs.insert(sym, builtin);
72 }
73
74 let sys_sym = interner.intern("currentSystem");
76 let system = if cfg!(target_arch = "aarch64") {
77 if cfg!(target_os = "macos") {
78 "aarch64-darwin"
79 } else {
80 "aarch64-linux"
81 }
82 } else if cfg!(target_os = "macos") {
83 "x86_64-darwin"
84 } else {
85 "x86_64-linux"
86 };
87 attrs.insert(sys_sym, VMValue::String(system.to_string()));
88
89 let ver_sym = interner.intern("nixVersion");
99 attrs.insert(
100 ver_sym,
101 VMValue::String(sui_compat::versions::IMPERSONATED_NIX_VERSION.to_string()),
102 );
103
104 let lang_sym = interner.intern("langVersion");
106 attrs.insert(lang_sym, VMValue::Int(sui_compat::versions::LANG_VERSION));
107
108 let true_sym = interner.intern("true");
110 attrs.insert(true_sym, VMValue::Bool(true));
111 let false_sym = interner.intern("false");
112 attrs.insert(false_sym, VMValue::Bool(false));
113 let null_sym = interner.intern("null");
114 attrs.insert(null_sym, VMValue::Null);
115
116 let store_sym = interner.intern("storeDir");
118 attrs.insert(store_sym, VMValue::String("/nix/store".to_string()));
119
120 let nixpath_sym = interner.intern("nixPath");
122 let nix_path_list = {
123 let nix_path = std::env::var("NIX_PATH").unwrap_or_default();
124 let entries: Vec<VMValue> = nix_path
125 .split(':')
126 .filter(|s| !s.is_empty())
127 .map(|entry| {
128 let (prefix, path) = if let Some(idx) = entry.find('=') {
129 (entry[..idx].to_string(), entry[idx + 1..].to_string())
130 } else {
131 (String::new(), entry.to_string())
132 };
133 let prefix_sym = interner.intern("prefix");
134 let path_sym = interner.intern("path");
135 let mut entry_attrs = BTreeMap::new();
136 entry_attrs.insert(prefix_sym, VMValue::String(prefix));
137 entry_attrs.insert(path_sym, VMValue::String(path));
138 VMValue::Attrs(entry_attrs)
139 })
140 .collect();
141 VMValue::List(entries)
142 };
143 attrs.insert(nixpath_sym, nix_path_list);
144
145 let time_sym = interner.intern("currentTime");
147 attrs.insert(time_sym, VMValue::Int(0));
148
149 let self_sym = interner.intern("builtins");
160 let inner = attrs.clone();
161 attrs.insert(self_sym, VMValue::Attrs(inner));
162
163 VMValue::Attrs(attrs)
164 }
165
166 #[must_use]
168 pub fn name(&self, index: u16) -> Option<&'static str> {
169 self.entries.get(index as usize).map(|e| e.name)
170 }
171
172 fn register(
173 &mut self,
174 name: &'static str,
175 arity: u8,
176 func: impl Fn(Vec<VMValue>) -> Result<VMValue, VMError> + 'static,
177 ) {
178 self.entries.push(BuiltinEntry {
179 name,
180 func: Rc::new(func),
181 arity,
182 });
183 }
184
185 fn register_all(&mut self) {
186 self.register_type_checks();
187 self.register_list_ops();
188 self.register_higher_order_ops();
189 self.register_attrset_ops();
190 self.register_string_ops();
191 self.register_conversion_ops();
192 self.register_control_ops();
193 self.register_arithmetic_ops();
194 self.register_derivation_ops();
195 self.register_missing_builtins();
196 }
197
198 fn register_type_checks(&mut self) {
201 self.register("typeOf", 1, |args| {
202 let name = match &args[0] {
203 VMValue::Null => "null",
204 VMValue::Bool(_) => "bool",
205 VMValue::Int(_) => "int",
206 VMValue::Float(_) => "float",
207 VMValue::String(_) => "string",
208 VMValue::Path(_) => "path",
209 VMValue::List(_) => "list",
210 VMValue::Attrs(_) => "set",
211 VMValue::Closure(_) | VMValue::Builtin(_) | VMValue::HigherOrderBuiltin(_) => "lambda",
212 VMValue::Thunk(_) => "thunk",
213 };
214 Ok(VMValue::String(name.to_string()))
215 });
216 self.register("isNull", 1, |args| {
217 Ok(VMValue::Bool(matches!(args[0], VMValue::Null)))
218 });
219 self.register("isInt", 1, |args| {
220 Ok(VMValue::Bool(matches!(args[0], VMValue::Int(_))))
221 });
222 self.register("isFloat", 1, |args| {
223 Ok(VMValue::Bool(matches!(args[0], VMValue::Float(_))))
224 });
225 self.register("isBool", 1, |args| {
226 Ok(VMValue::Bool(matches!(args[0], VMValue::Bool(_))))
227 });
228 self.register("isString", 1, |args| {
229 Ok(VMValue::Bool(matches!(args[0], VMValue::String(_))))
230 });
231 self.register("isList", 1, |args| {
232 Ok(VMValue::Bool(matches!(args[0], VMValue::List(_))))
233 });
234 self.register("isAttrs", 1, |args| {
235 Ok(VMValue::Bool(matches!(args[0], VMValue::Attrs(_))))
236 });
237 self.register("isFunction", 1, |args| {
238 Ok(VMValue::Bool(matches!(
239 args[0],
240 VMValue::Closure(_) | VMValue::Builtin(_) | VMValue::HigherOrderBuiltin(_)
241 )))
242 });
243 self.register("isPath", 1, |args| {
244 Ok(VMValue::Bool(matches!(args[0], VMValue::Path(_))))
245 });
246 }
247
248 fn register_list_ops(&mut self) {
251 self.register("length", 1, |args| {
252 let list = as_list(&args[0])?;
253 Ok(VMValue::Int(list.len() as i64))
254 });
255
256 self.register("head", 1, |args| {
257 let list = as_list(&args[0])?;
258 list.first()
259 .cloned()
260 .ok_or_else(|| VMError::Throw("head: empty list".to_string()))
261 });
262
263 self.register("tail", 1, |args| {
264 let list = as_list(&args[0])?;
265 if list.is_empty() {
266 return Err(VMError::Throw("tail: empty list".to_string()));
267 }
268 Ok(VMValue::List(list[1..].to_vec()))
269 });
270
271 self.register("elemAt", 1, |args| {
272 let list = as_list(&args[0])?.to_vec();
273 Ok(VMValue::Builtin(VMBuiltin {
274 name: "elemAt<partial>",
275 func: Rc::new(move |args2| {
276 let idx = as_int(&args2[0])? as usize;
277 list.get(idx).cloned().ok_or_else(|| {
278 VMError::Throw(format!("elemAt: index {idx} out of bounds"))
279 })
280 }),
281 arity: 1,
282 }))
283 });
284
285 self.register("elem", 1, |args| {
286 let needle = args[0].clone();
287 Ok(VMValue::HigherOrderBuiltin(HigherOrderBuiltin {
288 op: HigherOrderOp::Elem,
289 func: Box::new(needle),
290 extra_args: Vec::new(),
291 }))
292 });
293
294 self.register("genList", 1, |args| {
295 Ok(VMValue::HigherOrderBuiltin(HigherOrderBuiltin {
296 op: HigherOrderOp::GenList,
297 func: Box::new(args[0].clone()),
298 extra_args: Vec::new(),
299 }))
300 });
301
302 self.register("map", 1, |args| {
304 Ok(VMValue::HigherOrderBuiltin(HigherOrderBuiltin {
305 op: HigherOrderOp::Map,
306 func: Box::new(args[0].clone()),
307 extra_args: Vec::new(),
308 }))
309 });
310
311 self.register("filter", 1, |args| {
313 Ok(VMValue::HigherOrderBuiltin(HigherOrderBuiltin {
314 op: HigherOrderOp::Filter,
315 func: Box::new(args[0].clone()),
316 extra_args: Vec::new(),
317 }))
318 });
319
320 self.register("concatLists", 1, |args| {
321 let lists = as_list(&args[0])?;
322 let mut result = Vec::new();
323 for v in &lists {
324 let inner = as_list(v)?;
325 result.extend(inner);
326 }
327 Ok(VMValue::List(result))
328 });
329
330 self.register("sort", 1, |args| {
331 Ok(VMValue::HigherOrderBuiltin(HigherOrderBuiltin {
332 op: HigherOrderOp::Sort,
333 func: Box::new(args[0].clone()),
334 extra_args: Vec::new(),
335 }))
336 });
337 }
338
339
340 fn register_higher_order_ops(&mut self) {
343 self.register("foldl'", 1, |args| {
344 Ok(VMValue::HigherOrderBuiltin(HigherOrderBuiltin {
345 op: HigherOrderOp::FoldlP1,
346 func: Box::new(args[0].clone()),
347 extra_args: Vec::new(),
348 }))
349 });
350 self.register("concatMap", 1, |args| {
351 Ok(VMValue::HigherOrderBuiltin(HigherOrderBuiltin {
352 op: HigherOrderOp::ConcatMap,
353 func: Box::new(args[0].clone()),
354 extra_args: Vec::new(),
355 }))
356 });
357 self.register("any", 1, |args| {
358 Ok(VMValue::HigherOrderBuiltin(HigherOrderBuiltin {
359 op: HigherOrderOp::Any,
360 func: Box::new(args[0].clone()),
361 extra_args: Vec::new(),
362 }))
363 });
364 self.register("all", 1, |args| {
365 Ok(VMValue::HigherOrderBuiltin(HigherOrderBuiltin {
366 op: HigherOrderOp::All,
367 func: Box::new(args[0].clone()),
368 extra_args: Vec::new(),
369 }))
370 });
371 self.register("partition", 1, |args| {
372 Ok(VMValue::HigherOrderBuiltin(HigherOrderBuiltin {
373 op: HigherOrderOp::Partition,
374 func: Box::new(args[0].clone()),
375 extra_args: Vec::new(),
376 }))
377 });
378 self.register("groupBy", 1, |args| {
379 Ok(VMValue::HigherOrderBuiltin(HigherOrderBuiltin {
380 op: HigherOrderOp::GroupBy,
381 func: Box::new(args[0].clone()),
382 extra_args: Vec::new(),
383 }))
384 });
385 self.register("mapAttrs", 1, |args| {
386 Ok(VMValue::HigherOrderBuiltin(HigherOrderBuiltin {
387 op: HigherOrderOp::MapAttrs,
388 func: Box::new(args[0].clone()),
389 extra_args: Vec::new(),
390 }))
391 });
392 self.register("functionArgs", 1, |args| {
398 match &args[0] {
399 VMValue::Builtin(_) | VMValue::HigherOrderBuiltin(_) => {
400 Ok(VMValue::Attrs(BTreeMap::new()))
401 }
402 VMValue::Closure(closure) => {
403 let mut result = BTreeMap::new();
404 let mut interner = crate::intern::Interner::new();
405 for (name, has_default) in &closure.formals {
406 let sym = interner.intern(name);
407 result.insert(sym, VMValue::Bool(*has_default));
408 }
409 Ok(VMValue::Attrs(result))
410 }
411 other => Err(VMError::TypeError {
412 expected: "lambda",
413 got: other.type_name(),
414 context: "functionArgs".to_string(),
415 }),
416 }
417 });
418 self.register("catAttrs", 1, |args| {
419 let name = as_string(&args[0])?.to_string();
420 Ok(VMValue::Builtin(VMBuiltin {
421 name: "catAttrs<partial>",
422 func: Rc::new(move |args2| {
423 let list = force_as_list(&args2[0])?;
424 let mut result: Vec<VMValue> = Vec::new();
425 for item in &list {
426 if let VMValue::Attrs(_) = item {
427 let _ = &name;
428 }
429 }
430 Err(VMError::Throw(
431 "catAttrs: requires interner access (use VM dispatch)".to_string(),
432 ))
433 }),
434 arity: 1,
435 }))
436 });
437 }
438
439 fn register_attrset_ops(&mut self) {
442 self.register("attrNames", 1, |args| {
443 let attrs = as_attrs(&args[0])?;
444 let _ = attrs;
448 Err(VMError::Throw(
449 "attrNames: requires interner access (use VM dispatch)".to_string(),
450 ))
451 });
452
453 self.register("attrValues", 1, |_args| {
467 Err(VMError::Throw(
468 "attrValues: requires interner access (use VM dispatch)".to_string(),
469 ))
470 });
471
472 self.register("hasAttr", 1, |args| {
473 let name = as_string(&args[0])?.to_string();
474 Ok(VMValue::Builtin(VMBuiltin {
475 name: "hasAttr<partial>",
476 func: Rc::new(move |_args2| {
477 let _ = &name;
479 Err(VMError::Throw(
480 "hasAttr: requires interner access (use VM dispatch)".to_string(),
481 ))
482 }),
483 arity: 1,
484 }))
485 });
486
487 self.register("getAttr", 1, |args| {
488 let name = as_string(&args[0])?.to_string();
489 Ok(VMValue::Builtin(VMBuiltin {
490 name: "getAttr<partial>",
491 func: Rc::new(move |_args2| {
492 let _ = &name;
493 Err(VMError::Throw(
494 "getAttr: requires interner access (use VM dispatch)".to_string(),
495 ))
496 }),
497 arity: 1,
498 }))
499 });
500
501 self.register("intersectAttrs", 1, |args| {
502 let a_attrs = as_attrs(&args[0])?.clone();
503 Ok(VMValue::Builtin(VMBuiltin {
504 name: "intersectAttrs<partial>",
505 func: Rc::new(move |args2| {
506 let b_attrs = as_attrs(&args2[0])?;
507 let mut result = BTreeMap::new();
508 for (k, v) in b_attrs {
509 if a_attrs.contains_key(k) {
510 result.insert(*k, v.clone());
511 }
512 }
513 Ok(VMValue::Attrs(result))
514 }),
515 arity: 1,
516 }))
517 });
518
519 self.register("removeAttrs", 1, |args| {
520 let set = as_attrs(&args[0])?.clone();
521 Ok(VMValue::Builtin(VMBuiltin {
522 name: "removeAttrs<partial>",
523 func: Rc::new(move |_args2| {
524 let _ = &set;
526 Err(VMError::Throw(
527 "removeAttrs: requires interner access".to_string(),
528 ))
529 }),
530 arity: 1,
531 }))
532 });
533
534 self.register("listToAttrs", 1, |_args| {
535 Err(VMError::Throw(
536 "listToAttrs: requires interner access".to_string(),
537 ))
538 });
539 }
540
541 fn register_string_ops(&mut self) {
544 self.register("stringLength", 1, |args| {
545 let s = as_string(&args[0])?;
546 Ok(VMValue::Int(s.len() as i64))
547 });
548
549 self.register("substring", 1, |args| {
550 let start_i = as_int(&args[0])?;
563 Ok(VMValue::Builtin(VMBuiltin {
564 name: "substring<p1>",
565 func: Rc::new(move |args2| {
566 let len_i = as_int(&args2[0])?;
567 Ok(VMValue::Builtin(VMBuiltin {
568 name: "substring<p2>",
569 func: Rc::new(move |args3| {
570 let s = as_string(&args3[0])?;
571 if start_i < 0 {
572 return Err(VMError::Throw(
573 "substring: negative start position".to_string(),
574 ));
575 }
576 let s_len = s.len();
577 let start = (start_i as usize).min(s_len);
578 let end = if len_i < 0 {
579 s_len
580 } else {
581 start.saturating_add(len_i as usize).min(s_len)
582 };
583 Ok(VMValue::String(s[start..end].to_string()))
584 }),
585 arity: 1,
586 }))
587 }),
588 arity: 1,
589 }))
590 });
591
592 self.register("concatStringsSep", 1, |args| {
593 let sep = as_string(&args[0])?.to_string();
594 Ok(VMValue::Builtin(VMBuiltin {
595 name: "concatStringsSep<partial>",
596 func: Rc::new(move |args2| {
597 let list = force_as_list(&args2[0])?;
598 let strings: Result<Vec<String>, _> =
599 list.iter().map(|v| force_as_string(v)).collect();
600 Ok(VMValue::String(strings?.join(&sep)))
601 }),
602 arity: 1,
603 }))
604 });
605
606 self.register("replaceStrings", 1, |args| {
607 let from: Vec<String> = force_as_list(&args[0])?
608 .iter()
609 .map(|v| force_as_string(v))
610 .collect::<Result<_, _>>()?;
611 Ok(VMValue::Builtin(VMBuiltin {
612 name: "replaceStrings<p1>",
613 func: Rc::new(move |args2| {
614 let to: Vec<String> = force_as_list(&args2[0])?
615 .iter()
616 .map(|v| force_as_string(v))
617 .collect::<Result<_, _>>()?;
618 let from2 = from.clone();
619 Ok(VMValue::Builtin(VMBuiltin {
620 name: "replaceStrings<p2>",
621 func: Rc::new(move |args3| {
622 let mut s = as_string(&args3[0])?.to_string();
623 for (f, t) in from2.iter().zip(to.iter()) {
624 if !f.is_empty() {
625 s = s.replace(f.as_str(), t);
626 }
627 }
628 Ok(VMValue::String(s))
629 }),
630 arity: 1,
631 }))
632 }),
633 arity: 1,
634 }))
635 });
636
637 }
643
644 fn register_conversion_ops(&mut self) {
647 self.register("toString", 1, |args| {
648 vm_coerce_to_string(&args[0])
649 });
650
651 self.register("toJSON", 1, |args| {
652 let json = vm_value_to_json(&args[0])?;
653 let s = serde_json::to_string(&json)
654 .unwrap_or_else(|_| "null".to_string());
655 Ok(VMValue::String(s))
656 });
657
658 self.register("fromJSON", 1, |args| {
659 let s = as_string(&args[0])?;
660 let json: serde_json::Value = serde_json::from_str(s).map_err(|e| {
661 VMError::Throw(format!("fromJSON: {e}"))
662 })?;
663 Ok(json_to_vm_value(&json))
664 });
665
666 self.register("toInt", 1, |args| {
667 let s = as_string(&args[0])?;
668 let n: i64 = s.trim().parse().map_err(|e| {
669 VMError::Throw(format!("toInt: {e}"))
670 })?;
671 Ok(VMValue::Int(n))
672 });
673 }
674
675 fn register_control_ops(&mut self) {
678 self.register("throw", 1, |args| {
679 let msg = as_string(&args[0])?;
680 Err(VMError::Throw(format!("throw: {msg}")))
681 });
682
683 self.register("abort", 1, |args| {
684 let msg = as_string(&args[0])?;
685 Err(VMError::Throw(format!("abort: {msg}")))
686 });
687
688 self.register("seq", 1, |args| {
689 let _forced = args[0].clone();
690 Ok(VMValue::Builtin(VMBuiltin {
691 name: "seq<partial>",
692 func: Rc::new(|args2| Ok(args2[0].clone())),
693 arity: 1,
694 }))
695 });
696
697 self.register("deepSeq", 1, |args| {
698 let _forced = args[0].clone();
699 Ok(VMValue::Builtin(VMBuiltin {
700 name: "deepSeq<partial>",
701 func: Rc::new(|args2| Ok(args2[0].clone())),
702 arity: 1,
703 }))
704 });
705
706 self.register("tryEval", 1, |args| {
707 let val = args[0].clone();
710 let _ = val;
714 Err(VMError::Throw(
715 "tryEval: requires VM-level implementation".to_string(),
716 ))
717 });
718
719 self.register("trace", 1, |args| {
720 let msg = args[0].clone();
721 eprintln!("trace: {msg}");
722 Ok(VMValue::Builtin(VMBuiltin {
723 name: "trace<partial>",
724 func: Rc::new(|args2| Ok(args2[0].clone())),
725 arity: 1,
726 }))
727 });
728 }
729
730 fn register_arithmetic_ops(&mut self) {
733 self.register("add", 1, |args| {
734 let a = args[0].clone();
735 Ok(VMValue::Builtin(VMBuiltin {
736 name: "add<partial>",
737 func: Rc::new(move |args2| match (&a, &args2[0]) {
738 (VMValue::Int(x), VMValue::Int(y)) => Ok(VMValue::Int(x + y)),
739 (VMValue::Float(x), VMValue::Float(y)) => Ok(VMValue::Float(x + y)),
740 (VMValue::Int(x), VMValue::Float(y)) => Ok(VMValue::Float(*x as f64 + y)),
741 (VMValue::Float(x), VMValue::Int(y)) => Ok(VMValue::Float(x + *y as f64)),
742 _ => Err(VMError::Throw("add: expected numbers".to_string())),
743 }),
744 arity: 1,
745 }))
746 });
747
748 self.register("sub", 1, |args| {
752 let a = args[0].clone();
753 Ok(VMValue::Builtin(VMBuiltin {
754 name: "sub<partial>",
755 func: Rc::new(move |args2| vm_numeric_binop("sub", &a, &args2[0], |x, y| x - y, |x, y| x - y)),
756 arity: 1,
757 }))
758 });
759
760 self.register("mul", 1, |args| {
761 let a = args[0].clone();
762 Ok(VMValue::Builtin(VMBuiltin {
763 name: "mul<partial>",
764 func: Rc::new(move |args2| vm_numeric_binop("mul", &a, &args2[0], |x, y| x * y, |x, y| x * y)),
765 arity: 1,
766 }))
767 });
768
769 self.register("div", 1, |args| {
770 let a = args[0].clone();
771 Ok(VMValue::Builtin(VMBuiltin {
772 name: "div<partial>",
773 func: Rc::new(move |args2| match (&a, &args2[0]) {
774 (VMValue::Int(_), VMValue::Int(0)) => Err(VMError::DivisionByZero),
775 (VMValue::Int(x), VMValue::Int(y)) => Ok(VMValue::Int(x / y)),
776 (VMValue::Float(x), VMValue::Float(y)) => Ok(VMValue::Float(x / y)),
777 (VMValue::Int(x), VMValue::Float(y)) => Ok(VMValue::Float(*x as f64 / *y)),
778 (VMValue::Float(x), VMValue::Int(y)) => Ok(VMValue::Float(*x / *y as f64)),
779 _ => Err(VMError::Throw("div: expected numbers".to_string())),
780 }),
781 arity: 1,
782 }))
783 });
784
785 self.register("ceil", 1, |args| {
786 let f = as_float(&args[0])?;
787 Ok(VMValue::Int(f.ceil() as i64))
788 });
789
790 self.register("floor", 1, |args| {
791 let f = as_float(&args[0])?;
792 Ok(VMValue::Int(f.floor() as i64))
793 });
794
795 self.register("bitAnd", 1, |args| {
796 let a = as_int(&args[0])?;
797 Ok(VMValue::Builtin(VMBuiltin {
798 name: "bitAnd<partial>",
799 func: Rc::new(move |args2| {
800 let b = as_int(&args2[0])?;
801 Ok(VMValue::Int(a & b))
802 }),
803 arity: 1,
804 }))
805 });
806
807 self.register("bitOr", 1, |args| {
808 let a = as_int(&args[0])?;
809 Ok(VMValue::Builtin(VMBuiltin {
810 name: "bitOr<partial>",
811 func: Rc::new(move |args2| {
812 let b = as_int(&args2[0])?;
813 Ok(VMValue::Int(a | b))
814 }),
815 arity: 1,
816 }))
817 });
818
819 self.register("bitXor", 1, |args| {
820 let a = as_int(&args[0])?;
821 Ok(VMValue::Builtin(VMBuiltin {
822 name: "bitXor<partial>",
823 func: Rc::new(move |args2| {
824 let b = as_int(&args2[0])?;
825 Ok(VMValue::Int(a ^ b))
826 }),
827 arity: 1,
828 }))
829 });
830 }
831
832 fn register_derivation_ops(&mut self) {
835 self.register("derivation", 1, |_args| {
840 Err(VMError::Throw(
841 "derivation: requires VM-level dispatch".to_string(),
842 ))
843 });
844 self.register("derivationStrict", 1, |_args| {
845 Err(VMError::Throw(
846 "derivationStrict: requires VM-level dispatch".to_string(),
847 ))
848 });
849 self.register("getFlake", 1, |_args| {
851 Err(VMError::Throw(
852 "getFlake: requires VM-level dispatch".to_string(),
853 ))
854 });
855 self.register("scopedImport", 1, |_args| {
857 Err(VMError::Throw(
858 "scopedImport: requires VM-level dispatch".to_string(),
859 ))
860 });
861
862 self.register("addErrorContext", 1, |args| {
866 Ok(VMValue::Builtin(VMBuiltin {
868 name: "addErrorContext<partial>",
869 func: Rc::new(move |inner_args: Vec<VMValue>| Ok(inner_args[0].clone())),
870 arity: 1,
871 }))
872 });
873
874 self.register("unsafeGetAttrPos", 1, |_args| {
876 Ok(VMValue::Builtin(VMBuiltin {
877 name: "unsafeGetAttrPos<partial>",
878 func: Rc::new(|_args: Vec<VMValue>| Ok(VMValue::Null)),
879 arity: 1,
880 }))
881 });
882
883 self.register("pathExists", 1, |args| {
885 let path = match &args[0] {
886 VMValue::Path(p) => p.clone(),
887 VMValue::String(s) => s.clone(),
888 other => {
889 return Err(VMError::TypeError {
890 expected: "path or string",
891 got: other.type_name(),
892 context: "pathExists".to_string(),
893 })
894 }
895 };
896 let read_path = crate::bridge::materialize(&path);
901 Ok(VMValue::Bool(std::path::Path::new(&read_path).exists()))
902 });
903
904 self.register("readFile", 1, |args| {
906 let path = match &args[0] {
907 VMValue::Path(p) => p.clone(),
908 VMValue::String(s) => s.clone(),
909 other => {
910 return Err(VMError::TypeError {
911 expected: "path or string",
912 got: other.type_name(),
913 context: "readFile".to_string(),
914 })
915 }
916 };
917 let read_path = crate::bridge::materialize(&path);
921 let content = std::fs::read_to_string(&read_path)
922 .map_err(|e| VMError::Throw(format!("readFile {path}: {e}")))?;
923 Ok(VMValue::String(content))
924 });
925
926 self.register("readDir", 1, |args| {
928 let path = match &args[0] {
929 VMValue::Path(p) => p.clone(),
930 VMValue::String(s) => s.clone(),
931 other => {
932 return Err(VMError::TypeError {
933 expected: "path or string",
934 got: other.type_name(),
935 context: "readDir".to_string(),
936 })
937 }
938 };
939 let _ = path;
945 Err(VMError::Throw(
946 "readDir: requires the tree-walker bridge (no interner access here)".to_string(),
947 ))
948 });
949
950 self.register("baseNameOf", 1, |args| {
952 let path = match &args[0] {
953 VMValue::Path(p) => p.clone(),
954 VMValue::String(s) => s.clone(),
955 other => {
956 return Err(VMError::TypeError {
957 expected: "path or string",
958 got: other.type_name(),
959 context: "baseNameOf".to_string(),
960 })
961 }
962 };
963 let base = std::path::Path::new(&path)
964 .file_name()
965 .map(|f| f.to_string_lossy().to_string())
966 .unwrap_or_default();
967 Ok(VMValue::String(base))
968 });
969
970 self.register("dirOf", 1, |args| {
972 let path = match &args[0] {
973 VMValue::Path(p) => p.clone(),
974 VMValue::String(s) => s.clone(),
975 other => {
976 return Err(VMError::TypeError {
977 expected: "path or string",
978 got: other.type_name(),
979 context: "dirOf".to_string(),
980 })
981 }
982 };
983 let dir = std::path::Path::new(&path)
984 .parent()
985 .map(|p| p.to_string_lossy().to_string())
986 .unwrap_or_else(|| ".".to_string());
987 Ok(VMValue::String(dir))
988 });
989
990 self.register("genericClosure", 1, |_args| {
992 Err(VMError::Throw(
993 "genericClosure: requires VM-level dispatch".to_string(),
994 ))
995 });
996
997 self.register("placeholder", 1, |args| {
999 let output = match &args[0] {
1000 VMValue::String(s) => s.clone(),
1001 _ => "out".to_string(),
1002 };
1003 Ok(VMValue::String(format!("/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9/{output}")))
1004 });
1005
1006 self.register("split", 1, |args| {
1008 let _pattern = as_string(&args[0])?;
1009 Ok(VMValue::Builtin(VMBuiltin {
1010 name: "split<partial>",
1011 func: Rc::new(|_inner_args: Vec<VMValue>| {
1012 Err(VMError::Throw("split: requires VM-level dispatch".to_string()))
1013 }),
1014 arity: 1,
1015 }))
1016 });
1017
1018 self.register("match", 1, |args| {
1020 let _pattern = as_string(&args[0])?;
1021 Ok(VMValue::Builtin(VMBuiltin {
1022 name: "match<partial>",
1023 func: Rc::new(|_inner_args: Vec<VMValue>| {
1024 Err(VMError::Throw("match: requires VM-level dispatch".to_string()))
1025 }),
1026 arity: 1,
1027 }))
1028 });
1029
1030 self.register("fromTOML", 1, |args| {
1032 let s = as_string(&args[0])?;
1033 Err(VMError::Throw(format!("fromTOML: not yet implemented")))
1035 });
1036
1037 self.register("fetchurl", 1, |_args| {
1046 Err(VMError::Throw("fetchurl: not supported in eval mode".to_string()))
1047 });
1048 self.register("fetchTarball", 1, |_args| {
1049 Err(VMError::Throw("fetchTarball: not supported in eval mode".to_string()))
1050 });
1051 self.register("fetchGit", 1, |_args| {
1052 Err(VMError::Throw("fetchGit: not supported in eval mode".to_string()))
1053 });
1054 self.register("fetchTree", 1, |_args| {
1055 Err(VMError::Throw("fetchTree: not supported in eval mode".to_string()))
1056 });
1057 self.register("fetchMercurial", 1, |_args| {
1058 Err(VMError::Throw("fetchMercurial: not supported in eval mode".to_string()))
1059 });
1060
1061 self.register("toFile", 1, |_args| {
1063 Err(VMError::Throw("toFile: not supported in eval mode".to_string()))
1064 });
1065
1066 self.register("toPath", 1, |args| {
1068 let s = as_string(&args[0])?;
1069 Ok(VMValue::Path(s.to_string()))
1070 });
1071
1072 self.register("parseDrvName", 1, |args| {
1077 let name = as_string(&args[0])?;
1078 let mut split_pos = None;
1080 let bytes = name.as_bytes();
1081 for i in (0..bytes.len()).rev() {
1082 if bytes[i] == b'-' && i + 1 < bytes.len() && bytes[i + 1].is_ascii_digit() {
1083 split_pos = Some(i);
1084 break;
1085 }
1086 }
1087 match split_pos {
1088 Some(pos) => {
1089 Err(VMError::Throw(
1090 "parseDrvName: requires VM-level dispatch for interner access".to_string(),
1091 ))
1092 }
1093 None => {
1094 Err(VMError::Throw(
1095 "parseDrvName: requires VM-level dispatch for interner access".to_string(),
1096 ))
1097 }
1098 }
1099 });
1100
1101 self.register("compareVersions", 1, |args| {
1106 let a = as_string(&args[0])?.to_string();
1107 Ok(VMValue::Builtin(VMBuiltin {
1108 name: "compareVersions<partial>",
1109 func: Rc::new(move |inner_args: Vec<VMValue>| {
1110 let b = as_string(&inner_args[0])?;
1111 Ok(VMValue::Int(
1112 sui_compat::versions::compare_versions(&a, b),
1113 ))
1114 }),
1115 arity: 1,
1116 }))
1117 });
1118
1119 self.register("splitVersion", 1, |args| {
1122 let version = as_string(&args[0])?;
1123 let parts: Vec<VMValue> = sui_compat::versions::split_version(version)
1124 .into_iter()
1125 .map(VMValue::String)
1126 .collect();
1127 Ok(VMValue::List(parts))
1128 });
1129
1130 self.register("unsafeDiscardStringContext", 1, |args| {
1142 Ok(args[0].clone())
1144 });
1145 self.register("getContext", 1, |_args| {
1146 Err(VMError::Throw(
1149 "getContext: requires VM-level dispatch for interner access".to_string(),
1150 ))
1151 });
1152 self.register("appendContext", 1, |args| {
1153 Ok(VMValue::Builtin(VMBuiltin {
1155 name: "appendContext<partial>",
1156 func: Rc::new(move |inner_args: Vec<VMValue>| Ok(inner_args[0].clone())),
1157 arity: 1,
1158 }))
1159 });
1160 self.register("hasContext", 1, |_args| {
1161 Ok(VMValue::Bool(false))
1162 });
1163 self.register("unsafeDiscardOutputDependency", 1, |args| {
1164 Ok(args[0].clone())
1165 });
1166 self.register("addDrvOutputDependencies", 1, |args| {
1167 Ok(args[0].clone())
1168 });
1169
1170 self.register("storePath", 1, |args| {
1172 Ok(args[0].clone())
1173 });
1174 self.register("isStorePath", 1, |args| {
1175 let s = match &args[0] {
1176 VMValue::String(s) => s.as_str(),
1177 VMValue::Path(p) => p.as_str(),
1178 _ => return Ok(VMValue::Bool(false)),
1179 };
1180 Ok(VMValue::Bool(s.starts_with("/nix/store/")))
1181 });
1182 self.register("hashString", 1, |args| {
1183 let algo = as_string(&args[0])?.to_string();
1184 Ok(VMValue::Builtin(VMBuiltin {
1185 name: "hashString<partial>",
1186 func: Rc::new(move |inner_args: Vec<VMValue>| {
1187 let s = as_string(&inner_args[0])?;
1188 match algo.as_str() {
1189 "sha256" => {
1190 use sha2::{Sha256, Digest};
1191 let mut hasher = Sha256::new();
1192 hasher.update(s.as_bytes());
1193 let result = hasher.finalize();
1194 let hex: String = result
1195 .iter()
1196 .map(|b| format!("{b:02x}"))
1197 .collect();
1198 Ok(VMValue::String(hex))
1199 }
1200 _ => Err(VMError::Throw(format!("hashString: unsupported algorithm: {algo}")))
1201 }
1202 }),
1203 arity: 1,
1204 }))
1205 });
1206 self.register("hashFile", 1, |_args| {
1207 Err(VMError::Throw("hashFile: not supported in eval mode".to_string()))
1208 });
1209
1210 self.register("import", 1, |_args| {
1215 Err(VMError::Throw(
1216 "import: requires VM-level dispatch".to_string(),
1217 ))
1218 });
1219
1220 self.register("zipAttrsWith", 1, |_args| {
1222 Err(VMError::Throw(
1223 "zipAttrsWith: requires VM-level dispatch".to_string(),
1224 ))
1225 });
1226 }
1227
1228 fn register_missing_builtins(&mut self) {
1235 self.register("getEnv", 1, |args| {
1239 let name = as_string(&args[0])?;
1240 let val = std::env::var(name).unwrap_or_default();
1241 Ok(VMValue::String(val))
1242 });
1243
1244 self.register("readFileType", 1, |args| {
1246 let path = match &args[0] {
1247 VMValue::Path(p) => p.clone(),
1248 VMValue::String(s) => s.clone(),
1249 other => {
1250 return Err(VMError::TypeError {
1251 expected: "path or string",
1252 got: other.type_name(),
1253 context: "readFileType".to_string(),
1254 });
1255 }
1256 };
1257 let read_path = crate::bridge::materialize(&path);
1259 match std::fs::symlink_metadata(&read_path) {
1260 Ok(meta) => {
1261 let kind = if meta.is_symlink() {
1262 "symlink"
1263 } else if meta.is_dir() {
1264 "directory"
1265 } else if meta.is_file() {
1266 "regular"
1267 } else {
1268 "unknown"
1269 };
1270 Ok(VMValue::String(kind.to_string()))
1271 }
1272 Err(e) => Err(VMError::Throw(format!("readFileType {path}: {e}"))),
1273 }
1274 });
1275
1276 self.register("findFile", 1, |args| {
1278 let search_path = as_list(&args[0])?.clone();
1279 Ok(VMValue::Builtin(VMBuiltin {
1280 name: "findFile<partial>",
1281 func: Rc::new(move |args2| {
1282 let name = as_string(&args2[0])?;
1283 for entry in &search_path {
1284 if let VMValue::Attrs(a) = entry {
1285 let _ = a;
1290 }
1291 }
1292 bridge_call("findFile", vec![
1294 VMValue::List(search_path.clone()),
1295 VMValue::String(name.to_string()),
1296 ])
1297 }),
1298 arity: 1,
1299 }))
1300 });
1301
1302 self.register("lessThan", 1, |args| {
1304 let a = args[0].clone();
1305 Ok(VMValue::Builtin(VMBuiltin {
1306 name: "lessThan<partial>",
1307 func: Rc::new(move |args2| match (&a, &args2[0]) {
1308 (VMValue::Int(x), VMValue::Int(y)) => Ok(VMValue::Bool(*x < *y)),
1309 (VMValue::Float(x), VMValue::Float(y)) => Ok(VMValue::Bool(*x < *y)),
1310 (VMValue::Int(x), VMValue::Float(y)) => {
1311 Ok(VMValue::Bool((*x as f64) < *y))
1312 }
1313 (VMValue::Float(x), VMValue::Int(y)) => {
1314 Ok(VMValue::Bool(*x < (*y as f64)))
1315 }
1316 (VMValue::String(x), VMValue::String(y)) => Ok(VMValue::Bool(*x < *y)),
1317 _ => Err(VMError::Throw(
1318 "lessThan: expected comparable types".to_string(),
1319 )),
1320 }),
1321 arity: 1,
1322 }))
1323 });
1324
1325 self.register("warn", 1, |args| {
1327 if let Ok(msg) = as_string(&args[0]) {
1328 eprintln!("evaluation warning: {msg}");
1329 }
1330 Ok(VMValue::Builtin(VMBuiltin {
1331 name: "warn<partial>",
1332 func: Rc::new(|args2| Ok(args2[0].clone())),
1333 arity: 1,
1334 }))
1335 });
1336
1337 self.register("traceVerbose", 1, |args| {
1339 if std::env::var("SUI_TRACE_VERBOSE").ok().as_deref() == Some("1") {
1340 eprintln!("trace: {}", args[0]);
1341 }
1342 Ok(VMValue::Builtin(VMBuiltin {
1343 name: "traceVerbose<partial>",
1344 func: Rc::new(|args2| Ok(args2[0].clone())),
1345 arity: 1,
1346 }))
1347 });
1348
1349 self.register("break", 1, |args| Ok(args[0].clone()));
1351
1352 for name in &["convertHash", "toXML", "toFile", "filterSource",
1380 "fetchClosure", "outputOf", "hashFile", "hashString",
1381 "path", "parseFlakeRef", "flakeRefToString"]
1382 {
1383 let n = (*name).to_string();
1384 self.register(name, 1, move |args| {
1385 bridge_call(&n, args.to_vec())
1386 });
1387 }
1388 }
1389}
1390
1391fn vm_numeric_binop(
1399 name: &'static str,
1400 a: &VMValue,
1401 b: &VMValue,
1402 int_op: impl Fn(i64, i64) -> i64,
1403 float_op: impl Fn(f64, f64) -> f64,
1404) -> Result<VMValue, VMError> {
1405 match (a, b) {
1406 (VMValue::Int(x), VMValue::Int(y)) => Ok(VMValue::Int(int_op(*x, *y))),
1407 (VMValue::Float(x), VMValue::Float(y)) => Ok(VMValue::Float(float_op(*x, *y))),
1408 (VMValue::Int(x), VMValue::Float(y)) => Ok(VMValue::Float(float_op(*x as f64, *y))),
1409 (VMValue::Float(x), VMValue::Int(y)) => Ok(VMValue::Float(float_op(*x, *y as f64))),
1410 _ => Err(VMError::Throw(format!("{name}: expected numbers"))),
1411 }
1412}
1413
1414fn bridge_call(name: &str, args: Vec<VMValue>) -> Result<VMValue, VMError> {
1415 use crate::intern::Interner;
1416 let tmp_interner = Interner::new();
1419 let sk_args: Vec<crate::value::StringKeyedValue> = args
1420 .iter()
1421 .map(|a| a.to_string_keyed(&tmp_interner))
1422 .collect();
1423
1424 let _ = crate::fallback::record(crate::fallback::Layer::Builtin, name);
1431
1432 match crate::bridge::call_builtin_bridge(name, sk_args) {
1433 Ok(Some(result)) => Ok(string_keyed_to_vmvalue(&result, &mut Interner::new())),
1434 Ok(None) => Err(VMError::Throw(format!(
1435 "builtin '{name}' requires bridge but no bridge is set"
1436 ))),
1437 Err(e) => Err(VMError::Throw(e)),
1438 }
1439}
1440
1441pub fn string_keyed_to_vmvalue(
1446 sk: &crate::value::StringKeyedValue,
1447 interner: &mut crate::intern::Interner,
1448) -> VMValue {
1449 use crate::value::StringKeyedValue;
1450 match sk {
1451 StringKeyedValue::Null => VMValue::Null,
1452 StringKeyedValue::Bool(b) => VMValue::Bool(*b),
1453 StringKeyedValue::Int(n) => VMValue::Int(*n),
1454 StringKeyedValue::Float(f) => VMValue::Float(*f),
1455 StringKeyedValue::String(s) => VMValue::String(s.clone()),
1456 StringKeyedValue::Path(p) => VMValue::Path(p.clone()),
1457 StringKeyedValue::List(items) => VMValue::List(
1458 items
1459 .iter()
1460 .map(|v| string_keyed_to_vmvalue(v, interner))
1461 .collect(),
1462 ),
1463 StringKeyedValue::Attrs(map) => {
1464 let mut attrs = BTreeMap::new();
1465 for (k, v) in map {
1466 let sym = interner.intern(k);
1467 attrs.insert(sym, string_keyed_to_vmvalue(v, interner));
1468 }
1469 VMValue::Attrs(attrs)
1470 }
1471 StringKeyedValue::Lambda => VMValue::Null,
1472 StringKeyedValue::Callable(cb) => {
1473 let cb_clone = Rc::clone(cb);
1474 VMValue::Builtin(crate::value::VMBuiltin {
1475 name: "<bridge-fn>",
1476 arity: 1,
1477 func: Rc::new(move |args: Vec<VMValue>| {
1478 let interner = crate::intern::Interner::new();
1479 let sk_arg = args.into_iter().next()
1480 .unwrap_or(VMValue::Null)
1481 .to_string_keyed(&interner);
1482 let sk_result = cb_clone(sk_arg)
1483 .map_err(|e| VMError::Throw(e))?;
1484 let mut tmp_interner = crate::intern::Interner::new();
1485 Ok(string_keyed_to_vmvalue(&sk_result, &mut tmp_interner))
1486 }),
1487 })
1488 }
1489 StringKeyedValue::Thunk(cb) => {
1490 let cb_clone = Rc::clone(cb);
1492 VMValue::Thunk(crate::value::VMThunk::new_native(move || {
1493 let sk_val = cb_clone().map_err(|e| VMError::Throw(e))?;
1494 let mut tmp = crate::intern::Interner::new();
1496 Ok(string_keyed_to_vmvalue(&sk_val, &mut tmp))
1497 }))
1498 }
1499 }
1500}
1501
1502impl Default for BuiltinRegistry {
1503 fn default() -> Self {
1504 Self::new()
1505 }
1506}
1507
1508fn try_unwrap_done_thunk(v: &VMValue) -> Option<Result<VMValue, VMError>> {
1515 match v {
1516 VMValue::Thunk(thunk) => {
1517 let state = thunk.state.take();
1518 match state {
1519 Some(ThunkState::Done(boxed)) => {
1520 let inner = *boxed.clone();
1521 thunk.state.set(Some(ThunkState::Done(boxed)));
1522 match &inner {
1524 VMValue::Thunk(_) => Some(try_unwrap_done_thunk(&inner)
1525 .unwrap_or(Ok(inner))),
1526 _ => Some(Ok(inner)),
1527 }
1528 }
1529 other => {
1530 thunk.state.set(other);
1531 Some(Err(VMError::TypeError {
1532 expected: "concrete value",
1533 got: "thunk (pending)",
1534 context: "builtin argument (thunk needs VM to force)".to_string(),
1535 }))
1536 }
1537 }
1538 }
1539 _ => None, }
1541}
1542
1543fn as_list(v: &VMValue) -> Result<Vec<VMValue>, VMError> {
1546 match v {
1547 VMValue::List(l) => Ok(l.clone()),
1548 VMValue::Thunk(_) => {
1549 let forced = force_vmvalue(v.clone())?;
1550 match forced {
1551 VMValue::List(l) => Ok(l),
1552 other => Err(VMError::TypeError {
1553 expected: "list",
1554 got: other.type_name(),
1555 context: "builtin argument".to_string(),
1556 }),
1557 }
1558 }
1559 other => Err(VMError::TypeError {
1560 expected: "list",
1561 got: other.type_name(),
1562 context: "builtin argument".to_string(),
1563 }),
1564 }
1565}
1566
1567fn force_vmvalue(v: VMValue) -> Result<VMValue, VMError> {
1571 match v {
1572 VMValue::Thunk(ref thunk) => {
1573 let state = thunk.state.take();
1574 match state {
1575 Some(ThunkState::Done(boxed)) => {
1576 let inner = *boxed.clone();
1577 thunk.state.set(Some(ThunkState::Done(boxed)));
1578 force_vmvalue(inner) }
1580 Some(ThunkState::NativeCallback(cb)) => {
1581 thunk.state.set(Some(ThunkState::Evaluating));
1582 match cb() {
1583 Ok(sk_val) => {
1584 let result = sk_to_vmvalue(&sk_val);
1586 thunk.state.set(Some(ThunkState::Done(Box::new(result.clone()))));
1587 force_vmvalue(result)
1588 }
1589 Err(e) => {
1590 thunk.state.set(Some(ThunkState::NativeCallback(cb)));
1591 Err(VMError::Throw(e))
1592 }
1593 }
1594 }
1595 other => {
1596 thunk.state.set(other);
1597 Err(VMError::TypeError {
1599 expected: "concrete value",
1600 got: "thunk (pending)",
1601 context: "builtin argument (thunk needs VM to force)".to_string(),
1602 })
1603 }
1604 }
1605 }
1606 other => Ok(other),
1607 }
1608}
1609
1610fn sk_to_vmvalue(sk: &crate::value::StringKeyedValue) -> VMValue {
1612 use crate::value::StringKeyedValue;
1613 match sk {
1614 StringKeyedValue::Null => VMValue::Null,
1615 StringKeyedValue::Bool(b) => VMValue::Bool(*b),
1616 StringKeyedValue::Int(n) => VMValue::Int(*n),
1617 StringKeyedValue::Float(f) => VMValue::Float(*f),
1618 StringKeyedValue::String(s) => VMValue::String(s.clone()),
1619 StringKeyedValue::Path(p) => VMValue::Path(p.clone()),
1620 StringKeyedValue::List(items) => {
1621 VMValue::List(items.iter().map(|i| sk_to_vmvalue(i)).collect())
1622 }
1623 StringKeyedValue::Attrs(map) => {
1624 let mut interner = crate::intern::Interner::new();
1626 VMValue::Attrs(map.iter().map(|(k, v)| {
1627 (interner.intern(k), sk_to_vmvalue(v))
1628 }).collect())
1629 }
1630 StringKeyedValue::Lambda => VMValue::Null, StringKeyedValue::Thunk(cb) => {
1632 let cb = cb.clone();
1634 VMValue::Thunk(crate::value::VMThunk {
1635 state: Rc::new(Cell::new(Some(ThunkState::NativeCallback(cb)))),
1636 })
1637 }
1638 StringKeyedValue::Callable(_) => VMValue::Null, }
1640}
1641
1642fn as_attrs(v: &VMValue) -> Result<&BTreeMap<Symbol, VMValue>, VMError> {
1643 match v {
1644 VMValue::Attrs(a) => Ok(a),
1645 VMValue::Thunk(_) => Err(VMError::TypeError {
1646 expected: "set",
1647 got: "thunk",
1648 context: "builtin argument".to_string(),
1649 }),
1650 other => Err(VMError::TypeError {
1651 expected: "set",
1652 got: other.type_name(),
1653 context: "builtin argument".to_string(),
1654 }),
1655 }
1656}
1657
1658fn as_string(v: &VMValue) -> Result<&str, VMError> {
1659 match v {
1660 VMValue::String(s) => Ok(s),
1661 other => Err(VMError::TypeError {
1662 expected: "string",
1663 got: other.type_name(),
1664 context: "builtin argument".to_string(),
1665 }),
1666 }
1667}
1668
1669fn force_as_string(v: &VMValue) -> Result<String, VMError> {
1672 match v {
1673 VMValue::String(s) => Ok(s.clone()),
1674 VMValue::Thunk(_) => {
1675 let forced = force_vmvalue(v.clone())?;
1676 match forced {
1677 VMValue::String(s) => Ok(s),
1678 other => Err(VMError::TypeError {
1679 expected: "string",
1680 got: other.type_name(),
1681 context: "builtin argument (after forcing thunk)".to_string(),
1682 }),
1683 }
1684 }
1685 other => Err(VMError::TypeError {
1686 expected: "string",
1687 got: other.type_name(),
1688 context: "builtin argument".to_string(),
1689 }),
1690 }
1691}
1692
1693fn force_as_list(v: &VMValue) -> Result<Vec<VMValue>, VMError> {
1695 match v {
1696 VMValue::List(l) => Ok(l.clone()),
1697 VMValue::Thunk(_) => {
1698 let forced = force_vmvalue(v.clone())?;
1699 match forced {
1700 VMValue::List(l) => Ok(l),
1701 other => Err(VMError::TypeError {
1702 expected: "list",
1703 got: other.type_name(),
1704 context: "builtin argument (after forcing thunk)".to_string(),
1705 }),
1706 }
1707 }
1708 other => Err(VMError::TypeError {
1709 expected: "list",
1710 got: other.type_name(),
1711 context: "builtin argument".to_string(),
1712 }),
1713 }
1714}
1715
1716fn as_int(v: &VMValue) -> Result<i64, VMError> {
1717 match v {
1718 VMValue::Int(n) => Ok(*n),
1719 other => Err(VMError::TypeError {
1720 expected: "int",
1721 got: other.type_name(),
1722 context: "builtin argument".to_string(),
1723 }),
1724 }
1725}
1726
1727fn as_float(v: &VMValue) -> Result<f64, VMError> {
1728 match v {
1729 VMValue::Float(f) => Ok(*f),
1730 VMValue::Int(n) => Ok(*n as f64),
1731 other => Err(VMError::TypeError {
1732 expected: "float",
1733 got: other.type_name(),
1734 context: "builtin argument".to_string(),
1735 }),
1736 }
1737}
1738
1739fn vm_coerce_to_string(v: &VMValue) -> Result<VMValue, VMError> {
1746 match v {
1747 VMValue::String(s) => Ok(VMValue::String(s.clone())),
1748 VMValue::Int(n) => Ok(VMValue::String(n.to_string())),
1749 VMValue::Float(f) => Ok(VMValue::String(format!("{f:.6}"))),
1751 VMValue::Bool(true) => Ok(VMValue::String("1".to_string())),
1752 VMValue::Bool(false) => Ok(VMValue::String(String::new())),
1753 VMValue::Null => Ok(VMValue::String(String::new())),
1754 VMValue::Path(p) => Ok(VMValue::String(p.clone())),
1755 VMValue::Attrs(attrs) => {
1756 let to_str_sym = crate::intern::intern("__toString");
1759 if attrs.contains_key(&to_str_sym) {
1760 return Err(VMError::Throw(
1764 "toString: __toString requires VM bridge".to_string(),
1765 ));
1766 }
1767 let out_path_sym = crate::intern::intern("outPath");
1768 if let Some(out_path) = attrs.get(&out_path_sym) {
1769 vm_coerce_to_string(out_path)
1770 } else {
1771 Err(VMError::Throw(
1772 "cannot coerce a set to a string, but it has no __toString or outPath".to_string(),
1773 ))
1774 }
1775 }
1776 VMValue::List(items) => {
1777 let mut parts = Vec::with_capacity(items.len());
1778 for item in items {
1779 match vm_coerce_to_string(item)? {
1780 VMValue::String(s) => parts.push(s),
1781 _ => unreachable!("vm_coerce_to_string always returns String"),
1782 }
1783 }
1784 Ok(VMValue::String(parts.join(" ")))
1785 }
1786 VMValue::Closure(_) | VMValue::Builtin(_) | VMValue::HigherOrderBuiltin(_) => {
1787 Err(VMError::Throw(
1788 "cannot coerce a function to a string".to_string(),
1789 ))
1790 }
1791 VMValue::Thunk(_) => {
1792 Err(VMError::Throw(
1793 "toString: thunk should be forced first".to_string(),
1794 ))
1795 }
1796 }
1797}
1798
1799fn vm_value_to_json(v: &VMValue) -> Result<serde_json::Value, VMError> {
1801 match v {
1802 VMValue::Null => Ok(serde_json::Value::Null),
1803 VMValue::Bool(b) => Ok(serde_json::Value::Bool(*b)),
1804 VMValue::Int(n) => Ok(serde_json::Value::Number(
1805 serde_json::Number::from(*n),
1806 )),
1807 VMValue::Float(f) => serde_json::Number::from_f64(*f)
1808 .map(serde_json::Value::Number)
1809 .ok_or_else(|| VMError::Throw("toJSON: invalid float".to_string())),
1810 VMValue::String(s) => Ok(serde_json::Value::String(s.clone())),
1811 VMValue::Path(p) => Ok(serde_json::Value::String(p.clone())),
1812 VMValue::List(items) => {
1813 let arr: Result<Vec<_>, _> = items.iter().map(vm_value_to_json).collect();
1814 Ok(serde_json::Value::Array(arr?))
1815 }
1816 VMValue::Attrs(_) => {
1817 Err(VMError::Throw(
1819 "toJSON: attrset conversion requires interner".to_string(),
1820 ))
1821 }
1822 VMValue::Closure(_) | VMValue::Builtin(_) | VMValue::HigherOrderBuiltin(_) => {
1823 Err(VMError::Throw("toJSON: cannot convert function".to_string()))
1824 }
1825 VMValue::Thunk(_) => {
1826 Err(VMError::Throw("toJSON: thunk should be forced first".to_string()))
1827 }
1828 }
1829}
1830
1831fn json_to_vm_value(v: &serde_json::Value) -> VMValue {
1833 match v {
1834 serde_json::Value::Null => VMValue::Null,
1835 serde_json::Value::Bool(b) => VMValue::Bool(*b),
1836 serde_json::Value::Number(n) => {
1837 if let Some(i) = n.as_i64() {
1838 VMValue::Int(i)
1839 } else {
1840 VMValue::Float(n.as_f64().unwrap_or(0.0))
1841 }
1842 }
1843 serde_json::Value::String(s) => VMValue::String(s.clone()),
1844 serde_json::Value::Array(arr) => {
1845 VMValue::List(arr.iter().map(json_to_vm_value).collect())
1846 }
1847 serde_json::Value::Object(_) => {
1848 VMValue::Null
1851 }
1852 }
1853}
1854
1855#[cfg(test)]
1856mod tests {
1857 use super::*;
1858
1859 #[test]
1860 fn registry_has_builtins() {
1861 let reg = BuiltinRegistry::new();
1862 assert!(reg.lookup("length").is_some());
1863 assert!(reg.lookup("typeOf").is_some());
1864 assert!(reg.lookup("head").is_some());
1865 assert!(reg.lookup("tail").is_some());
1866 assert!(reg.lookup("throw").is_some());
1867 assert!(reg.lookup("nonexistent").is_none());
1868 }
1869
1870 #[test]
1871 fn call_length() {
1872 let reg = BuiltinRegistry::new();
1873 let idx = reg.lookup("length").unwrap();
1874 let result = reg
1875 .call(idx, vec![VMValue::List(vec![VMValue::Int(1), VMValue::Int(2)])])
1876 .unwrap();
1877 assert_eq!(result, VMValue::Int(2));
1878 }
1879
1880 #[test]
1881 fn call_head() {
1882 let reg = BuiltinRegistry::new();
1883 let idx = reg.lookup("head").unwrap();
1884 let result = reg
1885 .call(idx, vec![VMValue::List(vec![VMValue::Int(10)])])
1886 .unwrap();
1887 assert_eq!(result, VMValue::Int(10));
1888 }
1889
1890 #[test]
1891 fn call_head_empty() {
1892 let reg = BuiltinRegistry::new();
1893 let idx = reg.lookup("head").unwrap();
1894 let result = reg.call(idx, vec![VMValue::List(vec![])]);
1895 assert!(result.is_err());
1896 }
1897
1898 #[test]
1899 fn call_type_of() {
1900 let reg = BuiltinRegistry::new();
1901 let idx = reg.lookup("typeOf").unwrap();
1902 assert_eq!(
1903 reg.call(idx, vec![VMValue::Int(42)]).unwrap(),
1904 VMValue::String("int".to_string())
1905 );
1906 assert_eq!(
1907 reg.call(idx, vec![VMValue::String("hello".to_string())])
1908 .unwrap(),
1909 VMValue::String("string".to_string())
1910 );
1911 }
1912
1913 #[test]
1914 fn call_string_length() {
1915 let reg = BuiltinRegistry::new();
1916 let idx = reg.lookup("stringLength").unwrap();
1917 let result = reg
1918 .call(idx, vec![VMValue::String("hello".to_string())])
1919 .unwrap();
1920 assert_eq!(result, VMValue::Int(5));
1921 }
1922
1923 #[test]
1924 fn call_throw() {
1925 let reg = BuiltinRegistry::new();
1926 let idx = reg.lookup("throw").unwrap();
1927 let result = reg.call(idx, vec![VMValue::String("test error".to_string())]);
1928 assert!(matches!(result, Err(VMError::Throw(_))));
1929 }
1930
1931 #[test]
1932 fn call_to_string() {
1933 let reg = BuiltinRegistry::new();
1934 let idx = reg.lookup("toString").unwrap();
1935 assert_eq!(
1936 reg.call(idx, vec![VMValue::Int(42)]).unwrap(),
1937 VMValue::String("42".to_string())
1938 );
1939 assert_eq!(
1940 reg.call(idx, vec![VMValue::Bool(true)]).unwrap(),
1941 VMValue::String("1".to_string())
1942 );
1943 }
1944
1945 #[test]
1946 fn builtins_attrset() {
1947 let reg = BuiltinRegistry::new();
1948 let mut interner = Interner::new();
1949 let builtins = reg.make_builtins_attrset(&mut interner);
1950 match &builtins {
1951 VMValue::Attrs(attrs) => {
1952 let length_sym = interner.lookup("length").unwrap();
1953 assert!(attrs.contains_key(&length_sym));
1954 }
1955 _ => panic!("expected Attrs"),
1956 }
1957 }
1958}