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