// Concrete operator evaluation — unary_op and binary_op.
// Reference: cedar-policy-core/src/evaluator.rs
///|
/// Evaluate a unary operator on a concrete value.
pub fn eval_unary(
op : @ast.UnaryOp,
arg : @ast.Value,
) -> @ast.Value raise EvalError {
match op {
@ast.UnaryOp::Not =>
match arg {
@ast.Value::Bool(b) => @ast.Value::Bool(!b)
_ => raise TypeMismatch("Not requires Bool argument")
}
@ast.UnaryOp::Neg =>
match arg {
@ast.Value::Long(n) =>
// 0 - n with overflow check (min_value negation overflows)
sub_long(@ast.Value::Long(0L), @ast.Value::Long(n))
_ => raise TypeMismatch("Neg requires Long argument")
}
@ast.UnaryOp::IsEmpty =>
match arg {
@ast.Value::Set(arr) => @ast.Value::Bool(arr.length() == 0)
@ast.Value::Record(map) => {
let empty : Map[String, @ast.Value] = Map([])
@ast.Value::Bool(map == empty)
}
_ => raise TypeMismatch("IsEmpty requires Set or Record argument")
}
}
}
///|
/// Evaluate a binary operator on two concrete values.
/// Does NOT handle In_ — that requires EntityStore access
/// and is handled in expr_eval.mbt.
pub fn eval_binary(
op : @ast.BinaryOp,
lhs : @ast.Value,
rhs : @ast.Value,
) -> @ast.Value raise EvalError {
match op {
@ast.BinaryOp::Eq => @ast.Value::Bool(lhs == rhs)
@ast.BinaryOp::Ne => @ast.Value::Bool(!(lhs == rhs))
@ast.BinaryOp::Less => compare_long(lhs, rhs, fn(a, b) { a < b }, "<")
@ast.BinaryOp::LessEq => compare_long(lhs, rhs, fn(a, b) { a <= b }, "<=")
@ast.BinaryOp::Gt => compare_long(lhs, rhs, fn(a, b) { a > b }, ">")
@ast.BinaryOp::Ge => compare_long(lhs, rhs, fn(a, b) { a >= b }, ">=")
@ast.BinaryOp::Add => add_long(lhs, rhs)
@ast.BinaryOp::Sub => sub_long(lhs, rhs)
@ast.BinaryOp::Mul => mul_long(lhs, rhs)
@ast.BinaryOp::In_ =>
raise InvalidOperator("In_ requires EntityStore — handle in expr_eval")
@ast.BinaryOp::Contains => eval_contains(lhs, rhs)
@ast.BinaryOp::ContainsAll => eval_contains_all(lhs, rhs)
@ast.BinaryOp::ContainsAny => eval_contains_any(lhs, rhs)
}
}
// ---------------------------------------------------------------------------
// Comparison helpers
// ---------------------------------------------------------------------------
///|
fn compare_long(
lhs : @ast.Value,
rhs : @ast.Value,
cmp : (Int64, Int64) -> Bool,
op_name : String,
) -> @ast.Value raise EvalError {
match (lhs, rhs) {
(@ast.Value::Long(a), @ast.Value::Long(b)) => @ast.Value::Bool(cmp(a, b))
_ => raise TypeMismatch("\{op_name} requires Long arguments")
}
}
// ---------------------------------------------------------------------------
// Arithmetic helpers (checked)
// ---------------------------------------------------------------------------
///|
fn add_long(lhs : @ast.Value, rhs : @ast.Value) -> @ast.Value raise EvalError {
match (lhs, rhs) {
(@ast.Value::Long(a), @ast.Value::Long(b)) => {
let result = a + b
if (a > 0L && b > 0L && result < 0L) || (a < 0L && b < 0L && result > 0L) {
raise IntegerOverflow
}
@ast.Value::Long(result)
}
_ => raise TypeMismatch("+ requires Long arguments")
}
}
///|
pub fn sub_long(
lhs : @ast.Value,
rhs : @ast.Value,
) -> @ast.Value raise EvalError {
match (lhs, rhs) {
(@ast.Value::Long(a), @ast.Value::Long(b)) => {
let result = a - b
if (a > 0L && b < 0L && result < 0L) || (a < 0L && b > 0L && result > 0L) {
raise IntegerOverflow
}
@ast.Value::Long(result)
}
_ => raise TypeMismatch("- requires Long arguments")
}
}
///|
fn mul_long(lhs : @ast.Value, rhs : @ast.Value) -> @ast.Value raise EvalError {
match (lhs, rhs) {
(@ast.Value::Long(a), @ast.Value::Long(b)) => {
if a == 0L || b == 0L {
return @ast.Value::Long(0L)
}
let result = a * b
if result / b != a {
raise IntegerOverflow
}
@ast.Value::Long(result)
}
_ => raise TypeMismatch("* requires Long arguments")
}
}
// ---------------------------------------------------------------------------
// Set operations
// ---------------------------------------------------------------------------
///|
/// .contains(elem) — elem in set
fn eval_contains(
lhs : @ast.Value,
rhs : @ast.Value,
) -> @ast.Value raise EvalError {
match lhs {
@ast.Value::Set(arr) => @ast.Value::Bool(arr.contains(rhs))
_ => raise TypeMismatch(".contains requires Set operand")
}
}
///|
/// .containsAll(rhs) — all rhs elements are in lhs
fn eval_contains_all(
lhs : @ast.Value,
rhs : @ast.Value,
) -> @ast.Value raise EvalError {
match (lhs, rhs) {
(@ast.Value::Set(lhs_arr), @ast.Value::Set(rhs_arr)) => {
for elem in rhs_arr {
if !lhs_arr.contains(elem) {
return @ast.Value::Bool(false)
}
}
@ast.Value::Bool(true)
}
_ => raise TypeMismatch(".containsAll requires Set operands")
}
}
///|
/// .containsAny(rhs) — at least one rhs element is in lhs
fn eval_contains_any(
lhs : @ast.Value,
rhs : @ast.Value,
) -> @ast.Value raise EvalError {
match (lhs, rhs) {
(@ast.Value::Set(lhs_arr), @ast.Value::Set(rhs_arr)) => {
for elem in rhs_arr {
if lhs_arr.contains(elem) {
return @ast.Value::Bool(true)
}
}
@ast.Value::Bool(false)
}
_ => raise TypeMismatch(".containsAny requires Set operands")
}
}