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