Skip to main content

harn_vm/value/
core.rs

1use std::collections::{BTreeMap, HashMap};
2use std::rc::Rc;
3use std::sync::atomic::Ordering;
4use std::{cell::RefCell, future::Future, pin::Pin};
5
6use crate::mcp::VmMcpClientHandle;
7use crate::BuiltinId;
8
9use super::{VmAtomicHandle, VmChannelHandle, VmClosure, VmError, VmGenerator, VmRange};
10
11/// An async builtin function for the VM.
12pub type VmAsyncBuiltinFn =
13    Rc<dyn Fn(Vec<VmValue>) -> Pin<Box<dyn Future<Output = Result<VmValue, VmError>>>>>;
14
15/// Indexed runtime layout for a Harn struct instance.
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct StructLayout {
18    struct_name: String,
19    field_names: Vec<String>,
20    field_indexes: HashMap<String, usize>,
21}
22
23impl StructLayout {
24    pub fn new(struct_name: impl Into<String>, field_names: Vec<String>) -> Self {
25        let mut deduped = Vec::with_capacity(field_names.len());
26        let mut field_indexes = HashMap::with_capacity(field_names.len());
27        for field_name in field_names {
28            if field_indexes.contains_key(&field_name) {
29                continue;
30            }
31            let index = deduped.len();
32            field_indexes.insert(field_name.clone(), index);
33            deduped.push(field_name);
34        }
35
36        Self {
37            struct_name: struct_name.into(),
38            field_names: deduped,
39            field_indexes,
40        }
41    }
42
43    pub fn from_map(struct_name: impl Into<String>, fields: &BTreeMap<String, VmValue>) -> Self {
44        Self::new(struct_name, fields.keys().cloned().collect())
45    }
46
47    pub fn struct_name(&self) -> &str {
48        &self.struct_name
49    }
50
51    pub fn field_names(&self) -> &[String] {
52        &self.field_names
53    }
54
55    pub fn field_index(&self, field_name: &str) -> Option<usize> {
56        if self.field_names.len() <= 8 {
57            return self
58                .field_names
59                .iter()
60                .position(|candidate| candidate == field_name);
61        }
62        self.field_indexes.get(field_name).copied()
63    }
64
65    pub fn with_appended_field(&self, field_name: String) -> Self {
66        if self.field_indexes.contains_key(&field_name) {
67            return self.clone();
68        }
69        let mut field_names = self.field_names.clone();
70        field_names.push(field_name);
71        Self::new(self.struct_name.clone(), field_names)
72    }
73}
74
75/// VM runtime value.
76///
77/// Rare compound payloads use shared pointers so cloning or moving common
78/// values does not pay for the largest enum alternatives. Unsafe layouts such
79/// as NaN boxing or tagged pointers are deliberately deferred until Harn has a
80/// stronger object/heap story.
81#[derive(Debug, Clone)]
82pub enum VmValue {
83    Int(i64),
84    Float(f64),
85    String(Rc<str>),
86    Bytes(Rc<Vec<u8>>),
87    Bool(bool),
88    Nil,
89    List(Rc<Vec<VmValue>>),
90    Dict(Rc<BTreeMap<String, VmValue>>),
91    Closure(Rc<VmClosure>),
92    /// Reference to a registered builtin function, used when a builtin name is
93    /// referenced as a value (e.g. `snake_dict.rekey(snake_to_camel)`). The
94    /// contained string is the builtin's registered name.
95    BuiltinRef(Rc<str>),
96    /// Compact builtin reference for callback positions. Carries the name for
97    /// policy, diagnostics, and fallback if the ID cannot be used.
98    BuiltinRefId {
99        id: BuiltinId,
100        name: Rc<str>,
101    },
102    Duration(u64),
103    EnumVariant {
104        enum_name: Rc<str>,
105        variant: Rc<str>,
106        fields: Rc<Vec<VmValue>>,
107    },
108    StructInstance {
109        layout: Rc<StructLayout>,
110        fields: Rc<Vec<Option<VmValue>>>,
111    },
112    TaskHandle(String),
113    Channel(VmChannelHandle),
114    Atomic(VmAtomicHandle),
115    McpClient(VmMcpClientHandle),
116    Set(Rc<Vec<VmValue>>),
117    Generator(VmGenerator),
118    Range(VmRange),
119    /// Lazy iterator handle. Single-pass, fused. See `crate::vm::iter::VmIter`.
120    Iter(Rc<RefCell<crate::vm::iter::VmIter>>),
121    /// Two-element pair value. Produced by `pair(a, b)`, yielded by the
122    /// Dict iterator source, and (later) by `zip` / `enumerate` combinators.
123    /// Accessed via `.first` / `.second`, and destructurable in
124    /// `for (a, b) in ...` loops.
125    Pair(Rc<(VmValue, VmValue)>),
126}
127
128impl VmValue {
129    pub fn enum_variant(
130        enum_name: impl Into<Rc<str>>,
131        variant: impl Into<Rc<str>>,
132        fields: Vec<VmValue>,
133    ) -> Self {
134        VmValue::EnumVariant {
135            enum_name: enum_name.into(),
136            variant: variant.into(),
137            fields: Rc::new(fields),
138        }
139    }
140
141    pub fn struct_instance(
142        struct_name: impl Into<Rc<str>>,
143        fields: BTreeMap<String, VmValue>,
144    ) -> Self {
145        Self::struct_instance_from_map(struct_name.into().to_string(), fields)
146    }
147
148    pub fn is_truthy(&self) -> bool {
149        match self {
150            VmValue::Bool(b) => *b,
151            VmValue::Nil => false,
152            VmValue::Int(n) => *n != 0,
153            VmValue::Float(n) => *n != 0.0,
154            VmValue::String(s) => !s.is_empty(),
155            VmValue::Bytes(bytes) => !bytes.is_empty(),
156            VmValue::List(l) => !l.is_empty(),
157            VmValue::Dict(d) => !d.is_empty(),
158            VmValue::Closure(_) => true,
159            VmValue::BuiltinRef(_) => true,
160            VmValue::BuiltinRefId { .. } => true,
161            VmValue::Duration(ms) => *ms > 0,
162            VmValue::EnumVariant { .. } => true,
163            VmValue::StructInstance { .. } => true,
164            VmValue::TaskHandle(_) => true,
165            VmValue::Channel(_) => true,
166            VmValue::Atomic(_) => true,
167            VmValue::McpClient(_) => true,
168            VmValue::Set(s) => !s.is_empty(),
169            VmValue::Generator(_) => true,
170            // Match Python semantics: range objects are always truthy,
171            // even the empty range (analogous to generators / iterators).
172            VmValue::Range(_) => true,
173            VmValue::Iter(_) => true,
174            VmValue::Pair(_) => true,
175        }
176    }
177
178    pub fn type_name(&self) -> &'static str {
179        match self {
180            VmValue::String(_) => "string",
181            VmValue::Bytes(_) => "bytes",
182            VmValue::Int(_) => "int",
183            VmValue::Float(_) => "float",
184            VmValue::Bool(_) => "bool",
185            VmValue::Nil => "nil",
186            VmValue::List(_) => "list",
187            VmValue::Dict(_) => "dict",
188            VmValue::Closure(_) => "closure",
189            VmValue::BuiltinRef(_) => "builtin",
190            VmValue::BuiltinRefId { .. } => "builtin",
191            VmValue::Duration(_) => "duration",
192            VmValue::EnumVariant { .. } => "enum",
193            VmValue::StructInstance { .. } => "struct",
194            VmValue::TaskHandle(_) => "task_handle",
195            VmValue::Channel(_) => "channel",
196            VmValue::Atomic(_) => "atomic",
197            VmValue::McpClient(_) => "mcp_client",
198            VmValue::Set(_) => "set",
199            VmValue::Generator(_) => "generator",
200            VmValue::Range(_) => "range",
201            VmValue::Iter(_) => "iter",
202            VmValue::Pair(_) => "pair",
203        }
204    }
205
206    pub fn struct_name(&self) -> Option<&str> {
207        match self {
208            VmValue::StructInstance { layout, .. } => Some(layout.struct_name()),
209            _ => None,
210        }
211    }
212
213    pub fn struct_field(&self, field_name: &str) -> Option<&VmValue> {
214        match self {
215            VmValue::StructInstance { layout, fields } => layout
216                .field_index(field_name)
217                .and_then(|index| fields.get(index))
218                .and_then(Option::as_ref),
219            _ => None,
220        }
221    }
222
223    pub fn struct_fields_map(&self) -> Option<BTreeMap<String, VmValue>> {
224        match self {
225            VmValue::StructInstance { layout, fields } => {
226                Some(struct_fields_to_map(layout, fields))
227            }
228            _ => None,
229        }
230    }
231
232    pub fn struct_instance_from_map(
233        struct_name: impl Into<String>,
234        fields: BTreeMap<String, VmValue>,
235    ) -> Self {
236        let layout = Rc::new(StructLayout::from_map(struct_name, &fields));
237        let slots = layout
238            .field_names()
239            .iter()
240            .map(|name| fields.get(name).cloned())
241            .collect();
242        VmValue::StructInstance {
243            layout,
244            fields: Rc::new(slots),
245        }
246    }
247
248    pub fn struct_instance_with_layout(
249        struct_name: impl Into<String>,
250        field_names: Vec<String>,
251        field_values: BTreeMap<String, VmValue>,
252    ) -> Self {
253        let layout = Rc::new(StructLayout::new(struct_name, field_names));
254        let fields = layout
255            .field_names()
256            .iter()
257            .map(|name| field_values.get(name).cloned())
258            .collect();
259        VmValue::StructInstance {
260            layout,
261            fields: Rc::new(fields),
262        }
263    }
264
265    pub fn struct_instance_with_property(
266        &self,
267        field_name: String,
268        value: VmValue,
269    ) -> Option<Self> {
270        let VmValue::StructInstance { layout, fields } = self else {
271            return None;
272        };
273
274        let mut new_fields = fields.as_ref().clone();
275        let layout = match layout.field_index(&field_name) {
276            Some(index) => {
277                if index >= new_fields.len() {
278                    new_fields.resize(index + 1, None);
279                }
280                new_fields[index] = Some(value);
281                Rc::clone(layout)
282            }
283            None => {
284                let new_layout = Rc::new(layout.with_appended_field(field_name));
285                new_fields.push(Some(value));
286                new_layout
287            }
288        };
289
290        Some(VmValue::StructInstance {
291            layout,
292            fields: Rc::new(new_fields),
293        })
294    }
295
296    pub fn display(&self) -> String {
297        let mut out = String::new();
298        self.write_display(&mut out);
299        out
300    }
301
302    /// Writes the display representation directly into `out`,
303    /// avoiding intermediate Vec<String> allocations for collections.
304    pub fn write_display(&self, out: &mut String) {
305        use std::fmt::Write;
306
307        match self {
308            VmValue::Int(n) => {
309                let _ = write!(out, "{n}");
310            }
311            VmValue::Float(n) => {
312                if *n == (*n as i64) as f64 && n.abs() < 1e15 {
313                    let _ = write!(out, "{n:.1}");
314                } else {
315                    let _ = write!(out, "{n}");
316                }
317            }
318            VmValue::String(s) => out.push_str(s),
319            VmValue::Bytes(bytes) => {
320                const MAX_PREVIEW_BYTES: usize = 32;
321
322                out.push_str("b\"");
323                for byte in bytes.iter().take(MAX_PREVIEW_BYTES) {
324                    let _ = write!(out, "{byte:02x}");
325                }
326                if bytes.len() > MAX_PREVIEW_BYTES {
327                    let _ = write!(out, "...+{}", bytes.len() - MAX_PREVIEW_BYTES);
328                }
329                out.push('"');
330            }
331            VmValue::Bool(b) => out.push_str(if *b { "true" } else { "false" }),
332            VmValue::Nil => out.push_str("nil"),
333            VmValue::List(items) => {
334                out.push('[');
335                for (i, item) in items.iter().enumerate() {
336                    if i > 0 {
337                        out.push_str(", ");
338                    }
339                    item.write_display(out);
340                }
341                out.push(']');
342            }
343            VmValue::Dict(map) => {
344                out.push('{');
345                for (i, (k, v)) in map.iter().enumerate() {
346                    if i > 0 {
347                        out.push_str(", ");
348                    }
349                    out.push_str(k);
350                    out.push_str(": ");
351                    v.write_display(out);
352                }
353                out.push('}');
354            }
355            VmValue::Closure(c) => {
356                let _ = write!(out, "<fn({})>", c.func.params.join(", "));
357            }
358            VmValue::BuiltinRef(name) => {
359                let _ = write!(out, "<builtin {name}>");
360            }
361            VmValue::BuiltinRefId { name, .. } => {
362                let _ = write!(out, "<builtin {name}>");
363            }
364            VmValue::Duration(ms) => {
365                if *ms >= 3_600_000 && ms % 3_600_000 == 0 {
366                    let _ = write!(out, "{}h", ms / 3_600_000);
367                } else if *ms >= 60_000 && ms % 60_000 == 0 {
368                    let _ = write!(out, "{}m", ms / 60_000);
369                } else if *ms >= 1000 && ms % 1000 == 0 {
370                    let _ = write!(out, "{}s", ms / 1000);
371                } else {
372                    let _ = write!(out, "{}ms", ms);
373                }
374            }
375            VmValue::EnumVariant {
376                enum_name,
377                variant,
378                fields,
379            } => {
380                if fields.is_empty() {
381                    let _ = write!(out, "{enum_name}.{variant}");
382                } else {
383                    let _ = write!(out, "{enum_name}.{variant}(");
384                    for (i, v) in fields.iter().enumerate() {
385                        if i > 0 {
386                            out.push_str(", ");
387                        }
388                        v.write_display(out);
389                    }
390                    out.push(')');
391                }
392            }
393            VmValue::StructInstance { layout, fields } => {
394                let _ = write!(out, "{} {{", layout.struct_name());
395                for (i, (k, v)) in struct_fields_to_map(layout, fields).iter().enumerate() {
396                    if i > 0 {
397                        out.push_str(", ");
398                    }
399                    out.push_str(k);
400                    out.push_str(": ");
401                    v.write_display(out);
402                }
403                out.push('}');
404            }
405            VmValue::TaskHandle(id) => {
406                let _ = write!(out, "<task:{id}>");
407            }
408            VmValue::Channel(ch) => {
409                let _ = write!(out, "<channel:{}>", ch.name);
410            }
411            VmValue::Atomic(a) => {
412                let _ = write!(out, "<atomic:{}>", a.value.load(Ordering::SeqCst));
413            }
414            VmValue::McpClient(c) => {
415                let _ = write!(out, "<mcp_client:{}>", c.name);
416            }
417            VmValue::Set(items) => {
418                out.push_str("set(");
419                for (i, item) in items.iter().enumerate() {
420                    if i > 0 {
421                        out.push_str(", ");
422                    }
423                    item.write_display(out);
424                }
425                out.push(')');
426            }
427            VmValue::Generator(g) => {
428                if g.done.get() {
429                    out.push_str("<generator (done)>");
430                } else {
431                    out.push_str("<generator>");
432                }
433            }
434            // Print form mirrors source syntax: `1 to 5` / `0 to 3 exclusive`.
435            // `.to_list()` is the explicit path to materialize for display.
436            VmValue::Range(r) => {
437                let _ = write!(out, "{} to {}", r.start, r.end);
438                if !r.inclusive {
439                    out.push_str(" exclusive");
440                }
441            }
442            VmValue::Iter(h) => {
443                if matches!(&*h.borrow(), crate::vm::iter::VmIter::Exhausted) {
444                    out.push_str("<iter (exhausted)>");
445                } else {
446                    out.push_str("<iter>");
447                }
448            }
449            VmValue::Pair(p) => {
450                out.push('(');
451                p.0.write_display(out);
452                out.push_str(", ");
453                p.1.write_display(out);
454                out.push(')');
455            }
456        }
457    }
458
459    /// Get the value as a BTreeMap reference, if it's a Dict.
460    pub fn as_dict(&self) -> Option<&BTreeMap<String, VmValue>> {
461        if let VmValue::Dict(d) = self {
462            Some(d)
463        } else {
464            None
465        }
466    }
467
468    pub fn as_int(&self) -> Option<i64> {
469        if let VmValue::Int(n) = self {
470            Some(*n)
471        } else {
472            None
473        }
474    }
475
476    pub fn as_bytes(&self) -> Option<&[u8]> {
477        if let VmValue::Bytes(bytes) = self {
478            Some(bytes.as_slice())
479        } else {
480            None
481        }
482    }
483}
484
485pub fn struct_fields_to_map(
486    layout: &StructLayout,
487    fields: &[Option<VmValue>],
488) -> BTreeMap<String, VmValue> {
489    layout
490        .field_names()
491        .iter()
492        .enumerate()
493        .filter_map(|(index, name)| {
494            fields
495                .get(index)
496                .and_then(Option::as_ref)
497                .map(|value| (name.clone(), value.clone()))
498        })
499        .collect()
500}
501
502/// Sync builtin function for the VM.
503pub type VmBuiltinFn = Rc<dyn Fn(&[VmValue], &mut String) -> Result<VmValue, VmError>>;