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