1use lex_ast::{CExpr, CLit};
27
28#[derive(Debug, Clone, PartialEq)]
29pub enum DischargeOutcome {
30 Proved,
33 Refuted { reason: String },
36 Deferred { reason: String },
40}
41
42pub fn try_discharge(
46 predicate: &CExpr,
47 binding_name: &str,
48 arg: &CExpr,
49) -> DischargeOutcome {
50 let v = match arg_to_concrete(arg) {
51 Some(v) => v,
52 None => return DischargeOutcome::Deferred {
53 reason: "argument is not a literal; static discharge can't \
54 evaluate it (slice 3 will add a runtime check)".into(),
55 },
56 };
57 match eval(predicate, binding_name, &v) {
58 Ok(Concrete::Bool(true)) => DischargeOutcome::Proved,
59 Ok(Concrete::Bool(false)) => DischargeOutcome::Refuted {
60 reason: format!(
61 "predicate failed for {} = {}",
62 binding_name, v.show()),
63 },
64 Ok(other) => DischargeOutcome::Deferred {
65 reason: format!("predicate didn't reduce to a Bool (got {})",
66 other.show()),
67 },
68 Err(e) => DischargeOutcome::Deferred { reason: e },
69 }
70}
71
72fn arg_to_concrete(e: &CExpr) -> Option<Concrete> {
78 match e {
79 CExpr::Literal { value } => Some(Concrete::from_lit(value)),
80 CExpr::UnaryOp { op, expr } if op == "-" => match expr.as_ref() {
81 CExpr::Literal { value: CLit::Int { value } } =>
82 Some(Concrete::Int(-value)),
83 CExpr::Literal { value: CLit::Float { value } } => {
84 value.parse::<f64>().ok().map(|f| Concrete::Float(-f))
85 }
86 _ => None,
87 },
88 _ => None,
89 }
90}
91
92#[derive(Debug, Clone, PartialEq)]
93enum Concrete {
94 Int(i64),
95 Float(f64),
96 Bool(bool),
97 Str(String),
98}
99
100impl Concrete {
101 fn from_lit(l: &CLit) -> Self {
102 match l {
103 CLit::Int { value } => Concrete::Int(*value),
104 CLit::Float { value } => Concrete::Float(value.parse().unwrap_or(0.0)),
105 CLit::Bool { value } => Concrete::Bool(*value),
106 CLit::Str { value } => Concrete::Str(value.clone()),
107 _ => Concrete::Bool(false),
110 }
111 }
112 fn show(&self) -> String {
113 match self {
114 Concrete::Int(n) => format!("{n}"),
115 Concrete::Float(f) => format!("{f}"),
116 Concrete::Bool(b) => format!("{b}"),
117 Concrete::Str(s) => format!("{s:?}"),
118 }
119 }
120}
121
122fn eval(e: &CExpr, name: &str, value: &Concrete) -> Result<Concrete, String> {
126 match e {
127 CExpr::Literal { value: lit } => Ok(Concrete::from_lit(lit)),
128 CExpr::Var { name: n } => {
129 if n == name { Ok(value.clone()) }
130 else { Err(format!("predicate references free var `{n}`; \
131 slice 2 only supports the binding itself")) }
132 }
133 CExpr::UnaryOp { op, expr } => {
134 let v = eval(expr, name, value)?;
135 match (op.as_str(), v) {
136 ("not", Concrete::Bool(b)) => Ok(Concrete::Bool(!b)),
137 ("-", Concrete::Int(n)) => Ok(Concrete::Int(-n)),
138 ("-", Concrete::Float(n)) => Ok(Concrete::Float(-n)),
139 (o, v) => Err(format!("unsupported unary `{o}` on {}", v.show())),
140 }
141 }
142 CExpr::BinOp { op, lhs, rhs } => {
143 if op == "and" || op == "or" {
146 let l = eval(lhs, name, value)?;
147 let lb = match l {
148 Concrete::Bool(b) => b,
149 other => return Err(format!("`{op}` on non-bool: {}", other.show())),
150 };
151 if op == "and" && !lb { return Ok(Concrete::Bool(false)); }
152 if op == "or" && lb { return Ok(Concrete::Bool(true)); }
153 let r = eval(rhs, name, value)?;
154 return match r {
155 Concrete::Bool(b) => Ok(Concrete::Bool(b)),
156 other => Err(format!("`{op}` on non-bool: {}", other.show())),
157 };
158 }
159 let l = eval(lhs, name, value)?;
160 let r = eval(rhs, name, value)?;
161 apply_binop(op, &l, &r)
162 }
163 _ => Err(format!("unsupported predicate node: {e:?}")),
168 }
169}
170
171fn apply_binop(op: &str, l: &Concrete, r: &Concrete) -> Result<Concrete, String> {
172 use Concrete::*;
173 match (op, l, r) {
174 ("+", Int(a), Int(b)) => Ok(Int(a + b)),
175 ("-", Int(a), Int(b)) => Ok(Int(a - b)),
176 ("*", Int(a), Int(b)) => Ok(Int(a * b)),
177 ("/", Int(a), Int(b)) if *b != 0 => Ok(Int(a / b)),
178 ("%", Int(a), Int(b)) if *b != 0 => Ok(Int(a % b)),
179 ("+", Float(a), Float(b)) => Ok(Float(a + b)),
180 ("-", Float(a), Float(b)) => Ok(Float(a - b)),
181 ("*", Float(a), Float(b)) => Ok(Float(a * b)),
182 ("/", Float(a), Float(b)) => Ok(Float(a / b)),
183
184 ("==", a, b) => Ok(Bool(a == b)),
185 ("!=", a, b) => Ok(Bool(a != b)),
186
187 ("<", Int(a), Int(b)) => Ok(Bool(a < b)),
188 ("<=", Int(a), Int(b)) => Ok(Bool(a <= b)),
189 (">", Int(a), Int(b)) => Ok(Bool(a > b)),
190 (">=", Int(a), Int(b)) => Ok(Bool(a >= b)),
191
192 ("<", Float(a), Float(b)) => Ok(Bool(a < b)),
193 ("<=", Float(a), Float(b)) => Ok(Bool(a <= b)),
194 (">", Float(a), Float(b)) => Ok(Bool(a > b)),
195 (">=", Float(a), Float(b)) => Ok(Bool(a >= b)),
196
197 (op, a, b) => Err(format!(
198 "unsupported binop `{op}` on {} and {}", a.show(), b.show())),
199 }
200}