1use logicaffeine_proof::{ProofExpr, ProofTerm};
19use std::collections::BTreeSet;
20
21const ROLE_ORDER: &[&str] = &[
24 "Agent", "Patient", "Theme", "Recipient", "Goal", "Source", "Instrument",
25 "Location", "Time", "Manner", "Result", "Depictive",
26];
27
28pub fn fol_to_model_checker(
31 premises: &[ProofExpr],
32 goal: &ProofExpr,
33 english: &str,
34 fol: &str,
35) -> String {
36 fol_to_model_checker_impl(premises, goal, english, fol, true)
37}
38
39pub fn fol_to_model_checker_module(
43 premises: &[ProofExpr],
44 goal: &ProofExpr,
45 english: &str,
46 fol: &str,
47) -> String {
48 fol_to_model_checker_impl(premises, goal, english, fol, false)
49}
50
51fn fol_to_model_checker_impl(
52 premises: &[ProofExpr],
53 goal: &ProofExpr,
54 english: &str,
55 fol: &str,
56 emit_main: bool,
57) -> String {
58 let temporal = premises.iter().any(contains_temporal) || contains_temporal(goal);
59
60 let mut consts: BTreeSet<String> = BTreeSet::new();
61 let mut facts: BTreeSet<(String, Vec<String>)> = BTreeSet::new();
62 for p in premises {
63 collect(p, &mut consts, &mut facts);
64 }
65 collect(goal, &mut consts, &mut facts);
66
67 let header = doc_header(english, fol);
68 if temporal {
69 emit_temporal_module(premises, goal, &consts, &facts, &header, emit_main)
70 } else {
71 emit_world_module(premises, goal, &consts, &facts, &header, emit_main)
72 }
73}
74
75fn emit_world_module(
78 premises: &[ProofExpr],
79 goal: &ProofExpr,
80 consts: &BTreeSet<String>,
81 facts: &BTreeSet<(String, Vec<String>)>,
82 header: &str,
83 emit_main: bool,
84) -> String {
85 let mut body = entailment(premises, goal, &|e| emit_expr(e, "w"));
86 for f in free_vars_of(premises, goal) {
89 body = format!("w.domain.iter().all(|&{}| {})", sanitize_var(&f), body);
90 }
91 let main = if emit_main {
92 format!(
93 "\nfn main() {{\n let w = {demo};\n println!(\"holds = {{}}\", holds(&w));\n}}\n",
94 demo = demo_world(consts, facts),
95 )
96 } else {
97 String::new()
98 };
99 format!(
100 "{header}{world}\n\
101 /// Evaluate the rule against a world.\n\
102 pub fn holds(w: &World) -> bool {{\n {body}\n}}\n{main}",
103 header = header,
104 world = WORLD_DEF,
105 body = body,
106 main = main,
107 )
108}
109
110fn emit_temporal_module(
113 premises: &[ProofExpr],
114 goal: &ProofExpr,
115 consts: &BTreeSet<String>,
116 facts: &BTreeSet<(String, Vec<String>)>,
117 header: &str,
118 emit_main: bool,
119) -> String {
120 let mut body = entailment(premises, goal, &|e| emit_expr_trace(e, "0", 0));
121 for f in free_vars_of(premises, goal) {
123 body = format!("trace.first().map_or(true, |w| w.domain.iter().all(|&{}| {}))", sanitize_var(&f), body);
124 }
125 let demo = demo_world(consts, facts);
126 let main = if emit_main {
127 format!(
128 "fn main() {{\n \
129 let trace = vec![{demo}, {demo}];\n \
130 println!(\"holds = {{}}\", holds(&trace));\n}}\n",
131 demo = demo,
132 )
133 } else {
134 String::new()
135 };
136 format!(
137 "{header}{world}\n\
138 /// Evaluate the temporal rule over a finite trace of worlds.\n\
139 pub fn holds(trace: &[World]) -> bool {{\n {body}\n}}\n\n\
140 /// Incremental monitor: feed worlds as they happen.\n\
141 #[derive(Default)]\n\
142 pub struct Monitor {{ trace: Vec<World> }}\n\n\
143 impl Monitor {{\n \
144 pub fn new() -> Self {{ Self::default() }}\n \
145 /// Append a world and report whether the rule still holds over the trace so far.\n \
146 pub fn step(&mut self, w: World) -> bool {{ self.trace.push(w); holds(&self.trace) }}\n}}\n\n\
147 {main}",
148 header = header,
149 world = WORLD_DEF,
150 body = body,
151 main = main,
152 )
153}
154
155fn entailment(premises: &[ProofExpr], goal: &ProofExpr, emit: &dyn Fn(&ProofExpr) -> String) -> String {
157 let g = emit(goal);
158 if premises.is_empty() {
159 g
160 } else {
161 let prem: Vec<String> = premises.iter().map(|p| emit(p)).collect();
162 format!("(!({}) || {})", prem.join(" && "), g)
163 }
164}
165
166const WORLD_DEF: &str = "\
168use std::collections::HashSet;\n\n\
169/// A finite world: named individuals (including events) and the facts that hold.\n\
170#[derive(Default, Clone)]\n\
171pub struct World {\n \
172pub domain: Vec<&'static str>,\n \
173pub facts: HashSet<(&'static str, Vec<&'static str>)>,\n}\n\n\
174impl World {\n \
175pub fn new() -> Self { Self::default() }\n \
176/// Add a named individual to the domain.\n \
177pub fn individual(mut self, name: &'static str) -> Self {\n \
178if !self.domain.contains(&name) { self.domain.push(name); }\n \
179self\n }\n \
180/// Assert `name(args...)`; any new individuals are added to the domain.\n \
181pub fn fact(mut self, name: &'static str, args: &[&'static str]) -> Self {\n \
182for &a in args { if !self.domain.contains(&a) { self.domain.push(a); } }\n \
183self.facts.insert((name, args.to_vec()));\n \
184self\n }\n \
185/// True iff `name(args...)` holds in this world.\n \
186pub fn pred(&self, name: &'static str, args: &[&'static str]) -> bool {\n \
187self.facts.contains(&(name, args.to_vec()))\n }\n}\n";
188
189fn doc_header(english: &str, fol: &str) -> String {
190 let one_line = |s: &str| {
191 s.lines()
192 .map(|l| l.trim())
193 .filter(|l| !l.is_empty())
194 .collect::<Vec<_>>()
195 .join(" | ")
196 };
197 let mut h = String::from("// Auto-generated rule from logic — a reusable World + `holds`.\n");
198 let e = one_line(english);
199 if !e.is_empty() {
200 h.push_str(&format!("// English: {}\n", e));
201 }
202 let f = one_line(fol);
203 if !f.is_empty() {
204 h.push_str(&format!("// FOL: {}\n", f));
205 }
206 h.push('\n');
207 h
208}
209
210fn demo_world(consts: &BTreeSet<String>, facts: &BTreeSet<(String, Vec<String>)>) -> String {
212 let mut chain = String::from("World::new()");
213 for (name, args) in facts {
214 let a: Vec<String> = args.iter().map(|x| format!("{:?}", x)).collect();
215 chain.push_str(&format!(".fact({:?}, &[{}])", name, a.join(", ")));
216 }
217 let in_fact: BTreeSet<&String> = facts.iter().flat_map(|(_, a)| a.iter()).collect();
219 for c in consts {
220 if !in_fact.contains(c) {
221 chain.push_str(&format!(".individual({:?})", c));
222 }
223 }
224 chain
225}
226
227fn emit_expr(e: &ProofExpr, world: &str) -> String {
230 match e {
231 ProofExpr::Predicate { name, args, .. } => {
232 let a: Vec<String> = args.iter().map(emit_term).collect();
233 format!("{}.pred({:?}, &[{}])", world, name, a.join(", "))
234 }
235 ProofExpr::Atom(s) => format!("{}.pred({:?}, &[])", world, s),
236 ProofExpr::Identity(a, b) => format!("({} == {})", emit_term(a), emit_term(b)),
237 ProofExpr::And(l, r) => format!("({} && {})", emit_expr(l, world), emit_expr(r, world)),
238 ProofExpr::Or(l, r) => format!("({} || {})", emit_expr(l, world), emit_expr(r, world)),
239 ProofExpr::Implies(l, r) => format!("(!{} || {})", emit_expr(l, world), emit_expr(r, world)),
240 ProofExpr::Iff(l, r) => format!("({} == {})", emit_expr(l, world), emit_expr(r, world)),
241 ProofExpr::Not(x) => format!("(!{})", emit_expr(x, world)),
242 ProofExpr::ForAll { variable, body } => format!(
243 "{}.domain.iter().all(|&{}| {})",
244 world,
245 sanitize_var(variable),
246 emit_expr(body, world)
247 ),
248 ProofExpr::Exists { variable, body } => format!(
249 "{}.domain.iter().any(|&{}| {})",
250 world,
251 sanitize_var(variable),
252 emit_expr(body, world)
253 ),
254 ProofExpr::NeoEvent { event_var, verb, roles } => {
257 let e = sanitize_var(event_var);
258 let mut sorted = roles.clone();
259 sorted.sort_by_key(|(r, _)| {
260 ROLE_ORDER.iter().position(|x| x == r).unwrap_or(ROLE_ORDER.len())
261 });
262 let mut parts = vec![format!("{}.pred({:?}, &[{}])", world, verb, e)];
263 for (role, term) in &sorted {
264 parts.push(format!(
265 "{}.pred({:?}, &[{}, {}])",
266 world,
267 role,
268 e,
269 emit_term(term)
270 ));
271 }
272 format!("{}.domain.iter().any(|&{}| {})", world, e, parts.join(" && "))
273 }
274 other => format!("true /* unsupported: {} */", variant_name(other)),
275 }
276}
277
278fn emit_expr_trace(e: &ProofExpr, idx: &str, depth: usize) -> String {
281 if !contains_temporal(e) {
283 return emit_expr(e, &format!("(&trace[{}])", idx));
284 }
285 match e {
286 ProofExpr::Temporal { operator, body } => {
287 let v = format!("i{}", depth);
288 let inner = emit_expr_trace(body, &v, depth + 1);
289 match operator.as_str() {
290 "Always" => format!("({idx}..trace.len()).all(|{v}| {inner})", idx = idx, v = v, inner = inner),
291 "Eventually" | "Future" => {
292 format!("({idx}..trace.len()).any(|{v}| {inner})", idx = idx, v = v, inner = inner)
293 }
294 "Past" => format!("(0..={idx}).any(|{v}| {inner})", idx = idx, v = v, inner = inner),
295 "Next" => format!(
296 "{{ let {v} = {idx} + 1; {v} < trace.len() && {inner} }}",
297 v = v,
298 idx = idx,
299 inner = inner
300 ),
301 _ => format!("({idx}..trace.len()).all(|{v}| {inner})", idx = idx, v = v, inner = inner),
302 }
303 }
304 ProofExpr::TemporalBinary { operator, left, right } => {
305 let k = format!("k{}", depth);
306 let j = format!("j{}", depth);
307 let r_at_k = emit_expr_trace(right, &k, depth + 1);
308 let l_at_j = emit_expr_trace(left, &j, depth + 1);
309 match operator.as_str() {
310 "Release" => format!(
311 "({idx}..trace.len()).all(|{k}| {r} || ({idx}..{k}).any(|{j}| {l}))",
312 idx = idx, k = k, j = j, r = r_at_k, l = l_at_j
313 ),
314 "WeakUntil" => {
315 let l_all = emit_expr_trace(left, &j, depth + 1);
316 format!(
317 "(({idx}..trace.len()).all(|{j}| {l_all})) || (({idx}..trace.len()).any(|{k}| {r} && ({idx}..{k}).all(|{j}| {l})))",
318 idx = idx, k = k, j = j, l_all = l_all, r = r_at_k, l = l_at_j
319 )
320 }
321 _ => format!(
323 "({idx}..trace.len()).any(|{k}| {r} && ({idx}..{k}).all(|{j}| {l}))",
324 idx = idx, k = k, j = j, r = r_at_k, l = l_at_j
325 ),
326 }
327 }
328 ProofExpr::And(l, r) => {
329 format!("({} && {})", emit_expr_trace(l, idx, depth), emit_expr_trace(r, idx, depth))
330 }
331 ProofExpr::Or(l, r) => {
332 format!("({} || {})", emit_expr_trace(l, idx, depth), emit_expr_trace(r, idx, depth))
333 }
334 ProofExpr::Implies(l, r) => {
335 format!("(!{} || {})", emit_expr_trace(l, idx, depth), emit_expr_trace(r, idx, depth))
336 }
337 ProofExpr::Iff(l, r) => {
338 format!("({} == {})", emit_expr_trace(l, idx, depth), emit_expr_trace(r, idx, depth))
339 }
340 ProofExpr::Not(x) => format!("(!{})", emit_expr_trace(x, idx, depth)),
341 _ => emit_expr(e, &format!("(&trace[{}])", idx)),
344 }
345}
346
347fn free_vars_of(premises: &[ProofExpr], goal: &ProofExpr) -> BTreeSet<String> {
350 let mut out = BTreeSet::new();
351 let mut bound: Vec<String> = Vec::new();
352 for p in premises {
353 free_vars(p, &mut bound, &mut out);
354 }
355 free_vars(goal, &mut bound, &mut out);
356 out
357}
358
359fn free_vars(e: &ProofExpr, bound: &mut Vec<String>, out: &mut BTreeSet<String>) {
360 match e {
361 ProofExpr::Predicate { args, .. } => {
362 for a in args {
363 free_term(a, bound, out);
364 }
365 }
366 ProofExpr::Identity(a, b) => {
367 free_term(a, bound, out);
368 free_term(b, bound, out);
369 }
370 ProofExpr::And(l, r)
371 | ProofExpr::Or(l, r)
372 | ProofExpr::Implies(l, r)
373 | ProofExpr::Iff(l, r) => {
374 free_vars(l, bound, out);
375 free_vars(r, bound, out);
376 }
377 ProofExpr::Not(x) => free_vars(x, bound, out),
378 ProofExpr::ForAll { variable, body } | ProofExpr::Exists { variable, body } => {
379 bound.push(variable.clone());
380 free_vars(body, bound, out);
381 bound.pop();
382 }
383 ProofExpr::Temporal { body, .. } => free_vars(body, bound, out),
384 ProofExpr::TemporalBinary { left, right, .. } => {
385 free_vars(left, bound, out);
386 free_vars(right, bound, out);
387 }
388 ProofExpr::NeoEvent { event_var, roles, .. } => {
389 bound.push(event_var.clone());
390 for (_, t) in roles {
391 free_term(t, bound, out);
392 }
393 bound.pop();
394 }
395 _ => {}
396 }
397}
398
399fn free_term(t: &ProofTerm, bound: &[String], out: &mut BTreeSet<String>) {
400 match t {
401 ProofTerm::Variable(v) | ProofTerm::BoundVarRef(v) => {
402 if !bound.contains(v) {
403 out.insert(v.clone());
404 }
405 }
406 ProofTerm::Function(_, args) | ProofTerm::Group(args) => {
407 for a in args {
408 free_term(a, bound, out);
409 }
410 }
411 ProofTerm::Constant(_) => {}
412 }
413}
414
415fn contains_temporal(e: &ProofExpr) -> bool {
416 match e {
417 ProofExpr::Temporal { .. } | ProofExpr::TemporalBinary { .. } => true,
418 ProofExpr::And(l, r) | ProofExpr::Or(l, r) | ProofExpr::Implies(l, r) | ProofExpr::Iff(l, r) => {
419 contains_temporal(l) || contains_temporal(r)
420 }
421 ProofExpr::Not(x) => contains_temporal(x),
422 ProofExpr::ForAll { body, .. } | ProofExpr::Exists { body, .. } => contains_temporal(body),
423 _ => false,
424 }
425}
426
427fn emit_term(t: &ProofTerm) -> String {
428 match t {
429 ProofTerm::Constant(c) => format!("{:?}", c),
430 ProofTerm::Variable(v) | ProofTerm::BoundVarRef(v) => sanitize_var(v),
431 ProofTerm::Function(name, _) => format!("{:?}", name),
432 ProofTerm::Group(_) => "\"?\"".to_string(),
433 }
434}
435
436fn sanitize_var(v: &str) -> String {
437 let cleaned: String = v
438 .chars()
439 .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' })
440 .collect();
441 format!("v_{}", cleaned)
442}
443
444fn collect(e: &ProofExpr, consts: &mut BTreeSet<String>, facts: &mut BTreeSet<(String, Vec<String>)>) {
445 match e {
446 ProofExpr::Predicate { name, args, .. } => {
447 for a in args {
448 collect_term(a, consts);
449 }
450 let mut ground = Vec::new();
451 let mut all_const = true;
452 for a in args {
453 match a {
454 ProofTerm::Constant(c) => ground.push(c.clone()),
455 _ => {
456 all_const = false;
457 break;
458 }
459 }
460 }
461 if all_const {
462 facts.insert((name.clone(), ground));
463 }
464 }
465 ProofExpr::Atom(s) => {
466 facts.insert((s.clone(), vec![]));
467 }
468 ProofExpr::Identity(a, b) => {
469 collect_term(a, consts);
470 collect_term(b, consts);
471 }
472 ProofExpr::And(l, r)
473 | ProofExpr::Or(l, r)
474 | ProofExpr::Implies(l, r)
475 | ProofExpr::Iff(l, r) => {
476 collect(l, consts, facts);
477 collect(r, consts, facts);
478 }
479 ProofExpr::Not(x) => collect(x, consts, facts),
480 ProofExpr::ForAll { body, .. } | ProofExpr::Exists { body, .. } => collect(body, consts, facts),
481 ProofExpr::Temporal { body, .. } => collect(body, consts, facts),
482 ProofExpr::TemporalBinary { left, right, .. } => {
483 collect(left, consts, facts);
484 collect(right, consts, facts);
485 }
486 ProofExpr::NeoEvent { roles, .. } => {
487 for (_, term) in roles {
488 collect_term(term, consts);
489 }
490 }
491 _ => {}
492 }
493}
494
495fn collect_term(t: &ProofTerm, consts: &mut BTreeSet<String>) {
496 match t {
497 ProofTerm::Constant(c) => {
498 consts.insert(c.clone());
499 }
500 ProofTerm::Function(_, args) | ProofTerm::Group(args) => {
501 for a in args {
502 collect_term(a, consts);
503 }
504 }
505 _ => {}
506 }
507}
508
509fn variant_name(e: &ProofExpr) -> &'static str {
510 match e {
511 ProofExpr::Modal { .. } => "modal",
512 ProofExpr::Counterfactual { .. } => "counterfactual",
513 ProofExpr::Lambda { .. } => "lambda",
514 ProofExpr::App(..) => "application",
515 ProofExpr::Ctor { .. } => "constructor",
516 ProofExpr::Match { .. } => "match",
517 ProofExpr::Fixpoint { .. } => "fixpoint",
518 ProofExpr::Temporal { .. } | ProofExpr::TemporalBinary { .. } => "temporal",
519 _ => "construct",
520 }
521}