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