1use crate::ast::logic::{
16 BinaryTemporalOp, LogicExpr, ModalDomain, ModalFlavor, QuantifierKind, TemporalOperator, Term,
17 ThematicRole,
18};
19use crate::intern::Interner;
20use crate::lexicon::get_canonical_noun;
21use logicaffeine_proof::{ProofExpr, ProofTerm};
22use crate::token::TokenType;
23
24fn canonical_comparison_name(name: &str) -> Option<&'static str> {
34 match name {
35 "Greater" | "Gt" => Some("Gt"),
36 "Less" | "Lt" => Some("Lt"),
37 "GreaterEqual" | "Gte" => Some("Gte"),
38 "LessEqual" | "Lte" => Some("Lte"),
39 "Equal" | "Eq" => Some("Eq"),
40 "NotEqual" | "Neq" => Some("Neq"),
41 _ => None,
42 }
43}
44
45fn canonical_arithmetic_fn(name: &str) -> Option<&'static str> {
50 match name {
51 "add" | "Add" => Some("Add"),
52 "sub" | "Sub" => Some("Sub"),
53 "mul" | "Mul" => Some("Mul"),
54 "div" | "Div" => Some("Div"),
55 _ => None,
56 }
57}
58
59fn subst_proof_term(t: &ProofTerm, from: &str, to: &str) -> ProofTerm {
61 match t {
62 ProofTerm::Variable(v) if v == from => ProofTerm::Variable(to.to_string()),
63 ProofTerm::BoundVarRef(v) if v == from => ProofTerm::BoundVarRef(to.to_string()),
64 ProofTerm::Function(name, args) => ProofTerm::Function(
65 name.clone(),
66 args.iter().map(|a| subst_proof_term(a, from, to)).collect(),
67 ),
68 ProofTerm::Group(args) => {
69 ProofTerm::Group(args.iter().map(|a| subst_proof_term(a, from, to)).collect())
70 }
71 other => other.clone(),
72 }
73}
74
75fn subst_proof_var(e: &ProofExpr, from: &str, to: &str) -> ProofExpr {
79 match e {
80 ProofExpr::Predicate { name, args, world } => ProofExpr::Predicate {
81 name: name.clone(),
82 args: args.iter().map(|a| subst_proof_term(a, from, to)).collect(),
83 world: world.clone(),
84 },
85 ProofExpr::Identity(a, b) => {
86 ProofExpr::Identity(subst_proof_term(a, from, to), subst_proof_term(b, from, to))
87 }
88 ProofExpr::And(l, r) => ProofExpr::And(
89 Box::new(subst_proof_var(l, from, to)),
90 Box::new(subst_proof_var(r, from, to)),
91 ),
92 ProofExpr::Or(l, r) => ProofExpr::Or(
93 Box::new(subst_proof_var(l, from, to)),
94 Box::new(subst_proof_var(r, from, to)),
95 ),
96 ProofExpr::Implies(l, r) => ProofExpr::Implies(
97 Box::new(subst_proof_var(l, from, to)),
98 Box::new(subst_proof_var(r, from, to)),
99 ),
100 ProofExpr::Iff(l, r) => ProofExpr::Iff(
101 Box::new(subst_proof_var(l, from, to)),
102 Box::new(subst_proof_var(r, from, to)),
103 ),
104 ProofExpr::Not(x) => ProofExpr::Not(Box::new(subst_proof_var(x, from, to))),
105 ProofExpr::ForAll { variable, body } if variable != from => ProofExpr::ForAll {
106 variable: variable.clone(),
107 body: Box::new(subst_proof_var(body, from, to)),
108 },
109 ProofExpr::Exists { variable, body } if variable != from => ProofExpr::Exists {
110 variable: variable.clone(),
111 body: Box::new(subst_proof_var(body, from, to)),
112 },
113 ProofExpr::Temporal { operator, body } => ProofExpr::Temporal {
114 operator: operator.clone(),
115 body: Box::new(subst_proof_var(body, from, to)),
116 },
117 ProofExpr::Term(t) => ProofExpr::Term(subst_proof_term(t, from, to)),
118 other => other.clone(),
119 }
120}
121
122fn subst_term_with(t: &ProofTerm, from: &str, to: &ProofTerm) -> ProofTerm {
123 match t {
124 ProofTerm::Variable(v) if v == from => to.clone(),
125 ProofTerm::BoundVarRef(v) if v == from => to.clone(),
126 ProofTerm::Function(name, args) => ProofTerm::Function(
127 name.clone(),
128 args.iter().map(|a| subst_term_with(a, from, to)).collect(),
129 ),
130 ProofTerm::Group(args) => {
131 ProofTerm::Group(args.iter().map(|a| subst_term_with(a, from, to)).collect())
132 }
133 other => other.clone(),
134 }
135}
136
137pub fn instantiate_var_with_constant(e: &ProofExpr, from: &str, to: &str) -> ProofExpr {
142 let c = ProofTerm::Constant(to.to_string());
143 subst_expr_with(e, from, &c)
144}
145
146fn subst_expr_with(e: &ProofExpr, from: &str, to: &ProofTerm) -> ProofExpr {
147 match e {
148 ProofExpr::Predicate { name, args, world } => ProofExpr::Predicate {
149 name: name.clone(),
150 args: args.iter().map(|a| subst_term_with(a, from, to)).collect(),
151 world: world.clone(),
152 },
153 ProofExpr::Identity(a, b) => {
154 ProofExpr::Identity(subst_term_with(a, from, to), subst_term_with(b, from, to))
155 }
156 ProofExpr::And(l, r) => ProofExpr::And(
157 Box::new(subst_expr_with(l, from, to)),
158 Box::new(subst_expr_with(r, from, to)),
159 ),
160 ProofExpr::Or(l, r) => ProofExpr::Or(
161 Box::new(subst_expr_with(l, from, to)),
162 Box::new(subst_expr_with(r, from, to)),
163 ),
164 ProofExpr::Implies(l, r) => ProofExpr::Implies(
165 Box::new(subst_expr_with(l, from, to)),
166 Box::new(subst_expr_with(r, from, to)),
167 ),
168 ProofExpr::Iff(l, r) => ProofExpr::Iff(
169 Box::new(subst_expr_with(l, from, to)),
170 Box::new(subst_expr_with(r, from, to)),
171 ),
172 ProofExpr::Not(x) => ProofExpr::Not(Box::new(subst_expr_with(x, from, to))),
173 ProofExpr::ForAll { variable, body } if variable != from => ProofExpr::ForAll {
174 variable: variable.clone(),
175 body: Box::new(subst_expr_with(body, from, to)),
176 },
177 ProofExpr::Exists { variable, body } if variable != from => ProofExpr::Exists {
178 variable: variable.clone(),
179 body: Box::new(subst_expr_with(body, from, to)),
180 },
181 ProofExpr::Temporal { operator, body } => ProofExpr::Temporal {
182 operator: operator.clone(),
183 body: Box::new(subst_expr_with(body, from, to)),
184 },
185 ProofExpr::Term(t) => ProofExpr::Term(subst_term_with(t, from, to)),
186 other => other.clone(),
187 }
188}
189
190pub fn logic_expr_to_proof_expr<'a>(expr: &LogicExpr<'a>, interner: &Interner) -> ProofExpr {
195 match expr {
196 LogicExpr::Predicate { name, args, world } => {
198 let name_str = interner.resolve(*name);
203 let normalized = match (args.len() == 2)
208 .then(|| canonical_comparison_name(name_str))
209 .flatten()
210 {
211 Some(canon) => canon.to_string(),
212 None => get_canonical_noun(&name_str.to_lowercase())
213 .map(|lemma| lemma.to_lowercase())
214 .unwrap_or_else(|| name_str.to_lowercase()),
215 };
216
217 ProofExpr::Predicate {
218 name: normalized,
219 args: args.iter().map(|t| term_to_proof_term(t, interner)).collect(),
220 world: world.map(|w| interner.resolve(w).to_string()),
221 }
222 }
223
224 LogicExpr::Identity { left, right } => ProofExpr::Identity(
225 term_to_proof_term(left, interner),
226 term_to_proof_term(right, interner),
227 ),
228
229 LogicExpr::Atom(s) => ProofExpr::Atom(interner.resolve(*s).to_string()),
230
231 LogicExpr::Quantifier {
233 kind,
234 variable,
235 body,
236 ..
237 } => {
238 let var_name = interner.resolve(*variable).to_string();
239 let body_expr = Box::new(logic_expr_to_proof_expr(body, interner));
240
241 match kind {
242 QuantifierKind::Universal => ProofExpr::ForAll {
243 variable: var_name,
244 body: body_expr,
245 },
246 QuantifierKind::Existential => ProofExpr::Exists {
247 variable: var_name,
248 body: body_expr,
249 },
250 QuantifierKind::Most => ProofExpr::Unsupported("Most quantifier".into()),
252 QuantifierKind::Few => ProofExpr::Unsupported("Few quantifier".into()),
253 QuantifierKind::Many => ProofExpr::Unsupported("Many quantifier".into()),
254 QuantifierKind::Generic => ProofExpr::ForAll {
255 variable: var_name,
256 body: body_expr,
257 },
258 QuantifierKind::Cardinal(1) => {
259 let x = var_name;
264 let y = format!("{x}_uniq");
265 let phi_y = Box::new(subst_proof_var(&body_expr, &x, &y));
266 let uniqueness = ProofExpr::ForAll {
267 variable: y.clone(),
268 body: Box::new(ProofExpr::Implies(
269 phi_y,
270 Box::new(ProofExpr::Identity(
271 ProofTerm::Variable(y),
272 ProofTerm::Variable(x.clone()),
273 )),
274 )),
275 };
276 ProofExpr::Exists {
277 variable: x,
278 body: Box::new(ProofExpr::And(body_expr, Box::new(uniqueness))),
279 }
280 }
281 QuantifierKind::Cardinal(n) => {
282 ProofExpr::Exists {
285 variable: format!("{}_{}", var_name, n),
286 body: body_expr,
287 }
288 }
289 QuantifierKind::AtLeast(_) | QuantifierKind::AtMost(_) => {
290 ProofExpr::Unsupported("Counting quantifier".into())
291 }
292 }
293 }
294
295 LogicExpr::BinaryOp { left, op, right } => {
297 let l = Box::new(logic_expr_to_proof_expr(left, interner));
298 let r = Box::new(logic_expr_to_proof_expr(right, interner));
299
300 match op {
301 TokenType::And => ProofExpr::And(l, r),
302 TokenType::Or => ProofExpr::Or(l, r),
303 TokenType::If | TokenType::Implies | TokenType::Then => ProofExpr::Implies(l, r),
304 TokenType::Iff => ProofExpr::Iff(l, r),
305 _ => ProofExpr::Unsupported(format!("Binary operator {:?}", op)),
306 }
307 }
308
309 LogicExpr::UnaryOp { op, operand } => {
310 let inner = Box::new(logic_expr_to_proof_expr(operand, interner));
311 match op {
312 TokenType::Not => ProofExpr::Not(inner),
313 _ => ProofExpr::Unsupported(format!("Unary operator {:?}", op)),
314 }
315 }
316
317 LogicExpr::Modal { vector, operand } => {
319 let body = Box::new(logic_expr_to_proof_expr(operand, interner));
320 let domain = match vector.domain {
321 ModalDomain::Alethic => "Alethic",
322 ModalDomain::Deontic => "Deontic",
323 ModalDomain::Temporal => "Temporal",
324 };
325 let flavor = match vector.flavor {
326 ModalFlavor::Root => "Root",
327 ModalFlavor::Epistemic => "Epistemic",
328 ModalFlavor::Evidential => "Evidential",
329 ModalFlavor::Bouletic => "Bouletic",
330 };
331 ProofExpr::Modal {
332 domain: domain.to_string(),
333 force: vector.force,
334 flavor: flavor.to_string(),
335 body,
336 }
337 }
338
339 LogicExpr::Temporal { operator, body } => {
341 let body_expr = Box::new(logic_expr_to_proof_expr(body, interner));
342 let op_name = match operator {
343 TemporalOperator::Past => "Past",
344 TemporalOperator::Future => "Future",
345 TemporalOperator::Always => "Always",
346 TemporalOperator::Eventually
347 | TemporalOperator::BoundedEventually(_) => "Eventually",
348 TemporalOperator::Next => "Next",
349 };
350 ProofExpr::Temporal {
351 operator: op_name.to_string(),
352 body: body_expr,
353 }
354 }
355
356 LogicExpr::TemporalBinary { operator, left, right } => ProofExpr::TemporalBinary {
357 operator: format!("{:?}", operator),
358 left: Box::new(logic_expr_to_proof_expr(left, interner)),
359 right: Box::new(logic_expr_to_proof_expr(right, interner)),
360 },
361
362 LogicExpr::Lambda { variable, body } => ProofExpr::Lambda {
364 variable: interner.resolve(*variable).to_string(),
365 body: Box::new(logic_expr_to_proof_expr(body, interner)),
366 },
367
368 LogicExpr::App { function, argument } => ProofExpr::App(
369 Box::new(logic_expr_to_proof_expr(function, interner)),
370 Box::new(logic_expr_to_proof_expr(argument, interner)),
371 ),
372
373 LogicExpr::NeoEvent(data) => {
375 let roles: Vec<(String, ProofTerm)> = data
376 .roles
377 .iter()
378 .map(|(role, term)| {
379 let role_name = match role {
380 ThematicRole::Agent => "Agent",
381 ThematicRole::Patient => "Patient",
382 ThematicRole::Theme => "Theme",
383 ThematicRole::Recipient => "Recipient",
384 ThematicRole::Goal => "Goal",
385 ThematicRole::Source => "Source",
386 ThematicRole::Instrument => "Instrument",
387 ThematicRole::Location => "Location",
388 ThematicRole::Time => "Time",
389 ThematicRole::Manner => "Manner",
390 ThematicRole::Result => "Result",
391 ThematicRole::Depictive => "Depictive",
392 };
393 (role_name.to_string(), term_to_proof_term(term, interner))
394 })
395 .collect();
396
397 ProofExpr::NeoEvent {
398 event_var: interner.resolve(data.event_var).to_string(),
399 verb: interner.resolve(data.verb).to_string(),
400 roles,
401 }
402 }
403
404 LogicExpr::Counterfactual {
406 antecedent,
407 consequent,
408 } => {
409 ProofExpr::Counterfactual {
413 antecedent: Box::new(logic_expr_to_proof_expr(antecedent, interner)),
414 consequent: Box::new(logic_expr_to_proof_expr(consequent, interner)),
415 }
416 }
417
418 LogicExpr::Categorical(_) => ProofExpr::Unsupported("Categorical (legacy)".into()),
420 LogicExpr::Relation(_) => ProofExpr::Unsupported("Relation (legacy)".into()),
421 LogicExpr::Metaphor { .. } => ProofExpr::Unsupported("Metaphor".into()),
422 LogicExpr::Question { wh_variable, body } => ProofExpr::Exists {
427 variable: interner.resolve(*wh_variable).to_string(),
428 body: Box::new(logic_expr_to_proof_expr(body, interner)),
429 },
430 LogicExpr::YesNoQuestion { .. } => ProofExpr::Unsupported("YesNoQuestion".into()),
431 LogicExpr::Intensional { .. } => ProofExpr::Unsupported("Intensional".into()),
432 LogicExpr::Event { .. } => ProofExpr::Unsupported("Event (legacy)".into()),
433 LogicExpr::Imperative { action } => {
434 ProofExpr::Modal {
438 domain: "Deontic".to_string(),
439 force: 1.0,
440 flavor: "Bouletic".to_string(),
441 body: Box::new(logic_expr_to_proof_expr(action, interner)),
442 }
443 }
444 LogicExpr::Exclamative { body, .. } => {
445 logic_expr_to_proof_expr(body, interner)
447 }
448 LogicExpr::Optative { wish } => {
449 ProofExpr::Modal {
452 domain: "Deontic".to_string(),
453 force: 1.0,
454 flavor: "Bouletic".to_string(),
455 body: Box::new(logic_expr_to_proof_expr(wish, interner)),
456 }
457 }
458 LogicExpr::Implicature { assertion, .. } => {
459 logic_expr_to_proof_expr(assertion, interner)
462 }
463 LogicExpr::SpeechAct { performer, act_type, .. } => {
464 ProofExpr::Predicate {
469 name: interner.resolve(*act_type).to_lowercase(),
470 args: vec![term_to_proof_term(&Term::Constant(*performer), interner)],
471 world: None,
472 }
473 }
474 LogicExpr::Causal { .. } => ProofExpr::Unsupported("Causal".into()),
475 LogicExpr::Concessive { main, .. } => {
476 logic_expr_to_proof_expr(main, interner)
479 }
480 LogicExpr::Comparative { .. } => ProofExpr::Unsupported("Comparative".into()),
481 LogicExpr::Superlative { .. } => ProofExpr::Unsupported("Superlative".into()),
482 LogicExpr::Scopal { .. } => ProofExpr::Unsupported("Scopal".into()),
483 LogicExpr::Control { .. } => ProofExpr::Unsupported("Control".into()),
484 LogicExpr::Presupposition {
485 assertion,
486 presupposition,
487 } => {
488 ProofExpr::And(
494 Box::new(logic_expr_to_proof_expr(assertion, interner)),
495 Box::new(logic_expr_to_proof_expr(presupposition, interner)),
496 )
497 }
498 LogicExpr::Focus { scope, .. } => {
499 logic_expr_to_proof_expr(scope, interner)
503 }
504 LogicExpr::TemporalAnchor { .. } => ProofExpr::Unsupported("TemporalAnchor".into()),
505 LogicExpr::Distributive { predicate } => {
506 let base = logic_expr_to_proof_expr(predicate, interner);
510 match find_sigma_symbol(predicate) {
511 Some(noun) => {
512 let noun_str = interner.resolve(noun).to_string();
513 let noun_pred = get_canonical_noun(&noun_str.to_lowercase())
514 .map(|lemma| lemma.to_lowercase())
515 .unwrap_or_else(|| noun_str.to_lowercase());
516 let var = format!("each_{}", noun_pred);
517 let sigma_term = ProofTerm::Variable(noun_str);
518 let member = ProofTerm::Variable(var.clone());
519 let body = replace_proof_term(&base, &sigma_term, &member);
520 ProofExpr::ForAll {
521 variable: var.clone(),
522 body: Box::new(ProofExpr::Implies(
523 Box::new(ProofExpr::Predicate {
524 name: noun_pred,
525 args: vec![ProofTerm::Variable(var)],
526 world: None,
527 }),
528 Box::new(body),
529 )),
530 }
531 }
532 None => base,
533 }
534 }
535 LogicExpr::GroupQuantifier {
536 group_var,
537 count,
538 member_var,
539 restriction,
540 body,
541 } => {
542 let g = interner.resolve(*group_var).to_string();
544 let x = interner.resolve(*member_var).to_string();
545 let group_pred = ProofExpr::Predicate {
546 name: "group".to_string(),
547 args: vec![ProofTerm::Variable(g.clone())],
548 world: None,
549 };
550 let count_pred = ProofExpr::Predicate {
551 name: "count".to_string(),
552 args: vec![
553 ProofTerm::Variable(g.clone()),
554 ProofTerm::Constant(count.to_string()),
555 ],
556 world: None,
557 };
558 let member_pred = ProofExpr::Predicate {
559 name: "member".to_string(),
560 args: vec![ProofTerm::Variable(x.clone()), ProofTerm::Variable(g.clone())],
561 world: None,
562 };
563 let members = ProofExpr::ForAll {
564 variable: x,
565 body: Box::new(ProofExpr::Implies(
566 Box::new(member_pred),
567 Box::new(logic_expr_to_proof_expr(restriction, interner)),
568 )),
569 };
570 ProofExpr::Exists {
571 variable: g,
572 body: Box::new(ProofExpr::And(
573 Box::new(ProofExpr::And(
574 Box::new(ProofExpr::And(Box::new(group_pred), Box::new(count_pred))),
575 Box::new(members),
576 )),
577 Box::new(logic_expr_to_proof_expr(body, interner)),
578 )),
579 }
580 }
581 LogicExpr::Aspectual { body, .. } => logic_expr_to_proof_expr(body, interner),
585 LogicExpr::Voice { .. } => ProofExpr::Unsupported("Voice".into()),
586 }
587}
588
589#[derive(Debug, Clone)]
593pub struct DefaultRule {
594 pub ab_name: String,
596 pub restriction_pred: Option<String>,
599 pub unary: bool,
601}
602
603pub fn logic_expr_to_proof_expr_defeasible<'a>(
610 expr: &LogicExpr<'a>,
611 interner: &Interner,
612 defaults: &mut Vec<DefaultRule>,
613) -> ProofExpr {
614 match expr {
615 LogicExpr::Quantifier {
616 kind: QuantifierKind::Generic,
617 variable,
618 body,
619 ..
620 } => {
621 let var = interner.resolve(*variable).to_string();
622 if let LogicExpr::BinaryOp {
623 left: restriction,
624 op: TokenType::Implies | TokenType::If,
625 right: nucleus,
626 } = body
627 {
628 let ab_name = format!("ab_{}", defaults.len() + 1);
629 let restriction_pred = match restriction {
630 LogicExpr::Predicate { name, .. } => {
631 let s = interner.resolve(*name);
632 Some(
633 get_canonical_noun(&s.to_lowercase())
634 .map(|l| l.to_lowercase())
635 .unwrap_or_else(|| s.to_lowercase()),
636 )
637 }
638 _ => None,
639 };
640 defaults.push(DefaultRule {
641 ab_name: ab_name.clone(),
642 restriction_pred,
643 unary: true,
644 });
645 let guarded = ProofExpr::And(
646 Box::new(logic_expr_to_proof_expr(restriction, interner)),
647 Box::new(ProofExpr::Not(Box::new(ProofExpr::Predicate {
648 name: ab_name,
649 args: vec![ProofTerm::Variable(var.clone())],
650 world: None,
651 }))),
652 );
653 return ProofExpr::ForAll {
654 variable: var,
655 body: Box::new(ProofExpr::Implies(
656 Box::new(guarded),
657 Box::new(logic_expr_to_proof_expr(nucleus, interner)),
658 )),
659 };
660 }
661 logic_expr_to_proof_expr(expr, interner)
662 }
663 LogicExpr::Implicature {
664 assertion,
665 implicature,
666 } => {
667 let ab_name = format!("ab_{}", defaults.len() + 1);
668 defaults.push(DefaultRule {
669 ab_name: ab_name.clone(),
670 restriction_pred: None,
671 unary: false,
672 });
673 ProofExpr::And(
674 Box::new(logic_expr_to_proof_expr(assertion, interner)),
675 Box::new(ProofExpr::Implies(
676 Box::new(ProofExpr::Not(Box::new(ProofExpr::Atom(ab_name)))),
677 Box::new(logic_expr_to_proof_expr(implicature, interner)),
678 )),
679 )
680 }
681 LogicExpr::BinaryOp { left, op, right } => {
684 let l = logic_expr_to_proof_expr_defeasible(left, interner, defaults);
685 let r = logic_expr_to_proof_expr_defeasible(right, interner, defaults);
686 match op {
687 TokenType::And => ProofExpr::And(Box::new(l), Box::new(r)),
688 TokenType::Or => ProofExpr::Or(Box::new(l), Box::new(r)),
689 TokenType::If | TokenType::Implies => {
690 ProofExpr::Implies(Box::new(l), Box::new(r))
691 }
692 TokenType::Iff => ProofExpr::Iff(Box::new(l), Box::new(r)),
693 _ => logic_expr_to_proof_expr(expr, interner),
694 }
695 }
696 LogicExpr::UnaryOp {
697 op: TokenType::Not,
698 operand,
699 } => ProofExpr::Not(Box::new(logic_expr_to_proof_expr_defeasible(
700 operand, interner, defaults,
701 ))),
702 LogicExpr::Presupposition {
703 assertion,
704 presupposition,
705 } => ProofExpr::And(
706 Box::new(logic_expr_to_proof_expr_defeasible(
707 assertion, interner, defaults,
708 )),
709 Box::new(logic_expr_to_proof_expr_defeasible(
710 presupposition,
711 interner,
712 defaults,
713 )),
714 ),
715 _ => logic_expr_to_proof_expr(expr, interner),
716 }
717}
718
719fn find_sigma_symbol<'a>(expr: &LogicExpr<'a>) -> Option<crate::Symbol> {
722 fn in_term<'a>(term: &Term<'a>) -> Option<crate::Symbol> {
723 match term {
724 Term::Sigma(s) => Some(*s),
725 Term::Function(_, args) | Term::Group(args) => args.iter().find_map(in_term),
726 Term::Possessed { possessor, .. } => in_term(possessor),
727 _ => None,
728 }
729 }
730 match expr {
731 LogicExpr::Predicate { args, .. } => args.iter().find_map(in_term),
732 LogicExpr::NeoEvent(data) => data.roles.iter().find_map(|(_, t)| in_term(t)),
733 LogicExpr::Quantifier { body, .. } => find_sigma_symbol(body),
734 LogicExpr::BinaryOp { left, right, .. } => {
735 find_sigma_symbol(left).or_else(|| find_sigma_symbol(right))
736 }
737 LogicExpr::UnaryOp { operand, .. } => find_sigma_symbol(operand),
738 LogicExpr::Modal { operand, .. } => find_sigma_symbol(operand),
739 LogicExpr::Temporal { body, .. } => find_sigma_symbol(body),
740 LogicExpr::Aspectual { body, .. } => find_sigma_symbol(body),
741 LogicExpr::Distributive { predicate } => find_sigma_symbol(predicate),
742 LogicExpr::Presupposition { assertion, .. } => find_sigma_symbol(assertion),
743 _ => None,
744 }
745}
746
747fn replace_proof_term(expr: &ProofExpr, from: &ProofTerm, to: &ProofTerm) -> ProofExpr {
750 fn in_term(term: &ProofTerm, from: &ProofTerm, to: &ProofTerm) -> ProofTerm {
751 if term == from {
752 return to.clone();
753 }
754 match term {
755 ProofTerm::Function(name, args) => ProofTerm::Function(
756 name.clone(),
757 args.iter().map(|t| in_term(t, from, to)).collect(),
758 ),
759 ProofTerm::Group(terms) => {
760 ProofTerm::Group(terms.iter().map(|t| in_term(t, from, to)).collect())
761 }
762 other => other.clone(),
763 }
764 }
765 match expr {
766 ProofExpr::Predicate { name, args, world } => ProofExpr::Predicate {
767 name: name.clone(),
768 args: args.iter().map(|t| in_term(t, from, to)).collect(),
769 world: world.clone(),
770 },
771 ProofExpr::Identity(l, r) => {
772 ProofExpr::Identity(in_term(l, from, to), in_term(r, from, to))
773 }
774 ProofExpr::And(l, r) => ProofExpr::And(
775 Box::new(replace_proof_term(l, from, to)),
776 Box::new(replace_proof_term(r, from, to)),
777 ),
778 ProofExpr::Or(l, r) => ProofExpr::Or(
779 Box::new(replace_proof_term(l, from, to)),
780 Box::new(replace_proof_term(r, from, to)),
781 ),
782 ProofExpr::Implies(l, r) => ProofExpr::Implies(
783 Box::new(replace_proof_term(l, from, to)),
784 Box::new(replace_proof_term(r, from, to)),
785 ),
786 ProofExpr::Iff(l, r) => ProofExpr::Iff(
787 Box::new(replace_proof_term(l, from, to)),
788 Box::new(replace_proof_term(r, from, to)),
789 ),
790 ProofExpr::Not(inner) => {
791 ProofExpr::Not(Box::new(replace_proof_term(inner, from, to)))
792 }
793 ProofExpr::ForAll { variable, body } => ProofExpr::ForAll {
794 variable: variable.clone(),
795 body: Box::new(replace_proof_term(body, from, to)),
796 },
797 ProofExpr::Exists { variable, body } => ProofExpr::Exists {
798 variable: variable.clone(),
799 body: Box::new(replace_proof_term(body, from, to)),
800 },
801 ProofExpr::Modal {
802 domain,
803 force,
804 flavor,
805 body,
806 } => ProofExpr::Modal {
807 domain: domain.clone(),
808 force: *force,
809 flavor: flavor.clone(),
810 body: Box::new(replace_proof_term(body, from, to)),
811 },
812 ProofExpr::Counterfactual {
813 antecedent,
814 consequent,
815 } => ProofExpr::Counterfactual {
816 antecedent: Box::new(replace_proof_term(antecedent, from, to)),
817 consequent: Box::new(replace_proof_term(consequent, from, to)),
818 },
819 ProofExpr::Temporal { operator, body } => ProofExpr::Temporal {
820 operator: operator.clone(),
821 body: Box::new(replace_proof_term(body, from, to)),
822 },
823 ProofExpr::NeoEvent {
824 event_var,
825 verb,
826 roles,
827 } => ProofExpr::NeoEvent {
828 event_var: event_var.clone(),
829 verb: verb.clone(),
830 roles: roles
831 .iter()
832 .map(|(r, t)| (r.clone(), in_term(t, from, to)))
833 .collect(),
834 },
835 other => other.clone(),
836 }
837}
838
839pub fn term_to_proof_term<'a>(term: &Term<'a>, interner: &Interner) -> ProofTerm {
840 match term {
841 Term::Constant(s) => ProofTerm::Constant(interner.resolve(*s).to_string()),
842
843 Term::Variable(s) => ProofTerm::Variable(interner.resolve(*s).to_string()),
844
845 Term::Function(name, args) => {
846 let name_str = interner.resolve(*name);
851 let canon = (args.len() == 2)
852 .then(|| canonical_arithmetic_fn(name_str))
853 .flatten()
854 .map(|s| s.to_string())
855 .unwrap_or_else(|| name_str.to_string());
856 ProofTerm::Function(
857 canon,
858 args.iter().map(|t| term_to_proof_term(t, interner)).collect(),
859 )
860 }
861
862 Term::Group(terms) => {
863 ProofTerm::Group(terms.iter().map(|t| term_to_proof_term(t, interner)).collect())
864 }
865
866 Term::Possessed { possessor, possessed } => {
867 ProofTerm::Function(
869 "has".to_string(),
870 vec![
871 term_to_proof_term(possessor, interner),
872 ProofTerm::Constant(interner.resolve(*possessed).to_string()),
873 ],
874 )
875 }
876
877 Term::Sigma(s) => {
878 ProofTerm::Variable(interner.resolve(*s).to_string())
880 }
881
882 Term::Intension(s) => {
883 ProofTerm::Constant(format!("^{}", interner.resolve(*s)))
885 }
886
887 Term::Kind(s) => {
888 ProofTerm::Constant(format!("^{}", interner.resolve(*s)))
891 }
892
893 Term::Proposition(expr) => {
894 let proof_expr = logic_expr_to_proof_expr(expr, interner);
897 ProofTerm::Constant(format!("[{}]", proof_expr))
898 }
899
900 Term::Value { kind, unit, .. } => {
901 use crate::ast::logic::NumberKind;
903 match kind {
904 NumberKind::Integer(n) => {
905 if let Some(u) = unit {
906 ProofTerm::Constant(format!("{}{}", n, interner.resolve(*u)))
907 } else {
908 ProofTerm::Constant(n.to_string())
909 }
910 }
911 NumberKind::Real(f) => {
912 if let Some(u) = unit {
913 ProofTerm::Constant(format!("{}{}", f, interner.resolve(*u)))
914 } else {
915 ProofTerm::Constant(f.to_string())
916 }
917 }
918 NumberKind::Symbolic(s) => ProofTerm::Constant(interner.resolve(*s).to_string()),
919 }
920 }
921 }
922}
923
924#[cfg(test)]
925mod tests {
926 use super::*;
927 use crate::arena::Arena;
928
929 #[test]
930 fn test_convert_predicate() {
931 let mut interner = Interner::new();
932 let name = interner.intern("Man");
933 let arg = interner.intern("socrates");
934
935 let arena: Arena<Term> = Arena::new();
936 let args = arena.alloc_slice([Term::Constant(arg)]);
937
938 let expr = LogicExpr::Predicate {
939 name,
940 args,
941 world: None,
942 };
943
944 let result = logic_expr_to_proof_expr(&expr, &interner);
945
946 match result {
947 ProofExpr::Predicate { name, args, world } => {
948 assert_eq!(name, "man");
950 assert_eq!(args.len(), 1);
951 assert!(matches!(&args[0], ProofTerm::Constant(s) if s == "socrates"));
953 assert!(world.is_none());
954 }
955 _ => panic!("Expected Predicate, got {:?}", result),
956 }
957 }
958
959 #[test]
960 fn test_convert_universal() {
961 let mut interner = Interner::new();
962 let var = interner.intern("x");
963 let pred = interner.intern("P");
964
965 let arena: Arena<LogicExpr> = Arena::new();
966 let term_arena: Arena<Term> = Arena::new();
967
968 let body = arena.alloc(LogicExpr::Predicate {
969 name: pred,
970 args: term_arena.alloc_slice([Term::Variable(var)]),
971 world: None,
972 });
973
974 let expr = LogicExpr::Quantifier {
975 kind: QuantifierKind::Universal,
976 variable: var,
977 body,
978 island_id: 0,
979 };
980
981 let result = logic_expr_to_proof_expr(&expr, &interner);
982
983 match result {
984 ProofExpr::ForAll { variable, body } => {
985 assert_eq!(variable, "x");
986 assert!(matches!(*body, ProofExpr::Predicate { .. }));
987 }
988 _ => panic!("Expected ForAll, got {:?}", result),
989 }
990 }
991
992 #[test]
993 fn test_convert_implication() {
994 let mut interner = Interner::new();
995 let p = interner.intern("P");
996 let q = interner.intern("Q");
997
998 let arena: Arena<LogicExpr> = Arena::new();
999
1000 let left = arena.alloc(LogicExpr::Atom(p));
1001 let right = arena.alloc(LogicExpr::Atom(q));
1002
1003 let expr = LogicExpr::BinaryOp {
1004 left,
1005 op: TokenType::If,
1006 right,
1007 };
1008
1009 let result = logic_expr_to_proof_expr(&expr, &interner);
1010
1011 match result {
1012 ProofExpr::Implies(l, r) => {
1013 assert!(matches!(*l, ProofExpr::Atom(ref s) if s == "P"));
1014 assert!(matches!(*r, ProofExpr::Atom(ref s) if s == "Q"));
1015 }
1016 _ => panic!("Expected Implies, got {:?}", result),
1017 }
1018 }
1019
1020 #[test]
1032 fn arithmetic_offset_uses_canonical_add() {
1033 let mut interner = Interner::new();
1035 let score = interner.intern("Score");
1036 let add = interner.intern("add");
1037 let tara = interner.intern("tara");
1038 let bessie = interner.intern("bessie");
1039
1040 let terms: Arena<Term> = Arena::new();
1041
1042 let score_tara = Term::Function(score, terms.alloc_slice([Term::Constant(tara)]));
1043 let score_bessie = Term::Function(score, terms.alloc_slice([Term::Constant(bessie)]));
1044 let offset = Term::Value {
1045 kind: crate::ast::logic::NumberKind::Integer(3),
1046 unit: None,
1047 dimension: None,
1048 };
1049 let rhs = Term::Function(add, terms.alloc_slice([score_bessie, offset]));
1050 let expr = LogicExpr::Identity {
1051 left: terms.alloc(score_tara),
1052 right: terms.alloc(rhs),
1053 };
1054
1055 match logic_expr_to_proof_expr(&expr, &interner) {
1056 ProofExpr::Identity(_, ProofTerm::Function(name, args)) => {
1057 assert_eq!(name, "Add", "offset function must be canonical Add; got {name}");
1058 assert!(
1059 matches!(&args[1], ProofTerm::Constant(s) if s == "3"),
1060 "offset constant must be the bare integer 3; got {:?}",
1061 args[1]
1062 );
1063 }
1064 other => panic!("expected Identity with an Add rhs, got {:?}", other),
1065 }
1066 }
1067
1068 #[test]
1069 fn arithmetic_offset_uses_canonical_sub() {
1070 let mut interner = Interner::new();
1072 let ord = interner.intern("Ord");
1073 let sub = interner.intern("sub");
1074 let a = interner.intern("a");
1075 let b = interner.intern("b");
1076 let terms: Arena<Term> = Arena::new();
1077 let ord_a = Term::Function(ord, terms.alloc_slice([Term::Constant(a)]));
1078 let ord_b = Term::Function(ord, terms.alloc_slice([Term::Constant(b)]));
1079 let offset = Term::Value {
1080 kind: crate::ast::logic::NumberKind::Integer(2),
1081 unit: None,
1082 dimension: None,
1083 };
1084 let rhs = Term::Function(sub, terms.alloc_slice([ord_b, offset]));
1085 let expr = LogicExpr::Identity {
1086 left: terms.alloc(ord_a),
1087 right: terms.alloc(rhs),
1088 };
1089 match logic_expr_to_proof_expr(&expr, &interner) {
1090 ProofExpr::Identity(_, ProofTerm::Function(name, _)) => {
1091 assert_eq!(name, "Sub", "offset function must be canonical Sub; got {name}");
1092 }
1093 other => panic!("expected Identity with a Sub rhs, got {:?}", other),
1094 }
1095 }
1096
1097 fn convert_binary_predicate(raw_name: &str) -> String {
1098 let mut interner = Interner::new();
1099 let name = interner.intern(raw_name);
1100 let a = interner.intern("a");
1101 let b = interner.intern("b");
1102 let terms: Arena<Term> = Arena::new();
1103 let args = terms.alloc_slice([Term::Constant(a), Term::Constant(b)]);
1104 let expr = LogicExpr::Predicate { name, args, world: None };
1105 match logic_expr_to_proof_expr(&expr, &interner) {
1106 ProofExpr::Predicate { name, .. } => name,
1107 other => panic!("expected Predicate, got {:?}", other),
1108 }
1109 }
1110
1111 #[test]
1112 fn comparison_predicates_use_canonical_names() {
1113 assert_eq!(convert_binary_predicate("Greater"), "Gt");
1116 assert_eq!(convert_binary_predicate("Less"), "Lt");
1117 assert_eq!(convert_binary_predicate("GreaterEqual"), "Gte");
1118 assert_eq!(convert_binary_predicate("LessEqual"), "Lte");
1119 assert_eq!(convert_binary_predicate("Equal"), "Eq");
1120 assert_eq!(convert_binary_predicate("NotEqual"), "Neq");
1121 }
1122
1123 #[test]
1124 fn ordinary_binary_predicate_is_not_remapped_to_a_comparison() {
1125 let name = convert_binary_predicate("Loves");
1129 assert!(
1130 !["Gt", "Lt", "Gte", "Lte", "Eq", "Neq"].contains(&name.as_str()),
1131 "ordinary predicate must not become a comparison op; got {name}"
1132 );
1133 assert_eq!(
1134 name,
1135 name.to_lowercase(),
1136 "ordinary predicate keeps the noun-normalised lowercase form; got {name}"
1137 );
1138 }
1139}