Skip to main content

harn_vm/value/
core.rs

1use std::collections::BTreeMap;
2use std::rc::Rc;
3use std::sync::atomic::Ordering;
4use std::{cell::RefCell, future::Future, pin::Pin};
5
6use crate::mcp::VmMcpClientHandle;
7
8use super::{VmAtomicHandle, VmChannelHandle, VmClosure, VmError, VmGenerator, VmRange};
9
10/// An async builtin function for the VM.
11pub type VmAsyncBuiltinFn =
12    Rc<dyn Fn(Vec<VmValue>) -> Pin<Box<dyn Future<Output = Result<VmValue, VmError>>>>>;
13
14/// VM runtime value.
15#[derive(Debug, Clone)]
16pub enum VmValue {
17    Int(i64),
18    Float(f64),
19    String(Rc<str>),
20    Bool(bool),
21    Nil,
22    List(Rc<Vec<VmValue>>),
23    Dict(Rc<BTreeMap<String, VmValue>>),
24    Closure(Rc<VmClosure>),
25    /// Reference to a registered builtin function, used when a builtin name is
26    /// referenced as a value (e.g. `snake_dict.rekey(snake_to_camel)`). The
27    /// contained string is the builtin's registered name.
28    BuiltinRef(Rc<str>),
29    Duration(u64),
30    EnumVariant {
31        enum_name: String,
32        variant: String,
33        fields: Vec<VmValue>,
34    },
35    StructInstance {
36        struct_name: String,
37        fields: BTreeMap<String, VmValue>,
38    },
39    TaskHandle(String),
40    Channel(VmChannelHandle),
41    Atomic(VmAtomicHandle),
42    McpClient(VmMcpClientHandle),
43    Set(Rc<Vec<VmValue>>),
44    Generator(VmGenerator),
45    Range(VmRange),
46    /// Lazy iterator handle. Single-pass, fused. See `crate::vm::iter::VmIter`.
47    Iter(Rc<RefCell<crate::vm::iter::VmIter>>),
48    /// Two-element pair value. Produced by `pair(a, b)`, yielded by the
49    /// Dict iterator source, and (later) by `zip` / `enumerate` combinators.
50    /// Accessed via `.first` / `.second`, and destructurable in
51    /// `for (a, b) in ...` loops.
52    Pair(Rc<(VmValue, VmValue)>),
53}
54
55impl VmValue {
56    pub fn is_truthy(&self) -> bool {
57        match self {
58            VmValue::Bool(b) => *b,
59            VmValue::Nil => false,
60            VmValue::Int(n) => *n != 0,
61            VmValue::Float(n) => *n != 0.0,
62            VmValue::String(s) => !s.is_empty(),
63            VmValue::List(l) => !l.is_empty(),
64            VmValue::Dict(d) => !d.is_empty(),
65            VmValue::Closure(_) => true,
66            VmValue::BuiltinRef(_) => true,
67            VmValue::Duration(ms) => *ms > 0,
68            VmValue::EnumVariant { .. } => true,
69            VmValue::StructInstance { .. } => true,
70            VmValue::TaskHandle(_) => true,
71            VmValue::Channel(_) => true,
72            VmValue::Atomic(_) => true,
73            VmValue::McpClient(_) => true,
74            VmValue::Set(s) => !s.is_empty(),
75            VmValue::Generator(_) => true,
76            // Match Python semantics: range objects are always truthy,
77            // even the empty range (analogous to generators / iterators).
78            VmValue::Range(_) => true,
79            VmValue::Iter(_) => true,
80            VmValue::Pair(_) => true,
81        }
82    }
83
84    pub fn type_name(&self) -> &'static str {
85        match self {
86            VmValue::String(_) => "string",
87            VmValue::Int(_) => "int",
88            VmValue::Float(_) => "float",
89            VmValue::Bool(_) => "bool",
90            VmValue::Nil => "nil",
91            VmValue::List(_) => "list",
92            VmValue::Dict(_) => "dict",
93            VmValue::Closure(_) => "closure",
94            VmValue::BuiltinRef(_) => "builtin",
95            VmValue::Duration(_) => "duration",
96            VmValue::EnumVariant { .. } => "enum",
97            VmValue::StructInstance { .. } => "struct",
98            VmValue::TaskHandle(_) => "task_handle",
99            VmValue::Channel(_) => "channel",
100            VmValue::Atomic(_) => "atomic",
101            VmValue::McpClient(_) => "mcp_client",
102            VmValue::Set(_) => "set",
103            VmValue::Generator(_) => "generator",
104            VmValue::Range(_) => "range",
105            VmValue::Iter(_) => "iter",
106            VmValue::Pair(_) => "pair",
107        }
108    }
109
110    pub fn display(&self) -> String {
111        let mut out = String::new();
112        self.write_display(&mut out);
113        out
114    }
115
116    /// Writes the display representation directly into `out`,
117    /// avoiding intermediate Vec<String> allocations for collections.
118    pub fn write_display(&self, out: &mut String) {
119        use std::fmt::Write;
120
121        match self {
122            VmValue::Int(n) => {
123                let _ = write!(out, "{n}");
124            }
125            VmValue::Float(n) => {
126                if *n == (*n as i64) as f64 && n.abs() < 1e15 {
127                    let _ = write!(out, "{n:.1}");
128                } else {
129                    let _ = write!(out, "{n}");
130                }
131            }
132            VmValue::String(s) => out.push_str(s),
133            VmValue::Bool(b) => out.push_str(if *b { "true" } else { "false" }),
134            VmValue::Nil => out.push_str("nil"),
135            VmValue::List(items) => {
136                out.push('[');
137                for (i, item) in items.iter().enumerate() {
138                    if i > 0 {
139                        out.push_str(", ");
140                    }
141                    item.write_display(out);
142                }
143                out.push(']');
144            }
145            VmValue::Dict(map) => {
146                out.push('{');
147                for (i, (k, v)) in map.iter().enumerate() {
148                    if i > 0 {
149                        out.push_str(", ");
150                    }
151                    out.push_str(k);
152                    out.push_str(": ");
153                    v.write_display(out);
154                }
155                out.push('}');
156            }
157            VmValue::Closure(c) => {
158                let _ = write!(out, "<fn({})>", c.func.params.join(", "));
159            }
160            VmValue::BuiltinRef(name) => {
161                let _ = write!(out, "<builtin {name}>");
162            }
163            VmValue::Duration(ms) => {
164                if *ms >= 3_600_000 && ms % 3_600_000 == 0 {
165                    let _ = write!(out, "{}h", ms / 3_600_000);
166                } else if *ms >= 60_000 && ms % 60_000 == 0 {
167                    let _ = write!(out, "{}m", ms / 60_000);
168                } else if *ms >= 1000 && ms % 1000 == 0 {
169                    let _ = write!(out, "{}s", ms / 1000);
170                } else {
171                    let _ = write!(out, "{}ms", ms);
172                }
173            }
174            VmValue::EnumVariant {
175                enum_name,
176                variant,
177                fields,
178            } => {
179                if fields.is_empty() {
180                    let _ = write!(out, "{enum_name}.{variant}");
181                } else {
182                    let _ = write!(out, "{enum_name}.{variant}(");
183                    for (i, v) in fields.iter().enumerate() {
184                        if i > 0 {
185                            out.push_str(", ");
186                        }
187                        v.write_display(out);
188                    }
189                    out.push(')');
190                }
191            }
192            VmValue::StructInstance {
193                struct_name,
194                fields,
195            } => {
196                let _ = write!(out, "{struct_name} {{");
197                for (i, (k, v)) in fields.iter().enumerate() {
198                    if i > 0 {
199                        out.push_str(", ");
200                    }
201                    out.push_str(k);
202                    out.push_str(": ");
203                    v.write_display(out);
204                }
205                out.push('}');
206            }
207            VmValue::TaskHandle(id) => {
208                let _ = write!(out, "<task:{id}>");
209            }
210            VmValue::Channel(ch) => {
211                let _ = write!(out, "<channel:{}>", ch.name);
212            }
213            VmValue::Atomic(a) => {
214                let _ = write!(out, "<atomic:{}>", a.value.load(Ordering::SeqCst));
215            }
216            VmValue::McpClient(c) => {
217                let _ = write!(out, "<mcp_client:{}>", c.name);
218            }
219            VmValue::Set(items) => {
220                out.push_str("set(");
221                for (i, item) in items.iter().enumerate() {
222                    if i > 0 {
223                        out.push_str(", ");
224                    }
225                    item.write_display(out);
226                }
227                out.push(')');
228            }
229            VmValue::Generator(g) => {
230                if g.done.get() {
231                    out.push_str("<generator (done)>");
232                } else {
233                    out.push_str("<generator>");
234                }
235            }
236            // Print form mirrors source syntax: `1 to 5` / `0 to 3 exclusive`.
237            // `.to_list()` is the explicit path to materialize for display.
238            VmValue::Range(r) => {
239                let _ = write!(out, "{} to {}", r.start, r.end);
240                if !r.inclusive {
241                    out.push_str(" exclusive");
242                }
243            }
244            VmValue::Iter(h) => {
245                if matches!(&*h.borrow(), crate::vm::iter::VmIter::Exhausted) {
246                    out.push_str("<iter (exhausted)>");
247                } else {
248                    out.push_str("<iter>");
249                }
250            }
251            VmValue::Pair(p) => {
252                out.push('(');
253                p.0.write_display(out);
254                out.push_str(", ");
255                p.1.write_display(out);
256                out.push(')');
257            }
258        }
259    }
260
261    /// Get the value as a BTreeMap reference, if it's a Dict.
262    pub fn as_dict(&self) -> Option<&BTreeMap<String, VmValue>> {
263        if let VmValue::Dict(d) = self {
264            Some(d)
265        } else {
266            None
267        }
268    }
269
270    pub fn as_int(&self) -> Option<i64> {
271        if let VmValue::Int(n) = self {
272            Some(*n)
273        } else {
274            None
275        }
276    }
277}
278
279/// Sync builtin function for the VM.
280pub type VmBuiltinFn = Rc<dyn Fn(&[VmValue], &mut String) -> Result<VmValue, VmError>>;