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