Skip to main content

grift_parser/
value.rs

1//! Value types for the Lisp parser
2//!
3//! This module contains the core Value enum and related types like Builtin and StdLib.
4//!
5//! Note: The `define_builtins!` and `define_stdlib!` macros have been moved to `src/macros.rs`.
6
7use grift_arena::{ArenaIndex, Trace};
8
9// Define all built-in functions using the macro.
10// To add a new builtin, add an entry here and implement its evaluation in grift_eval.
11define_builtins! {
12    // List operations
13    /// car - Get first element of pair
14    Car => "car",
15    /// cdr - Get second element of pair
16    Cdr => "cdr",
17    /// cons - Create a pair
18    Cons => "cons",
19    /// list - Create a list from arguments
20    List => "list",
21    
22    // Predicates (Scheme R7RS compliant)
23    /// null? - Check if value is the empty list
24    Null => "null?",
25    /// pair? - Check if value is a pair
26    Pairp => "pair?",
27    /// number? - Check if value is a number
28    Numberp => "number?",
29    /// boolean? - Check if value is a boolean
30    Booleanp => "boolean?",
31    /// procedure? - Check if value is a procedure
32    Procedurep => "procedure?",
33    /// symbol? - Check if value is a symbol
34    Symbolp => "symbol?",
35    /// eq? - Scheme-compliant identity equality
36    EqP => "eq?",
37    /// eqv? - Scheme-compliant value equality
38    EqvP => "eqv?",
39    /// equal? - Scheme-compliant recursive structural equality
40    EqualP => "equal?",
41    
42    // Arithmetic
43    /// + - Addition
44    Add => "+",
45    /// - - Subtraction
46    Sub => "-",
47    /// * - Multiplication
48    Mul => "*",
49    /// / - Division
50    Div => "/",
51    /// modulo - Scheme modulo (result has sign of divisor)
52    Modulo => "modulo",
53    /// remainder - Scheme remainder (result has sign of dividend)
54    Remainder => "remainder",
55    /// quotient - Integer quotient (truncated towards zero)
56    Quotient => "quotient",
57    /// abs - Absolute value
58    Abs => "abs",
59    /// max - Maximum of numbers
60    Max => "max",
61    /// min - Minimum of numbers
62    Min => "min",
63    /// gcd - Greatest common divisor
64    Gcd => "gcd",
65    /// lcm - Least common multiple
66    Lcm => "lcm",
67    /// expt - Exponentiation
68    Expt => "expt",
69    /// square - Square of a number
70    Square => "square",
71    
72    // Numeric predicates
73    /// zero? - Check if number is zero
74    Zerop => "zero?",
75    /// positive? - Check if number is positive
76    Positivep => "positive?",
77    /// negative? - Check if number is negative
78    Negativep => "negative?",
79    /// odd? - Check if number is odd
80    Oddp => "odd?",
81    /// even? - Check if number is even
82    Evenp => "even?",
83    /// integer? - Check if value is an integer
84    Integerp => "integer?",
85    /// exact? - Check if number is exact (always true for integers)
86    Exactp => "exact?",
87    /// inexact? - Check if number is inexact (always false for integers)
88    Inexactp => "inexact?",
89    /// exact-integer? - Check if value is an exact integer
90    ExactIntegerp => "exact-integer?",
91    
92    // Rounding operations (R7RS Section 6.2.6) - Identity for integers
93    /// floor - Largest integer not greater than x (identity for integers)
94    Floor => "floor",
95    /// ceiling - Smallest integer not less than x (identity for integers)
96    Ceiling => "ceiling",
97    /// truncate - Integer closest to x whose absolute value is not larger (identity for integers)
98    Truncate => "truncate",
99    /// round - Closest integer to x, rounding to even when x is halfway (identity for integers)
100    Round => "round",
101    
102    // Comparison
103    /// < - Less than
104    Lt => "<",
105    /// > - Greater than
106    Gt => ">",
107    /// <= - Less than or equal
108    Le => "<=",
109    /// >= - Greater than or equal
110    Ge => ">=",
111    /// = - Numeric equality
112    NumEq => "=",
113    
114    // Boolean operations
115    /// not - Boolean negation
116    Not => "not",
117    
118    // I/O
119    /// newline - Print a newline
120    Newline => "newline",
121    /// display - Print value without quotes
122    Display => "display",
123    
124    // Error handling
125    /// error - Raise an error
126    Error => "error",
127    
128    // Mutation operations
129    /// set-car! - Mutate car of pair
130    SetCar => "set-car!",
131    /// set-cdr! - Mutate cdr of pair
132    SetCdr => "set-cdr!",
133    
134    // Vector operations (R7RS Section 6.8)
135    /// vector? - Check if value is a vector
136    Vectorp => "vector?",
137    /// make-vector - Create a vector with optional fill value
138    MakeVector => "make-vector",
139    /// vector - Create vector from arguments
140    Vector => "vector",
141    /// vector-length - Get length of vector
142    VectorLength => "vector-length",
143    /// vector-ref - Get element at index
144    VectorRef => "vector-ref",
145    /// vector-set! - Set element at index
146    VectorSet => "vector-set!",
147    /// vector->list - Convert vector to list
148    VectorToList => "vector->list",
149    /// list->vector - Convert list to vector
150    ListToVector => "list->vector",
151    /// vector-fill! - Fill vector with value
152    VectorFill => "vector-fill!",
153    /// vector-copy - Copy a vector
154    VectorCopy => "vector-copy",
155    
156    // Character operations (R7RS Section 6.6)
157    /// char? - Check if value is a character
158    Charp => "char?",
159    /// char=? - Character equality
160    CharEq => "char=?",
161    /// char<? - Character less than
162    CharLt => "char<?",
163    /// char>? - Character greater than
164    CharGt => "char>?",
165    /// char<=? - Character less than or equal
166    CharLe => "char<=?",
167    /// char>=? - Character greater than or equal
168    CharGe => "char>=?",
169    /// char->integer - Convert character to its Unicode code point
170    CharToInteger => "char->integer",
171    /// integer->char - Convert Unicode code point to character
172    IntegerToChar => "integer->char",
173    /// char-upcase - Convert character to uppercase
174    CharUpcase => "char-upcase",
175    /// char-downcase - Convert character to lowercase
176    CharDowncase => "char-downcase",
177    
178    // String operations (R7RS Section 6.7)
179    /// string? - Check if value is a string
180    Stringp => "string?",
181    /// make-string - Create a string of given length
182    MakeString => "make-string",
183    /// string - Create string from characters
184    String => "string",
185    /// string-length - Get length of string
186    StringLength => "string-length",
187    /// string-ref - Get character at index
188    StringRef => "string-ref",
189    /// string-set! - Set character at index
190    StringSet => "string-set!",
191    /// string=? - String equality
192    StringEq => "string=?",
193    /// string<? - String less than
194    StringLt => "string<?",
195    /// string>? - String greater than
196    StringGt => "string>?",
197    /// string<=? - String less than or equal
198    StringLe => "string<=?",
199    /// string>=? - String greater than or equal
200    StringGe => "string>=?",
201    /// string-append - Concatenate strings
202    StringAppend => "string-append",
203    /// string->list - Convert string to list of characters
204    StringToList => "string->list",
205    /// list->string - Convert list of characters to string
206    ListToString => "list->string",
207    /// substring - Extract a substring
208    Substring => "substring",
209    /// string-copy - Copy a string
210    StringCopy => "string-copy",
211    
212    // Garbage collection and arena control
213    /// gc - Manually trigger garbage collection
214    Gc => "gc",
215    /// gc-enable - Enable automatic garbage collection
216    GcEnable => "gc-enable",
217    /// gc-disable - Disable automatic garbage collection
218    GcDisable => "gc-disable",
219    /// gc-enabled? - Check if GC is enabled
220    GcEnabledP => "gc-enabled?",
221    /// arena-stats - Get arena statistics as a list
222    ArenaStats => "arena-stats",
223}
224
225// Define all standard library functions using the include_stdlib! macro.
226// This macro reads the stdlib.scm file and generates the StdLib enum.
227// To add a new function, simply add a new entry in stdlib.scm.
228// Note: member/assoc use eq? for comparison (like Scheme's memq/assq).
229// This works for symbols and identical objects. For value comparison,
230// define a custom function or use fold with a predicate.
231grift_macros::include_stdlib!("src/stdlib.scm");
232
233/// A Lisp value
234/// 
235/// # Memory Optimization
236/// 
237/// This enum inlines fixed-size data directly into variants to save arena slots
238/// and improve cache locality. Variable-length data (strings, arrays) still uses
239/// arena storage but with inline length for O(1) access.
240#[derive(Clone, Copy, Debug, PartialEq)]
241pub enum Value {
242    /// The empty list (NOT false - use False for that)
243    Nil,
244    
245    /// Boolean true (#t)
246    True,
247    
248    /// Boolean false (#f) - the ONLY false value
249    False,
250    
251    /// Integer number
252    Number(isize),
253    
254    /// Single character (used in strings and symbol storage)
255    Char(char),
256    
257    /// Cons cell (pair) with inline car/cdr indices
258    /// 
259    /// Both car and cdr are stored inline, saving 2 arena slots per cons.
260    /// 
261    /// # Memory Savings
262    /// 
263    /// Previously: 1 slot for Cons + 2 slots for [Ref(car), Ref(cdr)] = 3 slots
264    /// Now: 1 slot for Cons with inline car/cdr = 1 slot (saves 2 slots)
265    /// 
266    /// Use `Lisp::cons()`, `Lisp::car()`, `Lisp::cdr()` to create and access.
267    Cons { car: ArenaIndex, cdr: ArenaIndex },
268    
269    /// Symbol (contains a contiguous string)
270    /// Points to a Value::String which contains the symbol name.
271    /// The length is obtained from the String value, providing a single source of truth.
272    Symbol(ArenaIndex),
273    
274    /// Lambda / closure with inline params and body_env indices
275    /// 
276    /// - `params`: ArenaIndex to list of parameter symbols
277    /// - `body_env`: ArenaIndex to a cons cell containing (body . env)
278    /// 
279    /// # Memory Savings
280    /// 
281    /// Previously: 1 slot for Lambda + 3 slots for [Ref(params), Ref(body), Ref(env)] = 4 slots
282    /// Now: 1 slot for Lambda with inline params/body_env + 1 cons for body.env = 2 slots (saves 2 slots)
283    /// 
284    /// Use `Lisp::lambda()` to create and `Lisp::lambda_parts()` to extract.
285    Lambda { params: ArenaIndex, body_env: ArenaIndex },
286    
287    /// Built-in function (optimized)
288    Builtin(Builtin),
289    
290    /// Standard library function (stored in static memory)
291    /// 
292    /// Unlike Lambda which stores code in the arena, StdLib references static
293    /// function definitions. The function body is parsed on each call.
294    /// 
295    /// # Memory Efficiency
296    /// 
297    /// - Function definitions are in static memory (const strings)
298    /// - No arena allocation for the function definition itself
299    /// - Parsed AST is temporary and GC'd after evaluation
300    StdLib(StdLib),
301    
302    /// Vector/Array with inline length and data pointer
303    /// 
304    /// Vectors store values contiguously in the arena. The length is inlined
305    /// for O(1) access, saving 1 arena slot per array.
306    /// 
307    /// # Memory Layout
308    /// 
309    /// - `len`: Number of elements (inline)
310    /// - `data`: Points directly to first element (no length header in arena)
311    /// - Empty arrays have len=0 and data == NIL
312    /// 
313    /// # Memory Savings
314    /// 
315    /// Previously: 1 slot for Array + Number(len) header + elements
316    /// Now: 1 slot for Array with inline len + elements only (saves 1 slot)
317    /// 
318    /// # Example
319    /// 
320    /// ```lisp
321    /// (define vec (make-vector 3 0))  ; Create vector of 3 zeros
322    /// (vector-set! vec 1 42)          ; Set index 1 to 42
323    /// (vector-ref vec 1)              ; => 42
324    /// (vector-length vec)             ; => 3 (O(1) - inline!)
325    /// #(1 2 3)                        ; Vector literal syntax
326    /// ```
327    Array { len: usize, data: ArenaIndex },
328    
329    /// String with inline length and data pointer
330    /// 
331    /// Strings store characters contiguously in the arena. The length is inlined
332    /// for O(1) access, saving 1 arena slot per string.
333    /// 
334    /// # Memory Layout
335    /// 
336    /// - `len`: Number of characters (inline)
337    /// - `data`: Points directly to first Char value (no length header in arena)
338    /// - Empty strings have len=0 and data == NIL
339    /// 
340    /// # Memory Savings
341    /// 
342    /// Previously: 1 slot for String + Number(len) header + chars
343    /// Now: 1 slot for String with inline len + chars only (saves 1 slot)
344    /// 
345    /// # Example
346    /// 
347    /// ```lisp
348    /// (string-length "hello")   ; => 5 (O(1) - inline!)
349    /// (string-ref "hello" 0)    ; => #\h
350    /// ```
351    String { len: usize, data: ArenaIndex },
352    
353    /// Native function with inline id and name_hash
354    ///
355    /// Native functions are registered at runtime and identified by their ID.
356    /// The actual function pointer is stored in the evaluator's NativeRegistry.
357    ///
358    /// # Memory Savings
359    /// 
360    /// Previously: 1 slot for Native + 2 slots for [Usize(id), Usize(name_hash)] = 3 slots
361    /// Now: 1 slot for Native with inline id/name_hash = 1 slot (saves 2 slots)
362    Native { id: usize, name_hash: usize },
363    
364    /// Raw arena index reference
365    /// 
366    /// Used internally for storing arena indices in contiguous blocks.
367    /// This allows other variants (like Cons) to store their references
368    /// in the arena rather than inline, enabling memory optimizations.
369    /// 
370    /// # Note
371    /// 
372    /// This is an internal implementation detail and should not be
373    /// exposed to Lisp code directly. It's traced by the GC like any
374    /// other reference.
375    Ref(ArenaIndex),
376    
377    /// Unsigned integer (internal use)
378    /// 
379    /// Used for storing unsigned values like array lengths, indices,
380    /// or other internal counters that need the full positive range
381    /// of a machine word.
382    /// 
383    /// # Note
384    /// 
385    /// This is primarily an internal implementation detail. For user-facing
386    /// integers, prefer `Number(isize)` which supports negative values.
387    Usize(usize),
388    
389    /// Syntax-rules macro transformer
390    ///
391    /// Stores a compiled macro with literals, rules, and definition environment.
392    /// Following the same 2-index constraint as Lambda, we pack fields into cons cells.
393    ///
394    /// # Memory Layout
395    ///
396    /// - `literals`: ArenaIndex to list of literal keyword symbols
397    /// - `rules_env`: ArenaIndex to cons cell (rules . definition_env)
398    ///   - car: list of (pattern . template) pairs
399    ///   - cdr: environment where macro was defined (for hygiene)
400    ///
401    /// This matches Lambda's layout: 2 inline ArenaIndex fields = 1 arena slot.
402    ///
403    /// # Example
404    ///
405    /// ```scheme
406    /// (define-syntax when
407    ///   (syntax-rules ()
408    ///     ((when test body ...)
409    ///      (if test (begin body ...)))))
410    /// ```
411    ///
412    /// Stored as:
413    /// - literals: ()
414    /// - rules_env: (rules_list . global_env)
415    ///   where rules_list = (((when test body ...) . (if test (begin body ...))))
416    SyntaxRules {
417        literals: ArenaIndex,   // list of literal keyword symbols
418        rules_env: ArenaIndex,  // cons cell: (rules . definition_env)
419    },
420}
421
422impl Value {
423    /// Check if this value is nil (empty list)
424    #[inline]
425    pub const fn is_nil(&self) -> bool {
426        matches!(self, Value::Nil)
427    }
428    
429    /// Check if this value is false (#f)
430    /// This is the ONLY way to be false in this Lisp
431    #[inline]
432    pub const fn is_false(&self) -> bool {
433        matches!(self, Value::False)
434    }
435    
436    /// Check if this value is true (#t)
437    #[inline]
438    pub const fn is_true(&self) -> bool {
439        matches!(self, Value::True)
440    }
441    
442    /// Check if this value is a boolean (#t or #f)
443    #[inline]
444    pub const fn is_boolean(&self) -> bool {
445        matches!(self, Value::True | Value::False)
446    }
447    
448    /// Check if this value is an atom (not a cons cell)
449    #[inline]
450    pub const fn is_atom(&self) -> bool {
451        !matches!(self, Value::Cons { .. })
452    }
453    
454    /// Check if this value is a number
455    #[inline]
456    pub const fn is_number(&self) -> bool {
457        matches!(self, Value::Number(_))
458    }
459    
460    /// Check if this value is an integer
461    #[inline]
462    pub const fn is_integer(&self) -> bool {
463        matches!(self, Value::Number(_))
464    }
465    
466    /// Extract ArenaIndex from a Ref value.
467    /// Returns None if not a Ref.
468    #[inline]
469    pub const fn as_ref(&self) -> Option<ArenaIndex> {
470        match self {
471            Value::Ref(idx) => Some(*idx),
472            _ => None,
473        }
474    }
475    
476    /// Extract ArenaIndex from a Ref value, panicking if not a Ref.
477    /// Use only when you are certain the value is a Ref (e.g., after alloc_contiguous for Refs).
478    #[inline]
479    pub fn unwrap_ref(self) -> ArenaIndex {
480        match self {
481            Value::Ref(idx) => idx,
482            _ => panic!("expected Ref"),
483        }
484    }
485    
486    /// Check if this value is a symbol
487    #[inline]
488    pub const fn is_symbol(&self) -> bool {
489        matches!(self, Value::Symbol(_))
490    }
491    
492    /// Check if this value is a cons cell (pair)
493    #[inline]
494    pub const fn is_cons(&self) -> bool {
495        matches!(self, Value::Cons { .. })
496    }
497    
498    /// Check if this value is a lambda
499    #[inline]
500    pub const fn is_lambda(&self) -> bool {
501        matches!(self, Value::Lambda { .. })
502    }
503    
504    /// Check if this value is a builtin
505    #[inline]
506    pub const fn is_builtin(&self) -> bool {
507        matches!(self, Value::Builtin(_))
508    }
509    
510    /// Check if this value is a stdlib function
511    #[inline]
512    pub const fn is_stdlib(&self) -> bool {
513        matches!(self, Value::StdLib(_))
514    }
515    
516    /// Check if this value is a native (Rust) function
517    #[inline]
518    pub const fn is_native(&self) -> bool {
519        matches!(self, Value::Native { .. })
520    }
521    
522    /// Check if this value is a procedure (lambda, builtin, stdlib, or native function)
523    #[inline]
524    pub const fn is_procedure(&self) -> bool {
525        matches!(self, Value::Lambda { .. } | Value::Builtin(_) | Value::StdLib(_) | Value::Native { .. })
526    }
527    
528    /// Check if this value is an array
529    #[inline]
530    pub const fn is_array(&self) -> bool {
531        matches!(self, Value::Array { .. })
532    }
533    
534    /// Check if this value is a string
535    #[inline]
536    pub const fn is_string(&self) -> bool {
537        matches!(self, Value::String { .. })
538    }
539    
540    /// Check if this value is a ref (internal arena index reference)
541    #[inline]
542    pub const fn is_ref(&self) -> bool {
543        matches!(self, Value::Ref(_))
544    }
545    
546    /// Get the number value if this is an integer
547    #[inline]
548    pub const fn as_number(&self) -> Option<isize> {
549        match self {
550            Value::Number(n) => Some(*n),
551            _ => None,
552        }
553    }
554    
555    /// Get the char value if this is a char
556    #[inline]
557    pub const fn as_char(&self) -> Option<char> {
558        match self {
559            Value::Char(c) => Some(*c),
560            _ => None,
561        }
562    }
563    
564    /// Check if this value is a usize (internal unsigned integer)
565    #[inline]
566    pub const fn is_usize(&self) -> bool {
567        matches!(self, Value::Usize(_))
568    }
569    
570    /// Get the usize value if this is a Usize
571    #[inline]
572    pub const fn as_usize(&self) -> Option<usize> {
573        match self {
574            Value::Usize(n) => Some(*n),
575            _ => None,
576        }
577    }
578    
579    /// Get a human-readable type name
580    pub const fn type_name(&self) -> &'static str {
581        match self {
582            Value::Nil => "nil",
583            Value::True | Value::False => "boolean",
584            Value::Number(_) => "number",
585            Value::Char(_) => "char",
586            Value::Cons { .. } => "pair",
587            Value::Symbol(_) => "symbol",
588            Value::Lambda { .. } => "procedure",
589            Value::Builtin(_) => "procedure",
590            Value::StdLib(_) => "procedure",
591            Value::Native { .. } => "native",
592            Value::Array { .. } => "array",
593            Value::String { .. } => "string",
594            Value::Ref(_) => "ref",
595            Value::Usize(_) => "usize",
596            Value::SyntaxRules { .. } => "syntax-rules",
597        }
598    }
599    
600    /// Check if this value is a syntax-rules transformer
601    #[inline]
602    pub const fn is_syntax_rules(&self) -> bool {
603        matches!(self, Value::SyntaxRules { .. })
604    }
605}
606
607/// Implement Trace for GC support
608/// 
609/// With inline fields, tracing is simpler - we just trace the ArenaIndex fields directly.
610/// No arena access needed to determine structure, which improves GC performance.
611impl<const N: usize> Trace<Value, N> for Value {
612    fn trace<F: FnMut(ArenaIndex)>(&self, mut tracer: F) {
613        match self {
614            Value::Nil | Value::True | Value::False | 
615            Value::Number(_) | Value::Char(_) | Value::Builtin(_) |
616            Value::StdLib(_) | Value::Usize(_) => {
617                // No references
618            }
619            Value::Ref(idx) => {
620                // Trace the referenced value
621                tracer(*idx);
622            }
623            Value::Cons { car, cdr } => {
624                // Inline car and cdr - trace both directly
625                tracer(*car);
626                tracer(*cdr);
627            }
628            Value::Native { .. } => {
629                // id and name_hash are inline usize values, no arena references to trace
630            }
631            Value::Symbol(chars) => {
632                // chars points to a Value::String, which handles its own tracing
633                tracer(*chars);
634            }
635            Value::Lambda { params, body_env } => {
636                // params and body_env are inline ArenaIndex - trace both
637                // body_env points to a cons cell (body . env)
638                tracer(*params);
639                tracer(*body_env);
640            }
641            Value::SyntaxRules { literals, rules_env } => {
642                // literals and rules_env are inline ArenaIndex - trace both
643                // rules_env points to a cons cell (rules . definition_env)
644                tracer(*literals);
645                tracer(*rules_env);
646            }
647            Value::Array { len, data } => {
648                // For non-empty arrays, trace all elements
649                // Empty arrays have len=0 and data == NIL
650                if *len > 0 {
651                    let base_idx = data.raw();
652                    for i in 0..*len {
653                        // Elements are at data, data+1, ..., data+len-1
654                        let elem_idx = ArenaIndex::new(base_idx + i);
655                        tracer(elem_idx);
656                    }
657                }
658            }
659            Value::String { len, data } => {
660                // For non-empty strings, trace all Char slots
661                // Empty strings have len=0 and data == NIL
662                if *len > 0 {
663                    let base_idx = data.raw();
664                    for i in 0..*len {
665                        // Characters are at data, data+1, ..., data+len-1
666                        let char_idx = ArenaIndex::new(base_idx + i);
667                        tracer(char_idx);
668                    }
669                }
670            }
671        }
672    }
673    
674    fn trace_with_arena<F: FnMut(ArenaIndex)>(&self, _arena: &grift_arena::Arena<Value, N>, tracer: F) {
675        // With inline length fields, we no longer need arena access for tracing.
676        // Simply delegate to the standard trace method.
677        <Value as Trace<Value, N>>::trace(self, tracer)
678    }
679}