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