use crate::model::Expression;
use crate::types::Type;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Value {
Bool(bool),
U64(u64),
U128(u128),
I64(i64),
Address(String),
}
impl Value {
pub fn get_type(&self) -> Type {
match self {
Self::Bool(_) => Type::Bool,
Self::U64(_) => Type::U64,
Self::U128(_) => Type::U128,
Self::I64(_) => Type::I64,
Self::Address(_) => Type::Address,
}
}
pub fn to_bool(&self) -> Result<bool, EvaluationError> {
match self {
Self::Bool(b) => Ok(*b),
Self::U64(n) => Ok(*n != 0),
Self::U128(n) => Ok(*n != 0),
Self::I64(n) => Ok(*n != 0),
Self::Address(a) => Ok(!a.is_empty()),
}
}
#[allow(dead_code)]
fn as_u64(&self) -> Result<u64, EvaluationError> {
match self {
Self::U64(n) => Ok(*n),
Self::U128(n) => {
if *n <= u64::MAX as u128 {
Ok(*n as u64)
} else {
Err(EvaluationError::ConversionOverflow)
}
}
Self::I64(n) => {
if *n >= 0 {
Ok(*n as u64)
} else {
Err(EvaluationError::ConversionOverflow)
}
}
_ => Err(EvaluationError::TypeError),
}
}
#[allow(dead_code)]
fn as_i64(&self) -> Result<i64, EvaluationError> {
match self {
Self::I64(n) => Ok(*n),
Self::U64(n) => {
if *n <= i64::MAX as u64 {
Ok(*n as i64)
} else {
Err(EvaluationError::ConversionOverflow)
}
}
_ => Err(EvaluationError::TypeError),
}
}
}
impl std::fmt::Display for Value {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Bool(b) => write!(f, "{}", b),
Self::U64(n) => write!(f, "{}", n),
Self::U128(n) => write!(f, "{}", n),
Self::I64(n) => write!(f, "{}", n),
Self::Address(a) => write!(f, "{}", a),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EvaluationError {
Overflow,
Underflow,
TypeError,
DivisionByZero,
UndefinedVariable(String),
UndefinedFunction(String),
InvalidArgument(String),
ConversionOverflow,
Custom(String),
}
impl std::fmt::Display for EvaluationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Overflow => write!(f, "arithmetic overflow"),
Self::Underflow => write!(f, "arithmetic underflow"),
Self::TypeError => write!(f, "type error"),
Self::DivisionByZero => write!(f, "division by zero"),
Self::UndefinedVariable(name) => write!(f, "undefined variable '{}'", name),
Self::UndefinedFunction(name) => write!(f, "undefined function '{}'", name),
Self::InvalidArgument(msg) => write!(f, "invalid argument: {}", msg),
Self::ConversionOverflow => write!(f, "conversion overflow"),
Self::Custom(msg) => write!(f, "{}", msg),
}
}
}
pub type EvalResult<T> = Result<T, EvaluationError>;
pub type EvalFunction = fn(&[Value]) -> EvalResult<Value>;
pub struct ExecutionContext {
pub state_vars: BTreeMap<String, Value>,
pub functions: BTreeMap<String, EvalFunction>,
}
impl ExecutionContext {
pub fn new() -> Self {
Self {
state_vars: BTreeMap::new(),
functions: BTreeMap::new(),
}
}
pub fn set_state(&mut self, name: String, value: Value) {
self.state_vars.insert(name, value);
}
pub fn register_function(&mut self, name: String, func: fn(&[Value]) -> EvalResult<Value>) {
self.functions.insert(name, func);
}
}
impl Default for ExecutionContext {
fn default() -> Self {
Self::new()
}
}
pub struct Evaluator {
context: ExecutionContext,
}
impl Evaluator {
pub fn new(context: ExecutionContext) -> Self {
Self { context }
}
pub fn evaluate(&self, expr: &Expression) -> EvalResult<Value> {
match expr {
Expression::Boolean(b) => Ok(Value::Bool(*b)),
Expression::Int(val) => {
if *val < 0 {
Ok(Value::I64(*val as i64))
} else if *val <= u64::MAX as i128 {
Ok(Value::U64(*val as u64))
} else {
Ok(Value::U128(*val as u128))
}
}
Expression::Var(name) => self
.context
.state_vars
.get(name)
.cloned()
.ok_or_else(|| EvaluationError::UndefinedVariable(name.clone())),
Expression::LayerVar { layer, var } => {
let qualified_name = format!("{}::{}", layer, var);
self.context
.state_vars
.get(&qualified_name)
.cloned()
.or_else(|| self.context.state_vars.get(var).cloned())
.ok_or(EvaluationError::UndefinedVariable(qualified_name))
}
Expression::PhaseQualifiedVar { phase, layer, var } => {
let qualified_name = format!("{}::{}::{}", phase, layer, var);
self.context
.state_vars
.get(&qualified_name)
.cloned()
.or_else(|| {
let layer_var = format!("{}::{}", layer, var);
self.context.state_vars.get(&layer_var).cloned()
})
.or_else(|| self.context.state_vars.get(var).cloned())
.ok_or(EvaluationError::UndefinedVariable(qualified_name))
}
Expression::PhaseConstraint {
phase: _,
constraint,
} => {
self.evaluate(constraint)
}
Expression::CrossPhaseRelation {
phase1: _,
expr1,
phase2: _,
expr2,
op,
} => {
let left_val = self.evaluate(expr1)?;
let right_val = self.evaluate(expr2)?;
self.eval_binary_op(&left_val, op, &right_val)
}
Expression::BinaryOp { left, op, right } => {
let left_val = self.evaluate(left)?;
let right_val = self.evaluate(right)?;
self.eval_binary_op(&left_val, op, &right_val)
}
Expression::Logical { left, op, right } => {
use crate::model::LogicalOp;
let left_val = self.evaluate(left)?.to_bool()?;
match op {
LogicalOp::And => {
if !left_val {
return Ok(Value::Bool(false));
}
let right_val = self.evaluate(right)?.to_bool()?;
Ok(Value::Bool(right_val))
}
LogicalOp::Or => {
if left_val {
return Ok(Value::Bool(true));
}
let right_val = self.evaluate(right)?.to_bool()?;
Ok(Value::Bool(right_val))
}
}
}
Expression::Not(expr) => {
let val = self.evaluate(expr)?.to_bool()?;
Ok(Value::Bool(!val))
}
Expression::FunctionCall { name, args } => {
let func = self
.context
.functions
.get(name)
.ok_or_else(|| EvaluationError::UndefinedFunction(name.clone()))?;
let arg_vals: EvalResult<Vec<Value>> =
args.iter().map(|arg| self.evaluate(arg)).collect();
func(&arg_vals?)
}
Expression::Tuple(exprs) => {
if exprs.is_empty() {
Ok(Value::Bool(true))
} else {
self.evaluate(&exprs[0])
}
}
}
}
fn eval_binary_op(
&self,
left: &Value,
op: &crate::model::BinaryOp,
right: &Value,
) -> EvalResult<Value> {
use crate::model::BinaryOp;
match op {
BinaryOp::Eq => Ok(Value::Bool(left == right)),
BinaryOp::Neq => Ok(Value::Bool(left != right)),
BinaryOp::Lt => match (left, right) {
(Value::U64(l), Value::U64(r)) => Ok(Value::Bool(l < r)),
(Value::I64(l), Value::I64(r)) => Ok(Value::Bool(l < r)),
(Value::U128(l), Value::U128(r)) => Ok(Value::Bool(l < r)),
_ => Err(EvaluationError::TypeError),
},
BinaryOp::Gt => match (left, right) {
(Value::U64(l), Value::U64(r)) => Ok(Value::Bool(l > r)),
(Value::I64(l), Value::I64(r)) => Ok(Value::Bool(l > r)),
(Value::U128(l), Value::U128(r)) => Ok(Value::Bool(l > r)),
_ => Err(EvaluationError::TypeError),
},
BinaryOp::Lte => match (left, right) {
(Value::U64(l), Value::U64(r)) => Ok(Value::Bool(l <= r)),
(Value::I64(l), Value::I64(r)) => Ok(Value::Bool(l <= r)),
(Value::U128(l), Value::U128(r)) => Ok(Value::Bool(l <= r)),
_ => Err(EvaluationError::TypeError),
},
BinaryOp::Gte => match (left, right) {
(Value::U64(l), Value::U64(r)) => Ok(Value::Bool(l >= r)),
(Value::I64(l), Value::I64(r)) => Ok(Value::Bool(l >= r)),
(Value::U128(l), Value::U128(r)) => Ok(Value::Bool(l >= r)),
_ => Err(EvaluationError::TypeError),
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_value_type_detection() {
assert_eq!(Value::Bool(true).get_type(), Type::Bool);
assert_eq!(Value::U64(42).get_type(), Type::U64);
assert_eq!(Value::I64(-42).get_type(), Type::I64);
}
#[test]
fn test_simple_evaluation() {
let ctx = ExecutionContext::new();
let evaluator = Evaluator::new(ctx);
let expr = Expression::Boolean(true);
let result = evaluator.evaluate(&expr);
assert_eq!(result, Ok(Value::Bool(true)));
}
#[test]
fn test_state_variable_evaluation() {
let mut ctx = ExecutionContext::new();
ctx.set_state("balance".to_string(), Value::U64(100));
let evaluator = Evaluator::new(ctx);
let expr = Expression::Var("balance".to_string());
let result = evaluator.evaluate(&expr);
assert_eq!(result, Ok(Value::U64(100)));
}
#[test]
fn test_comparison_evaluation() {
let ctx = ExecutionContext::new();
let evaluator = Evaluator::new(ctx);
let expr = Expression::BinaryOp {
left: Box::new(Expression::Int(10)),
op: crate::model::BinaryOp::Lt,
right: Box::new(Expression::Int(20)),
};
let result = evaluator.evaluate(&expr);
assert_eq!(result, Ok(Value::Bool(true)));
}
#[test]
fn test_logical_short_circuit() {
let ctx = ExecutionContext::new();
let evaluator = Evaluator::new(ctx);
let expr = Expression::Logical {
left: Box::new(Expression::Boolean(false)),
op: crate::model::LogicalOp::And,
right: Box::new(Expression::Var("undefined".to_string())),
};
let result = evaluator.evaluate(&expr);
assert_eq!(result, Ok(Value::Bool(false)));
}
}