Skip to main content

bop/
value.rs

1//! Value type for the Bop interpreter.
2//!
3//! Heap-allocating variants use newtypes with private fields.
4//! The only way to construct them is through the tracked constructors
5//! (`Value::new_str`, `Value::new_array`, `Value::new_dict`), which
6//! call `bop_alloc`. This is enforced by the type system — code outside
7//! this module cannot access the private inner fields.
8
9#[cfg(feature = "no_std")]
10use alloc::{boxed::Box, format, rc::Rc, string::{String, ToString}, vec::Vec};
11
12#[cfg(not(feature = "no_std"))]
13use std::rc::Rc;
14
15use core::cell::RefCell;
16
17use crate::memory::{bop_alloc, bop_dealloc};
18use crate::parser::Stmt;
19
20// ─── Tracked newtypes ──────────────────────────────────────────────────────
21//
22// Private inner fields prevent direct construction from outside this module.
23
24#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
25pub struct BopStr(String);
26
27#[derive(Debug)]
28pub struct BopArray(Vec<Value>);
29
30#[derive(Debug)]
31pub struct BopDict(Vec<(String, Value)>);
32
33/// A user-defined struct value. Carries the module it was
34/// declared in plus the bare type name, so two modules that
35/// happen to declare `struct Color { ... }` independently
36/// produce distinct values even when they share a name. The
37/// module path is `<root>` for the top-level program and
38/// `<builtin>` for engine-registered builtins like
39/// `RuntimeError`; for user modules it's the dot-joined `use`
40/// path (`"std.math"`, `"game.entity"`, …). Fields are stored
41/// in declaration order so iteration and `Display` stay stable.
42#[derive(Debug)]
43pub struct BopStruct {
44    module_path: String,
45    type_name: String,
46    fields: Vec<(String, Value)>,
47}
48
49/// A user-defined enum variant value — the concrete data side of
50/// Bop's sum types. Like [`BopStruct`], it's identified by the
51/// `(module_path, type_name)` pair, plus the selected variant's
52/// name and payload. Two enums declared in different modules with
53/// the same type name and even the same variants still compare
54/// as distinct types.
55#[derive(Debug)]
56pub struct BopEnumVariant {
57    module_path: String,
58    type_name: String,
59    variant: String,
60    payload: EnumPayload,
61}
62
63/// Module path used for engine-registered builtins (`Result`,
64/// `RuntimeError`). Surfaces wherever a struct / enum value
65/// needs to carry its declaring module; the engines all agree
66/// on this literal so patterns + equality line up across
67/// walker / VM / AOT.
68pub const BUILTIN_MODULE_PATH: &str = "<builtin>";
69
70/// Module path used for types declared directly in the program
71/// root (not in any imported module). Same literal across every
72/// engine.
73pub const ROOT_MODULE_PATH: &str = "<root>";
74
75/// Runtime payload attached to a `BopEnumVariant`. Mirrors the
76/// three variant shapes the parser recognises
77/// (`VariantKind::{Unit, Tuple, Struct}`).
78#[derive(Debug)]
79pub enum EnumPayload {
80    Unit,
81    Tuple(Vec<Value>),
82    Struct(Vec<(String, Value)>),
83}
84
85/// A Bop function value — the runtime representation of a closure
86/// or a reified `fn foo(...) { ... }` declaration. Shared by `Rc`
87/// so first-class usage (`let g = f; pass(f)`) is cheap.
88///
89/// The body is engine-opaque: the tree-walker produces an
90/// [`FnBody::Ast`] for direct interpretation; the bytecode VM
91/// produces an [`FnBody::Compiled`] carrying a pre-compiled body.
92/// Each engine only ever dispatches its own variant.
93pub struct BopFn {
94    pub params: Vec<String>,
95    /// Values captured from the enclosing scope at construction
96    /// time, cloned by value. Free variables in the body that
97    /// aren't parameters and aren't in this list fall through to
98    /// the outer module / global lookup at call time.
99    pub captures: Vec<(String, Value)>,
100    pub body: FnBody,
101    /// `Some(name)` when this `BopFn` is bound to its own name
102    /// for self-reference (the lowering of `fn foo(...) { ... }`).
103    /// Lambdas created from an `fn(...) { ... }` expression leave
104    /// this `None`.
105    pub self_name: Option<String>,
106}
107
108/// Engine-specific representation of a function body.
109///
110/// - The tree-walker creates `Ast` bodies and re-walks the AST on
111///   every call.
112/// - The bytecode VM creates `Compiled` bodies carrying a
113///   pre-compiled form (typically `Rc<bop_vm::Chunk>`). `Rc<dyn
114///   Any>` keeps `bop-lang` from taking a dep on any particular
115///   engine crate.
116///
117/// An engine that only understands one variant errors cleanly
118/// when handed the other, rather than silently misbehaving.
119pub enum FnBody {
120    Ast(Vec<Stmt>),
121    Compiled(Rc<dyn core::any::Any + 'static>),
122}
123
124impl core::fmt::Debug for BopFn {
125    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
126        f.debug_struct("BopFn")
127            .field("params", &self.params)
128            .field("captures", &self.captures.len())
129            .field("body", &self.body)
130            .field("self_name", &self.self_name)
131            .finish()
132    }
133}
134
135impl core::fmt::Debug for FnBody {
136    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
137        match self {
138            FnBody::Ast(stmts) => write!(f, "Ast({} stmts)", stmts.len()),
139            FnBody::Compiled(_) => write!(f, "Compiled(<opaque>)"),
140        }
141    }
142}
143
144// ─── Value enum ────────────────────────────────────────────────────────────
145
146#[derive(Debug)]
147pub enum Value {
148    /// 64-bit signed integer. The go-to type for counts,
149    /// indices, and any arithmetic that wants exactness. Added
150    /// in phase 6; produced by integer literals (`42`), the
151    /// `int()` builtin, `len`, `range` elements, and the new
152    /// `//` integer-division operator.
153    Int(i64),
154    /// 64-bit IEEE-754 float. Produced by decimal literals
155    /// (`3.14`, `4.0`), the `float()` builtin, and by `/` on
156    /// any numeric pair (Python-style: `/` always floats).
157    Number(f64),
158    Str(BopStr),
159    Bool(bool),
160    None,
161    Array(BopArray),
162    Dict(BopDict),
163    Fn(Rc<BopFn>),
164    // `Struct` and `EnumVariant` live behind a `Box` so the
165    // `Value` enum stays compact (roughly the size of a `Vec`
166    // header) rather than ballooning to the size of the widest
167    // user-type variant. Keeps per-call stack frames small
168    // enough for deep recursion to halt on the call-depth
169    // counter before overflowing the native stack.
170    Struct(Box<BopStruct>),
171    EnumVariant(Box<BopEnumVariant>),
172    /// Namespace value produced by an aliased `use` statement
173    /// (`use std.math as m` binds `m` as a `Module`). Field
174    /// access dispatches to the module's exported `let` / `fn`
175    /// bindings; the runtime also consults the type list for
176    /// `m.Type { ... }` / `m.Type::Variant(...)` forms so those
177    /// namespaced constructors find the right declared type.
178    Module(Rc<BopModule>),
179    /// Lazy iterator. Cloning shares state (like `Value::Fn`) —
180    /// `let b = a; a.next(); b.next()` advances the same
181    /// underlying position, matching iterator semantics in
182    /// Python / Rust / JS. See [`BopIter`] for the built-in
183    /// variants; user-defined iterators are ordinary struct
184    /// values that happen to implement `.next()`.
185    Iter(Rc<RefCell<BopIter>>),
186}
187
188/// Built-in lazy iterator shapes. Each one holds a snapshot of
189/// the source sequence plus a cursor; advancing via [`Self::next`]
190/// yields items until exhausted. A user-defined iterator doesn't
191/// need to live here — it's just a struct with a `.next()`
192/// method, dispatched through the ordinary method path.
193#[derive(Debug)]
194pub enum BopIter {
195    /// Over a cloned-off array snapshot. Subsequent mutation of
196    /// the original array doesn't affect the iterator — matches
197    /// how most scripting languages present iteration.
198    Array { items: Vec<Value>, pos: usize },
199    /// Over a string's Unicode code points, one item per code
200    /// point. Each yielded value is a single-char string.
201    String { chars: Vec<char>, pos: usize },
202    /// Over a dict's keys, in declaration order. Same shape
203    /// `for k in dict` uses when the receiver is a plain dict.
204    Dict { keys: Vec<String>, pos: usize },
205}
206
207impl BopIter {
208    /// Advance by one and return the next item, or `None` when
209    /// the iterator is exhausted. Caller wraps the result in
210    /// `Iter::Next(v)` / `Iter::Done` for user code.
211    pub fn next(&mut self) -> Option<Value> {
212        match self {
213            BopIter::Array { items, pos } => {
214                if *pos < items.len() {
215                    let v = items[*pos].clone();
216                    *pos += 1;
217                    Some(v)
218                } else {
219                    None
220                }
221            }
222            BopIter::String { chars, pos } => {
223                if *pos < chars.len() {
224                    let v = Value::new_str(chars[*pos].to_string());
225                    *pos += 1;
226                    Some(v)
227                } else {
228                    None
229                }
230            }
231            BopIter::Dict { keys, pos } => {
232                if *pos < keys.len() {
233                    let v = Value::new_str(keys[*pos].clone());
234                    *pos += 1;
235                    Some(v)
236                } else {
237                    None
238                }
239            }
240        }
241    }
242}
243
244impl Drop for BopIter {
245    fn drop(&mut self) {
246        // Release the buffer tracked at construction time. The
247        // inner Values (for Array) free themselves through their
248        // own Drop; strings inside Dict keys do the same via
249        // `key_bytes` accounting below.
250        match self {
251            BopIter::Array { items, .. } => {
252                bop_dealloc(items.capacity() * core::mem::size_of::<Value>());
253            }
254            BopIter::String { chars, .. } => {
255                bop_dealloc(chars.capacity() * core::mem::size_of::<char>());
256            }
257            BopIter::Dict { keys, .. } => {
258                let key_bytes: usize = keys.iter().map(|k| k.capacity()).sum();
259                bop_dealloc(
260                    keys.capacity() * core::mem::size_of::<String>() + key_bytes,
261                );
262            }
263        }
264    }
265}
266
267/// Exported surface of a module, as presented through an aliased
268/// `use` statement. `Rc<BopModule>` is what a `Value::Module`
269/// carries so cloning the Value stays cheap.
270#[derive(Debug)]
271pub struct BopModule {
272    /// The dotted path the module was loaded from ("std.math",
273    /// "game.entity", …). Useful for error messages.
274    pub path: String,
275    /// Exported `let` / `fn` / `const` bindings, in declaration
276    /// order. Accessed via `m.name` field reads.
277    pub bindings: Vec<(String, Value)>,
278    /// Names of struct / enum types the module declared.
279    /// Construction through the namespace (`m.Entity { ... }`)
280    /// verifies the type name appears in this list before
281    /// falling through to the engine's type registry.
282    pub types: Vec<String>,
283}
284
285// ─── Tracked constructors ──────────────────────────────────────────────────
286//
287// These call bop_alloc() to track the allocation but do NOT check the limit.
288// Enforcement happens at tick() via bop_memory_exceeded(). This means a single
289// operation can overshoot the limit before the next tick catches it. High-risk
290// operations (string repeat, string/array concat) use bop_would_exceed() as a
291// preflight check in the evaluator to avoid this.
292
293impl Value {
294    pub fn new_str(s: String) -> Self {
295        bop_alloc(s.capacity());
296        Value::Str(BopStr(s))
297    }
298
299    pub fn new_array(items: Vec<Value>) -> Self {
300        bop_alloc(items.capacity() * core::mem::size_of::<Value>());
301        Value::Array(BopArray(items))
302    }
303
304    pub fn new_dict(entries: Vec<(String, Value)>) -> Self {
305        let key_bytes: usize = entries.iter().map(|(k, _)| k.capacity()).sum();
306        bop_alloc(entries.capacity() * core::mem::size_of::<(String, Value)>() + key_bytes);
307        Value::Dict(BopDict(entries))
308    }
309
310    /// Build a user-defined struct value. `module_path` is the
311    /// module in which the type was declared (`<root>` at the
312    /// top level, `<builtin>` for engine-registered shapes like
313    /// `RuntimeError`, or the dot-joined `use` path for user
314    /// modules). Two structs are only the same type when both
315    /// the module path *and* the type name match — so a
316    /// `struct Color { ... }` declared in two separate modules
317    /// produces genuinely distinct values.
318    pub fn new_struct(
319        module_path: String,
320        type_name: String,
321        fields: Vec<(String, Value)>,
322    ) -> Self {
323        let key_bytes: usize = fields.iter().map(|(k, _)| k.capacity()).sum();
324        bop_alloc(
325            module_path.capacity()
326                + type_name.capacity()
327                + fields.capacity() * core::mem::size_of::<(String, Value)>()
328                + key_bytes,
329        );
330        Value::Struct(Box::new(BopStruct {
331            module_path,
332            type_name,
333            fields,
334        }))
335    }
336
337    /// Build a built-in iterator that yields each item of
338    /// `items` in order. Cloning the returned `Value::Iter`
339    /// shares the iteration cursor, so `let b = a; a.next()`
340    /// advances `b` too.
341    pub fn new_array_iter(items: Vec<Value>) -> Self {
342        bop_alloc(items.capacity() * core::mem::size_of::<Value>());
343        Value::Iter(Rc::new(RefCell::new(BopIter::Array { items, pos: 0 })))
344    }
345
346    /// Build a built-in iterator over a string's Unicode code
347    /// points.
348    pub fn new_string_iter(chars: Vec<char>) -> Self {
349        bop_alloc(chars.capacity() * core::mem::size_of::<char>());
350        Value::Iter(Rc::new(RefCell::new(BopIter::String { chars, pos: 0 })))
351    }
352
353    /// Build a built-in iterator over a dict's keys (declaration
354    /// order).
355    pub fn new_dict_iter(keys: Vec<String>) -> Self {
356        let key_bytes: usize = keys.iter().map(|k| k.capacity()).sum();
357        bop_alloc(keys.capacity() * core::mem::size_of::<String>() + key_bytes);
358        Value::Iter(Rc::new(RefCell::new(BopIter::Dict { keys, pos: 0 })))
359    }
360
361    pub fn new_enum_unit(module_path: String, type_name: String, variant: String) -> Self {
362        bop_alloc(module_path.capacity() + type_name.capacity() + variant.capacity());
363        Value::EnumVariant(Box::new(BopEnumVariant {
364            module_path,
365            type_name,
366            variant,
367            payload: EnumPayload::Unit,
368        }))
369    }
370
371    pub fn new_enum_tuple(
372        module_path: String,
373        type_name: String,
374        variant: String,
375        items: Vec<Value>,
376    ) -> Self {
377        bop_alloc(
378            module_path.capacity()
379                + type_name.capacity()
380                + variant.capacity()
381                + items.capacity() * core::mem::size_of::<Value>(),
382        );
383        Value::EnumVariant(Box::new(BopEnumVariant {
384            module_path,
385            type_name,
386            variant,
387            payload: EnumPayload::Tuple(items),
388        }))
389    }
390
391    pub fn new_enum_struct(
392        module_path: String,
393        type_name: String,
394        variant: String,
395        fields: Vec<(String, Value)>,
396    ) -> Self {
397        let key_bytes: usize = fields.iter().map(|(k, _)| k.capacity()).sum();
398        bop_alloc(
399            module_path.capacity()
400                + type_name.capacity()
401                + variant.capacity()
402                + fields.capacity() * core::mem::size_of::<(String, Value)>()
403                + key_bytes,
404        );
405        Value::EnumVariant(Box::new(BopEnumVariant {
406            module_path,
407            type_name,
408            variant,
409            payload: EnumPayload::Struct(fields),
410        }))
411    }
412
413    /// Build a tree-walker-ready closure value. The AST body moves
414    /// into a shared [`BopFn`] behind an `Rc`; subsequent clones
415    /// of the resulting `Value::Fn` just bump the refcount.
416    pub fn new_fn(
417        params: Vec<String>,
418        captures: Vec<(String, Value)>,
419        body: Vec<Stmt>,
420        self_name: Option<String>,
421    ) -> Self {
422        Value::Fn(Rc::new(BopFn {
423            params,
424            captures,
425            body: FnBody::Ast(body),
426            self_name,
427        }))
428    }
429
430    /// Build a closure value with an engine-opaque compiled body.
431    /// Used by the bytecode VM (and any future engine) to carry
432    /// its pre-compiled form inside a `Value::Fn` without
433    /// `bop-lang` depending on the engine crate.
434    pub fn new_compiled_fn(
435        params: Vec<String>,
436        captures: Vec<(String, Value)>,
437        body: Rc<dyn core::any::Any + 'static>,
438        self_name: Option<String>,
439    ) -> Self {
440        Value::Fn(Rc::new(BopFn {
441            params,
442            captures,
443            body: FnBody::Compiled(body),
444            self_name,
445        }))
446    }
447}
448
449// ─── Clone (tracks allocations) ────────────────────────────────────────────
450//
451// For Array and Dict, the inner .clone() recursively clones each element,
452// and each element's Clone impl calls bop_alloc for itself. We then ALSO
453// bop_alloc for the Vec buffer. This is correct — the buffer and the elements
454// are separate allocations that both need tracking.
455
456impl Clone for Value {
457    fn clone(&self) -> Self {
458        match self {
459            Value::Int(n) => Value::Int(*n),
460            Value::Number(n) => Value::Number(*n),
461            Value::Bool(b) => Value::Bool(*b),
462            Value::None => Value::None,
463            Value::Str(s) => {
464                let cloned = s.0.clone();
465                bop_alloc(cloned.capacity());
466                Value::Str(BopStr(cloned))
467            }
468            Value::Array(arr) => {
469                let cloned = arr.0.clone(); // each element's Clone tracks itself
470                bop_alloc(cloned.capacity() * core::mem::size_of::<Value>());
471                Value::Array(BopArray(cloned))
472            }
473            Value::Dict(d) => {
474                let cloned = d.0.clone(); // each Value's Clone tracks itself
475                let key_bytes: usize = cloned.iter().map(|(k, _)| k.capacity()).sum();
476                bop_alloc(cloned.capacity() * core::mem::size_of::<(String, Value)>() + key_bytes);
477                Value::Dict(BopDict(cloned))
478            }
479            Value::Struct(s) => {
480                let cloned_mp = s.module_path.clone();
481                let cloned_tn = s.type_name.clone();
482                let cloned_fields = s.fields.clone();
483                let key_bytes: usize =
484                    cloned_fields.iter().map(|(k, _)| k.capacity()).sum();
485                bop_alloc(
486                    cloned_mp.capacity()
487                        + cloned_tn.capacity()
488                        + cloned_fields.capacity()
489                            * core::mem::size_of::<(String, Value)>()
490                        + key_bytes,
491                );
492                Value::Struct(Box::new(BopStruct {
493                    module_path: cloned_mp,
494                    type_name: cloned_tn,
495                    fields: cloned_fields,
496                }))
497            }
498            // Closures are reference-counted: cloning a Value::Fn
499            // is O(1) and doesn't duplicate the body or captures.
500            // Tracking the captures' memory happens once, at the
501            // moment the BopFn is constructed (by `new_fn`), via
502            // their own Value Clone/Drop hooks.
503            Value::Fn(f) => Value::Fn(Rc::clone(f)),
504            // Modules are reference-counted — same cheap clone as
505            // fns. The `bindings` and `types` vectors track their
506            // own memory when the `BopModule` is first built.
507            Value::Module(m) => Value::Module(Rc::clone(m)),
508            // Iterators are reference-counted and intentionally
509            // share their cursor — cloning `a = b` doesn't fork
510            // the iteration state, matching iterator semantics
511            // in Python / Rust / JS. The buffer was tracked once
512            // by the constructor and is dealloc'd by BopIter's
513            // Drop when the last clone goes away.
514            Value::Iter(it) => Value::Iter(Rc::clone(it)),
515            Value::EnumVariant(e) => {
516                let mp = e.module_path.clone();
517                let tn = e.type_name.clone();
518                let vn = e.variant.clone();
519                let base = mp.capacity() + tn.capacity() + vn.capacity();
520                let payload = match &e.payload {
521                    EnumPayload::Unit => {
522                        bop_alloc(base);
523                        EnumPayload::Unit
524                    }
525                    EnumPayload::Tuple(items) => {
526                        let cloned = items.clone();
527                        bop_alloc(
528                            base + cloned.capacity() * core::mem::size_of::<Value>(),
529                        );
530                        EnumPayload::Tuple(cloned)
531                    }
532                    EnumPayload::Struct(fields) => {
533                        let cloned = fields.clone();
534                        let key_bytes: usize =
535                            cloned.iter().map(|(k, _)| k.capacity()).sum();
536                        bop_alloc(
537                            base
538                                + cloned.capacity()
539                                    * core::mem::size_of::<(String, Value)>()
540                                + key_bytes,
541                        );
542                        EnumPayload::Struct(cloned)
543                    }
544                };
545                Value::EnumVariant(Box::new(BopEnumVariant {
546                    module_path: mp,
547                    type_name: tn,
548                    variant: vn,
549                    payload,
550                }))
551            }
552        }
553    }
554}
555
556// ─── Drop (tracks deallocations) ───────────────────────────────────────────
557
558impl Drop for Value {
559    fn drop(&mut self) {
560        match self {
561            Value::Str(s) => bop_dealloc(s.0.capacity()),
562            Value::Array(arr) => {
563                bop_dealloc(arr.0.capacity() * core::mem::size_of::<Value>());
564            }
565            Value::Dict(d) => {
566                let key_bytes: usize = d.0.iter().map(|(k, _)| k.capacity()).sum();
567                bop_dealloc(d.0.capacity() * core::mem::size_of::<(String, Value)>() + key_bytes);
568            }
569            Value::Struct(s) => {
570                let key_bytes: usize = s.fields.iter().map(|(k, _)| k.capacity()).sum();
571                bop_dealloc(
572                    s.module_path.capacity()
573                        + s.type_name.capacity()
574                        + s.fields.capacity()
575                            * core::mem::size_of::<(String, Value)>()
576                        + key_bytes,
577                );
578            }
579            Value::EnumVariant(e) => {
580                let base = e.module_path.capacity()
581                    + e.type_name.capacity()
582                    + e.variant.capacity();
583                match &e.payload {
584                    EnumPayload::Unit => bop_dealloc(base),
585                    EnumPayload::Tuple(items) => bop_dealloc(
586                        base + items.capacity() * core::mem::size_of::<Value>(),
587                    ),
588                    EnumPayload::Struct(fields) => {
589                        let key_bytes: usize =
590                            fields.iter().map(|(k, _)| k.capacity()).sum();
591                        bop_dealloc(
592                            base
593                                + fields.capacity()
594                                    * core::mem::size_of::<(String, Value)>()
595                                + key_bytes,
596                        );
597                    }
598                }
599            }
600            // Value::Fn drops by releasing its Rc. The inner
601            // captures' Drop impls fire only when the refcount
602            // reaches zero; no per-Value accounting here.
603            _ => {}
604        }
605    }
606}
607
608// ─── Display ───────────────────────────────────────────────────────────────
609
610impl core::fmt::Display for Value {
611    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
612        match self {
613            Value::Int(n) => write!(f, "{}", n),
614            Value::Number(n) => {
615                if *n == (*n as i64 as f64) && *n - *n == 0.0 {
616                    write!(f, "{}", *n as i64)
617                } else {
618                    write!(f, "{}", n)
619                }
620            }
621            Value::Str(s) => write!(f, "{}", s.0),
622            Value::Bool(b) => write!(f, "{}", b),
623            Value::None => write!(f, "none"),
624            Value::Array(items) => {
625                write!(f, "[")?;
626                for (i, item) in items.0.iter().enumerate() {
627                    if i > 0 {
628                        write!(f, ", ")?;
629                    }
630                    write!(f, "{}", item.inspect())?;
631                }
632                write!(f, "]")
633            }
634            Value::Dict(entries) => {
635                write!(f, "{{")?;
636                for (i, (k, v)) in entries.0.iter().enumerate() {
637                    if i > 0 {
638                        write!(f, ", ")?;
639                    }
640                    write!(f, "\"{}\": {}", k, v.inspect())?;
641                }
642                write!(f, "}}")
643            }
644            Value::Fn(func) => match &func.self_name {
645                Some(name) => write!(f, "<fn {}>", name),
646                None => write!(f, "<fn>"),
647            },
648            Value::Module(m) => write!(f, "<module {}>", m.path),
649            Value::Iter(it) => {
650                // Peek at the inner state for a useful Display —
651                // callers see `<iter array 0/3>` rather than a
652                // bare `<iter>`. If the RefCell is already
653                // borrowed (nested Display during a panic
654                // backtrace, say), fall back to the bare form.
655                match it.try_borrow() {
656                    Ok(inner) => match &*inner {
657                        BopIter::Array { items, pos } => {
658                            write!(f, "<iter array {}/{}>", pos, items.len())
659                        }
660                        BopIter::String { chars, pos } => {
661                            write!(f, "<iter string {}/{}>", pos, chars.len())
662                        }
663                        BopIter::Dict { keys, pos } => {
664                            write!(f, "<iter dict {}/{}>", pos, keys.len())
665                        }
666                    },
667                    Err(_) => write!(f, "<iter>"),
668                }
669            }
670            Value::Struct(s) => {
671                write!(f, "{} {{", s.type_name)?;
672                for (i, (k, v)) in s.fields.iter().enumerate() {
673                    if i > 0 {
674                        write!(f, ",")?;
675                    }
676                    write!(f, " {}: {}", k, v.inspect())?;
677                }
678                if !s.fields.is_empty() {
679                    write!(f, " ")?;
680                }
681                write!(f, "}}")
682            }
683            Value::EnumVariant(e) => match &e.payload {
684                EnumPayload::Unit => write!(f, "{}::{}", e.type_name, e.variant),
685                EnumPayload::Tuple(items) => {
686                    write!(f, "{}::{}(", e.type_name, e.variant)?;
687                    for (i, v) in items.iter().enumerate() {
688                        if i > 0 {
689                            write!(f, ", ")?;
690                        }
691                        write!(f, "{}", v.inspect())?;
692                    }
693                    write!(f, ")")
694                }
695                EnumPayload::Struct(fields) => {
696                    write!(f, "{}::{} {{", e.type_name, e.variant)?;
697                    for (i, (k, v)) in fields.iter().enumerate() {
698                        if i > 0 {
699                            write!(f, ",")?;
700                        }
701                        write!(f, " {}: {}", k, v.inspect())?;
702                    }
703                    if !fields.is_empty() {
704                        write!(f, " ")?;
705                    }
706                    write!(f, "}}")
707                }
708            },
709        }
710    }
711}
712
713impl core::fmt::Display for BopStr {
714    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
715        write!(f, "{}", self.0)
716    }
717}
718
719// ─── Value helpers ─────────────────────────────────────────────────────────
720
721impl Value {
722    pub fn inspect(&self) -> String {
723        match self {
724            Value::Str(s) => format!("\"{}\"", s.0),
725            other => format!("{}", other),
726        }
727    }
728
729    pub fn type_name(&self) -> &'static str {
730        match self {
731            Value::Int(_) => "int",
732            Value::Number(_) => "number",
733            Value::Str(_) => "string",
734            Value::Bool(_) => "bool",
735            Value::None => "none",
736            Value::Array(_) => "array",
737            Value::Dict(_) => "dict",
738            Value::Fn(_) => "fn",
739            // Generic bucket — the *specific* type name lives on
740            // the value itself (`struct_type_name()`). `type()`
741            // returns the Bop type name via the display path, so
742            // `type(Point { ... })` shows `"Point"`.
743            Value::Struct(_) => "struct",
744            Value::EnumVariant(_) => "enum",
745            Value::Module(_) => "module",
746            Value::Iter(_) => "iter",
747        }
748    }
749
750    /// The user-facing name for this value's type. For struct
751    /// values it's the declared type (`"Point"`); for enum
752    /// variants it's the enum's type name; for built-in
753    /// variants it matches [`Self::type_name`].
754    pub fn display_type_name(&self) -> String {
755        match self {
756            Value::Struct(s) => s.type_name.clone(),
757            Value::EnumVariant(e) => e.type_name.clone(),
758            other => other.type_name().to_string(),
759        }
760    }
761
762    pub fn is_truthy(&self) -> bool {
763        match self {
764            Value::Bool(b) => *b,
765            Value::None => false,
766            Value::Int(n) => *n != 0,
767            Value::Number(n) => *n != 0.0,
768            Value::Str(s) => !s.0.is_empty(),
769            Value::Array(a) => !a.0.is_empty(),
770            Value::Dict(d) => !d.0.is_empty(),
771            // A callable is always a "thing" — match other
772            // non-empty runtime objects.
773            Value::Fn(_) => true,
774            // Structs carry fielded data and are always truthy,
775            // even if they have no fields (the "unit struct"
776            // use case) — matching how classes / records behave
777            // in most scripting languages.
778            Value::Struct(_) => true,
779            // Enum variants represent a tagged choice; always
780            // truthy regardless of payload.
781            Value::EnumVariant(_) => true,
782            // A module is always a concrete thing — matches
783            // fn's behaviour.
784            Value::Module(_) => true,
785            // Iterators are always truthy, even when exhausted.
786            // Callers check `Iter::Done` via `.next()`, not via
787            // truthiness — matches how fns / modules behave.
788            Value::Iter(_) => true,
789        }
790    }
791}
792
793// ─── Deref for read access ─────────────────────────────────────────────────
794
795impl BopStr {
796    pub fn as_str(&self) -> &str {
797        &self.0
798    }
799}
800
801impl core::ops::Deref for BopStr {
802    type Target = str;
803    fn deref(&self) -> &str {
804        &self.0
805    }
806}
807
808impl core::ops::Deref for BopArray {
809    type Target = [Value];
810    fn deref(&self) -> &[Value] {
811        &self.0
812    }
813}
814
815impl core::ops::Deref for BopDict {
816    type Target = [(String, Value)];
817    fn deref(&self) -> &[(String, Value)] {
818        &self.0
819    }
820}
821
822// ─── Mutation methods ──────────────────────────────────────────────────────
823
824impl BopArray {
825    /// Take the inner Vec, leaving an empty array. Deallocates the buffer
826    /// from the memory tracker since it's leaving Value's control.
827    pub fn take(&mut self) -> Vec<Value> {
828        let taken = core::mem::take(&mut self.0);
829        bop_dealloc(taken.capacity() * core::mem::size_of::<Value>());
830        taken
831    }
832
833    /// Set a value at the given index. The old value at that index is dropped
834    /// (firing its Drop impl which calls bop_dealloc). No capacity change.
835    pub fn set(&mut self, index: usize, val: Value) {
836        self.0[index] = val;
837    }
838}
839
840impl BopStruct {
841    pub fn type_name(&self) -> &str {
842        &self.type_name
843    }
844
845    /// Module this struct type was declared in. Forms one half
846    /// of the type's identity — the other half is the bare
847    /// [`Self::type_name`].
848    pub fn module_path(&self) -> &str {
849        &self.module_path
850    }
851
852    pub fn fields(&self) -> &[(String, Value)] {
853        &self.fields
854    }
855
856    /// Look up a field by name. `None` if no such field.
857    pub fn field(&self, name: &str) -> Option<&Value> {
858        self.fields.iter().find(|(k, _)| k == name).map(|(_, v)| v)
859    }
860
861    /// Replace the value of an existing field. Returns `true` if
862    /// the field was present; `false` if the caller should raise
863    /// a "no such field" error. The old value is dropped (firing
864    /// its allocation tracking); no capacity change in the Vec.
865    pub fn set_field(&mut self, name: &str, value: Value) -> bool {
866        if let Some(entry) = self.fields.iter_mut().find(|(k, _)| k == name) {
867            entry.1 = value;
868            true
869        } else {
870            false
871        }
872    }
873}
874
875impl BopEnumVariant {
876    pub fn type_name(&self) -> &str {
877        &self.type_name
878    }
879
880    /// Module this enum type was declared in. Paired with
881    /// [`Self::type_name`] to form the type's identity.
882    pub fn module_path(&self) -> &str {
883        &self.module_path
884    }
885
886    pub fn variant(&self) -> &str {
887        &self.variant
888    }
889
890    pub fn payload(&self) -> &EnumPayload {
891        &self.payload
892    }
893
894    /// Field access for struct-variant payloads — mirrors
895    /// [`BopStruct::field`]. Returns `None` for unit / tuple
896    /// variants or when the field isn't in this variant's
897    /// payload.
898    pub fn field(&self, name: &str) -> Option<&Value> {
899        match &self.payload {
900            EnumPayload::Struct(fields) => {
901                fields.iter().find(|(k, _)| k == name).map(|(_, v)| v)
902            }
903            _ => None,
904        }
905    }
906}
907
908impl BopDict {
909    /// Set a key-value pair. If the key exists, replaces the value.
910    /// If new, tracks the key's allocation and any Vec capacity growth
911    /// from the push (Vec may reallocate to a larger buffer).
912    pub fn set_key(&mut self, key: &str, val: Value) {
913        if let Some(entry) = self.0.iter_mut().find(|(k, _)| k == key) {
914            entry.1 = val;
915        } else {
916            let old_cap = self.0.capacity();
917            let key = key.to_string();
918            bop_alloc(key.capacity());
919            self.0.push((key, val));
920            let new_cap = self.0.capacity();
921            if new_cap > old_cap {
922                bop_alloc((new_cap - old_cap) * core::mem::size_of::<(String, Value)>());
923            }
924        }
925    }
926}
927
928// ─── Equality ──────────────────────────────────────────────────────────────
929
930pub fn values_equal(a: &Value, b: &Value) -> bool {
931    match (a, b) {
932        (Value::Int(x), Value::Int(y)) => x == y,
933        (Value::Number(x), Value::Number(y)) => x == y,
934        // Cross-type numeric equality: `1 == 1.0` is true, same
935        // as Python / JS. Widens the Int through f64 for the
936        // comparison — lossy for magnitudes above 2^53, but
937        // that's the cost of the convenience. Stricter-typed
938        // code can call `int()` / `float()` explicitly first.
939        (Value::Int(x), Value::Number(y)) => (*x as f64) == *y,
940        (Value::Number(x), Value::Int(y)) => *x == (*y as f64),
941        (Value::Str(x), Value::Str(y)) => x == y,
942        (Value::Bool(x), Value::Bool(y)) => x == y,
943        (Value::None, Value::None) => true,
944        (Value::Array(x), Value::Array(y)) => {
945            x.len() == y.len() && x.iter().zip(y.iter()).all(|(a, b)| values_equal(a, b))
946        }
947        (Value::Dict(x), Value::Dict(y)) => {
948            x.len() == y.len()
949                && x.iter().all(|(k, v)| {
950                    y.iter()
951                        .find(|(k2, _)| k2 == k)
952                        .is_some_and(|(_, v2)| values_equal(v, v2))
953                })
954        }
955        // Functions have identity-based equality: two references
956        // to the same `BopFn` compare equal; structurally identical
957        // closures constructed independently do not.
958        (Value::Fn(a), Value::Fn(b)) => Rc::ptr_eq(a, b),
959        // Structural equality for user structs: full type
960        // identity (module_path + type_name) AND every field
961        // equal in declaration order. Two structs with the same
962        // name declared in different modules deliberately compare
963        // as *not equal* — they're distinct types.
964        (Value::Struct(a), Value::Struct(b)) => {
965            a.module_path == b.module_path
966                && a.type_name == b.type_name
967                && a.fields.len() == b.fields.len()
968                && a.fields
969                    .iter()
970                    .zip(b.fields.iter())
971                    .all(|((ka, va), (kb, vb))| ka == kb && values_equal(va, vb))
972        }
973        // Enum variants: same full type identity (module_path +
974        // type_name), same variant name, same payload shape +
975        // structural equality on payload items.
976        (Value::EnumVariant(a), Value::EnumVariant(b)) => {
977            a.module_path == b.module_path
978                && a.type_name == b.type_name
979                && a.variant == b.variant
980                && match (&a.payload, &b.payload) {
981                    (EnumPayload::Unit, EnumPayload::Unit) => true,
982                    (EnumPayload::Tuple(ax), EnumPayload::Tuple(bx)) => {
983                        ax.len() == bx.len()
984                            && ax
985                                .iter()
986                                .zip(bx.iter())
987                                .all(|(x, y)| values_equal(x, y))
988                    }
989                    (EnumPayload::Struct(af), EnumPayload::Struct(bf)) => {
990                        af.len() == bf.len()
991                            && af
992                                .iter()
993                                .zip(bf.iter())
994                                .all(|((ka, va), (kb, vb))| {
995                                    ka == kb && values_equal(va, vb)
996                                })
997                    }
998                    _ => false,
999                }
1000        }
1001        _ => false,
1002    }
1003}