Skip to main content

doge_runtime/methods/
mod.rs

1//! Built-in method dispatch for collection values — the counterpart of
2//! [`crate::objects`] for `many` instances. A method call on a non-`Object`
3//! receiver (`xs.append(1)`, `d.keys()`) routes here; the generated `call_method`
4//! dispatcher handles `Object` receivers itself and forwards everything else.
5//!
6//! Behaviour lives here, not in codegen (generated Rust is thin glue): each
7//! method mutates or reads the value's shared cell and returns a `Value`, and
8//! every failure is a catchable [`DogeError`], never a panic.
9
10use std::rc::Rc;
11
12use bigdecimal::ToPrimitive;
13
14use crate::error::{DogeError, DogeResult};
15use crate::objects::method_arity_error;
16use crate::value::Value;
17
18mod bytes;
19mod dict;
20mod list;
21mod str;
22#[cfg(test)]
23mod tests;
24
25use bytes::bytes_method;
26use dict::dict_method;
27use list::list_method;
28use str::str_method;
29
30/// Dispatch a method call on a List or Dict. `recv` is the receiver, `name` the
31/// method, `args` the already-evaluated arguments. A non-collection receiver, an
32/// unknown method, or a wrong argument count is a catchable error.
33pub fn builtin_method(recv: &Value, name: &str, args: Vec<Value>) -> DogeResult {
34    match recv {
35        Value::List(_) => list_method(recv, name, args),
36        Value::Dict(_) => dict_method(recv, name, args),
37        Value::Bytes(_) => bytes_method(recv, name, args),
38        Value::Str(_) => str_method(recv, name, args),
39        // The dispatcher routes objects to its own class match, never here; this
40        // defensive branch names the class rather than claiming "no methods".
41        Value::Object(_) => Err(crate::objects::no_such_method(recv, name)),
42        // No other value has methods at all. Listed by variant rather than a
43        // wildcard, so a new Value variant with methods forces a decision here.
44        Value::Int(_)
45        | Value::Float(_)
46        | Value::Decimal(_)
47        | Value::Bool(_)
48        | Value::None
49        | Value::Function(_)
50        | Value::Class(_)
51        | Value::BoundMethod(_)
52        | Value::Error(_)
53        | Value::Socket(_)
54        | Value::Pup(_)
55        | Value::Bowl(_) => Err(crate::objects::no_methods_error(recv)),
56    }
57}
58
59/// Whether a List or Dict has a built-in method named `name` — the gate a bound
60/// method read (`such f = xs.append`) checks before capturing the receiver. The
61/// method-name sets live beside each collection's dispatch (`list::LIST_METHODS`,
62/// `dict::DICT_METHODS`), so binding and dispatch never disagree.
63pub fn has_builtin_method(recv: &Value, name: &str) -> bool {
64    match recv {
65        Value::List(_) => list::LIST_METHODS.contains(&name),
66        Value::Dict(_) => dict::DICT_METHODS.contains(&name),
67        Value::Bytes(_) => bytes::BYTES_METHODS.contains(&name),
68        Value::Str(_) => str::STR_METHODS.contains(&name),
69        _ => false,
70    }
71}
72
73/// The arity gate every method runs first: reuses the object-method wording so a
74/// List/Dict arity error reads exactly like `Shibe.speak takes 1 argument, got 0`.
75pub(super) fn check_arity(
76    class: &str,
77    method: &str,
78    expected: usize,
79    got: usize,
80) -> DogeResult<()> {
81    if got == expected {
82        Ok(())
83    } else {
84        Err(method_arity_error(
85            class,
86            method,
87            expected,
88            Some(expected),
89            got,
90        ))
91    }
92}
93
94/// Take an argument that must be an Int, or raise the standard type error naming
95/// the method and what it got. `what` is the argument's role, e.g. `List.insert
96/// needs an Int index`.
97pub(super) fn expect_int(value: Value, what: &str) -> DogeResult<i64> {
98    match value {
99        // The argument is a machine index/count; a value past the i64 range can
100        // never be a valid one, so it is a catchable error rather than a panic.
101        Value::Int(n) => n
102            .to_i64()
103            .ok_or_else(|| DogeError::value_error(format!("{what}, but {n} is too large"))),
104        other => Err(DogeError::type_error(format!(
105            "{what}, got {}",
106            other.describe()
107        ))),
108    }
109}
110
111/// Take an argument that must be a Str, or raise the standard type error naming
112/// the method and what it got.
113pub(super) fn expect_str(value: Value, what: &str) -> DogeResult<Rc<str>> {
114    match value {
115        Value::Str(s) => Ok(s),
116        other => Err(DogeError::type_error(format!(
117            "{what}, got {}",
118            other.describe()
119        ))),
120    }
121}