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) {
302 self.register_global("print", builtin_print);
304 self.register_global("println", builtin_println);
305 self.register_global("debug", builtin_debug);
306 self.register_global("assert", builtin_assert);
307 self.register_global("todo", builtin_todo);
308 self.register_global("unreachable", builtin_unreachable);
309
310 self.register(TypeTag::String, "len", string_len);
312 self.register(TypeTag::String, "to_string", string_to_string);
313
314 self.register(TypeTag::List, "len", list_len);
316 self.register(TypeTag::List, "get", list_get);
317 self.register(TypeTag::List, "push", list_push);
318
319 self.register(TypeTag::Map, "len", map_len);
321 self.register(TypeTag::Map, "get", map_get);
322 self.register(TypeTag::Map, "set", map_set);
323
324 self.register(TypeTag::Int, "to_float", int_to_float);
326 self.register(TypeTag::Float, "to_int", float_to_int);
327 self.register(TypeTag::Bool, "to_int", bool_to_int);
328 self.register(TypeTag::Char, "to_int", char_to_int);
329
330 self.register(TypeTag::Int, "to_string", universal_to_string);
333 self.register(TypeTag::Float, "to_string", universal_to_string);
334 self.register(TypeTag::Bool, "to_string", universal_to_string);
335 self.register(TypeTag::Char, "to_string", universal_to_string);
336 self.register(TypeTag::Void, "to_string", universal_to_string);
337 self.register(TypeTag::List, "to_string", universal_to_string);
338 self.register(TypeTag::Map, "to_string", universal_to_string);
339 self.register(TypeTag::Set, "to_string", universal_to_string);
340 self.register(TypeTag::Tuple, "to_string", universal_to_string);
341 self.register(TypeTag::Record, "to_string", universal_to_string);
342 self.register(TypeTag::Enum, "to_string", universal_to_string);
343 self.register(TypeTag::Function, "to_string", universal_to_string);
344 self.register(TypeTag::Optional, "to_string", universal_to_string);
345 self.register(TypeTag::Result, "to_string", universal_to_string);
346 self.register(TypeTag::Range, "to_string", universal_to_string);
347 self.register(TypeTag::Iterator, "to_string", universal_to_string);
348 self.register(TypeTag::StringBuilder, "to_string", universal_to_string);
349 self.register(TypeTag::Duration, "to_string", universal_to_string);
350 self.register(TypeTag::Instant, "to_string", universal_to_string);
351 self.register(TypeTag::Channel, "to_string", universal_to_string);
352 }
353}
354
355fn builtin_print(args: &[Value]) -> Result<Value, RuntimeError> {
359 let parts: Vec<String> = args.iter().map(|v| v.to_string()).collect();
360 print!("{}", parts.join(" "));
361 Ok(Value::Void)
362}
363
364fn builtin_println(args: &[Value]) -> Result<Value, RuntimeError> {
366 let parts: Vec<String> = args.iter().map(|v| v.to_string()).collect();
367 println!("{}", parts.join(" "));
368 Ok(Value::Void)
369}
370
371fn builtin_debug(args: &[Value]) -> Result<Value, RuntimeError> {
373 for arg in args {
374 println!("{arg:?}");
375 }
376 Ok(Value::Void)
377}
378
379fn builtin_assert(args: &[Value]) -> Result<Value, RuntimeError> {
381 let condition = match args.first() {
382 Some(Value::Bool(b)) => *b,
383 Some(other) => {
384 return Err(RuntimeError::TypeError(format!(
385 "assert expects Bool, got {other}"
386 )))
387 }
388 None => {
389 return Err(RuntimeError::ArityMismatch {
390 expected: 1,
391 got: 0,
392 })
393 }
394 };
395 if condition {
396 Ok(Value::Void)
397 } else {
398 let msg = match args.get(1) {
399 Some(Value::String(s)) => format!("assertion failed: {}", s.as_str()),
400 Some(other) => format!("assertion failed: {other}"),
401 None => "assertion failed".to_string(),
402 };
403 Err(RuntimeError::AssertionFailed(msg))
404 }
405}
406
407fn builtin_todo(args: &[Value]) -> Result<Value, RuntimeError> {
409 let msg = match args.first() {
410 Some(Value::String(s)) => format!("not yet implemented: {}", s.as_str()),
411 Some(other) => format!("not yet implemented: {other}"),
412 None => "not yet implemented".to_string(),
413 };
414 Err(RuntimeError::NotImplemented(msg))
415}
416
417fn builtin_unreachable(_args: &[Value]) -> Result<Value, RuntimeError> {
419 Err(RuntimeError::Unreachable)
420}
421
422fn universal_to_string(args: &[Value]) -> Result<Value, RuntimeError> {
426 let receiver = args
427 .first()
428 .ok_or_else(|| RuntimeError::TypeError("to_string requires a receiver".to_string()))?;
429 Ok(Value::String(BockString::new(receiver.to_string())))
430}
431
432fn int_to_float(args: &[Value]) -> Result<Value, RuntimeError> {
436 match args.first() {
437 Some(Value::Int(n)) => Ok(Value::Float(OrdF64(*n as f64))),
438 _ => Err(RuntimeError::TypeError(
439 "Int.to_float called on non-Int".to_string(),
440 )),
441 }
442}
443
444fn float_to_int(args: &[Value]) -> Result<Value, RuntimeError> {
446 match args.first() {
447 Some(Value::Float(f)) => {
448 if f.0.is_nan() || f.0.is_infinite() {
449 Err(RuntimeError::TypeError(
450 "cannot convert NaN or Infinity to Int".to_string(),
451 ))
452 } else {
453 Ok(Value::Int(f.0 as i64))
454 }
455 }
456 _ => Err(RuntimeError::TypeError(
457 "Float.to_int called on non-Float".to_string(),
458 )),
459 }
460}
461
462fn bool_to_int(args: &[Value]) -> Result<Value, RuntimeError> {
464 match args.first() {
465 Some(Value::Bool(b)) => Ok(Value::Int(if *b { 1 } else { 0 })),
466 _ => Err(RuntimeError::TypeError(
467 "Bool.to_int called on non-Bool".to_string(),
468 )),
469 }
470}
471
472fn char_to_int(args: &[Value]) -> Result<Value, RuntimeError> {
474 match args.first() {
475 Some(Value::Char(c)) => Ok(Value::Int(*c as i64)),
476 _ => Err(RuntimeError::TypeError(
477 "Char.to_int called on non-Char".to_string(),
478 )),
479 }
480}
481
482fn string_len(args: &[Value]) -> Result<Value, RuntimeError> {
486 let receiver = args
487 .first()
488 .ok_or_else(|| RuntimeError::TypeError("String.len requires a receiver".to_string()))?;
489 match receiver {
490 Value::String(s) => Ok(Value::Int(s.as_str().chars().count() as i64)),
491 _ => Err(RuntimeError::TypeError(
492 "String.len called on non-String".to_string(),
493 )),
494 }
495}
496
497fn string_to_string(args: &[Value]) -> Result<Value, RuntimeError> {
499 let receiver = args.first().ok_or_else(|| {
500 RuntimeError::TypeError("String.to_string requires a receiver".to_string())
501 })?;
502 Ok(receiver.clone())
503}
504
505fn list_len(args: &[Value]) -> Result<Value, RuntimeError> {
509 match args.first() {
510 Some(Value::List(items)) => Ok(Value::Int(items.len() as i64)),
511 _ => Err(RuntimeError::TypeError(
512 "List.len called on non-List".to_string(),
513 )),
514 }
515}
516
517fn list_get(args: &[Value]) -> Result<Value, RuntimeError> {
519 let items = match args.first() {
520 Some(Value::List(items)) => items,
521 _ => {
522 return Err(RuntimeError::TypeError(
523 "List.get called on non-List".to_string(),
524 ))
525 }
526 };
527 let idx = args.get(1).ok_or(RuntimeError::ArityMismatch {
528 expected: 1,
529 got: 0,
530 })?;
531 match idx {
532 Value::Int(i) => {
533 let i = *i;
534 if i < 0 || i as usize >= items.len() {
535 Ok(Value::Optional(None))
536 } else {
537 Ok(Value::Optional(Some(Box::new(items[i as usize].clone()))))
538 }
539 }
540 _ => Err(RuntimeError::TypeError(
541 "List.get expects an Int index".to_string(),
542 )),
543 }
544}
545
546fn list_push(args: &[Value]) -> Result<Value, RuntimeError> {
548 let items = match args.first() {
549 Some(Value::List(items)) => items,
550 _ => {
551 return Err(RuntimeError::TypeError(
552 "List.push called on non-List".to_string(),
553 ))
554 }
555 };
556 let val = args.get(1).ok_or(RuntimeError::ArityMismatch {
557 expected: 1,
558 got: 0,
559 })?;
560 let mut new_list = items.clone();
561 new_list.push(val.clone());
562 Ok(Value::List(new_list))
563}
564
565fn map_len(args: &[Value]) -> Result<Value, RuntimeError> {
569 match args.first() {
570 Some(Value::Map(map)) => Ok(Value::Int(map.len() as i64)),
571 _ => Err(RuntimeError::TypeError(
572 "Map.len called on non-Map".to_string(),
573 )),
574 }
575}
576
577fn map_get(args: &[Value]) -> Result<Value, RuntimeError> {
579 let map = match args.first() {
580 Some(Value::Map(map)) => map,
581 _ => {
582 return Err(RuntimeError::TypeError(
583 "Map.get called on non-Map".to_string(),
584 ))
585 }
586 };
587 let key = args.get(1).ok_or(RuntimeError::ArityMismatch {
588 expected: 1,
589 got: 0,
590 })?;
591 Ok(Value::Optional(map.get(key).cloned().map(Box::new)))
592}
593
594fn map_set(args: &[Value]) -> Result<Value, RuntimeError> {
596 let map = match args.first() {
597 Some(Value::Map(map)) => map,
598 _ => {
599 return Err(RuntimeError::TypeError(
600 "Map.set called on non-Map".to_string(),
601 ))
602 }
603 };
604 if args.len() < 3 {
605 return Err(RuntimeError::ArityMismatch {
606 expected: 2,
607 got: args.len() - 1,
608 });
609 }
610 let key = args[1].clone();
611 let val = args[2].clone();
612 let mut new_map = map.clone();
613 new_map.insert(key, val);
614 Ok(Value::Map(new_map))
615}
616
617use crate::value::RecordValue;
620use std::collections::BTreeMap;
621
622fn builtin_expect(args: &[Value]) -> Result<Value, RuntimeError> {
624 let actual = args.first().cloned().unwrap_or(Value::Void);
625 let mut fields = BTreeMap::new();
626 fields.insert("actual".to_string(), actual);
627 Ok(Value::Record(RecordValue {
628 type_name: "Expectation".to_string(),
629 fields,
630 }))
631}
632
633fn get_expectation_actual(args: &[Value]) -> Result<Value, RuntimeError> {
635 match args.first() {
636 Some(Value::Record(r)) if r.type_name == "Expectation" => r
637 .fields
638 .get("actual")
639 .cloned()
640 .ok_or_else(|| RuntimeError::TypeError("malformed Expectation".to_string())),
641 _ => Err(RuntimeError::TypeError(
642 "assertion method called on non-Expectation value".to_string(),
643 )),
644 }
645}
646
647fn expect_to_equal(args: &[Value]) -> Result<Value, RuntimeError> {
649 let actual = get_expectation_actual(args)?;
650 let expected = args.get(1).ok_or(RuntimeError::ArityMismatch {
651 expected: 1,
652 got: 0,
653 })?;
654 if actual != *expected {
655 return Err(RuntimeError::AssertionFailed(format!(
656 "expected {expected}, got {actual}"
657 )));
658 }
659 Ok(Value::Void)
660}
661
662fn expect_to_be_ok(args: &[Value]) -> Result<Value, RuntimeError> {
664 let actual = get_expectation_actual(args)?;
665 match &actual {
666 Value::Result(Ok(_)) => Ok(Value::Void),
667 _ => Err(RuntimeError::AssertionFailed(format!(
668 "expected Ok(...), got {actual}"
669 ))),
670 }
671}
672
673fn expect_to_be_err(args: &[Value]) -> Result<Value, RuntimeError> {
675 let actual = get_expectation_actual(args)?;
676 match &actual {
677 Value::Result(Err(_)) => Ok(Value::Void),
678 _ => Err(RuntimeError::AssertionFailed(format!(
679 "expected Err(...), got {actual}"
680 ))),
681 }
682}
683
684fn expect_to_be_some(args: &[Value]) -> Result<Value, RuntimeError> {
686 let actual = get_expectation_actual(args)?;
687 match &actual {
688 Value::Optional(Some(_)) => Ok(Value::Void),
689 _ => Err(RuntimeError::AssertionFailed(format!(
690 "expected Some(...), got {actual}"
691 ))),
692 }
693}
694
695fn expect_to_be_none(args: &[Value]) -> Result<Value, RuntimeError> {
697 let actual = get_expectation_actual(args)?;
698 match &actual {
699 Value::Optional(None) => Ok(Value::Void),
700 _ => Err(RuntimeError::AssertionFailed(format!(
701 "expected None, got {actual}"
702 ))),
703 }
704}
705
706fn expect_to_throw(args: &[Value]) -> Result<Value, RuntimeError> {
710 expect_to_be_err(args)
711}
712
713fn expect_to_be_true(args: &[Value]) -> Result<Value, RuntimeError> {
715 let actual = get_expectation_actual(args)?;
716 if actual != Value::Bool(true) {
717 return Err(RuntimeError::AssertionFailed(format!(
718 "expected true, got {actual}"
719 )));
720 }
721 Ok(Value::Void)
722}
723
724fn expect_to_be_false(args: &[Value]) -> Result<Value, RuntimeError> {
726 let actual = get_expectation_actual(args)?;
727 if actual != Value::Bool(false) {
728 return Err(RuntimeError::AssertionFailed(format!(
729 "expected false, got {actual}"
730 )));
731 }
732 Ok(Value::Void)
733}
734
735#[cfg(test)]
738mod tests {
739 use std::collections::BTreeMap;
740
741 use super::*;
742
743 fn make_registry() -> BuiltinRegistry {
744 let mut reg = BuiltinRegistry::new();
745 reg.register_defaults();
746 reg
747 }
748
749 #[test]
752 fn type_tag_of_all_variants() {
753 assert_eq!(TypeTag::of(&Value::Int(0)), TypeTag::Int);
754 assert_eq!(TypeTag::of(&Value::Float(0.0.into())), TypeTag::Float);
755 assert_eq!(TypeTag::of(&Value::Bool(true)), TypeTag::Bool);
756 assert_eq!(
757 TypeTag::of(&Value::String(BockString::new("x"))),
758 TypeTag::String
759 );
760 assert_eq!(TypeTag::of(&Value::Char('x')), TypeTag::Char);
761 assert_eq!(TypeTag::of(&Value::Void), TypeTag::Void);
762 assert_eq!(TypeTag::of(&Value::List(vec![])), TypeTag::List);
763 assert_eq!(TypeTag::of(&Value::Map(BTreeMap::new())), TypeTag::Map);
764 }
765
766 #[test]
767 fn type_tag_display() {
768 assert_eq!(TypeTag::Int.to_string(), "Int");
769 assert_eq!(TypeTag::String.to_string(), "String");
770 assert_eq!(TypeTag::List.to_string(), "List");
771 }
772
773 #[test]
776 fn println_returns_void() {
777 let reg = make_registry();
778 let result = reg
779 .call_global("println", &[Value::String(BockString::new("hello"))])
780 .unwrap();
781 assert_eq!(result.unwrap(), Value::Void);
782 }
783
784 #[test]
785 fn print_returns_void() {
786 let reg = make_registry();
787 let result = reg.call_global("print", &[Value::Int(42)]).unwrap();
788 assert_eq!(result.unwrap(), Value::Void);
789 }
790
791 #[test]
792 fn debug_returns_void() {
793 let reg = make_registry();
794 let result = reg.call_global("debug", &[Value::Bool(true)]).unwrap();
795 assert_eq!(result.unwrap(), Value::Void);
796 }
797
798 #[test]
799 fn unknown_global_returns_none() {
800 let reg = make_registry();
801 assert!(reg.call_global("nonexistent", &[]).is_none());
802 }
803
804 #[test]
807 fn string_len_counts_chars() {
808 let reg = make_registry();
809 let recv = Value::String(BockString::new("héllo"));
810 let result = reg.call(TypeTag::String, "len", &[recv]).unwrap().unwrap();
811 assert_eq!(result, Value::Int(5));
812 }
813
814 #[test]
815 fn string_to_string_identity() {
816 let reg = make_registry();
817 let recv = Value::String(BockString::new("test"));
818 let result = reg
819 .call(TypeTag::String, "to_string", &[recv.clone()])
820 .unwrap()
821 .unwrap();
822 assert_eq!(result, recv);
823 }
824
825 #[test]
828 fn list_len_works() {
829 let reg = make_registry();
830 let recv = Value::List(vec![Value::Int(1), Value::Int(2), Value::Int(3)]);
831 let result = reg.call(TypeTag::List, "len", &[recv]).unwrap().unwrap();
832 assert_eq!(result, Value::Int(3));
833 }
834
835 #[test]
836 fn list_get_valid_index() {
837 let reg = make_registry();
838 let recv = Value::List(vec![Value::Int(10), Value::Int(20)]);
839 let result = reg
840 .call(TypeTag::List, "get", &[recv, Value::Int(1)])
841 .unwrap()
842 .unwrap();
843 assert_eq!(result, Value::Optional(Some(Box::new(Value::Int(20)))));
844 }
845
846 #[test]
847 fn list_get_out_of_bounds() {
848 let reg = make_registry();
849 let recv = Value::List(vec![Value::Int(10)]);
850 let result = reg
851 .call(TypeTag::List, "get", &[recv, Value::Int(5)])
852 .unwrap()
853 .unwrap();
854 assert_eq!(result, Value::Optional(None));
855 }
856
857 #[test]
858 fn list_push_appends() {
859 let reg = make_registry();
860 let recv = Value::List(vec![Value::Int(1)]);
861 let result = reg
862 .call(TypeTag::List, "push", &[recv, Value::Int(2)])
863 .unwrap()
864 .unwrap();
865 assert_eq!(result, Value::List(vec![Value::Int(1), Value::Int(2)]));
866 }
867
868 #[test]
871 fn map_len_works() {
872 let reg = make_registry();
873 let mut m = BTreeMap::new();
874 m.insert(Value::Int(1), Value::Bool(true));
875 let recv = Value::Map(m);
876 let result = reg.call(TypeTag::Map, "len", &[recv]).unwrap().unwrap();
877 assert_eq!(result, Value::Int(1));
878 }
879
880 #[test]
881 fn map_get_existing_key() {
882 let reg = make_registry();
883 let mut m = BTreeMap::new();
884 m.insert(Value::String(BockString::new("a")), Value::Int(42));
885 let recv = Value::Map(m);
886 let result = reg
887 .call(
888 TypeTag::Map,
889 "get",
890 &[recv, Value::String(BockString::new("a"))],
891 )
892 .unwrap()
893 .unwrap();
894 assert_eq!(result, Value::Optional(Some(Box::new(Value::Int(42)))));
895 }
896
897 #[test]
898 fn map_get_missing_key() {
899 let reg = make_registry();
900 let recv = Value::Map(BTreeMap::new());
901 let result = reg
902 .call(
903 TypeTag::Map,
904 "get",
905 &[recv, Value::String(BockString::new("missing"))],
906 )
907 .unwrap()
908 .unwrap();
909 assert_eq!(result, Value::Optional(None));
910 }
911
912 #[test]
913 fn map_set_inserts() {
914 let reg = make_registry();
915 let recv = Value::Map(BTreeMap::new());
916 let result = reg
917 .call(
918 TypeTag::Map,
919 "set",
920 &[recv, Value::Int(1), Value::Bool(true)],
921 )
922 .unwrap()
923 .unwrap();
924 let mut expected = BTreeMap::new();
925 expected.insert(Value::Int(1), Value::Bool(true));
926 assert_eq!(result, Value::Map(expected));
927 }
928
929 #[test]
932 fn unknown_method_returns_none() {
933 let reg = make_registry();
934 let recv = Value::Int(42);
935 assert!(reg.call(TypeTag::Int, "nonexistent", &[recv]).is_none());
936 }
937
938 #[test]
941 fn external_registration_works() {
942 let mut reg = make_registry();
943 fn custom_method(args: &[Value]) -> Result<Value, RuntimeError> {
944 Ok(args.first().cloned().unwrap_or(Value::Void))
945 }
946 reg.register(TypeTag::Int, "custom", custom_method);
947 let result = reg
948 .call(TypeTag::Int, "custom", &[Value::Int(99)])
949 .unwrap()
950 .unwrap();
951 assert_eq!(result, Value::Int(99));
952 }
953
954 #[test]
957 fn int_to_string() {
958 let reg = make_registry();
959 let result = reg
960 .call(TypeTag::Int, "to_string", &[Value::Int(42)])
961 .unwrap()
962 .unwrap();
963 assert_eq!(result, Value::String(BockString::new("42")));
964 }
965
966 #[test]
967 fn bool_to_string() {
968 let reg = make_registry();
969 let result = reg
970 .call(TypeTag::Bool, "to_string", &[Value::Bool(true)])
971 .unwrap()
972 .unwrap();
973 assert_eq!(result, Value::String(BockString::new("true")));
974 }
975
976 #[test]
979 fn assert_true_passes() {
980 let reg = make_registry();
981 let result = reg.call_global("assert", &[Value::Bool(true)]).unwrap();
982 assert_eq!(result.unwrap(), Value::Void);
983 }
984
985 #[test]
986 fn assert_false_errors() {
987 let reg = make_registry();
988 let result = reg.call_global("assert", &[Value::Bool(false)]).unwrap();
989 assert!(result.is_err());
990 }
991
992 #[test]
993 fn assert_false_with_message() {
994 let reg = make_registry();
995 let result = reg
996 .call_global(
997 "assert",
998 &[Value::Bool(false), Value::String(BockString::new("bad"))],
999 )
1000 .unwrap();
1001 match result {
1002 Err(RuntimeError::AssertionFailed(msg)) => assert!(msg.contains("bad")),
1003 other => panic!("expected AssertionFailed, got {other:?}"),
1004 }
1005 }
1006
1007 #[test]
1008 fn todo_produces_runtime_error() {
1009 let reg = make_registry();
1010 let result = reg.call_global("todo", &[]).unwrap();
1011 match result {
1012 Err(RuntimeError::NotImplemented(msg)) => {
1013 assert!(msg.contains("not yet implemented"));
1014 }
1015 other => panic!("expected NotImplemented, got {other:?}"),
1016 }
1017 }
1018
1019 #[test]
1020 fn unreachable_produces_runtime_error() {
1021 let reg = make_registry();
1022 let result = reg.call_global("unreachable", &[]).unwrap();
1023 assert!(matches!(result, Err(RuntimeError::Unreachable)));
1024 }
1025}