Skip to main content

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}