Skip to main content

sema_core/
value.rs

1use std::any::Any;
2use std::cell::{Cell, RefCell};
3use std::collections::BTreeMap;
4use std::fmt;
5use std::hash::{Hash, Hasher};
6use std::rc::Rc;
7
8use hashbrown::HashMap as SpurMap;
9use lasso::{Key, Rodeo, Spur};
10
11use crate::error::SemaError;
12use crate::EvalContext;
13
14// Compile-time check: NaN-boxing requires 64-bit pointers that fit in 48-bit VA space.
15// 32-bit platforms cannot use this representation (pointers don't fit the encoding).
16// wasm32 is exempted because its 32-bit pointers always fit in 45 bits.
17#[cfg(not(any(target_pointer_width = "64", target_arch = "wasm32")))]
18compile_error!("sema-core NaN-boxed Value requires a 64-bit platform (or wasm32)");
19
20// ── String interning ──────────────────────────────────────────────
21
22thread_local! {
23    static INTERNER: RefCell<Rodeo> = RefCell::new(Rodeo::default());
24}
25
26/// Intern a string, returning a Spur key.
27pub fn intern(s: &str) -> Spur {
28    INTERNER.with(|r| r.borrow_mut().get_or_intern(s))
29}
30
31/// Resolve a Spur key back to a String.
32pub fn resolve(spur: Spur) -> String {
33    INTERNER.with(|r| r.borrow().resolve(&spur).to_string())
34}
35
36// A `Spur` must fit in the 32-bit NaN-box payload below for the packing to round-trip.
37const _: () = assert!(std::mem::size_of::<Spur>() == 4);
38
39/// Pack an interned [`Spur`] into the 32 raw bits stored in a NaN-boxed `Value`
40/// symbol/keyword payload (inverse of [`bits_to_spur`]).
41///
42/// The bits are the Spur's underlying `NonZeroU32` value, so two `Value`s holding
43/// the same symbol compare equal by raw bits. This and [`bits_to_spur`] are the
44/// single place that encodes the Spur↔bits mapping (via lasso's stable [`Key`]
45/// API) — both `sema-core` and `sema-vm` go through them instead of `transmute`.
46#[inline(always)]
47pub fn spur_to_bits(spur: Spur) -> u32 {
48    // `Key::into_usize` is offset-by-one (it returns `inner.get() - 1`); the bits
49    // we store are the raw `NonZeroU32` value, i.e. `into_usize() + 1`.
50    spur.into_usize() as u32 + 1
51}
52
53/// Reconstruct a [`Spur`] from the 32 raw bits stored in a NaN-boxed `Value`
54/// symbol/keyword payload (inverse of [`spur_to_bits`]).
55///
56/// `bits` is always a value produced by [`spur_to_bits`] from a real interned
57/// key, so it is non-zero and the conversion cannot fail; a zero/invalid `bits`
58/// would indicate memory corruption and panics.
59#[inline(always)]
60pub fn bits_to_spur(bits: u32) -> Spur {
61    Spur::try_from_usize((bits - 1) as usize)
62        .expect("NaN-boxed symbol/keyword payload is not a valid interned key")
63}
64
65/// Resolve a Spur and call f with the &str, avoiding allocation.
66pub fn with_resolved<F, R>(spur: Spur, f: F) -> R
67where
68    F: FnOnce(&str) -> R,
69{
70    INTERNER.with(|r| {
71        let interner = r.borrow();
72        f(interner.resolve(&spur))
73    })
74}
75
76/// Return interner statistics: (count, estimated_memory_bytes).
77pub fn interner_stats() -> (usize, usize) {
78    INTERNER.with(|r| {
79        let interner = r.borrow();
80        let count = interner.len();
81        let bytes = count * 16; // approximate: Spur (4 bytes) + average string data
82        (count, bytes)
83    })
84}
85
86// ── Gensym counter ────────────────────────────────────────────────
87
88thread_local! {
89    static GENSYM_COUNTER: Cell<u64> = const { Cell::new(0) };
90}
91
92/// Generate a unique symbol name: `<prefix>__<counter>`.
93/// Used by both manual `(gensym)` and auto-gensym `foo#` in quasiquote.
94/// Single shared counter prevents collisions between the two mechanisms.
95pub fn next_gensym(prefix: &str) -> String {
96    GENSYM_COUNTER.with(|c| {
97        let val = c.get();
98        c.set(val.wrapping_add(1));
99        format!("{prefix}__{val}")
100    })
101}
102
103/// Compare two Spurs by their resolved string content (lexicographic).
104pub fn compare_spurs(a: Spur, b: Spur) -> std::cmp::Ordering {
105    if a == b {
106        return std::cmp::Ordering::Equal;
107    }
108    INTERNER.with(|r| {
109        let interner = r.borrow();
110        interner.resolve(&a).cmp(interner.resolve(&b))
111    })
112}
113
114// ── Supporting types (unchanged public API) ───────────────────────
115
116/// A native function callable from Sema.
117pub type NativeFnInner = dyn Fn(&EvalContext, &[Value]) -> Result<Value, SemaError>;
118
119pub struct NativeFn {
120    pub name: String,
121    pub func: Box<NativeFnInner>,
122    pub payload: Option<Rc<dyn Any>>,
123    /// True when this `NativeFn` is actually the fallback wrapper for a VM
124    /// closure (a user-defined `lambda`/`fn`), not a genuine builtin. The VM
125    /// represents closures as `NativeFn`s carrying a `VmClosurePayload`; this
126    /// flag lets `type`/`type_name` report `:lambda` instead of `:native-fn`
127    /// without sema-core/sema-stdlib needing to know the VM's payload type.
128    pub is_closure: bool,
129}
130
131impl NativeFn {
132    pub fn simple(
133        name: impl Into<String>,
134        f: impl Fn(&[Value]) -> Result<Value, SemaError> + 'static,
135    ) -> Self {
136        Self {
137            name: name.into(),
138            func: Box::new(move |_ctx, args| f(args)),
139            payload: None,
140            is_closure: false,
141        }
142    }
143
144    pub fn with_ctx(
145        name: impl Into<String>,
146        f: impl Fn(&EvalContext, &[Value]) -> Result<Value, SemaError> + 'static,
147    ) -> Self {
148        Self {
149            name: name.into(),
150            func: Box::new(f),
151            payload: None,
152            is_closure: false,
153        }
154    }
155
156    pub fn with_payload(
157        name: impl Into<String>,
158        payload: Rc<dyn Any>,
159        f: impl Fn(&EvalContext, &[Value]) -> Result<Value, SemaError> + 'static,
160    ) -> Self {
161        Self {
162            name: name.into(),
163            func: Box::new(f),
164            payload: Some(payload),
165            is_closure: false,
166        }
167    }
168}
169
170impl fmt::Debug for NativeFn {
171    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172        write!(f, "<native-fn {}>", self.name)
173    }
174}
175
176/// A user-defined lambda.
177#[derive(Debug, Clone)]
178pub struct Lambda {
179    pub params: Vec<Spur>,
180    pub rest_param: Option<Spur>,
181    pub body: Vec<Value>,
182    pub env: Env,
183    pub name: Option<Spur>,
184}
185
186/// A macro definition.
187#[derive(Debug, Clone)]
188pub struct Macro {
189    pub params: Vec<Spur>,
190    pub rest_param: Option<Spur>,
191    pub body: Vec<Value>,
192    pub name: Spur,
193}
194
195/// A lazy promise: delay/force with memoization.
196pub struct Thunk {
197    pub body: Value,
198    pub forced: RefCell<Option<Value>>,
199}
200
201impl fmt::Debug for Thunk {
202    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203        if self.forced.borrow().is_some() {
204            write!(f, "<promise (forced)>")
205        } else {
206            write!(f, "<promise>")
207        }
208    }
209}
210
211impl Clone for Thunk {
212    fn clone(&self) -> Self {
213        Thunk {
214            body: self.body.clone(),
215            forced: RefCell::new(self.forced.borrow().clone()),
216        }
217    }
218}
219
220/// State of an async promise/future.
221///
222/// `Cancelled` is a peer of `Rejected`, not a sub-kind of it: a promise that
223/// the user explicitly cancels via `async/cancel` is *not* a normal rejection
224/// (which a user might catch and recover from). Keeping the two distinct lets
225/// `async/cancelled?` be precise without string-matching, and lets
226/// `async/rejected?` honestly report `#f` for cancelled promises.
227#[derive(Debug, Clone, PartialEq, Eq)]
228pub enum PromiseState {
229    Pending,
230    Resolved(Value),
231    Rejected(String),
232    Cancelled,
233}
234
235/// An async promise: represents a value that will be available in the future.
236pub struct AsyncPromise {
237    pub state: RefCell<PromiseState>,
238    pub task_id: Cell<u64>,
239}
240
241impl fmt::Debug for AsyncPromise {
242    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
243        match &*self.state.borrow() {
244            PromiseState::Pending => write!(f, "<async-promise pending>"),
245            PromiseState::Resolved(_) => write!(f, "<async-promise resolved>"),
246            PromiseState::Rejected(e) => write!(f, "<async-promise rejected: {e}>"),
247            PromiseState::Cancelled => write!(f, "<async-promise cancelled>"),
248        }
249    }
250}
251
252impl Clone for AsyncPromise {
253    fn clone(&self) -> Self {
254        AsyncPromise {
255            state: RefCell::new(self.state.borrow().clone()),
256            task_id: Cell::new(self.task_id.get()),
257        }
258    }
259}
260
261/// A bounded async channel for communication between coroutines.
262pub struct Channel {
263    pub buffer: RefCell<std::collections::VecDeque<Value>>,
264    pub capacity: usize,
265    pub closed: Cell<bool>,
266}
267
268impl fmt::Debug for Channel {
269    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
270        let len = self.buffer.borrow().len();
271        write!(f, "<channel {len}/{}>", self.capacity)
272    }
273}
274
275impl Clone for Channel {
276    fn clone(&self) -> Self {
277        Channel {
278            buffer: RefCell::new(self.buffer.borrow().clone()),
279            capacity: self.capacity,
280            closed: Cell::new(self.closed.get()),
281        }
282    }
283}
284
285/// A record: tagged product type created by define-record-type.
286#[derive(Debug, Clone)]
287pub struct Record {
288    pub type_tag: Spur,
289    pub field_names: Vec<Spur>,
290    pub fields: Vec<Value>,
291}
292
293/// A message role in a conversation.
294#[derive(Debug, Clone, PartialEq, Eq)]
295pub enum Role {
296    System,
297    User,
298    Assistant,
299    Tool,
300}
301
302impl fmt::Display for Role {
303    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
304        match self {
305            Role::System => write!(f, "system"),
306            Role::User => write!(f, "user"),
307            Role::Assistant => write!(f, "assistant"),
308            Role::Tool => write!(f, "tool"),
309        }
310    }
311}
312
313/// A base64-encoded image attachment.
314#[derive(Debug, Clone)]
315pub struct ImageAttachment {
316    pub data: String,
317    pub media_type: String,
318}
319
320/// A single message in a conversation.
321#[derive(Debug, Clone)]
322pub struct Message {
323    pub role: Role,
324    pub content: String,
325    /// Optional image attachments (base64-encoded).
326    pub images: Vec<ImageAttachment>,
327}
328
329/// A prompt: a structured list of messages.
330#[derive(Debug, Clone)]
331pub struct Prompt {
332    pub messages: Vec<Message>,
333}
334
335/// A conversation: immutable history + provider config.
336#[derive(Debug, Clone)]
337pub struct Conversation {
338    pub messages: Vec<Message>,
339    pub model: String,
340    pub metadata: BTreeMap<String, String>,
341}
342
343/// A tool definition for LLM function calling.
344#[derive(Debug, Clone)]
345pub struct ToolDefinition {
346    pub name: String,
347    pub description: String,
348    pub parameters: Value,
349    pub handler: Value,
350}
351
352/// An agent: system prompt + tools + config for autonomous loops.
353#[derive(Debug, Clone)]
354pub struct Agent {
355    pub name: String,
356    pub system: String,
357    pub tools: Vec<Value>,
358    pub max_turns: usize,
359    pub model: String,
360}
361
362/// A multimethod: dispatch-function + method table.
363/// Interior-mutable so `defmethod` can add methods after creation.
364pub struct MultiMethod {
365    pub name: Spur,
366    pub dispatch_fn: Value,
367    pub methods: RefCell<BTreeMap<Value, Value>>,
368    pub default: RefCell<Option<Value>>,
369}
370
371impl fmt::Debug for MultiMethod {
372    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
373        write!(f, "<multimethod {}>", resolve(self.name))
374    }
375}
376
377/// Trait for stream implementations (files, buffers, serial ports, etc.).
378/// All methods take `&self` — interior mutability is handled by the implementation.
379pub trait SemaStream: fmt::Debug {
380    fn read(&self, buf: &mut [u8]) -> Result<usize, SemaError>;
381    fn write(&self, data: &[u8]) -> Result<usize, SemaError>;
382    fn available(&self) -> Result<bool, SemaError> {
383        Ok(false)
384    }
385    fn flush(&self) -> Result<(), SemaError> {
386        Ok(())
387    }
388    fn close(&self) -> Result<(), SemaError> {
389        Ok(())
390    }
391    fn is_readable(&self) -> bool {
392        true
393    }
394    fn is_writable(&self) -> bool {
395        true
396    }
397    fn stream_type(&self) -> &'static str;
398    fn as_any(&self) -> &dyn std::any::Any;
399}
400
401/// Sized wrapper around `dyn SemaStream` for NaN-boxing (thin pointer via Rc<StreamBox>).
402/// Tracks closed state centrally so all impls get close-guarding for free.
403pub struct StreamBox {
404    inner: RefCell<Box<dyn SemaStream>>,
405    closed: Cell<bool>,
406}
407
408impl StreamBox {
409    pub fn new(s: impl SemaStream + 'static) -> Self {
410        StreamBox {
411            inner: RefCell::new(Box::new(s)),
412            closed: Cell::new(false),
413        }
414    }
415
416    pub fn read(&self, buf: &mut [u8]) -> Result<usize, SemaError> {
417        if self.closed.get() {
418            return Err(SemaError::eval("stream/read: stream is closed"));
419        }
420        self.inner.borrow().read(buf)
421    }
422
423    pub fn write(&self, data: &[u8]) -> Result<usize, SemaError> {
424        if self.closed.get() {
425            return Err(SemaError::eval("stream/write: stream is closed"));
426        }
427        self.inner.borrow().write(data)
428    }
429
430    pub fn flush(&self) -> Result<(), SemaError> {
431        if self.closed.get() {
432            return Err(SemaError::eval("stream/flush: stream is closed"));
433        }
434        self.inner.borrow().flush()
435    }
436
437    pub fn close(&self) -> Result<(), SemaError> {
438        if self.closed.get() {
439            return Ok(()); // double-close is a no-op
440        }
441        self.inner.borrow().close()?;
442        self.closed.set(true);
443        Ok(())
444    }
445
446    pub fn is_closed(&self) -> bool {
447        self.closed.get()
448    }
449
450    pub fn is_readable(&self) -> bool {
451        !self.closed.get() && self.inner.borrow().is_readable()
452    }
453
454    pub fn is_writable(&self) -> bool {
455        !self.closed.get() && self.inner.borrow().is_writable()
456    }
457
458    pub fn available(&self) -> Result<bool, SemaError> {
459        if self.closed.get() {
460            return Ok(false);
461        }
462        self.inner.borrow().available()
463    }
464
465    pub fn stream_type(&self) -> &'static str {
466        self.inner.borrow().stream_type()
467    }
468
469    pub fn borrow_inner(&self) -> std::cell::Ref<'_, Box<dyn SemaStream>> {
470        self.inner.borrow()
471    }
472}
473
474impl fmt::Debug for StreamBox {
475    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
476        write!(f, "<stream:{}>", self.stream_type())
477    }
478}
479
480impl Clone for MultiMethod {
481    fn clone(&self) -> Self {
482        MultiMethod {
483            name: self.name,
484            dispatch_fn: self.dispatch_fn.clone(),
485            methods: RefCell::new(self.methods.borrow().clone()),
486            default: RefCell::new(self.default.borrow().clone()),
487        }
488    }
489}
490
491// ── NaN-boxing constants ──────────────────────────────────────────
492
493// IEEE 754 double layout:
494//   bit 63:     sign
495//   bits 62-52: exponent (11 bits)
496//   bits 51-0:  mantissa (52 bits), bit 51 = quiet NaN bit
497//
498// Boxed (non-float) values use: sign=1, exp=all 1s, quiet=1
499//   Then bits 50-45 = TAG (6 bits), bits 44-0 = PAYLOAD (45 bits)
500
501/// Mask for checking if a value is boxed (sign + exponent + quiet bit)
502const BOX_MASK: u64 = 0xFFF8_0000_0000_0000;
503
504/// The 45-bit payload mask
505const PAYLOAD_MASK: u64 = (1u64 << 45) - 1; // 0x1FFF_FFFF_FFFF
506
507/// Sign-extension bit for 45-bit signed integers
508const INT_SIGN_BIT: u64 = 1u64 << 44;
509
510/// 6-bit mask for extracting the tag from a boxed value (bits 50-45).
511const TAG_MASK_6BIT: u64 = 0x3F;
512
513/// Canonical quiet NaN (sign=0) — used for NaN float values to avoid collision with boxed
514const CANONICAL_NAN: u64 = 0x7FF8_0000_0000_0000;
515
516// Tags (6 bits, encoded in bits 50-45)
517const TAG_NIL: u64 = 0;
518const TAG_FALSE: u64 = 1;
519const TAG_TRUE: u64 = 2;
520const TAG_INT_SMALL: u64 = 3;
521const TAG_CHAR: u64 = 4;
522const TAG_SYMBOL: u64 = 5;
523const TAG_KEYWORD: u64 = 6;
524const TAG_INT_BIG: u64 = 7;
525const TAG_STRING: u64 = 8;
526const TAG_LIST: u64 = 9;
527const TAG_VECTOR: u64 = 10;
528const TAG_MAP: u64 = 11;
529const TAG_HASHMAP: u64 = 12;
530const TAG_LAMBDA: u64 = 13;
531const TAG_MACRO: u64 = 14;
532pub const TAG_NATIVE_FN: u64 = 15;
533const TAG_PROMPT: u64 = 16;
534const TAG_MESSAGE: u64 = 17;
535const TAG_CONVERSATION: u64 = 18;
536const TAG_TOOL_DEF: u64 = 19;
537const TAG_AGENT: u64 = 20;
538const TAG_THUNK: u64 = 21;
539const TAG_RECORD: u64 = 22;
540const TAG_BYTEVECTOR: u64 = 23;
541const TAG_MULTIMETHOD: u64 = 24;
542const TAG_STREAM: u64 = 25;
543const TAG_F64_ARRAY: u64 = 26;
544const TAG_I64_ARRAY: u64 = 27;
545const TAG_ASYNC_PROMISE: u64 = 28;
546const TAG_CHANNEL: u64 = 29;
547
548/// Small-int range: [-2^44, 2^44 - 1] = [-17_592_186_044_416, +17_592_186_044_415]
549const SMALL_INT_MIN: i64 = -(1i64 << 44);
550const SMALL_INT_MAX: i64 = (1i64 << 44) - 1;
551
552// ── Public NaN-boxing constants for VM use ────────────────────────
553
554/// Tag + box combined mask: upper 19 bits (sign + exponent + quiet + 6-bit tag).
555pub const NAN_TAG_MASK: u64 = BOX_MASK | (TAG_MASK_6BIT << 45); // 0xFFFF_E000_0000_0000
556
557/// The expected upper bits for a small int value: BOX_MASK | (TAG_INT_SMALL << 45).
558pub const NAN_INT_SMALL_PATTERN: u64 = BOX_MASK | (TAG_INT_SMALL << 45);
559
560/// Public payload mask (45 bits).
561pub const NAN_PAYLOAD_MASK: u64 = PAYLOAD_MASK;
562
563/// Sign bit within the 45-bit payload (bit 44) — for sign-extending small ints.
564pub const NAN_INT_SIGN_BIT: u64 = INT_SIGN_BIT;
565
566/// Number of payload bits in NaN-boxed values (45).
567pub const NAN_PAYLOAD_BITS: u32 = 45;
568
569// ── Helpers for encoding/decoding ─────────────────────────────────
570
571#[inline(always)]
572fn make_boxed(tag: u64, payload: u64) -> u64 {
573    BOX_MASK | (tag << 45) | (payload & PAYLOAD_MASK)
574}
575
576#[inline(always)]
577fn is_boxed(bits: u64) -> bool {
578    (bits & BOX_MASK) == BOX_MASK
579}
580
581#[inline(always)]
582fn get_tag(bits: u64) -> u64 {
583    (bits >> 45) & TAG_MASK_6BIT
584}
585
586#[inline(always)]
587fn get_payload(bits: u64) -> u64 {
588    bits & PAYLOAD_MASK
589}
590
591#[inline(always)]
592fn ptr_to_payload(ptr: *const u8) -> u64 {
593    let raw = ptr as u64;
594    debug_assert!(raw & 0x7 == 0, "pointer not 8-byte aligned: 0x{:x}", raw);
595    debug_assert!(
596        raw >> 48 == 0,
597        "pointer exceeds 48-bit VA space: 0x{:x}",
598        raw
599    );
600    raw >> 3
601}
602
603#[inline(always)]
604fn payload_to_ptr(payload: u64) -> *const u8 {
605    (payload << 3) as *const u8
606}
607
608// ── ValueView: pattern-matching enum ──────────────────────────────
609
610/// A view of a NaN-boxed Value for pattern matching.
611/// Returned by `Value::view()`. Heap types hold Rc (refcount bumped).
612pub enum ValueView {
613    Nil,
614    Bool(bool),
615    Int(i64),
616    Float(f64),
617    String(Rc<String>),
618    Symbol(Spur),
619    Keyword(Spur),
620    Char(char),
621    List(Rc<Vec<Value>>),
622    Vector(Rc<Vec<Value>>),
623    Map(Rc<BTreeMap<Value, Value>>),
624    HashMap(Rc<hashbrown::HashMap<Value, Value>>),
625    Lambda(Rc<Lambda>),
626    Macro(Rc<Macro>),
627    NativeFn(Rc<NativeFn>),
628    Prompt(Rc<Prompt>),
629    Message(Rc<Message>),
630    Conversation(Rc<Conversation>),
631    ToolDef(Rc<ToolDefinition>),
632    Agent(Rc<Agent>),
633    Thunk(Rc<Thunk>),
634    Record(Rc<Record>),
635    Bytevector(Rc<Vec<u8>>),
636    MultiMethod(Rc<MultiMethod>),
637    Stream(Rc<StreamBox>),
638    F64Array(Rc<Vec<f64>>),
639    I64Array(Rc<Vec<i64>>),
640    AsyncPromise(Rc<AsyncPromise>),
641    Channel(Rc<Channel>),
642}
643
644// ── The NaN-boxed Value type ──────────────────────────────────────
645
646/// The core Value type for all Sema data.
647/// NaN-boxed: stored as 8 bytes. Floats stored directly,
648/// everything else encoded in quiet-NaN payload space.
649#[repr(transparent)]
650pub struct Value(u64);
651
652// ── Constructors ──────────────────────────────────────────────────
653
654impl Value {
655    // -- Immediate constructors --
656
657    pub const NIL: Value = Value(make_boxed_const(TAG_NIL, 0));
658    pub const TRUE: Value = Value(make_boxed_const(TAG_TRUE, 0));
659    pub const FALSE: Value = Value(make_boxed_const(TAG_FALSE, 0));
660
661    #[inline(always)]
662    pub fn nil() -> Value {
663        Value::NIL
664    }
665
666    #[inline(always)]
667    pub fn bool(b: bool) -> Value {
668        if b {
669            Value::TRUE
670        } else {
671            Value::FALSE
672        }
673    }
674
675    #[inline(always)]
676    pub fn int(n: i64) -> Value {
677        if (SMALL_INT_MIN..=SMALL_INT_MAX).contains(&n) {
678            // Encode as small int (45-bit two's complement)
679            let payload = (n as u64) & PAYLOAD_MASK;
680            Value(make_boxed(TAG_INT_SMALL, payload))
681        } else {
682            // Out of range: heap-allocate
683            let rc = Rc::new(n);
684            let ptr = Rc::into_raw(rc) as *const u8;
685            Value(make_boxed(TAG_INT_BIG, ptr_to_payload(ptr)))
686        }
687    }
688
689    #[inline(always)]
690    pub fn float(f: f64) -> Value {
691        let bits = f.to_bits();
692        if f.is_nan() {
693            // Canonicalize NaN to avoid collision with boxed patterns
694            Value(CANONICAL_NAN)
695        } else {
696            // Check: a non-NaN float could still have the BOX_MASK pattern
697            // This happens for negative infinity and some subnormals — but
698            // negative infinity is 0xFFF0_0000_0000_0000 which does NOT match
699            // BOX_MASK (0xFFF8...) because bit 51 (quiet) is 0.
700            // In IEEE 754, the only values with all exponent bits set AND quiet bit set
701            // are quiet NaNs, which we've already canonicalized above.
702            debug_assert!(
703                !is_boxed(bits),
704                "non-NaN float collides with boxed pattern: {:?} = 0x{:016x}",
705                f,
706                bits
707            );
708            Value(bits)
709        }
710    }
711
712    #[inline(always)]
713    pub fn char(c: char) -> Value {
714        Value(make_boxed(TAG_CHAR, c as u64))
715    }
716
717    #[inline(always)]
718    pub fn symbol_from_spur(spur: Spur) -> Value {
719        Value(make_boxed(TAG_SYMBOL, spur_to_bits(spur) as u64))
720    }
721
722    pub fn symbol(s: &str) -> Value {
723        Value::symbol_from_spur(intern(s))
724    }
725
726    #[inline(always)]
727    pub fn keyword_from_spur(spur: Spur) -> Value {
728        Value(make_boxed(TAG_KEYWORD, spur_to_bits(spur) as u64))
729    }
730
731    pub fn keyword(s: &str) -> Value {
732        Value::keyword_from_spur(intern(s))
733    }
734
735    // -- Heap constructors --
736
737    fn from_rc_ptr<T>(tag: u64, rc: Rc<T>) -> Value {
738        let ptr = Rc::into_raw(rc) as *const u8;
739        Value(make_boxed(tag, ptr_to_payload(ptr)))
740    }
741
742    pub fn string(s: &str) -> Value {
743        Value::from_rc_ptr(TAG_STRING, Rc::new(s.to_string()))
744    }
745
746    pub fn string_from_rc(rc: Rc<String>) -> Value {
747        Value::from_rc_ptr(TAG_STRING, rc)
748    }
749
750    pub fn list(v: Vec<Value>) -> Value {
751        Value::from_rc_ptr(TAG_LIST, Rc::new(v))
752    }
753
754    pub fn list_from_rc(rc: Rc<Vec<Value>>) -> Value {
755        Value::from_rc_ptr(TAG_LIST, rc)
756    }
757
758    pub fn vector(v: Vec<Value>) -> Value {
759        Value::from_rc_ptr(TAG_VECTOR, Rc::new(v))
760    }
761
762    pub fn vector_from_rc(rc: Rc<Vec<Value>>) -> Value {
763        Value::from_rc_ptr(TAG_VECTOR, rc)
764    }
765
766    pub fn map(m: BTreeMap<Value, Value>) -> Value {
767        Value::from_rc_ptr(TAG_MAP, Rc::new(m))
768    }
769
770    pub fn map_from_rc(rc: Rc<BTreeMap<Value, Value>>) -> Value {
771        Value::from_rc_ptr(TAG_MAP, rc)
772    }
773
774    pub fn hashmap(entries: Vec<(Value, Value)>) -> Value {
775        let map: hashbrown::HashMap<Value, Value> = entries.into_iter().collect();
776        Value::from_rc_ptr(TAG_HASHMAP, Rc::new(map))
777    }
778
779    pub fn hashmap_from_rc(rc: Rc<hashbrown::HashMap<Value, Value>>) -> Value {
780        Value::from_rc_ptr(TAG_HASHMAP, rc)
781    }
782
783    pub fn lambda(l: Lambda) -> Value {
784        Value::from_rc_ptr(TAG_LAMBDA, Rc::new(l))
785    }
786
787    pub fn lambda_from_rc(rc: Rc<Lambda>) -> Value {
788        Value::from_rc_ptr(TAG_LAMBDA, rc)
789    }
790
791    pub fn macro_val(m: Macro) -> Value {
792        Value::from_rc_ptr(TAG_MACRO, Rc::new(m))
793    }
794
795    pub fn macro_from_rc(rc: Rc<Macro>) -> Value {
796        Value::from_rc_ptr(TAG_MACRO, rc)
797    }
798
799    pub fn native_fn(f: NativeFn) -> Value {
800        Value::from_rc_ptr(TAG_NATIVE_FN, Rc::new(f))
801    }
802
803    pub fn native_fn_from_rc(rc: Rc<NativeFn>) -> Value {
804        Value::from_rc_ptr(TAG_NATIVE_FN, rc)
805    }
806
807    pub fn prompt(p: Prompt) -> Value {
808        Value::from_rc_ptr(TAG_PROMPT, Rc::new(p))
809    }
810
811    pub fn prompt_from_rc(rc: Rc<Prompt>) -> Value {
812        Value::from_rc_ptr(TAG_PROMPT, rc)
813    }
814
815    pub fn message(m: Message) -> Value {
816        Value::from_rc_ptr(TAG_MESSAGE, Rc::new(m))
817    }
818
819    pub fn message_from_rc(rc: Rc<Message>) -> Value {
820        Value::from_rc_ptr(TAG_MESSAGE, rc)
821    }
822
823    pub fn conversation(c: Conversation) -> Value {
824        Value::from_rc_ptr(TAG_CONVERSATION, Rc::new(c))
825    }
826
827    pub fn conversation_from_rc(rc: Rc<Conversation>) -> Value {
828        Value::from_rc_ptr(TAG_CONVERSATION, rc)
829    }
830
831    pub fn tool_def(t: ToolDefinition) -> Value {
832        Value::from_rc_ptr(TAG_TOOL_DEF, Rc::new(t))
833    }
834
835    pub fn tool_def_from_rc(rc: Rc<ToolDefinition>) -> Value {
836        Value::from_rc_ptr(TAG_TOOL_DEF, rc)
837    }
838
839    pub fn agent(a: Agent) -> Value {
840        Value::from_rc_ptr(TAG_AGENT, Rc::new(a))
841    }
842
843    pub fn agent_from_rc(rc: Rc<Agent>) -> Value {
844        Value::from_rc_ptr(TAG_AGENT, rc)
845    }
846
847    pub fn thunk(t: Thunk) -> Value {
848        Value::from_rc_ptr(TAG_THUNK, Rc::new(t))
849    }
850
851    pub fn thunk_from_rc(rc: Rc<Thunk>) -> Value {
852        Value::from_rc_ptr(TAG_THUNK, rc)
853    }
854
855    pub fn record(r: Record) -> Value {
856        Value::from_rc_ptr(TAG_RECORD, Rc::new(r))
857    }
858
859    pub fn record_from_rc(rc: Rc<Record>) -> Value {
860        Value::from_rc_ptr(TAG_RECORD, rc)
861    }
862
863    pub fn bytevector(bytes: Vec<u8>) -> Value {
864        Value::from_rc_ptr(TAG_BYTEVECTOR, Rc::new(bytes))
865    }
866
867    pub fn bytevector_from_rc(rc: Rc<Vec<u8>>) -> Value {
868        Value::from_rc_ptr(TAG_BYTEVECTOR, rc)
869    }
870
871    pub fn f64_array(data: Vec<f64>) -> Value {
872        Value::from_rc_ptr(TAG_F64_ARRAY, Rc::new(data))
873    }
874
875    pub fn f64_array_from_rc(rc: Rc<Vec<f64>>) -> Value {
876        Value::from_rc_ptr(TAG_F64_ARRAY, rc)
877    }
878
879    pub fn i64_array(data: Vec<i64>) -> Value {
880        Value::from_rc_ptr(TAG_I64_ARRAY, Rc::new(data))
881    }
882
883    pub fn i64_array_from_rc(rc: Rc<Vec<i64>>) -> Value {
884        Value::from_rc_ptr(TAG_I64_ARRAY, rc)
885    }
886
887    pub fn multimethod(m: MultiMethod) -> Value {
888        Value::from_rc_ptr(TAG_MULTIMETHOD, Rc::new(m))
889    }
890
891    pub fn multimethod_from_rc(rc: Rc<MultiMethod>) -> Value {
892        Value::from_rc_ptr(TAG_MULTIMETHOD, rc)
893    }
894
895    pub fn stream(s: impl SemaStream + 'static) -> Value {
896        Value::from_rc_ptr(TAG_STREAM, Rc::new(StreamBox::new(s)))
897    }
898
899    pub fn stream_from_rc(rc: Rc<StreamBox>) -> Value {
900        Value::from_rc_ptr(TAG_STREAM, rc)
901    }
902
903    pub fn async_promise(promise: AsyncPromise) -> Value {
904        Value::from_rc_ptr(TAG_ASYNC_PROMISE, Rc::new(promise))
905    }
906    pub fn async_promise_from_rc(rc: Rc<AsyncPromise>) -> Value {
907        Value::from_rc_ptr(TAG_ASYNC_PROMISE, rc)
908    }
909    pub fn channel(ch: Channel) -> Value {
910        Value::from_rc_ptr(TAG_CHANNEL, Rc::new(ch))
911    }
912    pub fn channel_from_rc(rc: Rc<Channel>) -> Value {
913        Value::from_rc_ptr(TAG_CHANNEL, rc)
914    }
915}
916
917// Const-compatible boxed encoding (no function calls)
918const fn make_boxed_const(tag: u64, payload: u64) -> u64 {
919    BOX_MASK | (tag << 45) | (payload & PAYLOAD_MASK)
920}
921
922// ── Accessors ─────────────────────────────────────────────────────
923
924impl Value {
925    /// Get the raw bits (for debugging/testing).
926    #[inline(always)]
927    pub fn raw_bits(&self) -> u64 {
928        self.0
929    }
930
931    /// Construct a Value from raw NaN-boxed bits.
932    ///
933    /// # Safety
934    ///
935    /// Caller must ensure `bits` represents a valid NaN-boxed value.
936    /// For immediate types (nil, bool, int, symbol, keyword, char), this is always safe.
937    /// For heap-pointer types, the encoded pointer must be valid and have its Rc ownership
938    /// accounted for (i.e., the caller must ensure the refcount is correct).
939    #[inline(always)]
940    pub unsafe fn from_raw_bits(bits: u64) -> Value {
941        Value(bits)
942    }
943
944    /// Get the NaN-boxing tag of a boxed value (0-63).
945    /// Returns `None` for non-boxed values (floats).
946    #[inline(always)]
947    pub fn raw_tag(&self) -> Option<u64> {
948        if is_boxed(self.0) {
949            Some(get_tag(self.0))
950        } else {
951            None
952        }
953    }
954
955    /// Borrow the underlying NativeFn without bumping the Rc refcount.
956    /// SAFETY: The returned reference is valid as long as this Value is alive.
957    #[inline(always)]
958    pub fn as_native_fn_ref(&self) -> Option<&NativeFn> {
959        if is_boxed(self.0) && get_tag(self.0) == TAG_NATIVE_FN {
960            Some(unsafe { self.borrow_ref::<NativeFn>() })
961        } else {
962            None
963        }
964    }
965
966    /// Check if this is a float (non-boxed).
967    #[inline(always)]
968    pub fn is_float(&self) -> bool {
969        !is_boxed(self.0)
970    }
971
972    /// Recover an Rc<T> pointer from the payload WITHOUT consuming ownership.
973    /// This increments the refcount (returns a new Rc).
974    #[inline(always)]
975    unsafe fn get_rc<T>(&self) -> Rc<T> {
976        let payload = get_payload(self.0);
977        let ptr = payload_to_ptr(payload) as *const T;
978        Rc::increment_strong_count(ptr);
979        Rc::from_raw(ptr)
980    }
981
982    /// Borrow the underlying T from a heap-tagged Value.
983    /// SAFETY: caller must ensure the tag matches and T is correct.
984    #[inline(always)]
985    unsafe fn borrow_ref<T>(&self) -> &T {
986        let payload = get_payload(self.0);
987        let ptr = payload_to_ptr(payload) as *const T;
988        &*ptr
989    }
990
991    /// Pattern-match friendly view of this value.
992    /// For heap types, this bumps the Rc refcount.
993    pub fn view(&self) -> ValueView {
994        if !is_boxed(self.0) {
995            return ValueView::Float(f64::from_bits(self.0));
996        }
997        let tag = get_tag(self.0);
998        match tag {
999            TAG_NIL => ValueView::Nil,
1000            TAG_FALSE => ValueView::Bool(false),
1001            TAG_TRUE => ValueView::Bool(true),
1002            TAG_INT_SMALL => {
1003                let payload = get_payload(self.0);
1004                let val = if payload & INT_SIGN_BIT != 0 {
1005                    (payload | !PAYLOAD_MASK) as i64
1006                } else {
1007                    payload as i64
1008                };
1009                ValueView::Int(val)
1010            }
1011            TAG_CHAR => {
1012                let payload = get_payload(self.0);
1013                ValueView::Char(unsafe { char::from_u32_unchecked(payload as u32) })
1014            }
1015            TAG_SYMBOL => {
1016                let payload = get_payload(self.0);
1017                ValueView::Symbol(bits_to_spur(payload as u32))
1018            }
1019            TAG_KEYWORD => {
1020                let payload = get_payload(self.0);
1021                ValueView::Keyword(bits_to_spur(payload as u32))
1022            }
1023            TAG_INT_BIG => {
1024                let val = unsafe { *self.borrow_ref::<i64>() };
1025                ValueView::Int(val)
1026            }
1027            // SAFETY: every TAG_X arm below calls `get_rc::<T>()` where T matches the
1028            // type stored by the corresponding Value::<x>() constructor. The Clone and
1029            // Drop impls elsewhere in this file mirror this dispatch table — when adding
1030            // a new tag here, update both. The tag check above each branch is what makes
1031            // the transmute inside get_rc sound.
1032            TAG_STRING => ValueView::String(unsafe { self.get_rc::<String>() }),
1033            TAG_LIST => ValueView::List(unsafe { self.get_rc::<Vec<Value>>() }),
1034            TAG_VECTOR => ValueView::Vector(unsafe { self.get_rc::<Vec<Value>>() }),
1035            TAG_MAP => ValueView::Map(unsafe { self.get_rc::<BTreeMap<Value, Value>>() }),
1036            TAG_HASHMAP => {
1037                ValueView::HashMap(unsafe { self.get_rc::<hashbrown::HashMap<Value, Value>>() })
1038            }
1039            TAG_LAMBDA => ValueView::Lambda(unsafe { self.get_rc::<Lambda>() }),
1040            TAG_MACRO => ValueView::Macro(unsafe { self.get_rc::<Macro>() }),
1041            TAG_NATIVE_FN => ValueView::NativeFn(unsafe { self.get_rc::<NativeFn>() }),
1042            TAG_PROMPT => ValueView::Prompt(unsafe { self.get_rc::<Prompt>() }),
1043            TAG_MESSAGE => ValueView::Message(unsafe { self.get_rc::<Message>() }),
1044            TAG_CONVERSATION => ValueView::Conversation(unsafe { self.get_rc::<Conversation>() }),
1045            TAG_TOOL_DEF => ValueView::ToolDef(unsafe { self.get_rc::<ToolDefinition>() }),
1046            TAG_AGENT => ValueView::Agent(unsafe { self.get_rc::<Agent>() }),
1047            TAG_THUNK => ValueView::Thunk(unsafe { self.get_rc::<Thunk>() }),
1048            TAG_RECORD => ValueView::Record(unsafe { self.get_rc::<Record>() }),
1049            TAG_BYTEVECTOR => ValueView::Bytevector(unsafe { self.get_rc::<Vec<u8>>() }),
1050            TAG_MULTIMETHOD => ValueView::MultiMethod(unsafe { self.get_rc::<MultiMethod>() }),
1051            TAG_STREAM => ValueView::Stream(unsafe { self.get_rc::<StreamBox>() }),
1052            TAG_F64_ARRAY => ValueView::F64Array(unsafe { self.get_rc::<Vec<f64>>() }),
1053            TAG_I64_ARRAY => ValueView::I64Array(unsafe { self.get_rc::<Vec<i64>>() }),
1054            TAG_ASYNC_PROMISE => ValueView::AsyncPromise(unsafe { self.get_rc::<AsyncPromise>() }),
1055            TAG_CHANNEL => ValueView::Channel(unsafe { self.get_rc::<Channel>() }),
1056            _ => unreachable!("invalid NaN-boxed tag: {}", tag),
1057        }
1058    }
1059
1060    // -- Typed accessors (ergonomic, avoid full view match) --
1061
1062    #[inline(always)]
1063    pub fn type_name(&self) -> &'static str {
1064        if !is_boxed(self.0) {
1065            return "float";
1066        }
1067        match get_tag(self.0) {
1068            TAG_NIL => "nil",
1069            TAG_FALSE | TAG_TRUE => "bool",
1070            TAG_INT_SMALL | TAG_INT_BIG => "int",
1071            TAG_CHAR => "char",
1072            TAG_SYMBOL => "symbol",
1073            TAG_KEYWORD => "keyword",
1074            TAG_STRING => "string",
1075            TAG_LIST => "list",
1076            TAG_VECTOR => "vector",
1077            TAG_MAP => "map",
1078            TAG_HASHMAP => "hashmap",
1079            TAG_LAMBDA => "lambda",
1080            TAG_MACRO => "macro",
1081            TAG_NATIVE_FN => "native-fn",
1082            TAG_PROMPT => "prompt",
1083            TAG_MESSAGE => "message",
1084            TAG_CONVERSATION => "conversation",
1085            TAG_TOOL_DEF => "tool",
1086            TAG_AGENT => "agent",
1087            TAG_THUNK => "promise",
1088            TAG_RECORD => "record",
1089            TAG_BYTEVECTOR => "bytevector",
1090            TAG_MULTIMETHOD => "multimethod",
1091            TAG_STREAM => "stream",
1092            TAG_F64_ARRAY => "f64-array",
1093            TAG_I64_ARRAY => "i64-array",
1094            TAG_ASYNC_PROMISE => "async-promise",
1095            TAG_CHANNEL => "channel",
1096            _ => "unknown",
1097        }
1098    }
1099
1100    #[inline(always)]
1101    pub fn is_nil(&self) -> bool {
1102        self.0 == Value::NIL.0
1103    }
1104
1105    #[inline(always)]
1106    pub fn is_truthy(&self) -> bool {
1107        self.0 != Value::NIL.0 && self.0 != Value::FALSE.0
1108    }
1109
1110    #[inline(always)]
1111    pub fn is_falsy(&self) -> bool {
1112        !self.is_truthy()
1113    }
1114
1115    #[inline(always)]
1116    pub fn is_bool(&self) -> bool {
1117        self.0 == Value::TRUE.0 || self.0 == Value::FALSE.0
1118    }
1119
1120    #[inline(always)]
1121    pub fn is_int(&self) -> bool {
1122        is_boxed(self.0) && matches!(get_tag(self.0), TAG_INT_SMALL | TAG_INT_BIG)
1123    }
1124
1125    #[inline(always)]
1126    pub fn is_symbol(&self) -> bool {
1127        is_boxed(self.0) && get_tag(self.0) == TAG_SYMBOL
1128    }
1129
1130    #[inline(always)]
1131    pub fn is_keyword(&self) -> bool {
1132        is_boxed(self.0) && get_tag(self.0) == TAG_KEYWORD
1133    }
1134
1135    #[inline(always)]
1136    pub fn is_string(&self) -> bool {
1137        is_boxed(self.0) && get_tag(self.0) == TAG_STRING
1138    }
1139
1140    #[inline(always)]
1141    pub fn is_list(&self) -> bool {
1142        is_boxed(self.0) && get_tag(self.0) == TAG_LIST
1143    }
1144
1145    #[inline(always)]
1146    pub fn is_pair(&self) -> bool {
1147        if let Some(items) = self.as_list() {
1148            !items.is_empty()
1149        } else {
1150            false
1151        }
1152    }
1153
1154    #[inline(always)]
1155    pub fn is_vector(&self) -> bool {
1156        is_boxed(self.0) && get_tag(self.0) == TAG_VECTOR
1157    }
1158
1159    #[inline(always)]
1160    pub fn is_map(&self) -> bool {
1161        is_boxed(self.0) && matches!(get_tag(self.0), TAG_MAP | TAG_HASHMAP)
1162    }
1163
1164    #[inline(always)]
1165    pub fn is_lambda(&self) -> bool {
1166        is_boxed(self.0) && get_tag(self.0) == TAG_LAMBDA
1167    }
1168
1169    #[inline(always)]
1170    pub fn is_native_fn(&self) -> bool {
1171        is_boxed(self.0) && get_tag(self.0) == TAG_NATIVE_FN
1172    }
1173
1174    #[inline(always)]
1175    pub fn is_thunk(&self) -> bool {
1176        is_boxed(self.0) && get_tag(self.0) == TAG_THUNK
1177    }
1178
1179    #[inline(always)]
1180    pub fn is_async_promise(&self) -> bool {
1181        is_boxed(self.0) && get_tag(self.0) == TAG_ASYNC_PROMISE
1182    }
1183    #[inline(always)]
1184    pub fn is_channel(&self) -> bool {
1185        is_boxed(self.0) && get_tag(self.0) == TAG_CHANNEL
1186    }
1187
1188    #[inline(always)]
1189    pub fn is_record(&self) -> bool {
1190        is_boxed(self.0) && get_tag(self.0) == TAG_RECORD
1191    }
1192
1193    #[inline(always)]
1194    pub fn as_int(&self) -> Option<i64> {
1195        if !is_boxed(self.0) {
1196            return None;
1197        }
1198        match get_tag(self.0) {
1199            TAG_INT_SMALL => {
1200                let payload = get_payload(self.0);
1201                let val = if payload & INT_SIGN_BIT != 0 {
1202                    (payload | !PAYLOAD_MASK) as i64
1203                } else {
1204                    payload as i64
1205                };
1206                Some(val)
1207            }
1208            TAG_INT_BIG => Some(unsafe { *self.borrow_ref::<i64>() }),
1209            _ => None,
1210        }
1211    }
1212
1213    /// Convert a user-supplied integer to a `usize` index/count, rejecting
1214    /// non-integers and negative values. Centralizes the negativity guard that
1215    /// `list/take`, `list/drop`, `string/repeat` (and the Pattern-A audit sites)
1216    /// all need — a bare `as usize` would wrap a negative `i64` to a huge value
1217    /// and trigger an OOM allocation or out-of-bounds panic.
1218    pub fn as_index(&self, name: &str) -> Result<usize, SemaError> {
1219        let n = self.as_int().ok_or_else(|| {
1220            SemaError::type_error("int", self.type_name())
1221                .with_hint(format!("{name}: argument must be an integer"))
1222        })?;
1223        if n < 0 {
1224            return Err(SemaError::eval(format!(
1225                "{name}: expected a non-negative integer, got {n}"
1226            ))
1227            .with_hint("pass 0 or a positive integer"));
1228        }
1229        Ok(n as usize)
1230    }
1231
1232    #[inline(always)]
1233    pub fn as_float(&self) -> Option<f64> {
1234        if !is_boxed(self.0) {
1235            return Some(f64::from_bits(self.0));
1236        }
1237        match get_tag(self.0) {
1238            TAG_INT_SMALL => {
1239                let payload = get_payload(self.0);
1240                let val = if payload & INT_SIGN_BIT != 0 {
1241                    (payload | !PAYLOAD_MASK) as i64
1242                } else {
1243                    payload as i64
1244                };
1245                Some(val as f64)
1246            }
1247            TAG_INT_BIG => Some(unsafe { *self.borrow_ref::<i64>() } as f64),
1248            _ => None,
1249        }
1250    }
1251
1252    #[inline(always)]
1253    pub fn as_bool(&self) -> Option<bool> {
1254        if self.0 == Value::TRUE.0 {
1255            Some(true)
1256        } else if self.0 == Value::FALSE.0 {
1257            Some(false)
1258        } else {
1259            None
1260        }
1261    }
1262
1263    #[inline(always)]
1264    pub fn as_str(&self) -> Option<&str> {
1265        if is_boxed(self.0) && get_tag(self.0) == TAG_STRING {
1266            Some(unsafe { self.borrow_ref::<String>() })
1267        } else {
1268            None
1269        }
1270    }
1271
1272    pub fn as_string_rc(&self) -> Option<Rc<String>> {
1273        if is_boxed(self.0) && get_tag(self.0) == TAG_STRING {
1274            Some(unsafe { self.get_rc::<String>() })
1275        } else {
1276            None
1277        }
1278    }
1279
1280    pub fn as_symbol(&self) -> Option<String> {
1281        self.as_symbol_spur().map(resolve)
1282    }
1283
1284    pub fn as_symbol_spur(&self) -> Option<Spur> {
1285        if is_boxed(self.0) && get_tag(self.0) == TAG_SYMBOL {
1286            let payload = get_payload(self.0);
1287            Some(bits_to_spur(payload as u32))
1288        } else {
1289            None
1290        }
1291    }
1292
1293    pub fn as_keyword(&self) -> Option<String> {
1294        self.as_keyword_spur().map(resolve)
1295    }
1296
1297    pub fn as_keyword_spur(&self) -> Option<Spur> {
1298        if is_boxed(self.0) && get_tag(self.0) == TAG_KEYWORD {
1299            let payload = get_payload(self.0);
1300            Some(bits_to_spur(payload as u32))
1301        } else {
1302            None
1303        }
1304    }
1305
1306    pub fn as_char(&self) -> Option<char> {
1307        if is_boxed(self.0) && get_tag(self.0) == TAG_CHAR {
1308            let payload = get_payload(self.0);
1309            char::from_u32(payload as u32)
1310        } else {
1311            None
1312        }
1313    }
1314
1315    pub fn as_list(&self) -> Option<&[Value]> {
1316        if is_boxed(self.0) && get_tag(self.0) == TAG_LIST {
1317            Some(unsafe { self.borrow_ref::<Vec<Value>>() })
1318        } else {
1319            None
1320        }
1321    }
1322
1323    pub fn as_list_rc(&self) -> Option<Rc<Vec<Value>>> {
1324        if is_boxed(self.0) && get_tag(self.0) == TAG_LIST {
1325            Some(unsafe { self.get_rc::<Vec<Value>>() })
1326        } else {
1327            None
1328        }
1329    }
1330
1331    /// Returns the contents as a slice if this is a list OR a vector.
1332    pub fn as_seq(&self) -> Option<&[Value]> {
1333        self.as_list().or_else(|| self.as_vector())
1334    }
1335
1336    pub fn as_vector(&self) -> Option<&[Value]> {
1337        if is_boxed(self.0) && get_tag(self.0) == TAG_VECTOR {
1338            Some(unsafe { self.borrow_ref::<Vec<Value>>() })
1339        } else {
1340            None
1341        }
1342    }
1343
1344    pub fn as_vector_rc(&self) -> Option<Rc<Vec<Value>>> {
1345        if is_boxed(self.0) && get_tag(self.0) == TAG_VECTOR {
1346            Some(unsafe { self.get_rc::<Vec<Value>>() })
1347        } else {
1348            None
1349        }
1350    }
1351
1352    pub fn as_map_rc(&self) -> Option<Rc<BTreeMap<Value, Value>>> {
1353        if is_boxed(self.0) && get_tag(self.0) == TAG_MAP {
1354            Some(unsafe { self.get_rc::<BTreeMap<Value, Value>>() })
1355        } else {
1356            None
1357        }
1358    }
1359
1360    pub fn as_hashmap_rc(&self) -> Option<Rc<hashbrown::HashMap<Value, Value>>> {
1361        if is_boxed(self.0) && get_tag(self.0) == TAG_HASHMAP {
1362            Some(unsafe { self.get_rc::<hashbrown::HashMap<Value, Value>>() })
1363        } else {
1364            None
1365        }
1366    }
1367
1368    /// Borrow the underlying HashMap without bumping the Rc refcount.
1369    #[inline(always)]
1370    pub fn as_hashmap_ref(&self) -> Option<&hashbrown::HashMap<Value, Value>> {
1371        if is_boxed(self.0) && get_tag(self.0) == TAG_HASHMAP {
1372            Some(unsafe { self.borrow_ref::<hashbrown::HashMap<Value, Value>>() })
1373        } else {
1374            None
1375        }
1376    }
1377
1378    /// Borrow the underlying BTreeMap without bumping the Rc refcount.
1379    #[inline(always)]
1380    pub fn as_map_ref(&self) -> Option<&BTreeMap<Value, Value>> {
1381        if is_boxed(self.0) && get_tag(self.0) == TAG_MAP {
1382            Some(unsafe { self.borrow_ref::<BTreeMap<Value, Value>>() })
1383        } else {
1384            None
1385        }
1386    }
1387
1388    /// If this is a hashmap with refcount==1, mutate it in place.
1389    /// Returns `None` if not a hashmap or if shared (refcount > 1).
1390    /// SAFETY: relies on no other references to the inner data existing.
1391    #[inline(always)]
1392    pub fn with_hashmap_mut_if_unique<R>(
1393        &self,
1394        f: impl FnOnce(&mut hashbrown::HashMap<Value, Value>) -> R,
1395    ) -> Option<R> {
1396        if !is_boxed(self.0) || get_tag(self.0) != TAG_HASHMAP {
1397            return None;
1398        }
1399        let payload = get_payload(self.0);
1400        let ptr = payload_to_ptr(payload) as *const hashbrown::HashMap<Value, Value>;
1401        let rc = std::mem::ManuallyDrop::new(unsafe { Rc::from_raw(ptr) });
1402        if Rc::strong_count(&rc) != 1 {
1403            return None;
1404        }
1405        // strong_count==1: we are the sole owner, safe to mutate
1406        let ptr_mut = ptr as *mut hashbrown::HashMap<Value, Value>;
1407        Some(f(unsafe { &mut *ptr_mut }))
1408    }
1409
1410    /// If this is a map (BTreeMap) with refcount==1, mutate it in place.
1411    /// Returns `None` if not a map or if shared (refcount > 1).
1412    #[inline(always)]
1413    pub fn with_map_mut_if_unique<R>(
1414        &self,
1415        f: impl FnOnce(&mut BTreeMap<Value, Value>) -> R,
1416    ) -> Option<R> {
1417        if !is_boxed(self.0) || get_tag(self.0) != TAG_MAP {
1418            return None;
1419        }
1420        let payload = get_payload(self.0);
1421        let ptr = payload_to_ptr(payload) as *const BTreeMap<Value, Value>;
1422        let rc = std::mem::ManuallyDrop::new(unsafe { Rc::from_raw(ptr) });
1423        if Rc::strong_count(&rc) != 1 {
1424            return None;
1425        }
1426        let ptr_mut = ptr as *mut BTreeMap<Value, Value>;
1427        Some(f(unsafe { &mut *ptr_mut }))
1428    }
1429
1430    /// Consume this Value and extract the inner Rc without a refcount bump.
1431    /// Returns `Err(self)` if not a hashmap.
1432    pub fn into_hashmap_rc(self) -> Result<Rc<hashbrown::HashMap<Value, Value>>, Value> {
1433        if is_boxed(self.0) && get_tag(self.0) == TAG_HASHMAP {
1434            let payload = get_payload(self.0);
1435            let ptr = payload_to_ptr(payload) as *const hashbrown::HashMap<Value, Value>;
1436            // Prevent Drop from decrementing the refcount — we're taking ownership
1437            std::mem::forget(self);
1438            Ok(unsafe { Rc::from_raw(ptr) })
1439        } else {
1440            Err(self)
1441        }
1442    }
1443
1444    /// Consume this Value and extract the inner Rc without a refcount bump.
1445    /// Returns `Err(self)` if not a map.
1446    pub fn into_map_rc(self) -> Result<Rc<BTreeMap<Value, Value>>, Value> {
1447        if is_boxed(self.0) && get_tag(self.0) == TAG_MAP {
1448            let payload = get_payload(self.0);
1449            let ptr = payload_to_ptr(payload) as *const BTreeMap<Value, Value>;
1450            std::mem::forget(self);
1451            Ok(unsafe { Rc::from_raw(ptr) })
1452        } else {
1453            Err(self)
1454        }
1455    }
1456
1457    pub fn as_lambda_rc(&self) -> Option<Rc<Lambda>> {
1458        if is_boxed(self.0) && get_tag(self.0) == TAG_LAMBDA {
1459            Some(unsafe { self.get_rc::<Lambda>() })
1460        } else {
1461            None
1462        }
1463    }
1464
1465    pub fn as_macro_rc(&self) -> Option<Rc<Macro>> {
1466        if is_boxed(self.0) && get_tag(self.0) == TAG_MACRO {
1467            Some(unsafe { self.get_rc::<Macro>() })
1468        } else {
1469            None
1470        }
1471    }
1472
1473    pub fn as_native_fn_rc(&self) -> Option<Rc<NativeFn>> {
1474        if is_boxed(self.0) && get_tag(self.0) == TAG_NATIVE_FN {
1475            Some(unsafe { self.get_rc::<NativeFn>() })
1476        } else {
1477            None
1478        }
1479    }
1480
1481    pub fn as_thunk_rc(&self) -> Option<Rc<Thunk>> {
1482        if is_boxed(self.0) && get_tag(self.0) == TAG_THUNK {
1483            Some(unsafe { self.get_rc::<Thunk>() })
1484        } else {
1485            None
1486        }
1487    }
1488
1489    pub fn as_record(&self) -> Option<&Record> {
1490        if is_boxed(self.0) && get_tag(self.0) == TAG_RECORD {
1491            Some(unsafe { self.borrow_ref::<Record>() })
1492        } else {
1493            None
1494        }
1495    }
1496
1497    pub fn as_record_rc(&self) -> Option<Rc<Record>> {
1498        if is_boxed(self.0) && get_tag(self.0) == TAG_RECORD {
1499            Some(unsafe { self.get_rc::<Record>() })
1500        } else {
1501            None
1502        }
1503    }
1504
1505    pub fn as_bytevector(&self) -> Option<&[u8]> {
1506        if is_boxed(self.0) && get_tag(self.0) == TAG_BYTEVECTOR {
1507            Some(unsafe { self.borrow_ref::<Vec<u8>>() })
1508        } else {
1509            None
1510        }
1511    }
1512
1513    pub fn as_bytevector_rc(&self) -> Option<Rc<Vec<u8>>> {
1514        if is_boxed(self.0) && get_tag(self.0) == TAG_BYTEVECTOR {
1515            Some(unsafe { self.get_rc::<Vec<u8>>() })
1516        } else {
1517            None
1518        }
1519    }
1520
1521    pub fn as_f64_array(&self) -> Option<&[f64]> {
1522        if is_boxed(self.0) && get_tag(self.0) == TAG_F64_ARRAY {
1523            Some(unsafe { self.borrow_ref::<Vec<f64>>() })
1524        } else {
1525            None
1526        }
1527    }
1528
1529    pub fn as_f64_array_rc(&self) -> Option<Rc<Vec<f64>>> {
1530        if is_boxed(self.0) && get_tag(self.0) == TAG_F64_ARRAY {
1531            Some(unsafe { self.get_rc::<Vec<f64>>() })
1532        } else {
1533            None
1534        }
1535    }
1536
1537    pub fn as_i64_array(&self) -> Option<&[i64]> {
1538        if is_boxed(self.0) && get_tag(self.0) == TAG_I64_ARRAY {
1539            Some(unsafe { self.borrow_ref::<Vec<i64>>() })
1540        } else {
1541            None
1542        }
1543    }
1544
1545    pub fn as_i64_array_rc(&self) -> Option<Rc<Vec<i64>>> {
1546        if is_boxed(self.0) && get_tag(self.0) == TAG_I64_ARRAY {
1547            Some(unsafe { self.get_rc::<Vec<i64>>() })
1548        } else {
1549            None
1550        }
1551    }
1552
1553    pub fn as_stream(&self) -> Option<&StreamBox> {
1554        if is_boxed(self.0) && get_tag(self.0) == TAG_STREAM {
1555            Some(unsafe { self.borrow_ref::<StreamBox>() })
1556        } else {
1557            None
1558        }
1559    }
1560
1561    pub fn as_stream_rc(&self) -> Option<Rc<StreamBox>> {
1562        if is_boxed(self.0) && get_tag(self.0) == TAG_STREAM {
1563            Some(unsafe { self.get_rc::<StreamBox>() })
1564        } else {
1565            None
1566        }
1567    }
1568
1569    pub fn as_prompt_rc(&self) -> Option<Rc<Prompt>> {
1570        if is_boxed(self.0) && get_tag(self.0) == TAG_PROMPT {
1571            Some(unsafe { self.get_rc::<Prompt>() })
1572        } else {
1573            None
1574        }
1575    }
1576
1577    pub fn as_message_rc(&self) -> Option<Rc<Message>> {
1578        if is_boxed(self.0) && get_tag(self.0) == TAG_MESSAGE {
1579            Some(unsafe { self.get_rc::<Message>() })
1580        } else {
1581            None
1582        }
1583    }
1584
1585    pub fn as_conversation_rc(&self) -> Option<Rc<Conversation>> {
1586        if is_boxed(self.0) && get_tag(self.0) == TAG_CONVERSATION {
1587            Some(unsafe { self.get_rc::<Conversation>() })
1588        } else {
1589            None
1590        }
1591    }
1592
1593    pub fn as_tool_def_rc(&self) -> Option<Rc<ToolDefinition>> {
1594        if is_boxed(self.0) && get_tag(self.0) == TAG_TOOL_DEF {
1595            Some(unsafe { self.get_rc::<ToolDefinition>() })
1596        } else {
1597            None
1598        }
1599    }
1600
1601    pub fn as_agent_rc(&self) -> Option<Rc<Agent>> {
1602        if is_boxed(self.0) && get_tag(self.0) == TAG_AGENT {
1603            Some(unsafe { self.get_rc::<Agent>() })
1604        } else {
1605            None
1606        }
1607    }
1608
1609    pub fn as_multimethod_rc(&self) -> Option<Rc<MultiMethod>> {
1610        if is_boxed(self.0) && get_tag(self.0) == TAG_MULTIMETHOD {
1611            Some(unsafe { self.get_rc::<MultiMethod>() })
1612        } else {
1613            None
1614        }
1615    }
1616}
1617
1618// ── Clone ─────────────────────────────────────────────────────────
1619
1620impl Clone for Value {
1621    #[inline(always)]
1622    fn clone(&self) -> Self {
1623        if !is_boxed(self.0) {
1624            // Float: trivial copy
1625            return Value(self.0);
1626        }
1627        let tag = get_tag(self.0);
1628        match tag {
1629            // Immediates: trivial copy
1630            TAG_NIL | TAG_FALSE | TAG_TRUE | TAG_INT_SMALL | TAG_CHAR | TAG_SYMBOL
1631            | TAG_KEYWORD => Value(self.0),
1632            // Heap pointers: increment refcount
1633            _ => {
1634                let payload = get_payload(self.0);
1635                let ptr = payload_to_ptr(payload);
1636                // Increment refcount based on type
1637                unsafe {
1638                    match tag {
1639                        TAG_INT_BIG => Rc::increment_strong_count(ptr as *const i64),
1640                        TAG_STRING => Rc::increment_strong_count(ptr as *const String),
1641                        TAG_LIST | TAG_VECTOR => {
1642                            Rc::increment_strong_count(ptr as *const Vec<Value>)
1643                        }
1644                        TAG_MAP => Rc::increment_strong_count(ptr as *const BTreeMap<Value, Value>),
1645                        TAG_HASHMAP => Rc::increment_strong_count(
1646                            ptr as *const hashbrown::HashMap<Value, Value>,
1647                        ),
1648                        TAG_LAMBDA => Rc::increment_strong_count(ptr as *const Lambda),
1649                        TAG_MACRO => Rc::increment_strong_count(ptr as *const Macro),
1650                        TAG_NATIVE_FN => Rc::increment_strong_count(ptr as *const NativeFn),
1651                        TAG_PROMPT => Rc::increment_strong_count(ptr as *const Prompt),
1652                        TAG_MESSAGE => Rc::increment_strong_count(ptr as *const Message),
1653                        TAG_CONVERSATION => Rc::increment_strong_count(ptr as *const Conversation),
1654                        TAG_TOOL_DEF => Rc::increment_strong_count(ptr as *const ToolDefinition),
1655                        TAG_AGENT => Rc::increment_strong_count(ptr as *const Agent),
1656                        TAG_THUNK => Rc::increment_strong_count(ptr as *const Thunk),
1657                        TAG_RECORD => Rc::increment_strong_count(ptr as *const Record),
1658                        TAG_BYTEVECTOR => Rc::increment_strong_count(ptr as *const Vec<u8>),
1659                        TAG_MULTIMETHOD => Rc::increment_strong_count(ptr as *const MultiMethod),
1660                        TAG_STREAM => Rc::increment_strong_count(ptr as *const StreamBox),
1661                        TAG_F64_ARRAY => Rc::increment_strong_count(ptr as *const Vec<f64>),
1662                        TAG_I64_ARRAY => Rc::increment_strong_count(ptr as *const Vec<i64>),
1663                        TAG_ASYNC_PROMISE => Rc::increment_strong_count(ptr as *const AsyncPromise),
1664                        TAG_CHANNEL => Rc::increment_strong_count(ptr as *const Channel),
1665                        _ => unreachable!("invalid heap tag in clone: {}", tag),
1666                    }
1667                }
1668                Value(self.0)
1669            }
1670        }
1671    }
1672}
1673
1674// ── Drop ──────────────────────────────────────────────────────────
1675
1676impl Drop for Value {
1677    #[inline(always)]
1678    fn drop(&mut self) {
1679        if !is_boxed(self.0) {
1680            return; // Float
1681        }
1682        let tag = get_tag(self.0);
1683        match tag {
1684            // Immediates: nothing to free
1685            TAG_NIL | TAG_FALSE | TAG_TRUE | TAG_INT_SMALL | TAG_CHAR | TAG_SYMBOL
1686            | TAG_KEYWORD => {}
1687            // Heap pointers: drop the Rc
1688            _ => {
1689                let payload = get_payload(self.0);
1690                let ptr = payload_to_ptr(payload);
1691                unsafe {
1692                    match tag {
1693                        TAG_INT_BIG => drop(Rc::from_raw(ptr as *const i64)),
1694                        TAG_STRING => drop(Rc::from_raw(ptr as *const String)),
1695                        TAG_LIST | TAG_VECTOR => drop(Rc::from_raw(ptr as *const Vec<Value>)),
1696                        TAG_MAP => drop(Rc::from_raw(ptr as *const BTreeMap<Value, Value>)),
1697                        TAG_HASHMAP => {
1698                            drop(Rc::from_raw(ptr as *const hashbrown::HashMap<Value, Value>))
1699                        }
1700                        TAG_LAMBDA => drop(Rc::from_raw(ptr as *const Lambda)),
1701                        TAG_MACRO => drop(Rc::from_raw(ptr as *const Macro)),
1702                        TAG_NATIVE_FN => drop(Rc::from_raw(ptr as *const NativeFn)),
1703                        TAG_PROMPT => drop(Rc::from_raw(ptr as *const Prompt)),
1704                        TAG_MESSAGE => drop(Rc::from_raw(ptr as *const Message)),
1705                        TAG_CONVERSATION => drop(Rc::from_raw(ptr as *const Conversation)),
1706                        TAG_TOOL_DEF => drop(Rc::from_raw(ptr as *const ToolDefinition)),
1707                        TAG_AGENT => drop(Rc::from_raw(ptr as *const Agent)),
1708                        TAG_THUNK => drop(Rc::from_raw(ptr as *const Thunk)),
1709                        TAG_RECORD => drop(Rc::from_raw(ptr as *const Record)),
1710                        TAG_BYTEVECTOR => drop(Rc::from_raw(ptr as *const Vec<u8>)),
1711                        TAG_MULTIMETHOD => drop(Rc::from_raw(ptr as *const MultiMethod)),
1712                        TAG_STREAM => drop(Rc::from_raw(ptr as *const StreamBox)),
1713                        TAG_F64_ARRAY => drop(Rc::from_raw(ptr as *const Vec<f64>)),
1714                        TAG_I64_ARRAY => drop(Rc::from_raw(ptr as *const Vec<i64>)),
1715                        TAG_ASYNC_PROMISE => drop(Rc::from_raw(ptr as *const AsyncPromise)),
1716                        TAG_CHANNEL => drop(Rc::from_raw(ptr as *const Channel)),
1717                        _ => {} // unreachable, but don't panic in drop
1718                    }
1719                }
1720            }
1721        }
1722    }
1723}
1724
1725// ── PartialEq / Eq ────────────────────────────────────────────────
1726
1727impl PartialEq for Value {
1728    fn eq(&self, other: &Self) -> bool {
1729        // Fast path: identical bits
1730        if self.0 == other.0 {
1731            // For floats, NaN != NaN per IEEE, but our canonical NaN is unique,
1732            // so identical bits means equal for all types.
1733            // Exception: need to handle -0.0 == +0.0
1734            if !is_boxed(self.0) {
1735                let f = f64::from_bits(self.0);
1736                // NaN check: if both are canonical NaN (same bits), we say not equal
1737                if f.is_nan() {
1738                    return false;
1739                }
1740                return true;
1741            }
1742            return true;
1743        }
1744        // Different bits: could still be equal for heap types or -0.0/+0.0
1745        match (self.view(), other.view()) {
1746            (ValueView::Nil, ValueView::Nil) => true,
1747            (ValueView::Bool(a), ValueView::Bool(b)) => a == b,
1748            (ValueView::Int(a), ValueView::Int(b)) => a == b,
1749            (ValueView::Float(a), ValueView::Float(b)) => a == b,
1750            (ValueView::String(a), ValueView::String(b)) => a == b,
1751            (ValueView::Symbol(a), ValueView::Symbol(b)) => a == b,
1752            (ValueView::Keyword(a), ValueView::Keyword(b)) => a == b,
1753            (ValueView::Char(a), ValueView::Char(b)) => a == b,
1754            (ValueView::List(a), ValueView::List(b)) => a == b,
1755            (ValueView::Vector(a), ValueView::Vector(b)) => a == b,
1756            (ValueView::Map(a), ValueView::Map(b)) => a == b,
1757            (ValueView::HashMap(a), ValueView::HashMap(b)) => a == b,
1758            (ValueView::Record(a), ValueView::Record(b)) => {
1759                a.type_tag == b.type_tag && a.fields == b.fields
1760            }
1761            (ValueView::Bytevector(a), ValueView::Bytevector(b)) => a == b,
1762            (ValueView::F64Array(a), ValueView::F64Array(b)) => {
1763                a.len() == b.len()
1764                    && a.iter()
1765                        .zip(b.iter())
1766                        .all(|(x, y)| x.to_bits() == y.to_bits())
1767            }
1768            (ValueView::I64Array(a), ValueView::I64Array(b)) => a == b,
1769            (ValueView::Stream(a), ValueView::Stream(b)) => Rc::ptr_eq(&a, &b),
1770            (ValueView::AsyncPromise(a), ValueView::AsyncPromise(b)) => Rc::ptr_eq(&a, &b),
1771            (ValueView::Channel(a), ValueView::Channel(b)) => Rc::ptr_eq(&a, &b),
1772            _ => false,
1773        }
1774    }
1775}
1776
1777impl Eq for Value {}
1778
1779// ── Hash ──────────────────────────────────────────────────────────
1780
1781impl Hash for Value {
1782    fn hash<H: Hasher>(&self, state: &mut H) {
1783        match self.view() {
1784            ValueView::Nil => 0u8.hash(state),
1785            ValueView::Bool(b) => {
1786                1u8.hash(state);
1787                b.hash(state);
1788            }
1789            ValueView::Int(n) => {
1790                2u8.hash(state);
1791                n.hash(state);
1792            }
1793            ValueView::Float(f) => {
1794                3u8.hash(state);
1795                // Normalize -0.0 to +0.0 so equal values hash identically
1796                let bits = if f == 0.0 { 0u64 } else { f.to_bits() };
1797                bits.hash(state);
1798            }
1799            ValueView::String(s) => {
1800                4u8.hash(state);
1801                s.hash(state);
1802            }
1803            ValueView::Symbol(s) => {
1804                5u8.hash(state);
1805                s.hash(state);
1806            }
1807            ValueView::Keyword(s) => {
1808                6u8.hash(state);
1809                s.hash(state);
1810            }
1811            ValueView::Char(c) => {
1812                7u8.hash(state);
1813                c.hash(state);
1814            }
1815            ValueView::List(l) => {
1816                8u8.hash(state);
1817                l.hash(state);
1818            }
1819            ValueView::Vector(v) => {
1820                9u8.hash(state);
1821                v.hash(state);
1822            }
1823            ValueView::Record(r) => {
1824                10u8.hash(state);
1825                r.type_tag.hash(state);
1826                r.fields.hash(state);
1827            }
1828            ValueView::Bytevector(bv) => {
1829                11u8.hash(state);
1830                bv.hash(state);
1831            }
1832            ValueView::F64Array(arr) => {
1833                26u8.hash(state);
1834                for v in arr.iter() {
1835                    v.to_bits().hash(state);
1836                }
1837            }
1838            ValueView::I64Array(arr) => {
1839                27u8.hash(state);
1840                arr.hash(state);
1841            }
1842            ValueView::Stream(s) => {
1843                25u8.hash(state);
1844                (Rc::as_ptr(&s) as usize).hash(state);
1845            }
1846            ValueView::AsyncPromise(p) => {
1847                28u8.hash(state);
1848                (Rc::as_ptr(&p) as usize).hash(state);
1849            }
1850            ValueView::Channel(c) => {
1851                29u8.hash(state);
1852                (Rc::as_ptr(&c) as usize).hash(state);
1853            }
1854            _ => {}
1855        }
1856    }
1857}
1858
1859// ── Ord ───────────────────────────────────────────────────────────
1860
1861impl PartialOrd for Value {
1862    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1863        Some(self.cmp(other))
1864    }
1865}
1866
1867impl Ord for Value {
1868    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1869        use std::cmp::Ordering;
1870        fn type_order(v: &Value) -> u8 {
1871            match v.view() {
1872                ValueView::Nil => 0,
1873                ValueView::Bool(_) => 1,
1874                ValueView::Int(_) => 2,
1875                ValueView::Float(_) => 3,
1876                ValueView::Char(_) => 4,
1877                ValueView::String(_) => 5,
1878                ValueView::Symbol(_) => 6,
1879                ValueView::Keyword(_) => 7,
1880                ValueView::List(_) => 8,
1881                ValueView::Vector(_) => 9,
1882                ValueView::Map(_) => 10,
1883                ValueView::HashMap(_) => 11,
1884                ValueView::Record(_) => 12,
1885                ValueView::Bytevector(_) => 13,
1886                ValueView::F64Array(_) => 14,
1887                ValueView::I64Array(_) => 15,
1888                ValueView::Stream(_) => 16,
1889                _ => 17,
1890            }
1891        }
1892        match (self.view(), other.view()) {
1893            (ValueView::Nil, ValueView::Nil) => Ordering::Equal,
1894            (ValueView::Bool(a), ValueView::Bool(b)) => a.cmp(&b),
1895            (ValueView::Int(a), ValueView::Int(b)) => a.cmp(&b),
1896            (ValueView::Float(a), ValueView::Float(b)) => a.total_cmp(&b),
1897            (ValueView::String(a), ValueView::String(b)) => a.cmp(&b),
1898            (ValueView::Symbol(a), ValueView::Symbol(b)) => compare_spurs(a, b),
1899            (ValueView::Keyword(a), ValueView::Keyword(b)) => compare_spurs(a, b),
1900            (ValueView::Char(a), ValueView::Char(b)) => a.cmp(&b),
1901            (ValueView::List(a), ValueView::List(b)) => a.cmp(&b),
1902            (ValueView::Vector(a), ValueView::Vector(b)) => a.cmp(&b),
1903            (ValueView::Record(a), ValueView::Record(b)) => {
1904                compare_spurs(a.type_tag, b.type_tag).then_with(|| a.fields.cmp(&b.fields))
1905            }
1906            (ValueView::Bytevector(a), ValueView::Bytevector(b)) => a.cmp(&b),
1907            (ValueView::I64Array(a), ValueView::I64Array(b)) => a.cmp(&b),
1908            (ValueView::F64Array(a), ValueView::F64Array(b)) => a
1909                .iter()
1910                .zip(b.iter())
1911                .map(|(x, y)| x.total_cmp(y))
1912                .find(|o| *o != std::cmp::Ordering::Equal)
1913                .unwrap_or_else(|| a.len().cmp(&b.len())),
1914            _ => type_order(self).cmp(&type_order(other)),
1915        }
1916    }
1917}
1918
1919// ── Display ───────────────────────────────────────────────────────
1920
1921fn truncate(s: &str, max: usize) -> String {
1922    let mut iter = s.chars();
1923    let prefix: String = iter.by_ref().take(max).collect();
1924    if iter.next().is_none() {
1925        prefix
1926    } else {
1927        format!("{prefix}...")
1928    }
1929}
1930
1931impl fmt::Display for Value {
1932    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1933        match self.view() {
1934            ValueView::Nil => write!(f, "nil"),
1935            ValueView::Bool(true) => write!(f, "#t"),
1936            ValueView::Bool(false) => write!(f, "#f"),
1937            ValueView::Int(n) => write!(f, "{n}"),
1938            ValueView::Float(n) => {
1939                if n.fract() == 0.0 {
1940                    write!(f, "{n:.1}")
1941                } else {
1942                    write!(f, "{n}")
1943                }
1944            }
1945            ValueView::String(s) => {
1946                // Readable form: escape so the output re-reads to the same string
1947                // (round-trip). Bare strings printed via display/println/str go
1948                // through a separate raw path, so this only affects nested/readable
1949                // output. The reader parses these escapes back.
1950                write!(f, "\"")?;
1951                for c in s.chars() {
1952                    match c {
1953                        '"' => write!(f, "\\\"")?,
1954                        '\\' => write!(f, "\\\\")?,
1955                        '\n' => write!(f, "\\n")?,
1956                        '\t' => write!(f, "\\t")?,
1957                        '\r' => write!(f, "\\r")?,
1958                        c => write!(f, "{c}")?,
1959                    }
1960                }
1961                write!(f, "\"")
1962            }
1963            ValueView::Symbol(s) => with_resolved(s, |name| write!(f, "{name}")),
1964            ValueView::Keyword(s) => with_resolved(s, |name| write!(f, ":{name}")),
1965            ValueView::Char(c) => match c {
1966                ' ' => write!(f, "#\\space"),
1967                '\n' => write!(f, "#\\newline"),
1968                '\t' => write!(f, "#\\tab"),
1969                '\r' => write!(f, "#\\return"),
1970                '\0' => write!(f, "#\\nul"),
1971                _ => write!(f, "#\\{c}"),
1972            },
1973            ValueView::List(items) => {
1974                write!(f, "(")?;
1975                for (i, item) in items.iter().enumerate() {
1976                    if i > 0 {
1977                        write!(f, " ")?;
1978                    }
1979                    write!(f, "{item}")?;
1980                }
1981                write!(f, ")")
1982            }
1983            ValueView::Vector(items) => {
1984                write!(f, "[")?;
1985                for (i, item) in items.iter().enumerate() {
1986                    if i > 0 {
1987                        write!(f, " ")?;
1988                    }
1989                    write!(f, "{item}")?;
1990                }
1991                write!(f, "]")
1992            }
1993            ValueView::Map(map) => {
1994                write!(f, "{{")?;
1995                for (i, (k, v)) in map.iter().enumerate() {
1996                    if i > 0 {
1997                        write!(f, " ")?;
1998                    }
1999                    write!(f, "{k} {v}")?;
2000                }
2001                write!(f, "}}")
2002            }
2003            ValueView::HashMap(map) => {
2004                let mut entries: Vec<_> = map.iter().collect();
2005                entries.sort_by_key(|(k1, _)| *k1);
2006                write!(f, "{{")?;
2007                for (i, (k, v)) in entries.iter().enumerate() {
2008                    if i > 0 {
2009                        write!(f, " ")?;
2010                    }
2011                    write!(f, "{k} {v}")?;
2012                }
2013                write!(f, "}}")
2014            }
2015            ValueView::Lambda(l) => {
2016                if let Some(name) = &l.name {
2017                    with_resolved(*name, |n| write!(f, "<lambda {n}>"))
2018                } else {
2019                    write!(f, "<lambda>")
2020                }
2021            }
2022            ValueView::Macro(m) => with_resolved(m.name, |n| write!(f, "<macro {n}>")),
2023            ValueView::NativeFn(n) => write!(f, "<native-fn {}>", n.name),
2024            ValueView::Prompt(p) => write!(f, "<prompt {} messages>", p.messages.len()),
2025            ValueView::Message(m) => {
2026                write!(f, "<message {} \"{}\">", m.role, truncate(&m.content, 40))
2027            }
2028            ValueView::Conversation(c) => {
2029                write!(f, "<conversation {} messages>", c.messages.len())
2030            }
2031            ValueView::ToolDef(t) => write!(f, "<tool {}>", t.name),
2032            ValueView::Agent(a) => write!(f, "<agent {}>", a.name),
2033            ValueView::Thunk(t) => {
2034                if t.forced.borrow().is_some() {
2035                    write!(f, "<promise (forced)>")
2036                } else {
2037                    write!(f, "<promise>")
2038                }
2039            }
2040            ValueView::Record(r) => {
2041                with_resolved(r.type_tag, |tag| write!(f, "#<record {tag}"))?;
2042                for field in &r.fields {
2043                    write!(f, " {field}")?;
2044                }
2045                write!(f, ">")
2046            }
2047            ValueView::Bytevector(bv) => {
2048                write!(f, "#u8(")?;
2049                for (i, byte) in bv.iter().enumerate() {
2050                    if i > 0 {
2051                        write!(f, " ")?;
2052                    }
2053                    write!(f, "{byte}")?;
2054                }
2055                write!(f, ")")
2056            }
2057            ValueView::F64Array(arr) => {
2058                write!(f, "#f64(")?;
2059                for (i, v) in arr.iter().enumerate() {
2060                    if i > 0 {
2061                        write!(f, " ")?;
2062                    }
2063                    write!(f, "{v}")?;
2064                }
2065                write!(f, ")")
2066            }
2067            ValueView::I64Array(arr) => {
2068                write!(f, "#i64(")?;
2069                for (i, v) in arr.iter().enumerate() {
2070                    if i > 0 {
2071                        write!(f, " ")?;
2072                    }
2073                    write!(f, "{v}")?;
2074                }
2075                write!(f, ")")
2076            }
2077            ValueView::MultiMethod(m) => with_resolved(m.name, |n| write!(f, "<multimethod {n}>")),
2078            ValueView::Stream(s) => write!(f, "<stream:{}>", s.stream_type()),
2079            ValueView::AsyncPromise(p) => match &*p.state.borrow() {
2080                PromiseState::Pending => write!(f, "<async-promise pending>"),
2081                PromiseState::Resolved(v) => write!(f, "<async-promise resolved: {v}>"),
2082                PromiseState::Rejected(e) => write!(f, "<async-promise rejected: {e}>"),
2083                PromiseState::Cancelled => write!(f, "<async-promise cancelled>"),
2084            },
2085            ValueView::Channel(c) => {
2086                let len = c.buffer.borrow().len();
2087                if c.closed.get() {
2088                    write!(f, "<channel {len}/{} closed>", c.capacity)
2089                } else {
2090                    write!(f, "<channel {len}/{}>", c.capacity)
2091                }
2092            }
2093        }
2094    }
2095}
2096
2097// ── Pretty-print ──────────────────────────────────────────────────
2098
2099/// Pretty-print a value with line breaks and indentation when the compact
2100/// representation exceeds `max_width` columns.  Small values that fit in
2101/// one line are returned in the normal compact format.
2102pub fn pretty_print(value: &Value, max_width: usize) -> String {
2103    let compact = format!("{value}");
2104    if compact.len() <= max_width {
2105        return compact;
2106    }
2107    let mut buf = String::new();
2108    pp_value(value, 0, max_width, &mut buf);
2109    buf
2110}
2111
2112/// Render `value` into `buf` at the given `indent` level.  If the compact
2113/// form fits in `max_width - indent` columns we use it; otherwise we break
2114/// the container across multiple lines.
2115fn pp_value(value: &Value, indent: usize, max_width: usize, buf: &mut String) {
2116    let compact = format!("{value}");
2117    let remaining = max_width.saturating_sub(indent);
2118    if compact.len() <= remaining {
2119        buf.push_str(&compact);
2120        return;
2121    }
2122
2123    match value.view() {
2124        ValueView::List(items) => {
2125            pp_seq(items.iter(), '(', ')', indent, max_width, buf);
2126        }
2127        ValueView::Vector(items) => {
2128            pp_seq(items.iter(), '[', ']', indent, max_width, buf);
2129        }
2130        ValueView::Map(map) => {
2131            pp_map(
2132                map.iter().map(|(k, v)| (k.clone(), v.clone())),
2133                indent,
2134                max_width,
2135                buf,
2136            );
2137        }
2138        ValueView::HashMap(map) => {
2139            let mut entries: Vec<_> = map.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
2140            entries.sort_by(|(k1, _), (k2, _)| k1.cmp(k2));
2141            pp_map(entries.into_iter(), indent, max_width, buf);
2142        }
2143        _ => buf.push_str(&compact),
2144    }
2145}
2146
2147/// Pretty-print a list or vector.
2148fn pp_seq<'a>(
2149    items: impl Iterator<Item = &'a Value>,
2150    open: char,
2151    close: char,
2152    indent: usize,
2153    max_width: usize,
2154    buf: &mut String,
2155) {
2156    buf.push(open);
2157    let child_indent = indent + 1;
2158    let pad = " ".repeat(child_indent);
2159    for (i, item) in items.enumerate() {
2160        if i > 0 {
2161            buf.push('\n');
2162            buf.push_str(&pad);
2163        }
2164        pp_value(item, child_indent, max_width, buf);
2165    }
2166    buf.push(close);
2167}
2168
2169/// Pretty-print a map (BTreeMap or HashMap).
2170fn pp_map(
2171    entries: impl Iterator<Item = (Value, Value)>,
2172    indent: usize,
2173    max_width: usize,
2174    buf: &mut String,
2175) {
2176    buf.push('{');
2177    let child_indent = indent + 1;
2178    let pad = " ".repeat(child_indent);
2179    for (i, (k, v)) in entries.enumerate() {
2180        if i > 0 {
2181            buf.push('\n');
2182            buf.push_str(&pad);
2183        }
2184        // Key is always compact
2185        let key_str = format!("{k}");
2186        buf.push_str(&key_str);
2187
2188        // Check if the value fits inline after the key
2189        let inline_indent = child_indent + key_str.len() + 1;
2190        let compact_val = format!("{v}");
2191        let remaining = max_width.saturating_sub(inline_indent);
2192
2193        if compact_val.len() <= remaining {
2194            // Fits inline
2195            buf.push(' ');
2196            buf.push_str(&compact_val);
2197        } else if is_compound(&v) {
2198            // Complex value: break to next line indented 2 from key
2199            let nested_indent = child_indent + 2;
2200            let nested_pad = " ".repeat(nested_indent);
2201            buf.push('\n');
2202            buf.push_str(&nested_pad);
2203            pp_value(&v, nested_indent, max_width, buf);
2204        } else {
2205            // Simple value that's just long: keep inline
2206            buf.push(' ');
2207            buf.push_str(&compact_val);
2208        }
2209    }
2210    buf.push('}');
2211}
2212
2213/// Check whether a value is a compound container (list, vector, map, hashmap).
2214fn is_compound(value: &Value) -> bool {
2215    matches!(
2216        value.view(),
2217        ValueView::List(_) | ValueView::Vector(_) | ValueView::Map(_) | ValueView::HashMap(_)
2218    )
2219}
2220
2221// ── Debug ─────────────────────────────────────────────────────────
2222
2223impl fmt::Debug for Value {
2224    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2225        match self.view() {
2226            ValueView::Nil => write!(f, "Nil"),
2227            ValueView::Bool(b) => write!(f, "Bool({b})"),
2228            ValueView::Int(n) => write!(f, "Int({n})"),
2229            ValueView::Float(n) => write!(f, "Float({n})"),
2230            ValueView::String(s) => write!(f, "String({:?})", &**s),
2231            ValueView::Symbol(s) => write!(f, "Symbol({})", resolve(s)),
2232            ValueView::Keyword(s) => write!(f, "Keyword({})", resolve(s)),
2233            ValueView::Char(c) => write!(f, "Char({c:?})"),
2234            ValueView::List(items) => write!(f, "List({items:?})"),
2235            ValueView::Vector(items) => write!(f, "Vector({items:?})"),
2236            ValueView::Map(map) => write!(f, "Map({map:?})"),
2237            ValueView::HashMap(map) => write!(f, "HashMap({map:?})"),
2238            ValueView::Lambda(l) => write!(f, "{l:?}"),
2239            ValueView::Macro(m) => write!(f, "{m:?}"),
2240            ValueView::NativeFn(n) => write!(f, "{n:?}"),
2241            ValueView::Prompt(p) => write!(f, "{p:?}"),
2242            ValueView::Message(m) => write!(f, "{m:?}"),
2243            ValueView::Conversation(c) => write!(f, "{c:?}"),
2244            ValueView::ToolDef(t) => write!(f, "{t:?}"),
2245            ValueView::Agent(a) => write!(f, "{a:?}"),
2246            ValueView::Thunk(t) => write!(f, "{t:?}"),
2247            ValueView::Record(r) => write!(f, "{r:?}"),
2248            ValueView::Bytevector(bv) => write!(f, "Bytevector({bv:?})"),
2249            ValueView::F64Array(arr) => write!(f, "F64Array({arr:?})"),
2250            ValueView::I64Array(arr) => write!(f, "I64Array({arr:?})"),
2251            ValueView::MultiMethod(m) => write!(f, "{m:?}"),
2252            ValueView::Stream(s) => write!(f, "Stream({:?})", s.stream_type()),
2253            ValueView::AsyncPromise(p) => write!(f, "{p:?}"),
2254            ValueView::Channel(c) => write!(f, "{c:?}"),
2255        }
2256    }
2257}
2258
2259// ── Env ───────────────────────────────────────────────────────────
2260
2261/// A Sema environment: a chain of scopes with bindings.
2262#[derive(Debug, Clone)]
2263pub struct Env {
2264    pub bindings: Rc<RefCell<SpurMap<Spur, Value>>>,
2265    pub parent: Option<Rc<Env>>,
2266    pub version: Cell<u64>,
2267}
2268
2269impl Env {
2270    pub fn new() -> Self {
2271        Env {
2272            bindings: Rc::new(RefCell::new(SpurMap::new())),
2273            parent: None,
2274            version: Cell::new(0),
2275        }
2276    }
2277
2278    pub fn with_parent(parent: Rc<Env>) -> Self {
2279        Env {
2280            bindings: Rc::new(RefCell::new(SpurMap::new())),
2281            parent: Some(parent),
2282            version: Cell::new(0),
2283        }
2284    }
2285
2286    /// Bump the environment's version counter. The VM's inline global cache is
2287    /// keyed on this version, so call this after mutating `bindings` through a
2288    /// different `Env` handle that shares the same `bindings` Rc but has its own
2289    /// version cell (e.g. a `load`ed module body run on a cloned-Env VM), so a
2290    /// VM observing this `Env` re-reads instead of serving a stale cached value.
2291    pub fn bump_version(&self) {
2292        self.version.set(self.version.get().wrapping_add(1));
2293    }
2294
2295    pub fn get(&self, name: Spur) -> Option<Value> {
2296        if let Some(val) = self.bindings.borrow().get(&name) {
2297            Some(val.clone())
2298        } else if let Some(parent) = &self.parent {
2299            parent.get(name)
2300        } else {
2301            None
2302        }
2303    }
2304
2305    pub fn get_str(&self, name: &str) -> Option<Value> {
2306        self.get(intern(name))
2307    }
2308
2309    pub fn set(&self, name: Spur, val: Value) {
2310        self.bindings.borrow_mut().insert(name, val);
2311        self.bump_version();
2312    }
2313
2314    pub fn set_str(&self, name: &str, val: Value) {
2315        self.set(intern(name), val);
2316    }
2317
2318    /// Update a binding that already exists in the current scope.
2319    pub fn update(&self, name: Spur, val: Value) {
2320        let mut bindings = self.bindings.borrow_mut();
2321        if let Some(entry) = bindings.get_mut(&name) {
2322            *entry = val;
2323        } else {
2324            bindings.insert(name, val);
2325        }
2326        drop(bindings);
2327        self.bump_version();
2328    }
2329
2330    /// Remove and return a binding from the current scope only.
2331    pub fn take(&self, name: Spur) -> Option<Value> {
2332        let result = self.bindings.borrow_mut().remove(&name);
2333        if result.is_some() {
2334            self.bump_version();
2335        }
2336        result
2337    }
2338
2339    /// Remove and return a binding from any scope in the parent chain.
2340    pub fn take_anywhere(&self, name: Spur) -> Option<Value> {
2341        if let Some(val) = self.bindings.borrow_mut().remove(&name) {
2342            self.bump_version();
2343            Some(val)
2344        } else if let Some(parent) = &self.parent {
2345            parent.take_anywhere(name)
2346        } else {
2347            None
2348        }
2349    }
2350
2351    /// Set a variable in the scope where it's defined (for set!).
2352    pub fn set_existing(&self, name: Spur, val: Value) -> bool {
2353        let mut bindings = self.bindings.borrow_mut();
2354        if let Some(entry) = bindings.get_mut(&name) {
2355            *entry = val;
2356            drop(bindings);
2357            self.bump_version();
2358            true
2359        } else {
2360            drop(bindings);
2361            if let Some(parent) = &self.parent {
2362                parent.set_existing(name, val)
2363            } else {
2364                false
2365            }
2366        }
2367    }
2368
2369    /// Collect all bound variable names across all scopes (for suggestions).
2370    pub fn all_names(&self) -> Vec<Spur> {
2371        let mut names: Vec<Spur> = self.bindings.borrow().keys().copied().collect();
2372        if let Some(parent) = &self.parent {
2373            names.extend(parent.all_names());
2374        }
2375        names.sort_unstable();
2376        names.dedup();
2377        names
2378    }
2379
2380    /// Iterate over bindings in the current scope only (not parent scopes).
2381    pub fn iter_bindings(&self, mut f: impl FnMut(Spur, &Value)) {
2382        let bindings = self.bindings.borrow();
2383        for (&spur, value) in bindings.iter() {
2384            f(spur, value);
2385        }
2386    }
2387
2388    /// Get a binding from the current scope only (not parent scopes).
2389    pub fn get_local(&self, name: Spur) -> Option<Value> {
2390        self.bindings.borrow().get(&name).cloned()
2391    }
2392
2393    /// Replace all bindings in the current scope with the given iterator.
2394    /// Used for bulk restore (e.g., undo/rollback).
2395    pub fn replace_bindings(&self, new_bindings: impl IntoIterator<Item = (Spur, Value)>) {
2396        let mut bindings = self.bindings.borrow_mut();
2397        bindings.clear();
2398        for (spur, value) in new_bindings {
2399            bindings.insert(spur, value);
2400        }
2401        drop(bindings);
2402        self.bump_version();
2403    }
2404}
2405
2406impl Default for Env {
2407    fn default() -> Self {
2408        Self::new()
2409    }
2410}
2411
2412// ── Tests ─────────────────────────────────────────────────────────
2413
2414#[cfg(test)]
2415#[allow(clippy::approx_constant)]
2416mod tests {
2417    use super::*;
2418
2419    #[test]
2420    fn test_size_of_value() {
2421        assert_eq!(std::mem::size_of::<Value>(), 8);
2422    }
2423
2424    #[test]
2425    fn test_spur_bits_round_trip() {
2426        // spur_to_bits / bits_to_spur must be exact inverses for freshly interned
2427        // keys, and a NaN-boxed symbol/keyword must round-trip to the same Spur.
2428        for s in ["x", "map", "string->symbol", "a-very-long-symbol-name", "λ"] {
2429            let spur = intern(s);
2430            assert_eq!(
2431                bits_to_spur(spur_to_bits(spur)),
2432                spur,
2433                "raw round-trip for {s:?}"
2434            );
2435
2436            let sym = Value::symbol_from_spur(spur);
2437            assert_eq!(sym.as_symbol_spur(), Some(spur), "symbol Value for {s:?}");
2438            assert_eq!(resolve(spur), s);
2439
2440            let kw = Value::keyword_from_spur(spur);
2441            assert_eq!(kw.as_keyword_spur(), Some(spur), "keyword Value for {s:?}");
2442        }
2443    }
2444
2445    #[test]
2446    fn as_index_rejects_negative() {
2447        let e = Value::int(-1).as_index("test").unwrap_err();
2448        assert!(
2449            matches!(e.inner(), SemaError::Eval(_)),
2450            "expected Eval error, got {e:?}"
2451        );
2452        assert!(e.to_string().contains("test"));
2453    }
2454
2455    #[test]
2456    fn as_index_accepts_non_negative() {
2457        assert_eq!(Value::int(0).as_index("test").unwrap(), 0);
2458        assert_eq!(Value::int(5).as_index("test").unwrap(), 5);
2459    }
2460
2461    #[test]
2462    fn as_index_rejects_non_int() {
2463        assert!(Value::string("x").as_index("test").is_err());
2464    }
2465
2466    #[test]
2467    fn test_nil() {
2468        let v = Value::nil();
2469        assert!(v.is_nil());
2470        assert!(!v.is_truthy());
2471        assert_eq!(v.type_name(), "nil");
2472        assert_eq!(format!("{v}"), "nil");
2473    }
2474
2475    #[test]
2476    fn test_bool() {
2477        let t = Value::bool(true);
2478        let f = Value::bool(false);
2479        assert!(t.is_truthy());
2480        assert!(!f.is_truthy());
2481        assert_eq!(t.as_bool(), Some(true));
2482        assert_eq!(f.as_bool(), Some(false));
2483        assert_eq!(format!("{t}"), "#t");
2484        assert_eq!(format!("{f}"), "#f");
2485    }
2486
2487    #[test]
2488    fn test_small_int() {
2489        let v = Value::int(42);
2490        assert_eq!(v.as_int(), Some(42));
2491        assert_eq!(v.type_name(), "int");
2492        assert_eq!(format!("{v}"), "42");
2493
2494        let neg = Value::int(-100);
2495        assert_eq!(neg.as_int(), Some(-100));
2496        assert_eq!(format!("{neg}"), "-100");
2497
2498        let zero = Value::int(0);
2499        assert_eq!(zero.as_int(), Some(0));
2500    }
2501
2502    #[test]
2503    fn test_small_int_boundaries() {
2504        let max = Value::int(SMALL_INT_MAX);
2505        assert_eq!(max.as_int(), Some(SMALL_INT_MAX));
2506
2507        let min = Value::int(SMALL_INT_MIN);
2508        assert_eq!(min.as_int(), Some(SMALL_INT_MIN));
2509    }
2510
2511    #[test]
2512    fn test_big_int() {
2513        let big = Value::int(i64::MAX);
2514        assert_eq!(big.as_int(), Some(i64::MAX));
2515        assert_eq!(big.type_name(), "int");
2516
2517        let big_neg = Value::int(i64::MIN);
2518        assert_eq!(big_neg.as_int(), Some(i64::MIN));
2519
2520        // Just outside small range
2521        let just_over = Value::int(SMALL_INT_MAX + 1);
2522        assert_eq!(just_over.as_int(), Some(SMALL_INT_MAX + 1));
2523    }
2524
2525    #[test]
2526    fn test_float() {
2527        let v = Value::float(3.14);
2528        assert_eq!(v.as_float(), Some(3.14));
2529        assert_eq!(v.type_name(), "float");
2530
2531        let neg = Value::float(-0.5);
2532        assert_eq!(neg.as_float(), Some(-0.5));
2533
2534        let inf = Value::float(f64::INFINITY);
2535        assert_eq!(inf.as_float(), Some(f64::INFINITY));
2536
2537        let neg_inf = Value::float(f64::NEG_INFINITY);
2538        assert_eq!(neg_inf.as_float(), Some(f64::NEG_INFINITY));
2539    }
2540
2541    #[test]
2542    fn test_float_nan() {
2543        let nan = Value::float(f64::NAN);
2544        let f = nan.as_float().unwrap();
2545        assert!(f.is_nan());
2546    }
2547
2548    #[test]
2549    fn test_string() {
2550        let v = Value::string("hello");
2551        assert_eq!(v.as_str(), Some("hello"));
2552        assert_eq!(v.type_name(), "string");
2553        assert_eq!(format!("{v}"), "\"hello\"");
2554    }
2555
2556    #[test]
2557    fn test_symbol() {
2558        let v = Value::symbol("foo");
2559        assert!(v.as_symbol_spur().is_some());
2560        assert_eq!(v.as_symbol(), Some("foo".to_string()));
2561        assert_eq!(v.type_name(), "symbol");
2562        assert_eq!(format!("{v}"), "foo");
2563    }
2564
2565    #[test]
2566    fn test_keyword() {
2567        let v = Value::keyword("bar");
2568        assert!(v.as_keyword_spur().is_some());
2569        assert_eq!(v.as_keyword(), Some("bar".to_string()));
2570        assert_eq!(v.type_name(), "keyword");
2571        assert_eq!(format!("{v}"), ":bar");
2572    }
2573
2574    #[test]
2575    fn test_char() {
2576        let v = Value::char('λ');
2577        assert_eq!(v.as_char(), Some('λ'));
2578        assert_eq!(v.type_name(), "char");
2579    }
2580
2581    #[test]
2582    fn test_list() {
2583        let v = Value::list(vec![Value::int(1), Value::int(2), Value::int(3)]);
2584        assert_eq!(v.as_list().unwrap().len(), 3);
2585        assert_eq!(v.type_name(), "list");
2586        assert_eq!(format!("{v}"), "(1 2 3)");
2587    }
2588
2589    #[test]
2590    fn test_clone_immediate() {
2591        let v = Value::int(42);
2592        let v2 = v.clone();
2593        assert_eq!(v.as_int(), v2.as_int());
2594    }
2595
2596    #[test]
2597    fn test_clone_heap() {
2598        let v = Value::string("hello");
2599        let v2 = v.clone();
2600        assert_eq!(v.as_str(), v2.as_str());
2601        // Both should work after clone
2602        assert_eq!(format!("{v}"), format!("{v2}"));
2603    }
2604
2605    #[test]
2606    fn test_equality() {
2607        assert_eq!(Value::int(42), Value::int(42));
2608        assert_ne!(Value::int(42), Value::int(43));
2609        assert_eq!(Value::nil(), Value::nil());
2610        assert_eq!(Value::bool(true), Value::bool(true));
2611        assert_ne!(Value::bool(true), Value::bool(false));
2612        assert_eq!(Value::string("a"), Value::string("a"));
2613        assert_ne!(Value::string("a"), Value::string("b"));
2614        assert_eq!(Value::symbol("x"), Value::symbol("x"));
2615    }
2616
2617    #[test]
2618    fn record_field_names_do_not_affect_language_semantics() {
2619        use std::collections::hash_map::DefaultHasher;
2620        use std::hash::{Hash, Hasher};
2621
2622        let a = Value::record(Record {
2623            type_tag: intern("point"),
2624            field_names: vec![intern("x"), intern("y")],
2625            fields: vec![Value::int(1), Value::int(2)],
2626        });
2627        let b = Value::record(Record {
2628            type_tag: intern("point"),
2629            field_names: vec![intern("left"), intern("top")],
2630            fields: vec![Value::int(1), Value::int(2)],
2631        });
2632
2633        assert_eq!(a, b);
2634        assert_eq!(a.cmp(&b), std::cmp::Ordering::Equal);
2635        assert_eq!(format!("{a}"), "#<record point 1 2>");
2636        assert_eq!(format!("{a}"), format!("{b}"));
2637
2638        let mut a_hasher = DefaultHasher::new();
2639        a.hash(&mut a_hasher);
2640        let mut b_hasher = DefaultHasher::new();
2641        b.hash(&mut b_hasher);
2642        assert_eq!(a_hasher.finish(), b_hasher.finish());
2643    }
2644
2645    #[test]
2646    fn test_big_int_equality() {
2647        assert_eq!(Value::int(i64::MAX), Value::int(i64::MAX));
2648        assert_ne!(Value::int(i64::MAX), Value::int(i64::MIN));
2649    }
2650
2651    #[test]
2652    fn test_view_pattern_matching() {
2653        let v = Value::int(42);
2654        match v.view() {
2655            ValueView::Int(n) => assert_eq!(n, 42),
2656            _ => panic!("expected int"),
2657        }
2658
2659        let v = Value::string("hello");
2660        match v.view() {
2661            ValueView::String(s) => assert_eq!(&**s, "hello"),
2662            _ => panic!("expected string"),
2663        }
2664    }
2665
2666    #[test]
2667    fn test_env() {
2668        let env = Env::new();
2669        env.set_str("x", Value::int(42));
2670        assert_eq!(env.get_str("x"), Some(Value::int(42)));
2671    }
2672
2673    #[test]
2674    fn test_native_fn_simple() {
2675        let f = NativeFn::simple("add1", |args| Ok(args[0].clone()));
2676        let ctx = EvalContext::new();
2677        assert!((f.func)(&ctx, &[Value::int(42)]).is_ok());
2678    }
2679
2680    #[test]
2681    fn test_native_fn_with_ctx() {
2682        let f = NativeFn::with_ctx("get-depth", |ctx, _args| {
2683            Ok(Value::int(ctx.eval_depth.get() as i64))
2684        });
2685        let ctx = EvalContext::new();
2686        assert_eq!((f.func)(&ctx, &[]).unwrap(), Value::int(0));
2687    }
2688
2689    #[test]
2690    fn test_drop_doesnt_leak() {
2691        // Create and drop many heap values to check for leaks
2692        for _ in 0..10000 {
2693            let _ = Value::string("test");
2694            let _ = Value::list(vec![Value::int(1), Value::int(2)]);
2695            let _ = Value::int(i64::MAX); // big int
2696        }
2697    }
2698
2699    #[test]
2700    fn test_is_truthy() {
2701        assert!(!Value::nil().is_truthy());
2702        assert!(!Value::bool(false).is_truthy());
2703        assert!(Value::bool(true).is_truthy());
2704        assert!(Value::int(0).is_truthy());
2705        assert!(Value::int(1).is_truthy());
2706        assert!(Value::string("").is_truthy());
2707        assert!(Value::list(vec![]).is_truthy());
2708    }
2709
2710    #[test]
2711    fn test_as_float_from_int() {
2712        assert_eq!(Value::int(42).as_float(), Some(42.0));
2713        assert_eq!(Value::float(3.14).as_float(), Some(3.14));
2714    }
2715
2716    #[test]
2717    fn test_next_gensym_unique() {
2718        let a = next_gensym("x");
2719        let b = next_gensym("x");
2720        let c = next_gensym("y");
2721        assert_ne!(a, b);
2722        assert_ne!(a, c);
2723        assert_ne!(b, c);
2724        assert!(a.starts_with("x__"));
2725        assert!(b.starts_with("x__"));
2726        assert!(c.starts_with("y__"));
2727    }
2728
2729    #[test]
2730    fn test_next_gensym_counter_does_not_panic_near_max() {
2731        // Set counter near u64::MAX and verify no panic on wrapping
2732        GENSYM_COUNTER.with(|c| c.set(u64::MAX - 1));
2733        let a = next_gensym("z");
2734        assert!(a.contains(&(u64::MAX - 1).to_string()));
2735        // This would panic with `val + 1` instead of wrapping_add
2736        let b = next_gensym("z");
2737        assert!(b.contains(&u64::MAX.to_string()));
2738        // Wraps to 0
2739        let c = next_gensym("z");
2740        assert!(c.contains("__0"));
2741    }
2742
2743    // ── StreamBox tests ──────────────────────────────────────────────
2744
2745    #[derive(Debug)]
2746    struct TestStream {
2747        data: RefCell<Vec<u8>>,
2748        readable: bool,
2749        writable: bool,
2750    }
2751
2752    impl TestStream {
2753        fn new(readable: bool, writable: bool) -> Self {
2754            TestStream {
2755                data: RefCell::new(Vec::new()),
2756                readable,
2757                writable,
2758            }
2759        }
2760    }
2761
2762    impl SemaStream for TestStream {
2763        fn read(&self, buf: &mut [u8]) -> Result<usize, SemaError> {
2764            let mut data = self.data.borrow_mut();
2765            let n = buf.len().min(data.len());
2766            buf[..n].copy_from_slice(&data[..n]);
2767            data.drain(..n);
2768            Ok(n)
2769        }
2770
2771        fn write(&self, data: &[u8]) -> Result<usize, SemaError> {
2772            self.data.borrow_mut().extend_from_slice(data);
2773            Ok(data.len())
2774        }
2775
2776        fn flush(&self) -> Result<(), SemaError> {
2777            Ok(())
2778        }
2779
2780        fn close(&self) -> Result<(), SemaError> {
2781            Ok(())
2782        }
2783
2784        fn available(&self) -> Result<bool, SemaError> {
2785            Ok(!self.data.borrow().is_empty())
2786        }
2787
2788        fn is_readable(&self) -> bool {
2789            self.readable
2790        }
2791
2792        fn is_writable(&self) -> bool {
2793            self.writable
2794        }
2795
2796        fn stream_type(&self) -> &'static str {
2797            "test"
2798        }
2799
2800        fn as_any(&self) -> &dyn std::any::Any {
2801            self
2802        }
2803    }
2804
2805    #[test]
2806    fn streambox_read_writes_data() {
2807        let sb = StreamBox::new(TestStream::new(true, true));
2808        sb.write(b"hello").unwrap();
2809        let mut buf = [0u8; 5];
2810        let n = sb.read(&mut buf).unwrap();
2811        assert_eq!(n, 5);
2812        assert_eq!(&buf, b"hello");
2813    }
2814
2815    #[test]
2816    fn streambox_close_prevents_read() {
2817        let sb = StreamBox::new(TestStream::new(true, true));
2818        sb.close().unwrap();
2819        let mut buf = [0u8; 5];
2820        let err = sb.read(&mut buf).unwrap_err();
2821        assert!(err.to_string().contains("closed"));
2822    }
2823
2824    #[test]
2825    fn streambox_close_prevents_write() {
2826        let sb = StreamBox::new(TestStream::new(true, true));
2827        sb.close().unwrap();
2828        let err = sb.write(b"data").unwrap_err();
2829        assert!(err.to_string().contains("closed"));
2830    }
2831
2832    #[test]
2833    fn streambox_close_prevents_flush() {
2834        let sb = StreamBox::new(TestStream::new(true, true));
2835        sb.close().unwrap();
2836        let err = sb.flush().unwrap_err();
2837        assert!(err.to_string().contains("closed"));
2838    }
2839
2840    #[test]
2841    fn streambox_double_close_is_noop() {
2842        let sb = StreamBox::new(TestStream::new(true, true));
2843        sb.close().unwrap();
2844        sb.close().unwrap(); // second close should be Ok
2845    }
2846
2847    #[test]
2848    fn streambox_is_closed() {
2849        let sb = StreamBox::new(TestStream::new(true, true));
2850        assert!(!sb.is_closed());
2851        sb.close().unwrap();
2852        assert!(sb.is_closed());
2853    }
2854
2855    #[test]
2856    fn streambox_is_readable() {
2857        let sb = StreamBox::new(TestStream::new(true, false));
2858        assert!(sb.is_readable());
2859        sb.close().unwrap();
2860        assert!(!sb.is_readable());
2861    }
2862
2863    #[test]
2864    fn streambox_is_writable() {
2865        let sb = StreamBox::new(TestStream::new(false, true));
2866        assert!(sb.is_writable());
2867        sb.close().unwrap();
2868        assert!(!sb.is_writable());
2869    }
2870
2871    #[test]
2872    fn streambox_available_when_closed() {
2873        let sb = StreamBox::new(TestStream::new(true, true));
2874        sb.close().unwrap();
2875        assert!(!sb.available().unwrap());
2876    }
2877
2878    #[test]
2879    fn streambox_stream_type() {
2880        let sb = StreamBox::new(TestStream::new(true, true));
2881        assert_eq!(sb.stream_type(), "test");
2882    }
2883}