logicaffeine_language/parser/
clause.rs

1//! Clause-level parsing: sentences, conditionals, conjunctions, and relative clauses.
2//!
3//! This module handles the top-level sentence structures including:
4//!
5//! - **Simple sentences**: Subject-verb-object patterns
6//! - **Conditionals**: "If P then Q" with DRS scope handling
7//! - **Counterfactuals**: "If P were/had, Q would" (subjunctive)
8//! - **Disjunctions**: "Either P or Q", "P or Q"
9//! - **Conjunctions**: "P and Q"
10//! - **Relative clauses**: "who/that/which" attaching to noun phrases
11//! - **VP ellipsis**: "John ran and Mary did too"
12//!
13//! The [`ClauseParsing`] trait defines the interface implemented by [`Parser`].
14
15use super::modal::ModalParsing;
16use super::noun::NounParsing;
17use super::pragmatics::PragmaticsParsing;
18use super::quantifier::QuantifierParsing;
19use super::question::QuestionParsing;
20use super::verb::LogicVerbParsing;
21use super::{EventTemplate, ParseResult, Parser};
22use crate::ast::{AspectOperator, LogicExpr, NeoEventData, NounPhrase, QuantifierKind, TemporalOperator, Term, ThematicRole};
23use crate::lexer::Lexer;
24use crate::lexicon::Time;
25use crate::drs::{BoxType, Gender, Number};
26use super::ParserMode;
27use crate::error::{ParseError, ParseErrorKind};
28use logicaffeine_base::Symbol;
29use crate::lexicon::Definiteness;
30use crate::token::TokenType;
31
32/// Trait for parsing clause-level structures.
33///
34/// Defines methods for parsing sentences, conditionals, conjunctions,
35/// and other clause-level constructs.
36pub trait ClauseParsing<'a, 'ctx, 'int> {
37    /// Parses a complete sentence, handling imperatives, ellipsis, and questions.
38    fn parse_sentence(&mut self) -> ParseResult<&'a LogicExpr<'a>>;
39    /// Parses "if P then Q" conditionals with DRS scope handling.
40    fn parse_conditional(&mut self) -> ParseResult<&'a LogicExpr<'a>>;
41    /// Parses "either P or Q" exclusive disjunctions.
42    fn parse_either_or(&mut self) -> ParseResult<&'a LogicExpr<'a>>;
43    /// Parses "P or Q" disjunctions.
44    fn parse_disjunction(&mut self) -> ParseResult<&'a LogicExpr<'a>>;
45    /// Parses "P and Q" conjunctions with scope coordination.
46    fn parse_conjunction(&mut self) -> ParseResult<&'a LogicExpr<'a>>;
47    /// Parses "who/that/which" relative clauses attaching to noun phrases.
48    fn parse_relative_clause(&mut self, gap_var: Symbol) -> ParseResult<&'a LogicExpr<'a>>;
49    /// Parses a clause with a gap filled by borrowed verb (for VP coordination).
50    fn parse_gapped_clause(&mut self, borrowed_verb: Symbol) -> ParseResult<&'a LogicExpr<'a>>;
51    /// Parses "if P were/had" counterfactual antecedent (subjunctive).
52    fn parse_counterfactual_antecedent(&mut self) -> ParseResult<&'a LogicExpr<'a>>;
53    /// Parses "Q would" counterfactual consequent.
54    fn parse_counterfactual_consequent(&mut self) -> ParseResult<&'a LogicExpr<'a>>;
55    /// Checks if current token is a wh-word (who, what, which, etc.).
56    fn check_wh_word(&self) -> bool;
57    /// Returns true if parsing a counterfactual context.
58    fn is_counterfactual_context(&self) -> bool;
59    /// Returns true if expression is a complete clause.
60    fn is_complete_clause(&self, expr: &LogicExpr<'a>) -> bool;
61    /// Extracts the main verb from an expression.
62    fn extract_verb_from_expr(&self, expr: &LogicExpr<'a>) -> Option<Symbol>;
63    /// Attempts to parse VP ellipsis ("Mary did too").
64    fn try_parse_ellipsis(&mut self) -> Option<ParseResult<&'a LogicExpr<'a>>>;
65    /// Checks for ellipsis auxiliary (did, does, can, etc.).
66    fn check_ellipsis_auxiliary(&self) -> bool;
67    /// Checks for ellipsis terminator (too, also, as well).
68    fn check_ellipsis_terminator(&self) -> bool;
69}
70
71impl<'a, 'ctx, 'int> ClauseParsing<'a, 'ctx, 'int> for Parser<'a, 'ctx, 'int> {
72    fn parse_sentence(&mut self) -> ParseResult<&'a LogicExpr<'a>> {
73        // In imperative mode, handle Let statements by converting to LogicExpr
74        // This supports declarative parser being called after process_block_headers()
75        // Let x is/= value -> returns the value expression (the test just checks parsing succeeds)
76        if self.mode == ParserMode::Imperative && self.check(&TokenType::Let) {
77            self.advance(); // consume "Let"
78            let _var = self.expect_identifier()?;
79            // Accept "is", "be", "=" as assignment operators
80            if self.check(&TokenType::Is) || self.check(&TokenType::Be) || self.check(&TokenType::Equals) || self.check(&TokenType::Identity) || self.check(&TokenType::Assign) {
81                self.advance(); // consume the operator
82            }
83            // Parse the value and return it (test just checks parsing succeeds)
84            return self.parse_disjunction();
85        }
86
87        // Check for ellipsis pattern: "Mary does too." / "Mary can too."
88        if let Some(result) = self.try_parse_ellipsis() {
89            return result;
90        }
91
92        if self.check_verb() {
93            let verb_pos = self.current;
94            let mut temp_pos = self.current + 1;
95            while temp_pos < self.tokens.len() {
96                if matches!(self.tokens[temp_pos].kind, TokenType::Exclamation) {
97                    self.current = verb_pos;
98                    let verb = self.consume_verb();
99                    while !matches!(self.peek().kind, TokenType::Exclamation | TokenType::EOF) {
100                        self.advance();
101                    }
102                    if self.check(&TokenType::Exclamation) {
103                        self.advance();
104                    }
105                    let addressee = self.interner.intern("addressee");
106                    let action = self.ctx.exprs.alloc(LogicExpr::Predicate {
107                        name: verb,
108                        args: self.ctx.terms.alloc_slice([Term::Variable(addressee)]),
109                        world: None,
110                    });
111                    return Ok(self.ctx.exprs.alloc(LogicExpr::Imperative { action }));
112                }
113                if matches!(self.tokens[temp_pos].kind, TokenType::Period | TokenType::EOF) {
114                    break;
115                }
116                temp_pos += 1;
117            }
118        }
119
120        // "While X, Y" as temporal duration subordinator
121        // Duration semantics: Y holds for the entire interval where X is true.
122        // Lowered as implication checked globally: G(X → Y)
123        if self.check(&TokenType::While) {
124            self.advance(); // consume "While"
125            let condition = self.parse_sentence()?;
126            if self.check(&TokenType::Comma) {
127                self.advance();
128            }
129            let consequent = self.parse_sentence()?;
130            return Ok(self.ctx.exprs.alloc(LogicExpr::BinaryOp {
131                left: condition,
132                op: TokenType::Implies,
133                right: consequent,
134            }));
135        }
136
137        // "When X, Y" as temporal subordinator (before wh-question check)
138        // Disambiguate: subordinator has comma-separated clauses, question does not
139        if self.check(&TokenType::When) {
140            let saved = self.current;
141            let mut found_comma = false;
142            for i in (self.current + 1)..self.tokens.len() {
143                match &self.tokens[i].kind {
144                    TokenType::Comma => { found_comma = true; break; }
145                    TokenType::Period => break,
146                    _ => {}
147                }
148            }
149            if found_comma {
150                self.advance(); // consume "When"
151                let condition = self.parse_sentence()?;
152                if self.check(&TokenType::Comma) {
153                    self.advance();
154                }
155                let consequent = self.parse_sentence()?;
156                return Ok(self.ctx.exprs.alloc(LogicExpr::BinaryOp {
157                    left: condition,
158                    op: TokenType::Implies,
159                    right: consequent,
160                }));
161            }
162            self.current = saved;
163        }
164
165        // "Whenever X, Y" → same as "When X, Y"
166        if self.check_content_word() {
167            let word = self.interner.resolve(self.peek().lexeme).to_string();
168            if word == "Whenever" || word == "whenever" {
169                self.advance(); // consume "Whenever"
170                let condition = self.parse_sentence()?;
171                if self.check(&TokenType::Comma) {
172                    self.advance();
173                }
174                let consequent = self.parse_sentence()?;
175                return Ok(self.ctx.exprs.alloc(LogicExpr::BinaryOp {
176                    left: condition,
177                    op: TokenType::Implies,
178                    right: consequent,
179                }));
180            }
181        }
182
183        if self.check_wh_word() {
184            return self.parse_wh_question();
185        }
186
187        if self.check(&TokenType::Does)
188            || self.check(&TokenType::Do)
189            || self.check(&TokenType::Is)
190            || self.check(&TokenType::Are)
191            || self.check(&TokenType::Was)
192            || self.check(&TokenType::Were)
193            || self.check(&TokenType::Would)
194            || self.check(&TokenType::Could)
195            || self.check(&TokenType::Can)
196        {
197            return self.parse_yes_no_question();
198        }
199
200        if self.match_token(&[TokenType::If]) {
201            return self.parse_conditional();
202        }
203
204        // Handle "Either X or Y" disjunction
205        // Special case: "Either NP1 or NP2 is/are PRED" should apply PRED to both
206        if self.match_token(&[TokenType::Either]) {
207            return self.parse_either_or();
208        }
209
210        if self.check_modal() {
211            self.advance();
212            return self.parse_modal();
213        }
214
215        if self.match_token(&[TokenType::Not]) {
216            self.negative_depth += 1;
217            let inner = self.parse_sentence()?;
218            self.negative_depth -= 1;
219            return Ok(self.ctx.exprs.alloc(LogicExpr::UnaryOp {
220                op: TokenType::Not,
221                operand: inner,
222            }));
223        }
224
225        // "not both every request is valid and every grant is valid" → ¬(X ∧ Y)
226        // Only triggers for clausal conjunction: "both" + quantifier/determiner
227        // NOT for conjoined NP: "both Socrates and Plato are men"
228        if self.check(&TokenType::Both) {
229            // Peek at token after "both" — if it's a quantifier, this is clausal
230            let next_is_clausal = if self.current + 1 < self.tokens.len() {
231                matches!(self.tokens[self.current + 1].kind,
232                    TokenType::All | TokenType::No | TokenType::Some | TokenType::Any
233                    | TokenType::Most | TokenType::Few | TokenType::Many
234                    | TokenType::Cardinal(_) | TokenType::AtLeast(_) | TokenType::AtMost(_)
235                    | TokenType::Article(_)
236                )
237            } else {
238                false
239            };
240            if next_is_clausal {
241                self.advance(); // consume "both"
242                let first = self.parse_atom()?;
243                if self.check(&TokenType::And) {
244                    self.advance(); // consume "and"
245                }
246                let second = self.parse_atom()?;
247                return Ok(self.ctx.exprs.alloc(LogicExpr::BinaryOp {
248                    left: first,
249                    op: TokenType::And,
250                    right: second,
251                }));
252            }
253        }
254
255        // Sentence-initial temporal operators for hardware verification:
256        // "Always, P" → Temporal { Always, P }
257        // "Eventually, P" → Temporal { Eventually, P }
258        // "Next, P" → Temporal { Next, P }
259        // "Never P" → Temporal { Always, ¬P }
260        {
261            let temporal_op = match &self.peek().kind {
262                TokenType::Adverb(sym) | TokenType::ScopalAdverb(sym) | TokenType::TemporalAdverb(sym) => {
263                    let resolved = self.interner.resolve(*sym).to_string();
264                    match resolved.as_str() {
265                        "Always" => Some(crate::ast::logic::TemporalOperator::Always),
266                        "Eventually" => Some(crate::ast::logic::TemporalOperator::Eventually),
267                        "Next" => Some(crate::ast::logic::TemporalOperator::Next),
268                        _ => None,
269                    }
270                }
271                // Handle "next" as an adjective token (common fallback)
272                TokenType::Adjective(sym) => {
273                    let resolved = self.interner.resolve(*sym).to_string();
274                    if resolved == "Next" {
275                        Some(crate::ast::logic::TemporalOperator::Next)
276                    } else {
277                        None
278                    }
279                }
280                _ => None,
281            };
282            if let Some(op) = temporal_op {
283                self.advance(); // consume the token
284                // Optionally consume comma: "Always, P"
285                if self.check(&TokenType::Comma) {
286                    self.advance();
287                }
288                let body = self.parse_sentence()?;
289                return Ok(self.ctx.exprs.alloc(LogicExpr::Temporal {
290                    operator: op,
291                    body,
292                }));
293            }
294        }
295        // "Never P" → G(¬P): Always { Not { P } }
296        if self.check(&TokenType::Never) {
297            self.advance(); // consume "Never"
298            // Optionally consume comma
299            if self.check(&TokenType::Comma) {
300                self.advance();
301            }
302            let body = self.parse_sentence()?;
303            let negated = self.ctx.exprs.alloc(LogicExpr::UnaryOp {
304                op: TokenType::Not,
305                operand: body,
306            });
307            return Ok(self.ctx.exprs.alloc(LogicExpr::Temporal {
308                operator: crate::ast::logic::TemporalOperator::Always,
309                body: negated,
310            }));
311        }
312
313        // "After X, Y" → X → Y (temporal sequence)
314        // "Before X, Y" → Y → X
315        // Handles both "After reset is deasserted, ..." (full clause)
316        // and "After request, ..." (bare signal/event noun)
317        if self.check_preposition_is("after") || self.check_preposition_is("After") {
318            self.advance(); // consume "after"
319
320            // Check for bare noun/signal + comma pattern: "After request, ..."
321            // Also handle Performative tokens (e.g., "request" when not after determiner)
322            let is_bare_noun_comma = self.current + 1 < self.tokens.len()
323                && matches!(self.tokens[self.current + 1].kind, TokenType::Comma)
324                && (self.check_content_word()
325                    || matches!(self.peek().kind, TokenType::Performative(_)));
326            let antecedent = if is_bare_noun_comma {
327                let noun = match self.advance().kind.clone() {
328                    TokenType::Performative(s) => s,
329                    TokenType::Noun(s) | TokenType::Adjective(s) | TokenType::ProperName(s) => s,
330                    TokenType::Verb { lemma, .. } => lemma,
331                    _ => return Err(crate::error::ParseError {
332                        kind: crate::error::ParseErrorKind::ExpectedContentWord { found: self.peek().kind.clone() },
333                        span: self.current_span(),
334                    }),
335                };
336                self.ctx.exprs.alloc(LogicExpr::Atom(noun))
337            } else {
338                self.parse_sentence()?
339            };
340
341            if self.check(&TokenType::Comma) {
342                self.advance();
343            }
344            let consequent = self.parse_sentence()?;
345            let consequent = self.try_wrap_bounded_delay(consequent);
346            return Ok(self.ctx.exprs.alloc(LogicExpr::BinaryOp {
347                left: antecedent,
348                op: TokenType::Implies,
349                right: consequent,
350            }));
351        }
352        if self.check_preposition_is("before") || self.check_preposition_is("Before") {
353            self.advance(); // consume "before"
354            let first_clause = self.parse_sentence()?;
355            if self.check(&TokenType::Comma) {
356                self.advance();
357            }
358            let second_clause = self.parse_sentence()?;
359            return Ok(self.ctx.exprs.alloc(LogicExpr::BinaryOp {
360                left: second_clause,
361                op: TokenType::Implies,
362                right: first_clause,
363            }));
364        }
365
366        self.parse_disjunction()
367    }
368
369    fn check_wh_word(&self) -> bool {
370        if matches!(
371            self.peek().kind,
372            TokenType::Who
373                | TokenType::What
374                | TokenType::Where
375                | TokenType::When
376                | TokenType::Why
377        ) {
378            return true;
379        }
380        if self.check_preposition() && self.current + 1 < self.tokens.len() {
381            matches!(
382                self.tokens[self.current + 1].kind,
383                TokenType::Who
384                    | TokenType::What
385                    | TokenType::Where
386                    | TokenType::When
387                    | TokenType::Why
388            )
389        } else {
390            false
391        }
392    }
393
394    fn parse_conditional(&mut self) -> ParseResult<&'a LogicExpr<'a>> {
395        let is_counterfactual = self.is_counterfactual_context();
396
397        // Enter DRS antecedent box - indefinites here get universal force
398        self.drs.enter_box(BoxType::ConditionalAntecedent);
399        let mut antecedent = self.parse_counterfactual_antecedent()?;
400
401        // Handle conjunction of clauses in antecedent: "If X is Y and Z is W, ..."
402        while self.check(&TokenType::And) {
403            self.advance(); // consume "and"
404            let second = self.parse_counterfactual_antecedent()?;
405            antecedent = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
406                left: antecedent,
407                op: TokenType::And,
408                right: second,
409            });
410        }
411        self.drs.exit_box();
412
413        if self.check(&TokenType::Comma) {
414            self.advance();
415        }
416
417        if self.check(&TokenType::Then) {
418            self.advance();
419        }
420
421        // Enter DRS consequent box - can access antecedent referents
422        self.drs.enter_box(BoxType::ConditionalConsequent);
423        let consequent = self.parse_counterfactual_consequent()?;
424        self.drs.exit_box();
425
426        // Get DRS referents that need universal quantification
427        let universal_refs = self.drs.get_universal_referents();
428
429        // Build the conditional expression
430        let conditional = if is_counterfactual {
431            self.ctx.exprs.alloc(LogicExpr::Counterfactual {
432                antecedent,
433                consequent,
434            })
435        } else {
436            self.ctx.exprs.alloc(LogicExpr::BinaryOp {
437                left: antecedent,
438                op: TokenType::If,
439                right: consequent,
440            })
441        };
442
443        // Wrap with universal quantifiers for DRS referents
444        let mut result = conditional;
445        for var in universal_refs.into_iter().rev() {
446            result = self.ctx.exprs.alloc(LogicExpr::Quantifier {
447                kind: QuantifierKind::Universal,
448                variable: var,
449                body: result,
450                island_id: self.current_island,
451            });
452        }
453
454        Ok(result)
455    }
456
457    /// Parse "Either NP1 or NP2 is/are PRED" or "Either S1 or S2"
458    ///
459    /// Handles coordination: "Either Alice or Bob is guilty" should become
460    /// guilty(Alice) ∨ guilty(Bob), not Alice ∨ guilty(Bob)
461    fn parse_either_or(&mut self) -> ParseResult<&'a LogicExpr<'a>> {
462        // Save position for potential backtracking
463        let start_pos = self.current;
464
465        // Try to parse as "Either NP1 or NP2 VP"
466        // First, try to parse just a proper name (not a full clause)
467        if let TokenType::ProperName(name1) = self.peek().kind {
468            self.advance(); // consume first proper name
469
470            if self.check(&TokenType::Or) {
471                self.advance(); // consume "or"
472
473                if let TokenType::ProperName(name2) = self.peek().kind {
474                    self.advance(); // consume second proper name
475
476                    // Check for shared predicate: "is/are ADJECTIVE"
477                    let is_copula = matches!(
478                        self.peek().kind,
479                        TokenType::Is | TokenType::Are | TokenType::Was | TokenType::Were
480                    );
481                    if is_copula {
482                        self.advance(); // consume copula
483
484                        // Check for negation: "is not"
485                        let is_negated = self.match_token(&[TokenType::Not]);
486
487                        // Try to get an adjective
488                        if let TokenType::Adjective(adj) = self.peek().kind {
489                            self.advance(); // consume adjective
490
491                            // Create predicate for each NP
492                            let pred1 = self.ctx.exprs.alloc(LogicExpr::Predicate {
493                                name: adj,
494                                args: self.ctx.terms.alloc_slice(vec![
495                                    Term::Constant(name1)
496                                ]),
497                                world: None,
498                            });
499                            let pred2 = self.ctx.exprs.alloc(LogicExpr::Predicate {
500                                name: adj,
501                                args: self.ctx.terms.alloc_slice(vec![
502                                    Term::Constant(name2)
503                                ]),
504                                world: None,
505                            });
506
507                            // Apply negation if needed
508                            let left = if is_negated {
509                                self.ctx.exprs.alloc(LogicExpr::UnaryOp {
510                                    op: TokenType::Not,
511                                    operand: pred1,
512                                })
513                            } else {
514                                pred1
515                            };
516                            let right = if is_negated {
517                                self.ctx.exprs.alloc(LogicExpr::UnaryOp {
518                                    op: TokenType::Not,
519                                    operand: pred2,
520                                })
521                            } else {
522                                pred2
523                            };
524
525                            return Ok(self.ctx.exprs.alloc(LogicExpr::BinaryOp {
526                                left,
527                                op: TokenType::Or,
528                                right,
529                            }));
530                        }
531                    }
532                }
533            }
534
535            // Backtrack if the special case didn't match
536            self.current = start_pos;
537        }
538
539        // Fall back to general disjunction parsing
540        // Enter disjunct box for left side - referents here are inaccessible outward
541        self.drs.enter_box(BoxType::Disjunct);
542        let left = self.parse_conjunction()?;
543        self.drs.exit_box();
544
545        if !self.check(&TokenType::Or) {
546            return Err(ParseError {
547                kind: ParseErrorKind::ExpectedKeyword { keyword: "or".to_string() },
548                span: self.current_span(),
549            });
550        }
551        self.advance(); // consume "or"
552
553        // Enter disjunct box for right side - referents here are also inaccessible outward
554        self.drs.enter_box(BoxType::Disjunct);
555        let right = self.parse_conjunction()?;
556        self.drs.exit_box();
557
558        Ok(self.ctx.exprs.alloc(LogicExpr::BinaryOp {
559            left,
560            op: TokenType::Or,
561            right,
562        }))
563    }
564
565    fn is_counterfactual_context(&self) -> bool {
566        for i in 0..5 {
567            if self.current + i >= self.tokens.len() {
568                break;
569            }
570            let token = &self.tokens[self.current + i];
571            if matches!(token.kind, TokenType::Were | TokenType::Had) {
572                return true;
573            }
574            if matches!(token.kind, TokenType::Comma | TokenType::Period) {
575                break;
576            }
577        }
578        false
579    }
580
581    fn parse_counterfactual_antecedent(&mut self) -> ParseResult<&'a LogicExpr<'a>> {
582        let unknown = self.interner.intern("?");
583        if self.check_content_word() || self.check_pronoun() || self.check_article() {
584            // Weather verb detection: "if it rains" → ∃e(Rain(e))
585            // Must check BEFORE pronoun resolution since "it" would resolve to "?"
586            if self.check_pronoun() {
587                let token = self.peek();
588                let token_text = self.interner.resolve(token.lexeme);
589                if token_text.eq_ignore_ascii_case("it") {
590                    // Look ahead for weather verb: "it rains" or "it is raining"
591                    if self.current + 1 < self.tokens.len() {
592                        // Check for "it + verb" pattern
593                        if let TokenType::Verb { lemma, time, .. } = &self.tokens[self.current + 1].kind {
594                            let lemma_str = self.interner.resolve(*lemma);
595                            if Lexer::is_weather_verb(lemma_str) {
596                                let verb = *lemma;
597                                let verb_time = *time;
598                                self.advance(); // consume "it"
599                                self.advance(); // consume weather verb
600
601                                let event_var = self.get_event_var();
602
603                                // Weather verbs are impersonal - no pronoun resolution needed
604                                // Event var gets universal force from transpiler when suppress_existential=true
605                                let suppress_existential = self.drs.in_conditional_antecedent();
606
607                                let mut result: &'a LogicExpr<'a> = self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
608                                    event_var,
609                                    verb,
610                                    roles: self.ctx.roles.alloc_slice(vec![]),
611                                    modifiers: self.ctx.syms.alloc_slice(vec![]),
612                                    suppress_existential,
613                                    world: None,
614                                })));
615
616                                // Handle coordinated weather verbs: "rains and thunders" or "rains or thunders"
617                                // SHARE the same event_var for all coordinated verbs
618                                while self.check(&TokenType::And) || self.check(&TokenType::Or) {
619                                    let is_disjunction = self.check(&TokenType::Or);
620                                    self.advance(); // consume "and" or "or"
621
622                                    if let TokenType::Verb { lemma: lemma2, .. } = &self.peek().kind.clone() {
623                                        let lemma2_str = self.interner.resolve(*lemma2);
624                                        if Lexer::is_weather_verb(lemma2_str) {
625                                            let verb2 = *lemma2;
626                                            self.advance(); // consume second weather verb
627
628                                            // REUSE same event_var - no new variable, no DRS registration
629                                            let neo_event2 = self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
630                                                event_var,  // Same variable as first weather verb
631                                                verb: verb2,
632                                                roles: self.ctx.roles.alloc_slice(vec![]),
633                                                modifiers: self.ctx.syms.alloc_slice(vec![]),
634                                                suppress_existential,
635                                                world: None,
636                                            })));
637
638                                            let op = if is_disjunction { TokenType::Or } else { TokenType::And };
639                                            result = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
640                                                left: result,
641                                                op,
642                                                right: neo_event2,
643                                            });
644                                        } else {
645                                            break; // Not a weather verb, stop coordination
646                                        }
647                                    } else {
648                                        break;
649                                    }
650                                }
651
652                                return Ok(match verb_time {
653                                    Time::Past => self.ctx.exprs.alloc(LogicExpr::Temporal {
654                                        operator: TemporalOperator::Past,
655                                        body: result,
656                                    }),
657                                    Time::Future => self.ctx.exprs.alloc(LogicExpr::Temporal {
658                                        operator: TemporalOperator::Future,
659                                        body: result,
660                                    }),
661                                    _ => result,
662                                });
663                            }
664                        }
665                        // Check for "it + is/are + verb" pattern: "it is raining"
666                        else if self.current + 2 < self.tokens.len() {
667                            let is_copula = matches!(
668                                self.tokens[self.current + 1].kind,
669                                TokenType::Is | TokenType::Are | TokenType::Was | TokenType::Were
670                            );
671                            if is_copula {
672                                if let TokenType::Verb { lemma, .. } = &self.tokens[self.current + 2].kind {
673                                    let lemma_str = self.interner.resolve(*lemma);
674                                    if Lexer::is_weather_verb(lemma_str) {
675                                        let verb = *lemma;
676                                        let verb_time = if matches!(
677                                            self.tokens[self.current + 1].kind,
678                                            TokenType::Was | TokenType::Were
679                                        ) {
680                                            Time::Past
681                                        } else {
682                                            Time::Present
683                                        };
684                                        self.advance(); // consume "it"
685                                        self.advance(); // consume "is/are/was/were"
686                                        self.advance(); // consume weather verb
687
688                                        let event_var = self.get_event_var();
689                                        // Weather verbs are impersonal - no pronoun resolution needed
690                                        // Event var gets universal force from transpiler when suppress_existential=true
691                                        let suppress_existential = self.drs.in_conditional_antecedent();
692
693                                        let neo_event = self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
694                                            event_var,
695                                            verb,
696                                            roles: self.ctx.roles.alloc_slice(vec![]),
697                                            modifiers: self.ctx.syms.alloc_slice(vec![]),
698                                            suppress_existential,
699                                            world: None,
700                                        })));
701
702                                        // Progressive aspect for "is raining"
703                                        let with_aspect = self.ctx.exprs.alloc(LogicExpr::Aspectual {
704                                            operator: AspectOperator::Progressive,
705                                            body: neo_event,
706                                        });
707
708                                        return Ok(match verb_time {
709                                            Time::Past => self.ctx.exprs.alloc(LogicExpr::Temporal {
710                                                operator: TemporalOperator::Past,
711                                                body: with_aspect,
712                                            }),
713                                            _ => with_aspect,
714                                        });
715                                    }
716                                }
717                            }
718                        }
719                    }
720                }
721            }
722
723            // Track if subject is an indefinite that needs DRS registration
724            let (subject, subject_type_pred) = if self.check_pronoun() {
725                let token = self.advance().clone();
726                let token_text = self.interner.resolve(token.lexeme);
727                // Handle first/second person pronouns as constants (deictic reference)
728                let resolved = if token_text.eq_ignore_ascii_case("i") {
729                    self.interner.intern("Speaker")
730                } else if token_text.eq_ignore_ascii_case("you") {
731                    self.interner.intern("Addressee")
732                } else if let TokenType::Pronoun { gender, number, .. } = token.kind {
733                    let resolved_pronoun = self.resolve_pronoun(gender, number)?;
734                    match resolved_pronoun {
735                        super::ResolvedPronoun::Variable(s) | super::ResolvedPronoun::Constant(s) => s,
736                    }
737                } else {
738                    unknown
739                };
740                (resolved, None)
741            } else {
742                let np = self.parse_noun_phrase(true)?;
743
744                // Check if this NP should introduce a DRS referent
745                // Both indefinites ("a dog") and definites ("the dog") introduce referents
746                // For definites without antecedent, this implements "global accommodation"
747                if np.definiteness == Some(Definiteness::Indefinite)
748                    || np.definiteness == Some(Definiteness::Definite)
749                    || np.definiteness == Some(Definiteness::Distal) {
750                    let gender = Self::infer_noun_gender(self.interner.resolve(np.noun));
751                    let number = if Self::is_plural_noun(self.interner.resolve(np.noun)) {
752                        Number::Plural
753                    } else {
754                        Number::Singular
755                    };
756
757                    // Register in DRS using noun as variable (for pronoun resolution)
758                    // For DEFINITES ("the X"), use MainClause source to avoid universal force
759                    // This ensures "the butler" in conditionals is treated as a constant
760                    // For INDEFINITES ("a X"), use default source (gets universal force in antecedent)
761                    if np.definiteness == Some(Definiteness::Definite) || np.definiteness == Some(Definiteness::Distal) {
762                        self.drs.introduce_referent_with_source(np.noun, np.noun, gender, number, crate::drs::ReferentSource::MainClause);
763                    } else {
764                        self.drs.introduce_referent(np.noun, np.noun, gender, number);
765                    }
766
767                    // Create type predicate: Farmer(noun)
768                    let type_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
769                        name: np.noun,
770                        args: self.ctx.terms.alloc_slice([Term::Variable(np.noun)]),
771                        world: None,
772                    });
773
774                    (np.noun, Some(type_pred))
775                } else {
776                    // Proper name - use as constant (proper names have their own registration)
777                    (np.noun, None)
778                }
779            };
780
781            // Determine the subject term type
782            let subject_term = if subject_type_pred.is_some() {
783                Term::Variable(subject)
784            } else {
785                Term::Constant(subject)
786            };
787
788            // Handle presupposition triggers in antecedent: "If John stopped smoking, ..."
789            // Only trigger if followed by gerund complement
790            if self.check_presup_trigger() && self.is_followed_by_gerund() {
791                let presup_kind = match self.advance().kind {
792                    TokenType::PresupTrigger(kind) => kind,
793                    TokenType::Verb { lemma, .. } => {
794                        let s = self.interner.resolve(lemma).to_lowercase();
795                        crate::lexicon::lookup_presup_trigger(&s)
796                            .expect("Lexicon mismatch: Verb flagged as trigger but lookup failed")
797                    }
798                    _ => panic!("Expected presupposition trigger"),
799                };
800                let np = NounPhrase {
801                    noun: subject,
802                    definiteness: None,
803                    adjectives: &[],
804                    possessor: None,
805                    pps: &[],
806                    superlative: None,
807                };
808                return self.parse_presupposition(&np, presup_kind);
809            }
810
811            if self.check(&TokenType::Were) {
812                self.advance();
813                let predicate = if self.check_pronoun() {
814                    let token = self.advance().clone();
815                    if let TokenType::Pronoun { gender, number, .. } = token.kind {
816                        let token_text = self.interner.resolve(token.lexeme);
817                        if token_text.eq_ignore_ascii_case("i") {
818                            self.interner.intern("Speaker")
819                        } else if token_text.eq_ignore_ascii_case("you") {
820                            self.interner.intern("Addressee")
821                        } else {
822                            let resolved_pronoun = self.resolve_pronoun(gender, number)?;
823                            match resolved_pronoun {
824                                super::ResolvedPronoun::Variable(s) | super::ResolvedPronoun::Constant(s) => s,
825                            }
826                        }
827                    } else {
828                        unknown
829                    }
830                } else {
831                    self.consume_content_word()?
832                };
833                let be = self.interner.intern("Be");
834                let be_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
835                    name: be,
836                    args: self.ctx.terms.alloc_slice([
837                        subject_term,
838                        Term::Constant(predicate),
839                    ]),
840                    world: None,
841                });
842                // Combine with type predicate if indefinite subject
843                return Ok(if let Some(type_pred) = subject_type_pred {
844                    self.ctx.exprs.alloc(LogicExpr::BinaryOp {
845                        left: type_pred,
846                        op: TokenType::And,
847                        right: be_pred,
848                    })
849                } else {
850                    be_pred
851                });
852            }
853
854            if self.check(&TokenType::Had) {
855                self.advance();
856                let verb = self.consume_content_word()?;
857                let main_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
858                    name: verb,
859                    args: self.ctx.terms.alloc_slice([subject_term]),
860                    world: None,
861                });
862
863                // Handle "because" causal clause in antecedent
864                // Phase 35: Do NOT consume if followed by string literal (Trust justification)
865                if self.check(&TokenType::Because) && !self.peek_next_is_string_literal() {
866                    self.advance();
867                    let cause = self.parse_atom()?;
868                    let causal = self.ctx.exprs.alloc(LogicExpr::Causal {
869                        effect: main_pred,
870                        cause,
871                    });
872                    // Combine with type predicate if indefinite subject
873                    return Ok(if let Some(type_pred) = subject_type_pred {
874                        self.ctx.exprs.alloc(LogicExpr::BinaryOp {
875                            left: type_pred,
876                            op: TokenType::And,
877                            right: causal,
878                        })
879                    } else {
880                        causal
881                    });
882                }
883
884                // Combine with type predicate if indefinite subject
885                return Ok(if let Some(type_pred) = subject_type_pred {
886                    self.ctx.exprs.alloc(LogicExpr::BinaryOp {
887                        left: type_pred,
888                        op: TokenType::And,
889                        right: main_pred,
890                    })
891                } else {
892                    main_pred
893                });
894            }
895
896            // Parse verb phrase with subject
897            // Use variable term for indefinite subjects, constant for definites/proper names
898            let verb_phrase = if subject_type_pred.is_some() {
899                self.parse_predicate_with_subject_as_var(subject)?
900            } else {
901                self.parse_predicate_with_subject(subject)?
902            };
903
904            // Combine with type predicate if indefinite subject
905            return Ok(if let Some(type_pred) = subject_type_pred {
906                self.ctx.exprs.alloc(LogicExpr::BinaryOp {
907                    left: type_pred,
908                    op: TokenType::And,
909                    right: verb_phrase,
910                })
911            } else {
912                verb_phrase
913            });
914        }
915
916        self.parse_sentence()
917    }
918
919    fn parse_counterfactual_consequent(&mut self) -> ParseResult<&'a LogicExpr<'a>> {
920        let unknown = self.interner.intern("?");
921        if self.check_content_word() || self.check_pronoun() {
922            // Check for grammatically incorrect "its" + weather adjective
923            // "its" is possessive, "it's" is contraction - common typo
924            if self.check_pronoun() {
925                let token = self.peek();
926                let token_text = self.interner.resolve(token.lexeme).to_lowercase();
927                if token_text == "its" {
928                    // Check if followed by weather adjective
929                    if self.current + 1 < self.tokens.len() {
930                        let next_token = &self.tokens[self.current + 1];
931                        let next_str = self.interner.resolve(next_token.lexeme).to_lowercase();
932                        if let Some(meta) = crate::lexicon::lookup_adjective_db(&next_str) {
933                            if meta.features.contains(&crate::lexicon::Feature::Weather) {
934                                return Err(ParseError {
935                                    kind: ParseErrorKind::GrammarError(
936                                        "Did you mean 'it's' (it is)? 'its' is a possessive pronoun.".to_string()
937                                    ),
938                                    span: self.current_span(),
939                                });
940                            }
941                        }
942                    }
943                }
944            }
945
946            // Check for expletive "it" + copula + weather adjective: "it's wet" → Wet
947            if self.check_pronoun() {
948                let token_text = self.interner.resolve(self.peek().lexeme).to_lowercase();
949                if token_text == "it" {
950                    // Look ahead for copula + weather adjective
951                    // Handle both "it is wet" and "it's wet" (where 's is Possessive token)
952                    if self.current + 2 < self.tokens.len() {
953                        let next = &self.tokens[self.current + 1].kind;
954                        if matches!(next, TokenType::Is | TokenType::Was | TokenType::Possessive) {
955                            // Check if followed by weather adjective
956                            let adj_token = &self.tokens[self.current + 2];
957                            let adj_sym = adj_token.lexeme;
958                            let adj_str = self.interner.resolve(adj_sym).to_lowercase();
959                            if let Some(meta) = crate::lexicon::lookup_adjective_db(&adj_str) {
960                                if meta.features.contains(&crate::lexicon::Feature::Weather) {
961                                    self.advance(); // consume "it"
962                                    self.advance(); // consume copula
963                                    self.advance(); // consume adjective token
964
965                                    // Use the canonical lemma from lexicon (e.g., "Wet" not "wet")
966                                    let adj_lemma = self.interner.intern(meta.lemma);
967
968                                    // Get event variable from DRS (introduced in antecedent)
969                                    let event_var = self.drs.get_last_event_referent(self.interner)
970                                        .unwrap_or_else(|| self.interner.intern("e"));
971
972                                    // First weather adjective predicate
973                                    let mut result: &'a LogicExpr<'a> = self.ctx.exprs.alloc(LogicExpr::Predicate {
974                                        name: adj_lemma,
975                                        args: self.ctx.terms.alloc_slice([Term::Variable(event_var)]),
976                                        world: None,
977                                    });
978
979                                    // Handle coordinated adjectives: "wet and cold"
980                                    while self.check(&TokenType::And) {
981                                        self.advance(); // consume "and"
982                                        if self.check_content_word() {
983                                            let adj2_lexeme = self.peek().lexeme;
984                                            let adj2_str = self.interner.resolve(adj2_lexeme).to_lowercase();
985
986                                            // Check if it's also a weather adjective
987                                            if let Some(meta2) = crate::lexicon::lookup_adjective_db(&adj2_str) {
988                                                if meta2.features.contains(&crate::lexicon::Feature::Weather) {
989                                                    self.advance(); // consume adjective token
990                                                    // Use the canonical lemma from lexicon (e.g., "Cold" not "cold")
991                                                    let adj2_lemma = self.interner.intern(meta2.lemma);
992                                                    let pred2 = self.ctx.exprs.alloc(LogicExpr::Predicate {
993                                                        name: adj2_lemma,
994                                                        args: self.ctx.terms.alloc_slice([Term::Variable(event_var)]),
995                                                        world: None,
996                                                    });
997                                                    result = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
998                                                        left: result,
999                                                        op: TokenType::And,
1000                                                        right: pred2,
1001                                                    });
1002                                                    continue;
1003                                                }
1004                                            }
1005                                        }
1006                                        break;
1007                                    }
1008
1009                                    return Ok(result);
1010                                }
1011                            }
1012                        }
1013                    }
1014                }
1015            }
1016
1017            let subject = if self.check_pronoun() {
1018                let token = self.advance().clone();
1019                let token_text = self.interner.resolve(token.lexeme);
1020                // Handle first/second person pronouns as constants (deictic reference)
1021                if token_text.eq_ignore_ascii_case("i") {
1022                    self.interner.intern("Speaker")
1023                } else if token_text.eq_ignore_ascii_case("you") {
1024                    self.interner.intern("Addressee")
1025                } else if let TokenType::Pronoun { gender, number, .. } = token.kind {
1026                    let resolved_pronoun = self.resolve_pronoun(gender, number)?;
1027                    match resolved_pronoun {
1028                        super::ResolvedPronoun::Variable(s) | super::ResolvedPronoun::Constant(s) => s,
1029                    }
1030                } else {
1031                    unknown
1032                }
1033            } else {
1034                self.parse_noun_phrase(true)?.noun
1035            };
1036
1037            if self.check(&TokenType::Would) {
1038                self.advance();
1039                if self.check_content_word() {
1040                    let next_word = self.interner.resolve(self.peek().lexeme).to_lowercase();
1041                    if next_word == "have" {
1042                        self.advance();
1043                    }
1044                }
1045                let verb = self.consume_content_word()?;
1046                return Ok(self.ctx.exprs.alloc(LogicExpr::Predicate {
1047                    name: verb,
1048                    args: self.ctx.terms.alloc_slice([Term::Constant(subject)]),
1049                    world: None,
1050                }));
1051            }
1052
1053            return self.parse_predicate_with_subject(subject);
1054        }
1055
1056        self.parse_sentence()
1057    }
1058
1059    fn extract_verb_from_expr(&self, expr: &LogicExpr<'a>) -> Option<Symbol> {
1060        match expr {
1061            // NeoEvent directly contains the verb
1062            LogicExpr::NeoEvent(data) => Some(data.verb),
1063            // Control structures directly contain the verb
1064            LogicExpr::Control { verb, .. } => Some(*verb),
1065            // Phase 46: For BinaryOp, try to find NeoEvent first (either side),
1066            // then fall back to Predicate. This handles both:
1067            // - Transitive: Apple(x) ∧ ∃e(Eat(e)...) - NeoEvent on right
1068            // - Motion PP: ∃e(Walk(e)...) ∧ To(e, Park) - NeoEvent on left
1069            LogicExpr::BinaryOp { left, right, .. } => {
1070                // First check if left contains a NeoEvent (motion PP case)
1071                if let Some(verb) = self.extract_neo_event_verb(left) {
1072                    return Some(verb);
1073                }
1074                // Then check right (transitive case with type predicate on left)
1075                if let Some(verb) = self.extract_neo_event_verb(right) {
1076                    return Some(verb);
1077                }
1078                // Fall back to any extractable verb
1079                self.extract_verb_from_expr(left)
1080                    .or_else(|| self.extract_verb_from_expr(right))
1081            }
1082            // Plain predicate - last resort (might be type predicate or PP)
1083            LogicExpr::Predicate { name, .. } => Some(*name),
1084            LogicExpr::Modal { operand, .. } => self.extract_verb_from_expr(operand),
1085            LogicExpr::Presupposition { assertion, .. } => self.extract_verb_from_expr(assertion),
1086            LogicExpr::Temporal { body, .. } => self.extract_verb_from_expr(body),
1087            LogicExpr::TemporalAnchor { body, .. } => self.extract_verb_from_expr(body),
1088            LogicExpr::Aspectual { body, .. } => self.extract_verb_from_expr(body),
1089            LogicExpr::Quantifier { body, .. } => self.extract_verb_from_expr(body),
1090            _ => None,
1091        }
1092    }
1093
1094    /// Phase 46: Generalized gapping with template-guided reconstruction.
1095    /// Handles NPs, PPs, temporal adverbs, and preserves roles from EventTemplate.
1096    fn parse_gapped_clause(&mut self, borrowed_verb: Symbol) -> ParseResult<&'a LogicExpr<'a>> {
1097        let subject = self.parse_noun_phrase(true)?;
1098
1099        if self.check(&TokenType::Comma) {
1100            self.advance();
1101        }
1102
1103        let subject_term = self.noun_phrase_to_term(&subject);
1104        let event_var = self.get_event_var();
1105        let suppress_existential = self.drs.in_conditional_antecedent();
1106
1107        // Get template for role guidance
1108        let template = self.last_event_template.clone();
1109
1110        // Collect arguments (NPs, PPs, temporal adverbs) from gapped clause
1111        let mut np_args: Vec<Term<'a>> = Vec::new();
1112        let mut pp_args: Vec<(Symbol, Term<'a>)> = Vec::new();
1113        let mut override_adverb: Option<Symbol> = None;
1114
1115        loop {
1116            if self.check_temporal_adverb() {
1117                // Temporal adverb: override template modifier
1118                if let TokenType::TemporalAdverb(sym) = self.advance().kind {
1119                    override_adverb = Some(sym);
1120                }
1121            } else if self.check_preposition() {
1122                // PP argument: "to the school", "on the table"
1123                let prep = if let TokenType::Preposition(sym) = self.advance().kind {
1124                    sym
1125                } else {
1126                    continue;
1127                };
1128                let np = self.parse_noun_phrase(false)?;
1129                pp_args.push((prep, self.noun_phrase_to_term(&np)));
1130            } else if self.check_content_word() || self.check_article() {
1131                // NP argument
1132                let np = self.parse_noun_phrase(false)?;
1133                np_args.push(self.noun_phrase_to_term(&np));
1134                if self.check(&TokenType::Comma) {
1135                    self.advance();
1136                }
1137            } else {
1138                break;
1139            }
1140        }
1141
1142        // Build roles using template guidance
1143        let roles = self.build_gapped_roles(subject_term, &np_args, &pp_args, &template);
1144
1145        // Handle modifiers: override if adverb provided, else inherit from template
1146        let modifiers = match (override_adverb, &template) {
1147            (Some(adv), Some(tmpl)) => {
1148                // Filter out temporal modifiers from template, add new one
1149                let mut mods: Vec<Symbol> = tmpl
1150                    .modifiers
1151                    .iter()
1152                    .filter(|m| !self.is_temporal_modifier(**m))
1153                    .cloned()
1154                    .collect();
1155                mods.push(adv);
1156                mods
1157            }
1158            (Some(adv), None) => vec![adv],
1159            (None, Some(tmpl)) => tmpl.modifiers.clone(),
1160            (None, None) => vec![],
1161        };
1162
1163        Ok(self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
1164            event_var,
1165            verb: borrowed_verb,
1166            roles: self.ctx.roles.alloc_slice(roles),
1167            modifiers: self.ctx.syms.alloc_slice(modifiers),
1168            suppress_existential,
1169            world: None,
1170        }))))
1171    }
1172
1173    fn is_complete_clause(&self, expr: &LogicExpr<'a>) -> bool {
1174        match expr {
1175            LogicExpr::Atom(_) => false,
1176            LogicExpr::Predicate { .. } => true,
1177            LogicExpr::Quantifier { .. } => true,
1178            LogicExpr::Modal { .. } => true,
1179            LogicExpr::Temporal { .. } => true,
1180            LogicExpr::Aspectual { .. } => true,
1181            LogicExpr::BinaryOp { .. } => true,
1182            LogicExpr::UnaryOp { .. } => true,
1183            LogicExpr::Control { .. } => true,
1184            LogicExpr::Presupposition { .. } => true,
1185            LogicExpr::Categorical(_) => true,
1186            LogicExpr::Relation(_) => true,
1187            _ => true,
1188        }
1189    }
1190
1191    /// Parse disjunction (Or/Iff) - lowest precedence logical connectives.
1192    /// Calls parse_conjunction for operands to ensure And binds tighter.
1193    fn parse_disjunction(&mut self) -> ParseResult<&'a LogicExpr<'a>> {
1194        let mut expr = self.parse_conjunction()?;
1195
1196        while self.check(&TokenType::Comma)
1197            || self.check(&TokenType::Or)
1198            || self.check(&TokenType::Iff)
1199        {
1200            if self.check(&TokenType::Comma) {
1201                self.advance();
1202            }
1203            if !self.match_token(&[TokenType::Or, TokenType::Iff]) {
1204                break;
1205            }
1206            let operator = self.previous().kind.clone();
1207            self.current_island += 1;
1208
1209            let saved_pos = self.current;
1210            let standard_attempt = self.try_parse(|p| p.parse_conjunction());
1211
1212            // Gapping in disjunction: only for Or, not Iff. Use original (non-expanded) trigger.
1213            // Expanded gapping (with Period/is_at_end) only applies in parse_conjunction.
1214            let use_gapping = match &standard_attempt {
1215                Some(right) => {
1216                    !self.is_complete_clause(right)
1217                        && (self.check(&TokenType::Comma) || self.check_content_word())
1218                        && operator != TokenType::Iff // Don't gap on biconditional
1219                }
1220                None => operator != TokenType::Iff, // For Iff, require successful parse
1221            };
1222
1223            if !use_gapping {
1224                if let Some(right) = standard_attempt {
1225                    expr = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1226                        left: expr,
1227                        op: operator,
1228                        right,
1229                    });
1230                }
1231            } else {
1232                self.current = saved_pos;
1233
1234                let borrowed_verb = self.extract_verb_from_expr(expr).ok_or(ParseError {
1235                    kind: ParseErrorKind::GappingResolutionFailed,
1236                    span: self.current_span(),
1237                })?;
1238
1239                let right = self.parse_gapped_clause(borrowed_verb)?;
1240
1241                expr = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1242                    left: expr,
1243                    op: operator,
1244                    right,
1245                });
1246            }
1247        }
1248
1249        // Handle binary temporal connectives (lowest precedence temporal)
1250        // "P until Q" → TemporalBinary { Until, P, Q }
1251        // "P release Q" → TemporalBinary { Release, P, Q }
1252        // "P weak-until Q" → TemporalBinary { WeakUntil, P, Q }
1253        if self.check(&TokenType::Until) || self.check(&TokenType::Release) || self.check(&TokenType::WeakUntil) {
1254            let op = match self.peek().kind {
1255                TokenType::Release => crate::ast::logic::BinaryTemporalOp::Release,
1256                TokenType::WeakUntil => crate::ast::logic::BinaryTemporalOp::WeakUntil,
1257                _ => crate::ast::logic::BinaryTemporalOp::Until,
1258            };
1259            self.advance();
1260            let right = self.parse_conjunction()?;
1261            expr = self.ctx.exprs.alloc(LogicExpr::TemporalBinary {
1262                operator: op,
1263                left: expr,
1264                right,
1265            });
1266        }
1267
1268        // Check for trailing "within N cycles" bounded temporal delay
1269        let expr = self.try_wrap_bounded_delay(expr);
1270
1271        Ok(expr)
1272    }
1273
1274    /// Parse conjunction (And) - higher precedence than Or.
1275    /// Calls parse_atom for operands.
1276    fn parse_conjunction(&mut self) -> ParseResult<&'a LogicExpr<'a>> {
1277        let mut expr = self.parse_atom()?;
1278
1279        // Handle causal "because" at conjunction level
1280        // Phase 35: Do NOT consume if followed by string literal (Trust justification)
1281        if self.check(&TokenType::Because) && !self.peek_next_is_string_literal() {
1282            self.advance();
1283            let cause = self.parse_atom()?;
1284            return Ok(self.ctx.exprs.alloc(LogicExpr::Causal {
1285                effect: expr,
1286                cause,
1287            }));
1288        }
1289
1290        while self.check(&TokenType::Comma) || self.check(&TokenType::And) {
1291            if self.check(&TokenType::Comma) {
1292                self.advance();
1293            }
1294            if !self.match_token(&[TokenType::And]) {
1295                break;
1296            }
1297            let operator = self.previous().kind.clone();
1298            self.current_island += 1;
1299
1300            let saved_pos = self.current;
1301            let standard_attempt = self.try_parse(|p| p.parse_atom());
1302
1303            // Phase 46: Expanded gapping trigger to support PP gapping, temporal override,
1304            // and intransitive gapping (bare subject at clause boundary)
1305            let use_gapping = match &standard_attempt {
1306                Some(right) => {
1307                    !self.is_complete_clause(right)
1308                        && (self.check(&TokenType::Comma)
1309                            || self.check_content_word()
1310                            || self.check_preposition()
1311                            || self.check_temporal_adverb()
1312                            || self.check(&TokenType::Period)
1313                            || self.is_at_end())
1314                }
1315                None => true,
1316            };
1317
1318            if !use_gapping {
1319                if let Some(right) = standard_attempt {
1320                    expr = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1321                        left: expr,
1322                        op: operator,
1323                        right,
1324                    });
1325                }
1326            } else {
1327                self.current = saved_pos;
1328
1329                let borrowed_verb = self.extract_verb_from_expr(expr).ok_or(ParseError {
1330                    kind: ParseErrorKind::GappingResolutionFailed,
1331                    span: self.current_span(),
1332                })?;
1333
1334                let right = self.parse_gapped_clause(borrowed_verb)?;
1335
1336                expr = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1337                    left: expr,
1338                    op: operator,
1339                    right,
1340                });
1341            }
1342        }
1343
1344        Ok(expr)
1345    }
1346
1347    fn parse_relative_clause(&mut self, gap_var: Symbol) -> ParseResult<&'a LogicExpr<'a>> {
1348        if self.check_verb() {
1349            return self.parse_verb_phrase_for_restriction(gap_var);
1350        }
1351
1352        // Handle "do/does (not)" in relative clauses: "who do not shave themselves"
1353        if self.check(&TokenType::Do) || self.check(&TokenType::Does) {
1354            self.advance(); // consume "do/does"
1355
1356            let is_negated = self.check(&TokenType::Not);
1357            if is_negated {
1358                self.advance(); // consume "not"
1359            }
1360
1361            if self.check_verb() {
1362                let verb = self.consume_verb();
1363
1364                // Check for reflexive object: "shave themselves"
1365                let roles = if self.check(&TokenType::Reflexive) {
1366                    self.advance(); // consume "themselves/himself"
1367                    vec![
1368                        (ThematicRole::Agent, Term::Variable(gap_var)),
1369                        (ThematicRole::Theme, Term::Variable(gap_var)),
1370                    ]
1371                } else if self.check_content_word() || self.check_article() {
1372                    // Parse object NP
1373                    let obj = self.parse_noun_phrase(false)?;
1374                    vec![
1375                        (ThematicRole::Agent, Term::Variable(gap_var)),
1376                        (ThematicRole::Theme, Term::Constant(obj.noun)),
1377                    ]
1378                } else {
1379                    // Intransitive
1380                    vec![(ThematicRole::Agent, Term::Variable(gap_var))]
1381                };
1382
1383                let event_var = self.get_event_var();
1384                let suppress_existential = self.drs.in_conditional_antecedent();
1385                let event = self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
1386                    event_var,
1387                    verb,
1388                    roles: self.ctx.roles.alloc_slice(roles),
1389                    modifiers: self.ctx.syms.alloc_slice(vec![]),
1390                    suppress_existential,
1391                    world: None,
1392                })));
1393
1394                if is_negated {
1395                    return Ok(self.ctx.exprs.alloc(LogicExpr::UnaryOp {
1396                        op: TokenType::Not,
1397                        operand: event,
1398                    }));
1399                }
1400                return Ok(event);
1401            }
1402        }
1403
1404        if self.check_content_word() || self.check_article() {
1405            let rel_subject = self.parse_noun_phrase_for_relative()?;
1406
1407            let nested_relative = if matches!(self.peek().kind, TokenType::Article(_)) {
1408                let nested_var = self.next_var_name();
1409                Some((nested_var, self.parse_relative_clause(nested_var)?))
1410            } else {
1411                None
1412            };
1413
1414            if self.check_verb() {
1415                let verb = self.consume_verb();
1416
1417                let mut roles: Vec<(ThematicRole, Term<'a>)> = vec![
1418                    (ThematicRole::Agent, Term::Constant(rel_subject.noun)),
1419                    (ThematicRole::Theme, Term::Variable(gap_var)),
1420                ];
1421
1422                while self.check_to_preposition() {
1423                    self.advance();
1424                    if self.check_content_word() || self.check_article() {
1425                        let recipient = self.parse_noun_phrase(false)?;
1426                        roles.push((ThematicRole::Recipient, Term::Constant(recipient.noun)));
1427                    }
1428                }
1429
1430                let event_var = self.get_event_var();
1431                let suppress_existential = self.drs.in_conditional_antecedent();
1432                let this_event = self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
1433                    event_var,
1434                    verb,
1435                    roles: self.ctx.roles.alloc_slice(roles),
1436                    modifiers: self.ctx.syms.alloc_slice(vec![]),
1437                    suppress_existential,
1438                    world: None,
1439                })));
1440
1441                if let Some((nested_var, nested_clause)) = nested_relative {
1442                    let type_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
1443                        name: rel_subject.noun,
1444                        args: self.ctx.terms.alloc_slice([Term::Variable(nested_var)]),
1445                        world: None,
1446                    });
1447
1448                    let inner = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1449                        left: type_pred,
1450                        op: TokenType::And,
1451                        right: nested_clause,
1452                    });
1453
1454                    let combined = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1455                        left: inner,
1456                        op: TokenType::And,
1457                        right: this_event,
1458                    });
1459
1460                    return Ok(self.ctx.exprs.alloc(LogicExpr::Quantifier {
1461                        kind: crate::ast::QuantifierKind::Existential,
1462                        variable: nested_var,
1463                        body: combined,
1464                        island_id: self.current_island,
1465                    }));
1466                }
1467
1468                return Ok(this_event);
1469            }
1470        }
1471
1472        if self.check_verb() {
1473            return self.parse_verb_phrase_for_restriction(gap_var);
1474        }
1475
1476        let unknown = self.interner.intern("?");
1477        Ok(self.ctx.exprs.alloc(LogicExpr::Atom(unknown)))
1478    }
1479
1480    fn check_ellipsis_auxiliary(&self) -> bool {
1481        matches!(
1482            self.peek().kind,
1483            TokenType::Does | TokenType::Do |
1484            TokenType::Can | TokenType::Could | TokenType::Would |
1485            TokenType::May | TokenType::Must | TokenType::Should
1486        )
1487    }
1488
1489    fn check_ellipsis_terminator(&self) -> bool {
1490        if self.is_at_end() || self.check(&TokenType::Period) {
1491            return true;
1492        }
1493        if self.check_content_word() {
1494            let word = self.interner.resolve(self.peek().lexeme).to_lowercase();
1495            return word == "too" || word == "also";
1496        }
1497        false
1498    }
1499
1500    fn try_parse_ellipsis(&mut self) -> Option<ParseResult<&'a LogicExpr<'a>>> {
1501        // Need a stored template to reconstruct from
1502        if self.last_event_template.is_none() {
1503            return None;
1504        }
1505
1506        let saved_pos = self.current;
1507
1508        // Pattern: Subject + Auxiliary + (not)? + Terminator
1509        // Subject must be proper name or pronoun
1510        let subject_sym = if matches!(self.peek().kind, TokenType::ProperName(_)) {
1511            if let TokenType::ProperName(sym) = self.advance().kind {
1512                sym
1513            } else {
1514                self.current = saved_pos;
1515                return None;
1516            }
1517        } else if self.check_pronoun() {
1518            let token = self.advance().clone();
1519            if let TokenType::Pronoun { gender, number, .. } = token.kind {
1520                match self.resolve_pronoun(gender, number) {
1521                    Ok(resolved) => match resolved {
1522                        super::ResolvedPronoun::Variable(s) | super::ResolvedPronoun::Constant(s) => s,
1523                    },
1524                    Err(e) => return Some(Err(e)),
1525                }
1526            } else {
1527                self.current = saved_pos;
1528                return None;
1529            }
1530        } else {
1531            return None;
1532        };
1533
1534        // Must be followed by ellipsis auxiliary
1535        if !self.check_ellipsis_auxiliary() {
1536            self.current = saved_pos;
1537            return None;
1538        }
1539        let aux_token = self.advance().kind.clone();
1540
1541        // Check for negation
1542        let is_negated = self.match_token(&[TokenType::Not]);
1543
1544        // Must end with terminator
1545        if !self.check_ellipsis_terminator() {
1546            self.current = saved_pos;
1547            return None;
1548        }
1549
1550        // Consume "too"/"also" if present
1551        if self.check_content_word() {
1552            let word = self.interner.resolve(self.peek().lexeme).to_lowercase();
1553            if word == "too" || word == "also" {
1554                self.advance();
1555            }
1556        }
1557
1558        // Reconstruct from template
1559        let template = self.last_event_template.clone().unwrap();
1560        let event_var = self.get_event_var();
1561        let suppress_existential = self.drs.in_conditional_antecedent();
1562
1563        // Build roles with new subject as Agent
1564        let mut roles: Vec<(ThematicRole, Term<'a>)> = vec![
1565            (ThematicRole::Agent, Term::Constant(subject_sym))
1566        ];
1567        roles.extend(template.non_agent_roles.iter().cloned());
1568
1569        let neo_event = self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
1570            event_var,
1571            verb: template.verb,
1572            roles: self.ctx.roles.alloc_slice(roles),
1573            modifiers: self.ctx.syms.alloc_slice(template.modifiers.clone()),
1574            suppress_existential,
1575            world: None,
1576        })));
1577
1578        // Apply modal if auxiliary is modal
1579        let with_modal = match aux_token {
1580            TokenType::Can | TokenType::Could => {
1581                let vector = self.token_to_vector(&aux_token);
1582                self.ctx.modal(vector, neo_event)
1583            }
1584            TokenType::Would | TokenType::May | TokenType::Must | TokenType::Should => {
1585                let vector = self.token_to_vector(&aux_token);
1586                self.ctx.modal(vector, neo_event)
1587            }
1588            _ => neo_event,
1589        };
1590
1591        // Apply negation if present
1592        let result = if is_negated {
1593            self.ctx.exprs.alloc(LogicExpr::UnaryOp {
1594                op: TokenType::Not,
1595                operand: with_modal,
1596            })
1597        } else {
1598            with_modal
1599        };
1600
1601        Some(Ok(result))
1602    }
1603}
1604
1605// Phase 46: Helper methods for generalized gapping (not part of trait)
1606impl<'a, 'ctx, 'int> Parser<'a, 'ctx, 'int> {
1607    /// Helper to extract verb specifically from NeoEvent structures
1608    fn extract_neo_event_verb(&self, expr: &LogicExpr<'a>) -> Option<Symbol> {
1609        match expr {
1610            LogicExpr::NeoEvent(data) => Some(data.verb),
1611            LogicExpr::Quantifier { body, .. } => self.extract_neo_event_verb(body),
1612            LogicExpr::BinaryOp { left, right, .. } => {
1613                self.extract_neo_event_verb(left)
1614                    .or_else(|| self.extract_neo_event_verb(right))
1615            }
1616            LogicExpr::Temporal { body, .. } => self.extract_neo_event_verb(body),
1617            LogicExpr::Aspectual { body, .. } => self.extract_neo_event_verb(body),
1618            _ => None,
1619        }
1620    }
1621
1622    /// Build roles for gapped clause using template guidance.
1623    /// NP args map to Theme/Recipient roles, PP args map by preposition type.
1624    fn build_gapped_roles(
1625        &self,
1626        subject_term: Term<'a>,
1627        np_args: &[Term<'a>],
1628        pp_args: &[(Symbol, Term<'a>)],
1629        template: &Option<EventTemplate<'a>>,
1630    ) -> Vec<(ThematicRole, Term<'a>)> {
1631        let mut roles = vec![(ThematicRole::Agent, subject_term)];
1632
1633        match template {
1634            Some(tmpl) => {
1635                let template_roles = &tmpl.non_agent_roles;
1636
1637                // Separate template roles into NP-type and PP-type
1638                let np_template_roles: Vec<_> = template_roles
1639                    .iter()
1640                    .filter(|(r, _)| {
1641                        matches!(
1642                            r,
1643                            ThematicRole::Theme | ThematicRole::Recipient | ThematicRole::Patient
1644                        )
1645                    })
1646                    .collect();
1647
1648                let pp_template_roles: Vec<_> = template_roles
1649                    .iter()
1650                    .filter(|(r, _)| {
1651                        matches!(
1652                            r,
1653                            ThematicRole::Goal
1654                                | ThematicRole::Source
1655                                | ThematicRole::Location
1656                                | ThematicRole::Instrument
1657                        )
1658                    })
1659                    .collect();
1660
1661                // Handle NPs by matching to template NP roles
1662                match (np_template_roles.len(), np_args.len()) {
1663                    (0, 0) => {} // Intransitive - no NP roles
1664                    (_, 0) => {
1665                        // Use all template NP roles unchanged
1666                        for (role, term) in &np_template_roles {
1667                            roles.push((*role, term.clone()));
1668                        }
1669                    }
1670                    (n, 1) if n > 0 => {
1671                        // 1 NP arg: replace LAST NP role (usually Theme), keep others
1672                        for (role, term) in np_template_roles.iter().take(n - 1) {
1673                            roles.push((*role, term.clone()));
1674                        }
1675                        if let Some((last_role, _)) = np_template_roles.last() {
1676                            roles.push((*last_role, np_args[0].clone()));
1677                        }
1678                    }
1679                    (n, m) if m == n => {
1680                        // Same count: replace all NP roles in order
1681                        for ((role, _), arg) in np_template_roles.iter().zip(np_args.iter()) {
1682                            roles.push((*role, arg.clone()));
1683                        }
1684                    }
1685                    (_, _) => {
1686                        // Fallback: assign Theme to each NP
1687                        for (i, arg) in np_args.iter().enumerate() {
1688                            let role = np_template_roles
1689                                .get(i)
1690                                .map(|(r, _)| *r)
1691                                .unwrap_or(ThematicRole::Theme);
1692                            roles.push((role, arg.clone()));
1693                        }
1694                    }
1695                }
1696
1697                // Handle PPs: use parsed PPs if provided, else use template
1698                if pp_args.is_empty() {
1699                    // Use template PP roles unchanged
1700                    for (role, term) in &pp_template_roles {
1701                        roles.push((*role, term.clone()));
1702                    }
1703                } else {
1704                    // Use parsed PPs, map preposition to role
1705                    for (prep, term) in pp_args {
1706                        let role = self.preposition_to_role(*prep);
1707                        roles.push((role, term.clone()));
1708                    }
1709                }
1710            }
1711            None => {
1712                // No template: backward-compat hardcoded Agent + Theme
1713                for arg in np_args {
1714                    roles.push((ThematicRole::Theme, arg.clone()));
1715                }
1716                for (prep, term) in pp_args {
1717                    let role = self.preposition_to_role(*prep);
1718                    roles.push((role, term.clone()));
1719                }
1720            }
1721        }
1722        roles
1723    }
1724
1725    /// Map preposition to thematic role
1726    fn preposition_to_role(&self, prep: Symbol) -> ThematicRole {
1727        let prep_str = self.interner.resolve(prep).to_lowercase();
1728        match prep_str.as_str() {
1729            "to" | "toward" | "towards" => ThematicRole::Goal,
1730            "from" => ThematicRole::Source,
1731            "in" | "on" | "at" => ThematicRole::Location,
1732            "with" | "by" => ThematicRole::Instrument,
1733            _ => ThematicRole::Location, // Default fallback
1734        }
1735    }
1736
1737    /// Check if modifier is temporal (for override filtering)
1738    fn is_temporal_modifier(&self, sym: Symbol) -> bool {
1739        let s = self.interner.resolve(sym).to_lowercase();
1740        matches!(
1741            s.as_str(),
1742            "yesterday" | "today" | "tomorrow" | "now" | "then" | "past" | "future"
1743        )
1744    }
1745}