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