Skip to main content

harn_vm/value/
core.rs

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