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