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