Skip to main content

logicaffeine_language/parser/
quantifier.rs

1//! Quantifier parsing and scope management.
2//!
3//! This module handles determiners with quantificational force:
4//!
5//! - **Universal**: every, all, each → `∀x`
6//! - **Existential**: a, an, some → `∃x`
7//! - **Negative**: no, neither → `¬∃x` or `∀x(... → ¬...)`
8//! - **Proportional**: most, few, many → generalized quantifiers
9//! - **Definite**: the → uniqueness presupposition (ιx)
10//!
11//! # Quantifier Scope
12//!
13//! Quantifiers are assigned to scope islands during parsing. The `island_id` field
14//! tracks which island a quantifier belongs to, preventing illicit scope inversions
15//! (e.g., extracting from relative clauses).
16//!
17//! # Donkey Anaphora
18//!
19//! Indefinites in conditional antecedents or relative clauses receive universal
20//! force when bound by a pronoun in the main clause:
21//!
22//! "If a farmer owns a donkey, he beats it" → `∀x∀y((Farmer(x) ∧ Donkey(y) ∧ Owns(x,y)) → Beats(x,y))`
23
24use super::clause::ClauseParsing;
25use super::modal::ModalParsing;
26use super::noun::NounParsing;
27use super::pragmatics::PragmaticsParsing;
28use super::{NegativeScopeMode, ParseResult, Parser};
29use crate::ast::{LogicExpr, NeoEventData, NounPhrase, QuantifierKind, Term, ThematicRole};
30use crate::drs::{Gender, Number};
31use crate::drs::ReferentSource;
32use crate::error::{ParseError, ParseErrorKind};
33use logicaffeine_base::Symbol;
34use crate::lexer::Lexer;
35use crate::lexicon::{
36    get_canonical_verb, is_subsective, lookup_relational_adjective, lookup_verb_db, Definiteness,
37    Feature, Time,
38};
39use crate::token::{PresupKind, TokenType};
40
41/// Trait for parsing quantified expressions and managing scope.
42///
43/// Provides methods for parsing quantifiers (every, some, no, most),
44/// their restrictions, and wrapping expressions with appropriate scope.
45pub trait QuantifierParsing<'a, 'ctx, 'int> {
46    /// Parses a quantified expression from a quantifier determiner.
47    fn parse_quantified(&mut self) -> ParseResult<&'a LogicExpr<'a>>;
48
49    /// The quantifier-parsing body; `parse_quantified` wraps its result with any pending
50    /// partitive-superset presupposition (§5.3).
51    fn parse_quantified_core(&mut self) -> ParseResult<&'a LogicExpr<'a>>;
52    /// Parses the restrictor clause for a quantifier.
53    fn parse_restriction(&mut self, var_name: Symbol) -> ParseResult<&'a LogicExpr<'a>>;
54    /// Builds the restriction conjunct for one pre-nominal adjective, dispatching
55    /// on its lexical class (relational/subsective/intersective). Shared by every
56    /// NP-restriction path so all paths model adjective classes identically.
57    fn adjective_restriction(&mut self, adj: Symbol, var: Symbol, noun: Symbol) -> &'a LogicExpr<'a>;
58    /// Parses a verb phrase as the nuclear scope of a quantifier.
59    fn parse_verb_phrase_for_restriction(&mut self, var_name: Symbol) -> ParseResult<&'a LogicExpr<'a>>;
60    /// Combines multiple expressions with conjunction.
61    fn combine_with_and(&self, exprs: Vec<&'a LogicExpr<'a>>) -> ParseResult<&'a LogicExpr<'a>>;
62    fn wrap_with_definiteness_full(
63        &mut self,
64        np: &NounPhrase<'a>,
65        predicate: &'a LogicExpr<'a>,
66    ) -> ParseResult<&'a LogicExpr<'a>>;
67    fn wrap_with_definiteness(
68        &mut self,
69        definiteness: Option<Definiteness>,
70        noun: Symbol,
71        predicate: &'a LogicExpr<'a>,
72    ) -> ParseResult<&'a LogicExpr<'a>>;
73    fn wrap_with_definiteness_and_adjectives(
74        &mut self,
75        definiteness: Option<Definiteness>,
76        noun: Symbol,
77        adjectives: &[Symbol],
78        predicate: &'a LogicExpr<'a>,
79    ) -> ParseResult<&'a LogicExpr<'a>>;
80    fn wrap_with_definiteness_and_adjectives_and_pps(
81        &mut self,
82        definiteness: Option<Definiteness>,
83        noun: Symbol,
84        adjectives: &[Symbol],
85        pps: &[&'a LogicExpr<'a>],
86        predicate: &'a LogicExpr<'a>,
87    ) -> ParseResult<&'a LogicExpr<'a>>;
88    fn wrap_with_definiteness_for_object(
89        &mut self,
90        definiteness: Option<Definiteness>,
91        noun: Symbol,
92        predicate: &'a LogicExpr<'a>,
93    ) -> ParseResult<&'a LogicExpr<'a>>;
94    fn substitute_pp_placeholder(&mut self, pp: &'a LogicExpr<'a>, var: Symbol) -> &'a LogicExpr<'a>;
95    fn substitute_constant_with_var(
96        &self,
97        expr: &'a LogicExpr<'a>,
98        constant_name: Symbol,
99        var_name: Symbol,
100    ) -> ParseResult<&'a LogicExpr<'a>>;
101    fn substitute_constant_with_var_sym(
102        &self,
103        expr: &'a LogicExpr<'a>,
104        constant_name: Symbol,
105        var_name: Symbol,
106    ) -> ParseResult<&'a LogicExpr<'a>>;
107    fn substitute_constant_with_sigma(
108        &self,
109        expr: &'a LogicExpr<'a>,
110        constant_name: Symbol,
111        sigma_term: Term<'a>,
112    ) -> ParseResult<&'a LogicExpr<'a>>;
113    /// Rewrite a relativized gap variable into a constant, so a subject's
114    /// relative-clause restriction (built over `from_var`) can be folded into a
115    /// predicate keyed on `to_const` and then re-bound uniformly by
116    /// `wrap_with_definiteness`.
117    fn substitute_variable_with_constant(
118        &self,
119        expr: &'a LogicExpr<'a>,
120        from_var: Symbol,
121        to_const: Symbol,
122    ) -> ParseResult<&'a LogicExpr<'a>>;
123    fn find_main_verb_name(&self, expr: &LogicExpr<'a>) -> Option<Symbol>;
124    fn transform_cardinal_to_group(&mut self, expr: &'a LogicExpr<'a>) -> ParseResult<&'a LogicExpr<'a>>;
125    fn build_verb_neo_event(
126        &mut self,
127        verb: Symbol,
128        subject_var: Symbol,
129        object: Option<Term<'a>>,
130        modifiers: Vec<Symbol>,
131    ) -> &'a LogicExpr<'a>;
132    /// Parse a copula PP complement under a quantifier — "is in Florida or in
133    /// Maine" → In(subj,Florida) ∨ In(subj,Maine), `subj_var` being the bound
134    /// variable. Captures the or-coordination ("or in B" repeats the preposition,
135    /// "or B" reuses it) that the backtracking fallback path otherwise drops.
136    fn parse_copula_pp_complement(
137        &mut self,
138        subj_var: Symbol,
139    ) -> ParseResult<&'a LogicExpr<'a>>;
140}
141
142impl<'a, 'ctx, 'int> QuantifierParsing<'a, 'ctx, 'int> for Parser<'a, 'ctx, 'int> {
143    fn parse_copula_pp_complement(
144        &mut self,
145        subj_var: Symbol,
146    ) -> ParseResult<&'a LogicExpr<'a>> {
147        let prep_sym = match self.advance().kind {
148            TokenType::Preposition(s) => s,
149            _ => unreachable!("guarded by check_preposition"),
150        };
151        let saved_ctx = self.nominal_np_context;
152        self.nominal_np_context = true;
153        let obj_res = self.parse_noun_phrase(true);
154        self.nominal_np_context = saved_ctx;
155        let obj = obj_res?;
156        let first: &'a LogicExpr<'a> = self.ctx.exprs.alloc(LogicExpr::Predicate {
157            name: prep_sym,
158            args: self
159                .ctx
160                .terms
161                .alloc_slice([Term::Variable(subj_var), Term::Constant(obj.noun)]),
162            world: None,
163        });
164        let mut base = self.attach_pp_object_modifiers(first, &obj);
165        while self.check(&TokenType::Or) {
166            let cp = self.checkpoint();
167            self.advance(); // "or"
168            let disj_prep = if self.check_preposition() && !self.check_by_preposition() {
169                match self.advance().kind {
170                    TokenType::Preposition(s) => s,
171                    _ => prep_sym,
172                }
173            } else {
174                prep_sym
175            };
176            if !(self.check_content_word()
177                || self.check_number()
178                || matches!(self.peek().kind, TokenType::Article(_)))
179            {
180                self.restore(cp);
181                break;
182            }
183            let saved = self.nominal_np_context;
184            self.nominal_np_context = true;
185            let disj_obj_res = self.parse_noun_phrase(true);
186            self.nominal_np_context = saved;
187            let disj_obj = disj_obj_res?;
188            let disj: &'a LogicExpr<'a> = self.ctx.exprs.alloc(LogicExpr::Predicate {
189                name: disj_prep,
190                args: self
191                    .ctx
192                    .terms
193                    .alloc_slice([Term::Variable(subj_var), Term::Constant(disj_obj.noun)]),
194                world: None,
195            });
196            let disj = self.attach_pp_object_modifiers(disj, &disj_obj);
197            base = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
198                left: base,
199                op: TokenType::Or,
200                right: disj,
201            });
202        }
203        Ok(base)
204    }
205
206    fn parse_quantified(&mut self) -> ParseResult<&'a LogicExpr<'a>> {
207        // The specialized quantified-VP grammar below covers many frames but
208        // not all of them. A parse that stops mid-clause silently drops the
209        // remainder's meaning, so when it under-consumes (or fails), re-parse
210        // the clause delegating the VP to the full predicate parser. The
211        // specialized result is kept whenever the delegation does no better.
212        let cp = self.checkpoint();
213        let core = self.parse_quantified_core();
214        let core_complete = core.is_ok() && self.at_clause_boundary();
215        let result = if core_complete {
216            core?
217        } else {
218            let core_end = self.checkpoint();
219            let core_partitive = self.pending_partitive.take();
220            self.restore(cp);
221            match self.parse_quantified_delegating() {
222                Ok(r) if self.at_clause_boundary() => r,
223                _ => match core {
224                    Ok(r) => {
225                        self.restore(core_end);
226                        self.pending_partitive = core_partitive;
227                        r
228                    }
229                    Err(e) => return Err(e),
230                },
231            }
232        };
233        // §5.3: a partitive "of the [Num]" frame presupposes a salient set of that
234        // cardinality. Surface it as `assertion [Presup: ∃=n x Restriction(x)]` rather
235        // than discarding the superset.
236        if let Some((n, restriction, var)) = self.pending_partitive.take() {
237            let superset = self.ctx.exprs.alloc(LogicExpr::Quantifier {
238                kind: QuantifierKind::Cardinal(n),
239                variable: var,
240                body: restriction,
241                island_id: self.current_island,
242            });
243            return Ok(self.ctx.exprs.alloc(LogicExpr::Presupposition {
244                assertion: result,
245                presupposition: superset,
246            }));
247        }
248        Ok(result)
249    }
250
251    fn parse_quantified_core(&mut self) -> ParseResult<&'a LogicExpr<'a>> {
252        let quantifier_token = self.previous().kind.clone();
253        let var_name = self.next_var_name();
254
255        // Track if we're inside a "No" quantifier - referents introduced here
256        // are inaccessible for cross-sentence anaphora
257        let was_in_negative_quantifier = self.in_negative_quantifier;
258        if matches!(quantifier_token, TokenType::No) {
259            self.in_negative_quantifier = true;
260        }
261
262        // Partitive: "Two of the three students passed.", "Most of the students
263        // passed." The "of the [Num]" frame restricts the quantifier to a salient
264        // presupposed definite set. Consume the frame so the quantifier ranges over
265        // the head noun normally; the leading cardinal/proportion is the count, the
266        // optional inner cardinal is the presupposed superset size.
267        let mut partitive_superset: Option<u32> = None;
268        if matches!(
269            quantifier_token,
270            TokenType::Cardinal(_)
271                | TokenType::Most
272                | TokenType::Few
273                | TokenType::Many
274                | TokenType::Some
275                | TokenType::AtLeast(_)
276                | TokenType::AtMost(_)
277        ) && self.check_preposition_is("of")
278            && self.current + 1 < self.tokens.len()
279            && matches!(self.tokens[self.current + 1].kind, TokenType::Article(_))
280        {
281            self.advance(); // consume "of"
282            self.advance(); // consume "the"
283            if let TokenType::Cardinal(n) = self.peek().kind {
284                partitive_superset = Some(n);
285                self.advance(); // consume the superset cardinal ("three")
286            }
287        }
288        // "At most one of X, Y, and Z is P" — counting quantifier with explicit list
289        if matches!(quantifier_token, TokenType::AtMost(_) | TokenType::AtLeast(_) | TokenType::Cardinal(_))
290            && self.check_preposition_is("of")
291        {
292            self.advance(); // consume "of"
293
294            // Parse comma-separated list of identifiers: "grant0, grant1, and grant2"
295            let mut signal_names: Vec<Symbol> = Vec::new();
296            loop {
297                let name = self.consume_content_word()?;
298                signal_names.push(name);
299
300                if self.check(&TokenType::Comma) {
301                    self.advance(); // consume ","
302                    // Skip optional "and" after comma: "X, Y, and Z"
303                    if self.check(&TokenType::And) {
304                        self.advance();
305                    }
306                } else if self.check(&TokenType::And) {
307                    self.advance(); // consume "and" (two-element: "X and Y")
308                } else {
309                    break;
310                }
311            }
312
313            // Now parse the predicate: "is asserted", "is valid", etc.
314            // In hardware context, the predicate is implicit — what matters
315            // is each signal name being high/low. Consume but don't use.
316            let mut is_negated = false;
317            if self.check(&TokenType::Is) || self.check(&TokenType::Are) {
318                self.advance(); // consume copula
319                is_negated = self.check(&TokenType::Not);
320                if is_negated {
321                    self.advance();
322                }
323                // Consume the predicate adjective/verb (e.g., "asserted", "valid")
324                let _ = self.consume_content_word();
325                // Consume optional trailing "at any time"
326                while self.check_preposition_is("at") {
327                    self.advance();
328                    if self.check(&TokenType::Any) {
329                        self.advance();
330                    }
331                    if self.check_content_word() {
332                        self.advance();
333                    }
334                }
335            }
336
337            // Build the body: each signal as an Atom, joined by OR
338            // SVA synthesis maps Atom(sig) → signal name directly
339            let mut signal_exprs: Vec<&'a LogicExpr<'a>> = Vec::new();
340            for &sig in &signal_names {
341                let atom: &'a LogicExpr<'a> = self.ctx.exprs.alloc(LogicExpr::Atom(sig));
342                let sig_expr = if is_negated {
343                    self.ctx.exprs.alloc(LogicExpr::UnaryOp {
344                        op: TokenType::Not,
345                        operand: atom,
346                    })
347                } else {
348                    atom
349                };
350                signal_exprs.push(sig_expr);
351            }
352
353            let body = if signal_exprs.len() == 1 {
354                signal_exprs[0]
355            } else {
356                let mut combined = signal_exprs[0];
357                for &expr in &signal_exprs[1..] {
358                    combined = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
359                        left: combined,
360                        op: TokenType::Or,
361                        right: expr,
362                    });
363                }
364                combined
365            };
366
367            let kind = match quantifier_token {
368                TokenType::AtMost(n) => QuantifierKind::AtMost(n),
369                TokenType::AtLeast(n) => QuantifierKind::AtLeast(n),
370                TokenType::Cardinal(n) => QuantifierKind::Cardinal(n),
371                _ => unreachable!(),
372            };
373
374            self.in_negative_quantifier = was_in_negative_quantifier;
375
376            return Ok(self.ctx.exprs.alloc(LogicExpr::Quantifier {
377                kind,
378                variable: var_name,
379                body,
380                island_id: self.current_island,
381            }));
382        }
383
384        let subject_pred = self.parse_restriction(var_name)?;
385
386        // §5.3: stash the partitive superset now that the restriction predicate exists; the
387        // `parse_quantified` wrapper turns it into a presupposed `∃=n x Restriction(x)`.
388        if let Some(n) = partitive_superset {
389            self.pending_partitive = Some((n, subject_pred, var_name));
390        }
391
392        if self.check_modal() {
393            use crate::ast::ModalFlavor;
394
395            self.advance();
396            let vector = self.token_to_vector(&self.previous().kind.clone());
397            let verb = self.consume_content_word()?;
398
399            // Parse object if present (e.g., "can enter the room" -> room is object)
400            let obj_term = if self.check_content_word() || self.check_article() {
401                let obj_np = self.parse_noun_phrase(false)?;
402                Some(self.noun_phrase_to_term(&obj_np))
403            } else {
404                None
405            };
406
407            // Collect any trailing adverbs
408            let modifiers = self.collect_adverbs();
409            let verb_pred = self.build_verb_neo_event(verb, var_name, obj_term, modifiers);
410
411            // Determine quantifier kind first (shared by both branches)
412            let kind = match quantifier_token {
413                TokenType::All | TokenType::No => QuantifierKind::Universal,
414                TokenType::Any => {
415                    if self.is_negative_context() {
416                        QuantifierKind::Existential
417                    } else {
418                        QuantifierKind::Universal
419                    }
420                }
421                TokenType::Some => QuantifierKind::Existential,
422                TokenType::Most => QuantifierKind::Most,
423                TokenType::Few => QuantifierKind::Few,
424                TokenType::Many => QuantifierKind::Many,
425                TokenType::Cardinal(n) => QuantifierKind::Cardinal(n),
426                TokenType::AtLeast(n) => QuantifierKind::AtLeast(n),
427                TokenType::AtMost(n) => QuantifierKind::AtMost(n),
428                _ => {
429                    return Err(ParseError {
430                        kind: ParseErrorKind::UnknownQuantifier {
431                            found: quantifier_token.clone(),
432                        },
433                        span: self.current_span(),
434                    })
435                }
436            };
437
438            // Branch on modal flavor for scope handling
439            if vector.flavor == ModalFlavor::Root {
440                // === NARROW SCOPE (De Re) ===
441                // Root modals (can, must, should) attach to the predicate inside the quantifier
442                // "Some birds can fly" → ∃x(Bird(x) ∧ ◇Fly(x))
443
444                // Wrap the verb predicate in the modal
445                let modal_verb = self.ctx.exprs.alloc(LogicExpr::Modal {
446                    vector,
447                    operand: verb_pred,
448                });
449
450                let body = match quantifier_token {
451                    TokenType::All => self.ctx.exprs.alloc(LogicExpr::BinaryOp {
452                        left: subject_pred,
453                        op: TokenType::Implies,
454                        right: modal_verb,
455                    }),
456                    TokenType::Any => {
457                        if self.is_negative_context() {
458                            self.ctx.exprs.alloc(LogicExpr::BinaryOp {
459                                left: subject_pred,
460                                op: TokenType::And,
461                                right: modal_verb,
462                            })
463                        } else {
464                            self.ctx.exprs.alloc(LogicExpr::BinaryOp {
465                                left: subject_pred,
466                                op: TokenType::Implies,
467                                right: modal_verb,
468                            })
469                        }
470                    }
471                    TokenType::Some
472                    | TokenType::Most
473                    | TokenType::Few
474                    | TokenType::Many
475                    | TokenType::Cardinal(_)
476                    | TokenType::AtLeast(_)
477                    | TokenType::AtMost(_) => self.ctx.exprs.alloc(LogicExpr::BinaryOp {
478                        left: subject_pred,
479                        op: TokenType::And,
480                        right: modal_verb,
481                    }),
482                    TokenType::No => {
483                        let neg = self.ctx.exprs.alloc(LogicExpr::UnaryOp {
484                            op: TokenType::Not,
485                            operand: modal_verb,
486                        });
487                        self.ctx.exprs.alloc(LogicExpr::BinaryOp {
488                            left: subject_pred,
489                            op: TokenType::Implies,
490                            right: neg,
491                        })
492                    }
493                    _ => {
494                        return Err(ParseError {
495                            kind: ParseErrorKind::UnknownQuantifier {
496                                found: quantifier_token.clone(),
497                            },
498                            span: self.current_span(),
499                        })
500                    }
501                };
502
503                // Build quantifier (modal is inside)
504                let mut result = self.ctx.exprs.alloc(LogicExpr::Quantifier {
505                    kind,
506                    variable: var_name,
507                    body,
508                    island_id: self.current_island,
509                });
510
511                // Process donkey bindings for indefinites in restrictions (e.g., "who lacks a key")
512                for (_noun, donkey_var, used, wide_neg) in self.donkey_bindings.iter().rev() {
513                    if *used {
514                        // Donkey anaphora: wrap with ∀ at outer scope
515                        result = self.ctx.exprs.alloc(LogicExpr::Quantifier {
516                            kind: QuantifierKind::Universal,
517                            variable: *donkey_var,
518                            body: result,
519                            island_id: self.current_island,
520                        });
521                    } else {
522                        // Non-donkey: wrap with ∃ INSIDE the restriction
523                        result = self.wrap_donkey_in_restriction(result, *donkey_var, *wide_neg);
524                    }
525                }
526                self.donkey_bindings.clear();
527
528                self.in_negative_quantifier = was_in_negative_quantifier;
529                return Ok(result);
530
531            } else {
532                // === WIDE SCOPE (De Dicto) ===
533                // Epistemic modals (might, may) wrap the entire quantifier
534                // "Some unicorns might exist" → ◇∃x(Unicorn(x) ∧ Exist(x))
535
536                let body = match quantifier_token {
537                    TokenType::All => self.ctx.exprs.alloc(LogicExpr::BinaryOp {
538                        left: subject_pred,
539                        op: TokenType::Implies,
540                        right: verb_pred,
541                    }),
542                    TokenType::Any => {
543                        if self.is_negative_context() {
544                            self.ctx.exprs.alloc(LogicExpr::BinaryOp {
545                                left: subject_pred,
546                                op: TokenType::And,
547                                right: verb_pred,
548                            })
549                        } else {
550                            self.ctx.exprs.alloc(LogicExpr::BinaryOp {
551                                left: subject_pred,
552                                op: TokenType::Implies,
553                                right: verb_pred,
554                            })
555                        }
556                    }
557                    TokenType::Some
558                    | TokenType::Most
559                    | TokenType::Few
560                    | TokenType::Many
561                    | TokenType::Cardinal(_)
562                    | TokenType::AtLeast(_)
563                    | TokenType::AtMost(_) => self.ctx.exprs.alloc(LogicExpr::BinaryOp {
564                        left: subject_pred,
565                        op: TokenType::And,
566                        right: verb_pred,
567                    }),
568                    TokenType::No => {
569                        let neg = self.ctx.exprs.alloc(LogicExpr::UnaryOp {
570                            op: TokenType::Not,
571                            operand: verb_pred,
572                        });
573                        self.ctx.exprs.alloc(LogicExpr::BinaryOp {
574                            left: subject_pred,
575                            op: TokenType::Implies,
576                            right: neg,
577                        })
578                    }
579                    _ => {
580                        return Err(ParseError {
581                            kind: ParseErrorKind::UnknownQuantifier {
582                                found: quantifier_token.clone(),
583                            },
584                            span: self.current_span(),
585                        })
586                    }
587                };
588
589                let mut result = self.ctx.exprs.alloc(LogicExpr::Quantifier {
590                    kind,
591                    variable: var_name,
592                    body,
593                    island_id: self.current_island,
594                });
595
596                // Process donkey bindings for indefinites in restrictions (e.g., "who lacks a key")
597                for (_noun, donkey_var, used, wide_neg) in self.donkey_bindings.iter().rev() {
598                    if *used {
599                        // Donkey anaphora: wrap with ∀ at outer scope
600                        result = self.ctx.exprs.alloc(LogicExpr::Quantifier {
601                            kind: QuantifierKind::Universal,
602                            variable: *donkey_var,
603                            body: result,
604                            island_id: self.current_island,
605                        });
606                    } else {
607                        // Non-donkey: wrap with ∃ INSIDE the restriction
608                        result = self.wrap_donkey_in_restriction(result, *donkey_var, *wide_neg);
609                    }
610                }
611                self.donkey_bindings.clear();
612
613                // Wrap the entire quantifier in the modal
614                self.in_negative_quantifier = was_in_negative_quantifier;
615                return Ok(self.ctx.exprs.alloc(LogicExpr::Modal {
616                    vector,
617                    operand: result,
618                }));
619            }
620        }
621
622        if self.check_auxiliary() {
623            let aux_token = self.advance();
624            let aux_time = if let TokenType::Auxiliary(time) = aux_token.kind.clone() {
625                time
626            } else {
627                Time::None
628            };
629            self.pending_time = Some(aux_time);
630
631            let is_negated = self.match_token(&[TokenType::Not]);
632            if is_negated {
633                self.negative_depth += 1;
634            }
635
636            // A polarity adverb between the auxiliary and the verb ("will EVER be
637            // used", "would NEVER be seen") is an NPI licensed by the negative
638            // quantifier — vacuous to the truth conditions here, so skip it.
639            while self.check(&TokenType::Ever) {
640                self.advance();
641            }
642
643            if self.check_verb() {
644                let verb = self.consume_verb();
645
646                // Passive auxiliary "will/would BE used": the copula `be`/`been` is
647                // followed by a past-participle verb that is the real predicate — its
648                // lemma fills the Theme ("will be USED" → Be(e) ∧ Theme(e, Use)),
649                // mirroring the non-quantified passive path.
650                let verb_lower = self.interner.resolve(verb).to_lowercase();
651                let obj_term = if matches!(verb_lower.as_str(), "be" | "been")
652                    && self.check_verb()
653                {
654                    Some(Term::Constant(self.consume_verb()))
655                } else {
656                    None
657                };
658
659                // Convert aux_time to modifier
660                let mut modifiers = match aux_time {
661                    Time::Past => vec![self.interner.intern("Past")],
662                    Time::Future => vec![self.interner.intern("Future")],
663                    _ => vec![],
664                };
665                // Manner adverbs after the participle ("were running quickly").
666                modifiers.extend(self.collect_adverbs());
667
668                // Frequency comparative "... used MORE THAN ONCE": Comparative +
669                // "than" + count noun. Absorb it as an event modifier (the exact
670                // count is immaterial to the bijection a grid eliminates).
671                if self.check_comparative() {
672                    self.advance(); // "more"
673                    if self.check(&TokenType::Than) {
674                        self.advance();
675                    }
676                    if self.check_content_word() || self.check_number() {
677                        self.advance(); // the count word ("once", "twice", a number)
678                    }
679                    modifiers.push(self.interner.intern("MoreThanOnce"));
680                }
681
682                let verb_pred = self.build_verb_neo_event(verb, var_name, obj_term, modifiers);
683
684                let maybe_negated = if is_negated {
685                    self.negative_depth -= 1;
686                    self.ctx.exprs.alloc(LogicExpr::UnaryOp {
687                        op: TokenType::Not,
688                        operand: verb_pred,
689                    })
690                } else {
691                    verb_pred
692                };
693
694                let body = match quantifier_token {
695                    TokenType::All | TokenType::Any => self.ctx.exprs.alloc(LogicExpr::BinaryOp {
696                        left: subject_pred,
697                        op: TokenType::Implies,
698                        right: maybe_negated,
699                    }),
700                    // "No N will/did V" = ∀x(N(x) → ¬V(x)). The aux branch was the
701                    // sole VP path that dropped the negation for `No`, emitting the
702                    // (much stronger, false) ∀x(N(x) ∧ V(x)).
703                    TokenType::No => self.ctx.exprs.alloc(LogicExpr::BinaryOp {
704                        left: subject_pred,
705                        op: TokenType::Implies,
706                        right: self.ctx.exprs.alloc(LogicExpr::UnaryOp {
707                            op: TokenType::Not,
708                            operand: maybe_negated,
709                        }),
710                    }),
711                    _ => self.ctx.exprs.alloc(LogicExpr::BinaryOp {
712                        left: subject_pred,
713                        op: TokenType::And,
714                        right: maybe_negated,
715                    }),
716                };
717
718                let kind = match quantifier_token {
719                    TokenType::All | TokenType::No => QuantifierKind::Universal,
720                    TokenType::Some => QuantifierKind::Existential,
721                    TokenType::Most => QuantifierKind::Most,
722                    TokenType::Few => QuantifierKind::Few,
723                    TokenType::Cardinal(n) => QuantifierKind::Cardinal(n),
724                    TokenType::AtLeast(n) => QuantifierKind::AtLeast(n),
725                    TokenType::AtMost(n) => QuantifierKind::AtMost(n),
726                    _ => QuantifierKind::Universal,
727                };
728
729                self.in_negative_quantifier = was_in_negative_quantifier;
730                return Ok(self.ctx.exprs.alloc(LogicExpr::Quantifier {
731                    kind,
732                    variable: var_name,
733                    body,
734                    island_id: self.current_island,
735                }));
736            }
737        }
738
739        // Only trigger presupposition if followed by gerund complement
740        if self.check_presup_trigger() && self.is_followed_by_gerund() {
741            let presup_kind = match self.advance().kind {
742                TokenType::PresupTrigger(kind) => kind,
743                TokenType::Verb { lemma, .. } => {
744                    let s = self.interner.resolve(lemma).to_lowercase();
745                    crate::lexicon::lookup_presup_trigger(&s)
746                        .expect("Lexicon mismatch: Verb flagged as trigger but lookup failed")
747                }
748                _ => panic!("Expected presupposition trigger"),
749            };
750
751            let complement = if self.check_verb() {
752                let verb = self.consume_verb();
753                let modifiers = self.collect_adverbs();
754                self.build_verb_neo_event(verb, var_name, None, modifiers)
755            } else {
756                let unknown = self.interner.intern("?");
757                self.ctx.exprs.alloc(LogicExpr::Atom(unknown))
758            };
759
760            let verb_pred = match presup_kind {
761                PresupKind::Stop => self.ctx.exprs.alloc(LogicExpr::UnaryOp {
762                    op: TokenType::Not,
763                    operand: complement,
764                }),
765                PresupKind::Start | PresupKind::Continue => complement,
766                PresupKind::Regret | PresupKind::Realize | PresupKind::Know => complement,
767            };
768
769            let body = match quantifier_token {
770                TokenType::All | TokenType::Any => self.ctx.exprs.alloc(LogicExpr::BinaryOp {
771                    left: subject_pred,
772                    op: TokenType::Implies,
773                    right: verb_pred,
774                }),
775                _ => self.ctx.exprs.alloc(LogicExpr::BinaryOp {
776                    left: subject_pred,
777                    op: TokenType::And,
778                    right: verb_pred,
779                }),
780            };
781
782            let kind = match quantifier_token {
783                TokenType::All | TokenType::No => QuantifierKind::Universal,
784                TokenType::Some => QuantifierKind::Existential,
785                TokenType::Most => QuantifierKind::Most,
786                TokenType::Few => QuantifierKind::Few,
787                TokenType::Many => QuantifierKind::Many,
788                TokenType::Cardinal(n) => QuantifierKind::Cardinal(n),
789                TokenType::AtLeast(n) => QuantifierKind::AtLeast(n),
790                TokenType::AtMost(n) => QuantifierKind::AtMost(n),
791                _ => QuantifierKind::Universal,
792            };
793
794            self.in_negative_quantifier = was_in_negative_quantifier;
795            return Ok(self.ctx.exprs.alloc(LogicExpr::Quantifier {
796                kind,
797                variable: var_name,
798                body,
799                island_id: self.current_island,
800            }));
801        }
802
803        if self.check_verb() {
804            let verb = self.consume_verb();
805            let mut args = vec![Term::Variable(var_name)];
806
807            if self.check_pronoun() {
808                let token = self.peek().clone();
809                if let TokenType::Pronoun { gender, .. } = token.kind {
810                    self.advance();
811                    if let Some(donkey_var) = self.resolve_donkey_pronoun(gender) {
812                        args.push(Term::Variable(donkey_var));
813                    } else {
814                        let resolved = self.resolve_pronoun(gender, Number::Singular)?;
815                        let term = match resolved {
816                            super::ResolvedPronoun::Variable(s) => Term::Variable(s),
817                            super::ResolvedPronoun::Constant(s) => Term::Constant(s),
818                        };
819                        args.push(term);
820                    }
821                }
822            } else if self.check_npi_object() {
823                let npi_token = self.advance().kind.clone();
824                let obj_var = self.next_var_name();
825
826                let restriction_name = match npi_token {
827                    TokenType::Anything => "Thing",
828                    TokenType::Anyone => "Person",
829                    _ => "Thing",
830                };
831
832                let restriction_sym = self.interner.intern(restriction_name);
833                let obj_restriction = self.ctx.exprs.alloc(LogicExpr::Predicate {
834                    name: restriction_sym,
835                    args: self.ctx.terms.alloc_slice([Term::Variable(obj_var)]),
836                    world: None,
837                });
838
839                let npi_modifiers = self.collect_adverbs();
840                let verb_with_obj = self.build_verb_neo_event(
841                    verb,
842                    var_name,
843                    Some(Term::Variable(obj_var)),
844                    npi_modifiers,
845                );
846
847                let npi_body = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
848                    left: obj_restriction,
849                    op: TokenType::And,
850                    right: verb_with_obj,
851                });
852
853                let npi_quantified = self.ctx.exprs.alloc(LogicExpr::Quantifier {
854                    kind: QuantifierKind::Existential,
855                    variable: obj_var,
856                    body: npi_body,
857                    island_id: self.current_island,
858                });
859
860                let negated_npi = self.ctx.exprs.alloc(LogicExpr::UnaryOp {
861                    op: TokenType::Not,
862                    operand: npi_quantified,
863                });
864
865                let body = match quantifier_token {
866                    TokenType::All | TokenType::No => self.ctx.exprs.alloc(LogicExpr::BinaryOp {
867                        left: subject_pred,
868                        op: TokenType::Implies,
869                        right: negated_npi,
870                    }),
871                    _ => self.ctx.exprs.alloc(LogicExpr::BinaryOp {
872                        left: subject_pred,
873                        op: TokenType::And,
874                        right: negated_npi,
875                    }),
876                };
877
878                let kind = match quantifier_token {
879                    TokenType::All | TokenType::No => QuantifierKind::Universal,
880                    TokenType::Some => QuantifierKind::Existential,
881                    TokenType::Most => QuantifierKind::Most,
882                    TokenType::Few => QuantifierKind::Few,
883                    TokenType::Many => QuantifierKind::Many,
884                    TokenType::Cardinal(n) => QuantifierKind::Cardinal(n),
885                    TokenType::AtLeast(n) => QuantifierKind::AtLeast(n),
886                    TokenType::AtMost(n) => QuantifierKind::AtMost(n),
887                    _ => QuantifierKind::Universal,
888                };
889
890                self.in_negative_quantifier = was_in_negative_quantifier;
891                return Ok(self.ctx.exprs.alloc(LogicExpr::Quantifier {
892                    kind,
893                    variable: var_name,
894                    body,
895                    island_id: self.current_island,
896                }));
897            } else if self.check_quantifier() || self.check_article() || self.check_possessive_pronoun() {
898                let obj_quantifier = if self.check_possessive_pronoun() {
899                    // Possessive NP object ("his dog"): parse_noun_phrase
900                    // consumes the possessor; no quantifier wrapper.
901                    None
902                } else if self.check_quantifier() {
903                    Some(self.advance().kind.clone())
904                } else {
905                    let art = self.advance().kind.clone();
906                    if let TokenType::Article(def) = art {
907                        if def == Definiteness::Indefinite {
908                            Some(TokenType::Some)
909                        } else {
910                            None
911                        }
912                    } else {
913                        None
914                    }
915                };
916
917                let object = self.parse_noun_phrase(false)?;
918
919                if let Some(obj_q) = obj_quantifier {
920                    let obj_var = self.next_var_name();
921
922                    // Introduce object referent in DRS for cross-sentence anaphora (telescoping)
923                    // BUT: If inside "No X" quantifier, mark with NegationScope to block accessibility
924                    let obj_gender = Self::infer_noun_gender(self.interner.resolve(object.noun));
925                    let obj_number = if Self::is_plural_noun(self.interner.resolve(object.noun)) {
926                        Number::Plural
927                    } else {
928                        Number::Singular
929                    };
930                    if self.in_negative_quantifier {
931                        self.drs.introduce_referent_with_source(obj_var, object.noun, obj_gender, obj_number, ReferentSource::NegationScope);
932                    } else {
933                        self.drs.introduce_referent(obj_var, object.noun, obj_gender, obj_number);
934                    }
935
936                    let mut obj_restriction: &'a LogicExpr<'a> = self.ctx.exprs.alloc(LogicExpr::Predicate {
937                        name: object.noun,
938                        args: self.ctx.terms.alloc_slice([Term::Variable(obj_var)]),
939                        world: None,
940                    });
941                    // The object's adjectives ("a RED book") and PP restrictors
942                    // ("a maximum range OF 475 ft") are part of its description —
943                    // dropping them is a meaning-loss parse.
944                    for &adj in object.adjectives {
945                        let adj_pred = self.adjective_restriction(adj, obj_var, object.noun);
946                        obj_restriction = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
947                            left: obj_restriction,
948                            op: TokenType::And,
949                            right: adj_pred,
950                        });
951                    }
952                    for pp in object.pps {
953                        let pp_sub = self.substitute_pp_placeholder(pp, obj_var);
954                        obj_restriction = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
955                            left: obj_restriction,
956                            op: TokenType::And,
957                            right: pp_sub,
958                        });
959                    }
960
961                    let obj_modifiers = self.collect_adverbs();
962                    let verb_with_obj = self.build_verb_neo_event(
963                        verb,
964                        var_name,
965                        Some(Term::Variable(obj_var)),
966                        obj_modifiers,
967                    );
968
969                    let obj_kind = match obj_q {
970                        TokenType::All => QuantifierKind::Universal,
971                        TokenType::Some => QuantifierKind::Existential,
972                        TokenType::No => QuantifierKind::Universal,
973                        TokenType::Most => QuantifierKind::Most,
974                        TokenType::Few => QuantifierKind::Few,
975                        TokenType::Many => QuantifierKind::Many,
976                        TokenType::Cardinal(n) => QuantifierKind::Cardinal(n),
977                        TokenType::AtLeast(n) => QuantifierKind::AtLeast(n),
978                        TokenType::AtMost(n) => QuantifierKind::AtMost(n),
979                        _ => QuantifierKind::Existential,
980                    };
981
982                    let obj_body = match obj_q {
983                        TokenType::All => self.ctx.exprs.alloc(LogicExpr::BinaryOp {
984                            left: obj_restriction,
985                            op: TokenType::Implies,
986                            right: verb_with_obj,
987                        }),
988                        TokenType::No => {
989                            let neg = self.ctx.exprs.alloc(LogicExpr::UnaryOp {
990                                op: TokenType::Not,
991                                operand: verb_with_obj,
992                            });
993                            self.ctx.exprs.alloc(LogicExpr::BinaryOp {
994                                left: obj_restriction,
995                                op: TokenType::Implies,
996                                right: neg,
997                            })
998                        }
999                        _ => self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1000                            left: obj_restriction,
1001                            op: TokenType::And,
1002                            right: verb_with_obj,
1003                        }),
1004                    };
1005
1006                    let obj_quantified = self.ctx.exprs.alloc(LogicExpr::Quantifier {
1007                        kind: obj_kind,
1008                        variable: obj_var,
1009                        body: obj_body,
1010                        island_id: self.current_island,
1011                    });
1012
1013                    let subj_kind = match quantifier_token {
1014                        TokenType::All | TokenType::No => QuantifierKind::Universal,
1015                        TokenType::Any => {
1016                            if self.is_negative_context() {
1017                                QuantifierKind::Existential
1018                            } else {
1019                                QuantifierKind::Universal
1020                            }
1021                        }
1022                        TokenType::Some => QuantifierKind::Existential,
1023                        TokenType::Most => QuantifierKind::Most,
1024                        TokenType::Few => QuantifierKind::Few,
1025                        TokenType::Many => QuantifierKind::Many,
1026                        TokenType::Cardinal(n) => QuantifierKind::Cardinal(n),
1027                        TokenType::AtLeast(n) => QuantifierKind::AtLeast(n),
1028                        TokenType::AtMost(n) => QuantifierKind::AtMost(n),
1029                        _ => QuantifierKind::Universal,
1030                    };
1031
1032                    let subj_body = match quantifier_token {
1033                        TokenType::All => self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1034                            left: subject_pred,
1035                            op: TokenType::Implies,
1036                            right: obj_quantified,
1037                        }),
1038                        TokenType::No => {
1039                            let neg = self.ctx.exprs.alloc(LogicExpr::UnaryOp {
1040                                op: TokenType::Not,
1041                                operand: obj_quantified,
1042                            });
1043                            self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1044                                left: subject_pred,
1045                                op: TokenType::Implies,
1046                                right: neg,
1047                            })
1048                        }
1049                        _ => self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1050                            left: subject_pred,
1051                            op: TokenType::And,
1052                            right: obj_quantified,
1053                        }),
1054                    };
1055
1056                    self.in_negative_quantifier = was_in_negative_quantifier;
1057                    let mut result = self.ctx.exprs.alloc(LogicExpr::Quantifier {
1058                        kind: subj_kind,
1059                        variable: var_name,
1060                        body: subj_body,
1061                        island_id: self.current_island,
1062                    });
1063                    // Close any donkey-anaphora indefinites from the subject's
1064                    // relative clause (e.g. "a donkey" in "Every farmer who owns
1065                    // a donkey feeds every animal"). This quantified-object path
1066                    // previously returned WITHOUT the closure run by the main VP
1067                    // path, leaving the donkey variable FREE in the formula.
1068                    for (_noun, donkey_var, used, wide_neg) in self.donkey_bindings.iter().rev() {
1069                        if *used {
1070                            result = self.ctx.exprs.alloc(LogicExpr::Quantifier {
1071                                kind: QuantifierKind::Universal,
1072                                variable: *donkey_var,
1073                                body: result,
1074                                island_id: self.current_island,
1075                            });
1076                        } else {
1077                            result = self.wrap_donkey_in_restriction(result, *donkey_var, *wide_neg);
1078                        }
1079                    }
1080                    self.donkey_bindings.clear();
1081                    return Ok(result);
1082                } else {
1083                    args.push(Term::Constant(object.noun));
1084                }
1085            } else if self.check_content_word() {
1086                let object = self.parse_noun_phrase(false)?;
1087                args.push(Term::Constant(object.noun));
1088            }
1089
1090            // Extract object term from args if present (args[0] is subject, args[1] is object)
1091            let obj_term = if args.len() > 1 {
1092                Some(args.remove(1))
1093            } else {
1094                None
1095            };
1096            // Collect any trailing adverbs (e.g., "bark loudly")
1097            let modifiers = self.collect_adverbs();
1098            let verb_pred = self.build_verb_neo_event(verb, var_name, obj_term, modifiers);
1099
1100            let body = match quantifier_token {
1101                TokenType::All => self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1102                    left: subject_pred,
1103                    op: TokenType::Implies,
1104                    right: verb_pred,
1105                }),
1106                TokenType::Any => {
1107                    if self.is_negative_context() {
1108                        self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1109                            left: subject_pred,
1110                            op: TokenType::And,
1111                            right: verb_pred,
1112                        })
1113                    } else {
1114                        self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1115                            left: subject_pred,
1116                            op: TokenType::Implies,
1117                            right: verb_pred,
1118                        })
1119                    }
1120                }
1121                TokenType::Some
1122                | TokenType::Most
1123                | TokenType::Few
1124                | TokenType::Many
1125                | TokenType::Cardinal(_)
1126                | TokenType::AtLeast(_)
1127                | TokenType::AtMost(_) => self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1128                    left: subject_pred,
1129                    op: TokenType::And,
1130                    right: verb_pred,
1131                }),
1132                TokenType::No => {
1133                    let neg = self.ctx.exprs.alloc(LogicExpr::UnaryOp {
1134                        op: TokenType::Not,
1135                        operand: verb_pred,
1136                    });
1137                    self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1138                        left: subject_pred,
1139                        op: TokenType::Implies,
1140                        right: neg,
1141                    })
1142                }
1143                _ => {
1144                    return Err(ParseError {
1145                        kind: ParseErrorKind::UnknownQuantifier {
1146                            found: quantifier_token.clone(),
1147                        },
1148                        span: self.current_span(),
1149                    })
1150                }
1151            };
1152
1153            let kind = match quantifier_token {
1154                TokenType::All | TokenType::No => QuantifierKind::Universal,
1155                TokenType::Any => {
1156                    if self.is_negative_context() {
1157                        QuantifierKind::Existential
1158                    } else {
1159                        QuantifierKind::Universal
1160                    }
1161                }
1162                TokenType::Some => QuantifierKind::Existential,
1163                TokenType::Most => QuantifierKind::Most,
1164                TokenType::Few => QuantifierKind::Few,
1165                TokenType::Many => QuantifierKind::Many,
1166                TokenType::Cardinal(n) => QuantifierKind::Cardinal(n),
1167                TokenType::AtLeast(n) => QuantifierKind::AtLeast(n),
1168                TokenType::AtMost(n) => QuantifierKind::AtMost(n),
1169                _ => {
1170                    return Err(ParseError {
1171                        kind: ParseErrorKind::UnknownQuantifier {
1172                            found: quantifier_token.clone(),
1173                        },
1174                        span: self.current_span(),
1175                    })
1176                }
1177            };
1178
1179            let mut result = self.ctx.exprs.alloc(LogicExpr::Quantifier {
1180                kind,
1181                variable: var_name,
1182                body,
1183                island_id: self.current_island,
1184            });
1185
1186            for (_noun, donkey_var, used, wide_neg) in self.donkey_bindings.iter().rev() {
1187                if *used {
1188                    // Donkey anaphora: wrap with ∀ at outer scope
1189                    result = self.ctx.exprs.alloc(LogicExpr::Quantifier {
1190                        kind: QuantifierKind::Universal,
1191                        variable: *donkey_var,
1192                        body: result,
1193                        island_id: self.current_island,
1194                    });
1195                } else {
1196                    // Non-donkey: wrap with ∃ INSIDE the restriction
1197                    result = self.wrap_donkey_in_restriction(result, *donkey_var, *wide_neg);
1198                }
1199            }
1200            self.donkey_bindings.clear();
1201
1202            self.in_negative_quantifier = was_in_negative_quantifier;
1203            return Ok(result);
1204        }
1205
1206        // Handle do-support: "every X does not hold" → ¬Hold(x)
1207        if self.check(&TokenType::Does) || self.check(&TokenType::Do) {
1208            self.advance(); // consume "does"/"do"
1209            let negative = self.match_token(&[TokenType::Not]);
1210            // The verb after "does not" becomes the predicate
1211            let verb_sym = self.consume_verb();
1212            let predicate_expr = self.ctx.exprs.alloc(LogicExpr::Predicate {
1213                name: verb_sym,
1214                args: self.ctx.terms.alloc_slice([Term::Variable(var_name)]),
1215                world: None,
1216            });
1217            let final_predicate = if negative {
1218                self.ctx.exprs.alloc(LogicExpr::UnaryOp {
1219                    op: TokenType::Not,
1220                    operand: predicate_expr,
1221                })
1222            } else {
1223                predicate_expr
1224            };
1225
1226            let body = match quantifier_token {
1227                TokenType::All => self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1228                    left: subject_pred,
1229                    op: TokenType::Implies,
1230                    right: final_predicate,
1231                }),
1232                _ => self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1233                    left: subject_pred,
1234                    op: TokenType::And,
1235                    right: final_predicate,
1236                }),
1237            };
1238
1239            let result = self.ctx.exprs.alloc(LogicExpr::Quantifier {
1240                kind: match quantifier_token {
1241                    TokenType::All => QuantifierKind::Universal,
1242                    _ => QuantifierKind::Existential,
1243                },
1244                variable: var_name,
1245                body: body,
1246                island_id: self.current_island,
1247            });
1248            self.in_negative_quantifier = was_in_negative_quantifier;
1249            return Ok(result);
1250        }
1251
1252        self.consume_copula()?;
1253
1254        let negative = self.match_token(&[TokenType::Not]);
1255
1256        // Copula PP complement under a quantifier: "Every trip is in Florida (or in
1257        // Maine)" → ∀x(Trip(x) → In(x,Florida) [∨ In(x,Maine)]) — the domain-closure
1258        // a logic-grid bijection eliminates over. A leading preposition makes
1259        // parse_noun_phrase fail, dropping the clause to a backtracking path that
1260        // loses the or-coordination AND any trailing PP, so handle the PP — with its
1261        // disjunction — directly here.
1262        let is_pp_complement = self.check_preposition() && !self.check_by_preposition();
1263        let mut final_predicate = if is_pp_complement {
1264            let pp = self.parse_copula_pp_complement(var_name)?;
1265            if negative {
1266                self.ctx.exprs.alloc(LogicExpr::UnaryOp { op: TokenType::Not, operand: pp })
1267            } else {
1268                pp
1269            }
1270        } else {
1271            let predicate_np = self.parse_noun_phrase(true)?;
1272            let predicate_expr = self.ctx.exprs.alloc(LogicExpr::Predicate {
1273                name: predicate_np.noun,
1274                args: self.ctx.terms.alloc_slice([Term::Variable(var_name)]),
1275                world: None,
1276            });
1277            if negative {
1278                self.ctx.exprs.alloc(LogicExpr::UnaryOp {
1279                    op: TokenType::Not,
1280                    operand: predicate_expr,
1281                })
1282            } else {
1283                predicate_expr
1284            }
1285        };
1286
1287        // Disjunctive copula complement: "is red or blue", "is red, blue, or
1288        // green". Each disjunct is the same predicate form applied to the SAME
1289        // bound variable, so the `or` stays INSIDE the consequent (distributing
1290        // the variable) rather than being lifted to sentence level. A comma list
1291        // ("A, B, or C") coordinates with `or`; the trailing `or` before the
1292        // last item is optional after a comma. A `Comma` that is NOT part of such
1293        // a list (no following predicate complement) is left for the caller.
1294        // (The PP-complement case consumed its own or-coordination above.)
1295        while !is_pp_complement {
1296            let is_or = self.check(&TokenType::Or);
1297            let is_comma_list = self.check(&TokenType::Comma)
1298                && matches!(
1299                    self.tokens.get(self.current + 1).map(|t| &t.kind),
1300                    Some(TokenType::Adjective(_))
1301                        | Some(TokenType::Noun(_))
1302                        | Some(TokenType::ProperName(_))
1303                        | Some(TokenType::Or)
1304                );
1305            if !is_or && !is_comma_list {
1306                break;
1307            }
1308            if is_comma_list {
1309                self.advance(); // consume ","
1310                // "A, B, or C": drop the coordinating "or"/"and" after the comma.
1311                if self.check(&TokenType::Or) || self.check(&TokenType::And) {
1312                    self.advance();
1313                }
1314            } else {
1315                self.advance(); // consume "or"
1316            }
1317
1318            let disj_negative = self.match_token(&[TokenType::Not]);
1319            let disj_np = self.parse_noun_phrase(true)?;
1320            let disj_pred: &'a LogicExpr<'a> = self.ctx.exprs.alloc(LogicExpr::Predicate {
1321                name: disj_np.noun,
1322                args: self.ctx.terms.alloc_slice([Term::Variable(var_name)]),
1323                world: None,
1324            });
1325            let disj_pred = if disj_negative {
1326                self.ctx.exprs.alloc(LogicExpr::UnaryOp {
1327                    op: TokenType::Not,
1328                    operand: disj_pred,
1329                })
1330            } else {
1331                disj_pred
1332            };
1333            final_predicate = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1334                left: final_predicate,
1335                op: TokenType::Or,
1336                right: disj_pred,
1337            });
1338        }
1339
1340        let body = match quantifier_token {
1341            TokenType::All => self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1342                left: subject_pred,
1343                op: TokenType::Implies,
1344                right: final_predicate,
1345            }),
1346            TokenType::Any => {
1347                if self.is_negative_context() {
1348                    self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1349                        left: subject_pred,
1350                        op: TokenType::And,
1351                        right: final_predicate,
1352                    })
1353                } else {
1354                    self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1355                        left: subject_pred,
1356                        op: TokenType::Implies,
1357                        right: final_predicate,
1358                    })
1359                }
1360            }
1361            TokenType::Some
1362            | TokenType::Most
1363            | TokenType::Few
1364            | TokenType::Many
1365            | TokenType::Cardinal(_)
1366            | TokenType::AtLeast(_)
1367            | TokenType::AtMost(_) => self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1368                left: subject_pred,
1369                op: TokenType::And,
1370                right: final_predicate,
1371            }),
1372            TokenType::No => {
1373                let neg = self.ctx.exprs.alloc(LogicExpr::UnaryOp {
1374                    op: TokenType::Not,
1375                    operand: final_predicate,
1376                });
1377                self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1378                    left: subject_pred,
1379                    op: TokenType::Implies,
1380                    right: neg,
1381                })
1382            }
1383            _ => {
1384                return Err(ParseError {
1385                    kind: ParseErrorKind::UnknownQuantifier {
1386                        found: quantifier_token.clone(),
1387                    },
1388                    span: self.current_span(),
1389                })
1390            }
1391        };
1392
1393        let kind = match quantifier_token {
1394            TokenType::All | TokenType::No => QuantifierKind::Universal,
1395            TokenType::Any => {
1396                if self.is_negative_context() {
1397                    QuantifierKind::Existential
1398                } else {
1399                    QuantifierKind::Universal
1400                }
1401            }
1402            TokenType::Some => QuantifierKind::Existential,
1403            TokenType::Most => QuantifierKind::Most,
1404            TokenType::Few => QuantifierKind::Few,
1405            TokenType::Many => QuantifierKind::Many,
1406            TokenType::Cardinal(n) => QuantifierKind::Cardinal(n),
1407            TokenType::AtLeast(n) => QuantifierKind::AtLeast(n),
1408            TokenType::AtMost(n) => QuantifierKind::AtMost(n),
1409            _ => {
1410                return Err(ParseError {
1411                    kind: ParseErrorKind::UnknownQuantifier {
1412                        found: quantifier_token.clone(),
1413                    },
1414                    span: self.current_span(),
1415                })
1416            }
1417        };
1418
1419        let mut result = self.ctx.exprs.alloc(LogicExpr::Quantifier {
1420            kind,
1421            variable: var_name,
1422            body,
1423            island_id: self.current_island,
1424        });
1425
1426        for (_noun, donkey_var, used, wide_neg) in self.donkey_bindings.iter().rev() {
1427            if *used {
1428                // Donkey anaphora: wrap with ∀ at outer scope
1429                result = self.ctx.exprs.alloc(LogicExpr::Quantifier {
1430                    kind: QuantifierKind::Universal,
1431                    variable: *donkey_var,
1432                    body: result,
1433                    island_id: self.current_island,
1434                });
1435            } else {
1436                // Non-donkey: wrap with ∃ INSIDE the restriction
1437                result = self.wrap_donkey_in_restriction(result, *donkey_var, *wide_neg);
1438            }
1439        }
1440        self.donkey_bindings.clear();
1441
1442        self.in_negative_quantifier = was_in_negative_quantifier;
1443        Ok(result)
1444    }
1445
1446    /// Build the restriction conjunct contributed by a pre-nominal adjective,
1447    /// dispatching on the adjective's lexical class. This is the single shared
1448    /// site every NP-restriction path routes through, so the four classes are
1449    /// modeled identically everywhere (universal, indefinite, definite, copular):
1450    ///
1451    /// - **Relational / pertainymic** (lexicon `relational`): predicate of a
1452    ///   kind by default — `Rel(x, ^Base)` (no ∃) — or, at `level: Instance`,
1453    ///   an existential over a base-noun individual — `∃y(Base(y) ∧ Rel(x, y))`.
1454    ///   (McNally & Boleda 2004.)
1455    /// - **Subsective**: `Adj(x, ^Noun)` — graded against the head-noun kind.
1456    /// - **Intersective / other** (incl. NonIntersective, whose privative
1457    ///   meaning is supplied later by the axiom layer): flat `Adj(x)`.
1458    fn adjective_restriction(
1459        &mut self,
1460        adj: Symbol,
1461        var: Symbol,
1462        noun: Symbol,
1463    ) -> &'a LogicExpr<'a> {
1464        let adj_str = self.interner.resolve(adj).to_lowercase();
1465
1466        if let Some((base, relation, level)) = lookup_relational_adjective(&adj_str) {
1467            let base_sym = self.interner.intern(base);
1468            let rel_sym = self.interner.intern(relation);
1469            if level == "Instance" {
1470                // ∃y( Base(y) ∧ Rel(x, y) )
1471                let y = self.next_var_name();
1472                let base_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
1473                    name: base_sym,
1474                    args: self.ctx.terms.alloc_slice([Term::Variable(y)]),
1475                    world: None,
1476                });
1477                let rel_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
1478                    name: rel_sym,
1479                    args: self
1480                        .ctx
1481                        .terms
1482                        .alloc_slice([Term::Variable(var), Term::Variable(y)]),
1483                    world: None,
1484                });
1485                let body = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1486                    left: base_pred,
1487                    op: TokenType::And,
1488                    right: rel_pred,
1489                });
1490                return self.ctx.exprs.alloc(LogicExpr::Quantifier {
1491                    kind: QuantifierKind::Existential,
1492                    variable: y,
1493                    body,
1494                    island_id: self.current_island,
1495                });
1496            }
1497            // kind-level (default): Rel(x, ^Base)
1498            return self.ctx.exprs.alloc(LogicExpr::Predicate {
1499                name: rel_sym,
1500                args: self
1501                    .ctx
1502                    .terms
1503                    .alloc_slice([Term::Variable(var), Term::Kind(base_sym)]),
1504                world: None,
1505            });
1506        }
1507
1508        if is_subsective(&adj_str) {
1509            return self.ctx.exprs.alloc(LogicExpr::Predicate {
1510                name: adj,
1511                args: self
1512                    .ctx
1513                    .terms
1514                    .alloc_slice([Term::Variable(var), Term::Intension(noun)]),
1515                world: None,
1516            });
1517        }
1518
1519        self.ctx.exprs.alloc(LogicExpr::Predicate {
1520            name: adj,
1521            args: self.ctx.terms.alloc_slice([Term::Variable(var)]),
1522            world: None,
1523        })
1524    }
1525
1526    fn parse_restriction(&mut self, var_name: Symbol) -> ParseResult<&'a LogicExpr<'a>> {
1527        // Collect leading adjectives, then consume the head noun. The adjective
1528        // predicate forms (subsective `Adj(x, ^Noun)`, relational expansions)
1529        // need the head noun, so the noun is resolved before they are emitted.
1530        let mut adj_syms: Vec<Symbol> = Vec::new();
1531
1532        loop {
1533            if self.is_at_end() {
1534                break;
1535            }
1536
1537            let is_adjective = matches!(self.peek().kind, TokenType::Adjective(_));
1538            if !is_adjective {
1539                break;
1540            }
1541
1542            let next_is_content = if self.current + 1 < self.tokens.len() {
1543                matches!(
1544                    self.tokens[self.current + 1].kind,
1545                    TokenType::Noun(_) | TokenType::Adjective(_) | TokenType::ProperName(_)
1546                )
1547            } else {
1548                false
1549            };
1550
1551            if next_is_content {
1552                if let TokenType::Adjective(adj) = self.advance().kind.clone() {
1553                    adj_syms.push(adj);
1554                }
1555            } else {
1556                break;
1557            }
1558        }
1559
1560        let mut noun = self.consume_content_word()?;
1561        // Noun-noun compound restrictor ("Every chess game", "Each fire truck"):
1562        // join consecutive nouns into one symbol, mirroring parse_noun_phrase's
1563        // head compounding, so "Every chess game" doesn't strand "game".
1564        while let TokenType::Noun(next) = self.peek().kind {
1565            self.advance();
1566            noun = self.interner.intern(&format!(
1567                "{}_{}",
1568                self.interner.resolve(noun),
1569                self.interner.resolve(next)
1570            ));
1571        }
1572
1573        let mut conditions: Vec<&'a LogicExpr<'a>> = Vec::new();
1574        for adj in &adj_syms {
1575            let adj_pred = self.adjective_restriction(*adj, var_name, noun);
1576            conditions.push(adj_pred);
1577        }
1578        conditions.push(self.ctx.exprs.alloc(LogicExpr::Predicate {
1579            name: noun,
1580            args: self.ctx.terms.alloc_slice([Term::Variable(var_name)]),
1581            world: None,
1582        }));
1583
1584        // PP post-modifiers on the restrictor head ("every dog IN the park", "no
1585        // option IN any category", "every member OF the team"): conjoin
1586        // `Prep(var, obj)` so the quantifier ranges over the PP-restricted set. In
1587        // restrictor position the VP has not begun, so a preposition with an NP
1588        // object is always a restrictor modifier; a following verb/modal/copula ends
1589        // the restriction and is left untouched.
1590        while self.check_preposition() {
1591            let object_follows = matches!(
1592                self.tokens.get(self.current + 1).map(|t| &t.kind),
1593                Some(TokenType::Article(_))
1594                    | Some(TokenType::Noun(_))
1595                    | Some(TokenType::ProperName(_))
1596                    | Some(TokenType::All)
1597                    | Some(TokenType::Some)
1598                    | Some(TokenType::Any)
1599                    | Some(TokenType::Cardinal(_))
1600                    | Some(TokenType::Number(_))
1601            );
1602            if !object_follows {
1603                break;
1604            }
1605            let prep_token = self.advance().clone();
1606            let prep_name = if let TokenType::Preposition(sym) = prep_token.kind {
1607                sym
1608            } else {
1609                self.current -= 1;
1610                break;
1611            };
1612            // Skip a leading quantifier determiner in the PP object ("in ANY
1613            // category", "in EACH group") — the restrictor PP names the sort, and
1614            // `parse_noun_phrase` does not consume a bare quantifier determiner.
1615            if matches!(
1616                self.peek().kind,
1617                TokenType::Any | TokenType::All | TokenType::Some | TokenType::No
1618                    | TokenType::Most | TokenType::Few | TokenType::Many
1619            ) && matches!(
1620                self.tokens.get(self.current + 1).map(|t| &t.kind),
1621                Some(TokenType::Noun(_)) | Some(TokenType::Adjective(_))
1622            ) {
1623                self.advance();
1624            }
1625            let pp_np = self.parse_noun_phrase(false)?;
1626            conditions.push(self.ctx.exprs.alloc(LogicExpr::Predicate {
1627                name: prep_name,
1628                args: self
1629                    .ctx
1630                    .terms
1631                    .alloc_slice([Term::Variable(var_name), Term::Constant(pp_np.noun)]),
1632                world: None,
1633            }));
1634        }
1635
1636        while self.check(&TokenType::That) || self.check(&TokenType::Who) {
1637            self.advance();
1638            let clause_pred = self.parse_relative_clause(var_name)?;
1639            conditions.push(clause_pred);
1640        }
1641
1642        self.combine_with_and(conditions)
1643    }
1644
1645    fn parse_verb_phrase_for_restriction(&mut self, var_name: Symbol) -> ParseResult<&'a LogicExpr<'a>> {
1646        let var_term = Term::Variable(var_name);
1647        let verb = self.consume_verb();
1648        let verb_str_owned = self.interner.resolve(verb).to_string();
1649
1650        // Check EARLY if verb is lexically negative (e.g., "lacks" -> "Have" with negation)
1651        // This determines whether donkey bindings need wide scope negation
1652        let (canonical_verb, is_negative) = get_canonical_verb(&verb_str_owned.to_lowercase())
1653            .map(|(lemma, neg)| (self.interner.intern(lemma), neg))
1654            .unwrap_or((verb, false));
1655
1656        // Determine if this binding needs wide scope negation wrapping
1657        let needs_wide_scope = is_negative && self.negative_scope_mode == NegativeScopeMode::Wide;
1658
1659        if Lexer::is_raising_verb(&verb_str_owned) && self.check_to() {
1660            self.advance();
1661            if self.check_verb() {
1662                let inf_verb = self.consume_verb();
1663                let inf_verb_str = self.interner.resolve(inf_verb).to_lowercase();
1664
1665                if inf_verb_str == "be" && self.check_content_word() {
1666                    let adj = self.consume_content_word()?;
1667                    let embedded = self.ctx.exprs.alloc(LogicExpr::Predicate {
1668                        name: adj,
1669                        args: self.ctx.terms.alloc_slice([Term::Variable(var_name)]),
1670                        world: None,
1671                    });
1672                    return Ok(self.ctx.exprs.alloc(LogicExpr::Scopal {
1673                        operator: verb,
1674                        body: embedded,
1675                    }));
1676                }
1677
1678                let embedded = self.ctx.exprs.alloc(LogicExpr::Predicate {
1679                    name: inf_verb,
1680                    args: self.ctx.terms.alloc_slice([Term::Variable(var_name)]),
1681                    world: None,
1682                });
1683                return Ok(self.ctx.exprs.alloc(LogicExpr::Scopal {
1684                    operator: verb,
1685                    body: embedded,
1686                }));
1687            } else if self.check(&TokenType::Is) || self.check(&TokenType::Are) {
1688                self.advance();
1689                if self.check_content_word() {
1690                    let adj = self.consume_content_word()?;
1691                    let embedded = self.ctx.exprs.alloc(LogicExpr::Predicate {
1692                        name: adj,
1693                        args: self.ctx.terms.alloc_slice([Term::Variable(var_name)]),
1694                        world: None,
1695                    });
1696                    return Ok(self.ctx.exprs.alloc(LogicExpr::Scopal {
1697                        operator: verb,
1698                        body: embedded,
1699                    }));
1700                }
1701            }
1702        }
1703
1704        let mut args = vec![var_term];
1705        let mut extra_conditions: Vec<&'a LogicExpr<'a>> = Vec::new();
1706        // A counting-NP object inside the relative clause ("who did 49 previous
1707        // jumps") becomes a ∃=n entity wrapping the whole restriction (see end).
1708        let mut object_cardinal: Option<(u32, crate::intern::Symbol)> = None;
1709
1710        if self.check(&TokenType::Reflexive) {
1711            self.advance();
1712            args.push(Term::Variable(var_name));
1713        } else if let Some(n) = self.counting_np_lookahead() {
1714            // "who did 49 previous jumps" — a digit-led counting NP with an
1715            // adjective (or a deverbal-noun head): bind a fresh ∃=n entity that
1716            // is the Theme, predicating the noun + adjectives + PPs of it, so the
1717            // count and modifiers survive instead of collapsing to a measure that
1718            // eats the adjective.
1719            self.advance(); // the number
1720            let obj_var = self.next_var_name();
1721            self.nominal_np_context = true;
1722            let obj_np_result = self.parse_noun_phrase(false);
1723            self.nominal_np_context = false;
1724            let obj_np = obj_np_result?;
1725            let mut obj_restr: &'a LogicExpr<'a> = self.ctx.exprs.alloc(LogicExpr::Predicate {
1726                name: obj_np.noun,
1727                args: self.ctx.terms.alloc_slice([Term::Variable(obj_var)]),
1728                world: None,
1729            });
1730            for &adj in obj_np.adjectives {
1731                let adj_pred = self.adjective_restriction(adj, obj_var, obj_np.noun);
1732                obj_restr = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1733                    left: obj_restr,
1734                    op: TokenType::And,
1735                    right: adj_pred,
1736                });
1737            }
1738            for pp in obj_np.pps {
1739                let pp_sub = self.substitute_pp_placeholder(pp, obj_var);
1740                obj_restr = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1741                    left: obj_restr,
1742                    op: TokenType::And,
1743                    right: pp_sub,
1744                });
1745            }
1746            extra_conditions.push(obj_restr);
1747            args.push(Term::Variable(obj_var));
1748            object_cardinal = Some((n, obj_var));
1749        } else if self.check_number() {
1750            // "N unit NOUN" is a measure-PREMODIFIED noun ("requires 190 degree
1751            // WATER") — ONE folded entity (190_degree_water), not a measure with
1752            // the head stranded. A noun/ambiguous head TWO tokens past the number
1753            // (after the unit word) signals this; parse the whole NP (nominal
1754            // context so an ambiguous head folds). A pure finite VERB at +2 is the
1755            // MATRIX verb, not a head ("scored 190 points WON", "saw 6 manatees
1756            // WON") — those stay a bare measured object.
1757            let premodified_noun = matches!(
1758                self.tokens.get(self.current + 2).map(|t| &t.kind),
1759                Some(TokenType::Noun(_)) | Some(TokenType::Ambiguous { .. })
1760            );
1761            if premodified_noun {
1762                let saved_ctx = self.nominal_np_context;
1763                self.nominal_np_context = true;
1764                let np_result = self.parse_noun_phrase(false);
1765                self.nominal_np_context = saved_ctx;
1766                let np = np_result?;
1767                args.push(Term::Constant(np.noun));
1768            } else {
1769                // Measured object inside a relative clause ("that saw 6 manatees"):
1770                // the count is the Theme so the constraint isn't dropped.
1771                let measure = self.parse_measure_phrase()?;
1772                args.push(*measure);
1773            }
1774        } else if (self.check_content_word() || self.check_article())
1775            && (!self.check_verb() || {
1776                // A verb-headed object is normally NOT an object (avoids eating a
1777                // second verb), EXCEPT a gerund-noun compound ("used BOWLING
1778                // pins") where the -ing word is a pre-nominal modifier of the
1779                // following noun, so the whole thing is the noun object.
1780                matches!(
1781                    self.peek().kind,
1782                    TokenType::Verb { aspect: crate::lexicon::Aspect::Progressive, .. }
1783                ) && matches!(
1784                    self.tokens.get(self.current + 1).map(|t| &t.kind),
1785                    Some(TokenType::Noun(_))
1786                )
1787            })
1788        {
1789            if matches!(
1790                self.peek().kind,
1791                TokenType::Article(Definiteness::Indefinite)
1792            ) {
1793                // Parse the FULL object NP so a noun-noun compound folds ("a yoga
1794                // REGIMEN" → Yoga_regimen) instead of stranding the head — a single
1795                // consume_content_word() grabbed only the first word. Adjectives /
1796                // PPs are conjoined below (zero loss). NOTE: do NOT set
1797                // nominal_np_context here — in "Most farmers who own a donkey BEAT
1798                // it" the base-form NUCLEAR verb "beat" follows the object, and
1799                // nominal context would greedily fold it ("donkey_beat"), eating
1800                // the quantifier's nuclear scope. The Noun+Noun fold needs no flag.
1801                let obj_np = self.parse_noun_phrase(false)?;
1802                let noun = obj_np.noun;
1803                let donkey_var = self.next_var_name();
1804
1805                if needs_wide_scope {
1806                    // === WIDE SCOPE MODE ===
1807                    // Build ¬∃y(Key(y) ∧ ∃e(Have(e) ∧ Agent(e,x) ∧ Theme(e,y))) directly
1808                    //
1809                    // We capture the binding HERE and return the complete structure.
1810                    // DO NOT push to donkey_bindings - that would leak y to outer scope.
1811
1812                    // Build: Key(y)
1813                    let restriction_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
1814                        name: noun,
1815                        args: self.ctx.terms.alloc_slice([Term::Variable(donkey_var)]),
1816                        world: None,
1817                    });
1818
1819                    // Build: ∃e(Have(e) ∧ Agent(e,x) ∧ Theme(e,y)) using Neo-Davidsonian semantics
1820                    // IMPORTANT: Use build_verb_neo_event() for consistent Full-tier formatting
1821                    let inner_modifiers = self.collect_adverbs();
1822                    let verb_pred = self.build_verb_neo_event(
1823                        canonical_verb,
1824                        var_name,
1825                        Some(Term::Variable(donkey_var)),
1826                        inner_modifiers,
1827                    );
1828
1829                    // Build: Key(y) ∧ ∃e(Have(e) ∧ Agent(e,x) ∧ Theme(e,y))
1830                    let body = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1831                        left: restriction_pred,
1832                        op: TokenType::And,
1833                        right: verb_pred,
1834                    });
1835
1836                    // Build: ∃y(Key(y) ∧ ∃e(Have(e) ∧ ...))
1837                    let existential = self.ctx.exprs.alloc(LogicExpr::Quantifier {
1838                        kind: QuantifierKind::Existential,
1839                        variable: donkey_var,
1840                        body,
1841                        island_id: self.current_island,
1842                    });
1843
1844                    // Build: ¬∃y(Key(y) ∧ ∃e(Have(e) ∧ ...))
1845                    let negated_existential = self.ctx.exprs.alloc(LogicExpr::UnaryOp {
1846                        op: TokenType::Not,
1847                        operand: existential,
1848                    });
1849
1850                    // Return the complete wide-scope structure directly
1851                    return Ok(negated_existential);
1852                }
1853
1854                // === NARROW SCOPE MODE ===
1855                // Push binding for later processing (normal donkey binding flow)
1856                self.donkey_bindings.push((noun, donkey_var, false, false));
1857
1858                let mut obj_restr: &'a LogicExpr<'a> = self.ctx.exprs.alloc(LogicExpr::Predicate {
1859                    name: noun,
1860                    args: self.ctx.terms.alloc_slice([Term::Variable(donkey_var)]),
1861                    world: None,
1862                });
1863                for &adj in obj_np.adjectives {
1864                    let adj_pred = self.adjective_restriction(adj, donkey_var, noun);
1865                    obj_restr = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1866                        left: obj_restr,
1867                        op: TokenType::And,
1868                        right: adj_pred,
1869                    });
1870                }
1871                for pp in obj_np.pps {
1872                    let pp_sub = self.substitute_pp_placeholder(pp, donkey_var);
1873                    obj_restr = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1874                        left: obj_restr,
1875                        op: TokenType::And,
1876                        right: pp_sub,
1877                    });
1878                }
1879                extra_conditions.push(obj_restr);
1880
1881                args.push(Term::Variable(donkey_var));
1882            } else {
1883                let object = self.parse_noun_phrase(false)?;
1884
1885                if self.check(&TokenType::That) || self.check(&TokenType::Who) {
1886                    self.advance();
1887                    let nested_var = self.next_var_name();
1888                    let nested_rel = self.parse_relative_clause(nested_var)?;
1889
1890                    extra_conditions.push(self.ctx.exprs.alloc(LogicExpr::Predicate {
1891                        name: object.noun,
1892                        args: self.ctx.terms.alloc_slice([Term::Variable(nested_var)]),
1893                        world: None,
1894                    }));
1895                    extra_conditions.push(nested_rel);
1896                    args.push(Term::Variable(nested_var));
1897                } else {
1898                    args.push(Term::Constant(object.noun));
1899                    // The object's PP modifiers ("won the prize IN CHEMISTRY") restrict
1900                    // the object; lower them over the object constant so they are not
1901                    // dropped — the main-clause VP keeps them, this relative VP must too.
1902                    for pp in object.pps {
1903                        let pp_sub =
1904                            self.substitute_pp_self_term(pp, Term::Constant(object.noun));
1905                        extra_conditions.push(pp_sub);
1906                    }
1907                }
1908            }
1909        }
1910
1911        while self.check_preposition() {
1912            let prep_sym = match self.peek().kind {
1913                TokenType::Preposition(s) => Some(s),
1914                _ => None,
1915            };
1916            self.advance();
1917            // After a direct object has been taken, a trailing PP modifies the EVENT
1918            // ("won the prize IN CHEMISTRY", "graduated FROM Yale"): the object stays
1919            // the Theme and the PP becomes a separate predicate over the event var
1920            // (the same one build_verb_neo_event uses). Previously these trailing PP
1921            // objects were pushed past args[1] and silently dropped by `obj_term =
1922            // args.remove(1)`, losing both the preposition and the object — the
1923            // main-clause VP keeps them, so the relative/quantified VP must too. With
1924            // no direct object yet, the legacy measured-arg behavior is preserved.
1925            let attach_to_event = prep_sym.is_some() && args.len() > 1;
1926            if self.check(&TokenType::Reflexive) {
1927                self.advance();
1928                args.push(Term::Variable(var_name));
1929            } else if self.check_number() {
1930                // Numeric PP object in a relative clause ("that cooks at 385
1931                // degrees", "that sell for $2.50"): keep the measured amount.
1932                let measure = self.parse_measure_phrase()?;
1933                if attach_to_event {
1934                    let ev = self.get_event_var();
1935                    extra_conditions.push(self.ctx.exprs.alloc(LogicExpr::Predicate {
1936                        name: prep_sym.unwrap(),
1937                        args: self.ctx.terms.alloc_slice([Term::Variable(ev), *measure]),
1938                        world: None,
1939                    }));
1940                } else {
1941                    args.push(*measure);
1942                }
1943            } else if self.check_content_word() || self.check_article() {
1944                let object = self.parse_noun_phrase(false)?;
1945
1946                if self.check(&TokenType::That) || self.check(&TokenType::Who) {
1947                    self.advance();
1948                    let nested_var = self.next_var_name();
1949                    let nested_rel = self.parse_relative_clause(nested_var)?;
1950                    extra_conditions.push(self.ctx.exprs.alloc(LogicExpr::Predicate {
1951                        name: object.noun,
1952                        args: self.ctx.terms.alloc_slice([Term::Variable(nested_var)]),
1953                        world: None,
1954                    }));
1955                    extra_conditions.push(nested_rel);
1956                    args.push(Term::Variable(nested_var));
1957                } else if attach_to_event {
1958                    let ev = self.get_event_var();
1959                    extra_conditions.push(self.ctx.exprs.alloc(LogicExpr::Predicate {
1960                        name: prep_sym.unwrap(),
1961                        args: self
1962                            .ctx
1963                            .terms
1964                            .alloc_slice([Term::Variable(ev), Term::Constant(object.noun)]),
1965                        world: None,
1966                    }));
1967                } else {
1968                    args.push(Term::Constant(object.noun));
1969                }
1970            }
1971        }
1972
1973        // Use the canonical verb determined at top of function
1974        // Extract object term from args if present (args[0] is subject, args[1] is object)
1975        let obj_term = if args.len() > 1 {
1976            Some(args.remove(1))
1977        } else {
1978            None
1979        };
1980        let final_modifiers = self.collect_adverbs();
1981        let base_pred = self.build_verb_neo_event(canonical_verb, var_name, obj_term, final_modifiers);
1982
1983        // Wrap in negation only for NARROW scope mode (de re reading)
1984        // Wide scope mode: negation handled via donkey binding flag in wrap_donkey_in_restriction
1985        // - Narrow: ∃y(Key(y) ∧ ¬Have(x,y)) - "missing ANY key"
1986        // - Wide:   ¬∃y(Key(y) ∧ Have(x,y)) - "has NO keys"
1987        let verb_pred = if is_negative && self.negative_scope_mode == NegativeScopeMode::Narrow {
1988            self.ctx.exprs.alloc(LogicExpr::UnaryOp {
1989                op: TokenType::Not,
1990                operand: base_pred,
1991            })
1992        } else {
1993            base_pred
1994        };
1995
1996        let mut restriction = if extra_conditions.is_empty() {
1997            verb_pred
1998        } else {
1999            extra_conditions.push(verb_pred);
2000            self.combine_with_and(extra_conditions)?
2001        };
2002
2003        // A trailing positional/temporal adverb inside the relative clause ("the
2004        // person who went FIRST", "the racer who finished LAST") anchors the
2005        // event's ordinal position — the same TemporalAnchor the main clause
2006        // builds. Without this it strands and the relative clause fails to close.
2007        if self.check_temporal_adverb() {
2008            if let TokenType::TemporalAdverb(anchor) = self.advance().kind.clone() {
2009                restriction = self.ctx.exprs.alloc(LogicExpr::TemporalAnchor {
2010                    anchor,
2011                    body: restriction,
2012                });
2013            }
2014        }
2015
2016        // A counting-NP object binds the relative-clause body as ∃=n: "the
2017        // skydiver who did 49 previous jumps" → Skydiver(s) ∧ ∃=49 j(Jump(j) ∧
2018        // Previous(j) ∧ event(s, j)). The object quantifier scopes inside the
2019        // subject's own binding (the caller conjoins the head-noun predicate).
2020        if let Some((n, obj_var)) = object_cardinal {
2021            restriction = self.ctx.exprs.alloc(LogicExpr::Quantifier {
2022                kind: QuantifierKind::Cardinal(n),
2023                variable: obj_var,
2024                body: restriction,
2025                island_id: self.current_island,
2026            });
2027        }
2028
2029        Ok(restriction)
2030    }
2031
2032    fn combine_with_and(&self, mut exprs: Vec<&'a LogicExpr<'a>>) -> ParseResult<&'a LogicExpr<'a>> {
2033        if exprs.is_empty() {
2034            return Err(ParseError {
2035                kind: ParseErrorKind::EmptyRestriction,
2036                span: self.current_span(),
2037            });
2038        }
2039        if exprs.len() == 1 {
2040            return Ok(exprs.remove(0));
2041        }
2042        let mut root = exprs.remove(0);
2043        for expr in exprs {
2044            root = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
2045                left: root,
2046                op: TokenType::And,
2047                right: expr,
2048            });
2049        }
2050        Ok(root)
2051    }
2052
2053    fn wrap_with_definiteness_full(
2054        &mut self,
2055        np: &NounPhrase<'a>,
2056        predicate: &'a LogicExpr<'a>,
2057    ) -> ParseResult<&'a LogicExpr<'a>> {
2058        let result = self.wrap_with_definiteness_and_adjectives_and_pps(
2059            np.definiteness,
2060            np.noun,
2061            np.adjectives,
2062            np.pps,
2063            predicate,
2064        )?;
2065
2066        // If NP has a superlative, add the superlative constraint
2067        let result = if let Some(adj) = np.superlative {
2068            let superlative_expr = self.ctx.exprs.alloc(LogicExpr::Superlative {
2069                adjective: adj,
2070                subject: self.ctx.terms.alloc(Term::Constant(np.noun)),
2071                domain: np.noun,
2072            });
2073            self.ctx.exprs.alloc(LogicExpr::BinaryOp {
2074                left: result,
2075                op: TokenType::And,
2076                right: superlative_expr,
2077            })
2078        } else {
2079            result
2080        };
2081
2082        // A possessive definite carries an existence presupposition (§8.2):
2083        // "His children are happy." ≫ He has children. The presupposed
2084        // possession is a real event — the same shape "He has children."
2085        // parses to — so it is derivable, not just printable.
2086        if let Some(possessor) = np.possessor {
2087            use crate::ast::logic::NeoEventData;
2088            use crate::ast::ThematicRole;
2089            let possessed = Term::Constant(np.noun);
2090            // A DESCRIPTIVE possessor ("the Woodard family", "the red team")
2091            // carries its own restrictor; collapsing it to the bare head silently
2092            // drops the modifier ("Woodard"/"red"). `possessor_entity` lifts it to
2093            // its own existential entity carrying the full restrictor, so the
2094            // possession event's Agent is that entity, not a stripped constant:
2095            //   ∃p(Restrictor(p) ∧ ∃e(Have(e) ∧ Agent(e,p) ∧ Theme(e,possessed))).
2096            // A bare possessor ("Agnes", "His") keeps the constant-Agent form.
2097            let (agent_term, agent_restr) = self.possessor_entity(possessor);
2098            let have = self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
2099                event_var: self.interner.intern("e"),
2100                verb: self.interner.intern("Have"),
2101                roles: self.ctx.roles.alloc_slice(vec![
2102                    (ThematicRole::Agent, agent_term),
2103                    (ThematicRole::Theme, possessed),
2104                ]),
2105                modifiers: self.ctx.syms.alloc_slice(vec![]),
2106                suppress_existential: false,
2107                world: None,
2108            })));
2109            let presupposition = self.wrap_in_possessor_entity(agent_restr, have);
2110            return Ok(self.ctx.exprs.alloc(LogicExpr::Presupposition {
2111                assertion: result,
2112                presupposition,
2113            }));
2114        }
2115
2116        Ok(result)
2117    }
2118
2119    fn wrap_with_definiteness(
2120        &mut self,
2121        definiteness: Option<Definiteness>,
2122        noun: Symbol,
2123        predicate: &'a LogicExpr<'a>,
2124    ) -> ParseResult<&'a LogicExpr<'a>> {
2125        self.wrap_with_definiteness_and_adjectives_and_pps(definiteness, noun, &[], &[], predicate)
2126    }
2127
2128    fn wrap_with_definiteness_and_adjectives(
2129        &mut self,
2130        definiteness: Option<Definiteness>,
2131        noun: Symbol,
2132        adjectives: &[Symbol],
2133        predicate: &'a LogicExpr<'a>,
2134    ) -> ParseResult<&'a LogicExpr<'a>> {
2135        self.wrap_with_definiteness_and_adjectives_and_pps(
2136            definiteness,
2137            noun,
2138            adjectives,
2139            &[],
2140            predicate,
2141        )
2142    }
2143
2144    fn wrap_with_definiteness_and_adjectives_and_pps(
2145        &mut self,
2146        definiteness: Option<Definiteness>,
2147        noun: Symbol,
2148        adjectives: &[Symbol],
2149        pps: &[&'a LogicExpr<'a>],
2150        predicate: &'a LogicExpr<'a>,
2151    ) -> ParseResult<&'a LogicExpr<'a>> {
2152        // Fold in a stashed subject relative-clause restriction (expressed over
2153        // `Constant(noun)`): conjoining it onto the predicate means every
2154        // definiteness branch's constant→variable substitution re-binds it to
2155        // the same entity, so the relative clause is never silently dropped.
2156        let predicate = match self.pending_subject_restriction {
2157            Some((restr_noun, restr)) if restr_noun == noun => {
2158                self.pending_subject_restriction = None;
2159                self.ctx.exprs.alloc(LogicExpr::BinaryOp {
2160                    left: restr,
2161                    op: TokenType::And,
2162                    right: predicate,
2163                })
2164            }
2165            _ => predicate,
2166        };
2167        match definiteness {
2168            Some(Definiteness::Indefinite) => {
2169                let var = self.next_var_name();
2170
2171                // Introduce referent into DRS for cross-sentence anaphora
2172                // If inside a "No" quantifier, mark as NegationScope (inaccessible)
2173                let gender = Self::infer_noun_gender(self.interner.resolve(noun));
2174                let number = if Self::is_plural_noun(self.interner.resolve(noun)) {
2175                    Number::Plural
2176                } else {
2177                    Number::Singular
2178                };
2179                if self.in_negative_quantifier {
2180                    self.drs.introduce_referent_with_source(var, noun, gender, number, ReferentSource::NegationScope);
2181                } else {
2182                    self.drs.introduce_referent(var, noun, gender, number);
2183                }
2184
2185                let mut restriction = self.ctx.exprs.alloc(LogicExpr::Predicate {
2186                    name: noun,
2187                    args: self.ctx.terms.alloc_slice([Term::Variable(var)]),
2188                    world: None,
2189                });
2190
2191                for adj in adjectives {
2192                    let adj_pred = self.adjective_restriction(*adj, var, noun);
2193                    restriction = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
2194                        left: restriction,
2195                        op: TokenType::And,
2196                        right: adj_pred,
2197                    });
2198                }
2199
2200                for pp in pps {
2201                    let substituted_pp = self.substitute_pp_placeholder(pp, var);
2202                    restriction = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
2203                        left: restriction,
2204                        op: TokenType::And,
2205                        right: substituted_pp,
2206                    });
2207                }
2208
2209                let substituted = self.substitute_constant_with_var_sym(predicate, noun, var)?;
2210                let body = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
2211                    left: restriction,
2212                    op: TokenType::And,
2213                    right: substituted,
2214                });
2215                Ok(self.ctx.exprs.alloc(LogicExpr::Quantifier {
2216                    kind: QuantifierKind::Existential,
2217                    variable: var,
2218                    body,
2219                    island_id: self.current_island,
2220                }))
2221            }
2222            Some(Definiteness::Definite) => {
2223                let noun_str = self.interner.resolve(noun).to_string();
2224
2225                if Self::is_plural_noun(&noun_str) {
2226                    let singular = Self::singularize_noun(&noun_str);
2227                    let singular_sym = self.interner.intern(&singular);
2228                    let sigma_term = Term::Sigma(singular_sym);
2229
2230                    let mut substituted =
2231                        self.substitute_constant_with_sigma(predicate, noun, sigma_term)?;
2232
2233                    // A definite plural's adjectives and PPs restrict the σ-term
2234                    // — "The species from Australia won." keeps From(σSpecies,
2235                    // Australia) instead of dropping the origin constraint.
2236                    for adj in adjectives {
2237                        let adj_pred = self.adjective_restriction(*adj, singular_sym, noun);
2238                        let adj_on_sigma =
2239                            self.substitute_constant_with_sigma(adj_pred, singular_sym, sigma_term)?;
2240                        substituted = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
2241                            left: adj_on_sigma,
2242                            op: TokenType::And,
2243                            right: substituted,
2244                        });
2245                    }
2246                    let placeholder = self.interner.intern("_PP_SELF_");
2247                    for pp in pps {
2248                        let pp_sub = match pp {
2249                            LogicExpr::Predicate { name, args, world } => {
2250                                let new_args: Vec<Term<'a>> = args
2251                                    .iter()
2252                                    .map(|a| match a {
2253                                        Term::Variable(v) if *v == placeholder => sigma_term,
2254                                        other => *other,
2255                                    })
2256                                    .collect();
2257                                self.ctx.exprs.alloc(LogicExpr::Predicate {
2258                                    name: *name,
2259                                    args: self.ctx.terms.alloc_slice(new_args),
2260                                    world: *world,
2261                                })
2262                            }
2263                            other => *other,
2264                        };
2265                        substituted = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
2266                            left: substituted,
2267                            op: TokenType::And,
2268                            right: pp_sub,
2269                        });
2270                    }
2271
2272                    let verb_name = self.find_main_verb_name(predicate);
2273                    let is_collective = verb_name
2274                        .map(|v| {
2275                            let lemma = self.interner.resolve(v);
2276                            Lexer::is_collective_verb(lemma)
2277                                // A mixed verb ("lift", "carry") defaults to
2278                                // the COLLECTIVE reading for a plural definite
2279                                // ("the boys lifted the piano" — together);
2280                                // a floated "all/each" forces distribution.
2281                                || (Lexer::is_mixed_verb(lemma) && !self.distributive_marker)
2282                        })
2283                        .unwrap_or(false);
2284
2285                    // Introduce definite plural referent to DRS for cross-sentence pronoun resolution
2286                    // E.g., "The dogs ran. They barked." - "they" refers to "dogs"
2287                    // Definite descriptions presuppose existence, so they should be globally accessible.
2288                    let gender = Gender::Unknown;  // Plural entities have unknown gender
2289                    self.drs.introduce_referent_with_source(singular_sym, singular_sym, gender, Number::Plural, ReferentSource::MainClause);
2290
2291                    if is_collective {
2292                        Ok(substituted)
2293                    } else {
2294                        Ok(self.ctx.exprs.alloc(LogicExpr::Distributive {
2295                            predicate: substituted,
2296                        }))
2297                    }
2298                } else {
2299                    // Van der Sandt: a definite BINDS to an accessible
2300                    // antecedent before accommodating — a re-mentioned "the
2301                    // kettle" reuses the referent instead of asserting a
2302                    // second Russell expansion (existence + uniqueness).
2303                    if adjectives.is_empty() && pps.is_empty() {
2304                        if let Some(prior) =
2305                            self.drs.resolve_definite(self.drs.current_box_index(), noun)
2306                        {
2307                            if prior == noun {
2308                                return Ok(predicate);
2309                            }
2310                            return self.substitute_constant_with_var_sym(
2311                                predicate, noun, prior,
2312                            );
2313                        }
2314                    }
2315
2316                    // Context-driven coreference for a MODIFIED definite whose
2317                    // head noun did not already resolve: "the hunting trip"
2318                    // binds to a prior "the hunting vacation" because the
2319                    // distinguishing modifier (Hunt) matches and trip/vacation
2320                    // are sort-compatible occasions. The modifier does the
2321                    // referring; the head noun is a soft type. No synonymy
2322                    // axiom is asserted — the later description simply reuses
2323                    // the earlier referent's variable.
2324                    if !adjectives.is_empty() && pps.is_empty() {
2325                        if let Some(prior) = self.drs.resolve_definite_by_modifier(
2326                            self.interner,
2327                            self.drs.current_box_index(),
2328                            noun,
2329                            adjectives,
2330                        ) {
2331                            return self.substitute_constant_with_var_sym(
2332                                predicate, noun, prior,
2333                            );
2334                        }
2335                    }
2336
2337                    // In DISCOURSE mode an unbound, non-bridging definite is
2338                    // SKOLEMIZED: Russell's uniqueness licenses a witness
2339                    // constant, so cross-sentence mentions stay rigidly
2340                    // linked — `Barber(B) ∧ ∀y(Barber(y) → y = B) ∧ P(B)`
2341                    // (the very form the proof engine's definite-description
2342                    // machinery expects) — never an ∃ whose bound variable
2343                    // later premises cannot reach.
2344                    if self.world_state.in_discourse_mode()
2345                        && adjectives.is_empty()
2346                        && pps.is_empty()
2347                        && self.drs.resolve_bridging(self.interner, noun).is_none()
2348                    {
2349                        let y = self.next_var_name();
2350                        let gender = Self::infer_noun_gender(self.interner.resolve(noun));
2351                        let number = if Self::is_plural_noun(self.interner.resolve(noun)) {
2352                            Number::Plural
2353                        } else {
2354                            Number::Singular
2355                        };
2356                        // Rigid (skolem) referent: later definites and
2357                        // pronouns resolve to the constant itself.
2358                        self.drs.introduce_referent_with_source(
2359                            noun,
2360                            noun,
2361                            gender,
2362                            number,
2363                            ReferentSource::ProperName,
2364                        );
2365                        let restriction = self.ctx.exprs.alloc(LogicExpr::Predicate {
2366                            name: noun,
2367                            args: self.ctx.terms.alloc_slice([Term::Constant(noun)]),
2368                            world: None,
2369                        });
2370                        let y_restriction = self.ctx.exprs.alloc(LogicExpr::Predicate {
2371                            name: noun,
2372                            args: self.ctx.terms.alloc_slice([Term::Variable(y)]),
2373                            world: None,
2374                        });
2375                        let identity = self.ctx.exprs.alloc(LogicExpr::Identity {
2376                            left: self.ctx.terms.alloc(Term::Variable(y)),
2377                            right: self.ctx.terms.alloc(Term::Constant(noun)),
2378                        });
2379                        let uniqueness = self.ctx.exprs.alloc(LogicExpr::Quantifier {
2380                            kind: QuantifierKind::Universal,
2381                            variable: y,
2382                            body: self.ctx.exprs.alloc(LogicExpr::BinaryOp {
2383                                left: y_restriction,
2384                                op: TokenType::Implies,
2385                                right: identity,
2386                            }),
2387                            island_id: self.current_island,
2388                        });
2389                        let described = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
2390                            left: restriction,
2391                            op: TokenType::And,
2392                            right: uniqueness,
2393                        });
2394                        return Ok(self.ctx.exprs.alloc(LogicExpr::BinaryOp {
2395                            left: described,
2396                            op: TokenType::And,
2397                            right: predicate,
2398                        }));
2399                    }
2400
2401                    let x = self.next_var_name();
2402                    let y = self.next_var_name();
2403
2404                    let mut restriction = self.ctx.exprs.alloc(LogicExpr::Predicate {
2405                        name: noun,
2406                        args: self.ctx.terms.alloc_slice([Term::Variable(x)]),
2407                        world: None,
2408                    });
2409
2410                    for adj in adjectives {
2411                        let adj_pred = self.adjective_restriction(*adj, x, noun);
2412                        restriction = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
2413                            left: restriction,
2414                            op: TokenType::And,
2415                            right: adj_pred,
2416                        });
2417                    }
2418
2419                    for pp in pps {
2420                        let substituted_pp = self.substitute_pp_placeholder(pp, x);
2421                        restriction = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
2422                            left: restriction,
2423                            op: TokenType::And,
2424                            right: substituted_pp,
2425                        });
2426                    }
2427
2428                    // Bridging anaphora: check if this noun is a part of a previously mentioned whole
2429                    // E.g., "I bought a car. The engine smoked." - engine is part of car
2430                    let has_prior_antecedent = self.drs.resolve_definite(
2431                        self.drs.current_box_index(),
2432                        noun
2433                    ).is_some();
2434
2435                    if !has_prior_antecedent {
2436                        if let Some((whole_var, _whole_name)) = self.drs.resolve_bridging(self.interner, noun) {
2437                            let part_of_sym = self.interner.intern("PartOf");
2438                            let part_of_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
2439                                name: part_of_sym,
2440                                args: self.ctx.terms.alloc_slice([
2441                                    Term::Variable(x),
2442                                    Term::Constant(whole_var),
2443                                ]),
2444                                world: None,
2445                            });
2446                            restriction = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
2447                                left: restriction,
2448                                op: TokenType::And,
2449                                right: part_of_pred,
2450                            });
2451                        }
2452                    }
2453
2454                    // Introduce definite referent to DRS for cross-sentence pronoun resolution
2455                    // E.g., "The engine smoked. It broke." - "it" refers to "engine"
2456                    // Definite descriptions presuppose existence, so they should be globally
2457                    // accessible even when introduced inside conditional antecedents.
2458                    let gender = Self::infer_noun_gender(self.interner.resolve(noun));
2459                    let number = if Self::is_plural_noun(self.interner.resolve(noun)) {
2460                        Number::Plural
2461                    } else {
2462                        Number::Singular
2463                    };
2464                    // Carry the distinguishing modifier(s) so a later definite
2465                    // ("the hunting trip") can corefer to this one ("the
2466                    // hunting vacation") through a shared modifier + compatible
2467                    // occasion sort.
2468                    self.drs.introduce_referent_with_modifiers(
2469                        x,
2470                        noun,
2471                        gender,
2472                        number,
2473                        ReferentSource::MainClause,
2474                        adjectives.to_vec(),
2475                    );
2476
2477                    let mut y_restriction = self.ctx.exprs.alloc(LogicExpr::Predicate {
2478                        name: noun,
2479                        args: self.ctx.terms.alloc_slice([Term::Variable(y)]),
2480                        world: None,
2481                    });
2482                    for adj in adjectives {
2483                        let adj_pred = self.adjective_restriction(*adj, y, noun);
2484                        y_restriction = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
2485                            left: y_restriction,
2486                            op: TokenType::And,
2487                            right: adj_pred,
2488                        });
2489                    }
2490
2491                    for pp in pps {
2492                        let substituted_pp = self.substitute_pp_placeholder(pp, y);
2493                        y_restriction = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
2494                            left: y_restriction,
2495                            op: TokenType::And,
2496                            right: substituted_pp,
2497                        });
2498                    }
2499
2500                    let identity = self.ctx.exprs.alloc(LogicExpr::Identity {
2501                        left: self.ctx.terms.alloc(Term::Variable(y)),
2502                        right: self.ctx.terms.alloc(Term::Variable(x)),
2503                    });
2504                    let uniqueness_body = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
2505                        left: y_restriction,
2506                        op: TokenType::Implies,
2507                        right: identity,
2508                    });
2509                    let uniqueness = self.ctx.exprs.alloc(LogicExpr::Quantifier {
2510                        kind: QuantifierKind::Universal,
2511                        variable: y,
2512                        body: uniqueness_body,
2513                        island_id: self.current_island,
2514                    });
2515
2516                    let main_pred = self.substitute_constant_with_var_sym(predicate, noun, x)?;
2517
2518                    let inner = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
2519                        left: restriction,
2520                        op: TokenType::And,
2521                        right: uniqueness,
2522                    });
2523                    let body = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
2524                        left: inner,
2525                        op: TokenType::And,
2526                        right: main_pred,
2527                    });
2528
2529                    Ok(self.ctx.exprs.alloc(LogicExpr::Quantifier {
2530                        kind: QuantifierKind::Existential,
2531                        variable: x,
2532                        body,
2533                        island_id: self.current_island,
2534                    }))
2535                }
2536            }
2537            Some(Definiteness::Proximal) | Some(Definiteness::Distal) => {
2538                let var = self.next_var_name();
2539
2540                let mut restriction = self.ctx.exprs.alloc(LogicExpr::Predicate {
2541                    name: noun,
2542                    args: self.ctx.terms.alloc_slice([Term::Variable(var)]),
2543                    world: None,
2544                });
2545
2546                let deictic_name = if matches!(definiteness, Some(Definiteness::Proximal)) {
2547                    self.interner.intern("Proximal")
2548                } else {
2549                    self.interner.intern("Distal")
2550                };
2551                let deictic_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
2552                    name: deictic_name,
2553                    args: self.ctx.terms.alloc_slice([Term::Variable(var)]),
2554                    world: None,
2555                });
2556                restriction = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
2557                    left: restriction,
2558                    op: TokenType::And,
2559                    right: deictic_pred,
2560                });
2561
2562                for adj in adjectives {
2563                    let adj_pred = self.adjective_restriction(*adj, var, noun);
2564                    restriction = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
2565                        left: restriction,
2566                        op: TokenType::And,
2567                        right: adj_pred,
2568                    });
2569                }
2570
2571                for pp in pps {
2572                    let substituted_pp = self.substitute_pp_placeholder(pp, var);
2573                    restriction = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
2574                        left: restriction,
2575                        op: TokenType::And,
2576                        right: substituted_pp,
2577                    });
2578                }
2579
2580                let substituted = self.substitute_constant_with_var_sym(predicate, noun, var)?;
2581                let body = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
2582                    left: restriction,
2583                    op: TokenType::And,
2584                    right: substituted,
2585                });
2586                Ok(self.ctx.exprs.alloc(LogicExpr::Quantifier {
2587                    kind: QuantifierKind::Existential,
2588                    variable: var,
2589                    body,
2590                    island_id: self.current_island,
2591                }))
2592            }
2593            None => Ok(predicate),
2594        }
2595    }
2596
2597    fn wrap_with_definiteness_for_object(
2598        &mut self,
2599        definiteness: Option<Definiteness>,
2600        noun: Symbol,
2601        predicate: &'a LogicExpr<'a>,
2602    ) -> ParseResult<&'a LogicExpr<'a>> {
2603        match definiteness {
2604            Some(Definiteness::Indefinite) => {
2605                let var = self.next_var_name();
2606
2607                // Introduce referent into DRS for cross-sentence anaphora
2608                // If inside a "No" quantifier, mark as NegationScope (inaccessible)
2609                let gender = Self::infer_noun_gender(self.interner.resolve(noun));
2610                let number = if Self::is_plural_noun(self.interner.resolve(noun)) {
2611                    Number::Plural
2612                } else {
2613                    Number::Singular
2614                };
2615                if self.in_negative_quantifier {
2616                    self.drs.introduce_referent_with_source(var, noun, gender, number, ReferentSource::NegationScope);
2617                } else {
2618                    self.drs.introduce_referent(var, noun, gender, number);
2619                }
2620
2621                let type_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
2622                    name: noun,
2623                    args: self.ctx.terms.alloc_slice([Term::Variable(var)]),
2624                    world: None,
2625                });
2626                let substituted = self.substitute_constant_with_var(predicate, noun, var)?;
2627                let body = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
2628                    left: type_pred,
2629                    op: TokenType::And,
2630                    right: substituted,
2631                });
2632                Ok(self.ctx.exprs.alloc(LogicExpr::Quantifier {
2633                    kind: QuantifierKind::Existential,
2634                    variable: var,
2635                    body,
2636                    island_id: self.current_island,
2637                }))
2638            }
2639            Some(Definiteness::Definite) => {
2640                let x = self.next_var_name();
2641                let y = self.next_var_name();
2642
2643                let type_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
2644                    name: noun,
2645                    args: self.ctx.terms.alloc_slice([Term::Variable(x)]),
2646                    world: None,
2647                });
2648
2649                let identity = self.ctx.exprs.alloc(LogicExpr::Identity {
2650                    left: self.ctx.terms.alloc(Term::Variable(y)),
2651                    right: self.ctx.terms.alloc(Term::Variable(x)),
2652                });
2653                let inner_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
2654                    name: noun,
2655                    args: self.ctx.terms.alloc_slice([Term::Variable(y)]),
2656                    world: None,
2657                });
2658                let uniqueness_body = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
2659                    left: inner_pred,
2660                    op: TokenType::Implies,
2661                    right: identity,
2662                });
2663                let uniqueness = self.ctx.exprs.alloc(LogicExpr::Quantifier {
2664                    kind: QuantifierKind::Universal,
2665                    variable: y,
2666                    body: uniqueness_body,
2667                    island_id: self.current_island,
2668                });
2669
2670                let main_pred = self.substitute_constant_with_var(predicate, noun, x)?;
2671
2672                let type_and_unique = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
2673                    left: type_pred,
2674                    op: TokenType::And,
2675                    right: uniqueness,
2676                });
2677                let body = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
2678                    left: type_and_unique,
2679                    op: TokenType::And,
2680                    right: main_pred,
2681                });
2682
2683                Ok(self.ctx.exprs.alloc(LogicExpr::Quantifier {
2684                    kind: QuantifierKind::Existential,
2685                    variable: x,
2686                    body,
2687                    island_id: self.current_island,
2688                }))
2689            }
2690            Some(Definiteness::Proximal) | Some(Definiteness::Distal) => {
2691                let var = self.next_var_name();
2692
2693                let mut restriction = self.ctx.exprs.alloc(LogicExpr::Predicate {
2694                    name: noun,
2695                    args: self.ctx.terms.alloc_slice([Term::Variable(var)]),
2696                    world: None,
2697                });
2698
2699                let deictic_name = if matches!(definiteness, Some(Definiteness::Proximal)) {
2700                    self.interner.intern("Proximal")
2701                } else {
2702                    self.interner.intern("Distal")
2703                };
2704                let deictic_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
2705                    name: deictic_name,
2706                    args: self.ctx.terms.alloc_slice([Term::Variable(var)]),
2707                    world: None,
2708                });
2709                restriction = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
2710                    left: restriction,
2711                    op: TokenType::And,
2712                    right: deictic_pred,
2713                });
2714
2715                let substituted = self.substitute_constant_with_var(predicate, noun, var)?;
2716                let body = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
2717                    left: restriction,
2718                    op: TokenType::And,
2719                    right: substituted,
2720                });
2721                Ok(self.ctx.exprs.alloc(LogicExpr::Quantifier {
2722                    kind: QuantifierKind::Existential,
2723                    variable: var,
2724                    body,
2725                    island_id: self.current_island,
2726                }))
2727            }
2728            None => Ok(predicate),
2729        }
2730    }
2731
2732    fn substitute_pp_placeholder(&mut self, pp: &'a LogicExpr<'a>, var: Symbol) -> &'a LogicExpr<'a> {
2733        let placeholder = self.interner.intern("_PP_SELF_");
2734        match pp {
2735            LogicExpr::Predicate { name, args, .. } => {
2736                let new_args: Vec<Term<'a>> = args
2737                    .iter()
2738                    .map(|arg| match arg {
2739                        Term::Variable(v) if *v == placeholder => Term::Variable(var),
2740                        other => *other,
2741                    })
2742                    .collect();
2743                self.ctx.exprs.alloc(LogicExpr::Predicate {
2744                    name: *name,
2745                    args: self.ctx.terms.alloc_slice(new_args),
2746                    world: None,
2747                })
2748            }
2749            // Recurse through connectives / quantifiers / events so a complex
2750            // restrictor (a reduced relative built as a NeoEvent with its gap in a
2751            // thematic role, plus event complements) binds its `_PP_SELF_` gap to
2752            // the NP's variable wherever it sits.
2753            LogicExpr::BinaryOp { left, op, right } => {
2754                let l = self.substitute_pp_placeholder(left, var);
2755                let r = self.substitute_pp_placeholder(right, var);
2756                self.ctx.exprs.alloc(LogicExpr::BinaryOp { left: l, op: op.clone(), right: r })
2757            }
2758            LogicExpr::UnaryOp { op, operand } => {
2759                let o = self.substitute_pp_placeholder(operand, var);
2760                self.ctx.exprs.alloc(LogicExpr::UnaryOp { op: op.clone(), operand: o })
2761            }
2762            LogicExpr::Quantifier { kind, variable, body, island_id } => {
2763                let b = self.substitute_pp_placeholder(body, var);
2764                self.ctx.exprs.alloc(LogicExpr::Quantifier {
2765                    kind: *kind,
2766                    variable: *variable,
2767                    body: b,
2768                    island_id: *island_id,
2769                })
2770            }
2771            LogicExpr::Temporal { operator, body } => {
2772                let b = self.substitute_pp_placeholder(body, var);
2773                self.ctx.exprs.alloc(LogicExpr::Temporal { operator: *operator, body: b })
2774            }
2775            LogicExpr::NeoEvent(data) => {
2776                let new_roles: Vec<(ThematicRole, Term<'a>)> = data
2777                    .roles
2778                    .iter()
2779                    .map(|(role, term)| {
2780                        let new_term = match term {
2781                            Term::Variable(v) if *v == placeholder => Term::Variable(var),
2782                            other => *other,
2783                        };
2784                        (*role, new_term)
2785                    })
2786                    .collect();
2787                self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
2788                    event_var: data.event_var,
2789                    verb: data.verb,
2790                    roles: self.ctx.roles.alloc_slice(new_roles),
2791                    modifiers: data.modifiers,
2792                    suppress_existential: data.suppress_existential,
2793                    world: data.world,
2794                })))
2795            }
2796            _ => pp,
2797        }
2798    }
2799
2800    fn substitute_constant_with_var(
2801        &self,
2802        expr: &'a LogicExpr<'a>,
2803        constant_name: Symbol,
2804        var_name: Symbol,
2805    ) -> ParseResult<&'a LogicExpr<'a>> {
2806        match expr {
2807            LogicExpr::Predicate { name, args, .. } => {
2808                let new_args: Vec<Term<'a>> = args
2809                    .iter()
2810                    .map(|arg| match arg {
2811                        Term::Constant(c) if *c == constant_name => Term::Variable(var_name),
2812                        Term::Constant(c) => Term::Constant(*c),
2813                        Term::Variable(v) => Term::Variable(*v),
2814                        Term::Function(n, a) => Term::Function(*n, *a),
2815                        Term::Group(m) => Term::Group(*m),
2816                        Term::Possessed { possessor, possessed } => Term::Possessed {
2817                            possessor: *possessor,
2818                            possessed: *possessed,
2819                        },
2820                        Term::Sigma(p) => Term::Sigma(*p),
2821                        Term::Intension(p) => Term::Intension(*p),
2822                        Term::Kind(k) => Term::Kind(*k),
2823                        Term::Proposition(e) => Term::Proposition(*e),
2824                        Term::Value { kind, unit, dimension } => Term::Value {
2825                            kind: *kind,
2826                            unit: *unit,
2827                            dimension: *dimension,
2828                        },
2829                    })
2830                    .collect();
2831                Ok(self.ctx.exprs.alloc(LogicExpr::Predicate {
2832                    name: *name,
2833                    args: self.ctx.terms.alloc_slice(new_args),
2834                    world: None,
2835                }))
2836            }
2837            LogicExpr::Temporal { operator, body } => Ok(self.ctx.exprs.alloc(LogicExpr::Temporal {
2838                operator: *operator,
2839                body: self.substitute_constant_with_var(body, constant_name, var_name)?,
2840            })),
2841            LogicExpr::Aspectual { operator, body } => Ok(self.ctx.exprs.alloc(LogicExpr::Aspectual {
2842                operator: *operator,
2843                body: self.substitute_constant_with_var(body, constant_name, var_name)?,
2844            })),
2845            LogicExpr::UnaryOp { op, operand } => Ok(self.ctx.exprs.alloc(LogicExpr::UnaryOp {
2846                op: op.clone(),
2847                operand: self.substitute_constant_with_var(operand, constant_name, var_name)?,
2848            })),
2849            LogicExpr::BinaryOp { left, op, right } => Ok(self.ctx.exprs.alloc(LogicExpr::BinaryOp {
2850                left: self.substitute_constant_with_var(left, constant_name, var_name)?,
2851                op: op.clone(),
2852                right: self.substitute_constant_with_var(right, constant_name, var_name)?,
2853            })),
2854            LogicExpr::Event { predicate, adverbs } => Ok(self.ctx.exprs.alloc(LogicExpr::Event {
2855                predicate: self.substitute_constant_with_var(predicate, constant_name, var_name)?,
2856                adverbs: *adverbs,
2857            })),
2858            LogicExpr::TemporalAnchor { anchor, body } => {
2859                Ok(self.ctx.exprs.alloc(LogicExpr::TemporalAnchor {
2860                    anchor: *anchor,
2861                    body: self.substitute_constant_with_var(body, constant_name, var_name)?,
2862                }))
2863            }
2864            LogicExpr::NeoEvent(data) => {
2865                // Substitute constants in thematic roles (Agent, Theme, etc.)
2866                let new_roles: Vec<(crate::ast::ThematicRole, Term<'a>)> = data
2867                    .roles
2868                    .iter()
2869                    .map(|(role, term)| {
2870                        let new_term = match term {
2871                            Term::Constant(c) if *c == constant_name => Term::Variable(var_name),
2872                            Term::Constant(c) => Term::Constant(*c),
2873                            Term::Variable(v) => Term::Variable(*v),
2874                            Term::Function(n, a) => Term::Function(*n, *a),
2875                            Term::Group(m) => Term::Group(*m),
2876                            Term::Possessed { possessor, possessed } => Term::Possessed {
2877                                possessor: *possessor,
2878                                possessed: *possessed,
2879                            },
2880                            Term::Sigma(p) => Term::Sigma(*p),
2881                            Term::Intension(p) => Term::Intension(*p),
2882                            Term::Kind(k) => Term::Kind(*k),
2883                            Term::Proposition(e) => Term::Proposition(*e),
2884                            Term::Value { kind, unit, dimension } => Term::Value {
2885                                kind: *kind,
2886                                unit: *unit,
2887                                dimension: *dimension,
2888                            },
2889                        };
2890                        (*role, new_term)
2891                    })
2892                    .collect();
2893                Ok(self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(crate::ast::NeoEventData {
2894                    event_var: data.event_var,
2895                    verb: data.verb,
2896                    roles: self.ctx.roles.alloc_slice(new_roles),
2897                    modifiers: data.modifiers,
2898                    suppress_existential: data.suppress_existential,
2899                    world: None,
2900                }))))
2901            }
2902            // Recurse into nested quantifiers to substitute constants in their bodies
2903            LogicExpr::Quantifier { kind, variable, body, island_id } => {
2904                let new_body = self.substitute_constant_with_var(body, constant_name, var_name)?;
2905                Ok(self.ctx.exprs.alloc(LogicExpr::Quantifier {
2906                    kind: *kind,
2907                    variable: *variable,
2908                    body: new_body,
2909                    island_id: *island_id,
2910                }))
2911            }
2912            LogicExpr::Control { verb, subject, object, infinitive } => {
2913                let sub_term = |t: &Term<'a>| -> Term<'a> {
2914                    match t {
2915                        Term::Constant(c) if *c == constant_name => Term::Variable(var_name),
2916                        other => other.clone(),
2917                    }
2918                };
2919                Ok(self.ctx.exprs.alloc(LogicExpr::Control {
2920                    verb: *verb,
2921                    subject: self.ctx.terms.alloc(sub_term(subject)),
2922                    object: match object {
2923                        Some(o) => Some(&*self.ctx.terms.alloc(sub_term(o))),
2924                        None => None,
2925                    },
2926                    infinitive: self.substitute_constant_with_var(infinitive, constant_name, var_name)?,
2927                }))
2928            }
2929            _ => Ok(expr),
2930        }
2931    }
2932
2933    fn substitute_constant_with_var_sym(
2934        &self,
2935        expr: &'a LogicExpr<'a>,
2936        constant_name: Symbol,
2937        var_name: Symbol,
2938    ) -> ParseResult<&'a LogicExpr<'a>> {
2939        self.substitute_constant_with_var(expr, constant_name, var_name)
2940    }
2941
2942    fn substitute_variable_with_constant(
2943        &self,
2944        expr: &'a LogicExpr<'a>,
2945        from_var: Symbol,
2946        to_const: Symbol,
2947    ) -> ParseResult<&'a LogicExpr<'a>> {
2948        let map_term = |t: &Term<'a>| -> Term<'a> {
2949            match t {
2950                Term::Variable(v) if *v == from_var => Term::Constant(to_const),
2951                other => other.clone(),
2952            }
2953        };
2954        match expr {
2955            LogicExpr::Predicate { name, args, world } => {
2956                let new_args: Vec<Term<'a>> = args.iter().map(&map_term).collect();
2957                Ok(self.ctx.exprs.alloc(LogicExpr::Predicate {
2958                    name: *name,
2959                    args: self.ctx.terms.alloc_slice(new_args),
2960                    world: *world,
2961                }))
2962            }
2963            LogicExpr::Identity { left, right } => Ok(self.ctx.exprs.alloc(LogicExpr::Identity {
2964                left: self.ctx.terms.alloc(map_term(left)),
2965                right: self.ctx.terms.alloc(map_term(right)),
2966            })),
2967            LogicExpr::Temporal { operator, body } => Ok(self.ctx.exprs.alloc(LogicExpr::Temporal {
2968                operator: *operator,
2969                body: self.substitute_variable_with_constant(body, from_var, to_const)?,
2970            })),
2971            LogicExpr::Aspectual { operator, body } => {
2972                Ok(self.ctx.exprs.alloc(LogicExpr::Aspectual {
2973                    operator: *operator,
2974                    body: self.substitute_variable_with_constant(body, from_var, to_const)?,
2975                }))
2976            }
2977            LogicExpr::UnaryOp { op, operand } => Ok(self.ctx.exprs.alloc(LogicExpr::UnaryOp {
2978                op: op.clone(),
2979                operand: self.substitute_variable_with_constant(operand, from_var, to_const)?,
2980            })),
2981            LogicExpr::BinaryOp { left, op, right } => Ok(self.ctx.exprs.alloc(LogicExpr::BinaryOp {
2982                left: self.substitute_variable_with_constant(left, from_var, to_const)?,
2983                op: op.clone(),
2984                right: self.substitute_variable_with_constant(right, from_var, to_const)?,
2985            })),
2986            LogicExpr::Event { predicate, adverbs } => Ok(self.ctx.exprs.alloc(LogicExpr::Event {
2987                predicate: self.substitute_variable_with_constant(predicate, from_var, to_const)?,
2988                adverbs: *adverbs,
2989            })),
2990            LogicExpr::TemporalAnchor { anchor, body } => {
2991                Ok(self.ctx.exprs.alloc(LogicExpr::TemporalAnchor {
2992                    anchor: *anchor,
2993                    body: self.substitute_variable_with_constant(body, from_var, to_const)?,
2994                }))
2995            }
2996            LogicExpr::NeoEvent(data) => {
2997                let new_roles: Vec<(crate::ast::ThematicRole, Term<'a>)> =
2998                    data.roles.iter().map(|(role, term)| (*role, map_term(term))).collect();
2999                Ok(self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(crate::ast::NeoEventData {
3000                    event_var: data.event_var,
3001                    verb: data.verb,
3002                    roles: self.ctx.roles.alloc_slice(new_roles),
3003                    modifiers: data.modifiers,
3004                    suppress_existential: data.suppress_existential,
3005                    world: None,
3006                }))))
3007            }
3008            LogicExpr::Quantifier { kind, variable, body, island_id } => {
3009                // A nested quantifier that re-binds `from_var` shadows the gap;
3010                // leave its body untouched in that case.
3011                if *variable == from_var {
3012                    return Ok(expr);
3013                }
3014                Ok(self.ctx.exprs.alloc(LogicExpr::Quantifier {
3015                    kind: *kind,
3016                    variable: *variable,
3017                    body: self.substitute_variable_with_constant(body, from_var, to_const)?,
3018                    island_id: *island_id,
3019                }))
3020            }
3021            LogicExpr::Control { verb, subject, object, infinitive } => {
3022                Ok(self.ctx.exprs.alloc(LogicExpr::Control {
3023                    verb: *verb,
3024                    subject: self.ctx.terms.alloc(map_term(subject)),
3025                    object: object.map(|o| &*self.ctx.terms.alloc(map_term(o))),
3026                    infinitive: self.substitute_variable_with_constant(infinitive, from_var, to_const)?,
3027                }))
3028            }
3029            _ => Ok(expr),
3030        }
3031    }
3032
3033    fn substitute_constant_with_sigma(
3034        &self,
3035        expr: &'a LogicExpr<'a>,
3036        constant_name: Symbol,
3037        sigma_term: Term<'a>,
3038    ) -> ParseResult<&'a LogicExpr<'a>> {
3039        match expr {
3040            LogicExpr::Predicate { name, args, .. } => {
3041                let new_args: Vec<Term<'a>> = args
3042                    .iter()
3043                    .map(|arg| match arg {
3044                        Term::Constant(c) if *c == constant_name => sigma_term.clone(),
3045                        Term::Constant(c) => Term::Constant(*c),
3046                        Term::Variable(v) => Term::Variable(*v),
3047                        Term::Function(n, a) => Term::Function(*n, *a),
3048                        Term::Group(m) => Term::Group(*m),
3049                        Term::Possessed { possessor, possessed } => Term::Possessed {
3050                            possessor: *possessor,
3051                            possessed: *possessed,
3052                        },
3053                        Term::Sigma(p) => Term::Sigma(*p),
3054                        Term::Intension(p) => Term::Intension(*p),
3055                        Term::Kind(k) => Term::Kind(*k),
3056                        Term::Proposition(e) => Term::Proposition(*e),
3057                        Term::Value { kind, unit, dimension } => Term::Value {
3058                            kind: *kind,
3059                            unit: *unit,
3060                            dimension: *dimension,
3061                        },
3062                    })
3063                    .collect();
3064                Ok(self.ctx.exprs.alloc(LogicExpr::Predicate {
3065                    name: *name,
3066                    args: self.ctx.terms.alloc_slice(new_args),
3067                    world: None,
3068                }))
3069            }
3070            LogicExpr::Temporal { operator, body } => Ok(self.ctx.exprs.alloc(LogicExpr::Temporal {
3071                operator: *operator,
3072                body: self.substitute_constant_with_sigma(body, constant_name, sigma_term)?,
3073            })),
3074            LogicExpr::Aspectual { operator, body } => Ok(self.ctx.exprs.alloc(LogicExpr::Aspectual {
3075                operator: *operator,
3076                body: self.substitute_constant_with_sigma(body, constant_name, sigma_term)?,
3077            })),
3078            LogicExpr::UnaryOp { op, operand } => Ok(self.ctx.exprs.alloc(LogicExpr::UnaryOp {
3079                op: op.clone(),
3080                operand: self.substitute_constant_with_sigma(operand, constant_name, sigma_term)?,
3081            })),
3082            LogicExpr::BinaryOp { left, op, right } => Ok(self.ctx.exprs.alloc(LogicExpr::BinaryOp {
3083                left: self.substitute_constant_with_sigma(
3084                    left,
3085                    constant_name,
3086                    sigma_term.clone(),
3087                )?,
3088                op: op.clone(),
3089                right: self.substitute_constant_with_sigma(right, constant_name, sigma_term)?,
3090            })),
3091            LogicExpr::Event { predicate, adverbs } => Ok(self.ctx.exprs.alloc(LogicExpr::Event {
3092                predicate: self.substitute_constant_with_sigma(
3093                    predicate,
3094                    constant_name,
3095                    sigma_term,
3096                )?,
3097                adverbs: *adverbs,
3098            })),
3099            LogicExpr::TemporalAnchor { anchor, body } => {
3100                Ok(self.ctx.exprs.alloc(LogicExpr::TemporalAnchor {
3101                    anchor: *anchor,
3102                    body: self.substitute_constant_with_sigma(body, constant_name, sigma_term)?,
3103                }))
3104            }
3105            LogicExpr::NeoEvent(data) => {
3106                let new_roles: Vec<(crate::ast::ThematicRole, Term<'a>)> = data
3107                    .roles
3108                    .iter()
3109                    .map(|(role, term)| {
3110                        let new_term = match term {
3111                            Term::Constant(c) if *c == constant_name => sigma_term.clone(),
3112                            Term::Constant(c) => Term::Constant(*c),
3113                            Term::Variable(v) => Term::Variable(*v),
3114                            Term::Function(n, a) => Term::Function(*n, *a),
3115                            Term::Group(m) => Term::Group(*m),
3116                            Term::Possessed { possessor, possessed } => Term::Possessed {
3117                                possessor: *possessor,
3118                                possessed: *possessed,
3119                            },
3120                            Term::Sigma(p) => Term::Sigma(*p),
3121                            Term::Intension(p) => Term::Intension(*p),
3122                            Term::Kind(k) => Term::Kind(*k),
3123                            Term::Proposition(e) => Term::Proposition(*e),
3124                            Term::Value { kind, unit, dimension } => Term::Value {
3125                                kind: *kind,
3126                                unit: *unit,
3127                                dimension: *dimension,
3128                            },
3129                        };
3130                        (*role, new_term)
3131                    })
3132                    .collect();
3133                Ok(self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(crate::ast::NeoEventData {
3134                    event_var: data.event_var,
3135                    verb: data.verb,
3136                    roles: self.ctx.roles.alloc_slice(new_roles),
3137                    modifiers: data.modifiers,
3138                    suppress_existential: data.suppress_existential,
3139                    world: None,
3140                }))))
3141            }
3142            LogicExpr::Distributive { predicate } => Ok(self.ctx.exprs.alloc(LogicExpr::Distributive {
3143                predicate: self.substitute_constant_with_sigma(predicate, constant_name, sigma_term)?,
3144            })),
3145            _ => Ok(expr),
3146        }
3147    }
3148
3149    fn find_main_verb_name(&self, expr: &LogicExpr<'a>) -> Option<Symbol> {
3150        match expr {
3151            LogicExpr::Predicate { name, .. } => Some(*name),
3152            LogicExpr::NeoEvent(data) => Some(data.verb),
3153            LogicExpr::Temporal { body, .. } => self.find_main_verb_name(body),
3154            LogicExpr::Aspectual { body, .. } => self.find_main_verb_name(body),
3155            LogicExpr::Event { predicate, .. } => self.find_main_verb_name(predicate),
3156            LogicExpr::TemporalAnchor { body, .. } => self.find_main_verb_name(body),
3157            LogicExpr::UnaryOp { operand, .. } => self.find_main_verb_name(operand),
3158            LogicExpr::BinaryOp { left, .. } => self.find_main_verb_name(left),
3159            _ => None,
3160        }
3161    }
3162
3163    fn transform_cardinal_to_group(&mut self, expr: &'a LogicExpr<'a>) -> ParseResult<&'a LogicExpr<'a>> {
3164        match expr {
3165            LogicExpr::Quantifier { kind: QuantifierKind::Cardinal(n), variable, body, .. } => {
3166                let group_var = self.interner.intern("g");
3167                let member_var = *variable;
3168
3169                // Extract the restriction (first conjunct) and the body (rest)
3170                // The structure is: restriction ∧ body_rest
3171                let (restriction, body_rest) = match body {
3172                    LogicExpr::BinaryOp { left, op: TokenType::And, right } => (*left, *right),
3173                    _ => return Ok(expr),
3174                };
3175
3176                // Substitute the member variable with the group variable in the body
3177                let transformed_body = self.substitute_constant_with_var_sym(body_rest, member_var, group_var)?;
3178
3179                Ok(self.ctx.exprs.alloc(LogicExpr::GroupQuantifier {
3180                    group_var,
3181                    count: *n,
3182                    member_var,
3183                    restriction,
3184                    body: transformed_body,
3185                }))
3186            }
3187            // Recursively transform nested expressions
3188            LogicExpr::Temporal { operator, body } => {
3189                let transformed = self.transform_cardinal_to_group(body)?;
3190                Ok(self.ctx.exprs.alloc(LogicExpr::Temporal {
3191                    operator: *operator,
3192                    body: transformed,
3193                }))
3194            }
3195            LogicExpr::Aspectual { operator, body } => {
3196                let transformed = self.transform_cardinal_to_group(body)?;
3197                Ok(self.ctx.exprs.alloc(LogicExpr::Aspectual {
3198                    operator: *operator,
3199                    body: transformed,
3200                }))
3201            }
3202            LogicExpr::UnaryOp { op, operand } => {
3203                let transformed = self.transform_cardinal_to_group(operand)?;
3204                Ok(self.ctx.exprs.alloc(LogicExpr::UnaryOp {
3205                    op: op.clone(),
3206                    operand: transformed,
3207                }))
3208            }
3209            LogicExpr::BinaryOp { left, op, right } => {
3210                let transformed_left = self.transform_cardinal_to_group(left)?;
3211                let transformed_right = self.transform_cardinal_to_group(right)?;
3212                Ok(self.ctx.exprs.alloc(LogicExpr::BinaryOp {
3213                    left: transformed_left,
3214                    op: op.clone(),
3215                    right: transformed_right,
3216                }))
3217            }
3218            LogicExpr::Distributive { predicate } => {
3219                let transformed = self.transform_cardinal_to_group(predicate)?;
3220                Ok(self.ctx.exprs.alloc(LogicExpr::Distributive {
3221                    predicate: transformed,
3222                }))
3223            }
3224            LogicExpr::Quantifier { kind, variable, body, island_id } => {
3225                let transformed = self.transform_cardinal_to_group(body)?;
3226                Ok(self.ctx.exprs.alloc(LogicExpr::Quantifier {
3227                    kind: kind.clone(),
3228                    variable: *variable,
3229                    body: transformed,
3230                    island_id: *island_id,
3231                }))
3232            }
3233            _ => Ok(expr),
3234        }
3235    }
3236
3237    fn build_verb_neo_event(
3238        &mut self,
3239        verb: Symbol,
3240        subject_var: Symbol,
3241        object: Option<Term<'a>>,
3242        modifiers: Vec<Symbol>,
3243    ) -> &'a LogicExpr<'a> {
3244        let event_var = self.get_event_var();
3245
3246        // Check if verb is unaccusative (intransitive subject is Theme, not Agent)
3247        let verb_str = self.interner.resolve(verb).to_lowercase();
3248        let is_unaccusative = lookup_verb_db(&verb_str)
3249            .map(|meta| meta.features.contains(&Feature::Unaccusative))
3250            .unwrap_or(false);
3251
3252        // Determine subject role: unaccusative verbs without object use Theme
3253        let has_object = object.is_some();
3254        let subject_role = if is_unaccusative && !has_object {
3255            ThematicRole::Theme
3256        } else {
3257            ThematicRole::Agent
3258        };
3259
3260        // Build roles vector
3261        let mut roles = vec![(subject_role, Term::Variable(subject_var))];
3262        if let Some(obj_term) = object {
3263            roles.push((ThematicRole::Theme, obj_term));
3264        }
3265
3266        // Create NeoEventData with suppress_existential: false
3267        // Each quantified individual gets their own event (distributive reading)
3268        self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
3269            event_var,
3270            verb,
3271            roles: self.ctx.roles.alloc_slice(roles),
3272            modifiers: self.ctx.syms.alloc_slice(modifiers),
3273            suppress_existential: false,
3274            world: None,
3275        })))
3276    }
3277}
3278
3279// Helper methods for donkey binding scope handling
3280impl<'a, 'ctx, 'int> Parser<'a, 'ctx, 'int> {
3281    /// Is the parser at a position where a finished clause may legitimately
3282    /// end? Used to detect under-consumption by specialized sub-grammars.
3283    pub(super) fn at_clause_boundary(&self) -> bool {
3284        matches!(
3285            self.peek().kind,
3286            TokenType::Period
3287                | TokenType::Exclamation
3288                | TokenType::EOF
3289                | TokenType::Comma
3290                | TokenType::RParen
3291                | TokenType::And
3292                | TokenType::Or
3293                | TokenType::Iff
3294                | TokenType::Then
3295        )
3296    }
3297
3298    /// Quantified clause whose VP is parsed by the FULL predicate parser with
3299    /// the subject bound to the quantified variable. The under-consumption
3300    /// fallback for frames the specialized quantified grammar does not cover
3301    /// (reflexives, reciprocals, control infinitives, ditransitives,
3302    /// comparatives, adverbs, …).
3303    fn parse_quantified_delegating(&mut self) -> ParseResult<&'a LogicExpr<'a>> {
3304        use super::verb::LogicVerbParsing;
3305
3306        let quantifier_token = self.previous().kind.clone();
3307        let var_name = self.next_var_name();
3308
3309        let was_in_negative_quantifier = self.in_negative_quantifier;
3310        if matches!(quantifier_token, TokenType::No) {
3311            self.in_negative_quantifier = true;
3312        }
3313
3314        let restriction = self.parse_restriction(var_name)?;
3315
3316        // Reciprocal VP ("helped each other"): every pair of distinct members
3317        // of the restriction stands in the relation —
3318        // ∀y((R(y) ∧ ¬(y = x)) → ∃e(V(e) ∧ Agent(e, x) ∧ Theme(e, y))).
3319        let mut copula_vp: Option<&'a LogicExpr<'a>> = None;
3320        if matches!(self.peek().kind, TokenType::Verb { .. })
3321            && matches!(
3322                self.tokens.get(self.current + 1).map(|t| t.kind.clone()),
3323                Some(TokenType::Reciprocal)
3324            )
3325        {
3326            let (verb, recip_time, _, _) = self.consume_verb_with_metadata();
3327            self.advance(); // the reciprocal
3328            let other_var = self.next_var_name();
3329            let other_restriction = self.rename_var_in_expr(restriction, var_name, other_var);
3330            let distinct = self.ctx.exprs.alloc(LogicExpr::UnaryOp {
3331                op: TokenType::Not,
3332                operand: self.ctx.exprs.alloc(LogicExpr::Identity {
3333                    left: self.ctx.terms.alloc(Term::Variable(other_var)),
3334                    right: self.ctx.terms.alloc(Term::Variable(var_name)),
3335                }),
3336            });
3337            let modifiers = match recip_time {
3338                Time::Past => vec![self.interner.intern("Past")],
3339                Time::Future => vec![self.interner.intern("Future")],
3340                _ => vec![],
3341            };
3342            let event_var = self.get_event_var();
3343            let suppress_existential = self.drs.in_conditional_antecedent();
3344            let event = self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
3345                event_var,
3346                verb,
3347                roles: self.ctx.roles.alloc_slice(vec![
3348                    (ThematicRole::Agent, Term::Variable(var_name)),
3349                    (ThematicRole::Theme, Term::Variable(other_var)),
3350                ]),
3351                modifiers: self.ctx.syms.alloc_slice(modifiers),
3352                suppress_existential,
3353                world: None,
3354            })));
3355            let antecedent = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
3356                left: other_restriction,
3357                op: TokenType::And,
3358                right: distinct,
3359            });
3360            let pair_body = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
3361                left: antecedent,
3362                op: TokenType::Implies,
3363                right: event,
3364            });
3365            copula_vp = Some(self.ctx.exprs.alloc(LogicExpr::Quantifier {
3366                kind: QuantifierKind::Universal,
3367                variable: other_var,
3368                body: pair_body,
3369                island_id: self.current_island,
3370            }));
3371        }
3372
3373        // Copula-initial VP. A progressive participle keeps the full predicate
3374        // parser (the copula only carries tense: "were running quickly"); a
3375        // passive participle predicates the subject variable as Theme with an
3376        // optional by-phrase Agent ("were read by some students").
3377        if matches!(
3378            self.peek().kind,
3379            TokenType::Is | TokenType::Are | TokenType::Was | TokenType::Were
3380        ) {
3381            let copula_past = matches!(self.peek().kind, TokenType::Was | TokenType::Were);
3382            if let Some(TokenType::Verb { aspect, .. }) =
3383                self.tokens.get(self.current + 1).map(|t| t.kind.clone())
3384            {
3385                if aspect == crate::lexicon::Aspect::Progressive {
3386                    self.advance(); // copula carries tense only
3387                    self.pending_time =
3388                        Some(if copula_past { Time::Past } else { Time::None });
3389                } else {
3390                    self.advance(); // copula
3391                    let (verb, _, _, _) = self.consume_verb_with_metadata();
3392                    let mut modifiers = if copula_past {
3393                        vec![self.interner.intern("Past")]
3394                    } else {
3395                        vec![]
3396                    };
3397                    modifiers.extend(self.collect_adverbs());
3398
3399                    let mut roles = vec![(ThematicRole::Theme, Term::Variable(var_name))];
3400                    let mut agent_quant: Option<(TokenType, Symbol, Symbol)> = None;
3401                    if self.check_preposition_is("by") {
3402                        self.advance(); // by
3403                        if self.check_quantifier() {
3404                            let q = self.advance().kind.clone();
3405                            let a_np = self.parse_noun_phrase(false)?;
3406                            let a_var = self.next_var_name();
3407                            roles.push((ThematicRole::Agent, Term::Variable(a_var)));
3408                            agent_quant = Some((q, a_var, a_np.noun));
3409                        } else if self.check_content_word() || self.check_article() {
3410                            let a_np = self.parse_noun_phrase(false)?;
3411                            roles.push((ThematicRole::Agent, Term::Constant(a_np.noun)));
3412                        }
3413                    }
3414
3415                    let event_var = self.get_event_var();
3416                    let suppress_existential = self.drs.in_conditional_antecedent();
3417                    let passive = self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(
3418                        NeoEventData {
3419                            event_var,
3420                            verb,
3421                            roles: self.ctx.roles.alloc_slice(roles),
3422                            modifiers: self.ctx.syms.alloc_slice(modifiers),
3423                            suppress_existential,
3424                            world: None,
3425                        },
3426                    )));
3427                    copula_vp = Some(if let Some((q, a_var, a_noun)) = agent_quant {
3428                        let a_restriction = self.ctx.exprs.alloc(LogicExpr::Predicate {
3429                            name: a_noun,
3430                            args: self.ctx.terms.alloc_slice([Term::Variable(a_var)]),
3431                            world: None,
3432                        });
3433                        let (a_kind, a_op) = match q {
3434                            TokenType::All => (QuantifierKind::Universal, TokenType::Implies),
3435                            TokenType::Most => (QuantifierKind::Most, TokenType::And),
3436                            TokenType::Few => (QuantifierKind::Few, TokenType::And),
3437                            TokenType::Many => (QuantifierKind::Many, TokenType::And),
3438                            _ => (QuantifierKind::Existential, TokenType::And),
3439                        };
3440                        let a_body = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
3441                            left: a_restriction,
3442                            op: a_op,
3443                            right: passive,
3444                        });
3445                        self.ctx.exprs.alloc(LogicExpr::Quantifier {
3446                            kind: a_kind,
3447                            variable: a_var,
3448                            body: a_body,
3449                            island_id: self.current_island,
3450                        })
3451                    } else {
3452                        passive
3453                    });
3454                }
3455            }
3456        }
3457
3458        let vp = match copula_vp {
3459            Some(vp) => vp,
3460            None => self.parse_predicate_with_subject_as_var(var_name)?,
3461        };
3462        // Sentence-final temporal anchor ("studied yesterday") — the same
3463        // wrapping the simple-subject clause path applies.
3464        let vp = if self.check_temporal_adverb() {
3465            if let TokenType::TemporalAdverb(anchor) = self.advance().kind.clone() {
3466                &*self.ctx.exprs.alloc(LogicExpr::TemporalAnchor { anchor, body: vp })
3467            } else {
3468                vp
3469            }
3470        } else {
3471            vp
3472        };
3473        self.in_negative_quantifier = was_in_negative_quantifier;
3474
3475        // "No N VP" denies the VP of every N.
3476        let consequent = if matches!(quantifier_token, TokenType::No) {
3477            self.ctx.exprs.alloc(LogicExpr::UnaryOp {
3478                op: TokenType::Not,
3479                operand: vp,
3480            })
3481        } else {
3482            vp
3483        };
3484
3485        let universal_frame = matches!(
3486            quantifier_token,
3487            TokenType::All | TokenType::Any | TokenType::No
3488        );
3489        let body = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
3490            left: restriction,
3491            op: if universal_frame { TokenType::Implies } else { TokenType::And },
3492            right: consequent,
3493        });
3494
3495        // Donkey closure: an indefinite introduced inside the restriction and
3496        // picked up by the VP ("Every man who owns a book gives it …") is a
3497        // free variable in both — close it at the quantifier (universal force
3498        // under a universal frame, existential otherwise).
3499        let mut restriction_vars = Vec::new();
3500        self.collect_unbound_vars(restriction, &mut vec![var_name], &mut restriction_vars);
3501        let mut body = body;
3502        for donkey_var in restriction_vars {
3503            if self.expr_mentions_var(consequent, donkey_var) {
3504                body = self.ctx.exprs.alloc(LogicExpr::Quantifier {
3505                    kind: if universal_frame {
3506                        QuantifierKind::Universal
3507                    } else {
3508                        QuantifierKind::Existential
3509                    },
3510                    variable: donkey_var,
3511                    body,
3512                    island_id: self.current_island,
3513                });
3514            }
3515        }
3516
3517        let kind = match quantifier_token {
3518            TokenType::All | TokenType::Any | TokenType::No => QuantifierKind::Universal,
3519            TokenType::Some => QuantifierKind::Existential,
3520            TokenType::Most => QuantifierKind::Most,
3521            TokenType::Few => QuantifierKind::Few,
3522            TokenType::Many => QuantifierKind::Many,
3523            TokenType::Cardinal(n) => QuantifierKind::Cardinal(n),
3524            TokenType::AtLeast(n) => QuantifierKind::AtLeast(n),
3525            TokenType::AtMost(n) => QuantifierKind::AtMost(n),
3526            _ => QuantifierKind::Universal,
3527        };
3528
3529        Ok(self.ctx.exprs.alloc(LogicExpr::Quantifier {
3530            kind,
3531            variable: var_name,
3532            body,
3533            island_id: self.current_island,
3534        }))
3535    }
3536
3537    /// Rebuild `expr` with free occurrences of variable `from` renamed to
3538    /// `to`; binders that shadow `from` keep their bodies untouched.
3539    fn rename_var_in_expr(
3540        &self,
3541        expr: &'a LogicExpr<'a>,
3542        from: Symbol,
3543        to: Symbol,
3544    ) -> &'a LogicExpr<'a> {
3545        let rename_term = |t: &Term<'a>| -> Term<'a> {
3546            match t {
3547                Term::Variable(v) if *v == from => Term::Variable(to),
3548                other => other.clone(),
3549            }
3550        };
3551        match expr {
3552            LogicExpr::Predicate { name, args, world } => {
3553                let new_args: Vec<Term<'a>> = args.iter().map(|a| rename_term(a)).collect();
3554                self.ctx.exprs.alloc(LogicExpr::Predicate {
3555                    name: *name,
3556                    args: self.ctx.terms.alloc_slice(new_args),
3557                    world: world.clone(),
3558                })
3559            }
3560            LogicExpr::Identity { left, right } => self.ctx.exprs.alloc(LogicExpr::Identity {
3561                left: self.ctx.terms.alloc(rename_term(left)),
3562                right: self.ctx.terms.alloc(rename_term(right)),
3563            }),
3564            LogicExpr::NeoEvent(data) => {
3565                let new_roles: Vec<(ThematicRole, Term<'a>)> = data
3566                    .roles
3567                    .iter()
3568                    .map(|(role, term)| (*role, rename_term(term)))
3569                    .collect();
3570                self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
3571                    event_var: data.event_var,
3572                    verb: data.verb,
3573                    roles: self.ctx.roles.alloc_slice(new_roles),
3574                    modifiers: data.modifiers,
3575                    suppress_existential: data.suppress_existential,
3576                    world: data.world.clone(),
3577                })))
3578            }
3579            LogicExpr::BinaryOp { left, op, right } => self.ctx.exprs.alloc(LogicExpr::BinaryOp {
3580                left: self.rename_var_in_expr(left, from, to),
3581                op: op.clone(),
3582                right: self.rename_var_in_expr(right, from, to),
3583            }),
3584            LogicExpr::UnaryOp { op, operand } => self.ctx.exprs.alloc(LogicExpr::UnaryOp {
3585                op: op.clone(),
3586                operand: self.rename_var_in_expr(operand, from, to),
3587            }),
3588            LogicExpr::Quantifier { kind, variable, body, island_id } if *variable != from => {
3589                self.ctx.exprs.alloc(LogicExpr::Quantifier {
3590                    kind: *kind,
3591                    variable: *variable,
3592                    body: self.rename_var_in_expr(body, from, to),
3593                    island_id: *island_id,
3594                })
3595            }
3596            LogicExpr::Temporal { operator, body } => self.ctx.exprs.alloc(LogicExpr::Temporal {
3597                operator: *operator,
3598                body: self.rename_var_in_expr(body, from, to),
3599            }),
3600            LogicExpr::Aspectual { operator, body } => {
3601                self.ctx.exprs.alloc(LogicExpr::Aspectual {
3602                    operator: *operator,
3603                    body: self.rename_var_in_expr(body, from, to),
3604                })
3605            }
3606            LogicExpr::Event { predicate, adverbs } => self.ctx.exprs.alloc(LogicExpr::Event {
3607                predicate: self.rename_var_in_expr(predicate, from, to),
3608                adverbs: *adverbs,
3609            }),
3610            other => other,
3611        }
3612    }
3613
3614    /// Collect variables that occur free in `expr` (not bound by an inner
3615    /// quantifier and not in `bound`), in first-occurrence order.
3616    fn collect_unbound_vars(
3617        &self,
3618        expr: &LogicExpr<'a>,
3619        bound: &mut Vec<Symbol>,
3620        out: &mut Vec<Symbol>,
3621    ) {
3622        fn term_vars(term: &Term<'_>, bound: &[Symbol], out: &mut Vec<Symbol>) {
3623            match term {
3624                Term::Variable(v) => {
3625                    if !bound.contains(v) && !out.contains(v) {
3626                        out.push(*v);
3627                    }
3628                }
3629                Term::Function(_, args) => {
3630                    for t in args.iter() {
3631                        term_vars(t, bound, out);
3632                    }
3633                }
3634                _ => {}
3635            }
3636        }
3637
3638        match expr {
3639            LogicExpr::Predicate { args, .. } => {
3640                for t in args.iter() {
3641                    term_vars(t, bound, out);
3642                }
3643            }
3644            LogicExpr::NeoEvent(data) => {
3645                for (_, t) in data.roles.iter() {
3646                    term_vars(t, bound, out);
3647                }
3648            }
3649            LogicExpr::BinaryOp { left, right, .. } => {
3650                self.collect_unbound_vars(left, bound, out);
3651                self.collect_unbound_vars(right, bound, out);
3652            }
3653            LogicExpr::UnaryOp { operand, .. } => self.collect_unbound_vars(operand, bound, out),
3654            LogicExpr::Quantifier { variable, body, .. } => {
3655                bound.push(*variable);
3656                self.collect_unbound_vars(body, bound, out);
3657                bound.pop();
3658            }
3659            LogicExpr::Temporal { body, .. } => self.collect_unbound_vars(body, bound, out),
3660            LogicExpr::Aspectual { body, .. } => self.collect_unbound_vars(body, bound, out),
3661            LogicExpr::Event { predicate, .. } => self.collect_unbound_vars(predicate, bound, out),
3662            LogicExpr::Modal { operand, .. } => self.collect_unbound_vars(operand, bound, out),
3663            LogicExpr::Scopal { body, .. } => self.collect_unbound_vars(body, bound, out),
3664            _ => {}
3665        }
3666    }
3667
3668    /// Check if an expression mentions a specific variable
3669    fn expr_mentions_var(&self, expr: &LogicExpr<'a>, var: Symbol) -> bool {
3670        match expr {
3671            LogicExpr::Predicate { args, .. } => {
3672                args.iter().any(|term| self.term_mentions_var(term, var))
3673            }
3674            LogicExpr::BinaryOp { left, right, .. } => {
3675                self.expr_mentions_var(left, var) || self.expr_mentions_var(right, var)
3676            }
3677            LogicExpr::UnaryOp { operand, .. } => self.expr_mentions_var(operand, var),
3678            LogicExpr::Quantifier { body, .. } => self.expr_mentions_var(body, var),
3679            LogicExpr::NeoEvent(data) => {
3680                data.roles.iter().any(|(_, term)| self.term_mentions_var(term, var))
3681            }
3682            LogicExpr::Temporal { body, .. } => self.expr_mentions_var(body, var),
3683            LogicExpr::Aspectual { body, .. } => self.expr_mentions_var(body, var),
3684            LogicExpr::Event { predicate, .. } => self.expr_mentions_var(predicate, var),
3685            LogicExpr::Modal { operand, .. } => self.expr_mentions_var(operand, var),
3686            LogicExpr::Scopal { body, .. } => self.expr_mentions_var(body, var),
3687            _ => false,
3688        }
3689    }
3690
3691    fn term_mentions_var(&self, term: &Term<'a>, var: Symbol) -> bool {
3692        match term {
3693            Term::Variable(v) => *v == var,
3694            Term::Function(_, args) => args.iter().any(|t| self.term_mentions_var(t, var)),
3695            _ => false,
3696        }
3697    }
3698
3699    /// Collect all conjuncts from a conjunction tree
3700    fn collect_conjuncts(&self, expr: &'a LogicExpr<'a>) -> Vec<&'a LogicExpr<'a>> {
3701        match expr {
3702            LogicExpr::BinaryOp { left, op: TokenType::And, right } => {
3703                let mut result = self.collect_conjuncts(left);
3704                result.extend(self.collect_conjuncts(right));
3705                result
3706            }
3707            _ => vec![expr],
3708        }
3709    }
3710
3711    /// Wrap unused donkey bindings inside the restriction/body of a quantifier structure.
3712    ///
3713    /// For universals (implications):
3714    ///   Transform: ∀x((P(x) ∧ Q(y)) → R(x)) with unused y
3715    ///   Into:      ∀x((P(x) ∧ ∃y(Q(y))) → R(x))
3716    ///
3717    /// For existentials (conjunctions):
3718    ///   Transform: ∃x(P(x) ∧ Q(y) ∧ R(x)) with unused y
3719    ///   Into:      ∃x(P(x) ∧ ∃y(Q(y)) ∧ R(x))
3720    ///
3721    /// If wide_scope_negation is true, wrap the existential in negation:
3722    ///   Into:      ∀x((P(x) ∧ ¬∃y(Q(y))) → R(x))
3723    fn wrap_donkey_in_restriction(
3724        &self,
3725        body: &'a LogicExpr<'a>,
3726        donkey_var: Symbol,
3727        wide_scope_negation: bool,
3728    ) -> &'a LogicExpr<'a> {
3729        // Handle Quantifier wrapping first
3730        if let LogicExpr::Quantifier { kind, variable, body: inner_body, island_id } = body {
3731            let transformed = self.wrap_donkey_in_restriction(inner_body, donkey_var, wide_scope_negation);
3732            return self.ctx.exprs.alloc(LogicExpr::Quantifier {
3733                kind: kind.clone(),
3734                variable: *variable,
3735                body: transformed,
3736                island_id: *island_id,
3737            });
3738        }
3739
3740        // Handle implication (universal quantifiers)
3741        if let LogicExpr::BinaryOp { left, op: TokenType::Implies, right } = body {
3742            return self.wrap_in_implication(*left, *right, donkey_var, wide_scope_negation);
3743        }
3744
3745        // Handle conjunction (existential quantifiers)
3746        if let LogicExpr::BinaryOp { left: _, op: TokenType::And, right: _ } = body {
3747            return self.wrap_in_conjunction(body, donkey_var, wide_scope_negation);
3748        }
3749
3750        // Not a structure we can process
3751        body
3752    }
3753
3754    /// Wrap donkey binding in an implication structure (∀x(P(x) → Q(x)))
3755    fn wrap_in_implication(
3756        &self,
3757        restriction: &'a LogicExpr<'a>,
3758        consequent: &'a LogicExpr<'a>,
3759        donkey_var: Symbol,
3760        wide_scope_negation: bool,
3761    ) -> &'a LogicExpr<'a> {
3762        // Collect all conjuncts in the restriction
3763        let conjuncts = self.collect_conjuncts(restriction);
3764
3765        // Partition into those mentioning the donkey var and those not
3766        let (with_var, without_var): (Vec<_>, Vec<_>) = conjuncts
3767            .into_iter()
3768            .partition(|c| self.expr_mentions_var(c, donkey_var));
3769
3770        if with_var.is_empty() {
3771            // Variable not found in restriction, return original implication
3772            return self.ctx.exprs.alloc(LogicExpr::BinaryOp {
3773                left: restriction,
3774                op: TokenType::Implies,
3775                right: consequent,
3776            });
3777        }
3778
3779        // Combine the "with var" conjuncts
3780        let with_var_combined = self.combine_conjuncts(&with_var);
3781
3782        // Wrap with existential
3783        let existential = self.ctx.exprs.alloc(LogicExpr::Quantifier {
3784            kind: QuantifierKind::Existential,
3785            variable: donkey_var,
3786            body: with_var_combined,
3787            island_id: self.current_island,
3788        });
3789
3790        // For wide scope negation (de dicto reading of "lacks"), wrap ∃ in ¬
3791        let wrapped = if wide_scope_negation {
3792            self.ctx.exprs.alloc(LogicExpr::UnaryOp {
3793                op: TokenType::Not,
3794                operand: existential,
3795            })
3796        } else {
3797            existential
3798        };
3799
3800        // Combine with "without var" conjuncts
3801        let new_restriction = if without_var.is_empty() {
3802            wrapped
3803        } else {
3804            let without_combined = self.combine_conjuncts(&without_var);
3805            self.ctx.exprs.alloc(LogicExpr::BinaryOp {
3806                left: without_combined,
3807                op: TokenType::And,
3808                right: wrapped,
3809            })
3810        };
3811
3812        // Rebuild the implication
3813        self.ctx.exprs.alloc(LogicExpr::BinaryOp {
3814            left: new_restriction,
3815            op: TokenType::Implies,
3816            right: consequent,
3817        })
3818    }
3819
3820    /// Wrap donkey binding in a conjunction structure (∃x(P(x) ∧ Q(x)))
3821    fn wrap_in_conjunction(
3822        &self,
3823        body: &'a LogicExpr<'a>,
3824        donkey_var: Symbol,
3825        wide_scope_negation: bool,
3826    ) -> &'a LogicExpr<'a> {
3827        // Collect all conjuncts
3828        let conjuncts = self.collect_conjuncts(body);
3829
3830        // Partition into those mentioning the donkey var and those not
3831        let (with_var, without_var): (Vec<_>, Vec<_>) = conjuncts
3832            .into_iter()
3833            .partition(|c| self.expr_mentions_var(c, donkey_var));
3834
3835        if with_var.is_empty() {
3836            // Variable not found, return unchanged
3837            return body;
3838        }
3839
3840        // Combine the "with var" conjuncts
3841        let with_var_combined = self.combine_conjuncts(&with_var);
3842
3843        // Wrap with existential
3844        let existential = self.ctx.exprs.alloc(LogicExpr::Quantifier {
3845            kind: QuantifierKind::Existential,
3846            variable: donkey_var,
3847            body: with_var_combined,
3848            island_id: self.current_island,
3849        });
3850
3851        // For wide scope negation (de dicto reading of "lacks"), wrap ∃ in ¬
3852        let wrapped = if wide_scope_negation {
3853            self.ctx.exprs.alloc(LogicExpr::UnaryOp {
3854                op: TokenType::Not,
3855                operand: existential,
3856            })
3857        } else {
3858            existential
3859        };
3860
3861        // Combine with "without var" conjuncts
3862        if without_var.is_empty() {
3863            wrapped
3864        } else {
3865            let without_combined = self.combine_conjuncts(&without_var);
3866            self.ctx.exprs.alloc(LogicExpr::BinaryOp {
3867                left: without_combined,
3868                op: TokenType::And,
3869                right: wrapped,
3870            })
3871        }
3872    }
3873
3874    fn combine_conjuncts(&self, conjuncts: &[&'a LogicExpr<'a>]) -> &'a LogicExpr<'a> {
3875        if conjuncts.is_empty() {
3876            panic!("Cannot combine empty conjuncts");
3877        }
3878        if conjuncts.len() == 1 {
3879            return conjuncts[0];
3880        }
3881        let mut result = conjuncts[0];
3882        for c in &conjuncts[1..] {
3883            result = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
3884                left: result,
3885                op: TokenType::And,
3886                right: *c,
3887            });
3888        }
3889        result
3890    }
3891}