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 = sui_compat::versions::nix_json_to_string(&json)
655 .unwrap_or_else(|_| "null".to_string());
656 Ok(VMValue::String(s))
657 });
658
659 self.register("fromJSON", 1, |args| {
660 let s = as_string(&args[0])?;
661 let json: serde_json::Value = serde_json::from_str(s).map_err(|e| {
662 VMError::Throw(format!("fromJSON: {e}"))
663 })?;
664 Ok(json_to_vm_value(&json))
665 });
666
667 self.register("toInt", 1, |args| {
668 let s = as_string(&args[0])?;
669 let n: i64 = s.trim().parse().map_err(|e| {
670 VMError::Throw(format!("toInt: {e}"))
671 })?;
672 Ok(VMValue::Int(n))
673 });
674 }
675
676 fn register_control_ops(&mut self) {
679 self.register("throw", 1, |args| {
680 let msg = as_string(&args[0])?;
681 Err(VMError::Throw(format!("throw: {msg}")))
682 });
683
684 self.register("abort", 1, |args| {
685 let msg = as_string(&args[0])?;
686 Err(VMError::Throw(format!("abort: {msg}")))
687 });
688
689 self.register("seq", 1, |args| {
690 let _forced = args[0].clone();
691 Ok(VMValue::Builtin(VMBuiltin {
692 name: "seq<partial>",
693 func: Rc::new(|args2| Ok(args2[0].clone())),
694 arity: 1,
695 }))
696 });
697
698 self.register("deepSeq", 1, |args| {
699 let _forced = args[0].clone();
700 Ok(VMValue::Builtin(VMBuiltin {
701 name: "deepSeq<partial>",
702 func: Rc::new(|args2| Ok(args2[0].clone())),
703 arity: 1,
704 }))
705 });
706
707 self.register("tryEval", 1, |args| {
708 let val = args[0].clone();
711 let _ = val;
715 Err(VMError::Throw(
716 "tryEval: requires VM-level implementation".to_string(),
717 ))
718 });
719
720 self.register("trace", 1, |args| {
721 let msg = args[0].clone();
722 eprintln!("trace: {msg}");
723 Ok(VMValue::Builtin(VMBuiltin {
724 name: "trace<partial>",
725 func: Rc::new(|args2| Ok(args2[0].clone())),
726 arity: 1,
727 }))
728 });
729 }
730
731 fn register_arithmetic_ops(&mut self) {
734 self.register("add", 1, |args| {
735 let a = args[0].clone();
736 Ok(VMValue::Builtin(VMBuiltin {
737 name: "add<partial>",
738 func: Rc::new(move |args2| match (&a, &args2[0]) {
739 (VMValue::Int(x), VMValue::Int(y)) => Ok(VMValue::Int(x + y)),
740 (VMValue::Float(x), VMValue::Float(y)) => Ok(VMValue::Float(x + y)),
741 (VMValue::Int(x), VMValue::Float(y)) => Ok(VMValue::Float(*x as f64 + y)),
742 (VMValue::Float(x), VMValue::Int(y)) => Ok(VMValue::Float(x + *y as f64)),
743 _ => Err(VMError::Throw("add: expected numbers".to_string())),
744 }),
745 arity: 1,
746 }))
747 });
748
749 self.register("sub", 1, |args| {
753 let a = args[0].clone();
754 Ok(VMValue::Builtin(VMBuiltin {
755 name: "sub<partial>",
756 func: Rc::new(move |args2| vm_numeric_binop("sub", &a, &args2[0], |x, y| x - y, |x, y| x - y)),
757 arity: 1,
758 }))
759 });
760
761 self.register("mul", 1, |args| {
762 let a = args[0].clone();
763 Ok(VMValue::Builtin(VMBuiltin {
764 name: "mul<partial>",
765 func: Rc::new(move |args2| vm_numeric_binop("mul", &a, &args2[0], |x, y| x * y, |x, y| x * y)),
766 arity: 1,
767 }))
768 });
769
770 self.register("div", 1, |args| {
771 let a = args[0].clone();
772 Ok(VMValue::Builtin(VMBuiltin {
773 name: "div<partial>",
774 func: Rc::new(move |args2| match (&a, &args2[0]) {
775 (VMValue::Int(_), VMValue::Int(0)) => Err(VMError::DivisionByZero),
776 (VMValue::Int(x), VMValue::Int(y)) => Ok(VMValue::Int(x / y)),
777 (VMValue::Float(x), VMValue::Float(y)) => Ok(VMValue::Float(x / y)),
778 (VMValue::Int(x), VMValue::Float(y)) => Ok(VMValue::Float(*x as f64 / *y)),
779 (VMValue::Float(x), VMValue::Int(y)) => Ok(VMValue::Float(*x / *y as f64)),
780 _ => Err(VMError::Throw("div: expected numbers".to_string())),
781 }),
782 arity: 1,
783 }))
784 });
785
786 self.register("ceil", 1, |args| {
787 let f = as_float(&args[0])?;
788 Ok(VMValue::Int(f.ceil() as i64))
789 });
790
791 self.register("floor", 1, |args| {
792 let f = as_float(&args[0])?;
793 Ok(VMValue::Int(f.floor() as i64))
794 });
795
796 self.register("bitAnd", 1, |args| {
797 let a = as_int(&args[0])?;
798 Ok(VMValue::Builtin(VMBuiltin {
799 name: "bitAnd<partial>",
800 func: Rc::new(move |args2| {
801 let b = as_int(&args2[0])?;
802 Ok(VMValue::Int(a & b))
803 }),
804 arity: 1,
805 }))
806 });
807
808 self.register("bitOr", 1, |args| {
809 let a = as_int(&args[0])?;
810 Ok(VMValue::Builtin(VMBuiltin {
811 name: "bitOr<partial>",
812 func: Rc::new(move |args2| {
813 let b = as_int(&args2[0])?;
814 Ok(VMValue::Int(a | b))
815 }),
816 arity: 1,
817 }))
818 });
819
820 self.register("bitXor", 1, |args| {
821 let a = as_int(&args[0])?;
822 Ok(VMValue::Builtin(VMBuiltin {
823 name: "bitXor<partial>",
824 func: Rc::new(move |args2| {
825 let b = as_int(&args2[0])?;
826 Ok(VMValue::Int(a ^ b))
827 }),
828 arity: 1,
829 }))
830 });
831 }
832
833 fn register_derivation_ops(&mut self) {
836 self.register("derivation", 1, |_args| {
841 Err(VMError::Throw(
842 "derivation: requires VM-level dispatch".to_string(),
843 ))
844 });
845 self.register("derivationStrict", 1, |_args| {
846 Err(VMError::Throw(
847 "derivationStrict: requires VM-level dispatch".to_string(),
848 ))
849 });
850 self.register("getFlake", 1, |_args| {
852 Err(VMError::Throw(
853 "getFlake: requires VM-level dispatch".to_string(),
854 ))
855 });
856 self.register("scopedImport", 1, |_args| {
858 Err(VMError::Throw(
859 "scopedImport: requires VM-level dispatch".to_string(),
860 ))
861 });
862
863 self.register("addErrorContext", 1, |args| {
867 Ok(VMValue::Builtin(VMBuiltin {
869 name: "addErrorContext<partial>",
870 func: Rc::new(move |inner_args: Vec<VMValue>| Ok(inner_args[0].clone())),
871 arity: 1,
872 }))
873 });
874
875 self.register("unsafeGetAttrPos", 1, |_args| {
877 Ok(VMValue::Builtin(VMBuiltin {
878 name: "unsafeGetAttrPos<partial>",
879 func: Rc::new(|_args: Vec<VMValue>| Ok(VMValue::Null)),
880 arity: 1,
881 }))
882 });
883
884 self.register("pathExists", 1, |args| {
886 let path = match &args[0] {
887 VMValue::Path(p) => p.clone(),
888 VMValue::String(s) => s.clone(),
889 other => {
890 return Err(VMError::TypeError {
891 expected: "path or string",
892 got: other.type_name(),
893 context: "pathExists".to_string(),
894 })
895 }
896 };
897 let read_path = crate::bridge::materialize(&path);
902 Ok(VMValue::Bool(std::path::Path::new(&read_path).exists()))
903 });
904
905 self.register("readFile", 1, |args| {
907 let path = match &args[0] {
908 VMValue::Path(p) => p.clone(),
909 VMValue::String(s) => s.clone(),
910 other => {
911 return Err(VMError::TypeError {
912 expected: "path or string",
913 got: other.type_name(),
914 context: "readFile".to_string(),
915 })
916 }
917 };
918 let read_path = crate::bridge::materialize(&path);
922 let content = std::fs::read_to_string(&read_path)
923 .map_err(|e| VMError::Throw(format!("readFile {path}: {e}")))?;
924 Ok(VMValue::String(content))
925 });
926
927 self.register("readDir", 1, |args| {
929 let path = match &args[0] {
930 VMValue::Path(p) => p.clone(),
931 VMValue::String(s) => s.clone(),
932 other => {
933 return Err(VMError::TypeError {
934 expected: "path or string",
935 got: other.type_name(),
936 context: "readDir".to_string(),
937 })
938 }
939 };
940 let _ = path;
946 Err(VMError::Throw(
947 "readDir: requires the tree-walker bridge (no interner access here)".to_string(),
948 ))
949 });
950
951 self.register("baseNameOf", 1, |args| {
953 let path = match &args[0] {
954 VMValue::Path(p) => p.clone(),
955 VMValue::String(s) => s.clone(),
956 other => {
957 return Err(VMError::TypeError {
958 expected: "path or string",
959 got: other.type_name(),
960 context: "baseNameOf".to_string(),
961 })
962 }
963 };
964 let base = std::path::Path::new(&path)
965 .file_name()
966 .map(|f| f.to_string_lossy().to_string())
967 .unwrap_or_default();
968 Ok(VMValue::String(base))
969 });
970
971 self.register("dirOf", 1, |args| {
973 let path = match &args[0] {
974 VMValue::Path(p) => p.clone(),
975 VMValue::String(s) => s.clone(),
976 other => {
977 return Err(VMError::TypeError {
978 expected: "path or string",
979 got: other.type_name(),
980 context: "dirOf".to_string(),
981 })
982 }
983 };
984 let dir = std::path::Path::new(&path)
985 .parent()
986 .map(|p| p.to_string_lossy().to_string())
987 .unwrap_or_else(|| ".".to_string());
988 Ok(VMValue::String(dir))
989 });
990
991 self.register("genericClosure", 1, |_args| {
993 Err(VMError::Throw(
994 "genericClosure: requires VM-level dispatch".to_string(),
995 ))
996 });
997
998 self.register("placeholder", 1, |args| {
1000 let output = match &args[0] {
1001 VMValue::String(s) => s.clone(),
1002 _ => "out".to_string(),
1003 };
1004 Ok(VMValue::String(format!("/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9/{output}")))
1005 });
1006
1007 self.register("split", 1, |args| {
1009 let _pattern = as_string(&args[0])?;
1010 Ok(VMValue::Builtin(VMBuiltin {
1011 name: "split<partial>",
1012 func: Rc::new(|_inner_args: Vec<VMValue>| {
1013 Err(VMError::Throw("split: requires VM-level dispatch".to_string()))
1014 }),
1015 arity: 1,
1016 }))
1017 });
1018
1019 self.register("match", 1, |args| {
1021 let _pattern = as_string(&args[0])?;
1022 Ok(VMValue::Builtin(VMBuiltin {
1023 name: "match<partial>",
1024 func: Rc::new(|_inner_args: Vec<VMValue>| {
1025 Err(VMError::Throw("match: requires VM-level dispatch".to_string()))
1026 }),
1027 arity: 1,
1028 }))
1029 });
1030
1031 self.register("fromTOML", 1, |args| {
1033 let s = as_string(&args[0])?;
1034 Err(VMError::Throw(format!("fromTOML: not yet implemented")))
1036 });
1037
1038 self.register("fetchurl", 1, |_args| {
1047 Err(VMError::Throw("fetchurl: not supported in eval mode".to_string()))
1048 });
1049 self.register("fetchTarball", 1, |_args| {
1050 Err(VMError::Throw("fetchTarball: not supported in eval mode".to_string()))
1051 });
1052 self.register("fetchGit", 1, |_args| {
1053 Err(VMError::Throw("fetchGit: not supported in eval mode".to_string()))
1054 });
1055 self.register("fetchTree", 1, |_args| {
1056 Err(VMError::Throw("fetchTree: not supported in eval mode".to_string()))
1057 });
1058 self.register("fetchMercurial", 1, |_args| {
1059 Err(VMError::Throw("fetchMercurial: not supported in eval mode".to_string()))
1060 });
1061
1062 self.register("toFile", 1, |_args| {
1064 Err(VMError::Throw("toFile: not supported in eval mode".to_string()))
1065 });
1066
1067 self.register("toPath", 1, |args| {
1069 let s = as_string(&args[0])?;
1070 Ok(VMValue::Path(s.to_string()))
1071 });
1072
1073 self.register("parseDrvName", 1, |args| {
1078 let name = as_string(&args[0])?;
1079 let mut split_pos = None;
1081 let bytes = name.as_bytes();
1082 for i in (0..bytes.len()).rev() {
1083 if bytes[i] == b'-' && i + 1 < bytes.len() && bytes[i + 1].is_ascii_digit() {
1084 split_pos = Some(i);
1085 break;
1086 }
1087 }
1088 match split_pos {
1089 Some(pos) => {
1090 Err(VMError::Throw(
1091 "parseDrvName: requires VM-level dispatch for interner access".to_string(),
1092 ))
1093 }
1094 None => {
1095 Err(VMError::Throw(
1096 "parseDrvName: requires VM-level dispatch for interner access".to_string(),
1097 ))
1098 }
1099 }
1100 });
1101
1102 self.register("compareVersions", 1, |args| {
1107 let a = as_string(&args[0])?.to_string();
1108 Ok(VMValue::Builtin(VMBuiltin {
1109 name: "compareVersions<partial>",
1110 func: Rc::new(move |inner_args: Vec<VMValue>| {
1111 let b = as_string(&inner_args[0])?;
1112 Ok(VMValue::Int(
1113 sui_compat::versions::compare_versions(&a, b),
1114 ))
1115 }),
1116 arity: 1,
1117 }))
1118 });
1119
1120 self.register("splitVersion", 1, |args| {
1123 let version = as_string(&args[0])?;
1124 let parts: Vec<VMValue> = sui_compat::versions::split_version(version)
1125 .into_iter()
1126 .map(VMValue::String)
1127 .collect();
1128 Ok(VMValue::List(parts))
1129 });
1130
1131 self.register("unsafeDiscardStringContext", 1, |args| {
1143 Ok(args[0].clone())
1145 });
1146 self.register("getContext", 1, |_args| {
1147 Err(VMError::Throw(
1150 "getContext: requires VM-level dispatch for interner access".to_string(),
1151 ))
1152 });
1153 self.register("appendContext", 1, |args| {
1154 Ok(VMValue::Builtin(VMBuiltin {
1156 name: "appendContext<partial>",
1157 func: Rc::new(move |inner_args: Vec<VMValue>| Ok(inner_args[0].clone())),
1158 arity: 1,
1159 }))
1160 });
1161 self.register("hasContext", 1, |_args| {
1162 Ok(VMValue::Bool(false))
1163 });
1164 self.register("unsafeDiscardOutputDependency", 1, |args| {
1165 Ok(args[0].clone())
1166 });
1167 self.register("addDrvOutputDependencies", 1, |args| {
1168 Ok(args[0].clone())
1169 });
1170
1171 self.register("storePath", 1, |args| {
1173 Ok(args[0].clone())
1174 });
1175 self.register("isStorePath", 1, |args| {
1176 let s = match &args[0] {
1177 VMValue::String(s) => s.as_str(),
1178 VMValue::Path(p) => p.as_str(),
1179 _ => return Ok(VMValue::Bool(false)),
1180 };
1181 Ok(VMValue::Bool(s.starts_with("/nix/store/")))
1182 });
1183 self.register("hashString", 1, |args| {
1184 let algo = as_string(&args[0])?.to_string();
1185 Ok(VMValue::Builtin(VMBuiltin {
1186 name: "hashString<partial>",
1187 func: Rc::new(move |inner_args: Vec<VMValue>| {
1188 let s = as_string(&inner_args[0])?;
1189 match algo.as_str() {
1190 "sha256" => {
1191 use sha2::{Sha256, Digest};
1192 let mut hasher = Sha256::new();
1193 hasher.update(s.as_bytes());
1194 let result = hasher.finalize();
1195 let hex: String = result
1196 .iter()
1197 .map(|b| format!("{b:02x}"))
1198 .collect();
1199 Ok(VMValue::String(hex))
1200 }
1201 _ => Err(VMError::Throw(format!("hashString: unsupported algorithm: {algo}")))
1202 }
1203 }),
1204 arity: 1,
1205 }))
1206 });
1207 self.register("hashFile", 1, |_args| {
1208 Err(VMError::Throw("hashFile: not supported in eval mode".to_string()))
1209 });
1210
1211 self.register("import", 1, |_args| {
1216 Err(VMError::Throw(
1217 "import: requires VM-level dispatch".to_string(),
1218 ))
1219 });
1220
1221 self.register("zipAttrsWith", 1, |_args| {
1223 Err(VMError::Throw(
1224 "zipAttrsWith: requires VM-level dispatch".to_string(),
1225 ))
1226 });
1227 }
1228
1229 fn register_missing_builtins(&mut self) {
1236 self.register("getEnv", 1, |args| {
1240 let name = as_string(&args[0])?;
1241 let val = std::env::var(name).unwrap_or_default();
1242 Ok(VMValue::String(val))
1243 });
1244
1245 self.register("readFileType", 1, |args| {
1247 let path = match &args[0] {
1248 VMValue::Path(p) => p.clone(),
1249 VMValue::String(s) => s.clone(),
1250 other => {
1251 return Err(VMError::TypeError {
1252 expected: "path or string",
1253 got: other.type_name(),
1254 context: "readFileType".to_string(),
1255 });
1256 }
1257 };
1258 let read_path = crate::bridge::materialize(&path);
1260 match std::fs::symlink_metadata(&read_path) {
1261 Ok(meta) => {
1262 let kind = if meta.is_symlink() {
1263 "symlink"
1264 } else if meta.is_dir() {
1265 "directory"
1266 } else if meta.is_file() {
1267 "regular"
1268 } else {
1269 "unknown"
1270 };
1271 Ok(VMValue::String(kind.to_string()))
1272 }
1273 Err(e) => Err(VMError::Throw(format!("readFileType {path}: {e}"))),
1274 }
1275 });
1276
1277 self.register("findFile", 1, |args| {
1279 let search_path = as_list(&args[0])?.clone();
1280 Ok(VMValue::Builtin(VMBuiltin {
1281 name: "findFile<partial>",
1282 func: Rc::new(move |args2| {
1283 let name = as_string(&args2[0])?;
1284 for entry in &search_path {
1285 if let VMValue::Attrs(a) = entry {
1286 let _ = a;
1291 }
1292 }
1293 bridge_call("findFile", vec![
1295 VMValue::List(search_path.clone()),
1296 VMValue::String(name.to_string()),
1297 ])
1298 }),
1299 arity: 1,
1300 }))
1301 });
1302
1303 self.register("lessThan", 1, |args| {
1305 let a = args[0].clone();
1306 Ok(VMValue::Builtin(VMBuiltin {
1307 name: "lessThan<partial>",
1308 func: Rc::new(move |args2| match (&a, &args2[0]) {
1309 (VMValue::Int(x), VMValue::Int(y)) => Ok(VMValue::Bool(*x < *y)),
1310 (VMValue::Float(x), VMValue::Float(y)) => Ok(VMValue::Bool(*x < *y)),
1311 (VMValue::Int(x), VMValue::Float(y)) => {
1312 Ok(VMValue::Bool((*x as f64) < *y))
1313 }
1314 (VMValue::Float(x), VMValue::Int(y)) => {
1315 Ok(VMValue::Bool(*x < (*y as f64)))
1316 }
1317 (VMValue::String(x), VMValue::String(y)) => Ok(VMValue::Bool(*x < *y)),
1318 _ => Err(VMError::Throw(
1319 "lessThan: expected comparable types".to_string(),
1320 )),
1321 }),
1322 arity: 1,
1323 }))
1324 });
1325
1326 self.register("warn", 1, |args| {
1328 if let Ok(msg) = as_string(&args[0]) {
1329 eprintln!("evaluation warning: {msg}");
1330 }
1331 Ok(VMValue::Builtin(VMBuiltin {
1332 name: "warn<partial>",
1333 func: Rc::new(|args2| Ok(args2[0].clone())),
1334 arity: 1,
1335 }))
1336 });
1337
1338 self.register("traceVerbose", 1, |args| {
1340 if std::env::var("SUI_TRACE_VERBOSE").ok().as_deref() == Some("1") {
1341 eprintln!("trace: {}", args[0]);
1342 }
1343 Ok(VMValue::Builtin(VMBuiltin {
1344 name: "traceVerbose<partial>",
1345 func: Rc::new(|args2| Ok(args2[0].clone())),
1346 arity: 1,
1347 }))
1348 });
1349
1350 self.register("break", 1, |args| Ok(args[0].clone()));
1352
1353 for name in &["convertHash", "toXML", "toFile", "filterSource",
1381 "fetchClosure", "outputOf", "hashFile", "hashString",
1382 "path", "parseFlakeRef", "flakeRefToString"]
1383 {
1384 let n = (*name).to_string();
1385 self.register(name, 1, move |args| {
1386 bridge_call(&n, args.to_vec())
1387 });
1388 }
1389 }
1390}
1391
1392fn vm_numeric_binop(
1400 name: &'static str,
1401 a: &VMValue,
1402 b: &VMValue,
1403 int_op: impl Fn(i64, i64) -> i64,
1404 float_op: impl Fn(f64, f64) -> f64,
1405) -> Result<VMValue, VMError> {
1406 match (a, b) {
1407 (VMValue::Int(x), VMValue::Int(y)) => Ok(VMValue::Int(int_op(*x, *y))),
1408 (VMValue::Float(x), VMValue::Float(y)) => Ok(VMValue::Float(float_op(*x, *y))),
1409 (VMValue::Int(x), VMValue::Float(y)) => Ok(VMValue::Float(float_op(*x as f64, *y))),
1410 (VMValue::Float(x), VMValue::Int(y)) => Ok(VMValue::Float(float_op(*x, *y as f64))),
1411 _ => Err(VMError::Throw(format!("{name}: expected numbers"))),
1412 }
1413}
1414
1415fn bridge_call(name: &str, args: Vec<VMValue>) -> Result<VMValue, VMError> {
1416 use crate::intern::Interner;
1417 let tmp_interner = Interner::new();
1420 let sk_args: Vec<crate::value::StringKeyedValue> = args
1421 .iter()
1422 .map(|a| a.to_string_keyed(&tmp_interner))
1423 .collect();
1424
1425 let _ = crate::fallback::record(crate::fallback::Layer::Builtin, name);
1432
1433 match crate::bridge::call_builtin_bridge(name, sk_args) {
1434 Ok(Some(result)) => Ok(string_keyed_to_vmvalue(&result, &mut Interner::new())),
1435 Ok(None) => Err(VMError::Throw(format!(
1436 "builtin '{name}' requires bridge but no bridge is set"
1437 ))),
1438 Err(e) => Err(VMError::Throw(e)),
1439 }
1440}
1441
1442pub fn string_keyed_to_vmvalue(
1447 sk: &crate::value::StringKeyedValue,
1448 interner: &mut crate::intern::Interner,
1449) -> VMValue {
1450 use crate::value::StringKeyedValue;
1451 match sk {
1452 StringKeyedValue::Null => VMValue::Null,
1453 StringKeyedValue::Bool(b) => VMValue::Bool(*b),
1454 StringKeyedValue::Int(n) => VMValue::Int(*n),
1455 StringKeyedValue::Float(f) => VMValue::Float(*f),
1456 StringKeyedValue::String(s) => VMValue::String(s.clone()),
1457 StringKeyedValue::Path(p) => VMValue::Path(p.clone()),
1458 StringKeyedValue::List(items) => VMValue::List(
1459 items
1460 .iter()
1461 .map(|v| string_keyed_to_vmvalue(v, interner))
1462 .collect(),
1463 ),
1464 StringKeyedValue::Attrs(map) => {
1465 let mut attrs = BTreeMap::new();
1466 for (k, v) in map {
1467 let sym = interner.intern(k);
1468 attrs.insert(sym, string_keyed_to_vmvalue(v, interner));
1469 }
1470 VMValue::Attrs(attrs)
1471 }
1472 StringKeyedValue::Lambda => VMValue::Null,
1473 StringKeyedValue::Callable(cb) => {
1474 let cb_clone = Rc::clone(cb);
1475 VMValue::Builtin(crate::value::VMBuiltin {
1476 name: "<bridge-fn>",
1477 arity: 1,
1478 func: Rc::new(move |args: Vec<VMValue>| {
1479 let interner = crate::intern::Interner::new();
1480 let sk_arg = args.into_iter().next()
1481 .unwrap_or(VMValue::Null)
1482 .to_string_keyed(&interner);
1483 let sk_result = cb_clone(sk_arg)
1484 .map_err(|e| VMError::Throw(e))?;
1485 let mut tmp_interner = crate::intern::Interner::new();
1486 Ok(string_keyed_to_vmvalue(&sk_result, &mut tmp_interner))
1487 }),
1488 })
1489 }
1490 StringKeyedValue::Thunk(cb) => {
1491 let cb_clone = Rc::clone(cb);
1493 VMValue::Thunk(crate::value::VMThunk::new_native(move || {
1494 let sk_val = cb_clone().map_err(|e| VMError::Throw(e))?;
1495 let mut tmp = crate::intern::Interner::new();
1497 Ok(string_keyed_to_vmvalue(&sk_val, &mut tmp))
1498 }))
1499 }
1500 }
1501}
1502
1503impl Default for BuiltinRegistry {
1504 fn default() -> Self {
1505 Self::new()
1506 }
1507}
1508
1509fn try_unwrap_done_thunk(v: &VMValue) -> Option<Result<VMValue, VMError>> {
1516 match v {
1517 VMValue::Thunk(thunk) => {
1518 let state = thunk.state.take();
1519 match state {
1520 Some(ThunkState::Done(boxed)) => {
1521 let inner = *boxed.clone();
1522 thunk.state.set(Some(ThunkState::Done(boxed)));
1523 match &inner {
1525 VMValue::Thunk(_) => Some(try_unwrap_done_thunk(&inner)
1526 .unwrap_or(Ok(inner))),
1527 _ => Some(Ok(inner)),
1528 }
1529 }
1530 other => {
1531 thunk.state.set(other);
1532 Some(Err(VMError::TypeError {
1533 expected: "concrete value",
1534 got: "thunk (pending)",
1535 context: "builtin argument (thunk needs VM to force)".to_string(),
1536 }))
1537 }
1538 }
1539 }
1540 _ => None, }
1542}
1543
1544fn as_list(v: &VMValue) -> Result<Vec<VMValue>, VMError> {
1547 match v {
1548 VMValue::List(l) => Ok(l.clone()),
1549 VMValue::Thunk(_) => {
1550 let forced = force_vmvalue(v.clone())?;
1551 match forced {
1552 VMValue::List(l) => Ok(l),
1553 other => Err(VMError::TypeError {
1554 expected: "list",
1555 got: other.type_name(),
1556 context: "builtin argument".to_string(),
1557 }),
1558 }
1559 }
1560 other => Err(VMError::TypeError {
1561 expected: "list",
1562 got: other.type_name(),
1563 context: "builtin argument".to_string(),
1564 }),
1565 }
1566}
1567
1568fn force_vmvalue(v: VMValue) -> Result<VMValue, VMError> {
1572 match v {
1573 VMValue::Thunk(ref thunk) => {
1574 let state = thunk.state.take();
1575 match state {
1576 Some(ThunkState::Done(boxed)) => {
1577 let inner = *boxed.clone();
1578 thunk.state.set(Some(ThunkState::Done(boxed)));
1579 force_vmvalue(inner) }
1581 Some(ThunkState::NativeCallback(cb)) => {
1582 thunk.state.set(Some(ThunkState::Evaluating));
1583 match cb() {
1584 Ok(sk_val) => {
1585 let result = sk_to_vmvalue(&sk_val);
1587 thunk.state.set(Some(ThunkState::Done(Box::new(result.clone()))));
1588 force_vmvalue(result)
1589 }
1590 Err(e) => {
1591 thunk.state.set(Some(ThunkState::NativeCallback(cb)));
1592 Err(VMError::Throw(e))
1593 }
1594 }
1595 }
1596 other => {
1597 thunk.state.set(other);
1598 Err(VMError::TypeError {
1600 expected: "concrete value",
1601 got: "thunk (pending)",
1602 context: "builtin argument (thunk needs VM to force)".to_string(),
1603 })
1604 }
1605 }
1606 }
1607 other => Ok(other),
1608 }
1609}
1610
1611fn sk_to_vmvalue(sk: &crate::value::StringKeyedValue) -> VMValue {
1613 use crate::value::StringKeyedValue;
1614 match sk {
1615 StringKeyedValue::Null => VMValue::Null,
1616 StringKeyedValue::Bool(b) => VMValue::Bool(*b),
1617 StringKeyedValue::Int(n) => VMValue::Int(*n),
1618 StringKeyedValue::Float(f) => VMValue::Float(*f),
1619 StringKeyedValue::String(s) => VMValue::String(s.clone()),
1620 StringKeyedValue::Path(p) => VMValue::Path(p.clone()),
1621 StringKeyedValue::List(items) => {
1622 VMValue::List(items.iter().map(|i| sk_to_vmvalue(i)).collect())
1623 }
1624 StringKeyedValue::Attrs(map) => {
1625 let mut interner = crate::intern::Interner::new();
1627 VMValue::Attrs(map.iter().map(|(k, v)| {
1628 (interner.intern(k), sk_to_vmvalue(v))
1629 }).collect())
1630 }
1631 StringKeyedValue::Lambda => VMValue::Null, StringKeyedValue::Thunk(cb) => {
1633 let cb = cb.clone();
1635 VMValue::Thunk(crate::value::VMThunk {
1636 state: Rc::new(Cell::new(Some(ThunkState::NativeCallback(cb)))),
1637 })
1638 }
1639 StringKeyedValue::Callable(_) => VMValue::Null, }
1641}
1642
1643fn as_attrs(v: &VMValue) -> Result<&BTreeMap<Symbol, VMValue>, VMError> {
1644 match v {
1645 VMValue::Attrs(a) => Ok(a),
1646 VMValue::Thunk(_) => Err(VMError::TypeError {
1647 expected: "set",
1648 got: "thunk",
1649 context: "builtin argument".to_string(),
1650 }),
1651 other => Err(VMError::TypeError {
1652 expected: "set",
1653 got: other.type_name(),
1654 context: "builtin argument".to_string(),
1655 }),
1656 }
1657}
1658
1659fn as_string(v: &VMValue) -> Result<&str, VMError> {
1660 match v {
1661 VMValue::String(s) => Ok(s),
1662 other => Err(VMError::TypeError {
1663 expected: "string",
1664 got: other.type_name(),
1665 context: "builtin argument".to_string(),
1666 }),
1667 }
1668}
1669
1670fn force_as_string(v: &VMValue) -> Result<String, VMError> {
1673 match v {
1674 VMValue::String(s) => Ok(s.clone()),
1675 VMValue::Thunk(_) => {
1676 let forced = force_vmvalue(v.clone())?;
1677 match forced {
1678 VMValue::String(s) => Ok(s),
1679 other => Err(VMError::TypeError {
1680 expected: "string",
1681 got: other.type_name(),
1682 context: "builtin argument (after forcing thunk)".to_string(),
1683 }),
1684 }
1685 }
1686 other => Err(VMError::TypeError {
1687 expected: "string",
1688 got: other.type_name(),
1689 context: "builtin argument".to_string(),
1690 }),
1691 }
1692}
1693
1694fn force_as_list(v: &VMValue) -> Result<Vec<VMValue>, VMError> {
1696 match v {
1697 VMValue::List(l) => Ok(l.clone()),
1698 VMValue::Thunk(_) => {
1699 let forced = force_vmvalue(v.clone())?;
1700 match forced {
1701 VMValue::List(l) => Ok(l),
1702 other => Err(VMError::TypeError {
1703 expected: "list",
1704 got: other.type_name(),
1705 context: "builtin argument (after forcing thunk)".to_string(),
1706 }),
1707 }
1708 }
1709 other => Err(VMError::TypeError {
1710 expected: "list",
1711 got: other.type_name(),
1712 context: "builtin argument".to_string(),
1713 }),
1714 }
1715}
1716
1717fn as_int(v: &VMValue) -> Result<i64, VMError> {
1718 match v {
1719 VMValue::Int(n) => Ok(*n),
1720 other => Err(VMError::TypeError {
1721 expected: "int",
1722 got: other.type_name(),
1723 context: "builtin argument".to_string(),
1724 }),
1725 }
1726}
1727
1728fn as_float(v: &VMValue) -> Result<f64, VMError> {
1729 match v {
1730 VMValue::Float(f) => Ok(*f),
1731 VMValue::Int(n) => Ok(*n as f64),
1732 other => Err(VMError::TypeError {
1733 expected: "float",
1734 got: other.type_name(),
1735 context: "builtin argument".to_string(),
1736 }),
1737 }
1738}
1739
1740fn vm_coerce_to_string(v: &VMValue) -> Result<VMValue, VMError> {
1747 match v {
1748 VMValue::String(s) => Ok(VMValue::String(s.clone())),
1749 VMValue::Int(n) => Ok(VMValue::String(n.to_string())),
1750 VMValue::Float(f) => Ok(VMValue::String(format!("{f:.6}"))),
1752 VMValue::Bool(true) => Ok(VMValue::String("1".to_string())),
1753 VMValue::Bool(false) => Ok(VMValue::String(String::new())),
1754 VMValue::Null => Ok(VMValue::String(String::new())),
1755 VMValue::Path(p) => Ok(VMValue::String(p.clone())),
1756 VMValue::Attrs(attrs) => {
1757 let to_str_sym = crate::intern::intern("__toString");
1760 if attrs.contains_key(&to_str_sym) {
1761 return Err(VMError::Throw(
1765 "toString: __toString requires VM bridge".to_string(),
1766 ));
1767 }
1768 let out_path_sym = crate::intern::intern("outPath");
1769 if let Some(out_path) = attrs.get(&out_path_sym) {
1770 vm_coerce_to_string(out_path)
1771 } else {
1772 Err(VMError::Throw(
1773 "cannot coerce a set to a string, but it has no __toString or outPath".to_string(),
1774 ))
1775 }
1776 }
1777 VMValue::List(items) => {
1778 let mut parts = Vec::with_capacity(items.len());
1779 for item in items {
1780 match vm_coerce_to_string(item)? {
1781 VMValue::String(s) => parts.push(s),
1782 _ => unreachable!("vm_coerce_to_string always returns String"),
1783 }
1784 }
1785 Ok(VMValue::String(parts.join(" ")))
1786 }
1787 VMValue::Closure(_) | VMValue::Builtin(_) | VMValue::HigherOrderBuiltin(_) => {
1788 Err(VMError::Throw(
1789 "cannot coerce a function to a string".to_string(),
1790 ))
1791 }
1792 VMValue::Thunk(_) => {
1793 Err(VMError::Throw(
1794 "toString: thunk should be forced first".to_string(),
1795 ))
1796 }
1797 }
1798}
1799
1800fn vm_value_to_json(v: &VMValue) -> Result<serde_json::Value, VMError> {
1802 match v {
1803 VMValue::Null => Ok(serde_json::Value::Null),
1804 VMValue::Bool(b) => Ok(serde_json::Value::Bool(*b)),
1805 VMValue::Int(n) => Ok(serde_json::Value::Number(
1806 serde_json::Number::from(*n),
1807 )),
1808 VMValue::Float(f) => serde_json::Number::from_f64(*f)
1809 .map(serde_json::Value::Number)
1810 .ok_or_else(|| VMError::Throw("toJSON: invalid float".to_string())),
1811 VMValue::String(s) => Ok(serde_json::Value::String(s.clone())),
1812 VMValue::Path(p) => Ok(serde_json::Value::String(p.clone())),
1813 VMValue::List(items) => {
1814 let arr: Result<Vec<_>, _> = items.iter().map(vm_value_to_json).collect();
1815 Ok(serde_json::Value::Array(arr?))
1816 }
1817 VMValue::Attrs(_) => {
1818 Err(VMError::Throw(
1820 "toJSON: attrset conversion requires interner".to_string(),
1821 ))
1822 }
1823 VMValue::Closure(_) | VMValue::Builtin(_) | VMValue::HigherOrderBuiltin(_) => {
1824 Err(VMError::Throw("toJSON: cannot convert function".to_string()))
1825 }
1826 VMValue::Thunk(_) => {
1827 Err(VMError::Throw("toJSON: thunk should be forced first".to_string()))
1828 }
1829 }
1830}
1831
1832fn json_to_vm_value(v: &serde_json::Value) -> VMValue {
1834 match v {
1835 serde_json::Value::Null => VMValue::Null,
1836 serde_json::Value::Bool(b) => VMValue::Bool(*b),
1837 serde_json::Value::Number(n) => {
1838 if let Some(i) = n.as_i64() {
1839 VMValue::Int(i)
1840 } else {
1841 VMValue::Float(n.as_f64().unwrap_or(0.0))
1842 }
1843 }
1844 serde_json::Value::String(s) => VMValue::String(s.clone()),
1845 serde_json::Value::Array(arr) => {
1846 VMValue::List(arr.iter().map(json_to_vm_value).collect())
1847 }
1848 serde_json::Value::Object(_) => {
1849 VMValue::Null
1852 }
1853 }
1854}
1855
1856#[cfg(test)]
1857mod tests {
1858 use super::*;
1859
1860 #[test]
1861 fn registry_has_builtins() {
1862 let reg = BuiltinRegistry::new();
1863 assert!(reg.lookup("length").is_some());
1864 assert!(reg.lookup("typeOf").is_some());
1865 assert!(reg.lookup("head").is_some());
1866 assert!(reg.lookup("tail").is_some());
1867 assert!(reg.lookup("throw").is_some());
1868 assert!(reg.lookup("nonexistent").is_none());
1869 }
1870
1871 #[test]
1872 fn call_length() {
1873 let reg = BuiltinRegistry::new();
1874 let idx = reg.lookup("length").unwrap();
1875 let result = reg
1876 .call(idx, vec![VMValue::List(vec![VMValue::Int(1), VMValue::Int(2)])])
1877 .unwrap();
1878 assert_eq!(result, VMValue::Int(2));
1879 }
1880
1881 #[test]
1882 fn call_head() {
1883 let reg = BuiltinRegistry::new();
1884 let idx = reg.lookup("head").unwrap();
1885 let result = reg
1886 .call(idx, vec![VMValue::List(vec![VMValue::Int(10)])])
1887 .unwrap();
1888 assert_eq!(result, VMValue::Int(10));
1889 }
1890
1891 #[test]
1892 fn call_head_empty() {
1893 let reg = BuiltinRegistry::new();
1894 let idx = reg.lookup("head").unwrap();
1895 let result = reg.call(idx, vec![VMValue::List(vec![])]);
1896 assert!(result.is_err());
1897 }
1898
1899 #[test]
1900 fn call_type_of() {
1901 let reg = BuiltinRegistry::new();
1902 let idx = reg.lookup("typeOf").unwrap();
1903 assert_eq!(
1904 reg.call(idx, vec![VMValue::Int(42)]).unwrap(),
1905 VMValue::String("int".to_string())
1906 );
1907 assert_eq!(
1908 reg.call(idx, vec![VMValue::String("hello".to_string())])
1909 .unwrap(),
1910 VMValue::String("string".to_string())
1911 );
1912 }
1913
1914 #[test]
1915 fn call_string_length() {
1916 let reg = BuiltinRegistry::new();
1917 let idx = reg.lookup("stringLength").unwrap();
1918 let result = reg
1919 .call(idx, vec![VMValue::String("hello".to_string())])
1920 .unwrap();
1921 assert_eq!(result, VMValue::Int(5));
1922 }
1923
1924 #[test]
1925 fn call_throw() {
1926 let reg = BuiltinRegistry::new();
1927 let idx = reg.lookup("throw").unwrap();
1928 let result = reg.call(idx, vec![VMValue::String("test error".to_string())]);
1929 assert!(matches!(result, Err(VMError::Throw(_))));
1930 }
1931
1932 #[test]
1933 fn call_to_string() {
1934 let reg = BuiltinRegistry::new();
1935 let idx = reg.lookup("toString").unwrap();
1936 assert_eq!(
1937 reg.call(idx, vec![VMValue::Int(42)]).unwrap(),
1938 VMValue::String("42".to_string())
1939 );
1940 assert_eq!(
1941 reg.call(idx, vec![VMValue::Bool(true)]).unwrap(),
1942 VMValue::String("1".to_string())
1943 );
1944 }
1945
1946 #[test]
1947 fn builtins_attrset() {
1948 let reg = BuiltinRegistry::new();
1949 let mut interner = Interner::new();
1950 let builtins = reg.make_builtins_attrset(&mut interner);
1951 match &builtins {
1952 VMValue::Attrs(attrs) => {
1953 let length_sym = interner.lookup("length").unwrap();
1954 assert!(attrs.contains_key(&length_sym));
1955 }
1956 _ => panic!("expected Attrs"),
1957 }
1958 }
1959}