Skip to main content

cel_core/
ast.rs

1//! Unified AST representation for CEL expressions.
2//!
3//! This module provides the `Ast` type which wraps a parsed (and optionally type-checked)
4//! CEL expression. It follows the cel-go architecture pattern where a single `Ast` type
5//! can represent both parsed and checked expressions.
6//!
7//! # Example
8//!
9//! ```
10//! use cel_core::{Env, Ast};
11//!
12//! let env = Env::with_standard_library()
13//!     .with_variable("x", cel_core::CelType::Int);
14//!
15//! // Compile returns a checked Ast
16//! let ast = env.compile("x + 1").unwrap();
17//! assert!(ast.is_checked());
18//!
19//! // Convert to CEL string
20//! let cel_string = ast.to_cel_string();
21//! ```
22//!
23//! # Proto Conversion
24//!
25//! For proto wire format conversion (interop with cel-go, cel-cpp), use the
26//! `cel-core-proto` crate:
27//!
28//! ```ignore
29//! use cel_core_proto::AstToProto;
30//!
31//! let parsed_expr = ast.to_parsed_expr();
32//! let checked_expr = ast.to_checked_expr()?;
33//! ```
34
35use std::sync::Arc;
36
37use crate::checker::CheckResult;
38use crate::types::{CelType, SpannedExpr};
39
40/// Error type for Ast operations.
41#[derive(Debug, Clone)]
42pub enum AstError {
43    /// The AST has not been type-checked, but a checked operation was requested.
44    NotChecked,
45    /// Error during conversion or other operations.
46    Other(String),
47}
48
49impl std::fmt::Display for AstError {
50    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51        match self {
52            AstError::NotChecked => write!(f, "AST has not been type-checked"),
53            AstError::Other(msg) => write!(f, "{}", msg),
54        }
55    }
56}
57
58impl std::error::Error for AstError {}
59
60/// Unified AST representation matching cel-go's Ast type.
61///
62/// An `Ast` can be either checked (has type info) or unchecked (parsed only).
63/// This follows the cel-go pattern where a single type represents both states.
64///
65/// # Creating an Ast
66///
67/// - Use `Env::compile()` to get a checked `Ast`
68/// - Use `Env::parse_only()` to get an unchecked `Ast`
69///
70/// # Proto Conversion
71///
72/// For proto wire format conversion, use the `cel-core-proto` crate directly.
73#[derive(Debug, Clone)]
74pub struct Ast {
75    /// The expression tree.
76    expr: SpannedExpr,
77    /// The original source text.
78    source: Arc<str>,
79    /// Type checking results (None if unchecked).
80    type_info: Option<CheckResult>,
81}
82
83impl Ast {
84    /// Create an unchecked AST (after parsing, before type checking).
85    pub fn new_unchecked(expr: SpannedExpr, source: impl Into<Arc<str>>) -> Self {
86        Self {
87            expr,
88            source: source.into(),
89            type_info: None,
90        }
91    }
92
93    /// Create a checked AST (after type checking).
94    pub fn new_checked(
95        expr: SpannedExpr,
96        source: impl Into<Arc<str>>,
97        check_result: CheckResult,
98    ) -> Self {
99        Self {
100            expr,
101            source: source.into(),
102            type_info: Some(check_result),
103        }
104    }
105
106    /// Returns true if this AST has been type-checked.
107    pub fn is_checked(&self) -> bool {
108        self.type_info.is_some()
109    }
110
111    /// Get the expression tree.
112    pub fn expr(&self) -> &SpannedExpr {
113        &self.expr
114    }
115
116    /// Get the original source text.
117    pub fn source(&self) -> &str {
118        &self.source
119    }
120
121    /// Get type checking results (if checked).
122    pub fn type_info(&self) -> Option<&CheckResult> {
123        self.type_info.as_ref()
124    }
125
126    /// Get the result type of the expression (if checked).
127    ///
128    /// Returns the type of the root expression from the type map.
129    pub fn result_type(&self) -> Option<&CelType> {
130        self.type_info
131            .as_ref()
132            .and_then(|info| info.type_map.get(&self.expr.id))
133    }
134
135    // ==================== String Conversion ====================
136
137    /// Convert the AST back to CEL source text.
138    ///
139    /// The output is a valid CEL expression that is semantically equivalent
140    /// to the original. Formatting may differ (whitespace, parenthesization).
141    ///
142    /// # Example
143    ///
144    /// ```
145    /// use cel_core::Env;
146    ///
147    /// let env = Env::with_standard_library();
148    /// let ast = env.compile("1 + 2 * 3").unwrap();
149    /// assert_eq!(ast.to_cel_string(), "1 + 2 * 3");
150    /// ```
151    pub fn to_cel_string(&self) -> String {
152        crate::unparser::ast_to_string(&self.expr)
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159    use crate::Env;
160
161    #[test]
162    fn test_ast_is_checked() {
163        let env = Env::with_standard_library().with_variable("x", CelType::Int);
164
165        let ast = env.compile("x + 1").unwrap();
166        assert!(ast.is_checked());
167
168        let unchecked = env.parse_only("x + 1").unwrap();
169        assert!(!unchecked.is_checked());
170    }
171
172    #[test]
173    fn test_ast_result_type() {
174        let env = Env::with_standard_library().with_variable("x", CelType::Int);
175
176        let ast = env.compile("x + 1").unwrap();
177        assert_eq!(ast.result_type(), Some(&CelType::Int));
178
179        let unchecked = env.parse_only("x + 1").unwrap();
180        assert!(unchecked.result_type().is_none());
181    }
182
183    #[test]
184    fn test_ast_to_cel_string() {
185        let env = Env::with_standard_library().with_variable("x", CelType::Int);
186
187        let ast = env.compile("x + 1").unwrap();
188        assert_eq!(ast.to_cel_string(), "x + 1");
189
190        // Works for unchecked too
191        let unchecked = env.parse_only("x * 2 + 3").unwrap();
192        assert_eq!(unchecked.to_cel_string(), "x * 2 + 3");
193    }
194
195    #[test]
196    fn test_ast_to_cel_string_complex() {
197        let env = Env::with_standard_library();
198
199        let ast = env.compile("1 + 2 * 3").unwrap();
200        assert_eq!(ast.to_cel_string(), "1 + 2 * 3");
201
202        let ast = env.compile("(1 + 2) * 3").unwrap();
203        assert_eq!(ast.to_cel_string(), "(1 + 2) * 3");
204    }
205}