Skip to main content

doge_runtime/
functions.rs

1use std::rc::Rc;
2
3use crate::error::{DogeError, DogeResult};
4use crate::value::{Cell, FunctionData, Value};
5
6/// Read a captured cell, cloning the value out so the borrow ends here.
7pub fn cell_get(cell: &Cell) -> Value {
8    cell.borrow().clone()
9}
10
11/// Write a captured cell. Every writer shares the same cell, so the update is
12/// visible to every closure that captured it.
13pub fn cell_set(cell: &Cell, value: Value) {
14    *cell.borrow_mut() = value;
15}
16
17/// Resolve a callee to the function it names. A class value calls the same way —
18/// its `fn_id` is a constructor arm — so both unwrap to their shared
19/// [`FunctionData`]. Calling anything else is a catchable `TypeError`, worded
20/// from the caller's point of view.
21pub fn callee_function(value: &Value) -> DogeResult<Rc<FunctionData>> {
22    match value {
23        Value::Function(f) | Value::Class(f) => Ok(Rc::clone(f)),
24        other => Err(DogeError::type_error(format!(
25            "cannot call {} — it is not a function",
26            other.describe()
27        ))),
28    }
29}
30
31/// The "takes … arguments, got N" phrase shared by the function- and
32/// method-arity errors. `max` is `None` for a variadic header (no upper bound);
33/// when it equals `min` the header is fixed-arity and reads as a single count.
34pub(crate) fn arity_phrase(subject: &str, min: usize, max: Option<usize>, got: usize) -> String {
35    let noun = |count: usize| if count == 1 { "argument" } else { "arguments" };
36    match max {
37        Some(max) if max == min => {
38            format!("{subject} takes {min} {}, got {got}", noun(min))
39        }
40        Some(max) => format!("{subject} takes {min} to {max} arguments, got {got}"),
41        None => format!("{subject} takes at least {min} {}, got {got}", noun(min)),
42    }
43}
44
45/// The error an indirect call raises when the argument count is wrong, worded
46/// like the compiler's user-function arity message. `max` is `None` when the
47/// function is variadic.
48pub fn function_arity_error(name: &str, min: usize, max: Option<usize>, got: usize) -> DogeError {
49    DogeError::type_error(arity_phrase(name, min, max, got))
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55    use crate::error::ErrorKind;
56
57    #[test]
58    fn cell_round_trips_and_shares() {
59        let cell: Cell = Rc::new(std::cell::RefCell::new(Value::int(1)));
60        assert!(crate::values_equal(&cell_get(&cell), &Value::int(1)));
61        let shared = Rc::clone(&cell);
62        cell_set(&cell, Value::int(2));
63        // The write is visible through the shared handle.
64        assert!(crate::values_equal(&cell_get(&shared), &Value::int(2)));
65    }
66
67    #[test]
68    fn callee_function_unwraps_a_function() {
69        let f = Value::function(3, "greet", vec![]);
70        let got = callee_function(&f).expect("a function");
71        assert_eq!(got.fn_id, 3);
72    }
73
74    #[test]
75    fn calling_a_non_function_is_a_catchable_type_error() {
76        let err = callee_function(&Value::int(1)).unwrap_err();
77        assert_eq!(err.kind, ErrorKind::TypeError);
78        assert_eq!(err.message, "cannot call an Int — it is not a function");
79    }
80
81    #[test]
82    fn function_arity_error_matches_the_user_wording() {
83        assert_eq!(
84            function_arity_error("greet", 2, Some(2), 1).message,
85            "greet takes 2 arguments, got 1"
86        );
87        assert_eq!(
88            function_arity_error("f", 1, Some(1), 0).message,
89            "f takes 1 argument, got 0"
90        );
91    }
92
93    #[test]
94    fn function_arity_error_reports_ranges_and_variadics() {
95        assert_eq!(
96            function_arity_error("greet", 1, Some(3), 4).message,
97            "greet takes 1 to 3 arguments, got 4"
98        );
99        assert_eq!(
100            function_arity_error("party", 1, None, 0).message,
101            "party takes at least 1 argument, got 0"
102        );
103    }
104}