Skip to main content

sema_core/
value.rs

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