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}