Skip to main content

doge_runtime/
value.rs

1use std::cell::RefCell;
2use std::collections::HashMap;
3use std::net::{TcpListener, TcpStream};
4use std::rc::Rc;
5use std::thread::JoinHandle;
6
7use crate::error::{ErrorData, ErrorKind};
8use crate::ordered_map::OrderedMap;
9use crate::pack::{BowlHandle, Packed, PackedError};
10
11/// A shared, mutable binding cell. Closures capture enclosing variables by
12/// sharing these: a `such`/param captured by a nested function becomes a `Cell`,
13/// so a reassignment on either side is visible to the other.
14pub type Cell = Rc<RefCell<Value>>;
15
16/// A dynamically typed Doge value.
17#[derive(Debug, Clone)]
18pub enum Value {
19    Int(i64),
20    Float(f64),
21    Str(Rc<str>),
22    Bool(bool),
23    None,
24    List(Rc<RefCell<Vec<Value>>>),
25    Dict(Rc<RefCell<OrderedMap>>),
26    Object(Rc<RefCell<ObjectData>>),
27    Function(Rc<FunctionData>),
28    /// A class name used as a value: a callable that constructs an instance. It
29    /// carries the same [`FunctionData`] a function value does — its `fn_id` is a
30    /// constructor arm in the `call_function` dispatcher — so the whole indirect
31    /// call path works unchanged, but it keeps a distinct identity (prints
32    /// `<class Name>`, type `Class`) rather than masquerading as a function.
33    Class(Rc<FunctionData>),
34    /// A method bound to its receiver: `such f = a.speak` captures both the
35    /// object (or List/Dict) and the method name, so calling `f(...)` dispatches
36    /// exactly as `a.speak(...)` would. Name-based, like a direct method call — it
37    /// carries no `fn_id`, so both engines route it back through their method
38    /// dispatch. Prints `<method Class.name>`, type `Method`, equal only to the
39    /// same method bound to the very same receiver.
40    BoundMethod(Rc<BoundMethodData>),
41    Error(Rc<ErrorData>),
42    /// A network socket opened by the `howl` module: a TCP listener or an open
43    /// connection, or a closed handle once `howl.close` has run. Sockets are
44    /// opaque — they have no methods or fields, compare by identity, and close
45    /// automatically when the last reference is dropped. Two socket values are
46    /// the same socket only when they share this `Rc`.
47    Socket(Rc<SocketData>),
48    /// A pup: a function running on its own OS thread, spawned by `pack.zoom`.
49    /// Opaque like a socket — no methods or fields, identity comparison — and
50    /// waited on with `pack.fetch`, which returns the function's result (or
51    /// re-raises the error it hit). A pup cannot be sent to another pup.
52    Pup(Rc<PupData>),
53    /// A bowl: an unbounded channel opened by `pack.bowl`, over which pups pass
54    /// values (`pack.drop`/`pack.sniff`). Unlike every other value, a bowl is
55    /// *shared*, not copied, when it crosses a pup boundary — both sides talk over
56    /// the same channel. Opaque and compared by identity.
57    Bowl(Rc<BowlData>),
58}
59
60/// The innards of a [`Value::Socket`]: the live OS handle behind a `RefCell`, so
61/// `howl.recv`/`howl.close` can read and mutate it through a shared value.
62#[derive(Debug)]
63pub struct SocketData {
64    pub state: RefCell<SocketState>,
65}
66
67/// What a socket currently is: a listener waiting for connections, an open
68/// connection (with any bytes read past a line boundary held for the next read),
69/// or a closed handle. Every `howl` operation on a `Closed` socket is a catchable
70/// IOError rather than a panic.
71#[derive(Debug)]
72pub enum SocketState {
73    Listener(TcpListener),
74    Conn { stream: TcpStream, buf: Vec<u8> },
75    Closed,
76}
77
78/// The innards of a [`Value::Pup`]: the join handle of its OS thread behind a
79/// `RefCell` so `pack.fetch` can take it. The thread yields either the packed
80/// return value or a packed error. Once fetched, the state is [`PupState::Fetched`]
81/// and a second fetch is a catchable error.
82#[derive(Debug)]
83pub struct PupData {
84    pub state: RefCell<PupState>,
85}
86
87/// What a pup currently is: still running (its join handle is available to wait
88/// on), or already fetched (its result has been claimed).
89#[derive(Debug)]
90pub enum PupState {
91    Running(JoinHandle<Result<Packed, PackedError>>),
92    Fetched,
93}
94
95/// The innards of a [`Value::Bowl`]: the shared channel handle. Cloning a bowl
96/// value shares this handle, and so does sending a bowl to a pup — both reach the
97/// same channel.
98#[derive(Debug)]
99pub struct BowlData {
100    pub handle: BowlHandle,
101}
102
103/// A method captured together with the receiver it was read off. Two bound
104/// methods are equal only when they name the same method on the very same
105/// instance (`a.speak == a.speak`, but not `b.speak`).
106#[derive(Debug)]
107pub struct BoundMethodData {
108    pub receiver: Value,
109    pub method: Rc<str>,
110}
111
112/// A first-class function value: which compiled function it is (`fn_id`, matched
113/// by the generated `call_function` dispatcher), the name it prints and errors
114/// under, and the cells it captured from its enclosing scope. Two function values
115/// are equal only when they share both the definition and the captured cells.
116#[derive(Debug)]
117pub struct FunctionData {
118    pub fn_id: u32,
119    pub name: Rc<str>,
120    pub captures: Vec<Cell>,
121}
122
123/// The innards of a `many Name:` instance: which class it is (a compile-time id
124/// plus the display name) and its fields, which appear the moment they are
125/// assigned. Two instances are the same object only when they share this `Rc`.
126#[derive(Debug)]
127pub struct ObjectData {
128    pub class_id: u32,
129    pub class_name: Rc<str>,
130    pub fields: HashMap<String, Value>,
131}
132
133impl Value {
134    /// Build a `Str` value from anything string-like.
135    pub fn str(s: impl AsRef<str>) -> Value {
136        Value::Str(Rc::from(s.as_ref()))
137    }
138
139    /// Build a `List` value from a vector of elements.
140    pub fn list(items: Vec<Value>) -> Value {
141        Value::List(Rc::new(RefCell::new(items)))
142    }
143
144    /// Build a `Dict` value from an insertion-ordered map.
145    pub fn dict(entries: OrderedMap) -> Value {
146        Value::Dict(Rc::new(RefCell::new(entries)))
147    }
148
149    /// Build a fresh instance of the class with `class_id`/`class_name` and no
150    /// fields yet — the constructor fills them in with `attr_set`.
151    pub fn object(class_id: u32, class_name: &str) -> Value {
152        Value::Object(Rc::new(RefCell::new(ObjectData {
153            class_id,
154            class_name: Rc::from(class_name),
155            fields: HashMap::new(),
156        })))
157    }
158
159    /// Build a caught `Error` value from a raised error's category, message, and
160    /// the file/line it was raised at (`err.type` / `err.message` / `err.file` /
161    /// `err.line`). Built by [`crate::error::error_value`] at each catch site.
162    pub fn error(kind: ErrorKind, message: &str, file: Rc<str>, line: u32) -> Value {
163        Value::Error(Rc::new(ErrorData {
164            kind,
165            message: Rc::from(message),
166            file,
167            line,
168        }))
169    }
170
171    /// Build a first-class function value with `fn_id`, display `name`, and the
172    /// captured `captures` cells (empty for a top-level function or a closure that
173    /// captures nothing).
174    pub fn function(fn_id: u32, name: &str, captures: Vec<Cell>) -> Value {
175        Value::Function(Rc::new(FunctionData {
176            fn_id,
177            name: Rc::from(name),
178            captures,
179        }))
180    }
181
182    /// Build a bound-method value capturing `receiver` and the `method` name. The
183    /// receiver is any value method dispatch accepts — a `many` instance, or a
184    /// List/Dict for its collection methods.
185    pub fn bound_method(receiver: Value, method: &str) -> Value {
186        Value::BoundMethod(Rc::new(BoundMethodData {
187            receiver,
188            method: Rc::from(method),
189        }))
190    }
191
192    /// Build a socket value wrapping an initial [`SocketState`] — a fresh
193    /// listener or connection from the `howl` module.
194    pub fn socket(state: SocketState) -> Value {
195        Value::Socket(Rc::new(SocketData {
196            state: RefCell::new(state),
197        }))
198    }
199
200    /// Build a running pup value around the join handle of its OS thread.
201    pub fn pup(handle: JoinHandle<Result<Packed, PackedError>>) -> Value {
202        Value::Pup(Rc::new(PupData {
203            state: RefCell::new(PupState::Running(handle)),
204        }))
205    }
206
207    /// Build a bowl value around a channel handle — a fresh channel from
208    /// `pack.bowl`, or a shared handle rebuilt on the far side of a pup boundary.
209    pub fn bowl(handle: BowlHandle) -> Value {
210        Value::Bowl(Rc::new(BowlData { handle }))
211    }
212
213    /// Build a class value from the constructor arm `fn_id` and the class `name`.
214    /// A class captures nothing — calling it always builds a fresh instance — so
215    /// its `captures` are empty and two values for the same class compare equal.
216    pub fn class(fn_id: u32, name: &str) -> Value {
217        Value::Class(Rc::new(FunctionData {
218            fn_id,
219            name: Rc::from(name),
220            captures: Vec::new(),
221        }))
222    }
223
224    /// Build a `Dict` from key/value pairs evaluated by a dict literal. Every
225    /// key must be a `Str`; anything else is a catchable type error. Pairs are
226    /// inserted in order, so when a key repeats the last entry wins.
227    pub fn dict_from_pairs(pairs: Vec<(Value, Value)>) -> crate::error::DogeResult {
228        let mut entries = OrderedMap::new();
229        for (key, value) in pairs {
230            match key {
231                Value::Str(k) => {
232                    entries.insert(k.to_string(), value);
233                }
234                other => {
235                    return Err(crate::error::DogeError::type_error(format!(
236                        "dict keys must be a Str, got {}",
237                        other.describe()
238                    )))
239                }
240            }
241        }
242        Ok(Value::dict(entries))
243    }
244
245    /// Python-style truthiness: `0`, `0.0`, `""`, empty list/dict, `none` and
246    /// `false` are falsy; everything else is truthy.
247    pub fn truthy(&self) -> bool {
248        match self {
249            Value::Int(n) => *n != 0,
250            Value::Float(f) => *f != 0.0,
251            Value::Str(s) => !s.is_empty(),
252            Value::Bool(b) => *b,
253            Value::None => false,
254            Value::List(items) => !items.borrow().is_empty(),
255            Value::Dict(entries) => !entries.borrow().is_empty(),
256            Value::Object(_) => true,
257            Value::Function(_) => true,
258            Value::Class(_) => true,
259            Value::BoundMethod(_) => true,
260            Value::Error(_) => true,
261            Value::Socket(_) => true,
262            Value::Pup(_) => true,
263            Value::Bowl(_) => true,
264        }
265    }
266
267    /// The user-facing type name, used in error messages.
268    pub fn type_name(&self) -> &'static str {
269        match self {
270            Value::Int(_) => "Int",
271            Value::Float(_) => "Float",
272            Value::Str(_) => "Str",
273            Value::Bool(_) => "Bool",
274            Value::None => "None",
275            Value::List(_) => "List",
276            Value::Dict(_) => "Dict",
277            Value::Object(_) => "Object",
278            Value::Function(_) => "Function",
279            Value::Class(_) => "Class",
280            Value::BoundMethod(_) => "Method",
281            Value::Error(_) => "Error",
282            Value::Socket(_) => "Socket",
283            Value::Pup(_) => "Pup",
284            Value::Bowl(_) => "Bowl",
285        }
286    }
287
288    /// The type name with the right English article, for error messages —
289    /// `"a Str"`, `"an Int"`. Single source so every diagnostic reads the same.
290    pub fn describe(&self) -> String {
291        let name = self.type_name();
292        let article = match name.chars().next() {
293            Some('A' | 'E' | 'I' | 'O' | 'U') => "an",
294            _ => "a",
295        };
296        format!("{article} {name}")
297    }
298}
299
300#[cfg(test)]
301mod tests {
302    use super::*;
303
304    #[test]
305    fn truthiness_follows_python() {
306        assert!(!Value::Int(0).truthy());
307        assert!(Value::Int(1).truthy());
308        assert!(!Value::Float(0.0).truthy());
309        assert!(Value::Float(0.1).truthy());
310        assert!(!Value::str("").truthy());
311        assert!(Value::str("dog").truthy());
312        assert!(!Value::Bool(false).truthy());
313        assert!(Value::Bool(true).truthy());
314        assert!(!Value::None.truthy());
315        assert!(!Value::list(vec![]).truthy());
316        assert!(Value::list(vec![Value::Int(1)]).truthy());
317        assert!(!Value::dict(OrderedMap::new()).truthy());
318        // An object is always truthy, even with no fields.
319        assert!(Value::object(0, "Shibe").truthy());
320        // A function is always truthy.
321        assert!(Value::function(0, "greet", vec![]).truthy());
322    }
323
324    #[test]
325    fn type_names_match_design() {
326        assert_eq!(Value::Int(1).type_name(), "Int");
327        assert_eq!(Value::Float(1.0).type_name(), "Float");
328        assert_eq!(Value::str("x").type_name(), "Str");
329        assert_eq!(Value::Bool(true).type_name(), "Bool");
330        assert_eq!(Value::None.type_name(), "None");
331        assert_eq!(Value::list(vec![]).type_name(), "List");
332        assert_eq!(Value::dict(OrderedMap::new()).type_name(), "Dict");
333        assert_eq!(Value::object(0, "Shibe").type_name(), "Object");
334        assert_eq!(Value::function(0, "greet", vec![]).type_name(), "Function");
335    }
336
337    #[test]
338    fn describe_uses_the_right_article() {
339        assert_eq!(Value::Int(1).describe(), "an Int");
340        assert_eq!(Value::str("x").describe(), "a Str");
341        assert_eq!(Value::None.describe(), "a None");
342    }
343
344    #[test]
345    fn dict_from_pairs_last_duplicate_wins() {
346        let d = Value::dict_from_pairs(vec![
347            (Value::str("k"), Value::Int(1)),
348            (Value::str("k"), Value::Int(2)),
349        ])
350        .unwrap();
351        match d {
352            Value::Dict(entries) => {
353                let entries = entries.borrow();
354                assert_eq!(entries.len(), 1);
355                assert!(matches!(entries.get("k"), Some(Value::Int(2))));
356            }
357            _ => panic!("expected a dict"),
358        }
359    }
360
361    #[test]
362    fn dict_from_pairs_rejects_non_str_key() {
363        let err = Value::dict_from_pairs(vec![(Value::Int(1), Value::Int(2))]).unwrap_err();
364        assert_eq!(err.kind, crate::error::ErrorKind::TypeError);
365    }
366
367    #[test]
368    fn str_constructor_shares_via_rc() {
369        let a = Value::str("kabosu");
370        let b = a.clone();
371        // Cloning a Str clones the Rc, not the bytes — assignment never "moves".
372        match (&a, &b) {
373            (Value::Str(x), Value::Str(y)) => assert!(Rc::ptr_eq(x, y)),
374            _ => panic!("expected two Str values"),
375        }
376    }
377}