dazzle_core/scheme/value.rs
1//! Scheme value types
2//!
3//! This module defines the `Value` enum, which represents all Scheme values.
4//! Corresponds to OpenJade's `ELObj` class hierarchy.
5//!
6//! ## Design
7//!
8//! Like OpenJade, we use garbage collection for heap-allocated values:
9//! - OpenJade: Custom mark-and-sweep collector (`Collector` class)
10//! - Dazzle: Rust `gc` crate (conservative GC)
11//!
12//! ## Value Types
13//!
14//! **Basic types** (R4RS Scheme):
15//! - Nil: Empty list `()`
16//! - Bool: `#t` and `#f`
17//! - Integer: Exact integers (i64)
18//! - Real: Inexact reals (f64)
19//! - Char: Unicode characters
20//! - String: Immutable strings
21//! - Symbol: Interned identifiers
22//! - Pair: Cons cells (car, cdr)
23//! - Vector: Arrays
24//! - Procedure: Functions (built-in or user-defined)
25//!
26//! **DSSSL types** (code generation):
27//! - NodeList: Document tree node collections
28//! - Sosofo: Flow object sequences
29//!
30//! **DSSSL types** (document formatting - stubs for now):
31//! - Quantity, Color, Address, etc.
32
33// Suppress warnings from gc_derive macro (third-party crate issue)
34#![allow(non_local_definitions)]
35
36use gc::{Gc, GcCell};
37use std::fmt;
38use std::rc::Rc;
39
40// Import Position for source location tracking
41use crate::scheme::parser::Position;
42
43/// Source code location information
44///
45/// Used for error reporting and stack traces.
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct SourceInfo {
48 /// Source file path (template file)
49 pub file: String,
50 /// Position in the file (line:column)
51 pub pos: Position,
52}
53
54impl SourceInfo {
55 pub fn new(file: String, pos: Position) -> Self {
56 SourceInfo { file, pos }
57 }
58}
59
60impl fmt::Display for SourceInfo {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 write!(f, "{}:{}", self.file, self.pos)
63 }
64}
65
66/// DSSSL Unit (length dimension)
67///
68/// Represents the unit of a quantity. All units can be converted
69/// to a canonical representation (inches) for arithmetic operations.
70///
71/// **DSSSL Spec ยง6.2**: Quantities are dimensional values
72///
73/// **Unit Conversions** (from DSSSL spec):
74/// - 1in = 72pt (points)
75/// - 1in = 6pi (picas)
76/// - 1in = 25.4mm (millimeters)
77/// - 1cm = 10mm
78/// - 1pc = 1pi (alternate name)
79/// - 1em = context-dependent (font size)
80#[derive(Debug, Clone, Copy, PartialEq)]
81pub enum Unit {
82 /// Point (1/72 inch)
83 Point,
84 /// Pica (1/6 inch = 12 points)
85 Pica,
86 /// Inch (base unit)
87 Inch,
88 /// Millimeter (1/25.4 inch)
89 Millimeter,
90 /// Centimeter (10mm = 10/25.4 inch)
91 Centimeter,
92 /// Pixel (1/96 inch - CSS pixel standard)
93 Pixel,
94 /// Em (relative to font size - context-dependent)
95 Em,
96}
97
98impl Unit {
99 /// Convert this unit to inches (canonical form)
100 ///
101 /// All quantities are normalized to inches for arithmetic operations.
102 /// Em units default to 12pt unless a font size context is available.
103 pub fn to_inches(&self, magnitude: f64) -> f64 {
104 match self {
105 Unit::Inch => magnitude,
106 Unit::Point => magnitude / 72.0,
107 Unit::Pica => magnitude / 6.0,
108 Unit::Millimeter => magnitude / 25.4,
109 Unit::Centimeter => magnitude / 2.54,
110 Unit::Pixel => magnitude / 96.0, // CSS pixel: 1px = 1/96 inch
111 Unit::Em => magnitude * 12.0 / 72.0, // Default: 1em = 12pt
112 }
113 }
114
115 /// Convert from inches to this unit
116 pub fn from_inches(&self, inches: f64) -> f64 {
117 match self {
118 Unit::Inch => inches,
119 Unit::Point => inches * 72.0,
120 Unit::Pica => inches * 6.0,
121 Unit::Millimeter => inches * 25.4,
122 Unit::Centimeter => inches * 2.54,
123 Unit::Pixel => inches * 96.0, // CSS pixel: 1px = 1/96 inch
124 Unit::Em => inches * 72.0 / 12.0, // Default: 1em = 12pt
125 }
126 }
127
128 /// Parse unit suffix string
129 pub fn from_suffix(suffix: &str) -> Option<Unit> {
130 match suffix {
131 "pt" => Some(Unit::Point),
132 "pi" | "pc" => Some(Unit::Pica),
133 "in" => Some(Unit::Inch),
134 "mm" => Some(Unit::Millimeter),
135 "cm" => Some(Unit::Centimeter),
136 "px" => Some(Unit::Pixel),
137 "em" => Some(Unit::Em),
138 _ => None,
139 }
140 }
141
142 /// Get unit suffix string
143 pub fn suffix(&self) -> &'static str {
144 match self {
145 Unit::Point => "pt",
146 Unit::Pica => "pi",
147 Unit::Inch => "in",
148 Unit::Millimeter => "mm",
149 Unit::Centimeter => "cm",
150 Unit::Pixel => "px",
151 Unit::Em => "em",
152 }
153 }
154}
155
156/// A Scheme value
157///
158/// Corresponds to OpenJade's `ELObj` base class.
159///
160/// **Memory management**: Uses `Gc<T>` for heap-allocated values.
161/// The `gc` crate provides conservative garbage collection similar
162/// to OpenJade's `Collector`.
163///
164/// **Note**: We manually implement Trace/Finalize because Node and NodeList
165/// variants use Rc (not Gc) and contain trait objects.
166#[derive(Clone)]
167pub enum Value {
168 /// The empty list `()`
169 ///
170 /// OpenJade: `NilObj`
171 Nil,
172
173 /// Boolean: `#t` or `#f`
174 ///
175 /// OpenJade: `TrueObj` / `FalseObj`
176 Bool(bool),
177
178 /// Exact integer
179 ///
180 /// OpenJade: `IntegerObj` (long n_)
181 Integer(i64),
182
183 /// Inexact real number
184 ///
185 /// OpenJade: `RealObj` (double n_)
186 Real(f64),
187
188 /// DSSSL Quantity (dimensional value with unit)
189 ///
190 /// OpenJade: `LengthObj`
191 ///
192 /// Represents a dimensional quantity like `12pt`, `1.5in`, `210mm`.
193 /// Stores magnitude and unit, performs automatic unit conversion.
194 Quantity { magnitude: f64, unit: Unit },
195
196 /// Unicode character
197 ///
198 /// OpenJade: `CharObj` (Char ch_)
199 Char(char),
200
201 /// Immutable string
202 ///
203 /// OpenJade: `StringObj` (extends StringC)
204 ///
205 /// Using `Gc<String>` for garbage collection.
206 String(Gc<String>),
207
208 /// Interned symbol
209 ///
210 /// OpenJade: `SymbolObj` (StringObj* name_)
211 ///
212 /// Symbols are interned (shared) for efficiency.
213 /// Using `Rc<str>` since symbols are immutable and shared.
214 Symbol(Rc<str>),
215
216 /// Keyword (DSSSL extension)
217 ///
218 /// OpenJade: `KeywordObj`
219 ///
220 /// Keywords are like symbols but in a separate namespace.
221 Keyword(Rc<str>),
222
223 /// Cons cell (pair)
224 ///
225 /// OpenJade: `PairObj` (ELObj* car_, ELObj* cdr_)
226 ///
227 /// Using `Gc<PairData>` for garbage-collected pairs.
228 /// `GcCell` allows mutation (for set-car!/set-cdr!).
229 Pair(Gc<GcCell<PairData>>),
230
231 /// Vector (array)
232 ///
233 /// OpenJade: `VectorObj` (Vector<ELObj*>)
234 ///
235 /// Using `Gc<GcCell<Vec<Value>>>` for mutable vectors.
236 Vector(Gc<GcCell<Vec<Value>>>),
237
238 /// Procedure (function)
239 ///
240 /// OpenJade: `FunctionObj` (various subclasses)
241 ///
242 /// Can be:
243 /// - Built-in primitive (Rust function)
244 /// - User-defined lambda (compiled bytecode or AST)
245 Procedure(Gc<Procedure>),
246
247 // DSSSL types (grove and flow objects)
248 /// A node in the document grove
249 ///
250 /// Represents a node from the XML document tree.
251 /// Corresponds to OpenJade's node objects in the grove.
252 ///
253 /// **Phase 3**: Now fully implemented with libxml2 grove.
254 ///
255 /// Uses `Rc<Box<dyn Node>>` instead of Gc because:
256 /// - Nodes are owned by the grove, not the Scheme GC
257 /// - Grove lifetime is managed separately
258 /// - Trait objects can't derive Trace automatically
259 ///
260 /// NOTE: Managed by Rc, not GC - see manual Trace impl below
261 Node(Rc<Box<dyn crate::grove::Node>>),
262
263 /// Node list (grove query result)
264 ///
265 /// Represents a collection of nodes from grove queries.
266 /// Corresponds to DSSSL node-list objects.
267 ///
268 /// **Phase 3**: Now fully implemented with libxml2 grove.
269 ///
270 /// Uses `Rc<Box<dyn NodeList>>` for same reasons as Node.
271 ///
272 /// NOTE: Managed by Rc, not GC - see manual Trace impl below
273 NodeList(Rc<Box<dyn crate::grove::NodeList>>),
274
275 /// Sosofo (flow object sequence)
276 ///
277 /// Placeholder - will be properly implemented in Phase 4.
278 Sosofo,
279
280 /// Unspecified value
281 ///
282 /// Returned by expressions with unspecified results (like set!).
283 ///
284 /// OpenJade: `UnspecifiedObj`
285 Unspecified,
286
287 /// Error marker
288 ///
289 /// Used internally for error propagation.
290 ///
291 /// OpenJade: `ErrorObj`
292 Error,
293}
294
295/// Pair data (car and cdr)
296///
297/// Separated from `Value::Pair` to allow mutation via `GcCell`.
298#[derive(Clone, gc::Trace, gc::Finalize)]
299pub struct PairData {
300 pub car: Value,
301 pub cdr: Value,
302 /// Source position (for error reporting)
303 ///
304 /// Tracks where this pair (list expression) was parsed from.
305 /// Used to provide accurate error locations for expressions inside functions.
306 pub pos: Option<Position>,
307}
308
309impl PairData {
310 pub fn new(car: Value, cdr: Value) -> Self {
311 PairData { car, cdr, pos: None }
312 }
313
314 pub fn with_pos(car: Value, cdr: Value, pos: Position) -> Self {
315 PairData { car, cdr, pos: Some(pos) }
316 }
317}
318
319/// Procedure (function)
320///
321/// Can be either a built-in primitive or user-defined lambda.
322#[derive(gc::Finalize)]
323pub enum Procedure {
324 /// Built-in primitive function
325 ///
326 /// Takes arguments and returns a result.
327 /// Primitives can fail (return Err) or succeed (return Ok(Value)).
328 Primitive {
329 name: &'static str,
330 func: fn(&[Value]) -> Result<Value, String>,
331 },
332
333 /// User-defined lambda
334 ///
335 /// Captures:
336 /// - `params`: Parameter names (formal parameters, both required and optional)
337 /// - `required_count`: Number of required parameters (rest are optional)
338 /// - `optional_defaults`: Default expressions for optional parameters
339 /// - `body`: Expression to evaluate when called
340 /// - `env`: Closure environment (captures lexical scope)
341 /// - `source`: Source location (for error reporting)
342 /// - `name`: Optional name (for named procedures defined with `define`)
343 Lambda {
344 params: Gc<Vec<String>>,
345 required_count: usize,
346 optional_defaults: Gc<Vec<Value>>,
347 body: Gc<Value>,
348 env: Gc<crate::scheme::environment::Environment>,
349 source: Option<SourceInfo>,
350 name: Option<String>,
351 },
352}
353
354impl Clone for Procedure {
355 fn clone(&self) -> Self {
356 match self {
357 Procedure::Primitive { name, func } => Procedure::Primitive {
358 name,
359 func: *func,
360 },
361 Procedure::Lambda { params, required_count, optional_defaults, body, env, source, name } => Procedure::Lambda {
362 params: params.clone(),
363 required_count: *required_count,
364 optional_defaults: optional_defaults.clone(),
365 body: body.clone(),
366 env: env.clone(),
367 source: source.clone(),
368 name: name.clone(),
369 },
370 }
371 }
372}
373
374// Manual Trace implementation since function pointers don't need tracing
375unsafe impl gc::Trace for Procedure {
376 unsafe fn trace(&self) {
377 match self {
378 Procedure::Primitive { .. } => {
379 // Primitives don't have GC'd data
380 }
381 Procedure::Lambda { params, optional_defaults, body, env, source: _, name: _, required_count: _ } => {
382 // Trace the lambda's garbage-collected fields
383 // Note: source, name, and required_count are not GC'd, so we don't trace them
384 params.trace();
385 optional_defaults.trace();
386 body.trace();
387 env.trace();
388 }
389 }
390 }
391
392 unsafe fn root(&self) {
393 match self {
394 Procedure::Primitive { .. } => {}
395 Procedure::Lambda { params, optional_defaults, body, env, source: _, name: _, required_count: _ } => {
396 params.root();
397 optional_defaults.root();
398 body.root();
399 env.root();
400 }
401 }
402 }
403
404 unsafe fn unroot(&self) {
405 match self {
406 Procedure::Primitive { .. } => {}
407 Procedure::Lambda { params, optional_defaults, body, env, source: _, name: _, required_count: _ } => {
408 params.unroot();
409 optional_defaults.unroot();
410 body.unroot();
411 env.unroot();
412 }
413 }
414 }
415
416 fn finalize_glue(&self) {
417 gc::Finalize::finalize(self);
418 }
419}
420
421// =============================================================================
422// Value constructors (ergonomic API)
423// =============================================================================
424
425impl Value {
426 /// Create a boolean value
427 pub fn bool(b: bool) -> Self {
428 Value::Bool(b)
429 }
430
431 /// Create an integer value
432 pub fn integer(n: i64) -> Self {
433 Value::Integer(n)
434 }
435
436 /// Create a real value
437 pub fn real(n: f64) -> Self {
438 Value::Real(n)
439 }
440
441 /// Create a character value
442 pub fn char(ch: char) -> Self {
443 Value::Char(ch)
444 }
445
446 /// Create a string value
447 pub fn string(s: String) -> Self {
448 Value::String(Gc::new(s))
449 }
450
451 /// Create a symbol value
452 pub fn symbol(s: &str) -> Self {
453 Value::Symbol(Rc::from(s))
454 }
455
456 /// Create a keyword value
457 pub fn keyword(s: &str) -> Self {
458 Value::Keyword(Rc::from(s))
459 }
460
461 /// Create a cons cell (pair)
462 pub fn cons(car: Value, cdr: Value) -> Self {
463 Value::Pair(Gc::new(GcCell::new(PairData::new(car, cdr))))
464 }
465
466 /// Create a cons cell with source position
467 pub fn cons_with_pos(car: Value, cdr: Value, pos: Position) -> Self {
468 Value::Pair(Gc::new(GcCell::new(PairData::with_pos(car, cdr, pos))))
469 }
470
471 /// Create a vector
472 pub fn vector(elements: Vec<Value>) -> Self {
473 Value::Vector(Gc::new(GcCell::new(elements)))
474 }
475
476 /// Create a built-in primitive procedure
477 pub fn primitive(name: &'static str, func: fn(&[Value]) -> Result<Value, String>) -> Self {
478 Value::Procedure(Gc::new(Procedure::Primitive { name, func }))
479 }
480
481 /// Create a user-defined lambda procedure
482 pub fn lambda(
483 params: Vec<String>,
484 body: Value,
485 env: Gc<crate::scheme::environment::Environment>,
486 ) -> Self {
487 let required_count = params.len();
488 Value::Procedure(Gc::new(Procedure::Lambda {
489 params: Gc::new(params),
490 required_count,
491 optional_defaults: Gc::new(Vec::new()),
492 body: Gc::new(body),
493 env,
494 source: None,
495 name: None,
496 }))
497 }
498
499 /// Create a user-defined lambda procedure with source location
500 pub fn lambda_with_source(
501 params: Vec<String>,
502 body: Value,
503 env: Gc<crate::scheme::environment::Environment>,
504 source: Option<SourceInfo>,
505 name: Option<String>,
506 ) -> Self {
507 let required_count = params.len();
508 Value::Procedure(Gc::new(Procedure::Lambda {
509 params: Gc::new(params),
510 required_count,
511 optional_defaults: Gc::new(Vec::new()),
512 body: Gc::new(body),
513 env,
514 source,
515 name,
516 }))
517 }
518
519 /// Create a user-defined lambda procedure with optional parameters
520 pub fn lambda_with_optional(
521 params: Vec<String>,
522 required_count: usize,
523 optional_defaults: Vec<Value>,
524 body: Value,
525 env: Gc<crate::scheme::environment::Environment>,
526 source: Option<SourceInfo>,
527 name: Option<String>,
528 ) -> Self {
529 Value::Procedure(Gc::new(Procedure::Lambda {
530 params: Gc::new(params),
531 required_count,
532 optional_defaults: Gc::new(optional_defaults),
533 body: Gc::new(body),
534 env,
535 source,
536 name,
537 }))
538 }
539
540 /// Create a node value
541 pub fn node(node: Box<dyn crate::grove::Node>) -> Self {
542 Value::Node(Rc::new(node))
543 }
544
545 /// Create a node list value
546 pub fn node_list(node_list: Box<dyn crate::grove::NodeList>) -> Self {
547 Value::NodeList(Rc::new(node_list))
548 }
549}
550
551// =============================================================================
552// Value equality (Scheme equal? and eqv?)
553// =============================================================================
554
555impl Value {
556 /// Scheme `equal?` - Deep structural equality
557 ///
558 /// Corresponds to OpenJade's `ELObj::equal()`
559 ///
560 /// Recursively compares:
561 /// - Lists and vectors: Element-wise comparison
562 /// - Strings: Content comparison
563 /// - Numbers: Numeric equality
564 /// - Everything else: Same as `eqv?`
565 pub fn equal(&self, other: &Value) -> bool {
566 match (self, other) {
567 // Structural equality for lists
568 (Value::Pair(p1), Value::Pair(p2)) => {
569 let pair1 = p1.borrow();
570 let pair2 = p2.borrow();
571 pair1.car.equal(&pair2.car) && pair1.cdr.equal(&pair2.cdr)
572 }
573
574 // Structural equality for vectors
575 (Value::Vector(v1), Value::Vector(v2)) => {
576 let vec1 = v1.borrow();
577 let vec2 = v2.borrow();
578 if vec1.len() != vec2.len() {
579 return false;
580 }
581 vec1.iter().zip(vec2.iter()).all(|(a, b)| a.equal(b))
582 }
583
584 // String content comparison
585 (Value::String(s1), Value::String(s2)) => **s1 == **s2,
586
587 // Polymorphic string/symbol comparison (like OpenJade's stringData())
588 // This allows case statements to compare gi (symbol) with string literals
589 (Value::String(s), Value::Symbol(sym)) => s.as_ref() == sym.as_ref(),
590 (Value::Symbol(sym), Value::String(s)) => sym.as_ref() == s.as_ref(),
591
592 // For all other types, equal? is the same as eqv?
593 _ => self.eqv(other),
594 }
595 }
596
597 /// Scheme `eqv?` - Equivalence (same value, not necessarily same object)
598 ///
599 /// Corresponds to OpenJade's `ELObj::eqv()`
600 ///
601 /// Returns true if:
602 /// - Both are the same boolean value
603 /// - Both are the same number (integer or real)
604 /// - Both are the same character
605 /// - Both are the same symbol (symbols are interned)
606 /// - Both are the same keyword
607 /// - Both refer to the same pair/vector/procedure object
608 /// - Both are nil
609 pub fn eqv(&self, other: &Value) -> bool {
610 match (self, other) {
611 (Value::Nil, Value::Nil) => true,
612 (Value::Bool(b1), Value::Bool(b2)) => b1 == b2,
613 (Value::Integer(n1), Value::Integer(n2)) => n1 == n2,
614 (Value::Real(n1), Value::Real(n2)) => n1 == n2,
615 (Value::Quantity { magnitude: m1, unit: u1 }, Value::Quantity { magnitude: m2, unit: u2 }) => {
616 // Compare quantities: same value and same unit
617 (m1 - m2).abs() < 1e-10 && u1 == u2
618 }
619 (Value::Char(c1), Value::Char(c2)) => c1 == c2,
620
621 // Symbols and keywords: compare by content
622 // NOTE: Currently uses string content comparison (O(n)).
623 // FUTURE OPTIMIZATION: Implement global symbol table (interner) to enable
624 // pointer-equality comparison (O(1)). This would require:
625 // - SymbolTable in Evaluator to intern all symbols
626 // - Parser and Environment using the interner
627 // - Change to: Rc::ptr_eq(s1, s2)
628 // Current implementation is correct per R4RS, just not optimally fast.
629 (Value::Symbol(s1), Value::Symbol(s2)) => **s1 == **s2,
630 (Value::Keyword(k1), Value::Keyword(k2)) => **k1 == **k2,
631
632 // For heap-allocated objects, compare object identity
633 (Value::Pair(p1), Value::Pair(p2)) => Gc::ptr_eq(p1, p2),
634 (Value::Vector(v1), Value::Vector(v2)) => Gc::ptr_eq(v1, v2),
635 (Value::Procedure(proc1), Value::Procedure(proc2)) => Gc::ptr_eq(proc1, proc2),
636
637 // Strings are not compared by eqv? - use equal? or eq?
638 // (In Scheme, eqv? on strings is unspecified)
639 (Value::String(_), Value::String(_)) => false,
640
641 // Special types
642 (Value::Node(n1), Value::Node(n2)) => {
643 // Nodes are equal by pointer identity (same Rc)
644 Rc::ptr_eq(n1, n2)
645 }
646 (Value::NodeList(nl1), Value::NodeList(nl2)) => {
647 // NodeLists are equal by pointer identity (same object)
648 Rc::ptr_eq(nl1, nl2)
649 }
650 (Value::Sosofo, Value::Sosofo) => true,
651 (Value::Unspecified, Value::Unspecified) => true,
652 (Value::Error, Value::Error) => true,
653
654 // Different types are never eqv?
655 _ => false,
656 }
657 }
658
659 /// Scheme `eq?` - Object identity (same object in memory)
660 ///
661 /// For most types, same as `eqv?`. Symbols and keywords are interned,
662 /// so `eq?` and `eqv?` are equivalent for them.
663 pub fn eq(&self, other: &Value) -> bool {
664 // For our implementation, eq? is the same as eqv?
665 // since we intern symbols and use Gc pointers for heap objects
666 self.eqv(other)
667 }
668}
669
670// =============================================================================
671// Value predicates (type checking)
672// =============================================================================
673
674impl Value {
675 /// Is this the nil value?
676 pub fn is_nil(&self) -> bool {
677 matches!(self, Value::Nil)
678 }
679
680 /// Is this a boolean?
681 pub fn is_bool(&self) -> bool {
682 matches!(self, Value::Bool(_))
683 }
684
685 /// Is this true? (for conditionals)
686 ///
687 /// In Scheme, only `#f` is false; everything else (including nil) is true.
688 pub fn is_true(&self) -> bool {
689 !matches!(self, Value::Bool(false))
690 }
691
692 /// Is this an integer?
693 pub fn is_integer(&self) -> bool {
694 matches!(self, Value::Integer(_))
695 }
696
697 /// Is this a real number?
698 pub fn is_real(&self) -> bool {
699 matches!(self, Value::Real(_))
700 }
701
702 /// Is this a number (integer or real)?
703 pub fn is_number(&self) -> bool {
704 matches!(self, Value::Integer(_) | Value::Real(_))
705 }
706
707 /// Is this a character?
708 pub fn is_char(&self) -> bool {
709 matches!(self, Value::Char(_))
710 }
711
712 /// Is this a string?
713 pub fn is_string(&self) -> bool {
714 matches!(self, Value::String(_))
715 }
716
717 /// Is this a symbol?
718 pub fn is_symbol(&self) -> bool {
719 matches!(self, Value::Symbol(_))
720 }
721
722 /// Is this a pair?
723 pub fn is_pair(&self) -> bool {
724 matches!(self, Value::Pair(_))
725 }
726
727 /// Is this a list? (nil or pair)
728 pub fn is_list(&self) -> bool {
729 matches!(self, Value::Nil | Value::Pair(_))
730 }
731
732 /// Is this a vector?
733 pub fn is_vector(&self) -> bool {
734 matches!(self, Value::Vector(_))
735 }
736
737 /// Is this a procedure?
738 pub fn is_procedure(&self) -> bool {
739 matches!(self, Value::Procedure(_))
740 }
741
742 /// Is this a node?
743 pub fn is_node(&self) -> bool {
744 matches!(self, Value::Node(_))
745 }
746
747 /// Is this a node list?
748 pub fn is_node_list(&self) -> bool {
749 matches!(self, Value::NodeList(_))
750 }
751}
752
753// =============================================================================
754// Display / Debug
755// =============================================================================
756
757impl fmt::Debug for Value {
758 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
759 match self {
760 Value::Nil => write!(f, "()"),
761 Value::Bool(true) => write!(f, "#t"),
762 Value::Bool(false) => write!(f, "#f"),
763 Value::Integer(n) => write!(f, "{}", n),
764 Value::Real(n) => write!(f, "{}", n),
765 Value::Quantity { magnitude, unit } => write!(f, "{}{}", magnitude, unit.suffix()),
766 Value::Char(ch) => write!(f, "#\\{}", ch),
767 Value::String(s) => write!(f, "{:?}", **s),
768 Value::Symbol(s) => write!(f, "{}", s),
769 Value::Keyword(s) => write!(f, "#:{}", s),
770 Value::Pair(p) => {
771 let pair = p.borrow();
772 write!(f, "({:?} . {:?})", pair.car, pair.cdr)
773 }
774 Value::Vector(v) => {
775 let vec = v.borrow();
776 write!(f, "#(")?;
777 for (i, val) in vec.iter().enumerate() {
778 if i > 0 {
779 write!(f, " ")?;
780 }
781 write!(f, "{:?}", val)?;
782 }
783 write!(f, ")")
784 }
785 Value::Procedure(proc) => match &**proc {
786 Procedure::Primitive { name, .. } => write!(f, "#<primitive:{}>", name),
787 Procedure::Lambda { .. } => write!(f, "#<lambda>"),
788 },
789 Value::Node(node) => {
790 // Display node with its gi if available
791 if let Some(gi) = node.gi() {
792 write!(f, "#<node:{}>", gi)
793 } else {
794 write!(f, "#<node>")
795 }
796 }
797 Value::NodeList(nl) => write!(f, "#<node-list:{}>", nl.length()),
798 Value::Sosofo => write!(f, "#<sosofo>"),
799 Value::Unspecified => write!(f, "#<unspecified>"),
800 Value::Error => write!(f, "#<error>"),
801 }
802 }
803}
804
805// Implement Display to show values in a Scheme-readable way
806impl fmt::Display for Value {
807 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
808 write!(f, "{:?}", self)
809 }
810}
811
812// =============================================================================
813// Garbage Collection (Manual Trace Implementation)
814// =============================================================================
815
816/// Manual implementation of Trace for Value
817///
818/// We implement this manually because:
819/// - Node and NodeList use Rc<Box<dyn Trait>>, which doesn't implement Trace
820/// - These are managed by Rc, not the GC
821/// - All other variants use Gc and need proper tracing
822unsafe impl gc::Trace for Value {
823 unsafe fn trace(&self) {
824 match self {
825 Value::Nil => {}
826 Value::Bool(_) => {}
827 Value::Integer(_) => {}
828 Value::Real(_) => {}
829 Value::Quantity { .. } => {}
830 Value::Char(_) => {}
831 Value::String(s) => s.trace(),
832 Value::Symbol(s) => s.trace(),
833 Value::Keyword(k) => k.trace(),
834 Value::Pair(p) => p.trace(),
835 Value::Vector(v) => v.trace(),
836 Value::Procedure(proc) => proc.trace(),
837 // Node and NodeList use Rc, not Gc - no tracing needed
838 Value::Node(_) => {}
839 Value::NodeList(_) => {}
840 Value::Sosofo => {}
841 Value::Unspecified => {}
842 Value::Error => {}
843 }
844 }
845
846 unsafe fn root(&self) {
847 match self {
848 Value::String(s) => s.root(),
849 Value::Symbol(s) => s.root(),
850 Value::Keyword(k) => k.root(),
851 Value::Pair(p) => p.root(),
852 Value::Vector(v) => v.root(),
853 Value::Procedure(proc) => proc.root(),
854 _ => {}
855 }
856 }
857
858 unsafe fn unroot(&self) {
859 match self {
860 Value::String(s) => s.unroot(),
861 Value::Symbol(s) => s.unroot(),
862 Value::Keyword(k) => k.unroot(),
863 Value::Pair(p) => p.unroot(),
864 Value::Vector(v) => v.unroot(),
865 Value::Procedure(proc) => proc.unroot(),
866 _ => {}
867 }
868 }
869
870 fn finalize_glue(&self) {
871 match self {
872 Value::String(s) => s.finalize_glue(),
873 Value::Symbol(s) => s.finalize_glue(),
874 Value::Keyword(k) => k.finalize_glue(),
875 Value::Pair(p) => p.finalize_glue(),
876 Value::Vector(v) => v.finalize_glue(),
877 Value::Procedure(proc) => proc.finalize_glue(),
878 _ => {}
879 }
880 }
881}
882
883/// Manual implementation of Finalize for Value
884///
885/// No finalization needed - all cleanup is handled by Drop impls
886impl gc::Finalize for Value {}
887
888#[cfg(test)]
889mod tests {
890 use super::*;
891
892 #[test]
893 fn test_value_constructors() {
894 assert!(Value::bool(true).is_bool());
895 assert!(Value::integer(42).is_integer());
896 assert!(Value::real(3.14).is_real());
897 assert!(Value::char('a').is_char());
898 assert!(Value::string("hello".to_string()).is_string());
899 assert!(Value::symbol("foo").is_symbol());
900 assert!(Value::Nil.is_nil());
901 }
902
903 #[test]
904 fn test_truth_values() {
905 assert!(!Value::Bool(false).is_true());
906 assert!(Value::Bool(true).is_true());
907 assert!(Value::Nil.is_true()); // nil is true in Scheme!
908 assert!(Value::integer(0).is_true()); // 0 is true in Scheme!
909 }
910
911 #[test]
912 fn test_cons() {
913 let pair = Value::cons(Value::integer(1), Value::integer(2));
914 assert!(pair.is_pair());
915 assert!(pair.is_list());
916 }
917
918 #[test]
919 fn test_vector() {
920 let vec = Value::vector(vec![Value::integer(1), Value::integer(2), Value::integer(3)]);
921 assert!(vec.is_vector());
922 }
923
924 #[test]
925 fn test_equality_simple() {
926 // Numbers
927 assert!(Value::integer(42).eqv(&Value::integer(42)));
928 assert!(!Value::integer(42).eqv(&Value::integer(43)));
929 assert!(Value::real(3.14).eqv(&Value::real(3.14)));
930
931 // Booleans
932 assert!(Value::bool(true).eqv(&Value::bool(true)));
933 assert!(!Value::bool(true).eqv(&Value::bool(false)));
934
935 // Characters
936 assert!(Value::char('a').eqv(&Value::char('a')));
937 assert!(!Value::char('a').eqv(&Value::char('b')));
938
939 // Symbols (compared by content for now)
940 let sym1 = Value::symbol("foo");
941 let sym2 = Value::symbol("foo");
942 assert!(sym1.eqv(&sym2)); // Same symbol content
943
944 // Nil
945 assert!(Value::Nil.eqv(&Value::Nil));
946 }
947
948 #[test]
949 fn test_equality_strings() {
950 let s1 = Value::string("hello".to_string());
951 let s2 = Value::string("hello".to_string());
952 let s3 = Value::string("world".to_string());
953
954 // eqv? is false for different string objects (even with same content)
955 assert!(!s1.eqv(&s2));
956
957 // equal? compares string content
958 assert!(s1.equal(&s2));
959 assert!(!s1.equal(&s3));
960 }
961
962 #[test]
963 fn test_equality_lists() {
964 let list1 = Value::cons(
965 Value::integer(1),
966 Value::cons(Value::integer(2), Value::Nil),
967 );
968 let list2 = Value::cons(
969 Value::integer(1),
970 Value::cons(Value::integer(2), Value::Nil),
971 );
972 let list3 = Value::cons(
973 Value::integer(1),
974 Value::cons(Value::integer(3), Value::Nil),
975 );
976
977 // eqv? is false for different pair objects
978 assert!(!list1.eqv(&list2));
979
980 // equal? compares list structure
981 assert!(list1.equal(&list2));
982 assert!(!list1.equal(&list3));
983 }
984
985 #[test]
986 fn test_equality_vectors() {
987 let vec1 = Value::vector(vec![Value::integer(1), Value::integer(2)]);
988 let vec2 = Value::vector(vec![Value::integer(1), Value::integer(2)]);
989 let vec3 = Value::vector(vec![Value::integer(1), Value::integer(3)]);
990
991 // eqv? is false for different vector objects
992 assert!(!vec1.eqv(&vec2));
993
994 // equal? compares vector contents
995 assert!(vec1.equal(&vec2));
996 assert!(!vec1.equal(&vec3));
997 }
998}