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 {}