Skip to main content

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}