cel_cxx/macros/mod.rs
1//! CEL macro system for extending the expression language.
2//!
3//! This module provides functionality for defining and registering custom macros
4//! that can expand CEL expressions at compile time. Macros enable syntactic sugar
5//! and domain-specific language extensions without modifying the core parser.
6//!
7//! # Overview
8//!
9//! CEL macros transform parsed expressions into new expression trees before type
10//! checking and evaluation. This allows you to:
11//!
12//! - Add new syntactic constructs (e.g., `has(foo.bar)` for presence testing)
13//! - Implement domain-specific operators (e.g., `all(list, predicate)`)
14//! - Optimize common patterns by rewriting expressions
15//! - Extend CEL with custom comprehension forms
16//!
17//! # Macro Types
18//!
19//! There are two types of macros:
20//!
21//! - **Global macros**: Called as functions, e.g., `all([1, 2, 3], x, x > 0)`
22//! - **Receiver macros**: Called as methods, e.g., `list.filter(x, x > 0)`
23//!
24//! Both types can accept fixed or variable numbers of arguments.
25//!
26//! # Examples
27//!
28//! ## Defining a global macro
29//!
30//! ```rust,no_run
31//! use cel_cxx::macros::{Macro, MacroExprFactory, Expr};
32//!
33//! // Macro that creates a constant expression with doubled value
34//! let double_macro = Macro::new_global("double", 1, |factory, args| {
35//! if let Some(expr) = args.first() {
36//! if let Some(val) = expr.kind().and_then(|k| k.as_constant()) {
37//! if let Some(int_val) = val.as_int() {
38//! return Some(factory.new_const(int_val * 2));
39//! }
40//! }
41//! }
42//! None
43//! });
44//! ```
45//!
46//! ## Defining a receiver macro
47//!
48//! ```rust,no_run
49//! # use cel_cxx::macros::{Macro, MacroExprFactory, Expr};
50//! // Macro for optional element access: list.maybe_get(index)
51//! let maybe_get_macro = Macro::new_receiver("maybe_get", 1, |factory, target, args| {
52//! // Implementation would handle optional list indexing
53//! Some(factory.new_call("_?.[]", &[target, args[0].clone()]))
54//! });
55//! ```
56
57use crate::Error;
58use crate::ffi::{
59 Macro as FfiMacro,
60 Expr as FfiExpr,
61 GlobalMacroExpander as FfiGlobalMacroExpander,
62 ReceiverMacroExpander as FfiReceiverMacroExpander,
63};
64
65mod expr;
66mod factory;
67mod expander;
68
69pub use expr::*;
70pub use factory::*;
71pub use expander::*;
72
73/// A CEL macro that expands expressions at compile time.
74///
75/// A `Macro` represents a compile-time transformation rule that can be registered
76/// with a CEL environment. When the parser encounters a matching function or method
77/// call, the macro's expander is invoked to transform the expression tree.
78///
79/// # Macro Expansion
80///
81/// During compilation, macros are expanded before type checking:
82/// 1. Parser identifies function/method calls matching registered macros
83/// 2. Macro expander receives the call expression and its arguments
84/// 3. Expander returns a new expression tree or `None` to keep original
85/// 4. Type checker validates the expanded expression
86///
87/// # Thread Safety
88///
89/// The expander functions captured in macros must be `Send + Sync + 'static`,
90/// ensuring thread-safe usage across the CEL environment.
91///
92/// # Error Handling
93///
94/// Macro creation methods return `Result<Self, Error>` and can fail if:
95/// - The macro name is invalid
96/// - Internal FFI allocation fails
97/// - The expander closure cannot be registered
98///
99/// # Examples
100///
101/// ## Fixed argument count
102///
103/// ```rust,no_run
104/// # use cel_cxx::macros::{Macro, MacroExprFactory, Expr};
105/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
106/// // A macro that requires exactly 1 argument
107/// let add_one_macro = Macro::new_global("add_one", 1, |factory, mut args| {
108/// let arg = args.pop()?;
109/// Some(factory.new_call("_+_", &[arg, factory.new_const(1)]))
110/// })?;
111/// # Ok(())
112/// # }
113/// ```
114///
115/// ## Variable argument count
116///
117/// ```rust,no_run
118/// # use cel_cxx::macros::{Macro, MacroExprFactory, Expr};
119/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
120/// // A macro that accepts any number of arguments
121/// let debug_macro = Macro::new_global_var_arg("debug", |factory, args| {
122/// // Wrap all arguments in a list for debugging
123/// let elements: Vec<_> = args.iter()
124/// .map(|arg| factory.new_list_element(arg, false))
125/// .collect();
126/// Some(factory.new_list(&elements))
127/// })?;
128/// # Ok(())
129/// # }
130/// ```
131pub struct Macro(pub(crate) cxx::UniquePtr<FfiMacro>);
132
133impl Macro {
134 /// Creates a new global macro with a fixed number of arguments.
135 ///
136 /// Global macros are invoked like regular functions, e.g., `macro_name(arg1, arg2)`.
137 /// The macro will only be expanded when called with exactly `argument_count` arguments.
138 ///
139 /// # Parameters
140 ///
141 /// - `name`: The function name that triggers this macro (as a string reference)
142 /// - `argument_count`: The exact number of arguments required
143 /// - `expander`: The expansion function that transforms the expression
144 ///
145 /// # Returns
146 ///
147 /// - `Ok(Macro)`: Successfully created macro
148 /// - `Err(Error)`: Failed to create macro (e.g., invalid name or FFI error)
149 ///
150 /// # Expander Function
151 ///
152 /// The expander receives:
153 /// - `factory`: A mutable reference to [`MacroExprFactory`] for creating new expressions
154 /// - `args`: A vector of argument expressions
155 ///
156 /// The expander should return:
157 /// - `Some(Expr)`: The expanded expression to replace the original call
158 /// - `None`: Keep the original expression unchanged
159 ///
160 /// # Examples
161 ///
162 /// ```rust,no_run
163 /// # use cel_cxx::macros::{Macro, MacroExprFactory, Expr};
164 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
165 /// // Macro: not_zero(x) expands to x != 0
166 /// let macro_def = Macro::new_global("not_zero", 1, |factory, mut args| {
167 /// let arg = args.pop()?;
168 /// Some(factory.new_call("_!=_", &[arg, factory.new_const(0)]))
169 /// })?;
170 /// # Ok(())
171 /// # }
172 /// ```
173 pub fn new_global(
174 name: impl AsRef<str>,
175 argument_count: usize,
176 expander: impl GlobalMacroExpander + 'static,
177 ) -> Result<Self, Error> {
178 let ffi_expander: cxx::UniquePtr<FfiGlobalMacroExpander> = FfiGlobalMacroExpander::new(
179 move |ffi_factory, ffi_expr| -> cxx::UniquePtr<FfiExpr> {
180 let factory = MacroExprFactory::new(ffi_factory);
181 let expr = ffi_expr.into_iter()
182 .map(|ffi_expr| Expr::from(&**ffi_expr))
183 .collect::<Vec<_>>();
184 if let Some(result) = expander(&factory, expr) {
185 result.into()
186 } else {
187 cxx::UniquePtr::null()
188 }
189 }
190 );
191 let ffi_macro = FfiMacro::new_global(name.as_ref().into(), argument_count, ffi_expander)?;
192 Ok(Macro(ffi_macro))
193 }
194
195 /// Creates a new global macro that accepts a variable number of arguments.
196 ///
197 /// Variable-argument macros can handle any number of arguments, from zero to many.
198 /// The expander function receives all arguments and decides how to handle them.
199 ///
200 /// # Parameters
201 ///
202 /// - `name`: The function name that triggers this macro (as a string reference)
203 /// - `expander`: The expansion function that transforms the expression
204 ///
205 /// # Returns
206 ///
207 /// - `Ok(Macro)`: Successfully created macro
208 /// - `Err(Error)`: Failed to create macro (e.g., invalid name or FFI error)
209 ///
210 /// # Expander Function
211 ///
212 /// The expander receives:
213 /// - `factory`: A mutable reference to [`MacroExprFactory`] for creating new expressions
214 /// - `args`: A vector of argument expressions (can be empty)
215 ///
216 /// The expander should return:
217 /// - `Some(Expr)`: The expanded expression to replace the original call
218 /// - `None`: Keep the original expression unchanged
219 ///
220 /// # Examples
221 ///
222 /// ```rust,no_run
223 /// # use cel_cxx::macros::{Macro, MacroExprFactory, Expr};
224 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
225 /// // Macro: max(args...) finds maximum of all arguments
226 /// let max_macro = Macro::new_global_var_arg("max", |factory, args| {
227 /// if args.is_empty() {
228 /// return None;
229 /// }
230 /// // Implementation would build comparison chain
231 /// Some(args.into_iter().reduce(|acc, arg| {
232 /// factory.new_call("_>_?_:_", &[acc, arg.clone(), acc.clone(), arg])
233 /// })?)
234 /// })?;
235 /// # Ok(())
236 /// # }
237 /// ```
238 pub fn new_global_var_arg(
239 name: impl AsRef<str>,
240 expander: impl GlobalMacroExpander + 'static,
241 ) -> Result<Self, Error> {
242 let ffi_expander: cxx::UniquePtr<FfiGlobalMacroExpander> = FfiGlobalMacroExpander::new(
243 move |ffi_factory, ffi_expr| -> cxx::UniquePtr<FfiExpr> {
244 let factory = MacroExprFactory::new(ffi_factory);
245 let expr = ffi_expr.into_iter()
246 .map(|ffi_expr| Expr::from(&**ffi_expr))
247 .collect::<Vec<_>>();
248 if let Some(result) = expander(&factory, expr) {
249 result.into()
250 } else {
251 cxx::UniquePtr::null()
252 }
253 }
254 );
255 let ffi_macro = FfiMacro::new_global_var_arg(name.as_ref().into(), ffi_expander)?;
256 Ok(Macro(ffi_macro))
257 }
258
259 /// Creates a new receiver macro with a fixed number of arguments.
260 ///
261 /// Receiver macros are invoked as method calls on a target expression,
262 /// e.g., `target.macro_name(arg1, arg2)`. The macro will only be expanded
263 /// when called with exactly `argument_count` arguments.
264 ///
265 /// # Parameters
266 ///
267 /// - `name`: The method name that triggers this macro (as a string reference)
268 /// - `argument_count`: The exact number of arguments required (not including receiver)
269 /// - `expander`: The expansion function that receives the target and arguments
270 ///
271 /// # Returns
272 ///
273 /// - `Ok(Macro)`: Successfully created macro
274 /// - `Err(Error)`: Failed to create macro (e.g., invalid name or FFI error)
275 ///
276 /// # Expander Function
277 ///
278 /// The expander receives:
279 /// - `factory`: A mutable reference to [`MacroExprFactory`] for creating new expressions
280 /// - `target`: The receiver expression (the object before the dot)
281 /// - `args`: A vector of argument expressions
282 ///
283 /// The expander should return:
284 /// - `Some(Expr)`: The expanded expression to replace the original call
285 /// - `None`: Keep the original expression unchanged
286 ///
287 /// # Examples
288 ///
289 /// ```rust,no_run
290 /// # use cel_cxx::macros::{Macro, MacroExprFactory, Expr};
291 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
292 /// // Macro: list.is_empty() expands to size(list) == 0
293 /// let is_empty_macro = Macro::new_receiver("is_empty", 0, |factory, target, _args| {
294 /// let size_call = factory.new_call("size", &[target]);
295 /// Some(factory.new_call("_==_", &[size_call, factory.new_const(0)]))
296 /// })?;
297 /// # Ok(())
298 /// # }
299 /// ```
300 pub fn new_receiver(
301 name: impl AsRef<str>,
302 argument_count: usize,
303 expander: impl ReceiverMacroExpander + 'static,
304 ) -> Result<Self, Error> {
305 let ffi_expander: cxx::UniquePtr<FfiReceiverMacroExpander> = FfiReceiverMacroExpander::new(
306 move |ffi_factory, ffi_target, ffi_expr| -> cxx::UniquePtr<FfiExpr> {
307 let factory = MacroExprFactory::new(ffi_factory);
308 let target = Expr::from(&*ffi_target);
309 let expr = ffi_expr.into_iter()
310 .map(|ffi_expr| Expr::from(&**ffi_expr))
311 .collect::<Vec<_>>();
312 if let Some(result) = expander(&factory, target, expr) {
313 result.into()
314 } else {
315 cxx::UniquePtr::null()
316 }
317 }
318 );
319 let ffi_macro = FfiMacro::new_receiver(name.as_ref().into(), argument_count, ffi_expander)?;
320 Ok(Macro(ffi_macro))
321 }
322
323 /// Creates a new receiver macro that accepts a variable number of arguments.
324 ///
325 /// Variable-argument receiver macros can handle any number of arguments beyond
326 /// the target expression. The expander receives the target and all arguments.
327 ///
328 /// # Parameters
329 ///
330 /// - `name`: The method name that triggers this macro (as a string reference)
331 /// - `expander`: The expansion function that receives the target and arguments
332 ///
333 /// # Returns
334 ///
335 /// - `Ok(Macro)`: Successfully created macro
336 /// - `Err(Error)`: Failed to create macro (e.g., invalid name or FFI error)
337 ///
338 /// # Expander Function
339 ///
340 /// The expander receives:
341 /// - `factory`: A mutable reference to [`MacroExprFactory`] for creating new expressions
342 /// - `target`: The receiver expression (the object before the dot)
343 /// - `args`: A vector of argument expressions (can be empty)
344 ///
345 /// The expander should return:
346 /// - `Some(Expr)`: The expanded expression to replace the original call
347 /// - `None`: Keep the original expression unchanged
348 ///
349 /// # Examples
350 ///
351 /// ```rust,no_run
352 /// # use cel_cxx::macros::{Macro, MacroExprFactory, Expr};
353 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
354 /// // Macro: str.concat(parts...) concatenates multiple strings
355 /// let concat_macro = Macro::new_receiver_var_arg("concat", |factory, target, args| {
356 /// let mut result = target;
357 /// for arg in args {
358 /// result = factory.new_call("_+_", &[result, arg]);
359 /// }
360 /// Some(result)
361 /// })?;
362 /// # Ok(())
363 /// # }
364 /// ```
365 pub fn new_receiver_var_arg(
366 name: impl AsRef<str>,
367 expander: impl ReceiverMacroExpander + 'static,
368 ) -> Result<Self, Error> {
369 let ffi_expander: cxx::UniquePtr<FfiReceiverMacroExpander> = FfiReceiverMacroExpander::new(
370 move |ffi_factory, ffi_target, ffi_expr| -> cxx::UniquePtr<FfiExpr> {
371 let factory = MacroExprFactory::new(ffi_factory);
372 let target = Expr::from(&*ffi_target);
373 let expr = ffi_expr.into_iter()
374 .map(|ffi_expr| Expr::from(&**ffi_expr))
375 .collect::<Vec<_>>();
376 if let Some(result) = expander(&factory, target, expr) {
377 result.into()
378 } else {
379 cxx::UniquePtr::null()
380 }
381 }
382 );
383 let ffi_macro = FfiMacro::new_receiver_var_arg(name.as_ref().into(), ffi_expander)?;
384 Ok(Macro(ffi_macro))
385 }
386
387 /// Returns the name of this macro as a byte slice.
388 ///
389 /// This is the function or method name that triggers macro expansion.
390 /// The returned byte slice is UTF-8 encoded.
391 ///
392 /// # Examples
393 ///
394 /// ```rust,no_run
395 /// # use cel_cxx::macros::Macro;
396 /// # fn example(macro_def: &Macro) {
397 /// let name = macro_def.name();
398 /// let name_str = std::str::from_utf8(name).unwrap();
399 /// println!("Macro name: {}", name_str);
400 /// # }
401 /// ```
402 pub fn name(&self) -> &[u8] {
403 self.0.function().as_bytes()
404 }
405
406 /// Returns the expected number of arguments for this macro.
407 ///
408 /// Returns `Some(n)` for fixed-argument macros requiring exactly `n` arguments,
409 /// or `None` for variable-argument macros that accept any number of arguments.
410 ///
411 /// # Examples
412 ///
413 /// ```rust,no_run
414 /// # use cel_cxx::macros::Macro;
415 /// # fn example(macro_def: &Macro) {
416 /// match macro_def.argument_count() {
417 /// Some(n) => println!("Fixed macro with {} arguments", n),
418 /// None => println!("Variable-argument macro"),
419 /// }
420 /// # }
421 /// ```
422 pub fn argument_count(&self) -> Option<usize> {
423 if self.0.is_variadic() {
424 None
425 } else {
426 Some(self.0.argument_count())
427 }
428 }
429
430 /// Returns whether this is a receiver-style macro.
431 ///
432 /// Receiver-style macros are invoked as method calls (e.g., `target.method(args)`),
433 /// while non-receiver macros are invoked as function calls (e.g., `function(args)`).
434 ///
435 /// # Returns
436 ///
437 /// - `true`: This is a receiver macro (created with [`new_receiver`] or [`new_receiver_var_arg`])
438 /// - `false`: This is a global macro (created with [`new_global`] or [`new_global_var_arg`])
439 ///
440 /// [`new_receiver`]: Self::new_receiver
441 /// [`new_receiver_var_arg`]: Self::new_receiver_var_arg
442 /// [`new_global`]: Self::new_global
443 /// [`new_global_var_arg`]: Self::new_global_var_arg
444 pub fn is_receiver_style(&self) -> bool {
445 self.0.is_receiver_style()
446 }
447
448 /// Returns the unique key identifying this macro.
449 ///
450 /// The key is an internal identifier used by the CEL parser to match macro
451 /// invocations. It encodes the macro name, receiver style, and argument count.
452 ///
453 /// # Returns
454 ///
455 /// A byte slice representing the macro's unique key (UTF-8 encoded).
456 ///
457 /// # Note
458 ///
459 /// This method is primarily for internal use and debugging. Most users should
460 /// use [`name()`], [`is_receiver_style()`], and [`argument_count()`] instead.
461 ///
462 /// [`name()`]: Self::name
463 /// [`is_receiver_style()`]: Self::is_receiver_style
464 /// [`argument_count()`]: Self::argument_count
465 pub fn key(&self) -> &[u8] {
466 self.0.key().as_bytes()
467 }
468}
469
470impl From<Macro> for cxx::UniquePtr<crate::ffi::Macro> {
471 fn from(value: Macro) -> Self {
472 value.0
473 }
474}
475
476impl From<cxx::UniquePtr<crate::ffi::Macro>> for Macro {
477 fn from(value: cxx::UniquePtr<crate::ffi::Macro>) -> Self {
478 Macro(value)
479 }
480}
481
482impl std::fmt::Debug for Macro {
483 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
484 write!(f, "Macro({})", String::from_utf8_lossy(self.key()))
485 }
486}
487
488impl std::hash::Hash for Macro {
489 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
490 self.key().hash(state);
491 }
492}
493
494impl std::cmp::PartialEq for Macro {
495 fn eq(&self, other: &Self) -> bool {
496 self.key() == other.key()
497 }
498}
499
500impl std::cmp::Eq for Macro {}