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