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