Skip to main content

grift_core/
macros.rs

1//! Macros for the Lisp parser.
2//!
3//! This module contains macros for:
4//! - Defining builtin functions (`define_builtins!`)
5//! - Defining standard library functions (`define_stdlib!`)
6//! - Pack/unpack refs operations for Lisp context (`impl_pack_unpack_refs!`)
7
8// ============================================================================
9// Builtin Definition Macro
10// ============================================================================
11
12/// Macro for defining built-in functions.
13/// 
14/// This macro generates the `Builtin` enum, its `name()` method, and the `ALL` constant
15/// from a single declarative definition. To add a new builtin, simply add a new entry
16/// to the macro invocation (and implement its evaluation in grift_eval).
17/// 
18/// # Syntax
19/// 
20/// ```rust
21/// use grift_core::define_builtins;
22/// define_builtins! {
23///     /// Documentation comment
24///     VariantName => "lisp-name",
25///     // ... more builtins
26/// }
27/// ```
28/// 
29/// # Example
30/// 
31/// To add a new builtin `my-builtin`:
32/// 
33/// ```rust
34/// use grift_core::define_builtins;
35/// define_builtins! {
36///     // ... existing builtins ...
37///     /// (my-builtin x) - Does something with x
38///     MyBuiltin => "my-builtin",
39/// }
40/// ```
41/// 
42/// Note: After adding a builtin here, you must also implement its evaluation
43/// logic in the `grift_eval` crate.
44#[macro_export]
45macro_rules! define_builtins {
46    (
47        $(
48            $(#[$attr:meta])*
49            $variant:ident => $name:literal
50        ),* $(,)?
51    ) => {
52        /// Built-in functions (optimization to avoid symbol lookup)
53        /// 
54        /// NOTE: This Lisp supports mutation via set!, set-car!, and set-cdr!
55        /// - Mutation operations break referential transparency
56        /// - All evaluation is call-by-value (strict)
57        /// 
58        /// # Adding New Builtins
59        /// 
60        /// To add a new builtin:
61        /// 1. Add an entry to the `define_builtins!` macro invocation
62        /// 2. Implement its evaluation logic in `grift_eval`
63        #[derive(Clone, Copy, Debug, PartialEq, Eq)]
64        pub enum Builtin {
65            $(
66                $(#[$attr])*
67                $variant,
68            )*
69        }
70
71        impl Builtin {
72            /// Get the symbol name for this builtin
73            pub const fn name(&self) -> &'static str {
74                match self {
75                    $(
76                        Builtin::$variant => $name,
77                    )*
78                }
79            }
80            
81            /// All builtins for initialization
82            pub const ALL: &'static [Builtin] = &[
83                $(
84                    Builtin::$variant,
85                )*
86            ];
87            
88            /// Convert from usize discriminant (for continuation data stack encoding)
89            /// Returns the first builtin if out of range.
90            #[inline]
91            pub fn from_usize(n: usize) -> Self {
92                Self::ALL.get(n).copied().unwrap_or(Self::ALL[0])
93            }
94        }
95    };
96}
97
98// ============================================================================
99// Standard Library Definition Macro
100// ============================================================================
101
102/// Macro for defining standard library functions.
103/// 
104/// This macro generates the `StdLib` enum, its implementation, and the `ALL` constant
105/// from a single declarative definition. To add a new stdlib function, simply add
106/// a new entry to the macro invocation.
107/// 
108/// # Syntax
109/// 
110/// ```rust
111/// use grift_core::define_stdlib;
112/// define_stdlib! {
113///     /// Documentation comment
114///     VariantName("function-name", ["param1", "param2"], "lisp-body-code"),
115///     // ... more functions
116/// }
117/// ```
118/// 
119/// # Example
120/// 
121/// To add a new function `(my-func x y)` that returns `(+ x y)`:
122/// 
123/// ```rust
124/// use grift_core::define_stdlib;
125/// define_stdlib! {
126///     // ... existing functions ...
127///     /// (my-func x y) - Add two numbers
128///     MyFunc("my-func", ["x", "y"], "(+ x y)"),
129/// }
130/// ```
131#[macro_export]
132macro_rules! define_stdlib {
133    (
134        $(
135            $(#[$attr:meta])*
136            $variant:ident($name:literal, [$($param:literal),* $(,)?], $body:literal)
137        ),* $(,)?
138    ) => {
139        /// Standard library functions (stored in static memory, not arena)
140        /// 
141        /// These functions are defined as Lisp code in static strings and are parsed
142        /// on-demand when called. This provides:
143        /// - Zero arena cost for function definitions (static strings)
144        /// - Easy maintenance (just add entries to the `define_stdlib!` macro)
145        /// - Simple implementation (no build scripts needed)
146        /// 
147        /// The parsing overhead is minimal since:
148        /// 1. Standard library functions are typically called frequently (can optimize)
149        /// 2. Parsing is fast (simple recursive descent)
150        /// 3. Parsed AST is temporary and GC'd after evaluation
151        /// 
152        /// # Memory Layout
153        /// 
154        /// Each StdLib variant stores references to static data:
155        /// - Function name (for lookup and debugging)
156        /// - Parameter names (static slice)
157        /// - Body source code (static string, parsed on each call)
158        /// 
159        /// # Adding New Functions
160        /// 
161        /// To add a new stdlib function, add an entry to the `define_stdlib!` macro
162        /// invocation. No other code changes are needed.
163        #[derive(Clone, Copy, Debug, PartialEq, Eq)]
164        pub enum StdLib {
165            $(
166                $(#[$attr])*
167                $variant,
168            )*
169        }
170
171        impl StdLib {
172            /// Get the function name
173            pub const fn name(&self) -> &'static str {
174                match self {
175                    $(
176                        StdLib::$variant => $name,
177                    )*
178                }
179            }
180            
181            /// Get the parameter names for this function
182            pub const fn params(&self) -> &'static [&'static str] {
183                match self {
184                    $(
185                        StdLib::$variant => &[$($param),*],
186                    )*
187                }
188            }
189            
190            /// Get the body source code (Lisp expression as static string)
191            /// 
192            /// This string is parsed on each call to the function.
193            /// The parsed AST is temporary and GC'd after evaluation.
194            pub const fn body(&self) -> &'static str {
195                match self {
196                    $(
197                        StdLib::$variant => $body,
198                    )*
199                }
200            }
201            
202            /// All standard library functions for initialization
203            pub const ALL: &'static [StdLib] = &[
204                $(
205                    StdLib::$variant,
206                )*
207            ];
208        }
209    };
210}
211
212// ============================================================================
213// Pack/Unpack Refs Macro (Internal)
214// ============================================================================
215
216/// Internal macro to generate pack_refsN and unpack_refsN methods.
217/// Reduces ~120 lines of repetitive code to ~15 lines of macro invocations.
218macro_rules! impl_pack_unpack_refs {
219    // Special case for 1 (no contiguous allocation needed)
220    (1, $pack_name:ident, $unpack_name:ident) => {
221        #[inline]
222        pub fn $pack_name(&self, a: ArenaIndex) -> ArenaResult<ArenaIndex> {
223            self.arena.alloc(Value::Ref(a))
224        }
225        
226        #[inline]
227        pub fn $unpack_name(&self, data: ArenaIndex) -> ArenaResult<ArenaIndex> {
228            self.arena.get(data)?.as_ref().ok_or(ArenaError::InvalidIndex)
229        }
230    };
231    // General case for N >= 2
232    ($n:expr, $pack_name:ident, $unpack_name:ident, $set_fn:ident, $get_fn:ident, [$($var:ident),+ $(,)?]) => {
233        #[inline]
234        pub fn $pack_name(&self, $($var: ArenaIndex),+) -> ArenaResult<ArenaIndex> {
235            let data = self.arena.alloc_contiguous($n, Value::Nil)?;
236            self.arena.$set_fn(data, $(Value::Ref($var)),+)?;
237            Ok(data)
238        }
239        
240        #[inline]
241        pub fn $unpack_name(&self, data: ArenaIndex) -> ArenaResult<( $( impl_pack_unpack_refs!(@T $var) ),+ )> {
242            let ($($var),+) = self.arena.$get_fn(data)?;
243            match ($($var.as_ref()),+) {
244                ($(Some($var)),+) => Ok(($($var),+)),
245                _ => Err(ArenaError::InvalidIndex),
246            }
247        }
248    };
249    // Helper to generate ArenaIndex for tuple type
250    (@T $var:ident) => { ArenaIndex };
251}
252
253// Note: impl_pack_unpack_refs! is available crate-wide via #[macro_use] in lib.rs