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