logicaffeine_language/
proof_convert.rs

1//! Conversion from parser AST to proof engine representation.
2//!
3//! This module bridges the parser's arena-allocated AST ([`LogicExpr<'a>`]) to the
4//! proof engine's owned representation ([`ProofExpr`]).
5//!
6//! The conversion clones all data into owned Strings, enabling proof trees
7//! to persist beyond the arena's lifetime. Symbols are resolved using the
8//! interner at conversion time.
9//!
10//! # Key Function
11//!
12//! [`logic_expr_to_proof_expr`] is the main entry point for converting
13//! parsed expressions to the format expected by the proof search engine.
14
15use 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
24// =============================================================================
25// PUBLIC API
26// =============================================================================
27
28/// Convert a LogicExpr to ProofExpr.
29///
30/// This is the main entry point for bridging the parser to the proof engine.
31/// All Symbols are resolved to owned Strings using the interner.
32pub fn logic_expr_to_proof_expr<'a>(expr: &LogicExpr<'a>, interner: &Interner) -> ProofExpr {
33    match expr {
34        // --- Core FOL ---
35        LogicExpr::Predicate { name, args, world } => {
36            // Semantic Normalization:
37            // 1. Lemmatize: "cats" → "Cat", "men" → "Man" (canonical noun form)
38            // 2. Lowercase: "Cat" → "cat", "Mortal" → "mortal"
39            // This ensures "Mortal" (noun) == "mortal" (adj) == "mortals" (plural noun)
40            let name_str = interner.resolve(*name);
41            let normalized = get_canonical_noun(&name_str.to_lowercase())
42                .map(|lemma| lemma.to_lowercase())
43                .unwrap_or_else(|| name_str.to_lowercase());
44
45            ProofExpr::Predicate {
46                name: normalized,
47                args: args.iter().map(|t| term_to_proof_term(t, interner)).collect(),
48                world: world.map(|w| interner.resolve(w).to_string()),
49            }
50        }
51
52        LogicExpr::Identity { left, right } => ProofExpr::Identity(
53            term_to_proof_term(left, interner),
54            term_to_proof_term(right, interner),
55        ),
56
57        LogicExpr::Atom(s) => ProofExpr::Atom(interner.resolve(*s).to_string()),
58
59        // --- Quantifiers ---
60        LogicExpr::Quantifier {
61            kind,
62            variable,
63            body,
64            ..
65        } => {
66            let var_name = interner.resolve(*variable).to_string();
67            let body_expr = Box::new(logic_expr_to_proof_expr(body, interner));
68
69            match kind {
70                QuantifierKind::Universal => ProofExpr::ForAll {
71                    variable: var_name,
72                    body: body_expr,
73                },
74                QuantifierKind::Existential => ProofExpr::Exists {
75                    variable: var_name,
76                    body: body_expr,
77                },
78                // Map other quantifiers to existential with a note
79                QuantifierKind::Most => ProofExpr::Unsupported("Most quantifier".into()),
80                QuantifierKind::Few => ProofExpr::Unsupported("Few quantifier".into()),
81                QuantifierKind::Many => ProofExpr::Unsupported("Many quantifier".into()),
82                QuantifierKind::Generic => ProofExpr::ForAll {
83                    variable: var_name,
84                    body: body_expr,
85                },
86                QuantifierKind::Cardinal(n) => {
87                    // Cardinal(n) is existential in proof context
88                    ProofExpr::Exists {
89                        variable: format!("{}_{}", var_name, n),
90                        body: body_expr,
91                    }
92                }
93                QuantifierKind::AtLeast(_) | QuantifierKind::AtMost(_) => {
94                    ProofExpr::Unsupported("Counting quantifier".into())
95                }
96            }
97        }
98
99        // --- Logical Connectives ---
100        LogicExpr::BinaryOp { left, op, right } => {
101            let l = Box::new(logic_expr_to_proof_expr(left, interner));
102            let r = Box::new(logic_expr_to_proof_expr(right, interner));
103
104            match op {
105                TokenType::And => ProofExpr::And(l, r),
106                TokenType::Or => ProofExpr::Or(l, r),
107                TokenType::If | TokenType::Implies | TokenType::Then => ProofExpr::Implies(l, r),
108                TokenType::Iff => ProofExpr::Iff(l, r),
109                _ => ProofExpr::Unsupported(format!("Binary operator {:?}", op)),
110            }
111        }
112
113        LogicExpr::UnaryOp { op, operand } => {
114            let inner = Box::new(logic_expr_to_proof_expr(operand, interner));
115            match op {
116                TokenType::Not => ProofExpr::Not(inner),
117                _ => ProofExpr::Unsupported(format!("Unary operator {:?}", op)),
118            }
119        }
120
121        // --- Modal Logic ---
122        LogicExpr::Modal { vector, operand } => {
123            let body = Box::new(logic_expr_to_proof_expr(operand, interner));
124            let domain = match vector.domain {
125                ModalDomain::Alethic => "Alethic",
126                ModalDomain::Deontic => "Deontic",
127                ModalDomain::Temporal => "Temporal",
128            };
129            let flavor = match vector.flavor {
130                ModalFlavor::Root => "Root",
131                ModalFlavor::Epistemic => "Epistemic",
132            };
133            ProofExpr::Modal {
134                domain: domain.to_string(),
135                force: vector.force,
136                flavor: flavor.to_string(),
137                body,
138            }
139        }
140
141        // --- Temporal Logic ---
142        LogicExpr::Temporal { operator, body } => {
143            let body_expr = Box::new(logic_expr_to_proof_expr(body, interner));
144            let op_name = match operator {
145                TemporalOperator::Past => "Past",
146                TemporalOperator::Future => "Future",
147                TemporalOperator::Always => "Always",
148                TemporalOperator::Eventually
149                | TemporalOperator::BoundedEventually(_) => "Eventually",
150                TemporalOperator::Next => "Next",
151            };
152            ProofExpr::Temporal {
153                operator: op_name.to_string(),
154                body: body_expr,
155            }
156        }
157
158        LogicExpr::TemporalBinary { operator, left, right } => ProofExpr::TemporalBinary {
159            operator: format!("{:?}", operator),
160            left: Box::new(logic_expr_to_proof_expr(left, interner)),
161            right: Box::new(logic_expr_to_proof_expr(right, interner)),
162        },
163
164        // --- Lambda Calculus ---
165        LogicExpr::Lambda { variable, body } => ProofExpr::Lambda {
166            variable: interner.resolve(*variable).to_string(),
167            body: Box::new(logic_expr_to_proof_expr(body, interner)),
168        },
169
170        LogicExpr::App { function, argument } => ProofExpr::App(
171            Box::new(logic_expr_to_proof_expr(function, interner)),
172            Box::new(logic_expr_to_proof_expr(argument, interner)),
173        ),
174
175        // --- Event Semantics ---
176        LogicExpr::NeoEvent(data) => {
177            let roles: Vec<(String, ProofTerm)> = data
178                .roles
179                .iter()
180                .map(|(role, term)| {
181                    let role_name = match role {
182                        ThematicRole::Agent => "Agent",
183                        ThematicRole::Patient => "Patient",
184                        ThematicRole::Theme => "Theme",
185                        ThematicRole::Recipient => "Recipient",
186                        ThematicRole::Goal => "Goal",
187                        ThematicRole::Source => "Source",
188                        ThematicRole::Instrument => "Instrument",
189                        ThematicRole::Location => "Location",
190                        ThematicRole::Time => "Time",
191                        ThematicRole::Manner => "Manner",
192                    };
193                    (role_name.to_string(), term_to_proof_term(term, interner))
194                })
195                .collect();
196
197            ProofExpr::NeoEvent {
198                event_var: interner.resolve(data.event_var).to_string(),
199                verb: interner.resolve(data.verb).to_string(),
200                roles,
201            }
202        }
203
204        // --- Counterfactual ---
205        LogicExpr::Counterfactual {
206            antecedent,
207            consequent,
208        } => {
209            // Counterfactuals become implications in classical logic
210            ProofExpr::Implies(
211                Box::new(logic_expr_to_proof_expr(antecedent, interner)),
212                Box::new(logic_expr_to_proof_expr(consequent, interner)),
213            )
214        }
215
216        // --- Unsupported constructs (return Unsupported variant) ---
217        LogicExpr::Categorical(_) => ProofExpr::Unsupported("Categorical (legacy)".into()),
218        LogicExpr::Relation(_) => ProofExpr::Unsupported("Relation (legacy)".into()),
219        LogicExpr::Metaphor { .. } => ProofExpr::Unsupported("Metaphor".into()),
220        LogicExpr::Question { .. } => ProofExpr::Unsupported("Question".into()),
221        LogicExpr::YesNoQuestion { .. } => ProofExpr::Unsupported("YesNoQuestion".into()),
222        LogicExpr::Intensional { .. } => ProofExpr::Unsupported("Intensional".into()),
223        LogicExpr::Event { .. } => ProofExpr::Unsupported("Event (legacy)".into()),
224        LogicExpr::Imperative { .. } => ProofExpr::Unsupported("Imperative".into()),
225        LogicExpr::SpeechAct { .. } => ProofExpr::Unsupported("SpeechAct".into()),
226        LogicExpr::Causal { .. } => ProofExpr::Unsupported("Causal".into()),
227        LogicExpr::Comparative { .. } => ProofExpr::Unsupported("Comparative".into()),
228        LogicExpr::Superlative { .. } => ProofExpr::Unsupported("Superlative".into()),
229        LogicExpr::Scopal { .. } => ProofExpr::Unsupported("Scopal".into()),
230        LogicExpr::Control { .. } => ProofExpr::Unsupported("Control".into()),
231        LogicExpr::Presupposition { .. } => ProofExpr::Unsupported("Presupposition".into()),
232        LogicExpr::Focus { .. } => ProofExpr::Unsupported("Focus".into()),
233        LogicExpr::TemporalAnchor { .. } => ProofExpr::Unsupported("TemporalAnchor".into()),
234        LogicExpr::Distributive { .. } => ProofExpr::Unsupported("Distributive".into()),
235        LogicExpr::GroupQuantifier { .. } => ProofExpr::Unsupported("GroupQuantifier".into()),
236        // Aspectual wrappers (Imperfective, Perfective, etc.) are transparent to proof.
237        // "John runs" -> Aspectual(Imperfective, ∃e(Run(e) ∧ Agent(e, John)))
238        // We pass through to the inner event structure.
239        LogicExpr::Aspectual { body, .. } => logic_expr_to_proof_expr(body, interner),
240        LogicExpr::Voice { .. } => ProofExpr::Unsupported("Voice".into()),
241    }
242}
243
244/// Convert a Term to ProofTerm.
245pub fn term_to_proof_term<'a>(term: &Term<'a>, interner: &Interner) -> ProofTerm {
246    match term {
247        Term::Constant(s) => ProofTerm::Constant(interner.resolve(*s).to_string()),
248
249        Term::Variable(s) => ProofTerm::Variable(interner.resolve(*s).to_string()),
250
251        Term::Function(name, args) => ProofTerm::Function(
252            interner.resolve(*name).to_string(),
253            args.iter().map(|t| term_to_proof_term(t, interner)).collect(),
254        ),
255
256        Term::Group(terms) => {
257            ProofTerm::Group(terms.iter().map(|t| term_to_proof_term(t, interner)).collect())
258        }
259
260        Term::Possessed { possessor, possessed } => {
261            // Convert possession to function application: has(possessor, possessed)
262            ProofTerm::Function(
263                "has".to_string(),
264                vec![
265                    term_to_proof_term(possessor, interner),
266                    ProofTerm::Constant(interner.resolve(*possessed).to_string()),
267                ],
268            )
269        }
270
271        Term::Sigma(s) => {
272            // Sigma variables become regular variables
273            ProofTerm::Variable(interner.resolve(*s).to_string())
274        }
275
276        Term::Intension(s) => {
277            // Intensions become constants with ^ prefix
278            ProofTerm::Constant(format!("^{}", interner.resolve(*s)))
279        }
280
281        Term::Proposition(expr) => {
282            // Embedded propositions - convert recursively but wrap as constant
283            // This is a simplification; full handling would need reification
284            let proof_expr = logic_expr_to_proof_expr(expr, interner);
285            ProofTerm::Constant(format!("[{}]", proof_expr))
286        }
287
288        Term::Value { kind, unit, .. } => {
289            // Convert numeric values to constants
290            use crate::ast::logic::NumberKind;
291            match kind {
292                NumberKind::Integer(n) => {
293                    if let Some(u) = unit {
294                        ProofTerm::Constant(format!("{}{}", n, interner.resolve(*u)))
295                    } else {
296                        ProofTerm::Constant(n.to_string())
297                    }
298                }
299                NumberKind::Real(f) => {
300                    if let Some(u) = unit {
301                        ProofTerm::Constant(format!("{}{}", f, interner.resolve(*u)))
302                    } else {
303                        ProofTerm::Constant(f.to_string())
304                    }
305                }
306                NumberKind::Symbolic(s) => ProofTerm::Constant(interner.resolve(*s).to_string()),
307            }
308        }
309    }
310}
311
312#[cfg(test)]
313mod tests {
314    use super::*;
315    use crate::arena::Arena;
316
317    #[test]
318    fn test_convert_predicate() {
319        let mut interner = Interner::new();
320        let name = interner.intern("Man");
321        let arg = interner.intern("socrates");
322
323        let arena: Arena<Term> = Arena::new();
324        let args = arena.alloc_slice([Term::Constant(arg)]);
325
326        let expr = LogicExpr::Predicate {
327            name,
328            args,
329            world: None,
330        };
331
332        let result = logic_expr_to_proof_expr(&expr, &interner);
333
334        match result {
335            ProofExpr::Predicate { name, args, world } => {
336                // Predicate names are normalized to lowercase
337                assert_eq!(name, "man");
338                assert_eq!(args.len(), 1);
339                // Terms (constants) preserve their case
340                assert!(matches!(&args[0], ProofTerm::Constant(s) if s == "socrates"));
341                assert!(world.is_none());
342            }
343            _ => panic!("Expected Predicate, got {:?}", result),
344        }
345    }
346
347    #[test]
348    fn test_convert_universal() {
349        let mut interner = Interner::new();
350        let var = interner.intern("x");
351        let pred = interner.intern("P");
352
353        let arena: Arena<LogicExpr> = Arena::new();
354        let term_arena: Arena<Term> = Arena::new();
355
356        let body = arena.alloc(LogicExpr::Predicate {
357            name: pred,
358            args: term_arena.alloc_slice([Term::Variable(var)]),
359            world: None,
360        });
361
362        let expr = LogicExpr::Quantifier {
363            kind: QuantifierKind::Universal,
364            variable: var,
365            body,
366            island_id: 0,
367        };
368
369        let result = logic_expr_to_proof_expr(&expr, &interner);
370
371        match result {
372            ProofExpr::ForAll { variable, body } => {
373                assert_eq!(variable, "x");
374                assert!(matches!(*body, ProofExpr::Predicate { .. }));
375            }
376            _ => panic!("Expected ForAll, got {:?}", result),
377        }
378    }
379
380    #[test]
381    fn test_convert_implication() {
382        let mut interner = Interner::new();
383        let p = interner.intern("P");
384        let q = interner.intern("Q");
385
386        let arena: Arena<LogicExpr> = Arena::new();
387
388        let left = arena.alloc(LogicExpr::Atom(p));
389        let right = arena.alloc(LogicExpr::Atom(q));
390
391        let expr = LogicExpr::BinaryOp {
392            left,
393            op: TokenType::If,
394            right,
395        };
396
397        let result = logic_expr_to_proof_expr(&expr, &interner);
398
399        match result {
400            ProofExpr::Implies(l, r) => {
401                assert!(matches!(*l, ProofExpr::Atom(ref s) if s == "P"));
402                assert!(matches!(*r, ProofExpr::Atom(ref s) if s == "Q"));
403            }
404            _ => panic!("Expected Implies, got {:?}", result),
405        }
406    }
407}