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 let _ = crate::fallback::record(crate::fallback::Layer::Builtin, name);
1473
1474 match crate::bridge::call_builtin_bridge(name, sk_args) {
1475 Ok(Some(result)) => Ok(string_keyed_to_vmvalue(&result, &mut Interner::new())),
1476 Ok(None) => Err(VMError::Throw(format!(
1477 "builtin '{name}' requires bridge but no bridge is set"
1478 ))),
1479 Err(e) => Err(VMError::Throw(e)),
1480 }
1481}
1482
1483pub fn string_keyed_to_vmvalue(
1488 sk: &crate::value::StringKeyedValue,
1489 interner: &mut crate::intern::Interner,
1490) -> VMValue {
1491 use crate::value::StringKeyedValue;
1492 match sk {
1493 StringKeyedValue::Null => VMValue::Null,
1494 StringKeyedValue::Bool(b) => VMValue::Bool(*b),
1495 StringKeyedValue::Int(n) => VMValue::Int(*n),
1496 StringKeyedValue::Float(f) => VMValue::Float(*f),
1497 StringKeyedValue::String(s) => VMValue::String(s.clone()),
1498 StringKeyedValue::Path(p) => VMValue::Path(p.clone()),
1499 StringKeyedValue::List(items) => VMValue::List(
1500 items
1501 .iter()
1502 .map(|v| string_keyed_to_vmvalue(v, interner))
1503 .collect(),
1504 ),
1505 StringKeyedValue::Attrs(map) => {
1506 let mut attrs = BTreeMap::new();
1507 for (k, v) in map {
1508 let sym = interner.intern(k);
1509 attrs.insert(sym, string_keyed_to_vmvalue(v, interner));
1510 }
1511 VMValue::Attrs(attrs)
1512 }
1513 StringKeyedValue::Lambda => VMValue::Null,
1514 StringKeyedValue::Callable(cb) => {
1515 let cb_clone = Rc::clone(cb);
1516 VMValue::Builtin(crate::value::VMBuiltin {
1517 name: "<bridge-fn>",
1518 arity: 1,
1519 func: Rc::new(move |args: Vec<VMValue>| {
1520 let interner = crate::intern::Interner::new();
1521 let sk_arg = args.into_iter().next()
1522 .unwrap_or(VMValue::Null)
1523 .to_string_keyed(&interner);
1524 let sk_result = cb_clone(sk_arg)
1525 .map_err(|e| VMError::Throw(e))?;
1526 let mut tmp_interner = crate::intern::Interner::new();
1527 Ok(string_keyed_to_vmvalue(&sk_result, &mut tmp_interner))
1528 }),
1529 })
1530 }
1531 StringKeyedValue::Thunk(cb) => {
1532 let cb_clone = Rc::clone(cb);
1534 VMValue::Thunk(crate::value::VMThunk::new_native(move || {
1535 let sk_val = cb_clone().map_err(|e| VMError::Throw(e))?;
1536 let mut tmp = crate::intern::Interner::new();
1538 Ok(string_keyed_to_vmvalue(&sk_val, &mut tmp))
1539 }))
1540 }
1541 }
1542}
1543
1544impl Default for BuiltinRegistry {
1545 fn default() -> Self {
1546 Self::new()
1547 }
1548}
1549
1550fn try_unwrap_done_thunk(v: &VMValue) -> Option<Result<VMValue, VMError>> {
1557 match v {
1558 VMValue::Thunk(thunk) => {
1559 let state = thunk.state.take();
1560 match state {
1561 Some(ThunkState::Done(boxed)) => {
1562 let inner = *boxed.clone();
1563 thunk.state.set(Some(ThunkState::Done(boxed)));
1564 match &inner {
1566 VMValue::Thunk(_) => Some(try_unwrap_done_thunk(&inner)
1567 .unwrap_or(Ok(inner))),
1568 _ => Some(Ok(inner)),
1569 }
1570 }
1571 other => {
1572 thunk.state.set(other);
1573 Some(Err(VMError::TypeError {
1574 expected: "concrete value",
1575 got: "thunk (pending)",
1576 context: "builtin argument (thunk needs VM to force)".to_string(),
1577 }))
1578 }
1579 }
1580 }
1581 _ => None, }
1583}
1584
1585fn as_list(v: &VMValue) -> Result<Vec<VMValue>, VMError> {
1588 match v {
1589 VMValue::List(l) => Ok(l.clone()),
1590 VMValue::Thunk(_) => {
1591 let forced = force_vmvalue(v.clone())?;
1592 match forced {
1593 VMValue::List(l) => Ok(l),
1594 other => Err(VMError::TypeError {
1595 expected: "list",
1596 got: other.type_name(),
1597 context: "builtin argument".to_string(),
1598 }),
1599 }
1600 }
1601 other => Err(VMError::TypeError {
1602 expected: "list",
1603 got: other.type_name(),
1604 context: "builtin argument".to_string(),
1605 }),
1606 }
1607}
1608
1609fn force_vmvalue(v: VMValue) -> Result<VMValue, VMError> {
1613 match v {
1614 VMValue::Thunk(ref thunk) => {
1615 let state = thunk.state.take();
1616 match state {
1617 Some(ThunkState::Done(boxed)) => {
1618 let inner = *boxed.clone();
1619 thunk.state.set(Some(ThunkState::Done(boxed)));
1620 force_vmvalue(inner) }
1622 Some(ThunkState::NativeCallback(cb)) => {
1623 thunk.state.set(Some(ThunkState::Evaluating));
1624 match cb() {
1625 Ok(sk_val) => {
1626 let result = sk_to_vmvalue(&sk_val);
1628 thunk.state.set(Some(ThunkState::Done(Box::new(result.clone()))));
1629 force_vmvalue(result)
1630 }
1631 Err(e) => {
1632 thunk.state.set(Some(ThunkState::NativeCallback(cb)));
1633 Err(VMError::Throw(e))
1634 }
1635 }
1636 }
1637 other => {
1638 thunk.state.set(other);
1639 Err(VMError::TypeError {
1641 expected: "concrete value",
1642 got: "thunk (pending)",
1643 context: "builtin argument (thunk needs VM to force)".to_string(),
1644 })
1645 }
1646 }
1647 }
1648 other => Ok(other),
1649 }
1650}
1651
1652fn sk_to_vmvalue(sk: &crate::value::StringKeyedValue) -> VMValue {
1654 use crate::value::StringKeyedValue;
1655 match sk {
1656 StringKeyedValue::Null => VMValue::Null,
1657 StringKeyedValue::Bool(b) => VMValue::Bool(*b),
1658 StringKeyedValue::Int(n) => VMValue::Int(*n),
1659 StringKeyedValue::Float(f) => VMValue::Float(*f),
1660 StringKeyedValue::String(s) => VMValue::String(s.clone()),
1661 StringKeyedValue::Path(p) => VMValue::Path(p.clone()),
1662 StringKeyedValue::List(items) => {
1663 VMValue::List(items.iter().map(|i| sk_to_vmvalue(i)).collect())
1664 }
1665 StringKeyedValue::Attrs(map) => {
1666 let mut interner = crate::intern::Interner::new();
1668 VMValue::Attrs(map.iter().map(|(k, v)| {
1669 (interner.intern(k), sk_to_vmvalue(v))
1670 }).collect())
1671 }
1672 StringKeyedValue::Lambda => VMValue::Null, StringKeyedValue::Thunk(cb) => {
1674 let cb = cb.clone();
1676 VMValue::Thunk(crate::value::VMThunk {
1677 state: Rc::new(Cell::new(Some(ThunkState::NativeCallback(cb)))),
1678 })
1679 }
1680 StringKeyedValue::Callable(_) => VMValue::Null, }
1682}
1683
1684fn as_attrs(v: &VMValue) -> Result<&BTreeMap<Symbol, VMValue>, VMError> {
1685 match v {
1686 VMValue::Attrs(a) => Ok(a),
1687 VMValue::Thunk(_) => Err(VMError::TypeError {
1688 expected: "set",
1689 got: "thunk",
1690 context: "builtin argument".to_string(),
1691 }),
1692 other => Err(VMError::TypeError {
1693 expected: "set",
1694 got: other.type_name(),
1695 context: "builtin argument".to_string(),
1696 }),
1697 }
1698}
1699
1700fn as_string(v: &VMValue) -> Result<&str, VMError> {
1701 match v {
1702 VMValue::String(s) => Ok(s),
1703 other => Err(VMError::TypeError {
1704 expected: "string",
1705 got: other.type_name(),
1706 context: "builtin argument".to_string(),
1707 }),
1708 }
1709}
1710
1711fn force_as_string(v: &VMValue) -> Result<String, VMError> {
1714 match v {
1715 VMValue::String(s) => Ok(s.clone()),
1716 VMValue::Thunk(_) => {
1717 let forced = force_vmvalue(v.clone())?;
1718 match forced {
1719 VMValue::String(s) => Ok(s),
1720 other => Err(VMError::TypeError {
1721 expected: "string",
1722 got: other.type_name(),
1723 context: "builtin argument (after forcing thunk)".to_string(),
1724 }),
1725 }
1726 }
1727 other => Err(VMError::TypeError {
1728 expected: "string",
1729 got: other.type_name(),
1730 context: "builtin argument".to_string(),
1731 }),
1732 }
1733}
1734
1735fn force_as_list(v: &VMValue) -> Result<Vec<VMValue>, VMError> {
1737 match v {
1738 VMValue::List(l) => Ok(l.clone()),
1739 VMValue::Thunk(_) => {
1740 let forced = force_vmvalue(v.clone())?;
1741 match forced {
1742 VMValue::List(l) => Ok(l),
1743 other => Err(VMError::TypeError {
1744 expected: "list",
1745 got: other.type_name(),
1746 context: "builtin argument (after forcing thunk)".to_string(),
1747 }),
1748 }
1749 }
1750 other => Err(VMError::TypeError {
1751 expected: "list",
1752 got: other.type_name(),
1753 context: "builtin argument".to_string(),
1754 }),
1755 }
1756}
1757
1758fn as_int(v: &VMValue) -> Result<i64, VMError> {
1759 match v {
1760 VMValue::Int(n) => Ok(*n),
1761 other => Err(VMError::TypeError {
1762 expected: "int",
1763 got: other.type_name(),
1764 context: "builtin argument".to_string(),
1765 }),
1766 }
1767}
1768
1769fn as_float(v: &VMValue) -> Result<f64, VMError> {
1770 match v {
1771 VMValue::Float(f) => Ok(*f),
1772 VMValue::Int(n) => Ok(*n as f64),
1773 other => Err(VMError::TypeError {
1774 expected: "float",
1775 got: other.type_name(),
1776 context: "builtin argument".to_string(),
1777 }),
1778 }
1779}
1780
1781fn vm_coerce_to_string(v: &VMValue) -> Result<VMValue, VMError> {
1788 match v {
1789 VMValue::String(s) => Ok(VMValue::String(s.clone())),
1790 VMValue::Int(n) => Ok(VMValue::String(n.to_string())),
1791 VMValue::Float(f) => Ok(VMValue::String(format!("{f:.6}"))),
1793 VMValue::Bool(true) => Ok(VMValue::String("1".to_string())),
1794 VMValue::Bool(false) => Ok(VMValue::String(String::new())),
1795 VMValue::Null => Ok(VMValue::String(String::new())),
1796 VMValue::Path(p) => Ok(VMValue::String(p.clone())),
1797 VMValue::Attrs(attrs) => {
1798 let to_str_sym = crate::intern::intern("__toString");
1801 if attrs.contains_key(&to_str_sym) {
1802 return Err(VMError::Throw(
1806 "toString: __toString requires VM bridge".to_string(),
1807 ));
1808 }
1809 let out_path_sym = crate::intern::intern("outPath");
1810 if let Some(out_path) = attrs.get(&out_path_sym) {
1811 vm_coerce_to_string(out_path)
1812 } else {
1813 Err(VMError::Throw(
1814 "cannot coerce a set to a string, but it has no __toString or outPath".to_string(),
1815 ))
1816 }
1817 }
1818 VMValue::List(items) => {
1819 let mut parts = Vec::with_capacity(items.len());
1820 for item in items {
1821 match vm_coerce_to_string(item)? {
1822 VMValue::String(s) => parts.push(s),
1823 _ => unreachable!("vm_coerce_to_string always returns String"),
1824 }
1825 }
1826 Ok(VMValue::String(parts.join(" ")))
1827 }
1828 VMValue::Closure(_) | VMValue::Builtin(_) | VMValue::HigherOrderBuiltin(_) => {
1829 Err(VMError::Throw(
1830 "cannot coerce a function to a string".to_string(),
1831 ))
1832 }
1833 VMValue::Thunk(_) => {
1834 Err(VMError::Throw(
1835 "toString: thunk should be forced first".to_string(),
1836 ))
1837 }
1838 }
1839}
1840
1841fn vm_value_to_json(v: &VMValue) -> Result<serde_json::Value, VMError> {
1843 match v {
1844 VMValue::Null => Ok(serde_json::Value::Null),
1845 VMValue::Bool(b) => Ok(serde_json::Value::Bool(*b)),
1846 VMValue::Int(n) => Ok(serde_json::Value::Number(
1847 serde_json::Number::from(*n),
1848 )),
1849 VMValue::Float(f) => serde_json::Number::from_f64(*f)
1850 .map(serde_json::Value::Number)
1851 .ok_or_else(|| VMError::Throw("toJSON: invalid float".to_string())),
1852 VMValue::String(s) => Ok(serde_json::Value::String(s.clone())),
1853 VMValue::Path(p) => Ok(serde_json::Value::String(p.clone())),
1854 VMValue::List(items) => {
1855 let arr: Result<Vec<_>, _> = items.iter().map(vm_value_to_json).collect();
1856 Ok(serde_json::Value::Array(arr?))
1857 }
1858 VMValue::Attrs(_) => {
1859 Err(VMError::Throw(
1861 "toJSON: attrset conversion requires interner".to_string(),
1862 ))
1863 }
1864 VMValue::Closure(_) | VMValue::Builtin(_) | VMValue::HigherOrderBuiltin(_) => {
1865 Err(VMError::Throw("toJSON: cannot convert function".to_string()))
1866 }
1867 VMValue::Thunk(_) => {
1868 Err(VMError::Throw("toJSON: thunk should be forced first".to_string()))
1869 }
1870 }
1871}
1872
1873fn json_to_vm_value(v: &serde_json::Value) -> VMValue {
1875 match v {
1876 serde_json::Value::Null => VMValue::Null,
1877 serde_json::Value::Bool(b) => VMValue::Bool(*b),
1878 serde_json::Value::Number(n) => {
1879 if let Some(i) = n.as_i64() {
1880 VMValue::Int(i)
1881 } else {
1882 VMValue::Float(n.as_f64().unwrap_or(0.0))
1883 }
1884 }
1885 serde_json::Value::String(s) => VMValue::String(s.clone()),
1886 serde_json::Value::Array(arr) => {
1887 VMValue::List(arr.iter().map(json_to_vm_value).collect())
1888 }
1889 serde_json::Value::Object(_) => {
1890 VMValue::Null
1893 }
1894 }
1895}
1896
1897#[cfg(test)]
1898mod tests {
1899 use super::*;
1900
1901 #[test]
1902 fn registry_has_builtins() {
1903 let reg = BuiltinRegistry::new();
1904 assert!(reg.lookup("length").is_some());
1905 assert!(reg.lookup("typeOf").is_some());
1906 assert!(reg.lookup("head").is_some());
1907 assert!(reg.lookup("tail").is_some());
1908 assert!(reg.lookup("throw").is_some());
1909 assert!(reg.lookup("nonexistent").is_none());
1910 }
1911
1912 #[test]
1913 fn call_length() {
1914 let reg = BuiltinRegistry::new();
1915 let idx = reg.lookup("length").unwrap();
1916 let result = reg
1917 .call(idx, vec![VMValue::List(vec![VMValue::Int(1), VMValue::Int(2)])])
1918 .unwrap();
1919 assert_eq!(result, VMValue::Int(2));
1920 }
1921
1922 #[test]
1923 fn call_head() {
1924 let reg = BuiltinRegistry::new();
1925 let idx = reg.lookup("head").unwrap();
1926 let result = reg
1927 .call(idx, vec![VMValue::List(vec![VMValue::Int(10)])])
1928 .unwrap();
1929 assert_eq!(result, VMValue::Int(10));
1930 }
1931
1932 #[test]
1933 fn call_head_empty() {
1934 let reg = BuiltinRegistry::new();
1935 let idx = reg.lookup("head").unwrap();
1936 let result = reg.call(idx, vec![VMValue::List(vec![])]);
1937 assert!(result.is_err());
1938 }
1939
1940 #[test]
1941 fn call_type_of() {
1942 let reg = BuiltinRegistry::new();
1943 let idx = reg.lookup("typeOf").unwrap();
1944 assert_eq!(
1945 reg.call(idx, vec![VMValue::Int(42)]).unwrap(),
1946 VMValue::String("int".to_string())
1947 );
1948 assert_eq!(
1949 reg.call(idx, vec![VMValue::String("hello".to_string())])
1950 .unwrap(),
1951 VMValue::String("string".to_string())
1952 );
1953 }
1954
1955 #[test]
1956 fn call_string_length() {
1957 let reg = BuiltinRegistry::new();
1958 let idx = reg.lookup("stringLength").unwrap();
1959 let result = reg
1960 .call(idx, vec![VMValue::String("hello".to_string())])
1961 .unwrap();
1962 assert_eq!(result, VMValue::Int(5));
1963 }
1964
1965 #[test]
1966 fn call_throw() {
1967 let reg = BuiltinRegistry::new();
1968 let idx = reg.lookup("throw").unwrap();
1969 let result = reg.call(idx, vec![VMValue::String("test error".to_string())]);
1970 assert!(matches!(result, Err(VMError::Throw(_))));
1971 }
1972
1973 #[test]
1974 fn call_to_string() {
1975 let reg = BuiltinRegistry::new();
1976 let idx = reg.lookup("toString").unwrap();
1977 assert_eq!(
1978 reg.call(idx, vec![VMValue::Int(42)]).unwrap(),
1979 VMValue::String("42".to_string())
1980 );
1981 assert_eq!(
1982 reg.call(idx, vec![VMValue::Bool(true)]).unwrap(),
1983 VMValue::String("1".to_string())
1984 );
1985 }
1986
1987 #[test]
1988 fn builtins_attrset() {
1989 let reg = BuiltinRegistry::new();
1990 let mut interner = Interner::new();
1991 let builtins = reg.make_builtins_attrset(&mut interner);
1992 match &builtins {
1993 VMValue::Attrs(attrs) => {
1994 let length_sym = interner.lookup("length").unwrap();
1995 assert!(attrs.contains_key(&length_sym));
1996 }
1997 _ => panic!("expected Attrs"),
1998 }
1999 }
2000}