Skip to main content

bock_interp/
builtins.rs

1//! Built-in method dispatch registry for the Bock interpreter.
2//!
3//! Provides a `(TypeTag, method_name) → Rust function` dispatch table with a
4//! registration API. Phase 6 packages (P6.1–P6.6) can extend the registry
5//! without modifying this module.
6
7use std::collections::HashMap;
8
9use futures::future::BoxFuture;
10
11use crate::error::RuntimeError;
12use crate::value::{BockString, OrdF64, Value};
13
14// ─── TypeTag ──────────────────────────────────────────────────────────────────
15
16/// Identifies the runtime type of a [`Value`] for method dispatch.
17#[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    /// Determine the [`TypeTag`] for a given [`Value`].
45    #[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    /// Human-readable name for error messages.
74    #[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
109// ─── CallbackInvoker ──────────────────────────────────────────────────────────
110
111/// Trait for invoking Bock closures from within built-in functions.
112///
113/// Higher-order builtins (e.g., `List.map`, `Optional.flat_map`) receive a
114/// `&mut dyn CallbackInvoker` so they can call user-supplied closures without
115/// needing direct access to the interpreter.
116///
117/// Returns a boxed future so the trait stays object-safe while still letting
118/// the interpreter drive recursive async evaluation.
119pub trait CallbackInvoker: Send {
120    /// Invoke `callable` (a `Value::Function`) with the given arguments.
121    fn invoke<'a>(
122        &'a mut self,
123        callable: &'a Value,
124        args: &'a [Value],
125    ) -> BoxFuture<'a, Result<Value, RuntimeError>>;
126}
127
128/// A no-op invoker that always returns an error.
129///
130/// Used in tests and contexts where callback invocation is not available.
131pub 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
147// ─── BuiltinFn ────────────────────────────────────────────────────────────────
148
149/// Signature for a built-in method.
150///
151/// The first element of `args` is the receiver (the value the method is called on).
152/// Remaining elements are the method arguments.
153pub type BuiltinFn = fn(&[Value]) -> Result<Value, RuntimeError>;
154
155/// Signature for a higher-order built-in method that needs to invoke callbacks.
156///
157/// Like [`BuiltinFn`], the first element of `args` is the receiver.
158/// The `invoker` parameter allows calling Bock closures passed as arguments.
159///
160/// Returns a boxed future so the builtin can `.await` callback invocations.
161pub type HigherOrderBuiltinFn = for<'a> fn(
162    &'a [Value],
163    &'a mut dyn CallbackInvoker,
164) -> BoxFuture<'a, Result<Value, RuntimeError>>;
165
166// ─── BuiltinRegistry ─────────────────────────────────────────────────────────
167
168/// A dispatch table mapping `(TypeTag, method_name)` pairs to built-in
169/// implementations.
170///
171/// Also supports global built-in functions (e.g., `print`, `println`, `debug`).
172/// Higher-order methods (those needing callback invocation) are stored separately.
173#[derive(Clone)]
174pub struct BuiltinRegistry {
175    /// Method dispatch: `(TypeTag, name) → fn`.
176    methods: HashMap<(TypeTag, String), BuiltinFn>,
177    /// Higher-order method dispatch: `(TypeTag, name) → fn` (needs callback invoker).
178    ho_methods: HashMap<(TypeTag, String), HigherOrderBuiltinFn>,
179    /// Global built-in functions: `name → fn`.
180    globals: HashMap<String, BuiltinFn>,
181}
182
183impl Default for BuiltinRegistry {
184    fn default() -> Self {
185        Self::new()
186    }
187}
188
189impl BuiltinRegistry {
190    /// Create an empty registry.
191    #[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    /// Register a method for a specific type.
201    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    /// Register a higher-order method that needs callback invocation.
206    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    /// Register a global built-in function.
211    pub fn register_global(&mut self, name: &str, func: BuiltinFn) {
212        self.globals.insert(name.to_string(), func);
213    }
214
215    /// Look up and call a method on a value.
216    ///
217    /// `receiver` is the value the method is called on, `args` are the
218    /// additional arguments. Returns `None` if no method is registered,
219    /// allowing the interpreter to fall back to other dispatch mechanisms.
220    ///
221    /// Note: this does NOT check higher-order methods. Use `get_ho_method`
222    /// for those, since they require a [`CallbackInvoker`].
223    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    /// Look up and call a global built-in function.
235    ///
236    /// Returns `None` if no global with that name is registered.
237    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    /// Look up a higher-order method. Returns the function pointer if found.
242    ///
243    /// The caller is responsible for invoking the returned function with
244    /// a `&mut dyn CallbackInvoker`.
245    #[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    /// Check whether a method is registered for the given type (simple or higher-order).
251    #[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    /// Check whether a global function is registered.
258    #[must_use]
259    pub fn has_global(&self, name: &str) -> bool {
260        self.globals.contains_key(name)
261    }
262
263    /// Iterate all registered `(receiver type, method name)` pairs for both
264    /// ordinary and higher-order methods. Intended for introspection tools
265    /// (vocab emitter, documentation generators).
266    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    /// Iterate all registered global function names.
274    pub fn global_names(&self) -> impl Iterator<Item = &str> {
275        self.globals.keys().map(String::as_str)
276    }
277
278    /// Register test assertion built-ins (`expect` global and assertion methods).
279    ///
280    /// Call this when running in test mode so that `expect(x).to_equal(y)` and
281    /// related assertion methods are available.
282    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    /// Register the minimal bootstrap set of built-in methods and globals.
295    ///
296    /// This provides just enough to make the interpreter functional:
297    /// - Globals: `print`, `println`, `debug`
298    /// - String: `len`, `to_string`
299    /// - List: `len`, `get` (the in-place mutators — `push`/`pop`/… — are
300    ///   dispatched by the interpreter itself with receiver write-back, never
301    ///   through this value-returning registry; DQ18/DQ30)
302    /// - Map: `get`, `set`, `len`
303    pub fn register_defaults(&mut self) {
304        // ── Global functions ──────────────────────────────────────────────
305        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        // ── String methods ────────────────────────────────────────────────
313        self.register(TypeTag::String, "len", string_len);
314        self.register(TypeTag::String, "to_string", string_to_string);
315
316        // ── List methods ──────────────────────────────────────────────────
317        self.register(TypeTag::List, "len", list_len);
318        self.register(TypeTag::List, "get", list_get);
319
320        // ── Map methods ───────────────────────────────────────────────────
321        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        // ── Primitive conversion methods ─────────────────────────────────
326        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        // ── Equatable primitive bridge (`eq`) ─────────────────────────────
332        // The compiled targets lower `a.eq(b)` on a primitive receiver — and
333        // on a primitive reached through a generic `T: Equatable` bound — to
334        // the target's native equality (`PRIMITIVE_BRIDGE_METHODS` in
335        // bock-codegen). The interpreter mirrors that here so the same call
336        // dispatches under `bock run`/`bock test`; this is the dispatch
337        // `core.test.assert_eq[T: Equatable]` reaches for primitive `T`
338        // (Q-interp-assert-primitives).
339        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        // ── Universal ─────────────────────────────────────────────────────
346        // to_string for all types (registered per type for non-String)
347        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
370// ─── Global built-in functions ────────────────────────────────────────────────
371
372/// `print(args...)` — print values separated by spaces, no trailing newline.
373fn 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
379/// `println(args...)` — print values separated by spaces, with trailing newline.
380fn 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
386/// `debug(value)` — print a debug representation of a value.
387fn builtin_debug(args: &[Value]) -> Result<Value, RuntimeError> {
388    for arg in args {
389        println!("{arg:?}");
390    }
391    Ok(Value::Void)
392}
393
394/// `assert(condition, message?)` — panic if condition is false.
395fn 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
422/// `todo(message?)` — always panic with "not yet implemented".
423fn 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
432/// `unreachable(message?)` — always panic with "entered unreachable code".
433fn builtin_unreachable(_args: &[Value]) -> Result<Value, RuntimeError> {
434    Err(RuntimeError::Unreachable)
435}
436
437// ─── Universal methods ────────────────────────────────────────────────────────
438
439/// `value.to_string()` — convert any value to its string representation.
440fn 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
447// ─── Primitive conversion methods ────────────────────────────────────────────
448
449/// `int.to_float()` — convert Int to Float.
450fn 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
459/// `float.to_int()` — truncate Float to Int.
460fn 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
477/// `bool.to_int()` — convert Bool to Int (true=1, false=0).
478fn 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
487/// `char.to_int()` — convert Char to its Unicode codepoint.
488fn 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
497// ─── Equatable primitive bridge ──────────────────────────────────────────────
498
499/// `a.eq(b)` on a primitive receiver — the `Equatable` bridge method.
500///
501/// Compares with the same value equality the `==` operator uses, mirroring the
502/// compiled targets' lowering of the primitive bridge (`===` on js/ts, `==` on
503/// python/rust/go). Registered for `Int`/`Float`/`Bool`/`String`/`Char`, the
504/// primitive set the checker's `Primitive:<Ty>` / sealed `TraitBound:Equatable`
505/// receiver-kind stamps cover.
506fn 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
516// ─── String methods ───────────────────────────────────────────────────────────
517
518/// `string.len()` — number of characters (not bytes).
519fn 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
531/// `string.to_string()` — identity for strings.
532fn 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
539// ─── List methods ─────────────────────────────────────────────────────────────
540
541/// `list.len()` — number of elements.
542fn 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
551/// `list.get(index)` — get element at index, returning `Optional`.
552fn 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
580// ─── Map methods ──────────────────────────────────────────────────────────────
581
582/// `map.len()` — number of entries.
583fn 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
592/// `map.get(key)` — look up a key, returning `Optional`.
593fn 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
609/// `map.set(key, value)` — return a new map with the key-value pair added/updated.
610fn 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
632// ─── Test assertion built-ins ─────────────────────────────────────────────
633
634use crate::value::RecordValue;
635use std::collections::BTreeMap;
636
637/// `expect(value)` — create an `Expectation` record wrapping the given value.
638fn 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
648/// Extract the `actual` value from an Expectation record.
649fn 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
662/// `expect(x).to_equal(y)` — assert `x == y`.
663fn 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
677/// `expect(x).to_be_ok()` — assert `x` is `Ok(...)`.
678fn 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
688/// `expect(x).to_be_err()` — assert `x` is `Err(...)`.
689fn 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
699/// `expect(x).to_be_some()` — assert `x` is `Some(...)`.
700fn 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
710/// `expect(x).to_be_none()` — assert `x` is `None`.
711fn 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
721/// `expect(x).to_throw()` — assert `x` is an error value (Err variant).
722///
723/// Alias-like behavior for `to_be_err` but semantically about "throwing".
724fn expect_to_throw(args: &[Value]) -> Result<Value, RuntimeError> {
725    expect_to_be_err(args)
726}
727
728/// `expect(x).to_be_true()` — assert `x` is `true`.
729fn 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
739/// `expect(x).to_be_false()` — assert `x` is `false`.
740fn 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// ─── Tests ────────────────────────────────────────────────────────────────────
751
752#[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    // ── TypeTag ──────────────────────────────────────────────────────────
765
766    #[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    // ── Global functions ─────────────────────────────────────────────────
789
790    #[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    // ── String methods ───────────────────────────────────────────────────
820
821    #[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    // ── Equatable primitive bridge (`eq`) ────────────────────────────────
841
842    /// The Equatable bridge method `eq` must be registered for every
843    /// primitive type tag and mirror native equality (the compiled targets
844    /// lower `a.eq(b)` on primitives to `===`/`==`). This is the dispatch
845    /// `core.test.assert_eq[T: Equatable]` lands on for primitive `T`
846    /// (Q-interp-assert-primitives).
847    #[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    // ── List methods ─────────────────────────────────────────────────────
904
905    #[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    // ── Map methods ──────────────────────────────────────────────────────
936
937    #[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    // ── Unknown method produces clear error ──────────────────────────────
997
998    #[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    // ── Registration API extensibility ───────────────────────────────────
1006
1007    #[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    // ── Universal to_string ──────────────────────────────────────────────
1022
1023    #[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    // ── assert / todo / unreachable ─────────────────────────────────────
1044
1045    #[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}