1use crate::ast::*;
4use indexmap::IndexMap;
5use lex_ast::canonicalize_program;
6use lex_bytecode::{compile_program, vm::Vm, Value};
7use lex_runtime::{DefaultHandler, Policy};
8use lex_syntax::parse_source;
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
12#[serde(rename_all = "snake_case")]
13pub enum ProofStatus { Proved, Counterexample, Inconclusive }
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct Evidence {
17 pub method: String,
18 pub trials: u32,
19 #[serde(default, skip_serializing_if = "Option::is_none")]
21 pub counterexample: Option<IndexMap<String, serde_json::Value>>,
22 #[serde(default, skip_serializing_if = "Option::is_none")]
24 pub note: Option<String>,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct CheckResult {
29 pub spec_id: String,
30 pub status: ProofStatus,
31 pub evidence: Evidence,
32}
33
34pub fn check_spec(spec: &Spec, lex_source: &str, trials: u32) -> CheckResult {
41 let spec_id = spec_id(spec);
42 let prog = match parse_source(lex_source) {
43 Ok(p) => p,
44 Err(e) => return inconclusive(spec_id, format!("parse: {e}")),
45 };
46 let stages = canonicalize_program(&prog);
47 if let Err(errs) = lex_types::check_program(&stages) {
48 return inconclusive(spec_id, format!("typecheck: {errs:?}"));
49 }
50 let bc = compile_program(&stages);
51
52 if spec.quantifiers.iter().any(|q| q.ty == SpecType::Float) {
56 return CheckResult {
57 spec_id,
58 status: ProofStatus::Inconclusive,
59 evidence: Evidence {
60 method: "randomized".into(),
61 trials: 0,
62 counterexample: None,
63 note: Some("randomized search inconclusive on Float quantifiers; use SMT (see to_smtlib)".into()),
64 },
65 };
66 }
67
68 let mut rng = DetRng::new(seed_from_spec(spec));
69 let policy = Policy::permissive();
70
71 for trial in 0..trials {
72 let mut bindings = IndexMap::new();
73 let mut skip = false;
74 for q in &spec.quantifiers {
75 let v = sample(&q.ty, &mut rng);
76 bindings.insert(q.name.clone(), v);
77 if let Some(c) = &q.constraint {
79 match eval(c, &bindings, &bc, &policy) {
80 Ok(Value::Bool(true)) => {}
81 Ok(Value::Bool(false)) => { skip = true; break; }
82 Ok(_) => return inconclusive(spec_id, format!("constraint did not return Bool (trial {trial})")),
83 Err(e) => return inconclusive(spec_id, format!("constraint eval failed: {e}")),
84 }
85 }
86 }
87 if skip { continue; }
88
89 match eval(&spec.body, &bindings, &bc, &policy) {
90 Ok(Value::Bool(true)) => continue,
91 Ok(Value::Bool(false)) => {
92 return CheckResult {
93 spec_id,
94 status: ProofStatus::Counterexample,
95 evidence: Evidence {
96 method: "randomized".into(),
97 trials: trial + 1,
98 counterexample: Some(bindings_to_json(&bindings)),
99 note: None,
100 },
101 };
102 }
103 Ok(other) => return inconclusive(spec_id, format!("body returned non-bool: {other:?}")),
104 Err(e) => return inconclusive(spec_id, format!("body eval failed: {e}")),
105 }
106 }
107
108 CheckResult {
109 spec_id,
110 status: ProofStatus::Proved,
111 evidence: Evidence {
112 method: "randomized".into(),
113 trials,
114 counterexample: None,
115 note: Some(format!("survived {trials} random trials; not a deductive proof")),
116 },
117 }
118}
119
120fn inconclusive(spec_id: String, note: impl Into<String>) -> CheckResult {
121 CheckResult {
122 spec_id,
123 status: ProofStatus::Inconclusive,
124 evidence: Evidence {
125 method: "randomized".into(),
126 trials: 0,
127 counterexample: None,
128 note: Some(note.into()),
129 },
130 }
131}
132
133fn spec_id(spec: &Spec) -> String {
134 use sha2::{Digest, Sha256};
135 let v = serde_json::to_value(spec).unwrap();
136 let s = lex_ast::canon_json::to_canonical_string(&v);
137 let mut h = Sha256::new();
138 h.update(s.as_bytes());
139 let r = h.finalize();
140 let mut hex = String::with_capacity(64);
141 for b in r { hex.push_str(&format!("{:02x}", b)); }
142 hex
143}
144
145fn seed_from_spec(spec: &Spec) -> u64 {
146 use sha2::{Digest, Sha256};
147 let mut h = Sha256::new();
148 h.update(spec.name.as_bytes());
149 let r = h.finalize();
150 u64::from_le_bytes(r[..8].try_into().unwrap())
151}
152
153struct DetRng { state: u64 }
155impl DetRng {
156 fn new(seed: u64) -> Self { Self { state: seed.max(1) } }
157 fn next_u64(&mut self) -> u64 {
158 let mut x = self.state;
159 x ^= x << 13;
160 x ^= x >> 7;
161 x ^= x << 17;
162 self.state = x;
163 x
164 }
165 fn next_i64_in(&mut self, lo: i64, hi: i64) -> i64 {
166 if hi <= lo { return lo; }
167 let span = (hi - lo) as u64;
168 lo + (self.next_u64() % span) as i64
169 }
170}
171
172fn sample(ty: &SpecType, rng: &mut DetRng) -> Value {
173 match ty {
174 SpecType::Int => Value::Int(rng.next_i64_in(-1000, 1001)),
175 SpecType::Float => {
176 let n = rng.next_u64() as f64;
177 Value::Float((n / u64::MAX as f64) * 2000.0 - 1000.0)
178 }
179 SpecType::Bool => Value::Bool(rng.next_u64() & 1 == 0),
180 SpecType::Str => {
181 let n = rng.next_u64() % 6;
182 Value::Str(("ab".repeat(n as usize)).chars().take(n as usize).collect())
183 }
184 }
185}
186
187fn eval(
188 e: &SpecExpr,
189 bindings: &IndexMap<String, Value>,
190 bc: &lex_bytecode::Program,
191 policy: &Policy,
192) -> Result<Value, String> {
193 match e {
194 SpecExpr::IntLit { value } => Ok(Value::Int(*value)),
195 SpecExpr::FloatLit { value } => Ok(Value::Float(*value)),
196 SpecExpr::BoolLit { value } => Ok(Value::Bool(*value)),
197 SpecExpr::StrLit { value } => Ok(Value::Str(value.clone())),
198 SpecExpr::Var { name } => bindings.get(name).cloned()
199 .ok_or_else(|| format!("unbound spec var `{name}`")),
200 SpecExpr::Let { name, value, body } => {
201 let v = eval(value, bindings, bc, policy)?;
202 let mut next = bindings.clone();
203 next.insert(name.clone(), v);
204 eval(body, &next, bc, policy)
205 }
206 SpecExpr::Not { expr } => match eval(expr, bindings, bc, policy)? {
207 Value::Bool(b) => Ok(Value::Bool(!b)),
208 other => Err(format!("not on non-bool: {other:?}")),
209 },
210 SpecExpr::BinOp { op, lhs, rhs } => {
211 let a = eval(lhs, bindings, bc, policy)?;
212 let b = eval(rhs, bindings, bc, policy)?;
213 apply_binop(*op, a, b)
214 }
215 SpecExpr::Call { func, args } => {
216 let mut argv = Vec::new();
218 for a in args { argv.push(eval(a, bindings, bc, policy)?); }
219 let handler = DefaultHandler::new(policy.clone());
221 let mut vm = Vm::with_handler(bc, Box::new(handler));
222 vm.call(func, argv).map_err(|e| format!("call `{func}`: {e}"))
223 }
224 }
225}
226
227fn apply_binop(op: SpecOp, a: Value, b: Value) -> Result<Value, String> {
228 use SpecOp::*;
229 match (op, &a, &b) {
230 (Add, Value::Int(x), Value::Int(y)) => Ok(Value::Int(x + y)),
231 (Sub, Value::Int(x), Value::Int(y)) => Ok(Value::Int(x - y)),
232 (Mul, Value::Int(x), Value::Int(y)) => Ok(Value::Int(x * y)),
233 (Div, Value::Int(x), Value::Int(y)) if *y != 0 => Ok(Value::Int(x / y)),
234 (Mod, Value::Int(x), Value::Int(y)) if *y != 0 => Ok(Value::Int(x % y)),
235 (Add, Value::Float(x), Value::Float(y)) => Ok(Value::Float(x + y)),
236 (Sub, Value::Float(x), Value::Float(y)) => Ok(Value::Float(x - y)),
237 (Mul, Value::Float(x), Value::Float(y)) => Ok(Value::Float(x * y)),
238 (Div, Value::Float(x), Value::Float(y)) => Ok(Value::Float(x / y)),
239 (Eq, x, y) => Ok(Value::Bool(x == y)),
240 (Neq, x, y) => Ok(Value::Bool(x != y)),
241 (Lt, Value::Int(x), Value::Int(y)) => Ok(Value::Bool(x < y)),
242 (Le, Value::Int(x), Value::Int(y)) => Ok(Value::Bool(x <= y)),
243 (Gt, Value::Int(x), Value::Int(y)) => Ok(Value::Bool(x > y)),
244 (Ge, Value::Int(x), Value::Int(y)) => Ok(Value::Bool(x >= y)),
245 (Lt, Value::Float(x), Value::Float(y)) => Ok(Value::Bool(x < y)),
246 (Le, Value::Float(x), Value::Float(y)) => Ok(Value::Bool(x <= y)),
247 (Gt, Value::Float(x), Value::Float(y)) => Ok(Value::Bool(x > y)),
248 (Ge, Value::Float(x), Value::Float(y)) => Ok(Value::Bool(x >= y)),
249 (And, Value::Bool(x), Value::Bool(y)) => Ok(Value::Bool(*x && *y)),
250 (Or, Value::Bool(x), Value::Bool(y)) => Ok(Value::Bool(*x || *y)),
251 _ => Err(format!("invalid binop {op:?} on {a:?}, {b:?}")),
252 }
253}
254
255fn bindings_to_json(b: &IndexMap<String, Value>) -> IndexMap<String, serde_json::Value> {
256 let mut out = IndexMap::new();
257 for (k, v) in b { out.insert(k.clone(), value_to_json(v)); }
258 out
259}
260
261fn value_to_json(v: &Value) -> serde_json::Value { v.to_json() }