Skip to main content

sema_core/
value.rs

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