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");
91 attrs.insert(ver_sym, VMValue::String("2.24.0".to_string()));
92
93 let lang_sym = interner.intern("langVersion");
95 attrs.insert(lang_sym, VMValue::Int(6));
96
97 let true_sym = interner.intern("true");
99 attrs.insert(true_sym, VMValue::Bool(true));
100 let false_sym = interner.intern("false");
101 attrs.insert(false_sym, VMValue::Bool(false));
102 let null_sym = interner.intern("null");
103 attrs.insert(null_sym, VMValue::Null);
104
105 let store_sym = interner.intern("storeDir");
107 attrs.insert(store_sym, VMValue::String("/nix/store".to_string()));
108
109 let nixpath_sym = interner.intern("nixPath");
111 let nix_path_list = {
112 let nix_path = std::env::var("NIX_PATH").unwrap_or_default();
113 let entries: Vec<VMValue> = nix_path
114 .split(':')
115 .filter(|s| !s.is_empty())
116 .map(|entry| {
117 let (prefix, path) = if let Some(idx) = entry.find('=') {
118 (entry[..idx].to_string(), entry[idx + 1..].to_string())
119 } else {
120 (String::new(), entry.to_string())
121 };
122 let prefix_sym = interner.intern("prefix");
123 let path_sym = interner.intern("path");
124 let mut entry_attrs = BTreeMap::new();
125 entry_attrs.insert(prefix_sym, VMValue::String(prefix));
126 entry_attrs.insert(path_sym, VMValue::String(path));
127 VMValue::Attrs(entry_attrs)
128 })
129 .collect();
130 VMValue::List(entries)
131 };
132 attrs.insert(nixpath_sym, nix_path_list);
133
134 let time_sym = interner.intern("currentTime");
136 attrs.insert(time_sym, VMValue::Int(0));
137
138 VMValue::Attrs(attrs)
139 }
140
141 #[must_use]
143 pub fn name(&self, index: u16) -> Option<&'static str> {
144 self.entries.get(index as usize).map(|e| e.name)
145 }
146
147 fn register(
148 &mut self,
149 name: &'static str,
150 arity: u8,
151 func: impl Fn(Vec<VMValue>) -> Result<VMValue, VMError> + 'static,
152 ) {
153 self.entries.push(BuiltinEntry {
154 name,
155 func: Rc::new(func),
156 arity,
157 });
158 }
159
160 fn register_all(&mut self) {
161 self.register_type_checks();
162 self.register_list_ops();
163 self.register_higher_order_ops();
164 self.register_attrset_ops();
165 self.register_string_ops();
166 self.register_conversion_ops();
167 self.register_control_ops();
168 self.register_arithmetic_ops();
169 self.register_derivation_ops();
170 self.register_missing_builtins();
171 }
172
173 fn register_type_checks(&mut self) {
176 self.register("typeOf", 1, |args| {
177 let name = match &args[0] {
178 VMValue::Null => "null",
179 VMValue::Bool(_) => "bool",
180 VMValue::Int(_) => "int",
181 VMValue::Float(_) => "float",
182 VMValue::String(_) => "string",
183 VMValue::Path(_) => "path",
184 VMValue::List(_) => "list",
185 VMValue::Attrs(_) => "set",
186 VMValue::Closure(_) | VMValue::Builtin(_) | VMValue::HigherOrderBuiltin(_) => "lambda",
187 VMValue::Thunk(_) => "thunk",
188 };
189 Ok(VMValue::String(name.to_string()))
190 });
191 self.register("isNull", 1, |args| {
192 Ok(VMValue::Bool(matches!(args[0], VMValue::Null)))
193 });
194 self.register("isInt", 1, |args| {
195 Ok(VMValue::Bool(matches!(args[0], VMValue::Int(_))))
196 });
197 self.register("isFloat", 1, |args| {
198 Ok(VMValue::Bool(matches!(args[0], VMValue::Float(_))))
199 });
200 self.register("isBool", 1, |args| {
201 Ok(VMValue::Bool(matches!(args[0], VMValue::Bool(_))))
202 });
203 self.register("isString", 1, |args| {
204 Ok(VMValue::Bool(matches!(args[0], VMValue::String(_))))
205 });
206 self.register("isList", 1, |args| {
207 Ok(VMValue::Bool(matches!(args[0], VMValue::List(_))))
208 });
209 self.register("isAttrs", 1, |args| {
210 Ok(VMValue::Bool(matches!(args[0], VMValue::Attrs(_))))
211 });
212 self.register("isFunction", 1, |args| {
213 Ok(VMValue::Bool(matches!(
214 args[0],
215 VMValue::Closure(_) | VMValue::Builtin(_) | VMValue::HigherOrderBuiltin(_)
216 )))
217 });
218 self.register("isPath", 1, |args| {
219 Ok(VMValue::Bool(matches!(args[0], VMValue::Path(_))))
220 });
221 }
222
223 fn register_list_ops(&mut self) {
226 self.register("length", 1, |args| {
227 let list = as_list(&args[0])?;
228 Ok(VMValue::Int(list.len() as i64))
229 });
230
231 self.register("head", 1, |args| {
232 let list = as_list(&args[0])?;
233 list.first()
234 .cloned()
235 .ok_or_else(|| VMError::Throw("head: empty list".to_string()))
236 });
237
238 self.register("tail", 1, |args| {
239 let list = as_list(&args[0])?;
240 if list.is_empty() {
241 return Err(VMError::Throw("tail: empty list".to_string()));
242 }
243 Ok(VMValue::List(list[1..].to_vec()))
244 });
245
246 self.register("elemAt", 1, |args| {
247 let list = as_list(&args[0])?.to_vec();
248 Ok(VMValue::Builtin(VMBuiltin {
249 name: "elemAt<partial>",
250 func: Rc::new(move |args2| {
251 let idx = as_int(&args2[0])? as usize;
252 list.get(idx).cloned().ok_or_else(|| {
253 VMError::Throw(format!("elemAt: index {idx} out of bounds"))
254 })
255 }),
256 arity: 1,
257 }))
258 });
259
260 self.register("elem", 1, |args| {
261 let needle = args[0].clone();
262 Ok(VMValue::HigherOrderBuiltin(HigherOrderBuiltin {
263 op: HigherOrderOp::Elem,
264 func: Box::new(needle),
265 extra_args: Vec::new(),
266 }))
267 });
268
269 self.register("genList", 1, |args| {
270 Ok(VMValue::HigherOrderBuiltin(HigherOrderBuiltin {
271 op: HigherOrderOp::GenList,
272 func: Box::new(args[0].clone()),
273 extra_args: Vec::new(),
274 }))
275 });
276
277 self.register("map", 1, |args| {
279 Ok(VMValue::HigherOrderBuiltin(HigherOrderBuiltin {
280 op: HigherOrderOp::Map,
281 func: Box::new(args[0].clone()),
282 extra_args: Vec::new(),
283 }))
284 });
285
286 self.register("filter", 1, |args| {
288 Ok(VMValue::HigherOrderBuiltin(HigherOrderBuiltin {
289 op: HigherOrderOp::Filter,
290 func: Box::new(args[0].clone()),
291 extra_args: Vec::new(),
292 }))
293 });
294
295 self.register("concatLists", 1, |args| {
296 let lists = as_list(&args[0])?;
297 let mut result = Vec::new();
298 for v in &lists {
299 let inner = as_list(v)?;
300 result.extend(inner);
301 }
302 Ok(VMValue::List(result))
303 });
304
305 self.register("sort", 1, |args| {
306 Ok(VMValue::HigherOrderBuiltin(HigherOrderBuiltin {
307 op: HigherOrderOp::Sort,
308 func: Box::new(args[0].clone()),
309 extra_args: Vec::new(),
310 }))
311 });
312 }
313
314
315 fn register_higher_order_ops(&mut self) {
318 self.register("foldl'", 1, |args| {
319 Ok(VMValue::HigherOrderBuiltin(HigherOrderBuiltin {
320 op: HigherOrderOp::FoldlP1,
321 func: Box::new(args[0].clone()),
322 extra_args: Vec::new(),
323 }))
324 });
325 self.register("concatMap", 1, |args| {
326 Ok(VMValue::HigherOrderBuiltin(HigherOrderBuiltin {
327 op: HigherOrderOp::ConcatMap,
328 func: Box::new(args[0].clone()),
329 extra_args: Vec::new(),
330 }))
331 });
332 self.register("any", 1, |args| {
333 Ok(VMValue::HigherOrderBuiltin(HigherOrderBuiltin {
334 op: HigherOrderOp::Any,
335 func: Box::new(args[0].clone()),
336 extra_args: Vec::new(),
337 }))
338 });
339 self.register("all", 1, |args| {
340 Ok(VMValue::HigherOrderBuiltin(HigherOrderBuiltin {
341 op: HigherOrderOp::All,
342 func: Box::new(args[0].clone()),
343 extra_args: Vec::new(),
344 }))
345 });
346 self.register("partition", 1, |args| {
347 Ok(VMValue::HigherOrderBuiltin(HigherOrderBuiltin {
348 op: HigherOrderOp::Partition,
349 func: Box::new(args[0].clone()),
350 extra_args: Vec::new(),
351 }))
352 });
353 self.register("groupBy", 1, |args| {
354 Ok(VMValue::HigherOrderBuiltin(HigherOrderBuiltin {
355 op: HigherOrderOp::GroupBy,
356 func: Box::new(args[0].clone()),
357 extra_args: Vec::new(),
358 }))
359 });
360 self.register("mapAttrs", 1, |args| {
361 Ok(VMValue::HigherOrderBuiltin(HigherOrderBuiltin {
362 op: HigherOrderOp::MapAttrs,
363 func: Box::new(args[0].clone()),
364 extra_args: Vec::new(),
365 }))
366 });
367 self.register("filterAttrs", 1, |args| {
368 Ok(VMValue::HigherOrderBuiltin(HigherOrderBuiltin {
369 op: HigherOrderOp::FilterAttrs,
370 func: Box::new(args[0].clone()),
371 extra_args: Vec::new(),
372 }))
373 });
374 self.register("functionArgs", 1, |args| {
375 match &args[0] {
376 VMValue::Builtin(_) | VMValue::HigherOrderBuiltin(_) => {
377 Ok(VMValue::Attrs(BTreeMap::new()))
378 }
379 VMValue::Closure(closure) => {
380 let mut result = BTreeMap::new();
381 let mut interner = crate::intern::Interner::new();
382 for (name, has_default) in &closure.formals {
383 let sym = interner.intern(name);
384 result.insert(sym, VMValue::Bool(*has_default));
385 }
386 Ok(VMValue::Attrs(result))
387 }
388 other => Err(VMError::TypeError {
389 expected: "lambda",
390 got: other.type_name(),
391 context: "functionArgs".to_string(),
392 }),
393 }
394 });
395 self.register("catAttrs", 1, |args| {
396 let name = as_string(&args[0])?.to_string();
397 Ok(VMValue::Builtin(VMBuiltin {
398 name: "catAttrs<partial>",
399 func: Rc::new(move |args2| {
400 let list = force_as_list(&args2[0])?;
401 let mut result: Vec<VMValue> = Vec::new();
402 for item in &list {
403 if let VMValue::Attrs(_) = item {
404 let _ = &name;
405 }
406 }
407 Err(VMError::Throw(
408 "catAttrs: requires interner access (use VM dispatch)".to_string(),
409 ))
410 }),
411 arity: 1,
412 }))
413 });
414 }
415
416 fn register_attrset_ops(&mut self) {
419 self.register("attrNames", 1, |args| {
420 let attrs = as_attrs(&args[0])?;
421 let _ = attrs;
425 Err(VMError::Throw(
426 "attrNames: requires interner access (use VM dispatch)".to_string(),
427 ))
428 });
429
430 self.register("attrValues", 1, |_args| {
444 Err(VMError::Throw(
445 "attrValues: requires interner access (use VM dispatch)".to_string(),
446 ))
447 });
448
449 self.register("hasAttr", 1, |args| {
450 let name = as_string(&args[0])?.to_string();
451 Ok(VMValue::Builtin(VMBuiltin {
452 name: "hasAttr<partial>",
453 func: Rc::new(move |_args2| {
454 let _ = &name;
456 Err(VMError::Throw(
457 "hasAttr: requires interner access (use VM dispatch)".to_string(),
458 ))
459 }),
460 arity: 1,
461 }))
462 });
463
464 self.register("getAttr", 1, |args| {
465 let name = as_string(&args[0])?.to_string();
466 Ok(VMValue::Builtin(VMBuiltin {
467 name: "getAttr<partial>",
468 func: Rc::new(move |_args2| {
469 let _ = &name;
470 Err(VMError::Throw(
471 "getAttr: requires interner access (use VM dispatch)".to_string(),
472 ))
473 }),
474 arity: 1,
475 }))
476 });
477
478 self.register("intersectAttrs", 1, |args| {
479 let a_attrs = as_attrs(&args[0])?.clone();
480 Ok(VMValue::Builtin(VMBuiltin {
481 name: "intersectAttrs<partial>",
482 func: Rc::new(move |args2| {
483 let b_attrs = as_attrs(&args2[0])?;
484 let mut result = BTreeMap::new();
485 for (k, v) in b_attrs {
486 if a_attrs.contains_key(k) {
487 result.insert(*k, v.clone());
488 }
489 }
490 Ok(VMValue::Attrs(result))
491 }),
492 arity: 1,
493 }))
494 });
495
496 self.register("removeAttrs", 1, |args| {
497 let set = as_attrs(&args[0])?.clone();
498 Ok(VMValue::Builtin(VMBuiltin {
499 name: "removeAttrs<partial>",
500 func: Rc::new(move |_args2| {
501 let _ = &set;
503 Err(VMError::Throw(
504 "removeAttrs: requires interner access".to_string(),
505 ))
506 }),
507 arity: 1,
508 }))
509 });
510
511 self.register("listToAttrs", 1, |_args| {
512 Err(VMError::Throw(
513 "listToAttrs: requires interner access".to_string(),
514 ))
515 });
516 }
517
518 fn register_string_ops(&mut self) {
521 self.register("stringLength", 1, |args| {
522 let s = as_string(&args[0])?;
523 Ok(VMValue::Int(s.len() as i64))
524 });
525
526 self.register("substring", 1, |args| {
527 let start_i = as_int(&args[0])?;
540 Ok(VMValue::Builtin(VMBuiltin {
541 name: "substring<p1>",
542 func: Rc::new(move |args2| {
543 let len_i = as_int(&args2[0])?;
544 Ok(VMValue::Builtin(VMBuiltin {
545 name: "substring<p2>",
546 func: Rc::new(move |args3| {
547 let s = as_string(&args3[0])?;
548 if start_i < 0 {
549 return Err(VMError::Throw(
550 "substring: negative start position".to_string(),
551 ));
552 }
553 let s_len = s.len();
554 let start = (start_i as usize).min(s_len);
555 let end = if len_i < 0 {
556 s_len
557 } else {
558 start.saturating_add(len_i as usize).min(s_len)
559 };
560 Ok(VMValue::String(s[start..end].to_string()))
561 }),
562 arity: 1,
563 }))
564 }),
565 arity: 1,
566 }))
567 });
568
569 self.register("concatStringsSep", 1, |args| {
570 let sep = as_string(&args[0])?.to_string();
571 Ok(VMValue::Builtin(VMBuiltin {
572 name: "concatStringsSep<partial>",
573 func: Rc::new(move |args2| {
574 let list = force_as_list(&args2[0])?;
575 let strings: Result<Vec<String>, _> =
576 list.iter().map(|v| force_as_string(v)).collect();
577 Ok(VMValue::String(strings?.join(&sep)))
578 }),
579 arity: 1,
580 }))
581 });
582
583 self.register("replaceStrings", 1, |args| {
584 let from: Vec<String> = force_as_list(&args[0])?
585 .iter()
586 .map(|v| force_as_string(v))
587 .collect::<Result<_, _>>()?;
588 Ok(VMValue::Builtin(VMBuiltin {
589 name: "replaceStrings<p1>",
590 func: Rc::new(move |args2| {
591 let to: Vec<String> = force_as_list(&args2[0])?
592 .iter()
593 .map(|v| force_as_string(v))
594 .collect::<Result<_, _>>()?;
595 let from2 = from.clone();
596 Ok(VMValue::Builtin(VMBuiltin {
597 name: "replaceStrings<p2>",
598 func: Rc::new(move |args3| {
599 let mut s = as_string(&args3[0])?.to_string();
600 for (f, t) in from2.iter().zip(to.iter()) {
601 if !f.is_empty() {
602 s = s.replace(f.as_str(), t);
603 }
604 }
605 Ok(VMValue::String(s))
606 }),
607 arity: 1,
608 }))
609 }),
610 arity: 1,
611 }))
612 });
613
614 self.register("hasPrefix", 1, |args| {
615 let prefix = as_string(&args[0])?.to_string();
616 Ok(VMValue::Builtin(VMBuiltin {
617 name: "hasPrefix<partial>",
618 func: Rc::new(move |args2| {
619 let s = as_string(&args2[0])?;
620 Ok(VMValue::Bool(s.starts_with(&*prefix)))
621 }),
622 arity: 1,
623 }))
624 });
625
626 self.register("hasSuffix", 1, |args| {
627 let suffix = as_string(&args[0])?.to_string();
628 Ok(VMValue::Builtin(VMBuiltin {
629 name: "hasSuffix<partial>",
630 func: Rc::new(move |args2| {
631 let s = as_string(&args2[0])?;
632 Ok(VMValue::Bool(s.ends_with(&*suffix)))
633 }),
634 arity: 1,
635 }))
636 });
637
638 self.register("toLower", 1, |args| {
639 let s = as_string(&args[0])?;
640 Ok(VMValue::String(s.to_lowercase()))
641 });
642
643 self.register("toUpper", 1, |args| {
644 let s = as_string(&args[0])?;
645 Ok(VMValue::String(s.to_uppercase()))
646 });
647 }
648
649 fn register_conversion_ops(&mut self) {
652 self.register("toString", 1, |args| {
653 vm_coerce_to_string(&args[0])
654 });
655
656 self.register("toJSON", 1, |args| {
657 let json = vm_value_to_json(&args[0])?;
658 let s = serde_json::to_string(&json)
659 .unwrap_or_else(|_| "null".to_string());
660 Ok(VMValue::String(s))
661 });
662
663 self.register("fromJSON", 1, |args| {
664 let s = as_string(&args[0])?;
665 let json: serde_json::Value = serde_json::from_str(s).map_err(|e| {
666 VMError::Throw(format!("fromJSON: {e}"))
667 })?;
668 Ok(json_to_vm_value(&json))
669 });
670
671 self.register("toInt", 1, |args| {
672 let s = as_string(&args[0])?;
673 let n: i64 = s.trim().parse().map_err(|e| {
674 VMError::Throw(format!("toInt: {e}"))
675 })?;
676 Ok(VMValue::Int(n))
677 });
678 }
679
680 fn register_control_ops(&mut self) {
683 self.register("throw", 1, |args| {
684 let msg = as_string(&args[0])?;
685 Err(VMError::Throw(format!("throw: {msg}")))
686 });
687
688 self.register("abort", 1, |args| {
689 let msg = as_string(&args[0])?;
690 Err(VMError::Throw(format!("abort: {msg}")))
691 });
692
693 self.register("seq", 1, |args| {
694 let _forced = args[0].clone();
695 Ok(VMValue::Builtin(VMBuiltin {
696 name: "seq<partial>",
697 func: Rc::new(|args2| Ok(args2[0].clone())),
698 arity: 1,
699 }))
700 });
701
702 self.register("deepSeq", 1, |args| {
703 let _forced = args[0].clone();
704 Ok(VMValue::Builtin(VMBuiltin {
705 name: "deepSeq<partial>",
706 func: Rc::new(|args2| Ok(args2[0].clone())),
707 arity: 1,
708 }))
709 });
710
711 self.register("tryEval", 1, |args| {
712 let val = args[0].clone();
715 let _ = val;
719 Err(VMError::Throw(
720 "tryEval: requires VM-level implementation".to_string(),
721 ))
722 });
723
724 self.register("trace", 1, |args| {
725 let msg = args[0].clone();
726 eprintln!("trace: {msg}");
727 Ok(VMValue::Builtin(VMBuiltin {
728 name: "trace<partial>",
729 func: Rc::new(|args2| Ok(args2[0].clone())),
730 arity: 1,
731 }))
732 });
733 }
734
735 fn register_arithmetic_ops(&mut self) {
738 self.register("add", 1, |args| {
739 let a = args[0].clone();
740 Ok(VMValue::Builtin(VMBuiltin {
741 name: "add<partial>",
742 func: Rc::new(move |args2| match (&a, &args2[0]) {
743 (VMValue::Int(x), VMValue::Int(y)) => Ok(VMValue::Int(x + y)),
744 (VMValue::Float(x), VMValue::Float(y)) => Ok(VMValue::Float(x + y)),
745 (VMValue::Int(x), VMValue::Float(y)) => Ok(VMValue::Float(*x as f64 + y)),
746 (VMValue::Float(x), VMValue::Int(y)) => Ok(VMValue::Float(x + *y as f64)),
747 _ => Err(VMError::Throw("add: expected numbers".to_string())),
748 }),
749 arity: 1,
750 }))
751 });
752
753 self.register("sub", 1, |args| {
757 let a = args[0].clone();
758 Ok(VMValue::Builtin(VMBuiltin {
759 name: "sub<partial>",
760 func: Rc::new(move |args2| vm_numeric_binop("sub", &a, &args2[0], |x, y| x - y, |x, y| x - y)),
761 arity: 1,
762 }))
763 });
764
765 self.register("mul", 1, |args| {
766 let a = args[0].clone();
767 Ok(VMValue::Builtin(VMBuiltin {
768 name: "mul<partial>",
769 func: Rc::new(move |args2| vm_numeric_binop("mul", &a, &args2[0], |x, y| x * y, |x, y| x * y)),
770 arity: 1,
771 }))
772 });
773
774 self.register("div", 1, |args| {
775 let a = args[0].clone();
776 Ok(VMValue::Builtin(VMBuiltin {
777 name: "div<partial>",
778 func: Rc::new(move |args2| match (&a, &args2[0]) {
779 (VMValue::Int(_), VMValue::Int(0)) => Err(VMError::DivisionByZero),
780 (VMValue::Int(x), VMValue::Int(y)) => Ok(VMValue::Int(x / y)),
781 (VMValue::Float(x), VMValue::Float(y)) => Ok(VMValue::Float(x / y)),
782 (VMValue::Int(x), VMValue::Float(y)) => Ok(VMValue::Float(*x as f64 / *y)),
783 (VMValue::Float(x), VMValue::Int(y)) => Ok(VMValue::Float(*x / *y as f64)),
784 _ => Err(VMError::Throw("div: expected numbers".to_string())),
785 }),
786 arity: 1,
787 }))
788 });
789
790 self.register("ceil", 1, |args| {
791 let f = as_float(&args[0])?;
792 Ok(VMValue::Int(f.ceil() as i64))
793 });
794
795 self.register("floor", 1, |args| {
796 let f = as_float(&args[0])?;
797 Ok(VMValue::Int(f.floor() as i64))
798 });
799
800 self.register("bitAnd", 1, |args| {
801 let a = as_int(&args[0])?;
802 Ok(VMValue::Builtin(VMBuiltin {
803 name: "bitAnd<partial>",
804 func: Rc::new(move |args2| {
805 let b = as_int(&args2[0])?;
806 Ok(VMValue::Int(a & b))
807 }),
808 arity: 1,
809 }))
810 });
811
812 self.register("bitOr", 1, |args| {
813 let a = as_int(&args[0])?;
814 Ok(VMValue::Builtin(VMBuiltin {
815 name: "bitOr<partial>",
816 func: Rc::new(move |args2| {
817 let b = as_int(&args2[0])?;
818 Ok(VMValue::Int(a | b))
819 }),
820 arity: 1,
821 }))
822 });
823
824 self.register("bitXor", 1, |args| {
825 let a = as_int(&args[0])?;
826 Ok(VMValue::Builtin(VMBuiltin {
827 name: "bitXor<partial>",
828 func: Rc::new(move |args2| {
829 let b = as_int(&args2[0])?;
830 Ok(VMValue::Int(a ^ b))
831 }),
832 arity: 1,
833 }))
834 });
835 }
836
837 fn register_derivation_ops(&mut self) {
840 self.register("derivation", 1, |_args| {
845 Err(VMError::Throw(
846 "derivation: requires VM-level dispatch".to_string(),
847 ))
848 });
849 self.register("derivationStrict", 1, |_args| {
850 Err(VMError::Throw(
851 "derivationStrict: requires VM-level dispatch".to_string(),
852 ))
853 });
854 self.register("getFlake", 1, |_args| {
856 Err(VMError::Throw(
857 "getFlake: requires VM-level dispatch".to_string(),
858 ))
859 });
860 self.register("scopedImport", 1, |_args| {
862 Err(VMError::Throw(
863 "scopedImport: requires VM-level dispatch".to_string(),
864 ))
865 });
866
867 self.register("addErrorContext", 1, |args| {
871 Ok(VMValue::Builtin(VMBuiltin {
873 name: "addErrorContext<partial>",
874 func: Rc::new(move |inner_args: Vec<VMValue>| Ok(inner_args[0].clone())),
875 arity: 1,
876 }))
877 });
878
879 self.register("unsafeGetAttrPos", 1, |_args| {
881 Ok(VMValue::Builtin(VMBuiltin {
882 name: "unsafeGetAttrPos<partial>",
883 func: Rc::new(|_args: Vec<VMValue>| Ok(VMValue::Null)),
884 arity: 1,
885 }))
886 });
887
888 self.register("pathExists", 1, |args| {
890 let path = match &args[0] {
891 VMValue::Path(p) => p.clone(),
892 VMValue::String(s) => s.clone(),
893 other => {
894 return Err(VMError::TypeError {
895 expected: "path or string",
896 got: other.type_name(),
897 context: "pathExists".to_string(),
898 })
899 }
900 };
901 Ok(VMValue::Bool(std::path::Path::new(&path).exists()))
902 });
903
904 self.register("readFile", 1, |args| {
906 let path = match &args[0] {
907 VMValue::Path(p) => p.clone(),
908 VMValue::String(s) => s.clone(),
909 other => {
910 return Err(VMError::TypeError {
911 expected: "path or string",
912 got: other.type_name(),
913 context: "readFile".to_string(),
914 })
915 }
916 };
917 let content = std::fs::read_to_string(&path)
918 .map_err(|e| VMError::Throw(format!("readFile {path}: {e}")))?;
919 Ok(VMValue::String(content))
920 });
921
922 self.register("readDir", 1, |args| {
924 let path = match &args[0] {
925 VMValue::Path(p) => p.clone(),
926 VMValue::String(s) => s.clone(),
927 other => {
928 return Err(VMError::TypeError {
929 expected: "path or string",
930 got: other.type_name(),
931 context: "readDir".to_string(),
932 })
933 }
934 };
935 Err(VMError::Throw(
937 "readDir: requires VM-level dispatch for interner access".to_string(),
938 ))
939 });
940
941 self.register("baseNameOf", 1, |args| {
943 let path = match &args[0] {
944 VMValue::Path(p) => p.clone(),
945 VMValue::String(s) => s.clone(),
946 other => {
947 return Err(VMError::TypeError {
948 expected: "path or string",
949 got: other.type_name(),
950 context: "baseNameOf".to_string(),
951 })
952 }
953 };
954 let base = std::path::Path::new(&path)
955 .file_name()
956 .map(|f| f.to_string_lossy().to_string())
957 .unwrap_or_default();
958 Ok(VMValue::String(base))
959 });
960
961 self.register("dirOf", 1, |args| {
963 let path = match &args[0] {
964 VMValue::Path(p) => p.clone(),
965 VMValue::String(s) => s.clone(),
966 other => {
967 return Err(VMError::TypeError {
968 expected: "path or string",
969 got: other.type_name(),
970 context: "dirOf".to_string(),
971 })
972 }
973 };
974 let dir = std::path::Path::new(&path)
975 .parent()
976 .map(|p| p.to_string_lossy().to_string())
977 .unwrap_or_else(|| ".".to_string());
978 Ok(VMValue::String(dir))
979 });
980
981 self.register("genericClosure", 1, |_args| {
983 Err(VMError::Throw(
984 "genericClosure: requires VM-level dispatch".to_string(),
985 ))
986 });
987
988 self.register("placeholder", 1, |args| {
990 let output = match &args[0] {
991 VMValue::String(s) => s.clone(),
992 _ => "out".to_string(),
993 };
994 Ok(VMValue::String(format!("/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9/{output}")))
995 });
996
997 self.register("split", 1, |args| {
999 let _pattern = as_string(&args[0])?;
1000 Ok(VMValue::Builtin(VMBuiltin {
1001 name: "split<partial>",
1002 func: Rc::new(|_inner_args: Vec<VMValue>| {
1003 Err(VMError::Throw("split: requires VM-level dispatch".to_string()))
1004 }),
1005 arity: 1,
1006 }))
1007 });
1008
1009 self.register("match", 1, |args| {
1011 let _pattern = as_string(&args[0])?;
1012 Ok(VMValue::Builtin(VMBuiltin {
1013 name: "match<partial>",
1014 func: Rc::new(|_inner_args: Vec<VMValue>| {
1015 Err(VMError::Throw("match: requires VM-level dispatch".to_string()))
1016 }),
1017 arity: 1,
1018 }))
1019 });
1020
1021 self.register("fromTOML", 1, |args| {
1023 let s = as_string(&args[0])?;
1024 Err(VMError::Throw(format!("fromTOML: not yet implemented")))
1026 });
1027
1028 self.register("fetchurl", 1, |_args| {
1037 Err(VMError::Throw("fetchurl: not supported in eval mode".to_string()))
1038 });
1039 self.register("fetchTarball", 1, |_args| {
1040 Err(VMError::Throw("fetchTarball: not supported in eval mode".to_string()))
1041 });
1042 self.register("fetchGit", 1, |_args| {
1043 Err(VMError::Throw("fetchGit: not supported in eval mode".to_string()))
1044 });
1045 self.register("fetchTree", 1, |_args| {
1046 Err(VMError::Throw("fetchTree: not supported in eval mode".to_string()))
1047 });
1048 self.register("fetchMercurial", 1, |_args| {
1049 Err(VMError::Throw("fetchMercurial: not supported in eval mode".to_string()))
1050 });
1051
1052 self.register("toFile", 1, |_args| {
1054 Err(VMError::Throw("toFile: not supported in eval mode".to_string()))
1055 });
1056
1057 self.register("toPath", 1, |args| {
1059 let s = as_string(&args[0])?;
1060 Ok(VMValue::Path(s.to_string()))
1061 });
1062
1063 self.register("parseDrvName", 1, |args| {
1068 let name = as_string(&args[0])?;
1069 let mut split_pos = None;
1071 let bytes = name.as_bytes();
1072 for i in (0..bytes.len()).rev() {
1073 if bytes[i] == b'-' && i + 1 < bytes.len() && bytes[i + 1].is_ascii_digit() {
1074 split_pos = Some(i);
1075 break;
1076 }
1077 }
1078 match split_pos {
1079 Some(pos) => {
1080 Err(VMError::Throw(
1081 "parseDrvName: requires VM-level dispatch for interner access".to_string(),
1082 ))
1083 }
1084 None => {
1085 Err(VMError::Throw(
1086 "parseDrvName: requires VM-level dispatch for interner access".to_string(),
1087 ))
1088 }
1089 }
1090 });
1091
1092 self.register("compareVersions", 1, |args| {
1097 let a = as_string(&args[0])?.to_string();
1098 Ok(VMValue::Builtin(VMBuiltin {
1099 name: "compareVersions<partial>",
1100 func: Rc::new(move |inner_args: Vec<VMValue>| {
1101 let b = as_string(&inner_args[0])?;
1102 Ok(VMValue::Int(
1103 sui_compat::versions::compare_versions(&a, b),
1104 ))
1105 }),
1106 arity: 1,
1107 }))
1108 });
1109
1110 self.register("splitVersion", 1, |args| {
1113 let version = as_string(&args[0])?;
1114 let parts: Vec<VMValue> = sui_compat::versions::split_version(version)
1115 .into_iter()
1116 .map(VMValue::String)
1117 .collect();
1118 Ok(VMValue::List(parts))
1119 });
1120
1121 self.register("concatStrings", 1, |args| {
1123 let list = as_list(&args[0])?;
1124 let mut result = String::new();
1125 for item in &list {
1126 let item = force_vmvalue(item.clone()).unwrap_or_else(|_| item.clone());
1127 match &item {
1128 VMValue::String(s) => result.push_str(s),
1129 _ => {
1130 return Err(VMError::TypeError {
1131 expected: "string",
1132 got: item.type_name(),
1133 context: "concatStrings element".to_string(),
1134 })
1135 }
1136 }
1137 }
1138 Ok(VMValue::String(result))
1139 });
1140
1141 self.register("unsafeDiscardStringContext", 1, |args| {
1145 Ok(args[0].clone())
1147 });
1148 self.register("getContext", 1, |_args| {
1149 Err(VMError::Throw(
1152 "getContext: requires VM-level dispatch for interner access".to_string(),
1153 ))
1154 });
1155 self.register("appendContext", 1, |args| {
1156 Ok(VMValue::Builtin(VMBuiltin {
1158 name: "appendContext<partial>",
1159 func: Rc::new(move |inner_args: Vec<VMValue>| Ok(inner_args[0].clone())),
1160 arity: 1,
1161 }))
1162 });
1163 self.register("hasContext", 1, |_args| {
1164 Ok(VMValue::Bool(false))
1165 });
1166 self.register("unsafeDiscardOutputDependency", 1, |args| {
1167 Ok(args[0].clone())
1168 });
1169 self.register("addDrvOutputDependencies", 1, |args| {
1170 Ok(args[0].clone())
1171 });
1172
1173 self.register("storePath", 1, |args| {
1175 Ok(args[0].clone())
1176 });
1177 self.register("isStorePath", 1, |args| {
1178 let s = match &args[0] {
1179 VMValue::String(s) => s.as_str(),
1180 VMValue::Path(p) => p.as_str(),
1181 _ => return Ok(VMValue::Bool(false)),
1182 };
1183 Ok(VMValue::Bool(s.starts_with("/nix/store/")))
1184 });
1185 self.register("hashString", 1, |args| {
1186 let algo = as_string(&args[0])?.to_string();
1187 Ok(VMValue::Builtin(VMBuiltin {
1188 name: "hashString<partial>",
1189 func: Rc::new(move |inner_args: Vec<VMValue>| {
1190 let s = as_string(&inner_args[0])?;
1191 match algo.as_str() {
1192 "sha256" => {
1193 use sha2::{Sha256, Digest};
1194 let mut hasher = Sha256::new();
1195 hasher.update(s.as_bytes());
1196 let result = hasher.finalize();
1197 let hex: String = result
1198 .iter()
1199 .map(|b| format!("{b:02x}"))
1200 .collect();
1201 Ok(VMValue::String(hex))
1202 }
1203 _ => Err(VMError::Throw(format!("hashString: unsupported algorithm: {algo}")))
1204 }
1205 }),
1206 arity: 1,
1207 }))
1208 });
1209 self.register("hashFile", 1, |_args| {
1210 Err(VMError::Throw("hashFile: not supported in eval mode".to_string()))
1211 });
1212
1213 self.register("import", 1, |_args| {
1218 Err(VMError::Throw(
1219 "import: requires VM-level dispatch".to_string(),
1220 ))
1221 });
1222
1223 self.register("zipAttrsWith", 1, |_args| {
1225 Err(VMError::Throw(
1226 "zipAttrsWith: requires VM-level dispatch".to_string(),
1227 ))
1228 });
1229 }
1230
1231 fn register_missing_builtins(&mut self) {
1238 self.register("getEnv", 1, |args| {
1242 let name = as_string(&args[0])?;
1243 let val = std::env::var(name).unwrap_or_default();
1244 Ok(VMValue::String(val))
1245 });
1246
1247 self.register("readFileType", 1, |args| {
1249 let path = match &args[0] {
1250 VMValue::Path(p) => p.clone(),
1251 VMValue::String(s) => s.clone(),
1252 other => {
1253 return Err(VMError::TypeError {
1254 expected: "path or string",
1255 got: other.type_name(),
1256 context: "readFileType".to_string(),
1257 });
1258 }
1259 };
1260 match std::fs::symlink_metadata(&path) {
1261 Ok(meta) => {
1262 let kind = if meta.is_symlink() {
1263 "symlink"
1264 } else if meta.is_dir() {
1265 "directory"
1266 } else if meta.is_file() {
1267 "regular"
1268 } else {
1269 "unknown"
1270 };
1271 Ok(VMValue::String(kind.to_string()))
1272 }
1273 Err(e) => Err(VMError::Throw(format!("readFileType {path}: {e}"))),
1274 }
1275 });
1276
1277 self.register("findFile", 1, |args| {
1279 let search_path = as_list(&args[0])?.clone();
1280 Ok(VMValue::Builtin(VMBuiltin {
1281 name: "findFile<partial>",
1282 func: Rc::new(move |args2| {
1283 let name = as_string(&args2[0])?;
1284 for entry in &search_path {
1285 if let VMValue::Attrs(a) = entry {
1286 let _ = a;
1291 }
1292 }
1293 bridge_call("findFile", vec![
1295 VMValue::List(search_path.clone()),
1296 VMValue::String(name.to_string()),
1297 ])
1298 }),
1299 arity: 1,
1300 }))
1301 });
1302
1303 self.register("lessThan", 1, |args| {
1305 let a = args[0].clone();
1306 Ok(VMValue::Builtin(VMBuiltin {
1307 name: "lessThan<partial>",
1308 func: Rc::new(move |args2| match (&a, &args2[0]) {
1309 (VMValue::Int(x), VMValue::Int(y)) => Ok(VMValue::Bool(*x < *y)),
1310 (VMValue::Float(x), VMValue::Float(y)) => Ok(VMValue::Bool(*x < *y)),
1311 (VMValue::Int(x), VMValue::Float(y)) => {
1312 Ok(VMValue::Bool((*x as f64) < *y))
1313 }
1314 (VMValue::Float(x), VMValue::Int(y)) => {
1315 Ok(VMValue::Bool(*x < (*y as f64)))
1316 }
1317 (VMValue::String(x), VMValue::String(y)) => Ok(VMValue::Bool(*x < *y)),
1318 _ => Err(VMError::Throw(
1319 "lessThan: expected comparable types".to_string(),
1320 )),
1321 }),
1322 arity: 1,
1323 }))
1324 });
1325
1326 self.register("warn", 1, |args| {
1328 if let Ok(msg) = as_string(&args[0]) {
1329 eprintln!("evaluation warning: {msg}");
1330 }
1331 Ok(VMValue::Builtin(VMBuiltin {
1332 name: "warn<partial>",
1333 func: Rc::new(|args2| Ok(args2[0].clone())),
1334 arity: 1,
1335 }))
1336 });
1337
1338 self.register("traceVerbose", 1, |args| {
1340 if std::env::var("SUI_TRACE_VERBOSE").ok().as_deref() == Some("1") {
1341 eprintln!("trace: {}", args[0]);
1342 }
1343 Ok(VMValue::Builtin(VMBuiltin {
1344 name: "traceVerbose<partial>",
1345 func: Rc::new(|args2| Ok(args2[0].clone())),
1346 arity: 1,
1347 }))
1348 });
1349
1350 self.register("break", 1, |args| Ok(args[0].clone()));
1352
1353 for name in &["convertHash", "toXML", "toFile", "filterSource",
1372 "fetchClosure", "outputOf", "hashFile", "hashString"]
1373 {
1374 let n = (*name).to_string();
1375 self.register(name, 1, move |args| {
1376 bridge_call(&n, args.to_vec())
1377 });
1378 }
1379 }
1380}
1381
1382fn vm_numeric_binop(
1390 name: &'static str,
1391 a: &VMValue,
1392 b: &VMValue,
1393 int_op: impl Fn(i64, i64) -> i64,
1394 float_op: impl Fn(f64, f64) -> f64,
1395) -> Result<VMValue, VMError> {
1396 match (a, b) {
1397 (VMValue::Int(x), VMValue::Int(y)) => Ok(VMValue::Int(int_op(*x, *y))),
1398 (VMValue::Float(x), VMValue::Float(y)) => Ok(VMValue::Float(float_op(*x, *y))),
1399 (VMValue::Int(x), VMValue::Float(y)) => Ok(VMValue::Float(float_op(*x as f64, *y))),
1400 (VMValue::Float(x), VMValue::Int(y)) => Ok(VMValue::Float(float_op(*x, *y as f64))),
1401 _ => Err(VMError::Throw(format!("{name}: expected numbers"))),
1402 }
1403}
1404
1405fn bridge_call(name: &str, args: Vec<VMValue>) -> Result<VMValue, VMError> {
1406 use crate::intern::Interner;
1407 let tmp_interner = Interner::new();
1410 let sk_args: Vec<crate::value::StringKeyedValue> = args
1411 .iter()
1412 .map(|a| a.to_string_keyed(&tmp_interner))
1413 .collect();
1414
1415 match crate::bridge::call_builtin_bridge(name, sk_args) {
1416 Ok(Some(result)) => Ok(string_keyed_to_vmvalue(&result, &mut Interner::new())),
1417 Ok(None) => Err(VMError::Throw(format!(
1418 "builtin '{name}' requires bridge but no bridge is set"
1419 ))),
1420 Err(e) => Err(VMError::Throw(e)),
1421 }
1422}
1423
1424pub fn string_keyed_to_vmvalue(
1429 sk: &crate::value::StringKeyedValue,
1430 interner: &mut crate::intern::Interner,
1431) -> VMValue {
1432 use crate::value::StringKeyedValue;
1433 match sk {
1434 StringKeyedValue::Null => VMValue::Null,
1435 StringKeyedValue::Bool(b) => VMValue::Bool(*b),
1436 StringKeyedValue::Int(n) => VMValue::Int(*n),
1437 StringKeyedValue::Float(f) => VMValue::Float(*f),
1438 StringKeyedValue::String(s) => VMValue::String(s.clone()),
1439 StringKeyedValue::Path(p) => VMValue::Path(p.clone()),
1440 StringKeyedValue::List(items) => VMValue::List(
1441 items
1442 .iter()
1443 .map(|v| string_keyed_to_vmvalue(v, interner))
1444 .collect(),
1445 ),
1446 StringKeyedValue::Attrs(map) => {
1447 let mut attrs = BTreeMap::new();
1448 for (k, v) in map {
1449 let sym = interner.intern(k);
1450 attrs.insert(sym, string_keyed_to_vmvalue(v, interner));
1451 }
1452 VMValue::Attrs(attrs)
1453 }
1454 StringKeyedValue::Lambda => VMValue::Null,
1455 StringKeyedValue::Callable(cb) => {
1456 let cb_clone = Rc::clone(cb);
1457 VMValue::Builtin(crate::value::VMBuiltin {
1458 name: "<bridge-fn>",
1459 arity: 1,
1460 func: Rc::new(move |args: Vec<VMValue>| {
1461 let interner = crate::intern::Interner::new();
1462 let sk_arg = args.into_iter().next()
1463 .unwrap_or(VMValue::Null)
1464 .to_string_keyed(&interner);
1465 let sk_result = cb_clone(sk_arg)
1466 .map_err(|e| VMError::Throw(e))?;
1467 let mut tmp_interner = crate::intern::Interner::new();
1468 Ok(string_keyed_to_vmvalue(&sk_result, &mut tmp_interner))
1469 }),
1470 })
1471 }
1472 StringKeyedValue::Thunk(cb) => {
1473 let cb_clone = Rc::clone(cb);
1475 VMValue::Thunk(crate::value::VMThunk::new_native(move || {
1476 let sk_val = cb_clone().map_err(|e| VMError::Throw(e))?;
1477 let mut tmp = crate::intern::Interner::new();
1479 Ok(string_keyed_to_vmvalue(&sk_val, &mut tmp))
1480 }))
1481 }
1482 }
1483}
1484
1485impl Default for BuiltinRegistry {
1486 fn default() -> Self {
1487 Self::new()
1488 }
1489}
1490
1491fn try_unwrap_done_thunk(v: &VMValue) -> Option<Result<VMValue, VMError>> {
1498 match v {
1499 VMValue::Thunk(thunk) => {
1500 let state = thunk.state.take();
1501 match state {
1502 Some(ThunkState::Done(boxed)) => {
1503 let inner = *boxed.clone();
1504 thunk.state.set(Some(ThunkState::Done(boxed)));
1505 match &inner {
1507 VMValue::Thunk(_) => Some(try_unwrap_done_thunk(&inner)
1508 .unwrap_or(Ok(inner))),
1509 _ => Some(Ok(inner)),
1510 }
1511 }
1512 other => {
1513 thunk.state.set(other);
1514 Some(Err(VMError::TypeError {
1515 expected: "concrete value",
1516 got: "thunk (pending)",
1517 context: "builtin argument (thunk needs VM to force)".to_string(),
1518 }))
1519 }
1520 }
1521 }
1522 _ => None, }
1524}
1525
1526fn as_list(v: &VMValue) -> Result<Vec<VMValue>, VMError> {
1529 match v {
1530 VMValue::List(l) => Ok(l.clone()),
1531 VMValue::Thunk(_) => {
1532 let forced = force_vmvalue(v.clone())?;
1533 match forced {
1534 VMValue::List(l) => Ok(l),
1535 other => Err(VMError::TypeError {
1536 expected: "list",
1537 got: other.type_name(),
1538 context: "builtin argument".to_string(),
1539 }),
1540 }
1541 }
1542 other => Err(VMError::TypeError {
1543 expected: "list",
1544 got: other.type_name(),
1545 context: "builtin argument".to_string(),
1546 }),
1547 }
1548}
1549
1550fn force_vmvalue(v: VMValue) -> Result<VMValue, VMError> {
1554 match v {
1555 VMValue::Thunk(ref thunk) => {
1556 let state = thunk.state.take();
1557 match state {
1558 Some(ThunkState::Done(boxed)) => {
1559 let inner = *boxed.clone();
1560 thunk.state.set(Some(ThunkState::Done(boxed)));
1561 force_vmvalue(inner) }
1563 Some(ThunkState::NativeCallback(cb)) => {
1564 thunk.state.set(Some(ThunkState::Evaluating));
1565 match cb() {
1566 Ok(sk_val) => {
1567 let result = sk_to_vmvalue(&sk_val);
1569 thunk.state.set(Some(ThunkState::Done(Box::new(result.clone()))));
1570 force_vmvalue(result)
1571 }
1572 Err(e) => {
1573 thunk.state.set(Some(ThunkState::NativeCallback(cb)));
1574 Err(VMError::Throw(e))
1575 }
1576 }
1577 }
1578 other => {
1579 thunk.state.set(other);
1580 Err(VMError::TypeError {
1582 expected: "concrete value",
1583 got: "thunk (pending)",
1584 context: "builtin argument (thunk needs VM to force)".to_string(),
1585 })
1586 }
1587 }
1588 }
1589 other => Ok(other),
1590 }
1591}
1592
1593fn sk_to_vmvalue(sk: &crate::value::StringKeyedValue) -> VMValue {
1595 use crate::value::StringKeyedValue;
1596 match sk {
1597 StringKeyedValue::Null => VMValue::Null,
1598 StringKeyedValue::Bool(b) => VMValue::Bool(*b),
1599 StringKeyedValue::Int(n) => VMValue::Int(*n),
1600 StringKeyedValue::Float(f) => VMValue::Float(*f),
1601 StringKeyedValue::String(s) => VMValue::String(s.clone()),
1602 StringKeyedValue::Path(p) => VMValue::Path(p.clone()),
1603 StringKeyedValue::List(items) => {
1604 VMValue::List(items.iter().map(|i| sk_to_vmvalue(i)).collect())
1605 }
1606 StringKeyedValue::Attrs(map) => {
1607 let mut interner = crate::intern::Interner::new();
1609 VMValue::Attrs(map.iter().map(|(k, v)| {
1610 (interner.intern(k), sk_to_vmvalue(v))
1611 }).collect())
1612 }
1613 StringKeyedValue::Lambda => VMValue::Null, StringKeyedValue::Thunk(cb) => {
1615 let cb = cb.clone();
1617 VMValue::Thunk(crate::value::VMThunk {
1618 state: Rc::new(Cell::new(Some(ThunkState::NativeCallback(cb)))),
1619 })
1620 }
1621 StringKeyedValue::Callable(_) => VMValue::Null, }
1623}
1624
1625fn as_attrs(v: &VMValue) -> Result<&BTreeMap<Symbol, VMValue>, VMError> {
1626 match v {
1627 VMValue::Attrs(a) => Ok(a),
1628 VMValue::Thunk(_) => Err(VMError::TypeError {
1629 expected: "set",
1630 got: "thunk",
1631 context: "builtin argument".to_string(),
1632 }),
1633 other => Err(VMError::TypeError {
1634 expected: "set",
1635 got: other.type_name(),
1636 context: "builtin argument".to_string(),
1637 }),
1638 }
1639}
1640
1641fn as_string(v: &VMValue) -> Result<&str, VMError> {
1642 match v {
1643 VMValue::String(s) => Ok(s),
1644 other => Err(VMError::TypeError {
1645 expected: "string",
1646 got: other.type_name(),
1647 context: "builtin argument".to_string(),
1648 }),
1649 }
1650}
1651
1652fn force_as_string(v: &VMValue) -> Result<String, VMError> {
1655 match v {
1656 VMValue::String(s) => Ok(s.clone()),
1657 VMValue::Thunk(_) => {
1658 let forced = force_vmvalue(v.clone())?;
1659 match forced {
1660 VMValue::String(s) => Ok(s),
1661 other => Err(VMError::TypeError {
1662 expected: "string",
1663 got: other.type_name(),
1664 context: "builtin argument (after forcing thunk)".to_string(),
1665 }),
1666 }
1667 }
1668 other => Err(VMError::TypeError {
1669 expected: "string",
1670 got: other.type_name(),
1671 context: "builtin argument".to_string(),
1672 }),
1673 }
1674}
1675
1676fn force_as_list(v: &VMValue) -> Result<Vec<VMValue>, VMError> {
1678 match v {
1679 VMValue::List(l) => Ok(l.clone()),
1680 VMValue::Thunk(_) => {
1681 let forced = force_vmvalue(v.clone())?;
1682 match forced {
1683 VMValue::List(l) => Ok(l),
1684 other => Err(VMError::TypeError {
1685 expected: "list",
1686 got: other.type_name(),
1687 context: "builtin argument (after forcing thunk)".to_string(),
1688 }),
1689 }
1690 }
1691 other => Err(VMError::TypeError {
1692 expected: "list",
1693 got: other.type_name(),
1694 context: "builtin argument".to_string(),
1695 }),
1696 }
1697}
1698
1699fn as_int(v: &VMValue) -> Result<i64, VMError> {
1700 match v {
1701 VMValue::Int(n) => Ok(*n),
1702 other => Err(VMError::TypeError {
1703 expected: "int",
1704 got: other.type_name(),
1705 context: "builtin argument".to_string(),
1706 }),
1707 }
1708}
1709
1710fn as_float(v: &VMValue) -> Result<f64, VMError> {
1711 match v {
1712 VMValue::Float(f) => Ok(*f),
1713 VMValue::Int(n) => Ok(*n as f64),
1714 other => Err(VMError::TypeError {
1715 expected: "float",
1716 got: other.type_name(),
1717 context: "builtin argument".to_string(),
1718 }),
1719 }
1720}
1721
1722fn vm_coerce_to_string(v: &VMValue) -> Result<VMValue, VMError> {
1729 match v {
1730 VMValue::String(s) => Ok(VMValue::String(s.clone())),
1731 VMValue::Int(n) => Ok(VMValue::String(n.to_string())),
1732 VMValue::Float(f) => Ok(VMValue::String(format!("{f:.6}"))),
1734 VMValue::Bool(true) => Ok(VMValue::String("1".to_string())),
1735 VMValue::Bool(false) => Ok(VMValue::String(String::new())),
1736 VMValue::Null => Ok(VMValue::String(String::new())),
1737 VMValue::Path(p) => Ok(VMValue::String(p.clone())),
1738 VMValue::Attrs(attrs) => {
1739 let to_str_sym = crate::intern::intern("__toString");
1742 if attrs.contains_key(&to_str_sym) {
1743 return Err(VMError::Throw(
1747 "toString: __toString requires VM bridge".to_string(),
1748 ));
1749 }
1750 let out_path_sym = crate::intern::intern("outPath");
1751 if let Some(out_path) = attrs.get(&out_path_sym) {
1752 vm_coerce_to_string(out_path)
1753 } else {
1754 Err(VMError::Throw(
1755 "cannot coerce a set to a string, but it has no __toString or outPath".to_string(),
1756 ))
1757 }
1758 }
1759 VMValue::List(items) => {
1760 let mut parts = Vec::with_capacity(items.len());
1761 for item in items {
1762 match vm_coerce_to_string(item)? {
1763 VMValue::String(s) => parts.push(s),
1764 _ => unreachable!("vm_coerce_to_string always returns String"),
1765 }
1766 }
1767 Ok(VMValue::String(parts.join(" ")))
1768 }
1769 VMValue::Closure(_) | VMValue::Builtin(_) | VMValue::HigherOrderBuiltin(_) => {
1770 Err(VMError::Throw(
1771 "cannot coerce a function to a string".to_string(),
1772 ))
1773 }
1774 VMValue::Thunk(_) => {
1775 Err(VMError::Throw(
1776 "toString: thunk should be forced first".to_string(),
1777 ))
1778 }
1779 }
1780}
1781
1782fn vm_value_to_json(v: &VMValue) -> Result<serde_json::Value, VMError> {
1784 match v {
1785 VMValue::Null => Ok(serde_json::Value::Null),
1786 VMValue::Bool(b) => Ok(serde_json::Value::Bool(*b)),
1787 VMValue::Int(n) => Ok(serde_json::Value::Number(
1788 serde_json::Number::from(*n),
1789 )),
1790 VMValue::Float(f) => serde_json::Number::from_f64(*f)
1791 .map(serde_json::Value::Number)
1792 .ok_or_else(|| VMError::Throw("toJSON: invalid float".to_string())),
1793 VMValue::String(s) => Ok(serde_json::Value::String(s.clone())),
1794 VMValue::Path(p) => Ok(serde_json::Value::String(p.clone())),
1795 VMValue::List(items) => {
1796 let arr: Result<Vec<_>, _> = items.iter().map(vm_value_to_json).collect();
1797 Ok(serde_json::Value::Array(arr?))
1798 }
1799 VMValue::Attrs(_) => {
1800 Err(VMError::Throw(
1802 "toJSON: attrset conversion requires interner".to_string(),
1803 ))
1804 }
1805 VMValue::Closure(_) | VMValue::Builtin(_) | VMValue::HigherOrderBuiltin(_) => {
1806 Err(VMError::Throw("toJSON: cannot convert function".to_string()))
1807 }
1808 VMValue::Thunk(_) => {
1809 Err(VMError::Throw("toJSON: thunk should be forced first".to_string()))
1810 }
1811 }
1812}
1813
1814fn json_to_vm_value(v: &serde_json::Value) -> VMValue {
1816 match v {
1817 serde_json::Value::Null => VMValue::Null,
1818 serde_json::Value::Bool(b) => VMValue::Bool(*b),
1819 serde_json::Value::Number(n) => {
1820 if let Some(i) = n.as_i64() {
1821 VMValue::Int(i)
1822 } else {
1823 VMValue::Float(n.as_f64().unwrap_or(0.0))
1824 }
1825 }
1826 serde_json::Value::String(s) => VMValue::String(s.clone()),
1827 serde_json::Value::Array(arr) => {
1828 VMValue::List(arr.iter().map(json_to_vm_value).collect())
1829 }
1830 serde_json::Value::Object(_) => {
1831 VMValue::Null
1834 }
1835 }
1836}
1837
1838#[cfg(test)]
1839mod tests {
1840 use super::*;
1841
1842 #[test]
1843 fn registry_has_builtins() {
1844 let reg = BuiltinRegistry::new();
1845 assert!(reg.lookup("length").is_some());
1846 assert!(reg.lookup("typeOf").is_some());
1847 assert!(reg.lookup("head").is_some());
1848 assert!(reg.lookup("tail").is_some());
1849 assert!(reg.lookup("throw").is_some());
1850 assert!(reg.lookup("nonexistent").is_none());
1851 }
1852
1853 #[test]
1854 fn call_length() {
1855 let reg = BuiltinRegistry::new();
1856 let idx = reg.lookup("length").unwrap();
1857 let result = reg
1858 .call(idx, vec![VMValue::List(vec![VMValue::Int(1), VMValue::Int(2)])])
1859 .unwrap();
1860 assert_eq!(result, VMValue::Int(2));
1861 }
1862
1863 #[test]
1864 fn call_head() {
1865 let reg = BuiltinRegistry::new();
1866 let idx = reg.lookup("head").unwrap();
1867 let result = reg
1868 .call(idx, vec![VMValue::List(vec![VMValue::Int(10)])])
1869 .unwrap();
1870 assert_eq!(result, VMValue::Int(10));
1871 }
1872
1873 #[test]
1874 fn call_head_empty() {
1875 let reg = BuiltinRegistry::new();
1876 let idx = reg.lookup("head").unwrap();
1877 let result = reg.call(idx, vec![VMValue::List(vec![])]);
1878 assert!(result.is_err());
1879 }
1880
1881 #[test]
1882 fn call_type_of() {
1883 let reg = BuiltinRegistry::new();
1884 let idx = reg.lookup("typeOf").unwrap();
1885 assert_eq!(
1886 reg.call(idx, vec![VMValue::Int(42)]).unwrap(),
1887 VMValue::String("int".to_string())
1888 );
1889 assert_eq!(
1890 reg.call(idx, vec![VMValue::String("hello".to_string())])
1891 .unwrap(),
1892 VMValue::String("string".to_string())
1893 );
1894 }
1895
1896 #[test]
1897 fn call_string_length() {
1898 let reg = BuiltinRegistry::new();
1899 let idx = reg.lookup("stringLength").unwrap();
1900 let result = reg
1901 .call(idx, vec![VMValue::String("hello".to_string())])
1902 .unwrap();
1903 assert_eq!(result, VMValue::Int(5));
1904 }
1905
1906 #[test]
1907 fn call_throw() {
1908 let reg = BuiltinRegistry::new();
1909 let idx = reg.lookup("throw").unwrap();
1910 let result = reg.call(idx, vec![VMValue::String("test error".to_string())]);
1911 assert!(matches!(result, Err(VMError::Throw(_))));
1912 }
1913
1914 #[test]
1915 fn call_to_string() {
1916 let reg = BuiltinRegistry::new();
1917 let idx = reg.lookup("toString").unwrap();
1918 assert_eq!(
1919 reg.call(idx, vec![VMValue::Int(42)]).unwrap(),
1920 VMValue::String("42".to_string())
1921 );
1922 assert_eq!(
1923 reg.call(idx, vec![VMValue::Bool(true)]).unwrap(),
1924 VMValue::String("1".to_string())
1925 );
1926 }
1927
1928 #[test]
1929 fn builtins_attrset() {
1930 let reg = BuiltinRegistry::new();
1931 let mut interner = Interner::new();
1932 let builtins = reg.make_builtins_attrset(&mut interner);
1933 match &builtins {
1934 VMValue::Attrs(attrs) => {
1935 let length_sym = interner.lookup("length").unwrap();
1936 assert!(attrs.contains_key(&length_sym));
1937 }
1938 _ => panic!("expected Attrs"),
1939 }
1940 }
1941}