cel_core/parser/mod.rs
1//! CEL (Common Expression Language) parser.
2
3mod lexer;
4pub mod macros;
5mod parser;
6
7use crate::types::{Span, SpannedExpr};
8
9pub use lexer::{lex, LexError, SpannedToken, Token};
10pub use macros::{
11 ArgCount, Macro, MacroContext, MacroExpander, MacroExpansion, MacroRegistry, MacroStyle,
12};
13pub use parser::MacroCalls;
14
15/// A parse error with source location.
16#[derive(Debug, Clone)]
17pub struct ParseError {
18 pub message: String,
19 pub span: Span,
20}
21
22impl std::fmt::Display for ParseError {
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 write!(
25 f,
26 "{} at {}..{}",
27 self.message, self.span.start, self.span.end
28 )
29 }
30}
31
32impl std::error::Error for ParseError {}
33
34/// Options for parsing CEL expressions.
35#[derive(Debug, Clone, Default)]
36pub struct ParseOptions {
37 /// Custom macro registry. If None, uses standard CEL macros.
38 pub macros: Option<MacroRegistry>,
39}
40
41impl ParseOptions {
42 /// Create parse options with default settings (standard macros).
43 pub fn new() -> Self {
44 Self::default()
45 }
46
47 /// Create parse options with a custom macro registry.
48 pub fn with_macros(macros: MacroRegistry) -> Self {
49 Self {
50 macros: Some(macros),
51 }
52 }
53
54 /// Create parse options with no macros (all macro calls become regular calls).
55 pub fn without_macros() -> Self {
56 Self {
57 macros: Some(MacroRegistry::new()),
58 }
59 }
60}
61
62/// Result of parsing a CEL expression.
63///
64/// Supports error recovery: may return both an AST and errors.
65/// The AST may contain `Expr::Error` nodes where parsing failed.
66/// Includes macro_calls map for IDE features.
67#[derive(Debug, Clone)]
68pub struct ParseResult {
69 /// The parsed AST, if any parsing succeeded.
70 /// May contain `Expr::Error` nodes for unparseable sections.
71 /// Macros (has, all, exists, exists_one, map, filter) are expanded inline.
72 pub ast: Option<SpannedExpr>,
73 /// Map of comprehension/expansion IDs to original macro call expressions.
74 /// Used for IDE features like hover to show the original source form.
75 pub macro_calls: MacroCalls,
76 /// Any parse errors encountered.
77 pub errors: Vec<ParseError>,
78}
79
80impl ParseResult {
81 /// Returns true if parsing completed without errors.
82 pub fn is_ok(&self) -> bool {
83 self.errors.is_empty() && self.ast.is_some()
84 }
85
86 /// Returns true if there are any parse errors.
87 pub fn is_err(&self) -> bool {
88 !self.errors.is_empty()
89 }
90
91 /// Converts to a Result, discarding partial AST on error.
92 pub fn into_result(self) -> Result<SpannedExpr, Vec<ParseError>> {
93 if self.errors.is_empty() {
94 self.ast.ok_or_else(Vec::new)
95 } else {
96 Err(self.errors)
97 }
98 }
99
100 /// Unwraps the errors, panicking if there are none.
101 pub fn unwrap_err(self) -> Vec<ParseError> {
102 if self.errors.is_empty() {
103 panic!("called unwrap_err on a ParseResult with no errors");
104 }
105 self.errors
106 }
107}
108
109/// Parse a CEL expression from source.
110///
111/// Returns a `ParseResult` which may contain both a partial AST and errors
112/// when error recovery is successful. Macros (has, all, exists, exists_one,
113/// map, filter) are expanded inline during parsing.
114///
115/// The `macro_calls` field in the result maps comprehension/expansion IDs
116/// to the original macro call expressions, which is useful for IDE features.
117pub fn parse(input: &str) -> ParseResult {
118 parse_with_options(input, ParseOptions::new())
119}
120
121/// Parse a CEL expression with custom options.
122///
123/// This allows configuring the macro registry used during parsing.
124/// Use `ParseOptions::without_macros()` to disable macro expansion,
125/// or `ParseOptions::with_macros(registry)` for custom macros.
126pub fn parse_with_options(input: &str, options: ParseOptions) -> ParseResult {
127 // First, lex the input
128 let tokens = match lexer::lex(input) {
129 Ok(tokens) => tokens,
130 Err(e) => {
131 return ParseResult {
132 ast: None,
133 macro_calls: MacroCalls::new(),
134 errors: vec![ParseError {
135 message: e.message,
136 span: e.span,
137 }],
138 };
139 }
140 };
141
142 // Get macro registry
143 let macros = options.macros.unwrap_or_else(MacroRegistry::standard);
144
145 // Parse the tokens (with inline macro expansion)
146 let (ast, parse_errors, macro_calls) = parser::parse_tokens_with_macros(&tokens, macros);
147
148 let errors: Vec<ParseError> = parse_errors
149 .into_iter()
150 .map(|e| ParseError {
151 message: e.message,
152 span: e.span,
153 })
154 .collect();
155
156 ParseResult {
157 ast,
158 macro_calls,
159 errors,
160 }
161}