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