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/// A record: tagged product type created by define-record-type.
183#[derive(Debug, Clone)]
184pub struct Record {
185    pub type_tag: Spur,
186    pub fields: Vec<Value>,
187}
188
189/// A message role in a conversation.
190#[derive(Debug, Clone, PartialEq, Eq)]
191pub enum Role {
192    System,
193    User,
194    Assistant,
195    Tool,
196}
197
198impl fmt::Display for Role {
199    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
200        match self {
201            Role::System => write!(f, "system"),
202            Role::User => write!(f, "user"),
203            Role::Assistant => write!(f, "assistant"),
204            Role::Tool => write!(f, "tool"),
205        }
206    }
207}
208
209/// A base64-encoded image attachment.
210#[derive(Debug, Clone)]
211pub struct ImageAttachment {
212    pub data: String,
213    pub media_type: String,
214}
215
216/// A single message in a conversation.
217#[derive(Debug, Clone)]
218pub struct Message {
219    pub role: Role,
220    pub content: String,
221    /// Optional image attachments (base64-encoded).
222    pub images: Vec<ImageAttachment>,
223}
224
225/// A prompt: a structured list of messages.
226#[derive(Debug, Clone)]
227pub struct Prompt {
228    pub messages: Vec<Message>,
229}
230
231/// A conversation: immutable history + provider config.
232#[derive(Debug, Clone)]
233pub struct Conversation {
234    pub messages: Vec<Message>,
235    pub model: String,
236    pub metadata: BTreeMap<String, String>,
237}
238
239/// A tool definition for LLM function calling.
240#[derive(Debug, Clone)]
241pub struct ToolDefinition {
242    pub name: String,
243    pub description: String,
244    pub parameters: Value,
245    pub handler: Value,
246}
247
248/// An agent: system prompt + tools + config for autonomous loops.
249#[derive(Debug, Clone)]
250pub struct Agent {
251    pub name: String,
252    pub system: String,
253    pub tools: Vec<Value>,
254    pub max_turns: usize,
255    pub model: String,
256}
257
258/// A multimethod: dispatch-function + method table.
259/// Interior-mutable so `defmethod` can add methods after creation.
260pub struct MultiMethod {
261    pub name: Spur,
262    pub dispatch_fn: Value,
263    pub methods: RefCell<BTreeMap<Value, Value>>,
264    pub default: RefCell<Option<Value>>,
265}
266
267impl fmt::Debug for MultiMethod {
268    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
269        write!(f, "<multimethod {}>", resolve(self.name))
270    }
271}
272
273impl Clone for MultiMethod {
274    fn clone(&self) -> Self {
275        MultiMethod {
276            name: self.name,
277            dispatch_fn: self.dispatch_fn.clone(),
278            methods: RefCell::new(self.methods.borrow().clone()),
279            default: RefCell::new(self.default.borrow().clone()),
280        }
281    }
282}
283
284// ── NaN-boxing constants ──────────────────────────────────────────
285
286// IEEE 754 double layout:
287//   bit 63:     sign
288//   bits 62-52: exponent (11 bits)
289//   bits 51-0:  mantissa (52 bits), bit 51 = quiet NaN bit
290//
291// Boxed (non-float) values use: sign=1, exp=all 1s, quiet=1
292//   Then bits 50-45 = TAG (6 bits), bits 44-0 = PAYLOAD (45 bits)
293
294/// Mask for checking if a value is boxed (sign + exponent + quiet bit)
295const BOX_MASK: u64 = 0xFFF8_0000_0000_0000;
296
297/// The 45-bit payload mask
298const PAYLOAD_MASK: u64 = (1u64 << 45) - 1; // 0x1FFF_FFFF_FFFF
299
300/// Sign-extension bit for 45-bit signed integers
301const INT_SIGN_BIT: u64 = 1u64 << 44;
302
303/// 6-bit mask for extracting the tag from a boxed value (bits 50-45).
304const TAG_MASK_6BIT: u64 = 0x3F;
305
306/// Canonical quiet NaN (sign=0) — used for NaN float values to avoid collision with boxed
307const CANONICAL_NAN: u64 = 0x7FF8_0000_0000_0000;
308
309// Tags (6 bits, encoded in bits 50-45)
310const TAG_NIL: u64 = 0;
311const TAG_FALSE: u64 = 1;
312const TAG_TRUE: u64 = 2;
313const TAG_INT_SMALL: u64 = 3;
314const TAG_CHAR: u64 = 4;
315const TAG_SYMBOL: u64 = 5;
316const TAG_KEYWORD: u64 = 6;
317const TAG_INT_BIG: u64 = 7;
318const TAG_STRING: u64 = 8;
319const TAG_LIST: u64 = 9;
320const TAG_VECTOR: u64 = 10;
321const TAG_MAP: u64 = 11;
322const TAG_HASHMAP: u64 = 12;
323const TAG_LAMBDA: u64 = 13;
324const TAG_MACRO: u64 = 14;
325pub const TAG_NATIVE_FN: u64 = 15;
326const TAG_PROMPT: u64 = 16;
327const TAG_MESSAGE: u64 = 17;
328const TAG_CONVERSATION: u64 = 18;
329const TAG_TOOL_DEF: u64 = 19;
330const TAG_AGENT: u64 = 20;
331const TAG_THUNK: u64 = 21;
332const TAG_RECORD: u64 = 22;
333const TAG_BYTEVECTOR: u64 = 23;
334const TAG_MULTIMETHOD: u64 = 24;
335
336/// Small-int range: [-2^44, 2^44 - 1] = [-17_592_186_044_416, +17_592_186_044_415]
337const SMALL_INT_MIN: i64 = -(1i64 << 44);
338const SMALL_INT_MAX: i64 = (1i64 << 44) - 1;
339
340// ── Public NaN-boxing constants for VM use ────────────────────────
341
342/// Tag + box combined mask: upper 19 bits (sign + exponent + quiet + 6-bit tag).
343pub const NAN_TAG_MASK: u64 = BOX_MASK | (TAG_MASK_6BIT << 45); // 0xFFFF_E000_0000_0000
344
345/// The expected upper bits for a small int value: BOX_MASK | (TAG_INT_SMALL << 45).
346pub const NAN_INT_SMALL_PATTERN: u64 = BOX_MASK | (TAG_INT_SMALL << 45);
347
348/// Public payload mask (45 bits).
349pub const NAN_PAYLOAD_MASK: u64 = PAYLOAD_MASK;
350
351/// Sign bit within the 45-bit payload (bit 44) — for sign-extending small ints.
352pub const NAN_INT_SIGN_BIT: u64 = INT_SIGN_BIT;
353
354/// Number of payload bits in NaN-boxed values (45).
355pub const NAN_PAYLOAD_BITS: u32 = 45;
356
357// ── Helpers for encoding/decoding ─────────────────────────────────
358
359#[inline(always)]
360fn make_boxed(tag: u64, payload: u64) -> u64 {
361    BOX_MASK | (tag << 45) | (payload & PAYLOAD_MASK)
362}
363
364#[inline(always)]
365fn is_boxed(bits: u64) -> bool {
366    (bits & BOX_MASK) == BOX_MASK
367}
368
369#[inline(always)]
370fn get_tag(bits: u64) -> u64 {
371    (bits >> 45) & TAG_MASK_6BIT
372}
373
374#[inline(always)]
375fn get_payload(bits: u64) -> u64 {
376    bits & PAYLOAD_MASK
377}
378
379#[inline(always)]
380fn ptr_to_payload(ptr: *const u8) -> u64 {
381    let raw = ptr as u64;
382    debug_assert!(raw & 0x7 == 0, "pointer not 8-byte aligned: 0x{:x}", raw);
383    debug_assert!(
384        raw >> 48 == 0,
385        "pointer exceeds 48-bit VA space: 0x{:x}",
386        raw
387    );
388    raw >> 3
389}
390
391#[inline(always)]
392fn payload_to_ptr(payload: u64) -> *const u8 {
393    (payload << 3) as *const u8
394}
395
396// ── ValueView: pattern-matching enum ──────────────────────────────
397
398/// A view of a NaN-boxed Value for pattern matching.
399/// Returned by `Value::view()`. Heap types hold Rc (refcount bumped).
400pub enum ValueView {
401    Nil,
402    Bool(bool),
403    Int(i64),
404    Float(f64),
405    String(Rc<String>),
406    Symbol(Spur),
407    Keyword(Spur),
408    Char(char),
409    List(Rc<Vec<Value>>),
410    Vector(Rc<Vec<Value>>),
411    Map(Rc<BTreeMap<Value, Value>>),
412    HashMap(Rc<hashbrown::HashMap<Value, Value>>),
413    Lambda(Rc<Lambda>),
414    Macro(Rc<Macro>),
415    NativeFn(Rc<NativeFn>),
416    Prompt(Rc<Prompt>),
417    Message(Rc<Message>),
418    Conversation(Rc<Conversation>),
419    ToolDef(Rc<ToolDefinition>),
420    Agent(Rc<Agent>),
421    Thunk(Rc<Thunk>),
422    Record(Rc<Record>),
423    Bytevector(Rc<Vec<u8>>),
424    MultiMethod(Rc<MultiMethod>),
425}
426
427// ── The NaN-boxed Value type ──────────────────────────────────────
428
429/// The core Value type for all Sema data.
430/// NaN-boxed: stored as 8 bytes. Floats stored directly,
431/// everything else encoded in quiet-NaN payload space.
432#[repr(transparent)]
433pub struct Value(u64);
434
435// ── Constructors ──────────────────────────────────────────────────
436
437impl Value {
438    // -- Immediate constructors --
439
440    pub const NIL: Value = Value(make_boxed_const(TAG_NIL, 0));
441    pub const TRUE: Value = Value(make_boxed_const(TAG_TRUE, 0));
442    pub const FALSE: Value = Value(make_boxed_const(TAG_FALSE, 0));
443
444    #[inline(always)]
445    pub fn nil() -> Value {
446        Value::NIL
447    }
448
449    #[inline(always)]
450    pub fn bool(b: bool) -> Value {
451        if b {
452            Value::TRUE
453        } else {
454            Value::FALSE
455        }
456    }
457
458    #[inline(always)]
459    pub fn int(n: i64) -> Value {
460        if (SMALL_INT_MIN..=SMALL_INT_MAX).contains(&n) {
461            // Encode as small int (45-bit two's complement)
462            let payload = (n as u64) & PAYLOAD_MASK;
463            Value(make_boxed(TAG_INT_SMALL, payload))
464        } else {
465            // Out of range: heap-allocate
466            let rc = Rc::new(n);
467            let ptr = Rc::into_raw(rc) as *const u8;
468            Value(make_boxed(TAG_INT_BIG, ptr_to_payload(ptr)))
469        }
470    }
471
472    #[inline(always)]
473    pub fn float(f: f64) -> Value {
474        let bits = f.to_bits();
475        if f.is_nan() {
476            // Canonicalize NaN to avoid collision with boxed patterns
477            Value(CANONICAL_NAN)
478        } else {
479            // Check: a non-NaN float could still have the BOX_MASK pattern
480            // This happens for negative infinity and some subnormals — but
481            // negative infinity is 0xFFF0_0000_0000_0000 which does NOT match
482            // BOX_MASK (0xFFF8...) because bit 51 (quiet) is 0.
483            // In IEEE 754, the only values with all exponent bits set AND quiet bit set
484            // are quiet NaNs, which we've already canonicalized above.
485            debug_assert!(
486                !is_boxed(bits),
487                "non-NaN float collides with boxed pattern: {:?} = 0x{:016x}",
488                f,
489                bits
490            );
491            Value(bits)
492        }
493    }
494
495    #[inline(always)]
496    pub fn char(c: char) -> Value {
497        Value(make_boxed(TAG_CHAR, c as u64))
498    }
499
500    #[inline(always)]
501    pub fn symbol_from_spur(spur: Spur) -> Value {
502        let bits: u32 = unsafe { std::mem::transmute(spur) };
503        Value(make_boxed(TAG_SYMBOL, bits as u64))
504    }
505
506    pub fn symbol(s: &str) -> Value {
507        Value::symbol_from_spur(intern(s))
508    }
509
510    #[inline(always)]
511    pub fn keyword_from_spur(spur: Spur) -> Value {
512        let bits: u32 = unsafe { std::mem::transmute(spur) };
513        Value(make_boxed(TAG_KEYWORD, bits as u64))
514    }
515
516    pub fn keyword(s: &str) -> Value {
517        Value::keyword_from_spur(intern(s))
518    }
519
520    // -- Heap constructors --
521
522    fn from_rc_ptr<T>(tag: u64, rc: Rc<T>) -> Value {
523        let ptr = Rc::into_raw(rc) as *const u8;
524        Value(make_boxed(tag, ptr_to_payload(ptr)))
525    }
526
527    pub fn string(s: &str) -> Value {
528        Value::from_rc_ptr(TAG_STRING, Rc::new(s.to_string()))
529    }
530
531    pub fn string_from_rc(rc: Rc<String>) -> Value {
532        Value::from_rc_ptr(TAG_STRING, rc)
533    }
534
535    pub fn list(v: Vec<Value>) -> Value {
536        Value::from_rc_ptr(TAG_LIST, Rc::new(v))
537    }
538
539    pub fn list_from_rc(rc: Rc<Vec<Value>>) -> Value {
540        Value::from_rc_ptr(TAG_LIST, rc)
541    }
542
543    pub fn vector(v: Vec<Value>) -> Value {
544        Value::from_rc_ptr(TAG_VECTOR, Rc::new(v))
545    }
546
547    pub fn vector_from_rc(rc: Rc<Vec<Value>>) -> Value {
548        Value::from_rc_ptr(TAG_VECTOR, rc)
549    }
550
551    pub fn map(m: BTreeMap<Value, Value>) -> Value {
552        Value::from_rc_ptr(TAG_MAP, Rc::new(m))
553    }
554
555    pub fn map_from_rc(rc: Rc<BTreeMap<Value, Value>>) -> Value {
556        Value::from_rc_ptr(TAG_MAP, rc)
557    }
558
559    pub fn hashmap(entries: Vec<(Value, Value)>) -> Value {
560        let map: hashbrown::HashMap<Value, Value> = entries.into_iter().collect();
561        Value::from_rc_ptr(TAG_HASHMAP, Rc::new(map))
562    }
563
564    pub fn hashmap_from_rc(rc: Rc<hashbrown::HashMap<Value, Value>>) -> Value {
565        Value::from_rc_ptr(TAG_HASHMAP, rc)
566    }
567
568    pub fn lambda(l: Lambda) -> Value {
569        Value::from_rc_ptr(TAG_LAMBDA, Rc::new(l))
570    }
571
572    pub fn lambda_from_rc(rc: Rc<Lambda>) -> Value {
573        Value::from_rc_ptr(TAG_LAMBDA, rc)
574    }
575
576    pub fn macro_val(m: Macro) -> Value {
577        Value::from_rc_ptr(TAG_MACRO, Rc::new(m))
578    }
579
580    pub fn macro_from_rc(rc: Rc<Macro>) -> Value {
581        Value::from_rc_ptr(TAG_MACRO, rc)
582    }
583
584    pub fn native_fn(f: NativeFn) -> Value {
585        Value::from_rc_ptr(TAG_NATIVE_FN, Rc::new(f))
586    }
587
588    pub fn native_fn_from_rc(rc: Rc<NativeFn>) -> Value {
589        Value::from_rc_ptr(TAG_NATIVE_FN, rc)
590    }
591
592    pub fn prompt(p: Prompt) -> Value {
593        Value::from_rc_ptr(TAG_PROMPT, Rc::new(p))
594    }
595
596    pub fn prompt_from_rc(rc: Rc<Prompt>) -> Value {
597        Value::from_rc_ptr(TAG_PROMPT, rc)
598    }
599
600    pub fn message(m: Message) -> Value {
601        Value::from_rc_ptr(TAG_MESSAGE, Rc::new(m))
602    }
603
604    pub fn message_from_rc(rc: Rc<Message>) -> Value {
605        Value::from_rc_ptr(TAG_MESSAGE, rc)
606    }
607
608    pub fn conversation(c: Conversation) -> Value {
609        Value::from_rc_ptr(TAG_CONVERSATION, Rc::new(c))
610    }
611
612    pub fn conversation_from_rc(rc: Rc<Conversation>) -> Value {
613        Value::from_rc_ptr(TAG_CONVERSATION, rc)
614    }
615
616    pub fn tool_def(t: ToolDefinition) -> Value {
617        Value::from_rc_ptr(TAG_TOOL_DEF, Rc::new(t))
618    }
619
620    pub fn tool_def_from_rc(rc: Rc<ToolDefinition>) -> Value {
621        Value::from_rc_ptr(TAG_TOOL_DEF, rc)
622    }
623
624    pub fn agent(a: Agent) -> Value {
625        Value::from_rc_ptr(TAG_AGENT, Rc::new(a))
626    }
627
628    pub fn agent_from_rc(rc: Rc<Agent>) -> Value {
629        Value::from_rc_ptr(TAG_AGENT, rc)
630    }
631
632    pub fn thunk(t: Thunk) -> Value {
633        Value::from_rc_ptr(TAG_THUNK, Rc::new(t))
634    }
635
636    pub fn thunk_from_rc(rc: Rc<Thunk>) -> Value {
637        Value::from_rc_ptr(TAG_THUNK, rc)
638    }
639
640    pub fn record(r: Record) -> Value {
641        Value::from_rc_ptr(TAG_RECORD, Rc::new(r))
642    }
643
644    pub fn record_from_rc(rc: Rc<Record>) -> Value {
645        Value::from_rc_ptr(TAG_RECORD, rc)
646    }
647
648    pub fn bytevector(bytes: Vec<u8>) -> Value {
649        Value::from_rc_ptr(TAG_BYTEVECTOR, Rc::new(bytes))
650    }
651
652    pub fn bytevector_from_rc(rc: Rc<Vec<u8>>) -> Value {
653        Value::from_rc_ptr(TAG_BYTEVECTOR, rc)
654    }
655
656    pub fn multimethod(m: MultiMethod) -> Value {
657        Value::from_rc_ptr(TAG_MULTIMETHOD, Rc::new(m))
658    }
659
660    pub fn multimethod_from_rc(rc: Rc<MultiMethod>) -> Value {
661        Value::from_rc_ptr(TAG_MULTIMETHOD, rc)
662    }
663}
664
665// Const-compatible boxed encoding (no function calls)
666const fn make_boxed_const(tag: u64, payload: u64) -> u64 {
667    BOX_MASK | (tag << 45) | (payload & PAYLOAD_MASK)
668}
669
670// ── Accessors ─────────────────────────────────────────────────────
671
672impl Value {
673    /// Get the raw bits (for debugging/testing).
674    #[inline(always)]
675    pub fn raw_bits(&self) -> u64 {
676        self.0
677    }
678
679    /// Construct a Value from raw NaN-boxed bits.
680    ///
681    /// # Safety
682    ///
683    /// Caller must ensure `bits` represents a valid NaN-boxed value.
684    /// For immediate types (nil, bool, int, symbol, keyword, char), this is always safe.
685    /// For heap-pointer types, the encoded pointer must be valid and have its Rc ownership
686    /// accounted for (i.e., the caller must ensure the refcount is correct).
687    #[inline(always)]
688    pub unsafe fn from_raw_bits(bits: u64) -> Value {
689        Value(bits)
690    }
691
692    /// Get the NaN-boxing tag of a boxed value (0-63).
693    /// Returns `None` for non-boxed values (floats).
694    #[inline(always)]
695    pub fn raw_tag(&self) -> Option<u64> {
696        if is_boxed(self.0) {
697            Some(get_tag(self.0))
698        } else {
699            None
700        }
701    }
702
703    /// Borrow the underlying NativeFn without bumping the Rc refcount.
704    /// SAFETY: The returned reference is valid as long as this Value is alive.
705    #[inline(always)]
706    pub fn as_native_fn_ref(&self) -> Option<&NativeFn> {
707        if is_boxed(self.0) && get_tag(self.0) == TAG_NATIVE_FN {
708            Some(unsafe { self.borrow_ref::<NativeFn>() })
709        } else {
710            None
711        }
712    }
713
714    /// Check if this is a float (non-boxed).
715    #[inline(always)]
716    pub fn is_float(&self) -> bool {
717        !is_boxed(self.0)
718    }
719
720    /// Recover an Rc<T> pointer from the payload WITHOUT consuming ownership.
721    /// This increments the refcount (returns a new Rc).
722    #[inline(always)]
723    unsafe fn get_rc<T>(&self) -> Rc<T> {
724        let payload = get_payload(self.0);
725        let ptr = payload_to_ptr(payload) as *const T;
726        Rc::increment_strong_count(ptr);
727        Rc::from_raw(ptr)
728    }
729
730    /// Borrow the underlying T from a heap-tagged Value.
731    /// SAFETY: caller must ensure the tag matches and T is correct.
732    #[inline(always)]
733    unsafe fn borrow_ref<T>(&self) -> &T {
734        let payload = get_payload(self.0);
735        let ptr = payload_to_ptr(payload) as *const T;
736        &*ptr
737    }
738
739    /// Pattern-match friendly view of this value.
740    /// For heap types, this bumps the Rc refcount.
741    pub fn view(&self) -> ValueView {
742        if !is_boxed(self.0) {
743            return ValueView::Float(f64::from_bits(self.0));
744        }
745        let tag = get_tag(self.0);
746        match tag {
747            TAG_NIL => ValueView::Nil,
748            TAG_FALSE => ValueView::Bool(false),
749            TAG_TRUE => ValueView::Bool(true),
750            TAG_INT_SMALL => {
751                let payload = get_payload(self.0);
752                let val = if payload & INT_SIGN_BIT != 0 {
753                    (payload | !PAYLOAD_MASK) as i64
754                } else {
755                    payload as i64
756                };
757                ValueView::Int(val)
758            }
759            TAG_CHAR => {
760                let payload = get_payload(self.0);
761                ValueView::Char(unsafe { char::from_u32_unchecked(payload as u32) })
762            }
763            TAG_SYMBOL => {
764                let payload = get_payload(self.0);
765                let spur: Spur = unsafe { std::mem::transmute(payload as u32) };
766                ValueView::Symbol(spur)
767            }
768            TAG_KEYWORD => {
769                let payload = get_payload(self.0);
770                let spur: Spur = unsafe { std::mem::transmute(payload as u32) };
771                ValueView::Keyword(spur)
772            }
773            TAG_INT_BIG => {
774                let val = unsafe { *self.borrow_ref::<i64>() };
775                ValueView::Int(val)
776            }
777            TAG_STRING => ValueView::String(unsafe { self.get_rc::<String>() }),
778            TAG_LIST => ValueView::List(unsafe { self.get_rc::<Vec<Value>>() }),
779            TAG_VECTOR => ValueView::Vector(unsafe { self.get_rc::<Vec<Value>>() }),
780            TAG_MAP => ValueView::Map(unsafe { self.get_rc::<BTreeMap<Value, Value>>() }),
781            TAG_HASHMAP => {
782                ValueView::HashMap(unsafe { self.get_rc::<hashbrown::HashMap<Value, Value>>() })
783            }
784            TAG_LAMBDA => ValueView::Lambda(unsafe { self.get_rc::<Lambda>() }),
785            TAG_MACRO => ValueView::Macro(unsafe { self.get_rc::<Macro>() }),
786            TAG_NATIVE_FN => ValueView::NativeFn(unsafe { self.get_rc::<NativeFn>() }),
787            TAG_PROMPT => ValueView::Prompt(unsafe { self.get_rc::<Prompt>() }),
788            TAG_MESSAGE => ValueView::Message(unsafe { self.get_rc::<Message>() }),
789            TAG_CONVERSATION => ValueView::Conversation(unsafe { self.get_rc::<Conversation>() }),
790            TAG_TOOL_DEF => ValueView::ToolDef(unsafe { self.get_rc::<ToolDefinition>() }),
791            TAG_AGENT => ValueView::Agent(unsafe { self.get_rc::<Agent>() }),
792            TAG_THUNK => ValueView::Thunk(unsafe { self.get_rc::<Thunk>() }),
793            TAG_RECORD => ValueView::Record(unsafe { self.get_rc::<Record>() }),
794            TAG_BYTEVECTOR => ValueView::Bytevector(unsafe { self.get_rc::<Vec<u8>>() }),
795            TAG_MULTIMETHOD => ValueView::MultiMethod(unsafe { self.get_rc::<MultiMethod>() }),
796            _ => unreachable!("invalid NaN-boxed tag: {}", tag),
797        }
798    }
799
800    // -- Typed accessors (ergonomic, avoid full view match) --
801
802    pub fn type_name(&self) -> &'static str {
803        if !is_boxed(self.0) {
804            return "float";
805        }
806        match get_tag(self.0) {
807            TAG_NIL => "nil",
808            TAG_FALSE | TAG_TRUE => "bool",
809            TAG_INT_SMALL | TAG_INT_BIG => "int",
810            TAG_CHAR => "char",
811            TAG_SYMBOL => "symbol",
812            TAG_KEYWORD => "keyword",
813            TAG_STRING => "string",
814            TAG_LIST => "list",
815            TAG_VECTOR => "vector",
816            TAG_MAP => "map",
817            TAG_HASHMAP => "hashmap",
818            TAG_LAMBDA => "lambda",
819            TAG_MACRO => "macro",
820            TAG_NATIVE_FN => "native-fn",
821            TAG_PROMPT => "prompt",
822            TAG_MESSAGE => "message",
823            TAG_CONVERSATION => "conversation",
824            TAG_TOOL_DEF => "tool",
825            TAG_AGENT => "agent",
826            TAG_THUNK => "promise",
827            TAG_RECORD => "record",
828            TAG_BYTEVECTOR => "bytevector",
829            TAG_MULTIMETHOD => "multimethod",
830            _ => "unknown",
831        }
832    }
833
834    #[inline(always)]
835    pub fn is_nil(&self) -> bool {
836        self.0 == Value::NIL.0
837    }
838
839    #[inline(always)]
840    pub fn is_truthy(&self) -> bool {
841        self.0 != Value::NIL.0 && self.0 != Value::FALSE.0
842    }
843
844    #[inline(always)]
845    pub fn is_falsy(&self) -> bool {
846        !self.is_truthy()
847    }
848
849    #[inline(always)]
850    pub fn is_bool(&self) -> bool {
851        self.0 == Value::TRUE.0 || self.0 == Value::FALSE.0
852    }
853
854    #[inline(always)]
855    pub fn is_int(&self) -> bool {
856        is_boxed(self.0) && matches!(get_tag(self.0), TAG_INT_SMALL | TAG_INT_BIG)
857    }
858
859    #[inline(always)]
860    pub fn is_symbol(&self) -> bool {
861        is_boxed(self.0) && get_tag(self.0) == TAG_SYMBOL
862    }
863
864    #[inline(always)]
865    pub fn is_keyword(&self) -> bool {
866        is_boxed(self.0) && get_tag(self.0) == TAG_KEYWORD
867    }
868
869    #[inline(always)]
870    pub fn is_string(&self) -> bool {
871        is_boxed(self.0) && get_tag(self.0) == TAG_STRING
872    }
873
874    #[inline(always)]
875    pub fn is_list(&self) -> bool {
876        is_boxed(self.0) && get_tag(self.0) == TAG_LIST
877    }
878
879    #[inline(always)]
880    pub fn is_pair(&self) -> bool {
881        if let Some(items) = self.as_list() {
882            !items.is_empty()
883        } else {
884            false
885        }
886    }
887
888    #[inline(always)]
889    pub fn is_vector(&self) -> bool {
890        is_boxed(self.0) && get_tag(self.0) == TAG_VECTOR
891    }
892
893    #[inline(always)]
894    pub fn is_map(&self) -> bool {
895        is_boxed(self.0) && matches!(get_tag(self.0), TAG_MAP | TAG_HASHMAP)
896    }
897
898    #[inline(always)]
899    pub fn is_lambda(&self) -> bool {
900        is_boxed(self.0) && get_tag(self.0) == TAG_LAMBDA
901    }
902
903    #[inline(always)]
904    pub fn is_native_fn(&self) -> bool {
905        is_boxed(self.0) && get_tag(self.0) == TAG_NATIVE_FN
906    }
907
908    #[inline(always)]
909    pub fn is_thunk(&self) -> bool {
910        is_boxed(self.0) && get_tag(self.0) == TAG_THUNK
911    }
912
913    #[inline(always)]
914    pub fn is_record(&self) -> bool {
915        is_boxed(self.0) && get_tag(self.0) == TAG_RECORD
916    }
917
918    #[inline(always)]
919    pub fn as_int(&self) -> Option<i64> {
920        if !is_boxed(self.0) {
921            return None;
922        }
923        match get_tag(self.0) {
924            TAG_INT_SMALL => {
925                let payload = get_payload(self.0);
926                let val = if payload & INT_SIGN_BIT != 0 {
927                    (payload | !PAYLOAD_MASK) as i64
928                } else {
929                    payload as i64
930                };
931                Some(val)
932            }
933            TAG_INT_BIG => Some(unsafe { *self.borrow_ref::<i64>() }),
934            _ => None,
935        }
936    }
937
938    #[inline(always)]
939    pub fn as_float(&self) -> Option<f64> {
940        if !is_boxed(self.0) {
941            return Some(f64::from_bits(self.0));
942        }
943        match get_tag(self.0) {
944            TAG_INT_SMALL => {
945                let payload = get_payload(self.0);
946                let val = if payload & INT_SIGN_BIT != 0 {
947                    (payload | !PAYLOAD_MASK) as i64
948                } else {
949                    payload as i64
950                };
951                Some(val as f64)
952            }
953            TAG_INT_BIG => Some(unsafe { *self.borrow_ref::<i64>() } as f64),
954            _ => None,
955        }
956    }
957
958    #[inline(always)]
959    pub fn as_bool(&self) -> Option<bool> {
960        if self.0 == Value::TRUE.0 {
961            Some(true)
962        } else if self.0 == Value::FALSE.0 {
963            Some(false)
964        } else {
965            None
966        }
967    }
968
969    pub fn as_str(&self) -> Option<&str> {
970        if is_boxed(self.0) && get_tag(self.0) == TAG_STRING {
971            Some(unsafe { self.borrow_ref::<String>() })
972        } else {
973            None
974        }
975    }
976
977    pub fn as_string_rc(&self) -> Option<Rc<String>> {
978        if is_boxed(self.0) && get_tag(self.0) == TAG_STRING {
979            Some(unsafe { self.get_rc::<String>() })
980        } else {
981            None
982        }
983    }
984
985    pub fn as_symbol(&self) -> Option<String> {
986        self.as_symbol_spur().map(resolve)
987    }
988
989    pub fn as_symbol_spur(&self) -> Option<Spur> {
990        if is_boxed(self.0) && get_tag(self.0) == TAG_SYMBOL {
991            let payload = get_payload(self.0);
992            Some(unsafe { std::mem::transmute::<u32, Spur>(payload as u32) })
993        } else {
994            None
995        }
996    }
997
998    pub fn as_keyword(&self) -> Option<String> {
999        self.as_keyword_spur().map(resolve)
1000    }
1001
1002    pub fn as_keyword_spur(&self) -> Option<Spur> {
1003        if is_boxed(self.0) && get_tag(self.0) == TAG_KEYWORD {
1004            let payload = get_payload(self.0);
1005            Some(unsafe { std::mem::transmute::<u32, Spur>(payload as u32) })
1006        } else {
1007            None
1008        }
1009    }
1010
1011    pub fn as_char(&self) -> Option<char> {
1012        if is_boxed(self.0) && get_tag(self.0) == TAG_CHAR {
1013            let payload = get_payload(self.0);
1014            char::from_u32(payload as u32)
1015        } else {
1016            None
1017        }
1018    }
1019
1020    pub fn as_list(&self) -> Option<&[Value]> {
1021        if is_boxed(self.0) && get_tag(self.0) == TAG_LIST {
1022            Some(unsafe { self.borrow_ref::<Vec<Value>>() })
1023        } else {
1024            None
1025        }
1026    }
1027
1028    pub fn as_list_rc(&self) -> Option<Rc<Vec<Value>>> {
1029        if is_boxed(self.0) && get_tag(self.0) == TAG_LIST {
1030            Some(unsafe { self.get_rc::<Vec<Value>>() })
1031        } else {
1032            None
1033        }
1034    }
1035
1036    /// Returns the contents as a slice if this is a list OR a vector.
1037    pub fn as_seq(&self) -> Option<&[Value]> {
1038        self.as_list().or_else(|| self.as_vector())
1039    }
1040
1041    pub fn as_vector(&self) -> Option<&[Value]> {
1042        if is_boxed(self.0) && get_tag(self.0) == TAG_VECTOR {
1043            Some(unsafe { self.borrow_ref::<Vec<Value>>() })
1044        } else {
1045            None
1046        }
1047    }
1048
1049    pub fn as_vector_rc(&self) -> Option<Rc<Vec<Value>>> {
1050        if is_boxed(self.0) && get_tag(self.0) == TAG_VECTOR {
1051            Some(unsafe { self.get_rc::<Vec<Value>>() })
1052        } else {
1053            None
1054        }
1055    }
1056
1057    pub fn as_map_rc(&self) -> Option<Rc<BTreeMap<Value, Value>>> {
1058        if is_boxed(self.0) && get_tag(self.0) == TAG_MAP {
1059            Some(unsafe { self.get_rc::<BTreeMap<Value, Value>>() })
1060        } else {
1061            None
1062        }
1063    }
1064
1065    pub fn as_hashmap_rc(&self) -> Option<Rc<hashbrown::HashMap<Value, Value>>> {
1066        if is_boxed(self.0) && get_tag(self.0) == TAG_HASHMAP {
1067            Some(unsafe { self.get_rc::<hashbrown::HashMap<Value, Value>>() })
1068        } else {
1069            None
1070        }
1071    }
1072
1073    /// Borrow the underlying HashMap without bumping the Rc refcount.
1074    #[inline(always)]
1075    pub fn as_hashmap_ref(&self) -> Option<&hashbrown::HashMap<Value, Value>> {
1076        if is_boxed(self.0) && get_tag(self.0) == TAG_HASHMAP {
1077            Some(unsafe { self.borrow_ref::<hashbrown::HashMap<Value, Value>>() })
1078        } else {
1079            None
1080        }
1081    }
1082
1083    /// Borrow the underlying BTreeMap without bumping the Rc refcount.
1084    #[inline(always)]
1085    pub fn as_map_ref(&self) -> Option<&BTreeMap<Value, Value>> {
1086        if is_boxed(self.0) && get_tag(self.0) == TAG_MAP {
1087            Some(unsafe { self.borrow_ref::<BTreeMap<Value, Value>>() })
1088        } else {
1089            None
1090        }
1091    }
1092
1093    /// If this is a hashmap with refcount==1, mutate it in place.
1094    /// Returns `None` if not a hashmap or if shared (refcount > 1).
1095    /// SAFETY: relies on no other references to the inner data existing.
1096    #[inline(always)]
1097    pub fn with_hashmap_mut_if_unique<R>(
1098        &self,
1099        f: impl FnOnce(&mut hashbrown::HashMap<Value, Value>) -> R,
1100    ) -> Option<R> {
1101        if !is_boxed(self.0) || get_tag(self.0) != TAG_HASHMAP {
1102            return None;
1103        }
1104        let payload = get_payload(self.0);
1105        let ptr = payload_to_ptr(payload) as *const hashbrown::HashMap<Value, Value>;
1106        let rc = std::mem::ManuallyDrop::new(unsafe { Rc::from_raw(ptr) });
1107        if Rc::strong_count(&rc) != 1 {
1108            return None;
1109        }
1110        // strong_count==1: we are the sole owner, safe to mutate
1111        let ptr_mut = ptr as *mut hashbrown::HashMap<Value, Value>;
1112        Some(f(unsafe { &mut *ptr_mut }))
1113    }
1114
1115    /// If this is a map (BTreeMap) with refcount==1, mutate it in place.
1116    /// Returns `None` if not a map or if shared (refcount > 1).
1117    #[inline(always)]
1118    pub fn with_map_mut_if_unique<R>(
1119        &self,
1120        f: impl FnOnce(&mut BTreeMap<Value, Value>) -> R,
1121    ) -> Option<R> {
1122        if !is_boxed(self.0) || get_tag(self.0) != TAG_MAP {
1123            return None;
1124        }
1125        let payload = get_payload(self.0);
1126        let ptr = payload_to_ptr(payload) as *const BTreeMap<Value, Value>;
1127        let rc = std::mem::ManuallyDrop::new(unsafe { Rc::from_raw(ptr) });
1128        if Rc::strong_count(&rc) != 1 {
1129            return None;
1130        }
1131        let ptr_mut = ptr as *mut BTreeMap<Value, Value>;
1132        Some(f(unsafe { &mut *ptr_mut }))
1133    }
1134
1135    /// Consume this Value and extract the inner Rc without a refcount bump.
1136    /// Returns `Err(self)` if not a hashmap.
1137    pub fn into_hashmap_rc(self) -> Result<Rc<hashbrown::HashMap<Value, Value>>, Value> {
1138        if is_boxed(self.0) && get_tag(self.0) == TAG_HASHMAP {
1139            let payload = get_payload(self.0);
1140            let ptr = payload_to_ptr(payload) as *const hashbrown::HashMap<Value, Value>;
1141            // Prevent Drop from decrementing the refcount — we're taking ownership
1142            std::mem::forget(self);
1143            Ok(unsafe { Rc::from_raw(ptr) })
1144        } else {
1145            Err(self)
1146        }
1147    }
1148
1149    /// Consume this Value and extract the inner Rc without a refcount bump.
1150    /// Returns `Err(self)` if not a map.
1151    pub fn into_map_rc(self) -> Result<Rc<BTreeMap<Value, Value>>, Value> {
1152        if is_boxed(self.0) && get_tag(self.0) == TAG_MAP {
1153            let payload = get_payload(self.0);
1154            let ptr = payload_to_ptr(payload) as *const BTreeMap<Value, Value>;
1155            std::mem::forget(self);
1156            Ok(unsafe { Rc::from_raw(ptr) })
1157        } else {
1158            Err(self)
1159        }
1160    }
1161
1162    pub fn as_lambda_rc(&self) -> Option<Rc<Lambda>> {
1163        if is_boxed(self.0) && get_tag(self.0) == TAG_LAMBDA {
1164            Some(unsafe { self.get_rc::<Lambda>() })
1165        } else {
1166            None
1167        }
1168    }
1169
1170    pub fn as_macro_rc(&self) -> Option<Rc<Macro>> {
1171        if is_boxed(self.0) && get_tag(self.0) == TAG_MACRO {
1172            Some(unsafe { self.get_rc::<Macro>() })
1173        } else {
1174            None
1175        }
1176    }
1177
1178    pub fn as_native_fn_rc(&self) -> Option<Rc<NativeFn>> {
1179        if is_boxed(self.0) && get_tag(self.0) == TAG_NATIVE_FN {
1180            Some(unsafe { self.get_rc::<NativeFn>() })
1181        } else {
1182            None
1183        }
1184    }
1185
1186    pub fn as_thunk_rc(&self) -> Option<Rc<Thunk>> {
1187        if is_boxed(self.0) && get_tag(self.0) == TAG_THUNK {
1188            Some(unsafe { self.get_rc::<Thunk>() })
1189        } else {
1190            None
1191        }
1192    }
1193
1194    pub fn as_record(&self) -> Option<&Record> {
1195        if is_boxed(self.0) && get_tag(self.0) == TAG_RECORD {
1196            Some(unsafe { self.borrow_ref::<Record>() })
1197        } else {
1198            None
1199        }
1200    }
1201
1202    pub fn as_record_rc(&self) -> Option<Rc<Record>> {
1203        if is_boxed(self.0) && get_tag(self.0) == TAG_RECORD {
1204            Some(unsafe { self.get_rc::<Record>() })
1205        } else {
1206            None
1207        }
1208    }
1209
1210    pub fn as_bytevector(&self) -> Option<&[u8]> {
1211        if is_boxed(self.0) && get_tag(self.0) == TAG_BYTEVECTOR {
1212            Some(unsafe { self.borrow_ref::<Vec<u8>>() })
1213        } else {
1214            None
1215        }
1216    }
1217
1218    pub fn as_bytevector_rc(&self) -> Option<Rc<Vec<u8>>> {
1219        if is_boxed(self.0) && get_tag(self.0) == TAG_BYTEVECTOR {
1220            Some(unsafe { self.get_rc::<Vec<u8>>() })
1221        } else {
1222            None
1223        }
1224    }
1225
1226    pub fn as_prompt_rc(&self) -> Option<Rc<Prompt>> {
1227        if is_boxed(self.0) && get_tag(self.0) == TAG_PROMPT {
1228            Some(unsafe { self.get_rc::<Prompt>() })
1229        } else {
1230            None
1231        }
1232    }
1233
1234    pub fn as_message_rc(&self) -> Option<Rc<Message>> {
1235        if is_boxed(self.0) && get_tag(self.0) == TAG_MESSAGE {
1236            Some(unsafe { self.get_rc::<Message>() })
1237        } else {
1238            None
1239        }
1240    }
1241
1242    pub fn as_conversation_rc(&self) -> Option<Rc<Conversation>> {
1243        if is_boxed(self.0) && get_tag(self.0) == TAG_CONVERSATION {
1244            Some(unsafe { self.get_rc::<Conversation>() })
1245        } else {
1246            None
1247        }
1248    }
1249
1250    pub fn as_tool_def_rc(&self) -> Option<Rc<ToolDefinition>> {
1251        if is_boxed(self.0) && get_tag(self.0) == TAG_TOOL_DEF {
1252            Some(unsafe { self.get_rc::<ToolDefinition>() })
1253        } else {
1254            None
1255        }
1256    }
1257
1258    pub fn as_agent_rc(&self) -> Option<Rc<Agent>> {
1259        if is_boxed(self.0) && get_tag(self.0) == TAG_AGENT {
1260            Some(unsafe { self.get_rc::<Agent>() })
1261        } else {
1262            None
1263        }
1264    }
1265
1266    pub fn as_multimethod_rc(&self) -> Option<Rc<MultiMethod>> {
1267        if is_boxed(self.0) && get_tag(self.0) == TAG_MULTIMETHOD {
1268            Some(unsafe { self.get_rc::<MultiMethod>() })
1269        } else {
1270            None
1271        }
1272    }
1273}
1274
1275// ── Clone ─────────────────────────────────────────────────────────
1276
1277impl Clone for Value {
1278    #[inline(always)]
1279    fn clone(&self) -> Self {
1280        if !is_boxed(self.0) {
1281            // Float: trivial copy
1282            return Value(self.0);
1283        }
1284        let tag = get_tag(self.0);
1285        match tag {
1286            // Immediates: trivial copy
1287            TAG_NIL | TAG_FALSE | TAG_TRUE | TAG_INT_SMALL | TAG_CHAR | TAG_SYMBOL
1288            | TAG_KEYWORD => Value(self.0),
1289            // Heap pointers: increment refcount
1290            _ => {
1291                let payload = get_payload(self.0);
1292                let ptr = payload_to_ptr(payload);
1293                // Increment refcount based on type
1294                unsafe {
1295                    match tag {
1296                        TAG_INT_BIG => Rc::increment_strong_count(ptr as *const i64),
1297                        TAG_STRING => Rc::increment_strong_count(ptr as *const String),
1298                        TAG_LIST | TAG_VECTOR => {
1299                            Rc::increment_strong_count(ptr as *const Vec<Value>)
1300                        }
1301                        TAG_MAP => Rc::increment_strong_count(ptr as *const BTreeMap<Value, Value>),
1302                        TAG_HASHMAP => Rc::increment_strong_count(
1303                            ptr as *const hashbrown::HashMap<Value, Value>,
1304                        ),
1305                        TAG_LAMBDA => Rc::increment_strong_count(ptr as *const Lambda),
1306                        TAG_MACRO => Rc::increment_strong_count(ptr as *const Macro),
1307                        TAG_NATIVE_FN => Rc::increment_strong_count(ptr as *const NativeFn),
1308                        TAG_PROMPT => Rc::increment_strong_count(ptr as *const Prompt),
1309                        TAG_MESSAGE => Rc::increment_strong_count(ptr as *const Message),
1310                        TAG_CONVERSATION => Rc::increment_strong_count(ptr as *const Conversation),
1311                        TAG_TOOL_DEF => Rc::increment_strong_count(ptr as *const ToolDefinition),
1312                        TAG_AGENT => Rc::increment_strong_count(ptr as *const Agent),
1313                        TAG_THUNK => Rc::increment_strong_count(ptr as *const Thunk),
1314                        TAG_RECORD => Rc::increment_strong_count(ptr as *const Record),
1315                        TAG_BYTEVECTOR => Rc::increment_strong_count(ptr as *const Vec<u8>),
1316                        TAG_MULTIMETHOD => Rc::increment_strong_count(ptr as *const MultiMethod),
1317                        _ => unreachable!("invalid heap tag in clone: {}", tag),
1318                    }
1319                }
1320                Value(self.0)
1321            }
1322        }
1323    }
1324}
1325
1326// ── Drop ──────────────────────────────────────────────────────────
1327
1328impl Drop for Value {
1329    #[inline(always)]
1330    fn drop(&mut self) {
1331        if !is_boxed(self.0) {
1332            return; // Float
1333        }
1334        let tag = get_tag(self.0);
1335        match tag {
1336            // Immediates: nothing to free
1337            TAG_NIL | TAG_FALSE | TAG_TRUE | TAG_INT_SMALL | TAG_CHAR | TAG_SYMBOL
1338            | TAG_KEYWORD => {}
1339            // Heap pointers: drop the Rc
1340            _ => {
1341                let payload = get_payload(self.0);
1342                let ptr = payload_to_ptr(payload);
1343                unsafe {
1344                    match tag {
1345                        TAG_INT_BIG => drop(Rc::from_raw(ptr as *const i64)),
1346                        TAG_STRING => drop(Rc::from_raw(ptr as *const String)),
1347                        TAG_LIST | TAG_VECTOR => drop(Rc::from_raw(ptr as *const Vec<Value>)),
1348                        TAG_MAP => drop(Rc::from_raw(ptr as *const BTreeMap<Value, Value>)),
1349                        TAG_HASHMAP => {
1350                            drop(Rc::from_raw(ptr as *const hashbrown::HashMap<Value, Value>))
1351                        }
1352                        TAG_LAMBDA => drop(Rc::from_raw(ptr as *const Lambda)),
1353                        TAG_MACRO => drop(Rc::from_raw(ptr as *const Macro)),
1354                        TAG_NATIVE_FN => drop(Rc::from_raw(ptr as *const NativeFn)),
1355                        TAG_PROMPT => drop(Rc::from_raw(ptr as *const Prompt)),
1356                        TAG_MESSAGE => drop(Rc::from_raw(ptr as *const Message)),
1357                        TAG_CONVERSATION => drop(Rc::from_raw(ptr as *const Conversation)),
1358                        TAG_TOOL_DEF => drop(Rc::from_raw(ptr as *const ToolDefinition)),
1359                        TAG_AGENT => drop(Rc::from_raw(ptr as *const Agent)),
1360                        TAG_THUNK => drop(Rc::from_raw(ptr as *const Thunk)),
1361                        TAG_RECORD => drop(Rc::from_raw(ptr as *const Record)),
1362                        TAG_BYTEVECTOR => drop(Rc::from_raw(ptr as *const Vec<u8>)),
1363                        TAG_MULTIMETHOD => drop(Rc::from_raw(ptr as *const MultiMethod)),
1364                        _ => {} // unreachable, but don't panic in drop
1365                    }
1366                }
1367            }
1368        }
1369    }
1370}
1371
1372// ── PartialEq / Eq ────────────────────────────────────────────────
1373
1374impl PartialEq for Value {
1375    fn eq(&self, other: &Self) -> bool {
1376        // Fast path: identical bits
1377        if self.0 == other.0 {
1378            // For floats, NaN != NaN per IEEE, but our canonical NaN is unique,
1379            // so identical bits means equal for all types.
1380            // Exception: need to handle -0.0 == +0.0
1381            if !is_boxed(self.0) {
1382                let f = f64::from_bits(self.0);
1383                // NaN check: if both are canonical NaN (same bits), we say not equal
1384                if f.is_nan() {
1385                    return false;
1386                }
1387                return true;
1388            }
1389            return true;
1390        }
1391        // Different bits: could still be equal for heap types or -0.0/+0.0
1392        match (self.view(), other.view()) {
1393            (ValueView::Nil, ValueView::Nil) => true,
1394            (ValueView::Bool(a), ValueView::Bool(b)) => a == b,
1395            (ValueView::Int(a), ValueView::Int(b)) => a == b,
1396            (ValueView::Float(a), ValueView::Float(b)) => a == b,
1397            (ValueView::String(a), ValueView::String(b)) => a == b,
1398            (ValueView::Symbol(a), ValueView::Symbol(b)) => a == b,
1399            (ValueView::Keyword(a), ValueView::Keyword(b)) => a == b,
1400            (ValueView::Char(a), ValueView::Char(b)) => a == b,
1401            (ValueView::List(a), ValueView::List(b)) => a == b,
1402            (ValueView::Vector(a), ValueView::Vector(b)) => a == b,
1403            (ValueView::Map(a), ValueView::Map(b)) => a == b,
1404            (ValueView::HashMap(a), ValueView::HashMap(b)) => a == b,
1405            (ValueView::Record(a), ValueView::Record(b)) => {
1406                a.type_tag == b.type_tag && a.fields == b.fields
1407            }
1408            (ValueView::Bytevector(a), ValueView::Bytevector(b)) => a == b,
1409            _ => false,
1410        }
1411    }
1412}
1413
1414impl Eq for Value {}
1415
1416// ── Hash ──────────────────────────────────────────────────────────
1417
1418impl Hash for Value {
1419    fn hash<H: Hasher>(&self, state: &mut H) {
1420        match self.view() {
1421            ValueView::Nil => 0u8.hash(state),
1422            ValueView::Bool(b) => {
1423                1u8.hash(state);
1424                b.hash(state);
1425            }
1426            ValueView::Int(n) => {
1427                2u8.hash(state);
1428                n.hash(state);
1429            }
1430            ValueView::Float(f) => {
1431                3u8.hash(state);
1432                // Normalize -0.0 to +0.0 so equal values hash identically
1433                let bits = if f == 0.0 { 0u64 } else { f.to_bits() };
1434                bits.hash(state);
1435            }
1436            ValueView::String(s) => {
1437                4u8.hash(state);
1438                s.hash(state);
1439            }
1440            ValueView::Symbol(s) => {
1441                5u8.hash(state);
1442                s.hash(state);
1443            }
1444            ValueView::Keyword(s) => {
1445                6u8.hash(state);
1446                s.hash(state);
1447            }
1448            ValueView::Char(c) => {
1449                7u8.hash(state);
1450                c.hash(state);
1451            }
1452            ValueView::List(l) => {
1453                8u8.hash(state);
1454                l.hash(state);
1455            }
1456            ValueView::Vector(v) => {
1457                9u8.hash(state);
1458                v.hash(state);
1459            }
1460            ValueView::Record(r) => {
1461                10u8.hash(state);
1462                r.type_tag.hash(state);
1463                r.fields.hash(state);
1464            }
1465            ValueView::Bytevector(bv) => {
1466                11u8.hash(state);
1467                bv.hash(state);
1468            }
1469            _ => {}
1470        }
1471    }
1472}
1473
1474// ── Ord ───────────────────────────────────────────────────────────
1475
1476impl PartialOrd for Value {
1477    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1478        Some(self.cmp(other))
1479    }
1480}
1481
1482impl Ord for Value {
1483    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1484        use std::cmp::Ordering;
1485        fn type_order(v: &Value) -> u8 {
1486            match v.view() {
1487                ValueView::Nil => 0,
1488                ValueView::Bool(_) => 1,
1489                ValueView::Int(_) => 2,
1490                ValueView::Float(_) => 3,
1491                ValueView::Char(_) => 4,
1492                ValueView::String(_) => 5,
1493                ValueView::Symbol(_) => 6,
1494                ValueView::Keyword(_) => 7,
1495                ValueView::List(_) => 8,
1496                ValueView::Vector(_) => 9,
1497                ValueView::Map(_) => 10,
1498                ValueView::HashMap(_) => 11,
1499                ValueView::Record(_) => 12,
1500                ValueView::Bytevector(_) => 13,
1501                _ => 14,
1502            }
1503        }
1504        match (self.view(), other.view()) {
1505            (ValueView::Nil, ValueView::Nil) => Ordering::Equal,
1506            (ValueView::Bool(a), ValueView::Bool(b)) => a.cmp(&b),
1507            (ValueView::Int(a), ValueView::Int(b)) => a.cmp(&b),
1508            (ValueView::Float(a), ValueView::Float(b)) => a.total_cmp(&b),
1509            (ValueView::String(a), ValueView::String(b)) => a.cmp(&b),
1510            (ValueView::Symbol(a), ValueView::Symbol(b)) => compare_spurs(a, b),
1511            (ValueView::Keyword(a), ValueView::Keyword(b)) => compare_spurs(a, b),
1512            (ValueView::Char(a), ValueView::Char(b)) => a.cmp(&b),
1513            (ValueView::List(a), ValueView::List(b)) => a.cmp(&b),
1514            (ValueView::Vector(a), ValueView::Vector(b)) => a.cmp(&b),
1515            (ValueView::Record(a), ValueView::Record(b)) => {
1516                compare_spurs(a.type_tag, b.type_tag).then_with(|| a.fields.cmp(&b.fields))
1517            }
1518            (ValueView::Bytevector(a), ValueView::Bytevector(b)) => a.cmp(&b),
1519            _ => type_order(self).cmp(&type_order(other)),
1520        }
1521    }
1522}
1523
1524// ── Display ───────────────────────────────────────────────────────
1525
1526fn truncate(s: &str, max: usize) -> String {
1527    let mut iter = s.chars();
1528    let prefix: String = iter.by_ref().take(max).collect();
1529    if iter.next().is_none() {
1530        prefix
1531    } else {
1532        format!("{prefix}...")
1533    }
1534}
1535
1536impl fmt::Display for Value {
1537    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1538        match self.view() {
1539            ValueView::Nil => write!(f, "nil"),
1540            ValueView::Bool(true) => write!(f, "#t"),
1541            ValueView::Bool(false) => write!(f, "#f"),
1542            ValueView::Int(n) => write!(f, "{n}"),
1543            ValueView::Float(n) => {
1544                if n.fract() == 0.0 {
1545                    write!(f, "{n:.1}")
1546                } else {
1547                    write!(f, "{n}")
1548                }
1549            }
1550            ValueView::String(s) => write!(f, "\"{s}\""),
1551            ValueView::Symbol(s) => with_resolved(s, |name| write!(f, "{name}")),
1552            ValueView::Keyword(s) => with_resolved(s, |name| write!(f, ":{name}")),
1553            ValueView::Char(c) => match c {
1554                ' ' => write!(f, "#\\space"),
1555                '\n' => write!(f, "#\\newline"),
1556                '\t' => write!(f, "#\\tab"),
1557                '\r' => write!(f, "#\\return"),
1558                '\0' => write!(f, "#\\nul"),
1559                _ => write!(f, "#\\{c}"),
1560            },
1561            ValueView::List(items) => {
1562                write!(f, "(")?;
1563                for (i, item) in items.iter().enumerate() {
1564                    if i > 0 {
1565                        write!(f, " ")?;
1566                    }
1567                    write!(f, "{item}")?;
1568                }
1569                write!(f, ")")
1570            }
1571            ValueView::Vector(items) => {
1572                write!(f, "[")?;
1573                for (i, item) in items.iter().enumerate() {
1574                    if i > 0 {
1575                        write!(f, " ")?;
1576                    }
1577                    write!(f, "{item}")?;
1578                }
1579                write!(f, "]")
1580            }
1581            ValueView::Map(map) => {
1582                write!(f, "{{")?;
1583                for (i, (k, v)) in map.iter().enumerate() {
1584                    if i > 0 {
1585                        write!(f, " ")?;
1586                    }
1587                    write!(f, "{k} {v}")?;
1588                }
1589                write!(f, "}}")
1590            }
1591            ValueView::HashMap(map) => {
1592                let mut entries: Vec<_> = map.iter().collect();
1593                entries.sort_by(|(k1, _), (k2, _)| k1.cmp(k2));
1594                write!(f, "{{")?;
1595                for (i, (k, v)) in entries.iter().enumerate() {
1596                    if i > 0 {
1597                        write!(f, " ")?;
1598                    }
1599                    write!(f, "{k} {v}")?;
1600                }
1601                write!(f, "}}")
1602            }
1603            ValueView::Lambda(l) => {
1604                if let Some(name) = &l.name {
1605                    with_resolved(*name, |n| write!(f, "<lambda {n}>"))
1606                } else {
1607                    write!(f, "<lambda>")
1608                }
1609            }
1610            ValueView::Macro(m) => with_resolved(m.name, |n| write!(f, "<macro {n}>")),
1611            ValueView::NativeFn(n) => write!(f, "<native-fn {}>", n.name),
1612            ValueView::Prompt(p) => write!(f, "<prompt {} messages>", p.messages.len()),
1613            ValueView::Message(m) => {
1614                write!(f, "<message {} \"{}\">", m.role, truncate(&m.content, 40))
1615            }
1616            ValueView::Conversation(c) => {
1617                write!(f, "<conversation {} messages>", c.messages.len())
1618            }
1619            ValueView::ToolDef(t) => write!(f, "<tool {}>", t.name),
1620            ValueView::Agent(a) => write!(f, "<agent {}>", a.name),
1621            ValueView::Thunk(t) => {
1622                if t.forced.borrow().is_some() {
1623                    write!(f, "<promise (forced)>")
1624                } else {
1625                    write!(f, "<promise>")
1626                }
1627            }
1628            ValueView::Record(r) => {
1629                with_resolved(r.type_tag, |tag| write!(f, "#<record {tag}"))?;
1630                for field in &r.fields {
1631                    write!(f, " {field}")?;
1632                }
1633                write!(f, ">")
1634            }
1635            ValueView::Bytevector(bv) => {
1636                write!(f, "#u8(")?;
1637                for (i, byte) in bv.iter().enumerate() {
1638                    if i > 0 {
1639                        write!(f, " ")?;
1640                    }
1641                    write!(f, "{byte}")?;
1642                }
1643                write!(f, ")")
1644            }
1645            ValueView::MultiMethod(m) => with_resolved(m.name, |n| write!(f, "<multimethod {n}>")),
1646        }
1647    }
1648}
1649
1650// ── Pretty-print ──────────────────────────────────────────────────
1651
1652/// Pretty-print a value with line breaks and indentation when the compact
1653/// representation exceeds `max_width` columns.  Small values that fit in
1654/// one line are returned in the normal compact format.
1655pub fn pretty_print(value: &Value, max_width: usize) -> String {
1656    let compact = format!("{value}");
1657    if compact.len() <= max_width {
1658        return compact;
1659    }
1660    let mut buf = String::new();
1661    pp_value(value, 0, max_width, &mut buf);
1662    buf
1663}
1664
1665/// Render `value` into `buf` at the given `indent` level.  If the compact
1666/// form fits in `max_width - indent` columns we use it; otherwise we break
1667/// the container across multiple lines.
1668fn pp_value(value: &Value, indent: usize, max_width: usize, buf: &mut String) {
1669    let compact = format!("{value}");
1670    let remaining = max_width.saturating_sub(indent);
1671    if compact.len() <= remaining {
1672        buf.push_str(&compact);
1673        return;
1674    }
1675
1676    match value.view() {
1677        ValueView::List(items) => {
1678            pp_seq(items.iter(), '(', ')', indent, max_width, buf);
1679        }
1680        ValueView::Vector(items) => {
1681            pp_seq(items.iter(), '[', ']', indent, max_width, buf);
1682        }
1683        ValueView::Map(map) => {
1684            pp_map(
1685                map.iter().map(|(k, v)| (k.clone(), v.clone())),
1686                indent,
1687                max_width,
1688                buf,
1689            );
1690        }
1691        ValueView::HashMap(map) => {
1692            let mut entries: Vec<_> = map.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
1693            entries.sort_by(|(k1, _), (k2, _)| k1.cmp(k2));
1694            pp_map(entries.into_iter(), indent, max_width, buf);
1695        }
1696        _ => buf.push_str(&compact),
1697    }
1698}
1699
1700/// Pretty-print a list or vector.
1701fn pp_seq<'a>(
1702    items: impl Iterator<Item = &'a Value>,
1703    open: char,
1704    close: char,
1705    indent: usize,
1706    max_width: usize,
1707    buf: &mut String,
1708) {
1709    buf.push(open);
1710    let child_indent = indent + 1;
1711    let pad = " ".repeat(child_indent);
1712    for (i, item) in items.enumerate() {
1713        if i > 0 {
1714            buf.push('\n');
1715            buf.push_str(&pad);
1716        }
1717        pp_value(item, child_indent, max_width, buf);
1718    }
1719    buf.push(close);
1720}
1721
1722/// Pretty-print a map (BTreeMap or HashMap).
1723fn pp_map(
1724    entries: impl Iterator<Item = (Value, Value)>,
1725    indent: usize,
1726    max_width: usize,
1727    buf: &mut String,
1728) {
1729    buf.push('{');
1730    let child_indent = indent + 1;
1731    let pad = " ".repeat(child_indent);
1732    for (i, (k, v)) in entries.enumerate() {
1733        if i > 0 {
1734            buf.push('\n');
1735            buf.push_str(&pad);
1736        }
1737        // Key is always compact
1738        let key_str = format!("{k}");
1739        buf.push_str(&key_str);
1740
1741        // Check if the value fits inline after the key
1742        let inline_indent = child_indent + key_str.len() + 1;
1743        let compact_val = format!("{v}");
1744        let remaining = max_width.saturating_sub(inline_indent);
1745
1746        if compact_val.len() <= remaining {
1747            // Fits inline
1748            buf.push(' ');
1749            buf.push_str(&compact_val);
1750        } else if is_compound(&v) {
1751            // Complex value: break to next line indented 2 from key
1752            let nested_indent = child_indent + 2;
1753            let nested_pad = " ".repeat(nested_indent);
1754            buf.push('\n');
1755            buf.push_str(&nested_pad);
1756            pp_value(&v, nested_indent, max_width, buf);
1757        } else {
1758            // Simple value that's just long: keep inline
1759            buf.push(' ');
1760            buf.push_str(&compact_val);
1761        }
1762    }
1763    buf.push('}');
1764}
1765
1766/// Check whether a value is a compound container (list, vector, map, hashmap).
1767fn is_compound(value: &Value) -> bool {
1768    matches!(
1769        value.view(),
1770        ValueView::List(_) | ValueView::Vector(_) | ValueView::Map(_) | ValueView::HashMap(_)
1771    )
1772}
1773
1774// ── Debug ─────────────────────────────────────────────────────────
1775
1776impl fmt::Debug for Value {
1777    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1778        match self.view() {
1779            ValueView::Nil => write!(f, "Nil"),
1780            ValueView::Bool(b) => write!(f, "Bool({b})"),
1781            ValueView::Int(n) => write!(f, "Int({n})"),
1782            ValueView::Float(n) => write!(f, "Float({n})"),
1783            ValueView::String(s) => write!(f, "String({:?})", &**s),
1784            ValueView::Symbol(s) => write!(f, "Symbol({})", resolve(s)),
1785            ValueView::Keyword(s) => write!(f, "Keyword({})", resolve(s)),
1786            ValueView::Char(c) => write!(f, "Char({c:?})"),
1787            ValueView::List(items) => write!(f, "List({items:?})"),
1788            ValueView::Vector(items) => write!(f, "Vector({items:?})"),
1789            ValueView::Map(map) => write!(f, "Map({map:?})"),
1790            ValueView::HashMap(map) => write!(f, "HashMap({map:?})"),
1791            ValueView::Lambda(l) => write!(f, "{l:?}"),
1792            ValueView::Macro(m) => write!(f, "{m:?}"),
1793            ValueView::NativeFn(n) => write!(f, "{n:?}"),
1794            ValueView::Prompt(p) => write!(f, "{p:?}"),
1795            ValueView::Message(m) => write!(f, "{m:?}"),
1796            ValueView::Conversation(c) => write!(f, "{c:?}"),
1797            ValueView::ToolDef(t) => write!(f, "{t:?}"),
1798            ValueView::Agent(a) => write!(f, "{a:?}"),
1799            ValueView::Thunk(t) => write!(f, "{t:?}"),
1800            ValueView::Record(r) => write!(f, "{r:?}"),
1801            ValueView::Bytevector(bv) => write!(f, "Bytevector({bv:?})"),
1802            ValueView::MultiMethod(m) => write!(f, "{m:?}"),
1803        }
1804    }
1805}
1806
1807// ── Env ───────────────────────────────────────────────────────────
1808
1809/// A Sema environment: a chain of scopes with bindings.
1810#[derive(Debug, Clone)]
1811pub struct Env {
1812    pub bindings: Rc<RefCell<SpurMap<Spur, Value>>>,
1813    pub parent: Option<Rc<Env>>,
1814    pub version: Cell<u64>,
1815}
1816
1817impl Env {
1818    pub fn new() -> Self {
1819        Env {
1820            bindings: Rc::new(RefCell::new(SpurMap::new())),
1821            parent: None,
1822            version: Cell::new(0),
1823        }
1824    }
1825
1826    pub fn with_parent(parent: Rc<Env>) -> Self {
1827        Env {
1828            bindings: Rc::new(RefCell::new(SpurMap::new())),
1829            parent: Some(parent),
1830            version: Cell::new(0),
1831        }
1832    }
1833
1834    fn bump_version(&self) {
1835        self.version.set(self.version.get().wrapping_add(1));
1836    }
1837
1838    pub fn get(&self, name: Spur) -> Option<Value> {
1839        if let Some(val) = self.bindings.borrow().get(&name) {
1840            Some(val.clone())
1841        } else if let Some(parent) = &self.parent {
1842            parent.get(name)
1843        } else {
1844            None
1845        }
1846    }
1847
1848    pub fn get_str(&self, name: &str) -> Option<Value> {
1849        self.get(intern(name))
1850    }
1851
1852    pub fn set(&self, name: Spur, val: Value) {
1853        self.bindings.borrow_mut().insert(name, val);
1854        self.bump_version();
1855    }
1856
1857    pub fn set_str(&self, name: &str, val: Value) {
1858        self.set(intern(name), val);
1859    }
1860
1861    /// Update a binding that already exists in the current scope.
1862    pub fn update(&self, name: Spur, val: Value) {
1863        let mut bindings = self.bindings.borrow_mut();
1864        if let Some(entry) = bindings.get_mut(&name) {
1865            *entry = val;
1866        } else {
1867            bindings.insert(name, val);
1868        }
1869        drop(bindings);
1870        self.bump_version();
1871    }
1872
1873    /// Remove and return a binding from the current scope only.
1874    pub fn take(&self, name: Spur) -> Option<Value> {
1875        let result = self.bindings.borrow_mut().remove(&name);
1876        if result.is_some() {
1877            self.bump_version();
1878        }
1879        result
1880    }
1881
1882    /// Remove and return a binding from any scope in the parent chain.
1883    pub fn take_anywhere(&self, name: Spur) -> Option<Value> {
1884        if let Some(val) = self.bindings.borrow_mut().remove(&name) {
1885            self.bump_version();
1886            Some(val)
1887        } else if let Some(parent) = &self.parent {
1888            parent.take_anywhere(name)
1889        } else {
1890            None
1891        }
1892    }
1893
1894    /// Set a variable in the scope where it's defined (for set!).
1895    pub fn set_existing(&self, name: Spur, val: Value) -> bool {
1896        let mut bindings = self.bindings.borrow_mut();
1897        if let Some(entry) = bindings.get_mut(&name) {
1898            *entry = val;
1899            drop(bindings);
1900            self.bump_version();
1901            true
1902        } else {
1903            drop(bindings);
1904            if let Some(parent) = &self.parent {
1905                parent.set_existing(name, val)
1906            } else {
1907                false
1908            }
1909        }
1910    }
1911
1912    /// Collect all bound variable names across all scopes (for suggestions).
1913    pub fn all_names(&self) -> Vec<Spur> {
1914        let mut names: Vec<Spur> = self.bindings.borrow().keys().copied().collect();
1915        if let Some(parent) = &self.parent {
1916            names.extend(parent.all_names());
1917        }
1918        names.sort_unstable();
1919        names.dedup();
1920        names
1921    }
1922}
1923
1924impl Default for Env {
1925    fn default() -> Self {
1926        Self::new()
1927    }
1928}
1929
1930// ── Tests ─────────────────────────────────────────────────────────
1931
1932#[cfg(test)]
1933mod tests {
1934    use super::*;
1935
1936    #[test]
1937    fn test_size_of_value() {
1938        assert_eq!(std::mem::size_of::<Value>(), 8);
1939    }
1940
1941    #[test]
1942    fn test_nil() {
1943        let v = Value::nil();
1944        assert!(v.is_nil());
1945        assert!(!v.is_truthy());
1946        assert_eq!(v.type_name(), "nil");
1947        assert_eq!(format!("{v}"), "nil");
1948    }
1949
1950    #[test]
1951    fn test_bool() {
1952        let t = Value::bool(true);
1953        let f = Value::bool(false);
1954        assert!(t.is_truthy());
1955        assert!(!f.is_truthy());
1956        assert_eq!(t.as_bool(), Some(true));
1957        assert_eq!(f.as_bool(), Some(false));
1958        assert_eq!(format!("{t}"), "#t");
1959        assert_eq!(format!("{f}"), "#f");
1960    }
1961
1962    #[test]
1963    fn test_small_int() {
1964        let v = Value::int(42);
1965        assert_eq!(v.as_int(), Some(42));
1966        assert_eq!(v.type_name(), "int");
1967        assert_eq!(format!("{v}"), "42");
1968
1969        let neg = Value::int(-100);
1970        assert_eq!(neg.as_int(), Some(-100));
1971        assert_eq!(format!("{neg}"), "-100");
1972
1973        let zero = Value::int(0);
1974        assert_eq!(zero.as_int(), Some(0));
1975    }
1976
1977    #[test]
1978    fn test_small_int_boundaries() {
1979        let max = Value::int(SMALL_INT_MAX);
1980        assert_eq!(max.as_int(), Some(SMALL_INT_MAX));
1981
1982        let min = Value::int(SMALL_INT_MIN);
1983        assert_eq!(min.as_int(), Some(SMALL_INT_MIN));
1984    }
1985
1986    #[test]
1987    fn test_big_int() {
1988        let big = Value::int(i64::MAX);
1989        assert_eq!(big.as_int(), Some(i64::MAX));
1990        assert_eq!(big.type_name(), "int");
1991
1992        let big_neg = Value::int(i64::MIN);
1993        assert_eq!(big_neg.as_int(), Some(i64::MIN));
1994
1995        // Just outside small range
1996        let just_over = Value::int(SMALL_INT_MAX + 1);
1997        assert_eq!(just_over.as_int(), Some(SMALL_INT_MAX + 1));
1998    }
1999
2000    #[test]
2001    fn test_float() {
2002        let v = Value::float(3.14);
2003        assert_eq!(v.as_float(), Some(3.14));
2004        assert_eq!(v.type_name(), "float");
2005
2006        let neg = Value::float(-0.5);
2007        assert_eq!(neg.as_float(), Some(-0.5));
2008
2009        let inf = Value::float(f64::INFINITY);
2010        assert_eq!(inf.as_float(), Some(f64::INFINITY));
2011
2012        let neg_inf = Value::float(f64::NEG_INFINITY);
2013        assert_eq!(neg_inf.as_float(), Some(f64::NEG_INFINITY));
2014    }
2015
2016    #[test]
2017    fn test_float_nan() {
2018        let nan = Value::float(f64::NAN);
2019        let f = nan.as_float().unwrap();
2020        assert!(f.is_nan());
2021    }
2022
2023    #[test]
2024    fn test_string() {
2025        let v = Value::string("hello");
2026        assert_eq!(v.as_str(), Some("hello"));
2027        assert_eq!(v.type_name(), "string");
2028        assert_eq!(format!("{v}"), "\"hello\"");
2029    }
2030
2031    #[test]
2032    fn test_symbol() {
2033        let v = Value::symbol("foo");
2034        assert!(v.as_symbol_spur().is_some());
2035        assert_eq!(v.as_symbol(), Some("foo".to_string()));
2036        assert_eq!(v.type_name(), "symbol");
2037        assert_eq!(format!("{v}"), "foo");
2038    }
2039
2040    #[test]
2041    fn test_keyword() {
2042        let v = Value::keyword("bar");
2043        assert!(v.as_keyword_spur().is_some());
2044        assert_eq!(v.as_keyword(), Some("bar".to_string()));
2045        assert_eq!(v.type_name(), "keyword");
2046        assert_eq!(format!("{v}"), ":bar");
2047    }
2048
2049    #[test]
2050    fn test_char() {
2051        let v = Value::char('λ');
2052        assert_eq!(v.as_char(), Some('λ'));
2053        assert_eq!(v.type_name(), "char");
2054    }
2055
2056    #[test]
2057    fn test_list() {
2058        let v = Value::list(vec![Value::int(1), Value::int(2), Value::int(3)]);
2059        assert_eq!(v.as_list().unwrap().len(), 3);
2060        assert_eq!(v.type_name(), "list");
2061        assert_eq!(format!("{v}"), "(1 2 3)");
2062    }
2063
2064    #[test]
2065    fn test_clone_immediate() {
2066        let v = Value::int(42);
2067        let v2 = v.clone();
2068        assert_eq!(v.as_int(), v2.as_int());
2069    }
2070
2071    #[test]
2072    fn test_clone_heap() {
2073        let v = Value::string("hello");
2074        let v2 = v.clone();
2075        assert_eq!(v.as_str(), v2.as_str());
2076        // Both should work after clone
2077        assert_eq!(format!("{v}"), format!("{v2}"));
2078    }
2079
2080    #[test]
2081    fn test_equality() {
2082        assert_eq!(Value::int(42), Value::int(42));
2083        assert_ne!(Value::int(42), Value::int(43));
2084        assert_eq!(Value::nil(), Value::nil());
2085        assert_eq!(Value::bool(true), Value::bool(true));
2086        assert_ne!(Value::bool(true), Value::bool(false));
2087        assert_eq!(Value::string("a"), Value::string("a"));
2088        assert_ne!(Value::string("a"), Value::string("b"));
2089        assert_eq!(Value::symbol("x"), Value::symbol("x"));
2090    }
2091
2092    #[test]
2093    fn test_big_int_equality() {
2094        assert_eq!(Value::int(i64::MAX), Value::int(i64::MAX));
2095        assert_ne!(Value::int(i64::MAX), Value::int(i64::MIN));
2096    }
2097
2098    #[test]
2099    fn test_view_pattern_matching() {
2100        let v = Value::int(42);
2101        match v.view() {
2102            ValueView::Int(n) => assert_eq!(n, 42),
2103            _ => panic!("expected int"),
2104        }
2105
2106        let v = Value::string("hello");
2107        match v.view() {
2108            ValueView::String(s) => assert_eq!(&**s, "hello"),
2109            _ => panic!("expected string"),
2110        }
2111    }
2112
2113    #[test]
2114    fn test_env() {
2115        let env = Env::new();
2116        env.set_str("x", Value::int(42));
2117        assert_eq!(env.get_str("x"), Some(Value::int(42)));
2118    }
2119
2120    #[test]
2121    fn test_native_fn_simple() {
2122        let f = NativeFn::simple("add1", |args| Ok(args[0].clone()));
2123        let ctx = EvalContext::new();
2124        assert!((f.func)(&ctx, &[Value::int(42)]).is_ok());
2125    }
2126
2127    #[test]
2128    fn test_native_fn_with_ctx() {
2129        let f = NativeFn::with_ctx("get-depth", |ctx, _args| {
2130            Ok(Value::int(ctx.eval_depth.get() as i64))
2131        });
2132        let ctx = EvalContext::new();
2133        assert_eq!((f.func)(&ctx, &[]).unwrap(), Value::int(0));
2134    }
2135
2136    #[test]
2137    fn test_drop_doesnt_leak() {
2138        // Create and drop many heap values to check for leaks
2139        for _ in 0..10000 {
2140            let _ = Value::string("test");
2141            let _ = Value::list(vec![Value::int(1), Value::int(2)]);
2142            let _ = Value::int(i64::MAX); // big int
2143        }
2144    }
2145
2146    #[test]
2147    fn test_is_truthy() {
2148        assert!(!Value::nil().is_truthy());
2149        assert!(!Value::bool(false).is_truthy());
2150        assert!(Value::bool(true).is_truthy());
2151        assert!(Value::int(0).is_truthy());
2152        assert!(Value::int(1).is_truthy());
2153        assert!(Value::string("").is_truthy());
2154        assert!(Value::list(vec![]).is_truthy());
2155    }
2156
2157    #[test]
2158    fn test_as_float_from_int() {
2159        assert_eq!(Value::int(42).as_float(), Some(42.0));
2160        assert_eq!(Value::float(3.14).as_float(), Some(3.14));
2161    }
2162
2163    #[test]
2164    fn test_next_gensym_unique() {
2165        let a = next_gensym("x");
2166        let b = next_gensym("x");
2167        let c = next_gensym("y");
2168        assert_ne!(a, b);
2169        assert_ne!(a, c);
2170        assert_ne!(b, c);
2171        assert!(a.starts_with("x__"));
2172        assert!(b.starts_with("x__"));
2173        assert!(c.starts_with("y__"));
2174    }
2175
2176    #[test]
2177    fn test_next_gensym_counter_does_not_panic_near_max() {
2178        // Set counter near u64::MAX and verify no panic on wrapping
2179        GENSYM_COUNTER.with(|c| c.set(u64::MAX - 1));
2180        let a = next_gensym("z");
2181        assert!(a.contains(&(u64::MAX - 1).to_string()));
2182        // This would panic with `val + 1` instead of wrapping_add
2183        let b = next_gensym("z");
2184        assert!(b.contains(&u64::MAX.to_string()));
2185        // Wraps to 0
2186        let c = next_gensym("z");
2187        assert!(c.contains("__0"));
2188    }
2189}