1use std::collections::HashMap;
8
9use futures::future::BoxFuture;
10
11use crate::error::RuntimeError;
12use crate::value::{BockString, OrdF64, Value};
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18pub enum TypeTag {
19 Int,
20 Float,
21 Bool,
22 String,
23 Char,
24 Void,
25 List,
26 Map,
27 Set,
28 Tuple,
29 Record,
30 Enum,
31 Function,
32 Optional,
33 Result,
34 Range,
35 Iterator,
36 StringBuilder,
37 Future,
38 Duration,
39 Instant,
40 Channel,
41}
42
43impl TypeTag {
44 #[must_use]
46 pub fn of(value: &Value) -> Self {
47 match value {
48 Value::Int(_) => TypeTag::Int,
49 Value::Float(_) => TypeTag::Float,
50 Value::Bool(_) => TypeTag::Bool,
51 Value::String(_) => TypeTag::String,
52 Value::Char(_) => TypeTag::Char,
53 Value::Void => TypeTag::Void,
54 Value::List(_) => TypeTag::List,
55 Value::Map(_) => TypeTag::Map,
56 Value::Set(_) => TypeTag::Set,
57 Value::Tuple(_) => TypeTag::Tuple,
58 Value::Record(_) => TypeTag::Record,
59 Value::Enum(_) => TypeTag::Enum,
60 Value::Function(_) => TypeTag::Function,
61 Value::Optional(_) => TypeTag::Optional,
62 Value::Result(_) => TypeTag::Result,
63 Value::Range { .. } => TypeTag::Range,
64 Value::Iterator(_) => TypeTag::Iterator,
65 Value::StringBuilder(_) => TypeTag::StringBuilder,
66 Value::Future(_) => TypeTag::Future,
67 Value::Duration(_) => TypeTag::Duration,
68 Value::Instant(_) => TypeTag::Instant,
69 Value::Channel(_) => TypeTag::Channel,
70 }
71 }
72
73 #[must_use]
75 pub fn name(self) -> &'static str {
76 match self {
77 TypeTag::Int => "Int",
78 TypeTag::Float => "Float",
79 TypeTag::Bool => "Bool",
80 TypeTag::String => "String",
81 TypeTag::Char => "Char",
82 TypeTag::Void => "Void",
83 TypeTag::List => "List",
84 TypeTag::Map => "Map",
85 TypeTag::Set => "Set",
86 TypeTag::Tuple => "Tuple",
87 TypeTag::Record => "Record",
88 TypeTag::Enum => "Enum",
89 TypeTag::Function => "Function",
90 TypeTag::Optional => "Optional",
91 TypeTag::Result => "Result",
92 TypeTag::Range => "Range",
93 TypeTag::Iterator => "Iterator",
94 TypeTag::StringBuilder => "StringBuilder",
95 TypeTag::Future => "Future",
96 TypeTag::Duration => "Duration",
97 TypeTag::Instant => "Instant",
98 TypeTag::Channel => "Channel",
99 }
100 }
101}
102
103impl std::fmt::Display for TypeTag {
104 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105 f.write_str(self.name())
106 }
107}
108
109pub trait CallbackInvoker: Send {
120 fn invoke<'a>(
122 &'a mut self,
123 callable: &'a Value,
124 args: &'a [Value],
125 ) -> BoxFuture<'a, Result<Value, RuntimeError>>;
126}
127
128pub struct NoOpInvoker;
132
133impl CallbackInvoker for NoOpInvoker {
134 fn invoke<'a>(
135 &'a mut self,
136 _callable: &'a Value,
137 _args: &'a [Value],
138 ) -> BoxFuture<'a, Result<Value, RuntimeError>> {
139 Box::pin(async {
140 Err(RuntimeError::TypeError(
141 "callback invocation not available in this context".to_string(),
142 ))
143 })
144 }
145}
146
147pub type BuiltinFn = fn(&[Value]) -> Result<Value, RuntimeError>;
154
155pub type HigherOrderBuiltinFn = for<'a> fn(
162 &'a [Value],
163 &'a mut dyn CallbackInvoker,
164) -> BoxFuture<'a, Result<Value, RuntimeError>>;
165
166#[derive(Clone)]
174pub struct BuiltinRegistry {
175 methods: HashMap<(TypeTag, String), BuiltinFn>,
177 ho_methods: HashMap<(TypeTag, String), HigherOrderBuiltinFn>,
179 globals: HashMap<String, BuiltinFn>,
181}
182
183impl Default for BuiltinRegistry {
184 fn default() -> Self {
185 Self::new()
186 }
187}
188
189impl BuiltinRegistry {
190 #[must_use]
192 pub fn new() -> Self {
193 Self {
194 methods: HashMap::new(),
195 ho_methods: HashMap::new(),
196 globals: HashMap::new(),
197 }
198 }
199
200 pub fn register(&mut self, type_tag: TypeTag, name: &str, func: BuiltinFn) {
202 self.methods.insert((type_tag, name.to_string()), func);
203 }
204
205 pub fn register_ho(&mut self, type_tag: TypeTag, name: &str, func: HigherOrderBuiltinFn) {
207 self.ho_methods.insert((type_tag, name.to_string()), func);
208 }
209
210 pub fn register_global(&mut self, name: &str, func: BuiltinFn) {
212 self.globals.insert(name.to_string(), func);
213 }
214
215 pub fn call(
224 &self,
225 type_tag: TypeTag,
226 name: &str,
227 args: &[Value],
228 ) -> Option<Result<Value, RuntimeError>> {
229 self.methods
230 .get(&(type_tag, name.to_string()))
231 .map(|func| func(args))
232 }
233
234 pub fn call_global(&self, name: &str, args: &[Value]) -> Option<Result<Value, RuntimeError>> {
238 self.globals.get(name).map(|func| func(args))
239 }
240
241 #[must_use]
246 pub fn get_ho_method(&self, type_tag: TypeTag, name: &str) -> Option<HigherOrderBuiltinFn> {
247 self.ho_methods.get(&(type_tag, name.to_string())).copied()
248 }
249
250 #[must_use]
252 pub fn has_method(&self, type_tag: TypeTag, name: &str) -> bool {
253 let key = (type_tag, name.to_string());
254 self.methods.contains_key(&key) || self.ho_methods.contains_key(&key)
255 }
256
257 #[must_use]
259 pub fn has_global(&self, name: &str) -> bool {
260 self.globals.contains_key(name)
261 }
262
263 pub fn method_keys(&self) -> impl Iterator<Item = (TypeTag, &str)> {
267 self.methods
268 .keys()
269 .chain(self.ho_methods.keys())
270 .map(|(t, n)| (*t, n.as_str()))
271 }
272
273 pub fn global_names(&self) -> impl Iterator<Item = &str> {
275 self.globals.keys().map(String::as_str)
276 }
277
278 pub fn register_test_builtins(&mut self) {
283 self.register_global("expect", builtin_expect);
284 self.register(TypeTag::Record, "to_equal", expect_to_equal);
285 self.register(TypeTag::Record, "to_be_ok", expect_to_be_ok);
286 self.register(TypeTag::Record, "to_be_err", expect_to_be_err);
287 self.register(TypeTag::Record, "to_be_some", expect_to_be_some);
288 self.register(TypeTag::Record, "to_be_none", expect_to_be_none);
289 self.register(TypeTag::Record, "to_throw", expect_to_throw);
290 self.register(TypeTag::Record, "to_be_true", expect_to_be_true);
291 self.register(TypeTag::Record, "to_be_false", expect_to_be_false);
292 }
293
294 pub fn register_defaults(&mut self) {
304 self.register_global("print", builtin_print);
306 self.register_global("println", builtin_println);
307 self.register_global("debug", builtin_debug);
308 self.register_global("assert", builtin_assert);
309 self.register_global("todo", builtin_todo);
310 self.register_global("unreachable", builtin_unreachable);
311
312 self.register(TypeTag::String, "len", string_len);
314 self.register(TypeTag::String, "to_string", string_to_string);
315
316 self.register(TypeTag::List, "len", list_len);
318 self.register(TypeTag::List, "get", list_get);
319
320 self.register(TypeTag::Map, "len", map_len);
322 self.register(TypeTag::Map, "get", map_get);
323 self.register(TypeTag::Map, "set", map_set);
324
325 self.register(TypeTag::Int, "to_float", int_to_float);
327 self.register(TypeTag::Float, "to_int", float_to_int);
328 self.register(TypeTag::Bool, "to_int", bool_to_int);
329 self.register(TypeTag::Char, "to_int", char_to_int);
330
331 self.register(TypeTag::Int, "eq", primitive_eq);
340 self.register(TypeTag::Float, "eq", primitive_eq);
341 self.register(TypeTag::Bool, "eq", primitive_eq);
342 self.register(TypeTag::String, "eq", primitive_eq);
343 self.register(TypeTag::Char, "eq", primitive_eq);
344
345 self.register(TypeTag::Int, "to_string", universal_to_string);
348 self.register(TypeTag::Float, "to_string", universal_to_string);
349 self.register(TypeTag::Bool, "to_string", universal_to_string);
350 self.register(TypeTag::Char, "to_string", universal_to_string);
351 self.register(TypeTag::Void, "to_string", universal_to_string);
352 self.register(TypeTag::List, "to_string", universal_to_string);
353 self.register(TypeTag::Map, "to_string", universal_to_string);
354 self.register(TypeTag::Set, "to_string", universal_to_string);
355 self.register(TypeTag::Tuple, "to_string", universal_to_string);
356 self.register(TypeTag::Record, "to_string", universal_to_string);
357 self.register(TypeTag::Enum, "to_string", universal_to_string);
358 self.register(TypeTag::Function, "to_string", universal_to_string);
359 self.register(TypeTag::Optional, "to_string", universal_to_string);
360 self.register(TypeTag::Result, "to_string", universal_to_string);
361 self.register(TypeTag::Range, "to_string", universal_to_string);
362 self.register(TypeTag::Iterator, "to_string", universal_to_string);
363 self.register(TypeTag::StringBuilder, "to_string", universal_to_string);
364 self.register(TypeTag::Duration, "to_string", universal_to_string);
365 self.register(TypeTag::Instant, "to_string", universal_to_string);
366 self.register(TypeTag::Channel, "to_string", universal_to_string);
367 }
368}
369
370fn builtin_print(args: &[Value]) -> Result<Value, RuntimeError> {
374 let parts: Vec<String> = args.iter().map(|v| v.to_string()).collect();
375 print!("{}", parts.join(" "));
376 Ok(Value::Void)
377}
378
379fn builtin_println(args: &[Value]) -> Result<Value, RuntimeError> {
381 let parts: Vec<String> = args.iter().map(|v| v.to_string()).collect();
382 println!("{}", parts.join(" "));
383 Ok(Value::Void)
384}
385
386fn builtin_debug(args: &[Value]) -> Result<Value, RuntimeError> {
388 for arg in args {
389 println!("{arg:?}");
390 }
391 Ok(Value::Void)
392}
393
394fn builtin_assert(args: &[Value]) -> Result<Value, RuntimeError> {
396 let condition = match args.first() {
397 Some(Value::Bool(b)) => *b,
398 Some(other) => {
399 return Err(RuntimeError::TypeError(format!(
400 "assert expects Bool, got {other}"
401 )))
402 }
403 None => {
404 return Err(RuntimeError::ArityMismatch {
405 expected: 1,
406 got: 0,
407 })
408 }
409 };
410 if condition {
411 Ok(Value::Void)
412 } else {
413 let msg = match args.get(1) {
414 Some(Value::String(s)) => format!("assertion failed: {}", s.as_str()),
415 Some(other) => format!("assertion failed: {other}"),
416 None => "assertion failed".to_string(),
417 };
418 Err(RuntimeError::AssertionFailed(msg))
419 }
420}
421
422fn builtin_todo(args: &[Value]) -> Result<Value, RuntimeError> {
424 let msg = match args.first() {
425 Some(Value::String(s)) => format!("not yet implemented: {}", s.as_str()),
426 Some(other) => format!("not yet implemented: {other}"),
427 None => "not yet implemented".to_string(),
428 };
429 Err(RuntimeError::NotImplemented(msg))
430}
431
432fn builtin_unreachable(_args: &[Value]) -> Result<Value, RuntimeError> {
434 Err(RuntimeError::Unreachable)
435}
436
437fn universal_to_string(args: &[Value]) -> Result<Value, RuntimeError> {
441 let receiver = args
442 .first()
443 .ok_or_else(|| RuntimeError::TypeError("to_string requires a receiver".to_string()))?;
444 Ok(Value::String(BockString::new(receiver.to_string())))
445}
446
447fn int_to_float(args: &[Value]) -> Result<Value, RuntimeError> {
451 match args.first() {
452 Some(Value::Int(n)) => Ok(Value::Float(OrdF64(*n as f64))),
453 _ => Err(RuntimeError::TypeError(
454 "Int.to_float called on non-Int".to_string(),
455 )),
456 }
457}
458
459fn float_to_int(args: &[Value]) -> Result<Value, RuntimeError> {
461 match args.first() {
462 Some(Value::Float(f)) => {
463 if f.0.is_nan() || f.0.is_infinite() {
464 Err(RuntimeError::TypeError(
465 "cannot convert NaN or Infinity to Int".to_string(),
466 ))
467 } else {
468 Ok(Value::Int(f.0 as i64))
469 }
470 }
471 _ => Err(RuntimeError::TypeError(
472 "Float.to_int called on non-Float".to_string(),
473 )),
474 }
475}
476
477fn bool_to_int(args: &[Value]) -> Result<Value, RuntimeError> {
479 match args.first() {
480 Some(Value::Bool(b)) => Ok(Value::Int(if *b { 1 } else { 0 })),
481 _ => Err(RuntimeError::TypeError(
482 "Bool.to_int called on non-Bool".to_string(),
483 )),
484 }
485}
486
487fn char_to_int(args: &[Value]) -> Result<Value, RuntimeError> {
489 match args.first() {
490 Some(Value::Char(c)) => Ok(Value::Int(*c as i64)),
491 _ => Err(RuntimeError::TypeError(
492 "Char.to_int called on non-Char".to_string(),
493 )),
494 }
495}
496
497fn primitive_eq(args: &[Value]) -> Result<Value, RuntimeError> {
507 if args.len() != 2 {
508 return Err(RuntimeError::ArityMismatch {
509 expected: 2,
510 got: args.len(),
511 });
512 }
513 Ok(Value::Bool(args[0] == args[1]))
514}
515
516fn string_len(args: &[Value]) -> Result<Value, RuntimeError> {
520 let receiver = args
521 .first()
522 .ok_or_else(|| RuntimeError::TypeError("String.len requires a receiver".to_string()))?;
523 match receiver {
524 Value::String(s) => Ok(Value::Int(s.as_str().chars().count() as i64)),
525 _ => Err(RuntimeError::TypeError(
526 "String.len called on non-String".to_string(),
527 )),
528 }
529}
530
531fn string_to_string(args: &[Value]) -> Result<Value, RuntimeError> {
533 let receiver = args.first().ok_or_else(|| {
534 RuntimeError::TypeError("String.to_string requires a receiver".to_string())
535 })?;
536 Ok(receiver.clone())
537}
538
539fn list_len(args: &[Value]) -> Result<Value, RuntimeError> {
543 match args.first() {
544 Some(Value::List(items)) => Ok(Value::Int(items.len() as i64)),
545 _ => Err(RuntimeError::TypeError(
546 "List.len called on non-List".to_string(),
547 )),
548 }
549}
550
551fn list_get(args: &[Value]) -> Result<Value, RuntimeError> {
553 let items = match args.first() {
554 Some(Value::List(items)) => items,
555 _ => {
556 return Err(RuntimeError::TypeError(
557 "List.get called on non-List".to_string(),
558 ))
559 }
560 };
561 let idx = args.get(1).ok_or(RuntimeError::ArityMismatch {
562 expected: 1,
563 got: 0,
564 })?;
565 match idx {
566 Value::Int(i) => {
567 let i = *i;
568 if i < 0 || i as usize >= items.len() {
569 Ok(Value::Optional(None))
570 } else {
571 Ok(Value::Optional(Some(Box::new(items[i as usize].clone()))))
572 }
573 }
574 _ => Err(RuntimeError::TypeError(
575 "List.get expects an Int index".to_string(),
576 )),
577 }
578}
579
580fn map_len(args: &[Value]) -> Result<Value, RuntimeError> {
584 match args.first() {
585 Some(Value::Map(map)) => Ok(Value::Int(map.len() as i64)),
586 _ => Err(RuntimeError::TypeError(
587 "Map.len called on non-Map".to_string(),
588 )),
589 }
590}
591
592fn map_get(args: &[Value]) -> Result<Value, RuntimeError> {
594 let map = match args.first() {
595 Some(Value::Map(map)) => map,
596 _ => {
597 return Err(RuntimeError::TypeError(
598 "Map.get called on non-Map".to_string(),
599 ))
600 }
601 };
602 let key = args.get(1).ok_or(RuntimeError::ArityMismatch {
603 expected: 1,
604 got: 0,
605 })?;
606 Ok(Value::Optional(map.get(key).cloned().map(Box::new)))
607}
608
609fn map_set(args: &[Value]) -> Result<Value, RuntimeError> {
611 let map = match args.first() {
612 Some(Value::Map(map)) => map,
613 _ => {
614 return Err(RuntimeError::TypeError(
615 "Map.set called on non-Map".to_string(),
616 ))
617 }
618 };
619 if args.len() < 3 {
620 return Err(RuntimeError::ArityMismatch {
621 expected: 2,
622 got: args.len() - 1,
623 });
624 }
625 let key = args[1].clone();
626 let val = args[2].clone();
627 let mut new_map = map.clone();
628 new_map.insert(key, val);
629 Ok(Value::Map(new_map))
630}
631
632use crate::value::RecordValue;
635use std::collections::BTreeMap;
636
637fn builtin_expect(args: &[Value]) -> Result<Value, RuntimeError> {
639 let actual = args.first().cloned().unwrap_or(Value::Void);
640 let mut fields = BTreeMap::new();
641 fields.insert("actual".to_string(), actual);
642 Ok(Value::Record(RecordValue {
643 type_name: "Expectation".to_string(),
644 fields,
645 }))
646}
647
648fn get_expectation_actual(args: &[Value]) -> Result<Value, RuntimeError> {
650 match args.first() {
651 Some(Value::Record(r)) if r.type_name == "Expectation" => r
652 .fields
653 .get("actual")
654 .cloned()
655 .ok_or_else(|| RuntimeError::TypeError("malformed Expectation".to_string())),
656 _ => Err(RuntimeError::TypeError(
657 "assertion method called on non-Expectation value".to_string(),
658 )),
659 }
660}
661
662fn expect_to_equal(args: &[Value]) -> Result<Value, RuntimeError> {
664 let actual = get_expectation_actual(args)?;
665 let expected = args.get(1).ok_or(RuntimeError::ArityMismatch {
666 expected: 1,
667 got: 0,
668 })?;
669 if actual != *expected {
670 return Err(RuntimeError::AssertionFailed(format!(
671 "expected {expected}, got {actual}"
672 )));
673 }
674 Ok(Value::Void)
675}
676
677fn expect_to_be_ok(args: &[Value]) -> Result<Value, RuntimeError> {
679 let actual = get_expectation_actual(args)?;
680 match &actual {
681 Value::Result(Ok(_)) => Ok(Value::Void),
682 _ => Err(RuntimeError::AssertionFailed(format!(
683 "expected Ok(...), got {actual}"
684 ))),
685 }
686}
687
688fn expect_to_be_err(args: &[Value]) -> Result<Value, RuntimeError> {
690 let actual = get_expectation_actual(args)?;
691 match &actual {
692 Value::Result(Err(_)) => Ok(Value::Void),
693 _ => Err(RuntimeError::AssertionFailed(format!(
694 "expected Err(...), got {actual}"
695 ))),
696 }
697}
698
699fn expect_to_be_some(args: &[Value]) -> Result<Value, RuntimeError> {
701 let actual = get_expectation_actual(args)?;
702 match &actual {
703 Value::Optional(Some(_)) => Ok(Value::Void),
704 _ => Err(RuntimeError::AssertionFailed(format!(
705 "expected Some(...), got {actual}"
706 ))),
707 }
708}
709
710fn expect_to_be_none(args: &[Value]) -> Result<Value, RuntimeError> {
712 let actual = get_expectation_actual(args)?;
713 match &actual {
714 Value::Optional(None) => Ok(Value::Void),
715 _ => Err(RuntimeError::AssertionFailed(format!(
716 "expected None, got {actual}"
717 ))),
718 }
719}
720
721fn expect_to_throw(args: &[Value]) -> Result<Value, RuntimeError> {
725 expect_to_be_err(args)
726}
727
728fn expect_to_be_true(args: &[Value]) -> Result<Value, RuntimeError> {
730 let actual = get_expectation_actual(args)?;
731 if actual != Value::Bool(true) {
732 return Err(RuntimeError::AssertionFailed(format!(
733 "expected true, got {actual}"
734 )));
735 }
736 Ok(Value::Void)
737}
738
739fn expect_to_be_false(args: &[Value]) -> Result<Value, RuntimeError> {
741 let actual = get_expectation_actual(args)?;
742 if actual != Value::Bool(false) {
743 return Err(RuntimeError::AssertionFailed(format!(
744 "expected false, got {actual}"
745 )));
746 }
747 Ok(Value::Void)
748}
749
750#[cfg(test)]
753mod tests {
754 use std::collections::BTreeMap;
755
756 use super::*;
757
758 fn make_registry() -> BuiltinRegistry {
759 let mut reg = BuiltinRegistry::new();
760 reg.register_defaults();
761 reg
762 }
763
764 #[test]
767 fn type_tag_of_all_variants() {
768 assert_eq!(TypeTag::of(&Value::Int(0)), TypeTag::Int);
769 assert_eq!(TypeTag::of(&Value::Float(0.0.into())), TypeTag::Float);
770 assert_eq!(TypeTag::of(&Value::Bool(true)), TypeTag::Bool);
771 assert_eq!(
772 TypeTag::of(&Value::String(BockString::new("x"))),
773 TypeTag::String
774 );
775 assert_eq!(TypeTag::of(&Value::Char('x')), TypeTag::Char);
776 assert_eq!(TypeTag::of(&Value::Void), TypeTag::Void);
777 assert_eq!(TypeTag::of(&Value::List(vec![])), TypeTag::List);
778 assert_eq!(TypeTag::of(&Value::Map(BTreeMap::new())), TypeTag::Map);
779 }
780
781 #[test]
782 fn type_tag_display() {
783 assert_eq!(TypeTag::Int.to_string(), "Int");
784 assert_eq!(TypeTag::String.to_string(), "String");
785 assert_eq!(TypeTag::List.to_string(), "List");
786 }
787
788 #[test]
791 fn println_returns_void() {
792 let reg = make_registry();
793 let result = reg
794 .call_global("println", &[Value::String(BockString::new("hello"))])
795 .unwrap();
796 assert_eq!(result.unwrap(), Value::Void);
797 }
798
799 #[test]
800 fn print_returns_void() {
801 let reg = make_registry();
802 let result = reg.call_global("print", &[Value::Int(42)]).unwrap();
803 assert_eq!(result.unwrap(), Value::Void);
804 }
805
806 #[test]
807 fn debug_returns_void() {
808 let reg = make_registry();
809 let result = reg.call_global("debug", &[Value::Bool(true)]).unwrap();
810 assert_eq!(result.unwrap(), Value::Void);
811 }
812
813 #[test]
814 fn unknown_global_returns_none() {
815 let reg = make_registry();
816 assert!(reg.call_global("nonexistent", &[]).is_none());
817 }
818
819 #[test]
822 fn string_len_counts_chars() {
823 let reg = make_registry();
824 let recv = Value::String(BockString::new("héllo"));
825 let result = reg.call(TypeTag::String, "len", &[recv]).unwrap().unwrap();
826 assert_eq!(result, Value::Int(5));
827 }
828
829 #[test]
830 fn string_to_string_identity() {
831 let reg = make_registry();
832 let recv = Value::String(BockString::new("test"));
833 let result = reg
834 .call(TypeTag::String, "to_string", std::slice::from_ref(&recv))
835 .unwrap()
836 .unwrap();
837 assert_eq!(result, recv);
838 }
839
840 #[test]
848 fn primitive_eq_dispatches_for_all_primitive_tags() {
849 let reg = make_registry();
850 let cases: Vec<(TypeTag, Value, Value)> = vec![
851 (TypeTag::Int, Value::Int(3), Value::Int(3)),
852 (
853 TypeTag::Float,
854 Value::Float(2.5.into()),
855 Value::Float(2.5.into()),
856 ),
857 (TypeTag::Bool, Value::Bool(true), Value::Bool(true)),
858 (
859 TypeTag::String,
860 Value::String(BockString::new("ab")),
861 Value::String(BockString::new("ab")),
862 ),
863 (TypeTag::Char, Value::Char('x'), Value::Char('x')),
864 ];
865 for (tag, a, b) in cases {
866 let result = reg
867 .call(tag, "eq", &[a, b])
868 .unwrap_or_else(|| panic!("`eq` not registered for {tag}"))
869 .unwrap();
870 assert_eq!(result, Value::Bool(true), "{tag}.eq of equal values");
871 }
872 }
873
874 #[test]
875 fn primitive_eq_is_false_for_unequal_values() {
876 let reg = make_registry();
877 let result = reg
878 .call(TypeTag::Int, "eq", &[Value::Int(3), Value::Int(4)])
879 .unwrap()
880 .unwrap();
881 assert_eq!(result, Value::Bool(false));
882 let result = reg
883 .call(
884 TypeTag::String,
885 "eq",
886 &[
887 Value::String(BockString::new("ab")),
888 Value::String(BockString::new("ac")),
889 ],
890 )
891 .unwrap()
892 .unwrap();
893 assert_eq!(result, Value::Bool(false));
894 }
895
896 #[test]
897 fn primitive_eq_arity_mismatch_is_error() {
898 let reg = make_registry();
899 let result = reg.call(TypeTag::Int, "eq", &[Value::Int(3)]).unwrap();
900 assert!(matches!(result, Err(RuntimeError::ArityMismatch { .. })));
901 }
902
903 #[test]
906 fn list_len_works() {
907 let reg = make_registry();
908 let recv = Value::List(vec![Value::Int(1), Value::Int(2), Value::Int(3)]);
909 let result = reg.call(TypeTag::List, "len", &[recv]).unwrap().unwrap();
910 assert_eq!(result, Value::Int(3));
911 }
912
913 #[test]
914 fn list_get_valid_index() {
915 let reg = make_registry();
916 let recv = Value::List(vec![Value::Int(10), Value::Int(20)]);
917 let result = reg
918 .call(TypeTag::List, "get", &[recv, Value::Int(1)])
919 .unwrap()
920 .unwrap();
921 assert_eq!(result, Value::Optional(Some(Box::new(Value::Int(20)))));
922 }
923
924 #[test]
925 fn list_get_out_of_bounds() {
926 let reg = make_registry();
927 let recv = Value::List(vec![Value::Int(10)]);
928 let result = reg
929 .call(TypeTag::List, "get", &[recv, Value::Int(5)])
930 .unwrap()
931 .unwrap();
932 assert_eq!(result, Value::Optional(None));
933 }
934
935 #[test]
938 fn map_len_works() {
939 let reg = make_registry();
940 let mut m = BTreeMap::new();
941 m.insert(Value::Int(1), Value::Bool(true));
942 let recv = Value::Map(m);
943 let result = reg.call(TypeTag::Map, "len", &[recv]).unwrap().unwrap();
944 assert_eq!(result, Value::Int(1));
945 }
946
947 #[test]
948 fn map_get_existing_key() {
949 let reg = make_registry();
950 let mut m = BTreeMap::new();
951 m.insert(Value::String(BockString::new("a")), Value::Int(42));
952 let recv = Value::Map(m);
953 let result = reg
954 .call(
955 TypeTag::Map,
956 "get",
957 &[recv, Value::String(BockString::new("a"))],
958 )
959 .unwrap()
960 .unwrap();
961 assert_eq!(result, Value::Optional(Some(Box::new(Value::Int(42)))));
962 }
963
964 #[test]
965 fn map_get_missing_key() {
966 let reg = make_registry();
967 let recv = Value::Map(BTreeMap::new());
968 let result = reg
969 .call(
970 TypeTag::Map,
971 "get",
972 &[recv, Value::String(BockString::new("missing"))],
973 )
974 .unwrap()
975 .unwrap();
976 assert_eq!(result, Value::Optional(None));
977 }
978
979 #[test]
980 fn map_set_inserts() {
981 let reg = make_registry();
982 let recv = Value::Map(BTreeMap::new());
983 let result = reg
984 .call(
985 TypeTag::Map,
986 "set",
987 &[recv, Value::Int(1), Value::Bool(true)],
988 )
989 .unwrap()
990 .unwrap();
991 let mut expected = BTreeMap::new();
992 expected.insert(Value::Int(1), Value::Bool(true));
993 assert_eq!(result, Value::Map(expected));
994 }
995
996 #[test]
999 fn unknown_method_returns_none() {
1000 let reg = make_registry();
1001 let recv = Value::Int(42);
1002 assert!(reg.call(TypeTag::Int, "nonexistent", &[recv]).is_none());
1003 }
1004
1005 #[test]
1008 fn external_registration_works() {
1009 let mut reg = make_registry();
1010 fn custom_method(args: &[Value]) -> Result<Value, RuntimeError> {
1011 Ok(args.first().cloned().unwrap_or(Value::Void))
1012 }
1013 reg.register(TypeTag::Int, "custom", custom_method);
1014 let result = reg
1015 .call(TypeTag::Int, "custom", &[Value::Int(99)])
1016 .unwrap()
1017 .unwrap();
1018 assert_eq!(result, Value::Int(99));
1019 }
1020
1021 #[test]
1024 fn int_to_string() {
1025 let reg = make_registry();
1026 let result = reg
1027 .call(TypeTag::Int, "to_string", &[Value::Int(42)])
1028 .unwrap()
1029 .unwrap();
1030 assert_eq!(result, Value::String(BockString::new("42")));
1031 }
1032
1033 #[test]
1034 fn bool_to_string() {
1035 let reg = make_registry();
1036 let result = reg
1037 .call(TypeTag::Bool, "to_string", &[Value::Bool(true)])
1038 .unwrap()
1039 .unwrap();
1040 assert_eq!(result, Value::String(BockString::new("true")));
1041 }
1042
1043 #[test]
1046 fn assert_true_passes() {
1047 let reg = make_registry();
1048 let result = reg.call_global("assert", &[Value::Bool(true)]).unwrap();
1049 assert_eq!(result.unwrap(), Value::Void);
1050 }
1051
1052 #[test]
1053 fn assert_false_errors() {
1054 let reg = make_registry();
1055 let result = reg.call_global("assert", &[Value::Bool(false)]).unwrap();
1056 assert!(result.is_err());
1057 }
1058
1059 #[test]
1060 fn assert_false_with_message() {
1061 let reg = make_registry();
1062 let result = reg
1063 .call_global(
1064 "assert",
1065 &[Value::Bool(false), Value::String(BockString::new("bad"))],
1066 )
1067 .unwrap();
1068 match result {
1069 Err(RuntimeError::AssertionFailed(msg)) => assert!(msg.contains("bad")),
1070 other => panic!("expected AssertionFailed, got {other:?}"),
1071 }
1072 }
1073
1074 #[test]
1075 fn todo_produces_runtime_error() {
1076 let reg = make_registry();
1077 let result = reg.call_global("todo", &[]).unwrap();
1078 match result {
1079 Err(RuntimeError::NotImplemented(msg)) => {
1080 assert!(msg.contains("not yet implemented"));
1081 }
1082 other => panic!("expected NotImplemented, got {other:?}"),
1083 }
1084 }
1085
1086 #[test]
1087 fn unreachable_produces_runtime_error() {
1088 let reg = make_registry();
1089 let result = reg.call_global("unreachable", &[]).unwrap();
1090 assert!(matches!(result, Err(RuntimeError::Unreachable)));
1091 }
1092}