use lex_ast::{CExpr, CLit};
#[derive(Debug, Clone, PartialEq)]
pub enum DischargeOutcome {
Proved,
Refuted { reason: String },
Deferred { reason: String },
}
pub fn try_discharge(
predicate: &CExpr,
binding_name: &str,
arg: &CExpr,
) -> DischargeOutcome {
let v = match arg_to_concrete(arg) {
Some(v) => v,
None => return DischargeOutcome::Deferred {
reason: "argument is not a literal; static discharge can't \
evaluate it (slice 3 will add a runtime check)".into(),
},
};
match eval(predicate, binding_name, &v) {
Ok(Concrete::Bool(true)) => DischargeOutcome::Proved,
Ok(Concrete::Bool(false)) => DischargeOutcome::Refuted {
reason: format!(
"predicate failed for {} = {}",
binding_name, v.show()),
},
Ok(other) => DischargeOutcome::Deferred {
reason: format!("predicate didn't reduce to a Bool (got {})",
other.show()),
},
Err(e) => DischargeOutcome::Deferred { reason: e },
}
}
fn arg_to_concrete(e: &CExpr) -> Option<Concrete> {
match e {
CExpr::Literal { value } => Some(Concrete::from_lit(value)),
CExpr::UnaryOp { op, expr } if op == "-" => match expr.as_ref() {
CExpr::Literal { value: CLit::Int { value } } =>
Some(Concrete::Int(-value)),
CExpr::Literal { value: CLit::Float { value } } => {
value.parse::<f64>().ok().map(|f| Concrete::Float(-f))
}
_ => None,
},
_ => None,
}
}
#[derive(Debug, Clone, PartialEq)]
enum Concrete {
Int(i64),
Float(f64),
Bool(bool),
Str(String),
}
impl Concrete {
fn from_lit(l: &CLit) -> Self {
match l {
CLit::Int { value } => Concrete::Int(*value),
CLit::Float { value } => Concrete::Float(value.parse().unwrap_or(0.0)),
CLit::Bool { value } => Concrete::Bool(*value),
CLit::Str { value } => Concrete::Str(value.clone()),
_ => Concrete::Bool(false),
}
}
fn show(&self) -> String {
match self {
Concrete::Int(n) => format!("{n}"),
Concrete::Float(f) => format!("{f}"),
Concrete::Bool(b) => format!("{b}"),
Concrete::Str(s) => format!("{s:?}"),
}
}
}
fn eval(e: &CExpr, name: &str, value: &Concrete) -> Result<Concrete, String> {
match e {
CExpr::Literal { value: lit } => Ok(Concrete::from_lit(lit)),
CExpr::Var { name: n } => {
if n == name { Ok(value.clone()) }
else { Err(format!("predicate references free var `{n}`; \
slice 2 only supports the binding itself")) }
}
CExpr::UnaryOp { op, expr } => {
let v = eval(expr, name, value)?;
match (op.as_str(), v) {
("not", Concrete::Bool(b)) => Ok(Concrete::Bool(!b)),
("-", Concrete::Int(n)) => Ok(Concrete::Int(-n)),
("-", Concrete::Float(n)) => Ok(Concrete::Float(-n)),
(o, v) => Err(format!("unsupported unary `{o}` on {}", v.show())),
}
}
CExpr::BinOp { op, lhs, rhs } => {
if op == "and" || op == "or" {
let l = eval(lhs, name, value)?;
let lb = match l {
Concrete::Bool(b) => b,
other => return Err(format!("`{op}` on non-bool: {}", other.show())),
};
if op == "and" && !lb { return Ok(Concrete::Bool(false)); }
if op == "or" && lb { return Ok(Concrete::Bool(true)); }
let r = eval(rhs, name, value)?;
return match r {
Concrete::Bool(b) => Ok(Concrete::Bool(b)),
other => Err(format!("`{op}` on non-bool: {}", other.show())),
};
}
let l = eval(lhs, name, value)?;
let r = eval(rhs, name, value)?;
apply_binop(op, &l, &r)
}
_ => Err(format!("unsupported predicate node: {e:?}")),
}
}
fn apply_binop(op: &str, l: &Concrete, r: &Concrete) -> Result<Concrete, String> {
use Concrete::*;
match (op, l, r) {
("+", Int(a), Int(b)) => Ok(Int(a + b)),
("-", Int(a), Int(b)) => Ok(Int(a - b)),
("*", Int(a), Int(b)) => Ok(Int(a * b)),
("/", Int(a), Int(b)) if *b != 0 => Ok(Int(a / b)),
("%", Int(a), Int(b)) if *b != 0 => Ok(Int(a % b)),
("+", Float(a), Float(b)) => Ok(Float(a + b)),
("-", Float(a), Float(b)) => Ok(Float(a - b)),
("*", Float(a), Float(b)) => Ok(Float(a * b)),
("/", Float(a), Float(b)) => Ok(Float(a / b)),
("==", a, b) => Ok(Bool(a == b)),
("!=", a, b) => Ok(Bool(a != b)),
("<", Int(a), Int(b)) => Ok(Bool(a < b)),
("<=", Int(a), Int(b)) => Ok(Bool(a <= b)),
(">", Int(a), Int(b)) => Ok(Bool(a > b)),
(">=", Int(a), Int(b)) => Ok(Bool(a >= b)),
("<", Float(a), Float(b)) => Ok(Bool(a < b)),
("<=", Float(a), Float(b)) => Ok(Bool(a <= b)),
(">", Float(a), Float(b)) => Ok(Bool(a > b)),
(">=", Float(a), Float(b)) => Ok(Bool(a >= b)),
(op, a, b) => Err(format!(
"unsupported binop `{op}` on {} and {}", a.show(), b.show())),
}
}