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}