Skip to main content

sentri_core/
evaluator.rs

1//! Deterministic invariant expression evaluation engine.
2//!
3//! Supports both compile-time static evaluation and runtime evaluation.
4//! All operations use checked arithmetic with explicit overflow handling.
5//! No floating point. No randomness. No external I/O.
6
7use crate::model::Expression;
8use crate::types::Type;
9use serde::{Deserialize, Serialize};
10use std::collections::BTreeMap;
11
12/// A runtime value with type information.
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14pub enum Value {
15    /// Boolean value.
16    Bool(bool),
17    /// 64-bit unsigned integer.
18    U64(u64),
19    /// 128-bit unsigned integer.
20    U128(u128),
21    /// 64-bit signed integer.
22    I64(i64),
23    /// Address (hex string representation).
24    Address(String),
25}
26
27impl Value {
28    /// Get the type of this value.
29    pub fn get_type(&self) -> Type {
30        match self {
31            Self::Bool(_) => Type::Bool,
32            Self::U64(_) => Type::U64,
33            Self::U128(_) => Type::U128,
34            Self::I64(_) => Type::I64,
35            Self::Address(_) => Type::Address,
36        }
37    }
38
39    /// Convert to a boolean for conditional evaluation.
40    pub fn to_bool(&self) -> Result<bool, EvaluationError> {
41        match self {
42            Self::Bool(b) => Ok(*b),
43            Self::U64(n) => Ok(*n != 0),
44            Self::U128(n) => Ok(*n != 0),
45            Self::I64(n) => Ok(*n != 0),
46            Self::Address(a) => Ok(!a.is_empty()),
47        }
48    }
49
50    /// Safe integer conversion.
51    #[allow(dead_code)]
52    fn as_u64(&self) -> Result<u64, EvaluationError> {
53        match self {
54            Self::U64(n) => Ok(*n),
55            Self::U128(n) => {
56                if *n <= u64::MAX as u128 {
57                    Ok(*n as u64)
58                } else {
59                    Err(EvaluationError::ConversionOverflow)
60                }
61            }
62            Self::I64(n) => {
63                if *n >= 0 {
64                    Ok(*n as u64)
65                } else {
66                    Err(EvaluationError::ConversionOverflow)
67                }
68            }
69            _ => Err(EvaluationError::TypeError),
70        }
71    }
72
73    #[allow(dead_code)]
74    fn as_i64(&self) -> Result<i64, EvaluationError> {
75        match self {
76            Self::I64(n) => Ok(*n),
77            Self::U64(n) => {
78                if *n <= i64::MAX as u64 {
79                    Ok(*n as i64)
80                } else {
81                    Err(EvaluationError::ConversionOverflow)
82                }
83            }
84            _ => Err(EvaluationError::TypeError),
85        }
86    }
87}
88
89impl std::fmt::Display for Value {
90    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91        match self {
92            Self::Bool(b) => write!(f, "{}", b),
93            Self::U64(n) => write!(f, "{}", n),
94            Self::U128(n) => write!(f, "{}", n),
95            Self::I64(n) => write!(f, "{}", n),
96            Self::Address(a) => write!(f, "{}", a),
97        }
98    }
99}
100
101/// Evaluation errors.
102#[derive(Debug, Clone, PartialEq, Eq)]
103pub enum EvaluationError {
104    /// Arithmetic overflow.
105    Overflow,
106    /// Arithmetic underflow.
107    Underflow,
108    /// Type error during evaluation.
109    TypeError,
110    /// Division by zero.
111    DivisionByZero,
112    /// Undefined variable.
113    UndefinedVariable(String),
114    /// Undefined function.
115    UndefinedFunction(String),
116    /// Function argument error.
117    InvalidArgument(String),
118    /// Conversion overflow.
119    ConversionOverflow,
120    /// Custom error.
121    Custom(String),
122}
123
124impl std::fmt::Display for EvaluationError {
125    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126        match self {
127            Self::Overflow => write!(f, "arithmetic overflow"),
128            Self::Underflow => write!(f, "arithmetic underflow"),
129            Self::TypeError => write!(f, "type error"),
130            Self::DivisionByZero => write!(f, "division by zero"),
131            Self::UndefinedVariable(name) => write!(f, "undefined variable '{}'", name),
132            Self::UndefinedFunction(name) => write!(f, "undefined function '{}'", name),
133            Self::InvalidArgument(msg) => write!(f, "invalid argument: {}", msg),
134            Self::ConversionOverflow => write!(f, "conversion overflow"),
135            Self::Custom(msg) => write!(f, "{}", msg),
136        }
137    }
138}
139
140/// Result type for evaluation operations.
141pub type EvalResult<T> = Result<T, EvaluationError>;
142
143/// Type alias for function implementations.
144pub type EvalFunction = fn(&[Value]) -> EvalResult<Value>;
145
146/// Execution context for invariant evaluation.
147pub struct ExecutionContext {
148    /// Current state variable values.
149    pub state_vars: BTreeMap<String, Value>,
150    /// Function implementations.
151    pub functions: BTreeMap<String, EvalFunction>,
152}
153
154impl ExecutionContext {
155    /// Create a new empty context.
156    pub fn new() -> Self {
157        Self {
158            state_vars: BTreeMap::new(),
159            functions: BTreeMap::new(),
160        }
161    }
162
163    /// Set a state variable value.
164    pub fn set_state(&mut self, name: String, value: Value) {
165        self.state_vars.insert(name, value);
166    }
167
168    /// Register a built-in function.
169    pub fn register_function(&mut self, name: String, func: fn(&[Value]) -> EvalResult<Value>) {
170        self.functions.insert(name, func);
171    }
172}
173
174impl Default for ExecutionContext {
175    fn default() -> Self {
176        Self::new()
177    }
178}
179
180/// Deterministic invariant expression evaluator.
181pub struct Evaluator {
182    context: ExecutionContext,
183}
184
185impl Evaluator {
186    /// Create a new evaluator with an execution context.
187    pub fn new(context: ExecutionContext) -> Self {
188        Self { context }
189    }
190
191    /// Evaluate an expression against the current context.
192    pub fn evaluate(&self, expr: &Expression) -> EvalResult<Value> {
193        match expr {
194            Expression::Boolean(b) => Ok(Value::Bool(*b)),
195
196            Expression::Int(val) => {
197                // Determine appropriate type based on value
198                if *val < 0 {
199                    Ok(Value::I64(*val as i64))
200                } else if *val <= u64::MAX as i128 {
201                    Ok(Value::U64(*val as u64))
202                } else {
203                    Ok(Value::U128(*val as u128))
204                }
205            }
206
207            Expression::Var(name) => self
208                .context
209                .state_vars
210                .get(name)
211                .cloned()
212                .ok_or_else(|| EvaluationError::UndefinedVariable(name.clone())),
213
214            Expression::LayerVar { layer, var } => {
215                // Layer-qualified variables: look up by full qualified name
216                let qualified_name = format!("{}::{}", layer, var);
217                self.context
218                    .state_vars
219                    .get(&qualified_name)
220                    .cloned()
221                    .or_else(|| self.context.state_vars.get(var).cloned())
222                    .ok_or(EvaluationError::UndefinedVariable(qualified_name))
223            }
224
225            Expression::PhaseQualifiedVar { phase, layer, var } => {
226                // Phase-qualified variables: phase::layer::var
227                // For now, evaluate as layer::var (full phase support requires AA context)
228                let qualified_name = format!("{}::{}::{}", phase, layer, var);
229                self.context
230                    .state_vars
231                    .get(&qualified_name)
232                    .cloned()
233                    .or_else(|| {
234                        let layer_var = format!("{}::{}", layer, var);
235                        self.context.state_vars.get(&layer_var).cloned()
236                    })
237                    .or_else(|| self.context.state_vars.get(var).cloned())
238                    .ok_or(EvaluationError::UndefinedVariable(qualified_name))
239            }
240
241            Expression::PhaseConstraint {
242                phase: _,
243                constraint,
244            } => {
245                // Evaluate the constraint expression
246                // The phase is metadata for analysis; actual phase checking requires AA context
247                self.evaluate(constraint)
248            }
249
250            Expression::CrossPhaseRelation {
251                phase1: _,
252                expr1,
253                phase2: _,
254                expr2,
255                op,
256            } => {
257                // Evaluate cross-phase relation: expr1 op expr2
258                // Phase context requires AA context for snapshot lookup
259                let left_val = self.evaluate(expr1)?;
260                let right_val = self.evaluate(expr2)?;
261                self.eval_binary_op(&left_val, op, &right_val)
262            }
263
264            Expression::BinaryOp { left, op, right } => {
265                let left_val = self.evaluate(left)?;
266                let right_val = self.evaluate(right)?;
267                self.eval_binary_op(&left_val, op, &right_val)
268            }
269
270            Expression::Logical { left, op, right } => {
271                use crate::model::LogicalOp;
272
273                let left_val = self.evaluate(left)?.to_bool()?;
274
275                // Short-circuit evaluation
276                match op {
277                    LogicalOp::And => {
278                        if !left_val {
279                            return Ok(Value::Bool(false));
280                        }
281                        let right_val = self.evaluate(right)?.to_bool()?;
282                        Ok(Value::Bool(right_val))
283                    }
284                    LogicalOp::Or => {
285                        if left_val {
286                            return Ok(Value::Bool(true));
287                        }
288                        let right_val = self.evaluate(right)?.to_bool()?;
289                        Ok(Value::Bool(right_val))
290                    }
291                }
292            }
293
294            Expression::Not(expr) => {
295                let val = self.evaluate(expr)?.to_bool()?;
296                Ok(Value::Bool(!val))
297            }
298
299            Expression::FunctionCall { name, args } => {
300                let func = self
301                    .context
302                    .functions
303                    .get(name)
304                    .ok_or_else(|| EvaluationError::UndefinedFunction(name.clone()))?;
305
306                let arg_vals: EvalResult<Vec<Value>> =
307                    args.iter().map(|arg| self.evaluate(arg)).collect();
308
309                func(&arg_vals?)
310            }
311
312            Expression::Tuple(exprs) => {
313                // For now, evaluate first expression in tuple
314                if exprs.is_empty() {
315                    Ok(Value::Bool(true))
316                } else {
317                    self.evaluate(&exprs[0])
318                }
319            }
320        }
321    }
322
323    /// Evaluate a binary operation with checked arithmetic.
324    fn eval_binary_op(
325        &self,
326        left: &Value,
327        op: &crate::model::BinaryOp,
328        right: &Value,
329    ) -> EvalResult<Value> {
330        use crate::model::BinaryOp;
331
332        match op {
333            BinaryOp::Eq => Ok(Value::Bool(left == right)),
334
335            BinaryOp::Neq => Ok(Value::Bool(left != right)),
336
337            BinaryOp::Lt => match (left, right) {
338                (Value::U64(l), Value::U64(r)) => Ok(Value::Bool(l < r)),
339                (Value::I64(l), Value::I64(r)) => Ok(Value::Bool(l < r)),
340                (Value::U128(l), Value::U128(r)) => Ok(Value::Bool(l < r)),
341                _ => Err(EvaluationError::TypeError),
342            },
343
344            BinaryOp::Gt => match (left, right) {
345                (Value::U64(l), Value::U64(r)) => Ok(Value::Bool(l > r)),
346                (Value::I64(l), Value::I64(r)) => Ok(Value::Bool(l > r)),
347                (Value::U128(l), Value::U128(r)) => Ok(Value::Bool(l > r)),
348                _ => Err(EvaluationError::TypeError),
349            },
350
351            BinaryOp::Lte => match (left, right) {
352                (Value::U64(l), Value::U64(r)) => Ok(Value::Bool(l <= r)),
353                (Value::I64(l), Value::I64(r)) => Ok(Value::Bool(l <= r)),
354                (Value::U128(l), Value::U128(r)) => Ok(Value::Bool(l <= r)),
355                _ => Err(EvaluationError::TypeError),
356            },
357
358            BinaryOp::Gte => match (left, right) {
359                (Value::U64(l), Value::U64(r)) => Ok(Value::Bool(l >= r)),
360                (Value::I64(l), Value::I64(r)) => Ok(Value::Bool(l >= r)),
361                (Value::U128(l), Value::U128(r)) => Ok(Value::Bool(l >= r)),
362                _ => Err(EvaluationError::TypeError),
363            },
364        }
365    }
366}
367
368#[cfg(test)]
369mod tests {
370    use super::*;
371
372    #[test]
373    fn test_value_type_detection() {
374        assert_eq!(Value::Bool(true).get_type(), Type::Bool);
375        assert_eq!(Value::U64(42).get_type(), Type::U64);
376        assert_eq!(Value::I64(-42).get_type(), Type::I64);
377    }
378
379    #[test]
380    fn test_simple_evaluation() {
381        let ctx = ExecutionContext::new();
382        let evaluator = Evaluator::new(ctx);
383
384        let expr = Expression::Boolean(true);
385        let result = evaluator.evaluate(&expr);
386        assert_eq!(result, Ok(Value::Bool(true)));
387    }
388
389    #[test]
390    fn test_state_variable_evaluation() {
391        let mut ctx = ExecutionContext::new();
392        ctx.set_state("balance".to_string(), Value::U64(100));
393
394        let evaluator = Evaluator::new(ctx);
395        let expr = Expression::Var("balance".to_string());
396
397        let result = evaluator.evaluate(&expr);
398        assert_eq!(result, Ok(Value::U64(100)));
399    }
400
401    #[test]
402    fn test_comparison_evaluation() {
403        let ctx = ExecutionContext::new();
404        let evaluator = Evaluator::new(ctx);
405
406        let expr = Expression::BinaryOp {
407            left: Box::new(Expression::Int(10)),
408            op: crate::model::BinaryOp::Lt,
409            right: Box::new(Expression::Int(20)),
410        };
411
412        let result = evaluator.evaluate(&expr);
413        assert_eq!(result, Ok(Value::Bool(true)));
414    }
415
416    #[test]
417    fn test_logical_short_circuit() {
418        let ctx = ExecutionContext::new();
419        let evaluator = Evaluator::new(ctx);
420
421        // false && (undefined_var) should not evaluate the right side
422        let expr = Expression::Logical {
423            left: Box::new(Expression::Boolean(false)),
424            op: crate::model::LogicalOp::And,
425            right: Box::new(Expression::Var("undefined".to_string())),
426        };
427
428        let result = evaluator.evaluate(&expr);
429        assert_eq!(result, Ok(Value::Bool(false)));
430    }
431}