Skip to main content

cel_core/eval/
program.rs

1//! Compiled CEL program ready for evaluation.
2//!
3//! A `Program` combines a parsed/checked AST with a function registry,
4//! providing a convenient interface for evaluating expressions.
5
6use std::collections::HashMap;
7use std::sync::Arc;
8
9use super::proto_registry::ProtoRegistry;
10use super::{Activation, EmptyActivation, Evaluator, FunctionRegistry, Value};
11use crate::Ast;
12
13/// A compiled CEL program ready for evaluation.
14///
15/// The program holds a reference to the AST and a function registry,
16/// providing a convenient interface for evaluating the expression
17/// against different variable bindings.
18#[derive(Clone)]
19pub struct Program {
20    ast: Arc<Ast>,
21    functions: Arc<FunctionRegistry>,
22    proto_registry: Option<Arc<dyn ProtoRegistry>>,
23    abbreviations: Option<Arc<HashMap<String, String>>>,
24    strong_enums: bool,
25}
26
27impl Program {
28    /// Create a new program from an AST and function registry.
29    pub fn new(ast: Arc<Ast>, functions: Arc<FunctionRegistry>) -> Self {
30        Self {
31            ast,
32            functions,
33            proto_registry: None,
34            abbreviations: None,
35            strong_enums: true,
36        }
37    }
38
39    /// Create a new program with a type registry.
40    pub fn with_proto_registry(
41        ast: Arc<Ast>,
42        functions: Arc<FunctionRegistry>,
43        proto_registry: Arc<dyn ProtoRegistry>,
44    ) -> Self {
45        Self {
46            ast,
47            functions,
48            proto_registry: Some(proto_registry),
49            abbreviations: None,
50            strong_enums: true,
51        }
52    }
53
54    /// Create a new program with abbreviations.
55    pub fn with_abbreviations(
56        ast: Arc<Ast>,
57        functions: Arc<FunctionRegistry>,
58        abbreviations: HashMap<String, String>,
59    ) -> Self {
60        Self {
61            ast,
62            functions,
63            proto_registry: None,
64            abbreviations: Some(Arc::new(abbreviations)),
65            strong_enums: true,
66        }
67    }
68
69    /// Create a new program with a type registry and abbreviations.
70    pub fn with_proto_registry_and_abbreviations(
71        ast: Arc<Ast>,
72        functions: Arc<FunctionRegistry>,
73        proto_registry: Arc<dyn ProtoRegistry>,
74        abbreviations: HashMap<String, String>,
75    ) -> Self {
76        Self {
77            ast,
78            functions,
79            proto_registry: Some(proto_registry),
80            abbreviations: Some(Arc::new(abbreviations)),
81            strong_enums: true,
82        }
83    }
84
85    /// Use legacy (weak) enum mode where enum values are returned as plain integers.
86    pub fn with_legacy_enums(mut self) -> Self {
87        self.strong_enums = false;
88        self
89    }
90
91    /// Get the AST for this program.
92    pub fn ast(&self) -> &Ast {
93        &self.ast
94    }
95
96    /// Get the function registry for this program.
97    pub fn functions(&self) -> &FunctionRegistry {
98        &self.functions
99    }
100
101    /// Evaluate the program with the given variable bindings.
102    pub fn eval(&self, activation: &dyn Activation) -> Value {
103        self.eval_with_container(activation, "")
104    }
105
106    /// Evaluate the program with the given variable bindings and container namespace.
107    ///
108    /// The container is used for resolving unqualified type names following
109    /// C++ namespace rules. For example, with container "cel.expr.conformance.proto3"
110    /// and type name "TestAllTypes", resolution tries:
111    /// 1. cel.expr.conformance.proto3.TestAllTypes
112    /// 2. cel.expr.conformance.TestAllTypes
113    /// 3. cel.expr.TestAllTypes
114    /// 4. cel.TestAllTypes
115    /// 5. TestAllTypes
116    pub fn eval_with_container(&self, activation: &dyn Activation, container: &str) -> Value {
117        let mut evaluator = Evaluator::new(activation, &self.functions);
118
119        // Pass type info from checked AST
120        if let Some(type_info) = self.ast.type_info() {
121            evaluator = evaluator.with_reference_map(&type_info.reference_map);
122        }
123
124        // Pass type registry
125        if let Some(ref proto_registry) = self.proto_registry {
126            evaluator = evaluator.with_proto_registry(proto_registry.as_ref());
127        }
128
129        // Set container for type resolution
130        if !container.is_empty() {
131            evaluator = evaluator.with_container(container);
132        }
133
134        // Pass abbreviations
135        if let Some(ref abbreviations) = self.abbreviations {
136            evaluator = evaluator.with_abbreviations(abbreviations);
137        }
138
139        // Pass strong enum setting
140        if !self.strong_enums {
141            evaluator = evaluator.with_legacy_enums();
142        }
143
144        evaluator.eval(self.ast.expr())
145    }
146
147    /// Evaluate the program with no variable bindings.
148    pub fn eval_empty(&self) -> Value {
149        self.eval(&EmptyActivation)
150    }
151}
152
153impl std::fmt::Debug for Program {
154    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155        f.debug_struct("Program")
156            .field("ast", &self.ast)
157            .field("functions", &format!("{} functions", self.functions.len()))
158            .finish()
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165    use crate::eval::MapActivation;
166    use crate::parse;
167
168    fn create_program(source: &str) -> Program {
169        let result = parse(source);
170        assert!(result.errors.is_empty());
171        let ast = Ast::new_unchecked(result.ast.unwrap(), source);
172        Program::new(Arc::new(ast), Arc::new(FunctionRegistry::new()))
173    }
174
175    #[test]
176    fn test_eval_literal() {
177        let program = create_program("42");
178        assert_eq!(program.eval_empty(), Value::Int(42));
179    }
180
181    #[test]
182    fn test_eval_with_variables() {
183        let program = create_program("x + y");
184        let mut activation = MapActivation::new();
185        activation.insert("x", Value::Int(1));
186        activation.insert("y", Value::Int(2));
187        assert_eq!(program.eval(&activation), Value::Int(3));
188    }
189
190    #[test]
191    fn test_reuse_program() {
192        let program = create_program("x * 2");
193
194        let mut act1 = MapActivation::new();
195        act1.insert("x", Value::Int(5));
196        assert_eq!(program.eval(&act1), Value::Int(10));
197
198        let mut act2 = MapActivation::new();
199        act2.insert("x", Value::Int(21));
200        assert_eq!(program.eval(&act2), Value::Int(42));
201    }
202}