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(
336        &self,
337        field_name: String,
338        value: VmValue,
339    ) -> Option<Self> {
340        let VmValue::StructInstance { layout, fields } = self else {
341            return None;
342        };
343
344        let mut new_fields = fields.as_ref().clone();
345        let layout = match layout.field_index(&field_name) {
346            Some(index) => {
347                if index >= new_fields.len() {
348                    new_fields.resize(index + 1, None);
349                }
350                new_fields[index] = Some(value);
351                Rc::clone(layout)
352            }
353            None => {
354                let new_layout = Rc::new(layout.with_appended_field(field_name));
355                new_fields.push(Some(value));
356                new_layout
357            }
358        };
359
360        Some(VmValue::StructInstance {
361            layout,
362            fields: Rc::new(new_fields),
363        })
364    }
365
366    pub fn display(&self) -> String {
367        let mut out = String::new();
368        self.write_display(&mut out);
369        out
370    }
371
372    /// Writes the display representation directly into `out`,
373    /// avoiding intermediate Vec<String> allocations for collections.
374    pub fn write_display(&self, out: &mut String) {
375        use std::fmt::Write;
376
377        match self {
378            VmValue::Int(n) => {
379                let _ = write!(out, "{n}");
380            }
381            VmValue::Float(n) => {
382                if *n == (*n as i64) as f64 && n.abs() < 1e15 {
383                    let _ = write!(out, "{n:.1}");
384                } else {
385                    let _ = write!(out, "{n}");
386                }
387            }
388            VmValue::String(s) => out.push_str(s),
389            VmValue::Bytes(bytes) => {
390                const MAX_PREVIEW_BYTES: usize = 32;
391
392                out.push_str("b\"");
393                for byte in bytes.iter().take(MAX_PREVIEW_BYTES) {
394                    let _ = write!(out, "{byte:02x}");
395                }
396                if bytes.len() > MAX_PREVIEW_BYTES {
397                    let _ = write!(out, "...+{}", bytes.len() - MAX_PREVIEW_BYTES);
398                }
399                out.push('"');
400            }
401            VmValue::Bool(b) => out.push_str(if *b { "true" } else { "false" }),
402            VmValue::Nil => out.push_str("nil"),
403            VmValue::List(items) => {
404                out.push('[');
405                for (i, item) in items.iter().enumerate() {
406                    if i > 0 {
407                        out.push_str(", ");
408                    }
409                    item.write_display(out);
410                }
411                out.push(']');
412            }
413            VmValue::Dict(map) => {
414                out.push('{');
415                for (i, (k, v)) in map.iter().enumerate() {
416                    if i > 0 {
417                        out.push_str(", ");
418                    }
419                    out.push_str(k);
420                    out.push_str(": ");
421                    v.write_display(out);
422                }
423                out.push('}');
424            }
425            VmValue::Closure(c) => {
426                let names: Vec<&str> = c.func.param_names().collect();
427                let _ = write!(out, "<fn({})>", names.join(", "));
428            }
429            VmValue::BuiltinRef(name) => {
430                let _ = write!(out, "<builtin {name}>");
431            }
432            VmValue::BuiltinRefId { name, .. } => {
433                let _ = write!(out, "<builtin {name}>");
434            }
435            VmValue::Duration(ms) => {
436                let sign = if *ms < 0 { "-" } else { "" };
437                let abs_ms = ms.unsigned_abs();
438                if abs_ms >= 604_800_000 && abs_ms % 604_800_000 == 0 {
439                    let _ = write!(out, "{}{}w", sign, abs_ms / 604_800_000);
440                } else if abs_ms >= 86_400_000 && abs_ms % 86_400_000 == 0 {
441                    let _ = write!(out, "{}{}d", sign, abs_ms / 86_400_000);
442                } else if abs_ms >= 3_600_000 && abs_ms % 3_600_000 == 0 {
443                    let _ = write!(out, "{}{}h", sign, abs_ms / 3_600_000);
444                } else if abs_ms >= 60_000 && abs_ms % 60_000 == 0 {
445                    let _ = write!(out, "{}{}m", sign, abs_ms / 60_000);
446                } else if abs_ms >= 1000 && abs_ms % 1000 == 0 {
447                    let _ = write!(out, "{}{}s", sign, abs_ms / 1000);
448                } else {
449                    let _ = write!(out, "{}{}ms", sign, abs_ms);
450                }
451            }
452            VmValue::EnumVariant(enum_variant) => {
453                if enum_variant.fields.is_empty() {
454                    let _ = write!(out, "{}.{}", enum_variant.enum_name, enum_variant.variant);
455                } else {
456                    let _ = write!(out, "{}.{}(", enum_variant.enum_name, enum_variant.variant);
457                    for (i, v) in enum_variant.fields.iter().enumerate() {
458                        if i > 0 {
459                            out.push_str(", ");
460                        }
461                        v.write_display(out);
462                    }
463                    out.push(')');
464                }
465            }
466            VmValue::StructInstance { layout, fields } => {
467                let _ = write!(out, "{} {{", layout.struct_name());
468                for (i, (k, v)) in struct_fields_to_map(layout, fields).iter().enumerate() {
469                    if i > 0 {
470                        out.push_str(", ");
471                    }
472                    out.push_str(k);
473                    out.push_str(": ");
474                    v.write_display(out);
475                }
476                out.push('}');
477            }
478            VmValue::TaskHandle(id) => {
479                let _ = write!(out, "<task:{id}>");
480            }
481            VmValue::Channel(ch) => {
482                let _ = write!(out, "<channel:{}>", ch.name);
483            }
484            VmValue::Atomic(a) => {
485                let _ = write!(out, "<atomic:{}>", a.value.load(Ordering::SeqCst));
486            }
487            VmValue::Rng(_) => {
488                out.push_str("<rng>");
489            }
490            VmValue::SyncPermit(p) => {
491                let _ = write!(out, "<sync_permit:{}:{}>", p.kind(), p.key());
492            }
493            VmValue::McpClient(c) => {
494                let _ = write!(out, "<mcp_client:{}>", c.name);
495            }
496            VmValue::Set(items) => {
497                out.push_str("set(");
498                for (i, item) in items.iter().enumerate() {
499                    if i > 0 {
500                        out.push_str(", ");
501                    }
502                    item.write_display(out);
503                }
504                out.push(')');
505            }
506            VmValue::Generator(g) => {
507                if g.done.get() {
508                    out.push_str("<generator (done)>");
509                } else {
510                    out.push_str("<generator>");
511                }
512            }
513            VmValue::Stream(s) => {
514                if s.done.get() {
515                    out.push_str("<stream (done)>");
516                } else {
517                    out.push_str("<stream>");
518                }
519            }
520            // Print form mirrors source syntax: `1 to 5` / `0 to 3 exclusive`.
521            // `.to_list()` is the explicit path to materialize for display.
522            VmValue::Range(r) => {
523                let _ = write!(out, "{} to {}", r.start, r.end);
524                if !r.inclusive {
525                    out.push_str(" exclusive");
526                }
527            }
528            VmValue::Iter(h) => {
529                if matches!(&*h.borrow(), crate::vm::iter::VmIter::Exhausted) {
530                    out.push_str("<iter (exhausted)>");
531                } else {
532                    out.push_str("<iter>");
533                }
534            }
535            VmValue::Harness(h) => {
536                let _ = write!(out, "<{}>", h.type_name());
537            }
538            VmValue::Pair(p) => {
539                out.push('(');
540                p.0.write_display(out);
541                out.push_str(", ");
542                p.1.write_display(out);
543                out.push(')');
544            }
545        }
546    }
547
548    /// Get the value as a BTreeMap reference, if it's a Dict.
549    pub fn as_dict(&self) -> Option<&BTreeMap<String, VmValue>> {
550        if let VmValue::Dict(d) = self {
551            Some(d)
552        } else {
553            None
554        }
555    }
556
557    pub fn as_int(&self) -> Option<i64> {
558        if let VmValue::Int(n) = self {
559            Some(*n)
560        } else {
561            None
562        }
563    }
564
565    pub fn as_bytes(&self) -> Option<&[u8]> {
566        if let VmValue::Bytes(bytes) = self {
567            Some(bytes.as_slice())
568        } else {
569            None
570        }
571    }
572}
573
574pub fn struct_fields_to_map(
575    layout: &StructLayout,
576    fields: &[Option<VmValue>],
577) -> BTreeMap<String, VmValue> {
578    layout
579        .field_names()
580        .iter()
581        .enumerate()
582        .filter_map(|(index, name)| {
583            fields
584                .get(index)
585                .and_then(Option::as_ref)
586                .map(|value| (name.clone(), value.clone()))
587        })
588        .collect()
589}
590
591/// Sync builtin function for the VM.
592pub type VmBuiltinFn = Rc<dyn Fn(&[VmValue], &mut String) -> Result<VmValue, VmError>>;