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