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("filterAttrs", 1, |args| {
393 Ok(VMValue::HigherOrderBuiltin(HigherOrderBuiltin {
394 op: HigherOrderOp::FilterAttrs,
395 func: Box::new(args[0].clone()),
396 extra_args: Vec::new(),
397 }))
398 });
399 self.register("functionArgs", 1, |args| {
400 match &args[0] {
401 VMValue::Builtin(_) | VMValue::HigherOrderBuiltin(_) => {
402 Ok(VMValue::Attrs(BTreeMap::new()))
403 }
404 VMValue::Closure(closure) => {
405 let mut result = BTreeMap::new();
406 let mut interner = crate::intern::Interner::new();
407 for (name, has_default) in &closure.formals {
408 let sym = interner.intern(name);
409 result.insert(sym, VMValue::Bool(*has_default));
410 }
411 Ok(VMValue::Attrs(result))
412 }
413 other => Err(VMError::TypeError {
414 expected: "lambda",
415 got: other.type_name(),
416 context: "functionArgs".to_string(),
417 }),
418 }
419 });
420 self.register("catAttrs", 1, |args| {
421 let name = as_string(&args[0])?.to_string();
422 Ok(VMValue::Builtin(VMBuiltin {
423 name: "catAttrs<partial>",
424 func: Rc::new(move |args2| {
425 let list = force_as_list(&args2[0])?;
426 let mut result: Vec<VMValue> = Vec::new();
427 for item in &list {
428 if let VMValue::Attrs(_) = item {
429 let _ = &name;
430 }
431 }
432 Err(VMError::Throw(
433 "catAttrs: requires interner access (use VM dispatch)".to_string(),
434 ))
435 }),
436 arity: 1,
437 }))
438 });
439 }
440
441 fn register_attrset_ops(&mut self) {
444 self.register("attrNames", 1, |args| {
445 let attrs = as_attrs(&args[0])?;
446 let _ = attrs;
450 Err(VMError::Throw(
451 "attrNames: requires interner access (use VM dispatch)".to_string(),
452 ))
453 });
454
455 self.register("attrValues", 1, |_args| {
469 Err(VMError::Throw(
470 "attrValues: requires interner access (use VM dispatch)".to_string(),
471 ))
472 });
473
474 self.register("hasAttr", 1, |args| {
475 let name = as_string(&args[0])?.to_string();
476 Ok(VMValue::Builtin(VMBuiltin {
477 name: "hasAttr<partial>",
478 func: Rc::new(move |_args2| {
479 let _ = &name;
481 Err(VMError::Throw(
482 "hasAttr: requires interner access (use VM dispatch)".to_string(),
483 ))
484 }),
485 arity: 1,
486 }))
487 });
488
489 self.register("getAttr", 1, |args| {
490 let name = as_string(&args[0])?.to_string();
491 Ok(VMValue::Builtin(VMBuiltin {
492 name: "getAttr<partial>",
493 func: Rc::new(move |_args2| {
494 let _ = &name;
495 Err(VMError::Throw(
496 "getAttr: requires interner access (use VM dispatch)".to_string(),
497 ))
498 }),
499 arity: 1,
500 }))
501 });
502
503 self.register("intersectAttrs", 1, |args| {
504 let a_attrs = as_attrs(&args[0])?.clone();
505 Ok(VMValue::Builtin(VMBuiltin {
506 name: "intersectAttrs<partial>",
507 func: Rc::new(move |args2| {
508 let b_attrs = as_attrs(&args2[0])?;
509 let mut result = BTreeMap::new();
510 for (k, v) in b_attrs {
511 if a_attrs.contains_key(k) {
512 result.insert(*k, v.clone());
513 }
514 }
515 Ok(VMValue::Attrs(result))
516 }),
517 arity: 1,
518 }))
519 });
520
521 self.register("removeAttrs", 1, |args| {
522 let set = as_attrs(&args[0])?.clone();
523 Ok(VMValue::Builtin(VMBuiltin {
524 name: "removeAttrs<partial>",
525 func: Rc::new(move |_args2| {
526 let _ = &set;
528 Err(VMError::Throw(
529 "removeAttrs: requires interner access".to_string(),
530 ))
531 }),
532 arity: 1,
533 }))
534 });
535
536 self.register("listToAttrs", 1, |_args| {
537 Err(VMError::Throw(
538 "listToAttrs: requires interner access".to_string(),
539 ))
540 });
541 }
542
543 fn register_string_ops(&mut self) {
546 self.register("stringLength", 1, |args| {
547 let s = as_string(&args[0])?;
548 Ok(VMValue::Int(s.len() as i64))
549 });
550
551 self.register("substring", 1, |args| {
552 let start_i = as_int(&args[0])?;
565 Ok(VMValue::Builtin(VMBuiltin {
566 name: "substring<p1>",
567 func: Rc::new(move |args2| {
568 let len_i = as_int(&args2[0])?;
569 Ok(VMValue::Builtin(VMBuiltin {
570 name: "substring<p2>",
571 func: Rc::new(move |args3| {
572 let s = as_string(&args3[0])?;
573 if start_i < 0 {
574 return Err(VMError::Throw(
575 "substring: negative start position".to_string(),
576 ));
577 }
578 let s_len = s.len();
579 let start = (start_i as usize).min(s_len);
580 let end = if len_i < 0 {
581 s_len
582 } else {
583 start.saturating_add(len_i as usize).min(s_len)
584 };
585 Ok(VMValue::String(s[start..end].to_string()))
586 }),
587 arity: 1,
588 }))
589 }),
590 arity: 1,
591 }))
592 });
593
594 self.register("concatStringsSep", 1, |args| {
595 let sep = as_string(&args[0])?.to_string();
596 Ok(VMValue::Builtin(VMBuiltin {
597 name: "concatStringsSep<partial>",
598 func: Rc::new(move |args2| {
599 let list = force_as_list(&args2[0])?;
600 let strings: Result<Vec<String>, _> =
601 list.iter().map(|v| force_as_string(v)).collect();
602 Ok(VMValue::String(strings?.join(&sep)))
603 }),
604 arity: 1,
605 }))
606 });
607
608 self.register("replaceStrings", 1, |args| {
609 let from: Vec<String> = force_as_list(&args[0])?
610 .iter()
611 .map(|v| force_as_string(v))
612 .collect::<Result<_, _>>()?;
613 Ok(VMValue::Builtin(VMBuiltin {
614 name: "replaceStrings<p1>",
615 func: Rc::new(move |args2| {
616 let to: Vec<String> = force_as_list(&args2[0])?
617 .iter()
618 .map(|v| force_as_string(v))
619 .collect::<Result<_, _>>()?;
620 let from2 = from.clone();
621 Ok(VMValue::Builtin(VMBuiltin {
622 name: "replaceStrings<p2>",
623 func: Rc::new(move |args3| {
624 let mut s = as_string(&args3[0])?.to_string();
625 for (f, t) in from2.iter().zip(to.iter()) {
626 if !f.is_empty() {
627 s = s.replace(f.as_str(), t);
628 }
629 }
630 Ok(VMValue::String(s))
631 }),
632 arity: 1,
633 }))
634 }),
635 arity: 1,
636 }))
637 });
638
639 self.register("hasPrefix", 1, |args| {
640 let prefix = as_string(&args[0])?.to_string();
641 Ok(VMValue::Builtin(VMBuiltin {
642 name: "hasPrefix<partial>",
643 func: Rc::new(move |args2| {
644 let s = as_string(&args2[0])?;
645 Ok(VMValue::Bool(s.starts_with(&*prefix)))
646 }),
647 arity: 1,
648 }))
649 });
650
651 self.register("hasSuffix", 1, |args| {
652 let suffix = as_string(&args[0])?.to_string();
653 Ok(VMValue::Builtin(VMBuiltin {
654 name: "hasSuffix<partial>",
655 func: Rc::new(move |args2| {
656 let s = as_string(&args2[0])?;
657 Ok(VMValue::Bool(s.ends_with(&*suffix)))
658 }),
659 arity: 1,
660 }))
661 });
662
663 self.register("toLower", 1, |args| {
664 let s = as_string(&args[0])?;
665 Ok(VMValue::String(s.to_lowercase()))
666 });
667
668 self.register("toUpper", 1, |args| {
669 let s = as_string(&args[0])?;
670 Ok(VMValue::String(s.to_uppercase()))
671 });
672 }
673
674 fn register_conversion_ops(&mut self) {
677 self.register("toString", 1, |args| {
678 vm_coerce_to_string(&args[0])
679 });
680
681 self.register("toJSON", 1, |args| {
682 let json = vm_value_to_json(&args[0])?;
683 let s = serde_json::to_string(&json)
684 .unwrap_or_else(|_| "null".to_string());
685 Ok(VMValue::String(s))
686 });
687
688 self.register("fromJSON", 1, |args| {
689 let s = as_string(&args[0])?;
690 let json: serde_json::Value = serde_json::from_str(s).map_err(|e| {
691 VMError::Throw(format!("fromJSON: {e}"))
692 })?;
693 Ok(json_to_vm_value(&json))
694 });
695
696 self.register("toInt", 1, |args| {
697 let s = as_string(&args[0])?;
698 let n: i64 = s.trim().parse().map_err(|e| {
699 VMError::Throw(format!("toInt: {e}"))
700 })?;
701 Ok(VMValue::Int(n))
702 });
703 }
704
705 fn register_control_ops(&mut self) {
708 self.register("throw", 1, |args| {
709 let msg = as_string(&args[0])?;
710 Err(VMError::Throw(format!("throw: {msg}")))
711 });
712
713 self.register("abort", 1, |args| {
714 let msg = as_string(&args[0])?;
715 Err(VMError::Throw(format!("abort: {msg}")))
716 });
717
718 self.register("seq", 1, |args| {
719 let _forced = args[0].clone();
720 Ok(VMValue::Builtin(VMBuiltin {
721 name: "seq<partial>",
722 func: Rc::new(|args2| Ok(args2[0].clone())),
723 arity: 1,
724 }))
725 });
726
727 self.register("deepSeq", 1, |args| {
728 let _forced = args[0].clone();
729 Ok(VMValue::Builtin(VMBuiltin {
730 name: "deepSeq<partial>",
731 func: Rc::new(|args2| Ok(args2[0].clone())),
732 arity: 1,
733 }))
734 });
735
736 self.register("tryEval", 1, |args| {
737 let val = args[0].clone();
740 let _ = val;
744 Err(VMError::Throw(
745 "tryEval: requires VM-level implementation".to_string(),
746 ))
747 });
748
749 self.register("trace", 1, |args| {
750 let msg = args[0].clone();
751 eprintln!("trace: {msg}");
752 Ok(VMValue::Builtin(VMBuiltin {
753 name: "trace<partial>",
754 func: Rc::new(|args2| Ok(args2[0].clone())),
755 arity: 1,
756 }))
757 });
758 }
759
760 fn register_arithmetic_ops(&mut self) {
763 self.register("add", 1, |args| {
764 let a = args[0].clone();
765 Ok(VMValue::Builtin(VMBuiltin {
766 name: "add<partial>",
767 func: Rc::new(move |args2| match (&a, &args2[0]) {
768 (VMValue::Int(x), VMValue::Int(y)) => Ok(VMValue::Int(x + y)),
769 (VMValue::Float(x), VMValue::Float(y)) => Ok(VMValue::Float(x + y)),
770 (VMValue::Int(x), VMValue::Float(y)) => Ok(VMValue::Float(*x as f64 + y)),
771 (VMValue::Float(x), VMValue::Int(y)) => Ok(VMValue::Float(x + *y as f64)),
772 _ => Err(VMError::Throw("add: expected numbers".to_string())),
773 }),
774 arity: 1,
775 }))
776 });
777
778 self.register("sub", 1, |args| {
782 let a = args[0].clone();
783 Ok(VMValue::Builtin(VMBuiltin {
784 name: "sub<partial>",
785 func: Rc::new(move |args2| vm_numeric_binop("sub", &a, &args2[0], |x, y| x - y, |x, y| x - y)),
786 arity: 1,
787 }))
788 });
789
790 self.register("mul", 1, |args| {
791 let a = args[0].clone();
792 Ok(VMValue::Builtin(VMBuiltin {
793 name: "mul<partial>",
794 func: Rc::new(move |args2| vm_numeric_binop("mul", &a, &args2[0], |x, y| x * y, |x, y| x * y)),
795 arity: 1,
796 }))
797 });
798
799 self.register("div", 1, |args| {
800 let a = args[0].clone();
801 Ok(VMValue::Builtin(VMBuiltin {
802 name: "div<partial>",
803 func: Rc::new(move |args2| match (&a, &args2[0]) {
804 (VMValue::Int(_), VMValue::Int(0)) => Err(VMError::DivisionByZero),
805 (VMValue::Int(x), VMValue::Int(y)) => Ok(VMValue::Int(x / y)),
806 (VMValue::Float(x), VMValue::Float(y)) => Ok(VMValue::Float(x / y)),
807 (VMValue::Int(x), VMValue::Float(y)) => Ok(VMValue::Float(*x as f64 / *y)),
808 (VMValue::Float(x), VMValue::Int(y)) => Ok(VMValue::Float(*x / *y as f64)),
809 _ => Err(VMError::Throw("div: expected numbers".to_string())),
810 }),
811 arity: 1,
812 }))
813 });
814
815 self.register("ceil", 1, |args| {
816 let f = as_float(&args[0])?;
817 Ok(VMValue::Int(f.ceil() as i64))
818 });
819
820 self.register("floor", 1, |args| {
821 let f = as_float(&args[0])?;
822 Ok(VMValue::Int(f.floor() as i64))
823 });
824
825 self.register("bitAnd", 1, |args| {
826 let a = as_int(&args[0])?;
827 Ok(VMValue::Builtin(VMBuiltin {
828 name: "bitAnd<partial>",
829 func: Rc::new(move |args2| {
830 let b = as_int(&args2[0])?;
831 Ok(VMValue::Int(a & b))
832 }),
833 arity: 1,
834 }))
835 });
836
837 self.register("bitOr", 1, |args| {
838 let a = as_int(&args[0])?;
839 Ok(VMValue::Builtin(VMBuiltin {
840 name: "bitOr<partial>",
841 func: Rc::new(move |args2| {
842 let b = as_int(&args2[0])?;
843 Ok(VMValue::Int(a | b))
844 }),
845 arity: 1,
846 }))
847 });
848
849 self.register("bitXor", 1, |args| {
850 let a = as_int(&args[0])?;
851 Ok(VMValue::Builtin(VMBuiltin {
852 name: "bitXor<partial>",
853 func: Rc::new(move |args2| {
854 let b = as_int(&args2[0])?;
855 Ok(VMValue::Int(a ^ b))
856 }),
857 arity: 1,
858 }))
859 });
860 }
861
862 fn register_derivation_ops(&mut self) {
865 self.register("derivation", 1, |_args| {
870 Err(VMError::Throw(
871 "derivation: requires VM-level dispatch".to_string(),
872 ))
873 });
874 self.register("derivationStrict", 1, |_args| {
875 Err(VMError::Throw(
876 "derivationStrict: requires VM-level dispatch".to_string(),
877 ))
878 });
879 self.register("getFlake", 1, |_args| {
881 Err(VMError::Throw(
882 "getFlake: requires VM-level dispatch".to_string(),
883 ))
884 });
885 self.register("scopedImport", 1, |_args| {
887 Err(VMError::Throw(
888 "scopedImport: requires VM-level dispatch".to_string(),
889 ))
890 });
891
892 self.register("addErrorContext", 1, |args| {
896 Ok(VMValue::Builtin(VMBuiltin {
898 name: "addErrorContext<partial>",
899 func: Rc::new(move |inner_args: Vec<VMValue>| Ok(inner_args[0].clone())),
900 arity: 1,
901 }))
902 });
903
904 self.register("unsafeGetAttrPos", 1, |_args| {
906 Ok(VMValue::Builtin(VMBuiltin {
907 name: "unsafeGetAttrPos<partial>",
908 func: Rc::new(|_args: Vec<VMValue>| Ok(VMValue::Null)),
909 arity: 1,
910 }))
911 });
912
913 self.register("pathExists", 1, |args| {
915 let path = match &args[0] {
916 VMValue::Path(p) => p.clone(),
917 VMValue::String(s) => s.clone(),
918 other => {
919 return Err(VMError::TypeError {
920 expected: "path or string",
921 got: other.type_name(),
922 context: "pathExists".to_string(),
923 })
924 }
925 };
926 let read_path = crate::bridge::materialize(&path);
931 Ok(VMValue::Bool(std::path::Path::new(&read_path).exists()))
932 });
933
934 self.register("readFile", 1, |args| {
936 let path = match &args[0] {
937 VMValue::Path(p) => p.clone(),
938 VMValue::String(s) => s.clone(),
939 other => {
940 return Err(VMError::TypeError {
941 expected: "path or string",
942 got: other.type_name(),
943 context: "readFile".to_string(),
944 })
945 }
946 };
947 let read_path = crate::bridge::materialize(&path);
951 let content = std::fs::read_to_string(&read_path)
952 .map_err(|e| VMError::Throw(format!("readFile {path}: {e}")))?;
953 Ok(VMValue::String(content))
954 });
955
956 self.register("readDir", 1, |args| {
958 let path = match &args[0] {
959 VMValue::Path(p) => p.clone(),
960 VMValue::String(s) => s.clone(),
961 other => {
962 return Err(VMError::TypeError {
963 expected: "path or string",
964 got: other.type_name(),
965 context: "readDir".to_string(),
966 })
967 }
968 };
969 let _ = path;
975 Err(VMError::Throw(
976 "readDir: requires the tree-walker bridge (no interner access here)".to_string(),
977 ))
978 });
979
980 self.register("baseNameOf", 1, |args| {
982 let path = match &args[0] {
983 VMValue::Path(p) => p.clone(),
984 VMValue::String(s) => s.clone(),
985 other => {
986 return Err(VMError::TypeError {
987 expected: "path or string",
988 got: other.type_name(),
989 context: "baseNameOf".to_string(),
990 })
991 }
992 };
993 let base = std::path::Path::new(&path)
994 .file_name()
995 .map(|f| f.to_string_lossy().to_string())
996 .unwrap_or_default();
997 Ok(VMValue::String(base))
998 });
999
1000 self.register("dirOf", 1, |args| {
1002 let path = match &args[0] {
1003 VMValue::Path(p) => p.clone(),
1004 VMValue::String(s) => s.clone(),
1005 other => {
1006 return Err(VMError::TypeError {
1007 expected: "path or string",
1008 got: other.type_name(),
1009 context: "dirOf".to_string(),
1010 })
1011 }
1012 };
1013 let dir = std::path::Path::new(&path)
1014 .parent()
1015 .map(|p| p.to_string_lossy().to_string())
1016 .unwrap_or_else(|| ".".to_string());
1017 Ok(VMValue::String(dir))
1018 });
1019
1020 self.register("genericClosure", 1, |_args| {
1022 Err(VMError::Throw(
1023 "genericClosure: requires VM-level dispatch".to_string(),
1024 ))
1025 });
1026
1027 self.register("placeholder", 1, |args| {
1029 let output = match &args[0] {
1030 VMValue::String(s) => s.clone(),
1031 _ => "out".to_string(),
1032 };
1033 Ok(VMValue::String(format!("/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9/{output}")))
1034 });
1035
1036 self.register("split", 1, |args| {
1038 let _pattern = as_string(&args[0])?;
1039 Ok(VMValue::Builtin(VMBuiltin {
1040 name: "split<partial>",
1041 func: Rc::new(|_inner_args: Vec<VMValue>| {
1042 Err(VMError::Throw("split: requires VM-level dispatch".to_string()))
1043 }),
1044 arity: 1,
1045 }))
1046 });
1047
1048 self.register("match", 1, |args| {
1050 let _pattern = as_string(&args[0])?;
1051 Ok(VMValue::Builtin(VMBuiltin {
1052 name: "match<partial>",
1053 func: Rc::new(|_inner_args: Vec<VMValue>| {
1054 Err(VMError::Throw("match: requires VM-level dispatch".to_string()))
1055 }),
1056 arity: 1,
1057 }))
1058 });
1059
1060 self.register("fromTOML", 1, |args| {
1062 let s = as_string(&args[0])?;
1063 Err(VMError::Throw(format!("fromTOML: not yet implemented")))
1065 });
1066
1067 self.register("fetchurl", 1, |_args| {
1076 Err(VMError::Throw("fetchurl: not supported in eval mode".to_string()))
1077 });
1078 self.register("fetchTarball", 1, |_args| {
1079 Err(VMError::Throw("fetchTarball: not supported in eval mode".to_string()))
1080 });
1081 self.register("fetchGit", 1, |_args| {
1082 Err(VMError::Throw("fetchGit: not supported in eval mode".to_string()))
1083 });
1084 self.register("fetchTree", 1, |_args| {
1085 Err(VMError::Throw("fetchTree: not supported in eval mode".to_string()))
1086 });
1087 self.register("fetchMercurial", 1, |_args| {
1088 Err(VMError::Throw("fetchMercurial: not supported in eval mode".to_string()))
1089 });
1090
1091 self.register("toFile", 1, |_args| {
1093 Err(VMError::Throw("toFile: not supported in eval mode".to_string()))
1094 });
1095
1096 self.register("toPath", 1, |args| {
1098 let s = as_string(&args[0])?;
1099 Ok(VMValue::Path(s.to_string()))
1100 });
1101
1102 self.register("parseDrvName", 1, |args| {
1107 let name = as_string(&args[0])?;
1108 let mut split_pos = None;
1110 let bytes = name.as_bytes();
1111 for i in (0..bytes.len()).rev() {
1112 if bytes[i] == b'-' && i + 1 < bytes.len() && bytes[i + 1].is_ascii_digit() {
1113 split_pos = Some(i);
1114 break;
1115 }
1116 }
1117 match split_pos {
1118 Some(pos) => {
1119 Err(VMError::Throw(
1120 "parseDrvName: requires VM-level dispatch for interner access".to_string(),
1121 ))
1122 }
1123 None => {
1124 Err(VMError::Throw(
1125 "parseDrvName: requires VM-level dispatch for interner access".to_string(),
1126 ))
1127 }
1128 }
1129 });
1130
1131 self.register("compareVersions", 1, |args| {
1136 let a = as_string(&args[0])?.to_string();
1137 Ok(VMValue::Builtin(VMBuiltin {
1138 name: "compareVersions<partial>",
1139 func: Rc::new(move |inner_args: Vec<VMValue>| {
1140 let b = as_string(&inner_args[0])?;
1141 Ok(VMValue::Int(
1142 sui_compat::versions::compare_versions(&a, b),
1143 ))
1144 }),
1145 arity: 1,
1146 }))
1147 });
1148
1149 self.register("splitVersion", 1, |args| {
1152 let version = as_string(&args[0])?;
1153 let parts: Vec<VMValue> = sui_compat::versions::split_version(version)
1154 .into_iter()
1155 .map(VMValue::String)
1156 .collect();
1157 Ok(VMValue::List(parts))
1158 });
1159
1160 self.register("concatStrings", 1, |args| {
1162 let list = as_list(&args[0])?;
1163 let mut result = String::new();
1164 for item in &list {
1165 let item = force_vmvalue(item.clone()).unwrap_or_else(|_| item.clone());
1166 match &item {
1167 VMValue::String(s) => result.push_str(s),
1168 _ => {
1169 return Err(VMError::TypeError {
1170 expected: "string",
1171 got: item.type_name(),
1172 context: "concatStrings element".to_string(),
1173 })
1174 }
1175 }
1176 }
1177 Ok(VMValue::String(result))
1178 });
1179
1180 self.register("unsafeDiscardStringContext", 1, |args| {
1184 Ok(args[0].clone())
1186 });
1187 self.register("getContext", 1, |_args| {
1188 Err(VMError::Throw(
1191 "getContext: requires VM-level dispatch for interner access".to_string(),
1192 ))
1193 });
1194 self.register("appendContext", 1, |args| {
1195 Ok(VMValue::Builtin(VMBuiltin {
1197 name: "appendContext<partial>",
1198 func: Rc::new(move |inner_args: Vec<VMValue>| Ok(inner_args[0].clone())),
1199 arity: 1,
1200 }))
1201 });
1202 self.register("hasContext", 1, |_args| {
1203 Ok(VMValue::Bool(false))
1204 });
1205 self.register("unsafeDiscardOutputDependency", 1, |args| {
1206 Ok(args[0].clone())
1207 });
1208 self.register("addDrvOutputDependencies", 1, |args| {
1209 Ok(args[0].clone())
1210 });
1211
1212 self.register("storePath", 1, |args| {
1214 Ok(args[0].clone())
1215 });
1216 self.register("isStorePath", 1, |args| {
1217 let s = match &args[0] {
1218 VMValue::String(s) => s.as_str(),
1219 VMValue::Path(p) => p.as_str(),
1220 _ => return Ok(VMValue::Bool(false)),
1221 };
1222 Ok(VMValue::Bool(s.starts_with("/nix/store/")))
1223 });
1224 self.register("hashString", 1, |args| {
1225 let algo = as_string(&args[0])?.to_string();
1226 Ok(VMValue::Builtin(VMBuiltin {
1227 name: "hashString<partial>",
1228 func: Rc::new(move |inner_args: Vec<VMValue>| {
1229 let s = as_string(&inner_args[0])?;
1230 match algo.as_str() {
1231 "sha256" => {
1232 use sha2::{Sha256, Digest};
1233 let mut hasher = Sha256::new();
1234 hasher.update(s.as_bytes());
1235 let result = hasher.finalize();
1236 let hex: String = result
1237 .iter()
1238 .map(|b| format!("{b:02x}"))
1239 .collect();
1240 Ok(VMValue::String(hex))
1241 }
1242 _ => Err(VMError::Throw(format!("hashString: unsupported algorithm: {algo}")))
1243 }
1244 }),
1245 arity: 1,
1246 }))
1247 });
1248 self.register("hashFile", 1, |_args| {
1249 Err(VMError::Throw("hashFile: not supported in eval mode".to_string()))
1250 });
1251
1252 self.register("import", 1, |_args| {
1257 Err(VMError::Throw(
1258 "import: requires VM-level dispatch".to_string(),
1259 ))
1260 });
1261
1262 self.register("zipAttrsWith", 1, |_args| {
1264 Err(VMError::Throw(
1265 "zipAttrsWith: requires VM-level dispatch".to_string(),
1266 ))
1267 });
1268 }
1269
1270 fn register_missing_builtins(&mut self) {
1277 self.register("getEnv", 1, |args| {
1281 let name = as_string(&args[0])?;
1282 let val = std::env::var(name).unwrap_or_default();
1283 Ok(VMValue::String(val))
1284 });
1285
1286 self.register("readFileType", 1, |args| {
1288 let path = match &args[0] {
1289 VMValue::Path(p) => p.clone(),
1290 VMValue::String(s) => s.clone(),
1291 other => {
1292 return Err(VMError::TypeError {
1293 expected: "path or string",
1294 got: other.type_name(),
1295 context: "readFileType".to_string(),
1296 });
1297 }
1298 };
1299 let read_path = crate::bridge::materialize(&path);
1301 match std::fs::symlink_metadata(&read_path) {
1302 Ok(meta) => {
1303 let kind = if meta.is_symlink() {
1304 "symlink"
1305 } else if meta.is_dir() {
1306 "directory"
1307 } else if meta.is_file() {
1308 "regular"
1309 } else {
1310 "unknown"
1311 };
1312 Ok(VMValue::String(kind.to_string()))
1313 }
1314 Err(e) => Err(VMError::Throw(format!("readFileType {path}: {e}"))),
1315 }
1316 });
1317
1318 self.register("findFile", 1, |args| {
1320 let search_path = as_list(&args[0])?.clone();
1321 Ok(VMValue::Builtin(VMBuiltin {
1322 name: "findFile<partial>",
1323 func: Rc::new(move |args2| {
1324 let name = as_string(&args2[0])?;
1325 for entry in &search_path {
1326 if let VMValue::Attrs(a) = entry {
1327 let _ = a;
1332 }
1333 }
1334 bridge_call("findFile", vec![
1336 VMValue::List(search_path.clone()),
1337 VMValue::String(name.to_string()),
1338 ])
1339 }),
1340 arity: 1,
1341 }))
1342 });
1343
1344 self.register("lessThan", 1, |args| {
1346 let a = args[0].clone();
1347 Ok(VMValue::Builtin(VMBuiltin {
1348 name: "lessThan<partial>",
1349 func: Rc::new(move |args2| match (&a, &args2[0]) {
1350 (VMValue::Int(x), VMValue::Int(y)) => Ok(VMValue::Bool(*x < *y)),
1351 (VMValue::Float(x), VMValue::Float(y)) => Ok(VMValue::Bool(*x < *y)),
1352 (VMValue::Int(x), VMValue::Float(y)) => {
1353 Ok(VMValue::Bool((*x as f64) < *y))
1354 }
1355 (VMValue::Float(x), VMValue::Int(y)) => {
1356 Ok(VMValue::Bool(*x < (*y as f64)))
1357 }
1358 (VMValue::String(x), VMValue::String(y)) => Ok(VMValue::Bool(*x < *y)),
1359 _ => Err(VMError::Throw(
1360 "lessThan: expected comparable types".to_string(),
1361 )),
1362 }),
1363 arity: 1,
1364 }))
1365 });
1366
1367 self.register("warn", 1, |args| {
1369 if let Ok(msg) = as_string(&args[0]) {
1370 eprintln!("evaluation warning: {msg}");
1371 }
1372 Ok(VMValue::Builtin(VMBuiltin {
1373 name: "warn<partial>",
1374 func: Rc::new(|args2| Ok(args2[0].clone())),
1375 arity: 1,
1376 }))
1377 });
1378
1379 self.register("traceVerbose", 1, |args| {
1381 if std::env::var("SUI_TRACE_VERBOSE").ok().as_deref() == Some("1") {
1382 eprintln!("trace: {}", args[0]);
1383 }
1384 Ok(VMValue::Builtin(VMBuiltin {
1385 name: "traceVerbose<partial>",
1386 func: Rc::new(|args2| Ok(args2[0].clone())),
1387 arity: 1,
1388 }))
1389 });
1390
1391 self.register("break", 1, |args| Ok(args[0].clone()));
1393
1394 for name in &["convertHash", "toXML", "toFile", "filterSource",
1422 "fetchClosure", "outputOf", "hashFile", "hashString",
1423 "path", "parseFlakeRef", "flakeRefToString"]
1424 {
1425 let n = (*name).to_string();
1426 self.register(name, 1, move |args| {
1427 bridge_call(&n, args.to_vec())
1428 });
1429 }
1430 }
1431}
1432
1433fn vm_numeric_binop(
1441 name: &'static str,
1442 a: &VMValue,
1443 b: &VMValue,
1444 int_op: impl Fn(i64, i64) -> i64,
1445 float_op: impl Fn(f64, f64) -> f64,
1446) -> Result<VMValue, VMError> {
1447 match (a, b) {
1448 (VMValue::Int(x), VMValue::Int(y)) => Ok(VMValue::Int(int_op(*x, *y))),
1449 (VMValue::Float(x), VMValue::Float(y)) => Ok(VMValue::Float(float_op(*x, *y))),
1450 (VMValue::Int(x), VMValue::Float(y)) => Ok(VMValue::Float(float_op(*x as f64, *y))),
1451 (VMValue::Float(x), VMValue::Int(y)) => Ok(VMValue::Float(float_op(*x, *y as f64))),
1452 _ => Err(VMError::Throw(format!("{name}: expected numbers"))),
1453 }
1454}
1455
1456fn bridge_call(name: &str, args: Vec<VMValue>) -> Result<VMValue, VMError> {
1457 use crate::intern::Interner;
1458 let tmp_interner = Interner::new();
1461 let sk_args: Vec<crate::value::StringKeyedValue> = args
1462 .iter()
1463 .map(|a| a.to_string_keyed(&tmp_interner))
1464 .collect();
1465
1466 match crate::bridge::call_builtin_bridge(name, sk_args) {
1467 Ok(Some(result)) => Ok(string_keyed_to_vmvalue(&result, &mut Interner::new())),
1468 Ok(None) => Err(VMError::Throw(format!(
1469 "builtin '{name}' requires bridge but no bridge is set"
1470 ))),
1471 Err(e) => Err(VMError::Throw(e)),
1472 }
1473}
1474
1475pub fn string_keyed_to_vmvalue(
1480 sk: &crate::value::StringKeyedValue,
1481 interner: &mut crate::intern::Interner,
1482) -> VMValue {
1483 use crate::value::StringKeyedValue;
1484 match sk {
1485 StringKeyedValue::Null => VMValue::Null,
1486 StringKeyedValue::Bool(b) => VMValue::Bool(*b),
1487 StringKeyedValue::Int(n) => VMValue::Int(*n),
1488 StringKeyedValue::Float(f) => VMValue::Float(*f),
1489 StringKeyedValue::String(s) => VMValue::String(s.clone()),
1490 StringKeyedValue::Path(p) => VMValue::Path(p.clone()),
1491 StringKeyedValue::List(items) => VMValue::List(
1492 items
1493 .iter()
1494 .map(|v| string_keyed_to_vmvalue(v, interner))
1495 .collect(),
1496 ),
1497 StringKeyedValue::Attrs(map) => {
1498 let mut attrs = BTreeMap::new();
1499 for (k, v) in map {
1500 let sym = interner.intern(k);
1501 attrs.insert(sym, string_keyed_to_vmvalue(v, interner));
1502 }
1503 VMValue::Attrs(attrs)
1504 }
1505 StringKeyedValue::Lambda => VMValue::Null,
1506 StringKeyedValue::Callable(cb) => {
1507 let cb_clone = Rc::clone(cb);
1508 VMValue::Builtin(crate::value::VMBuiltin {
1509 name: "<bridge-fn>",
1510 arity: 1,
1511 func: Rc::new(move |args: Vec<VMValue>| {
1512 let interner = crate::intern::Interner::new();
1513 let sk_arg = args.into_iter().next()
1514 .unwrap_or(VMValue::Null)
1515 .to_string_keyed(&interner);
1516 let sk_result = cb_clone(sk_arg)
1517 .map_err(|e| VMError::Throw(e))?;
1518 let mut tmp_interner = crate::intern::Interner::new();
1519 Ok(string_keyed_to_vmvalue(&sk_result, &mut tmp_interner))
1520 }),
1521 })
1522 }
1523 StringKeyedValue::Thunk(cb) => {
1524 let cb_clone = Rc::clone(cb);
1526 VMValue::Thunk(crate::value::VMThunk::new_native(move || {
1527 let sk_val = cb_clone().map_err(|e| VMError::Throw(e))?;
1528 let mut tmp = crate::intern::Interner::new();
1530 Ok(string_keyed_to_vmvalue(&sk_val, &mut tmp))
1531 }))
1532 }
1533 }
1534}
1535
1536impl Default for BuiltinRegistry {
1537 fn default() -> Self {
1538 Self::new()
1539 }
1540}
1541
1542fn try_unwrap_done_thunk(v: &VMValue) -> Option<Result<VMValue, VMError>> {
1549 match v {
1550 VMValue::Thunk(thunk) => {
1551 let state = thunk.state.take();
1552 match state {
1553 Some(ThunkState::Done(boxed)) => {
1554 let inner = *boxed.clone();
1555 thunk.state.set(Some(ThunkState::Done(boxed)));
1556 match &inner {
1558 VMValue::Thunk(_) => Some(try_unwrap_done_thunk(&inner)
1559 .unwrap_or(Ok(inner))),
1560 _ => Some(Ok(inner)),
1561 }
1562 }
1563 other => {
1564 thunk.state.set(other);
1565 Some(Err(VMError::TypeError {
1566 expected: "concrete value",
1567 got: "thunk (pending)",
1568 context: "builtin argument (thunk needs VM to force)".to_string(),
1569 }))
1570 }
1571 }
1572 }
1573 _ => None, }
1575}
1576
1577fn as_list(v: &VMValue) -> Result<Vec<VMValue>, VMError> {
1580 match v {
1581 VMValue::List(l) => Ok(l.clone()),
1582 VMValue::Thunk(_) => {
1583 let forced = force_vmvalue(v.clone())?;
1584 match forced {
1585 VMValue::List(l) => Ok(l),
1586 other => Err(VMError::TypeError {
1587 expected: "list",
1588 got: other.type_name(),
1589 context: "builtin argument".to_string(),
1590 }),
1591 }
1592 }
1593 other => Err(VMError::TypeError {
1594 expected: "list",
1595 got: other.type_name(),
1596 context: "builtin argument".to_string(),
1597 }),
1598 }
1599}
1600
1601fn force_vmvalue(v: VMValue) -> Result<VMValue, VMError> {
1605 match v {
1606 VMValue::Thunk(ref thunk) => {
1607 let state = thunk.state.take();
1608 match state {
1609 Some(ThunkState::Done(boxed)) => {
1610 let inner = *boxed.clone();
1611 thunk.state.set(Some(ThunkState::Done(boxed)));
1612 force_vmvalue(inner) }
1614 Some(ThunkState::NativeCallback(cb)) => {
1615 thunk.state.set(Some(ThunkState::Evaluating));
1616 match cb() {
1617 Ok(sk_val) => {
1618 let result = sk_to_vmvalue(&sk_val);
1620 thunk.state.set(Some(ThunkState::Done(Box::new(result.clone()))));
1621 force_vmvalue(result)
1622 }
1623 Err(e) => {
1624 thunk.state.set(Some(ThunkState::NativeCallback(cb)));
1625 Err(VMError::Throw(e))
1626 }
1627 }
1628 }
1629 other => {
1630 thunk.state.set(other);
1631 Err(VMError::TypeError {
1633 expected: "concrete value",
1634 got: "thunk (pending)",
1635 context: "builtin argument (thunk needs VM to force)".to_string(),
1636 })
1637 }
1638 }
1639 }
1640 other => Ok(other),
1641 }
1642}
1643
1644fn sk_to_vmvalue(sk: &crate::value::StringKeyedValue) -> VMValue {
1646 use crate::value::StringKeyedValue;
1647 match sk {
1648 StringKeyedValue::Null => VMValue::Null,
1649 StringKeyedValue::Bool(b) => VMValue::Bool(*b),
1650 StringKeyedValue::Int(n) => VMValue::Int(*n),
1651 StringKeyedValue::Float(f) => VMValue::Float(*f),
1652 StringKeyedValue::String(s) => VMValue::String(s.clone()),
1653 StringKeyedValue::Path(p) => VMValue::Path(p.clone()),
1654 StringKeyedValue::List(items) => {
1655 VMValue::List(items.iter().map(|i| sk_to_vmvalue(i)).collect())
1656 }
1657 StringKeyedValue::Attrs(map) => {
1658 let mut interner = crate::intern::Interner::new();
1660 VMValue::Attrs(map.iter().map(|(k, v)| {
1661 (interner.intern(k), sk_to_vmvalue(v))
1662 }).collect())
1663 }
1664 StringKeyedValue::Lambda => VMValue::Null, StringKeyedValue::Thunk(cb) => {
1666 let cb = cb.clone();
1668 VMValue::Thunk(crate::value::VMThunk {
1669 state: Rc::new(Cell::new(Some(ThunkState::NativeCallback(cb)))),
1670 })
1671 }
1672 StringKeyedValue::Callable(_) => VMValue::Null, }
1674}
1675
1676fn as_attrs(v: &VMValue) -> Result<&BTreeMap<Symbol, VMValue>, VMError> {
1677 match v {
1678 VMValue::Attrs(a) => Ok(a),
1679 VMValue::Thunk(_) => Err(VMError::TypeError {
1680 expected: "set",
1681 got: "thunk",
1682 context: "builtin argument".to_string(),
1683 }),
1684 other => Err(VMError::TypeError {
1685 expected: "set",
1686 got: other.type_name(),
1687 context: "builtin argument".to_string(),
1688 }),
1689 }
1690}
1691
1692fn as_string(v: &VMValue) -> Result<&str, VMError> {
1693 match v {
1694 VMValue::String(s) => Ok(s),
1695 other => Err(VMError::TypeError {
1696 expected: "string",
1697 got: other.type_name(),
1698 context: "builtin argument".to_string(),
1699 }),
1700 }
1701}
1702
1703fn force_as_string(v: &VMValue) -> Result<String, VMError> {
1706 match v {
1707 VMValue::String(s) => Ok(s.clone()),
1708 VMValue::Thunk(_) => {
1709 let forced = force_vmvalue(v.clone())?;
1710 match forced {
1711 VMValue::String(s) => Ok(s),
1712 other => Err(VMError::TypeError {
1713 expected: "string",
1714 got: other.type_name(),
1715 context: "builtin argument (after forcing thunk)".to_string(),
1716 }),
1717 }
1718 }
1719 other => Err(VMError::TypeError {
1720 expected: "string",
1721 got: other.type_name(),
1722 context: "builtin argument".to_string(),
1723 }),
1724 }
1725}
1726
1727fn force_as_list(v: &VMValue) -> Result<Vec<VMValue>, VMError> {
1729 match v {
1730 VMValue::List(l) => Ok(l.clone()),
1731 VMValue::Thunk(_) => {
1732 let forced = force_vmvalue(v.clone())?;
1733 match forced {
1734 VMValue::List(l) => Ok(l),
1735 other => Err(VMError::TypeError {
1736 expected: "list",
1737 got: other.type_name(),
1738 context: "builtin argument (after forcing thunk)".to_string(),
1739 }),
1740 }
1741 }
1742 other => Err(VMError::TypeError {
1743 expected: "list",
1744 got: other.type_name(),
1745 context: "builtin argument".to_string(),
1746 }),
1747 }
1748}
1749
1750fn as_int(v: &VMValue) -> Result<i64, VMError> {
1751 match v {
1752 VMValue::Int(n) => Ok(*n),
1753 other => Err(VMError::TypeError {
1754 expected: "int",
1755 got: other.type_name(),
1756 context: "builtin argument".to_string(),
1757 }),
1758 }
1759}
1760
1761fn as_float(v: &VMValue) -> Result<f64, VMError> {
1762 match v {
1763 VMValue::Float(f) => Ok(*f),
1764 VMValue::Int(n) => Ok(*n as f64),
1765 other => Err(VMError::TypeError {
1766 expected: "float",
1767 got: other.type_name(),
1768 context: "builtin argument".to_string(),
1769 }),
1770 }
1771}
1772
1773fn vm_coerce_to_string(v: &VMValue) -> Result<VMValue, VMError> {
1780 match v {
1781 VMValue::String(s) => Ok(VMValue::String(s.clone())),
1782 VMValue::Int(n) => Ok(VMValue::String(n.to_string())),
1783 VMValue::Float(f) => Ok(VMValue::String(format!("{f:.6}"))),
1785 VMValue::Bool(true) => Ok(VMValue::String("1".to_string())),
1786 VMValue::Bool(false) => Ok(VMValue::String(String::new())),
1787 VMValue::Null => Ok(VMValue::String(String::new())),
1788 VMValue::Path(p) => Ok(VMValue::String(p.clone())),
1789 VMValue::Attrs(attrs) => {
1790 let to_str_sym = crate::intern::intern("__toString");
1793 if attrs.contains_key(&to_str_sym) {
1794 return Err(VMError::Throw(
1798 "toString: __toString requires VM bridge".to_string(),
1799 ));
1800 }
1801 let out_path_sym = crate::intern::intern("outPath");
1802 if let Some(out_path) = attrs.get(&out_path_sym) {
1803 vm_coerce_to_string(out_path)
1804 } else {
1805 Err(VMError::Throw(
1806 "cannot coerce a set to a string, but it has no __toString or outPath".to_string(),
1807 ))
1808 }
1809 }
1810 VMValue::List(items) => {
1811 let mut parts = Vec::with_capacity(items.len());
1812 for item in items {
1813 match vm_coerce_to_string(item)? {
1814 VMValue::String(s) => parts.push(s),
1815 _ => unreachable!("vm_coerce_to_string always returns String"),
1816 }
1817 }
1818 Ok(VMValue::String(parts.join(" ")))
1819 }
1820 VMValue::Closure(_) | VMValue::Builtin(_) | VMValue::HigherOrderBuiltin(_) => {
1821 Err(VMError::Throw(
1822 "cannot coerce a function to a string".to_string(),
1823 ))
1824 }
1825 VMValue::Thunk(_) => {
1826 Err(VMError::Throw(
1827 "toString: thunk should be forced first".to_string(),
1828 ))
1829 }
1830 }
1831}
1832
1833fn vm_value_to_json(v: &VMValue) -> Result<serde_json::Value, VMError> {
1835 match v {
1836 VMValue::Null => Ok(serde_json::Value::Null),
1837 VMValue::Bool(b) => Ok(serde_json::Value::Bool(*b)),
1838 VMValue::Int(n) => Ok(serde_json::Value::Number(
1839 serde_json::Number::from(*n),
1840 )),
1841 VMValue::Float(f) => serde_json::Number::from_f64(*f)
1842 .map(serde_json::Value::Number)
1843 .ok_or_else(|| VMError::Throw("toJSON: invalid float".to_string())),
1844 VMValue::String(s) => Ok(serde_json::Value::String(s.clone())),
1845 VMValue::Path(p) => Ok(serde_json::Value::String(p.clone())),
1846 VMValue::List(items) => {
1847 let arr: Result<Vec<_>, _> = items.iter().map(vm_value_to_json).collect();
1848 Ok(serde_json::Value::Array(arr?))
1849 }
1850 VMValue::Attrs(_) => {
1851 Err(VMError::Throw(
1853 "toJSON: attrset conversion requires interner".to_string(),
1854 ))
1855 }
1856 VMValue::Closure(_) | VMValue::Builtin(_) | VMValue::HigherOrderBuiltin(_) => {
1857 Err(VMError::Throw("toJSON: cannot convert function".to_string()))
1858 }
1859 VMValue::Thunk(_) => {
1860 Err(VMError::Throw("toJSON: thunk should be forced first".to_string()))
1861 }
1862 }
1863}
1864
1865fn json_to_vm_value(v: &serde_json::Value) -> VMValue {
1867 match v {
1868 serde_json::Value::Null => VMValue::Null,
1869 serde_json::Value::Bool(b) => VMValue::Bool(*b),
1870 serde_json::Value::Number(n) => {
1871 if let Some(i) = n.as_i64() {
1872 VMValue::Int(i)
1873 } else {
1874 VMValue::Float(n.as_f64().unwrap_or(0.0))
1875 }
1876 }
1877 serde_json::Value::String(s) => VMValue::String(s.clone()),
1878 serde_json::Value::Array(arr) => {
1879 VMValue::List(arr.iter().map(json_to_vm_value).collect())
1880 }
1881 serde_json::Value::Object(_) => {
1882 VMValue::Null
1885 }
1886 }
1887}
1888
1889#[cfg(test)]
1890mod tests {
1891 use super::*;
1892
1893 #[test]
1894 fn registry_has_builtins() {
1895 let reg = BuiltinRegistry::new();
1896 assert!(reg.lookup("length").is_some());
1897 assert!(reg.lookup("typeOf").is_some());
1898 assert!(reg.lookup("head").is_some());
1899 assert!(reg.lookup("tail").is_some());
1900 assert!(reg.lookup("throw").is_some());
1901 assert!(reg.lookup("nonexistent").is_none());
1902 }
1903
1904 #[test]
1905 fn call_length() {
1906 let reg = BuiltinRegistry::new();
1907 let idx = reg.lookup("length").unwrap();
1908 let result = reg
1909 .call(idx, vec![VMValue::List(vec![VMValue::Int(1), VMValue::Int(2)])])
1910 .unwrap();
1911 assert_eq!(result, VMValue::Int(2));
1912 }
1913
1914 #[test]
1915 fn call_head() {
1916 let reg = BuiltinRegistry::new();
1917 let idx = reg.lookup("head").unwrap();
1918 let result = reg
1919 .call(idx, vec![VMValue::List(vec![VMValue::Int(10)])])
1920 .unwrap();
1921 assert_eq!(result, VMValue::Int(10));
1922 }
1923
1924 #[test]
1925 fn call_head_empty() {
1926 let reg = BuiltinRegistry::new();
1927 let idx = reg.lookup("head").unwrap();
1928 let result = reg.call(idx, vec![VMValue::List(vec![])]);
1929 assert!(result.is_err());
1930 }
1931
1932 #[test]
1933 fn call_type_of() {
1934 let reg = BuiltinRegistry::new();
1935 let idx = reg.lookup("typeOf").unwrap();
1936 assert_eq!(
1937 reg.call(idx, vec![VMValue::Int(42)]).unwrap(),
1938 VMValue::String("int".to_string())
1939 );
1940 assert_eq!(
1941 reg.call(idx, vec![VMValue::String("hello".to_string())])
1942 .unwrap(),
1943 VMValue::String("string".to_string())
1944 );
1945 }
1946
1947 #[test]
1948 fn call_string_length() {
1949 let reg = BuiltinRegistry::new();
1950 let idx = reg.lookup("stringLength").unwrap();
1951 let result = reg
1952 .call(idx, vec![VMValue::String("hello".to_string())])
1953 .unwrap();
1954 assert_eq!(result, VMValue::Int(5));
1955 }
1956
1957 #[test]
1958 fn call_throw() {
1959 let reg = BuiltinRegistry::new();
1960 let idx = reg.lookup("throw").unwrap();
1961 let result = reg.call(idx, vec![VMValue::String("test error".to_string())]);
1962 assert!(matches!(result, Err(VMError::Throw(_))));
1963 }
1964
1965 #[test]
1966 fn call_to_string() {
1967 let reg = BuiltinRegistry::new();
1968 let idx = reg.lookup("toString").unwrap();
1969 assert_eq!(
1970 reg.call(idx, vec![VMValue::Int(42)]).unwrap(),
1971 VMValue::String("42".to_string())
1972 );
1973 assert_eq!(
1974 reg.call(idx, vec![VMValue::Bool(true)]).unwrap(),
1975 VMValue::String("1".to_string())
1976 );
1977 }
1978
1979 #[test]
1980 fn builtins_attrset() {
1981 let reg = BuiltinRegistry::new();
1982 let mut interner = Interner::new();
1983 let builtins = reg.make_builtins_attrset(&mut interner);
1984 match &builtins {
1985 VMValue::Attrs(attrs) => {
1986 let length_sym = interner.lookup("length").unwrap();
1987 assert!(attrs.contains_key(&length_sym));
1988 }
1989 _ => panic!("expected Attrs"),
1990 }
1991 }
1992}