Skip to main content

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