Skip to main content

sema_core/
value.rs

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