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/// Compare two Spurs by their resolved string content (lexicographic).
58pub fn compare_spurs(a: Spur, b: Spur) -> std::cmp::Ordering {
59    if a == b {
60        return std::cmp::Ordering::Equal;
61    }
62    INTERNER.with(|r| {
63        let interner = r.borrow();
64        interner.resolve(&a).cmp(interner.resolve(&b))
65    })
66}
67
68// ── Supporting types (unchanged public API) ───────────────────────
69
70/// A native function callable from Sema.
71pub type NativeFnInner = dyn Fn(&EvalContext, &[Value]) -> Result<Value, SemaError>;
72
73pub struct NativeFn {
74    pub name: String,
75    pub func: Box<NativeFnInner>,
76    pub payload: Option<Rc<dyn Any>>,
77}
78
79impl NativeFn {
80    pub fn simple(
81        name: impl Into<String>,
82        f: impl Fn(&[Value]) -> Result<Value, SemaError> + 'static,
83    ) -> Self {
84        Self {
85            name: name.into(),
86            func: Box::new(move |_ctx, args| f(args)),
87            payload: None,
88        }
89    }
90
91    pub fn with_ctx(
92        name: impl Into<String>,
93        f: impl Fn(&EvalContext, &[Value]) -> Result<Value, SemaError> + 'static,
94    ) -> Self {
95        Self {
96            name: name.into(),
97            func: Box::new(f),
98            payload: None,
99        }
100    }
101
102    pub fn with_payload(
103        name: impl Into<String>,
104        payload: Rc<dyn Any>,
105        f: impl Fn(&EvalContext, &[Value]) -> Result<Value, SemaError> + 'static,
106    ) -> Self {
107        Self {
108            name: name.into(),
109            func: Box::new(f),
110            payload: Some(payload),
111        }
112    }
113}
114
115impl fmt::Debug for NativeFn {
116    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117        write!(f, "<native-fn {}>", self.name)
118    }
119}
120
121/// A user-defined lambda.
122#[derive(Debug, Clone)]
123pub struct Lambda {
124    pub params: Vec<Spur>,
125    pub rest_param: Option<Spur>,
126    pub body: Vec<Value>,
127    pub env: Env,
128    pub name: Option<Spur>,
129}
130
131/// A macro definition.
132#[derive(Debug, Clone)]
133pub struct Macro {
134    pub params: Vec<Spur>,
135    pub rest_param: Option<Spur>,
136    pub body: Vec<Value>,
137    pub name: Spur,
138}
139
140/// A lazy promise: delay/force with memoization.
141pub struct Thunk {
142    pub body: Value,
143    pub forced: RefCell<Option<Value>>,
144}
145
146impl fmt::Debug for Thunk {
147    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148        if self.forced.borrow().is_some() {
149            write!(f, "<promise (forced)>")
150        } else {
151            write!(f, "<promise>")
152        }
153    }
154}
155
156impl Clone for Thunk {
157    fn clone(&self) -> Self {
158        Thunk {
159            body: self.body.clone(),
160            forced: RefCell::new(self.forced.borrow().clone()),
161        }
162    }
163}
164
165/// A record: tagged product type created by define-record-type.
166#[derive(Debug, Clone)]
167pub struct Record {
168    pub type_tag: Spur,
169    pub fields: Vec<Value>,
170}
171
172/// A message role in a conversation.
173#[derive(Debug, Clone, PartialEq, Eq)]
174pub enum Role {
175    System,
176    User,
177    Assistant,
178    Tool,
179}
180
181impl fmt::Display for Role {
182    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183        match self {
184            Role::System => write!(f, "system"),
185            Role::User => write!(f, "user"),
186            Role::Assistant => write!(f, "assistant"),
187            Role::Tool => write!(f, "tool"),
188        }
189    }
190}
191
192/// A base64-encoded image attachment.
193#[derive(Debug, Clone)]
194pub struct ImageAttachment {
195    pub data: String,
196    pub media_type: String,
197}
198
199/// A single message in a conversation.
200#[derive(Debug, Clone)]
201pub struct Message {
202    pub role: Role,
203    pub content: String,
204    /// Optional image attachments (base64-encoded).
205    pub images: Vec<ImageAttachment>,
206}
207
208/// A prompt: a structured list of messages.
209#[derive(Debug, Clone)]
210pub struct Prompt {
211    pub messages: Vec<Message>,
212}
213
214/// A conversation: immutable history + provider config.
215#[derive(Debug, Clone)]
216pub struct Conversation {
217    pub messages: Vec<Message>,
218    pub model: String,
219    pub metadata: BTreeMap<String, String>,
220}
221
222/// A tool definition for LLM function calling.
223#[derive(Debug, Clone)]
224pub struct ToolDefinition {
225    pub name: String,
226    pub description: String,
227    pub parameters: Value,
228    pub handler: Value,
229}
230
231/// An agent: system prompt + tools + config for autonomous loops.
232#[derive(Debug, Clone)]
233pub struct Agent {
234    pub name: String,
235    pub system: String,
236    pub tools: Vec<Value>,
237    pub max_turns: usize,
238    pub model: String,
239}
240
241// ── NaN-boxing constants ──────────────────────────────────────────
242
243// IEEE 754 double layout:
244//   bit 63:     sign
245//   bits 62-52: exponent (11 bits)
246//   bits 51-0:  mantissa (52 bits), bit 51 = quiet NaN bit
247//
248// Boxed (non-float) values use: sign=1, exp=all 1s, quiet=1
249//   Then bits 50-45 = TAG (6 bits), bits 44-0 = PAYLOAD (45 bits)
250
251/// Mask for checking if a value is boxed (sign + exponent + quiet bit)
252const BOX_MASK: u64 = 0xFFF8_0000_0000_0000;
253
254/// The 45-bit payload mask
255const PAYLOAD_MASK: u64 = (1u64 << 45) - 1; // 0x1FFF_FFFF_FFFF
256
257/// Sign-extension bit for 45-bit signed integers
258const INT_SIGN_BIT: u64 = 1u64 << 44;
259
260/// Canonical quiet NaN (sign=0) — used for NaN float values to avoid collision with boxed
261const CANONICAL_NAN: u64 = 0x7FF8_0000_0000_0000;
262
263// Tags (6 bits, encoded in bits 50-45)
264const TAG_NIL: u64 = 0;
265const TAG_FALSE: u64 = 1;
266const TAG_TRUE: u64 = 2;
267const TAG_INT_SMALL: u64 = 3;
268const TAG_CHAR: u64 = 4;
269const TAG_SYMBOL: u64 = 5;
270const TAG_KEYWORD: u64 = 6;
271const TAG_INT_BIG: u64 = 7;
272const TAG_STRING: u64 = 8;
273const TAG_LIST: u64 = 9;
274const TAG_VECTOR: u64 = 10;
275const TAG_MAP: u64 = 11;
276const TAG_HASHMAP: u64 = 12;
277const TAG_LAMBDA: u64 = 13;
278const TAG_MACRO: u64 = 14;
279const TAG_NATIVE_FN: u64 = 15;
280const TAG_PROMPT: u64 = 16;
281const TAG_MESSAGE: u64 = 17;
282const TAG_CONVERSATION: u64 = 18;
283const TAG_TOOL_DEF: u64 = 19;
284const TAG_AGENT: u64 = 20;
285const TAG_THUNK: u64 = 21;
286const TAG_RECORD: u64 = 22;
287const TAG_BYTEVECTOR: u64 = 23;
288
289/// Small-int range: [-2^44, 2^44 - 1] = [-17_592_186_044_416, +17_592_186_044_415]
290const SMALL_INT_MIN: i64 = -(1i64 << 44);
291const SMALL_INT_MAX: i64 = (1i64 << 44) - 1;
292
293// ── Public NaN-boxing constants for VM use ────────────────────────
294
295/// Tag + box combined mask: upper 19 bits (sign + exponent + quiet + 6-bit tag).
296pub const NAN_TAG_MASK: u64 = BOX_MASK | (0x3F << 45); // 0xFFFF_E000_0000_0000
297
298/// The expected upper bits for a small int value: BOX_MASK | (TAG_INT_SMALL << 45).
299pub const NAN_INT_SMALL_PATTERN: u64 = BOX_MASK | (TAG_INT_SMALL << 45);
300
301/// Public payload mask (45 bits).
302pub const NAN_PAYLOAD_MASK: u64 = PAYLOAD_MASK;
303
304/// Sign bit within the 45-bit payload (bit 44) — for sign-extending small ints.
305pub const NAN_INT_SIGN_BIT: u64 = INT_SIGN_BIT;
306
307// ── Helpers for encoding/decoding ─────────────────────────────────
308
309#[inline(always)]
310fn make_boxed(tag: u64, payload: u64) -> u64 {
311    BOX_MASK | (tag << 45) | (payload & PAYLOAD_MASK)
312}
313
314#[inline(always)]
315fn is_boxed(bits: u64) -> bool {
316    (bits & BOX_MASK) == BOX_MASK
317}
318
319#[inline(always)]
320fn get_tag(bits: u64) -> u64 {
321    (bits >> 45) & 0x3F
322}
323
324#[inline(always)]
325fn get_payload(bits: u64) -> u64 {
326    bits & PAYLOAD_MASK
327}
328
329#[inline(always)]
330fn ptr_to_payload(ptr: *const u8) -> u64 {
331    let raw = ptr as u64;
332    debug_assert!(raw & 0x7 == 0, "pointer not 8-byte aligned: 0x{:x}", raw);
333    debug_assert!(
334        raw >> 48 == 0,
335        "pointer exceeds 48-bit VA space: 0x{:x}",
336        raw
337    );
338    raw >> 3
339}
340
341#[inline(always)]
342fn payload_to_ptr(payload: u64) -> *const u8 {
343    (payload << 3) as *const u8
344}
345
346// ── ValueView: pattern-matching enum ──────────────────────────────
347
348/// A view of a NaN-boxed Value for pattern matching.
349/// Returned by `Value::view()`. Heap types hold Rc (refcount bumped).
350pub enum ValueView {
351    Nil,
352    Bool(bool),
353    Int(i64),
354    Float(f64),
355    String(Rc<String>),
356    Symbol(Spur),
357    Keyword(Spur),
358    Char(char),
359    List(Rc<Vec<Value>>),
360    Vector(Rc<Vec<Value>>),
361    Map(Rc<BTreeMap<Value, Value>>),
362    HashMap(Rc<hashbrown::HashMap<Value, Value>>),
363    Lambda(Rc<Lambda>),
364    Macro(Rc<Macro>),
365    NativeFn(Rc<NativeFn>),
366    Prompt(Rc<Prompt>),
367    Message(Rc<Message>),
368    Conversation(Rc<Conversation>),
369    ToolDef(Rc<ToolDefinition>),
370    Agent(Rc<Agent>),
371    Thunk(Rc<Thunk>),
372    Record(Rc<Record>),
373    Bytevector(Rc<Vec<u8>>),
374}
375
376// ── The NaN-boxed Value type ──────────────────────────────────────
377
378/// The core Value type for all Sema data.
379/// NaN-boxed: stored as 8 bytes. Floats stored directly,
380/// everything else encoded in quiet-NaN payload space.
381#[repr(transparent)]
382pub struct Value(u64);
383
384// ── Constructors ──────────────────────────────────────────────────
385
386impl Value {
387    // -- Immediate constructors --
388
389    pub const NIL: Value = Value(make_boxed_const(TAG_NIL, 0));
390    pub const TRUE: Value = Value(make_boxed_const(TAG_TRUE, 0));
391    pub const FALSE: Value = Value(make_boxed_const(TAG_FALSE, 0));
392
393    #[inline(always)]
394    pub fn nil() -> Value {
395        Value::NIL
396    }
397
398    #[inline(always)]
399    pub fn bool(b: bool) -> Value {
400        if b {
401            Value::TRUE
402        } else {
403            Value::FALSE
404        }
405    }
406
407    #[inline(always)]
408    pub fn int(n: i64) -> Value {
409        if (SMALL_INT_MIN..=SMALL_INT_MAX).contains(&n) {
410            // Encode as small int (45-bit two's complement)
411            let payload = (n as u64) & PAYLOAD_MASK;
412            Value(make_boxed(TAG_INT_SMALL, payload))
413        } else {
414            // Out of range: heap-allocate
415            let rc = Rc::new(n);
416            let ptr = Rc::into_raw(rc) as *const u8;
417            Value(make_boxed(TAG_INT_BIG, ptr_to_payload(ptr)))
418        }
419    }
420
421    #[inline(always)]
422    pub fn float(f: f64) -> Value {
423        let bits = f.to_bits();
424        if f.is_nan() {
425            // Canonicalize NaN to avoid collision with boxed patterns
426            Value(CANONICAL_NAN)
427        } else {
428            // Check: a non-NaN float could still have the BOX_MASK pattern
429            // This happens for negative infinity and some subnormals — but
430            // negative infinity is 0xFFF0_0000_0000_0000 which does NOT match
431            // BOX_MASK (0xFFF8...) because bit 51 (quiet) is 0.
432            // In IEEE 754, the only values with all exponent bits set AND quiet bit set
433            // are quiet NaNs, which we've already canonicalized above.
434            debug_assert!(
435                !is_boxed(bits),
436                "non-NaN float collides with boxed pattern: {:?} = 0x{:016x}",
437                f,
438                bits
439            );
440            Value(bits)
441        }
442    }
443
444    #[inline(always)]
445    pub fn char(c: char) -> Value {
446        Value(make_boxed(TAG_CHAR, c as u64))
447    }
448
449    #[inline(always)]
450    pub fn symbol_from_spur(spur: Spur) -> Value {
451        let bits: u32 = unsafe { std::mem::transmute(spur) };
452        Value(make_boxed(TAG_SYMBOL, bits as u64))
453    }
454
455    pub fn symbol(s: &str) -> Value {
456        Value::symbol_from_spur(intern(s))
457    }
458
459    #[inline(always)]
460    pub fn keyword_from_spur(spur: Spur) -> Value {
461        let bits: u32 = unsafe { std::mem::transmute(spur) };
462        Value(make_boxed(TAG_KEYWORD, bits as u64))
463    }
464
465    pub fn keyword(s: &str) -> Value {
466        Value::keyword_from_spur(intern(s))
467    }
468
469    // -- Heap constructors --
470
471    fn from_rc_ptr<T>(tag: u64, rc: Rc<T>) -> Value {
472        let ptr = Rc::into_raw(rc) as *const u8;
473        Value(make_boxed(tag, ptr_to_payload(ptr)))
474    }
475
476    pub fn string(s: &str) -> Value {
477        Value::from_rc_ptr(TAG_STRING, Rc::new(s.to_string()))
478    }
479
480    pub fn string_from_rc(rc: Rc<String>) -> Value {
481        Value::from_rc_ptr(TAG_STRING, rc)
482    }
483
484    pub fn list(v: Vec<Value>) -> Value {
485        Value::from_rc_ptr(TAG_LIST, Rc::new(v))
486    }
487
488    pub fn list_from_rc(rc: Rc<Vec<Value>>) -> Value {
489        Value::from_rc_ptr(TAG_LIST, rc)
490    }
491
492    pub fn vector(v: Vec<Value>) -> Value {
493        Value::from_rc_ptr(TAG_VECTOR, Rc::new(v))
494    }
495
496    pub fn vector_from_rc(rc: Rc<Vec<Value>>) -> Value {
497        Value::from_rc_ptr(TAG_VECTOR, rc)
498    }
499
500    pub fn map(m: BTreeMap<Value, Value>) -> Value {
501        Value::from_rc_ptr(TAG_MAP, Rc::new(m))
502    }
503
504    pub fn map_from_rc(rc: Rc<BTreeMap<Value, Value>>) -> Value {
505        Value::from_rc_ptr(TAG_MAP, rc)
506    }
507
508    pub fn hashmap(entries: Vec<(Value, Value)>) -> Value {
509        let map: hashbrown::HashMap<Value, Value> = entries.into_iter().collect();
510        Value::from_rc_ptr(TAG_HASHMAP, Rc::new(map))
511    }
512
513    pub fn hashmap_from_rc(rc: Rc<hashbrown::HashMap<Value, Value>>) -> Value {
514        Value::from_rc_ptr(TAG_HASHMAP, rc)
515    }
516
517    pub fn lambda(l: Lambda) -> Value {
518        Value::from_rc_ptr(TAG_LAMBDA, Rc::new(l))
519    }
520
521    pub fn lambda_from_rc(rc: Rc<Lambda>) -> Value {
522        Value::from_rc_ptr(TAG_LAMBDA, rc)
523    }
524
525    pub fn macro_val(m: Macro) -> Value {
526        Value::from_rc_ptr(TAG_MACRO, Rc::new(m))
527    }
528
529    pub fn macro_from_rc(rc: Rc<Macro>) -> Value {
530        Value::from_rc_ptr(TAG_MACRO, rc)
531    }
532
533    pub fn native_fn(f: NativeFn) -> Value {
534        Value::from_rc_ptr(TAG_NATIVE_FN, Rc::new(f))
535    }
536
537    pub fn native_fn_from_rc(rc: Rc<NativeFn>) -> Value {
538        Value::from_rc_ptr(TAG_NATIVE_FN, rc)
539    }
540
541    pub fn prompt(p: Prompt) -> Value {
542        Value::from_rc_ptr(TAG_PROMPT, Rc::new(p))
543    }
544
545    pub fn prompt_from_rc(rc: Rc<Prompt>) -> Value {
546        Value::from_rc_ptr(TAG_PROMPT, rc)
547    }
548
549    pub fn message(m: Message) -> Value {
550        Value::from_rc_ptr(TAG_MESSAGE, Rc::new(m))
551    }
552
553    pub fn message_from_rc(rc: Rc<Message>) -> Value {
554        Value::from_rc_ptr(TAG_MESSAGE, rc)
555    }
556
557    pub fn conversation(c: Conversation) -> Value {
558        Value::from_rc_ptr(TAG_CONVERSATION, Rc::new(c))
559    }
560
561    pub fn conversation_from_rc(rc: Rc<Conversation>) -> Value {
562        Value::from_rc_ptr(TAG_CONVERSATION, rc)
563    }
564
565    pub fn tool_def(t: ToolDefinition) -> Value {
566        Value::from_rc_ptr(TAG_TOOL_DEF, Rc::new(t))
567    }
568
569    pub fn tool_def_from_rc(rc: Rc<ToolDefinition>) -> Value {
570        Value::from_rc_ptr(TAG_TOOL_DEF, rc)
571    }
572
573    pub fn agent(a: Agent) -> Value {
574        Value::from_rc_ptr(TAG_AGENT, Rc::new(a))
575    }
576
577    pub fn agent_from_rc(rc: Rc<Agent>) -> Value {
578        Value::from_rc_ptr(TAG_AGENT, rc)
579    }
580
581    pub fn thunk(t: Thunk) -> Value {
582        Value::from_rc_ptr(TAG_THUNK, Rc::new(t))
583    }
584
585    pub fn thunk_from_rc(rc: Rc<Thunk>) -> Value {
586        Value::from_rc_ptr(TAG_THUNK, rc)
587    }
588
589    pub fn record(r: Record) -> Value {
590        Value::from_rc_ptr(TAG_RECORD, Rc::new(r))
591    }
592
593    pub fn record_from_rc(rc: Rc<Record>) -> Value {
594        Value::from_rc_ptr(TAG_RECORD, rc)
595    }
596
597    pub fn bytevector(bytes: Vec<u8>) -> Value {
598        Value::from_rc_ptr(TAG_BYTEVECTOR, Rc::new(bytes))
599    }
600
601    pub fn bytevector_from_rc(rc: Rc<Vec<u8>>) -> Value {
602        Value::from_rc_ptr(TAG_BYTEVECTOR, rc)
603    }
604}
605
606// Const-compatible boxed encoding (no function calls)
607const fn make_boxed_const(tag: u64, payload: u64) -> u64 {
608    BOX_MASK | (tag << 45) | (payload & PAYLOAD_MASK)
609}
610
611// ── Accessors ─────────────────────────────────────────────────────
612
613impl Value {
614    /// Get the raw bits (for debugging/testing).
615    #[inline(always)]
616    pub fn raw_bits(&self) -> u64 {
617        self.0
618    }
619
620    /// Construct a Value from raw NaN-boxed bits.
621    ///
622    /// # Safety
623    ///
624    /// Caller must ensure `bits` represents a valid NaN-boxed value.
625    /// For immediate types (nil, bool, int, symbol, keyword, char), this is always safe.
626    /// For heap-pointer types, the encoded pointer must be valid and have its Rc ownership
627    /// accounted for (i.e., the caller must ensure the refcount is correct).
628    #[inline(always)]
629    pub unsafe fn from_raw_bits(bits: u64) -> Value {
630        Value(bits)
631    }
632
633    /// Get the NaN-boxing tag of a boxed value (0-63).
634    /// Returns `None` for non-boxed values (floats).
635    #[inline(always)]
636    pub fn raw_tag(&self) -> Option<u64> {
637        if is_boxed(self.0) {
638            Some(get_tag(self.0))
639        } else {
640            None
641        }
642    }
643
644    /// Borrow the underlying NativeFn without bumping the Rc refcount.
645    /// SAFETY: The returned reference is valid as long as this Value is alive.
646    #[inline(always)]
647    pub fn as_native_fn_ref(&self) -> Option<&NativeFn> {
648        if is_boxed(self.0) && get_tag(self.0) == TAG_NATIVE_FN {
649            Some(unsafe { self.borrow_ref::<NativeFn>() })
650        } else {
651            None
652        }
653    }
654
655    /// Check if this is a float (non-boxed).
656    #[inline(always)]
657    pub fn is_float(&self) -> bool {
658        !is_boxed(self.0)
659    }
660
661    /// Recover an Rc<T> pointer from the payload WITHOUT consuming ownership.
662    /// This increments the refcount (returns a new Rc).
663    #[inline(always)]
664    unsafe fn get_rc<T>(&self) -> Rc<T> {
665        let payload = get_payload(self.0);
666        let ptr = payload_to_ptr(payload) as *const T;
667        Rc::increment_strong_count(ptr);
668        Rc::from_raw(ptr)
669    }
670
671    /// Borrow the underlying T from a heap-tagged Value.
672    /// SAFETY: caller must ensure the tag matches and T is correct.
673    #[inline(always)]
674    unsafe fn borrow_ref<T>(&self) -> &T {
675        let payload = get_payload(self.0);
676        let ptr = payload_to_ptr(payload) as *const T;
677        &*ptr
678    }
679
680    /// Pattern-match friendly view of this value.
681    /// For heap types, this bumps the Rc refcount.
682    pub fn view(&self) -> ValueView {
683        if !is_boxed(self.0) {
684            return ValueView::Float(f64::from_bits(self.0));
685        }
686        let tag = get_tag(self.0);
687        match tag {
688            TAG_NIL => ValueView::Nil,
689            TAG_FALSE => ValueView::Bool(false),
690            TAG_TRUE => ValueView::Bool(true),
691            TAG_INT_SMALL => {
692                let payload = get_payload(self.0);
693                let val = if payload & INT_SIGN_BIT != 0 {
694                    (payload | !PAYLOAD_MASK) as i64
695                } else {
696                    payload as i64
697                };
698                ValueView::Int(val)
699            }
700            TAG_CHAR => {
701                let payload = get_payload(self.0);
702                ValueView::Char(unsafe { char::from_u32_unchecked(payload as u32) })
703            }
704            TAG_SYMBOL => {
705                let payload = get_payload(self.0);
706                let spur: Spur = unsafe { std::mem::transmute(payload as u32) };
707                ValueView::Symbol(spur)
708            }
709            TAG_KEYWORD => {
710                let payload = get_payload(self.0);
711                let spur: Spur = unsafe { std::mem::transmute(payload as u32) };
712                ValueView::Keyword(spur)
713            }
714            TAG_INT_BIG => {
715                let val = unsafe { *self.borrow_ref::<i64>() };
716                ValueView::Int(val)
717            }
718            TAG_STRING => ValueView::String(unsafe { self.get_rc::<String>() }),
719            TAG_LIST => ValueView::List(unsafe { self.get_rc::<Vec<Value>>() }),
720            TAG_VECTOR => ValueView::Vector(unsafe { self.get_rc::<Vec<Value>>() }),
721            TAG_MAP => ValueView::Map(unsafe { self.get_rc::<BTreeMap<Value, Value>>() }),
722            TAG_HASHMAP => {
723                ValueView::HashMap(unsafe { self.get_rc::<hashbrown::HashMap<Value, Value>>() })
724            }
725            TAG_LAMBDA => ValueView::Lambda(unsafe { self.get_rc::<Lambda>() }),
726            TAG_MACRO => ValueView::Macro(unsafe { self.get_rc::<Macro>() }),
727            TAG_NATIVE_FN => ValueView::NativeFn(unsafe { self.get_rc::<NativeFn>() }),
728            TAG_PROMPT => ValueView::Prompt(unsafe { self.get_rc::<Prompt>() }),
729            TAG_MESSAGE => ValueView::Message(unsafe { self.get_rc::<Message>() }),
730            TAG_CONVERSATION => ValueView::Conversation(unsafe { self.get_rc::<Conversation>() }),
731            TAG_TOOL_DEF => ValueView::ToolDef(unsafe { self.get_rc::<ToolDefinition>() }),
732            TAG_AGENT => ValueView::Agent(unsafe { self.get_rc::<Agent>() }),
733            TAG_THUNK => ValueView::Thunk(unsafe { self.get_rc::<Thunk>() }),
734            TAG_RECORD => ValueView::Record(unsafe { self.get_rc::<Record>() }),
735            TAG_BYTEVECTOR => ValueView::Bytevector(unsafe { self.get_rc::<Vec<u8>>() }),
736            _ => unreachable!("invalid NaN-boxed tag: {}", tag),
737        }
738    }
739
740    // -- Typed accessors (ergonomic, avoid full view match) --
741
742    pub fn type_name(&self) -> &'static str {
743        if !is_boxed(self.0) {
744            return "float";
745        }
746        match get_tag(self.0) {
747            TAG_NIL => "nil",
748            TAG_FALSE | TAG_TRUE => "bool",
749            TAG_INT_SMALL | TAG_INT_BIG => "int",
750            TAG_CHAR => "char",
751            TAG_SYMBOL => "symbol",
752            TAG_KEYWORD => "keyword",
753            TAG_STRING => "string",
754            TAG_LIST => "list",
755            TAG_VECTOR => "vector",
756            TAG_MAP => "map",
757            TAG_HASHMAP => "hashmap",
758            TAG_LAMBDA => "lambda",
759            TAG_MACRO => "macro",
760            TAG_NATIVE_FN => "native-fn",
761            TAG_PROMPT => "prompt",
762            TAG_MESSAGE => "message",
763            TAG_CONVERSATION => "conversation",
764            TAG_TOOL_DEF => "tool",
765            TAG_AGENT => "agent",
766            TAG_THUNK => "promise",
767            TAG_RECORD => "record",
768            TAG_BYTEVECTOR => "bytevector",
769            _ => "unknown",
770        }
771    }
772
773    #[inline(always)]
774    pub fn is_nil(&self) -> bool {
775        self.0 == Value::NIL.0
776    }
777
778    #[inline(always)]
779    pub fn is_truthy(&self) -> bool {
780        self.0 != Value::NIL.0 && self.0 != Value::FALSE.0
781    }
782
783    #[inline(always)]
784    pub fn is_bool(&self) -> bool {
785        self.0 == Value::TRUE.0 || self.0 == Value::FALSE.0
786    }
787
788    #[inline(always)]
789    pub fn is_int(&self) -> bool {
790        is_boxed(self.0) && matches!(get_tag(self.0), TAG_INT_SMALL | TAG_INT_BIG)
791    }
792
793    #[inline(always)]
794    pub fn is_symbol(&self) -> bool {
795        is_boxed(self.0) && get_tag(self.0) == TAG_SYMBOL
796    }
797
798    #[inline(always)]
799    pub fn is_keyword(&self) -> bool {
800        is_boxed(self.0) && get_tag(self.0) == TAG_KEYWORD
801    }
802
803    #[inline(always)]
804    pub fn is_string(&self) -> bool {
805        is_boxed(self.0) && get_tag(self.0) == TAG_STRING
806    }
807
808    #[inline(always)]
809    pub fn is_list(&self) -> bool {
810        is_boxed(self.0) && get_tag(self.0) == TAG_LIST
811    }
812
813    #[inline(always)]
814    pub fn is_pair(&self) -> bool {
815        if let Some(items) = self.as_list() {
816            !items.is_empty()
817        } else {
818            false
819        }
820    }
821
822    #[inline(always)]
823    pub fn is_vector(&self) -> bool {
824        is_boxed(self.0) && get_tag(self.0) == TAG_VECTOR
825    }
826
827    #[inline(always)]
828    pub fn is_map(&self) -> bool {
829        is_boxed(self.0) && matches!(get_tag(self.0), TAG_MAP | TAG_HASHMAP)
830    }
831
832    #[inline(always)]
833    pub fn is_lambda(&self) -> bool {
834        is_boxed(self.0) && get_tag(self.0) == TAG_LAMBDA
835    }
836
837    #[inline(always)]
838    pub fn is_native_fn(&self) -> bool {
839        is_boxed(self.0) && get_tag(self.0) == TAG_NATIVE_FN
840    }
841
842    #[inline(always)]
843    pub fn is_thunk(&self) -> bool {
844        is_boxed(self.0) && get_tag(self.0) == TAG_THUNK
845    }
846
847    #[inline(always)]
848    pub fn is_record(&self) -> bool {
849        is_boxed(self.0) && get_tag(self.0) == TAG_RECORD
850    }
851
852    #[inline(always)]
853    pub fn as_int(&self) -> Option<i64> {
854        if !is_boxed(self.0) {
855            return None;
856        }
857        match get_tag(self.0) {
858            TAG_INT_SMALL => {
859                let payload = get_payload(self.0);
860                let val = if payload & INT_SIGN_BIT != 0 {
861                    (payload | !PAYLOAD_MASK) as i64
862                } else {
863                    payload as i64
864                };
865                Some(val)
866            }
867            TAG_INT_BIG => Some(unsafe { *self.borrow_ref::<i64>() }),
868            _ => None,
869        }
870    }
871
872    #[inline(always)]
873    pub fn as_float(&self) -> Option<f64> {
874        if !is_boxed(self.0) {
875            return Some(f64::from_bits(self.0));
876        }
877        match get_tag(self.0) {
878            TAG_INT_SMALL => {
879                let payload = get_payload(self.0);
880                let val = if payload & INT_SIGN_BIT != 0 {
881                    (payload | !PAYLOAD_MASK) as i64
882                } else {
883                    payload as i64
884                };
885                Some(val as f64)
886            }
887            TAG_INT_BIG => Some(unsafe { *self.borrow_ref::<i64>() } as f64),
888            _ => None,
889        }
890    }
891
892    #[inline(always)]
893    pub fn as_bool(&self) -> Option<bool> {
894        if self.0 == Value::TRUE.0 {
895            Some(true)
896        } else if self.0 == Value::FALSE.0 {
897            Some(false)
898        } else {
899            None
900        }
901    }
902
903    pub fn as_str(&self) -> Option<&str> {
904        if is_boxed(self.0) && get_tag(self.0) == TAG_STRING {
905            Some(unsafe { self.borrow_ref::<String>() })
906        } else {
907            None
908        }
909    }
910
911    pub fn as_string_rc(&self) -> Option<Rc<String>> {
912        if is_boxed(self.0) && get_tag(self.0) == TAG_STRING {
913            Some(unsafe { self.get_rc::<String>() })
914        } else {
915            None
916        }
917    }
918
919    pub fn as_symbol(&self) -> Option<String> {
920        self.as_symbol_spur().map(resolve)
921    }
922
923    pub fn as_symbol_spur(&self) -> Option<Spur> {
924        if is_boxed(self.0) && get_tag(self.0) == TAG_SYMBOL {
925            let payload = get_payload(self.0);
926            Some(unsafe { std::mem::transmute::<u32, Spur>(payload as u32) })
927        } else {
928            None
929        }
930    }
931
932    pub fn as_keyword(&self) -> Option<String> {
933        self.as_keyword_spur().map(resolve)
934    }
935
936    pub fn as_keyword_spur(&self) -> Option<Spur> {
937        if is_boxed(self.0) && get_tag(self.0) == TAG_KEYWORD {
938            let payload = get_payload(self.0);
939            Some(unsafe { std::mem::transmute::<u32, Spur>(payload as u32) })
940        } else {
941            None
942        }
943    }
944
945    pub fn as_char(&self) -> Option<char> {
946        if is_boxed(self.0) && get_tag(self.0) == TAG_CHAR {
947            let payload = get_payload(self.0);
948            char::from_u32(payload as u32)
949        } else {
950            None
951        }
952    }
953
954    pub fn as_list(&self) -> Option<&[Value]> {
955        if is_boxed(self.0) && get_tag(self.0) == TAG_LIST {
956            Some(unsafe { self.borrow_ref::<Vec<Value>>() })
957        } else {
958            None
959        }
960    }
961
962    pub fn as_list_rc(&self) -> Option<Rc<Vec<Value>>> {
963        if is_boxed(self.0) && get_tag(self.0) == TAG_LIST {
964            Some(unsafe { self.get_rc::<Vec<Value>>() })
965        } else {
966            None
967        }
968    }
969
970    pub fn as_vector(&self) -> Option<&[Value]> {
971        if is_boxed(self.0) && get_tag(self.0) == TAG_VECTOR {
972            Some(unsafe { self.borrow_ref::<Vec<Value>>() })
973        } else {
974            None
975        }
976    }
977
978    pub fn as_vector_rc(&self) -> Option<Rc<Vec<Value>>> {
979        if is_boxed(self.0) && get_tag(self.0) == TAG_VECTOR {
980            Some(unsafe { self.get_rc::<Vec<Value>>() })
981        } else {
982            None
983        }
984    }
985
986    pub fn as_map_rc(&self) -> Option<Rc<BTreeMap<Value, Value>>> {
987        if is_boxed(self.0) && get_tag(self.0) == TAG_MAP {
988            Some(unsafe { self.get_rc::<BTreeMap<Value, Value>>() })
989        } else {
990            None
991        }
992    }
993
994    pub fn as_hashmap_rc(&self) -> Option<Rc<hashbrown::HashMap<Value, Value>>> {
995        if is_boxed(self.0) && get_tag(self.0) == TAG_HASHMAP {
996            Some(unsafe { self.get_rc::<hashbrown::HashMap<Value, Value>>() })
997        } else {
998            None
999        }
1000    }
1001
1002    pub fn as_lambda_rc(&self) -> Option<Rc<Lambda>> {
1003        if is_boxed(self.0) && get_tag(self.0) == TAG_LAMBDA {
1004            Some(unsafe { self.get_rc::<Lambda>() })
1005        } else {
1006            None
1007        }
1008    }
1009
1010    pub fn as_macro_rc(&self) -> Option<Rc<Macro>> {
1011        if is_boxed(self.0) && get_tag(self.0) == TAG_MACRO {
1012            Some(unsafe { self.get_rc::<Macro>() })
1013        } else {
1014            None
1015        }
1016    }
1017
1018    pub fn as_native_fn_rc(&self) -> Option<Rc<NativeFn>> {
1019        if is_boxed(self.0) && get_tag(self.0) == TAG_NATIVE_FN {
1020            Some(unsafe { self.get_rc::<NativeFn>() })
1021        } else {
1022            None
1023        }
1024    }
1025
1026    pub fn as_thunk_rc(&self) -> Option<Rc<Thunk>> {
1027        if is_boxed(self.0) && get_tag(self.0) == TAG_THUNK {
1028            Some(unsafe { self.get_rc::<Thunk>() })
1029        } else {
1030            None
1031        }
1032    }
1033
1034    pub fn as_record(&self) -> Option<&Record> {
1035        if is_boxed(self.0) && get_tag(self.0) == TAG_RECORD {
1036            Some(unsafe { self.borrow_ref::<Record>() })
1037        } else {
1038            None
1039        }
1040    }
1041
1042    pub fn as_record_rc(&self) -> Option<Rc<Record>> {
1043        if is_boxed(self.0) && get_tag(self.0) == TAG_RECORD {
1044            Some(unsafe { self.get_rc::<Record>() })
1045        } else {
1046            None
1047        }
1048    }
1049
1050    pub fn as_bytevector(&self) -> Option<&[u8]> {
1051        if is_boxed(self.0) && get_tag(self.0) == TAG_BYTEVECTOR {
1052            Some(unsafe { self.borrow_ref::<Vec<u8>>() })
1053        } else {
1054            None
1055        }
1056    }
1057
1058    pub fn as_bytevector_rc(&self) -> Option<Rc<Vec<u8>>> {
1059        if is_boxed(self.0) && get_tag(self.0) == TAG_BYTEVECTOR {
1060            Some(unsafe { self.get_rc::<Vec<u8>>() })
1061        } else {
1062            None
1063        }
1064    }
1065
1066    pub fn as_prompt_rc(&self) -> Option<Rc<Prompt>> {
1067        if is_boxed(self.0) && get_tag(self.0) == TAG_PROMPT {
1068            Some(unsafe { self.get_rc::<Prompt>() })
1069        } else {
1070            None
1071        }
1072    }
1073
1074    pub fn as_message_rc(&self) -> Option<Rc<Message>> {
1075        if is_boxed(self.0) && get_tag(self.0) == TAG_MESSAGE {
1076            Some(unsafe { self.get_rc::<Message>() })
1077        } else {
1078            None
1079        }
1080    }
1081
1082    pub fn as_conversation_rc(&self) -> Option<Rc<Conversation>> {
1083        if is_boxed(self.0) && get_tag(self.0) == TAG_CONVERSATION {
1084            Some(unsafe { self.get_rc::<Conversation>() })
1085        } else {
1086            None
1087        }
1088    }
1089
1090    pub fn as_tool_def_rc(&self) -> Option<Rc<ToolDefinition>> {
1091        if is_boxed(self.0) && get_tag(self.0) == TAG_TOOL_DEF {
1092            Some(unsafe { self.get_rc::<ToolDefinition>() })
1093        } else {
1094            None
1095        }
1096    }
1097
1098    pub fn as_agent_rc(&self) -> Option<Rc<Agent>> {
1099        if is_boxed(self.0) && get_tag(self.0) == TAG_AGENT {
1100            Some(unsafe { self.get_rc::<Agent>() })
1101        } else {
1102            None
1103        }
1104    }
1105}
1106
1107// ── Clone ─────────────────────────────────────────────────────────
1108
1109impl Clone for Value {
1110    #[inline(always)]
1111    fn clone(&self) -> Self {
1112        if !is_boxed(self.0) {
1113            // Float: trivial copy
1114            return Value(self.0);
1115        }
1116        let tag = get_tag(self.0);
1117        match tag {
1118            // Immediates: trivial copy
1119            TAG_NIL | TAG_FALSE | TAG_TRUE | TAG_INT_SMALL | TAG_CHAR | TAG_SYMBOL
1120            | TAG_KEYWORD => Value(self.0),
1121            // Heap pointers: increment refcount
1122            _ => {
1123                let payload = get_payload(self.0);
1124                let ptr = payload_to_ptr(payload);
1125                // Increment refcount based on type
1126                unsafe {
1127                    match tag {
1128                        TAG_INT_BIG => Rc::increment_strong_count(ptr as *const i64),
1129                        TAG_STRING => Rc::increment_strong_count(ptr as *const String),
1130                        TAG_LIST | TAG_VECTOR => {
1131                            Rc::increment_strong_count(ptr as *const Vec<Value>)
1132                        }
1133                        TAG_MAP => Rc::increment_strong_count(ptr as *const BTreeMap<Value, Value>),
1134                        TAG_HASHMAP => Rc::increment_strong_count(
1135                            ptr as *const hashbrown::HashMap<Value, Value>,
1136                        ),
1137                        TAG_LAMBDA => Rc::increment_strong_count(ptr as *const Lambda),
1138                        TAG_MACRO => Rc::increment_strong_count(ptr as *const Macro),
1139                        TAG_NATIVE_FN => Rc::increment_strong_count(ptr as *const NativeFn),
1140                        TAG_PROMPT => Rc::increment_strong_count(ptr as *const Prompt),
1141                        TAG_MESSAGE => Rc::increment_strong_count(ptr as *const Message),
1142                        TAG_CONVERSATION => Rc::increment_strong_count(ptr as *const Conversation),
1143                        TAG_TOOL_DEF => Rc::increment_strong_count(ptr as *const ToolDefinition),
1144                        TAG_AGENT => Rc::increment_strong_count(ptr as *const Agent),
1145                        TAG_THUNK => Rc::increment_strong_count(ptr as *const Thunk),
1146                        TAG_RECORD => Rc::increment_strong_count(ptr as *const Record),
1147                        TAG_BYTEVECTOR => Rc::increment_strong_count(ptr as *const Vec<u8>),
1148                        _ => unreachable!("invalid heap tag in clone: {}", tag),
1149                    }
1150                }
1151                Value(self.0)
1152            }
1153        }
1154    }
1155}
1156
1157// ── Drop ──────────────────────────────────────────────────────────
1158
1159impl Drop for Value {
1160    #[inline(always)]
1161    fn drop(&mut self) {
1162        if !is_boxed(self.0) {
1163            return; // Float
1164        }
1165        let tag = get_tag(self.0);
1166        match tag {
1167            // Immediates: nothing to free
1168            TAG_NIL | TAG_FALSE | TAG_TRUE | TAG_INT_SMALL | TAG_CHAR | TAG_SYMBOL
1169            | TAG_KEYWORD => {}
1170            // Heap pointers: drop the Rc
1171            _ => {
1172                let payload = get_payload(self.0);
1173                let ptr = payload_to_ptr(payload);
1174                unsafe {
1175                    match tag {
1176                        TAG_INT_BIG => drop(Rc::from_raw(ptr as *const i64)),
1177                        TAG_STRING => drop(Rc::from_raw(ptr as *const String)),
1178                        TAG_LIST | TAG_VECTOR => drop(Rc::from_raw(ptr as *const Vec<Value>)),
1179                        TAG_MAP => drop(Rc::from_raw(ptr as *const BTreeMap<Value, Value>)),
1180                        TAG_HASHMAP => {
1181                            drop(Rc::from_raw(ptr as *const hashbrown::HashMap<Value, Value>))
1182                        }
1183                        TAG_LAMBDA => drop(Rc::from_raw(ptr as *const Lambda)),
1184                        TAG_MACRO => drop(Rc::from_raw(ptr as *const Macro)),
1185                        TAG_NATIVE_FN => drop(Rc::from_raw(ptr as *const NativeFn)),
1186                        TAG_PROMPT => drop(Rc::from_raw(ptr as *const Prompt)),
1187                        TAG_MESSAGE => drop(Rc::from_raw(ptr as *const Message)),
1188                        TAG_CONVERSATION => drop(Rc::from_raw(ptr as *const Conversation)),
1189                        TAG_TOOL_DEF => drop(Rc::from_raw(ptr as *const ToolDefinition)),
1190                        TAG_AGENT => drop(Rc::from_raw(ptr as *const Agent)),
1191                        TAG_THUNK => drop(Rc::from_raw(ptr as *const Thunk)),
1192                        TAG_RECORD => drop(Rc::from_raw(ptr as *const Record)),
1193                        TAG_BYTEVECTOR => drop(Rc::from_raw(ptr as *const Vec<u8>)),
1194                        _ => {} // unreachable, but don't panic in drop
1195                    }
1196                }
1197            }
1198        }
1199    }
1200}
1201
1202// ── PartialEq / Eq ────────────────────────────────────────────────
1203
1204impl PartialEq for Value {
1205    fn eq(&self, other: &Self) -> bool {
1206        // Fast path: identical bits
1207        if self.0 == other.0 {
1208            // For floats, NaN != NaN per IEEE, but our canonical NaN is unique,
1209            // so identical bits means equal for all types.
1210            // Exception: need to handle -0.0 == +0.0
1211            if !is_boxed(self.0) {
1212                let f = f64::from_bits(self.0);
1213                // NaN check: if both are canonical NaN (same bits), we say not equal
1214                if f.is_nan() {
1215                    return false;
1216                }
1217                return true;
1218            }
1219            return true;
1220        }
1221        // Different bits: could still be equal for heap types or -0.0/+0.0
1222        match (self.view(), other.view()) {
1223            (ValueView::Nil, ValueView::Nil) => true,
1224            (ValueView::Bool(a), ValueView::Bool(b)) => a == b,
1225            (ValueView::Int(a), ValueView::Int(b)) => a == b,
1226            (ValueView::Float(a), ValueView::Float(b)) => a.to_bits() == b.to_bits(),
1227            (ValueView::String(a), ValueView::String(b)) => a == b,
1228            (ValueView::Symbol(a), ValueView::Symbol(b)) => a == b,
1229            (ValueView::Keyword(a), ValueView::Keyword(b)) => a == b,
1230            (ValueView::Char(a), ValueView::Char(b)) => a == b,
1231            (ValueView::List(a), ValueView::List(b)) => a == b,
1232            (ValueView::Vector(a), ValueView::Vector(b)) => a == b,
1233            (ValueView::Map(a), ValueView::Map(b)) => a == b,
1234            (ValueView::HashMap(a), ValueView::HashMap(b)) => a == b,
1235            (ValueView::Record(a), ValueView::Record(b)) => {
1236                a.type_tag == b.type_tag && a.fields == b.fields
1237            }
1238            (ValueView::Bytevector(a), ValueView::Bytevector(b)) => a == b,
1239            _ => false,
1240        }
1241    }
1242}
1243
1244impl Eq for Value {}
1245
1246// ── Hash ──────────────────────────────────────────────────────────
1247
1248impl Hash for Value {
1249    fn hash<H: Hasher>(&self, state: &mut H) {
1250        match self.view() {
1251            ValueView::Nil => 0u8.hash(state),
1252            ValueView::Bool(b) => {
1253                1u8.hash(state);
1254                b.hash(state);
1255            }
1256            ValueView::Int(n) => {
1257                2u8.hash(state);
1258                n.hash(state);
1259            }
1260            ValueView::Float(f) => {
1261                3u8.hash(state);
1262                f.to_bits().hash(state);
1263            }
1264            ValueView::String(s) => {
1265                4u8.hash(state);
1266                s.hash(state);
1267            }
1268            ValueView::Symbol(s) => {
1269                5u8.hash(state);
1270                s.hash(state);
1271            }
1272            ValueView::Keyword(s) => {
1273                6u8.hash(state);
1274                s.hash(state);
1275            }
1276            ValueView::Char(c) => {
1277                7u8.hash(state);
1278                c.hash(state);
1279            }
1280            ValueView::List(l) => {
1281                8u8.hash(state);
1282                l.hash(state);
1283            }
1284            ValueView::Vector(v) => {
1285                9u8.hash(state);
1286                v.hash(state);
1287            }
1288            ValueView::Record(r) => {
1289                10u8.hash(state);
1290                r.type_tag.hash(state);
1291                r.fields.hash(state);
1292            }
1293            ValueView::Bytevector(bv) => {
1294                11u8.hash(state);
1295                bv.hash(state);
1296            }
1297            _ => {}
1298        }
1299    }
1300}
1301
1302// ── Ord ───────────────────────────────────────────────────────────
1303
1304impl PartialOrd for Value {
1305    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1306        Some(self.cmp(other))
1307    }
1308}
1309
1310impl Ord for Value {
1311    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1312        use std::cmp::Ordering;
1313        fn type_order(v: &Value) -> u8 {
1314            match v.view() {
1315                ValueView::Nil => 0,
1316                ValueView::Bool(_) => 1,
1317                ValueView::Int(_) => 2,
1318                ValueView::Float(_) => 3,
1319                ValueView::Char(_) => 4,
1320                ValueView::String(_) => 5,
1321                ValueView::Symbol(_) => 6,
1322                ValueView::Keyword(_) => 7,
1323                ValueView::List(_) => 8,
1324                ValueView::Vector(_) => 9,
1325                ValueView::Map(_) => 10,
1326                ValueView::HashMap(_) => 11,
1327                ValueView::Record(_) => 12,
1328                ValueView::Bytevector(_) => 13,
1329                _ => 14,
1330            }
1331        }
1332        match (self.view(), other.view()) {
1333            (ValueView::Nil, ValueView::Nil) => Ordering::Equal,
1334            (ValueView::Bool(a), ValueView::Bool(b)) => a.cmp(&b),
1335            (ValueView::Int(a), ValueView::Int(b)) => a.cmp(&b),
1336            (ValueView::Float(a), ValueView::Float(b)) => a.to_bits().cmp(&b.to_bits()),
1337            (ValueView::String(a), ValueView::String(b)) => a.cmp(&b),
1338            (ValueView::Symbol(a), ValueView::Symbol(b)) => compare_spurs(a, b),
1339            (ValueView::Keyword(a), ValueView::Keyword(b)) => compare_spurs(a, b),
1340            (ValueView::Char(a), ValueView::Char(b)) => a.cmp(&b),
1341            (ValueView::List(a), ValueView::List(b)) => a.cmp(&b),
1342            (ValueView::Vector(a), ValueView::Vector(b)) => a.cmp(&b),
1343            (ValueView::Record(a), ValueView::Record(b)) => {
1344                compare_spurs(a.type_tag, b.type_tag).then_with(|| a.fields.cmp(&b.fields))
1345            }
1346            (ValueView::Bytevector(a), ValueView::Bytevector(b)) => a.cmp(&b),
1347            _ => type_order(self).cmp(&type_order(other)),
1348        }
1349    }
1350}
1351
1352// ── Display ───────────────────────────────────────────────────────
1353
1354fn truncate(s: &str, max: usize) -> String {
1355    let mut iter = s.chars();
1356    let prefix: String = iter.by_ref().take(max).collect();
1357    if iter.next().is_none() {
1358        prefix
1359    } else {
1360        format!("{prefix}...")
1361    }
1362}
1363
1364impl fmt::Display for Value {
1365    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1366        match self.view() {
1367            ValueView::Nil => write!(f, "nil"),
1368            ValueView::Bool(true) => write!(f, "#t"),
1369            ValueView::Bool(false) => write!(f, "#f"),
1370            ValueView::Int(n) => write!(f, "{n}"),
1371            ValueView::Float(n) => {
1372                if n.fract() == 0.0 {
1373                    write!(f, "{n:.1}")
1374                } else {
1375                    write!(f, "{n}")
1376                }
1377            }
1378            ValueView::String(s) => write!(f, "\"{s}\""),
1379            ValueView::Symbol(s) => with_resolved(s, |name| write!(f, "{name}")),
1380            ValueView::Keyword(s) => with_resolved(s, |name| write!(f, ":{name}")),
1381            ValueView::Char(c) => match c {
1382                ' ' => write!(f, "#\\space"),
1383                '\n' => write!(f, "#\\newline"),
1384                '\t' => write!(f, "#\\tab"),
1385                '\r' => write!(f, "#\\return"),
1386                '\0' => write!(f, "#\\nul"),
1387                _ => write!(f, "#\\{c}"),
1388            },
1389            ValueView::List(items) => {
1390                write!(f, "(")?;
1391                for (i, item) in items.iter().enumerate() {
1392                    if i > 0 {
1393                        write!(f, " ")?;
1394                    }
1395                    write!(f, "{item}")?;
1396                }
1397                write!(f, ")")
1398            }
1399            ValueView::Vector(items) => {
1400                write!(f, "[")?;
1401                for (i, item) in items.iter().enumerate() {
1402                    if i > 0 {
1403                        write!(f, " ")?;
1404                    }
1405                    write!(f, "{item}")?;
1406                }
1407                write!(f, "]")
1408            }
1409            ValueView::Map(map) => {
1410                write!(f, "{{")?;
1411                for (i, (k, v)) in map.iter().enumerate() {
1412                    if i > 0 {
1413                        write!(f, " ")?;
1414                    }
1415                    write!(f, "{k} {v}")?;
1416                }
1417                write!(f, "}}")
1418            }
1419            ValueView::HashMap(map) => {
1420                let mut entries: Vec<_> = map.iter().collect();
1421                entries.sort_by(|(k1, _), (k2, _)| k1.cmp(k2));
1422                write!(f, "{{")?;
1423                for (i, (k, v)) in entries.iter().enumerate() {
1424                    if i > 0 {
1425                        write!(f, " ")?;
1426                    }
1427                    write!(f, "{k} {v}")?;
1428                }
1429                write!(f, "}}")
1430            }
1431            ValueView::Lambda(l) => {
1432                if let Some(name) = &l.name {
1433                    with_resolved(*name, |n| write!(f, "<lambda {n}>"))
1434                } else {
1435                    write!(f, "<lambda>")
1436                }
1437            }
1438            ValueView::Macro(m) => with_resolved(m.name, |n| write!(f, "<macro {n}>")),
1439            ValueView::NativeFn(n) => write!(f, "<native-fn {}>", n.name),
1440            ValueView::Prompt(p) => write!(f, "<prompt {} messages>", p.messages.len()),
1441            ValueView::Message(m) => {
1442                write!(f, "<message {} \"{}\">", m.role, truncate(&m.content, 40))
1443            }
1444            ValueView::Conversation(c) => {
1445                write!(f, "<conversation {} messages>", c.messages.len())
1446            }
1447            ValueView::ToolDef(t) => write!(f, "<tool {}>", t.name),
1448            ValueView::Agent(a) => write!(f, "<agent {}>", a.name),
1449            ValueView::Thunk(t) => {
1450                if t.forced.borrow().is_some() {
1451                    write!(f, "<promise (forced)>")
1452                } else {
1453                    write!(f, "<promise>")
1454                }
1455            }
1456            ValueView::Record(r) => {
1457                with_resolved(r.type_tag, |tag| write!(f, "#<record {tag}"))?;
1458                for field in &r.fields {
1459                    write!(f, " {field}")?;
1460                }
1461                write!(f, ">")
1462            }
1463            ValueView::Bytevector(bv) => {
1464                write!(f, "#u8(")?;
1465                for (i, byte) in bv.iter().enumerate() {
1466                    if i > 0 {
1467                        write!(f, " ")?;
1468                    }
1469                    write!(f, "{byte}")?;
1470                }
1471                write!(f, ")")
1472            }
1473        }
1474    }
1475}
1476
1477// ── Pretty-print ──────────────────────────────────────────────────
1478
1479/// Pretty-print a value with line breaks and indentation when the compact
1480/// representation exceeds `max_width` columns.  Small values that fit in
1481/// one line are returned in the normal compact format.
1482pub fn pretty_print(value: &Value, max_width: usize) -> String {
1483    let compact = format!("{value}");
1484    if compact.len() <= max_width {
1485        return compact;
1486    }
1487    let mut buf = String::new();
1488    pp_value(value, 0, max_width, &mut buf);
1489    buf
1490}
1491
1492/// Render `value` into `buf` at the given `indent` level.  If the compact
1493/// form fits in `max_width - indent` columns we use it; otherwise we break
1494/// the container across multiple lines.
1495fn pp_value(value: &Value, indent: usize, max_width: usize, buf: &mut String) {
1496    let compact = format!("{value}");
1497    let remaining = max_width.saturating_sub(indent);
1498    if compact.len() <= remaining {
1499        buf.push_str(&compact);
1500        return;
1501    }
1502
1503    match value.view() {
1504        ValueView::List(items) => {
1505            pp_seq(items.iter(), '(', ')', indent, max_width, buf);
1506        }
1507        ValueView::Vector(items) => {
1508            pp_seq(items.iter(), '[', ']', indent, max_width, buf);
1509        }
1510        ValueView::Map(map) => {
1511            pp_map(
1512                map.iter().map(|(k, v)| (k.clone(), v.clone())),
1513                indent,
1514                max_width,
1515                buf,
1516            );
1517        }
1518        ValueView::HashMap(map) => {
1519            let mut entries: Vec<_> = map.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
1520            entries.sort_by(|(k1, _), (k2, _)| k1.cmp(k2));
1521            pp_map(entries.into_iter(), indent, max_width, buf);
1522        }
1523        _ => buf.push_str(&compact),
1524    }
1525}
1526
1527/// Pretty-print a list or vector.
1528fn pp_seq<'a>(
1529    items: impl Iterator<Item = &'a Value>,
1530    open: char,
1531    close: char,
1532    indent: usize,
1533    max_width: usize,
1534    buf: &mut String,
1535) {
1536    buf.push(open);
1537    let child_indent = indent + 1;
1538    let pad = " ".repeat(child_indent);
1539    for (i, item) in items.enumerate() {
1540        if i > 0 {
1541            buf.push('\n');
1542            buf.push_str(&pad);
1543        }
1544        pp_value(item, child_indent, max_width, buf);
1545    }
1546    buf.push(close);
1547}
1548
1549/// Pretty-print a map (BTreeMap or HashMap).
1550fn pp_map(
1551    entries: impl Iterator<Item = (Value, Value)>,
1552    indent: usize,
1553    max_width: usize,
1554    buf: &mut String,
1555) {
1556    buf.push('{');
1557    let child_indent = indent + 1;
1558    let pad = " ".repeat(child_indent);
1559    for (i, (k, v)) in entries.enumerate() {
1560        if i > 0 {
1561            buf.push('\n');
1562            buf.push_str(&pad);
1563        }
1564        // Key is always compact
1565        let key_str = format!("{k}");
1566        buf.push_str(&key_str);
1567
1568        // Check if the value fits inline after the key
1569        let inline_indent = child_indent + key_str.len() + 1;
1570        let compact_val = format!("{v}");
1571        let remaining = max_width.saturating_sub(inline_indent);
1572
1573        if compact_val.len() <= remaining {
1574            // Fits inline
1575            buf.push(' ');
1576            buf.push_str(&compact_val);
1577        } else if is_compound(&v) {
1578            // Complex value: break to next line indented 2 from key
1579            let nested_indent = child_indent + 2;
1580            let nested_pad = " ".repeat(nested_indent);
1581            buf.push('\n');
1582            buf.push_str(&nested_pad);
1583            pp_value(&v, nested_indent, max_width, buf);
1584        } else {
1585            // Simple value that's just long: keep inline
1586            buf.push(' ');
1587            buf.push_str(&compact_val);
1588        }
1589    }
1590    buf.push('}');
1591}
1592
1593/// Check whether a value is a compound container (list, vector, map, hashmap).
1594fn is_compound(value: &Value) -> bool {
1595    matches!(
1596        value.view(),
1597        ValueView::List(_) | ValueView::Vector(_) | ValueView::Map(_) | ValueView::HashMap(_)
1598    )
1599}
1600
1601// ── Debug ─────────────────────────────────────────────────────────
1602
1603impl fmt::Debug for Value {
1604    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1605        match self.view() {
1606            ValueView::Nil => write!(f, "Nil"),
1607            ValueView::Bool(b) => write!(f, "Bool({b})"),
1608            ValueView::Int(n) => write!(f, "Int({n})"),
1609            ValueView::Float(n) => write!(f, "Float({n})"),
1610            ValueView::String(s) => write!(f, "String({:?})", &**s),
1611            ValueView::Symbol(s) => write!(f, "Symbol({})", resolve(s)),
1612            ValueView::Keyword(s) => write!(f, "Keyword({})", resolve(s)),
1613            ValueView::Char(c) => write!(f, "Char({c:?})"),
1614            ValueView::List(items) => write!(f, "List({items:?})"),
1615            ValueView::Vector(items) => write!(f, "Vector({items:?})"),
1616            ValueView::Map(map) => write!(f, "Map({map:?})"),
1617            ValueView::HashMap(map) => write!(f, "HashMap({map:?})"),
1618            ValueView::Lambda(l) => write!(f, "{l:?}"),
1619            ValueView::Macro(m) => write!(f, "{m:?}"),
1620            ValueView::NativeFn(n) => write!(f, "{n:?}"),
1621            ValueView::Prompt(p) => write!(f, "{p:?}"),
1622            ValueView::Message(m) => write!(f, "{m:?}"),
1623            ValueView::Conversation(c) => write!(f, "{c:?}"),
1624            ValueView::ToolDef(t) => write!(f, "{t:?}"),
1625            ValueView::Agent(a) => write!(f, "{a:?}"),
1626            ValueView::Thunk(t) => write!(f, "{t:?}"),
1627            ValueView::Record(r) => write!(f, "{r:?}"),
1628            ValueView::Bytevector(bv) => write!(f, "Bytevector({bv:?})"),
1629        }
1630    }
1631}
1632
1633// ── Env ───────────────────────────────────────────────────────────
1634
1635/// A Sema environment: a chain of scopes with bindings.
1636#[derive(Debug, Clone)]
1637pub struct Env {
1638    pub bindings: Rc<RefCell<SpurMap<Spur, Value>>>,
1639    pub parent: Option<Rc<Env>>,
1640    pub version: Cell<u64>,
1641}
1642
1643impl Env {
1644    pub fn new() -> Self {
1645        Env {
1646            bindings: Rc::new(RefCell::new(SpurMap::new())),
1647            parent: None,
1648            version: Cell::new(0),
1649        }
1650    }
1651
1652    pub fn with_parent(parent: Rc<Env>) -> Self {
1653        Env {
1654            bindings: Rc::new(RefCell::new(SpurMap::new())),
1655            parent: Some(parent),
1656            version: Cell::new(0),
1657        }
1658    }
1659
1660    fn bump_version(&self) {
1661        self.version.set(self.version.get().wrapping_add(1));
1662    }
1663
1664    pub fn get(&self, name: Spur) -> Option<Value> {
1665        if let Some(val) = self.bindings.borrow().get(&name) {
1666            Some(val.clone())
1667        } else if let Some(parent) = &self.parent {
1668            parent.get(name)
1669        } else {
1670            None
1671        }
1672    }
1673
1674    pub fn get_str(&self, name: &str) -> Option<Value> {
1675        self.get(intern(name))
1676    }
1677
1678    pub fn set(&self, name: Spur, val: Value) {
1679        self.bindings.borrow_mut().insert(name, val);
1680        self.bump_version();
1681    }
1682
1683    pub fn set_str(&self, name: &str, val: Value) {
1684        self.set(intern(name), val);
1685    }
1686
1687    /// Update a binding that already exists in the current scope.
1688    pub fn update(&self, name: Spur, val: Value) {
1689        let mut bindings = self.bindings.borrow_mut();
1690        if let Some(entry) = bindings.get_mut(&name) {
1691            *entry = val;
1692        } else {
1693            bindings.insert(name, val);
1694        }
1695        drop(bindings);
1696        self.bump_version();
1697    }
1698
1699    /// Remove and return a binding from the current scope only.
1700    pub fn take(&self, name: Spur) -> Option<Value> {
1701        let result = self.bindings.borrow_mut().remove(&name);
1702        if result.is_some() {
1703            self.bump_version();
1704        }
1705        result
1706    }
1707
1708    /// Remove and return a binding from any scope in the parent chain.
1709    pub fn take_anywhere(&self, name: Spur) -> Option<Value> {
1710        if let Some(val) = self.bindings.borrow_mut().remove(&name) {
1711            self.bump_version();
1712            Some(val)
1713        } else if let Some(parent) = &self.parent {
1714            parent.take_anywhere(name)
1715        } else {
1716            None
1717        }
1718    }
1719
1720    /// Set a variable in the scope where it's defined (for set!).
1721    pub fn set_existing(&self, name: Spur, val: Value) -> bool {
1722        let mut bindings = self.bindings.borrow_mut();
1723        if let Some(entry) = bindings.get_mut(&name) {
1724            *entry = val;
1725            drop(bindings);
1726            self.bump_version();
1727            true
1728        } else {
1729            drop(bindings);
1730            if let Some(parent) = &self.parent {
1731                parent.set_existing(name, val)
1732            } else {
1733                false
1734            }
1735        }
1736    }
1737}
1738
1739impl Default for Env {
1740    fn default() -> Self {
1741        Self::new()
1742    }
1743}
1744
1745// ── Tests ─────────────────────────────────────────────────────────
1746
1747#[cfg(test)]
1748mod tests {
1749    use super::*;
1750
1751    #[test]
1752    fn test_size_of_value() {
1753        assert_eq!(std::mem::size_of::<Value>(), 8);
1754    }
1755
1756    #[test]
1757    fn test_nil() {
1758        let v = Value::nil();
1759        assert!(v.is_nil());
1760        assert!(!v.is_truthy());
1761        assert_eq!(v.type_name(), "nil");
1762        assert_eq!(format!("{v}"), "nil");
1763    }
1764
1765    #[test]
1766    fn test_bool() {
1767        let t = Value::bool(true);
1768        let f = Value::bool(false);
1769        assert!(t.is_truthy());
1770        assert!(!f.is_truthy());
1771        assert_eq!(t.as_bool(), Some(true));
1772        assert_eq!(f.as_bool(), Some(false));
1773        assert_eq!(format!("{t}"), "#t");
1774        assert_eq!(format!("{f}"), "#f");
1775    }
1776
1777    #[test]
1778    fn test_small_int() {
1779        let v = Value::int(42);
1780        assert_eq!(v.as_int(), Some(42));
1781        assert_eq!(v.type_name(), "int");
1782        assert_eq!(format!("{v}"), "42");
1783
1784        let neg = Value::int(-100);
1785        assert_eq!(neg.as_int(), Some(-100));
1786        assert_eq!(format!("{neg}"), "-100");
1787
1788        let zero = Value::int(0);
1789        assert_eq!(zero.as_int(), Some(0));
1790    }
1791
1792    #[test]
1793    fn test_small_int_boundaries() {
1794        let max = Value::int(SMALL_INT_MAX);
1795        assert_eq!(max.as_int(), Some(SMALL_INT_MAX));
1796
1797        let min = Value::int(SMALL_INT_MIN);
1798        assert_eq!(min.as_int(), Some(SMALL_INT_MIN));
1799    }
1800
1801    #[test]
1802    fn test_big_int() {
1803        let big = Value::int(i64::MAX);
1804        assert_eq!(big.as_int(), Some(i64::MAX));
1805        assert_eq!(big.type_name(), "int");
1806
1807        let big_neg = Value::int(i64::MIN);
1808        assert_eq!(big_neg.as_int(), Some(i64::MIN));
1809
1810        // Just outside small range
1811        let just_over = Value::int(SMALL_INT_MAX + 1);
1812        assert_eq!(just_over.as_int(), Some(SMALL_INT_MAX + 1));
1813    }
1814
1815    #[test]
1816    fn test_float() {
1817        let v = Value::float(3.14);
1818        assert_eq!(v.as_float(), Some(3.14));
1819        assert_eq!(v.type_name(), "float");
1820
1821        let neg = Value::float(-0.5);
1822        assert_eq!(neg.as_float(), Some(-0.5));
1823
1824        let inf = Value::float(f64::INFINITY);
1825        assert_eq!(inf.as_float(), Some(f64::INFINITY));
1826
1827        let neg_inf = Value::float(f64::NEG_INFINITY);
1828        assert_eq!(neg_inf.as_float(), Some(f64::NEG_INFINITY));
1829    }
1830
1831    #[test]
1832    fn test_float_nan() {
1833        let nan = Value::float(f64::NAN);
1834        let f = nan.as_float().unwrap();
1835        assert!(f.is_nan());
1836    }
1837
1838    #[test]
1839    fn test_string() {
1840        let v = Value::string("hello");
1841        assert_eq!(v.as_str(), Some("hello"));
1842        assert_eq!(v.type_name(), "string");
1843        assert_eq!(format!("{v}"), "\"hello\"");
1844    }
1845
1846    #[test]
1847    fn test_symbol() {
1848        let v = Value::symbol("foo");
1849        assert!(v.as_symbol_spur().is_some());
1850        assert_eq!(v.as_symbol(), Some("foo".to_string()));
1851        assert_eq!(v.type_name(), "symbol");
1852        assert_eq!(format!("{v}"), "foo");
1853    }
1854
1855    #[test]
1856    fn test_keyword() {
1857        let v = Value::keyword("bar");
1858        assert!(v.as_keyword_spur().is_some());
1859        assert_eq!(v.as_keyword(), Some("bar".to_string()));
1860        assert_eq!(v.type_name(), "keyword");
1861        assert_eq!(format!("{v}"), ":bar");
1862    }
1863
1864    #[test]
1865    fn test_char() {
1866        let v = Value::char('λ');
1867        assert_eq!(v.as_char(), Some('λ'));
1868        assert_eq!(v.type_name(), "char");
1869    }
1870
1871    #[test]
1872    fn test_list() {
1873        let v = Value::list(vec![Value::int(1), Value::int(2), Value::int(3)]);
1874        assert_eq!(v.as_list().unwrap().len(), 3);
1875        assert_eq!(v.type_name(), "list");
1876        assert_eq!(format!("{v}"), "(1 2 3)");
1877    }
1878
1879    #[test]
1880    fn test_clone_immediate() {
1881        let v = Value::int(42);
1882        let v2 = v.clone();
1883        assert_eq!(v.as_int(), v2.as_int());
1884    }
1885
1886    #[test]
1887    fn test_clone_heap() {
1888        let v = Value::string("hello");
1889        let v2 = v.clone();
1890        assert_eq!(v.as_str(), v2.as_str());
1891        // Both should work after clone
1892        assert_eq!(format!("{v}"), format!("{v2}"));
1893    }
1894
1895    #[test]
1896    fn test_equality() {
1897        assert_eq!(Value::int(42), Value::int(42));
1898        assert_ne!(Value::int(42), Value::int(43));
1899        assert_eq!(Value::nil(), Value::nil());
1900        assert_eq!(Value::bool(true), Value::bool(true));
1901        assert_ne!(Value::bool(true), Value::bool(false));
1902        assert_eq!(Value::string("a"), Value::string("a"));
1903        assert_ne!(Value::string("a"), Value::string("b"));
1904        assert_eq!(Value::symbol("x"), Value::symbol("x"));
1905    }
1906
1907    #[test]
1908    fn test_big_int_equality() {
1909        assert_eq!(Value::int(i64::MAX), Value::int(i64::MAX));
1910        assert_ne!(Value::int(i64::MAX), Value::int(i64::MIN));
1911    }
1912
1913    #[test]
1914    fn test_view_pattern_matching() {
1915        let v = Value::int(42);
1916        match v.view() {
1917            ValueView::Int(n) => assert_eq!(n, 42),
1918            _ => panic!("expected int"),
1919        }
1920
1921        let v = Value::string("hello");
1922        match v.view() {
1923            ValueView::String(s) => assert_eq!(&**s, "hello"),
1924            _ => panic!("expected string"),
1925        }
1926    }
1927
1928    #[test]
1929    fn test_env() {
1930        let env = Env::new();
1931        env.set_str("x", Value::int(42));
1932        assert_eq!(env.get_str("x"), Some(Value::int(42)));
1933    }
1934
1935    #[test]
1936    fn test_native_fn_simple() {
1937        let f = NativeFn::simple("add1", |args| Ok(args[0].clone()));
1938        let ctx = EvalContext::new();
1939        assert!((f.func)(&ctx, &[Value::int(42)]).is_ok());
1940    }
1941
1942    #[test]
1943    fn test_native_fn_with_ctx() {
1944        let f = NativeFn::with_ctx("get-depth", |ctx, _args| {
1945            Ok(Value::int(ctx.eval_depth.get() as i64))
1946        });
1947        let ctx = EvalContext::new();
1948        assert_eq!((f.func)(&ctx, &[]).unwrap(), Value::int(0));
1949    }
1950
1951    #[test]
1952    fn test_drop_doesnt_leak() {
1953        // Create and drop many heap values to check for leaks
1954        for _ in 0..10000 {
1955            let _ = Value::string("test");
1956            let _ = Value::list(vec![Value::int(1), Value::int(2)]);
1957            let _ = Value::int(i64::MAX); // big int
1958        }
1959    }
1960
1961    #[test]
1962    fn test_is_truthy() {
1963        assert!(!Value::nil().is_truthy());
1964        assert!(!Value::bool(false).is_truthy());
1965        assert!(Value::bool(true).is_truthy());
1966        assert!(Value::int(0).is_truthy());
1967        assert!(Value::int(1).is_truthy());
1968        assert!(Value::string("").is_truthy());
1969        assert!(Value::list(vec![]).is_truthy());
1970    }
1971
1972    #[test]
1973    fn test_as_float_from_int() {
1974        assert_eq!(Value::int(42).as_float(), Some(42.0));
1975        assert_eq!(Value::float(3.14).as_float(), Some(3.14));
1976    }
1977}