Skip to main content

doge_runtime/
value.rs

1use std::cell::RefCell;
2use std::collections::HashMap;
3use std::rc::Rc;
4
5use crate::error::{ErrorData, ErrorKind};
6use crate::ordered_map::OrderedMap;
7
8/// A shared, mutable binding cell. Closures capture enclosing variables by
9/// sharing these: a `such`/param captured by a nested function becomes a `Cell`,
10/// so a reassignment on either side is visible to the other.
11pub type Cell = Rc<RefCell<Value>>;
12
13/// A dynamically typed Doge value.
14#[derive(Debug, Clone)]
15pub enum Value {
16    Int(i64),
17    Float(f64),
18    Str(Rc<str>),
19    Bool(bool),
20    None,
21    List(Rc<RefCell<Vec<Value>>>),
22    Dict(Rc<RefCell<OrderedMap>>),
23    Object(Rc<RefCell<ObjectData>>),
24    Function(Rc<FunctionData>),
25    /// A class name used as a value: a callable that constructs an instance. It
26    /// carries the same [`FunctionData`] a function value does — its `fn_id` is a
27    /// constructor arm in the `call_function` dispatcher — so the whole indirect
28    /// call path works unchanged, but it keeps a distinct identity (prints
29    /// `<class Name>`, type `Class`) rather than masquerading as a function.
30    Class(Rc<FunctionData>),
31    /// A method bound to its receiver: `such f = a.speak` captures both the
32    /// object (or List/Dict) and the method name, so calling `f(...)` dispatches
33    /// exactly as `a.speak(...)` would. Name-based, like a direct method call — it
34    /// carries no `fn_id`, so both engines route it back through their method
35    /// dispatch. Prints `<method Class.name>`, type `Method`, equal only to the
36    /// same method bound to the very same receiver.
37    BoundMethod(Rc<BoundMethodData>),
38    Error(Rc<ErrorData>),
39}
40
41/// A method captured together with the receiver it was read off. Two bound
42/// methods are equal only when they name the same method on the very same
43/// instance (`a.speak == a.speak`, but not `b.speak`).
44#[derive(Debug)]
45pub struct BoundMethodData {
46    pub receiver: Value,
47    pub method: Rc<str>,
48}
49
50/// A first-class function value: which compiled function it is (`fn_id`, matched
51/// by the generated `call_function` dispatcher), the name it prints and errors
52/// under, and the cells it captured from its enclosing scope. Two function values
53/// are equal only when they share both the definition and the captured cells.
54#[derive(Debug)]
55pub struct FunctionData {
56    pub fn_id: u32,
57    pub name: Rc<str>,
58    pub captures: Vec<Cell>,
59}
60
61/// The innards of a `many Name:` instance: which class it is (a compile-time id
62/// plus the display name) and its fields, which appear the moment they are
63/// assigned. Two instances are the same object only when they share this `Rc`.
64#[derive(Debug)]
65pub struct ObjectData {
66    pub class_id: u32,
67    pub class_name: Rc<str>,
68    pub fields: HashMap<String, Value>,
69}
70
71impl Value {
72    /// Build a `Str` value from anything string-like.
73    pub fn str(s: impl AsRef<str>) -> Value {
74        Value::Str(Rc::from(s.as_ref()))
75    }
76
77    /// Build a `List` value from a vector of elements.
78    pub fn list(items: Vec<Value>) -> Value {
79        Value::List(Rc::new(RefCell::new(items)))
80    }
81
82    /// Build a `Dict` value from an insertion-ordered map.
83    pub fn dict(entries: OrderedMap) -> Value {
84        Value::Dict(Rc::new(RefCell::new(entries)))
85    }
86
87    /// Build a fresh instance of the class with `class_id`/`class_name` and no
88    /// fields yet — the constructor fills them in with `attr_set`.
89    pub fn object(class_id: u32, class_name: &str) -> Value {
90        Value::Object(Rc::new(RefCell::new(ObjectData {
91            class_id,
92            class_name: Rc::from(class_name),
93            fields: HashMap::new(),
94        })))
95    }
96
97    /// Build a caught `Error` value from a raised error's category, message, and
98    /// the file/line it was raised at (`err.type` / `err.message` / `err.file` /
99    /// `err.line`). Built by [`crate::error::error_value`] at each catch site.
100    pub fn error(kind: ErrorKind, message: &str, file: Rc<str>, line: u32) -> Value {
101        Value::Error(Rc::new(ErrorData {
102            kind,
103            message: Rc::from(message),
104            file,
105            line,
106        }))
107    }
108
109    /// Build a first-class function value with `fn_id`, display `name`, and the
110    /// captured `captures` cells (empty for a top-level function or a closure that
111    /// captures nothing).
112    pub fn function(fn_id: u32, name: &str, captures: Vec<Cell>) -> Value {
113        Value::Function(Rc::new(FunctionData {
114            fn_id,
115            name: Rc::from(name),
116            captures,
117        }))
118    }
119
120    /// Build a bound-method value capturing `receiver` and the `method` name. The
121    /// receiver is any value method dispatch accepts — a `many` instance, or a
122    /// List/Dict for its collection methods.
123    pub fn bound_method(receiver: Value, method: &str) -> Value {
124        Value::BoundMethod(Rc::new(BoundMethodData {
125            receiver,
126            method: Rc::from(method),
127        }))
128    }
129
130    /// Build a class value from the constructor arm `fn_id` and the class `name`.
131    /// A class captures nothing — calling it always builds a fresh instance — so
132    /// its `captures` are empty and two values for the same class compare equal.
133    pub fn class(fn_id: u32, name: &str) -> Value {
134        Value::Class(Rc::new(FunctionData {
135            fn_id,
136            name: Rc::from(name),
137            captures: Vec::new(),
138        }))
139    }
140
141    /// Build a `Dict` from key/value pairs evaluated by a dict literal. Every
142    /// key must be a `Str`; anything else is a catchable type error. Pairs are
143    /// inserted in order, so when a key repeats the last entry wins.
144    pub fn dict_from_pairs(pairs: Vec<(Value, Value)>) -> crate::error::DogeResult {
145        let mut entries = OrderedMap::new();
146        for (key, value) in pairs {
147            match key {
148                Value::Str(k) => {
149                    entries.insert(k.to_string(), value);
150                }
151                other => {
152                    return Err(crate::error::DogeError::type_error(format!(
153                        "dict keys must be a Str, got {}",
154                        other.describe()
155                    )))
156                }
157            }
158        }
159        Ok(Value::dict(entries))
160    }
161
162    /// Python-style truthiness: `0`, `0.0`, `""`, empty list/dict, `none` and
163    /// `false` are falsy; everything else is truthy.
164    pub fn truthy(&self) -> bool {
165        match self {
166            Value::Int(n) => *n != 0,
167            Value::Float(f) => *f != 0.0,
168            Value::Str(s) => !s.is_empty(),
169            Value::Bool(b) => *b,
170            Value::None => false,
171            Value::List(items) => !items.borrow().is_empty(),
172            Value::Dict(entries) => !entries.borrow().is_empty(),
173            Value::Object(_) => true,
174            Value::Function(_) => true,
175            Value::Class(_) => true,
176            Value::BoundMethod(_) => true,
177            Value::Error(_) => true,
178        }
179    }
180
181    /// The user-facing type name, used in error messages.
182    pub fn type_name(&self) -> &'static str {
183        match self {
184            Value::Int(_) => "Int",
185            Value::Float(_) => "Float",
186            Value::Str(_) => "Str",
187            Value::Bool(_) => "Bool",
188            Value::None => "None",
189            Value::List(_) => "List",
190            Value::Dict(_) => "Dict",
191            Value::Object(_) => "Object",
192            Value::Function(_) => "Function",
193            Value::Class(_) => "Class",
194            Value::BoundMethod(_) => "Method",
195            Value::Error(_) => "Error",
196        }
197    }
198
199    /// The type name with the right English article, for error messages —
200    /// `"a Str"`, `"an Int"`. Single source so every diagnostic reads the same.
201    pub fn describe(&self) -> String {
202        let name = self.type_name();
203        let article = match name.chars().next() {
204            Some('A' | 'E' | 'I' | 'O' | 'U') => "an",
205            _ => "a",
206        };
207        format!("{article} {name}")
208    }
209}
210
211#[cfg(test)]
212mod tests {
213    use super::*;
214
215    #[test]
216    fn truthiness_follows_python() {
217        assert!(!Value::Int(0).truthy());
218        assert!(Value::Int(1).truthy());
219        assert!(!Value::Float(0.0).truthy());
220        assert!(Value::Float(0.1).truthy());
221        assert!(!Value::str("").truthy());
222        assert!(Value::str("dog").truthy());
223        assert!(!Value::Bool(false).truthy());
224        assert!(Value::Bool(true).truthy());
225        assert!(!Value::None.truthy());
226        assert!(!Value::list(vec![]).truthy());
227        assert!(Value::list(vec![Value::Int(1)]).truthy());
228        assert!(!Value::dict(OrderedMap::new()).truthy());
229        // An object is always truthy, even with no fields.
230        assert!(Value::object(0, "Shibe").truthy());
231        // A function is always truthy.
232        assert!(Value::function(0, "greet", vec![]).truthy());
233    }
234
235    #[test]
236    fn type_names_match_design() {
237        assert_eq!(Value::Int(1).type_name(), "Int");
238        assert_eq!(Value::Float(1.0).type_name(), "Float");
239        assert_eq!(Value::str("x").type_name(), "Str");
240        assert_eq!(Value::Bool(true).type_name(), "Bool");
241        assert_eq!(Value::None.type_name(), "None");
242        assert_eq!(Value::list(vec![]).type_name(), "List");
243        assert_eq!(Value::dict(OrderedMap::new()).type_name(), "Dict");
244        assert_eq!(Value::object(0, "Shibe").type_name(), "Object");
245        assert_eq!(Value::function(0, "greet", vec![]).type_name(), "Function");
246    }
247
248    #[test]
249    fn describe_uses_the_right_article() {
250        assert_eq!(Value::Int(1).describe(), "an Int");
251        assert_eq!(Value::str("x").describe(), "a Str");
252        assert_eq!(Value::None.describe(), "a None");
253    }
254
255    #[test]
256    fn dict_from_pairs_last_duplicate_wins() {
257        let d = Value::dict_from_pairs(vec![
258            (Value::str("k"), Value::Int(1)),
259            (Value::str("k"), Value::Int(2)),
260        ])
261        .unwrap();
262        match d {
263            Value::Dict(entries) => {
264                let entries = entries.borrow();
265                assert_eq!(entries.len(), 1);
266                assert!(matches!(entries.get("k"), Some(Value::Int(2))));
267            }
268            _ => panic!("expected a dict"),
269        }
270    }
271
272    #[test]
273    fn dict_from_pairs_rejects_non_str_key() {
274        let err = Value::dict_from_pairs(vec![(Value::Int(1), Value::Int(2))]).unwrap_err();
275        assert_eq!(err.kind, crate::error::ErrorKind::TypeError);
276    }
277
278    #[test]
279    fn str_constructor_shares_via_rc() {
280        let a = Value::str("kabosu");
281        let b = a.clone();
282        // Cloning a Str clones the Rc, not the bytes — assignment never "moves".
283        match (&a, &b) {
284            (Value::Str(x), Value::Str(y)) => assert!(Rc::ptr_eq(x, y)),
285            _ => panic!("expected two Str values"),
286        }
287    }
288}