Skip to main content

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