Skip to main content

cel_rs_macros/
lib.rs

1//! Procedural macros for the cel-rs crate.
2//!
3//! This crate provides procedural macros for working with CEL (Common Expression Language)
4//! expressions in Rust. It includes macros for validating CEL expressions at compile time
5//! and debugging token parsing.
6//!
7//! # Examples
8//!
9//! ## Validating CEL expressions
10//!
11//! ```rust
12//! use cel_rs_macros::expression;
13//!
14//! // This will compile only if the expression is valid CEL
15//! expression! {
16//!     54 + 25 * (11 + 6 * 6)
17//! };
18//! ```
19//!
20//! ## Debugging token parsing
21//!
22//! ```rust
23//! use cel_rs_macros::print_tokens;
24//!
25//! // This will print the parsed tokens to stdout during compilation
26//! print_tokens! {
27//!     10 + 20
28//! };
29//! ```
30
31use cel_parser::{CELParser, OpLookup};
32use proc_macro::TokenStream as ProcMacroTokenStream;
33use proc_macro2::{Literal, TokenStream};
34use quote::{quote, quote_spanned};
35
36/// Validates that the input contains a valid CEL expression.
37///
38/// ```rust
39/// use cel_rs_macros::expression;
40/// expression! {
41///     54 + 25 * (11 + 6 * 6)
42/// };
43/// ```
44#[proc_macro]
45pub fn expression(input: ProcMacroTokenStream) -> ProcMacroTokenStream {
46    let input = TokenStream::from(input);
47    let mut parser = CELParser::new(OpLookup::new());
48    match parser.parse_tokens(input.into_iter()) {
49        Ok(_) => ProcMacroTokenStream::new(),
50        Err(e) => {
51            let msg_lit = Literal::string(e.message());
52            let start_error = quote_spanned!(e.span() => compile_error!(#msg_lit));
53            if let Some(end) = e.end_span() {
54                // Intentional second diagnostic at the expression end span. A single merged
55                // underline requires `Span::join()`, which is not stable; until then we emit
56                // two `compile_error!` invocations so both start and end locations are reported.
57                let end_lit = Literal::string("expression continues here");
58                let end_error = quote_spanned!(end => compile_error!(#end_lit));
59                // Two bare compile_error!() are not valid in expression context;
60                // wrapping in a block makes the expansion a valid block expression
61                // while still causing the compiler to expand both invocations.
62                quote!({ #start_error; #end_error }).into()
63            } else {
64                start_error.into()
65            }
66        }
67    }
68}
69
70/// Prints the tokens for debugging purposes.
71///
72/// # Example
73/// ```rust
74/// use cel_rs_macros::print_tokens;
75/// print_tokens! {
76///     "hello"_key
77/// };
78/// ```
79#[proc_macro]
80pub fn print_tokens(input: ProcMacroTokenStream) -> ProcMacroTokenStream {
81    println!("{input}");
82    let input = TokenStream::from(input);
83    for e in input {
84        match e {
85            proc_macro2::TokenTree::Punct(punct) => {
86                eprintln!("punct: {punct:?}");
87            }
88            proc_macro2::TokenTree::Ident(ident) => {
89                eprintln!("ident: {ident:?}");
90            }
91            proc_macro2::TokenTree::Group(group) => {
92                eprintln!("group: {group:?}");
93            }
94            proc_macro2::TokenTree::Literal(lit) => {
95                eprintln!("literal: {lit:?}");
96            }
97        }
98    }
99    ProcMacroTokenStream::new()
100}