logicaffeine_language/
compile.rs

1//! # Compilation API
2//!
3//! This module provides the public entry points for natural language to first-order
4//! logic translation.
5//!
6//! ## Compilation Functions
7//!
8//! | Function | Use Case |
9//! |----------|----------|
10//! | [`compile`] | Single sentence, Unicode output |
11//! | [`compile_simple`] | Single sentence, ASCII output |
12//! | [`compile_kripke`] | Modal logic with world quantification |
13//! | [`compile_with_discourse`] | Multi-sentence with anaphora resolution |
14//! | [`compile_forest`] | Ambiguous sentences, all readings |
15//! | [`compile_all_scopes`] | All quantifier scope permutations |
16//! | [`compile_discourse`] | Multi-sentence with temporal ordering |
17//! | [`compile_theorem`] | Theorem proving with backward chaining |
18//!
19//! ## Example
20//!
21//! ```rust
22//! use logicaffeine_language::{compile, compile_forest};
23//!
24//! // Simple compilation
25//! let fol = compile("John loves Mary.").unwrap();
26//! assert!(fol.contains("Love"));
27//!
28//! // Handle ambiguity
29//! let readings = compile_forest("Every woman loves a man.");
30//! assert!(readings.len() >= 1); // Surface and possibly inverse scope
31//! ```
32
33use crate::{
34    analysis, Arena, CompileOptions, drs, Interner, lambda, lexicon, Lexer, mwe,
35    OutputFormat, Parser, pragmatics, semantics, SymbolRegistry, ParseError, token,
36    arena_ctx::AstContext,
37    parser::{NegativeScopeMode, ModalPreference, QuantifierParsing},
38};
39
40/// Maximum number of readings in a parse forest.
41/// Prevents exponential blowup from ambiguous sentences.
42pub const MAX_FOREST_READINGS: usize = 12;
43
44/// Compile natural language input to first-order logic with default options.
45pub fn compile(input: &str) -> Result<String, ParseError> {
46    compile_with_options(input, CompileOptions::default())
47}
48
49/// Compile with simple FOL format.
50pub fn compile_simple(input: &str) -> Result<String, ParseError> {
51    compile_with_options(input, CompileOptions {
52        format: OutputFormat::SimpleFOL,
53    })
54}
55
56/// Compile with Kripke semantics lowering.
57/// Modal operators are transformed into explicit possible world quantification.
58pub fn compile_kripke(input: &str) -> Result<String, ParseError> {
59    compile_with_options(input, CompileOptions {
60        format: OutputFormat::Kripke,
61    })
62}
63
64/// Compile to Kripke-lowered FOL and pass the AST to a callback.
65///
66/// The callback receives the Kripke-lowered LogicExpr and the Interner
67/// for symbol resolution. This avoids lifetime issues with arena-allocated ASTs.
68pub fn compile_kripke_with<F, R>(input: &str, f: F) -> Result<R, ParseError>
69where
70    F: FnOnce(&crate::ast::logic::LogicExpr<'_>, &Interner) -> R,
71{
72    if input.trim().is_empty() {
73        return Err(ParseError {
74            kind: crate::error::ParseErrorKind::Custom("Empty input".to_string()),
75            span: crate::token::Span { start: 0, end: 0 },
76        });
77    }
78    let mut interner = Interner::new();
79    let mut lexer = Lexer::new(input, &mut interner);
80    let tokens = lexer.tokenize();
81
82    let mwe_trie = mwe::build_mwe_trie();
83    let tokens = mwe::apply_mwe_pipeline(tokens, &mwe_trie, &mut interner);
84
85    let type_registry = {
86        let mut discovery = analysis::DiscoveryPass::new(&tokens, &mut interner);
87        discovery.run()
88    };
89
90    let expr_arena = Arena::new();
91    let term_arena = Arena::new();
92    let np_arena = Arena::new();
93    let sym_arena = Arena::new();
94    let role_arena = Arena::new();
95    let pp_arena = Arena::new();
96
97    let ctx = AstContext::new(
98        &expr_arena,
99        &term_arena,
100        &np_arena,
101        &sym_arena,
102        &role_arena,
103        &pp_arena,
104    );
105
106    let mut world_state = drs::WorldState::new();
107    let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ctx, type_registry);
108    let ast = parser.parse()?;
109    let ast = semantics::apply_axioms(ast, ctx.exprs, ctx.terms, &mut interner);
110    let ast = semantics::apply_kripke_lowering(ast, ctx.exprs, ctx.terms, &mut interner);
111
112    Ok(f(ast, &interner))
113}
114
115/// Compile natural language input to first-order logic with specified options.
116pub fn compile_with_options(input: &str, options: CompileOptions) -> Result<String, ParseError> {
117    if input.trim().is_empty() {
118        return Err(ParseError {
119            kind: crate::error::ParseErrorKind::Custom("Empty input".to_string()),
120            span: crate::token::Span { start: 0, end: 0 },
121        });
122    }
123    let mut interner = Interner::new();
124    let mut lexer = Lexer::new(input, &mut interner);
125    let tokens = lexer.tokenize();
126
127    // Apply MWE collapsing
128    let mwe_trie = mwe::build_mwe_trie();
129    let tokens = mwe::apply_mwe_pipeline(tokens, &mwe_trie, &mut interner);
130
131    // Pass 1: Discovery - scan for type definitions
132    let type_registry = {
133        let mut discovery = analysis::DiscoveryPass::new(&tokens, &mut interner);
134        discovery.run()
135    };
136
137    let expr_arena = Arena::new();
138    let term_arena = Arena::new();
139    let np_arena = Arena::new();
140    let sym_arena = Arena::new();
141    let role_arena = Arena::new();
142    let pp_arena = Arena::new();
143
144    let ctx = AstContext::new(
145        &expr_arena,
146        &term_arena,
147        &np_arena,
148        &sym_arena,
149        &role_arena,
150        &pp_arena,
151    );
152
153    // Pass 2: Parse with type context
154    let mut world_state = drs::WorldState::new();
155    let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ctx, type_registry);
156    let ast = parser.parse()?;
157    let ast = semantics::apply_axioms(ast, ctx.exprs, ctx.terms, &mut interner);
158
159    // Apply Kripke lowering for Kripke format (before pragmatics to preserve modal structure)
160    let ast = if options.format == OutputFormat::Kripke {
161        semantics::apply_kripke_lowering(ast, ctx.exprs, ctx.terms, &mut interner)
162    } else {
163        ast
164    };
165
166    let ast = pragmatics::apply_pragmatics(ast, ctx.exprs, &interner);
167    let mut registry = SymbolRegistry::new();
168    // Use transpile_discourse to format multiple sentences as numbered formulas
169    let main_output = ast.transpile_discourse(&mut registry, &interner, options.format);
170
171    // Append Reichenbach temporal constraints
172    let constraints = world_state.time_constraints();
173    if constraints.is_empty() {
174        Ok(main_output)
175    } else {
176        let constraint_strs: Vec<String> = constraints.iter().map(|c| {
177            match c.relation {
178                drs::TimeRelation::Precedes => format!("Precedes({}, {})", c.left, c.right),
179                drs::TimeRelation::Equals => format!("{}={}", c.left, c.right),
180            }
181        }).collect();
182        Ok(format!("{} ∧ {}", main_output, constraint_strs.join(" ∧ ")))
183    }
184}
185
186/// Compile with shared WorldState for cross-sentence discourse.
187pub fn compile_with_world_state(input: &str, world_state: &mut drs::WorldState) -> Result<String, ParseError> {
188    compile_with_world_state_options(input, world_state, CompileOptions::default())
189}
190
191/// Compile with shared WorldState and options.
192pub fn compile_with_world_state_options(
193    input: &str,
194    world_state: &mut drs::WorldState,
195    options: CompileOptions,
196) -> Result<String, ParseError> {
197    let mut interner = Interner::new();
198    compile_with_world_state_interner_options(input, world_state, &mut interner, options)
199}
200
201/// Compile with shared WorldState AND Interner for proper cross-sentence discourse.
202/// Use this when you need pronouns to resolve across multiple sentences.
203pub fn compile_with_discourse(
204    input: &str,
205    world_state: &mut drs::WorldState,
206    interner: &mut Interner,
207) -> Result<String, ParseError> {
208    compile_with_world_state_interner_options(input, world_state, interner, CompileOptions::default())
209}
210
211/// Compile with full control over WorldState, Interner, and options.
212pub fn compile_with_world_state_interner_options(
213    input: &str,
214    world_state: &mut drs::WorldState,
215    interner: &mut Interner,
216    options: CompileOptions,
217) -> Result<String, ParseError> {
218    let mut lexer = Lexer::new(input, interner);
219    let tokens = lexer.tokenize();
220
221    // Apply MWE collapsing
222    let mwe_trie = mwe::build_mwe_trie();
223    let tokens = mwe::apply_mwe_pipeline(tokens, &mwe_trie, interner);
224
225    // Pass 1: Discovery
226    let type_registry = {
227        let mut discovery = analysis::DiscoveryPass::new(&tokens, interner);
228        discovery.run()
229    };
230
231    let expr_arena = Arena::new();
232    let term_arena = Arena::new();
233    let np_arena = Arena::new();
234    let sym_arena = Arena::new();
235    let role_arena = Arena::new();
236    let pp_arena = Arena::new();
237
238    let ctx = AstContext::new(
239        &expr_arena,
240        &term_arena,
241        &np_arena,
242        &sym_arena,
243        &role_arena,
244        &pp_arena,
245    );
246
247    let mut parser = Parser::new(tokens, world_state, interner, ctx, type_registry);
248    // Swap DRS from WorldState into Parser at start
249    parser.swap_drs_with_world_state();
250    let ast = parser.parse()?;
251    // Swap DRS back to WorldState at end
252    parser.swap_drs_with_world_state();
253    let ast = semantics::apply_axioms(ast, ctx.exprs, ctx.terms, interner);
254
255    // Mark sentence boundary for telescoping support
256    world_state.end_sentence();
257
258    let ast = if options.format == OutputFormat::Kripke {
259        semantics::apply_kripke_lowering(ast, ctx.exprs, ctx.terms, interner)
260    } else {
261        ast
262    };
263
264    let ast = pragmatics::apply_pragmatics(ast, ctx.exprs, interner);
265    let mut registry = SymbolRegistry::new();
266    let main_output = ast.transpile_discourse(&mut registry, interner, options.format);
267
268    let constraints = world_state.time_constraints();
269    if constraints.is_empty() {
270        Ok(main_output)
271    } else {
272        let constraint_strs: Vec<String> = constraints.iter().map(|c| {
273            match c.relation {
274                drs::TimeRelation::Precedes => format!("Precedes({}, {})", c.left, c.right),
275                drs::TimeRelation::Equals => format!("{}={}", c.left, c.right),
276            }
277        }).collect();
278        Ok(format!("{} ∧ {}", main_output, constraint_strs.join(" ∧ ")))
279    }
280}
281
282/// Returns all possible scope readings for a sentence.
283/// For sentences with multiple quantifiers, this returns all permutations.
284/// Example: "Every woman loves a man" returns both:
285///   - Surface: ∀x(Woman(x) → ∃y(Man(y) ∧ Loves(x, y)))
286///   - Inverse: ∃y(Man(y) ∧ ∀x(Woman(x) → Loves(x, y)))
287pub fn compile_all_scopes(input: &str) -> Result<Vec<String>, ParseError> {
288    compile_all_scopes_with_options(input, CompileOptions::default())
289}
290
291/// Returns all scope readings with specified output format.
292pub fn compile_all_scopes_with_options(input: &str, options: CompileOptions) -> Result<Vec<String>, ParseError> {
293    let mut interner = Interner::new();
294    let mut lexer = Lexer::new(input, &mut interner);
295    let tokens = lexer.tokenize();
296
297    // Apply MWE collapsing
298    let mwe_trie = mwe::build_mwe_trie();
299    let tokens = mwe::apply_mwe_pipeline(tokens, &mwe_trie, &mut interner);
300
301    // Pass 1: Discovery - scan for type definitions
302    let type_registry = {
303        let mut discovery = analysis::DiscoveryPass::new(&tokens, &mut interner);
304        discovery.run()
305    };
306
307    let expr_arena = Arena::new();
308    let term_arena = Arena::new();
309    let np_arena = Arena::new();
310    let sym_arena = Arena::new();
311    let role_arena = Arena::new();
312    let pp_arena = Arena::new();
313
314    let ctx = AstContext::new(
315        &expr_arena,
316        &term_arena,
317        &np_arena,
318        &sym_arena,
319        &role_arena,
320        &pp_arena,
321    );
322
323    // Pass 2: Parse with type context
324    let mut world_state = drs::WorldState::new();
325    let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ctx, type_registry);
326    let ast = parser.parse()?;
327
328    let scope_arena = Arena::new();
329    let scope_term_arena = Arena::new();
330    let scopings = lambda::enumerate_scopings(ast, &mut interner, &scope_arena, &scope_term_arena);
331
332    let intensional_arena = Arena::new();
333    let intensional_term_arena = Arena::new();
334    let intensional_role_arena: Arena<(crate::ast::ThematicRole, crate::ast::Term)> = Arena::new();
335
336    let mut results = Vec::new();
337    for scoped_expr in scopings {
338        let intensional_readings = lambda::enumerate_intensional_readings(
339            scoped_expr,
340            &mut interner,
341            &intensional_arena,
342            &intensional_term_arena,
343            &intensional_role_arena,
344        );
345        for reading in intensional_readings {
346            let reading = semantics::apply_axioms(reading, &intensional_arena, &intensional_term_arena, &mut interner);
347            let mut registry = SymbolRegistry::new();
348            results.push(reading.transpile(&mut registry, &interner, options.format));
349        }
350    }
351
352    Ok(results)
353}
354
355// ═══════════════════════════════════════════════════════════════════
356// Parse Forest Compilation (Ambiguity Resolution)
357// ═══════════════════════════════════════════════════════════════════
358
359/// Compile natural language input, producing all valid parse readings.
360/// Handles lexical ambiguity (Noun/Verb) and structural ambiguity (PP attachment).
361pub fn compile_forest(input: &str) -> Vec<String> {
362    compile_forest_with_options(input, CompileOptions::default())
363}
364
365/// Compile natural language input with options, producing all valid parse readings.
366pub fn compile_forest_with_options(input: &str, options: CompileOptions) -> Vec<String> {
367    let mut interner = Interner::new();
368    let mut lexer = Lexer::new(input, &mut interner);
369    let tokens = lexer.tokenize();
370
371    // Apply MWE collapsing
372    let mwe_trie = mwe::build_mwe_trie();
373    let tokens = mwe::apply_mwe_pipeline(tokens, &mwe_trie, &mut interner);
374
375    // Pass 1: Discovery - scan for type definitions
376    let type_registry = {
377        let mut discovery = analysis::DiscoveryPass::new(&tokens, &mut interner);
378        discovery.run()
379    };
380
381    let has_lexical_ambiguity = tokens.iter().any(|t| {
382        matches!(t.kind, token::TokenType::Ambiguous { .. })
383    });
384
385    let has_pp_ambiguity = tokens.iter().any(|t| {
386        if let token::TokenType::Preposition(sym) = &t.kind {
387            let prep = interner.resolve(*sym);
388            prep == "with" || prep == "by" || prep == "for"
389        } else {
390            false
391        }
392    });
393
394    // Detect plurality ambiguity (mixed verb + plural subject)
395    let has_mixed_verb = tokens.iter().any(|t| {
396        if let token::TokenType::Verb { lemma, .. } = &t.kind {
397            Lexer::is_mixed_verb(interner.resolve(*lemma))
398        } else {
399            false
400        }
401    });
402
403    // Detect collective verbs (always require group reading with cardinals)
404    let has_collective_verb = tokens.iter().any(|t| {
405        if let token::TokenType::Verb { lemma, .. } = &t.kind {
406            Lexer::is_collective_verb(interner.resolve(*lemma))
407        } else {
408            false
409        }
410    });
411
412    let has_plural_subject = tokens.iter().any(|t| {
413        matches!(t.kind, token::TokenType::Cardinal(_))
414            || matches!(&t.kind, token::TokenType::Article(def) if matches!(def, lexicon::Definiteness::Definite))
415    });
416
417    let has_plurality_ambiguity = (has_mixed_verb || has_collective_verb) && has_plural_subject;
418
419    // Detect event adjective + agentive noun ambiguity
420    let has_event_adjective_ambiguity = {
421        let mut has_event_adj = false;
422        let mut has_agentive_noun = false;
423        for token in &tokens {
424            if let token::TokenType::Adjective(sym) = &token.kind {
425                if lexicon::is_event_modifier_adjective(interner.resolve(*sym)) {
426                    has_event_adj = true;
427                }
428            }
429            if let token::TokenType::Noun(sym) = &token.kind {
430                if lexicon::lookup_agentive_noun(interner.resolve(*sym)).is_some() {
431                    has_agentive_noun = true;
432                }
433            }
434        }
435        has_event_adj && has_agentive_noun
436    };
437
438    // Detect lexically negative verbs (e.g., "lacks", "miss") for scope ambiguity
439    let has_negative_verb = tokens.iter().any(|t| {
440        if let token::TokenType::Verb { lemma, .. } = &t.kind {
441            lexicon::get_canonical_verb(&interner.resolve(*lemma).to_lowercase())
442                .map(|(_, is_neg)| is_neg)
443                .unwrap_or(false)
444        } else {
445            false
446        }
447    });
448
449    // Detect modal polysemy (may, can, could)
450    let has_may = tokens.iter().any(|t| matches!(t.kind, token::TokenType::May));
451    let has_can = tokens.iter().any(|t| matches!(t.kind, token::TokenType::Can));
452    let has_could = tokens.iter().any(|t| matches!(t.kind, token::TokenType::Could));
453
454    let mut results: Vec<String> = Vec::new();
455
456    // Reading 1: Default mode (verb priority for Ambiguous tokens)
457    {
458        let expr_arena = Arena::new();
459        let term_arena = Arena::new();
460        let np_arena = Arena::new();
461        let sym_arena = Arena::new();
462        let role_arena = Arena::new();
463        let pp_arena = Arena::new();
464
465        let ast_ctx = AstContext::new(
466            &expr_arena,
467            &term_arena,
468            &np_arena,
469            &sym_arena,
470            &role_arena,
471            &pp_arena,
472        );
473
474        let mut world_state = drs::WorldState::new();
475        let mut parser = Parser::new(tokens.clone(), &mut world_state, &mut interner, ast_ctx, type_registry.clone());
476        parser.set_noun_priority_mode(false);
477
478        if let Ok(ast) = parser.parse() {
479            let ast = semantics::apply_axioms(ast, ast_ctx.exprs, ast_ctx.terms, &mut interner);
480            let ast = if options.format == OutputFormat::Kripke {
481                semantics::apply_kripke_lowering(ast, ast_ctx.exprs, ast_ctx.terms, &mut interner)
482            } else {
483                ast
484            };
485            let mut registry = SymbolRegistry::new();
486            results.push(ast.transpile_discourse(&mut registry, &interner, options.format));
487        }
488    }
489
490    // Reading 2: Noun priority mode (for lexical ambiguity)
491    if has_lexical_ambiguity {
492        let expr_arena = Arena::new();
493        let term_arena = Arena::new();
494        let np_arena = Arena::new();
495        let sym_arena = Arena::new();
496        let role_arena = Arena::new();
497        let pp_arena = Arena::new();
498
499        let ast_ctx = AstContext::new(
500            &expr_arena,
501            &term_arena,
502            &np_arena,
503            &sym_arena,
504            &role_arena,
505            &pp_arena,
506        );
507
508        let mut world_state = drs::WorldState::new();
509        let mut parser = Parser::new(tokens.clone(), &mut world_state, &mut interner, ast_ctx, type_registry.clone());
510        parser.set_noun_priority_mode(true);
511
512        if let Ok(ast) = parser.parse() {
513            let ast = semantics::apply_axioms(ast, ast_ctx.exprs, ast_ctx.terms, &mut interner);
514            let ast = if options.format == OutputFormat::Kripke {
515                semantics::apply_kripke_lowering(ast, ast_ctx.exprs, ast_ctx.terms, &mut interner)
516            } else {
517                ast
518            };
519            let mut registry = SymbolRegistry::new();
520            let reading = ast.transpile_discourse(&mut registry, &interner, options.format);
521            if !results.contains(&reading) {
522                results.push(reading);
523            }
524        }
525    }
526
527    // Reading 3: PP attachment mode (for structural ambiguity)
528    if has_pp_ambiguity {
529        let expr_arena = Arena::new();
530        let term_arena = Arena::new();
531        let np_arena = Arena::new();
532        let sym_arena = Arena::new();
533        let role_arena = Arena::new();
534        let pp_arena = Arena::new();
535
536        let ast_ctx = AstContext::new(
537            &expr_arena,
538            &term_arena,
539            &np_arena,
540            &sym_arena,
541            &role_arena,
542            &pp_arena,
543        );
544
545        let mut world_state = drs::WorldState::new();
546        let mut parser = Parser::new(tokens.clone(), &mut world_state, &mut interner, ast_ctx, type_registry.clone());
547        parser.set_pp_attachment_mode(true);
548
549        if let Ok(ast) = parser.parse() {
550            let ast = semantics::apply_axioms(ast, ast_ctx.exprs, ast_ctx.terms, &mut interner);
551            let ast = if options.format == OutputFormat::Kripke {
552                semantics::apply_kripke_lowering(ast, ast_ctx.exprs, ast_ctx.terms, &mut interner)
553            } else {
554                ast
555            };
556            let mut registry = SymbolRegistry::new();
557            let reading = ast.transpile_discourse(&mut registry, &interner, options.format);
558            if !results.contains(&reading) {
559                results.push(reading);
560            }
561        }
562    }
563
564    // Reading 4: Collective mode (for plurality ambiguity with mixed verbs)
565    if has_plurality_ambiguity {
566        let expr_arena = Arena::new();
567        let term_arena = Arena::new();
568        let np_arena = Arena::new();
569        let sym_arena = Arena::new();
570        let role_arena = Arena::new();
571        let pp_arena = Arena::new();
572
573        let ast_ctx = AstContext::new(
574            &expr_arena,
575            &term_arena,
576            &np_arena,
577            &sym_arena,
578            &role_arena,
579            &pp_arena,
580        );
581
582        let mut world_state = drs::WorldState::new();
583        let mut parser = Parser::new(tokens.clone(), &mut world_state, &mut interner, ast_ctx, type_registry.clone());
584        parser.set_collective_mode(true);
585
586        if let Ok(ast) = parser.parse() {
587            if let Ok(transformed) = parser.transform_cardinal_to_group(ast) {
588                let transformed = semantics::apply_axioms(transformed, ast_ctx.exprs, ast_ctx.terms, &mut interner);
589                let mut registry = SymbolRegistry::new();
590                let reading = transformed.transpile(&mut registry, &interner, options.format);
591                if !results.contains(&reading) {
592                    results.push(reading);
593                }
594            }
595        }
596    }
597
598    // Reading 5: Event adjective mode (for event-modifying adjectives with agentive nouns)
599    if has_event_adjective_ambiguity {
600        let expr_arena = Arena::new();
601        let term_arena = Arena::new();
602        let np_arena = Arena::new();
603        let sym_arena = Arena::new();
604        let role_arena = Arena::new();
605        let pp_arena = Arena::new();
606
607        let ast_ctx = AstContext::new(
608            &expr_arena,
609            &term_arena,
610            &np_arena,
611            &sym_arena,
612            &role_arena,
613            &pp_arena,
614        );
615
616        let mut world_state = drs::WorldState::new();
617        let mut parser = Parser::new(tokens.clone(), &mut world_state, &mut interner, ast_ctx, type_registry.clone());
618        parser.set_event_reading_mode(true);
619
620        if let Ok(ast) = parser.parse() {
621            let ast = semantics::apply_axioms(ast, ast_ctx.exprs, ast_ctx.terms, &mut interner);
622            let ast = if options.format == OutputFormat::Kripke {
623                semantics::apply_kripke_lowering(ast, ast_ctx.exprs, ast_ctx.terms, &mut interner)
624            } else {
625                ast
626            };
627            let mut registry = SymbolRegistry::new();
628            let reading = ast.transpile_discourse(&mut registry, &interner, options.format);
629            if !results.contains(&reading) {
630                results.push(reading);
631            }
632        }
633    }
634
635    // Reading 6: Wide scope negation mode (for lexically negative verbs like "lacks")
636    if has_negative_verb {
637        let expr_arena = Arena::new();
638        let term_arena = Arena::new();
639        let np_arena = Arena::new();
640        let sym_arena = Arena::new();
641        let role_arena = Arena::new();
642        let pp_arena = Arena::new();
643
644        let ast_ctx = AstContext::new(
645            &expr_arena,
646            &term_arena,
647            &np_arena,
648            &sym_arena,
649            &role_arena,
650            &pp_arena,
651        );
652
653        let mut world_state = drs::WorldState::new();
654        let mut parser = Parser::new(tokens.clone(), &mut world_state, &mut interner, ast_ctx, type_registry.clone());
655        parser.set_negative_scope_mode(NegativeScopeMode::Wide);
656
657        if let Ok(ast) = parser.parse() {
658            let ast = semantics::apply_axioms(ast, ast_ctx.exprs, ast_ctx.terms, &mut interner);
659            let ast = if options.format == OutputFormat::Kripke {
660                semantics::apply_kripke_lowering(ast, ast_ctx.exprs, ast_ctx.terms, &mut interner)
661            } else {
662                ast
663            };
664            let mut registry = SymbolRegistry::new();
665            let reading = ast.transpile_discourse(&mut registry, &interner, options.format);
666            if !results.contains(&reading) {
667                results.push(reading);
668            }
669        }
670    }
671
672    // Reading 7: Epistemic modal preference (May=Possibility, Could=Possibility)
673    if has_may || has_could {
674        let expr_arena = Arena::new();
675        let term_arena = Arena::new();
676        let np_arena = Arena::new();
677        let sym_arena = Arena::new();
678        let role_arena = Arena::new();
679        let pp_arena = Arena::new();
680
681        let ast_ctx = AstContext::new(
682            &expr_arena,
683            &term_arena,
684            &np_arena,
685            &sym_arena,
686            &role_arena,
687            &pp_arena,
688        );
689
690        let mut world_state = drs::WorldState::new();
691        let mut parser = Parser::new(tokens.clone(), &mut world_state, &mut interner, ast_ctx, type_registry.clone());
692        parser.set_modal_preference(ModalPreference::Epistemic);
693
694        if let Ok(ast) = parser.parse() {
695            let ast = semantics::apply_axioms(ast, ast_ctx.exprs, ast_ctx.terms, &mut interner);
696            let ast = if options.format == OutputFormat::Kripke {
697                semantics::apply_kripke_lowering(ast, ast_ctx.exprs, ast_ctx.terms, &mut interner)
698            } else {
699                ast
700            };
701            let mut registry = SymbolRegistry::new();
702            let reading = ast.transpile_discourse(&mut registry, &interner, options.format);
703            if !results.contains(&reading) {
704                results.push(reading);
705            }
706        }
707    }
708
709    // Reading 8: Deontic modal preference (Can=Permission)
710    if has_can {
711        let expr_arena = Arena::new();
712        let term_arena = Arena::new();
713        let np_arena = Arena::new();
714        let sym_arena = Arena::new();
715        let role_arena = Arena::new();
716        let pp_arena = Arena::new();
717
718        let ast_ctx = AstContext::new(
719            &expr_arena,
720            &term_arena,
721            &np_arena,
722            &sym_arena,
723            &role_arena,
724            &pp_arena,
725        );
726
727        let mut world_state = drs::WorldState::new();
728        let mut parser = Parser::new(tokens.clone(), &mut world_state, &mut interner, ast_ctx, type_registry.clone());
729        parser.set_modal_preference(ModalPreference::Deontic);
730
731        if let Ok(ast) = parser.parse() {
732            let ast = semantics::apply_axioms(ast, ast_ctx.exprs, ast_ctx.terms, &mut interner);
733            let ast = if options.format == OutputFormat::Kripke {
734                semantics::apply_kripke_lowering(ast, ast_ctx.exprs, ast_ctx.terms, &mut interner)
735            } else {
736                ast
737            };
738            let mut registry = SymbolRegistry::new();
739            let reading = ast.transpile_discourse(&mut registry, &interner, options.format);
740            if !results.contains(&reading) {
741                results.push(reading);
742            }
743        }
744    }
745
746    // Reading 9: Wide scope negation + Deontic modal preference
747    if has_negative_verb && has_can {
748        let expr_arena = Arena::new();
749        let term_arena = Arena::new();
750        let np_arena = Arena::new();
751        let sym_arena = Arena::new();
752        let role_arena = Arena::new();
753        let pp_arena = Arena::new();
754
755        let ast_ctx = AstContext::new(
756            &expr_arena,
757            &term_arena,
758            &np_arena,
759            &sym_arena,
760            &role_arena,
761            &pp_arena,
762        );
763
764        let mut world_state = drs::WorldState::new();
765        let mut parser = Parser::new(tokens.clone(), &mut world_state, &mut interner, ast_ctx, type_registry);
766        parser.set_negative_scope_mode(NegativeScopeMode::Wide);
767        parser.set_modal_preference(ModalPreference::Deontic);
768
769        if let Ok(ast) = parser.parse() {
770            let ast = semantics::apply_axioms(ast, ast_ctx.exprs, ast_ctx.terms, &mut interner);
771            let ast = if options.format == OutputFormat::Kripke {
772                semantics::apply_kripke_lowering(ast, ast_ctx.exprs, ast_ctx.terms, &mut interner)
773            } else {
774                ast
775            };
776            let mut registry = SymbolRegistry::new();
777            let reading = ast.transpile_discourse(&mut registry, &interner, options.format);
778            if !results.contains(&reading) {
779                results.push(reading);
780            }
781        }
782    }
783
784    // Enforce MAX_FOREST_READINGS limit
785    results.truncate(MAX_FOREST_READINGS);
786
787    results
788}
789
790// ═══════════════════════════════════════════════════════════════════
791// Discourse Compilation
792// ═══════════════════════════════════════════════════════════════════
793
794/// Compile multiple sentences as a discourse, tracking temporal ordering.
795pub fn compile_discourse(sentences: &[&str]) -> Result<String, ParseError> {
796    compile_discourse_with_options(sentences, CompileOptions::default())
797}
798
799/// Compile multiple sentences as a discourse with specified options.
800pub fn compile_discourse_with_options(sentences: &[&str], options: CompileOptions) -> Result<String, ParseError> {
801    let mut interner = Interner::new();
802    let mut world_state = drs::WorldState::new();
803    let mut results = Vec::new();
804    let mut registry = SymbolRegistry::new();
805    let mwe_trie = mwe::build_mwe_trie();
806
807    for sentence in sentences {
808        let event_var_name = world_state.next_event_var();
809        let event_var_symbol = interner.intern(&event_var_name);
810
811        let mut lexer = Lexer::new(sentence, &mut interner);
812        let tokens = lexer.tokenize();
813
814        // Apply MWE collapsing
815        let tokens = mwe::apply_mwe_pipeline(tokens, &mwe_trie, &mut interner);
816
817        // Pass 1: Discovery - scan for type definitions
818        let type_registry = {
819            let mut discovery = analysis::DiscoveryPass::new(&tokens, &mut interner);
820            discovery.run()
821        };
822
823        let expr_arena = Arena::new();
824        let term_arena = Arena::new();
825        let np_arena = Arena::new();
826        let sym_arena = Arena::new();
827        let role_arena = Arena::new();
828        let pp_arena = Arena::new();
829
830        let ast_ctx = AstContext::new(
831            &expr_arena,
832            &term_arena,
833            &np_arena,
834            &sym_arena,
835            &role_arena,
836            &pp_arena,
837        );
838
839        // Pass 2: Parse with WorldState (DRS persists across sentences)
840        let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
841        parser.set_discourse_event_var(event_var_symbol);
842        // Swap DRS from WorldState into Parser at start
843        parser.swap_drs_with_world_state();
844        let ast = parser.parse()?;
845        // Swap DRS back to WorldState at end
846        parser.swap_drs_with_world_state();
847
848        // Mark sentence boundary - collect telescope candidates for cross-sentence anaphora
849        world_state.end_sentence();
850
851        let ast = semantics::apply_axioms(ast, ast_ctx.exprs, ast_ctx.terms, &mut interner);
852        results.push(ast.transpile_discourse(&mut registry, &interner, options.format));
853    }
854
855    let event_history = world_state.event_history();
856    let mut precedes = Vec::new();
857    for i in 0..event_history.len().saturating_sub(1) {
858        precedes.push(format!("Precedes({}, {})", event_history[i], event_history[i + 1]));
859    }
860
861    if precedes.is_empty() {
862        Ok(results.join(" ∧ "))
863    } else {
864        Ok(format!("{} ∧ {}", results.join(" ∧ "), precedes.join(" ∧ ")))
865    }
866}
867
868// ═══════════════════════════════════════════════════════════════════
869// Ambiguity Handling
870// ═══════════════════════════════════════════════════════════════════
871
872/// Compile with PP attachment ambiguity detection.
873/// Returns multiple readings if structural ambiguity exists.
874pub fn compile_ambiguous(input: &str) -> Result<Vec<String>, ParseError> {
875    compile_ambiguous_with_options(input, CompileOptions::default())
876}
877
878/// Compile with PP attachment ambiguity detection and specified options.
879pub fn compile_ambiguous_with_options(input: &str, options: CompileOptions) -> Result<Vec<String>, ParseError> {
880    let mut interner = Interner::new();
881    let mut lexer = Lexer::new(input, &mut interner);
882    let tokens = lexer.tokenize();
883
884    // Apply MWE collapsing
885    let mwe_trie = mwe::build_mwe_trie();
886    let tokens = mwe::apply_mwe_pipeline(tokens, &mwe_trie, &mut interner);
887
888    // Pass 1: Discovery - scan for type definitions
889    let type_registry = {
890        let mut discovery = analysis::DiscoveryPass::new(&tokens, &mut interner);
891        discovery.run()
892    };
893
894    let expr_arena = Arena::new();
895    let term_arena = Arena::new();
896    let np_arena = Arena::new();
897    let sym_arena = Arena::new();
898    let role_arena = Arena::new();
899    let pp_arena = Arena::new();
900
901    let ctx = AstContext::new(
902        &expr_arena,
903        &term_arena,
904        &np_arena,
905        &sym_arena,
906        &role_arena,
907        &pp_arena,
908    );
909
910    // Pass 2: Parse with type context
911    let mut world_state = drs::WorldState::new();
912    let mut parser = Parser::new(tokens.clone(), &mut world_state, &mut interner, ctx, type_registry.clone());
913    let ast = parser.parse()?;
914    let ast = semantics::apply_axioms(ast, ctx.exprs, ctx.terms, &mut interner);
915    let mut registry = SymbolRegistry::new();
916    let reading1 = ast.transpile(&mut registry, &interner, options.format);
917
918    let has_pp_ambiguity = tokens.iter().any(|t| {
919        if let token::TokenType::Preposition(sym) = &t.kind {
920            let prep = interner.resolve(*sym);
921            prep == "with" || prep == "by" || prep == "for"
922        } else {
923            false
924        }
925    });
926
927    if has_pp_ambiguity {
928        let expr_arena2 = Arena::new();
929        let term_arena2 = Arena::new();
930        let np_arena2 = Arena::new();
931        let sym_arena2 = Arena::new();
932        let role_arena2 = Arena::new();
933        let pp_arena2 = Arena::new();
934
935        let ctx2 = AstContext::new(
936            &expr_arena2,
937            &term_arena2,
938            &np_arena2,
939            &sym_arena2,
940            &role_arena2,
941            &pp_arena2,
942        );
943
944        let mut world_state2 = drs::WorldState::new();
945        let mut parser2 = Parser::new(tokens, &mut world_state2, &mut interner, ctx2, type_registry);
946        parser2.set_pp_attachment_mode(true);
947        let ast2 = parser2.parse()?;
948        let ast2 = semantics::apply_axioms(ast2, ctx2.exprs, ctx2.terms, &mut interner);
949        let mut registry2 = SymbolRegistry::new();
950        let reading2 = ast2.transpile(&mut registry2, &interner, options.format);
951
952        if reading1 != reading2 {
953            return Ok(vec![reading1, reading2]);
954        }
955    }
956
957    Ok(vec![reading1])
958}
959
960// ═══════════════════════════════════════════════════════════════════
961// Theorem Compilation
962// ═══════════════════════════════════════════════════════════════════
963
964use crate::ast::{self, Stmt};
965use crate::token::Span;
966use crate::error::ParseErrorKind;
967use logicaffeine_proof::BackwardChainer;
968use crate::proof_convert::logic_expr_to_proof_expr;
969
970/// Compile and prove a theorem block.
971pub fn compile_theorem(input: &str) -> Result<String, ParseError> {
972    let mut interner = Interner::new();
973    let mut lexer = Lexer::new(input, &mut interner);
974    let tokens = lexer.tokenize();
975
976    // Apply MWE collapsing
977    let mwe_trie = mwe::build_mwe_trie();
978    let tokens = mwe::apply_mwe_pipeline(tokens, &mwe_trie, &mut interner);
979
980    // Pass 1: Discovery
981    let type_registry = {
982        let mut discovery = analysis::DiscoveryPass::new(&tokens, &mut interner);
983        discovery.run()
984    };
985
986    let expr_arena = Arena::new();
987    let term_arena = Arena::new();
988    let np_arena = Arena::new();
989    let sym_arena = Arena::new();
990    let role_arena = Arena::new();
991    let pp_arena = Arena::new();
992
993    let ctx = AstContext::new(
994        &expr_arena,
995        &term_arena,
996        &np_arena,
997        &sym_arena,
998        &role_arena,
999        &pp_arena,
1000    );
1001
1002    // Parse as program to get statements including Theorem blocks
1003    let mut world_state = drs::WorldState::new();
1004    let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ctx, type_registry);
1005    let statements = parser.parse_program()?;
1006
1007    // Find the first Theorem statement
1008    let theorem = statements
1009        .iter()
1010        .find_map(|stmt| {
1011            if let Stmt::Theorem(t) = stmt {
1012                Some(t)
1013            } else {
1014                None
1015            }
1016        })
1017        .ok_or_else(|| ParseError {
1018            kind: ParseErrorKind::Custom("No theorem block found in input".to_string()),
1019            span: Span::default(),
1020        })?;
1021
1022    // Convert premises from LogicExpr to ProofExpr
1023    let mut engine = BackwardChainer::new();
1024    for premise in &theorem.premises {
1025        let proof_expr = logic_expr_to_proof_expr(premise, &interner);
1026        engine.add_axiom(proof_expr);
1027    }
1028
1029    // Convert goal from LogicExpr to ProofExpr
1030    let goal = logic_expr_to_proof_expr(theorem.goal, &interner);
1031
1032    // Attempt to prove the goal
1033    match engine.prove(goal.clone()) {
1034        Ok(derivation) => {
1035            Ok(format!(
1036                "Theorem '{}' Proved!\n{}",
1037                theorem.name,
1038                derivation.display_tree()
1039            ))
1040        }
1041        Err(e) => {
1042            // Return error with context about what was attempted
1043            Err(ParseError {
1044                kind: ParseErrorKind::Custom(format!(
1045                    "Theorem '{}' failed.\n  Goal: {}\n  Premises: {}\n  Error: {}",
1046                    theorem.name,
1047                    goal,
1048                    theorem.premises.len(),
1049                    e
1050                )),
1051                span: Span::default(),
1052            })
1053        }
1054    }
1055}
1056
1057#[cfg(test)]
1058mod tests {
1059    use super::*;
1060
1061    #[test]
1062    fn test_compile_simple_sentence() {
1063        let result = compile("John runs.");
1064        assert!(result.is_ok());
1065        let output = result.unwrap();
1066        assert!(output.contains("Run"));
1067        assert!(output.contains("John"));
1068    }
1069
1070    #[test]
1071    fn test_compile_with_unicode_format() {
1072        let options = CompileOptions { format: OutputFormat::Unicode };
1073        let result = compile_with_options("Every dog barks.", options);
1074        assert!(result.is_ok());
1075        let output = result.unwrap();
1076        assert!(output.contains("∀") || output.contains("Forall"));
1077    }
1078
1079    #[test]
1080    fn test_compile_all_scopes() {
1081        let result = compile_all_scopes("Every woman loves a man.");
1082        assert!(result.is_ok());
1083        let readings = result.unwrap();
1084        assert!(readings.len() >= 1);
1085    }
1086}