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) {
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        if self.check_wh_word() {
121            return self.parse_wh_question();
122        }
123
124        if self.check(&TokenType::Does)
125            || self.check(&TokenType::Do)
126            || self.check(&TokenType::Is)
127            || self.check(&TokenType::Are)
128            || self.check(&TokenType::Was)
129            || self.check(&TokenType::Were)
130            || self.check(&TokenType::Would)
131            || self.check(&TokenType::Could)
132            || self.check(&TokenType::Can)
133        {
134            return self.parse_yes_no_question();
135        }
136
137        if self.match_token(&[TokenType::If]) {
138            return self.parse_conditional();
139        }
140
141        // Handle "Either X or Y" disjunction
142        // Special case: "Either NP1 or NP2 is/are PRED" should apply PRED to both
143        if self.match_token(&[TokenType::Either]) {
144            return self.parse_either_or();
145        }
146
147        if self.check_modal() {
148            self.advance();
149            return self.parse_modal();
150        }
151
152        if self.match_token(&[TokenType::Not]) {
153            self.negative_depth += 1;
154            let inner = self.parse_sentence()?;
155            self.negative_depth -= 1;
156            return Ok(self.ctx.exprs.alloc(LogicExpr::UnaryOp {
157                op: TokenType::Not,
158                operand: inner,
159            }));
160        }
161
162        self.parse_disjunction()
163    }
164
165    fn check_wh_word(&self) -> bool {
166        if matches!(
167            self.peek().kind,
168            TokenType::Who
169                | TokenType::What
170                | TokenType::Where
171                | TokenType::When
172                | TokenType::Why
173        ) {
174            return true;
175        }
176        if self.check_preposition() && self.current + 1 < self.tokens.len() {
177            matches!(
178                self.tokens[self.current + 1].kind,
179                TokenType::Who
180                    | TokenType::What
181                    | TokenType::Where
182                    | TokenType::When
183                    | TokenType::Why
184            )
185        } else {
186            false
187        }
188    }
189
190    fn parse_conditional(&mut self) -> ParseResult<&'a LogicExpr<'a>> {
191        let is_counterfactual = self.is_counterfactual_context();
192
193        // Enter DRS antecedent box - indefinites here get universal force
194        self.drs.enter_box(BoxType::ConditionalAntecedent);
195        let antecedent = self.parse_counterfactual_antecedent()?;
196        self.drs.exit_box();
197
198        if self.check(&TokenType::Comma) {
199            self.advance();
200        }
201
202        if self.check(&TokenType::Then) {
203            self.advance();
204        }
205
206        // Enter DRS consequent box - can access antecedent referents
207        self.drs.enter_box(BoxType::ConditionalConsequent);
208        let consequent = self.parse_counterfactual_consequent()?;
209        self.drs.exit_box();
210
211        // Get DRS referents that need universal quantification
212        let universal_refs = self.drs.get_universal_referents();
213
214        // Build the conditional expression
215        let conditional = if is_counterfactual {
216            self.ctx.exprs.alloc(LogicExpr::Counterfactual {
217                antecedent,
218                consequent,
219            })
220        } else {
221            self.ctx.exprs.alloc(LogicExpr::BinaryOp {
222                left: antecedent,
223                op: TokenType::If,
224                right: consequent,
225            })
226        };
227
228        // Wrap with universal quantifiers for DRS referents
229        let mut result = conditional;
230        for var in universal_refs.into_iter().rev() {
231            result = self.ctx.exprs.alloc(LogicExpr::Quantifier {
232                kind: QuantifierKind::Universal,
233                variable: var,
234                body: result,
235                island_id: self.current_island,
236            });
237        }
238
239        Ok(result)
240    }
241
242    /// Parse "Either NP1 or NP2 is/are PRED" or "Either S1 or S2"
243    ///
244    /// Handles coordination: "Either Alice or Bob is guilty" should become
245    /// guilty(Alice) ∨ guilty(Bob), not Alice ∨ guilty(Bob)
246    fn parse_either_or(&mut self) -> ParseResult<&'a LogicExpr<'a>> {
247        // Save position for potential backtracking
248        let start_pos = self.current;
249
250        // Try to parse as "Either NP1 or NP2 VP"
251        // First, try to parse just a proper name (not a full clause)
252        if let TokenType::ProperName(name1) = self.peek().kind {
253            self.advance(); // consume first proper name
254
255            if self.check(&TokenType::Or) {
256                self.advance(); // consume "or"
257
258                if let TokenType::ProperName(name2) = self.peek().kind {
259                    self.advance(); // consume second proper name
260
261                    // Check for shared predicate: "is/are ADJECTIVE"
262                    let is_copula = matches!(
263                        self.peek().kind,
264                        TokenType::Is | TokenType::Are | TokenType::Was | TokenType::Were
265                    );
266                    if is_copula {
267                        self.advance(); // consume copula
268
269                        // Check for negation: "is not"
270                        let is_negated = self.match_token(&[TokenType::Not]);
271
272                        // Try to get an adjective
273                        if let TokenType::Adjective(adj) = self.peek().kind {
274                            self.advance(); // consume adjective
275
276                            // Create predicate for each NP
277                            let pred1 = self.ctx.exprs.alloc(LogicExpr::Predicate {
278                                name: adj,
279                                args: self.ctx.terms.alloc_slice(vec![
280                                    Term::Constant(name1)
281                                ]),
282                                world: None,
283                            });
284                            let pred2 = self.ctx.exprs.alloc(LogicExpr::Predicate {
285                                name: adj,
286                                args: self.ctx.terms.alloc_slice(vec![
287                                    Term::Constant(name2)
288                                ]),
289                                world: None,
290                            });
291
292                            // Apply negation if needed
293                            let left = if is_negated {
294                                self.ctx.exprs.alloc(LogicExpr::UnaryOp {
295                                    op: TokenType::Not,
296                                    operand: pred1,
297                                })
298                            } else {
299                                pred1
300                            };
301                            let right = if is_negated {
302                                self.ctx.exprs.alloc(LogicExpr::UnaryOp {
303                                    op: TokenType::Not,
304                                    operand: pred2,
305                                })
306                            } else {
307                                pred2
308                            };
309
310                            return Ok(self.ctx.exprs.alloc(LogicExpr::BinaryOp {
311                                left,
312                                op: TokenType::Or,
313                                right,
314                            }));
315                        }
316                    }
317                }
318            }
319
320            // Backtrack if the special case didn't match
321            self.current = start_pos;
322        }
323
324        // Fall back to general disjunction parsing
325        // Enter disjunct box for left side - referents here are inaccessible outward
326        self.drs.enter_box(BoxType::Disjunct);
327        let left = self.parse_conjunction()?;
328        self.drs.exit_box();
329
330        if !self.check(&TokenType::Or) {
331            return Err(ParseError {
332                kind: ParseErrorKind::ExpectedKeyword { keyword: "or".to_string() },
333                span: self.current_span(),
334            });
335        }
336        self.advance(); // consume "or"
337
338        // Enter disjunct box for right side - referents here are also inaccessible outward
339        self.drs.enter_box(BoxType::Disjunct);
340        let right = self.parse_conjunction()?;
341        self.drs.exit_box();
342
343        Ok(self.ctx.exprs.alloc(LogicExpr::BinaryOp {
344            left,
345            op: TokenType::Or,
346            right,
347        }))
348    }
349
350    fn is_counterfactual_context(&self) -> bool {
351        for i in 0..5 {
352            if self.current + i >= self.tokens.len() {
353                break;
354            }
355            let token = &self.tokens[self.current + i];
356            if matches!(token.kind, TokenType::Were | TokenType::Had) {
357                return true;
358            }
359            if matches!(token.kind, TokenType::Comma | TokenType::Period) {
360                break;
361            }
362        }
363        false
364    }
365
366    fn parse_counterfactual_antecedent(&mut self) -> ParseResult<&'a LogicExpr<'a>> {
367        let unknown = self.interner.intern("?");
368        if self.check_content_word() || self.check_pronoun() || self.check_article() {
369            // Weather verb detection: "if it rains" → ∃e(Rain(e))
370            // Must check BEFORE pronoun resolution since "it" would resolve to "?"
371            if self.check_pronoun() {
372                let token = self.peek();
373                let token_text = self.interner.resolve(token.lexeme);
374                if token_text.eq_ignore_ascii_case("it") {
375                    // Look ahead for weather verb: "it rains" or "it is raining"
376                    if self.current + 1 < self.tokens.len() {
377                        // Check for "it + verb" pattern
378                        if let TokenType::Verb { lemma, time, .. } = &self.tokens[self.current + 1].kind {
379                            let lemma_str = self.interner.resolve(*lemma);
380                            if Lexer::is_weather_verb(lemma_str) {
381                                let verb = *lemma;
382                                let verb_time = *time;
383                                self.advance(); // consume "it"
384                                self.advance(); // consume weather verb
385
386                                let event_var = self.get_event_var();
387
388                                // Weather verbs are impersonal - no pronoun resolution needed
389                                // Event var gets universal force from transpiler when suppress_existential=true
390                                let suppress_existential = self.drs.in_conditional_antecedent();
391
392                                let mut result: &'a LogicExpr<'a> = self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
393                                    event_var,
394                                    verb,
395                                    roles: self.ctx.roles.alloc_slice(vec![]),
396                                    modifiers: self.ctx.syms.alloc_slice(vec![]),
397                                    suppress_existential,
398                                    world: None,
399                                })));
400
401                                // Handle coordinated weather verbs: "rains and thunders" or "rains or thunders"
402                                // SHARE the same event_var for all coordinated verbs
403                                while self.check(&TokenType::And) || self.check(&TokenType::Or) {
404                                    let is_disjunction = self.check(&TokenType::Or);
405                                    self.advance(); // consume "and" or "or"
406
407                                    if let TokenType::Verb { lemma: lemma2, .. } = &self.peek().kind.clone() {
408                                        let lemma2_str = self.interner.resolve(*lemma2);
409                                        if Lexer::is_weather_verb(lemma2_str) {
410                                            let verb2 = *lemma2;
411                                            self.advance(); // consume second weather verb
412
413                                            // REUSE same event_var - no new variable, no DRS registration
414                                            let neo_event2 = self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
415                                                event_var,  // Same variable as first weather verb
416                                                verb: verb2,
417                                                roles: self.ctx.roles.alloc_slice(vec![]),
418                                                modifiers: self.ctx.syms.alloc_slice(vec![]),
419                                                suppress_existential,
420                                                world: None,
421                                            })));
422
423                                            let op = if is_disjunction { TokenType::Or } else { TokenType::And };
424                                            result = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
425                                                left: result,
426                                                op,
427                                                right: neo_event2,
428                                            });
429                                        } else {
430                                            break; // Not a weather verb, stop coordination
431                                        }
432                                    } else {
433                                        break;
434                                    }
435                                }
436
437                                return Ok(match verb_time {
438                                    Time::Past => self.ctx.exprs.alloc(LogicExpr::Temporal {
439                                        operator: TemporalOperator::Past,
440                                        body: result,
441                                    }),
442                                    Time::Future => self.ctx.exprs.alloc(LogicExpr::Temporal {
443                                        operator: TemporalOperator::Future,
444                                        body: result,
445                                    }),
446                                    _ => result,
447                                });
448                            }
449                        }
450                        // Check for "it + is/are + verb" pattern: "it is raining"
451                        else if self.current + 2 < self.tokens.len() {
452                            let is_copula = matches!(
453                                self.tokens[self.current + 1].kind,
454                                TokenType::Is | TokenType::Are | TokenType::Was | TokenType::Were
455                            );
456                            if is_copula {
457                                if let TokenType::Verb { lemma, .. } = &self.tokens[self.current + 2].kind {
458                                    let lemma_str = self.interner.resolve(*lemma);
459                                    if Lexer::is_weather_verb(lemma_str) {
460                                        let verb = *lemma;
461                                        let verb_time = if matches!(
462                                            self.tokens[self.current + 1].kind,
463                                            TokenType::Was | TokenType::Were
464                                        ) {
465                                            Time::Past
466                                        } else {
467                                            Time::Present
468                                        };
469                                        self.advance(); // consume "it"
470                                        self.advance(); // consume "is/are/was/were"
471                                        self.advance(); // consume weather verb
472
473                                        let event_var = self.get_event_var();
474                                        // Weather verbs are impersonal - no pronoun resolution needed
475                                        // Event var gets universal force from transpiler when suppress_existential=true
476                                        let suppress_existential = self.drs.in_conditional_antecedent();
477
478                                        let neo_event = self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
479                                            event_var,
480                                            verb,
481                                            roles: self.ctx.roles.alloc_slice(vec![]),
482                                            modifiers: self.ctx.syms.alloc_slice(vec![]),
483                                            suppress_existential,
484                                            world: None,
485                                        })));
486
487                                        // Progressive aspect for "is raining"
488                                        let with_aspect = self.ctx.exprs.alloc(LogicExpr::Aspectual {
489                                            operator: AspectOperator::Progressive,
490                                            body: neo_event,
491                                        });
492
493                                        return Ok(match verb_time {
494                                            Time::Past => self.ctx.exprs.alloc(LogicExpr::Temporal {
495                                                operator: TemporalOperator::Past,
496                                                body: with_aspect,
497                                            }),
498                                            _ => with_aspect,
499                                        });
500                                    }
501                                }
502                            }
503                        }
504                    }
505                }
506            }
507
508            // Track if subject is an indefinite that needs DRS registration
509            let (subject, subject_type_pred) = if self.check_pronoun() {
510                let token = self.advance().clone();
511                let token_text = self.interner.resolve(token.lexeme);
512                // Handle first/second person pronouns as constants (deictic reference)
513                let resolved = if token_text.eq_ignore_ascii_case("i") {
514                    self.interner.intern("Speaker")
515                } else if token_text.eq_ignore_ascii_case("you") {
516                    self.interner.intern("Addressee")
517                } else if let TokenType::Pronoun { gender, number, .. } = token.kind {
518                    let resolved_pronoun = self.resolve_pronoun(gender, number)?;
519                    match resolved_pronoun {
520                        super::ResolvedPronoun::Variable(s) | super::ResolvedPronoun::Constant(s) => s,
521                    }
522                } else {
523                    unknown
524                };
525                (resolved, None)
526            } else {
527                let np = self.parse_noun_phrase(true)?;
528
529                // Check if this NP should introduce a DRS referent
530                // Both indefinites ("a dog") and definites ("the dog") introduce referents
531                // For definites without antecedent, this implements "global accommodation"
532                if np.definiteness == Some(Definiteness::Indefinite)
533                    || np.definiteness == Some(Definiteness::Definite)
534                    || np.definiteness == Some(Definiteness::Distal) {
535                    let gender = Self::infer_noun_gender(self.interner.resolve(np.noun));
536                    let number = if Self::is_plural_noun(self.interner.resolve(np.noun)) {
537                        Number::Plural
538                    } else {
539                        Number::Singular
540                    };
541
542                    // Register in DRS using noun as variable (for pronoun resolution)
543                    // For DEFINITES ("the X"), use MainClause source to avoid universal force
544                    // This ensures "the butler" in conditionals is treated as a constant
545                    // For INDEFINITES ("a X"), use default source (gets universal force in antecedent)
546                    if np.definiteness == Some(Definiteness::Definite) || np.definiteness == Some(Definiteness::Distal) {
547                        self.drs.introduce_referent_with_source(np.noun, np.noun, gender, number, crate::drs::ReferentSource::MainClause);
548                    } else {
549                        self.drs.introduce_referent(np.noun, np.noun, gender, number);
550                    }
551
552                    // Create type predicate: Farmer(noun)
553                    let type_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
554                        name: np.noun,
555                        args: self.ctx.terms.alloc_slice([Term::Variable(np.noun)]),
556                        world: None,
557                    });
558
559                    (np.noun, Some(type_pred))
560                } else {
561                    // Proper name - use as constant (proper names have their own registration)
562                    (np.noun, None)
563                }
564            };
565
566            // Determine the subject term type
567            let subject_term = if subject_type_pred.is_some() {
568                Term::Variable(subject)
569            } else {
570                Term::Constant(subject)
571            };
572
573            // Handle presupposition triggers in antecedent: "If John stopped smoking, ..."
574            // Only trigger if followed by gerund complement
575            if self.check_presup_trigger() && self.is_followed_by_gerund() {
576                let presup_kind = match self.advance().kind {
577                    TokenType::PresupTrigger(kind) => kind,
578                    TokenType::Verb { lemma, .. } => {
579                        let s = self.interner.resolve(lemma).to_lowercase();
580                        crate::lexicon::lookup_presup_trigger(&s)
581                            .expect("Lexicon mismatch: Verb flagged as trigger but lookup failed")
582                    }
583                    _ => panic!("Expected presupposition trigger"),
584                };
585                let np = NounPhrase {
586                    noun: subject,
587                    definiteness: None,
588                    adjectives: &[],
589                    possessor: None,
590                    pps: &[],
591                    superlative: None,
592                };
593                return self.parse_presupposition(&np, presup_kind);
594            }
595
596            if self.check(&TokenType::Were) {
597                self.advance();
598                let predicate = if self.check_pronoun() {
599                    let token = self.advance().clone();
600                    if let TokenType::Pronoun { gender, number, .. } = token.kind {
601                        let token_text = self.interner.resolve(token.lexeme);
602                        if token_text.eq_ignore_ascii_case("i") {
603                            self.interner.intern("Speaker")
604                        } else if token_text.eq_ignore_ascii_case("you") {
605                            self.interner.intern("Addressee")
606                        } else {
607                            let resolved_pronoun = self.resolve_pronoun(gender, number)?;
608                            match resolved_pronoun {
609                                super::ResolvedPronoun::Variable(s) | super::ResolvedPronoun::Constant(s) => s,
610                            }
611                        }
612                    } else {
613                        unknown
614                    }
615                } else {
616                    self.consume_content_word()?
617                };
618                let be = self.interner.intern("Be");
619                let be_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
620                    name: be,
621                    args: self.ctx.terms.alloc_slice([
622                        subject_term,
623                        Term::Constant(predicate),
624                    ]),
625                    world: None,
626                });
627                // Combine with type predicate if indefinite subject
628                return Ok(if let Some(type_pred) = subject_type_pred {
629                    self.ctx.exprs.alloc(LogicExpr::BinaryOp {
630                        left: type_pred,
631                        op: TokenType::And,
632                        right: be_pred,
633                    })
634                } else {
635                    be_pred
636                });
637            }
638
639            if self.check(&TokenType::Had) {
640                self.advance();
641                let verb = self.consume_content_word()?;
642                let main_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
643                    name: verb,
644                    args: self.ctx.terms.alloc_slice([subject_term]),
645                    world: None,
646                });
647
648                // Handle "because" causal clause in antecedent
649                // Phase 35: Do NOT consume if followed by string literal (Trust justification)
650                if self.check(&TokenType::Because) && !self.peek_next_is_string_literal() {
651                    self.advance();
652                    let cause = self.parse_atom()?;
653                    let causal = self.ctx.exprs.alloc(LogicExpr::Causal {
654                        effect: main_pred,
655                        cause,
656                    });
657                    // Combine with type predicate if indefinite subject
658                    return Ok(if let Some(type_pred) = subject_type_pred {
659                        self.ctx.exprs.alloc(LogicExpr::BinaryOp {
660                            left: type_pred,
661                            op: TokenType::And,
662                            right: causal,
663                        })
664                    } else {
665                        causal
666                    });
667                }
668
669                // Combine with type predicate if indefinite subject
670                return Ok(if let Some(type_pred) = subject_type_pred {
671                    self.ctx.exprs.alloc(LogicExpr::BinaryOp {
672                        left: type_pred,
673                        op: TokenType::And,
674                        right: main_pred,
675                    })
676                } else {
677                    main_pred
678                });
679            }
680
681            // Parse verb phrase with subject
682            // Use variable term for indefinite subjects, constant for definites/proper names
683            let verb_phrase = if subject_type_pred.is_some() {
684                self.parse_predicate_with_subject_as_var(subject)?
685            } else {
686                self.parse_predicate_with_subject(subject)?
687            };
688
689            // Combine with type predicate if indefinite subject
690            return Ok(if let Some(type_pred) = subject_type_pred {
691                self.ctx.exprs.alloc(LogicExpr::BinaryOp {
692                    left: type_pred,
693                    op: TokenType::And,
694                    right: verb_phrase,
695                })
696            } else {
697                verb_phrase
698            });
699        }
700
701        self.parse_sentence()
702    }
703
704    fn parse_counterfactual_consequent(&mut self) -> ParseResult<&'a LogicExpr<'a>> {
705        let unknown = self.interner.intern("?");
706        if self.check_content_word() || self.check_pronoun() {
707            // Check for grammatically incorrect "its" + weather adjective
708            // "its" is possessive, "it's" is contraction - common typo
709            if self.check_pronoun() {
710                let token = self.peek();
711                let token_text = self.interner.resolve(token.lexeme).to_lowercase();
712                if token_text == "its" {
713                    // Check if followed by weather adjective
714                    if self.current + 1 < self.tokens.len() {
715                        let next_token = &self.tokens[self.current + 1];
716                        let next_str = self.interner.resolve(next_token.lexeme).to_lowercase();
717                        if let Some(meta) = crate::lexicon::lookup_adjective_db(&next_str) {
718                            if meta.features.contains(&crate::lexicon::Feature::Weather) {
719                                return Err(ParseError {
720                                    kind: ParseErrorKind::GrammarError(
721                                        "Did you mean 'it's' (it is)? 'its' is a possessive pronoun.".to_string()
722                                    ),
723                                    span: self.current_span(),
724                                });
725                            }
726                        }
727                    }
728                }
729            }
730
731            // Check for expletive "it" + copula + weather adjective: "it's wet" → Wet
732            if self.check_pronoun() {
733                let token_text = self.interner.resolve(self.peek().lexeme).to_lowercase();
734                if token_text == "it" {
735                    // Look ahead for copula + weather adjective
736                    // Handle both "it is wet" and "it's wet" (where 's is Possessive token)
737                    if self.current + 2 < self.tokens.len() {
738                        let next = &self.tokens[self.current + 1].kind;
739                        if matches!(next, TokenType::Is | TokenType::Was | TokenType::Possessive) {
740                            // Check if followed by weather adjective
741                            let adj_token = &self.tokens[self.current + 2];
742                            let adj_sym = adj_token.lexeme;
743                            let adj_str = self.interner.resolve(adj_sym).to_lowercase();
744                            if let Some(meta) = crate::lexicon::lookup_adjective_db(&adj_str) {
745                                if meta.features.contains(&crate::lexicon::Feature::Weather) {
746                                    self.advance(); // consume "it"
747                                    self.advance(); // consume copula
748                                    self.advance(); // consume adjective token
749
750                                    // Use the canonical lemma from lexicon (e.g., "Wet" not "wet")
751                                    let adj_lemma = self.interner.intern(meta.lemma);
752
753                                    // Get event variable from DRS (introduced in antecedent)
754                                    let event_var = self.drs.get_last_event_referent(self.interner)
755                                        .unwrap_or_else(|| self.interner.intern("e"));
756
757                                    // First weather adjective predicate
758                                    let mut result: &'a LogicExpr<'a> = self.ctx.exprs.alloc(LogicExpr::Predicate {
759                                        name: adj_lemma,
760                                        args: self.ctx.terms.alloc_slice([Term::Variable(event_var)]),
761                                        world: None,
762                                    });
763
764                                    // Handle coordinated adjectives: "wet and cold"
765                                    while self.check(&TokenType::And) {
766                                        self.advance(); // consume "and"
767                                        if self.check_content_word() {
768                                            let adj2_lexeme = self.peek().lexeme;
769                                            let adj2_str = self.interner.resolve(adj2_lexeme).to_lowercase();
770
771                                            // Check if it's also a weather adjective
772                                            if let Some(meta2) = crate::lexicon::lookup_adjective_db(&adj2_str) {
773                                                if meta2.features.contains(&crate::lexicon::Feature::Weather) {
774                                                    self.advance(); // consume adjective token
775                                                    // Use the canonical lemma from lexicon (e.g., "Cold" not "cold")
776                                                    let adj2_lemma = self.interner.intern(meta2.lemma);
777                                                    let pred2 = self.ctx.exprs.alloc(LogicExpr::Predicate {
778                                                        name: adj2_lemma,
779                                                        args: self.ctx.terms.alloc_slice([Term::Variable(event_var)]),
780                                                        world: None,
781                                                    });
782                                                    result = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
783                                                        left: result,
784                                                        op: TokenType::And,
785                                                        right: pred2,
786                                                    });
787                                                    continue;
788                                                }
789                                            }
790                                        }
791                                        break;
792                                    }
793
794                                    return Ok(result);
795                                }
796                            }
797                        }
798                    }
799                }
800            }
801
802            let subject = if self.check_pronoun() {
803                let token = self.advance().clone();
804                let token_text = self.interner.resolve(token.lexeme);
805                // Handle first/second person pronouns as constants (deictic reference)
806                if token_text.eq_ignore_ascii_case("i") {
807                    self.interner.intern("Speaker")
808                } else if token_text.eq_ignore_ascii_case("you") {
809                    self.interner.intern("Addressee")
810                } else if let TokenType::Pronoun { gender, number, .. } = token.kind {
811                    let resolved_pronoun = self.resolve_pronoun(gender, number)?;
812                    match resolved_pronoun {
813                        super::ResolvedPronoun::Variable(s) | super::ResolvedPronoun::Constant(s) => s,
814                    }
815                } else {
816                    unknown
817                }
818            } else {
819                self.parse_noun_phrase(true)?.noun
820            };
821
822            if self.check(&TokenType::Would) {
823                self.advance();
824                if self.check_content_word() {
825                    let next_word = self.interner.resolve(self.peek().lexeme).to_lowercase();
826                    if next_word == "have" {
827                        self.advance();
828                    }
829                }
830                let verb = self.consume_content_word()?;
831                return Ok(self.ctx.exprs.alloc(LogicExpr::Predicate {
832                    name: verb,
833                    args: self.ctx.terms.alloc_slice([Term::Constant(subject)]),
834                    world: None,
835                }));
836            }
837
838            return self.parse_predicate_with_subject(subject);
839        }
840
841        self.parse_sentence()
842    }
843
844    fn extract_verb_from_expr(&self, expr: &LogicExpr<'a>) -> Option<Symbol> {
845        match expr {
846            // NeoEvent directly contains the verb
847            LogicExpr::NeoEvent(data) => Some(data.verb),
848            // Control structures directly contain the verb
849            LogicExpr::Control { verb, .. } => Some(*verb),
850            // Phase 46: For BinaryOp, try to find NeoEvent first (either side),
851            // then fall back to Predicate. This handles both:
852            // - Transitive: Apple(x) ∧ ∃e(Eat(e)...) - NeoEvent on right
853            // - Motion PP: ∃e(Walk(e)...) ∧ To(e, Park) - NeoEvent on left
854            LogicExpr::BinaryOp { left, right, .. } => {
855                // First check if left contains a NeoEvent (motion PP case)
856                if let Some(verb) = self.extract_neo_event_verb(left) {
857                    return Some(verb);
858                }
859                // Then check right (transitive case with type predicate on left)
860                if let Some(verb) = self.extract_neo_event_verb(right) {
861                    return Some(verb);
862                }
863                // Fall back to any extractable verb
864                self.extract_verb_from_expr(left)
865                    .or_else(|| self.extract_verb_from_expr(right))
866            }
867            // Plain predicate - last resort (might be type predicate or PP)
868            LogicExpr::Predicate { name, .. } => Some(*name),
869            LogicExpr::Modal { operand, .. } => self.extract_verb_from_expr(operand),
870            LogicExpr::Presupposition { assertion, .. } => self.extract_verb_from_expr(assertion),
871            LogicExpr::Temporal { body, .. } => self.extract_verb_from_expr(body),
872            LogicExpr::TemporalAnchor { body, .. } => self.extract_verb_from_expr(body),
873            LogicExpr::Aspectual { body, .. } => self.extract_verb_from_expr(body),
874            LogicExpr::Quantifier { body, .. } => self.extract_verb_from_expr(body),
875            _ => None,
876        }
877    }
878
879    /// Phase 46: Generalized gapping with template-guided reconstruction.
880    /// Handles NPs, PPs, temporal adverbs, and preserves roles from EventTemplate.
881    fn parse_gapped_clause(&mut self, borrowed_verb: Symbol) -> ParseResult<&'a LogicExpr<'a>> {
882        let subject = self.parse_noun_phrase(true)?;
883
884        if self.check(&TokenType::Comma) {
885            self.advance();
886        }
887
888        let subject_term = self.noun_phrase_to_term(&subject);
889        let event_var = self.get_event_var();
890        let suppress_existential = self.drs.in_conditional_antecedent();
891
892        // Get template for role guidance
893        let template = self.last_event_template.clone();
894
895        // Collect arguments (NPs, PPs, temporal adverbs) from gapped clause
896        let mut np_args: Vec<Term<'a>> = Vec::new();
897        let mut pp_args: Vec<(Symbol, Term<'a>)> = Vec::new();
898        let mut override_adverb: Option<Symbol> = None;
899
900        loop {
901            if self.check_temporal_adverb() {
902                // Temporal adverb: override template modifier
903                if let TokenType::TemporalAdverb(sym) = self.advance().kind {
904                    override_adverb = Some(sym);
905                }
906            } else if self.check_preposition() {
907                // PP argument: "to the school", "on the table"
908                let prep = if let TokenType::Preposition(sym) = self.advance().kind {
909                    sym
910                } else {
911                    continue;
912                };
913                let np = self.parse_noun_phrase(false)?;
914                pp_args.push((prep, self.noun_phrase_to_term(&np)));
915            } else if self.check_content_word() || self.check_article() {
916                // NP argument
917                let np = self.parse_noun_phrase(false)?;
918                np_args.push(self.noun_phrase_to_term(&np));
919                if self.check(&TokenType::Comma) {
920                    self.advance();
921                }
922            } else {
923                break;
924            }
925        }
926
927        // Build roles using template guidance
928        let roles = self.build_gapped_roles(subject_term, &np_args, &pp_args, &template);
929
930        // Handle modifiers: override if adverb provided, else inherit from template
931        let modifiers = match (override_adverb, &template) {
932            (Some(adv), Some(tmpl)) => {
933                // Filter out temporal modifiers from template, add new one
934                let mut mods: Vec<Symbol> = tmpl
935                    .modifiers
936                    .iter()
937                    .filter(|m| !self.is_temporal_modifier(**m))
938                    .cloned()
939                    .collect();
940                mods.push(adv);
941                mods
942            }
943            (Some(adv), None) => vec![adv],
944            (None, Some(tmpl)) => tmpl.modifiers.clone(),
945            (None, None) => vec![],
946        };
947
948        Ok(self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
949            event_var,
950            verb: borrowed_verb,
951            roles: self.ctx.roles.alloc_slice(roles),
952            modifiers: self.ctx.syms.alloc_slice(modifiers),
953            suppress_existential,
954            world: None,
955        }))))
956    }
957
958    fn is_complete_clause(&self, expr: &LogicExpr<'a>) -> bool {
959        match expr {
960            LogicExpr::Atom(_) => false,
961            LogicExpr::Predicate { .. } => true,
962            LogicExpr::Quantifier { .. } => true,
963            LogicExpr::Modal { .. } => true,
964            LogicExpr::Temporal { .. } => true,
965            LogicExpr::Aspectual { .. } => true,
966            LogicExpr::BinaryOp { .. } => true,
967            LogicExpr::UnaryOp { .. } => true,
968            LogicExpr::Control { .. } => true,
969            LogicExpr::Presupposition { .. } => true,
970            LogicExpr::Categorical(_) => true,
971            LogicExpr::Relation(_) => true,
972            _ => true,
973        }
974    }
975
976    /// Parse disjunction (Or/Iff) - lowest precedence logical connectives.
977    /// Calls parse_conjunction for operands to ensure And binds tighter.
978    fn parse_disjunction(&mut self) -> ParseResult<&'a LogicExpr<'a>> {
979        let mut expr = self.parse_conjunction()?;
980
981        while self.check(&TokenType::Comma)
982            || self.check(&TokenType::Or)
983            || self.check(&TokenType::Iff)
984        {
985            if self.check(&TokenType::Comma) {
986                self.advance();
987            }
988            if !self.match_token(&[TokenType::Or, TokenType::Iff]) {
989                break;
990            }
991            let operator = self.previous().kind.clone();
992            self.current_island += 1;
993
994            let saved_pos = self.current;
995            let standard_attempt = self.try_parse(|p| p.parse_conjunction());
996
997            // Gapping in disjunction: only for Or, not Iff. Use original (non-expanded) trigger.
998            // Expanded gapping (with Period/is_at_end) only applies in parse_conjunction.
999            let use_gapping = match &standard_attempt {
1000                Some(right) => {
1001                    !self.is_complete_clause(right)
1002                        && (self.check(&TokenType::Comma) || self.check_content_word())
1003                        && operator != TokenType::Iff // Don't gap on biconditional
1004                }
1005                None => operator != TokenType::Iff, // For Iff, require successful parse
1006            };
1007
1008            if !use_gapping {
1009                if let Some(right) = standard_attempt {
1010                    expr = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1011                        left: expr,
1012                        op: operator,
1013                        right,
1014                    });
1015                }
1016            } else {
1017                self.current = saved_pos;
1018
1019                let borrowed_verb = self.extract_verb_from_expr(expr).ok_or(ParseError {
1020                    kind: ParseErrorKind::GappingResolutionFailed,
1021                    span: self.current_span(),
1022                })?;
1023
1024                let right = self.parse_gapped_clause(borrowed_verb)?;
1025
1026                expr = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1027                    left: expr,
1028                    op: operator,
1029                    right,
1030                });
1031            }
1032        }
1033
1034        Ok(expr)
1035    }
1036
1037    /// Parse conjunction (And) - higher precedence than Or.
1038    /// Calls parse_atom for operands.
1039    fn parse_conjunction(&mut self) -> ParseResult<&'a LogicExpr<'a>> {
1040        let mut expr = self.parse_atom()?;
1041
1042        // Handle causal "because" at conjunction level
1043        // Phase 35: Do NOT consume if followed by string literal (Trust justification)
1044        if self.check(&TokenType::Because) && !self.peek_next_is_string_literal() {
1045            self.advance();
1046            let cause = self.parse_atom()?;
1047            return Ok(self.ctx.exprs.alloc(LogicExpr::Causal {
1048                effect: expr,
1049                cause,
1050            }));
1051        }
1052
1053        while self.check(&TokenType::Comma) || self.check(&TokenType::And) {
1054            if self.check(&TokenType::Comma) {
1055                self.advance();
1056            }
1057            if !self.match_token(&[TokenType::And]) {
1058                break;
1059            }
1060            let operator = self.previous().kind.clone();
1061            self.current_island += 1;
1062
1063            let saved_pos = self.current;
1064            let standard_attempt = self.try_parse(|p| p.parse_atom());
1065
1066            // Phase 46: Expanded gapping trigger to support PP gapping, temporal override,
1067            // and intransitive gapping (bare subject at clause boundary)
1068            let use_gapping = match &standard_attempt {
1069                Some(right) => {
1070                    !self.is_complete_clause(right)
1071                        && (self.check(&TokenType::Comma)
1072                            || self.check_content_word()
1073                            || self.check_preposition()
1074                            || self.check_temporal_adverb()
1075                            || self.check(&TokenType::Period)
1076                            || self.is_at_end())
1077                }
1078                None => true,
1079            };
1080
1081            if !use_gapping {
1082                if let Some(right) = standard_attempt {
1083                    expr = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1084                        left: expr,
1085                        op: operator,
1086                        right,
1087                    });
1088                }
1089            } else {
1090                self.current = saved_pos;
1091
1092                let borrowed_verb = self.extract_verb_from_expr(expr).ok_or(ParseError {
1093                    kind: ParseErrorKind::GappingResolutionFailed,
1094                    span: self.current_span(),
1095                })?;
1096
1097                let right = self.parse_gapped_clause(borrowed_verb)?;
1098
1099                expr = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1100                    left: expr,
1101                    op: operator,
1102                    right,
1103                });
1104            }
1105        }
1106
1107        Ok(expr)
1108    }
1109
1110    fn parse_relative_clause(&mut self, gap_var: Symbol) -> ParseResult<&'a LogicExpr<'a>> {
1111        if self.check_verb() {
1112            return self.parse_verb_phrase_for_restriction(gap_var);
1113        }
1114
1115        // Handle "do/does (not)" in relative clauses: "who do not shave themselves"
1116        if self.check(&TokenType::Do) || self.check(&TokenType::Does) {
1117            self.advance(); // consume "do/does"
1118
1119            let is_negated = self.check(&TokenType::Not);
1120            if is_negated {
1121                self.advance(); // consume "not"
1122            }
1123
1124            if self.check_verb() {
1125                let verb = self.consume_verb();
1126
1127                // Check for reflexive object: "shave themselves"
1128                let roles = if self.check(&TokenType::Reflexive) {
1129                    self.advance(); // consume "themselves/himself"
1130                    vec![
1131                        (ThematicRole::Agent, Term::Variable(gap_var)),
1132                        (ThematicRole::Theme, Term::Variable(gap_var)),
1133                    ]
1134                } else if self.check_content_word() || self.check_article() {
1135                    // Parse object NP
1136                    let obj = self.parse_noun_phrase(false)?;
1137                    vec![
1138                        (ThematicRole::Agent, Term::Variable(gap_var)),
1139                        (ThematicRole::Theme, Term::Constant(obj.noun)),
1140                    ]
1141                } else {
1142                    // Intransitive
1143                    vec![(ThematicRole::Agent, Term::Variable(gap_var))]
1144                };
1145
1146                let event_var = self.get_event_var();
1147                let suppress_existential = self.drs.in_conditional_antecedent();
1148                let event = self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
1149                    event_var,
1150                    verb,
1151                    roles: self.ctx.roles.alloc_slice(roles),
1152                    modifiers: self.ctx.syms.alloc_slice(vec![]),
1153                    suppress_existential,
1154                    world: None,
1155                })));
1156
1157                if is_negated {
1158                    return Ok(self.ctx.exprs.alloc(LogicExpr::UnaryOp {
1159                        op: TokenType::Not,
1160                        operand: event,
1161                    }));
1162                }
1163                return Ok(event);
1164            }
1165        }
1166
1167        if self.check_content_word() || self.check_article() {
1168            let rel_subject = self.parse_noun_phrase_for_relative()?;
1169
1170            let nested_relative = if matches!(self.peek().kind, TokenType::Article(_)) {
1171                let nested_var = self.next_var_name();
1172                Some((nested_var, self.parse_relative_clause(nested_var)?))
1173            } else {
1174                None
1175            };
1176
1177            if self.check_verb() {
1178                let verb = self.consume_verb();
1179
1180                let mut roles: Vec<(ThematicRole, Term<'a>)> = vec![
1181                    (ThematicRole::Agent, Term::Constant(rel_subject.noun)),
1182                    (ThematicRole::Theme, Term::Variable(gap_var)),
1183                ];
1184
1185                while self.check_to_preposition() {
1186                    self.advance();
1187                    if self.check_content_word() || self.check_article() {
1188                        let recipient = self.parse_noun_phrase(false)?;
1189                        roles.push((ThematicRole::Recipient, Term::Constant(recipient.noun)));
1190                    }
1191                }
1192
1193                let event_var = self.get_event_var();
1194                let suppress_existential = self.drs.in_conditional_antecedent();
1195                let this_event = self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
1196                    event_var,
1197                    verb,
1198                    roles: self.ctx.roles.alloc_slice(roles),
1199                    modifiers: self.ctx.syms.alloc_slice(vec![]),
1200                    suppress_existential,
1201                    world: None,
1202                })));
1203
1204                if let Some((nested_var, nested_clause)) = nested_relative {
1205                    let type_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
1206                        name: rel_subject.noun,
1207                        args: self.ctx.terms.alloc_slice([Term::Variable(nested_var)]),
1208                        world: None,
1209                    });
1210
1211                    let inner = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1212                        left: type_pred,
1213                        op: TokenType::And,
1214                        right: nested_clause,
1215                    });
1216
1217                    let combined = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1218                        left: inner,
1219                        op: TokenType::And,
1220                        right: this_event,
1221                    });
1222
1223                    return Ok(self.ctx.exprs.alloc(LogicExpr::Quantifier {
1224                        kind: crate::ast::QuantifierKind::Existential,
1225                        variable: nested_var,
1226                        body: combined,
1227                        island_id: self.current_island,
1228                    }));
1229                }
1230
1231                return Ok(this_event);
1232            }
1233        }
1234
1235        if self.check_verb() {
1236            return self.parse_verb_phrase_for_restriction(gap_var);
1237        }
1238
1239        let unknown = self.interner.intern("?");
1240        Ok(self.ctx.exprs.alloc(LogicExpr::Atom(unknown)))
1241    }
1242
1243    fn check_ellipsis_auxiliary(&self) -> bool {
1244        matches!(
1245            self.peek().kind,
1246            TokenType::Does | TokenType::Do |
1247            TokenType::Can | TokenType::Could | TokenType::Would |
1248            TokenType::May | TokenType::Must | TokenType::Should
1249        )
1250    }
1251
1252    fn check_ellipsis_terminator(&self) -> bool {
1253        if self.is_at_end() || self.check(&TokenType::Period) {
1254            return true;
1255        }
1256        if self.check_content_word() {
1257            let word = self.interner.resolve(self.peek().lexeme).to_lowercase();
1258            return word == "too" || word == "also";
1259        }
1260        false
1261    }
1262
1263    fn try_parse_ellipsis(&mut self) -> Option<ParseResult<&'a LogicExpr<'a>>> {
1264        // Need a stored template to reconstruct from
1265        if self.last_event_template.is_none() {
1266            return None;
1267        }
1268
1269        let saved_pos = self.current;
1270
1271        // Pattern: Subject + Auxiliary + (not)? + Terminator
1272        // Subject must be proper name or pronoun
1273        let subject_sym = if matches!(self.peek().kind, TokenType::ProperName(_)) {
1274            if let TokenType::ProperName(sym) = self.advance().kind {
1275                sym
1276            } else {
1277                self.current = saved_pos;
1278                return None;
1279            }
1280        } else if self.check_pronoun() {
1281            let token = self.advance().clone();
1282            if let TokenType::Pronoun { gender, number, .. } = token.kind {
1283                match self.resolve_pronoun(gender, number) {
1284                    Ok(resolved) => match resolved {
1285                        super::ResolvedPronoun::Variable(s) | super::ResolvedPronoun::Constant(s) => s,
1286                    },
1287                    Err(e) => return Some(Err(e)),
1288                }
1289            } else {
1290                self.current = saved_pos;
1291                return None;
1292            }
1293        } else {
1294            return None;
1295        };
1296
1297        // Must be followed by ellipsis auxiliary
1298        if !self.check_ellipsis_auxiliary() {
1299            self.current = saved_pos;
1300            return None;
1301        }
1302        let aux_token = self.advance().kind.clone();
1303
1304        // Check for negation
1305        let is_negated = self.match_token(&[TokenType::Not]);
1306
1307        // Must end with terminator
1308        if !self.check_ellipsis_terminator() {
1309            self.current = saved_pos;
1310            return None;
1311        }
1312
1313        // Consume "too"/"also" if present
1314        if self.check_content_word() {
1315            let word = self.interner.resolve(self.peek().lexeme).to_lowercase();
1316            if word == "too" || word == "also" {
1317                self.advance();
1318            }
1319        }
1320
1321        // Reconstruct from template
1322        let template = self.last_event_template.clone().unwrap();
1323        let event_var = self.get_event_var();
1324        let suppress_existential = self.drs.in_conditional_antecedent();
1325
1326        // Build roles with new subject as Agent
1327        let mut roles: Vec<(ThematicRole, Term<'a>)> = vec![
1328            (ThematicRole::Agent, Term::Constant(subject_sym))
1329        ];
1330        roles.extend(template.non_agent_roles.iter().cloned());
1331
1332        let neo_event = self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
1333            event_var,
1334            verb: template.verb,
1335            roles: self.ctx.roles.alloc_slice(roles),
1336            modifiers: self.ctx.syms.alloc_slice(template.modifiers.clone()),
1337            suppress_existential,
1338            world: None,
1339        })));
1340
1341        // Apply modal if auxiliary is modal
1342        let with_modal = match aux_token {
1343            TokenType::Can | TokenType::Could => {
1344                let vector = self.token_to_vector(&aux_token);
1345                self.ctx.modal(vector, neo_event)
1346            }
1347            TokenType::Would | TokenType::May | TokenType::Must | TokenType::Should => {
1348                let vector = self.token_to_vector(&aux_token);
1349                self.ctx.modal(vector, neo_event)
1350            }
1351            _ => neo_event,
1352        };
1353
1354        // Apply negation if present
1355        let result = if is_negated {
1356            self.ctx.exprs.alloc(LogicExpr::UnaryOp {
1357                op: TokenType::Not,
1358                operand: with_modal,
1359            })
1360        } else {
1361            with_modal
1362        };
1363
1364        Some(Ok(result))
1365    }
1366}
1367
1368// Phase 46: Helper methods for generalized gapping (not part of trait)
1369impl<'a, 'ctx, 'int> Parser<'a, 'ctx, 'int> {
1370    /// Helper to extract verb specifically from NeoEvent structures
1371    fn extract_neo_event_verb(&self, expr: &LogicExpr<'a>) -> Option<Symbol> {
1372        match expr {
1373            LogicExpr::NeoEvent(data) => Some(data.verb),
1374            LogicExpr::Quantifier { body, .. } => self.extract_neo_event_verb(body),
1375            LogicExpr::BinaryOp { left, right, .. } => {
1376                self.extract_neo_event_verb(left)
1377                    .or_else(|| self.extract_neo_event_verb(right))
1378            }
1379            LogicExpr::Temporal { body, .. } => self.extract_neo_event_verb(body),
1380            LogicExpr::Aspectual { body, .. } => self.extract_neo_event_verb(body),
1381            _ => None,
1382        }
1383    }
1384
1385    /// Build roles for gapped clause using template guidance.
1386    /// NP args map to Theme/Recipient roles, PP args map by preposition type.
1387    fn build_gapped_roles(
1388        &self,
1389        subject_term: Term<'a>,
1390        np_args: &[Term<'a>],
1391        pp_args: &[(Symbol, Term<'a>)],
1392        template: &Option<EventTemplate<'a>>,
1393    ) -> Vec<(ThematicRole, Term<'a>)> {
1394        let mut roles = vec![(ThematicRole::Agent, subject_term)];
1395
1396        match template {
1397            Some(tmpl) => {
1398                let template_roles = &tmpl.non_agent_roles;
1399
1400                // Separate template roles into NP-type and PP-type
1401                let np_template_roles: Vec<_> = template_roles
1402                    .iter()
1403                    .filter(|(r, _)| {
1404                        matches!(
1405                            r,
1406                            ThematicRole::Theme | ThematicRole::Recipient | ThematicRole::Patient
1407                        )
1408                    })
1409                    .collect();
1410
1411                let pp_template_roles: Vec<_> = template_roles
1412                    .iter()
1413                    .filter(|(r, _)| {
1414                        matches!(
1415                            r,
1416                            ThematicRole::Goal
1417                                | ThematicRole::Source
1418                                | ThematicRole::Location
1419                                | ThematicRole::Instrument
1420                        )
1421                    })
1422                    .collect();
1423
1424                // Handle NPs by matching to template NP roles
1425                match (np_template_roles.len(), np_args.len()) {
1426                    (0, 0) => {} // Intransitive - no NP roles
1427                    (_, 0) => {
1428                        // Use all template NP roles unchanged
1429                        for (role, term) in &np_template_roles {
1430                            roles.push((*role, term.clone()));
1431                        }
1432                    }
1433                    (n, 1) if n > 0 => {
1434                        // 1 NP arg: replace LAST NP role (usually Theme), keep others
1435                        for (role, term) in np_template_roles.iter().take(n - 1) {
1436                            roles.push((*role, term.clone()));
1437                        }
1438                        if let Some((last_role, _)) = np_template_roles.last() {
1439                            roles.push((*last_role, np_args[0].clone()));
1440                        }
1441                    }
1442                    (n, m) if m == n => {
1443                        // Same count: replace all NP roles in order
1444                        for ((role, _), arg) in np_template_roles.iter().zip(np_args.iter()) {
1445                            roles.push((*role, arg.clone()));
1446                        }
1447                    }
1448                    (_, _) => {
1449                        // Fallback: assign Theme to each NP
1450                        for (i, arg) in np_args.iter().enumerate() {
1451                            let role = np_template_roles
1452                                .get(i)
1453                                .map(|(r, _)| *r)
1454                                .unwrap_or(ThematicRole::Theme);
1455                            roles.push((role, arg.clone()));
1456                        }
1457                    }
1458                }
1459
1460                // Handle PPs: use parsed PPs if provided, else use template
1461                if pp_args.is_empty() {
1462                    // Use template PP roles unchanged
1463                    for (role, term) in &pp_template_roles {
1464                        roles.push((*role, term.clone()));
1465                    }
1466                } else {
1467                    // Use parsed PPs, map preposition to role
1468                    for (prep, term) in pp_args {
1469                        let role = self.preposition_to_role(*prep);
1470                        roles.push((role, term.clone()));
1471                    }
1472                }
1473            }
1474            None => {
1475                // No template: backward-compat hardcoded Agent + Theme
1476                for arg in np_args {
1477                    roles.push((ThematicRole::Theme, arg.clone()));
1478                }
1479                for (prep, term) in pp_args {
1480                    let role = self.preposition_to_role(*prep);
1481                    roles.push((role, term.clone()));
1482                }
1483            }
1484        }
1485        roles
1486    }
1487
1488    /// Map preposition to thematic role
1489    fn preposition_to_role(&self, prep: Symbol) -> ThematicRole {
1490        let prep_str = self.interner.resolve(prep).to_lowercase();
1491        match prep_str.as_str() {
1492            "to" | "toward" | "towards" => ThematicRole::Goal,
1493            "from" => ThematicRole::Source,
1494            "in" | "on" | "at" => ThematicRole::Location,
1495            "with" | "by" => ThematicRole::Instrument,
1496            _ => ThematicRole::Location, // Default fallback
1497        }
1498    }
1499
1500    /// Check if modifier is temporal (for override filtering)
1501    fn is_temporal_modifier(&self, sym: Symbol) -> bool {
1502        let s = self.interner.resolve(sym).to_lowercase();
1503        matches!(
1504            s.as_str(),
1505            "yesterday" | "today" | "tomorrow" | "now" | "then" | "past" | "future"
1506        )
1507    }
1508}