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