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