Skip to main content

logicaffeine_language/parser/
mod.rs

1//! Recursive descent parser for natural language to first-order logic.
2//!
3//! This module implements a hand-written recursive descent parser that transforms
4//! natural language sentences into logical expressions. The parser operates in two
5//! modes: **Declarative** for natural language propositions (Logicaffeine mode) and
6//! **Imperative** for strict, deterministic scoping (LOGOS mode).
7//!
8//! # Architecture
9//!
10//! The parser is split into specialized submodules:
11//!
12//! | Module | Responsibility |
13//! |--------|----------------|
14//! | `clause` | Sentence-level parsing: conditionals, conjunctions, relative clauses |
15//! | `noun` | Noun phrase parsing with determiners, adjectives, possessives |
16//! | `verb` | Verb phrase parsing, event semantics, thematic roles |
17//! | `modal` | Modal verbs (can, must, might) with Kripke semantics |
18//! | `quantifier` | Quantifier scope: every, some, no, most |
19//! | `question` | Wh-movement and question formation |
20//! | `pragmatics` | Pragmatic inference during parsing |
21//! | `common` | Shared utilities (copula lists, etc.) |
22//!
23//! # Key Types
24//!
25//! - [`Parser`]: The main parser struct holding tokens, state, and arenas
26//! - [`ParserMode`]: Declarative (NL) vs Imperative (LOGOS) mode
27//! - [`ParserGuard`]: RAII guard for speculative parsing with automatic rollback
28//! - [`NegativeScopeMode`]: Wide vs narrow scope for lexically negative verbs
29//! - [`ModalPreference`]: Default, epistemic, or deontic readings for modals
30//!
31//! # Example
32//!
33//! ```no_run
34//! use logicaffeine_language::parser::{Parser, ParserMode, ClauseParsing};
35//! # use logicaffeine_base::{Arena, Interner};
36//! # use logicaffeine_language::{lexer::Lexer, drs::WorldState, arena_ctx::AstContext, analysis::DiscoveryPass, ParseError};
37//! # fn main() -> Result<(), ParseError> {
38//! # let mut interner = Interner::new();
39//! # let mut lexer = Lexer::new("Every cat runs.", &mut interner);
40//! # let tokens = lexer.tokenize();
41//! # let type_registry = { let mut d = DiscoveryPass::new(&tokens, &mut interner); d.run() };
42//! # let ea = Arena::new(); let ta = Arena::new(); let na = Arena::new();
43//! # let sa = Arena::new(); let ra = Arena::new(); let pa = Arena::new();
44//! # let ctx = AstContext::new(&ea, &ta, &na, &sa, &ra, &pa);
45//! # let mut ws = WorldState::new();
46//! # let mut parser = Parser::new(tokens, &mut ws, &mut interner, ctx, type_registry);
47//!
48//! // Lexer produces tokens, then parser produces LogicExpr
49//! let expr = parser.parse_sentence()?;
50//! # Ok(())
51//! # }
52//! ```
53
54mod clause;
55mod common;
56mod modal;
57mod noun;
58mod pragmatics;
59mod quantifier;
60mod question;
61mod verb;
62
63#[cfg(test)]
64mod tests;
65
66pub use clause::ClauseParsing;
67pub use modal::ModalParsing;
68pub use noun::NounParsing;
69pub use pragmatics::PragmaticsParsing;
70pub use quantifier::QuantifierParsing;
71pub use question::QuestionParsing;
72pub use verb::{LogicVerbParsing, ImperativeVerbParsing};
73
74use crate::analysis::TypeRegistry;
75use crate::arena_ctx::AstContext;
76use crate::ast::{AspectOperator, CompressionCodec, SendLayout, LogicExpr, NeoEventData, NumberKind, QuantifierKind, TemporalOperator, Term, ThematicRole, Stmt, Expr, Literal, TypeExpr, BinaryOpKind, MatchArm};
77use crate::optimization::{by_keyword, pin_from_str, OptimizationConfig, PinSet};
78use crate::ast::stmt::{ReadSource, Pattern};
79use std::collections::HashSet;
80use crate::drs::{Case, Gender, Number, ReferentSource};
81use crate::drs::{Drs, BoxType, WorldState};
82use crate::error::{ParseError, ParseErrorKind};
83use logicaffeine_base::{Interner, Symbol, SymbolEq};
84use crate::lexer::Lexer;
85use crate::lexicon::{self, Aspect, Definiteness, Time, VerbClass};
86use crate::token::{BlockType, FocusKind, Span, Token, TokenType};
87
88pub(super) type ParseResult<T> = Result<T, ParseError>;
89
90use std::ops::{Deref, DerefMut};
91
92/// Determines how the parser interprets sentences.
93#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
94pub enum ParserMode {
95    /// Logicaffeine mode: propositions, NeoEvents, ambiguity allowed.
96    #[default]
97    Declarative,
98    /// LOGOS mode: statements, strict scoping, deterministic.
99    Imperative,
100}
101
102/// Temporal modifier after copula: "is always Y", "is never Y", "is eventually Y".
103#[derive(Debug, Clone, Copy)]
104pub(super) enum CopulaTemporal {
105    Always,
106    Never,
107    Eventually,
108}
109
110/// Controls scope of negation for lexically negative verbs (lacks, miss).
111/// "user who lacks a key" can mean:
112///   - Wide:   ¬∃y(Key(y) ∧ Have(x,y)) - "has NO keys" (natural reading)
113///   - Narrow: ∃y(Key(y) ∧ ¬Have(x,y)) - "missing SOME key" (literal reading)
114#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
115pub enum NegativeScopeMode {
116    /// Narrow scope negation (literal reading): ∃y(Key(y) ∧ ¬Have(x,y))
117    /// "User is missing some key" - need all keys (default/traditional reading)
118    #[default]
119    Narrow,
120    /// Wide scope negation (natural reading): ¬∃y(Key(y) ∧ Have(x,y))
121    /// "User has no keys" - need at least one key
122    Wide,
123}
124
125/// Controls interpretation of polysemous modals (may, can, could).
126/// Used by compile_forest to generate multiple semantic readings.
127///
128/// Semantic Matrix:
129///   may:   Default=Permission (Deontic, Root)    Epistemic=Possibility (Alethic, Epistemic)
130///   can:   Default=Ability (Alethic, Root)       Deontic=Permission (Deontic, Root)
131///   could: Default=PastAbility (Alethic, Root)   Epistemic=Possibility (Alethic, Epistemic)
132#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
133pub enum ModalPreference {
134    /// Default readings: may=Permission, can=Ability, could=PastAbility
135    #[default]
136    Default,
137    /// Epistemic readings: may=Possibility (wide scope), could=Possibility (wide scope)
138    Epistemic,
139    /// Deontic readings: can=Permission (narrow scope, deontic domain)
140    Deontic,
141}
142
143/// Result of pronoun resolution during parsing.
144///
145/// Determines whether a pronoun refers to a bound variable (anaphoric) or
146/// a deictic constant (referring to an entity outside the discourse).
147#[derive(Debug, Clone, Copy)]
148pub enum ResolvedPronoun {
149    /// Bound variable from DRS or telescope (use [`Term::Variable`]).
150    Variable(Symbol),
151    /// Constant (deictic or proper name) (use [`Term::Constant`]).
152    Constant(Symbol),
153}
154
155#[derive(Clone)]
156struct ParserCheckpoint {
157    pos: usize,
158    var_counter: usize,
159    bindings_len: usize,
160    island: u32,
161    time: Option<Time>,
162    negative_depth: u32,
163}
164
165/// RAII guard for speculative parsing with automatic rollback.
166///
167/// Created by [`Parser::guard()`], this guard saves the parser state
168/// on creation and automatically restores it if the guard is dropped
169/// without being committed.
170///
171/// # Usage
172///
173/// ```no_run
174/// # use logicaffeine_base::{Arena, Interner};
175/// # use logicaffeine_language::{lexer::Lexer, drs::WorldState, arena_ctx::AstContext, analysis::DiscoveryPass, ParseError};
176/// # use logicaffeine_language::parser::Parser;
177/// # fn try_parse<T>(_: &mut T) -> Result<(), ParseError> { todo!() }
178/// # fn main() -> Result<(), ParseError> {
179/// # let mut interner = Interner::new();
180/// # let mut lexer = Lexer::new("test.", &mut interner);
181/// # let tokens = lexer.tokenize();
182/// # let tr = { let mut d = DiscoveryPass::new(&tokens, &mut interner); d.run() };
183/// # let ea = Arena::new(); let ta = Arena::new(); let na = Arena::new();
184/// # let sa = Arena::new(); let ra = Arena::new(); let pa = Arena::new();
185/// # let ctx = AstContext::new(&ea, &ta, &na, &sa, &ra, &pa);
186/// # let mut ws = WorldState::new();
187/// # let mut parser = Parser::new(tokens, &mut ws, &mut interner, ctx, tr);
188/// let mut guard = parser.guard();
189/// if let Ok(result) = try_parse(&mut *guard) {
190///     guard.commit(); // Success - keep changes
191///     return Ok(result);
192/// }
193/// // guard dropped here - parser state restored
194/// # Ok(())
195/// # }
196/// ```
197pub struct ParserGuard<'p, 'a, 'ctx, 'int> {
198    parser: &'p mut Parser<'a, 'ctx, 'int>,
199    checkpoint: ParserCheckpoint,
200    committed: bool,
201}
202
203impl<'p, 'a, 'ctx, 'int> ParserGuard<'p, 'a, 'ctx, 'int> {
204    /// Commits the parse, preventing rollback when the guard is dropped.
205    pub fn commit(mut self) {
206        self.committed = true;
207    }
208}
209
210impl<'p, 'a, 'ctx, 'int> Drop for ParserGuard<'p, 'a, 'ctx, 'int> {
211    fn drop(&mut self) {
212        if !self.committed {
213            self.parser.restore(self.checkpoint.clone());
214        }
215    }
216}
217
218impl<'p, 'a, 'ctx, 'int> Deref for ParserGuard<'p, 'a, 'ctx, 'int> {
219    type Target = Parser<'a, 'ctx, 'int>;
220    fn deref(&self) -> &Self::Target {
221        self.parser
222    }
223}
224
225impl<'p, 'a, 'ctx, 'int> DerefMut for ParserGuard<'p, 'a, 'ctx, 'int> {
226    fn deref_mut(&mut self) -> &mut Self::Target {
227        self.parser
228    }
229}
230
231/// Template for constructing Neo-Davidsonian events.
232///
233/// Used during parsing to accumulate thematic roles and modifiers
234/// before the agent is known (e.g., in VP ellipsis resolution).
235#[derive(Clone, Debug)]
236pub struct EventTemplate<'a> {
237    /// The verb predicate.
238    pub verb: Symbol,
239    /// The agent term, for gapping patterns where the agent is shared/gapped
240    /// ("John gave Mary a book and Sue a pen" — John is the agent of both).
241    pub agent: Option<Term<'a>>,
242    /// Thematic roles excluding the agent (filled later).
243    pub non_agent_roles: Vec<(ThematicRole, Term<'a>)>,
244    /// Adverbial modifiers.
245    pub modifiers: Vec<Symbol>,
246}
247
248/// Recursive descent parser for natural language to first-order logic.
249///
250/// The parser transforms a token stream (from [`Lexer`]) into logical expressions
251/// ([`LogicExpr`]). It handles complex linguistic phenomena including:
252///
253/// - Quantifier scope ambiguity
254/// - Pronoun resolution via DRS
255/// - Modal verb interpretation
256/// - Temporal and aspectual marking
257/// - VP ellipsis resolution
258///
259/// # Lifetimes
260///
261/// - `'a`: Arena lifetime for allocated AST nodes
262/// - `'ctx`: WorldState lifetime for discourse tracking
263/// - `'int`: Interner lifetime for symbol management
264pub struct Parser<'a, 'ctx, 'int> {
265    /// Token stream from lexer.
266    pub(super) tokens: Vec<Token>,
267    /// Current position in token stream.
268    pub(super) current: usize,
269    /// File-level optimization decorators: a `## No <X>` that is NOT attached to a
270    /// following `## To` function (it appears before `## Main` or at EOF) applies
271    /// program-wide. Read after `parse_program` via [`Parser::program_opt_flags`].
272    pub(super) program_opt_flags: OptimizationConfig,
273    /// File-level tiered-optimizer pins from `## Tier <opt> <value>` decorators
274    /// (HOTSWAP §8): each pins one optimization to a hotness tier (or `eager`/`never`).
275    /// Program-wide; read after `parse_program` via [`Parser::program_tier_pins`].
276    pub(super) program_tier_pins: PinSet,
277    /// Counter for generating fresh variables.
278    pub(super) var_counter: usize,
279    /// Every name the program BINDS (`Let`, function params) — an
280    /// over-approximation that lets word literals (`infinity`, `nan`) yield
281    /// to user variables of the same name (ambiguity-preserving: a bound
282    /// name is never shadowed by a literal reading).
283    pub(super) user_bound: std::collections::HashSet<Symbol>,
284    /// Pending tense from temporal adverbs.
285    pub(super) pending_time: Option<Time>,
286    /// Donkey bindings: (noun, var, is_donkey_used, wide_scope_negation).
287    pub(super) donkey_bindings: Vec<(Symbol, Symbol, bool, bool)>,
288    /// String interner for symbol management.
289    pub(super) interner: &'int mut Interner,
290    /// Arena context for AST allocation.
291    pub(super) ctx: AstContext<'a>,
292    /// Current scope island ID.
293    pub(super) current_island: u32,
294    /// Whether PP attaches to noun (vs verb).
295    pub(super) pp_attach_to_noun: bool,
296    /// Filler for wh-movement gap.
297    pub(super) filler_gap: Option<Symbol>,
298    /// Depth of negation scope.
299    pub(super) negative_depth: u32,
300    /// Event variable for discourse coherence.
301    pub(super) discourse_event_var: Option<Symbol>,
302    /// Last event template for VP ellipsis.
303    pub(super) last_event_template: Option<EventTemplate<'a>>,
304    /// Pragmatic-enrichment mode: when set, gradable predication emits the degree-
305    /// standard form (§7.2) and vagueness penumbra (§8.5), and "either…or" emits its
306    /// exclusivity. Off by default so `compile()` stays literal/truth-conditional and
307    /// the proof engine sees simple predicates.
308    pub(super) pragmatic: bool,
309    /// Whether to prefer noun readings.
310    pub(super) noun_priority_mode: bool,
311    /// Whether plural NPs get collective readings.
312    pub(super) collective_mode: bool,
313    /// A floated quantifier ("the boys ALL/EACH left") forces the
314    /// distributive reading of a mixed verb that would otherwise default
315    /// to the collective one.
316    pub(super) distributive_marker: bool,
317    /// Pending cardinal for delayed quantification.
318    pub(super) pending_cardinal: Option<u32>,
319    /// Parser mode: Declarative or Imperative.
320    pub(super) mode: ParserMode,
321    /// Type registry for LOGOS mode.
322    pub(super) type_registry: Option<TypeRegistry>,
323    /// Whether to produce event readings.
324    pub(super) event_reading_mode: bool,
325    /// Internal DRS for sentence-level scope tracking.
326    pub(super) drs: Drs,
327    /// Negation scope mode for lexically negative verbs.
328    pub(super) negative_scope_mode: NegativeScopeMode,
329    /// Modal interpretation preference.
330    pub(super) modal_preference: ModalPreference,
331    /// WorldState for discourse-level parsing.
332    pub(super) world_state: &'ctx mut WorldState,
333    /// Whether inside "No X" quantifier scope.
334    pub(super) in_negative_quantifier: bool,
335    /// Pending partitive superset captured from an "of the [Num]" frame:
336    /// (superset_cardinality, restriction_predicate, restriction_variable). The asserted
337    /// count quantifier is wrapped in a `Presupposition` whose presupposition is the
338    /// superset's `Cardinal` quantifier over the same restriction (§5.3).
339    pub(super) pending_partitive: Option<(u32, &'a LogicExpr<'a>, Symbol)>,
340    /// A subject's relative-clause restriction that cannot be carried on the
341    /// `NounPhrase` (it is a full clause, not an adjective/PP/possessor) and so
342    /// is stashed — keyed to the subject noun and expressed over
343    /// `Constant(noun)` — to be folded in at the `wrap_with_definiteness`
344    /// chokepoint when the predicate (e.g. a copula complement) is built over
345    /// the same constant. Without this, "The scent that sold for $75 is the
346    /// white fragrance." silently drops the relative clause.
347    pub(super) pending_subject_restriction: Option<(Symbol, &'a LogicExpr<'a>)>,
348    /// Set while parsing an object NP that a determiner/cardinal/counting
349    /// quantifier has ALREADY established as nominal (e.g. the cardinal was
350    /// consumed by the object-quantifier gate before `parse_noun_phrase` runs).
351    /// A bare verb-word after an adjective is then a DEVERBAL NOUN head ("49
352    /// previous JUMPS", "six previous RUNS") — a cardinal/determiner cannot be
353    /// followed by a finite verb — so `parse_noun_phrase` recovers it as the
354    /// head instead of stranding it. Outside this context the determiner gate
355    /// (`definiteness.is_some()`) is the only signal, to avoid eating a real
356    /// main verb ("studies hard pass").
357    pub(super) nominal_np_context: bool,
358    /// Named acceptance contracts declared by `Accept computed <Name> where <param> is an
359    /// Int from <lo> to <hi>` → `(lo, hi)`. Pure parse-time sugar: a `Run <f> on <arg> under
360    /// <Name> into <var>` statement desugars to `Let <var> be run_accepted(<f>, <arg>, lo, hi)`,
361    /// inlining the named contract's bounds — no new AST node, no runtime contract registry.
362    pub(super) contracts: std::collections::HashMap<String, (i64, i64)>,
363    /// One source span per top-level statement `parse_program` returned, in
364    /// push order — the side-table that gives typechecker diagnostics,
365    /// ownership cause-links, and the rustc sourcemap real spans without a
366    /// span field on every `Stmt` variant. Read via [`Parser::stmt_spans`].
367    pub(super) stmt_spans: Vec<Span>,
368    /// Live recursion depth of the imperative descent (parenthesized
369    /// expressions, nested blocks). Checked against
370    /// [`crate::ast_depth::max_ast_depth`] so a parenthesis tower or block
371    /// pyramid errors gracefully instead of overflowing the parser's own
372    /// stack — the parse-time half of the AST depth gate.
373    pub(super) recursion_depth: usize,
374}
375
376impl<'a, 'ctx, 'int> Parser<'a, 'ctx, 'int> {
377    /// Create a parser with WorldState for discourse-level parsing.
378    /// WorldState is REQUIRED - there is no "single sentence mode".
379    /// A single sentence is just a discourse of length 1.
380    pub fn new(
381        mut tokens: Vec<Token>,
382        world_state: &'ctx mut WorldState,
383        interner: &'int mut Interner,
384        ctx: AstContext<'a>,
385        types: TypeRegistry,
386    ) -> Self {
387        // The parser's walk is total only over an EOF-terminated stream;
388        // fragments and block slices can arrive without one, and a missing
389        // terminator used to surface as an index-out-of-bounds panic deep
390        // in `peek()`.
391        if !matches!(tokens.last().map(|t| &t.kind), Some(TokenType::EOF)) {
392            let end = tokens.last().map(|t| t.span.end).unwrap_or(0);
393            let empty = interner.intern("");
394            tokens.push(Token::new(TokenType::EOF, empty, Span::new(end, end)));
395        }
396        Parser {
397            tokens,
398            current: 0,
399            contracts: std::collections::HashMap::new(),
400            program_opt_flags: OptimizationConfig::all_on(),
401            program_tier_pins: PinSet::none(),
402            var_counter: 0,
403            user_bound: std::collections::HashSet::new(),
404            pending_time: None,
405            donkey_bindings: Vec::new(),
406            interner,
407            ctx,
408            current_island: 0,
409            pp_attach_to_noun: false,
410            filler_gap: None,
411            negative_depth: 0,
412            discourse_event_var: None,
413            last_event_template: None,
414            pragmatic: false,
415            noun_priority_mode: false,
416            collective_mode: false,
417            distributive_marker: false,
418            pending_cardinal: None,
419            mode: ParserMode::Declarative,
420            type_registry: Some(types),
421            event_reading_mode: false,
422            drs: Drs::new(), // Internal DRS for sentence-level scope tracking
423            negative_scope_mode: NegativeScopeMode::default(),
424            modal_preference: ModalPreference::default(),
425            pending_subject_restriction: None,
426            nominal_np_context: false,
427            world_state,
428            in_negative_quantifier: false,
429            pending_partitive: None,
430            stmt_spans: Vec::new(),
431            recursion_depth: 0,
432        }
433    }
434
435    /// The span of each top-level statement from the last `parse_program`
436    /// call, aligned 1:1 with the returned statement list.
437    pub fn stmt_spans(&self) -> &[Span] {
438        &self.stmt_spans
439    }
440
441    /// Record the span for the statement just pushed: from the first token of
442    /// `start_tok` through the last consumed token (terminating period or
443    /// dedent included). `parse_program` asserts 1:1 alignment on return.
444    fn record_stmt_span(&mut self, start_tok: usize) {
445        let start = self
446            .tokens
447            .get(start_tok)
448            .map(|t| t.span.start)
449            .unwrap_or(0);
450        let end = self
451            .tokens
452            .get(self.current.saturating_sub(1))
453            .map(|t| t.span.end)
454            .unwrap_or(start);
455        self.stmt_spans.push(Span::new(start, end));
456    }
457
458    pub fn set_discourse_event_var(&mut self, var: Symbol) {
459        self.discourse_event_var = Some(var);
460    }
461
462    /// Get mutable reference to the active DRS (from WorldState).
463    pub fn drs_mut(&mut self) -> &mut Drs {
464        &mut self.world_state.drs
465    }
466
467    /// Get immutable reference to the active DRS (from WorldState).
468    pub fn drs_ref(&self) -> &Drs {
469        &self.world_state.drs
470    }
471
472    /// Swap DRS between Parser and WorldState.
473    /// Call at start of parsing to get the accumulated DRS from WorldState.
474    /// Call at end of parsing to save the updated DRS back to WorldState.
475    pub fn swap_drs_with_world_state(&mut self) {
476        std::mem::swap(&mut self.drs, &mut self.world_state.drs);
477    }
478
479    /// WorldState is always present (no "single sentence mode")
480    pub fn has_world_state(&self) -> bool {
481        true
482    }
483
484    pub fn mode(&self) -> ParserMode {
485        self.mode
486    }
487
488    /// Check if a symbol is a known type in the registry.
489    /// Used to disambiguate "Stack of Integers" (generic type) vs "Owner of House" (possessive).
490    pub fn is_known_type(&self, sym: Symbol) -> bool {
491        self.type_registry
492            .as_ref()
493            .map(|r| r.is_type(sym))
494            .unwrap_or(false)
495    }
496
497    /// Check if a symbol is a known generic type (takes type parameters).
498    /// Used to parse "Stack of Integers" as generic instantiation.
499    pub fn is_generic_type(&self, sym: Symbol) -> bool {
500        self.type_registry
501            .as_ref()
502            .map(|r| r.is_generic(sym))
503            .unwrap_or(false)
504    }
505
506    /// Get the parameter count for a generic type.
507    fn get_generic_param_count(&self, sym: Symbol) -> Option<usize> {
508        use crate::analysis::TypeDef;
509        self.type_registry.as_ref().and_then(|r| {
510            match r.get(sym) {
511                Some(TypeDef::Generic { param_count }) => Some(*param_count),
512                _ => None,
513            }
514        })
515    }
516
517    /// Phase 33: Check if a symbol is a known enum variant and return the enum name.
518    fn find_variant(&self, sym: Symbol) -> Option<Symbol> {
519        self.type_registry
520            .as_ref()
521            .and_then(|r| r.find_variant(sym).map(|(enum_name, _)| enum_name))
522    }
523
524    /// Consume a type name token (doesn't check entity registration).
525    fn consume_type_name(&mut self) -> ParseResult<Symbol> {
526        let t = self.advance().clone();
527        match t.kind {
528            TokenType::Noun(s) | TokenType::Adjective(s) => Ok(s),
529            TokenType::ProperName(s) => Ok(s),
530            // Verbs can be type names when lexed ambiguously (e.g., "Set" as type vs verb)
531            TokenType::Verb { .. } => Ok(t.lexeme),
532            // Phase 49b: CRDT type keywords are valid type names
533            TokenType::Tally => Ok(self.interner.intern("Tally")),
534            TokenType::SharedSet => Ok(self.interner.intern("SharedSet")),
535            TokenType::SharedSequence => Ok(self.interner.intern("SharedSequence")),
536            TokenType::CollaborativeSequence => Ok(self.interner.intern("CollaborativeSequence")),
537            TokenType::SharedMap => Ok(self.interner.intern("SharedMap")),
538            TokenType::Divergent => Ok(self.interner.intern("Divergent")),
539            // Type parameter names (e.g. T, U) can be tokenized as articles by the English lexer
540            TokenType::Article(_) => Ok(t.lexeme),
541            other => Err(ParseError {
542                kind: ParseErrorKind::ExpectedContentWord { found: other },
543                span: self.current_span(),
544            }),
545        }
546    }
547
548    /// Parse a type expression: Int, Text, List of Int, Result of Int and Text.
549    /// Phase 36: Also supports "Type from Module" for qualified imports.
550    /// Uses TypeRegistry to distinguish primitives from generics.
551    /// Also handles `fn(A, B) -> C` function types.
552    fn parse_type_expression(&mut self) -> ParseResult<TypeExpr<'a>> {
553        use noun::NounParsing;
554
555        // Handle `fn(A, B) -> C` function type syntax
556        if self.check_word("fn") {
557            if let Some(next) = self.tokens.get(self.current + 1) {
558                if matches!(next.kind, TokenType::LParen) {
559                    self.advance(); // consume "fn"
560                    self.advance(); // consume "("
561
562                    // Parse input types
563                    let mut inputs = Vec::new();
564                    if !self.check(&TokenType::RParen) {
565                        inputs.push(self.parse_type_expression()?);
566                        while self.check(&TokenType::Comma) {
567                            self.advance(); // consume ","
568                            inputs.push(self.parse_type_expression()?);
569                        }
570                    }
571
572                    if !self.check(&TokenType::RParen) {
573                        return Err(ParseError {
574                            kind: ParseErrorKind::ExpectedKeyword { keyword: ")".to_string() },
575                            span: self.current_span(),
576                        });
577                    }
578                    self.advance(); // consume ")"
579
580                    // Expect ->
581                    if !self.check(&TokenType::Arrow) {
582                        return Err(ParseError {
583                            kind: ParseErrorKind::ExpectedKeyword { keyword: "->".to_string() },
584                            span: self.current_span(),
585                        });
586                    }
587                    self.advance(); // consume "->"
588
589                    let output = self.parse_type_expression()?;
590                    let output_ref = self.ctx.alloc_type_expr(output);
591                    let inputs_ref = self.ctx.alloc_type_exprs(inputs);
592                    return Ok(TypeExpr::Function { inputs: inputs_ref, output: output_ref });
593                }
594            }
595        }
596
597        // Bug fix: Handle parenthesized type expressions: "Seq of (Seq of Int)"
598        if self.check(&TokenType::LParen) {
599            self.advance(); // consume "("
600            let inner = self.parse_type_expression()?;
601            if !self.check(&TokenType::RParen) {
602                return Err(ParseError {
603                    kind: ParseErrorKind::ExpectedKeyword { keyword: ")".to_string() },
604                    span: self.current_span(),
605                });
606            }
607            self.advance(); // consume ")"
608            return Ok(inner);
609        }
610
611        // Phase 53: Handle "Persistent T" type modifier
612        if self.check(&TokenType::Persistent) {
613            self.advance(); // consume "Persistent"
614            let inner = self.parse_type_expression()?;
615            let inner_ref = self.ctx.alloc_type_expr(inner);
616            return Ok(TypeExpr::Persistent { inner: inner_ref });
617        }
618
619        // Get the base type name (must be a noun or proper name - type names bypass entity check)
620        let mut base = self.consume_type_name()?;
621
622        // Phase 49c: Check for bias modifier on SharedSet: "SharedSet (RemoveWins) of T"
623        let base_name = self.interner.resolve(base);
624        if base_name == "SharedSet" || base_name == "ORSet" {
625            if self.check(&TokenType::LParen) {
626                self.advance(); // consume "("
627                if self.check(&TokenType::RemoveWins) {
628                    self.advance(); // consume "RemoveWins"
629                    base = self.interner.intern("SharedSet_RemoveWins");
630                } else if self.check(&TokenType::AddWins) {
631                    self.advance(); // consume "AddWins"
632                    // AddWins is default, but we can be explicit
633                    base = self.interner.intern("SharedSet_AddWins");
634                }
635                if !self.check(&TokenType::RParen) {
636                    return Err(ParseError {
637                        kind: ParseErrorKind::ExpectedKeyword { keyword: ")".to_string() },
638                        span: self.current_span(),
639                    });
640                }
641                self.advance(); // consume ")"
642            }
643        }
644
645        // Phase 49c: Check for algorithm modifier on SharedSequence: "SharedSequence (YATA) of T"
646        let base_name = self.interner.resolve(base);
647        if base_name == "SharedSequence" || base_name == "RGA" {
648            if self.check(&TokenType::LParen) {
649                self.advance(); // consume "("
650                if self.check(&TokenType::YATA) {
651                    self.advance(); // consume "YATA"
652                    base = self.interner.intern("SharedSequence_YATA");
653                }
654                if !self.check(&TokenType::RParen) {
655                    return Err(ParseError {
656                        kind: ParseErrorKind::ExpectedKeyword { keyword: ")".to_string() },
657                        span: self.current_span(),
658                    });
659                }
660                self.advance(); // consume ")"
661            }
662        }
663
664        // Phase 36: Check for "from Module" qualification
665        let base_type = if self.check(&TokenType::From) {
666            self.advance(); // consume "from"
667            let module_name = self.consume_type_name()?;
668            let module_str = self.interner.resolve(module_name);
669            let base_str = self.interner.resolve(base);
670            let qualified = format!("{}::{}", module_str, base_str);
671            let qualified_sym = self.interner.intern(&qualified);
672            TypeExpr::Named(qualified_sym)
673        } else {
674            // Phase 38: Get param count from registry OR from built-in std types
675            let base_name = self.interner.resolve(base);
676            // `Quantity of <Dimension>` reads its parameter leniently (a dimension word may collide
677            // with a keyword token); capture the predicate as a Copy bool so the `base_name` borrow
678            // does not span the `self.advance()` calls in the parameter loop below.
679            let base_is_quantity = base_name == "Quantity";
680            let param_count = self.get_generic_param_count(base)
681                .or_else(|| match base_name {
682                    // Built-in generic types for Phase 38 std library
683                    "Result" => Some(2),    // Result of T and E
684                    "Option" | "Maybe" => Some(1),    // Option of T / Maybe T
685                    "Seq" | "List" | "Vec" => Some(1),  // Seq of T
686                    "Set" | "HashSet" => Some(1), // Set of T
687                    "Map" | "HashMap" => Some(2), // Map of K and V
688                    "Pair" => Some(2),      // Pair of A and B
689                    "Triple" => Some(3),    // Triple of A and B and C
690                    // Phase 49b: CRDT generic types
691                    "SharedSet" | "ORSet" | "SharedSet_AddWins" | "SharedSet_RemoveWins" => Some(1),
692                    "SharedSequence" | "RGA" | "SharedSequence_YATA" | "CollaborativeSequence" => Some(1),
693                    "SharedMap" | "ORMap" => Some(2),      // SharedMap from K to V
694                    "Divergent" | "MVRegister" => Some(1), // Divergent T
695                    _ => None,
696                });
697
698            // Check if it's a known generic type with parameters
699            if let Some(count) = param_count {
700                let has_preposition = self.check_of_preposition() || self.check_preposition_is("from");
701                // Maybe supports direct syntax without "of": "Maybe Int", "Maybe Text"
702                let maybe_direct = !has_preposition && base_name == "Maybe" && matches!(
703                    self.peek().kind,
704                    TokenType::Noun(_) | TokenType::Adjective(_) | TokenType::ProperName(_) | TokenType::Verb { .. }
705                );
706                if has_preposition || maybe_direct {
707                    if has_preposition {
708                        self.advance(); // consume "of" or "from"
709                    }
710
711                    let mut params = Vec::new();
712                    for i in 0..count {
713                        if i > 0 {
714                            // Expect separator for params > 1: "and", "to", or ","
715                            if self.check(&TokenType::And) || self.check_to_preposition() || self.check(&TokenType::Comma) {
716                                self.advance();
717                            }
718                        }
719                        // `Quantity of <Dimension>` takes a DIMENSION NAME, which can collide with a
720                        // keyword token (`Length` is the `length of` keyword). Read it leniently by
721                        // lexeme so any dimension word is accepted in this position.
722                        let param = if base_is_quantity {
723                            let dim_sym = self.peek().lexeme;
724                            self.advance();
725                            TypeExpr::Named(dim_sym)
726                        } else {
727                            self.parse_type_expression()?
728                        };
729                        params.push(param);
730                    }
731
732                    let params_slice = self.ctx.alloc_type_exprs(params);
733                    TypeExpr::Generic { base, params: params_slice }
734                } else {
735                    // Generic type without parameters - treat as primitive or named
736                    let is_primitive = self.type_registry.as_ref().map(|r| r.is_type(base)).unwrap_or(false)
737                        || matches!(
738                        base_name,
739                        "Int" | "Nat" | "Text" | "Bool" | "Boolean" | "Real" | "Unit"
740                            | "Word8" | "Word16" | "Word32" | "Word64" | "Lanes8Word32"
741                            | "Lanes4Word32" | "Lanes16Word8" | "Lanes4Word64" | "Lanes16Word16"
742                    );
743                    if is_primitive {
744                        TypeExpr::Primitive(base)
745                    } else {
746                        TypeExpr::Named(base)
747                    }
748                }
749            } else {
750                // Check if it's a known primitive type (Int, Nat, Text, Bool, Real, Unit)
751                let is_primitive = self.type_registry.as_ref().map(|r| r.is_type(base)).unwrap_or(false)
752                    || matches!(
753                        base_name,
754                        "Int" | "Nat" | "Text" | "Bool" | "Boolean" | "Real" | "Unit"
755                            | "Word8" | "Word16" | "Word32" | "Word64" | "Lanes8Word32"
756                            | "Lanes4Word32" | "Lanes16Word8" | "Lanes4Word64" | "Lanes16Word16"
757                    );
758                if is_primitive {
759                    TypeExpr::Primitive(base)
760                } else {
761                    // User-defined or unknown type
762                    TypeExpr::Named(base)
763                }
764            }
765        };
766
767        // Phase 43C: Check for refinement "where" clause
768        if self.check(&TokenType::Where) {
769            self.advance(); // consume "where"
770
771            // Parse the predicate expression (supports compound: `x > 0 and x < 100`)
772            let predicate_expr = self.parse_condition()?;
773
774            // Extract bound variable from the left side of the expression
775            let bound_var = self.extract_bound_var(&predicate_expr)
776                .unwrap_or_else(|| self.interner.intern("it"));
777
778            // Convert imperative Expr to logic LogicExpr
779            let predicate = self.expr_to_logic_predicate(&predicate_expr, bound_var)
780                .ok_or_else(|| ParseError {
781                    kind: ParseErrorKind::InvalidRefinementPredicate,
782                    span: self.peek().span,
783                })?;
784
785            // Allocate the base type
786            let base_alloc = self.ctx.alloc_type_expr(base_type);
787
788            return Ok(TypeExpr::Refinement { base: base_alloc, var: bound_var, predicate });
789        }
790
791        Ok(base_type)
792    }
793
794    /// Extracts the leftmost identifier from an expression as the bound variable.
795    fn extract_bound_var(&self, expr: &Expr<'a>) -> Option<Symbol> {
796        match expr {
797            Expr::Identifier(sym) => Some(*sym),
798            Expr::BinaryOp { left, .. } => self.extract_bound_var(left),
799            _ => None,
800        }
801    }
802
803    /// Converts an imperative comparison Expr to a Logic Kernel LogicExpr.
804    /// Used for refinement type predicates: `Int where x > 0`
805    fn expr_to_logic_predicate(&mut self, expr: &Expr<'a>, bound_var: Symbol) -> Option<&'a LogicExpr<'a>> {
806        match expr {
807            Expr::BinaryOp { op, left, right } => {
808                // Map BinaryOpKind to predicate name
809                let pred_name = match op {
810                    BinaryOpKind::Gt => "Greater",
811                    BinaryOpKind::Lt => "Less",
812                    BinaryOpKind::GtEq => "GreaterEqual",
813                    BinaryOpKind::LtEq => "LessEqual",
814                    BinaryOpKind::Eq => "Equal",
815                    BinaryOpKind::NotEq => "NotEqual",
816                    BinaryOpKind::And => {
817                        // Handle compound `x > 0 and x < 100`
818                        let left_logic = self.expr_to_logic_predicate(left, bound_var)?;
819                        let right_logic = self.expr_to_logic_predicate(right, bound_var)?;
820                        return Some(self.ctx.exprs.alloc(LogicExpr::BinaryOp {
821                            left: left_logic,
822                            op: TokenType::And,
823                            right: right_logic,
824                        }));
825                    }
826                    BinaryOpKind::Or => {
827                        let left_logic = self.expr_to_logic_predicate(left, bound_var)?;
828                        let right_logic = self.expr_to_logic_predicate(right, bound_var)?;
829                        return Some(self.ctx.exprs.alloc(LogicExpr::BinaryOp {
830                            left: left_logic,
831                            op: TokenType::Or,
832                            right: right_logic,
833                        }));
834                    }
835                    _ => return None, // Arithmetic ops not valid as predicates
836                };
837                let pred_sym = self.interner.intern(pred_name);
838
839                // Convert operands to Terms
840                let left_term = self.expr_to_term(left)?;
841                let right_term = self.expr_to_term(right)?;
842
843                let args = self.ctx.terms.alloc_slice([left_term, right_term]);
844                Some(self.ctx.exprs.alloc(LogicExpr::Predicate { name: pred_sym, args, world: None }))
845            }
846            _ => None,
847        }
848    }
849
850    /// Converts an imperative Expr to a logic Term.
851    fn expr_to_term(&mut self, expr: &Expr<'a>) -> Option<Term<'a>> {
852        match expr {
853            Expr::Identifier(sym) => Some(Term::Variable(*sym)),
854            Expr::Literal(lit) => {
855                match lit {
856                    Literal::Number(n) => Some(Term::Value {
857                        kind: NumberKind::Integer(*n),
858                        unit: None,
859                        dimension: None,
860                    }),
861                    Literal::Boolean(b) => {
862                        let sym = self.interner.intern(if *b { "true" } else { "false" });
863                        Some(Term::Constant(sym))
864                    }
865                    _ => None, // Text, Nothing not supported in predicates
866                }
867            }
868            _ => None,
869        }
870    }
871
872    pub fn process_block_headers(&mut self) {
873        use crate::token::BlockType;
874
875        while self.current < self.tokens.len() {
876            if let TokenType::BlockHeader { block_type } = &self.tokens[self.current].kind {
877                self.mode = match block_type {
878                    BlockType::Main | BlockType::Function => ParserMode::Imperative,
879                    BlockType::Theorem | BlockType::Definition | BlockType::Define | BlockType::Proof |
880                    BlockType::Example | BlockType::Logic | BlockType::Note | BlockType::TypeDef |
881                    BlockType::Policy | BlockType::Requires | BlockType::Axiom | BlockType::Theory |
882                    BlockType::Hardware | BlockType::Property
883                    | BlockType::SuspectedTypo { .. } => ParserMode::Declarative,
884                    BlockType::No | BlockType::Tier => self.mode, // Annotation — keep current mode
885                };
886                self.current += 1;
887            } else {
888                break;
889            }
890        }
891    }
892
893    pub fn get_event_var(&mut self) -> Symbol {
894        self.discourse_event_var.unwrap_or_else(|| self.interner.intern("e"))
895    }
896
897    pub fn capture_event_template(&mut self, verb: Symbol, roles: &[(ThematicRole, Term<'a>)], modifiers: &[Symbol]) {
898        let non_agent_roles: Vec<_> = roles.iter()
899            .filter(|(role, _)| *role != ThematicRole::Agent)
900            .cloned()
901            .collect();
902        let agent = roles.iter()
903            .find(|(role, _)| *role == ThematicRole::Agent)
904            .map(|(_, t)| t.clone());
905        self.last_event_template = Some(EventTemplate {
906            verb,
907            agent,
908            non_agent_roles,
909            modifiers: modifiers.to_vec(),
910        });
911    }
912
913    /// A locative/circumstance relative clause headed by "where" ("the episode WHERE
914    /// the survivor brought the knife"). The head `gap_var` is the LOCATION of the
915    /// clause's event: parse the embedded subject + its full VP, then conjoin
916    /// In(event, gap_var). The event var is the stable discourse "e" the VP builder
917    /// uses, so In(e, gap) sits alongside the ∃e(...) like other event PPs.
918    /// A possessive relative clause headed by "whose" ("the town WHOSE mayor is Kevin
919    /// King", "the team WHOSE final score was 739"). The possessed NP belongs to the
920    /// head `gap_var`; introduce it as a fresh entity m with Noun(m) ∧ Possesses(gap,
921    /// m), then predicate the clause's VP over m, all under ∃m.
922    pub(super) fn parse_whose_relative(&mut self, gap_var: Symbol) -> ParseResult<&'a LogicExpr<'a>> {
923        self.advance(); // "whose"
924        let possessed = self.parse_noun_phrase(false)?;
925        let m = self.next_var_name();
926        let noun_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
927            name: possessed.noun,
928            args: self.ctx.terms.alloc_slice([Term::Variable(m)]),
929            world: None,
930        });
931        let possesses = self.interner.intern("Possesses");
932        let poss_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
933            name: possesses,
934            args: self
935                .ctx
936                .terms
937                .alloc_slice([Term::Variable(gap_var), Term::Variable(m)]),
938            world: None,
939        });
940        let mut body = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
941            left: noun_pred,
942            op: TokenType::And,
943            right: poss_pred,
944        });
945        for &adj in possessed.adjectives {
946            let adj_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
947                name: adj,
948                args: self.ctx.terms.alloc_slice([Term::Variable(m)]),
949                world: None,
950            });
951            body = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
952                left: body,
953                op: TokenType::And,
954                right: adj_pred,
955            });
956        }
957        let vp = self.parse_predicate_with_subject_as_var(m)?;
958        body = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
959            left: body,
960            op: TokenType::And,
961            right: vp,
962        });
963        Ok(self.ctx.exprs.alloc(LogicExpr::Quantifier {
964            kind: QuantifierKind::Existential,
965            variable: m,
966            body,
967            island_id: self.current_island,
968        }))
969    }
970
971    pub(super) fn parse_where_relative(&mut self, gap_var: Symbol) -> ParseResult<&'a LogicExpr<'a>> {
972        self.advance(); // "where"
973        // The embedded clause's own subject: a pronoun ("they"), a definite NP ("the
974        // survivor"), or a proper name. A pronoun is not parsed by parse_noun_phrase,
975        // so take its (capitalized) lexeme as the agent constant.
976        let subj_sym = if self.check_pronoun() {
977            let lex = self.interner.resolve(self.peek().lexeme).to_string();
978            self.advance();
979            let mut chars = lex.chars();
980            let cap = match chars.next() {
981                Some(f) => f.to_uppercase().collect::<String>() + chars.as_str(),
982                None => lex,
983            };
984            self.interner.intern(&cap)
985        } else {
986            self.parse_noun_phrase(true)?.noun
987        };
988        // Bound the embedded VP at the relative-clause boundary so it does not swallow
989        // an OUTER list coordinator ("the town where they met, and the city" / "…, and
990        // Carol"): a comma, or an "and"/"or" that introduces a NEW NP (article / proper
991        // name / quantifier), as opposed to a genuine VP coordinator ("where they met
992        // and left"). Temporarily retag that token as a clause terminator while the VP
993        // parses (the same boundary trick the of-pair path uses), then restore it.
994        let boundary = {
995            let mut found = None;
996            let mut k = self.current;
997            while let Some(tok) = self.tokens.get(k) {
998                match &tok.kind {
999                    TokenType::Comma => {
1000                        found = Some(k);
1001                        break;
1002                    }
1003                    TokenType::And | TokenType::Or
1004                        if matches!(
1005                            self.tokens.get(k + 1).map(|t| &t.kind),
1006                            Some(TokenType::Article(_))
1007                                | Some(TokenType::ProperName(_))
1008                                | Some(TokenType::All)
1009                                | Some(TokenType::Some)
1010                                | Some(TokenType::Any)
1011                                | Some(TokenType::No)
1012                                | Some(TokenType::Most)
1013                        ) =>
1014                    {
1015                        found = Some(k);
1016                        break;
1017                    }
1018                    TokenType::Period
1019                    | TokenType::EOF
1020                    | TokenType::Is
1021                    | TokenType::Are
1022                    | TokenType::Was
1023                    | TokenType::Were => break,
1024                    _ => k += 1,
1025                }
1026            }
1027            found
1028        };
1029        let saved_boundary = boundary.map(|i| (i, self.tokens[i].clone()));
1030        if let Some(i) = boundary {
1031            let span = self.tokens[i].span;
1032            self.tokens[i] = crate::token::Token::new(
1033                TokenType::Period,
1034                self.interner.intern("."),
1035                span,
1036            );
1037        }
1038        let inner_result = self.parse_predicate_with_subject(subj_sym);
1039        if let Some((i, tok)) = saved_boundary {
1040            self.tokens[i] = tok;
1041        }
1042        let inner = inner_result?;
1043        let ev = self.get_event_var();
1044        let loc = self.interner.intern("In");
1045        let loc_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
1046            name: loc,
1047            args: self
1048                .ctx
1049                .terms
1050                .alloc_slice([Term::Variable(ev), Term::Variable(gap_var)]),
1051            world: None,
1052        });
1053        Ok(self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1054            left: inner,
1055            op: TokenType::And,
1056            right: loc_pred,
1057        }))
1058    }
1059
1060    fn parse_embedded_wh_clause(&mut self) -> ParseResult<&'a LogicExpr<'a>> {
1061        // Parse embedded question body: "who runs", "what John ate"
1062        let var_name = self.interner.intern("x");
1063        let var_term = Term::Variable(var_name);
1064
1065        if self.check_verb() {
1066            // "who runs" pattern
1067            let verb = self.consume_verb();
1068            let body = self.ctx.exprs.alloc(LogicExpr::Predicate {
1069                name: verb,
1070                args: self.ctx.terms.alloc_slice([var_term]),
1071                world: None,
1072            });
1073            return Ok(body);
1074        }
1075
1076        if self.check_content_word() || self.check_article() {
1077            // "what John ate" pattern
1078            let subject = self.parse_noun_phrase(true)?;
1079            if self.check_verb() {
1080                let verb = self.consume_verb();
1081                let body = self.ctx.exprs.alloc(LogicExpr::Predicate {
1082                    name: verb,
1083                    args: self.ctx.terms.alloc_slice([
1084                        Term::Constant(subject.noun),
1085                        var_term,
1086                    ]),
1087                    world: None,
1088                });
1089                return Ok(body);
1090            }
1091        }
1092
1093        // Fallback: just the wh-variable
1094        Ok(self.ctx.exprs.alloc(LogicExpr::Atom(var_name)))
1095    }
1096
1097    pub fn set_pp_attachment_mode(&mut self, attach_to_noun: bool) {
1098        self.pp_attach_to_noun = attach_to_noun;
1099    }
1100
1101    pub fn set_noun_priority_mode(&mut self, mode: bool) {
1102        self.noun_priority_mode = mode;
1103    }
1104
1105    pub fn set_collective_mode(&mut self, mode: bool) {
1106        self.collective_mode = mode;
1107    }
1108
1109    /// Force the distributive (per-member) reading of mixed verbs with
1110    /// definite plurals — the readings enumerator's counterpart to the
1111    /// default collective reading.
1112    pub fn set_distributive_marker(&mut self, on: bool) {
1113        self.distributive_marker = on;
1114    }
1115
1116    /// Enable pragmatic enrichment (scalar implicatures, degree standards)
1117    /// for whole-program parses — the defeasible theorem door needs the
1118    /// implicature channel that the literal parse omits.
1119    pub fn set_pragmatic_mode(&mut self, on: bool) {
1120        self.pragmatic = on;
1121    }
1122
1123    pub fn set_event_reading_mode(&mut self, mode: bool) {
1124        self.event_reading_mode = mode;
1125    }
1126
1127    pub fn set_negative_scope_mode(&mut self, mode: NegativeScopeMode) {
1128        self.negative_scope_mode = mode;
1129    }
1130
1131    pub fn set_modal_preference(&mut self, pref: ModalPreference) {
1132        self.modal_preference = pref;
1133    }
1134
1135    fn checkpoint(&self) -> ParserCheckpoint {
1136        ParserCheckpoint {
1137            pos: self.current,
1138            var_counter: self.var_counter,
1139            bindings_len: self.donkey_bindings.len(),
1140            island: self.current_island,
1141            time: self.pending_time,
1142            negative_depth: self.negative_depth,
1143        }
1144    }
1145
1146    fn restore(&mut self, cp: ParserCheckpoint) {
1147        self.current = cp.pos;
1148        self.var_counter = cp.var_counter;
1149        self.donkey_bindings.truncate(cp.bindings_len);
1150        self.current_island = cp.island;
1151        self.pending_time = cp.time;
1152        self.negative_depth = cp.negative_depth;
1153    }
1154
1155    fn is_negative_context(&self) -> bool {
1156        self.negative_depth % 2 == 1
1157    }
1158
1159    pub fn guard(&mut self) -> ParserGuard<'_, 'a, 'ctx, 'int> {
1160        ParserGuard {
1161            checkpoint: self.checkpoint(),
1162            parser: self,
1163            committed: false,
1164        }
1165    }
1166
1167    pub(super) fn try_parse<F, T>(&mut self, op: F) -> Option<T>
1168    where
1169        F: FnOnce(&mut Self) -> ParseResult<T>,
1170    {
1171        let cp = self.checkpoint();
1172        match op(self) {
1173            Ok(res) => Some(res),
1174            Err(_) => {
1175                self.restore(cp);
1176                None
1177            }
1178        }
1179    }
1180
1181    fn resolve_pronoun(&mut self, gender: Gender, number: Number) -> ParseResult<ResolvedPronoun> {
1182        // MODAL BARRIER: In discourse mode, try telescope FIRST if prior sentence was modal.
1183        // This ensures the modal barrier check runs before we search the swapped DRS.
1184        // The DRS contains referents from all prior sentences (merged via swap), but
1185        // telescope has modal filtering to block hypothetical entities from reality.
1186        if self.world_state.in_discourse_mode() && self.world_state.has_prior_modal_context() {
1187            // Only use telescope for cross-sentence reference when prior was modal
1188            // Telescope has modal barrier: won't return modal candidates unless we're in modal context
1189            if let Some(candidate) = self.world_state.resolve_via_telescope(gender) {
1190                return Ok(ResolvedPronoun::Variable(candidate.variable));
1191            }
1192            // Modal barrier blocked telescope resolution - pronoun can't access hypothetical entities
1193            // from the prior modal sentence. Check if there are ANY telescope candidates that
1194            // were blocked due to modal scope. If so, this is a modal barrier error.
1195            let blocked_candidates: Vec<_> = self.world_state.telescope_candidates()
1196                .iter()
1197                .filter(|c| c.in_modal_scope)
1198                .collect();
1199            if !blocked_candidates.is_empty() {
1200                // Before returning error, look ahead for modal subordinating verbs (would, could, etc.)
1201                // If we see one, this sentence is also modal and can access the hypothetical entity.
1202                let has_upcoming_modal = self.has_modal_subordination_ahead();
1203                if has_upcoming_modal {
1204                    // Modal subordination detected - allow access to the first matching candidate
1205                    if let Some(candidate) = blocked_candidates.into_iter().find(|c| {
1206                        c.gender == gender || gender == Gender::Unknown || c.gender == Gender::Unknown
1207                    }) {
1208                        return Ok(ResolvedPronoun::Variable(candidate.variable));
1209                    }
1210                }
1211                // There were candidates but they're all in modal scope - modal barrier blocks access
1212                return Err(ParseError {
1213                    kind: ParseErrorKind::ScopeViolation(
1214                        "Cannot access hypothetical entity from reality. Use modal subordination (e.g., 'would') to continue a hypothetical context.".to_string()
1215                    ),
1216                    span: self.current_span(),
1217                });
1218            }
1219            // No modal candidates were blocked, continue to check for same-sentence referents
1220        }
1221
1222        // Try DRS resolution (scope-aware) for same-sentence referents
1223        let current_box = self.drs.current_box_index();
1224        match self.drs.resolve_pronoun(current_box, gender, number) {
1225            Ok(sym) => {
1226                // A rigid referent (proper name / deictic constant) denotes
1227                // the constant itself — "He has children." after "His
1228                // children…" keeps Him a constant, matching the premise.
1229                return Ok(if self.drs.is_rigid_referent(sym) {
1230                    ResolvedPronoun::Constant(sym)
1231                } else {
1232                    ResolvedPronoun::Variable(sym)
1233                });
1234            }
1235            Err(crate::drs::ScopeError::InaccessibleReferent { gender: g, reason, .. }) => {
1236                // Referent exists but is trapped in inaccessible scope.
1237                // In multi-sentence discourse, try telescoping first - the referent
1238                // from a previous sentence's conditional may be in telescope candidates.
1239                if self.world_state.in_discourse_mode() {
1240                    if let Some(candidate) = self.world_state.resolve_via_telescope(g) {
1241                        return Ok(ResolvedPronoun::Variable(candidate.variable));
1242                    }
1243                }
1244                // No telescope candidate found - this is a hard scope error
1245                return Err(ParseError {
1246                    kind: ParseErrorKind::ScopeViolation(reason),
1247                    span: self.current_span(),
1248                });
1249            }
1250            Err(crate::drs::ScopeError::NoMatchingReferent { gender: g, number: n }) => {
1251                // Try telescoping across sentence boundaries (if not already tried above)
1252                if !self.world_state.has_prior_modal_context() {
1253                    if let Some(candidate) = self.world_state.resolve_via_telescope(g) {
1254                        return Ok(ResolvedPronoun::Variable(candidate.variable));
1255                    }
1256                }
1257
1258                // In discourse mode (multi-sentence context), unresolved pronouns are an error
1259                if self.world_state.in_discourse_mode() {
1260                    return Err(ParseError {
1261                        kind: ParseErrorKind::UnresolvedPronoun {
1262                            gender: g,
1263                            number: n,
1264                        },
1265                        span: self.current_span(),
1266                    });
1267                }
1268
1269                // No prior referent - introduce deictic referent (pointing to someone in the world)
1270                // This handles sentences like "She told him a story" without prior discourse
1271                let deictic_name = match (g, n) {
1272                    (Gender::Male, Number::Singular) => "Him",
1273                    (Gender::Female, Number::Singular) => "Her",
1274                    (Gender::Neuter, Number::Singular) => "It",
1275                    (Gender::Male, Number::Plural) | (Gender::Female, Number::Plural) => "Them",
1276                    (Gender::Neuter, Number::Plural) => "Them",
1277                    (Gender::Unknown, _) => "Someone",
1278                };
1279                let sym = self.interner.intern(deictic_name);
1280                // Introduce the deictic as a RIGID referent (it denotes a
1281                // fixed individual in the world) so later pronouns resolving
1282                // to it also yield the constant.
1283                self.drs.introduce_referent_with_source(
1284                    sym,
1285                    sym,
1286                    g,
1287                    n,
1288                    crate::drs::ReferentSource::ProperName,
1289                );
1290                return Ok(ResolvedPronoun::Constant(sym));
1291            }
1292        }
1293    }
1294
1295    fn resolve_donkey_pronoun(&mut self, gender: Gender) -> Option<Symbol> {
1296        for (noun_class, var_name, used, _wide_neg) in self.donkey_bindings.iter_mut().rev() {
1297            let noun_str = self.interner.resolve(*noun_class);
1298            let noun_gender = Self::infer_noun_gender(noun_str);
1299            if noun_gender == gender || gender == Gender::Neuter || noun_gender == Gender::Unknown {
1300                *used = true; // Mark as used by a pronoun (donkey anaphor)
1301                return Some(*var_name);
1302            }
1303        }
1304        None
1305    }
1306
1307    fn infer_noun_gender(noun: &str) -> Gender {
1308        let lower = noun.to_lowercase();
1309        if lexicon::is_female_noun(&lower) {
1310            Gender::Female
1311        } else if lexicon::is_male_noun(&lower) {
1312            Gender::Male
1313        } else if lexicon::is_neuter_noun(&lower) {
1314            Gender::Neuter
1315        } else {
1316            Gender::Unknown
1317        }
1318    }
1319
1320    fn is_plural_noun(noun: &str) -> bool {
1321        let lower = noun.to_lowercase();
1322        // Proper names like "Socrates", "James", "Chris" end in 's' but aren't plurals
1323        if lexicon::is_proper_name(&lower) {
1324            return false;
1325        }
1326        if lexicon::is_irregular_plural(&lower) {
1327            return true;
1328        }
1329        lower.ends_with('s') && !lower.ends_with("ss") && lower.len() > 2
1330    }
1331
1332    fn singularize_noun(noun: &str) -> String {
1333        let lower = noun.to_lowercase();
1334        if let Some(singular) = lexicon::singularize(&lower) {
1335            return singular.to_string();
1336        }
1337        if lower.ends_with('s') && !lower.ends_with("ss") && lower.len() > 2 {
1338            let base = &lower[..lower.len() - 1];
1339            let mut chars: Vec<char> = base.chars().collect();
1340            if !chars.is_empty() {
1341                chars[0] = chars[0].to_uppercase().next().unwrap();
1342            }
1343            return chars.into_iter().collect();
1344        }
1345        let mut chars: Vec<char> = lower.chars().collect();
1346        if !chars.is_empty() {
1347            chars[0] = chars[0].to_uppercase().next().unwrap();
1348        }
1349        chars.into_iter().collect()
1350    }
1351
1352    fn infer_gender(name: &str) -> Gender {
1353        let lower = name.to_lowercase();
1354        if lexicon::is_male_name(&lower) {
1355            Gender::Male
1356        } else if lexicon::is_female_name(&lower) {
1357            Gender::Female
1358        } else {
1359            Gender::Unknown
1360        }
1361    }
1362
1363
1364    /// Builds the agent term for a subject, applying metonymic coercion (§8.6):
1365    /// a place that conventionally denotes an institution ("the White House") is
1366    /// coerced to that institution when it acts — `GovernmentOf(white_house)`.
1367    /// Strengthens a weak scalar quantifier with its scalar implicature (§8.7): on the
1368    /// Horn scale ⟨some, most, many … all⟩ a weak term implicates the negation of its
1369    /// stronger "all" alternative. Given `Q x(R(x) ∧ S(x))` with `Q ∈ {∃, Most, Many}`,
1370    /// produces `Q x(R(x) ∧ S(x)) +> ¬∀x(R(x) → S(x))`. Returns `None` for a strong term
1371    /// ("all") or any non-scalar shape. (Open-class scalars like warm/hot would extend
1372    /// this through a lexicon Horn-scale; the closed-class quantifier scale is here.)
1373    fn scalar_implicature(&mut self, expr: &'a LogicExpr<'a>) -> Option<&'a LogicExpr<'a>> {
1374        if let LogicExpr::Quantifier {
1375            kind: QuantifierKind::Existential | QuantifierKind::Most | QuantifierKind::Many,
1376            variable,
1377            body,
1378            island_id,
1379        } = expr
1380        {
1381            // The "all" alternative: restriction implies scope, universally.
1382            let alt_body = if let LogicExpr::BinaryOp {
1383                left,
1384                op: TokenType::And,
1385                right,
1386            } = body
1387            {
1388                self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1389                    left,
1390                    op: TokenType::Implies,
1391                    right,
1392                })
1393            } else {
1394                body
1395            };
1396            let universal = self.ctx.exprs.alloc(LogicExpr::Quantifier {
1397                kind: QuantifierKind::Universal,
1398                variable: *variable,
1399                body: alt_body,
1400                island_id: *island_id,
1401            });
1402            let negated = self.ctx.exprs.alloc(LogicExpr::UnaryOp {
1403                op: TokenType::Not,
1404                operand: universal,
1405            });
1406            Some(self.ctx.exprs.alloc(LogicExpr::Implicature {
1407                assertion: expr,
1408                implicature: negated,
1409            }))
1410        } else {
1411            None
1412        }
1413    }
1414
1415    fn coerce_agent(&mut self, np: &crate::ast::NounPhrase<'a>) -> Term<'a> {
1416        let base = self.noun_phrase_to_term(np);
1417        if crate::lexicon::is_institution_metonym(&self.interner.resolve(np.noun).to_lowercase()) {
1418            let gov = self.interner.intern("GovernmentOf");
1419            Term::Function(gov, self.ctx.terms.alloc_slice([base]))
1420        } else {
1421            base
1422        }
1423    }
1424
1425    fn next_var_name(&mut self) -> Symbol {
1426        const VARS: &[&str] = &["x", "y", "z", "w", "v", "u"];
1427        let idx = self.var_counter;
1428        self.var_counter += 1;
1429        if idx < VARS.len() {
1430            self.interner.intern(VARS[idx])
1431        } else {
1432            let name = format!("x{}", idx - VARS.len() + 1);
1433            self.interner.intern(&name)
1434        }
1435    }
1436
1437    /// Parse an INTEGER numeral lexeme EXACTLY: digit separators
1438    /// (`1_000_000`), radix literals (`0xff` / `0b1010` / `0o755`), and a
1439    /// HARD error when the value cannot fit an Int — never a silent 0.
1440    /// (BigInt literals ride the numeric-tower work; until then an overlong
1441    /// literal fails loudly.)
1442    fn parse_i64_numeral(&self, num_str: &str) -> ParseResult<i64> {
1443        self.parse_i64_numeral_signed(num_str, false)
1444    }
1445
1446    /// The signed core: `negative` attaches the sign to the DIGITS before
1447    /// parsing, so `-9223372036854775808` (i64::MIN, whose magnitude alone
1448    /// overflows by one) parses exactly.
1449    fn parse_i64_numeral_signed(&self, num_str: &str, negative: bool) -> ParseResult<i64> {
1450        let clean = num_str.replace('_', "");
1451        let sign = if negative { "-" } else { "" };
1452        // A RADIX literal (`0x…`/`0b…`/`0o…`) denotes a BIT PATTERN, so it may use the FULL unsigned
1453        // 64-bit range: `0xFFFFFFFFFFFFFFFF` is the all-ones `Word64`, and a SHA-512 round constant like
1454        // `0xb5c0fbcfec4d3b2f` (≥ 2⁶³) is a legitimate 64-bit value. When the signed parse overflows,
1455        // an unsigned value in (i64::MAX, u64::MAX] reinterprets as its two's-complement i64 bits —
1456        // exactly what `word64(…)`/`word32(…)` read back. A DECIMAL literal is magnitude, so it stays
1457        // i64-bound (BigInt magnitudes ride the numeric-tower work) and still fails loudly.
1458        let radix = |digits: &str, base: u32| -> Result<i64, ()> {
1459            if let Ok(v) = i64::from_str_radix(&format!("{}{}", sign, digits), base) {
1460                return Ok(v);
1461            }
1462            if !negative {
1463                if let Ok(u) = u64::from_str_radix(digits, base) {
1464                    return Ok(u as i64);
1465                }
1466            }
1467            Err(())
1468        };
1469        let parsed = if let Some(hex) = clean.strip_prefix("0x").or_else(|| clean.strip_prefix("0X")) {
1470            radix(hex, 16)
1471        } else if let Some(bin) = clean.strip_prefix("0b").or_else(|| clean.strip_prefix("0B")) {
1472            radix(bin, 2)
1473        } else if let Some(oct) = clean.strip_prefix("0o").or_else(|| clean.strip_prefix("0O")) {
1474            radix(oct, 8)
1475        } else {
1476            format!("{}{}", sign, clean).parse::<i64>().map_err(|_| ())
1477        };
1478        parsed.map_err(|_| ParseError {
1479            kind: ParseErrorKind::ExpectedKeyword {
1480                keyword: format!(
1481                    "a representable Int literal — `{}` does not fit the Int range (±9223372036854775807)",
1482                    num_str
1483                ),
1484            },
1485            span: self.current_span(),
1486        })
1487    }
1488
1489    /// True when a numeral lexeme is a RADIX literal (`0x…`/`0b…`/`0o…`) —
1490    /// checked BEFORE the float sniff, because `0x1E` contains an `E` that
1491    /// is a hex digit, not scientific notation.
1492    fn is_radix_numeral(num_str: &str) -> bool {
1493        let s = num_str.strip_prefix('_').unwrap_or(num_str);
1494        s.len() > 2
1495            && s.starts_with('0')
1496            && matches!(s.as_bytes()[1], b'x' | b'X' | b'b' | b'B' | b'o' | b'O')
1497    }
1498
1499    /// Parse the NUMERAL after `item`/`items` into an index literal. An
1500    /// integer numeral is guarded against the 1-based footgun (`item 0` is a
1501    /// clean compile error); a NON-integer numeral (a float map key like
1502    /// `item 1.0 of prices`) is a Float literal — never the silent
1503    /// `unwrap_or(0)` that used to turn it into a phantom `item 0`.
1504    fn parse_index_numeral(&mut self, sym: Symbol) -> ParseResult<&'a Expr<'a>> {
1505        let num_str = self.interner.resolve(sym).to_string();
1506        if let Ok(n) = self.parse_i64_numeral(&num_str) {
1507            if n == 0 {
1508                return Err(ParseError {
1509                    kind: ParseErrorKind::ZeroIndex,
1510                    span: self.current_span(),
1511                });
1512            }
1513            return Ok(self
1514                .ctx
1515                .alloc_imperative_expr(Expr::Literal(crate::ast::Literal::Number(n))));
1516        }
1517        if let Ok(f) = num_str.parse::<f64>() {
1518            return Ok(self
1519                .ctx
1520                .alloc_imperative_expr(Expr::Literal(crate::ast::Literal::Float(f))));
1521        }
1522        Err(ParseError {
1523            kind: ParseErrorKind::ExpectedKeyword {
1524                keyword: format!("a numeric index (got '{}')", num_str),
1525            },
1526            span: self.current_span(),
1527        })
1528    }
1529
1530    /// A fresh, user-uncollidable temporary for parser desugars ([`Stmt::Splice`]
1531    /// output). Shares `var_counter` so `ParserGuard` checkpoints roll it back.
1532    fn fresh_desugar_temp(&mut self, tag: &str) -> Symbol {
1533        let idx = self.var_counter;
1534        self.var_counter += 1;
1535        self.interner.intern(&format!("__{}_{}", tag, idx))
1536    }
1537
1538    /// Lower a write into a NESTED index place — `Set item j of (item i of
1539    /// grid) to v` — into read → element write → write-back:
1540    ///
1541    /// ```text
1542    /// Let __place_i be j.  Let __place_v be v.  Let __place_k be i.
1543    /// Let __place_t be item __place_k of grid.
1544    /// Set item __place_i of __place_t to __place_v.
1545    /// Set item __place_k of grid to __place_t.        (recursive for depth)
1546    /// ```
1547    ///
1548    /// The element write targets a plain BINDING, so each engine's own
1549    /// copy-on-write machinery preserves value semantics (an alias of the
1550    /// inner row never observes the write — `diff_let_binding_isolates`); the
1551    /// write-back stores the possibly-fresh inner collection into the outer
1552    /// place. Temp order (index, value, collection) matches the engines'
1553    /// evaluation order for a flat `SetIndex`.
1554    fn desugar_place_set_index(
1555        &mut self,
1556        collection: &'a Expr<'a>,
1557        index: &'a Expr<'a>,
1558        value: &'a Expr<'a>,
1559    ) -> Stmt<'a> {
1560        let (base, key) = match collection {
1561            Expr::Index { collection: base, index: key } => (*base, *key),
1562            _ => return Stmt::SetIndex { collection, index, value },
1563        };
1564        let t_idx = self.fresh_desugar_temp("place_i");
1565        let t_val = self.fresh_desugar_temp("place_v");
1566        let t_key = self.fresh_desugar_temp("place_k");
1567        let t_inner = self.fresh_desugar_temp("place_t");
1568        let idx_ref = self.ctx.alloc_imperative_expr(Expr::Identifier(t_idx));
1569        let val_ref = self.ctx.alloc_imperative_expr(Expr::Identifier(t_val));
1570        let key_ref = self.ctx.alloc_imperative_expr(Expr::Identifier(t_key));
1571        let inner_ref = self.ctx.alloc_imperative_expr(Expr::Identifier(t_inner));
1572        let read = self.ctx.alloc_imperative_expr(Expr::Index { collection: base, index: key_ref });
1573        let write_back = self.desugar_place_set_index(base, key_ref, inner_ref);
1574        let body = [
1575            Stmt::Let { var: t_idx, ty: None, value: index, mutable: false },
1576            Stmt::Let { var: t_val, ty: None, value, mutable: false },
1577            Stmt::Let { var: t_key, ty: None, value: key, mutable: false },
1578            Stmt::Let { var: t_inner, ty: None, value: read, mutable: true },
1579            Stmt::SetIndex { collection: inner_ref, index: idx_ref, value: val_ref },
1580            write_back,
1581        ];
1582        let body = self.ctx.stmts.expect("imperative arenas not initialized")
1583            .alloc_slice(body.into_iter());
1584        Stmt::Splice { body }
1585    }
1586
1587    /// Lower `Push v to <indexed place>` the same way (read → push into the
1588    /// binding → write-back). See [`Self::desugar_place_set_index`].
1589    fn desugar_place_push(&mut self, value: &'a Expr<'a>, collection: &'a Expr<'a>) -> Stmt<'a> {
1590        let (base, key) = match collection {
1591            Expr::Index { collection: base, index: key } => (*base, *key),
1592            _ => return Stmt::Push { value, collection },
1593        };
1594        let t_val = self.fresh_desugar_temp("place_v");
1595        let t_key = self.fresh_desugar_temp("place_k");
1596        let t_inner = self.fresh_desugar_temp("place_t");
1597        let val_ref = self.ctx.alloc_imperative_expr(Expr::Identifier(t_val));
1598        let key_ref = self.ctx.alloc_imperative_expr(Expr::Identifier(t_key));
1599        let inner_ref = self.ctx.alloc_imperative_expr(Expr::Identifier(t_inner));
1600        let read = self.ctx.alloc_imperative_expr(Expr::Index { collection: base, index: key_ref });
1601        let write_back = self.desugar_place_set_index(base, key_ref, inner_ref);
1602        let body = [
1603            Stmt::Let { var: t_val, ty: None, value, mutable: false },
1604            Stmt::Let { var: t_key, ty: None, value: key, mutable: false },
1605            Stmt::Let { var: t_inner, ty: None, value: read, mutable: true },
1606            Stmt::Push { value: val_ref, collection: inner_ref },
1607            write_back,
1608        ];
1609        let body = self.ctx.stmts.expect("imperative arenas not initialized")
1610            .alloc_slice(body.into_iter());
1611        Stmt::Splice { body }
1612    }
1613
1614    /// Parses the token stream into a logical expression.
1615    ///
1616    /// This is the main entry point for declarative/FOL parsing. It handles
1617    /// multi-sentence inputs by conjoining them with logical AND, and processes
1618    /// various sentence types including declaratives, questions, and imperatives.
1619    ///
1620    /// # Returns
1621    ///
1622    /// An arena-allocated [`LogicExpr`] representing the parsed input, or
1623    /// a [`ParseError`] with source location and Socratic explanation.
1624    ///
1625    /// # Discourse State
1626    ///
1627    /// The parser maintains discourse state across sentences, enabling
1628    /// anaphora resolution ("he", "she", "they" refer to prior entities)
1629    /// and temporal coherence (tense interpretation relative to reference time).
1630    pub fn parse(&mut self) -> ParseResult<&'a LogicExpr<'a>> {
1631        let mut result = self.parse_prefix()?;
1632
1633        // Postposed "while": "Y while X." mirrors the fronted "While X, Y."
1634        // — the condition restricts the main clause. A bare-predicate
1635        // condition ("… while idle") predicates over the main clause's
1636        // agent ("the transmitter shall not send data while [it is] idle").
1637        while self.check(&TokenType::While) {
1638            self.advance();
1639            let condition = if matches!(self.peek().kind, TokenType::Adjective(_))
1640                && matches!(
1641                    self.tokens.get(self.current + 1).map(|t| &t.kind),
1642                    Some(TokenType::Period) | Some(TokenType::EOF) | None
1643                ) {
1644                let adj = match self.advance().kind {
1645                    TokenType::Adjective(a) => a,
1646                    _ => unreachable!("guarded by the match above"),
1647                };
1648                let subject = Self::find_agent_term(result)
1649                    .unwrap_or(Term::Constant(self.interner.intern("System")));
1650                &*self.ctx.exprs.alloc(LogicExpr::Predicate {
1651                    name: adj,
1652                    args: self.ctx.terms.alloc_slice([subject]),
1653                    world: None,
1654                })
1655            } else {
1656                self.parse_sentence()?
1657            };
1658            if self.check(&TokenType::Period) {
1659                self.advance();
1660            }
1661            result = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1662                left: condition,
1663                op: TokenType::Implies,
1664                right: result,
1665            });
1666        }
1667
1668        // Postposed "at any time": a universal temporal adverbial — the
1669        // clause is an invariant ("At most one grant is asserted at any
1670        // time." → G(…)).
1671        let at_any_time_from = if matches!(&self.peek().kind, TokenType::Preposition(p) if self.interner.resolve(*p).eq_ignore_ascii_case("at"))
1672            && matches!(
1673                self.tokens.get(self.current + 1).map(|t| &t.kind),
1674                Some(TokenType::Any)
1675            )
1676            && self
1677                .tokens
1678                .get(self.current + 2)
1679                .map(|t| {
1680                    self.interner
1681                        .resolve(t.lexeme)
1682                        .eq_ignore_ascii_case("time")
1683                })
1684                .unwrap_or(false)
1685        {
1686            // "… at any time."
1687            Some(3)
1688        } else if matches!(self.peek().kind, TokenType::Any)
1689            && self
1690                .tokens
1691                .get(self.current + 1)
1692                .map(|t| {
1693                    self.interner
1694                        .resolve(t.lexeme)
1695                        .eq_ignore_ascii_case("time")
1696                })
1697                .unwrap_or(false)
1698        {
1699            // The clause parse consumed the bare "at" as a dangling PP head;
1700            // "any time" remains.
1701            Some(2)
1702        } else {
1703            None
1704        };
1705        if let Some(n) = at_any_time_from {
1706            for _ in 0..n {
1707                self.advance();
1708            }
1709            if self.check(&TokenType::Period) {
1710                self.advance();
1711            }
1712            result = self.ctx.exprs.alloc(LogicExpr::Temporal {
1713                operator: TemporalOperator::Always,
1714                body: result,
1715            });
1716        }
1717
1718        // A parse that stops mid-sentence is not a parse: accepting it would
1719        // silently drop the remainder's meaning.
1720        if !self.is_at_end() {
1721            return Err(ParseError {
1722                kind: crate::error::ParseErrorKind::TrailingTokens {
1723                    found: self.peek().kind.clone(),
1724                },
1725                span: self.current_span(),
1726            });
1727        }
1728
1729        Ok(result)
1730    }
1731
1732    /// The Agent term of the first event in an expression (used to supply
1733    /// the understood subject of a bare postposed condition).
1734    fn find_agent_term(expr: &'a LogicExpr<'a>) -> Option<Term<'a>> {
1735        match expr {
1736            LogicExpr::NeoEvent(data) => data
1737                .roles
1738                .iter()
1739                .find(|(role, _)| matches!(role, crate::ast::ThematicRole::Agent))
1740                .map(|(_, term)| term.clone()),
1741            LogicExpr::Modal { operand, .. } => Self::find_agent_term(operand),
1742            LogicExpr::UnaryOp { operand, .. } => Self::find_agent_term(operand),
1743            LogicExpr::Temporal { body, .. } => Self::find_agent_term(body),
1744            LogicExpr::Aspectual { body, .. } => Self::find_agent_term(body),
1745            LogicExpr::Quantifier { body, .. } => Self::find_agent_term(body),
1746            LogicExpr::BinaryOp { left, right, .. } => {
1747                Self::find_agent_term(left).or_else(|| Self::find_agent_term(right))
1748            }
1749            _ => None,
1750        }
1751    }
1752
1753    /// Parses a maximal declarative prefix WITHOUT requiring end-of-input.
1754    ///
1755    /// For embedded sub-parses whose end is marked by a host-grammar keyword
1756    /// the caller checks itself ("Trust ⟨proposition⟩ because …"). Whole-input
1757    /// parsing must use [`Self::parse`], which rejects trailing tokens.
1758    pub(super) fn parse_prefix(&mut self) -> ParseResult<&'a LogicExpr<'a>> {
1759        let mut result = self.parse_sentence()?;
1760
1761        // Loop: handle ANY number of additional sentences (unlimited)
1762        // Handle all sentence terminators: . ? !
1763        while self.check(&TokenType::Period) || self.check(&TokenType::Exclamation) {
1764            self.advance(); // consume terminator
1765            if !self.is_at_end() {
1766                let next = self.parse_sentence()?;
1767                result = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
1768                    left: result,
1769                    op: TokenType::And,
1770                    right: next,
1771                });
1772            }
1773        }
1774
1775        Ok(result)
1776    }
1777
1778    /// Parses with pragmatic enrichment: the truth-conditional parse PLUS scalar
1779    /// implicature (§8.7). The literal `parse()` is unchanged; here a leading weak
1780    /// scalar "some" is strengthened to `∃… +> ¬∀…`. Used by the pragmatic compile
1781    /// path so the implicature appears as a separate labeled component.
1782    pub fn parse_pragmatic(&mut self) -> ParseResult<&'a LogicExpr<'a>> {
1783        self.pragmatic = true;
1784        // A leading weak scalar quantifier (some / most / many) is the Horn-scale trigger;
1785        // its stronger "all" alternative is implicated. "all" is not a trigger.
1786        let scalar_trigger = matches!(
1787            self.peek().kind,
1788            TokenType::Some | TokenType::Most | TokenType::Many
1789        );
1790        let result = self.parse()?;
1791        if scalar_trigger {
1792            if let Some(strengthened) = self.scalar_implicature(result) {
1793                return Ok(strengthened);
1794            }
1795        }
1796        Ok(result)
1797    }
1798
1799    /// Parses a LOGOS program into a list of statements.
1800    ///
1801    /// This is the main entry point for imperative/executable LOGOS code.
1802    /// It handles block structures (Definition, Policy, Procedure, Theorem),
1803    /// function definitions, type definitions, and executable statements.
1804    ///
1805    /// # Returns
1806    ///
1807    /// A vector of arena-allocated [`Stmt`] representing the program, or
1808    /// a [`ParseError`] with source location and Socratic explanation.
1809    ///
1810    /// # Block Types
1811    ///
1812    /// - **Definition**: Type definitions (structs, enums, generics)
1813    /// - **Policy**: Security predicates and capability rules
1814    /// - **Procedure**: Executable code blocks
1815    /// - **Theorem**: Logical propositions with proof strategies
1816    pub fn parse_program(&mut self) -> ParseResult<Vec<Stmt<'a>>> {
1817        let mut statements = Vec::new();
1818        self.stmt_spans.clear();
1819        let mut in_definition_block = false;
1820        let mut pending_opt_flags = OptimizationConfig::all_on();
1821
1822        // Check if we started in a Definition block (from process_block_headers)
1823        if self.mode == ParserMode::Declarative {
1824            // Check if the previous token was a Definition header
1825            // For now, assume Definition blocks should be skipped
1826            // We'll detect them by checking the content pattern
1827        }
1828
1829        while !self.is_at_end() {
1830            // Handle block headers
1831            if let Some(Token { kind: TokenType::BlockHeader { block_type }, .. }) = self.tokens.get(self.current) {
1832                match block_type {
1833                    BlockType::SuspectedTypo { found, suggestion } => {
1834                        let found = self.interner.resolve(*found).to_string();
1835                        let suggestion_str = self.interner.resolve(*suggestion);
1836                        let mut canonical: String = suggestion_str.to_string();
1837                        if let Some(c) = canonical.get_mut(0..1) {
1838                            c.make_ascii_uppercase();
1839                        }
1840                        return Err(ParseError {
1841                            kind: ParseErrorKind::ExpectedKeyword {
1842                                keyword: format!(
1843                                    "a known header — `## {}` looks like a typo of `## {}` (if it is a prose heading, pick a name further from the code headers)",
1844                                    found, canonical
1845                                ),
1846                            },
1847                            span: self.current_span(),
1848                        });
1849                    }
1850                    BlockType::Definition => {
1851                        in_definition_block = true;
1852                        self.mode = ParserMode::Declarative;
1853                        self.advance();
1854                        continue;
1855                    }
1856                    BlockType::Main => {
1857                        // A `## No <X>` before `## Main` (not attached to a function)
1858                        // is a FILE-LEVEL decorator: fold it into the program config.
1859                        self.program_opt_flags =
1860                            self.program_opt_flags.merged(&pending_opt_flags);
1861                        pending_opt_flags = OptimizationConfig::all_on();
1862                        in_definition_block = false;
1863                        self.mode = ParserMode::Imperative;
1864                        self.advance();
1865                        continue;
1866                    }
1867                    BlockType::No => {
1868                        // Optimization annotation: `## No <Keyword>` clears that
1869                        // optimization for the following function; `## No Optimize`
1870                        // clears all of them. Keywords come from the registry.
1871                        self.advance(); // consume the `## No` header
1872                        if let Some(token) = self.tokens.get(self.current) {
1873                            let word = self.interner.resolve(token.lexeme).to_lowercase();
1874                            if word == "optimize" {
1875                                pending_opt_flags = OptimizationConfig::all_off();
1876                            } else if let Some(opt) = by_keyword(&word) {
1877                                pending_opt_flags.set(opt, false);
1878                            }
1879                            // Unknown annotation — ignore silently.
1880                            self.advance(); // consume the flag word
1881                        }
1882                        while self.check(&TokenType::Newline) {
1883                            self.advance();
1884                        }
1885                        continue;
1886                    }
1887                    BlockType::Tier => {
1888                        // Tiered-optimizer pin: `## Tier <opt> <eager|t1|t2|t3|never>`
1889                        // overrides the hotness tier at which that optimization runs.
1890                        // Program-wide (governs the whole program's tiering ladder),
1891                        // collected directly — no per-function pending state.
1892                        self.advance(); // consume the `## Tier` header
1893                        let kw = self
1894                            .tokens
1895                            .get(self.current)
1896                            .map(|t| self.interner.resolve(t.lexeme).to_lowercase());
1897                        if kw.is_some() {
1898                            self.advance(); // consume the optimization keyword
1899                        }
1900                        let val = self
1901                            .tokens
1902                            .get(self.current)
1903                            .map(|t| self.interner.resolve(t.lexeme).to_lowercase());
1904                        if val.is_some() {
1905                            self.advance(); // consume the tier value
1906                        }
1907                        if let (Some(kw), Some(val)) = (kw, val) {
1908                            if let (Some(opt), Some(pin)) = (by_keyword(&kw), pin_from_str(&val)) {
1909                                self.program_tier_pins.set(opt, pin);
1910                            }
1911                            // Unknown opt / value — ignore silently.
1912                        }
1913                        while self.check(&TokenType::Newline) {
1914                            self.advance();
1915                        }
1916                        continue;
1917                    }
1918                    BlockType::Function => {
1919                        in_definition_block = false;
1920                        self.mode = ParserMode::Imperative;
1921                        let header_tok = self.current;
1922                        self.advance();
1923                        // Parse function definition, passing pending annotations
1924                        let flags = std::mem::take(&mut pending_opt_flags);
1925                        let func_def = self.parse_function_def_with_flags(flags)?;
1926                        statements.push(func_def);
1927                        self.record_stmt_span(header_tok);
1928                        continue;
1929                    }
1930                    BlockType::TypeDef => {
1931                        // Type definitions are handled by DiscoveryPass
1932                        // Skip content until next block header
1933                        self.advance();
1934                        self.skip_type_def_content();
1935                        continue;
1936                    }
1937                    BlockType::Policy => {
1938                        // Phase 50: Policy definitions are handled by DiscoveryPass
1939                        // Skip content until next block header
1940                        in_definition_block = true;  // Reuse flag to skip content
1941                        self.mode = ParserMode::Declarative;
1942                        self.advance();
1943                        continue;
1944                    }
1945                    BlockType::Hardware | BlockType::Property => {
1946                        // Hardware signal declarations and temporal property assertions
1947                        // Skip content until next block header
1948                        in_definition_block = true;
1949                        self.mode = ParserMode::Declarative;
1950                        self.advance();
1951                        continue;
1952                    }
1953                    BlockType::Theorem => {
1954                        // Phase 63: Parse theorem block
1955                        in_definition_block = false;
1956                        self.mode = ParserMode::Declarative;
1957                        let header_tok = self.current;
1958                        self.advance();
1959                        let theorem = self.parse_theorem_block()?;
1960                        statements.push(theorem);
1961                        self.record_stmt_span(header_tok);
1962                        continue;
1963                    }
1964                    BlockType::Define => {
1965                        // Rung 0a: vernacular-logic predicate definition.
1966                        in_definition_block = false;
1967                        self.mode = ParserMode::Declarative;
1968                        let header_tok = self.current;
1969                        self.advance();
1970                        let definition = self.parse_define_block()?;
1971                        statements.push(definition);
1972                        self.record_stmt_span(header_tok);
1973                        continue;
1974                    }
1975                    BlockType::Axiom => {
1976                        // A named formal first-order axiom (the seam for Tarski).
1977                        in_definition_block = false;
1978                        self.mode = ParserMode::Declarative;
1979                        let header_tok = self.current;
1980                        self.advance();
1981                        let axiom = self.parse_axiom_block()?;
1982                        statements.push(axiom);
1983                        self.record_stmt_span(header_tok);
1984                        continue;
1985                    }
1986                    BlockType::Theory => {
1987                        // A named development grouping formal axioms and theorems.
1988                        in_definition_block = false;
1989                        self.mode = ParserMode::Declarative;
1990                        let header_tok = self.current;
1991                        self.advance();
1992                        let theory = self.parse_theory_block()?;
1993                        statements.push(theory);
1994                        self.record_stmt_span(header_tok);
1995                        continue;
1996                    }
1997                    BlockType::Requires => {
1998                        in_definition_block = false;
1999                        self.mode = ParserMode::Declarative;
2000                        let header_tok = self.current;
2001                        self.advance();
2002                        let deps = self.parse_requires_block()?;
2003                        for _ in 0..deps.len() {
2004                            self.record_stmt_span(header_tok);
2005                        }
2006                        statements.extend(deps);
2007                        continue;
2008                    }
2009                    _ => {
2010                        // Skip other declarative blocks (Proof, Example, Logic, Note)
2011                        in_definition_block = false;
2012                        self.mode = ParserMode::Declarative;
2013                        self.advance();
2014                        continue;
2015                    }
2016                }
2017            }
2018
2019            // Skip Definition block content - handled by DiscoveryPass
2020            if in_definition_block {
2021                self.advance();
2022                continue;
2023            }
2024
2025            // Skip indent/dedent/newline tokens at program level
2026            if self.check(&TokenType::Indent) || self.check(&TokenType::Dedent) || self.check(&TokenType::Newline) {
2027                self.advance();
2028                continue;
2029            }
2030
2031            // In imperative mode, parse statements
2032            if self.mode == ParserMode::Imperative {
2033                // `Accept computed <Name> where <param> is an Int from <lo> to <hi>` is a
2034                // pure parse-time contract declaration — it records the named bounds and
2035                // emits NO runtime statement (a later `Run … under <Name>` inlines them).
2036                if self.check_word("Accept") {
2037                    self.parse_accept_contract()?;
2038                    continue;
2039                }
2040                let start_tok = self.current;
2041                let stmt = self.parse_statement()?;
2042                statements.push(stmt);
2043
2044                if self.check(&TokenType::Period) {
2045                    self.advance();
2046                }
2047                self.record_stmt_span(start_tok);
2048            } else {
2049                // In declarative mode (Theorem, etc.), skip for now
2050                self.advance();
2051            }
2052        }
2053
2054        // Any decorators not consumed by a function (trailing, or a library with
2055        // no `## Main`) are file-level.
2056        self.program_opt_flags = self.program_opt_flags.merged(&pending_opt_flags);
2057        debug_assert_eq!(
2058            self.stmt_spans.len(),
2059            statements.len(),
2060            "stmt_spans must stay aligned 1:1 with the statement list"
2061        );
2062
2063        // The AST depth gate: "parsed ⇒ bounded". Any tree this function
2064        // returns is safe for every downstream recursive walker — deep
2065        // chains built iteratively above are caught here; deep parse-time
2066        // recursion (parenthesis towers) is caught by `recursion_depth`.
2067        crate::ast_depth::validate_program_depth(
2068            &statements,
2069            self.stmt_spans.first().copied().unwrap_or_else(|| self.current_span()),
2070        )?;
2071
2072        Ok(statements)
2073    }
2074
2075    /// Enter one level of imperative parse recursion, erroring gracefully at
2076    /// the depth limit (the parse-time half of the AST depth gate). Pair
2077    /// every call with [`Parser::leave_recursion`].
2078    pub(super) fn enter_recursion(&mut self) -> ParseResult<()> {
2079        self.recursion_depth += 1;
2080        let limit = crate::ast_depth::max_parse_recursion();
2081        if self.recursion_depth > limit {
2082            self.recursion_depth -= 1;
2083            return Err(ParseError {
2084                kind: ParseErrorKind::AstTooDeep { depth: limit + 1, max_depth: limit },
2085                span: self.current_span(),
2086            });
2087        }
2088        Ok(())
2089    }
2090
2091    /// Leave one level of imperative parse recursion.
2092    pub(super) fn leave_recursion(&mut self) {
2093        self.recursion_depth = self.recursion_depth.saturating_sub(1);
2094    }
2095
2096    /// The program-wide optimization config from file-level `## No <X>` decorators
2097    /// (all-on minus the file-level disables). The compile entry combines this with
2098    /// `from_env` and per-function flags.
2099    pub fn program_opt_flags(&self) -> OptimizationConfig {
2100        self.program_opt_flags
2101    }
2102
2103    /// The program-wide tiered-optimizer pins from file-level `## Tier <opt> <value>`
2104    /// decorators (HOTSWAP §8). The run-path engine overlays these onto the env
2105    /// [`crate::optimization::HotswapConfig`] before optimizing.
2106    pub fn program_tier_pins(&self) -> PinSet {
2107        self.program_tier_pins
2108    }
2109
2110    /// Depth-guarded wrapper: every nested construct (If/While/Repeat/Zone
2111    /// bodies, however each parses its block) recurses through here, so one
2112    /// guard covers all statement nesting. Flat statement lists enter and
2113    /// leave pairwise — sequential code never accumulates depth.
2114    fn parse_statement(&mut self) -> ParseResult<Stmt<'a>> {
2115        self.enter_recursion()?;
2116        let result = self.parse_statement_inner();
2117        self.leave_recursion();
2118        result
2119    }
2120
2121    fn parse_statement_inner(&mut self) -> ParseResult<Stmt<'a>> {
2122        // Phase 32: Function definitions can appear inside Main block
2123        // Handle both TokenType::To and Preposition("to")
2124        if self.check(&TokenType::To) || self.check_preposition_is("to") {
2125            return self.parse_function_def();
2126        }
2127        if self.check(&TokenType::Let) {
2128            return self.parse_let_statement();
2129        }
2130        // Phase 23b: Equals-style assignment with explicit `mut` keyword
2131        // Syntax: `mut x = 5` or `mut x: Int = 5`
2132        if self.check(&TokenType::Mut) {
2133            return self.parse_equals_assignment(true);
2134        }
2135        // Phase 23b: Equals-style assignment (identifier = value)
2136        // Syntax: `x = 5` or `x: Int = 5`
2137        if self.peek_equals_assignment() {
2138            return self.parse_equals_assignment(false);
2139        }
2140        // Compound assignment: `x += e`, `grid[i] *= 2`, `p.count += 1`.
2141        if self.peek_compound_assignment() {
2142            return self.parse_compound_assignment();
2143        }
2144        if self.check(&TokenType::Set) {
2145            return self.parse_set_statement();
2146        }
2147        if self.check(&TokenType::Return) {
2148            return self.parse_return_statement();
2149        }
2150        if self.check(&TokenType::Break) {
2151            return self.parse_break_statement();
2152        }
2153        if self.check(&TokenType::If) {
2154            return self.parse_if_statement();
2155        }
2156        if self.check(&TokenType::Assert) {
2157            return self.parse_assert_statement();
2158        }
2159        if self.check(&TokenType::Require) {
2160            return self.parse_require_statement();
2161        }
2162        // Phase 35: Trust statement
2163        if self.check(&TokenType::Trust) {
2164            return self.parse_trust_statement();
2165        }
2166        // Phase 50: Security Check statement
2167        if self.check(&TokenType::Check) {
2168            return self.parse_check_statement();
2169        }
2170        // Phase 51: P2P Networking statements
2171        if self.check(&TokenType::Listen) {
2172            return self.parse_listen_statement();
2173        }
2174        if self.check(&TokenType::NetConnect) {
2175            return self.parse_connect_statement();
2176        }
2177        if self.check(&TokenType::Sleep) {
2178            return self.parse_sleep_statement();
2179        }
2180        // `Run <f> on <arg> under <Name> into <var>` — invoke a shipped computation through a
2181        // named acceptance contract; desugars to `Let <var> be run_accepted(<f>, <arg>, lo, hi)`.
2182        if self.check_word("Run") {
2183            return self.parse_run_under_contract();
2184        }
2185        // Phase 52: GossipSub sync statement
2186        if self.check(&TokenType::Sync) {
2187            return self.parse_sync_statement();
2188        }
2189        // Phase 53: Persistent storage mount statement
2190        if self.check(&TokenType::Mount) {
2191            return self.parse_mount_statement();
2192        }
2193        if self.check(&TokenType::While) {
2194            return self.parse_while_statement();
2195        }
2196        if self.check(&TokenType::Repeat) {
2197            return self.parse_repeat_statement();
2198        }
2199        // Phase 30b: Allow "for" without "Repeat" keyword
2200        if self.check(&TokenType::For) {
2201            return self.parse_for_statement();
2202        }
2203        if self.check(&TokenType::Call) {
2204            return self.parse_call_statement();
2205        }
2206        if self.check(&TokenType::Give) {
2207            return self.parse_give_statement();
2208        }
2209        if self.check(&TokenType::Show) {
2210            return self.parse_show_statement();
2211        }
2212        // Phase 33: Pattern matching on sum types
2213        if self.check(&TokenType::Inspect) {
2214            return self.parse_inspect_statement();
2215        }
2216
2217        // Phase 43D: Collection operations
2218        if self.check(&TokenType::Push) {
2219            return self.parse_push_statement();
2220        }
2221        if self.check(&TokenType::Pop) {
2222            return self.parse_pop_statement();
2223        }
2224        // Set operations
2225        if self.check(&TokenType::Add) {
2226            return self.parse_add_statement();
2227        }
2228        if self.check(&TokenType::Remove) {
2229            return self.parse_remove_statement();
2230        }
2231
2232        // Phase 8.5: Memory zone block
2233        if self.check(&TokenType::Inside) {
2234            return self.parse_zone_statement();
2235        }
2236
2237        // Phase 9: Structured Concurrency blocks
2238        if self.check(&TokenType::Attempt) {
2239            return self.parse_concurrent_block();
2240        }
2241        if self.check(&TokenType::Simultaneously) {
2242            return self.parse_parallel_block();
2243        }
2244
2245        // Phase 10: IO statements
2246        if self.check(&TokenType::Read) {
2247            return self.parse_read_statement();
2248        }
2249        if self.check(&TokenType::Write) {
2250            return self.parse_write_statement();
2251        }
2252
2253        // Phase 46: Agent System statements
2254        if self.check(&TokenType::Spawn) {
2255            return self.parse_spawn_statement();
2256        }
2257        if self.check_word("Stream") {
2258            return self.parse_stream_statement();
2259        }
2260        if self.check(&TokenType::Send) {
2261            // Phase 54: Disambiguate "Send x into pipe" vs "Send x to agent"
2262            if self.lookahead_contains_into() {
2263                return self.parse_send_pipe_statement();
2264            }
2265            return self.parse_send_statement();
2266        }
2267        if self.check(&TokenType::Await) {
2268            // Phase 54: Disambiguate "Await the first of:" vs "Await response from agent"
2269            if self.lookahead_is_first_of() {
2270                return self.parse_select_statement();
2271            }
2272            return self.parse_await_statement();
2273        }
2274
2275        // Phase 49: CRDT statements
2276        if self.check(&TokenType::Merge) {
2277            return self.parse_merge_statement();
2278        }
2279        if self.check(&TokenType::Increase) {
2280            return self.parse_increase_statement();
2281        }
2282        // Phase 49b: Extended CRDT statements
2283        if self.check(&TokenType::Decrease) {
2284            return self.parse_decrease_statement();
2285        }
2286        if self.check(&TokenType::Append) {
2287            return self.parse_append_statement();
2288        }
2289        if self.check(&TokenType::Resolve) {
2290            return self.parse_resolve_statement();
2291        }
2292
2293        // Phase 54: Go-like Concurrency statements
2294        if self.check(&TokenType::Launch) {
2295            return self.parse_launch_statement();
2296        }
2297        if self.check(&TokenType::Stop) {
2298            return self.parse_stop_statement();
2299        }
2300        if self.check(&TokenType::Try) {
2301            return self.parse_try_statement();
2302        }
2303        if self.check(&TokenType::Receive) {
2304            return self.parse_receive_pipe_statement();
2305        }
2306
2307        // Escape hatch: raw foreign code blocks
2308        if self.check(&TokenType::Escape) {
2309            return self.parse_escape_statement();
2310        }
2311
2312        // Expression-statement: function call without "Call" keyword
2313        // e.g., `greet("Alice").` instead of `Call greet with "Alice".`
2314        // Check if next token is LParen (indicating a function call)
2315        if self.tokens.get(self.current + 1)
2316            .map(|t| matches!(t.kind, TokenType::LParen))
2317            .unwrap_or(false)
2318        {
2319            // Get the function name from current token
2320            let function = self.peek().lexeme;
2321            self.advance(); // consume function name
2322
2323            // Parse the call expression (starts from LParen)
2324            let expr = self.parse_call_expr(function)?;
2325            if let Expr::Call { function, args } = expr {
2326                return Ok(Stmt::Call { function: *function, args: args.clone() });
2327            }
2328        }
2329
2330        // `x is 5.` at statement position: value comparison spelled with the
2331        // predicate copula. Diagnose it specifically — a generic
2332        // ExpectedStatement would bury the actual lesson (say `equals`).
2333        // Only literal values qualify: `x is even.` is predication, not a
2334        // botched comparison.
2335        if self
2336            .tokens
2337            .get(self.current + 1)
2338            .is_some_and(|t| matches!(t.kind, TokenType::Is))
2339        {
2340            if let Some(value_token) = self.tokens.get(self.current + 2) {
2341                if matches!(
2342                    value_token.kind,
2343                    TokenType::Number(_)
2344                        | TokenType::StringLiteral(_)
2345                        | TokenType::InterpolatedString(_)
2346                ) {
2347                    let variable = self.interner.resolve(self.peek().lexeme).to_string();
2348                    let value = self.interner.resolve(value_token.lexeme).to_string();
2349                    let span = Span::new(self.peek().span.start, value_token.span.end);
2350                    return Err(ParseError {
2351                        kind: ParseErrorKind::IsValueEquality { variable, value },
2352                        span,
2353                    });
2354                }
2355            }
2356        }
2357
2358        Err(ParseError {
2359            kind: ParseErrorKind::ExpectedStatement,
2360            span: self.current_span(),
2361        })
2362    }
2363
2364    fn parse_if_statement(&mut self) -> ParseResult<Stmt<'a>> {
2365        self.advance(); // consume "If"
2366
2367        // Parse condition expression (simple: identifier equals value)
2368        let cond = self.parse_condition()?;
2369
2370        // Optionally consume "then" before the colon (supports "If x = 5 then:" syntax)
2371        if self.check(&TokenType::Then) {
2372            self.advance();
2373        }
2374
2375        // Expect colon
2376        if !self.check(&TokenType::Colon) {
2377            return Err(ParseError {
2378                kind: ParseErrorKind::ExpectedKeyword { keyword: ":".to_string() },
2379                span: self.current_span(),
2380            });
2381        }
2382        self.advance(); // consume ":"
2383
2384        // Parse the then body — an indented block, OR (inline guard) a single
2385        // statement on the same line: `If c: Return 1.`
2386        let mut then_stmts = Vec::new();
2387        if self.check(&TokenType::Indent) {
2388            self.advance(); // consume Indent
2389            while !self.check(&TokenType::Dedent) && !self.is_at_end() {
2390                let stmt = self.parse_statement()?;
2391                then_stmts.push(stmt);
2392                if self.check(&TokenType::Period) {
2393                    self.advance();
2394                }
2395            }
2396            if self.check(&TokenType::Dedent) {
2397                self.advance();
2398            }
2399        } else {
2400            let stmt = self.parse_statement()?;
2401            then_stmts.push(stmt);
2402            if self.check(&TokenType::Period) {
2403                self.advance();
2404            }
2405        }
2406
2407        // Allocate then_block in arena
2408        let then_block = self.ctx.stmts.expect("imperative arenas not initialized")
2409            .alloc_slice(then_stmts.into_iter());
2410
2411        // Check for else clause: Otherwise/Else/Otherwise If/Else If/elif
2412        let else_block = if self.check(&TokenType::Otherwise) || self.check(&TokenType::Else) {
2413            self.advance(); // consume "Otherwise" or "Else"
2414
2415            // Check for "Otherwise If" / "Else If" chain
2416            if self.check(&TokenType::If) {
2417                // Parse as else-if: create single-statement else block containing nested If
2418                let nested_if = self.parse_if_statement()?;
2419                let nested_slice = self.ctx.stmts.expect("imperative arenas not initialized")
2420                    .alloc_slice(std::iter::once(nested_if));
2421                Some(nested_slice)
2422            } else {
2423                // Regular else block - expect colon and indent
2424                if !self.check(&TokenType::Colon) {
2425                    return Err(ParseError {
2426                        kind: ParseErrorKind::ExpectedKeyword { keyword: ":".to_string() },
2427                        span: self.current_span(),
2428                    });
2429                }
2430                self.advance(); // consume ":"
2431
2432                // Indented block OR inline: `Otherwise: Show "x".`
2433                let mut else_stmts = Vec::new();
2434                if self.check(&TokenType::Indent) {
2435                    self.advance(); // consume Indent
2436                    while !self.check(&TokenType::Dedent) && !self.is_at_end() {
2437                        let stmt = self.parse_statement()?;
2438                        else_stmts.push(stmt);
2439                        if self.check(&TokenType::Period) {
2440                            self.advance();
2441                        }
2442                    }
2443                    if self.check(&TokenType::Dedent) {
2444                        self.advance();
2445                    }
2446                } else {
2447                    let stmt = self.parse_statement()?;
2448                    else_stmts.push(stmt);
2449                    if self.check(&TokenType::Period) {
2450                        self.advance();
2451                    }
2452                }
2453
2454                Some(self.ctx.stmts.expect("imperative arenas not initialized")
2455                    .alloc_slice(else_stmts.into_iter()))
2456            }
2457        } else if self.check(&TokenType::Elif) {
2458            // Python-style elif: equivalent to "Else If"
2459            self.advance(); // consume "elif"
2460            // Parse the condition and body directly (elif acts like "Else If" without the separate If token)
2461            let nested_if = self.parse_elif_as_if()?;
2462            let nested_slice = self.ctx.stmts.expect("imperative arenas not initialized")
2463                .alloc_slice(std::iter::once(nested_if));
2464            Some(nested_slice)
2465        } else {
2466            None
2467        };
2468
2469        Ok(Stmt::If {
2470            cond,
2471            then_block,
2472            else_block,
2473        })
2474    }
2475
2476    /// Parse an elif clause as an if statement.
2477    /// Called after "elif" has been consumed - parses condition and body directly.
2478    fn parse_elif_as_if(&mut self) -> ParseResult<Stmt<'a>> {
2479        // Parse condition expression (elif is already consumed)
2480        let cond = self.parse_condition()?;
2481
2482        // Expect colon
2483        if !self.check(&TokenType::Colon) {
2484            return Err(ParseError {
2485                kind: ParseErrorKind::ExpectedKeyword { keyword: ":".to_string() },
2486                span: self.current_span(),
2487            });
2488        }
2489        self.advance(); // consume ":"
2490
2491        // Parse the then body — an indented block, OR (inline guard) a single
2492        // statement on the same line: `If c: Return 1.`
2493        let mut then_stmts = Vec::new();
2494        if self.check(&TokenType::Indent) {
2495            self.advance(); // consume Indent
2496            while !self.check(&TokenType::Dedent) && !self.is_at_end() {
2497                let stmt = self.parse_statement()?;
2498                then_stmts.push(stmt);
2499                if self.check(&TokenType::Period) {
2500                    self.advance();
2501                }
2502            }
2503            if self.check(&TokenType::Dedent) {
2504                self.advance();
2505            }
2506        } else {
2507            let stmt = self.parse_statement()?;
2508            then_stmts.push(stmt);
2509            if self.check(&TokenType::Period) {
2510                self.advance();
2511            }
2512        }
2513
2514        // Allocate then_block in arena
2515        let then_block = self.ctx.stmts.expect("imperative arenas not initialized")
2516            .alloc_slice(then_stmts.into_iter());
2517
2518        // Check for else clause: Otherwise/Else/Otherwise If/Else If/elif
2519        let else_block = if self.check(&TokenType::Otherwise) || self.check(&TokenType::Else) {
2520            self.advance(); // consume "Otherwise" or "Else"
2521
2522            // Check for "Otherwise If" / "Else If" chain
2523            if self.check(&TokenType::If) {
2524                let nested_if = self.parse_if_statement()?;
2525                let nested_slice = self.ctx.stmts.expect("imperative arenas not initialized")
2526                    .alloc_slice(std::iter::once(nested_if));
2527                Some(nested_slice)
2528            } else {
2529                // Regular else block
2530                if !self.check(&TokenType::Colon) {
2531                    return Err(ParseError {
2532                        kind: ParseErrorKind::ExpectedKeyword { keyword: ":".to_string() },
2533                        span: self.current_span(),
2534                    });
2535                }
2536                self.advance(); // consume ":"
2537
2538                // Indented block OR inline: `Otherwise: Show "x".`
2539                let mut else_stmts = Vec::new();
2540                if self.check(&TokenType::Indent) {
2541                    self.advance(); // consume Indent
2542                    while !self.check(&TokenType::Dedent) && !self.is_at_end() {
2543                        let stmt = self.parse_statement()?;
2544                        else_stmts.push(stmt);
2545                        if self.check(&TokenType::Period) {
2546                            self.advance();
2547                        }
2548                    }
2549                    if self.check(&TokenType::Dedent) {
2550                        self.advance();
2551                    }
2552                } else {
2553                    let stmt = self.parse_statement()?;
2554                    else_stmts.push(stmt);
2555                    if self.check(&TokenType::Period) {
2556                        self.advance();
2557                    }
2558                }
2559
2560                Some(self.ctx.stmts.expect("imperative arenas not initialized")
2561                    .alloc_slice(else_stmts.into_iter()))
2562            }
2563        } else if self.check(&TokenType::Elif) {
2564            self.advance(); // consume "elif"
2565            let nested_if = self.parse_elif_as_if()?;
2566            let nested_slice = self.ctx.stmts.expect("imperative arenas not initialized")
2567                .alloc_slice(std::iter::once(nested_if));
2568            Some(nested_slice)
2569        } else {
2570            None
2571        };
2572
2573        Ok(Stmt::If {
2574            cond,
2575            then_block,
2576            else_block,
2577        })
2578    }
2579
2580    fn parse_while_statement(&mut self) -> ParseResult<Stmt<'a>> {
2581        self.advance(); // consume "While"
2582
2583        let cond = self.parse_condition()?;
2584
2585        // Phase 44: Parse optional (decreasing expr)
2586        let decreasing = if self.check(&TokenType::LParen) {
2587            self.advance(); // consume '('
2588
2589            // Expect "decreasing" keyword
2590            if !self.check_word("decreasing") {
2591                return Err(ParseError {
2592                    kind: ParseErrorKind::ExpectedKeyword { keyword: "decreasing".to_string() },
2593                    span: self.current_span(),
2594                });
2595            }
2596            self.advance(); // consume "decreasing"
2597
2598            let variant = self.parse_imperative_expr()?;
2599
2600            if !self.check(&TokenType::RParen) {
2601                return Err(ParseError {
2602                    kind: ParseErrorKind::ExpectedKeyword { keyword: ")".to_string() },
2603                    span: self.current_span(),
2604                });
2605            }
2606            self.advance(); // consume ')'
2607
2608            Some(variant)
2609        } else {
2610            None
2611        };
2612
2613        if !self.check(&TokenType::Colon) {
2614            return Err(ParseError {
2615                kind: ParseErrorKind::ExpectedKeyword { keyword: ":".to_string() },
2616                span: self.current_span(),
2617            });
2618        }
2619        self.advance(); // consume ":"
2620
2621        if !self.check(&TokenType::Indent) {
2622            return Err(ParseError {
2623                kind: ParseErrorKind::ExpectedStatement,
2624                span: self.current_span(),
2625            });
2626        }
2627        self.advance(); // consume Indent
2628
2629        let mut body_stmts = Vec::new();
2630        while !self.check(&TokenType::Dedent) && !self.is_at_end() {
2631            let stmt = self.parse_statement()?;
2632            body_stmts.push(stmt);
2633            if self.check(&TokenType::Period) {
2634                self.advance();
2635            }
2636        }
2637
2638        if self.check(&TokenType::Dedent) {
2639            self.advance();
2640        }
2641
2642        let body = self.ctx.stmts.expect("imperative arenas not initialized")
2643            .alloc_slice(body_stmts.into_iter());
2644
2645        Ok(Stmt::While { cond, body, decreasing })
2646    }
2647
2648    /// Parse a loop pattern: single identifier or tuple destructuring.
2649    /// Examples: `x` or `(k, v)` or `(a, b, c)`
2650    fn parse_loop_pattern(&mut self) -> ParseResult<Pattern> {
2651        use crate::ast::stmt::Pattern;
2652
2653        // Check for tuple pattern: (x, y, ...)
2654        if self.check(&TokenType::LParen) {
2655            self.advance(); // consume "("
2656
2657            let mut identifiers = Vec::new();
2658            loop {
2659                let id = self.expect_identifier()?;
2660                identifiers.push(id);
2661
2662                // Check for comma to continue
2663                if self.check(&TokenType::Comma) {
2664                    self.advance(); // consume ","
2665                    continue;
2666                }
2667                break;
2668            }
2669
2670            // Expect closing paren
2671            if !self.check(&TokenType::RParen) {
2672                return Err(ParseError {
2673                    kind: ParseErrorKind::ExpectedKeyword { keyword: ")".to_string() },
2674                    span: self.current_span(),
2675                });
2676            }
2677            self.advance(); // consume ")"
2678
2679            Ok(Pattern::Tuple(identifiers))
2680        } else {
2681            // Single identifier pattern
2682            let id = self.expect_identifier()?;
2683            Ok(Pattern::Identifier(id))
2684        }
2685    }
2686
2687    fn parse_repeat_statement(&mut self) -> ParseResult<Stmt<'a>> {
2688        self.advance(); // consume "Repeat"
2689
2690        // `Repeat forever:` — an infinite loop (exit via `Break`), lowered to `While true:`.
2691        if self.check_word("forever") {
2692            self.advance(); // consume "forever"
2693            if !self.check(&TokenType::Colon) {
2694                return Err(ParseError { kind: ParseErrorKind::ExpectedKeyword { keyword: ":".to_string() }, span: self.current_span() });
2695            }
2696            self.advance(); // consume ":"
2697            if !self.check(&TokenType::Indent) {
2698                return Err(ParseError { kind: ParseErrorKind::ExpectedStatement, span: self.current_span() });
2699            }
2700            self.advance(); // consume Indent
2701            let mut body_stmts = Vec::new();
2702            while !self.check(&TokenType::Dedent) && !self.is_at_end() {
2703                let stmt = self.parse_statement()?;
2704                body_stmts.push(stmt);
2705                if self.check(&TokenType::Period) { self.advance(); }
2706            }
2707            if self.check(&TokenType::Dedent) { self.advance(); }
2708            let body = self.ctx.stmts.expect("imperative arenas not initialized").alloc_slice(body_stmts.into_iter());
2709            let cond = self.ctx.alloc_imperative_expr(Expr::Literal(crate::ast::Literal::Boolean(true)));
2710            return Ok(Stmt::While { cond, body, decreasing: None });
2711        }
2712
2713        // Optional "for"
2714        if self.check(&TokenType::For) {
2715            self.advance();
2716        }
2717
2718        // Parse loop pattern: single identifier or tuple destructuring
2719        let pattern = self.parse_loop_pattern()?;
2720
2721        // Determine iteration type: "in" for collection, "from" for range
2722        let iterable = if self.check(&TokenType::From) || self.check_preposition_is("from") {
2723            self.advance(); // consume "from"
2724            let start = self.parse_imperative_expr()?;
2725
2726            // Expect "to" (can be keyword or preposition)
2727            if !self.check(&TokenType::To) && !self.check_preposition_is("to") {
2728                return Err(ParseError {
2729                    kind: ParseErrorKind::ExpectedKeyword { keyword: "to".to_string() },
2730                    span: self.current_span(),
2731                });
2732            }
2733            self.advance();
2734
2735            let end = self.parse_imperative_expr()?;
2736            self.ctx.alloc_imperative_expr(Expr::Range { start, end })
2737        } else if self.check(&TokenType::In) || self.check_preposition_is("in") {
2738            self.advance(); // consume "in"
2739            self.parse_imperative_expr()?
2740        } else {
2741            return Err(ParseError {
2742                kind: ParseErrorKind::ExpectedKeyword { keyword: "in or from".to_string() },
2743                span: self.current_span(),
2744            });
2745        };
2746
2747        // Expect colon
2748        if !self.check(&TokenType::Colon) {
2749            return Err(ParseError {
2750                kind: ParseErrorKind::ExpectedKeyword { keyword: ":".to_string() },
2751                span: self.current_span(),
2752            });
2753        }
2754        self.advance();
2755
2756        // Expect indent
2757        if !self.check(&TokenType::Indent) {
2758            return Err(ParseError {
2759                kind: ParseErrorKind::ExpectedStatement,
2760                span: self.current_span(),
2761            });
2762        }
2763        self.advance();
2764
2765        // Parse body statements
2766        let mut body_stmts = Vec::new();
2767        while !self.check(&TokenType::Dedent) && !self.is_at_end() {
2768            let stmt = self.parse_statement()?;
2769            body_stmts.push(stmt);
2770            if self.check(&TokenType::Period) {
2771                self.advance();
2772            }
2773        }
2774
2775        if self.check(&TokenType::Dedent) {
2776            self.advance();
2777        }
2778
2779        let body = self.ctx.stmts.expect("imperative arenas not initialized")
2780            .alloc_slice(body_stmts.into_iter());
2781
2782        Ok(Stmt::Repeat { pattern, iterable, body })
2783    }
2784
2785    /// Parse a for-loop without the "Repeat" keyword prefix.
2786    /// Syntax: `for <var> from <start> to <end>:` or `for <var> in <collection>:`
2787    fn parse_for_statement(&mut self) -> ParseResult<Stmt<'a>> {
2788        self.advance(); // consume "for"
2789
2790        // Parse loop pattern: single identifier or tuple destructuring
2791        let pattern = self.parse_loop_pattern()?;
2792
2793        // Determine iteration type: "in" for collection, "from" for range
2794        let iterable = if self.check(&TokenType::From) || self.check_preposition_is("from") {
2795            self.advance(); // consume "from"
2796            let start = self.parse_imperative_expr()?;
2797
2798            // Expect "to" (can be keyword or preposition)
2799            if !self.check(&TokenType::To) && !self.check_preposition_is("to") {
2800                return Err(ParseError {
2801                    kind: ParseErrorKind::ExpectedKeyword { keyword: "to".to_string() },
2802                    span: self.current_span(),
2803                });
2804            }
2805            self.advance();
2806
2807            let end = self.parse_imperative_expr()?;
2808            self.ctx.alloc_imperative_expr(Expr::Range { start, end })
2809        } else if self.check(&TokenType::In) || self.check_preposition_is("in") {
2810            self.advance(); // consume "in"
2811            self.parse_imperative_expr()?
2812        } else {
2813            return Err(ParseError {
2814                kind: ParseErrorKind::ExpectedKeyword { keyword: "in or from".to_string() },
2815                span: self.current_span(),
2816            });
2817        };
2818
2819        // Expect colon
2820        if !self.check(&TokenType::Colon) {
2821            return Err(ParseError {
2822                kind: ParseErrorKind::ExpectedKeyword { keyword: ":".to_string() },
2823                span: self.current_span(),
2824            });
2825        }
2826        self.advance();
2827
2828        // Expect indent
2829        if !self.check(&TokenType::Indent) {
2830            return Err(ParseError {
2831                kind: ParseErrorKind::ExpectedStatement,
2832                span: self.current_span(),
2833            });
2834        }
2835        self.advance();
2836
2837        // Parse body statements
2838        let mut body_stmts = Vec::new();
2839        while !self.check(&TokenType::Dedent) && !self.is_at_end() {
2840            let stmt = self.parse_statement()?;
2841            body_stmts.push(stmt);
2842            if self.check(&TokenType::Period) {
2843                self.advance();
2844            }
2845        }
2846
2847        if self.check(&TokenType::Dedent) {
2848            self.advance();
2849        }
2850
2851        let body = self.ctx.stmts.expect("imperative arenas not initialized")
2852            .alloc_slice(body_stmts.into_iter());
2853
2854        Ok(Stmt::Repeat { pattern, iterable, body })
2855    }
2856
2857    fn parse_call_statement(&mut self) -> ParseResult<Stmt<'a>> {
2858        self.advance(); // consume "Call"
2859
2860        // Parse function name (identifier)
2861        // Function names can be nouns, adjectives, or verbs (e.g., "work", "process")
2862        // Use the token's lexeme to match function definition casing
2863        let function = match &self.peek().kind {
2864            TokenType::Noun(sym) | TokenType::Adjective(sym) => {
2865                let s = *sym;
2866                self.advance();
2867                s
2868            }
2869            TokenType::Verb { .. } | TokenType::Ambiguous { .. } => {
2870                // Use lexeme (actual text) not lemma to preserve casing
2871                let s = self.peek().lexeme;
2872                self.advance();
2873                s
2874            }
2875            _ => {
2876                return Err(ParseError {
2877                    kind: ParseErrorKind::ExpectedIdentifier,
2878                    span: self.current_span(),
2879                });
2880            }
2881        };
2882
2883        // Expect "with" followed by arguments
2884        let args = if self.check_preposition_is("with") {
2885            self.advance(); // consume "with"
2886            self.parse_call_arguments()?
2887        } else {
2888            Vec::new()
2889        };
2890
2891        Ok(Stmt::Call { function, args })
2892    }
2893
2894    fn parse_call_arguments(&mut self) -> ParseResult<Vec<&'a Expr<'a>>> {
2895        let mut args = Vec::new();
2896
2897        // Parse first argument (may have Give keyword)
2898        let arg = self.parse_call_arg()?;
2899        args.push(arg);
2900
2901        // Parse additional arguments separated by "and" or ","
2902        while self.check(&TokenType::And) || self.check(&TokenType::Comma) {
2903            self.advance(); // consume "and" or ","
2904            // A trailing comma (`f(1, 2,)`) ends the argument list.
2905            if self.check(&TokenType::RParen) {
2906                break;
2907            }
2908            let arg = self.parse_call_arg()?;
2909            args.push(arg);
2910        }
2911
2912        Ok(args)
2913    }
2914
2915    fn parse_call_arg(&mut self) -> ParseResult<&'a Expr<'a>> {
2916        // Check for Give keyword to mark ownership transfer
2917        if self.check(&TokenType::Give) {
2918            self.advance(); // consume "Give"
2919            let value = self.parse_comparison()?;
2920            return Ok(self.ctx.alloc_imperative_expr(Expr::Give { value }));
2921        }
2922
2923        // Otherwise parse normal expression — use parse_comparison() so that
2924        // "and" remains available as an argument separator in parse_call_arguments()
2925        self.parse_comparison()
2926    }
2927
2928    fn parse_condition(&mut self) -> ParseResult<&'a Expr<'a>> {
2929        // Grand Challenge: Parse compound conditions with "and" and "or"
2930        // "or" has lower precedence than "and"
2931        let expr = self.parse_or_condition()?;
2932        // Postfix unit conversion: `<expr> in <unit>` → `convert(expr, "unit")`, e.g.
2933        // `2 inches + 5 centimeters in feet`. The lowest-precedence postfix, so `in` applies to the
2934        // whole expression. Gated on the token after `in` being a UNIT word, so `for each x in list`
2935        // (loop var via expect_identifier; collection is not a unit) and `chunk at i in zone` are
2936        // untouched — neither places a unit word right after `in`.
2937        if self.check(&TokenType::In) || self.check_preposition_is("in") {
2938            // Zoned time: `<moment> in "America/New_York"` → the local time in that zone. The token
2939            // after `in` is a STRING (a zone name), which distinguishes it from `in <unit>` below.
2940            let zone_sym = match self.tokens.get(self.current + 1).map(|t| &t.kind) {
2941                Some(TokenType::StringLiteral(s)) => Some(*s),
2942                _ => None,
2943            };
2944            if let Some(zone_sym) = zone_sym {
2945                self.advance(); // "in"
2946                self.advance(); // the zone string
2947                let zone = self.ctx.alloc_imperative_expr(Expr::Literal(crate::ast::Literal::Text(zone_sym)));
2948                let func = self.interner.intern("in_zone");
2949                return Ok(self
2950                    .ctx
2951                    .alloc_imperative_expr(Expr::Call { function: func, args: vec![expr, zone] }));
2952            }
2953            let unit_lexeme = self.tokens.get(self.current + 1).and_then(|t| {
2954                if matches!(t.kind, TokenType::Identifier | TokenType::Noun(_) | TokenType::Adjective(_)) {
2955                    Some(t.lexeme)
2956                } else {
2957                    None
2958                }
2959            });
2960            if let Some(unit_lexeme) = unit_lexeme {
2961                if logicaffeine_base::quantity::units::by_name(self.interner.resolve(unit_lexeme)).is_some() {
2962                    self.advance(); // consume "in"
2963                    self.advance(); // consume the unit word
2964                    let unit_expr = self
2965                        .ctx
2966                        .alloc_imperative_expr(crate::ast::Expr::Literal(crate::ast::Literal::Text(unit_lexeme)));
2967                    let func = self.interner.intern("convert");
2968                    return Ok(self
2969                        .ctx
2970                        .alloc_imperative_expr(crate::ast::Expr::Call { function: func, args: vec![expr, unit_expr] }));
2971                }
2972            }
2973            // Currency conversion: `<money> in EUR` → `to_currency(money, "EUR")` via the ambient
2974            // rate context. Checked AFTER units (a unit word never reads as a currency); gated on a
2975            // real ISO-4217 code, so `for each x in list` and friends are untouched.
2976            let code_lexeme = self.tokens.get(self.current + 1).and_then(|t| {
2977                if matches!(
2978                    t.kind,
2979                    TokenType::Identifier | TokenType::Noun(_) | TokenType::ProperName(_) | TokenType::Adjective(_)
2980                ) {
2981                    Some(t.lexeme)
2982                } else {
2983                    None
2984                }
2985            });
2986            if let Some(code_lexeme) = code_lexeme {
2987                if logicaffeine_base::money::currency::by_code(self.interner.resolve(code_lexeme)).is_some() {
2988                    self.advance(); // consume "in"
2989                    self.advance(); // consume the currency code
2990                    let code_expr = self
2991                        .ctx
2992                        .alloc_imperative_expr(crate::ast::Expr::Literal(crate::ast::Literal::Text(code_lexeme)));
2993                    let func = self.interner.intern("to_currency");
2994                    return Ok(self
2995                        .ctx
2996                        .alloc_imperative_expr(crate::ast::Expr::Call { function: func, args: vec![expr, code_expr] }));
2997                }
2998            }
2999        }
3000        Ok(expr)
3001    }
3002
3003    /// Parse "or" conditions (lower precedence than "and")
3004    fn parse_or_condition(&mut self) -> ParseResult<&'a Expr<'a>> {
3005        let mut left = self.parse_and_condition()?;
3006
3007        while self.check(&TokenType::Or) || self.check_word("or") {
3008            self.advance();
3009            let right = self.parse_and_condition()?;
3010            left = self.ctx.alloc_imperative_expr(Expr::BinaryOp {
3011                op: BinaryOpKind::Or,
3012                left,
3013                right,
3014            });
3015        }
3016
3017        Ok(left)
3018    }
3019
3020    /// Parse "and" conditions (higher precedence than "or")
3021    fn parse_and_condition(&mut self) -> ParseResult<&'a Expr<'a>> {
3022        let mut left = self.parse_comparison()?;
3023
3024        while self.check(&TokenType::And) || self.check_word("and") {
3025            self.advance();
3026            let right = self.parse_comparison()?;
3027            left = self.ctx.alloc_imperative_expr(Expr::BinaryOp {
3028                op: BinaryOpKind::And,
3029                left,
3030                right,
3031            });
3032        }
3033
3034        Ok(left)
3035    }
3036
3037    /// Whether the `in` at the current position introduces a postfix unit / currency / zone
3038    /// CONVERSION (handled by [`parse_condition`]) rather than Pythonic membership. Mirrors
3039    /// `parse_condition`'s exact gating: a zone STRING, or a unit / ISO-4217 currency word, right
3040    /// after `in`. Keeps `x in xs` membership working while restoring `<expr> in <unit|currency>`.
3041    fn in_introduces_conversion(&self) -> bool {
3042        match self.tokens.get(self.current + 1) {
3043            Some(t) => match &t.kind {
3044                TokenType::StringLiteral(_) => true,
3045                TokenType::Identifier | TokenType::Noun(_) | TokenType::ProperName(_) | TokenType::Adjective(_) => {
3046                    // A declared variable shadows a unit/currency abbreviation (declarer-wins):
3047                    // `20 in s`, where `s` is a bound set, is membership — NOT "convert 20 to
3048                    // seconds" (`s` = the seconds unit). A real conversion (`… in feet`, `… in USD`)
3049                    // never names a bound variable, so those are unaffected.
3050                    if self.user_bound.contains(&t.lexeme) {
3051                        return false;
3052                    }
3053                    let name = self.interner.resolve(t.lexeme);
3054                    logicaffeine_base::quantity::units::by_name(name).is_some()
3055                        || logicaffeine_base::money::currency::by_code(name).is_some()
3056                }
3057                _ => false,
3058            },
3059            None => false,
3060        }
3061    }
3062
3063    /// Grand Challenge: Parse a single comparison expression
3064    fn parse_comparison(&mut self) -> ParseResult<&'a Expr<'a>> {
3065        // Handle unary "not" operator: "not x" or "not (expr)"
3066        if self.check(&TokenType::Not) || self.check_word("not") {
3067            self.advance(); // consume "not"
3068            let operand = self.parse_comparison()?; // recursive for "not not x"
3069            return Ok(self.ctx.alloc_imperative_expr(Expr::Not { operand }));
3070        }
3071
3072        let left = self.parse_xor_expr()?;
3073
3074        // Key-first membership: `x in xs` → `xs contains x` (Pythonic); `x not in
3075        // xs` → its negation. Reuses `Expr::Contains` (operands reversed). The
3076        // `Repeat for x in xs` header consumes its own `in` before the iterable,
3077        // so it never reaches here.
3078        //
3079        // BUT decline when `in` introduces a POSTFIX CONVERSION — `<expr> in <unit|currency>` or
3080        // `<moment> in "<zone>"` — which `parse_condition` handles at a higher level and must see the
3081        // `in` for. Without this, `10.00 EUR in USD` parses as membership (`USD contains …`) and
3082        // fails with "Undefined variable: USD"; likewise `<moment> in "America/New_York"`.
3083        if self.check(&TokenType::In) && !self.in_introduces_conversion() {
3084            self.advance(); // consume "in"
3085            let collection = self.parse_xor_expr()?;
3086            return Ok(self.ctx.alloc_imperative_expr(Expr::Contains { collection, value: left }));
3087        }
3088        if (self.check(&TokenType::Not) || self.check_word("not"))
3089            && matches!(
3090                self.tokens.get(self.current + 1).map(|t| &t.kind),
3091                Some(TokenType::In)
3092            )
3093        {
3094            self.advance(); // consume "not"
3095            self.advance(); // consume "in"
3096            let collection = self.parse_xor_expr()?;
3097            let contains = self.ctx.alloc_imperative_expr(Expr::Contains { collection, value: left });
3098            return Ok(self.ctx.alloc_imperative_expr(Expr::Not { operand: contains }));
3099        }
3100
3101        // Check for comparison operators
3102        let op = if self.check(&TokenType::Equals) {
3103            self.advance();
3104            Some(BinaryOpKind::Eq)
3105        } else if self.check(&TokenType::Identity) {
3106            // "is equal to" was tokenized as TokenType::Identity
3107            self.advance();
3108            Some(BinaryOpKind::Eq)
3109        } else if self.check_word("is") {
3110            // Peek ahead to determine which comparison
3111            let saved_pos = self.current;
3112            self.advance(); // consume "is"
3113
3114            // Number predicates desugar to a complete modulo test (no right
3115            // operand to combine) — return the whole Bool expression directly:
3116            // `x is even` → `x % 2 == 0`, `x is odd` → `x % 2 == 1`,
3117            // `x is divisible by n` → `x % n == 0`.
3118            if self.check_word("even") || self.check_word("odd") {
3119                let is_odd = self.check_word("odd");
3120                self.advance(); // consume "even"/"odd"
3121                let two = self.ctx.alloc_imperative_expr(Expr::Literal(crate::ast::Literal::Number(2)));
3122                let rem = self.ctx.alloc_imperative_expr(Expr::BinaryOp {
3123                    op: BinaryOpKind::Modulo, left, right: two,
3124                });
3125                let want = self.ctx.alloc_imperative_expr(Expr::Literal(
3126                    crate::ast::Literal::Number(if is_odd { 1 } else { 0 }),
3127                ));
3128                return Ok(self.ctx.alloc_imperative_expr(Expr::BinaryOp {
3129                    op: BinaryOpKind::Eq, left: rem, right: want,
3130                }));
3131            }
3132            if self.check_word("divisible") {
3133                self.advance(); // consume "divisible"
3134                if self.check_word("by") || self.check_preposition_is("by") {
3135                    self.advance(); // consume "by"
3136                    let divisor = self.parse_xor_expr()?;
3137                    let rem = self.ctx.alloc_imperative_expr(Expr::BinaryOp {
3138                        op: BinaryOpKind::Modulo, left, right: divisor,
3139                    });
3140                    let zero = self.ctx.alloc_imperative_expr(Expr::Literal(crate::ast::Literal::Number(0)));
3141                    return Ok(self.ctx.alloc_imperative_expr(Expr::BinaryOp {
3142                        op: BinaryOpKind::Eq, left: rem, right: zero,
3143                    }));
3144                }
3145                self.current = saved_pos;
3146                return Ok(left);
3147            }
3148            // `x is between lo and hi` → `lo <= x and x <= hi` (inclusive).
3149            if self.check_word("between") {
3150                self.advance(); // consume "between"
3151                let lo = self.parse_xor_expr()?;
3152                if self.check_word("and") || self.check(&TokenType::And) {
3153                    self.advance(); // consume "and"
3154                    let hi = self.parse_xor_expr()?;
3155                    let lower = self.ctx.alloc_imperative_expr(Expr::BinaryOp {
3156                        op: BinaryOpKind::LtEq, left: lo, right: left,
3157                    });
3158                    let upper = self.ctx.alloc_imperative_expr(Expr::BinaryOp {
3159                        op: BinaryOpKind::LtEq, left, right: hi,
3160                    });
3161                    return Ok(self.ctx.alloc_imperative_expr(Expr::BinaryOp {
3162                        op: BinaryOpKind::And, left: lower, right: upper,
3163                    }));
3164                }
3165                self.current = saved_pos;
3166                return Ok(left);
3167            }
3168
3169            if self.check_word("greater") {
3170                self.advance(); // consume "greater"
3171                if self.check_word("than") || self.check_preposition_is("than") {
3172                    self.advance(); // consume "than"
3173                    Some(BinaryOpKind::Gt)
3174                } else {
3175                    self.current = saved_pos;
3176                    None
3177                }
3178            } else if self.check_word("less") {
3179                self.advance(); // consume "less"
3180                if self.check_word("than") || self.check_preposition_is("than") {
3181                    self.advance(); // consume "than"
3182                    Some(BinaryOpKind::Lt)
3183                } else {
3184                    self.current = saved_pos;
3185                    None
3186                }
3187            } else if self.check_word("at") {
3188                self.advance(); // consume "at"
3189                if self.check_word("least") {
3190                    self.advance(); // consume "least"
3191                    Some(BinaryOpKind::GtEq)
3192                } else if self.check_word("most") {
3193                    self.advance(); // consume "most"
3194                    Some(BinaryOpKind::LtEq)
3195                } else {
3196                    self.current = saved_pos;
3197                    None
3198                }
3199            } else if self.check_word("before") {
3200                // "is before X" → temporal `<` (a Moment/Date precedes another); generic `<`.
3201                self.advance(); // consume "before"
3202                Some(BinaryOpKind::Lt)
3203            } else if self.check_word("after") {
3204                // "is after X" → temporal `>` (a Moment/Date follows another); generic `>`.
3205                self.advance(); // consume "after"
3206                Some(BinaryOpKind::Gt)
3207            } else if self.check_word("not") || self.check(&TokenType::Not) {
3208                // "is not X" → NotEq
3209                self.advance(); // consume "not"
3210                Some(BinaryOpKind::NotEq)
3211            } else if self.check_word("equal") {
3212                // "is equal to X" → Eq
3213                self.advance(); // consume "equal"
3214                if self.check_preposition_is("to") {
3215                    self.advance(); // consume "to"
3216                    Some(BinaryOpKind::Eq)
3217                } else {
3218                    self.current = saved_pos;
3219                    None
3220                }
3221            } else if self.check_word("approximately") {
3222                // "is approximately X" → the tolerant float comparison
3223                // (`==` stays IEEE bit-exact). An optional "equal to" tail
3224                // also reads naturally: "is approximately equal to X".
3225                self.advance(); // consume "approximately"
3226                if self.check_word("equal") {
3227                    self.advance(); // consume "equal"
3228                    if self.check_preposition_is("to") {
3229                        self.advance(); // consume "to"
3230                    }
3231                }
3232                Some(BinaryOpKind::ApproxEq)
3233            } else if matches!(self.peek().kind, TokenType::Number(_))
3234                || (self.check(&TokenType::Minus)
3235                    && self.tokens.get(self.current + 1)
3236                        .is_some_and(|t| matches!(t.kind, TokenType::Number(_))))
3237            {
3238                // Bare `x is <number>` is equality (`x is equal to <number>` → `x == <number>`).
3239                // Guarded to a NUMBER literal (including a negated one), so `is a`/`is an`/`is the`/
3240                // `is nothing`/`is <predicate>` and every worded `is …` comparison above stay
3241                // untouched — nothing but a numeral reaches here. The numeral is left for the
3242                // right-operand parse below.
3243                Some(BinaryOpKind::Eq)
3244            } else {
3245                self.current = saved_pos;
3246                None
3247            }
3248        } else if self.check(&TokenType::Lt) {
3249            self.advance();
3250            Some(BinaryOpKind::Lt)
3251        } else if self.check(&TokenType::Gt) {
3252            self.advance();
3253            Some(BinaryOpKind::Gt)
3254        } else if self.check(&TokenType::LtEq) {
3255            self.advance();
3256            Some(BinaryOpKind::LtEq)
3257        } else if self.check(&TokenType::GtEq) {
3258            self.advance();
3259            Some(BinaryOpKind::GtEq)
3260        } else if self.check(&TokenType::EqEq) || self.check(&TokenType::Assign) {
3261            self.advance();
3262            Some(BinaryOpKind::Eq)
3263        } else if self.check(&TokenType::NotEq) {
3264            self.advance();
3265            Some(BinaryOpKind::NotEq)
3266        } else {
3267            None
3268        };
3269
3270        if let Some(op) = op {
3271            let right = self.parse_xor_expr()?;
3272            let first = self.ctx.alloc_imperative_expr(Expr::BinaryOp { op, left, right });
3273            // Chained comparison: `lo <= x <= hi` → `(lo <= x) and (x <= hi)`
3274            // (math / Python reading). Only the SYMBOLIC operators chain; the
3275            // worded forms don't. `right` is the shared middle operand.
3276            if let Some(op2) = self.try_symbolic_comparison_op() {
3277                let hi = self.parse_xor_expr()?;
3278                let second = self.ctx.alloc_imperative_expr(Expr::BinaryOp {
3279                    op: op2, left: right, right: hi,
3280                });
3281                return Ok(self.ctx.alloc_imperative_expr(Expr::BinaryOp {
3282                    op: BinaryOpKind::And, left: first, right: second,
3283                }));
3284            }
3285            Ok(first)
3286        } else {
3287            Ok(left)
3288        }
3289    }
3290
3291    /// Consume a SYMBOLIC comparison operator (`< <= > >= == !=`) if one is
3292    /// next, for chained comparisons. Worded forms (`is greater than`) are not
3293    /// chained. Returns `None` (consuming nothing) otherwise.
3294    fn try_symbolic_comparison_op(&mut self) -> Option<BinaryOpKind> {
3295        let op = if self.check(&TokenType::LtEq) {
3296            BinaryOpKind::LtEq
3297        } else if self.check(&TokenType::GtEq) {
3298            BinaryOpKind::GtEq
3299        } else if self.check(&TokenType::Lt) {
3300            BinaryOpKind::Lt
3301        } else if self.check(&TokenType::Gt) {
3302            BinaryOpKind::Gt
3303        } else if self.check(&TokenType::EqEq) {
3304            BinaryOpKind::Eq
3305        } else if self.check(&TokenType::NotEq) {
3306            BinaryOpKind::NotEq
3307        } else {
3308            return None;
3309        };
3310        self.advance();
3311        Some(op)
3312    }
3313
3314    fn parse_let_statement(&mut self) -> ParseResult<Stmt<'a>> {
3315        self.advance(); // consume "Let"
3316
3317        // Check for "mutable" keyword
3318        let mutable = if self.check_mutable_keyword() {
3319            self.advance();
3320            true
3321        } else {
3322            false
3323        };
3324
3325        // Get identifier
3326        let var = self.expect_identifier()?;
3327
3328        // Check for optional type annotation: `: Type`
3329        let ty = if self.check(&TokenType::Colon) {
3330            self.advance(); // consume ":"
3331            let type_expr = self.parse_type_expression()?;
3332            Some(self.ctx.alloc_type_expr(type_expr))
3333        } else {
3334            None
3335        };
3336
3337        // Expect "be" or "="
3338        if !self.check(&TokenType::Be) && !self.check(&TokenType::Assign) {
3339            return Err(ParseError {
3340                kind: ParseErrorKind::ExpectedKeyword { keyword: "be or =".to_string() },
3341                span: self.current_span(),
3342            });
3343        }
3344        self.advance(); // consume "be" or "="
3345
3346        // Phase 53: Check for "mounted at [path]" pattern (for Persistent types)
3347        if self.check_word("mounted") {
3348            self.advance(); // consume "mounted"
3349            if !self.check(&TokenType::At) && !self.check_preposition_is("at") {
3350                return Err(ParseError {
3351                    kind: ParseErrorKind::ExpectedKeyword { keyword: "at".to_string() },
3352                    span: self.current_span(),
3353                });
3354            }
3355            self.advance(); // consume "at"
3356            let path = self.parse_imperative_expr()?;
3357            return Ok(Stmt::Mount { var, path });
3358        }
3359
3360        // Phase 51: Check for "a PeerAgent at [addr]" pattern
3361        if self.check_article() {
3362            let saved_pos = self.current;
3363            self.advance(); // consume article
3364
3365            // Check if next word is "PeerAgent" (case insensitive)
3366            if let TokenType::Noun(sym) | TokenType::ProperName(sym) = self.peek().kind {
3367                let word = self.interner.resolve(sym).to_lowercase();
3368                if word == "peeragent" {
3369                    self.advance(); // consume "PeerAgent"
3370
3371                    // Check for "at" keyword
3372                    if self.check(&TokenType::At) || self.check_preposition_is("at") {
3373                        self.advance(); // consume "at"
3374
3375                        // Parse address expression
3376                        let address = self.parse_imperative_expr()?;
3377
3378                        return Ok(Stmt::LetPeerAgent { var, address });
3379                    }
3380                }
3381            }
3382            // Not a PeerAgent, backtrack
3383            self.current = saved_pos;
3384        }
3385
3386        // Phase 54: Check for "a [new] Pipe of Type" pattern. The optional `new`
3387        // matches the `a new X` idiom every other collection construction uses
3388        // (`a new Map`, `a new Set`); the guide writes `a new Pipe of Int`.
3389        if self.check_article() {
3390            let saved_pos = self.current;
3391            self.advance(); // consume article
3392            if self.check(&TokenType::New) {
3393                self.advance(); // consume optional "new"
3394            }
3395
3396            if self.check(&TokenType::Pipe) {
3397                self.advance(); // consume "Pipe"
3398
3399                // Expect "of"
3400                if !self.check_word("of") {
3401                    return Err(ParseError {
3402                        kind: ParseErrorKind::ExpectedKeyword { keyword: "of".to_string() },
3403                        span: self.current_span(),
3404                    });
3405                }
3406                self.advance(); // consume "of"
3407
3408                // Parse element type
3409                let element_type = self.expect_identifier()?;
3410
3411                // Variable registration now handled by DRS
3412
3413                return Ok(Stmt::CreatePipe { var, element_type, capacity: None });
3414            }
3415            // Not a Pipe, backtrack
3416            self.current = saved_pos;
3417        }
3418
3419        // Phase 54: Check for "Launch a task to..." pattern (for task handles)
3420        if self.check(&TokenType::Launch) {
3421            self.advance(); // consume "Launch"
3422
3423            // Expect "a"
3424            if !self.check_article() {
3425                return Err(ParseError {
3426                    kind: ParseErrorKind::ExpectedKeyword { keyword: "a".to_string() },
3427                    span: self.current_span(),
3428                });
3429            }
3430            self.advance();
3431
3432            // Expect "task"
3433            if !self.check(&TokenType::Task) {
3434                return Err(ParseError {
3435                    kind: ParseErrorKind::ExpectedKeyword { keyword: "task".to_string() },
3436                    span: self.current_span(),
3437                });
3438            }
3439            self.advance();
3440
3441            // Expect "to"
3442            if !self.check(&TokenType::To) && !self.check_word("to") {
3443                return Err(ParseError {
3444                    kind: ParseErrorKind::ExpectedKeyword { keyword: "to".to_string() },
3445                    span: self.current_span(),
3446                });
3447            }
3448            self.advance();
3449
3450            // Parse function name
3451            let function = self.expect_identifier()?;
3452
3453            // Parse optional arguments: "with arg1, arg2"
3454            let args = if self.check_word("with") {
3455                self.advance();
3456                self.parse_call_arguments()?
3457            } else {
3458                vec![]
3459            };
3460
3461            return Ok(Stmt::LaunchTaskWithHandle { handle: var, function, args });
3462        }
3463
3464        // Parse expression value (simple: just a number for now)
3465        let value = self.parse_imperative_expr()?;
3466
3467        // Phase 43B: Type check - verify declared type matches value type
3468        if let Some(declared_ty) = &ty {
3469            if let Some(inferred) = self.infer_literal_type(value) {
3470                if !self.check_type_compatibility(declared_ty, inferred) {
3471                    let expected = match declared_ty {
3472                        TypeExpr::Primitive(sym) | TypeExpr::Named(sym) => {
3473                            self.interner.resolve(*sym).to_string()
3474                        }
3475                        _ => "unknown".to_string(),
3476                    };
3477                    return Err(ParseError {
3478                        kind: ParseErrorKind::TypeMismatch {
3479                            expected,
3480                            found: inferred.to_string(),
3481                        },
3482                        span: self.current_span(),
3483                    });
3484                }
3485            }
3486        }
3487
3488        // Check for optional "with capacity <expr>" — wraps value in Expr::WithCapacity
3489        let value = if self.check_word("with") {
3490            let saved = self.current;
3491            self.advance(); // consume "with"
3492            if self.check_word("capacity") {
3493                self.advance(); // consume "capacity"
3494                let cap_expr = self.parse_imperative_expr()?;
3495                self.ctx.alloc_imperative_expr(Expr::WithCapacity { value, capacity: cap_expr })
3496            } else {
3497                self.current = saved; // backtrack — "with" belongs to something else
3498                value
3499            }
3500        } else {
3501            value
3502        };
3503
3504        // Register variable in WorldState's DRS with Owned state for ownership tracking
3505        self.world_state.drs.introduce_referent(var, var, crate::drs::Gender::Unknown, crate::drs::Number::Singular);
3506
3507        self.user_bound.insert(var);
3508        Ok(Stmt::Let { var, ty, value, mutable })
3509    }
3510
3511    fn check_mutable_keyword(&self) -> bool {
3512        // Check for TokenType::Mut (Phase 23b keyword)
3513        if matches!(self.peek().kind, TokenType::Mut) {
3514            return true;
3515        }
3516        // Check for "mutable" or "mut" as Noun/Adjective (backward compatibility)
3517        if let TokenType::Noun(sym) | TokenType::Adjective(sym) = self.peek().kind {
3518            let word = self.interner.resolve(sym).to_lowercase();
3519            word == "mutable" || word == "mut"
3520        } else {
3521            false
3522        }
3523    }
3524
3525    /// Phase 43B: Infer the type of a literal expression
3526    fn infer_literal_type(&self, expr: &Expr<'_>) -> Option<&'static str> {
3527        match expr {
3528            Expr::Literal(lit) => match lit {
3529                crate::ast::Literal::Number(_) => Some("Int"),
3530                crate::ast::Literal::Float(_) => Some("Real"),
3531                crate::ast::Literal::Text(_) => Some("Text"),
3532                crate::ast::Literal::Boolean(_) => Some("Bool"),
3533                crate::ast::Literal::Nothing => Some("Unit"),
3534                crate::ast::Literal::Char(_) => Some("Char"),
3535                crate::ast::Literal::Duration(_) => Some("Duration"),
3536                crate::ast::Literal::Date(_) => Some("Date"),
3537                crate::ast::Literal::Moment(_) => Some("Moment"),
3538                crate::ast::Literal::Span { .. } => Some("Span"),
3539                crate::ast::Literal::Time(_) => Some("Time"),
3540            },
3541            _ => None, // Can't infer type for non-literals yet
3542        }
3543    }
3544
3545    /// Phase 43B: Check if declared type matches inferred type
3546    fn check_type_compatibility(&self, declared: &TypeExpr<'_>, inferred: &str) -> bool {
3547        match declared {
3548            TypeExpr::Primitive(sym) | TypeExpr::Named(sym) => {
3549                let declared_name = self.interner.resolve(*sym);
3550                // Nat and Byte are compatible with Int literals; an integer literal is
3551                // also a valid Rational (`Let x: Rational be 5` → exact 5/1).
3552                declared_name.eq_ignore_ascii_case(inferred)
3553                    || (declared_name.eq_ignore_ascii_case("Nat") && inferred == "Int")
3554                    || (declared_name.eq_ignore_ascii_case("Byte") && inferred == "Int")
3555                    || (declared_name.eq_ignore_ascii_case("Rational") && inferred == "Int")
3556            }
3557            _ => true, // For generics/functions, skip check for now
3558        }
3559    }
3560
3561    // =========================================================================
3562    // Phase 23b: Equals-style Assignment (x = 5)
3563    // =========================================================================
3564
3565    /// Check if current token starts an equals-style assignment.
3566    /// Patterns: `identifier = value` or `identifier: Type = value`
3567    /// True when the statement is a compound assignment — it starts with an
3568    /// l-value root (identifier-like) and has a `+= -= *= /= %=` token before
3569    /// the statement terminator. The compound ops appear nowhere else, so their
3570    /// presence is unambiguous.
3571    fn peek_compound_assignment(&self) -> bool {
3572        let is_identifier = matches!(
3573            self.peek().kind,
3574            TokenType::Noun(_) | TokenType::ProperName(_) | TokenType::Identifier
3575                | TokenType::Adjective(_) | TokenType::Verb { .. }
3576                | TokenType::Particle(_) | TokenType::Ambiguous { .. }
3577                | TokenType::Pronoun { .. }
3578        );
3579        if !is_identifier {
3580            return false;
3581        }
3582        let mut offset = 1;
3583        while self.current + offset < self.tokens.len() {
3584            match &self.tokens[self.current + offset].kind {
3585                TokenType::PlusEq
3586                | TokenType::MinusEq
3587                | TokenType::StarEq
3588                | TokenType::SlashEq
3589                | TokenType::PercentEq => return true,
3590                TokenType::Period | TokenType::Newline | TokenType::EOF => return false,
3591                _ => offset += 1,
3592            }
3593        }
3594        false
3595    }
3596
3597    /// Parse `<lvalue> <op>= <expr>` → `Set <lvalue> to <lvalue> <op> <expr>`.
3598    /// The l-value (identifier / `p.field` / `xs[i]`) is parsed as an
3599    /// expression — it stops cleanly at the compound-op token.
3600    fn parse_compound_assignment(&mut self) -> ParseResult<Stmt<'a>> {
3601        use crate::ast::Expr;
3602        let target = self.parse_imperative_expr()?;
3603        let op = match self.peek().kind {
3604            TokenType::PlusEq => BinaryOpKind::Add,
3605            TokenType::MinusEq => BinaryOpKind::Subtract,
3606            TokenType::StarEq => BinaryOpKind::Multiply,
3607            TokenType::SlashEq => BinaryOpKind::Divide,
3608            TokenType::PercentEq => BinaryOpKind::Modulo,
3609            _ => {
3610                return Err(ParseError {
3611                    kind: ParseErrorKind::ExpectedKeyword { keyword: "+= -= *= /= %=".to_string() },
3612                    span: self.current_span(),
3613                });
3614            }
3615        };
3616        self.advance(); // consume the compound op
3617        let rhs = self.parse_imperative_expr()?;
3618        let combined = self.ctx.alloc_imperative_expr(Expr::BinaryOp { op, left: target, right: rhs });
3619        // Desugar by target shape (mirrors `Set`): identifier → Set (auto-mutable
3620        // via collect_mutable_vars), field → SetField, index → the place desugar.
3621        match target {
3622            Expr::Identifier(sym) => Ok(Stmt::Set { target: *sym, value: combined }),
3623            Expr::FieldAccess { object, field } => {
3624                Ok(Stmt::SetField { object, field: *field, value: combined })
3625            }
3626            Expr::Index { collection, index } => {
3627                Ok(self.desugar_place_set_index(collection, index, combined))
3628            }
3629            _ => Err(ParseError {
3630                kind: ParseErrorKind::ExpectedIdentifier,
3631                span: self.current_span(),
3632            }),
3633        }
3634    }
3635
3636    fn peek_equals_assignment(&self) -> bool {
3637        // Must start with an identifier-like token
3638        // Note: Unknown words default to Adjective in the lexer
3639        // Verbs, Particles, and Ambiguous can also be variable names
3640        let is_identifier = matches!(
3641            self.peek().kind,
3642            TokenType::Noun(_) | TokenType::ProperName(_) | TokenType::Identifier
3643            | TokenType::Adjective(_) | TokenType::Verb { .. }
3644            | TokenType::Particle(_) | TokenType::Ambiguous { .. }
3645            | TokenType::Pronoun { .. }
3646        );
3647        if !is_identifier {
3648            return false;
3649        }
3650
3651        // Check what follows the identifier
3652        if self.current + 1 >= self.tokens.len() {
3653            return false;
3654        }
3655
3656        let next = &self.tokens[self.current + 1].kind;
3657
3658        // Direct assignment: identifier = value
3659        if matches!(next, TokenType::Assign) {
3660            return true;
3661        }
3662
3663        // Type-annotated assignment: identifier: Type = value
3664        // Check for colon, then scan for = before Period/Newline
3665        if matches!(next, TokenType::Colon) {
3666            let mut offset = 2;
3667            while self.current + offset < self.tokens.len() {
3668                let tok = &self.tokens[self.current + offset].kind;
3669                if matches!(tok, TokenType::Assign) {
3670                    return true;
3671                }
3672                if matches!(tok, TokenType::Period | TokenType::Newline | TokenType::EOF) {
3673                    return false;
3674                }
3675                offset += 1;
3676            }
3677        }
3678
3679        false
3680    }
3681
3682    /// Parse equals-style assignment: `x = 5` or `x: Int = 5` or `mut x = 5`
3683    fn parse_equals_assignment(&mut self, explicit_mutable: bool) -> ParseResult<Stmt<'a>> {
3684        // If explicit_mutable is true, we've already checked for Mut token
3685        if explicit_mutable {
3686            self.advance(); // consume "mut"
3687        }
3688
3689        // Get variable name
3690        let var = self.expect_identifier()?;
3691
3692        // Check for optional type annotation: `: Type`
3693        let ty = if self.check(&TokenType::Colon) {
3694            self.advance(); // consume ":"
3695            let type_expr = self.parse_type_expression()?;
3696            Some(self.ctx.alloc_type_expr(type_expr))
3697        } else {
3698            None
3699        };
3700
3701        // Expect '='
3702        if !self.check(&TokenType::Assign) {
3703            return Err(ParseError {
3704                kind: ParseErrorKind::ExpectedKeyword { keyword: "=".to_string() },
3705                span: self.current_span(),
3706            });
3707        }
3708        self.advance(); // consume '='
3709
3710        // Parse value expression
3711        let value = self.parse_imperative_expr()?;
3712
3713        // Check for optional "with capacity <expr>" — wraps value in Expr::WithCapacity
3714        let value = if self.check_word("with") {
3715            let saved = self.current;
3716            self.advance(); // consume "with"
3717            if self.check_word("capacity") {
3718                self.advance(); // consume "capacity"
3719                let cap_expr = self.parse_imperative_expr()?;
3720                self.ctx.alloc_imperative_expr(Expr::WithCapacity { value, capacity: cap_expr })
3721            } else {
3722                self.current = saved; // backtrack
3723                value
3724            }
3725        } else {
3726            value
3727        };
3728
3729        // `x = e` on an EXISTING binding MUTATES it (no silent shadow — the
3730        // loop-body `total = total + i` footgun updated a fresh ghost while
3731        // the outer variable never moved). A `mut`/type-annotated form or a
3732        // fresh name is a new binding. The mutation auto-marks the target
3733        // mutable downstream (codegen derives `let mut` from the `Set`).
3734        if !explicit_mutable && ty.is_none() && self.user_bound.contains(&var) {
3735            return Ok(Stmt::Set { target: var, value });
3736        }
3737
3738        // Register variable in WorldState's DRS
3739        self.world_state.drs.introduce_referent(var, var, crate::drs::Gender::Unknown, crate::drs::Number::Singular);
3740
3741        self.user_bound.insert(var);
3742        Ok(Stmt::Let { var, ty, value, mutable: explicit_mutable })
3743    }
3744
3745    fn parse_set_statement(&mut self) -> ParseResult<Stmt<'a>> {
3746        use crate::ast::Expr;
3747        self.advance(); // consume "Set"
3748
3749        // Parse target - can be identifier or field access expression
3750        let target_expr = self.parse_imperative_expr()?;
3751
3752        // Support "Set X at KEY to VALUE" syntax for map insertion
3753        let target_expr = if self.check(&TokenType::At) {
3754            self.advance(); // consume "at"
3755            let key = self.parse_imperative_expr()?;
3756            self.ctx.alloc_imperative_expr(Expr::Index { collection: target_expr, index: key })
3757        } else {
3758            target_expr
3759        };
3760
3761        // Expect "to" - can be TokenType::To or Preposition("to")
3762        let is_to = self.check(&TokenType::To) || matches!(
3763            &self.peek().kind,
3764            TokenType::Preposition(sym) if self.interner.resolve(*sym) == "to"
3765        );
3766        if !is_to {
3767            return Err(ParseError {
3768                kind: ParseErrorKind::ExpectedKeyword { keyword: "to".to_string() },
3769                span: self.current_span(),
3770            });
3771        }
3772        self.advance(); // consume "to"
3773
3774        // Parse expression value
3775        let value = self.parse_imperative_expr()?;
3776
3777        // Phase 31: Handle field access targets
3778        // Also handle index targets: Set item N of X to Y
3779        match target_expr {
3780            Expr::FieldAccess { object, field } => {
3781                Ok(Stmt::SetField { object: *object, field: *field, value })
3782            }
3783            Expr::Identifier(target) => {
3784                Ok(Stmt::Set { target: *target, value })
3785            }
3786            Expr::Index { collection, index } => {
3787                Ok(self.desugar_place_set_index(*collection, *index, value))
3788            }
3789            _ => Err(ParseError {
3790                kind: ParseErrorKind::ExpectedIdentifier,
3791                span: self.current_span(),
3792            })
3793        }
3794    }
3795
3796    fn parse_return_statement(&mut self) -> ParseResult<Stmt<'a>> {
3797        self.advance(); // consume "Return"
3798
3799        // Check if there's a value or just "Return."
3800        if self.check(&TokenType::Period) || self.is_at_end() {
3801            return Ok(Stmt::Return { value: None });
3802        }
3803
3804        // Use parse_condition (the full value grammar: and/or, comparisons, AND the `in <unit>` /
3805        // `in "<zone>"` postfix) so a Return is as expressive as a Show/Let — `Return q in feet`,
3806        // `Return m in "America/New_York"`, `Return a is before b`.
3807        let value = self.parse_condition()?;
3808        // Trailing-condition guard: `Return X if c.` → `If c: Return X.`
3809        if self.check(&TokenType::If) {
3810            self.advance(); // consume "if"
3811            let cond = self.parse_condition()?;
3812            let ret = Stmt::Return { value: Some(value) };
3813            let then_block = self.ctx.stmts.expect("imperative arenas not initialized")
3814                .alloc_slice(std::iter::once(ret));
3815            return Ok(Stmt::If { cond, then_block, else_block: None });
3816        }
3817        Ok(Stmt::Return { value: Some(value) })
3818    }
3819
3820    fn parse_break_statement(&mut self) -> ParseResult<Stmt<'a>> {
3821        self.advance(); // consume "Break"
3822        Ok(Stmt::Break)
3823    }
3824
3825    fn parse_assert_statement(&mut self) -> ParseResult<Stmt<'a>> {
3826        self.advance(); // consume "Assert"
3827
3828        // Optionally consume "that" (may be tokenized as That or Article(Distal))
3829        if self.check(&TokenType::That) || matches!(self.peek().kind, TokenType::Article(Definiteness::Distal)) {
3830            self.advance();
3831        }
3832
3833        // Parse condition using imperative expression parser
3834        // This allows syntax like "Assert that b is not 0."
3835        let condition = self.parse_condition()?;
3836
3837        Ok(Stmt::RuntimeAssert { condition, hard: false })
3838    }
3839
3840    /// `Require that <cond>.` — an ENFORCED runtime invariant lowering to a hard
3841    /// `assert!` (survives release). Same surface as `Assert that` but `hard: true`;
3842    /// this is the form a proven property check (`Require that check_thm(args).`) uses.
3843    fn parse_require_statement(&mut self) -> ParseResult<Stmt<'a>> {
3844        self.advance(); // consume "Require"
3845
3846        // Optionally consume "that" (may be tokenized as That or Article(Distal)).
3847        if self.check(&TokenType::That) || matches!(self.peek().kind, TokenType::Article(Definiteness::Distal)) {
3848            self.advance();
3849        }
3850
3851        let condition = self.parse_condition()?;
3852        Ok(Stmt::RuntimeAssert { condition, hard: true })
3853    }
3854
3855    /// Phase 35: Parse Trust statement
3856    /// Syntax: Trust [that] [proposition] because [justification].
3857    fn parse_trust_statement(&mut self) -> ParseResult<Stmt<'a>> {
3858        self.advance(); // consume "Trust"
3859
3860        // Optionally consume "that" (may be tokenized as That or Article(Distal))
3861        if self.check(&TokenType::That) || matches!(self.peek().kind, TokenType::Article(Definiteness::Distal)) {
3862            self.advance();
3863        }
3864
3865        // Save current mode and switch to declarative for proposition parsing
3866        let saved_mode = self.mode;
3867        self.mode = ParserMode::Declarative;
3868
3869        // Parse the proposition using the Logic Kernel. The proposition is a
3870        // PREFIX of the statement — it ends where the host keyword "because"
3871        // begins, which is checked just below.
3872        let proposition = self.parse_prefix()?;
3873
3874        // Restore mode
3875        self.mode = saved_mode;
3876
3877        // Expect "because"
3878        if !self.check(&TokenType::Because) {
3879            return Err(ParseError {
3880                kind: ParseErrorKind::UnexpectedToken {
3881                    expected: TokenType::Because,
3882                    found: self.peek().kind.clone(),
3883                },
3884                span: self.current_span(),
3885            });
3886        }
3887        self.advance(); // consume "because"
3888
3889        // Parse justification (string literal)
3890        let justification = match &self.peek().kind {
3891            TokenType::StringLiteral(sym) => {
3892                let s = *sym;
3893                self.advance();
3894                s
3895            }
3896            _ => {
3897                return Err(ParseError {
3898                    kind: ParseErrorKind::UnexpectedToken {
3899                        expected: TokenType::StringLiteral(self.interner.intern("")),
3900                        found: self.peek().kind.clone(),
3901                    },
3902                    span: self.current_span(),
3903                });
3904            }
3905        };
3906
3907        Ok(Stmt::Trust { proposition, justification })
3908    }
3909
3910    /// Phase 50: Parse Check statement - mandatory security guard
3911    /// Syntax: Check that [subject] is [predicate].
3912    /// Syntax: Check that [subject] can [action] the [object].
3913    fn parse_check_statement(&mut self) -> ParseResult<Stmt<'a>> {
3914        let start_span = self.current_span();
3915        self.advance(); // consume "Check"
3916
3917        // Optionally consume "that"
3918        if self.check(&TokenType::That) {
3919            self.advance();
3920        }
3921
3922        // Consume optional "the"
3923        if matches!(self.peek().kind, TokenType::Article(_)) {
3924            self.advance();
3925        }
3926
3927        // Parse subject identifier (e.g., "user")
3928        let subject = match &self.peek().kind {
3929            TokenType::Noun(sym) | TokenType::Adjective(sym) | TokenType::ProperName(sym) => {
3930                let s = *sym;
3931                self.advance();
3932                s
3933            }
3934            _ => {
3935                // Try to get an identifier
3936                let tok = self.peek();
3937                let s = tok.lexeme;
3938                self.advance();
3939                s
3940            }
3941        };
3942
3943        // Determine if this is a predicate check ("is admin") or capability check ("can publish")
3944        let is_capability;
3945        let predicate;
3946        let object;
3947
3948        if self.check(&TokenType::Is) || self.check(&TokenType::Are) {
3949            // Predicate check: "user is admin"
3950            is_capability = false;
3951            self.advance(); // consume "is" / "are"
3952
3953            // Parse predicate name (e.g., "admin")
3954            predicate = match &self.peek().kind {
3955                TokenType::Noun(sym) | TokenType::Adjective(sym) | TokenType::ProperName(sym) => {
3956                    let s = *sym;
3957                    self.advance();
3958                    s
3959                }
3960                _ => {
3961                    let tok = self.peek();
3962                    let s = tok.lexeme;
3963                    self.advance();
3964                    s
3965                }
3966            };
3967            object = None;
3968        } else if self.check(&TokenType::Can) {
3969            // Capability check: "user can publish the document"
3970            is_capability = true;
3971            self.advance(); // consume "can"
3972
3973            // Parse action (e.g., "publish", "edit", "delete")
3974            predicate = match &self.peek().kind {
3975                TokenType::Verb { lemma, .. } => {
3976                    let s = *lemma;
3977                    self.advance();
3978                    s
3979                }
3980                TokenType::Noun(sym) | TokenType::Adjective(sym) | TokenType::ProperName(sym) => {
3981                    let s = *sym;
3982                    self.advance();
3983                    s
3984                }
3985                _ => {
3986                    let tok = self.peek();
3987                    let s = tok.lexeme;
3988                    self.advance();
3989                    s
3990                }
3991            };
3992
3993            // Consume optional "the"
3994            if matches!(self.peek().kind, TokenType::Article(_)) {
3995                self.advance();
3996            }
3997
3998            // Parse object (e.g., "document")
3999            let obj = match &self.peek().kind {
4000                TokenType::Noun(sym) | TokenType::Adjective(sym) | TokenType::ProperName(sym) => {
4001                    let s = *sym;
4002                    self.advance();
4003                    s
4004                }
4005                _ => {
4006                    let tok = self.peek();
4007                    let s = tok.lexeme;
4008                    self.advance();
4009                    s
4010                }
4011            };
4012            object = Some(obj);
4013        } else {
4014            return Err(ParseError {
4015                kind: ParseErrorKind::ExpectedKeyword { keyword: "is/can".to_string() },
4016                span: self.current_span(),
4017            });
4018        }
4019
4020        // Build source text for error message
4021        let source_text = if is_capability {
4022            let obj_name = self.interner.resolve(object.unwrap());
4023            let pred_name = self.interner.resolve(predicate);
4024            let subj_name = self.interner.resolve(subject);
4025            format!("{} can {} the {}", subj_name, pred_name, obj_name)
4026        } else {
4027            let pred_name = self.interner.resolve(predicate);
4028            let subj_name = self.interner.resolve(subject);
4029            format!("{} is {}", subj_name, pred_name)
4030        };
4031
4032        Ok(Stmt::Check {
4033            subject,
4034            predicate,
4035            is_capability,
4036            object,
4037            source_text,
4038            span: start_span,
4039        })
4040    }
4041
4042    /// Read the current token's lexeme as an owned `String` and advance.
4043    fn read_word_string(&mut self) -> String {
4044        let s = self.interner.resolve(self.peek().lexeme).to_string();
4045        self.advance();
4046        s
4047    }
4048
4049    /// Expect a specific keyword (case-insensitive) and advance; else a `Custom` error.
4050    fn expect_keyword(&mut self, word: &str) -> ParseResult<()> {
4051        if self.check_word(word) {
4052            self.advance();
4053            Ok(())
4054        } else {
4055            Err(ParseError {
4056                kind: ParseErrorKind::Custom(format!("expected `{}` here", word)),
4057                span: self.current_span(),
4058            })
4059        }
4060    }
4061
4062    /// Read an integer literal token and advance; else a `Custom` error.
4063    fn read_int_literal(&mut self) -> ParseResult<i64> {
4064        if let TokenType::Number(sym) = &self.peek().kind {
4065            let sym = *sym;
4066            self.advance();
4067            self.interner.resolve(sym).parse::<i64>().map_err(|_| ParseError {
4068                kind: ParseErrorKind::Custom("expected an integer bound".to_string()),
4069                span: self.current_span(),
4070            })
4071        } else {
4072            Err(ParseError {
4073                kind: ParseErrorKind::Custom("expected an integer bound".to_string()),
4074                span: self.current_span(),
4075            })
4076        }
4077    }
4078
4079    /// `Accept computed <Name> where <param> is an Int from <lo> to <hi>` — a parse-time
4080    /// acceptance-contract declaration. Records the named integer bounds (consumed by a later
4081    /// `Run … under <Name>`) and emits NO runtime statement.
4082    fn parse_accept_contract(&mut self) -> ParseResult<()> {
4083        self.advance(); // "Accept"
4084        self.expect_keyword("computed")?;
4085        let name = self.read_word_string();
4086        self.expect_keyword("where")?;
4087        let _param = self.read_word_string(); // the parameter name is documentation only
4088        self.expect_keyword("is")?;
4089        if self.check_word("a") || self.check_word("an") {
4090            self.advance();
4091        }
4092        self.expect_keyword("Int")?;
4093        self.expect_keyword("from")?;
4094        let lo = self.read_int_literal()?;
4095        // "to" can be the `To` token or the bare word.
4096        if self.check(&TokenType::To) || self.check_word("to") {
4097            self.advance();
4098        } else {
4099            return Err(ParseError {
4100                kind: ParseErrorKind::Custom("expected `to` between the contract bounds".to_string()),
4101                span: self.current_span(),
4102            });
4103        }
4104        let hi = self.read_int_literal()?;
4105        if self.check(&TokenType::Period) {
4106            self.advance(); // this declaration `continue`s in parse_program, so eat its own `.`
4107        }
4108        self.contracts.insert(name, (lo, hi));
4109        Ok(())
4110    }
4111
4112    /// `Run <f> on <arg> under <Name> into <var>` → `Let <var> be run_accepted(<f>, <arg>, lo, hi)`,
4113    /// inlining the named acceptance contract's bounds. The trailing `.` is consumed by
4114    /// `parse_program` (this returns a real statement).
4115    fn parse_run_under_contract(&mut self) -> ParseResult<Stmt<'a>> {
4116        self.advance(); // "Run"
4117        let f_expr = self.parse_imperative_expr()?;
4118        self.expect_keyword("on")?;
4119        let arg_expr = self.parse_imperative_expr()?;
4120        self.expect_keyword("under")?;
4121        let name = self.read_word_string();
4122        let (lo, hi) = *self.contracts.get(&name).ok_or_else(|| ParseError {
4123            kind: ParseErrorKind::Custom(format!(
4124                "unknown acceptance contract `{name}` — declare it with `Accept computed {name} where …`"
4125            )),
4126            span: self.current_span(),
4127        })?;
4128        self.expect_keyword("into")?;
4129        let var = self.peek().lexeme;
4130        self.advance();
4131        let lo_e = self.ctx.alloc_imperative_expr(Expr::Literal(crate::ast::Literal::Number(lo)));
4132        let hi_e = self.ctx.alloc_imperative_expr(Expr::Literal(crate::ast::Literal::Number(hi)));
4133        let run_sym = self.interner.intern("run_accepted");
4134        let call = self
4135            .ctx
4136            .alloc_imperative_expr(Expr::Call { function: run_sym, args: vec![f_expr, arg_expr, lo_e, hi_e] });
4137        Ok(Stmt::Let { var, ty: None, value: call, mutable: false })
4138    }
4139
4140    /// Phase 51: Parse Listen statement - bind to network address
4141    /// Syntax: Listen on [address].
4142    fn parse_listen_statement(&mut self) -> ParseResult<Stmt<'a>> {
4143        self.advance(); // consume "Listen"
4144
4145        // Expect "on" preposition
4146        if !self.check_preposition_is("on") {
4147            return Err(ParseError {
4148                kind: ParseErrorKind::ExpectedKeyword { keyword: "on".to_string() },
4149                span: self.current_span(),
4150            });
4151        }
4152        self.advance(); // consume "on"
4153
4154        // Parse address expression (string literal or variable)
4155        let address = self.parse_imperative_expr()?;
4156
4157        let secure = self.parse_secure_clause()?;
4158        Ok(Stmt::Listen { address, secure })
4159    }
4160
4161    /// Phase 51: Parse Connect statement - dial remote peer
4162    /// Syntax: Connect to [address].
4163    fn parse_connect_statement(&mut self) -> ParseResult<Stmt<'a>> {
4164        self.advance(); // consume "Connect"
4165
4166        // Expect "to" (can be TokenType::To or preposition)
4167        if !self.check(&TokenType::To) && !self.check_preposition_is("to") {
4168            return Err(ParseError {
4169                kind: ParseErrorKind::ExpectedKeyword { keyword: "to".to_string() },
4170                span: self.current_span(),
4171            });
4172        }
4173        self.advance(); // consume "to"
4174
4175        // Parse address expression
4176        let address = self.parse_imperative_expr()?;
4177
4178        let secure = self.parse_secure_clause()?;
4179        Ok(Stmt::ConnectTo { address, secure })
4180    }
4181
4182    /// Parse the optional PNP one-time-pad clause `with pad "<path>" as initiator|responder` that may
4183    /// trail a `Connect`/`Listen`. Returns `None` when the clause is absent (a bare statement), so the
4184    /// wire/behavior is byte-identical for programs that don't opt in.
4185    fn parse_secure_clause(&mut self) -> ParseResult<Option<crate::ast::SecurePad<'a>>> {
4186        // Only engage on the two-word lead `with pad`, so a stray `with` elsewhere is left alone.
4187        if !(self.check_word("with") && self.peek_word_at(1, "pad")) {
4188            return Ok(None);
4189        }
4190        self.advance(); // "with"
4191        self.advance(); // "pad"
4192        let pad = self.parse_imperative_expr()?;
4193        if !self.check_word("as") {
4194            return Err(ParseError {
4195                kind: ParseErrorKind::ExpectedKeyword { keyword: "as".to_string() },
4196                span: self.current_span(),
4197            });
4198        }
4199        self.advance(); // "as"
4200        let role = if self.check_word("initiator") {
4201            self.advance();
4202            crate::ast::SecureRole::Initiator
4203        } else if self.check_word("responder") {
4204            self.advance();
4205            crate::ast::SecureRole::Responder
4206        } else {
4207            return Err(ParseError {
4208                kind: ParseErrorKind::ExpectedKeyword { keyword: "initiator or responder".to_string() },
4209                span: self.current_span(),
4210            });
4211        };
4212        Ok(Some(crate::ast::SecurePad { pad, role }))
4213    }
4214
4215    /// Phase 51: Parse Sleep statement - pause execution
4216    /// Syntax: Sleep [milliseconds].
4217    fn parse_sleep_statement(&mut self) -> ParseResult<Stmt<'a>> {
4218        self.advance(); // consume "Sleep"
4219
4220        // Parse milliseconds expression (number or variable)
4221        let milliseconds = self.parse_imperative_expr()?;
4222
4223        Ok(Stmt::Sleep { milliseconds })
4224    }
4225
4226    /// Phase 52: Parse Sync statement - automatic CRDT replication
4227    /// Syntax: Sync [var] on [topic].
4228    fn parse_sync_statement(&mut self) -> ParseResult<Stmt<'a>> {
4229        self.advance(); // consume "Sync"
4230
4231        // Parse variable name (must be an identifier)
4232        // Phase 49: Also handle Verb and Ambiguous tokens (e.g., "state" can be verb or noun)
4233        let var = match &self.tokens[self.current].kind {
4234            TokenType::ProperName(sym) | TokenType::Noun(sym) | TokenType::Adjective(sym) => {
4235                let s = *sym;
4236                self.advance();
4237                s
4238            }
4239            TokenType::Verb { .. } | TokenType::Ambiguous { .. } => {
4240                let s = self.tokens[self.current].lexeme;
4241                self.advance();
4242                s
4243            }
4244            _ => {
4245                return Err(ParseError {
4246                    kind: ParseErrorKind::ExpectedKeyword { keyword: "variable name".to_string() },
4247                    span: self.current_span(),
4248                });
4249            }
4250        };
4251
4252        // Expect "on" preposition
4253        if !self.check_preposition_is("on") {
4254            return Err(ParseError {
4255                kind: ParseErrorKind::ExpectedKeyword { keyword: "on".to_string() },
4256                span: self.current_span(),
4257            });
4258        }
4259        self.advance(); // consume "on"
4260
4261        // Parse topic expression (string literal or variable)
4262        let topic = self.parse_imperative_expr()?;
4263
4264        Ok(Stmt::Sync { var, topic })
4265    }
4266
4267    /// Phase 53: Parse Mount statement
4268    /// Syntax: Mount [var] at [path].
4269    /// Example: Mount counter at "data/counter.journal".
4270    fn parse_mount_statement(&mut self) -> ParseResult<Stmt<'a>> {
4271        self.advance(); // consume "Mount"
4272
4273        // Parse variable name (must be an identifier)
4274        // Phase 49: Also handle Verb and Ambiguous tokens
4275        let var = match &self.tokens[self.current].kind {
4276            TokenType::ProperName(sym) | TokenType::Noun(sym) | TokenType::Adjective(sym) => {
4277                let s = *sym;
4278                self.advance();
4279                s
4280            }
4281            TokenType::Verb { .. } | TokenType::Ambiguous { .. } => {
4282                let s = self.tokens[self.current].lexeme;
4283                self.advance();
4284                s
4285            }
4286            _ => {
4287                return Err(ParseError {
4288                    kind: ParseErrorKind::ExpectedKeyword { keyword: "variable name".to_string() },
4289                    span: self.current_span(),
4290                });
4291            }
4292        };
4293
4294        // Expect "at" keyword (TokenType::At in imperative mode)
4295        if !self.check(&TokenType::At) {
4296            return Err(ParseError {
4297                kind: ParseErrorKind::ExpectedKeyword { keyword: "at".to_string() },
4298                span: self.current_span(),
4299            });
4300        }
4301        self.advance(); // consume "at"
4302
4303        // Parse path expression (string literal or variable)
4304        let path = self.parse_imperative_expr()?;
4305
4306        Ok(Stmt::Mount { var, path })
4307    }
4308
4309    // =========================================================================
4310    // Phase 54: Go-like Concurrency Parser Methods
4311    // =========================================================================
4312
4313    /// Helper: Check if lookahead contains "into" (for Send...into pipe disambiguation)
4314    fn lookahead_contains_into(&self) -> bool {
4315        for i in self.current..std::cmp::min(self.current + 5, self.tokens.len()) {
4316            if matches!(self.tokens[i].kind, TokenType::Into) {
4317                return true;
4318            }
4319        }
4320        false
4321    }
4322
4323    /// Helper: Check if lookahead is "the first of" (for Await select disambiguation)
4324    fn lookahead_is_first_of(&self) -> bool {
4325        // Check for "Await the first of:"
4326        self.current + 3 < self.tokens.len()
4327            && matches!(self.tokens.get(self.current + 1), Some(t) if matches!(t.kind, TokenType::Article(_)))
4328            && self.tokens.get(self.current + 2)
4329                .map(|t| self.interner.resolve(t.lexeme).to_lowercase() == "first")
4330                .unwrap_or(false)
4331    }
4332
4333    /// Phase 54: Parse Launch statement - spawn a task
4334    /// Syntax: Launch a task to verb(args).
4335    fn parse_launch_statement(&mut self) -> ParseResult<Stmt<'a>> {
4336        self.advance(); // consume "Launch"
4337
4338        // Expect "a"
4339        if !self.check_article() {
4340            return Err(ParseError {
4341                kind: ParseErrorKind::ExpectedKeyword { keyword: "a".to_string() },
4342                span: self.current_span(),
4343            });
4344        }
4345        self.advance();
4346
4347        // Expect "task"
4348        if !self.check(&TokenType::Task) {
4349            return Err(ParseError {
4350                kind: ParseErrorKind::ExpectedKeyword { keyword: "task".to_string() },
4351                span: self.current_span(),
4352            });
4353        }
4354        self.advance();
4355
4356        // Expect "to"
4357        if !self.check(&TokenType::To) && !self.check_preposition_is("to") {
4358            return Err(ParseError {
4359                kind: ParseErrorKind::ExpectedKeyword { keyword: "to".to_string() },
4360                span: self.current_span(),
4361            });
4362        }
4363        self.advance();
4364
4365        // Parse function name
4366        // Phase 49: Also handle Verb and Ambiguous tokens (e.g., "greet" can be a verb)
4367        let function = match &self.tokens[self.current].kind {
4368            TokenType::ProperName(sym) | TokenType::Noun(sym) | TokenType::Adjective(sym) => {
4369                let s = *sym;
4370                self.advance();
4371                s
4372            }
4373            TokenType::Verb { .. } | TokenType::Ambiguous { .. } => {
4374                let s = self.tokens[self.current].lexeme;
4375                self.advance();
4376                s
4377            }
4378            _ => {
4379                return Err(ParseError {
4380                    kind: ParseErrorKind::ExpectedKeyword { keyword: "function name".to_string() },
4381                    span: self.current_span(),
4382                });
4383            }
4384        };
4385
4386        // Optional arguments in parentheses or with "with" keyword
4387        let args = if self.check(&TokenType::LParen) {
4388            self.parse_call_arguments()?
4389        } else if self.check_word("with") {
4390            self.advance(); // consume "with"
4391            let mut args = Vec::new();
4392            let arg = self.parse_imperative_expr()?;
4393            args.push(arg);
4394            // Handle additional args separated by "and"
4395            while self.check(&TokenType::And) {
4396                self.advance();
4397                let arg = self.parse_imperative_expr()?;
4398                args.push(arg);
4399            }
4400            args
4401        } else {
4402            Vec::new()
4403        };
4404
4405        Ok(Stmt::LaunchTask { function, args })
4406    }
4407
4408    /// Phase 54: Parse Send into pipe statement
4409    /// Syntax: Send value into pipe.
4410    fn parse_send_pipe_statement(&mut self) -> ParseResult<Stmt<'a>> {
4411        self.advance(); // consume "Send"
4412
4413        // Parse value expression
4414        let value = self.parse_imperative_expr()?;
4415
4416        // Expect "into"
4417        if !self.check(&TokenType::Into) {
4418            return Err(ParseError {
4419                kind: ParseErrorKind::ExpectedKeyword { keyword: "into".to_string() },
4420                span: self.current_span(),
4421            });
4422        }
4423        self.advance();
4424
4425        // Parse pipe expression
4426        let pipe = self.parse_imperative_expr()?;
4427
4428        Ok(Stmt::SendPipe { value, pipe })
4429    }
4430
4431    /// Phase 54: Parse Receive from pipe statement
4432    /// Syntax: Receive x from pipe.
4433    fn parse_receive_pipe_statement(&mut self) -> ParseResult<Stmt<'a>> {
4434        self.advance(); // consume "Receive"
4435
4436        // Get variable name - use expect_identifier which handles various token types
4437        let var = self.expect_identifier()?;
4438
4439        // Expect "from"
4440        if !self.check(&TokenType::From) && !self.check_preposition_is("from") {
4441            return Err(ParseError {
4442                kind: ParseErrorKind::ExpectedKeyword { keyword: "from".to_string() },
4443                span: self.current_span(),
4444            });
4445        }
4446        self.advance();
4447
4448        // Parse pipe expression
4449        let pipe = self.parse_imperative_expr()?;
4450
4451        Ok(Stmt::ReceivePipe { var, pipe })
4452    }
4453
4454    /// Phase 54: Parse Try statement (non-blocking send/receive)
4455    /// Syntax: Try to send x into pipe. OR Try to receive x from pipe.
4456    fn parse_try_statement(&mut self) -> ParseResult<Stmt<'a>> {
4457        self.advance(); // consume "Try"
4458
4459        // Expect "to"
4460        if !self.check(&TokenType::To) && !self.check_preposition_is("to") {
4461            return Err(ParseError {
4462                kind: ParseErrorKind::ExpectedKeyword { keyword: "to".to_string() },
4463                span: self.current_span(),
4464            });
4465        }
4466        self.advance();
4467
4468        // Check if send or receive
4469        if self.check(&TokenType::Send) {
4470            self.advance(); // consume "Send"
4471            let value = self.parse_imperative_expr()?;
4472
4473            if !self.check(&TokenType::Into) {
4474                return Err(ParseError {
4475                    kind: ParseErrorKind::ExpectedKeyword { keyword: "into".to_string() },
4476                    span: self.current_span(),
4477                });
4478            }
4479            self.advance();
4480
4481            let pipe = self.parse_imperative_expr()?;
4482            Ok(Stmt::TrySendPipe { value, pipe, result: None })
4483        } else if self.check(&TokenType::Receive) {
4484            self.advance(); // consume "Receive"
4485
4486            let var = self.expect_identifier()?;
4487
4488            if !self.check(&TokenType::From) && !self.check_preposition_is("from") {
4489                return Err(ParseError {
4490                    kind: ParseErrorKind::ExpectedKeyword { keyword: "from".to_string() },
4491                    span: self.current_span(),
4492                });
4493            }
4494            self.advance();
4495
4496            let pipe = self.parse_imperative_expr()?;
4497            Ok(Stmt::TryReceivePipe { var, pipe })
4498        } else {
4499            Err(ParseError {
4500                kind: ParseErrorKind::ExpectedKeyword { keyword: "send or receive".to_string() },
4501                span: self.current_span(),
4502            })
4503        }
4504    }
4505
4506    /// Shared helper: consume `Escape to <Language>: Indent EscapeBlock Dedent`.
4507    /// Returns (language, code, span).
4508    fn parse_escape_body(&mut self) -> ParseResult<(crate::intern::Symbol, crate::intern::Symbol, crate::token::Span)> {
4509        let start_span = self.current_span();
4510        self.advance(); // consume "Escape"
4511
4512        // Expect "to"
4513        if !self.check(&TokenType::To) && !self.check_preposition_is("to") {
4514            return Err(ParseError {
4515                kind: ParseErrorKind::Custom(
4516                    "Expected 'to' after 'Escape'. Syntax: Escape to Rust:".to_string()
4517                ),
4518                span: self.current_span(),
4519            });
4520        }
4521        self.advance(); // consume "to"
4522
4523        // Parse language name — "Rust" will be tokenized as ProperName
4524        let language = match &self.peek().kind {
4525            TokenType::ProperName(sym) => {
4526                let s = *sym;
4527                self.advance();
4528                s
4529            }
4530            TokenType::Noun(sym) | TokenType::Adjective(sym) => {
4531                let s = *sym;
4532                self.advance();
4533                s
4534            }
4535            _ => {
4536                return Err(ParseError {
4537                    kind: ParseErrorKind::Custom(
4538                        "Expected language name after 'Escape to'. Currently only 'Rust' is supported.".to_string()
4539                    ),
4540                    span: self.current_span(),
4541                });
4542            }
4543        };
4544
4545        // Validate: only "Rust" is supported for now
4546        if !language.is(self.interner, "Rust") {
4547            let lang_str = self.interner.resolve(language);
4548            return Err(ParseError {
4549                kind: ParseErrorKind::Custom(
4550                    format!("Unsupported escape target '{}'. Only 'Rust' is supported.", lang_str)
4551                ),
4552                span: self.current_span(),
4553            });
4554        }
4555
4556        // Expect colon
4557        if !self.check(&TokenType::Colon) {
4558            return Err(ParseError {
4559                kind: ParseErrorKind::Custom(
4560                    "Expected ':' after 'Escape to Rust'. Syntax: Escape to Rust:".to_string()
4561                ),
4562                span: self.current_span(),
4563            });
4564        }
4565        self.advance(); // consume ":"
4566
4567        // Expect Indent (the indented block follows)
4568        if !self.check(&TokenType::Indent) {
4569            return Err(ParseError {
4570                kind: ParseErrorKind::Custom(
4571                    "Expected indented block after 'Escape to Rust:'.".to_string()
4572                ),
4573                span: self.current_span(),
4574            });
4575        }
4576        self.advance(); // consume Indent
4577
4578        // Expect the raw EscapeBlock token
4579        let code = match &self.peek().kind {
4580            TokenType::EscapeBlock(sym) => {
4581                let s = *sym;
4582                self.advance();
4583                s
4584            }
4585            _ => {
4586                return Err(ParseError {
4587                    kind: ParseErrorKind::Custom(
4588                        "Escape block body is empty or malformed.".to_string()
4589                    ),
4590                    span: self.current_span(),
4591                });
4592            }
4593        };
4594
4595        // Expect Dedent (block ends)
4596        if self.check(&TokenType::Dedent) {
4597            self.advance();
4598        }
4599
4600        let end_span = self.previous().span;
4601        Ok((language, code, crate::token::Span::new(start_span.start, end_span.end)))
4602    }
4603
4604    /// Parse an escape hatch block: `Escape to Rust: <indented raw code>`
4605    fn parse_escape_statement(&mut self) -> ParseResult<Stmt<'a>> {
4606        let (language, code, span) = self.parse_escape_body()?;
4607        Ok(Stmt::Escape { language, code, span })
4608    }
4609
4610    /// Parse an escape hatch expression: `Escape to Rust: <indented raw code>`
4611    /// Used in expression position: `Let x: Int be Escape to Rust:`
4612    fn parse_escape_expr(&mut self) -> ParseResult<&'a Expr<'a>> {
4613        let (language, code, _span) = self.parse_escape_body()?;
4614        Ok(self.ctx.alloc_imperative_expr(Expr::Escape { language, code }))
4615    }
4616
4617    /// Parse a `## Requires` block into a list of `Stmt::Require` nodes.
4618    /// Loops until the next block header or EOF, parsing one dependency per line.
4619    fn parse_requires_block(&mut self) -> ParseResult<Vec<Stmt<'a>>> {
4620        let mut deps = Vec::new();
4621
4622        loop {
4623            // Stop at next block header or EOF
4624            if self.is_at_end() {
4625                break;
4626            }
4627            if matches!(self.peek().kind, TokenType::BlockHeader { .. }) {
4628                break;
4629            }
4630
4631            // Skip whitespace tokens
4632            if self.check(&TokenType::Indent)
4633                || self.check(&TokenType::Dedent)
4634                || self.check(&TokenType::Newline)
4635            {
4636                self.advance();
4637                continue;
4638            }
4639
4640            // Each dependency line starts with an article ("The")
4641            if matches!(self.peek().kind, TokenType::Article(_)) {
4642                let dep = self.parse_require_line()?;
4643                deps.push(dep);
4644                continue;
4645            }
4646
4647            // Skip unexpected tokens (defensive)
4648            self.advance();
4649        }
4650
4651        Ok(deps)
4652    }
4653
4654    /// Parse a single dependency line:
4655    /// `The "serde" crate version "1.0" with features "derive" and "std" for serialization.`
4656    fn parse_require_line(&mut self) -> ParseResult<Stmt<'a>> {
4657        let start_span = self.current_span();
4658
4659        // Expect article "The"
4660        if !matches!(self.peek().kind, TokenType::Article(_)) {
4661            return Err(crate::error::ParseError {
4662                kind: crate::error::ParseErrorKind::Custom(
4663                    "Expected 'The' to begin a dependency declaration.".to_string(),
4664                ),
4665                span: self.current_span(),
4666            });
4667        }
4668        self.advance(); // consume "The"
4669
4670        // Expect string literal for crate name
4671        let crate_name = if let TokenType::StringLiteral(sym) = self.peek().kind {
4672            let s = sym;
4673            self.advance();
4674            s
4675        } else {
4676            return Err(crate::error::ParseError {
4677                kind: crate::error::ParseErrorKind::Custom(
4678                    "Expected a string literal for the crate name, e.g. \"serde\".".to_string(),
4679                ),
4680                span: self.current_span(),
4681            });
4682        };
4683
4684        // Expect word "crate"
4685        if !self.check_word("crate") {
4686            return Err(crate::error::ParseError {
4687                kind: crate::error::ParseErrorKind::Custom(
4688                    "Expected the word 'crate' after the crate name.".to_string(),
4689                ),
4690                span: self.current_span(),
4691            });
4692        }
4693        self.advance(); // consume "crate"
4694
4695        // Expect word "version"
4696        if !self.check_word("version") {
4697            return Err(crate::error::ParseError {
4698                kind: crate::error::ParseErrorKind::Custom(
4699                    "Expected 'version' after 'crate'.".to_string(),
4700                ),
4701                span: self.current_span(),
4702            });
4703        }
4704        self.advance(); // consume "version"
4705
4706        // Expect string literal for version
4707        let version = if let TokenType::StringLiteral(sym) = self.peek().kind {
4708            let s = sym;
4709            self.advance();
4710            s
4711        } else {
4712            return Err(crate::error::ParseError {
4713                kind: crate::error::ParseErrorKind::Custom(
4714                    "Expected a string literal for the version, e.g. \"1.0\".".to_string(),
4715                ),
4716                span: self.current_span(),
4717            });
4718        };
4719
4720        // Optional: "with features ..."
4721        let mut features = Vec::new();
4722        if self.check_preposition_is("with") {
4723            self.advance(); // consume "with"
4724
4725            // Expect word "features"
4726            if !self.check_word("features") {
4727                return Err(crate::error::ParseError {
4728                    kind: crate::error::ParseErrorKind::Custom(
4729                        "Expected 'features' after 'with'.".to_string(),
4730                    ),
4731                    span: self.current_span(),
4732                });
4733            }
4734            self.advance(); // consume "features"
4735
4736            // Parse first feature string
4737            if let TokenType::StringLiteral(sym) = self.peek().kind {
4738                features.push(sym);
4739                self.advance();
4740            } else {
4741                return Err(crate::error::ParseError {
4742                    kind: crate::error::ParseErrorKind::Custom(
4743                        "Expected a string literal for a feature name.".to_string(),
4744                    ),
4745                    span: self.current_span(),
4746                });
4747            }
4748
4749            // Parse additional features separated by "and"
4750            while self.check(&TokenType::And) {
4751                self.advance(); // consume "and"
4752                if let TokenType::StringLiteral(sym) = self.peek().kind {
4753                    features.push(sym);
4754                    self.advance();
4755                } else {
4756                    return Err(crate::error::ParseError {
4757                        kind: crate::error::ParseErrorKind::Custom(
4758                            "Expected a string literal for a feature name after 'and'.".to_string(),
4759                        ),
4760                        span: self.current_span(),
4761                    });
4762                }
4763            }
4764        }
4765
4766        // Optional: "for <description...>" — consume until period
4767        if self.check(&TokenType::For) {
4768            self.advance(); // consume "for"
4769            while !self.check(&TokenType::Period) && !self.check(&TokenType::EOF)
4770                && !self.check(&TokenType::Newline)
4771                && !matches!(self.peek().kind, TokenType::BlockHeader { .. })
4772            {
4773                self.advance();
4774            }
4775        }
4776
4777        // Consume trailing period
4778        if self.check(&TokenType::Period) {
4779            self.advance();
4780        }
4781
4782        let end_span = self.previous().span;
4783
4784        Ok(Stmt::Require {
4785            crate_name,
4786            version,
4787            features,
4788            span: crate::token::Span::new(start_span.start, end_span.end),
4789        })
4790    }
4791
4792    /// Phase 54: Parse Stop statement
4793    /// Syntax: Stop handle.
4794    fn parse_stop_statement(&mut self) -> ParseResult<Stmt<'a>> {
4795        self.advance(); // consume "Stop"
4796
4797        let handle = self.parse_imperative_expr()?;
4798
4799        Ok(Stmt::StopTask { handle })
4800    }
4801
4802    /// Phase 54: Parse Select statement
4803    /// Syntax:
4804    /// Await the first of:
4805    ///     Receive x from pipe:
4806    ///         ...
4807    ///     After N seconds:
4808    ///         ...
4809    fn parse_select_statement(&mut self) -> ParseResult<Stmt<'a>> {
4810        use crate::ast::stmt::SelectBranch;
4811
4812        self.advance(); // consume "Await"
4813
4814        // Expect "the"
4815        if !self.check_article() {
4816            return Err(ParseError {
4817                kind: ParseErrorKind::ExpectedKeyword { keyword: "the".to_string() },
4818                span: self.current_span(),
4819            });
4820        }
4821        self.advance();
4822
4823        // Expect "first"
4824        if !self.check_word("first") {
4825            return Err(ParseError {
4826                kind: ParseErrorKind::ExpectedKeyword { keyword: "first".to_string() },
4827                span: self.current_span(),
4828            });
4829        }
4830        self.advance();
4831
4832        // Expect "of"
4833        if !self.check_preposition_is("of") {
4834            return Err(ParseError {
4835                kind: ParseErrorKind::ExpectedKeyword { keyword: "of".to_string() },
4836                span: self.current_span(),
4837            });
4838        }
4839        self.advance();
4840
4841        // Expect colon
4842        if !self.check(&TokenType::Colon) {
4843            return Err(ParseError {
4844                kind: ParseErrorKind::ExpectedKeyword { keyword: ":".to_string() },
4845                span: self.current_span(),
4846            });
4847        }
4848        self.advance();
4849
4850        // Expect indent
4851        if !self.check(&TokenType::Indent) {
4852            return Err(ParseError {
4853                kind: ParseErrorKind::ExpectedStatement,
4854                span: self.current_span(),
4855            });
4856        }
4857        self.advance();
4858
4859        // Parse branches
4860        let mut branches = Vec::new();
4861        while !self.check(&TokenType::Dedent) && !self.is_at_end() {
4862            let branch = self.parse_select_branch()?;
4863            branches.push(branch);
4864        }
4865
4866        // Consume dedent
4867        if self.check(&TokenType::Dedent) {
4868            self.advance();
4869        }
4870
4871        Ok(Stmt::Select { branches })
4872    }
4873
4874    /// Phase 54: Parse a single select branch
4875    fn parse_select_branch(&mut self) -> ParseResult<crate::ast::stmt::SelectBranch<'a>> {
4876        use crate::ast::stmt::SelectBranch;
4877
4878        if self.check(&TokenType::Receive) {
4879            self.advance(); // consume "Receive"
4880
4881            let var = match &self.tokens[self.current].kind {
4882                TokenType::ProperName(sym) | TokenType::Noun(sym) | TokenType::Adjective(sym) => {
4883                    let s = *sym;
4884                    self.advance();
4885                    s
4886                }
4887                _ => {
4888                    return Err(ParseError {
4889                        kind: ParseErrorKind::ExpectedKeyword { keyword: "variable name".to_string() },
4890                        span: self.current_span(),
4891                    });
4892                }
4893            };
4894
4895            if !self.check(&TokenType::From) && !self.check_preposition_is("from") {
4896                return Err(ParseError {
4897                    kind: ParseErrorKind::ExpectedKeyword { keyword: "from".to_string() },
4898                    span: self.current_span(),
4899                });
4900            }
4901            self.advance();
4902
4903            let pipe = self.parse_imperative_expr()?;
4904
4905            // Expect colon
4906            if !self.check(&TokenType::Colon) {
4907                return Err(ParseError {
4908                    kind: ParseErrorKind::ExpectedKeyword { keyword: ":".to_string() },
4909                    span: self.current_span(),
4910                });
4911            }
4912            self.advance();
4913
4914            // Parse body
4915            let body = self.parse_indented_block()?;
4916
4917            Ok(SelectBranch::Receive { var, pipe, body })
4918        } else if self.check_word("after") {
4919            self.advance(); // consume "After"
4920
4921            let milliseconds = self.parse_imperative_expr()?;
4922
4923            // Skip "seconds" or "milliseconds" if present
4924            if self.check_word("seconds") || self.check_word("milliseconds") {
4925                self.advance();
4926            }
4927
4928            // Expect colon
4929            if !self.check(&TokenType::Colon) {
4930                return Err(ParseError {
4931                    kind: ParseErrorKind::ExpectedKeyword { keyword: ":".to_string() },
4932                    span: self.current_span(),
4933                });
4934            }
4935            self.advance();
4936
4937            // Parse body
4938            let body = self.parse_indented_block()?;
4939
4940            Ok(SelectBranch::Timeout { milliseconds, body })
4941        } else {
4942            Err(ParseError {
4943                kind: ParseErrorKind::ExpectedKeyword { keyword: "Receive or After".to_string() },
4944                span: self.current_span(),
4945            })
4946        }
4947    }
4948
4949    /// Phase 54: Parse an indented block of statements
4950    /// Depth-guarded wrapper: nested blocks recurse here once per level, so
4951    /// a block pyramid errors gracefully instead of overflowing the parser.
4952    fn parse_indented_block(&mut self) -> ParseResult<crate::ast::stmt::Block<'a>> {
4953        self.enter_recursion()?;
4954        let result = self.parse_indented_block_inner();
4955        self.leave_recursion();
4956        result
4957    }
4958
4959    fn parse_indented_block_inner(&mut self) -> ParseResult<crate::ast::stmt::Block<'a>> {
4960        // Expect indent
4961        if !self.check(&TokenType::Indent) {
4962            return Err(ParseError {
4963                kind: ParseErrorKind::ExpectedStatement,
4964                span: self.current_span(),
4965            });
4966        }
4967        self.advance();
4968
4969        let mut stmts = Vec::new();
4970        while !self.check(&TokenType::Dedent) && !self.is_at_end() {
4971            let stmt = self.parse_statement()?;
4972            stmts.push(stmt);
4973            if self.check(&TokenType::Period) {
4974                self.advance();
4975            }
4976        }
4977
4978        // Consume dedent
4979        if self.check(&TokenType::Dedent) {
4980            self.advance();
4981        }
4982
4983        let block = self.ctx.stmts.expect("imperative arenas not initialized")
4984            .alloc_slice(stmts.into_iter());
4985
4986        Ok(block)
4987    }
4988
4989    fn parse_give_statement(&mut self) -> ParseResult<Stmt<'a>> {
4990        self.advance(); // consume "Give"
4991
4992        // Parse the object being given: "x" or "the data"
4993        let object = self.parse_imperative_expr()?;
4994
4995        // Expect "to" preposition (can be TokenType::To when followed by verb-like word)
4996        if !self.check_to_preposition() {
4997            return Err(ParseError {
4998                kind: ParseErrorKind::ExpectedKeyword { keyword: "to".to_string() },
4999                span: self.current_span(),
5000            });
5001        }
5002        self.advance(); // consume "to"
5003
5004        // Parse the recipient: "processor" or "the console"
5005        let recipient = self.parse_imperative_expr()?;
5006
5007        // Mark variable as Moved after Give
5008        if let Expr::Identifier(sym) = object {
5009            self.world_state.set_ownership_by_var(*sym, crate::drs::OwnershipState::Moved);
5010        }
5011
5012        Ok(Stmt::Give { object, recipient })
5013    }
5014
5015    fn parse_show_statement(&mut self) -> ParseResult<Stmt<'a>> {
5016        self.advance(); // consume "Show"
5017
5018        // Parse the object being shown - use parse_condition to support
5019        // comparisons (x is less than y) and boolean operators (a and b)
5020        let object = self.parse_condition()?;
5021
5022        // Optional "to" preposition - if not present, default to "show" function
5023        // Note: Use check_to_preposition() to handle both TokenType::To (when followed by verb)
5024        // and TokenType::Preposition("to")
5025        let recipient = if self.check_to_preposition() {
5026            self.advance(); // consume "to"
5027
5028            // Phase 10: "Show x to console." or "Show x to the console."
5029            // is idiomatic for printing to stdout - use default show function
5030            if self.check_article() {
5031                self.advance(); // skip "the"
5032            }
5033            if self.check(&TokenType::Console) {
5034                self.advance(); // consume "console"
5035                let show_sym = self.interner.intern("show");
5036                self.ctx.alloc_imperative_expr(Expr::Identifier(show_sym))
5037            } else {
5038                // Parse the recipient: custom function
5039                self.parse_imperative_expr()?
5040            }
5041        } else {
5042            // Default recipient: the runtime "show" function
5043            let show_sym = self.interner.intern("show");
5044            self.ctx.alloc_imperative_expr(Expr::Identifier(show_sym))
5045        };
5046
5047        // Mark variable as Borrowed after Show
5048        if let Expr::Identifier(sym) = object {
5049            self.world_state.set_ownership_by_var(*sym, crate::drs::OwnershipState::Borrowed);
5050        }
5051
5052        Ok(Stmt::Show { object, recipient })
5053    }
5054
5055    /// Phase 43D: Parse Push statement for collection operations
5056    /// Syntax: Push x to items.
5057    fn parse_push_statement(&mut self) -> ParseResult<Stmt<'a>> {
5058        self.advance(); // consume "Push"
5059
5060        // Parse the value(s) being pushed — `Push a, b, c to xs.` appends each
5061        // in order (desugars to one Push per element).
5062        let mut values = vec![self.parse_imperative_expr()?];
5063        while self.check(&TokenType::Comma) {
5064            self.advance(); // consume ","
5065            values.push(self.parse_imperative_expr()?);
5066        }
5067
5068        // Expect "to" (can be keyword or preposition)
5069        if !self.check(&TokenType::To) && !self.check_preposition_is("to") {
5070            return Err(ParseError {
5071                kind: ParseErrorKind::ExpectedKeyword { keyword: "to".to_string() },
5072                span: self.current_span(),
5073            });
5074        }
5075        self.advance(); // consume "to"
5076
5077        // Parse the collection
5078        let collection = self.parse_imperative_expr()?;
5079
5080        if values.len() == 1 {
5081            Ok(self.desugar_place_push(values[0], collection))
5082        } else {
5083            let mut pushes = Vec::with_capacity(values.len());
5084            for value in values {
5085                pushes.push(self.desugar_place_push(value, collection));
5086            }
5087            let body = self.ctx.stmts.expect("imperative arenas not initialized")
5088                .alloc_slice(pushes.into_iter());
5089            Ok(Stmt::Splice { body })
5090        }
5091    }
5092
5093    /// Phase 43D: Parse Pop statement for collection operations
5094    /// Syntax: Pop from items. OR Pop from items into y.
5095    fn parse_pop_statement(&mut self) -> ParseResult<Stmt<'a>> {
5096        self.advance(); // consume "Pop"
5097
5098        // Expect "from" - can be keyword token or preposition
5099        if !self.check(&TokenType::From) && !self.check_preposition_is("from") {
5100            return Err(ParseError {
5101                kind: ParseErrorKind::ExpectedKeyword { keyword: "from".to_string() },
5102                span: self.current_span(),
5103            });
5104        }
5105        self.advance(); // consume "from"
5106
5107        // Parse the collection
5108        let collection = self.parse_imperative_expr()?;
5109
5110        // Check for optional "into" binding (can be Into keyword or preposition)
5111        let into = if self.check(&TokenType::Into) || self.check_preposition_is("into") {
5112            self.advance(); // consume "into"
5113
5114            // Parse variable name
5115            if let TokenType::Noun(sym) | TokenType::ProperName(sym) = &self.peek().kind {
5116                let sym = *sym;
5117                self.advance();
5118                Some(sym)
5119            } else if let Some(token) = self.tokens.get(self.current) {
5120                // Also handle identifier-like tokens
5121                let sym = token.lexeme;
5122                self.advance();
5123                Some(sym)
5124            } else {
5125                return Err(ParseError {
5126                    kind: ParseErrorKind::ExpectedIdentifier,
5127                    span: self.current_span(),
5128                });
5129            }
5130        } else {
5131            None
5132        };
5133
5134        Ok(Stmt::Pop { collection, into })
5135    }
5136
5137    /// Parse Add statement for Set insertion
5138    /// Syntax: Add x to set.
5139    fn parse_add_statement(&mut self) -> ParseResult<Stmt<'a>> {
5140        self.advance(); // consume "Add"
5141
5142        // Parse the value to add
5143        let value = self.parse_imperative_expr()?;
5144
5145        // Expect "to" preposition
5146        if !self.check_preposition_is("to") && !self.check(&TokenType::To) {
5147            return Err(ParseError {
5148                kind: ParseErrorKind::ExpectedKeyword { keyword: "to".to_string() },
5149                span: self.current_span(),
5150            });
5151        }
5152        self.advance(); // consume "to"
5153
5154        // Parse the collection expression
5155        let collection = self.parse_imperative_expr()?;
5156
5157        Ok(Stmt::Add { value, collection })
5158    }
5159
5160    /// Parse Remove statement for Set deletion
5161    /// Syntax: Remove x from set.
5162    fn parse_remove_statement(&mut self) -> ParseResult<Stmt<'a>> {
5163        self.advance(); // consume "Remove"
5164
5165        // Parse the value to remove
5166        let value = self.parse_imperative_expr()?;
5167
5168        // Expect "from" preposition
5169        if !self.check(&TokenType::From) && !self.check_preposition_is("from") {
5170            return Err(ParseError {
5171                kind: ParseErrorKind::ExpectedKeyword { keyword: "from".to_string() },
5172                span: self.current_span(),
5173            });
5174        }
5175        self.advance(); // consume "from"
5176
5177        // Parse the collection expression
5178        let collection = self.parse_imperative_expr()?;
5179
5180        Ok(Stmt::Remove { value, collection })
5181    }
5182
5183    /// Phase 10: Parse Read statement for console/file input
5184    /// Syntax: Read <var> from the console.
5185    ///         Read <var> from file <path>.
5186    fn parse_read_statement(&mut self) -> ParseResult<Stmt<'a>> {
5187        self.advance(); // consume "Read"
5188
5189        // Get the variable name
5190        let var = self.expect_identifier()?;
5191
5192        // Expect "from" preposition
5193        if !self.check(&TokenType::From) && !self.check_preposition_is("from") {
5194            return Err(ParseError {
5195                kind: ParseErrorKind::ExpectedKeyword { keyword: "from".to_string() },
5196                span: self.current_span(),
5197            });
5198        }
5199        self.advance(); // consume "from"
5200
5201        // Skip optional article "the"
5202        if self.check_article() {
5203            self.advance();
5204        }
5205
5206        // Determine source: console or file
5207        let source = if self.check(&TokenType::Console) {
5208            self.advance(); // consume "console"
5209            ReadSource::Console
5210        } else if self.check(&TokenType::File) {
5211            self.advance(); // consume "file"
5212            let path = self.parse_imperative_expr()?;
5213            ReadSource::File(path)
5214        } else {
5215            return Err(ParseError {
5216                kind: ParseErrorKind::ExpectedKeyword { keyword: "console or file".to_string() },
5217                span: self.current_span(),
5218            });
5219        };
5220
5221        Ok(Stmt::ReadFrom { var, source })
5222    }
5223
5224    /// Phase 10: Parse Write statement for file output
5225    /// Syntax: Write <content> to file <path>.
5226    fn parse_write_statement(&mut self) -> ParseResult<Stmt<'a>> {
5227        self.advance(); // consume "Write"
5228
5229        // Parse the content expression
5230        let content = self.parse_imperative_expr()?;
5231
5232        // Expect "to" (can be keyword or preposition)
5233        if !self.check(&TokenType::To) && !self.check_preposition_is("to") {
5234            return Err(ParseError {
5235                kind: ParseErrorKind::ExpectedKeyword { keyword: "to".to_string() },
5236                span: self.current_span(),
5237            });
5238        }
5239        self.advance(); // consume "to"
5240
5241        // Expect "file" keyword
5242        if !self.check(&TokenType::File) {
5243            return Err(ParseError {
5244                kind: ParseErrorKind::ExpectedKeyword { keyword: "file".to_string() },
5245                span: self.current_span(),
5246            });
5247        }
5248        self.advance(); // consume "file"
5249
5250        // Parse the path expression
5251        let path = self.parse_imperative_expr()?;
5252
5253        Ok(Stmt::WriteFile { content, path })
5254    }
5255
5256    /// Phase 8.5: Parse Zone statement for memory arena blocks
5257    /// Syntax variants:
5258    ///   - Inside a new zone called "Scratch":
5259    ///   - Inside a zone called "Buffer" of size 1 MB:
5260    ///   - Inside a zone called "Data" mapped from "file.bin":
5261    fn parse_zone_statement(&mut self) -> ParseResult<Stmt<'a>> {
5262        self.advance(); // consume "Inside"
5263
5264        // Optional article "a"
5265        if self.check_article() {
5266            self.advance();
5267        }
5268
5269        // Optional "new"
5270        if self.check(&TokenType::New) {
5271            self.advance();
5272        }
5273
5274        // Expect "zone"
5275        if !self.check(&TokenType::Zone) {
5276            return Err(ParseError {
5277                kind: ParseErrorKind::ExpectedKeyword { keyword: "zone".to_string() },
5278                span: self.current_span(),
5279            });
5280        }
5281        self.advance(); // consume "zone"
5282
5283        // Expect "called"
5284        if !self.check(&TokenType::Called) {
5285            return Err(ParseError {
5286                kind: ParseErrorKind::ExpectedKeyword { keyword: "called".to_string() },
5287                span: self.current_span(),
5288            });
5289        }
5290        self.advance(); // consume "called"
5291
5292        // Parse zone name (can be string literal or identifier)
5293        let name = match &self.peek().kind {
5294            TokenType::StringLiteral(sym) => {
5295                let s = *sym;
5296                self.advance();
5297                s
5298            }
5299            TokenType::ProperName(sym) | TokenType::Noun(sym) | TokenType::Adjective(sym) => {
5300                let s = *sym;
5301                self.advance();
5302                s
5303            }
5304            _ => {
5305                // Try to use the lexeme directly as an identifier
5306                let token = self.peek().clone();
5307                self.advance();
5308                token.lexeme
5309            }
5310        };
5311
5312        let mut capacity = None;
5313        let mut source_file = None;
5314
5315        // Check for "mapped from" (file-backed zone)
5316        if self.check(&TokenType::Mapped) {
5317            self.advance(); // consume "mapped"
5318
5319            // Expect "from"
5320            if !self.check(&TokenType::From) && !self.check_preposition_is("from") {
5321                return Err(ParseError {
5322                    kind: ParseErrorKind::ExpectedKeyword { keyword: "from".to_string() },
5323                    span: self.current_span(),
5324                });
5325            }
5326            self.advance(); // consume "from"
5327
5328            // Parse file path — a string literal, or a variable holding the path
5329            // (the path is often known only at runtime, e.g. a function parameter).
5330            if let TokenType::StringLiteral(path) = &self.peek().kind {
5331                let p = *path;
5332                self.advance();
5333                source_file = Some(crate::ast::stmt::ZoneSource::Literal(p));
5334            } else {
5335                let var = self.expect_identifier()?;
5336                source_file = Some(crate::ast::stmt::ZoneSource::Variable(var));
5337            }
5338        }
5339        // Check for "of size N Unit" (sized heap zone)
5340        else if self.check_of_preposition() {
5341            self.advance(); // consume "of"
5342
5343            // Expect "size"
5344            if !self.check(&TokenType::Size) {
5345                return Err(ParseError {
5346                    kind: ParseErrorKind::ExpectedKeyword { keyword: "size".to_string() },
5347                    span: self.current_span(),
5348                });
5349            }
5350            self.advance(); // consume "size"
5351
5352            // Parse size number
5353            let size_value = match &self.peek().kind {
5354                TokenType::Number(sym) => {
5355                    let num_str = self.interner.resolve(*sym).to_string();
5356                    let val = self.parse_i64_numeral(&num_str)? as usize;
5357                    self.advance();
5358                    val
5359                }
5360                TokenType::Cardinal(n) => {
5361                    let val = *n as usize;
5362                    self.advance();
5363                    val
5364                }
5365                _ => {
5366                    return Err(ParseError {
5367                        kind: ParseErrorKind::ExpectedNumber,
5368                        span: self.current_span(),
5369                    });
5370                }
5371            };
5372
5373            // Parse unit (KB, MB, GB, or B)
5374            let unit_multiplier = self.parse_size_unit()?;
5375            capacity = Some(size_value * unit_multiplier);
5376        }
5377
5378        // Expect colon
5379        if !self.check(&TokenType::Colon) {
5380            return Err(ParseError {
5381                kind: ParseErrorKind::ExpectedKeyword { keyword: ":".to_string() },
5382                span: self.current_span(),
5383            });
5384        }
5385        self.advance(); // consume ":"
5386
5387        // Expect indent
5388        if !self.check(&TokenType::Indent) {
5389            return Err(ParseError {
5390                kind: ParseErrorKind::ExpectedStatement,
5391                span: self.current_span(),
5392            });
5393        }
5394        self.advance(); // consume Indent
5395
5396        // Parse body statements
5397        let mut body_stmts = Vec::new();
5398        while !self.check(&TokenType::Dedent) && !self.is_at_end() {
5399            let stmt = self.parse_statement()?;
5400            body_stmts.push(stmt);
5401            if self.check(&TokenType::Period) {
5402                self.advance();
5403            }
5404        }
5405
5406        // Consume dedent
5407        if self.check(&TokenType::Dedent) {
5408            self.advance();
5409        }
5410
5411        let body = self.ctx.stmts.expect("imperative arenas not initialized")
5412            .alloc_slice(body_stmts.into_iter());
5413
5414        Ok(Stmt::Zone { name, capacity, source_file, body })
5415    }
5416
5417    /// Parse size unit (B, KB, MB, GB) and return multiplier
5418    fn parse_size_unit(&mut self) -> ParseResult<usize> {
5419        let token = self.peek().clone();
5420        let unit_str = self.interner.resolve(token.lexeme).to_uppercase();
5421        self.advance();
5422
5423        match unit_str.as_str() {
5424            "B" | "BYTES" | "BYTE" => Ok(1),
5425            "KB" | "KILOBYTE" | "KILOBYTES" => Ok(1024),
5426            "MB" | "MEGABYTE" | "MEGABYTES" => Ok(1024 * 1024),
5427            "GB" | "GIGABYTE" | "GIGABYTES" => Ok(1024 * 1024 * 1024),
5428            _ => Err(ParseError {
5429                kind: ParseErrorKind::ExpectedKeyword {
5430                    keyword: "size unit (B, KB, MB, GB)".to_string(),
5431                },
5432                span: token.span,
5433            }),
5434        }
5435    }
5436
5437    /// Phase 9: Parse concurrent execution block (async, I/O-bound)
5438    ///
5439    /// Syntax:
5440    /// ```logos
5441    /// Attempt all of the following:
5442    ///     Call fetch_user with id.
5443    ///     Call fetch_orders with id.
5444    /// ```
5445    fn parse_concurrent_block(&mut self) -> ParseResult<Stmt<'a>> {
5446        self.advance(); // consume "Attempt"
5447
5448        // Expect "all"
5449        if !self.check(&TokenType::All) {
5450            return Err(ParseError {
5451                kind: ParseErrorKind::ExpectedKeyword { keyword: "all".to_string() },
5452                span: self.current_span(),
5453            });
5454        }
5455        self.advance(); // consume "all"
5456
5457        // Expect "of" (preposition)
5458        if !self.check_of_preposition() {
5459            return Err(ParseError {
5460                kind: ParseErrorKind::ExpectedKeyword { keyword: "of".to_string() },
5461                span: self.current_span(),
5462            });
5463        }
5464        self.advance(); // consume "of"
5465
5466        // Expect "the"
5467        if !self.check_article() {
5468            return Err(ParseError {
5469                kind: ParseErrorKind::ExpectedKeyword { keyword: "the".to_string() },
5470                span: self.current_span(),
5471            });
5472        }
5473        self.advance(); // consume "the"
5474
5475        // Expect "following"
5476        if !self.check(&TokenType::Following) {
5477            return Err(ParseError {
5478                kind: ParseErrorKind::ExpectedKeyword { keyword: "following".to_string() },
5479                span: self.current_span(),
5480            });
5481        }
5482        self.advance(); // consume "following"
5483
5484        // Expect colon
5485        if !self.check(&TokenType::Colon) {
5486            return Err(ParseError {
5487                kind: ParseErrorKind::ExpectedKeyword { keyword: ":".to_string() },
5488                span: self.current_span(),
5489            });
5490        }
5491        self.advance(); // consume ":"
5492
5493        // Expect indent
5494        if !self.check(&TokenType::Indent) {
5495            return Err(ParseError {
5496                kind: ParseErrorKind::ExpectedStatement,
5497                span: self.current_span(),
5498            });
5499        }
5500        self.advance(); // consume Indent
5501
5502        // Parse body statements
5503        let mut task_stmts = Vec::new();
5504        while !self.check(&TokenType::Dedent) && !self.is_at_end() {
5505            let stmt = self.parse_statement()?;
5506            task_stmts.push(stmt);
5507            if self.check(&TokenType::Period) {
5508                self.advance();
5509            }
5510        }
5511
5512        // Consume dedent
5513        if self.check(&TokenType::Dedent) {
5514            self.advance();
5515        }
5516
5517        let tasks = self.ctx.stmts.expect("imperative arenas not initialized")
5518            .alloc_slice(task_stmts.into_iter());
5519
5520        Ok(Stmt::Concurrent { tasks })
5521    }
5522
5523    /// Phase 9: Parse parallel execution block (CPU-bound)
5524    ///
5525    /// Syntax:
5526    /// ```logos
5527    /// Simultaneously:
5528    ///     Call compute_hash with data1.
5529    ///     Call compute_hash with data2.
5530    /// ```
5531    fn parse_parallel_block(&mut self) -> ParseResult<Stmt<'a>> {
5532        self.advance(); // consume "Simultaneously"
5533
5534        // Expect colon
5535        if !self.check(&TokenType::Colon) {
5536            return Err(ParseError {
5537                kind: ParseErrorKind::ExpectedKeyword { keyword: ":".to_string() },
5538                span: self.current_span(),
5539            });
5540        }
5541        self.advance(); // consume ":"
5542
5543        // Expect indent
5544        if !self.check(&TokenType::Indent) {
5545            return Err(ParseError {
5546                kind: ParseErrorKind::ExpectedStatement,
5547                span: self.current_span(),
5548            });
5549        }
5550        self.advance(); // consume Indent
5551
5552        // Parse body statements
5553        let mut task_stmts = Vec::new();
5554        while !self.check(&TokenType::Dedent) && !self.is_at_end() {
5555            let stmt = self.parse_statement()?;
5556            task_stmts.push(stmt);
5557            if self.check(&TokenType::Period) {
5558                self.advance();
5559            }
5560        }
5561
5562        // Consume dedent
5563        if self.check(&TokenType::Dedent) {
5564            self.advance();
5565        }
5566
5567        let tasks = self.ctx.stmts.expect("imperative arenas not initialized")
5568            .alloc_slice(task_stmts.into_iter());
5569
5570        Ok(Stmt::Parallel { tasks })
5571    }
5572
5573    /// Phase 33: Parse Inspect statement for pattern matching
5574    /// Syntax: Inspect target:
5575    ///             If it is a Variant [(bindings)]:
5576    ///                 body...
5577    ///             Otherwise:
5578    ///                 body...
5579    fn parse_inspect_statement(&mut self) -> ParseResult<Stmt<'a>> {
5580        self.advance(); // consume "Inspect"
5581
5582        // Parse target expression
5583        let target = self.parse_imperative_expr()?;
5584
5585        // Expect colon
5586        if !self.check(&TokenType::Colon) {
5587            return Err(ParseError {
5588                kind: ParseErrorKind::ExpectedKeyword { keyword: ":".to_string() },
5589                span: self.current_span(),
5590            });
5591        }
5592        self.advance(); // consume ":"
5593
5594        // Expect indent
5595        if !self.check(&TokenType::Indent) {
5596            return Err(ParseError {
5597                kind: ParseErrorKind::ExpectedStatement,
5598                span: self.current_span(),
5599            });
5600        }
5601        self.advance(); // consume Indent
5602
5603        let mut arms = Vec::new();
5604        let mut has_otherwise = false;
5605
5606        // Parse match arms until dedent
5607        while !self.check(&TokenType::Dedent) && !self.is_at_end() {
5608            if self.check(&TokenType::Otherwise) {
5609                // Parse "Otherwise:" default arm
5610                self.advance(); // consume "Otherwise"
5611
5612                if !self.check(&TokenType::Colon) {
5613                    return Err(ParseError {
5614                        kind: ParseErrorKind::ExpectedKeyword { keyword: ":".to_string() },
5615                        span: self.current_span(),
5616                    });
5617                }
5618                self.advance(); // consume ":"
5619
5620                // Handle both inline (Otherwise: stmt.) and block body
5621                let body_stmts = if self.check(&TokenType::Indent) {
5622                    self.advance(); // consume Indent
5623                    let mut stmts = Vec::new();
5624                    while !self.check(&TokenType::Dedent) && !self.is_at_end() {
5625                        let stmt = self.parse_statement()?;
5626                        stmts.push(stmt);
5627                        if self.check(&TokenType::Period) {
5628                            self.advance();
5629                        }
5630                    }
5631                    if self.check(&TokenType::Dedent) {
5632                        self.advance();
5633                    }
5634                    stmts
5635                } else {
5636                    // Inline body: "Otherwise: Show x."
5637                    let stmt = self.parse_statement()?;
5638                    if self.check(&TokenType::Period) {
5639                        self.advance();
5640                    }
5641                    vec![stmt]
5642                };
5643
5644                let body = self.ctx.stmts.expect("imperative arenas not initialized")
5645                    .alloc_slice(body_stmts.into_iter());
5646
5647                arms.push(MatchArm { enum_name: None, variant: None, bindings: vec![], body });
5648                has_otherwise = true;
5649                break;
5650            }
5651
5652            if self.check(&TokenType::If) {
5653                // Parse "If it is a VariantName [(bindings)]:"
5654                let arm = self.parse_match_arm()?;
5655                arms.push(arm);
5656            } else if self.check(&TokenType::When) || self.check_word("When") {
5657                // Parse "When Variant [(bindings)]:" (concise syntax)
5658                let arm = self.parse_when_arm()?;
5659                arms.push(arm);
5660            } else if self.check(&TokenType::Newline) {
5661                // Skip newlines between arms
5662                self.advance();
5663            } else {
5664                // Skip unexpected tokens
5665                self.advance();
5666            }
5667        }
5668
5669        // Consume final dedent
5670        if self.check(&TokenType::Dedent) {
5671            self.advance();
5672        }
5673
5674        Ok(Stmt::Inspect { target, arms, has_otherwise })
5675    }
5676
5677    /// Parse a single match arm: "If it is a Variant [(field: binding)]:"
5678    fn parse_match_arm(&mut self) -> ParseResult<MatchArm<'a>> {
5679        self.advance(); // consume "If"
5680
5681        // Expect "it"
5682        if !self.check_word("it") {
5683            return Err(ParseError {
5684                kind: ParseErrorKind::ExpectedKeyword { keyword: "it".to_string() },
5685                span: self.current_span(),
5686            });
5687        }
5688        self.advance(); // consume "it"
5689
5690        // Expect "is"
5691        if !self.check(&TokenType::Is) {
5692            return Err(ParseError {
5693                kind: ParseErrorKind::ExpectedKeyword { keyword: "is".to_string() },
5694                span: self.current_span(),
5695            });
5696        }
5697        self.advance(); // consume "is"
5698
5699        // Consume article "a" or "an"
5700        if self.check_article() {
5701            self.advance();
5702        }
5703
5704        // Get variant name
5705        let variant = self.expect_identifier()?;
5706
5707        // Look up the enum name for this variant
5708        let enum_name = self.find_variant(variant);
5709
5710        // Optional: "(field)" or "(field: binding)" or "(f1, f2: b2)"
5711        let bindings = if self.check(&TokenType::LParen) {
5712            self.parse_pattern_bindings()?
5713        } else {
5714            vec![]
5715        };
5716
5717        // Expect colon
5718        if !self.check(&TokenType::Colon) {
5719            return Err(ParseError {
5720                kind: ParseErrorKind::ExpectedKeyword { keyword: ":".to_string() },
5721                span: self.current_span(),
5722            });
5723        }
5724        self.advance(); // consume ":"
5725
5726        // Expect indent
5727        if !self.check(&TokenType::Indent) {
5728            return Err(ParseError {
5729                kind: ParseErrorKind::ExpectedStatement,
5730                span: self.current_span(),
5731            });
5732        }
5733        self.advance(); // consume Indent
5734
5735        // Parse body statements
5736        let mut body_stmts = Vec::new();
5737        while !self.check(&TokenType::Dedent) && !self.is_at_end() {
5738            let stmt = self.parse_statement()?;
5739            body_stmts.push(stmt);
5740            if self.check(&TokenType::Period) {
5741                self.advance();
5742            }
5743        }
5744
5745        // Consume dedent
5746        if self.check(&TokenType::Dedent) {
5747            self.advance();
5748        }
5749
5750        let body = self.ctx.stmts.expect("imperative arenas not initialized")
5751            .alloc_slice(body_stmts.into_iter());
5752
5753        Ok(MatchArm { enum_name, variant: Some(variant), bindings, body })
5754    }
5755
5756    /// Parse a concise match arm: "When Variant [(bindings)]:" or "When Variant: stmt."
5757    fn parse_when_arm(&mut self) -> ParseResult<MatchArm<'a>> {
5758        self.advance(); // consume "When"
5759
5760        // Get variant name
5761        let variant = self.expect_identifier()?;
5762
5763        // Look up the enum name and variant definition for this variant
5764        let (enum_name, variant_fields) = self.type_registry
5765            .as_ref()
5766            .and_then(|r| r.find_variant(variant).map(|(enum_name, vdef)| {
5767                let fields: Vec<_> = vdef.fields.iter().map(|f| f.name).collect();
5768                (Some(enum_name), fields)
5769            }))
5770            .unwrap_or((None, vec![]));
5771
5772        // Optional: "(binding)" or "(b1, b2)" - positional bindings
5773        let bindings = if self.check(&TokenType::LParen) {
5774            let raw_bindings = self.parse_when_bindings()?;
5775            // Map positional bindings to actual field names
5776            raw_bindings.into_iter().enumerate().map(|(i, binding)| {
5777                let field = variant_fields.get(i).copied().unwrap_or(binding);
5778                (field, binding)
5779            }).collect()
5780        } else {
5781            vec![]
5782        };
5783
5784        // Expect colon
5785        if !self.check(&TokenType::Colon) {
5786            return Err(ParseError {
5787                kind: ParseErrorKind::ExpectedKeyword { keyword: ":".to_string() },
5788                span: self.current_span(),
5789            });
5790        }
5791        self.advance(); // consume ":"
5792
5793        // Handle both inline body (When Variant: stmt.) and block body
5794        let body_stmts = if self.check(&TokenType::Indent) {
5795            self.advance(); // consume Indent
5796            let mut stmts = Vec::new();
5797            while !self.check(&TokenType::Dedent) && !self.is_at_end() {
5798                let stmt = self.parse_statement()?;
5799                stmts.push(stmt);
5800                if self.check(&TokenType::Period) {
5801                    self.advance();
5802                }
5803            }
5804            if self.check(&TokenType::Dedent) {
5805                self.advance();
5806            }
5807            stmts
5808        } else {
5809            // Inline body: "When Red: Show x."
5810            let stmt = self.parse_statement()?;
5811            if self.check(&TokenType::Period) {
5812                self.advance();
5813            }
5814            vec![stmt]
5815        };
5816
5817        let body = self.ctx.stmts.expect("imperative arenas not initialized")
5818            .alloc_slice(body_stmts.into_iter());
5819
5820        Ok(MatchArm { enum_name, variant: Some(variant), bindings, body })
5821    }
5822
5823    /// Parse concise When bindings: "(r)" or "(w, h)" - just binding variable names
5824    fn parse_when_bindings(&mut self) -> ParseResult<Vec<Symbol>> {
5825        self.advance(); // consume '('
5826        let mut bindings = Vec::new();
5827
5828        loop {
5829            let binding = self.expect_identifier()?;
5830            bindings.push(binding);
5831
5832            if !self.check(&TokenType::Comma) {
5833                break;
5834            }
5835            self.advance(); // consume ','
5836        }
5837
5838        if !self.check(&TokenType::RParen) {
5839            return Err(ParseError {
5840                kind: ParseErrorKind::ExpectedKeyword { keyword: ")".to_string() },
5841                span: self.current_span(),
5842            });
5843        }
5844        self.advance(); // consume ')'
5845
5846        Ok(bindings)
5847    }
5848
5849    /// Parse pattern bindings: "(field)" or "(field: binding)" or "(f1, f2: b2)"
5850    fn parse_pattern_bindings(&mut self) -> ParseResult<Vec<(Symbol, Symbol)>> {
5851        self.advance(); // consume '('
5852        let mut bindings = Vec::new();
5853
5854        loop {
5855            let field = self.expect_identifier()?;
5856            let binding = if self.check(&TokenType::Colon) {
5857                self.advance(); // consume ":"
5858                self.expect_identifier()?
5859            } else {
5860                field // field name = binding name
5861            };
5862            bindings.push((field, binding));
5863
5864            if !self.check(&TokenType::Comma) {
5865                break;
5866            }
5867            self.advance(); // consume ','
5868        }
5869
5870        if !self.check(&TokenType::RParen) {
5871            return Err(ParseError {
5872                kind: ParseErrorKind::ExpectedKeyword { keyword: ")".to_string() },
5873                span: self.current_span(),
5874            });
5875        }
5876        self.advance(); // consume ')'
5877
5878        Ok(bindings)
5879    }
5880
5881    /// Parse constructor fields: "with field1 value1 [and field2 value2]..."
5882    /// Example: "with radius 10" or "with x 10 and y 20"
5883    /// Used for both variant constructors and struct initialization
5884    fn parse_constructor_fields(&mut self) -> ParseResult<Vec<(Symbol, &'a Expr<'a>)>> {
5885        use crate::ast::Expr;
5886        let mut fields = Vec::new();
5887
5888        // Consume "with"
5889        self.advance();
5890
5891        loop {
5892            // Parse field name
5893            let field_name = self.expect_identifier()?;
5894
5895            // Parse field value expression — use parse_comparison to stop before
5896            // "and"/"or" which serve as field separators in constructor syntax
5897            let value = self.parse_comparison()?;
5898
5899            fields.push((field_name, value));
5900
5901            // Check for "and" to continue
5902            if self.check(&TokenType::And) {
5903                self.advance(); // consume "and"
5904                continue;
5905            }
5906            break;
5907        }
5908
5909        Ok(fields)
5910    }
5911
5912    /// Alias for variant constructors (backwards compat)
5913    fn parse_variant_constructor_fields(&mut self) -> ParseResult<Vec<(Symbol, &'a Expr<'a>)>> {
5914        self.parse_constructor_fields()
5915    }
5916
5917    /// Alias for struct initialization
5918    fn parse_struct_init_fields(&mut self) -> ParseResult<Vec<(Symbol, &'a Expr<'a>)>> {
5919        self.parse_constructor_fields()
5920    }
5921
5922    /// Phase 34: Parse generic type arguments for constructor instantiation
5923    /// Parses "of Int" or "of Int and Text" after a generic type name
5924    /// Returns empty Vec for non-generic types
5925    fn parse_generic_type_args(&mut self, type_name: Symbol) -> ParseResult<Vec<TypeExpr<'a>>> {
5926        // Only parse type args if the type is a known generic
5927        if !self.is_generic_type(type_name) {
5928            return Ok(vec![]);
5929        }
5930
5931        // Expect "of" preposition
5932        if !self.check_preposition_is("of") {
5933            return Ok(vec![]);  // Generic type without arguments - will use defaults
5934        }
5935        self.advance(); // consume "of"
5936
5937        let mut type_args = Vec::new();
5938        loop {
5939            // Bug fix: Parse full type expression to support nested types like "Seq of (Seq of Int)"
5940            let type_arg = self.parse_type_expression()?;
5941            type_args.push(type_arg);
5942
5943            // Check for "and" or "to" to continue (for multi-param generics like "Map of Text to Int")
5944            if self.check(&TokenType::And) || self.check_to_preposition() {
5945                self.advance(); // consume separator
5946                continue;
5947            }
5948            break;
5949        }
5950
5951        Ok(type_args)
5952    }
5953
5954    /// Skip type definition content until next block header
5955    /// Used for TypeDef blocks (## A Point has:, ## A Color is one of:)
5956    /// The actual parsing is done by DiscoveryPass
5957    fn skip_type_def_content(&mut self) {
5958        while !self.is_at_end() {
5959            // Stop at next block header
5960            if matches!(
5961                self.tokens.get(self.current),
5962                Some(Token { kind: TokenType::BlockHeader { .. }, .. })
5963            ) {
5964                break;
5965            }
5966            self.advance();
5967        }
5968    }
5969
5970    /// Phase 63: Parse theorem block
5971    /// Syntax:
5972    ///   ## Theorem: Name
5973    ///   Given: Premise 1.
5974    ///   Given: Premise 2.
5975    ///   Prove: Goal.
5976    ///   Proof: Auto.
5977    fn parse_theorem_block(&mut self) -> ParseResult<Stmt<'a>> {
5978        use crate::ast::theorem::{TheoremBlock, ProofStrategy};
5979
5980        // Skip newlines and indentation
5981        self.skip_whitespace_tokens();
5982
5983        // Parse theorem name: expect "Name" after the ##Theorem header
5984        // The header has already been consumed; expect Colon then Identifier
5985        // Note: In some cases the colon might have been consumed with the header.
5986        // Let's be flexible and check for both.
5987        if self.check(&TokenType::Colon) {
5988            self.advance();
5989        }
5990
5991        // Skip whitespace again
5992        self.skip_whitespace_tokens();
5993
5994        // Get theorem name - should be an identifier (Noun, ProperName, etc.)
5995        let name = if let Some(token) = self.tokens.get(self.current) {
5996            match &token.kind {
5997                TokenType::Noun(_)
5998                | TokenType::ProperName(_)
5999                | TokenType::Verb { .. }
6000                | TokenType::Adjective(_) => {
6001                    let name = self.interner.resolve(token.lexeme).to_string();
6002                    self.advance();
6003                    name
6004                }
6005                _ => {
6006                    // Use the lexeme directly for unrecognized tokens
6007                    let lexeme = self.interner.resolve(token.lexeme);
6008                    if !lexeme.is_empty() && lexeme.chars().next().map(|c| c.is_alphanumeric()).unwrap_or(false) {
6009                        let name = lexeme.to_string();
6010                        self.advance();
6011                        name
6012                    } else {
6013                        "Anonymous".to_string()
6014                    }
6015                }
6016            }
6017        } else {
6018            "Anonymous".to_string()
6019        };
6020
6021        self.skip_whitespace_tokens();
6022
6023        // Skip period after name if present
6024        if self.check(&TokenType::Period) {
6025            self.advance();
6026        }
6027
6028        self.skip_whitespace_tokens();
6029
6030        // Parse premises (Given: statements)
6031        // Each premise is a sentence that can introduce referents for subsequent premises
6032        let mut premises = Vec::new();
6033        let mut premise_names: Vec<Option<String>> = Vec::new();
6034        while self.check(&TokenType::Given) {
6035            self.advance(); // consume "Given"
6036
6037            self.skip_whitespace_tokens();
6038
6039            // Optional premise NAME in parentheses: `Given (h): …` names this premise
6040            // `h`, so a `Proof:` script can say `cases h` instead of the positional `hp0`.
6041            let mut this_name: Option<String> = None;
6042            if self.check(&TokenType::LParen) {
6043                self.advance();
6044                self.skip_whitespace_tokens();
6045                if let Some(token) = self.tokens.get(self.current) {
6046                    if !matches!(token.kind, TokenType::RParen) {
6047                        this_name = Some(self.interner.resolve(token.lexeme).to_string());
6048                        self.advance();
6049                    }
6050                }
6051                self.skip_whitespace_tokens();
6052                if self.check(&TokenType::RParen) {
6053                    self.advance();
6054                }
6055                self.skip_whitespace_tokens();
6056            }
6057            premise_names.push(this_name);
6058
6059            // Expect colon
6060            if self.check(&TokenType::Colon) {
6061                self.advance();
6062            }
6063
6064            self.skip_whitespace_tokens();
6065
6066            // Parse the logic expression for this premise. In pragmatic mode
6067            // a leading weak scalar (some/most/many) carries its Horn-scale
6068            // implicature, exactly as in `parse_pragmatic`.
6069            let scalar_trigger = self.pragmatic
6070                && matches!(
6071                    self.peek().kind,
6072                    TokenType::Some | TokenType::Most | TokenType::Many
6073                );
6074            // An optative wish ("May you prosper!") is a valid premise; the
6075            // standalone path tries it before the declarative sentence parser,
6076            // so the theorem-premise path must too (it self-backtracks to None
6077            // for non-optatives, falling through to `parse_sentence`).
6078            let mut premise_expr = match self.try_parse_optative()? {
6079                Some(opt) => opt,
6080                None => self.parse_sentence()?,
6081            };
6082            if scalar_trigger {
6083                if let Some(strengthened) = self.scalar_implicature(premise_expr) {
6084                    premise_expr = strengthened;
6085                }
6086            }
6087            premises.push(premise_expr);
6088
6089            // Mark sentence boundary - this enables discourse mode and populates telescope
6090            // so pronouns in subsequent premises can resolve to referents from this premise
6091            self.world_state.end_sentence();
6092
6093            // Consume the trailing terminator if present ("!" for
6094            // exclamative/optative/imperative premises).
6095            if self.check(&TokenType::Period) || self.check(&TokenType::Exclamation) {
6096                self.advance();
6097            }
6098
6099            self.skip_whitespace_tokens();
6100        }
6101
6102        // Parse goal (Prove: statement)
6103        let goal = if self.check(&TokenType::Prove) {
6104            self.advance(); // consume "Prove"
6105
6106            if self.check(&TokenType::Colon) {
6107                self.advance();
6108            }
6109
6110            self.skip_whitespace_tokens();
6111
6112            // The goal may itself be an optative ("Prove: May you prosper!" —
6113            // identity through the wish encoding), same as the premise path.
6114            let goal_expr = match self.try_parse_optative()? {
6115                Some(opt) => opt,
6116                None => self.parse_sentence()?,
6117            };
6118
6119            if self.check(&TokenType::Period) || self.check(&TokenType::Exclamation) {
6120                self.advance();
6121            }
6122
6123            goal_expr
6124        } else {
6125            return Err(ParseError {
6126                kind: ParseErrorKind::ExpectedKeyword { keyword: "Prove".to_string() },
6127                span: self.current_span(),
6128            });
6129        };
6130
6131        self.skip_whitespace_tokens();
6132
6133        // Parse proof strategy (`Proof: Auto.` or `Proof: <tactic script>`). The word
6134        // `Proof` may lex as a real `Proof` block header OR — inline, after `Prove:` —
6135        // as an ordinary capitalized name; accept both so the `Proof:` line is found.
6136        let at_proof = self
6137            .check(&TokenType::BlockHeader { block_type: crate::token::BlockType::Proof })
6138            || self.tokens.get(self.current).is_some_and(|t| {
6139                self.interner.resolve(t.lexeme).eq_ignore_ascii_case("proof")
6140            });
6141        let strategy = if at_proof {
6142            self.advance();
6143            self.skip_whitespace_tokens();
6144
6145            if self.check(&TokenType::Colon) {
6146                self.advance();
6147            }
6148
6149            self.skip_whitespace_tokens();
6150
6151            if self.check(&TokenType::Auto) {
6152                self.advance();
6153                ProofStrategy::Auto
6154            } else {
6155                // Anything else is an explicit tactic SCRIPT, written in the
6156                // English-esque vernacular: collect every token's lexeme up to the end
6157                // of the block (the next `##` header, a dedent, or EOF) and join them —
6158                // the tactic-script parser re-lexes the result, treating sentence
6159                // punctuation as step separators. So a prose proof flows straight in.
6160                let mut parts: Vec<String> = Vec::new();
6161                while !self.is_at_end()
6162                    && !matches!(self.tokens[self.current].kind, TokenType::BlockHeader { .. })
6163                    && !self.check(&TokenType::Dedent)
6164                {
6165                    let lexeme = self.tokens[self.current].lexeme;
6166                    parts.push(self.interner.resolve(lexeme).to_string());
6167                    self.advance();
6168                }
6169                let script = parts.join(" ");
6170                if script.trim().is_empty() {
6171                    ProofStrategy::Auto
6172                } else {
6173                    ProofStrategy::Script(script)
6174                }
6175            }
6176        } else {
6177            // No explicit proof strategy, default to Auto
6178            ProofStrategy::Auto
6179        };
6180
6181        // Consume trailing period if present
6182        if self.check(&TokenType::Period) {
6183            self.advance();
6184        }
6185
6186        let theorem = TheoremBlock {
6187            name,
6188            premises,
6189            premise_names,
6190            goal,
6191            strategy,
6192        };
6193
6194        Ok(Stmt::Theorem(theorem))
6195    }
6196
6197    /// Parse a `## Define` block (Rung 0a): a vernacular-logic predicate
6198    /// definition written as one biconditional sentence,
6199    /// `<subject> is <predicate> if and only if <definiens>`. The LHS predicate
6200    /// is the definiendum (its name + parameter symbols); the RHS is the
6201    /// definiens. A body that is not a top-level biconditional whose left side
6202    /// is a predicate application is a malformed definition and errors.
6203    fn parse_define_block(&mut self) -> ParseResult<Stmt<'a>> {
6204        use crate::ast::definition::DefinitionBlock;
6205
6206        self.skip_whitespace_tokens();
6207
6208        // Optional `: Label` after the header — the definiendum names itself, so
6209        // a label is decorative. Consume and ignore one if present.
6210        if self.check(&TokenType::Colon) {
6211            self.advance();
6212            self.skip_whitespace_tokens();
6213            if let Some(token) = self.tokens.get(self.current) {
6214                if matches!(
6215                    token.kind,
6216                    TokenType::Noun(_)
6217                        | TokenType::ProperName(_)
6218                        | TokenType::Adjective(_)
6219                        | TokenType::Verb { .. }
6220                ) {
6221                    self.advance();
6222                }
6223            }
6224            if self.check(&TokenType::Period) {
6225                self.advance();
6226            }
6227        }
6228
6229        self.skip_whitespace_tokens();
6230
6231        // The body is one biconditional sentence.
6232        let sentence = self.parse_sentence()?;
6233        if self.check(&TokenType::Period) || self.check(&TokenType::Exclamation) {
6234            self.advance();
6235        }
6236
6237        // Split at the top-level biconditional.
6238        let (definiendum, definiens) = match sentence {
6239            LogicExpr::BinaryOp { left, op: TokenType::Iff, right } => (*left, *right),
6240            _ => {
6241                return Err(ParseError {
6242                    kind: ParseErrorKind::Custom(
6243                        "a `## Define` body must be a biconditional: \
6244                         `<subject> is <predicate> if and only if <definiens>`"
6245                            .to_string(),
6246                    ),
6247                    span: self.current_span(),
6248                });
6249            }
6250        };
6251
6252        // The definiendum must be a predicate applied to its parameters; the
6253        // predicate name is the new concept, its arguments are the parameters.
6254        let (name, params) = match definiendum {
6255            LogicExpr::Predicate { name, args, .. } => {
6256                let pname = self.interner.resolve(*name).to_string();
6257                let params: Vec<Symbol> = args
6258                    .iter()
6259                    .filter_map(|t| match t {
6260                        Term::Constant(s) | Term::Variable(s) => Some(*s),
6261                        _ => None,
6262                    })
6263                    .collect();
6264                (pname, params)
6265            }
6266            _ => {
6267                return Err(ParseError {
6268                    kind: ParseErrorKind::Custom(
6269                        "the left side of a `## Define` biconditional must be a \
6270                         predicate applied to its parameters (e.g. `x is a bachelor`)"
6271                            .to_string(),
6272                    ),
6273                    span: self.current_span(),
6274                });
6275            }
6276        };
6277
6278        Ok(Stmt::Definition(DefinitionBlock {
6279            name,
6280            params,
6281            definiendum,
6282            definiens,
6283        }))
6284    }
6285
6286    /// Parse a `## Axiom` block: `## Axiom <name>: <formula>`. The leading identifier is
6287    /// the axiom's name; the remainder of the block is captured as formula text and parsed
6288    /// downstream by the formal-formula parser (`logicaffeine_proof::formula`). The body is
6289    /// formal first-order logic — `for all a b, Cong(a, b, b, a)` — which the NL parser
6290    /// cannot express, so it is kept as text rather than parsed here.
6291    fn parse_axiom_block(&mut self) -> ParseResult<Stmt<'a>> {
6292        use crate::ast::axiom::AxiomBlock;
6293        self.skip_whitespace_tokens();
6294        let name = self.read_block_name();
6295        self.skip_whitespace_tokens();
6296        if self.check(&TokenType::Colon) {
6297            self.advance();
6298        }
6299        let formula = self.collect_block_text();
6300        Ok(Stmt::Axiom(AxiomBlock { name, formula }))
6301    }
6302
6303    /// Parse a `## Theory` block: `## Theory <name>` optionally followed by a development
6304    /// body (`Axiom …` / `Theorem …` declarations). The leading identifier is the theory's
6305    /// name; the remainder of the block is captured as development text, parsed downstream.
6306    fn parse_theory_block(&mut self) -> ParseResult<Stmt<'a>> {
6307        use crate::ast::axiom::TheoryBlock;
6308        self.skip_whitespace_tokens();
6309        let name = self.read_block_name();
6310        self.skip_whitespace_tokens();
6311        if self.check(&TokenType::Colon) {
6312            self.advance();
6313        }
6314        let body = self.collect_block_text();
6315        Ok(Stmt::Theory(TheoryBlock { name, body }))
6316    }
6317
6318    /// Read a block's leading name token (a proper name / noun / adjective / verb lexeme),
6319    /// returning its text. `"Anonymous"` if no name token is present.
6320    fn read_block_name(&mut self) -> String {
6321        if let Some(token) = self.tokens.get(self.current) {
6322            if matches!(
6323                token.kind,
6324                TokenType::Noun(_)
6325                    | TokenType::ProperName(_)
6326                    | TokenType::Adjective(_)
6327                    | TokenType::Verb { .. }
6328            ) {
6329                let name = self.interner.resolve(token.lexeme).to_string();
6330                self.advance();
6331                return name;
6332            }
6333        }
6334        "Anonymous".to_string()
6335    }
6336
6337    /// Collect the remaining tokens of the current block as text — each token's lexeme
6338    /// joined with spaces, up to the next `##` block header or EOF. Layout tokens
6339    /// (`Newline`/`Indent`/`Dedent`) are skipped rather than emitted, so a multi-line,
6340    /// indented development body flows through as clean whitespace-separated words for the
6341    /// downstream formal parser.
6342    fn collect_block_text(&mut self) -> String {
6343        let mut parts: Vec<String> = Vec::new();
6344        while !self.is_at_end()
6345            && !matches!(self.tokens[self.current].kind, TokenType::BlockHeader { .. })
6346        {
6347            match &self.tokens[self.current].kind {
6348                TokenType::Newline | TokenType::Indent | TokenType::Dedent => {}
6349                _ => {
6350                    let lexeme = self.tokens[self.current].lexeme;
6351                    parts.push(self.interner.resolve(lexeme).to_string());
6352                }
6353            }
6354            self.advance();
6355        }
6356        parts.join(" ")
6357    }
6358
6359    /// Skip whitespace tokens (newlines, indentation)
6360    fn skip_whitespace_tokens(&mut self) {
6361        while self.check(&TokenType::Newline) || self.check(&TokenType::Indent) || self.check(&TokenType::Dedent) {
6362            self.advance();
6363        }
6364    }
6365
6366    /// Insert each postcondition (`checks`) as a hard `RuntimeAssert` immediately
6367    /// before every `Return` reachable in `stmts`, recursing into the control-flow
6368    /// constructs whose returns EXIT the function (`If`/`While`/`Repeat`/`Inspect`/
6369    /// `Zone`). `Concurrent`/`Parallel` are intentionally left as leaves: their
6370    /// returns are swallowed (they don't exit the function), so a postcondition must
6371    /// NOT be inserted there. The fallthrough exit is handled by the caller.
6372    fn insert_ensures_before_returns(
6373        &self,
6374        stmts: &[Stmt<'a>],
6375        checks: &[&'a Expr<'a>],
6376    ) -> Vec<Stmt<'a>> {
6377        let mut out: Vec<Stmt<'a>> = Vec::new();
6378        for s in stmts {
6379            if matches!(s, Stmt::Return { .. }) {
6380                for &c in checks {
6381                    out.push(Stmt::RuntimeAssert { condition: c, hard: true });
6382                }
6383                out.push(s.clone());
6384                continue;
6385            }
6386            // Clone, then rewrite only the child blocks (via `..` patterns) — no need
6387            // to re-spell every field of every block-bearing variant.
6388            let mut c = s.clone();
6389            match &mut c {
6390                Stmt::If { then_block, else_block, .. } => {
6391                    *then_block = self.alloc_block(self.insert_ensures_before_returns(then_block, checks));
6392                    if let Some(eb) = else_block {
6393                        *eb = self.alloc_block(self.insert_ensures_before_returns(eb, checks));
6394                    }
6395                }
6396                Stmt::While { body, .. }
6397                | Stmt::Repeat { body, .. }
6398                | Stmt::Zone { body, .. } => {
6399                    *body = self.alloc_block(self.insert_ensures_before_returns(body, checks));
6400                }
6401                Stmt::Inspect { arms, .. } => {
6402                    for arm in arms.iter_mut() {
6403                        arm.body = self.alloc_block(self.insert_ensures_before_returns(arm.body, checks));
6404                    }
6405                }
6406                Stmt::Select { branches } => {
6407                    // A `Return` in a `select!` arm EXITS the function (unlike
6408                    // Concurrent/Parallel, whose returns are swallowed) — guard it too.
6409                    for br in branches.iter_mut() {
6410                        match br {
6411                            crate::ast::stmt::SelectBranch::Receive { body, .. }
6412                            | crate::ast::stmt::SelectBranch::Timeout { body, .. } => {
6413                                *body = self.alloc_block(self.insert_ensures_before_returns(body, checks));
6414                            }
6415                        }
6416                    }
6417                }
6418                // Concurrent/Parallel: their Returns are swallowed (don't exit the
6419                // function), so the postcondition must NOT be inserted there.
6420                _ => {}
6421            }
6422            out.push(c);
6423        }
6424        out
6425    }
6426
6427    /// Allocate a freshly-built statement list in the imperative arena.
6428    fn alloc_block(&self, stmts: Vec<Stmt<'a>>) -> &'a [Stmt<'a>] {
6429        self.ctx.stmts.expect("imperative arenas not initialized").alloc_slice(stmts.into_iter())
6430    }
6431
6432    /// Phase 32: Parse function definition after `## To` header
6433    /// Phase 32/38: Parse function definition
6434    /// Syntax: [To] [native] name (a: Type) [and (b: Type)] [-> ReturnType]
6435    ///         body statements... (only if not native)
6436    fn parse_function_def(&mut self) -> ParseResult<Stmt<'a>> {
6437        self.parse_function_def_with_flags(OptimizationConfig::all_on())
6438    }
6439
6440    /// Parse function definition with optimization annotation flags.
6441    fn parse_function_def_with_flags(&mut self, opt_flags: OptimizationConfig) -> ParseResult<Stmt<'a>> {
6442        // Consume "To" if present (when called from parse_statement)
6443        if self.check(&TokenType::To) || self.check_preposition_is("to") {
6444            self.advance();
6445        }
6446
6447        // Phase 38: Check for native modifier (prefix style: "## To native funcname ...")
6448        let mut is_native = if self.check(&TokenType::Native) {
6449            self.advance(); // consume "native"
6450            true
6451        } else {
6452            false
6453        };
6454
6455        // Parse function name (first identifier after ## To [native])
6456        let name = self.expect_identifier()?;
6457
6458        // Parse optional generic type parameters: "of [T]" or "of [T] and [U]"
6459        let mut parsed_generics: Vec<Symbol> = Vec::new();
6460        if self.check_preposition_is("of") {
6461            self.advance(); // consume "of"
6462            loop {
6463                if !self.check(&TokenType::LBracket) {
6464                    return Err(ParseError {
6465                        kind: ParseErrorKind::Custom("Expected '[TypeParam]' after 'of' in generic function".to_string()),
6466                        span: self.current_span(),
6467                    });
6468                }
6469                self.advance(); // consume [
6470                let type_param = self.expect_identifier()?;
6471                parsed_generics.push(type_param);
6472                if !self.check(&TokenType::RBracket) {
6473                    return Err(ParseError {
6474                        kind: ParseErrorKind::Custom("Expected ']' after type parameter name".to_string()),
6475                        span: self.current_span(),
6476                    });
6477                }
6478                self.advance(); // consume ]
6479                if self.check_word("and") {
6480                    self.advance(); // consume "and"
6481                } else {
6482                    break;
6483                }
6484            }
6485        }
6486
6487        // Parse parameters: (name: Type) groups separated by "and", or comma-separated in one group
6488        let mut params = Vec::new();
6489        while self.check(&TokenType::LParen) {
6490            self.advance(); // consume (
6491
6492            // Handle empty parameter list: ()
6493            if self.check(&TokenType::RParen) {
6494                self.advance(); // consume )
6495                break;
6496            }
6497
6498            // Parse parameters in this group (possibly comma-separated)
6499            loop {
6500                let param_name = self.expect_identifier()?;
6501
6502                // Expect colon
6503                if !self.check(&TokenType::Colon) {
6504                    return Err(ParseError {
6505                        kind: ParseErrorKind::ExpectedKeyword { keyword: ":".to_string() },
6506                        span: self.current_span(),
6507                    });
6508                }
6509                self.advance(); // consume :
6510
6511                // MVS: a `mutable` parameter passes by reference (callee mutations
6512                // visible to caller) under value semantics — carried as a
6513                // `TypeExpr::Mutable` wrapper so the parameter tuple is unchanged.
6514                let param_is_mutable = self.check_mutable_keyword();
6515                if param_is_mutable {
6516                    self.advance(); // consume the `mutable`/`mut` keyword
6517                }
6518
6519                // Phase 38: Parse full type expression instead of simple identifier
6520                let param_type_expr = self.parse_type_expression()?;
6521                let param_type_inner = self.ctx.alloc_type_expr(param_type_expr);
6522                let param_type = if param_is_mutable {
6523                    self.ctx.alloc_type_expr(TypeExpr::Mutable { inner: param_type_inner })
6524                } else {
6525                    param_type_inner
6526                };
6527
6528                params.push((param_name, param_type));
6529
6530                // Check for comma (more params in this group) or ) (end of group)
6531                if self.check(&TokenType::Comma) {
6532                    self.advance(); // consume ,
6533                    continue;
6534                }
6535                break;
6536            }
6537
6538            // Expect )
6539            if !self.check(&TokenType::RParen) {
6540                return Err(ParseError {
6541                    kind: ParseErrorKind::ExpectedKeyword { keyword: ")".to_string() },
6542                    span: self.current_span(),
6543                });
6544            }
6545            self.advance(); // consume )
6546
6547            // Check for "and", preposition, or "from" between parameter groups
6548            // Allows: "## To withdraw (amount: Int) from (balance: Int)"
6549            if self.check_word("and") || self.check_preposition() || self.check(&TokenType::From) {
6550                self.advance();
6551            }
6552        }
6553
6554        // Phase 38: Parse optional return type -> Type
6555        let return_type = if self.check(&TokenType::Arrow) {
6556            self.advance(); // consume ->
6557            let ret_type_expr = self.parse_type_expression()?;
6558            Some(self.ctx.alloc_type_expr(ret_type_expr))
6559        } else {
6560            None
6561        };
6562
6563        // FFI: Parse optional suffix modifiers: "is native <path>", "is exported [for <target>]"
6564        let mut native_path: Option<Symbol> = None;
6565        let mut is_exported = false;
6566        let mut export_target: Option<Symbol> = None;
6567
6568        if self.check_word("is") {
6569            self.advance(); // consume "is"
6570            if self.check(&TokenType::Native) {
6571                // "is native" suffix style with required path string
6572                self.advance(); // consume "native"
6573                is_native = true;
6574                if let TokenType::StringLiteral(sym) = self.peek().kind {
6575                    native_path = Some(sym);
6576                    self.advance(); // consume the path string
6577                } else {
6578                    return Err(ParseError {
6579                        kind: ParseErrorKind::Custom(
6580                            "Expected a string literal for native function path (e.g., is native \"reqwest::blocking::get\")".to_string()
6581                        ),
6582                        span: self.current_span(),
6583                    });
6584                }
6585            } else if self.check_word("exported") {
6586                // "is exported [for <target>]"
6587                self.advance(); // consume "exported"
6588                is_exported = true;
6589                if self.check_word("for") {
6590                    self.advance(); // consume "for"
6591                    // "native" lexes as a keyword token, not an identifier.
6592                    let target_sym = if self.check(&TokenType::Native) {
6593                        self.advance();
6594                        self.interner.intern("native")
6595                    } else {
6596                        self.expect_identifier()?
6597                    };
6598                    let target_str = self.interner.resolve(target_sym);
6599                    if !target_str.eq_ignore_ascii_case("c")
6600                        && !target_str.eq_ignore_ascii_case("wasm")
6601                        && !target_str.eq_ignore_ascii_case("native")
6602                    {
6603                        return Err(ParseError {
6604                            kind: ParseErrorKind::Custom(
6605                                format!("Unsupported export target \"{}\". Supported targets are \"c\", \"wasm\", and \"native\".", target_str)
6606                            ),
6607                            span: self.current_span(),
6608                        });
6609                    }
6610                    export_target = Some(target_sym);
6611                }
6612            }
6613        }
6614
6615        // Phase 38: Native functions have no body
6616        if is_native {
6617            // Consume trailing period or newline if present
6618            if self.check(&TokenType::Period) {
6619                self.advance();
6620            }
6621            if self.check(&TokenType::Newline) {
6622                self.advance();
6623            }
6624
6625            // Return with empty body
6626            let empty_body = self.ctx.stmts.expect("imperative arenas not initialized")
6627                .alloc_slice(std::iter::empty());
6628
6629            return Ok(Stmt::FunctionDef {
6630                name,
6631                generics: parsed_generics,
6632                params,
6633                body: empty_body,
6634                return_type,
6635                is_native: true,
6636                native_path,
6637                is_exported: false,
6638                export_target: None,
6639                opt_flags,
6640            });
6641        }
6642
6643        // Non-native: expect colon after parameter list / return type
6644        if !self.check(&TokenType::Colon) {
6645            return Err(ParseError {
6646                kind: ParseErrorKind::ExpectedKeyword { keyword: ":".to_string() },
6647                span: self.current_span(),
6648            });
6649        }
6650        self.advance(); // consume :
6651
6652        // Expect indent for function body
6653        if !self.check(&TokenType::Indent) {
6654            return Err(ParseError {
6655                kind: ParseErrorKind::ExpectedStatement,
6656                span: self.current_span(),
6657            });
6658        }
6659        self.advance(); // consume Indent
6660
6661        // Scope the `=`-mutation decision (and the infinity/nan literal guard)
6662        // to THIS function body: seed a fresh bound-set with the params, restore
6663        // the outer set on exit so a sibling function's or Main's `x = e` never
6664        // mistakes an out-of-scope `x` for a local (which would wrongly emit a
6665        // `Set` on an unbound name).
6666        let saved_user_bound = std::mem::take(&mut self.user_bound);
6667        for (param_name, _) in &params {
6668            self.user_bound.insert(*param_name);
6669        }
6670
6671        // Parse body statements
6672        let mut body_stmts = Vec::new();
6673        // Contract clauses: `Requires <cond>.` (precondition, checked at entry) and
6674        // `Ensures <cond>.` (postcondition, checked before every return). Collected
6675        // here and desugared into enforced (hard) asserts below — NOT emitted as
6676        // ordinary statements.
6677        let mut requires_checks: Vec<&'a Expr<'a>> = Vec::new();
6678        let mut ensures_checks: Vec<&'a Expr<'a>> = Vec::new();
6679        while !self.check(&TokenType::Dedent) && !self.is_at_end() {
6680            // Skip newlines between statements
6681            if self.check(&TokenType::Newline) {
6682                self.advance();
6683                continue;
6684            }
6685            // Stop if we hit another block header
6686            if matches!(self.peek().kind, TokenType::BlockHeader { .. }) {
6687                break;
6688            }
6689            if self.check(&TokenType::Requires) {
6690                self.advance(); // consume "Requires"
6691                requires_checks.push(self.parse_condition()?);
6692                if self.check(&TokenType::Period) { self.advance(); }
6693                continue;
6694            }
6695            if self.check(&TokenType::Ensures) {
6696                self.advance(); // consume "Ensures"
6697                ensures_checks.push(self.parse_condition()?);
6698                if self.check(&TokenType::Period) { self.advance(); }
6699                continue;
6700            }
6701            let stmt = self.parse_statement()?;
6702            body_stmts.push(stmt);
6703            if self.check(&TokenType::Period) {
6704                self.advance();
6705            }
6706        }
6707
6708        // Consume dedent if present
6709        if self.check(&TokenType::Dedent) {
6710            self.advance();
6711        }
6712
6713        // Leave the function scope: the outer bound-set regains control.
6714        self.user_bound = saved_user_bound;
6715
6716        // Desugar postconditions: a hard assert before EVERY return path (recursing
6717        // into nested control flow) and at fallthrough — so no exit escapes the check.
6718        if !ensures_checks.is_empty() {
6719            body_stmts = self.insert_ensures_before_returns(&body_stmts, &ensures_checks);
6720            if !matches!(body_stmts.last(), Some(Stmt::Return { .. })) {
6721                for &c in &ensures_checks {
6722                    body_stmts.push(Stmt::RuntimeAssert { condition: c, hard: true });
6723                }
6724            }
6725        }
6726        // Desugar preconditions: hard asserts at function entry.
6727        if !requires_checks.is_empty() {
6728            let mut prefixed: Vec<Stmt<'a>> = requires_checks
6729                .iter()
6730                .map(|&c| Stmt::RuntimeAssert { condition: c, hard: true })
6731                .collect();
6732            prefixed.extend(body_stmts);
6733            body_stmts = prefixed;
6734        }
6735
6736        // Allocate body in arena
6737        let body = self.ctx.stmts.expect("imperative arenas not initialized")
6738            .alloc_slice(body_stmts.into_iter());
6739
6740        Ok(Stmt::FunctionDef {
6741            name,
6742            generics: parsed_generics,
6743            params,
6744            body,
6745            return_type,
6746            is_native: false,
6747            native_path: None,
6748            is_exported,
6749            export_target,
6750            opt_flags,
6751        })
6752    }
6753
6754    /// Parse a primary expression (literal, identifier, index, slice, list, etc.)
6755    /// Depth-guarded wrapper: primary expressions are the re-entry point of
6756    /// the imperative expression descent (`( expr )`, prefix forms), so a
6757    /// parenthesis tower recurses here once per level — the guard turns a
6758    /// would-be parser stack overflow into a graceful `AstTooDeep`.
6759    fn parse_primary_expr(&mut self) -> ParseResult<&'a Expr<'a>> {
6760        self.enter_recursion()?;
6761        let result = self.parse_primary_expr_inner();
6762        self.leave_recursion();
6763        result
6764    }
6765
6766    fn parse_primary_expr_inner(&mut self) -> ParseResult<&'a Expr<'a>> {
6767        use crate::ast::{Expr, Literal};
6768
6769        // English prefix arithmetic: `the sum/product/difference/quotient/remainder of A and B`
6770        // → the matching binary op. Unambiguous (the distinctive `the <op-noun> of` prefix), so it
6771        // never shadows a plain variable. `A`/`B` are full sub-expressions; the `and` delimiter is
6772        // consumed here (it binds tighter than the boolean `and`, which lives far above additive).
6773        if self.check_word("the") && self.peek_word_at(2, "of") {
6774            let op = if self.peek_word_at(1, "sum") {
6775                Some(BinaryOpKind::Add)
6776            } else if self.peek_word_at(1, "product") {
6777                Some(BinaryOpKind::Multiply)
6778            } else if self.peek_word_at(1, "difference") {
6779                Some(BinaryOpKind::Subtract)
6780            } else if self.peek_word_at(1, "quotient") {
6781                Some(BinaryOpKind::Divide)
6782            } else if self.peek_word_at(1, "remainder") {
6783                Some(BinaryOpKind::Modulo)
6784            } else {
6785                None
6786            };
6787            if let Some(op) = op {
6788                self.advance(); // "the"
6789                self.advance(); // op-noun
6790                self.advance(); // "of"
6791                let a = self.parse_additive_expr()?;
6792                if self.check(&TokenType::And) || self.check_word("and") {
6793                    self.advance(); // "and"
6794                } else {
6795                    return Err(ParseError {
6796                        kind: ParseErrorKind::ExpectedKeyword { keyword: "and".to_string() },
6797                        span: self.current_span(),
6798                    });
6799                }
6800                let b = self.parse_additive_expr()?;
6801                return Ok(self.ctx.alloc_imperative_expr(Expr::BinaryOp { op, left: a, right: b }));
6802            }
6803        }
6804
6805        // Natural timestamp literal: `timestamp "2024-03-10T07:30:00Z"` → a Moment. Reads as a
6806        // value, not a function call; lowers to the (invisible) parse_timestamp. Only fires when an
6807        // identifier `timestamp` is immediately followed by a string, so a variable named
6808        // `timestamp` used on its own is unaffected.
6809        if matches!(self.peek().kind, TokenType::Identifier | TokenType::Noun(_))
6810            && self.interner.resolve(self.peek().lexeme).eq_ignore_ascii_case("timestamp")
6811        {
6812            let str_sym = match self.tokens.get(self.current + 1).map(|t| &t.kind) {
6813                Some(TokenType::StringLiteral(s)) => Some(*s),
6814                _ => None,
6815            };
6816            if let Some(s) = str_sym {
6817                self.advance(); // "timestamp"
6818                self.advance(); // the string
6819                let text = self.ctx.alloc_imperative_expr(Expr::Literal(Literal::Text(s)));
6820                let func = self.interner.intern("parse_timestamp");
6821                return Ok(self.ctx.alloc_imperative_expr(Expr::Call { function: func, args: vec![text] }));
6822            }
6823        }
6824
6825        // Natural UUID literal: `uuid "550e8400-…"` → a Uuid (parses/normalizes the text). Reads as a
6826        // value; lowers to the invisible `uuid(text)` builtin. Only fires when the identifier `uuid`
6827        // is immediately followed by a string, so a variable named `uuid` is unaffected.
6828        if matches!(self.peek().kind, TokenType::Identifier | TokenType::Noun(_))
6829            && self.interner.resolve(self.peek().lexeme).eq_ignore_ascii_case("uuid")
6830        {
6831            let str_sym = match self.tokens.get(self.current + 1).map(|t| &t.kind) {
6832                Some(TokenType::StringLiteral(s)) => Some(*s),
6833                _ => None,
6834            };
6835            if let Some(s) = str_sym {
6836                self.advance(); // "uuid"
6837                self.advance(); // the string
6838                let text = self.ctx.alloc_imperative_expr(Expr::Literal(Literal::Text(s)));
6839                let func = self.interner.intern("uuid");
6840                return Ok(self.ctx.alloc_imperative_expr(Expr::Call { function: func, args: vec![text] }));
6841            }
6842        }
6843
6844        let token = self.peek().clone();
6845        match &token.kind {
6846            // Phase 31: Constructor expression "new TypeName" or "a new TypeName"
6847            // Phase 33: Extended for variant constructors "new Circle with radius 10"
6848            // Phase 34: Extended for generic instantiation "new Box of Int"
6849            TokenType::New => {
6850                self.advance(); // consume "new"
6851                let base_type_name = self.expect_identifier()?;
6852
6853                // Phase 36: Check for "from Module" qualification
6854                let type_name = if self.check(&TokenType::From) {
6855                    self.advance(); // consume "from"
6856                    let module_name = self.expect_identifier()?;
6857                    let module_str = self.interner.resolve(module_name);
6858                    let base_str = self.interner.resolve(base_type_name);
6859                    let qualified = format!("{}::{}", module_str, base_str);
6860                    self.interner.intern(&qualified)
6861                } else {
6862                    base_type_name
6863                };
6864
6865                // Phase 33: Check if this is a variant constructor
6866                if let Some(enum_name) = self.find_variant(type_name) {
6867                    // Parse optional "with field value" pairs
6868                    let fields = if self.check_word("with") {
6869                        self.parse_variant_constructor_fields()?
6870                    } else {
6871                        vec![]
6872                    };
6873                    let base = self.ctx.alloc_imperative_expr(Expr::NewVariant {
6874                        enum_name,
6875                        variant: type_name,
6876                        fields,
6877                    });
6878                    return self.parse_field_access_chain(base);
6879                }
6880
6881                // Phase 34: Parse generic type arguments "of Int" or "of Int and Text"
6882                let type_args = self.parse_generic_type_args(type_name)?;
6883
6884                // Parse optional "with field value" pairs for struct initialization
6885                // But NOT "with capacity" — that's a pre-allocation hint handled by parse_let_statement
6886                let init_fields = if self.check_word("with") && !self.peek_word_at(1, "capacity") {
6887                    self.parse_struct_init_fields()?
6888                } else {
6889                    vec![]
6890                };
6891
6892                let base = self.ctx.alloc_imperative_expr(Expr::New { type_name, type_args, init_fields });
6893                return self.parse_field_access_chain(base);
6894            }
6895
6896            // Phase 31: Handle "a new TypeName" pattern OR single-letter identifier
6897            // Phase 33: Extended for variant constructors "a new Circle with radius 10"
6898            // Phase 34: Extended for generic instantiation "a new Box of Int"
6899            TokenType::Article(_) => {
6900                // Natural date-component accessor: `the year|month|day|weekday of <moment>` reads as
6901                // a sentence and desugars to the extractor (which threads through every tier). The
6902                // component words lex as CalendarUnit/Noun; we match by lexeme and require `of` next.
6903                let comp_lex = self.tokens.get(self.current + 1).map(|t| t.lexeme);
6904                let of_lex = self.tokens.get(self.current + 2).map(|t| t.lexeme);
6905                if let (Some(cl), Some(ol)) = (comp_lex, of_lex) {
6906                    let extractor = match self.interner.resolve(cl).to_ascii_lowercase().as_str() {
6907                        "year" => Some("year_of"),
6908                        "month" => Some("month_of"),
6909                        "day" => Some("day_of"),
6910                        "weekday" => Some("weekday_of"),
6911                        "hour" => Some("hour_of"),
6912                        "minute" => Some("minute_of"),
6913                        "second" => Some("second_of"),
6914                        "date" => Some("date_of"),
6915                        "time" => Some("time_of"),
6916                        "week" => Some("week_of"),
6917                        "quarter" => Some("quarter_of"),
6918                        _ => None,
6919                    };
6920                    if let Some(extractor) = extractor {
6921                        if self.interner.resolve(ol).eq_ignore_ascii_case("of") {
6922                            self.advance(); // "the"
6923                            self.advance(); // component noun
6924                            self.advance(); // "of"
6925                            let mut operand = self.parse_primary_expr()?;
6926                            // Zoned read: `the hour of m in "Asia/Tokyo"` → the LOCAL component. We
6927                            // intercept the `in "<zone>"` here (before it reaches the in_zone postfix,
6928                            // which would wrongly wrap the resulting Int) and shift the operand to the
6929                            // zone's local-as-UTC instant, so the SAME extractor yields the local field.
6930                            if self.check(&TokenType::In) || self.check_preposition_is("in") {
6931                                let zone_sym = match self.tokens.get(self.current + 1).map(|t| &t.kind) {
6932                                    Some(TokenType::StringLiteral(z)) => Some(*z),
6933                                    _ => None,
6934                                };
6935                                if let Some(z) = zone_sym {
6936                                    self.advance(); // "in"
6937                                    self.advance(); // the zone string
6938                                    let zone = self
6939                                        .ctx
6940                                        .alloc_imperative_expr(Expr::Literal(crate::ast::Literal::Text(z)));
6941                                    let local = self.interner.intern("local_instant");
6942                                    operand = self.ctx.alloc_imperative_expr(Expr::Call {
6943                                        function: local,
6944                                        args: vec![operand, zone],
6945                                    });
6946                                }
6947                            }
6948                            let func = self.interner.intern(extractor);
6949                            return Ok(self
6950                                .ctx
6951                                .alloc_imperative_expr(Expr::Call { function: func, args: vec![operand] }));
6952                        }
6953                    }
6954                    // Natural elapsed-time: `the <unit> between <a> and <b>`. FIXED-width units
6955                    // (second…week) divide the second-difference; CALENDAR units (month/year) count
6956                    // complete periods (a month is 28–31 days, so it cannot be a fixed division).
6957                    let unit_lexeme = self.interner.resolve(cl).to_ascii_lowercase();
6958                    let per_unit: Option<i64> = match unit_lexeme.as_str() {
6959                        "second" | "seconds" => Some(1),
6960                        "minute" | "minutes" => Some(60),
6961                        "hour" | "hours" => Some(3600),
6962                        "day" | "days" => Some(86400),
6963                        "week" | "weeks" => Some(604800),
6964                        _ => None,
6965                    };
6966                    let cal_func: Option<&str> = match unit_lexeme.as_str() {
6967                        "month" | "months" => Some("months_between"),
6968                        "year" | "years" => Some("years_between"),
6969                        _ => None,
6970                    };
6971                    if (per_unit.is_some() || cal_func.is_some())
6972                        && self.interner.resolve(ol).eq_ignore_ascii_case("between")
6973                    {
6974                        self.advance(); // "the"
6975                        self.advance(); // unit noun
6976                        self.advance(); // "between"
6977                        let a = self.parse_primary_expr()?;
6978                        if !(self.check(&TokenType::And) || self.check_word("and")) {
6979                            return Err(ParseError {
6980                                kind: ParseErrorKind::ExpectedKeyword { keyword: "and".to_string() },
6981                                span: self.current_span(),
6982                            });
6983                        }
6984                        self.advance(); // "and"
6985                        let b = self.parse_primary_expr()?;
6986                        if let Some(cal) = cal_func {
6987                            let func = self.interner.intern(cal);
6988                            return Ok(self
6989                                .ctx
6990                                .alloc_imperative_expr(Expr::Call { function: func, args: vec![a, b] }));
6991                        }
6992                        let per_unit = per_unit.unwrap();
6993                        let func = self.interner.intern("seconds_between");
6994                        let secs = self.ctx.alloc_imperative_expr(Expr::Call { function: func, args: vec![a, b] });
6995                        return Ok(if per_unit == 1 {
6996                            secs
6997                        } else {
6998                            let d = self.ctx.alloc_imperative_expr(Expr::Literal(Literal::Number(per_unit)));
6999                            self.ctx.alloc_imperative_expr(Expr::BinaryOp {
7000                                op: BinaryOpKind::Divide,
7001                                left: secs,
7002                                right: d,
7003                            })
7004                        });
7005                    }
7006                }
7007                // Phase 48: Check if followed by Manifest or Chunk token
7008                // Pattern: "the manifest of Zone" or "the chunk at N in Zone"
7009                if let Some(next) = self.tokens.get(self.current + 1) {
7010                    if matches!(next.kind, TokenType::Manifest) {
7011                        self.advance(); // consume "the"
7012                        // Delegate to Manifest handling
7013                        return self.parse_primary_expr();
7014                    }
7015                    if matches!(next.kind, TokenType::Chunk) {
7016                        self.advance(); // consume "the"
7017                        // Delegate to Chunk handling
7018                        return self.parse_primary_expr();
7019                    }
7020                    if matches!(next.kind, TokenType::Length) {
7021                        self.advance(); // consume "the"
7022                        return self.parse_primary_expr();
7023                    }
7024                }
7025                // Check if followed by New token
7026                if let Some(next) = self.tokens.get(self.current + 1) {
7027                    if matches!(next.kind, TokenType::New) {
7028                        self.advance(); // consume article "a"/"an"
7029                        self.advance(); // consume "new"
7030                        let base_type_name = self.expect_identifier()?;
7031
7032                        // Phase 36: Check for "from Module" qualification
7033                        let type_name = if self.check(&TokenType::From) {
7034                            self.advance(); // consume "from"
7035                            let module_name = self.expect_identifier()?;
7036                            let module_str = self.interner.resolve(module_name);
7037                            let base_str = self.interner.resolve(base_type_name);
7038                            let qualified = format!("{}::{}", module_str, base_str);
7039                            self.interner.intern(&qualified)
7040                        } else {
7041                            base_type_name
7042                        };
7043
7044                        // Phase 33: Check if this is a variant constructor
7045                        if let Some(enum_name) = self.find_variant(type_name) {
7046                            // Parse optional "with field value" pairs
7047                            let fields = if self.check_word("with") {
7048                                self.parse_variant_constructor_fields()?
7049                            } else {
7050                                vec![]
7051                            };
7052                            let base = self.ctx.alloc_imperative_expr(Expr::NewVariant {
7053                                enum_name,
7054                                variant: type_name,
7055                                fields,
7056                            });
7057                            return self.parse_field_access_chain(base);
7058                        }
7059
7060                        // Phase 34: Parse generic type arguments "of Int" or "of Int and Text"
7061                        let type_args = self.parse_generic_type_args(type_name)?;
7062
7063                        // Parse optional "with field value" pairs for struct initialization
7064                        // But NOT "with capacity" — that's a pre-allocation hint handled by parse_let_statement
7065                        let init_fields = if self.check_word("with") && !self.peek_word_at(1, "capacity") {
7066                            self.parse_struct_init_fields()?
7067                        } else {
7068                            vec![]
7069                        };
7070
7071                        let base = self.ctx.alloc_imperative_expr(Expr::New { type_name, type_args, init_fields });
7072                        return self.parse_field_access_chain(base);
7073                    }
7074                }
7075                // Phase 32: Treat as identifier (single-letter var like "a", "b")
7076                let sym = token.lexeme;
7077                self.advance();
7078                let base = self.ctx.alloc_imperative_expr(Expr::Identifier(sym));
7079                return self.parse_field_access_chain(base);
7080            }
7081
7082            // Index access: "item N of collection" or "item i of collection"
7083            TokenType::Item => {
7084                self.advance(); // consume "item"
7085
7086                // Grand Challenge: Parse index as expression (number, identifier, or parenthesized)
7087                let index = if let TokenType::Number(sym) = &self.peek().kind {
7088                    // Literal numeral — 0 is guarded, floats are real keys.
7089                    let sym = *sym;
7090                    self.advance();
7091                    self.parse_index_numeral(sym)?
7092                } else if self.check(&TokenType::Minus)
7093                    && matches!(
7094                        self.tokens.get(self.current + 1).map(|t| &t.kind),
7095                        Some(TokenType::Number(_))
7096                    )
7097                {
7098                    // Negative index literal — end-relative (`item -1 of xs`
7099                    // is the last element).
7100                    self.advance(); // consume '-'
7101                    let TokenType::Number(sym) = self.peek().kind else { unreachable!() };
7102                    self.advance();
7103                    let num_str = self.interner.resolve(sym).to_string();
7104                    let n = self.parse_i64_numeral_signed(&num_str, true).map_err(|_| ParseError {
7105                        kind: ParseErrorKind::ExpectedKeyword {
7106                            keyword: format!("a numeric index (got '{}')", num_str),
7107                        },
7108                        span: self.current_span(),
7109                    })?;
7110                    self.ctx
7111                        .alloc_imperative_expr(Expr::Literal(crate::ast::Literal::Number(n)))
7112                } else if self.check(&TokenType::LParen) {
7113                    // Parenthesized expression like (mid + 1) — or a TUPLE
7114                    // key like ("a", 1) (tuples are content-keyed map keys).
7115                    self.advance(); // consume '('
7116                    let first = self.parse_imperative_expr()?;
7117                    let inner = if self.check(&TokenType::Comma) {
7118                        let mut items = vec![first];
7119                        while self.check(&TokenType::Comma) {
7120                            self.advance(); // consume ","
7121                            items.push(self.parse_imperative_expr()?);
7122                        }
7123                        self.ctx.alloc_imperative_expr(Expr::Tuple(items))
7124                    } else {
7125                        first
7126                    };
7127                    if !self.check(&TokenType::RParen) {
7128                        return Err(ParseError {
7129                            kind: ParseErrorKind::ExpectedKeyword { keyword: ")".to_string() },
7130                            span: self.current_span(),
7131                        });
7132                    }
7133                    self.advance(); // consume ')'
7134                    inner
7135                } else if let TokenType::StringLiteral(sym) = self.peek().kind {
7136                    // Phase 57B: String literal key for Map access like item "iron" of prices
7137                    let sym = sym;
7138                    self.advance();
7139                    self.ctx.alloc_imperative_expr(Expr::Literal(crate::ast::Literal::Text(sym)))
7140                } else if self.check(&TokenType::LBracket) {
7141                    // `item [1, 2] of m` — a List as a map key, rejected by
7142                    // design (a live List can mutate after insertion and the
7143                    // map would lose the entry). Name the key problem here;
7144                    // the generic missing-'of' error buries the lesson.
7145                    return Err(ParseError {
7146                        kind: ParseErrorKind::Custom(
7147                            "A List cannot be a map key: keys must be immutable, and a live \
7148                             List can change after insertion — the map would lose the entry. \
7149                             Which fixed value identifies this entry? Use an Int or Text key, \
7150                             or a tuple like ('a', 1) for a compound key."
7151                                .to_string(),
7152                        ),
7153                        span: self.peek().span,
7154                    });
7155                } else if !self.check_preposition_is("of") {
7156                    // Check for boolean literals before treating as variable
7157                    let word = self.interner.resolve(self.peek().lexeme);
7158                    if word == "true" {
7159                        self.advance();
7160                        self.ctx.alloc_imperative_expr(Expr::Literal(crate::ast::Literal::Boolean(true)))
7161                    } else if word == "false" {
7162                        self.advance();
7163                        self.ctx.alloc_imperative_expr(Expr::Literal(crate::ast::Literal::Boolean(false)))
7164                    } else {
7165                        // Variable identifier like i, j, idx (any token that's not "of")
7166                        let sym = self.peek().lexeme;
7167                        self.advance();
7168                        self.ctx.alloc_imperative_expr(Expr::Identifier(sym))
7169                    }
7170                } else {
7171                    return Err(ParseError {
7172                        kind: ParseErrorKind::ExpectedExpression,
7173                        span: self.current_span(),
7174                    });
7175                };
7176
7177                // Expect "of"
7178                if !self.check_preposition_is("of") {
7179                    return Err(ParseError {
7180                        kind: ParseErrorKind::ExpectedKeyword { keyword: "of".to_string() },
7181                        span: self.current_span(),
7182                    });
7183                }
7184                self.advance(); // consume "of"
7185
7186                // Parse collection as primary expression (identifier or field chain)
7187                // Using primary_expr instead of imperative_expr prevents consuming operators
7188                let collection = self.parse_primary_expr()?;
7189
7190                Ok(self.ctx.alloc_imperative_expr(Expr::Index {
7191                    collection,
7192                    index,
7193                }))
7194            }
7195
7196            // Slice access: "items N through M of collection"
7197            // OR variable named "items" - disambiguate by checking if next token starts an expression
7198            TokenType::Items => {
7199                // Peek ahead to determine if this is slice syntax or variable usage
7200                // Slice syntax: "items" followed by number or paren (clear indicators of index)
7201                // Variable: "items" followed by something else (operator, dot, etc.)
7202                let is_slice_syntax = if let Some(next) = self.tokens.get(self.current + 1) {
7203                    matches!(next.kind, TokenType::Number(_) | TokenType::LParen)
7204                } else {
7205                    false
7206                };
7207
7208                if !is_slice_syntax {
7209                    // Treat "items" as a variable identifier
7210                    let sym = token.lexeme;
7211                    self.advance();
7212                    let base = self.ctx.alloc_imperative_expr(Expr::Identifier(sym));
7213                    return self.parse_field_access_chain(base);
7214                }
7215
7216                self.advance(); // consume "items"
7217
7218                // Grand Challenge: Parse start index as expression (number, identifier, or parenthesized)
7219                let start = if let TokenType::Number(sym) = &self.peek().kind {
7220                    // Literal number - check for zero index at compile time
7221                    let sym = *sym;
7222                    self.advance();
7223                    self.parse_index_numeral(sym)?
7224                } else if self.check(&TokenType::LParen) {
7225                    // Parenthesized expression like (mid + 1)
7226                    self.advance(); // consume '('
7227                    let inner = self.parse_imperative_expr()?;
7228                    if !self.check(&TokenType::RParen) {
7229                        return Err(ParseError {
7230                            kind: ParseErrorKind::ExpectedKeyword { keyword: ")".to_string() },
7231                            span: self.current_span(),
7232                        });
7233                    }
7234                    self.advance(); // consume ')'
7235                    inner
7236                } else if self.check(&TokenType::Length) {
7237                    // "length of X" compound expression as start index
7238                    self.advance(); // consume "length"
7239                    if self.check_preposition_is("of") {
7240                        self.advance(); // consume "of"
7241                        let target = self.parse_primary_expr()?;
7242                        self.ctx.alloc_imperative_expr(Expr::Length { collection: target })
7243                    } else {
7244                        // Bare "length" as identifier
7245                        let sym = self.tokens[self.current - 1].lexeme;
7246                        self.ctx.alloc_imperative_expr(Expr::Identifier(sym))
7247                    }
7248                } else if !self.check_preposition_is("through") {
7249                    // Variable identifier like mid, idx
7250                    let sym = self.peek().lexeme;
7251                    self.advance();
7252                    self.ctx.alloc_imperative_expr(Expr::Identifier(sym))
7253                } else {
7254                    return Err(ParseError {
7255                        kind: ParseErrorKind::ExpectedExpression,
7256                        span: self.current_span(),
7257                    });
7258                };
7259
7260                // Expect "through"
7261                if !self.check_preposition_is("through") {
7262                    return Err(ParseError {
7263                        kind: ParseErrorKind::ExpectedKeyword { keyword: "through".to_string() },
7264                        span: self.current_span(),
7265                    });
7266                }
7267                self.advance(); // consume "through"
7268
7269                // Grand Challenge: Parse end index as expression (number, identifier, or parenthesized)
7270                let end = if let TokenType::Number(sym) = &self.peek().kind {
7271                    // Literal number - check for zero index at compile time
7272                    let sym = *sym;
7273                    self.advance();
7274                    self.parse_index_numeral(sym)?
7275                } else if self.check(&TokenType::LParen) {
7276                    // Parenthesized expression like (mid + 1)
7277                    self.advance(); // consume '('
7278                    let inner = self.parse_imperative_expr()?;
7279                    if !self.check(&TokenType::RParen) {
7280                        return Err(ParseError {
7281                            kind: ParseErrorKind::ExpectedKeyword { keyword: ")".to_string() },
7282                            span: self.current_span(),
7283                        });
7284                    }
7285                    self.advance(); // consume ')'
7286                    inner
7287                } else if self.check(&TokenType::Length) {
7288                    // "length of X" compound expression as end index
7289                    self.advance(); // consume "length"
7290                    if self.check_preposition_is("of") {
7291                        self.advance(); // consume "of"
7292                        let target = self.parse_primary_expr()?;
7293                        self.ctx.alloc_imperative_expr(Expr::Length { collection: target })
7294                    } else {
7295                        // Bare "length" as identifier
7296                        let sym = self.tokens[self.current - 1].lexeme;
7297                        self.ctx.alloc_imperative_expr(Expr::Identifier(sym))
7298                    }
7299                } else if !self.check_preposition_is("of") {
7300                    // Variable identifier like n, mid
7301                    let sym = self.peek().lexeme;
7302                    self.advance();
7303                    self.ctx.alloc_imperative_expr(Expr::Identifier(sym))
7304                } else {
7305                    return Err(ParseError {
7306                        kind: ParseErrorKind::ExpectedExpression,
7307                        span: self.current_span(),
7308                    });
7309                };
7310
7311                // "of collection" is now optional - collection can be inferred from context
7312                // (e.g., "items 1 through mid" when items is the local variable)
7313                let collection = if self.check_preposition_is("of") {
7314                    self.advance(); // consume "of"
7315                    self.parse_imperative_expr()?
7316                } else {
7317                    // The variable is the collection itself (already consumed as "items")
7318                    // Re-intern "items" to use as the collection identifier
7319                    let items_sym = self.interner.intern("items");
7320                    self.ctx.alloc_imperative_expr(Expr::Identifier(items_sym))
7321                };
7322
7323                Ok(self.ctx.alloc_imperative_expr(Expr::Slice {
7324                    collection,
7325                    start,
7326                    end,
7327                }))
7328            }
7329
7330            // List literal: [1, 2, 3]
7331            TokenType::LBracket => {
7332                self.advance(); // consume "["
7333
7334                let mut items = Vec::new();
7335                if !self.check(&TokenType::RBracket) {
7336                    loop {
7337                        items.push(self.parse_imperative_expr()?);
7338                        if !self.check(&TokenType::Comma) {
7339                            break;
7340                        }
7341                        self.advance(); // consume ","
7342                        // A trailing comma (`[1, 2, 3,]`) ends the list.
7343                        if self.check(&TokenType::RBracket) {
7344                            break;
7345                        }
7346                    }
7347                }
7348
7349                if !self.check(&TokenType::RBracket) {
7350                    return Err(ParseError {
7351                        kind: ParseErrorKind::ExpectedKeyword { keyword: "]".to_string() },
7352                        span: self.current_span(),
7353                    });
7354                }
7355                self.advance(); // consume "]"
7356
7357                // Check for typed empty list: [] of Int
7358                if items.is_empty() && self.check_word("of") {
7359                    self.advance(); // consume "of"
7360                    let type_name = self.expect_identifier()?;
7361                    // Generate: Seq::<Type>::default()
7362                    let seq_sym = self.interner.intern("Seq");
7363                    return Ok(self.ctx.alloc_imperative_expr(Expr::New {
7364                        type_name: seq_sym,
7365                        type_args: vec![TypeExpr::Named(type_name)],
7366                        init_fields: vec![],
7367                    }));
7368                }
7369
7370                Ok(self.ctx.alloc_imperative_expr(Expr::List(items)))
7371            }
7372
7373            // `{k: v, …}` map literal / `{a, b, …}` set literal. A `:` after
7374            // the first element decides map-ness; the empty form requires an
7375            // element type exactly like `[] of Int` — `{} of Int` (set) /
7376            // `{} of Text to Int` (map). Non-empty literals lower to the
7377            // variadic `mapOf`/`setOf` builtins, so every engine shares one
7378            // construction path.
7379            TokenType::LBrace => {
7380                self.advance(); // consume "{"
7381
7382                if self.check(&TokenType::RBrace) {
7383                    self.advance(); // consume "}"
7384                    if !self.check_word("of") {
7385                        return Err(ParseError {
7386                            kind: ParseErrorKind::ExpectedKeyword {
7387                                keyword: "of — an empty {} needs its element type: `{} of Int` (set) or `{} of Text to Int` (map)".to_string(),
7388                            },
7389                            span: self.current_span(),
7390                        });
7391                    }
7392                    self.advance(); // consume "of"
7393                    let first_ty = self.expect_identifier()?;
7394                    if self.check(&TokenType::To) || self.check_preposition_is("to") {
7395                        self.advance(); // consume "to"
7396                        let val_ty = self.expect_identifier()?;
7397                        let map_sym = self.interner.intern("Map");
7398                        return Ok(self.ctx.alloc_imperative_expr(Expr::New {
7399                            type_name: map_sym,
7400                            type_args: vec![TypeExpr::Named(first_ty), TypeExpr::Named(val_ty)],
7401                            init_fields: vec![],
7402                        }));
7403                    }
7404                    let set_sym = self.interner.intern("Set");
7405                    return Ok(self.ctx.alloc_imperative_expr(Expr::New {
7406                        type_name: set_sym,
7407                        type_args: vec![TypeExpr::Named(first_ty)],
7408                        init_fields: vec![],
7409                    }));
7410                }
7411
7412                let first = self.parse_imperative_expr()?;
7413                if self.check(&TokenType::Colon) {
7414                    // Map literal: `{k: v, …}` → `mapOf(k, v, …)` (pairs flat,
7415                    // insertion order preserved by the ordered-map contract).
7416                    self.advance(); // consume ":"
7417                    let mut args = vec![first, self.parse_imperative_expr()?];
7418                    while self.check(&TokenType::Comma) {
7419                        self.advance(); // consume ","
7420                        // A trailing comma (`{a: 1, b: 2,}`) ends the map.
7421                        if self.check(&TokenType::RBrace) {
7422                            break;
7423                        }
7424                        args.push(self.parse_imperative_expr()?);
7425                        if !self.check(&TokenType::Colon) {
7426                            return Err(ParseError {
7427                                kind: ParseErrorKind::ExpectedKeyword { keyword: ":".to_string() },
7428                                span: self.current_span(),
7429                            });
7430                        }
7431                        self.advance(); // consume ":"
7432                        args.push(self.parse_imperative_expr()?);
7433                    }
7434                    if !self.check(&TokenType::RBrace) {
7435                        return Err(ParseError {
7436                            kind: ParseErrorKind::ExpectedKeyword { keyword: "}".to_string() },
7437                            span: self.current_span(),
7438                        });
7439                    }
7440                    self.advance(); // consume "}"
7441                    let map_of = self.interner.intern("mapOf");
7442                    return Ok(self.ctx.alloc_imperative_expr(Expr::Call { function: map_of, args }));
7443                }
7444
7445                // Set literal: `{a, b, …}` → `setOf(a, b, …)`.
7446                let mut args = vec![first];
7447                while self.check(&TokenType::Comma) {
7448                    self.advance(); // consume ","
7449                    // A trailing comma (`{a, b, c,}`) ends the set.
7450                    if self.check(&TokenType::RBrace) {
7451                        break;
7452                    }
7453                    args.push(self.parse_imperative_expr()?);
7454                }
7455                if !self.check(&TokenType::RBrace) {
7456                    return Err(ParseError {
7457                        kind: ParseErrorKind::ExpectedKeyword { keyword: "}".to_string() },
7458                        span: self.current_span(),
7459                    });
7460                }
7461                self.advance(); // consume "}"
7462                let set_of = self.interner.intern("setOf");
7463                Ok(self.ctx.alloc_imperative_expr(Expr::Call { function: set_of, args }))
7464            }
7465
7466            // Currency-symbol money literal (`$19.99`, `€5`, `¥100`) desugars to `money(num, "CODE")`,
7467            // the same shape as the spelled `19.99 USD` form. An exact (decimal) magnitude rides
7468            // `decimal(..)` so it never float-drifts; an integer magnitude passes through directly.
7469            TokenType::MoneyLiteral { amount, currency } => {
7470                let amount = *amount;
7471                let currency = *currency;
7472                let num_str = self.interner.resolve(amount).to_string();
7473                self.advance();
7474                let num_expr = if num_str.contains('.') {
7475                    let text = self.ctx.alloc_imperative_expr(Expr::Literal(Literal::Text(amount)));
7476                    let dec = self.interner.intern("decimal");
7477                    self.ctx.alloc_imperative_expr(Expr::Call { function: dec, args: vec![text] })
7478                } else {
7479                    let num = self.parse_i64_numeral(&num_str)?;
7480                    self.ctx.alloc_imperative_expr(Expr::Literal(Literal::Number(num)))
7481                };
7482                let code_expr = self.ctx.alloc_imperative_expr(Expr::Literal(Literal::Text(currency)));
7483                let func = self.interner.intern("money");
7484                return Ok(self
7485                    .ctx
7486                    .alloc_imperative_expr(Expr::Call { function: func, args: vec![num_expr, code_expr] }));
7487            }
7488
7489            TokenType::Number(sym) => {
7490                let num_str = self.interner.resolve(*sym).to_string();
7491                self.advance();
7492
7493                // Check if followed by CalendarUnit → Span literal
7494                if let TokenType::CalendarUnit(unit) = self.peek().kind {
7495                    return self.parse_span_literal_from_num(&num_str);
7496                }
7497
7498                // Dimensioned quantity literal: a number immediately followed by a unit word
7499                // (`2 inches`, `5 kilograms`, `20 celsius`) desugars to `quantity(num, "unit")`.
7500                // Imperative-only (this path allocs imperative exprs), so natural-language phrases
7501                // like "ran 2 miles" — parsed by the clause grammar — are untouched. An exact
7502                // (decimal) magnitude rides the `decimal(..)` constructor so it never float-drifts.
7503                if matches!(
7504                    self.peek().kind,
7505                    TokenType::Identifier | TokenType::Noun(_) | TokenType::Adjective(_)
7506                ) {
7507                    let unit_lexeme = self.peek().lexeme;
7508                    let is_unit =
7509                        logicaffeine_base::quantity::units::by_name(self.interner.resolve(unit_lexeme)).is_some();
7510                    if is_unit {
7511                        self.advance(); // consume the unit word
7512                        let num_expr = if num_str.contains('.') || num_str.contains('e') || num_str.contains('E') {
7513                            let text = self.ctx.alloc_imperative_expr(Expr::Literal(Literal::Text(*sym)));
7514                            let dec = self.interner.intern("decimal");
7515                            self.ctx.alloc_imperative_expr(Expr::Call { function: dec, args: vec![text] })
7516                        } else {
7517                            let num = self.parse_i64_numeral(&num_str)?;
7518                            self.ctx.alloc_imperative_expr(Expr::Literal(Literal::Number(num)))
7519                        };
7520                        let unit_expr = self.ctx.alloc_imperative_expr(Expr::Literal(Literal::Text(unit_lexeme)));
7521                        let func = self.interner.intern("quantity");
7522                        return Ok(self
7523                            .ctx
7524                            .alloc_imperative_expr(Expr::Call { function: func, args: vec![num_expr, unit_expr] }));
7525                    }
7526                }
7527
7528                // `19.99 USD` / `5 EUR` desugars to `money(num, "CODE")` — the same shape as the unit
7529                // sugar above, but the trailing token is an ISO-4217 currency code (checked AFTER
7530                // units so a unit word is never mis-read as money, and gated on `currency::by_code` so
7531                // only a real code triggers). An exact (decimal) amount rides `decimal(..)`.
7532                if matches!(
7533                    self.peek().kind,
7534                    TokenType::Identifier
7535                        | TokenType::Noun(_)
7536                        | TokenType::ProperName(_)
7537                        | TokenType::Adjective(_)
7538                ) {
7539                    let code_lexeme = self.peek().lexeme;
7540                    if logicaffeine_base::money::currency::by_code(self.interner.resolve(code_lexeme)).is_some() {
7541                        self.advance(); // consume the currency code
7542                        let num_expr = if num_str.contains('.') || num_str.contains('e') || num_str.contains('E') {
7543                            let text = self.ctx.alloc_imperative_expr(Expr::Literal(Literal::Text(*sym)));
7544                            let dec = self.interner.intern("decimal");
7545                            self.ctx.alloc_imperative_expr(Expr::Call { function: dec, args: vec![text] })
7546                        } else {
7547                            let num = self.parse_i64_numeral(&num_str)?;
7548                            self.ctx.alloc_imperative_expr(Expr::Literal(Literal::Number(num)))
7549                        };
7550                        let code_expr = self.ctx.alloc_imperative_expr(Expr::Literal(Literal::Text(code_lexeme)));
7551                        let func = self.interner.intern("money");
7552                        return Ok(self
7553                            .ctx
7554                            .alloc_imperative_expr(Expr::Call { function: func, args: vec![num_expr, code_expr] }));
7555                    }
7556                }
7557
7558                // Radix first (`0x1E` has an `E` that is a hex digit, not
7559                // scientific notation); then the float sniff; both parse
7560                // EXACTLY or fail loudly — never a silent 0.
7561                if Self::is_radix_numeral(&num_str) {
7562                    let num = self.parse_i64_numeral(&num_str)?;
7563                    Ok(self.ctx.alloc_imperative_expr(Expr::Literal(Literal::Number(num))))
7564                } else if num_str.contains('.') || num_str.contains('e') || num_str.contains('E') {
7565                    let num = num_str.replace('_', "").parse::<f64>().map_err(|_| ParseError {
7566                        kind: ParseErrorKind::ExpectedKeyword {
7567                            keyword: format!("a Float literal (got `{}`)", num_str),
7568                        },
7569                        span: self.current_span(),
7570                    })?;
7571                    Ok(self.ctx.alloc_imperative_expr(Expr::Literal(Literal::Float(num))))
7572                } else {
7573                    let num = self.parse_i64_numeral(&num_str)?;
7574                    Ok(self.ctx.alloc_imperative_expr(Expr::Literal(Literal::Number(num))))
7575                }
7576            }
7577
7578            // Phase 33: String literals
7579            TokenType::StringLiteral(sym) => {
7580                self.advance();
7581                Ok(self.ctx.alloc_imperative_expr(Expr::Literal(Literal::Text(*sym))))
7582            }
7583
7584            // String interpolation: "Hello, {name}!"
7585            TokenType::InterpolatedString(sym) => {
7586                let raw = self.interner.resolve(*sym).to_string();
7587                self.advance();
7588                let parts = self.parse_interpolation_parts(&raw)?;
7589                Ok(self.ctx.alloc_imperative_expr(Expr::InterpolatedString(parts)))
7590            }
7591
7592            // Character literals
7593            TokenType::CharLiteral(sym) => {
7594                let char_str = self.interner.resolve(*sym);
7595                let ch = char_str.chars().next().unwrap_or('\0');
7596                self.advance();
7597                Ok(self.ctx.alloc_imperative_expr(Expr::Literal(Literal::Char(ch))))
7598            }
7599
7600            // Duration literals: 500ms, 2s, 50ns
7601            TokenType::DurationLiteral { nanos, .. } => {
7602                let nanos = *nanos;
7603                self.advance();
7604                Ok(self.ctx.alloc_imperative_expr(Expr::Literal(Literal::Duration(nanos))))
7605            }
7606
7607            // Date literals: 2026-05-20
7608            // Also handles "DATE at TIME" → Moment
7609            TokenType::DateLiteral { days } => {
7610                let days = *days;
7611                self.advance();
7612
7613                // Check for "at TIME" to create a Moment
7614                if self.check(&TokenType::At) {
7615                    self.advance(); // consume "at"
7616
7617                    // Expect a TimeLiteral
7618                    if let TokenType::TimeLiteral { nanos_from_midnight } = self.peek().kind {
7619                        let time_nanos = nanos_from_midnight;
7620                        self.advance(); // consume time literal
7621
7622                        // Convert to Moment: days * 86400 * 1e9 + time_nanos
7623                        let moment_nanos = (days as i64) * 86_400_000_000_000 + time_nanos;
7624                        return Ok(self.ctx.alloc_imperative_expr(Expr::Literal(Literal::Moment(moment_nanos))));
7625                    } else {
7626                        return Err(ParseError {
7627                            kind: ParseErrorKind::ExpectedExpression,
7628                            span: self.current_span(),
7629                        });
7630                    }
7631                }
7632
7633                Ok(self.ctx.alloc_imperative_expr(Expr::Literal(Literal::Date(days))))
7634            }
7635
7636            // Time-of-day literals: 4pm, 9:30am, noon, midnight
7637            TokenType::TimeLiteral { nanos_from_midnight } => {
7638                let nanos = *nanos_from_midnight;
7639                self.advance();
7640                Ok(self.ctx.alloc_imperative_expr(Expr::Literal(Literal::Time(nanos))))
7641            }
7642
7643            // Handle 'nothing' literal
7644            TokenType::Nothing => {
7645                self.advance();
7646                Ok(self.ctx.alloc_imperative_expr(Expr::Literal(Literal::Nothing)))
7647            }
7648
7649            // Option constructors: "some <expr>" → Some(expr), "none" → None
7650            TokenType::Some => {
7651                self.advance(); // consume "some"
7652                let value = self.parse_imperative_expr()?;
7653                Ok(self.ctx.alloc_imperative_expr(Expr::OptionSome { value }))
7654            }
7655
7656            // Phase 43D: Length expression: "length of items" or "length(items)"
7657            TokenType::Length => {
7658                let func_name = self.peek().lexeme;
7659
7660                // Check for function call syntax: length(x)
7661                if self.tokens.get(self.current + 1)
7662                    .map(|t| matches!(t.kind, TokenType::LParen))
7663                    .unwrap_or(false)
7664                {
7665                    self.advance(); // consume "length"
7666                    return self.parse_call_expr(func_name);
7667                }
7668
7669                self.advance(); // consume "length"
7670
7671                // Expect "of" for natural syntax
7672                if !self.check_preposition_is("of") {
7673                    return Err(ParseError {
7674                        kind: ParseErrorKind::ExpectedKeyword { keyword: "of".to_string() },
7675                        span: self.current_span(),
7676                    });
7677                }
7678                self.advance(); // consume "of"
7679
7680                // Use parse_primary_expr so "length of arr / 2" binds as
7681                // "(length of arr) / 2" rather than "length of (arr / 2)"
7682                let collection = self.parse_primary_expr()?;
7683                Ok(self.ctx.alloc_imperative_expr(Expr::Length { collection }))
7684            }
7685
7686            // Phase 43D: Copy expression: "copy of slice" or "copy(slice)"
7687            TokenType::Copy => {
7688                let func_name = self.peek().lexeme;
7689
7690                // Check for function call syntax: copy(x)
7691                if self.tokens.get(self.current + 1)
7692                    .map(|t| matches!(t.kind, TokenType::LParen))
7693                    .unwrap_or(false)
7694                {
7695                    self.advance(); // consume "copy"
7696                    return self.parse_call_expr(func_name);
7697                }
7698
7699                self.advance(); // consume "copy"
7700
7701                // Expect "of" for natural syntax
7702                if !self.check_preposition_is("of") {
7703                    return Err(ParseError {
7704                        kind: ParseErrorKind::ExpectedKeyword { keyword: "of".to_string() },
7705                        span: self.current_span(),
7706                    });
7707                }
7708                self.advance(); // consume "of"
7709
7710                let expr = self.parse_imperative_expr()?;
7711                Ok(self.ctx.alloc_imperative_expr(Expr::Copy { expr }))
7712            }
7713
7714            // Phase 48: Manifest expression: "manifest of Zone"
7715            TokenType::Manifest => {
7716                self.advance(); // consume "manifest"
7717
7718                // Expect "of"
7719                if !self.check_preposition_is("of") {
7720                    return Err(ParseError {
7721                        kind: ParseErrorKind::ExpectedKeyword { keyword: "of".to_string() },
7722                        span: self.current_span(),
7723                    });
7724                }
7725                self.advance(); // consume "of"
7726
7727                let zone = self.parse_imperative_expr()?;
7728                Ok(self.ctx.alloc_imperative_expr(Expr::ManifestOf { zone }))
7729            }
7730
7731            // Phase 48: Chunk expression: "chunk at N in Zone"
7732            TokenType::Chunk => {
7733                self.advance(); // consume "chunk"
7734
7735                // Expect "at"
7736                if !self.check(&TokenType::At) {
7737                    return Err(ParseError {
7738                        kind: ParseErrorKind::ExpectedKeyword { keyword: "at".to_string() },
7739                        span: self.current_span(),
7740                    });
7741                }
7742                self.advance(); // consume "at"
7743
7744                // The index is a dimensionless position, so it is parsed BELOW every construct that
7745                // consumes `in`: `parse_xor_expr`, not `parse_imperative_expr`/`parse_or_condition`.
7746                // The `in` here is the chunk construct's own keyword and must not be swallowed as an
7747                // `in "<zone>"` / `in <unit>` conversion (handled above `parse_or_condition`) nor as
7748                // Pythonic membership `<index> in <collection>` (handled in `parse_comparison`, which
7749                // `parse_or_condition` descends through).
7750                let index = self.parse_xor_expr()?;
7751
7752                // Expect "in"
7753                if !self.check_preposition_is("in") && !self.check(&TokenType::In) {
7754                    return Err(ParseError {
7755                        kind: ParseErrorKind::ExpectedKeyword { keyword: "in".to_string() },
7756                        span: self.current_span(),
7757                    });
7758                }
7759                self.advance(); // consume "in"
7760
7761                let zone = self.parse_imperative_expr()?;
7762                Ok(self.ctx.alloc_imperative_expr(Expr::ChunkAt { index, zone }))
7763            }
7764
7765            // Handle verbs in expression context:
7766            // - "empty" is a literal Nothing
7767            // - Other verbs can be function names (e.g., read, write)
7768            TokenType::Verb { lemma, .. } => {
7769                let word = self.interner.resolve(*lemma).to_lowercase();
7770                if word == "empty" {
7771                    self.advance();
7772                    return Ok(self.ctx.alloc_imperative_expr(Expr::Literal(Literal::Nothing)));
7773                }
7774                // Phase 38: Allow verbs to be used as function calls
7775                let sym = token.lexeme;
7776                self.advance();
7777                if self.check(&TokenType::LParen) {
7778                    return self.parse_call_expr(sym);
7779                }
7780                // Treat as identifier reference
7781                self.verify_identifier_access(sym)?;
7782                let base = self.ctx.alloc_imperative_expr(Expr::Identifier(sym));
7783                self.parse_field_access_chain(base)
7784            }
7785
7786            // Phase 38: Adverbs as identifiers (e.g., "now" for time functions)
7787            TokenType::TemporalAdverb(_) | TokenType::ScopalAdverb(_) | TokenType::Adverb(_) => {
7788                let sym = token.lexeme;
7789                self.advance();
7790                if self.check(&TokenType::LParen) {
7791                    return self.parse_call_expr(sym);
7792                }
7793                // Treat as identifier reference (e.g., "Let t be now.")
7794                self.verify_identifier_access(sym)?;
7795                let base = self.ctx.alloc_imperative_expr(Expr::Identifier(sym));
7796                self.parse_field_access_chain(base)
7797            }
7798
7799            // Phase 10: IO keywords as function calls (e.g., "read", "write", "file")
7800            // Phase 57: Add/Remove keywords as function calls
7801            TokenType::Read | TokenType::Write | TokenType::File | TokenType::Console |
7802            TokenType::Add | TokenType::Remove => {
7803                let sym = token.lexeme;
7804                self.advance();
7805                if self.check(&TokenType::LParen) {
7806                    return self.parse_call_expr(sym);
7807                }
7808                // Treat as identifier reference
7809                self.verify_identifier_access(sym)?;
7810                let base = self.ctx.alloc_imperative_expr(Expr::Identifier(sym));
7811                self.parse_field_access_chain(base)
7812            }
7813
7814            // Unified identifier handling - all identifier-like tokens get verified
7815            // First check for boolean/special literals before treating as variable
7816            TokenType::Noun(sym) | TokenType::ProperName(sym) | TokenType::Adjective(sym) => {
7817                let sym = *sym;
7818                let word = self.interner.resolve(sym);
7819
7820                // Check for boolean literals
7821                if word == "true" {
7822                    self.advance();
7823                    return Ok(self.ctx.alloc_imperative_expr(Expr::Literal(Literal::Boolean(true))));
7824                }
7825                if word == "false" {
7826                    self.advance();
7827                    return Ok(self.ctx.alloc_imperative_expr(Expr::Literal(Literal::Boolean(false))));
7828                }
7829                // Float word literals (the `true`/`false` precedent): the
7830                // values every float tower needs a spelling for.
7831                if word == "infinity" && !self.user_bound.contains(&sym) {
7832                    self.advance();
7833                    return Ok(self.ctx.alloc_imperative_expr(Expr::Literal(Literal::Float(f64::INFINITY))));
7834                }
7835                if word == "nan" && !self.user_bound.contains(&sym) {
7836                    self.advance();
7837                    return Ok(self.ctx.alloc_imperative_expr(Expr::Literal(Literal::Float(f64::NAN))));
7838                }
7839
7840                // Check for 'empty' - treat as unit value for collections
7841                if word == "empty" {
7842                    self.advance();
7843                    return Ok(self.ctx.alloc_imperative_expr(Expr::Literal(Literal::Nothing)));
7844                }
7845
7846                // Option None literal: "none" → None
7847                if word == "none" {
7848                    self.advance();
7849                    return Ok(self.ctx.alloc_imperative_expr(Expr::OptionNone));
7850                }
7851
7852                // Don't verify as variable - might be a function call or enum variant
7853                self.advance();
7854
7855                // Phase 32: Check for function call: identifier(args)
7856                if self.check(&TokenType::LParen) {
7857                    return self.parse_call_expr(sym);
7858                }
7859
7860                // Phase 33: Check if this is a bare enum variant (e.g., "North" for Direction)
7861                if let Some(enum_name) = self.find_variant(sym) {
7862                    let fields = if self.check_word("with") {
7863                        self.parse_variant_constructor_fields()?
7864                    } else {
7865                        vec![]
7866                    };
7867                    let base = self.ctx.alloc_imperative_expr(Expr::NewVariant {
7868                        enum_name,
7869                        variant: sym,
7870                        fields,
7871                    });
7872                    return self.parse_field_access_chain(base);
7873                }
7874
7875                // Centralized verification for undefined/moved checks (only for variables)
7876                self.verify_identifier_access(sym)?;
7877                let base = self.ctx.alloc_imperative_expr(Expr::Identifier(sym));
7878                // Phase 31: Check for field access via possessive
7879                self.parse_field_access_chain(base)
7880            }
7881
7882            // Pronouns can be variable names in code context ("i", "it")
7883            TokenType::Pronoun { .. } => {
7884                let sym = token.lexeme;
7885                self.advance();
7886                let base = self.ctx.alloc_imperative_expr(Expr::Identifier(sym));
7887                // Phase 31: Check for field access via possessive
7888                self.parse_field_access_chain(base)
7889            }
7890
7891            // Phase 49: CRDT keywords can be function names (Merge, Increase)
7892            TokenType::Merge | TokenType::Increase => {
7893                let sym = token.lexeme;
7894                self.advance();
7895
7896                // Check for function call: Merge(args)
7897                if self.check(&TokenType::LParen) {
7898                    return self.parse_call_expr(sym);
7899                }
7900
7901                let base = self.ctx.alloc_imperative_expr(Expr::Identifier(sym));
7902                self.parse_field_access_chain(base)
7903            }
7904
7905            // Escape hatch in expression position: `Escape to Rust:`
7906            // Lookahead: if followed by "to", parse as escape expression.
7907            // Otherwise, treat as identifier (variable named "escape").
7908            TokenType::Escape => {
7909                if self.tokens.get(self.current + 1).map_or(false, |t|
7910                    matches!(t.kind, TokenType::To) || {
7911                        if let TokenType::Preposition(sym) = t.kind {
7912                            sym.is(self.interner, "to")
7913                        } else {
7914                            false
7915                        }
7916                    }
7917                ) {
7918                    return self.parse_escape_expr();
7919                }
7920                // Fall through to identifier handling
7921                let sym = token.lexeme;
7922                self.advance();
7923                if self.check(&TokenType::LParen) {
7924                    return self.parse_call_expr(sym);
7925                }
7926                self.verify_identifier_access(sym)?;
7927                let base = self.ctx.alloc_imperative_expr(Expr::Identifier(sym));
7928                self.parse_field_access_chain(base)
7929            }
7930
7931            // Keywords that can also be used as identifiers in expression context
7932            // These are contextual keywords - they have special meaning in specific positions
7933            // but can be used as variable names elsewhere
7934            TokenType::Values |    // "values" - can be a variable name
7935            TokenType::Both |      // correlative: "both X and Y"
7936            TokenType::Either |    // correlative: "either X or Y"
7937            TokenType::Combined |  // string concat: "combined with"
7938            TokenType::Followed |  // seq concat: "followed by" (also a valid variable name)
7939            TokenType::Shared |    // CRDT modifier
7940            TokenType::Particle(_) |  // phrasal verb particles: out, up, down, etc.
7941            TokenType::Preposition(_) |  // prepositions: from, into, etc.
7942            TokenType::All => {    // quantifier in FOL, but valid variable name in imperative code
7943                let sym = token.lexeme;
7944                self.advance();
7945
7946                // Check for function call
7947                if self.check(&TokenType::LParen) {
7948                    return self.parse_call_expr(sym);
7949                }
7950
7951                self.verify_identifier_access(sym)?;
7952                let base = self.ctx.alloc_imperative_expr(Expr::Identifier(sym));
7953                self.parse_field_access_chain(base)
7954            }
7955
7956            // Handle ambiguous tokens that might be identifiers or function calls
7957            TokenType::Ambiguous { primary, alternatives } => {
7958                // Always use lexeme for identifier access - preserves original casing
7959                // (using verb lemma can give wrong casing like "Result" instead of "result")
7960                let sym = token.lexeme;
7961
7962                // Check if this token can be used as identifier (has Noun/Verb/etc. interpretation)
7963                let is_identifier_token = match &**primary {
7964                    TokenType::Noun(_) | TokenType::Adjective(_) | TokenType::ProperName(_) |
7965                    TokenType::Verb { .. } => true,
7966                    _ => alternatives.iter().any(|t| matches!(t,
7967                        TokenType::Noun(_) | TokenType::Adjective(_) | TokenType::ProperName(_) |
7968                        TokenType::Verb { .. }
7969                    ))
7970                };
7971
7972                if is_identifier_token {
7973                    self.advance();
7974
7975                    // Check for function call: ambiguous_name(args)
7976                    if self.check(&TokenType::LParen) {
7977                        return self.parse_call_expr(sym);
7978                    }
7979
7980                    self.verify_identifier_access(sym)?;
7981                    let base = self.ctx.alloc_imperative_expr(Expr::Identifier(sym));
7982                    // Phase 31: Check for field access via possessive
7983                    self.parse_field_access_chain(base)
7984                } else {
7985                    Err(ParseError {
7986                        kind: ParseErrorKind::ExpectedExpression,
7987                        span: self.current_span(),
7988                    })
7989                }
7990            }
7991
7992            // Parenthesized expression, tuple literal, or closure
7993            TokenType::LParen => {
7994                // Try closure parse first using speculative parsing.
7995                // Closure syntax: `(name: Type, ...) -> expr` or `() -> expr`
7996                if let Some(closure) = self.try_parse(|p| p.parse_closure_expr()) {
7997                    return Ok(closure);
7998                }
7999
8000                // Not a closure — parse as parenthesized expression or tuple
8001                self.advance(); // consume '('
8002                let first = self.parse_imperative_expr()?;
8003
8004                // Check if this is a tuple (has comma) or just grouping
8005                if self.check(&TokenType::Comma) {
8006                    // It's a tuple - parse remaining elements
8007                    let mut items = vec![first];
8008                    while self.check(&TokenType::Comma) {
8009                        self.advance(); // consume ","
8010                        items.push(self.parse_imperative_expr()?);
8011                    }
8012
8013                    if !self.check(&TokenType::RParen) {
8014                        return Err(ParseError {
8015                            kind: ParseErrorKind::ExpectedKeyword { keyword: ")".to_string() },
8016                            span: self.current_span(),
8017                        });
8018                    }
8019                    self.advance(); // consume ')'
8020
8021                    let base = self.ctx.alloc_imperative_expr(Expr::Tuple(items));
8022                    self.parse_field_access_chain(base)
8023                } else {
8024                    // Just a parenthesized expression
8025                    if !self.check(&TokenType::RParen) {
8026                        return Err(ParseError {
8027                            kind: ParseErrorKind::ExpectedKeyword { keyword: ")".to_string() },
8028                            span: self.current_span(),
8029                        });
8030                    }
8031                    self.advance(); // consume ')'
8032                    // A parenthesized expression supports the same postfix chain
8033                    // as any primary: `(5).double()`, `(p).x`, `(xs)[1]`.
8034                    self.parse_field_access_chain(first)
8035                }
8036            }
8037
8038            // "Call funcName with args" as an expression
8039            TokenType::Call => {
8040                self.advance(); // consume "Call"
8041                let function = match &self.peek().kind {
8042                    TokenType::Noun(sym) | TokenType::Adjective(sym) => {
8043                        let s = *sym;
8044                        self.advance();
8045                        s
8046                    }
8047                    TokenType::Verb { .. } | TokenType::Ambiguous { .. } => {
8048                        let s = self.peek().lexeme;
8049                        self.advance();
8050                        s
8051                    }
8052                    _ => {
8053                        return Err(ParseError {
8054                            kind: ParseErrorKind::ExpectedIdentifier,
8055                            span: self.current_span(),
8056                        });
8057                    }
8058                };
8059                let args = if self.check_preposition_is("with") {
8060                    self.advance(); // consume "with"
8061                    self.parse_call_arguments()?
8062                } else {
8063                    Vec::new()
8064                };
8065                Ok(self.ctx.alloc_imperative_expr(Expr::Call { function, args }))
8066            }
8067
8068            _ => {
8069                Err(ParseError {
8070                    kind: ParseErrorKind::ExpectedExpression,
8071                    span: self.current_span(),
8072                })
8073            }
8074        }
8075    }
8076
8077    /// Parse a closure expression: `(params) -> expr` or `(params) ->:` block.
8078    /// Called speculatively — will fail (and rollback) if not a closure.
8079    fn parse_closure_expr(&mut self) -> ParseResult<&'a Expr<'a>> {
8080        use crate::ast::stmt::ClosureBody;
8081
8082        // Expect '('
8083        if !self.check(&TokenType::LParen) {
8084            return Err(ParseError {
8085                kind: ParseErrorKind::ExpectedExpression,
8086                span: self.current_span(),
8087            });
8088        }
8089        self.advance(); // consume '('
8090
8091        // Parse parameter list
8092        let mut params = Vec::new();
8093        if !self.check(&TokenType::RParen) {
8094            // First parameter: name: Type
8095            let name = self.expect_identifier()?;
8096            if !self.check(&TokenType::Colon) {
8097                return Err(ParseError {
8098                    kind: ParseErrorKind::ExpectedKeyword { keyword: ":".to_string() },
8099                    span: self.current_span(),
8100                });
8101            }
8102            self.advance(); // consume ':'
8103            let ty = self.parse_type_expression()?;
8104            let ty_ref = self.ctx.alloc_type_expr(ty);
8105            params.push((name, ty_ref));
8106
8107            // Additional parameters
8108            while self.check(&TokenType::Comma) {
8109                self.advance(); // consume ','
8110                let name = self.expect_identifier()?;
8111                if !self.check(&TokenType::Colon) {
8112                    return Err(ParseError {
8113                        kind: ParseErrorKind::ExpectedKeyword { keyword: ":".to_string() },
8114                        span: self.current_span(),
8115                    });
8116                }
8117                self.advance(); // consume ':'
8118                let ty = self.parse_type_expression()?;
8119                let ty_ref = self.ctx.alloc_type_expr(ty);
8120                params.push((name, ty_ref));
8121            }
8122        }
8123
8124        // Expect ')'
8125        if !self.check(&TokenType::RParen) {
8126            return Err(ParseError {
8127                kind: ParseErrorKind::ExpectedKeyword { keyword: ")".to_string() },
8128                span: self.current_span(),
8129            });
8130        }
8131        self.advance(); // consume ')'
8132
8133        // Expect '->'
8134        if !self.check(&TokenType::Arrow) {
8135            return Err(ParseError {
8136                kind: ParseErrorKind::ExpectedKeyword { keyword: "->".to_string() },
8137                span: self.current_span(),
8138            });
8139        }
8140        self.advance(); // consume '->'
8141
8142        // Check for block body (->:) vs expression body (-> expr)
8143        let body = if self.check(&TokenType::Colon) {
8144            self.advance(); // consume ':'
8145            // Parse indented block
8146            if !self.check(&TokenType::Indent) {
8147                return Err(ParseError {
8148                    kind: ParseErrorKind::ExpectedStatement,
8149                    span: self.current_span(),
8150                });
8151            }
8152            self.advance(); // consume Indent
8153
8154            let mut block_stmts = Vec::new();
8155            while !self.check(&TokenType::Dedent) && !self.is_at_end() {
8156                let stmt = self.parse_statement()?;
8157                block_stmts.push(stmt);
8158                if self.check(&TokenType::Period) {
8159                    self.advance();
8160                }
8161            }
8162            if self.check(&TokenType::Dedent) {
8163                self.advance(); // consume Dedent
8164            }
8165
8166            let block = self.ctx.stmts.expect("imperative arenas not initialized")
8167                .alloc_slice(block_stmts.into_iter());
8168            ClosureBody::Block(block)
8169        } else {
8170            // Single expression body — use parse_condition to support comparisons and boolean ops
8171            let expr = self.parse_condition()?;
8172            ClosureBody::Expression(expr)
8173        };
8174
8175        Ok(self.ctx.alloc_imperative_expr(Expr::Closure {
8176            params,
8177            body,
8178            return_type: None,
8179        }))
8180    }
8181
8182    /// Parse a complete imperative expression with full operator precedence.
8183    /// Delegates to parse_condition to support and/or/xor/shifts/comparisons
8184    /// in all expression contexts (Let, Set, function arguments, etc.).
8185    fn parse_imperative_expr(&mut self) -> ParseResult<&'a Expr<'a>> {
8186        self.parse_condition()
8187    }
8188
8189    /// The bitwise chain, C precedence: `|` lowest, then `^`/`xor`, then
8190    /// `&`, all below comparison and above additive. On Sets the same
8191    /// operators are union / symmetric-difference / intersection.
8192    fn parse_xor_expr(&mut self) -> ParseResult<&'a Expr<'a>> {
8193        let mut left = self.parse_bitxor_expr()?;
8194
8195        while self.check(&TokenType::VBar) {
8196            self.advance(); // consume "|"
8197            let right = self.parse_bitxor_expr()?;
8198            left = self.ctx.alloc_imperative_expr(Expr::BinaryOp {
8199                op: BinaryOpKind::BitOr,
8200                left,
8201                right,
8202            });
8203        }
8204
8205        Ok(left)
8206    }
8207
8208    /// `^` (or the word `xor`): bitwise XOR / set symmetric difference.
8209    fn parse_bitxor_expr(&mut self) -> ParseResult<&'a Expr<'a>> {
8210        let mut left = self.parse_bitand_expr()?;
8211
8212        while self.check(&TokenType::Xor) || self.check(&TokenType::Caret) {
8213            self.advance(); // consume "xor" / "^"
8214            let right = self.parse_bitand_expr()?;
8215            left = self.ctx.alloc_imperative_expr(Expr::BinaryOp {
8216                op: BinaryOpKind::BitXor,
8217                left,
8218                right,
8219            });
8220        }
8221
8222        Ok(left)
8223    }
8224
8225    /// `&`: bitwise AND / set intersection.
8226    fn parse_bitand_expr(&mut self) -> ParseResult<&'a Expr<'a>> {
8227        let mut left = self.parse_additive_expr()?;
8228
8229        while self.check(&TokenType::Amp) {
8230            self.advance(); // consume "&"
8231            let right = self.parse_additive_expr()?;
8232            left = self.ctx.alloc_imperative_expr(Expr::BinaryOp {
8233                op: BinaryOpKind::BitAnd,
8234                left,
8235                right,
8236            });
8237        }
8238
8239        Ok(left)
8240    }
8241
8242    /// Parse additive expressions (+, -, combined with, union, intersection, contains) - left-to-right associative
8243    fn parse_additive_expr(&mut self) -> ParseResult<&'a Expr<'a>> {
8244        let mut left = self.parse_shift_expr()?;
8245
8246        loop {
8247            match &self.peek().kind {
8248                TokenType::Plus => {
8249                    self.advance();
8250                    let right = self.parse_shift_expr()?;
8251                    left = self.ctx.alloc_imperative_expr(Expr::BinaryOp {
8252                        op: BinaryOpKind::Add,
8253                        left,
8254                        right,
8255                    });
8256                }
8257                TokenType::Minus => {
8258                    self.advance();
8259                    let right = self.parse_shift_expr()?;
8260                    left = self.ctx.alloc_imperative_expr(Expr::BinaryOp {
8261                        op: BinaryOpKind::Subtract,
8262                        left,
8263                        right,
8264                    });
8265                }
8266                // Phase 53: "combined with" for string concatenation
8267                TokenType::Combined => {
8268                    self.advance(); // consume "combined"
8269                    // Expect "with" (preposition)
8270                    if !self.check_preposition_is("with") {
8271                        return Err(ParseError {
8272                            kind: ParseErrorKind::ExpectedKeyword { keyword: "with".to_string() },
8273                            span: self.current_span(),
8274                        });
8275                    }
8276                    self.advance(); // consume "with"
8277                    let right = self.parse_shift_expr()?;
8278                    left = self.ctx.alloc_imperative_expr(Expr::BinaryOp {
8279                        op: BinaryOpKind::Concat,
8280                        left,
8281                        right,
8282                    });
8283                }
8284                // "followed by" for sequence concatenation (merge two sequences into one)
8285                TokenType::Followed => {
8286                    self.advance(); // consume "followed"
8287                    if !self.check_preposition_is("by") {
8288                        return Err(ParseError {
8289                            kind: ParseErrorKind::ExpectedKeyword { keyword: "by".to_string() },
8290                            span: self.current_span(),
8291                        });
8292                    }
8293                    self.advance(); // consume "by"
8294                    let right = self.parse_shift_expr()?;
8295                    left = self.ctx.alloc_imperative_expr(Expr::BinaryOp {
8296                        op: BinaryOpKind::SeqConcat,
8297                        left,
8298                        right,
8299                    });
8300                }
8301                // Set operations: union, intersection
8302                TokenType::Union => {
8303                    self.advance(); // consume "union"
8304                    let right = self.parse_shift_expr()?;
8305                    left = self.ctx.alloc_imperative_expr(Expr::Union {
8306                        left,
8307                        right,
8308                    });
8309                }
8310                TokenType::Intersection => {
8311                    self.advance(); // consume "intersection"
8312                    let right = self.parse_shift_expr()?;
8313                    left = self.ctx.alloc_imperative_expr(Expr::Intersection {
8314                        left,
8315                        right,
8316                    });
8317                }
8318                // Set membership: "set contains value"
8319                TokenType::Contains => {
8320                    self.advance(); // consume "contains"
8321                    let value = self.parse_shift_expr()?;
8322                    left = self.ctx.alloc_imperative_expr(Expr::Contains {
8323                        collection: left,
8324                        value,
8325                    });
8326                }
8327                // `a without b` — the English set difference (`-` is the
8328                // symbolic spelling). Lowers to Subtract, which dispatches on
8329                // Set operands.
8330                _ if self.check_word("without") => {
8331                    self.advance(); // consume "without"
8332                    let right = self.parse_shift_expr()?;
8333                    left = self.ctx.alloc_imperative_expr(Expr::BinaryOp {
8334                        op: BinaryOpKind::Subtract,
8335                        left,
8336                        right,
8337                    });
8338                }
8339                // `n copies of x` — the English fill: a fresh sequence of n
8340                // deep-copied slots (`[x] * n` is the symbolic spelling).
8341                // Guarded on the following `of`, so a plain variable named
8342                // `copies` keeps resolving as an identifier.
8343                _ if self.check_word("copies") && self.peek_word_at(1, "of") => {
8344                    self.advance(); // consume "copies"
8345                    self.advance(); // consume "of"
8346                    let element = self.parse_shift_expr()?;
8347                    let repeat_sym = self.interner.intern("repeatSeq");
8348                    left = self.ctx.alloc_imperative_expr(Expr::Call {
8349                        function: repeat_sym,
8350                        args: vec![element, left],
8351                    });
8352                }
8353                // English infix `A plus B` / `A minus B` — the spoken spellings of `+`/`-`,
8354                // additive precedence. `check_op_word` yields to a same-named variable.
8355                _ if self.check_op_word("plus") => {
8356                    self.advance(); // consume "plus"
8357                    let right = self.parse_shift_expr()?;
8358                    left = self.ctx.alloc_imperative_expr(Expr::BinaryOp { op: BinaryOpKind::Add, left, right });
8359                }
8360                _ if self.check_op_word("minus") => {
8361                    self.advance(); // consume "minus"
8362                    let right = self.parse_shift_expr()?;
8363                    left = self.ctx.alloc_imperative_expr(Expr::BinaryOp { op: BinaryOpKind::Subtract, left, right });
8364                }
8365                _ => break,
8366            }
8367        }
8368
8369        Ok(left)
8370    }
8371
8372    /// Parse shift expressions: "x shifted left by y" / "x shifted right by y"
8373    /// Precedence: below additive, above multiplicative.
8374    fn parse_shift_expr(&mut self) -> ParseResult<&'a Expr<'a>> {
8375        let mut left = self.parse_multiplicative_expr()?;
8376
8377        loop {
8378            if !self.check(&TokenType::Shifted) {
8379                break;
8380            }
8381            self.advance(); // consume "shifted"
8382
8383            let is_left = self.check_word("left");
8384            if is_left {
8385                self.advance(); // consume "left"
8386            } else if self.check_word("right") {
8387                self.advance(); // consume "right"
8388            } else {
8389                return Err(ParseError {
8390                    kind: ParseErrorKind::ExpectedKeyword { keyword: "left or right".to_string() },
8391                    span: self.current_span(),
8392                });
8393            }
8394
8395            // Expect "by"
8396            if !self.check_preposition_is("by") && !self.check_word("by") {
8397                return Err(ParseError {
8398                    kind: ParseErrorKind::ExpectedKeyword { keyword: "by".to_string() },
8399                    span: self.current_span(),
8400                });
8401            }
8402            self.advance(); // consume "by"
8403
8404            let right = self.parse_multiplicative_expr()?;
8405            let op = if is_left { BinaryOpKind::Shl } else { BinaryOpKind::Shr };
8406            left = self.ctx.alloc_imperative_expr(Expr::BinaryOp { op, left, right });
8407        }
8408
8409        Ok(left)
8410    }
8411
8412    /// Parse unary expressions (currently just unary minus)
8413    fn parse_unary_expr(&mut self) -> ParseResult<&'a Expr<'a>> {
8414        use crate::ast::{Expr, Literal};
8415
8416        if self.check(&TokenType::Tilde) {
8417            self.advance(); // consume "~"
8418            let operand = self.parse_unary_expr()?;
8419            // `~x` IS `x ^ -1` in two's complement — reuse the existing
8420            // BitXor path end to end (no new op anywhere downstream).
8421            let neg_one = self.ctx.alloc_imperative_expr(Expr::Literal(Literal::Number(-1)));
8422            return Ok(self.ctx.alloc_imperative_expr(Expr::BinaryOp {
8423                op: BinaryOpKind::BitXor,
8424                left: operand,
8425                right: neg_one,
8426            }));
8427        }
8428        if self.check(&TokenType::Minus) {
8429            // A negative LITERAL parses as one exact literal — the sign
8430            // attaches to the digits, so `-9223372036854775808` (i64::MIN,
8431            // whose magnitude alone overflows) and `-1.5` (a real Float
8432            // literal, not `0 - 1.5`) both come out exact.
8433            if let Some(TokenType::Number(sym)) =
8434                self.tokens.get(self.current + 1).map(|t| t.kind.clone())
8435            {
8436                self.advance(); // consume '-'
8437                self.advance(); // consume the numeral
8438                let num_str = self.interner.resolve(sym).to_string();
8439                if !Self::is_radix_numeral(&num_str)
8440                    && (num_str.contains('.') || num_str.contains('e') || num_str.contains('E'))
8441                {
8442                    let f = num_str.replace('_', "").parse::<f64>().map_err(|_| ParseError {
8443                        kind: ParseErrorKind::ExpectedKeyword {
8444                            keyword: format!("a Float literal (got `-{}`)", num_str),
8445                        },
8446                        span: self.current_span(),
8447                    })?;
8448                    return Ok(self.ctx.alloc_imperative_expr(Expr::Literal(Literal::Float(-f))));
8449                }
8450                let n = self.parse_i64_numeral_signed(&num_str, true)?;
8451                return Ok(self.ctx.alloc_imperative_expr(Expr::Literal(Literal::Number(n))));
8452            }
8453            if self
8454                .tokens
8455                .get(self.current + 1)
8456                .is_some_and(|t| self.interner.resolve(t.lexeme) == "infinity"
8457                    && !self.user_bound.contains(&t.lexeme))
8458            {
8459                self.advance(); // consume '-'
8460                self.advance(); // consume "infinity"
8461                return Ok(self
8462                    .ctx
8463                    .alloc_imperative_expr(Expr::Literal(Literal::Float(f64::NEG_INFINITY))));
8464            }
8465            self.advance(); // consume '-'
8466            let operand = self.parse_unary_expr()?; // recursive for --5
8467            // Implement as 0 - operand (no UnaryOp variant in Expr)
8468            return Ok(self.ctx.alloc_imperative_expr(Expr::BinaryOp {
8469                op: BinaryOpKind::Subtract,
8470                left: self.ctx.alloc_imperative_expr(Expr::Literal(Literal::Number(0))),
8471                right: operand,
8472            }));
8473        }
8474        self.parse_primary_expr()
8475    }
8476
8477    /// Parse multiplicative expressions (*, /, %) - left-to-right associative
8478    /// Exponentiation  — binds tighter than , RIGHT-associative
8479    /// ( = ). The base is a unary expression; the exponent
8480    /// recurses into itself for right-associativity.
8481    fn parse_power_expr(&mut self) -> ParseResult<&'a Expr<'a>> {
8482        let base = self.parse_unary_expr()?;
8483        // `**` or the English spelling `A to the power of B` — both right-associative.
8484        let symbolic = self.check(&TokenType::StarStar);
8485        let spoken = (self.check_to_preposition() || self.check_word("to"))
8486            && self.peek_word_at(1, "the")
8487            && self.peek_word_at(2, "power")
8488            && self.peek_word_at(3, "of");
8489        if symbolic || spoken {
8490            if symbolic {
8491                self.advance(); // consume "**"
8492            } else {
8493                self.advance(); // "to"
8494                self.advance(); // "the"
8495                self.advance(); // "power"
8496                self.advance(); // "of"
8497            }
8498            let exp = self.parse_power_expr()?;
8499            return Ok(self.ctx.alloc_imperative_expr(Expr::BinaryOp {
8500                op: BinaryOpKind::Pow,
8501                left: base,
8502                right: exp,
8503            }));
8504        }
8505        Ok(base)
8506    }
8507
8508    fn parse_multiplicative_expr(&mut self) -> ParseResult<&'a Expr<'a>> {
8509        let mut left = self.parse_power_expr()?;
8510
8511        loop {
8512            let op = match &self.peek().kind {
8513                TokenType::Star => {
8514                    self.advance();
8515                    BinaryOpKind::Multiply
8516                }
8517                TokenType::Slash => {
8518                    self.advance();
8519                    BinaryOpKind::Divide
8520                }
8521                TokenType::SlashSlash => {
8522                    self.advance();
8523                    BinaryOpKind::FloorDivide
8524                }
8525                TokenType::Percent => {
8526                    self.advance();
8527                    BinaryOpKind::Modulo
8528                }
8529                // English infix `A times B`, `A divided by B`, `A modulo B` (`mod`) — the spoken
8530                // spellings of `*`/`/`/`%`, multiplicative precedence. `check_op_word` yields to a
8531                // same-named variable (declarer-wins).
8532                _ if self.check_op_word("times") => {
8533                    self.advance(); // consume "times"
8534                    BinaryOpKind::Multiply
8535                }
8536                _ if self.check_op_word("divided") && self.peek_word_at(1, "by") => {
8537                    self.advance(); // consume "divided"
8538                    self.advance(); // consume "by"
8539                    BinaryOpKind::Divide
8540                }
8541                _ if self.check_op_word("modulo") || self.check_op_word("mod") => {
8542                    self.advance(); // consume "modulo"/"mod"
8543                    BinaryOpKind::Modulo
8544                }
8545                _ => break,
8546            };
8547            let right = self.parse_power_expr()?;
8548            left = self.ctx.alloc_imperative_expr(Expr::BinaryOp {
8549                op,
8550                left,
8551                right,
8552            });
8553        }
8554
8555        Ok(left)
8556    }
8557
8558    /// Try to parse a binary operator (+, -, *, /)
8559    fn try_parse_binary_op(&mut self) -> Option<BinaryOpKind> {
8560        match &self.peek().kind {
8561            TokenType::Plus => {
8562                self.advance();
8563                Some(BinaryOpKind::Add)
8564            }
8565            TokenType::Minus => {
8566                self.advance();
8567                Some(BinaryOpKind::Subtract)
8568            }
8569            TokenType::Star => {
8570                self.advance();
8571                Some(BinaryOpKind::Multiply)
8572            }
8573            TokenType::Slash => {
8574                self.advance();
8575                Some(BinaryOpKind::Divide)
8576            }
8577            _ => None,
8578        }
8579    }
8580
8581    /// Parse a Span literal starting from a number that was already consumed.
8582    /// Handles patterns like: "3 days", "2 months", "1 year and 3 days"
8583    /// Parse the contents of an interpolated string into a sequence of literal/expression parts.
8584    ///
8585    /// Input is the raw string content (without quotes), e.g., `Hello, {name}! Value: {x:.2}`
8586    /// `{{` and `}}` are escape sequences for literal braces.
8587    fn parse_interpolation_parts(&mut self, raw: &str) -> ParseResult<Vec<crate::ast::stmt::StringPart<'a>>> {
8588        use crate::ast::stmt::StringPart;
8589
8590        let mut parts = Vec::new();
8591        let chars: Vec<char> = raw.chars().collect();
8592        let mut i = 0;
8593        let mut literal_buf = String::new();
8594
8595        while i < chars.len() {
8596            match chars[i] {
8597                '{' if i + 1 < chars.len() && chars[i + 1] == '{' => {
8598                    // Escaped brace: {{ → literal {
8599                    literal_buf.push('{');
8600                    i += 2;
8601                }
8602                '{' => {
8603                    // Flush literal buffer
8604                    if !literal_buf.is_empty() {
8605                        let sym = self.interner.intern(&literal_buf);
8606                        parts.push(StringPart::Literal(sym));
8607                        literal_buf.clear();
8608                    }
8609
8610                    // Find matching closing brace
8611                    let start = i + 1;
8612                    let mut depth = 1;
8613                    let mut j = start;
8614                    while j < chars.len() && depth > 0 {
8615                        if chars[j] == '{' { depth += 1; }
8616                        if chars[j] == '}' { depth -= 1; }
8617                        if depth > 0 { j += 1; }
8618                    }
8619                    if depth != 0 {
8620                        return Err(ParseError {
8621                            kind: crate::error::ParseErrorKind::Custom(
8622                                "Unclosed interpolation brace in string".to_string()
8623                            ),
8624                            span: self.current_span(),
8625                        });
8626                    }
8627
8628                    let hole_content: String = chars[start..j].iter().collect();
8629
8630                    // Detect debug format: {var=} or {var=:.2}
8631                    // Debug `=` is always the LAST `=` in the hole content.
8632                    // Using rfind ensures comparison operators like `==` don't trigger
8633                    // false positives — `{x == 5}` has no trailing `=` after an identifier.
8634                    let (hole_after_debug, is_debug) = {
8635                        if let Some(eq_pos) = hole_content.rfind('=') {
8636                            let before_eq = hole_content[..eq_pos].trim();
8637                            // Only treat as debug if the part before = is a simple identifier
8638                            // and the `=` is not part of `==`, `<=`, `>=`, `!=`
8639                            let is_double_eq = eq_pos > 0 && hole_content.as_bytes().get(eq_pos - 1) == Some(&b'=');
8640                            let is_preceded_by_comparison = eq_pos > 0 && matches!(hole_content.as_bytes().get(eq_pos - 1), Some(b'!' | b'<' | b'>'));
8641                            if !is_double_eq && !is_preceded_by_comparison
8642                                && !before_eq.is_empty()
8643                                && before_eq.chars().all(|c| c.is_alphanumeric() || c == '_')
8644                            {
8645                                (hole_content[..eq_pos].to_string() + &hole_content[eq_pos + 1..], true)
8646                            } else {
8647                                (hole_content.clone(), false)
8648                            }
8649                        } else {
8650                            (hole_content.clone(), false)
8651                        }
8652                    };
8653
8654                    // Split on `:` for format specifier (but not `::`)
8655                    let (expr_str, format_spec) = if let Some(colon_pos) = hole_after_debug.rfind(':') {
8656                        let before = &hole_after_debug[..colon_pos];
8657                        let after = &hole_after_debug[colon_pos + 1..];
8658                        // Only treat as format spec if the part after `:` looks like a format spec
8659                        // (starts with `.`, `<`, `>`, `^`, `$`, or a digit, or is a known specifier letter)
8660                        if !after.is_empty() && (after.starts_with('.') || after.starts_with('<') || after.starts_with('>') || after.starts_with('^') || after == "$" || after.chars().next().map_or(false, |c| c.is_ascii_digit())) {
8661                            // Validate the format spec matches a known pattern
8662                            let valid = if after == "$" {
8663                                true
8664                            } else if after.starts_with('.') {
8665                                after[1..].parse::<usize>().is_ok()
8666                            } else if after.starts_with('<') || after.starts_with('>') || after.starts_with('^') {
8667                                after[1..].parse::<usize>().is_ok()
8668                            } else {
8669                                after.parse::<usize>().is_ok()
8670                            };
8671                            if !valid {
8672                                return Err(ParseError {
8673                                    kind: crate::error::ParseErrorKind::Custom(
8674                                        format!("Invalid format specifier `{}` in interpolation hole", after)
8675                                    ),
8676                                    span: self.current_span(),
8677                                });
8678                            }
8679                            (before.to_string(), Some(after.to_string()))
8680                        } else {
8681                            (hole_after_debug.clone(), None)
8682                        }
8683                    } else {
8684                        (hole_after_debug.clone(), None)
8685                    };
8686
8687                    // Parse the expression through a sub-parser
8688                    let expr_source = expr_str.trim().to_string();
8689                    if expr_source.is_empty() {
8690                        return Err(ParseError {
8691                            kind: crate::error::ParseErrorKind::Custom(
8692                                "Empty interpolation hole in string".to_string()
8693                            ),
8694                            span: self.current_span(),
8695                        });
8696                    }
8697
8698                    // Lex + parse the expression fragment by temporarily swapping
8699                    // the parser's token stream
8700                    let sub_expr = {
8701                        let mut sub_lexer = crate::lexer::Lexer::new(&expr_source, self.interner);
8702                        let sub_tokens = sub_lexer.tokenize();
8703
8704                        // Save and swap parser state
8705                        let saved_tokens = std::mem::replace(&mut self.tokens, sub_tokens);
8706                        let saved_current = self.current;
8707                        self.current = 0;
8708
8709                        let result = self.parse_primary_or_binary_expr();
8710
8711                        // Restore parser state
8712                        self.tokens = saved_tokens;
8713                        self.current = saved_current;
8714
8715                        result?
8716                    };
8717
8718                    let format_sym = format_spec.map(|s| self.interner.intern(&s));
8719                    parts.push(StringPart::Expr {
8720                        value: sub_expr,
8721                        format_spec: format_sym,
8722                        debug: is_debug,
8723                    });
8724
8725                    i = j + 1; // skip past closing '}'
8726                }
8727                '}' if i + 1 < chars.len() && chars[i + 1] == '}' => {
8728                    // Escaped brace: }} → literal }
8729                    literal_buf.push('}');
8730                    i += 2;
8731                }
8732                _ => {
8733                    literal_buf.push(chars[i]);
8734                    i += 1;
8735                }
8736            }
8737        }
8738
8739        // Flush remaining literal
8740        if !literal_buf.is_empty() {
8741            let sym = self.interner.intern(&literal_buf);
8742            parts.push(StringPart::Literal(sym));
8743        }
8744
8745        Ok(parts)
8746    }
8747
8748    /// Parse a primary expression or a full binary expression for interpolation holes.
8749    fn parse_primary_or_binary_expr(&mut self) -> ParseResult<&'a Expr<'a>> {
8750        self.parse_imperative_expr()
8751    }
8752
8753    fn parse_span_literal_from_num(&mut self, first_num_str: &str) -> ParseResult<&'a Expr<'a>> {
8754        use crate::ast::Literal;
8755        use crate::token::CalendarUnit;
8756
8757        let first_num = self.parse_i64_numeral(first_num_str)?;
8758
8759        // We expect a CalendarUnit after the number
8760        let unit = match self.peek().kind {
8761            TokenType::CalendarUnit(u) => u,
8762            _ => {
8763                return Err(ParseError {
8764                    kind: ParseErrorKind::ExpectedKeyword { keyword: "calendar unit (day, week, month, year)".to_string() },
8765                    span: self.current_span(),
8766                });
8767            }
8768        };
8769        self.advance(); // consume the CalendarUnit
8770
8771        // Calendar fields (months/days) feed a `Span`; sub-day CLOCK units
8772        // (hour/minute/second) feed a nanosecond `Duration`. A literal built
8773        // purely from clock units IS a Duration — `After 5 seconds`,
8774        // `Sleep for 2 minutes`; a calendar (or mixed) literal stays a Span,
8775        // which models only months/days.
8776        let mut total_months: i64 = 0;
8777        let mut total_days: i64 = 0;
8778        let mut total_nanos: i64 = 0;
8779        let mut saw_calendar = false;
8780        let mut saw_clock = false;
8781
8782        fn apply(
8783            unit: CalendarUnit,
8784            n: i64,
8785            months: &mut i64,
8786            days: &mut i64,
8787            nanos: &mut i64,
8788            cal: &mut bool,
8789            clk: &mut bool,
8790        ) {
8791            match unit {
8792                CalendarUnit::Day => {
8793                    *days += n;
8794                    *cal = true;
8795                }
8796                CalendarUnit::Week => {
8797                    *days += n * 7;
8798                    *cal = true;
8799                }
8800                CalendarUnit::Month => {
8801                    *months += n;
8802                    *cal = true;
8803                }
8804                CalendarUnit::Year => {
8805                    *months += n * 12;
8806                    *cal = true;
8807                }
8808                CalendarUnit::Hour => {
8809                    *nanos += n * 3_600_000_000_000;
8810                    *clk = true;
8811                }
8812                CalendarUnit::Minute => {
8813                    *nanos += n * 60_000_000_000;
8814                    *clk = true;
8815                }
8816                CalendarUnit::Second => {
8817                    *nanos += n * 1_000_000_000;
8818                    *clk = true;
8819                }
8820            }
8821        }
8822        apply(
8823            unit, first_num, &mut total_months, &mut total_days, &mut total_nanos,
8824            &mut saw_calendar, &mut saw_clock,
8825        );
8826
8827        // Check for "and" followed by more Number + CalendarUnit
8828        while self.check(&TokenType::And) {
8829            self.advance(); // consume "and"
8830
8831            // Expect another Number
8832            let next_num = match &self.peek().kind {
8833                TokenType::Number(sym) => {
8834                    let num_str = self.interner.resolve(*sym).to_string();
8835                    self.advance();
8836                    self.parse_i64_numeral(&num_str)?
8837                }
8838                _ => break, // Not a number, backtrack is complex so just stop
8839            };
8840
8841            // Expect another CalendarUnit
8842            let next_unit = match self.peek().kind {
8843                TokenType::CalendarUnit(u) => {
8844                    self.advance();
8845                    u
8846                }
8847                _ => break, // Not a unit, backtrack is complex so just stop
8848            };
8849
8850            apply(
8851                next_unit, next_num, &mut total_months, &mut total_days, &mut total_nanos,
8852                &mut saw_calendar, &mut saw_clock,
8853            );
8854        }
8855
8856        if saw_clock && !saw_calendar {
8857            Ok(self.ctx.alloc_imperative_expr(Expr::Literal(Literal::Duration(total_nanos))))
8858        } else {
8859            Ok(self.ctx.alloc_imperative_expr(Expr::Literal(Literal::Span {
8860                months: total_months as i32,
8861                days: total_days as i32,
8862            })))
8863        }
8864    }
8865
8866    /// Phase 32: Parse function call expression: f(x, y, ...)
8867    fn parse_call_expr(&mut self, function: Symbol) -> ParseResult<&'a Expr<'a>> {
8868        use crate::ast::Expr;
8869
8870        self.advance(); // consume '('
8871
8872        let mut args = Vec::new();
8873        if !self.check(&TokenType::RParen) {
8874            loop {
8875                args.push(self.parse_imperative_expr()?);
8876                if !self.check(&TokenType::Comma) {
8877                    break;
8878                }
8879                self.advance(); // consume ','
8880            }
8881        }
8882
8883        if !self.check(&TokenType::RParen) {
8884            return Err(ParseError {
8885                kind: ParseErrorKind::ExpectedKeyword { keyword: ")".to_string() },
8886                span: self.current_span(),
8887            });
8888        }
8889        self.advance(); // consume ')'
8890
8891        Ok(self.ctx.alloc_imperative_expr(Expr::Call { function, args }))
8892    }
8893
8894    /// Phase 31: Parse field access chain via possessive ('s) and bracket indexing
8895    /// Handles patterns like: p's x, p's x's y, items[1], items[i]'s field
8896    fn parse_field_access_chain(&mut self, base: &'a Expr<'a>) -> ParseResult<&'a Expr<'a>> {
8897        use crate::ast::Expr;
8898
8899        let mut result = base;
8900
8901        // Keep parsing field accesses and bracket indexing
8902        loop {
8903            if self.check(&TokenType::Dot) {
8904                // The dot: `p.x` field access (≡ `p's x`) or `xs.f(a)` UFCS
8905                // method syntax (≡ `f(xs, a)` — the RECEIVER becomes the first
8906                // argument, so every method spelling lowers to a plain call and
8907                // all engines get it free).
8908                self.advance(); // consume "."
8909                let name = self.expect_identifier()?;
8910                if self.check(&TokenType::LParen) {
8911                    self.advance(); // consume "("
8912                    let mut args = vec![result]; // receiver first
8913                    if !self.check(&TokenType::RParen) {
8914                        loop {
8915                            args.push(self.parse_imperative_expr()?);
8916                            if !self.check(&TokenType::Comma) {
8917                                break;
8918                            }
8919                            self.advance(); // consume ","
8920                        }
8921                    }
8922                    if !self.check(&TokenType::RParen) {
8923                        return Err(ParseError {
8924                            kind: ParseErrorKind::ExpectedKeyword { keyword: ")".to_string() },
8925                            span: self.current_span(),
8926                        });
8927                    }
8928                    self.advance(); // consume ")"
8929                    result = self.ctx.alloc_imperative_expr(Expr::Call { function: name, args });
8930                } else {
8931                    result = self.ctx.alloc_imperative_expr(Expr::FieldAccess {
8932                        object: result,
8933                        field: name,
8934                    });
8935                }
8936            } else if self.check(&TokenType::Possessive) {
8937                // Field access: p's x
8938                self.advance(); // consume "'s"
8939                let field = self.expect_identifier()?;
8940                result = self.ctx.alloc_imperative_expr(Expr::FieldAccess {
8941                    object: result,
8942                    field,
8943                });
8944            } else if self.check(&TokenType::LBracket) {
8945                // Bracket indexing: items[1], items[i]
8946                self.advance(); // consume "["
8947                let index = self.parse_imperative_expr()?;
8948
8949                if !self.check(&TokenType::RBracket) {
8950                    return Err(ParseError {
8951                        kind: ParseErrorKind::ExpectedKeyword { keyword: "]".to_string() },
8952                        span: self.current_span(),
8953                    });
8954                }
8955                self.advance(); // consume "]"
8956
8957                result = self.ctx.alloc_imperative_expr(Expr::Index {
8958                    collection: result,
8959                    index,
8960                });
8961            } else {
8962                break;
8963            }
8964        }
8965
8966        Ok(result)
8967    }
8968
8969    /// Centralized verification for identifier access in imperative mode.
8970    /// Checks for use-after-move errors on known variables.
8971    fn verify_identifier_access(&self, sym: Symbol) -> ParseResult<()> {
8972        if self.mode != ParserMode::Imperative {
8973            return Ok(());
8974        }
8975
8976        // Check if variable has been moved
8977        if let Some(crate::drs::OwnershipState::Moved) = self.world_state.get_ownership_by_var(sym) {
8978            return Err(ParseError {
8979                kind: ParseErrorKind::UseAfterMove {
8980                    name: self.interner.resolve(sym).to_string()
8981                },
8982                span: self.current_span(),
8983            });
8984        }
8985
8986        Ok(())
8987    }
8988
8989    fn expect_identifier(&mut self) -> ParseResult<Symbol> {
8990        let token = self.peek().clone();
8991        match &token.kind {
8992            // Standard identifiers
8993            TokenType::Noun(sym) | TokenType::ProperName(sym) | TokenType::Adjective(sym) => {
8994                self.advance();
8995                Ok(*sym)
8996            }
8997            // Verbs can be variable names in code context ("empty", "run", etc.)
8998            // Use raw lexeme to preserve original casing
8999            TokenType::Verb { .. } => {
9000                let sym = token.lexeme;
9001                self.advance();
9002                Ok(sym)
9003            }
9004            // Phase 32: Articles can be single-letter identifiers (a, an)
9005            TokenType::Article(_) => {
9006                let sym = token.lexeme;
9007                self.advance();
9008                Ok(sym)
9009            }
9010            // Overloaded tokens that are valid identifiers in code context
9011            TokenType::Pronoun { .. } |  // "i", "it"
9012            TokenType::Items |           // "items"
9013            TokenType::Values |          // "values"
9014            TokenType::Item |            // "item"
9015            TokenType::Nothing |         // "nothing"
9016            // Phase 38: Adverbs can be function names (now, sleep, etc.)
9017            TokenType::TemporalAdverb(_) |
9018            TokenType::ScopalAdverb(_) |
9019            TokenType::Adverb(_) |
9020            // Phase 10: IO keywords can be function names (read, write, file, console)
9021            TokenType::Read |
9022            TokenType::Write |
9023            TokenType::File |
9024            TokenType::Console |
9025            // Phase 49: CRDT keywords can be type/function names
9026            TokenType::Merge |
9027            TokenType::Increase |
9028            TokenType::Decrease |
9029            // Phase 51: "sleep" leads the Sleep statement but also names a native
9030            // (`## To native sleep`) — in name position the declarer wins.
9031            TokenType::Sleep |
9032            // Phase 49b: CRDT type keywords can be type names
9033            TokenType::Tally |
9034            TokenType::SharedSet |
9035            TokenType::SharedSequence |
9036            TokenType::CollaborativeSequence |
9037            // Phase 54: "first", "second", etc. can be variable names
9038            // Phase 57: "add", "remove" can be function names
9039            TokenType::Add |
9040            TokenType::Remove |
9041            TokenType::First |
9042            // Correlative conjunctions and other keywords usable as identifiers
9043            TokenType::Both |            // "both" (correlative: both X and Y)
9044            TokenType::Either |          // "either" (correlative: either X or Y)
9045            TokenType::Combined |        // "combined" (string concat: combined with)
9046            TokenType::Followed |        // "followed" (seq concat: followed by; also a variable name)
9047            TokenType::Shared |          // "shared" (CRDT type modifier)
9048            TokenType::All |             // "all" (FOL quantifier, valid variable name in imperative)
9049            // Calendar units can be type/variable names (Day, Week, Month, Year)
9050            TokenType::CalendarUnit(_) |
9051            // Phase 103: Focus particles can be variant names (Just, Only, Even)
9052            TokenType::Focus(_) |
9053            // Phrasal verb particles can be variable names (out, up, down, etc.)
9054            TokenType::Particle(_) |
9055            // Prepositions can be variable names in code context (from, into, etc.)
9056            TokenType::Preposition(_) |
9057            // Escape hatch keyword can be a variable name
9058            TokenType::Escape => {
9059                // Use the raw lexeme (interned string) as the symbol
9060                let sym = token.lexeme;
9061                self.advance();
9062                Ok(sym)
9063            }
9064            TokenType::Ambiguous { .. } => {
9065                // For ambiguous tokens, always use the raw lexeme to preserve original casing
9066                // (using verb lemma can give wrong casing like "State" instead of "state")
9067                let sym = token.lexeme;
9068                self.advance();
9069                Ok(sym)
9070            }
9071            _ => Err(ParseError {
9072                kind: ParseErrorKind::ExpectedIdentifier,
9073                span: self.current_span(),
9074            }),
9075        }
9076    }
9077
9078    fn consume_content_word_for_relative(&mut self) -> ParseResult<Symbol> {
9079        let t = self.advance().clone();
9080        match t.kind {
9081            TokenType::Noun(s) | TokenType::Adjective(s) => Ok(s),
9082            TokenType::ProperName(s) => Ok(s),
9083            TokenType::Verb { lemma, .. } => Ok(lemma),
9084            other => Err(ParseError {
9085                kind: ParseErrorKind::ExpectedContentWord { found: other },
9086                span: self.current_span(),
9087            }),
9088        }
9089    }
9090
9091    fn check_modal(&self) -> bool {
9092        matches!(
9093            self.peek().kind,
9094            TokenType::Must
9095                | TokenType::Shall
9096                | TokenType::Should
9097                | TokenType::Can
9098                | TokenType::May
9099                | TokenType::Cannot
9100                | TokenType::Could
9101                | TokenType::Would
9102                | TokenType::Might
9103        )
9104    }
9105
9106    /// Does an NP head follow the current (possessive) token — i.e. zero or
9107    /// more adjectives ending in a noun? "his dog" / "her red dog" yes;
9108    /// "its wet" (the it's typo) no.
9109    fn possessive_np_head_follows(&self) -> bool {
9110        let mut i = self.current + 1;
9111        while let Some(token) = self.tokens.get(i) {
9112            match &token.kind {
9113                TokenType::Adjective(_) => i += 1,
9114                TokenType::Noun(_) => return true,
9115                // A noun READING suffices ("its value" — "value" lexes
9116                // Ambiguous{Verb|Noun}); the NP parse commits it to noun.
9117                TokenType::Ambiguous { primary, alternatives } => {
9118                    return matches!(**primary, TokenType::Noun(_))
9119                        || alternatives.iter().any(|t| matches!(t, TokenType::Noun(_)));
9120                }
9121                _ => return false,
9122            }
9123        }
9124        false
9125    }
9126
9127    fn check_pronoun(&self) -> bool {
9128        match &self.peek().kind {
9129            TokenType::Pronoun { case, .. } => {
9130                if matches!(case, Case::Possessive) {
9131                    // In noun_priority_mode, possessive pronouns start NPs,
9132                    // not standalone objects.
9133                    if self.noun_priority_mode {
9134                        return false;
9135                    }
9136                    // A possessive followed by an NP head ("his dog",
9137                    // "her red dog") is a possessive NP, never a standalone
9138                    // pronoun. A possessive followed by a bare adjective
9139                    // ("its wet") stays on the pronoun path so the it's-typo
9140                    // grammar error still fires.
9141                    if self.possessive_np_head_follows() {
9142                        return false;
9143                    }
9144                }
9145                true
9146            }
9147            TokenType::Ambiguous { primary, alternatives } => {
9148                let has_possessive = matches!(**primary, TokenType::Pronoun { case: Case::Possessive, .. })
9149                    || alternatives.iter().any(|t| matches!(t, TokenType::Pronoun { case: Case::Possessive, .. }));
9150                // In noun_priority_mode, if there's a possessive alternative, prefer noun path
9151                if self.noun_priority_mode && has_possessive {
9152                    return false;
9153                }
9154                // An object/possessive-ambiguous pronoun ("her") stands alone
9155                // only when no NP head follows: "Mary saw her." is a pronoun
9156                // object, "Mary saw her dog." is a possessive NP.
9157                if has_possessive && self.possessive_np_head_follows() {
9158                    return false;
9159                }
9160                matches!(**primary, TokenType::Pronoun { .. })
9161                    || alternatives.iter().any(|t| matches!(t, TokenType::Pronoun { .. }))
9162            }
9163            _ => false,
9164        }
9165    }
9166
9167    /// True when the cursor heads a REDUCED-RELATIVE participle — a non-auxiliary
9168    /// verb, optionally behind a single leading ordinal/temporal adverb ("FIRST
9169    /// climbed", "originally seen"). The list-subject gate uses it so a first member
9170    /// carrying a reduced relative ("The peak CLIMBED in 1845, the dog, and Mt. Quinn
9171    /// are three different mountains") routes into the list parser, which consumes
9172    /// the reduced relative per member via `try_consume_reduced_relative`.
9173    fn peek_heads_reduced_relative_participle(&self) -> bool {
9174        let idx = if matches!(
9175            self.peek().kind,
9176            TokenType::Adverb(_) | TokenType::TemporalAdverb(_)
9177        ) && self
9178            .tokens
9179            .get(self.current + 1)
9180            .map_or(false, |t| self.kind_is_verb(&t.kind))
9181        {
9182            self.current + 1
9183        } else {
9184            self.current
9185        };
9186        let Some(t) = self.tokens.get(idx) else {
9187            return false;
9188        };
9189        if !self.kind_is_verb(&t.kind) {
9190            return false;
9191        }
9192        // A perfect/passive/do auxiliary heads a finite matrix VP, not a reduced
9193        // relative ("The apple HAS been eaten").
9194        if let TokenType::Verb { lemma, .. } = &t.kind {
9195            return !matches!(
9196                self.interner.resolve(*lemma).to_lowercase().as_str(),
9197                "have" | "be" | "do"
9198            );
9199        }
9200        true
9201    }
9202
9203    /// True when a comma appears before the end of the current clause (the next
9204    /// Period/EOF). The list-subject gate requires one before speculating on a
9205    /// reduced-relative first member — an enumerated list ("A, B, and C are …")
9206    /// always has a comma, an ordinary main clause does not.
9207    fn has_comma_before_clause_end(&self) -> bool {
9208        for i in self.current..self.tokens.len() {
9209            match &self.tokens[i].kind {
9210                TokenType::Period | TokenType::EOF => return false,
9211                TokenType::Comma => return true,
9212                _ => {}
9213            }
9214        }
9215        false
9216    }
9217
9218    /// Consume a post-nominal REDUCED RELATIVE — an optional leading ordinal/temporal
9219    /// adverb ("first"), a past/-ing participle ("climbed", "seen", "going"), and its
9220    /// PP / measure / directional-"to" complements ("in 1845", "by Captain Norris",
9221    /// "to Pete's") — as restriction predicates over `term`. Returns their conjunction,
9222    /// or None when no participle heads the cursor.
9223    ///
9224    /// `parse_noun_phrase` attaches reduced relatives only when the participle is
9225    /// unambiguous; its conservative, matrix-verb-gated post-subject handler keeps
9226    /// "The team won in 1989" a main clause. In a NOMINAL COORDINATION position — a
9227    /// list member, an either/or disjunct, an of-pair member — the NP cannot be a
9228    /// main-clause subject, so a participle there IS a reduced relative. This helper
9229    /// attaches it so the member's restrictor survives, shared so every coordination
9230    /// site composes once. Callers gate it to those positions (the participle here is
9231    /// committed unconditionally, no matrix-verb lookahead).
9232    fn try_consume_reduced_relative(
9233        &mut self,
9234        term: Term<'a>,
9235    ) -> ParseResult<Option<&'a LogicExpr<'a>>> {
9236        if !self.peek_heads_reduced_relative_participle() {
9237            return Ok(None);
9238        }
9239        let mut preds: Vec<&'a LogicExpr<'a>> = Vec::new();
9240        // Leading ordinal/temporal adverb over the gap ("first climbed" → First(x)).
9241        if matches!(
9242            self.peek().kind,
9243            TokenType::Adverb(_) | TokenType::TemporalAdverb(_)
9244        ) {
9245            let adv = match self.peek().kind {
9246                TokenType::Adverb(s) | TokenType::TemporalAdverb(s) => s,
9247                _ => unreachable!(),
9248            };
9249            self.advance();
9250            preds.push(self.ctx.exprs.alloc(LogicExpr::Predicate {
9251                name: adv,
9252                args: self.ctx.terms.alloc_slice([term]),
9253                world: None,
9254            }));
9255        }
9256        let (red_verb, _t, _a, _c) = self.consume_verb_with_metadata();
9257        preds.push(self.ctx.exprs.alloc(LogicExpr::Predicate {
9258            name: red_verb,
9259            args: self.ctx.terms.alloc_slice([term]),
9260            world: None,
9261        }));
9262        // Complements: PP objects ("from a beetle", "in March"), directional
9263        // "to <NP>" ("going to Pete's"), numeric measures ("in 1845"). Mirrors the
9264        // post-subject reduced-relative handler.
9265        loop {
9266            let prep = if self.check_preposition() {
9267                match self.advance().kind {
9268                    TokenType::Preposition(s) => s,
9269                    _ => break,
9270                }
9271            } else if self.check(&TokenType::To)
9272                && matches!(
9273                    self.tokens.get(self.current + 1).map(|t| &t.kind),
9274                    Some(TokenType::Article(_))
9275                        | Some(TokenType::Noun(_))
9276                        | Some(TokenType::ProperName(_))
9277                )
9278            {
9279                self.advance();
9280                self.interner.intern("To")
9281            } else {
9282                break;
9283            };
9284            if self.check_number() {
9285                let m = self.parse_measure_phrase()?;
9286                preds.push(self.ctx.exprs.alloc(LogicExpr::Predicate {
9287                    name: prep,
9288                    args: self.ctx.terms.alloc_slice([term, *m]),
9289                    world: None,
9290                }));
9291            } else if self.check_article() || self.check_content_word() {
9292                let obj = self.parse_noun_phrase(true)?;
9293                preds.push(self.ctx.exprs.alloc(LogicExpr::Predicate {
9294                    name: prep,
9295                    args: self.ctx.terms.alloc_slice([term, Term::Constant(obj.noun)]),
9296                    world: None,
9297                }));
9298            } else {
9299                break;
9300            }
9301        }
9302        let mut result = preds[0];
9303        for p in &preds[1..] {
9304            result = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
9305                left: result,
9306                op: TokenType::And,
9307                right: p,
9308            });
9309        }
9310        Ok(Some(result))
9311    }
9312
9313    /// Lower ONE list member to a distinct entity. A DESCRIPTION (adjectives /
9314    /// possessor / PPs / relative clause — and, when `bare_definite_distinct`, a
9315    /// bare definite) becomes a fresh ∃-VARIABLE carrying its full restrictor
9316    /// (head + adjectives + possessor + PPs + relative, all over the variable);
9317    /// anything else is a referring CONSTANT. Returns `(term, ∃-var?, restrictor?)`.
9318    ///
9319    /// Shared by both list shapes so members lower IDENTICALLY: the list subject
9320    /// ("A, B and C are different X") needs every definite member distinct for the
9321    /// "different" distinctness (`bare_definite_distinct = true`); the enumerated
9322    /// identity ("The four X are A, B and C") keeps a bare named definite ("the
9323    /// hustle") a constant but still must not collapse a DESCRIBED member ("the
9324    /// person from Spain", "the winner who won") to its head (`= false`).
9325    fn parse_list_member_np(
9326        &mut self,
9327        np: &crate::ast::NounPhrase<'a>,
9328        bare_definite_distinct: bool,
9329    ) -> ParseResult<(Term<'a>, Option<Symbol>, Option<&'a LogicExpr<'a>>)> {
9330        let has_rel = self.check(&TokenType::Who)
9331            || self.check(&TokenType::That)
9332            || self.check(&TokenType::Where)
9333            || self.check(&TokenType::Whose);
9334        let is_desc = has_rel
9335            || (bare_definite_distinct && np.definiteness.is_some())
9336            || !np.adjectives.is_empty()
9337            || np.possessor.is_some()
9338            || !np.pps.is_empty();
9339        if is_desc {
9340            let var = self.next_var_name();
9341            let term = Term::Variable(var);
9342            let mut r = self.nominal_predication(term, np);
9343            if let Some(poss) = np.possessor {
9344                let possesses = self.interner.intern("Possesses");
9345                let pr = self.ctx.exprs.alloc(LogicExpr::Predicate {
9346                    name: possesses,
9347                    args: self.ctx.terms.alloc_slice([Term::Constant(poss.noun), term]),
9348                    world: None,
9349                });
9350                r = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
9351                    left: r,
9352                    op: TokenType::And,
9353                    right: pr,
9354                });
9355            }
9356            for pp in np.pps {
9357                let pp_sub = self.substitute_pp_self_term(pp, term);
9358                r = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
9359                    left: r,
9360                    op: TokenType::And,
9361                    right: pp_sub,
9362                });
9363            }
9364            // who/that/where/whose attach uniformly via the shared dispatcher.
9365            if let Some(rc) = self.try_attach_relative(term)? {
9366                r = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
9367                    left: r,
9368                    op: TokenType::And,
9369                    right: rc,
9370                });
9371            }
9372            // A REDUCED relative ("the peak climbed in 1845", "the island first seen
9373            // by Captain Norris") — a participle a list member can carry but
9374            // parse_noun_phrase's conservative handler leaves stranded.
9375            if let Some(rr) = self.try_consume_reduced_relative(term)? {
9376                r = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
9377                    left: r,
9378                    op: TokenType::And,
9379                    right: rr,
9380                });
9381            }
9382            Ok((term, Some(var), Some(r)))
9383        } else {
9384            Ok((Term::Constant(np.noun), None, None))
9385        }
9386    }
9387
9388    /// Assemble an enumerated identity ("The four X are A, B and C") from an
9389    /// already-built first member. Parses the remaining comma/and-separated members
9390    /// through the shared distinct-entity builder, identifies the plural subject
9391    /// with the group of member terms, conjoins every member restrictor, and binds
9392    /// the described members' ∃ variables around the whole result.
9393    fn finish_enumerated_identity(
9394        &mut self,
9395        subject: &crate::ast::NounPhrase<'a>,
9396        is_negated: bool,
9397        first: (Term<'a>, Option<Symbol>, Option<&'a LogicExpr<'a>>),
9398    ) -> ParseResult<&'a LogicExpr<'a>> {
9399        let mut members: Vec<Term<'a>> = Vec::new();
9400        let mut member_vars: Vec<Symbol> = Vec::new();
9401        let mut member_restrictors: Vec<&'a LogicExpr<'a>> = Vec::new();
9402        let (t0, v0, r0) = first;
9403        members.push(t0);
9404        if let Some(v) = v0 {
9405            member_vars.push(v);
9406        }
9407        if let Some(r) = r0 {
9408            member_restrictors.push(r);
9409        }
9410        while self.check(&TokenType::Comma) || self.check(&TokenType::And) {
9411            // Consume separators ("," / "and" / ", and").
9412            while self.check(&TokenType::Comma) || self.check(&TokenType::And) {
9413                self.advance();
9414            }
9415            if !(self.check_article() || self.check_content_word()) {
9416                break;
9417            }
9418            let item = self.parse_noun_phrase(true)?;
9419            let (t, v, r) = self.parse_list_member_np(&item, false)?;
9420            members.push(t);
9421            if let Some(vv) = v {
9422                member_vars.push(vv);
9423            }
9424            if let Some(rr) = r {
9425                member_restrictors.push(rr);
9426            }
9427        }
9428        let group = Term::Group(self.ctx.terms.alloc_slice(members));
9429        let identity: &'a LogicExpr<'a> = self.ctx.exprs.alloc(LogicExpr::Identity {
9430            left: self.ctx.terms.alloc(Term::Constant(subject.noun)),
9431            right: self.ctx.terms.alloc(group),
9432        });
9433        let mut result = if is_negated {
9434            &*self.ctx.exprs.alloc(LogicExpr::UnaryOp {
9435                op: TokenType::Not,
9436                operand: identity,
9437            })
9438        } else {
9439            identity
9440        };
9441        for rc in member_restrictors {
9442            result = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
9443                left: result,
9444                op: TokenType::And,
9445                right: rc,
9446            });
9447        }
9448        for v in member_vars.into_iter().rev() {
9449            result = self.ctx.exprs.alloc(LogicExpr::Quantifier {
9450                kind: QuantifierKind::Existential,
9451                variable: v,
9452                body: result,
9453                island_id: self.current_island,
9454            });
9455        }
9456        self.wrap_with_definiteness_full(subject, result)
9457    }
9458
9459    /// Comma-coordinated list subject: "A, B and C are reptiles" / "A, B and C
9460    /// are all different animals". Distributes the predicate over each member;
9461    /// "(all) different" yields pairwise distinctness — the AllDifferent
9462    /// constraint the puzzle solver depends on. Returns `None` (restoring) when
9463    /// the comma does not open such a list (e.g. topicalization / apposition).
9464    fn try_parse_list_subject(
9465        &mut self,
9466        first: &crate::ast::NounPhrase<'a>,
9467    ) -> ParseResult<Option<&'a LogicExpr<'a>>> {
9468        let cp = self.checkpoint();
9469        // Each member becomes a DISTINCT entity: a DESCRIPTION (determiner /
9470        // adjectives / possessor / PPs / relative clause) is a fresh ∃-VARIABLE
9471        // carrying its restrictor (head + adjectives + possessor + PPs + rel clause,
9472        // all over the variable); a bare proper name is a referring CONSTANT
9473        // (distinct by unique-name). This makes "three different boxes" a MEANINGFUL
9474        // ¬x=y instead of the vacuous ¬Box=Box a shared head-noun constant gives.
9475        let mut member_terms: Vec<Term<'a>> = Vec::new();
9476        let mut member_vars: Vec<Symbol> = Vec::new();
9477        let mut member_restrictors: Vec<&'a LogicExpr<'a>> = Vec::new();
9478        macro_rules! add_member {
9479            ($np:expr) => {{
9480                let np = $np;
9481                match self.parse_list_member_np(&np, true) {
9482                    Ok((term, var, restrictor)) => {
9483                        member_terms.push(term);
9484                        if let Some(v) = var {
9485                            member_vars.push(v);
9486                        }
9487                        if let Some(r) = restrictor {
9488                            member_restrictors.push(r);
9489                        }
9490                    }
9491                    Err(_) => {
9492                        self.restore(cp);
9493                        return Ok(None);
9494                    }
9495                }
9496            }};
9497        }
9498        add_member!(*first);
9499        // The list gate: a comma OR "and" must follow the first member (and its
9500        // optional relative clause, "The juggler WHO USED BATONS, …"). A bare
9501        // 2-member "A and B verb"/"A and B are happy" with NO relative reaches
9502        // try_parse_plural_subject FIRST (the "and"-directly-after-subject gate),
9503        // so this path only sees DESCRIPTIVE members (a relativizer routed us
9504        // here) — those need the list parser's member restrictors + distinctness.
9505        if !self.check(&TokenType::Comma) && !self.check(&TokenType::And) {
9506            self.restore(cp);
9507            return Ok(None);
9508        }
9509        let mut saw_and = false;
9510        loop {
9511            let mut advanced = false;
9512            if self.check(&TokenType::Comma) {
9513                self.advance();
9514                advanced = true;
9515            }
9516            if self.check(&TokenType::And) {
9517                self.advance();
9518                saw_and = true;
9519                advanced = true;
9520            }
9521            if !advanced {
9522                break;
9523            }
9524            if !(self.check_article() || self.check_content_word()) {
9525                break;
9526            }
9527            let np = match self.parse_noun_phrase(true) {
9528                Ok(np) => np,
9529                Err(_) => {
9530                    self.restore(cp);
9531                    return Ok(None);
9532                }
9533            };
9534            add_member!(np);
9535        }
9536
9537        // A genuine coordination needs the final "and" and ≥2 members. The
9538        // simple 2-member "A and B" (no relative) is try_parse_plural_subject's
9539        // job and never reaches here; a 2-member list that DID reach here has a
9540        // descriptive (relative-clause) member that the plural path can't carry,
9541        // so handle it ("The cat that purrs and the dog that barks are different
9542        // animals").
9543        if !saw_and || member_terms.len() < 2 {
9544            self.restore(cp);
9545            return Ok(None);
9546        }
9547
9548        let copula_past = self.check(&TokenType::Were);
9549        if !(self.check(&TokenType::Are)
9550            || self.check(&TokenType::Were)
9551            || self.check(&TokenType::Is))
9552        {
9553            self.restore(cp);
9554            return Ok(None);
9555        }
9556        self.advance(); // copula
9557
9558        if self.check(&TokenType::All) {
9559            self.advance();
9560        }
9561
9562        // "are THREE different vegetables" — a cardinal that restates the member
9563        // count before "different" is subsumed by the pairwise distinctness, so
9564        // skip it (only when "different" actually follows, to stay conservative).
9565        if matches!(self.peek().kind, TokenType::Cardinal(_))
9566            && self
9567                .tokens
9568                .get(self.current + 1)
9569                .map(|t| self.interner.resolve(t.lexeme).eq_ignore_ascii_case("different"))
9570                .unwrap_or(false)
9571        {
9572            self.advance();
9573        }
9574
9575        // Each member's restrictor (head + adjectives + possessor + PPs + relative
9576        // clause, over its entity term) was built during parsing — start the
9577        // conjunction with them so nothing is dropped.
9578        let mut conjuncts: Vec<&'a LogicExpr<'a>> = member_restrictors;
9579
9580        let is_different = self
9581            .interner
9582            .resolve(self.peek().lexeme)
9583            .eq_ignore_ascii_case("different");
9584        if is_different {
9585            self.advance(); // "different"
9586            // Optional type noun ("animals"): predicate it of each member entity.
9587            if self.check_content_word() {
9588                let type_noun = self.consume_content_word()?;
9589                for t in &member_terms {
9590                    conjuncts.push(self.ctx.exprs.alloc(LogicExpr::Predicate {
9591                        name: type_noun,
9592                        args: self.ctx.terms.alloc_slice([*t]),
9593                        world: None,
9594                    }));
9595                    // A category DECLARATION ("2001, 2002, 2003, and 2004 are four
9596                    // different years.") records item→category in the shared
9597                    // discourse DRS, so a later definite LABEL ("the 2003 holiday")
9598                    // can recover the category and un-fuse to the same relation the
9599                    // prepositional-phrase form yields. Only a referring CONSTANT
9600                    // member names an item the label can match.
9601                    if let Term::Constant(item) = t {
9602                        self.drs.register_item_category(*item, type_noun);
9603                    }
9604                }
9605            }
9606            // Pairwise distinctness — the AllDifferent constraint over the DISTINCT
9607            // member entities (variables / constants), so it is non-vacuous.
9608            for i in 0..member_terms.len() {
9609                for j in (i + 1)..member_terms.len() {
9610                    let id = self.ctx.exprs.alloc(LogicExpr::Identity {
9611                        left: self.ctx.terms.alloc(member_terms[i]),
9612                        right: self.ctx.terms.alloc(member_terms[j]),
9613                    });
9614                    conjuncts.push(self.ctx.exprs.alloc(LogicExpr::UnaryOp {
9615                        op: TokenType::Not,
9616                        operand: id,
9617                    }));
9618                }
9619            }
9620        } else if self.check_article() || self.check_content_word() {
9621            // Distributive predication: each member satisfies the predicate NP.
9622            let pred_np = self.parse_noun_phrase(true)?;
9623            for t in &member_terms {
9624                conjuncts.push(self.ctx.exprs.alloc(LogicExpr::Predicate {
9625                    name: pred_np.noun,
9626                    args: self.ctx.terms.alloc_slice([*t]),
9627                    world: None,
9628                }));
9629            }
9630        } else {
9631            self.restore(cp);
9632            return Ok(None);
9633        }
9634
9635        if conjuncts.is_empty() {
9636            self.restore(cp);
9637            return Ok(None);
9638        }
9639        let mut result = conjuncts[0];
9640        for c in &conjuncts[1..] {
9641            result = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
9642                left: result,
9643                op: TokenType::And,
9644                right: c,
9645            });
9646        }
9647        if copula_past {
9648            result = self.ctx.exprs.alloc(LogicExpr::Temporal {
9649                operator: TemporalOperator::Past,
9650                body: result,
9651            });
9652        }
9653        // Bind each descriptive member's existential variable around the whole
9654        // conjunction (∃x∃y∃z(restrictors ∧ types ∧ ¬x=y ∧ …)).
9655        for &var in member_vars.iter().rev() {
9656            result = self.ctx.exprs.alloc(LogicExpr::Quantifier {
9657                kind: QuantifierKind::Existential,
9658                variable: var,
9659                body: result,
9660                island_id: self.current_island,
9661            });
9662        }
9663        Ok(Some(result))
9664    }
9665
9666    fn parse_atom(&mut self) -> ParseResult<&'a LogicExpr<'a>> {
9667        // A fresh clause starts with no stashed subject restriction — guard
9668        // against a prior clause leaking one that its copula branch never
9669        // consumed.
9670        self.pending_subject_restriction = None;
9671
9672        // Handle Focus particles: "Only John loves Mary", "Even John ran"
9673        if self.check_focus() {
9674            return self.parse_focus();
9675        }
9676
9677        // Handle mass noun measure: "Much water flows", "Little time remains"
9678        if self.check_measure() {
9679            return self.parse_measure();
9680        }
9681
9682        // Gerund subject ("Flying planes can be dangerous."): a sentence-initial
9683        // progressive verb + NP names an ACTIVITY; the matrix predicate applies
9684        // to that activity as a proposition. Gated on the full shape
9685        // (gerund + noun + modal/copula) so imperatives are untouched.
9686        if let TokenType::Verb {
9687            lemma,
9688            aspect: Aspect::Progressive,
9689            time: Time::None,
9690            ..
9691        } = self.peek().kind
9692        {
9693            let noun_next = matches!(
9694                self.tokens.get(self.current + 1).map(|t| t.kind.clone()),
9695                Some(TokenType::Noun(_))
9696            );
9697            let finite_after = matches!(
9698                self.tokens.get(self.current + 2).map(|t| t.kind.clone()),
9699                Some(
9700                    TokenType::Can
9701                        | TokenType::Could
9702                        | TokenType::May
9703                        | TokenType::Must
9704                        | TokenType::Should
9705                        | TokenType::Would
9706                        | TokenType::Is
9707                        | TokenType::Are
9708                )
9709            );
9710            if noun_next && finite_after {
9711                self.advance(); // gerund
9712                let noun_sym = match self.advance().kind {
9713                    TokenType::Noun(n) => n,
9714                    _ => unreachable!("guarded by noun_next"),
9715                };
9716
9717                let event_var = self.get_event_var();
9718                let activity = self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
9719                    event_var,
9720                    verb: lemma,
9721                    roles: self
9722                        .ctx
9723                        .roles
9724                        .alloc_slice(vec![(ThematicRole::Theme, Term::Constant(noun_sym))]),
9725                    modifiers: self.ctx.syms.alloc_slice(vec![]),
9726                    suppress_existential: false,
9727                    world: None,
9728                })));
9729
9730                let modal_tok = if matches!(
9731                    self.peek().kind,
9732                    TokenType::Can
9733                        | TokenType::Could
9734                        | TokenType::May
9735                        | TokenType::Must
9736                        | TokenType::Should
9737                        | TokenType::Would
9738                ) {
9739                    Some(self.advance().kind.clone())
9740                } else {
9741                    None
9742                };
9743                // Copula: bare "be" under a modal, or finite "is"/"are".
9744                if matches!(self.peek().kind, TokenType::Is | TokenType::Are | TokenType::Be) {
9745                    self.advance();
9746                } else if matches!(self.peek().kind, TokenType::Verb { .. })
9747                    && self
9748                        .interner
9749                        .resolve(self.peek().lexeme)
9750                        .eq_ignore_ascii_case("be")
9751                {
9752                    self.advance();
9753                }
9754
9755                if let TokenType::Adjective(adj) = self.peek().kind {
9756                    self.advance();
9757                    let pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
9758                        name: adj,
9759                        args: self.ctx.terms.alloc_slice([Term::Proposition(activity)]),
9760                        world: None,
9761                    });
9762                    let result = match modal_tok {
9763                        Some(tok) => {
9764                            let force = match tok {
9765                                TokenType::Must => 1.0,
9766                                TokenType::Should | TokenType::Would => 0.6,
9767                                TokenType::Could => 0.4,
9768                                _ => 0.5,
9769                            };
9770                            &*self.ctx.exprs.alloc(LogicExpr::Modal {
9771                                vector: crate::ast::ModalVector::new(
9772                                    crate::ast::ModalDomain::Alethic,
9773                                    force,
9774                                    crate::ast::ModalFlavor::Root,
9775                                ),
9776                                operand: pred,
9777                            })
9778                        }
9779                        None => pred,
9780                    };
9781                    return Ok(result);
9782                }
9783                return Err(ParseError {
9784                    kind: ParseErrorKind::ExpectedContentWord {
9785                        found: self.peek().kind.clone(),
9786                    },
9787                    span: self.current_span(),
9788                });
9789            }
9790        }
9791
9792        if self.check_quantifier() {
9793            self.advance();
9794            return self.parse_quantified();
9795        }
9796
9797        if self.check_npi_quantifier() {
9798            return self.parse_npi_quantified();
9799        }
9800
9801        if self.check_temporal_npi() {
9802            return self.parse_temporal_npi();
9803        }
9804
9805        if self.match_token(&[TokenType::LParen]) {
9806            let expr = self.parse_sentence()?;
9807            self.consume(TokenType::RParen)?;
9808            return Ok(expr);
9809        }
9810
9811        // Handle pronoun as subject
9812        if self.check_pronoun() {
9813            let token = self.advance().clone();
9814            let (gender, number) = match &token.kind {
9815                TokenType::Pronoun { gender, number, .. } => (*gender, *number),
9816                TokenType::Ambiguous { primary, alternatives } => {
9817                    if let TokenType::Pronoun { gender, number, .. } = **primary {
9818                        (gender, number)
9819                    } else {
9820                        alternatives.iter().find_map(|t| {
9821                            if let TokenType::Pronoun { gender, number, .. } = t {
9822                                Some((*gender, *number))
9823                            } else {
9824                                None
9825                            }
9826                        }).unwrap_or((Gender::Unknown, Number::Singular))
9827                    }
9828                }
9829                _ => (Gender::Unknown, Number::Singular),
9830            };
9831
9832            let token_text = self.interner.resolve(token.lexeme);
9833
9834            // Weather verb + expletive "it" detection: "it rains" → ∃e(Rain(e))
9835            // Must check BEFORE pronoun resolution since "it" resolves to "?"
9836            if token_text.eq_ignore_ascii_case("it") && self.check_verb() {
9837                if let TokenType::Verb { lemma, time, .. } = &self.peek().kind {
9838                    let lemma_str = self.interner.resolve(*lemma);
9839                    if Lexer::is_weather_verb(lemma_str) {
9840                        let verb = *lemma;
9841                        let verb_time = *time;
9842                        self.advance(); // consume the weather verb
9843
9844                        let event_var = self.get_event_var();
9845                        let suppress_existential = self.drs.in_conditional_antecedent();
9846                        if suppress_existential {
9847                            let event_class = self.interner.intern("Event");
9848                            self.drs.introduce_referent(event_var, event_class, Gender::Neuter, Number::Singular);
9849                        }
9850                        let neo_event = self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
9851                            event_var,
9852                            verb,
9853                            roles: self.ctx.roles.alloc_slice(vec![]), // No thematic roles
9854                            modifiers: self.ctx.syms.alloc_slice(vec![]),
9855                            suppress_existential,
9856                            world: None,
9857                        })));
9858
9859                        return Ok(match verb_time {
9860                            Time::Past => self.ctx.exprs.alloc(LogicExpr::Temporal {
9861                                operator: TemporalOperator::Past,
9862                                body: neo_event,
9863                            }),
9864                            Time::Future => self.ctx.exprs.alloc(LogicExpr::Temporal {
9865                                operator: TemporalOperator::Future,
9866                                body: neo_event,
9867                            }),
9868                            _ => neo_event,
9869                        });
9870                    }
9871                }
9872            }
9873
9874            // Handle deictic pronouns that don't need discourse resolution
9875            let resolved = if token_text.eq_ignore_ascii_case("i") {
9876                ResolvedPronoun::Constant(self.interner.intern("Speaker"))
9877            } else if token_text.eq_ignore_ascii_case("you") {
9878                ResolvedPronoun::Constant(self.interner.intern("Addressee"))
9879            } else {
9880                // Try discourse resolution for anaphoric pronouns
9881                self.resolve_pronoun(gender, number)?
9882            };
9883
9884            // Skip the performativity marker "hereby": "I hereby resign."
9885            if self
9886                .interner
9887                .resolve(self.peek().lexeme)
9888                .eq_ignore_ascii_case("hereby")
9889            {
9890                self.advance();
9891            }
9892
9893            // Check for performative: "I (hereby) promise to/that ...", "I hereby resign."
9894            if self.check_performative() {
9895                if let TokenType::Performative(act) = self.advance().kind.clone() {
9896                    let sym = match resolved {
9897                        ResolvedPronoun::Variable(s) | ResolvedPronoun::Constant(s) => s,
9898                    };
9899
9900                    // Infinitive complement: "I promise to call you." The complement
9901                    // verb may be noun-tagged ("call"), so accept any content word and
9902                    // capture its object so the hearer survives (P3 structured content).
9903                    if self.check(&TokenType::To) {
9904                        self.advance(); // consume "to"
9905
9906                        if !self.is_at_end() && !self.check(&TokenType::Period) {
9907                            // The complement verb may be lexed as a reserved keyword
9908                            // (e.g. "call" → TokenType::Call), so take the lexeme
9909                            // directly as the predicate name rather than requiring a
9910                            // content-word token.
9911                            let lexeme =
9912                                self.interner.resolve(self.peek().lexeme).to_string();
9913                            let verb_name = lexeme
9914                                .chars()
9915                                .next()
9916                                .map(|c| c.to_uppercase().collect::<String>() + &lexeme[1..])
9917                                .unwrap_or(lexeme);
9918                            let infinitive_verb = self.interner.intern(&verb_name);
9919                            self.advance(); // consume the complement verb
9920                            let content = self.performative_predicate(infinitive_verb, sym);
9921                            return Ok(self.ctx.exprs.alloc(LogicExpr::SpeechAct {
9922                                performer: sym,
9923                                act_type: act,
9924                                content,
9925                            }));
9926                        }
9927                    }
9928
9929                    // Clausal complement: "I declare that S."
9930                    if self.check(&TokenType::That) {
9931                        self.advance();
9932                        let content = self.parse_sentence()?;
9933                        return Ok(self.ctx.exprs.alloc(LogicExpr::SpeechAct {
9934                            performer: sym,
9935                            act_type: act,
9936                            content,
9937                        }));
9938                    }
9939
9940                    // Bare performative — the saying is the doing — but ONLY when the
9941                    // remainder is empty ("I hereby resign.") or a lone object NP
9942                    // ("I thank you."). Object-control performatives that take a
9943                    // further complement ("I order you to leave.") fall through to the
9944                    // standard clausal parse below so the complement is not truncated.
9945                    let at_end = self.check(&TokenType::Period) || self.is_at_end();
9946                    let lone_object_then_end = matches!(
9947                        self.peek().kind,
9948                        TokenType::Pronoun { .. } | TokenType::ProperName(_)
9949                    ) && self.current + 1 < self.tokens.len()
9950                        && matches!(
9951                            self.tokens[self.current + 1].kind,
9952                            TokenType::Period | TokenType::EOF
9953                        );
9954                    if at_end || lone_object_then_end {
9955                        let content = self.performative_predicate(act, sym);
9956                        return Ok(self.ctx.exprs.alloc(LogicExpr::SpeechAct {
9957                            performer: sym,
9958                            act_type: act,
9959                            content,
9960                        }));
9961                    }
9962
9963                    // Fallback (object-control performatives, residual material):
9964                    // preserve the prior behavior of parsing the remainder as content.
9965                    let content = self.parse_sentence()?;
9966                    return Ok(self.ctx.exprs.alloc(LogicExpr::SpeechAct {
9967                        performer: sym,
9968                        act_type: act,
9969                        content,
9970                    }));
9971                }
9972            }
9973
9974            // Continue parsing verb phrase with resolved subject
9975            // Use as_var=true for bound variables, as_var=false for constants
9976            return match resolved {
9977                ResolvedPronoun::Variable(sym) => self.parse_predicate_with_subject_as_var(sym),
9978                ResolvedPronoun::Constant(sym) => self.parse_predicate_with_subject(sym),
9979            };
9980        }
9981
9982        // Consume "both" correlative marker if present: "both X and Y"
9983        // The existing try_parse_plural_subject will handle the "X and Y" pattern
9984        let _had_both = self.match_token(&[TokenType::Both]);
9985
9986        let mut subject = self.parse_noun_phrase(true)?;
9987
9988        // Floating (stranded) quantifier: "The boys all left.", "The students each
9989        // solved a problem.", "The boys both ran." A quantifier stranded after the
9990        // subject re-associates to it and distributes the predicate over its members.
9991        // Consume it so the (plural definite) subject's distributive reading applies.
9992        if self.check(&TokenType::All)
9993            || self.check(&TokenType::Both)
9994            || self.interner.resolve(self.peek().lexeme).eq_ignore_ascii_case("each")
9995        {
9996            self.advance();
9997            self.collective_mode = false; // force the distributive (per-member) reading
9998            self.distributive_marker = true;
9999        }
10000
10001        // Introduce subject NP to DRS for cross-sentence pronoun resolution (accommodation)
10002        // This allows "A man walked. He fell." to work
10003        // Use noun as both variable and noun_class (like proper names) so pronouns resolve to it
10004        // NOTE: Definite NPs are NOT introduced here - they go through wrap_with_definiteness
10005        // where bridging anaphora can link them to prior wholes (e.g., "I bought a car. The engine smoked.")
10006        if subject.definiteness == Some(Definiteness::Indefinite)
10007            || subject.definiteness == Some(Definiteness::Distal) {
10008            let gender = Self::infer_noun_gender(self.interner.resolve(subject.noun));
10009            let number = if Self::is_plural_noun(self.interner.resolve(subject.noun)) {
10010                Number::Plural
10011            } else {
10012                Number::Singular
10013            };
10014            // Use noun as variable so pronoun resolution returns the noun name
10015            self.drs.introduce_referent(subject.noun, subject.noun, gender, number);
10016        }
10017
10018        // Handle plural subjects: "John and Mary verb"
10019        if self.check(&TokenType::And) {
10020            match self.try_parse_plural_subject(&subject) {
10021                Ok(Some(result)) => return Ok(result),
10022                Ok(None) => {} // Not a plural subject, continue
10023                Err(e) => return Err(e), // Semantic error (e.g., respectively mismatch)
10024            }
10025        }
10026
10027        // Handle scopal adverbs: "John almost died"
10028        if self.check_scopal_adverb() {
10029            return self.parse_scopal_adverb(&subject);
10030        }
10031
10032        // Non-restrictive (appositive) RC (§3.1): "John, who loves Mary, left." — a
10033        // comma-delimited RC on a referential head is a SIDE-ASSERTION about the
10034        // head referent, conjoined with the main clause, NOT a restriction.
10035        if self.check(&TokenType::Comma)
10036            && matches!(
10037                self.tokens.get(self.current + 1).map(|t| &t.kind),
10038                Some(TokenType::Who) | Some(TokenType::That)
10039            )
10040        {
10041            self.advance(); // comma
10042            self.advance(); // who / which / that
10043            // The RC predication, with the head referent filling the gap.
10044            let rc = self.parse_relative_clause(subject.noun)?;
10045            if self.check(&TokenType::Comma) {
10046                self.advance(); // closing comma
10047            }
10048            // The main clause predicated of the same head.
10049            let main = self.parse_predicate_with_subject(subject.noun)?;
10050            let conj = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
10051                left: main,
10052                op: TokenType::And,
10053                right: rc,
10054            });
10055            return self.wrap_with_definiteness_full(&subject, conj);
10056        }
10057
10058        // Comma-coordinated list subject: "A, B and C are all different animals."
10059        // Also fire when a relativizer follows the first member ("The scent THAT
10060        // sold for $75, the white fragrance, and the purple perfume are three
10061        // different perfumes") — the list parser consumes the first member's
10062        // relative clause before the comma gate. It restores cleanly when this is
10063        // an ordinary relative-clause subject, so the normal handler still runs.
10064        if self.check(&TokenType::Comma)
10065            || self.check(&TokenType::Who)
10066            || self.check(&TokenType::That)
10067            // "and" too: try_parse_plural_subject (called above) handles the simple
10068            // "A and B verb"/"A and B are happy" and returned None here only when a
10069            // member it can't carry follows (a relative clause on the SECOND member:
10070            // "Mr. Gray's timepiece and the timepiece THAT sold for $1,800 were
10071            // different clocks"). The list parser carries descriptive members; it
10072            // restores cleanly if this is not a coordinated list.
10073            || self.check(&TokenType::And)
10074            // A reduced-relative participle on the first member ("The peak CLIMBED
10075            // in 1845, …, and X are three different mountains") — the bare NP stops
10076            // at the head, so the comma is hidden behind the participle; route in so
10077            // the list parser consumes the reduced relative per member. An enumerated
10078            // list needs a comma before the clause end, so gate on one: a plain main
10079            // clause ("The team won in 1989") then never triggers the speculation.
10080            || (self.peek_heads_reduced_relative_participle() && self.has_comma_before_clause_end())
10081        {
10082            if let Some(result) = self.try_parse_list_subject(&subject)? {
10083                return Ok(result);
10084            }
10085        }
10086
10087        // Handle topicalization: "The cake, John ate." - first NP is object, not subject
10088        if self.check(&TokenType::Comma) {
10089            let saved_pos = self.current;
10090            self.advance(); // consume comma
10091
10092            // Check if followed by pronoun subject (e.g., "The book, he read.")
10093            if self.check_pronoun() {
10094                let topic_attempt = self.try_parse(|p| {
10095                    let token = p.peek().clone();
10096                    let pronoun_features = match &token.kind {
10097                        TokenType::Pronoun { gender, number, .. } => Some((*gender, *number)),
10098                        TokenType::Ambiguous { primary, alternatives } => {
10099                            if let TokenType::Pronoun { gender, number, .. } = **primary {
10100                                Some((gender, number))
10101                            } else {
10102                                alternatives.iter().find_map(|t| {
10103                                    if let TokenType::Pronoun { gender, number, .. } = t {
10104                                        Some((*gender, *number))
10105                                    } else {
10106                                        None
10107                                    }
10108                                })
10109                            }
10110                        }
10111                        _ => None,
10112                    };
10113
10114                    if let Some((gender, number)) = pronoun_features {
10115                        p.advance(); // consume pronoun
10116                        let resolved = p.resolve_pronoun(gender, number)?;
10117                        let resolved_term = match resolved {
10118                            ResolvedPronoun::Variable(s) => Term::Variable(s),
10119                            ResolvedPronoun::Constant(s) => Term::Constant(s),
10120                        };
10121
10122                        if p.check_verb() {
10123                            let verb = p.consume_verb();
10124                            let predicate = p.ctx.exprs.alloc(LogicExpr::Predicate {
10125                                name: verb,
10126                                args: p.ctx.terms.alloc_slice([
10127                                    resolved_term,
10128                                    Term::Constant(subject.noun),
10129                                ]),
10130                                world: None,
10131                            });
10132                            p.wrap_with_definiteness_full(&subject, predicate)
10133                        } else {
10134                            Err(ParseError {
10135                                kind: ParseErrorKind::ExpectedVerb { found: p.peek().kind.clone() },
10136                                span: p.current_span(),
10137                            })
10138                        }
10139                    } else {
10140                        Err(ParseError {
10141                            kind: ParseErrorKind::ExpectedContentWord { found: token.kind },
10142                            span: p.current_span(),
10143                        })
10144                    }
10145                });
10146
10147                if let Some(result) = topic_attempt {
10148                    return Ok(result);
10149                }
10150            }
10151
10152            // Check if followed by another NP and then a verb (topicalization pattern)
10153            if self.check_content_word() {
10154                let topic_attempt = self.try_parse(|p| {
10155                    let real_subject = p.parse_noun_phrase(true)?;
10156                    if p.check_verb() {
10157                        let verb = p.consume_verb();
10158                        let predicate = p.ctx.exprs.alloc(LogicExpr::Predicate {
10159                            name: verb,
10160                            args: p.ctx.terms.alloc_slice([
10161                                Term::Constant(real_subject.noun),
10162                                Term::Constant(subject.noun),
10163                            ]),
10164                            world: None,
10165                        });
10166                        p.wrap_with_definiteness_full(&subject, predicate)
10167                    } else {
10168                        Err(ParseError {
10169                            kind: ParseErrorKind::ExpectedVerb { found: p.peek().kind.clone() },
10170                            span: p.current_span(),
10171                        })
10172                    }
10173                });
10174
10175                if let Some(result) = topic_attempt {
10176                    return Ok(result);
10177                }
10178            }
10179
10180            // Restore position if topicalization didn't match
10181            self.current = saved_pos;
10182        }
10183
10184        // Handle relative clause after subject: "The cat that the dog chased ran."
10185        let mut relative_clause: Option<(Symbol, &'a LogicExpr<'a>)> = None;
10186        if self.check(&TokenType::That) || self.check(&TokenType::Who) {
10187            self.advance();
10188            let var_name = self.next_var_name();
10189            let rel_pred = self.parse_relative_clause(var_name)?;
10190            relative_clause = Some((var_name, rel_pred));
10191        } else if self.check(&TokenType::Where) {
10192            // Locative/circumstance relative: "the episode WHERE the survivor brought
10193            // the knife", "the trip WHERE they saw 16 stars". Unlike who/that (an
10194            // argument gap), the clause has its OWN subject and the head is the
10195            // LOCATION/occasion of the event — so parse the embedded subject + its full
10196            // VP and conjoin In(event, head) over the gap. Without this the clue
10197            // stranded at "where" (TrailingTokens).
10198            let var_name = self.next_var_name();
10199            let rel_pred = self.parse_where_relative(var_name)?;
10200            relative_clause = Some((var_name, rel_pred));
10201        } else if matches!(self.peek().kind, TokenType::Article(_)) && self.is_contact_clause_pattern() {
10202            // Contact clause (reduced relative): "The cat the dog chased ran."
10203            // NP + NP + Verb pattern indicates embedded relative without explicit "that"
10204            let var_name = self.next_var_name();
10205            let rel_pred = self.parse_relative_clause(var_name)?;
10206            relative_clause = Some((var_name, rel_pred));
10207        }
10208
10209        // Handle main verb after relative clause: "The cat that the dog chased ran."
10210        if let Some((var_name, rel_clause)) = relative_clause {
10211            // Aspectual / presupposition-trigger matrix over a relativized subject
10212            // ("the person WHO WON STARTED skydiving 2 years after Leslie") — the
10213            // SAME presupposition grammar as a bare subject, via the term-parametric
10214            // form, so the feature composes with relative-clause subjects.
10215            if self.check_presup_trigger()
10216                && !self.is_followed_by_np_object()
10217                && self.is_followed_by_gerund()
10218            {
10219                let presup_kind = self.consume_presup_trigger();
10220                let main_pred =
10221                    self.parse_presupposition_for_term(Term::Variable(var_name), presup_kind, false)?;
10222                let type_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
10223                    name: subject.noun,
10224                    args: self.ctx.terms.alloc_slice([Term::Variable(var_name)]),
10225                    world: None,
10226                });
10227                let inner = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
10228                    left: type_pred,
10229                    op: TokenType::And,
10230                    right: rel_clause,
10231                });
10232                let body = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
10233                    left: inner,
10234                    op: TokenType::And,
10235                    right: main_pred,
10236                });
10237                return Ok(self.ctx.exprs.alloc(LogicExpr::Quantifier {
10238                    kind: QuantifierKind::Existential,
10239                    variable: var_name,
10240                    body,
10241                    island_id: self.current_island,
10242                }));
10243            }
10244            if self.check_verb() {
10245                let (verb, verb_time, _, _) = self.consume_verb_with_metadata();
10246                let var_term = Term::Variable(var_name);
10247
10248                // A bare verb keeps the simple event; material after it
10249                // ("The farmer who saw a cat CHASED IT.") takes the full VP
10250                // grammar over the relativized variable.
10251                let main_pred = if self.at_clause_boundary() {
10252                    let event_var = self.get_event_var();
10253                    let suppress_existential = self.drs.in_conditional_antecedent();
10254                    let mut modifiers = vec![];
10255                    if verb_time == Time::Past {
10256                        modifiers.push(self.interner.intern("Past"));
10257                    }
10258                    &*self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
10259                        event_var,
10260                        verb,
10261                        roles: self.ctx.roles.alloc_slice(vec![
10262                            (ThematicRole::Agent, var_term),
10263                        ]),
10264                        modifiers: self.ctx.syms.alloc_slice(modifiers),
10265                        suppress_existential,
10266                        world: None,
10267                    })))
10268                } else {
10269                    self.current -= 1; // hand the verb back to the VP parser
10270                    self.parse_predicate_with_subject_as_var(var_name)?
10271                };
10272
10273                let type_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
10274                    name: subject.noun,
10275                    args: self.ctx.terms.alloc_slice([Term::Variable(var_name)]),
10276                    world: None,
10277                });
10278
10279                let inner = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
10280                    left: type_pred,
10281                    op: TokenType::And,
10282                    right: rel_clause,
10283                });
10284
10285                let body = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
10286                    left: inner,
10287                    op: TokenType::And,
10288                    right: main_pred,
10289                });
10290
10291                return Ok(self.ctx.exprs.alloc(LogicExpr::Quantifier {
10292                    kind: QuantifierKind::Existential,
10293                    variable: var_name,
10294                    body,
10295                    island_id: self.current_island,
10296                }));
10297            }
10298
10299            // No main verb - just the relative clause: "The cat that runs" as a complete NP
10300            // Build: ∃x(Cat(x) ∧ Runs(x) ∧ ∀y(Cat(y) → y=x))
10301            if self.is_at_end() || self.check(&TokenType::Period) || self.check(&TokenType::Comma) {
10302                let type_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
10303                    name: subject.noun,
10304                    args: self.ctx.terms.alloc_slice([Term::Variable(var_name)]),
10305                    world: None,
10306                });
10307
10308                let body = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
10309                    left: type_pred,
10310                    op: TokenType::And,
10311                    right: rel_clause,
10312                });
10313
10314                // Add uniqueness for definite description
10315                let uniqueness_body = if subject.definiteness == Some(Definiteness::Definite) {
10316                    let y_var = self.next_var_name();
10317                    let type_pred_y = self.ctx.exprs.alloc(LogicExpr::Predicate {
10318                        name: subject.noun,
10319                        args: self.ctx.terms.alloc_slice([Term::Variable(y_var)]),
10320                        world: None,
10321                    });
10322                    let identity = self.ctx.exprs.alloc(LogicExpr::Identity {
10323                        left: self.ctx.terms.alloc(Term::Variable(y_var)),
10324                        right: self.ctx.terms.alloc(Term::Variable(var_name)),
10325                    });
10326                    let uniqueness_cond = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
10327                        left: type_pred_y,
10328                        op: TokenType::Implies,
10329                        right: identity,
10330                    });
10331                    let uniqueness = self.ctx.exprs.alloc(LogicExpr::Quantifier {
10332                        kind: QuantifierKind::Universal,
10333                        variable: y_var,
10334                        body: uniqueness_cond,
10335                        island_id: self.current_island,
10336                    });
10337                    self.ctx.exprs.alloc(LogicExpr::BinaryOp {
10338                        left: body,
10339                        op: TokenType::And,
10340                        right: uniqueness,
10341                    })
10342                } else {
10343                    body
10344                };
10345
10346                return Ok(self.ctx.exprs.alloc(LogicExpr::Quantifier {
10347                    kind: QuantifierKind::Existential,
10348                    variable: var_name,
10349                    body: uniqueness_body,
10350                    island_id: self.current_island,
10351                }));
10352            }
10353
10354            // Re-store for copula handling below
10355            relative_clause = Some((var_name, rel_clause));
10356        }
10357
10358        // Identity check: "Clark is equal to Superman"
10359        if self.check(&TokenType::Identity) {
10360            self.advance();
10361            let right = self.consume_content_word()?;
10362            return Ok(self.ctx.exprs.alloc(LogicExpr::Identity {
10363                left: self.ctx.terms.alloc(Term::Constant(subject.noun)),
10364                right: self.ctx.terms.alloc(Term::Constant(right)),
10365            }));
10366        }
10367
10368        if self.check_modal() {
10369            if let Some((var_name, rel_clause)) = relative_clause {
10370                let modal_pred = self.parse_aspect_chain_with_term(Term::Variable(var_name))?;
10371
10372                let type_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
10373                    name: subject.noun,
10374                    args: self.ctx.terms.alloc_slice([Term::Variable(var_name)]),
10375                    world: None,
10376                });
10377
10378                let inner = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
10379                    left: type_pred,
10380                    op: TokenType::And,
10381                    right: rel_clause,
10382                });
10383
10384                let body = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
10385                    left: inner,
10386                    op: TokenType::And,
10387                    right: modal_pred,
10388                });
10389
10390                return Ok(self.ctx.exprs.alloc(LogicExpr::Quantifier {
10391                    kind: QuantifierKind::Existential,
10392                    variable: var_name,
10393                    body,
10394                    island_id: self.current_island,
10395                }));
10396            }
10397
10398            let modal_pred = self.parse_aspect_chain(subject.noun)?;
10399            return self.wrap_with_definiteness_full(&subject, modal_pred);
10400        }
10401
10402        // Reduced relative clause on the subject before a copular matrix
10403        // predicate: "The drug sourced from a beetle is X.", "The mushroom going
10404        // to Pete's is Y.", "The raptor belonging to Ivan is trained by Z." A
10405        // participle directly after the subject — followed, once its complements
10406        // are consumed, by the matrix copula — restricts the subject (it is NOT
10407        // the matrix verb). Parse it as restriction predicates over the subject
10408        // and fold them into the subject's PPs, so the existing copular path then
10409        // predicates the rest. Backtracking: commit only if a copula truly
10410        // follows, so a finite main verb ("The drug treats X") is untouched.
10411        // An auxiliary (have/be/do) heads a perfect/passive/progressive matrix
10412        // ("The apple has been eaten."), NOT a reduced relative — never treat it
10413        // as the participle.
10414        // A reduced relative may carry a leading ordinal/temporal adverb ("The peak
10415        // FIRST climbed in 1845 is tall.", "The island FIRST seen by Captain Norris
10416        // is small."). The adverb hides the participle from the verb check below; look
10417        // one token past a single leading adverb to find the participle that heads the
10418        // relative, and consume it inside the speculative parse so the token survives
10419        // as a modifier on the subject.
10420        let leading_adverb = if matches!(
10421            self.peek().kind,
10422            TokenType::Adverb(_) | TokenType::TemporalAdverb(_)
10423        ) && self
10424            .tokens
10425            .get(self.current + 1)
10426            .map_or(false, |t| self.kind_is_verb(&t.kind))
10427        {
10428            match self.peek().kind {
10429                TokenType::Adverb(s) | TokenType::TemporalAdverb(s) => Some(s),
10430                _ => None,
10431            }
10432        } else {
10433            None
10434        };
10435        let head_verb_idx = if leading_adverb.is_some() {
10436            self.current + 1
10437        } else {
10438            self.current
10439        };
10440        let head_verb_is_aux = if let Some(TokenType::Verb { lemma, .. }) =
10441            self.tokens.get(head_verb_idx).map(|t| &t.kind)
10442        {
10443            matches!(
10444                self.interner.resolve(*lemma).to_lowercase().as_str(),
10445                "have" | "be" | "do"
10446            )
10447        } else {
10448            false
10449        };
10450        let heads_reduced_rel = self
10451            .tokens
10452            .get(head_verb_idx)
10453            .map_or(false, |t| self.kind_is_verb(&t.kind));
10454        if subject.definiteness.is_some()
10455            && heads_reduced_rel
10456            && self.pending_time.is_none()
10457            && !head_verb_is_aux
10458        {
10459            if let Some(restrs) = self.try_parse(|p| {
10460                let placeholder = p.interner.intern("_PP_SELF_");
10461                let mut preds: Vec<&'a LogicExpr<'a>> = Vec::new();
10462                if let Some(adv) = leading_adverb {
10463                    p.advance();
10464                    preds.push(p.ctx.exprs.alloc(LogicExpr::Predicate {
10465                        name: adv,
10466                        args: p.ctx.terms.alloc_slice([Term::Variable(placeholder)]),
10467                        world: None,
10468                    }));
10469                }
10470                let (red_verb, _t, _a, _c) = p.consume_verb_with_metadata();
10471                preds.push(p.ctx.exprs.alloc(LogicExpr::Predicate {
10472                    name: red_verb,
10473                    args: p.ctx.terms.alloc_slice([Term::Variable(placeholder)]),
10474                    world: None,
10475                }));
10476                // Complements: PP objects ("from a beetle", "in March"),
10477                // directional "to <NP>" ("going to Pete's"), numeric measures.
10478                loop {
10479                    let prep = if p.check_preposition() {
10480                        match p.advance().kind {
10481                            TokenType::Preposition(s) => s,
10482                            _ => break,
10483                        }
10484                    } else if p.check(&TokenType::To)
10485                        && matches!(
10486                            p.tokens.get(p.current + 1).map(|t| &t.kind),
10487                            Some(TokenType::Article(_))
10488                                | Some(TokenType::Noun(_))
10489                                | Some(TokenType::ProperName(_))
10490                        )
10491                    {
10492                        p.advance(); // "to" (directional, NP object follows)
10493                        p.interner.intern("To")
10494                    } else {
10495                        break;
10496                    };
10497                    if p.check_number() {
10498                        let m = p.parse_measure_phrase()?;
10499                        preds.push(p.ctx.exprs.alloc(LogicExpr::Predicate {
10500                            name: prep,
10501                            args: p.ctx.terms.alloc_slice([Term::Variable(placeholder), *m]),
10502                            world: None,
10503                        }));
10504                    } else if p.check_article() || p.check_content_word() {
10505                        let obj = p.parse_noun_phrase(true)?;
10506                        preds.push(p.ctx.exprs.alloc(LogicExpr::Predicate {
10507                            name: prep,
10508                            args: p
10509                                .ctx
10510                                .terms
10511                                .alloc_slice([Term::Variable(placeholder), Term::Constant(obj.noun)]),
10512                            world: None,
10513                        }));
10514                    } else {
10515                        break;
10516                    }
10517                }
10518                // Commit only if the matrix predicate follows — a copula, a FINITE
10519                // main verb, or a do-support / perfect AUXILIARY heading the matrix
10520                // VP. The "participle (+ complements) THEN a matrix predicate" pattern
10521                // is the reduced-relative signature ("the box [found on May 3] HELD the
10522                // ring", "the book [priced at $55] DOESN'T have a cover", "the well
10523                // [cut through gravel] HAD a leak"); a lone main verb ("The box found
10524                // the key") has no matrix here, so it backtracks and keeps its verb
10525                // reading. The matrix verb must be FINITE — an -ing (progressive) form
10526                // is not a finite matrix verb but a gerund modifier of the verb's
10527                // object ("used BOWLING pins"), so it must NOT trigger the reading.
10528                let matrix_finite_verb = matches!(
10529                    p.peek().kind,
10530                    TokenType::Verb { aspect, .. } if !matches!(aspect, Aspect::Progressive)
10531                );
10532                if !(p.check(&TokenType::Is)
10533                    || p.check(&TokenType::Are)
10534                    || p.check(&TokenType::Was)
10535                    || p.check(&TokenType::Were)
10536                    || p.check(&TokenType::Does)
10537                    || p.check(&TokenType::Do)
10538                    || p.check(&TokenType::Had)
10539                    || matrix_finite_verb)
10540                {
10541                    return Err(ParseError {
10542                        kind: ParseErrorKind::ExpectedCopula,
10543                        span: p.current_span(),
10544                    });
10545                }
10546                Ok(preds)
10547            }) {
10548                let mut pps: Vec<&'a LogicExpr<'a>> = subject.pps.to_vec();
10549                pps.extend(restrs);
10550                subject = crate::ast::NounPhrase {
10551                    pps: self.ctx.pps.alloc_slice(pps),
10552                    ..subject
10553                };
10554            }
10555        }
10556
10557        // Bare-plural copular subject = kind predication (§6.1):
10558        // "Penguins are birds." → Gen x(Penguin(x) → Bird(x)), never the
10559        // ordinary individual-level copular path.
10560        if (self.check(&TokenType::Are) || self.check(&TokenType::Is))
10561            && subject.definiteness.is_none()
10562            && subject.possessor.is_none()
10563        {
10564            let noun_str = self.interner.resolve(subject.noun);
10565            let lower = noun_str.to_lowercase();
10566            let is_known_plural = Self::is_plural_noun(noun_str)
10567                && (crate::lexicon::is_common_noun(&lower)
10568                    || matches!(
10569                        crate::lexicon::analyze_word(&lower),
10570                        Some(crate::lexicon::WordAnalysis::Noun(_))
10571                    ));
10572            let nominal_or_adj_follows = matches!(
10573                self.tokens.get(self.current + 1).map(|t| &t.kind),
10574                Some(TokenType::Noun(_))
10575                    | Some(TokenType::Adjective(_))
10576                    | Some(TokenType::Article(_))
10577            );
10578            if is_known_plural && nominal_or_adj_follows {
10579                self.advance(); // copula
10580                let negated = self.check(&TokenType::Not);
10581                if negated {
10582                    self.advance();
10583                }
10584                if self.check_article() {
10585                    self.advance();
10586                }
10587                let var_name = self.next_var_name();
10588                let type_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
10589                    name: subject.noun,
10590                    args: self.ctx.terms.alloc_slice([Term::Variable(var_name)]),
10591                    world: None,
10592                });
10593                let pred_name = match self.peek().kind {
10594                    TokenType::Noun(s) | TokenType::Adjective(s) => {
10595                        self.advance();
10596                        s
10597                    }
10598                    _ => self.consume_content_word()?,
10599                };
10600                let mut nucleus: &'a LogicExpr<'a> =
10601                    self.ctx.exprs.alloc(LogicExpr::Predicate {
10602                        name: pred_name,
10603                        args: self.ctx.terms.alloc_slice([Term::Variable(var_name)]),
10604                        world: None,
10605                    });
10606                if negated {
10607                    nucleus = self.ctx.exprs.alloc(LogicExpr::UnaryOp {
10608                        op: TokenType::Not,
10609                        operand: nucleus,
10610                    });
10611                }
10612                let body = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
10613                    left: type_pred,
10614                    op: TokenType::Implies,
10615                    right: nucleus,
10616                });
10617                return Ok(self.ctx.exprs.alloc(LogicExpr::Quantifier {
10618                    kind: QuantifierKind::Generic,
10619                    variable: var_name,
10620                    body,
10621                    island_id: self.current_island,
10622                }));
10623            }
10624        }
10625
10626        // A relative-clause subject feeding a copula ("The scent that sold for
10627        // $75 is the white fragrance.") — stash the restriction, rewritten over
10628        // the subject constant, so the copula's wrap_with_definiteness re-binds
10629        // it instead of silently dropping it. The copula branches all key their
10630        // predicate on Constant(subject.noun), so the fold unifies cleanly.
10631        if let Some((rc_var, rc_pred)) = relative_clause {
10632            if self.check(&TokenType::Is) || self.check(&TokenType::Are)
10633                || self.check(&TokenType::Was) || self.check(&TokenType::Were)
10634            {
10635                let restr =
10636                    self.substitute_variable_with_constant(rc_pred, rc_var, subject.noun)?;
10637                self.pending_subject_restriction = Some((subject.noun, restr));
10638            }
10639        }
10640
10641        if self.check(&TokenType::Is) || self.check(&TokenType::Are)
10642            || self.check(&TokenType::Was) || self.check(&TokenType::Were)
10643        {
10644            let copula_time = if self.check(&TokenType::Was) || self.check(&TokenType::Were) {
10645                Time::Past
10646            } else {
10647                Time::Present
10648            };
10649            self.advance();
10650
10651            // Check for negation: "was not caught", "is not happy"
10652            let is_negated = self.check(&TokenType::Not);
10653            if is_negated {
10654                self.advance(); // consume "not"
10655            }
10656
10657            // Check for temporal adverbs after copula: "is always Y", "is never Y"
10658            let mut copula_temporal = None;
10659            if !is_negated {
10660                if self.check(&TokenType::Never) {
10661                    self.advance();
10662                    copula_temporal = Some(CopulaTemporal::Never);
10663                } else if let TokenType::Adverb(sym) | TokenType::ScopalAdverb(sym) | TokenType::TemporalAdverb(sym) = &self.peek().kind {
10664                    let resolved = self.interner.resolve(*sym).to_string();
10665                    if resolved == "Always" || resolved == "always" {
10666                        self.advance();
10667                        copula_temporal = Some(CopulaTemporal::Always);
10668                    } else if resolved == "Eventually" || resolved == "eventually" {
10669                        self.advance();
10670                        copula_temporal = Some(CopulaTemporal::Eventually);
10671                    }
10672                }
10673            }
10674
10675            // Membership partitive: "Tom is one of the boys." → Boy(Tom).
10676            // Member-of-the-sum is atomic parthood in the Link lattice, which
10677            // for a definite plural is just the noun predicate (§5.4/§6.2).
10678            if matches!(self.peek().kind, TokenType::Cardinal(1)) {
10679                let of_follows = matches!(
10680                    self.tokens.get(self.current + 1).map(|t| &t.kind),
10681                    Some(TokenType::Preposition(p)) if self.interner.resolve(*p).eq_ignore_ascii_case("of")
10682                );
10683                let article_then_noun = matches!(
10684                    self.tokens.get(self.current + 2).map(|t| &t.kind),
10685                    Some(TokenType::Article(_))
10686                ) && matches!(
10687                    self.tokens.get(self.current + 3).map(|t| &t.kind),
10688                    Some(TokenType::Noun(_))
10689                );
10690                if of_follows && article_then_noun {
10691                    self.advance(); // "one"
10692                    self.advance(); // "of"
10693                    self.advance(); // "the"
10694                    let noun_sym = match self.advance().kind {
10695                        TokenType::Noun(s) => s,
10696                        _ => unreachable!("guarded by article_then_noun"),
10697                    };
10698                    let noun_str = self.interner.resolve(noun_sym).to_string();
10699                    let singular = Self::singularize_noun(&noun_str);
10700                    let name_sym = self.interner.intern(&singular);
10701                    let mut result: &'a LogicExpr<'a> =
10702                        self.ctx.exprs.alloc(LogicExpr::Predicate {
10703                            name: name_sym,
10704                            args: self.ctx.terms.alloc_slice([Term::Constant(subject.noun)]),
10705                            world: None,
10706                        });
10707                    if is_negated {
10708                        result = self.ctx.exprs.alloc(LogicExpr::UnaryOp {
10709                            op: TokenType::Not,
10710                            operand: result,
10711                        });
10712                    }
10713                    return self.wrap_with_definiteness_full(&subject, result);
10714                }
10715            }
10716
10717            // A degree adverb directly before a comparative ("is somewhat shorter
10718            // than", "is slightly wider than") must be skipped here so the
10719            // comparative branch fires — otherwise it is read as a bare adjective
10720            // complement and the comparative strands. The degree-adverb class is a
10721            // closed lexical category (lexicon `degree_adverbs`), not a parser list,
10722            // so a real adjective complement ("is happy") is untouched.
10723            if matches!(
10724                self.tokens.get(self.current + 1).map(|t| &t.kind),
10725                Some(TokenType::Comparative(_))
10726            ) && lexicon::is_degree_adverb(
10727                &self.interner.resolve(self.peek().lexeme).to_lowercase(),
10728            ) {
10729                self.advance(); // degree adverb
10730            }
10731
10732            // Check for Number token (measure phrase) before comparative or adjective
10733            // "John is 2 inches taller than Mary" or "The rope is 5 meters long"
10734            if self.check_number() {
10735                let measure = self.parse_measure_phrase()?;
10736
10737                // Check if followed by comparative: "2 inches taller than"
10738                if self.check_comparative() {
10739                    return self.parse_comparative(&subject, copula_time, Some(measure));
10740                }
10741
10742                // Check for dimensional adjective: "5 meters long"
10743                if self.check_content_word() {
10744                    let adj = self.consume_content_word()?;
10745                    let result = self.ctx.exprs.alloc(LogicExpr::Predicate {
10746                        name: adj,
10747                        args: self.ctx.terms.alloc_slice([
10748                            Term::Constant(subject.noun),
10749                            *measure,
10750                        ]),
10751                        world: None,
10752                    });
10753                    return self.wrap_with_definiteness_full(&subject, result);
10754                }
10755
10756                // Bare measure phrase: "The temperature is 98.6 degrees."
10757                // Output: Identity(subject, measure)
10758                if self.check(&TokenType::Period) || self.is_at_end() {
10759                    // In imperative mode, reject "x is 5" - suggest "x equals 5"
10760                    if self.mode == ParserMode::Imperative {
10761                        let variable = self.interner.resolve(subject.noun).to_string();
10762                        let value = if let Term::Value { kind, .. } = measure {
10763                            format!("{:?}", kind)
10764                        } else {
10765                            "value".to_string()
10766                        };
10767                        return Err(ParseError {
10768                            kind: ParseErrorKind::IsValueEquality { variable, value },
10769                            span: self.current_span(),
10770                        });
10771                    }
10772                    let result = self.ctx.exprs.alloc(LogicExpr::Identity {
10773                        left: self.ctx.terms.alloc(Term::Constant(subject.noun)),
10774                        right: measure,
10775                    });
10776                    return self.wrap_with_definiteness_full(&subject, result);
10777                }
10778            }
10779
10780            // Check for equative: "is as tall as Y" → an at-least (≥) comparison.
10781            if self.interner.resolve(self.peek().lexeme).eq_ignore_ascii_case("as") {
10782                return self.parse_equative(&subject);
10783            }
10784
10785            // Check for comparative: "is taller than"
10786            if self.check_comparative() {
10787                return self.parse_comparative(&subject, copula_time, None);
10788            }
10789
10790            // Check for existential "is": "God is." - bare copula followed by period/EOF
10791            if self.check(&TokenType::Period) || self.is_at_end() {
10792                let var = self.next_var_name();
10793                let body = self.ctx.exprs.alloc(LogicExpr::Identity {
10794                    left: self.ctx.terms.alloc(Term::Variable(var)),
10795                    right: self.ctx.terms.alloc(Term::Constant(subject.noun)),
10796                });
10797                return Ok(self.ctx.exprs.alloc(LogicExpr::Quantifier {
10798                    kind: QuantifierKind::Existential,
10799                    variable: var,
10800                    body,
10801                    island_id: self.current_island,
10802                }));
10803            }
10804
10805            // Check for superlative: "is the tallest man"
10806            if self.check(&TokenType::Article(Definiteness::Definite)) {
10807                let saved_pos = self.current;
10808                self.advance();
10809                if self.check_superlative() {
10810                    return self.parse_superlative(&subject);
10811                }
10812                self.current = saved_pos;
10813            }
10814
10815            // Check for predicate NP: "Juliet is the sun" or "John is a man"
10816            if self.check_article() {
10817                // The copula is already consumed, so a verb-word in the predicate
10818                // NP can never be the matrix verb — it is a deverbal noun head
10819                // ("is the orange PACK", "is the Russell Road PROJECT").
10820                let saved_ctx = self.nominal_np_context;
10821                self.nominal_np_context = true;
10822                let predicate_np_result = self.parse_noun_phrase(true);
10823                self.nominal_np_context = saved_ctx;
10824                let predicate_np = predicate_np_result?;
10825                let predicate_noun = predicate_np.noun;
10826
10827                // Enumerated identity: "The four dances are the hustle, the
10828                // lindy, the twist, and the waltz." A genuine comma list (≥3
10829                // members) identifies the plural subject with the group; a bare
10830                // "is a doctor and wealthy" is predication conjunction, not a
10831                // group identity, so a comma is required to enter this path.
10832                // A who/that relative on the FIRST member hides the comma that
10833                // opens the list ("The four winners are the winner WHO won, the
10834                // runner-up, …"). Speculatively consume the member-with-relative and
10835                // commit only if a comma actually follows; otherwise restore exactly
10836                // so the single "X is the Y who Z" copula path below runs unchanged.
10837                if self.check(&TokenType::Who) || self.check(&TokenType::That) {
10838                    let rel_cp = self.checkpoint();
10839                    if let Ok(first) = self.parse_list_member_np(&predicate_np, false) {
10840                        if self.check(&TokenType::Comma) {
10841                            return self.finish_enumerated_identity(&subject, is_negated, first);
10842                        }
10843                    }
10844                    self.restore(rel_cp);
10845                }
10846                if self.check(&TokenType::Comma) {
10847                    let first = self.parse_list_member_np(&predicate_np, false)?;
10848                    return self.finish_enumerated_identity(&subject, is_negated, first);
10849                }
10850
10851                // Phase 41: Event adjective reading
10852                // "beautiful dancer" in event mode → ∃e(Dance(e) ∧ Agent(e, x) ∧ Beautiful(e))
10853                if self.event_reading_mode {
10854                    let noun_str = self.interner.resolve(predicate_noun);
10855                    if let Some(base_verb) = lexicon::lookup_agentive_noun(noun_str) {
10856                        // Check if any adjective can modify events
10857                        let event_adj = predicate_np.adjectives.iter().find(|adj| {
10858                            lexicon::is_event_modifier_adjective(self.interner.resolve(**adj))
10859                        });
10860
10861                        if let Some(&adj_sym) = event_adj {
10862                            // Build event reading: ∃e(Verb(e) ∧ Agent(e, subject) ∧ Adj(e))
10863                            let verb_sym = self.interner.intern(base_verb);
10864                            let event_var = self.get_event_var();
10865
10866                            let verb_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
10867                                name: verb_sym,
10868                                args: self.ctx.terms.alloc_slice([Term::Variable(event_var)]),
10869                                world: None,
10870                            });
10871
10872                            let agent_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
10873                                name: self.interner.intern("Agent"),
10874                                args: self.ctx.terms.alloc_slice([
10875                                    Term::Variable(event_var),
10876                                    Term::Constant(subject.noun),
10877                                ]),
10878                                world: None,
10879                            });
10880
10881                            let adj_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
10882                                name: adj_sym,
10883                                args: self.ctx.terms.alloc_slice([Term::Variable(event_var)]),
10884                                world: None,
10885                            });
10886
10887                            // Conjoin: Verb(e) ∧ Agent(e, x)
10888                            let verb_agent = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
10889                                left: verb_pred,
10890                                op: TokenType::And,
10891                                right: agent_pred,
10892                            });
10893
10894                            // Conjoin: (Verb(e) ∧ Agent(e, x)) ∧ Adj(e)
10895                            let body = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
10896                                left: verb_agent,
10897                                op: TokenType::And,
10898                                right: adj_pred,
10899                            });
10900
10901                            // Wrap in existential: ∃e(...)
10902                            let event_reading = self.ctx.exprs.alloc(LogicExpr::Quantifier {
10903                                kind: QuantifierKind::Existential,
10904                                variable: event_var,
10905                                body,
10906                                island_id: self.current_island,
10907                            });
10908
10909                            return self.wrap_with_definiteness_full(&subject, event_reading);
10910                        }
10911                    }
10912                }
10913
10914                let subject_sort = lexicon::lookup_sort(self.interner.resolve(subject.noun));
10915                let predicate_sort = lexicon::lookup_sort(self.interner.resolve(predicate_noun));
10916
10917                if let (Some(s_sort), Some(p_sort)) = (subject_sort, predicate_sort) {
10918                    if !s_sort.is_compatible_with(p_sort) && !p_sort.is_compatible_with(s_sort) {
10919                        let metaphor: &'a LogicExpr<'a> = self.ctx.exprs.alloc(LogicExpr::Metaphor {
10920                            tenor: self.ctx.terms.alloc(Term::Constant(subject.noun)),
10921                            vehicle: self.ctx.terms.alloc(Term::Constant(predicate_noun)),
10922                        });
10923                        let metaphor = if is_negated {
10924                            &*self.ctx.exprs.alloc(LogicExpr::UnaryOp {
10925                                op: TokenType::Not,
10926                                operand: metaphor,
10927                            })
10928                        } else {
10929                            metaphor
10930                        };
10931                        return self.wrap_with_definiteness_full(&subject, metaphor);
10932                    }
10933                }
10934
10935                // Default: intersective reading for adjectives
10936                // Build Adj1(x) ∧ Adj2(x) ∧ ... ∧ Noun(x)
10937                let mut predicates: Vec<&'a LogicExpr<'a>> = Vec::new();
10938
10939                // Add adjective predicates
10940                for &adj_sym in predicate_np.adjectives {
10941                    let adj_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
10942                        name: adj_sym,
10943                        args: self.ctx.terms.alloc_slice([Term::Constant(subject.noun)]),
10944                        world: None,
10945                    });
10946                    predicates.push(adj_pred);
10947                }
10948
10949                // Add noun predicate
10950                let noun_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
10951                    name: predicate_noun,
10952                    args: self.ctx.terms.alloc_slice([Term::Constant(subject.noun)]),
10953                    world: None,
10954                });
10955                predicates.push(noun_pred);
10956
10957                // A predicate-nominal genitive ("X is the Woodard family's house",
10958                // "X is the red team's trophy") binds the possessor to the subject.
10959                // Routed through the shared possessor lowering so a MODIFIED possessor
10960                // keeps its restrictor (∃p(Team(p) ∧ Red(p) ∧ Possesses(p, X))) instead
10961                // of collapsing to a bare constant that drops "red" — a meaning-loss
10962                // parse the prover can't use.
10963                if let Some(possessor) = predicate_np.possessor {
10964                    let poss_pred =
10965                        self.possessor_predication(possessor, Term::Constant(subject.noun));
10966                    predicates.push(poss_pred);
10967                }
10968
10969                // The predicate nominal's PP restrictors ("X is the gator FROM Lynn",
10970                // "the gator CAUGHT in Lynn", "the doll 80 years OLD") are predicated of
10971                // the subject; dropping them is meaning loss. Each pp is written over the
10972                // _PP_SELF_ placeholder — rebind it to the subject. Only the pps are
10973                // merged here (not the whole block via nominal_predication_with_pps),
10974                // because the metaphor / sort-incompatibility / event-adjective readings
10975                // above are intersective-reading-specific and must stay reachable.
10976                for pp in predicate_np.pps {
10977                    let pp_sub = self.substitute_pp_self_term(pp, Term::Constant(subject.noun));
10978                    predicates.push(pp_sub);
10979                }
10980
10981                // Conjoin all predicates
10982                let mut result = if predicates.len() == 1 {
10983                    predicates[0]
10984                } else {
10985                    let mut combined = predicates[0];
10986                    for pred in &predicates[1..] {
10987                        combined = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
10988                            left: combined,
10989                            op: TokenType::And,
10990                            right: *pred,
10991                        });
10992                    }
10993                    combined
10994                };
10995
10996                // A relative clause on the predicate nominal ("X is the player
10997                // WHO played", "X was the one THAT won 9 games") is predicated of
10998                // the SUBJECT — being that player entails X played. The gap binds
10999                // to the subject constant, and the restriction-VP composes its own
11000                // features (objects, counting NPs). Previously "who"/"that" here
11001                // stranded (TrailingTokens), failing every "X is the Y who/that Z".
11002                let mut result = result;
11003                if let Some(rc) = self.try_attach_relative(Term::Constant(subject.noun))? {
11004                    result = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
11005                        left: result,
11006                        op: TokenType::And,
11007                        right: rc,
11008                    });
11009                }
11010
11011                // Non-parallel copula coordination: "Mary is a doctor and wealthy"
11012                // / "is a doctor and a lawyer" — each conjunct predicates the SAME
11013                // subject (a comma list ≥3 is group identity, handled above). Stop
11014                // at anything that isn't an adjective or article-NP complement so a
11015                // genuine clause coordination ("… and Bob runs") is left intact.
11016                loop {
11017                    if !self.check(&TokenType::And) {
11018                        break;
11019                    }
11020                    let save = self.current;
11021                    self.advance(); // "and"
11022                    // A bare predicate word (adjective, or a noun the lexicon
11023                    // didn't mark adjectival like "wealthy") is a shared predicate;
11024                    // a noun is only taken when a clause boundary / "and" follows,
11025                    // so "… and Bob runs" / "… and cats sleep" stay separate clauses.
11026                    let next_is_boundary = matches!(
11027                        self.tokens.get(self.current + 1).map(|t| &t.kind),
11028                        Some(TokenType::Period) | Some(TokenType::Comma) | Some(TokenType::And)
11029                            | Some(TokenType::EOF) | Some(TokenType::Exclamation) | None
11030                    );
11031                    let conjunct: Option<&'a LogicExpr<'a>> = match self.peek().kind {
11032                        TokenType::Adjective(a) | TokenType::NonIntersectiveAdjective(a) => {
11033                            self.advance();
11034                            Some(self.ctx.exprs.alloc(LogicExpr::Predicate {
11035                                name: a,
11036                                args: self.ctx.terms.alloc_slice([Term::Constant(subject.noun)]),
11037                                world: None,
11038                            }))
11039                        }
11040                        TokenType::Noun(a) if next_is_boundary => {
11041                            self.advance();
11042                            Some(self.ctx.exprs.alloc(LogicExpr::Predicate {
11043                                name: a,
11044                                args: self.ctx.terms.alloc_slice([Term::Constant(subject.noun)]),
11045                                world: None,
11046                            }))
11047                        }
11048                        _ if self.check_article() => match self.parse_noun_phrase(true) {
11049                            Ok(np) => {
11050                                Some(self.nominal_predication(Term::Constant(subject.noun), &np))
11051                            }
11052                            Err(_) => None,
11053                        },
11054                        _ => None,
11055                    };
11056                    match conjunct {
11057                        Some(c) => {
11058                            result = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
11059                                left: result,
11060                                op: TokenType::And,
11061                                right: c,
11062                            });
11063                        }
11064                        None => {
11065                            self.current = save;
11066                            break;
11067                        }
11068                    }
11069                }
11070
11071                // "is NOT a man" negates the whole nominal predication.
11072                let result = if is_negated {
11073                    &*self.ctx.exprs.alloc(LogicExpr::UnaryOp {
11074                        op: TokenType::Not,
11075                        operand: result,
11076                    })
11077                } else {
11078                    result
11079                };
11080
11081                // Use the full NP so the SUBJECT's adjectives / PPs / possessor
11082                // survive — "X is the Y" must not drop them (e.g. "Ray Ricardo's
11083                // stamp is the Yellownose" keeps the genitive, "The red stamp is
11084                // the Yellownose" keeps "red"). Mirrors the enumerated-identity
11085                // (above) and adjective-complement (below) sites.
11086                return self.wrap_with_definiteness_full(&subject, result);
11087            }
11088
11089            // After copula, prefer Adjective over simple-aspect Verb for ambiguous tokens
11090            // "is open" (Adj: state) is standard; "is open" (Verb: habitual) is ungrammatical here
11091            let prefer_adjective = if let TokenType::Ambiguous { primary, alternatives } = &self.peek().kind {
11092                let is_simple_verb = if let TokenType::Verb { aspect, .. } = **primary {
11093                    aspect == Aspect::Simple
11094                } else {
11095                    false
11096                };
11097                let has_adj_alt = alternatives.iter().any(|t| matches!(t, TokenType::Adjective(_)));
11098                is_simple_verb && has_adj_alt
11099            } else {
11100                false
11101            };
11102
11103            if !prefer_adjective && self.check_verb() {
11104                let (verb, _verb_time, verb_aspect, verb_class) = self.consume_verb_with_metadata();
11105
11106                // Stative verbs cannot be progressive
11107                if verb_class.is_stative() && verb_aspect == Aspect::Progressive {
11108                    return Err(ParseError {
11109                        kind: ParseErrorKind::StativeProgressiveConflict,
11110                        span: self.current_span(),
11111                    });
11112                }
11113
11114                // Collect any prepositional phrases before "by" (for ditransitives)
11115                // "given to Mary by John" → goal = Mary, then agent = John
11116                let mut goal_args: Vec<Term<'a>> = Vec::new();
11117                while self.check_to_preposition() {
11118                    self.advance(); // consume "to"
11119                    let goal = self.parse_noun_phrase(true)?;
11120                    goal_args.push(self.noun_phrase_to_term(&goal));
11121                }
11122
11123                // Check for passive: "was loved by John" or "was given to Mary by John"
11124                if self.check_by_preposition() {
11125                    self.advance(); // consume "by"
11126                    let agent = self.parse_noun_phrase(true)?;
11127
11128                    // A DESCRIPTIVE by-agent ("the red team", "the Woodard family")
11129                    // becomes its own restrictor-carrying entity scoping the relation
11130                    // — `∃p(Restrictor(p) ∧ Verb(p, theme))` — instead of collapsing
11131                    // to the bare head. A bare agent keeps the constant form.
11132                    let (agent_term, agent_restr) = self.possessor_entity(&agent);
11133                    let mut args = vec![agent_term, self.noun_phrase_to_term(&subject)];
11134                    args.extend(goal_args);
11135
11136                    let predicate = self.ctx.exprs.alloc(LogicExpr::Predicate {
11137                        name: verb,
11138                        args: self.ctx.terms.alloc_slice(args),
11139                        world: None,
11140                    });
11141                    let predicate = self.wrap_in_possessor_entity(agent_restr, predicate);
11142
11143                    let with_time = if copula_time == Time::Past {
11144                        self.ctx.exprs.alloc(LogicExpr::Temporal {
11145                            operator: TemporalOperator::Past,
11146                            body: predicate,
11147                        })
11148                    } else {
11149                        predicate
11150                    };
11151
11152                    return self.wrap_with_definiteness_full(&subject, with_time);
11153                }
11154
11155                // Agentless passive: "The book was read" → ∃x.Read(x, Book)
11156                // For DEFINITE subjects ("The butler was caught"), use simpler reading
11157                // without existential over implicit agent: Past(catch(butler))
11158                // This makes negation cleaner for theorem proving: ¬Past(catch(butler))
11159                //
11160                // The existential-over-type reading is correct for a bare plural /
11161                // indefinite ("Drugs were approved", "A book was read") — a known
11162                // COMMON noun. A PROPER NAME ("Pluniden was approved") is a specific
11163                // referent, not a type, so it falls through to the constant path
11164                // below (which also carries trailing PP adjuncts and offsets).
11165                let subject_is_common_noun = {
11166                    let n = self.interner.resolve(subject.noun);
11167                    lexicon::lookup_noun_db(n).is_some()
11168                        || (n.ends_with('s')
11169                            && lexicon::lookup_noun_db(&n[..n.len() - 1]).is_some())
11170                };
11171                if copula_time == Time::Past && verb_aspect == Aspect::Simple
11172                    && subject.definiteness != Some(Definiteness::Definite)
11173                    && subject_is_common_noun {
11174                    // Indefinite agentless passive - treat as existential over implicit agent
11175                    let var_name = self.next_var_name();
11176                    let predicate = self.ctx.exprs.alloc(LogicExpr::Predicate {
11177                        name: verb,
11178                        args: self.ctx.terms.alloc_slice([
11179                            Term::Variable(var_name),
11180                            Term::Constant(subject.noun),
11181                        ]),
11182                        world: None,
11183                    });
11184
11185                    let type_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
11186                        name: subject.noun,
11187                        args: self.ctx.terms.alloc_slice([Term::Variable(var_name)]),
11188                        world: None,
11189                    });
11190
11191                    let temporal = self.ctx.exprs.alloc(LogicExpr::Temporal {
11192                        operator: TemporalOperator::Past,
11193                        body: predicate,
11194                    });
11195
11196                    let body = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
11197                        left: type_pred,
11198                        op: TokenType::And,
11199                        right: temporal,
11200                    });
11201
11202                    let result = self.ctx.exprs.alloc(LogicExpr::Quantifier {
11203                        kind: QuantifierKind::Existential,
11204                        variable: var_name,
11205                        body,
11206                        island_id: self.current_island,
11207                    });
11208
11209                    // Apply negation if "was not caught"
11210                    if is_negated {
11211                        return Ok(self.ctx.exprs.alloc(LogicExpr::UnaryOp {
11212                            op: TokenType::Not,
11213                            operand: result,
11214                        }));
11215                    }
11216                    return Ok(result);
11217                }
11218
11219                // Check if verb is an intensional predicate (e.g., "rising", "changing")
11220                // Intensional predicates take intensions, not extensions
11221                let verb_str = self.interner.resolve(verb).to_lowercase();
11222                let subject_term = if lexicon::is_intensional_predicate(&verb_str) {
11223                    Term::Intension(subject.noun)
11224                } else {
11225                    Term::Constant(subject.noun)
11226                };
11227
11228                let mut predicate: &'a LogicExpr<'a> = self.ctx.exprs.alloc(LogicExpr::Predicate {
11229                    name: verb,
11230                    args: self.ctx.terms.alloc_slice([subject_term]),
11231                    world: None,
11232                });
11233
11234                // A PROGRESSIVE transitive ("is paying the rent", "is reading a
11235                // book", "is paying $700") takes a DIRECT OBJECT → Verb(subject,
11236                // object). A passive past-participle has none (its theme IS the
11237                // subject), so gate on Progressive. Not a preposition (that's a PP
11238                // adjunct, handled below) and not "by"/"to" (passive/dative above).
11239                if verb_aspect == Aspect::Progressive
11240                    && !self.check_preposition()
11241                    && (self.check_article() || self.check_number() || self.check_content_word())
11242                {
11243                    let obj_term = if self.check_number() {
11244                        *self.parse_measure_phrase()?
11245                    } else {
11246                        let obj_np = self.parse_noun_phrase(false)?;
11247                        self.noun_phrase_to_term(&obj_np)
11248                    };
11249                    predicate = self.ctx.exprs.alloc(LogicExpr::Predicate {
11250                        name: verb,
11251                        args: self.ctx.terms.alloc_slice([subject_term, obj_term]),
11252                        world: None,
11253                    });
11254                }
11255
11256                // Trailing locative/temporal/material PP adjuncts on the passive ("was
11257                // taken on May 9", "was found in Spain", "was made OF rosewood") —
11258                // predicated of the theme (the subject). "of" after a passive participle
11259                // is the MATERIAL complement (Of(x, rosewood)), not a possessive.
11260                while self.check_preposition() {
11261                    let prep = match self.advance().kind {
11262                        TokenType::Preposition(s) => s,
11263                        _ => break,
11264                    };
11265                    let pp = if self.check_number() {
11266                        let m = self.parse_measure_phrase()?;
11267                        self.ctx.exprs.alloc(LogicExpr::Predicate {
11268                            name: prep,
11269                            args: self.ctx.terms.alloc_slice([subject_term, *m]),
11270                            world: None,
11271                        })
11272                    } else if self.check_content_word()
11273                        || matches!(self.peek().kind, TokenType::Article(_))
11274                    {
11275                        let obj = self.parse_noun_phrase(true)?;
11276                        self.ctx.exprs.alloc(LogicExpr::Predicate {
11277                            name: prep,
11278                            args: self.ctx.terms.alloc_slice([subject_term, Term::Constant(obj.noun)]),
11279                            world: None,
11280                        })
11281                    } else {
11282                        break;
11283                    };
11284                    predicate = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
11285                        left: predicate,
11286                        op: TokenType::And,
11287                        right: pp,
11288                    });
11289                }
11290
11291                // A trailing calendar-unit offset ("was taken 1 month after Y",
11292                // "was found 3 days before Z") relates the theme's temporal
11293                // position to a distinct standard — solver-ready, shared with the
11294                // active VP path.
11295                if let Some(constraint) = self.parse_temporal_offset_constraint(subject_term)? {
11296                    predicate = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
11297                        left: predicate,
11298                        op: TokenType::And,
11299                        right: constraint,
11300                    });
11301                }
11302                // A bare temporal ordering ("was taken sometime before the photo of
11303                // the red panda", "was bought sometime before Faye's pet") relates
11304                // the theme to a distinct standard by time.
11305                else if let Some(constraint) = self.parse_bare_temporal_constraint(subject_term)? {
11306                    predicate = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
11307                        left: predicate,
11308                        op: TokenType::And,
11309                        right: constraint,
11310                    });
11311                }
11312
11313                // Manner adverbs after the participle ("was running quickly")
11314                // modify the event under the aspect operator.
11315                let adverbs = self.collect_adverbs();
11316                let predicate = if adverbs.is_empty() {
11317                    predicate
11318                } else {
11319                    self.ctx.exprs.alloc(LogicExpr::Event {
11320                        predicate,
11321                        adverbs: self.ctx.syms.alloc_slice(adverbs),
11322                    })
11323                };
11324
11325                let with_aspect = if verb_aspect == Aspect::Progressive {
11326                    // Semelfactive + Progressive → Iterative
11327                    let operator = if verb_class == VerbClass::Semelfactive {
11328                        AspectOperator::Iterative
11329                    } else {
11330                        AspectOperator::Progressive
11331                    };
11332                    self.ctx.exprs.alloc(LogicExpr::Aspectual {
11333                        operator,
11334                        body: predicate,
11335                    })
11336                } else {
11337                    predicate
11338                };
11339
11340                let with_time = if copula_time == Time::Past {
11341                    self.ctx.exprs.alloc(LogicExpr::Temporal {
11342                        operator: TemporalOperator::Past,
11343                        body: with_aspect,
11344                    })
11345                } else {
11346                    with_aspect
11347                };
11348
11349                let final_expr = if is_negated {
11350                    self.ctx.exprs.alloc(LogicExpr::UnaryOp {
11351                        op: TokenType::Not,
11352                        operand: with_time,
11353                    })
11354                } else {
11355                    with_time
11356                };
11357
11358                // For a BARE definite subject, return directly without the
11359                // Russellian wrapper: "The butler was caught" → Past(catch(butler)),
11360                // keeping the output simple for theorem proving. But a definite
11361                // subject WITH restrictions (adjectives, PPs like "at 40.5912 N",
11362                // a possessor) must go through the full wrapper so those
11363                // constraints survive — dropping them is meaning loss.
11364                if subject.definiteness == Some(Definiteness::Definite)
11365                    && subject.adjectives.is_empty()
11366                    && subject.pps.is_empty()
11367                    && subject.possessor.is_none()
11368                {
11369                    return Ok(final_expr);
11370                }
11371
11372                return self.wrap_with_definiteness_full(&subject, final_expr);
11373            }
11374
11375            // Handle relative clause with copula: "The book that John read is good."
11376            // The complement uses the same forms as a plain copula — proper-name
11377            // identity, "either A or B" disjunction, and (the)-NP predication —
11378            // predicated of the relative-clause variable, not just a bare adjective.
11379            if let Some((var_name, rel_clause)) = relative_clause {
11380                let var_term = Term::Variable(var_name);
11381
11382                let main_pred: &'a LogicExpr<'a> = if self.check(&TokenType::Either) {
11383                    self.advance();
11384                    let np1 = self.parse_noun_phrase(true)?;
11385                    let p1 = self.nominal_predication_with_pps(var_term, &np1);
11386                    let p1 = self.conjoin_trailing_relative(p1, var_term)?;
11387                    if self.check(&TokenType::Or) {
11388                        self.advance();
11389                        let np2 = self.parse_noun_phrase(true)?;
11390                        let p2 = self.nominal_predication_with_pps(var_term, &np2);
11391                        let p2 = self.conjoin_trailing_relative(p2, var_term)?;
11392                        self.ctx.exprs.alloc(LogicExpr::BinaryOp {
11393                            left: p1,
11394                            op: TokenType::Or,
11395                            right: p2,
11396                        })
11397                    } else {
11398                        p1
11399                    }
11400                } else if matches!(self.peek().kind, TokenType::ProperName(_))
11401                    && matches!(
11402                        self.tokens.get(self.current + 1).map(|t| &t.kind),
11403                        Some(TokenType::Possessive)
11404                    )
11405                {
11406                    // "is Kerry's project" — a possessive NP complement.
11407                    let np = self.parse_noun_phrase(true)?;
11408                    self.nominal_predication_with_pps(var_term, &np)
11409                } else if let TokenType::ProperName(pname) = self.peek().kind {
11410                    self.advance();
11411                    self.ctx.exprs.alloc(LogicExpr::Identity {
11412                        left: self.ctx.terms.alloc(var_term),
11413                        right: self.ctx.terms.alloc(Term::Constant(pname)),
11414                    })
11415                } else if self.check_article() {
11416                    let np = self.parse_noun_phrase(true)?;
11417                    let pred = self.nominal_predication_with_pps(var_term, &np);
11418                    self.conjoin_trailing_relative(pred, var_term)?
11419                } else if self.check_preposition() && !self.check_by_preposition() {
11420                    // PP predicate complement: "… is FROM Australia", "… is ON
11421                    // Holly Street", "… is AT 40 degrees" — predicated of the
11422                    // relativized variable. Mirrors the plain-copula PP path.
11423                    let prep_sym = match self.advance().kind {
11424                        TokenType::Preposition(s) => s,
11425                        _ => unreachable!("guarded by check_preposition()"),
11426                    };
11427                    if self.check_number() {
11428                        let measure = self.parse_measure_phrase()?;
11429                        self.ctx.exprs.alloc(LogicExpr::Predicate {
11430                            name: prep_sym,
11431                            args: self.ctx.terms.alloc_slice([var_term, *measure]),
11432                            world: None,
11433                        })
11434                    } else {
11435                        let obj = self.parse_noun_phrase(true)?;
11436                        self.ctx.exprs.alloc(LogicExpr::Predicate {
11437                            name: prep_sym,
11438                            args: self.ctx.terms.alloc_slice([var_term, Term::Constant(obj.noun)]),
11439                            world: None,
11440                        })
11441                    }
11442                } else {
11443                    let pred_word = self.consume_content_word()?;
11444                    self.ctx.exprs.alloc(LogicExpr::Predicate {
11445                        name: pred_word,
11446                        args: self.ctx.terms.alloc_slice([var_term]),
11447                        world: None,
11448                    })
11449                };
11450                let main_pred = if is_negated {
11451                    &*self.ctx.exprs.alloc(LogicExpr::UnaryOp {
11452                        op: TokenType::Not,
11453                        operand: main_pred,
11454                    })
11455                } else {
11456                    main_pred
11457                };
11458
11459                let type_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
11460                    name: subject.noun,
11461                    args: self.ctx.terms.alloc_slice([Term::Variable(var_name)]),
11462                    world: None,
11463                });
11464
11465                let inner = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
11466                    left: type_pred,
11467                    op: TokenType::And,
11468                    right: rel_clause,
11469                });
11470
11471                let body = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
11472                    left: inner,
11473                    op: TokenType::And,
11474                    right: main_pred,
11475                });
11476
11477                return Ok(self.ctx.exprs.alloc(LogicExpr::Quantifier {
11478                    kind: QuantifierKind::Existential,
11479                    variable: var_name,
11480                    body,
11481                    island_id: self.current_island,
11482                }));
11483            }
11484
11485            // Note: is_negated was already set after copula consumption above
11486
11487            // Possessive predicate with an elided noun: "The scent is Camille's." →
11488            // Possesses(Camille, scent) — the subject BELONGS to the named
11489            // possessor. Detected by a "'s" right after the name with no possessed
11490            // noun (a clause boundary follows); "X is Camille's perfume" (explicit
11491            // noun) falls through to the nominal-predicate paths.
11492            if let TokenType::ProperName(predicate_name) = self.peek().kind {
11493                let next_is_possessive = matches!(
11494                    self.tokens.get(self.current + 1).map(|t| &t.kind),
11495                    Some(TokenType::Possessive)
11496                );
11497                let elided = matches!(
11498                    self.tokens.get(self.current + 2).map(|t| &t.kind),
11499                    Some(TokenType::Period)
11500                        | Some(TokenType::EOF)
11501                        | Some(TokenType::Comma)
11502                        | Some(TokenType::And)
11503                        | Some(TokenType::Or)
11504                        | None
11505                );
11506                if next_is_possessive && elided {
11507                    self.advance(); // proper name
11508                    self.advance(); // "'s"
11509                    let poss = self.ctx.exprs.alloc(LogicExpr::Predicate {
11510                        name: self.interner.intern("Possesses"),
11511                        args: self.ctx.terms.alloc_slice([
11512                            Term::Constant(predicate_name),
11513                            Term::Constant(subject.noun),
11514                        ]),
11515                        world: None,
11516                    });
11517                    let result = if is_negated {
11518                        self.ctx.exprs.alloc(LogicExpr::UnaryOp {
11519                            op: TokenType::Not,
11520                            operand: poss,
11521                        })
11522                    } else {
11523                        poss
11524                    };
11525                    return self.wrap_with_definiteness_full(&subject, result);
11526                }
11527            }
11528
11529            // Possessive NP predicate with an EXPLICIT noun: "The box is Kerry's
11530            // project" → Project(box) ∧ Possesses(Kerry, box). (The elided form "is
11531            // Camille's" is handled above; the bare identity below.)
11532            if matches!(self.peek().kind, TokenType::ProperName(_))
11533                && matches!(
11534                    self.tokens.get(self.current + 1).map(|t| &t.kind),
11535                    Some(TokenType::Possessive)
11536                )
11537            {
11538                let np = self.parse_noun_phrase(true)?;
11539                let pred = self.nominal_predication_with_pps(Term::Constant(subject.noun), &np);
11540                let result = if is_negated {
11541                    self.ctx.exprs.alloc(LogicExpr::UnaryOp {
11542                        op: TokenType::Not,
11543                        operand: pred,
11544                    })
11545                } else {
11546                    pred
11547                };
11548                return self.wrap_with_definiteness_full(&subject, result);
11549            }
11550
11551            // Handle identity: "Clark is Superman" - NP copula ProperName → Identity
11552            // This enables Leibniz's Law: if Clark = Superman and mortal(Clark), then mortal(Superman)
11553            if let TokenType::ProperName(predicate_name) = self.peek().kind {
11554                self.advance(); // consume the proper name
11555                // Absorb subsequent capitalized words into one multi-word proper
11556                // name ("Porcher Place", "Highland Drive") so the identity does
11557                // not strand the second word.
11558                let predicate_name = self.absorb_multiword_proper_name(predicate_name);
11559
11560                // Enumerated identity whose FIRST member is a proper name: "The four
11561                // people are Anthony, the waiter, the musician and the dentist." A
11562                // comma list (≥2 members) identifies the plural subject with the
11563                // group; members may be proper names OR definite descriptions. Mirrors
11564                // the common-noun-first enumerated-identity path above so a leading
11565                // proper name no longer strands the rest of the list.
11566                if self.check(&TokenType::Comma) {
11567                    let mut members: Vec<Term<'a>> = vec![Term::Constant(predicate_name)];
11568                    let mut member_rels: Vec<&'a LogicExpr<'a>> = Vec::new();
11569                    while self.check(&TokenType::Comma) || self.check(&TokenType::And) {
11570                        while self.check(&TokenType::Comma) || self.check(&TokenType::And) {
11571                            self.advance();
11572                        }
11573                        if let TokenType::ProperName(pn) = self.peek().kind {
11574                            self.advance();
11575                            let pn = self.absorb_multiword_proper_name(pn);
11576                            members.push(Term::Constant(pn));
11577                        } else if self.check_article() || self.check_content_word() {
11578                            let item = self.parse_noun_phrase(true)?;
11579                            members.push(Term::Constant(item.noun));
11580                            if let Some(rc) = self.try_attach_relative(Term::Constant(item.noun))? {
11581                                member_rels.push(rc);
11582                            }
11583                        } else {
11584                            break;
11585                        }
11586                    }
11587                    let group = Term::Group(self.ctx.terms.alloc_slice(members));
11588                    let identity: &'a LogicExpr<'a> = self.ctx.exprs.alloc(LogicExpr::Identity {
11589                        left: self.ctx.terms.alloc(Term::Constant(subject.noun)),
11590                        right: self.ctx.terms.alloc(group),
11591                    });
11592                    let mut result = if is_negated {
11593                        &*self.ctx.exprs.alloc(LogicExpr::UnaryOp {
11594                            op: TokenType::Not,
11595                            operand: identity,
11596                        })
11597                    } else {
11598                        identity
11599                    };
11600                    for rc in member_rels {
11601                        result = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
11602                            left: result,
11603                            op: TokenType::And,
11604                            right: rc,
11605                        });
11606                    }
11607                    return self.wrap_with_definiteness_full(&subject, result);
11608                }
11609
11610                let identity = self.ctx.exprs.alloc(LogicExpr::Identity {
11611                    left: self.ctx.terms.alloc(Term::Constant(subject.noun)),
11612                    right: self.ctx.terms.alloc(Term::Constant(predicate_name)),
11613                });
11614                let result = if is_negated {
11615                    self.ctx.exprs.alloc(LogicExpr::UnaryOp {
11616                        op: TokenType::Not,
11617                        operand: identity,
11618                    })
11619                } else {
11620                    identity
11621                };
11622                return self.wrap_with_definiteness_full(&subject, result);
11623            }
11624
11625            // Handle "X is either A or B" — disjunctive predication in copula position.
11626            // "The dancer is either Tara or Bessie" → Tara(dancer) ∨ Bessie(dancer)
11627            if self.check(&TokenType::Either) {
11628                self.advance(); // consume "either"
11629                // Parse one disjunct: an NP optionally restricted by a relative
11630                // clause predicated of the subject ("the player who wore number
11631                // 29" → Player(Frank) ∧ ∃e(Wore(e) ∧ Agent(e, Frank) ∧ …)).
11632                let parse_disjunct = |p: &mut Self| -> ParseResult<&'a LogicExpr<'a>> {
11633                    // The copula is consumed, so a verb-word in the disjunct NP is a
11634                    // deverbal noun head ("either Leroy's PACK or the silver PACK").
11635                    let saved_ctx = p.nominal_np_context;
11636                    p.nominal_np_context = true;
11637                    let np_result = p.parse_noun_phrase(true);
11638                    p.nominal_np_context = saved_ctx;
11639                    let np = np_result?;
11640                    // nominal_predication keeps the disjunct NP's possessor and
11641                    // adjectives ("Elodie's perfume" → Perfume(x) ∧
11642                    // Possesses(Elodie, x)) — a bare Predicate would drop them and
11643                    // collapse "either A's perfume or B's perfume" to Perfume ∨
11644                    // Perfume (a vacuous, prover-useless disjunction).
11645                    let mut pred: &'a LogicExpr<'a> =
11646                        p.nominal_predication_with_pps(Term::Constant(subject.noun), &np);
11647                    if let Some(rel) = p.try_attach_relative(Term::Constant(subject.noun))? {
11648                        pred = p.ctx.exprs.alloc(LogicExpr::BinaryOp {
11649                            left: pred,
11650                            op: TokenType::And,
11651                            right: rel,
11652                        });
11653                    }
11654                    // A reduced relative on the disjunct ("either the mountain FIRST
11655                    // CLIMBED in 1845 or …") restricts the subject — the disjunct NP
11656                    // cannot be a main clause here.
11657                    if let Some(rr) = p.try_consume_reduced_relative(Term::Constant(subject.noun))? {
11658                        pred = p.ctx.exprs.alloc(LogicExpr::BinaryOp {
11659                            left: pred,
11660                            op: TokenType::And,
11661                            right: rr,
11662                        });
11663                    }
11664                    Ok(pred)
11665                };
11666                let pred1 = parse_disjunct(self)?;
11667                if self.check(&TokenType::Or) {
11668                    self.advance(); // consume "or"
11669                    let pred2 = parse_disjunct(self)?;
11670                    let disj = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
11671                        left: pred1,
11672                        op: TokenType::Or,
11673                        right: pred2,
11674                    });
11675                    let with_time = if copula_time == Time::Past {
11676                        self.ctx.exprs.alloc(LogicExpr::Temporal {
11677                            operator: TemporalOperator::Past,
11678                            body: disj,
11679                        })
11680                    } else {
11681                        disj
11682                    };
11683                    let result = if is_negated {
11684                        self.ctx.exprs.alloc(LogicExpr::UnaryOp {
11685                            op: TokenType::Not,
11686                            operand: with_time,
11687                        })
11688                    } else {
11689                        with_time
11690                    };
11691                    // Full NP so the subject's adjectives / PPs / possessor survive
11692                    // ("the class with 15 people is either …" must keep the PP).
11693                    return self.wrap_with_definiteness_full(&subject, result);
11694                }
11695            }
11696
11697            // Handle "X is from Y" / "X is on Y" — PP predicate in copula position.
11698            // Excludes "by" which is passive-agent and handled above.
11699            if self.check_preposition() && !self.check_by_preposition() {
11700                let prep_token = self.advance().clone();
11701                let prep_sym = match prep_token.kind {
11702                    TokenType::Preposition(s) => s,
11703                    _ => unreachable!("guarded by check_preposition()"),
11704                };
11705                // A numeric PP object is a measure ("is at 40.5912 N", "is at 385
11706                // degrees") — keep the amount and its unit/direction; otherwise an
11707                // ordinary NP object.
11708                let base = if self.check_number() {
11709                    let m = self.parse_measure_phrase()?;
11710                    self.ctx.exprs.alloc(LogicExpr::Predicate {
11711                        name: prep_sym,
11712                        args: self.ctx.terms.alloc_slice([Term::Constant(subject.noun), *m]),
11713                        world: None,
11714                    })
11715                } else {
11716                    // A copula + preposition rules out the matrix verb, so a
11717                    // verb-tagged head in the PP object is a deverbal noun ("was
11718                    // after the skydiving TRIP", "is from New Guinea").
11719                    let saved_ctx = self.nominal_np_context;
11720                    self.nominal_np_context = true;
11721                    let pp_obj_result = self.parse_noun_phrase(true);
11722                    self.nominal_np_context = saved_ctx;
11723                    let pp_obj = pp_obj_result?;
11724                    let pred: &'a LogicExpr<'a> = self.ctx.exprs.alloc(LogicExpr::Predicate {
11725                        name: prep_sym,
11726                        args: self.ctx.terms.alloc_slice([
11727                            Term::Constant(subject.noun),
11728                            Term::Constant(pp_obj.noun),
11729                        ]),
11730                        world: None,
11731                    });
11732                    // The PP object's own ADJECTIVES ("is on the BIG table" →
11733                    // On(x, Table) ∧ Big(Table)) and nested PPs survive via the shared
11734                    // helper, dropping them would be meaning loss.
11735                    self.attach_pp_object_modifiers(pred, &pp_obj)
11736                };
11737                // Disjunctive PP complement — "is in Florida OR in Maine" / "is in
11738                // A or B" → In(x,A) ∨ In(x,B): the domain-closure shape a logic-grid
11739                // bijection eliminates over ("not Florida ⇒ Maine"). "or in B"
11740                // repeats the preposition; "or B" reuses the first one.
11741                let mut base: &'a LogicExpr<'a> = base;
11742                while self.check(&TokenType::Or) {
11743                    let cp = self.checkpoint();
11744                    self.advance(); // "or"
11745                    let disj_prep = if self.check_preposition() && !self.check_by_preposition() {
11746                        match self.advance().kind {
11747                            TokenType::Preposition(s) => s,
11748                            _ => prep_sym,
11749                        }
11750                    } else {
11751                        prep_sym
11752                    };
11753                    if !(self.check_content_word()
11754                        || self.check_number()
11755                        || matches!(self.peek().kind, TokenType::Article(_)))
11756                    {
11757                        self.restore(cp);
11758                        break;
11759                    }
11760                    let saved_ctx = self.nominal_np_context;
11761                    self.nominal_np_context = true;
11762                    let disj_obj_res = self.parse_noun_phrase(true);
11763                    self.nominal_np_context = saved_ctx;
11764                    let disj_obj = disj_obj_res?;
11765                    let disj_pred: &'a LogicExpr<'a> = self.ctx.exprs.alloc(LogicExpr::Predicate {
11766                        name: disj_prep,
11767                        args: self
11768                            .ctx
11769                            .terms
11770                            .alloc_slice([Term::Constant(subject.noun), Term::Constant(disj_obj.noun)]),
11771                        world: None,
11772                    });
11773                    let disj_pred = self.attach_pp_object_modifiers(disj_pred, &disj_obj);
11774                    base = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
11775                        left: base,
11776                        op: TokenType::Or,
11777                        right: disj_pred,
11778                    });
11779                }
11780                let with_time = if copula_time == Time::Past {
11781                    self.ctx.exprs.alloc(LogicExpr::Temporal {
11782                        operator: TemporalOperator::Past,
11783                        body: base,
11784                    })
11785                } else {
11786                    base
11787                };
11788                let result = if is_negated {
11789                    self.ctx.exprs.alloc(LogicExpr::UnaryOp {
11790                        op: TokenType::Not,
11791                        operand: with_time,
11792                    })
11793                } else {
11794                    with_time
11795                };
11796                // Use the full NP so a subject genitive ("The Alvarado family's
11797                // house is on Cora Street.") keeps its possession presupposition.
11798                return self.wrap_with_definiteness_full(&subject, result);
11799            }
11800
11801            // Copula complement led by a temporal/ordinal adverb: "was FIRST", "is
11802            // NOW the leader". Shared with parse_predicate's copula path; wrapped here
11803            // with parse_atom's own tense / negation / definiteness.
11804            if let Some(base) =
11805                self.copula_temporal_adverb_complement(Term::Constant(subject.noun))?
11806            {
11807                let with_time = if copula_time == Time::Past {
11808                    self.ctx.exprs.alloc(LogicExpr::Temporal {
11809                        operator: TemporalOperator::Past,
11810                        body: base,
11811                    })
11812                } else {
11813                    base
11814                };
11815                let result = if is_negated {
11816                    self.ctx.exprs.alloc(LogicExpr::UnaryOp {
11817                        op: TokenType::Not,
11818                        operand: with_time,
11819                    })
11820                } else {
11821                    with_time
11822                };
11823                return self.wrap_with_definiteness_full(&subject, result);
11824            }
11825
11826            // Handle "The king is bald" or "Alice is not guilty" - NP copula (not)? ADJ/NOUN
11827            // Also handles bare noun predicates like "Time is money"
11828            let predicate_name = self.consume_content_word()?;
11829
11830            // Coordinated predicate adjectives — "is black and red" → Black(x) ∧
11831            // Red(x). The copula complement must consume the whole coordination;
11832            // otherwise "and red" is mis-parsed as a separate clause. Requires an
11833            // "and" before each extra adjective (a bare juxtaposed pair is left
11834            // alone — it may be a compound shade like "dark green").
11835            {
11836                let mut coord_adjs: Vec<Symbol> = vec![predicate_name];
11837                while self.check(&TokenType::And) {
11838                    let cp = self.checkpoint();
11839                    self.advance(); // "and"
11840                    if let TokenType::Adjective(a) = self.peek().kind {
11841                        // "and ADJ is/are/verb …" — ADJ is the SUBJECT of a new
11842                        // clause ("valid is high AND ready is high"), not a
11843                        // coordinated predicate adjective. Only a clause-final
11844                        // adjective ("is black AND red.") coordinates.
11845                        if matches!(
11846                            self.tokens.get(self.current + 1).map(|t| &t.kind),
11847                            Some(TokenType::Is) | Some(TokenType::Are)
11848                                | Some(TokenType::Was) | Some(TokenType::Were)
11849                                | Some(TokenType::Verb { .. })
11850                        ) {
11851                            self.restore(cp);
11852                            break;
11853                        }
11854                        self.advance();
11855                        coord_adjs.push(a);
11856                    } else {
11857                        self.restore(cp); // "and X" is not adjective coordination
11858                        break;
11859                    }
11860                }
11861                if coord_adjs.len() > 1 {
11862                    let mut conj: &'a LogicExpr<'a> = self.ctx.exprs.alloc(LogicExpr::Predicate {
11863                        name: coord_adjs[0],
11864                        args: self.ctx.terms.alloc_slice([Term::Constant(subject.noun)]),
11865                        world: None,
11866                    });
11867                    for &a in &coord_adjs[1..] {
11868                        let p = self.ctx.exprs.alloc(LogicExpr::Predicate {
11869                            name: a,
11870                            args: self.ctx.terms.alloc_slice([Term::Constant(subject.noun)]),
11871                            world: None,
11872                        });
11873                        conj = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
11874                            left: conj,
11875                            op: TokenType::And,
11876                            right: p,
11877                        });
11878                    }
11879                    let conj = if is_negated {
11880                        self.ctx.exprs.alloc(LogicExpr::UnaryOp { op: TokenType::Not, operand: conj })
11881                    } else {
11882                        conj
11883                    };
11884                    let conj = if copula_time == Time::Past {
11885                        self.ctx.exprs.alloc(LogicExpr::Temporal {
11886                            operator: TemporalOperator::Past,
11887                            body: conj,
11888                        })
11889                    } else {
11890                        conj
11891                    };
11892                    return self.wrap_with_definiteness_full(&subject, conj);
11893                }
11894            }
11895
11896            // A predicate adjective taking a POSTPOSED measure complement — "is
11897            // worth $26 billion", "is worth 5 dollars". The measure-BEFORE-adjective
11898            // form ("is 5 feet tall") is handled earlier; here the amount follows
11899            // the adjective and is its degree argument: Worth(subject, $26 billion).
11900            // A number after a predicate adjective is never a separate clause.
11901            if self.check_number() {
11902                let measure = self.parse_measure_phrase()?;
11903                let pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
11904                    name: predicate_name,
11905                    args: self.ctx.terms.alloc_slice([Term::Constant(subject.noun), *measure]),
11906                    world: None,
11907                });
11908                let pred = if is_negated {
11909                    self.ctx.exprs.alloc(LogicExpr::UnaryOp { op: TokenType::Not, operand: pred })
11910                } else {
11911                    pred
11912                };
11913                let pred = if copula_time == Time::Past {
11914                    self.ctx.exprs.alloc(LogicExpr::Temporal {
11915                        operator: TemporalOperator::Past,
11916                        body: pred,
11917                    })
11918                } else {
11919                    pred
11920                };
11921                return self.wrap_with_definiteness_full(&subject, pred);
11922            }
11923
11924            // Check for sort violation (metaphor detection)
11925            let subject_sort = lexicon::lookup_sort(self.interner.resolve(subject.noun));
11926            let predicate_str = self.interner.resolve(predicate_name);
11927
11928            // Check ontology's predicate sort requirements (for adjectives like "happy")
11929            if let Some(s_sort) = subject_sort {
11930                if !crate::ontology::check_sort_compatibility(predicate_str, s_sort) {
11931                    let metaphor = self.ctx.exprs.alloc(LogicExpr::Metaphor {
11932                        tenor: self.ctx.terms.alloc(Term::Constant(subject.noun)),
11933                        vehicle: self.ctx.terms.alloc(Term::Constant(predicate_name)),
11934                    });
11935                    return self.wrap_with_definiteness_full(&subject, metaphor);
11936                }
11937            }
11938
11939            // Check copular NP predicate sort compatibility (for "Time is money")
11940            let predicate_sort = lexicon::lookup_sort(predicate_str);
11941            if let (Some(s_sort), Some(p_sort)) = (subject_sort, predicate_sort) {
11942                if s_sort != p_sort && !s_sort.is_compatible_with(p_sort) && !p_sort.is_compatible_with(s_sort) {
11943                    let metaphor = self.ctx.exprs.alloc(LogicExpr::Metaphor {
11944                        tenor: self.ctx.terms.alloc(Term::Constant(subject.noun)),
11945                        vehicle: self.ctx.terms.alloc(Term::Constant(predicate_name)),
11946                    });
11947                    return self.wrap_with_definiteness_full(&subject, metaphor);
11948                }
11949            }
11950
11951            // Bare gradable predication (§7.2) relies on a contextual standard: a
11952            // GRADABLE adjective ("tall", "heavy" — lexicon-tagged) is `∃d(Adj(x,d)
11953            // ∧ d > θ_C)`, exceeding a context standard θ_C, not a crisp predicate.
11954            let predicate = if self.pragmatic
11955                && crate::lexicon::is_gradable_adjective(
11956                    &self.interner.resolve(predicate_name).to_lowercase(),
11957                ) {
11958                let d = self.next_var_name();
11959                let theta = self.interner.intern("θ_C");
11960                let adj_d = self.ctx.exprs.alloc(LogicExpr::Predicate {
11961                    name: predicate_name,
11962                    args: self
11963                        .ctx
11964                        .terms
11965                        .alloc_slice([Term::Constant(subject.noun), Term::Variable(d)]),
11966                    world: None,
11967                });
11968                let exceeds = self.ctx.exprs.alloc(LogicExpr::Predicate {
11969                    name: self.interner.intern(">"),
11970                    args: self
11971                        .ctx
11972                        .terms
11973                        .alloc_slice([Term::Variable(d), Term::Constant(theta)]),
11974                    world: None,
11975                });
11976                let mut body = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
11977                    left: adj_d,
11978                    op: TokenType::And,
11979                    right: exceeds,
11980                });
11981                // Vagueness (§8.5): a vague adjective's threshold has a PENUMBRA —
11982                // a borderline region — marked Borderline(θ_C). Sorites-susceptible.
11983                if crate::lexicon::is_vague_adjective(
11984                    &self.interner.resolve(predicate_name).to_lowercase(),
11985                ) {
11986                    let borderline = self.ctx.exprs.alloc(LogicExpr::Predicate {
11987                        name: self.interner.intern("Borderline"),
11988                        args: self.ctx.terms.alloc_slice([Term::Constant(theta)]),
11989                        world: None,
11990                    });
11991                    body = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
11992                        left: body,
11993                        op: TokenType::And,
11994                        right: borderline,
11995                    });
11996                }
11997                self.ctx.exprs.alloc(LogicExpr::Quantifier {
11998                    kind: QuantifierKind::Existential,
11999                    variable: d,
12000                    body,
12001                    island_id: self.current_island,
12002                })
12003            } else {
12004                self.ctx.exprs.alloc(LogicExpr::Predicate {
12005                    name: predicate_name,
12006                    args: self.ctx.terms.alloc_slice([Term::Constant(subject.noun)]),
12007                    world: None,
12008                })
12009            };
12010
12011            // Apply negation if "is not"
12012            let result = if is_negated {
12013                self.ctx.exprs.alloc(LogicExpr::UnaryOp {
12014                    op: TokenType::Not,
12015                    operand: predicate,
12016                })
12017            } else {
12018                predicate
12019            };
12020
12021            // Apply temporal wrapper for "is always Y" → G(Y(X)) or "is never Y" → G(¬Y(X))
12022            let result = match copula_temporal {
12023                Some(CopulaTemporal::Always) => {
12024                    self.ctx.exprs.alloc(LogicExpr::Temporal {
12025                        operator: TemporalOperator::Always,
12026                        body: result,
12027                    })
12028                }
12029                Some(CopulaTemporal::Never) => {
12030                    let negated = self.ctx.exprs.alloc(LogicExpr::UnaryOp {
12031                        op: TokenType::Not,
12032                        operand: predicate,
12033                    });
12034                    self.ctx.exprs.alloc(LogicExpr::Temporal {
12035                        operator: TemporalOperator::Always,
12036                        body: negated,
12037                    })
12038                }
12039                Some(CopulaTemporal::Eventually) => {
12040                    self.ctx.exprs.alloc(LogicExpr::Temporal {
12041                        operator: TemporalOperator::Eventually,
12042                        body: result,
12043                    })
12044                }
12045                None => result,
12046            };
12047
12048            // Use the full NP so the subject's adjectives/PPs are applied to the
12049            // restriction — copular predication must not drop them (e.g. the
12050            // adjective in "A red car is fast." → ∃x((Car(x) ∧ Red(x)) ∧ Fast(x))).
12051            return self.wrap_with_definiteness_full(&subject, result);
12052        }
12053
12054        // "did" as a MAIN verb ("Philip did the dishes", "the diver did 49
12055        // previous jumps") — an Auxiliary(Past) followed by an object NP, not
12056        // do-support. parse_predicate_impl (verb.rs) already handles this; the
12057        // simple-clause path here did not, so proper-name-subject "did X"
12058        // stranded. (Object NPs only; "did not"/"did run" stay auxiliary.)
12059        if self.check_auxiliary_as_main_verb() {
12060            let subject_term = self.noun_phrase_to_term(&subject);
12061            let result = self.parse_do_as_main_verb(subject_term)?;
12062            return self.wrap_with_definiteness_full(&subject, result);
12063        }
12064
12065        // Handle auxiliary: set pending_time, handle negation
12066        // BUT: "did it" should be parsed as verb "do" with object "it"
12067        // We lookahead to check if this is truly an auxiliary usage
12068        if self.check_auxiliary() && self.is_true_auxiliary_usage() {
12069            let aux_time = if let TokenType::Auxiliary(time) = self.advance().kind {
12070                time
12071            } else {
12072                Time::None
12073            };
12074            self.pending_time = Some(aux_time);
12075
12076            // Handle negation: "John did not see dogs"
12077            if self.match_token(&[TokenType::Not]) {
12078                self.negative_depth += 1;
12079
12080                // Skip "ever" if present: "John did not ever run"
12081                if self.check(&TokenType::Ever) {
12082                    self.advance();
12083                }
12084
12085                // Presupposition trigger under negation: the presupposition PROJECTS
12086                // through the ¬ (Van der Sandt). "Mary does not regret lying." →
12087                // ¬Regret(Mary) [Presup: P(Lie(Mary))] — the lying survives.
12088                if self.check_presup_trigger()
12089                    && !self.is_followed_by_np_object()
12090                    && self.is_followed_by_gerund()
12091                {
12092                    let presup_kind = self.consume_presup_trigger();
12093                    self.negative_depth -= 1; // parse_presupposition handles the negation
12094                    return self.parse_presupposition(&subject, presup_kind, true);
12095                }
12096
12097                // A bare verb the lexicon ALSO lists as a performative ("didn't ORDER")
12098                // is the clause's main verb after do-support, not a speech act — re-tag
12099                // it so check_verb consumes it (tense comes from the "did" auxiliary).
12100                if let TokenType::Performative(_) = self.peek().kind {
12101                    let lemma = self
12102                        .interner
12103                        .intern(&self.interner.resolve(self.peek().lexeme).to_lowercase());
12104                    self.tokens[self.current].kind = TokenType::Verb {
12105                        lemma,
12106                        time: Time::None,
12107                        aspect: Aspect::Simple,
12108                        class: crate::lexicon::VerbClass::Activity,
12109                    };
12110                }
12111                // Check for verb or "do" (TokenType::Do is separate from TokenType::Verb)
12112                if self.check_verb() || self.check(&TokenType::Do) {
12113                    let (verb, verb_time, verb_aspect, verb_class) =
12114                        if self.check(&TokenType::Do) {
12115                            self.advance(); // consume "do"
12116                            (
12117                                self.interner.intern("Do"),
12118                                Time::None,
12119                                Aspect::Simple,
12120                                crate::lexicon::VerbClass::Activity,
12121                            )
12122                        } else {
12123                            self.consume_verb_with_metadata()
12124                        };
12125                    let subject_term = self.noun_phrase_to_term(&subject);
12126
12127                    // Check for NPI object first: "John did not see anything"
12128                    if self.check_npi_object() {
12129                        let npi_token = self.advance().kind.clone();
12130                        let obj_var = self.next_var_name();
12131
12132                        let restriction_name = match npi_token {
12133                            TokenType::Anything => "Thing",
12134                            TokenType::Anyone => "Person",
12135                            _ => "Thing",
12136                        };
12137
12138                        let restriction_sym = self.interner.intern(restriction_name);
12139                        let obj_restriction = self.ctx.exprs.alloc(LogicExpr::Predicate {
12140                            name: restriction_sym,
12141                            args: self.ctx.terms.alloc_slice([Term::Variable(obj_var)]),
12142                            world: None,
12143                        });
12144
12145                        let verb_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
12146                            name: verb,
12147                            args: self.ctx.terms.alloc_slice([subject_term.clone(), Term::Variable(obj_var)]),
12148                            world: None,
12149                        });
12150
12151                        let body = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
12152                            left: obj_restriction,
12153                            op: TokenType::And,
12154                            right: verb_pred,
12155                        });
12156
12157                        let quantified = self.ctx.exprs.alloc(LogicExpr::Quantifier {
12158                            kind: QuantifierKind::Existential,
12159                            variable: obj_var,
12160                            body,
12161                            island_id: self.current_island,
12162                        });
12163
12164                        let effective_time = self.pending_time.take().unwrap_or(Time::None);
12165                        let with_time = match effective_time {
12166                            Time::Past => self.ctx.exprs.alloc(LogicExpr::Temporal {
12167                                operator: TemporalOperator::Past,
12168                                body: quantified,
12169                            }),
12170                            Time::Future => self.ctx.exprs.alloc(LogicExpr::Temporal {
12171                                operator: TemporalOperator::Future,
12172                                body: quantified,
12173                            }),
12174                            _ => quantified,
12175                        };
12176
12177                        self.negative_depth -= 1;
12178                        return Ok(self.ctx.exprs.alloc(LogicExpr::UnaryOp {
12179                            op: TokenType::Not,
12180                            operand: with_time,
12181                        }));
12182                    }
12183
12184                    // Check for quantifier object: "John did not see any dogs"
12185                    if self.check_quantifier() {
12186                        let quantifier_token = self.advance().kind.clone();
12187                        let object_np = self.parse_noun_phrase(false)?;
12188                        let obj_var = self.next_var_name();
12189
12190                        let obj_restriction = self.ctx.exprs.alloc(LogicExpr::Predicate {
12191                            name: object_np.noun,
12192                            args: self.ctx.terms.alloc_slice([Term::Variable(obj_var)]),
12193                            world: None,
12194                        });
12195
12196                        let verb_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
12197                            name: verb,
12198                            args: self.ctx.terms.alloc_slice([subject_term.clone(), Term::Variable(obj_var)]),
12199                            world: None,
12200                        });
12201
12202                        let (kind, body) = match quantifier_token {
12203                            TokenType::Any => {
12204                                if self.is_negative_context() {
12205                                    (
12206                                        QuantifierKind::Existential,
12207                                        self.ctx.exprs.alloc(LogicExpr::BinaryOp {
12208                                            left: obj_restriction,
12209                                            op: TokenType::And,
12210                                            right: verb_pred,
12211                                        }),
12212                                    )
12213                                } else {
12214                                    (
12215                                        QuantifierKind::Universal,
12216                                        self.ctx.exprs.alloc(LogicExpr::BinaryOp {
12217                                            left: obj_restriction,
12218                                            op: TokenType::Implies,
12219                                            right: verb_pred,
12220                                        }),
12221                                    )
12222                                }
12223                            }
12224                            TokenType::Some => (
12225                                QuantifierKind::Existential,
12226                                self.ctx.exprs.alloc(LogicExpr::BinaryOp {
12227                                    left: obj_restriction,
12228                                    op: TokenType::And,
12229                                    right: verb_pred,
12230                                }),
12231                            ),
12232                            TokenType::All => (
12233                                QuantifierKind::Universal,
12234                                self.ctx.exprs.alloc(LogicExpr::BinaryOp {
12235                                    left: obj_restriction,
12236                                    op: TokenType::Implies,
12237                                    right: verb_pred,
12238                                }),
12239                            ),
12240                            _ => (
12241                                QuantifierKind::Existential,
12242                                self.ctx.exprs.alloc(LogicExpr::BinaryOp {
12243                                    left: obj_restriction,
12244                                    op: TokenType::And,
12245                                    right: verb_pred,
12246                                }),
12247                            ),
12248                        };
12249
12250                        let quantified = self.ctx.exprs.alloc(LogicExpr::Quantifier {
12251                            kind,
12252                            variable: obj_var,
12253                            body,
12254                            island_id: self.current_island,
12255                        });
12256
12257                        let effective_time = self.pending_time.take().unwrap_or(Time::None);
12258                        let with_time = match effective_time {
12259                            Time::Past => self.ctx.exprs.alloc(LogicExpr::Temporal {
12260                                operator: TemporalOperator::Past,
12261                                body: quantified,
12262                            }),
12263                            Time::Future => self.ctx.exprs.alloc(LogicExpr::Temporal {
12264                                operator: TemporalOperator::Future,
12265                                body: quantified,
12266                            }),
12267                            _ => quantified,
12268                        };
12269
12270                        self.negative_depth -= 1;
12271                        return Ok(self.ctx.exprs.alloc(LogicExpr::UnaryOp {
12272                            op: TokenType::Not,
12273                            operand: with_time,
12274                        }));
12275                    }
12276
12277                    // A pronoun object ("did not do it") stays LITERAL, and the
12278                    // negation is returned UNWRAPPED. Resolving the pronoun or
12279                    // wrapping the definite subject in a uniqueness clause would
12280                    // desync a modus-tollens goal from the conditional antecedent it
12281                    // must refute ("if the butler did it …" keeps Theme(e, it)).
12282                    if self.check_pronoun() {
12283                        let pronoun_token = self.advance();
12284                        let pronoun_sym = pronoun_token.lexeme;
12285                        let roles = vec![
12286                            (ThematicRole::Agent, subject_term.clone()),
12287                            (ThematicRole::Theme, Term::Constant(pronoun_sym)),
12288                        ];
12289                        let effective_time = self.pending_time.take().unwrap_or(Time::None);
12290                        let mut modifiers: Vec<Symbol> = vec![];
12291                        match effective_time {
12292                            Time::Past => modifiers.push(self.interner.intern("Past")),
12293                            Time::Future => modifiers.push(self.interner.intern("Future")),
12294                            _ => {}
12295                        }
12296                        let event_var = self.get_event_var();
12297                        let suppress_existential = self.drs.in_conditional_antecedent();
12298                        let neo_event = self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(
12299                            NeoEventData {
12300                                event_var,
12301                                verb,
12302                                roles: self.ctx.roles.alloc_slice(roles),
12303                                modifiers: self.ctx.syms.alloc_slice(modifiers),
12304                                suppress_existential,
12305                                world: None,
12306                            },
12307                        )));
12308                        self.negative_depth -= 1;
12309                        return Ok(self.ctx.exprs.alloc(LogicExpr::UnaryOp {
12310                            op: TokenType::Not,
12311                            operand: neo_event,
12312                        }));
12313                    }
12314
12315                    // The remaining complement forms (NP / measure / PP / aspect)
12316                    // fold through the shared finite-verb builder so "did not VERB …"
12317                    // folds exactly like the positive path; the auxiliary tense rides
12318                    // in via pending_time. The result is returned UNWRAPPED — a
12319                    // negated definite subject is not given a uniqueness clause here,
12320                    // keeping proof goals structurally clean.
12321                    let vp = self.build_verb_vp(
12322                        subject.noun,
12323                        subject_term,
12324                        false,
12325                        verb,
12326                        verb_time,
12327                        verb_aspect,
12328                        verb_class,
12329                    )?;
12330
12331                    self.negative_depth -= 1;
12332                    return Ok(self.ctx.exprs.alloc(LogicExpr::UnaryOp {
12333                        op: TokenType::Not,
12334                        operand: vp,
12335                    }));
12336                }
12337
12338                self.negative_depth -= 1;
12339            }
12340            // Non-negated auxiliary: pending_time is set, fall through to normal verb handling
12341        }
12342
12343        // Check for presupposition triggers: "stopped", "started", "regrets", "knows"
12344        // Factive verbs like "know" only trigger presupposition with clausal complements
12345        // "John knows that..." → presupposition, "John knows Mary" → regular verb
12346        // Only trigger presupposition if followed by a gerund (e.g., "stopped smoking")
12347        // "John stopped." alone should parse as intransitive verb, not presupposition
12348        if self.check_presup_trigger() && !self.is_followed_by_np_object() && self.is_followed_by_gerund() {
12349            let presup_kind = self.consume_presup_trigger();
12350            return self.parse_presupposition(&subject, presup_kind, false);
12351        }
12352
12353        // Handle bare plurals: "Birds fly." → Gen x. Bird(x) → Fly(x)
12354        let noun_str = self.interner.resolve(subject.noun);
12355        // Only a KNOWN plural common noun is a generic restrictor — an
12356        // unknown s-final proper name ("Opus flies.") is not a bare plural.
12357        let is_known_plural = {
12358            let lower = noun_str.to_lowercase();
12359            Self::is_plural_noun(noun_str)
12360                && (crate::lexicon::is_common_noun(&lower)
12361                    || matches!(
12362                        crate::lexicon::analyze_word(&lower),
12363                        Some(crate::lexicon::WordAnalysis::Noun(_))
12364                    ))
12365        };
12366        // GEN also covers do-negated characterizing sentences
12367        // ("Penguins do not fly."); the copular kind-predication case
12368        // ("Penguins are birds.") is intercepted at the copular dispatch.
12369        let generic_do_not = matches!(self.peek().kind, TokenType::Does | TokenType::Do)
12370            && matches!(
12371                self.tokens.get(self.current + 1).map(|t| &t.kind),
12372                Some(TokenType::Not)
12373            );
12374        let is_bare_plural = subject.definiteness.is_none()
12375            && subject.possessor.is_none()
12376            && is_known_plural
12377            && (self.check_verb() || generic_do_not);
12378
12379        if is_bare_plural && generic_do_not {
12380            // "Penguins do not fly." → Gen x(Penguin(x) → ¬∃e(Fly(e) ∧ Agent(e, x))).
12381            // The nucleus is a real event so the generic matches what an
12382            // instance sentence ("Opus does not fly.") parses to.
12383            self.advance(); // do/does
12384            self.advance(); // not
12385            let var_name = self.next_var_name();
12386            let (verb, _t, _a, _) = self.consume_verb_with_metadata();
12387            let type_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
12388                name: subject.noun,
12389                args: self.ctx.terms.alloc_slice([Term::Variable(var_name)]),
12390                world: None,
12391            });
12392            let event_var = self.get_event_var();
12393            let verb_pred = self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
12394                event_var,
12395                verb,
12396                roles: self
12397                    .ctx
12398                    .roles
12399                    .alloc_slice(vec![(ThematicRole::Agent, Term::Variable(var_name))]),
12400                modifiers: self.ctx.syms.alloc_slice(vec![]),
12401                suppress_existential: false,
12402                world: None,
12403            })));
12404            let negated = self.ctx.exprs.alloc(LogicExpr::UnaryOp {
12405                op: TokenType::Not,
12406                operand: verb_pred,
12407            });
12408            let body = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
12409                left: type_pred,
12410                op: TokenType::Implies,
12411                right: negated,
12412            });
12413            return Ok(self.ctx.exprs.alloc(LogicExpr::Quantifier {
12414                kind: QuantifierKind::Generic,
12415                variable: var_name,
12416                body,
12417                island_id: self.current_island,
12418            }));
12419        }
12420
12421        if is_bare_plural {
12422            let var_name = self.next_var_name();
12423            let (verb, verb_time, verb_aspect, _) = self.consume_verb_with_metadata();
12424
12425            let type_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
12426                name: subject.noun,
12427                args: self.ctx.terms.alloc_slice([Term::Variable(var_name)]),
12428                world: None,
12429            });
12430
12431            // The generic nucleus is a real event — the same shape an
12432            // instance sentence parses to, so GEN defaults are derivable for
12433            // individuals ("Birds fly." + Bird(Tweety) ⊨~ Tweety flies).
12434            let mut roles = vec![(ThematicRole::Agent, Term::Variable(var_name))];
12435            if self.check_content_word() {
12436                let object = self.parse_noun_phrase(false)?;
12437                roles.push((ThematicRole::Theme, self.noun_phrase_to_term(&object)));
12438            }
12439
12440            // A trailing manner adverb attaches to the generic nucleus's event
12441            // ("Dogs bark loudly." → Gen x(Dog(x) → ∃e(Bark(e) ∧ … ∧ Loudly(e)))),
12442            // the same event modifier an instance sentence produces.
12443            let manner_adverbs = self.collect_adverbs();
12444
12445            let event_var = self.get_event_var();
12446            let verb_pred = self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
12447                event_var,
12448                verb,
12449                roles: self.ctx.roles.alloc_slice(roles),
12450                modifiers: self.ctx.syms.alloc_slice(manner_adverbs),
12451                suppress_existential: false,
12452                world: None,
12453            })));
12454
12455            let effective_time = self.pending_time.take().unwrap_or(verb_time);
12456            let with_time = match effective_time {
12457                Time::Past => self.ctx.exprs.alloc(LogicExpr::Temporal {
12458                    operator: TemporalOperator::Past,
12459                    body: verb_pred,
12460                }),
12461                Time::Future => self.ctx.exprs.alloc(LogicExpr::Temporal {
12462                    operator: TemporalOperator::Future,
12463                    body: verb_pred,
12464                }),
12465                _ => verb_pred,
12466            };
12467
12468            let with_aspect = if verb_aspect == Aspect::Progressive {
12469                self.ctx.exprs.alloc(LogicExpr::Aspectual {
12470                    operator: AspectOperator::Progressive,
12471                    body: with_time,
12472                })
12473            } else {
12474                with_time
12475            };
12476
12477            let body = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
12478                left: type_pred,
12479                op: TokenType::Implies,
12480                right: with_aspect,
12481            });
12482
12483            return Ok(self.ctx.exprs.alloc(LogicExpr::Quantifier {
12484                kind: QuantifierKind::Generic,
12485                variable: var_name,
12486                body,
12487                island_id: self.current_island,
12488            }));
12489        }
12490
12491        // Handle do-support: "John does not exist" or "John does run"
12492        if self.check(&TokenType::Does) || self.check(&TokenType::Do) {
12493            self.advance(); // consume does/do
12494            let is_negated = self.match_token(&[TokenType::Not]);
12495
12496            // Presupposition trigger under do-support negation projects (Van der
12497            // Sandt): "Mary does not regret lying." → ¬Regret(Mary) [Presup: Lie(Mary)].
12498            if self.check_presup_trigger()
12499                && !self.is_followed_by_np_object()
12500                && self.is_followed_by_gerund()
12501            {
12502                let presup_kind = self.consume_presup_trigger();
12503                return self.parse_presupposition(&subject, presup_kind, is_negated);
12504            }
12505
12506            if self.check_verb() {
12507                let (verb, verb_time, verb_aspect, verb_class) =
12508                    self.consume_verb_with_metadata();
12509                let verb_lemma = self.interner.resolve(verb).to_lowercase();
12510
12511                // Check for embedded wh-clause with negation: "I don't know who"
12512                if self.check_wh_word() {
12513                    let wh_token = self.advance().kind.clone();
12514                    let is_who = matches!(wh_token, TokenType::Who);
12515                    let is_what = matches!(wh_token, TokenType::What);
12516
12517                    let is_sluicing = self.is_at_end() ||
12518                        self.check(&TokenType::Period) ||
12519                        self.check(&TokenType::Comma);
12520
12521                    if is_sluicing {
12522                        if let Some(template) = self.last_event_template.clone() {
12523                            let wh_var = self.next_var_name();
12524                            let subject_term = self.noun_phrase_to_term(&subject);
12525
12526                            let roles: Vec<_> = if is_who {
12527                                std::iter::once((ThematicRole::Agent, Term::Variable(wh_var)))
12528                                    .chain(template.non_agent_roles.iter().cloned())
12529                                    .collect()
12530                            } else if is_what {
12531                                vec![
12532                                    (ThematicRole::Agent, subject_term.clone()),
12533                                    (ThematicRole::Theme, Term::Variable(wh_var)),
12534                                ]
12535                            } else {
12536                                std::iter::once((ThematicRole::Agent, Term::Variable(wh_var)))
12537                                    .chain(template.non_agent_roles.iter().cloned())
12538                                    .collect()
12539                            };
12540
12541                            let event_var = self.get_event_var();
12542                            let suppress_existential = self.drs.in_conditional_antecedent();
12543                            if suppress_existential {
12544                                let event_class = self.interner.intern("Event");
12545                                self.drs.introduce_referent(event_var, event_class, Gender::Neuter, Number::Singular);
12546                            }
12547                            let reconstructed = self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
12548                                event_var,
12549                                verb: template.verb,
12550                                roles: self.ctx.roles.alloc_slice(roles),
12551                                modifiers: self.ctx.syms.alloc_slice(template.modifiers.clone()),
12552                                suppress_existential,
12553                                world: None,
12554                            })));
12555
12556                            let question = self.ctx.exprs.alloc(LogicExpr::Question {
12557                                wh_variable: wh_var,
12558                                body: reconstructed,
12559                            });
12560
12561                            let know_event_var = self.get_event_var();
12562                            let suppress_existential2 = self.drs.in_conditional_antecedent();
12563                            if suppress_existential2 {
12564                                let event_class = self.interner.intern("Event");
12565                                self.drs.introduce_referent(know_event_var, event_class, Gender::Neuter, Number::Singular);
12566                            }
12567                            let know_event = self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
12568                                event_var: know_event_var,
12569                                verb,
12570                                roles: self.ctx.roles.alloc_slice(vec![
12571                                    (ThematicRole::Agent, subject_term),
12572                                    (ThematicRole::Theme, Term::Proposition(question)),
12573                                ]),
12574                                modifiers: self.ctx.syms.alloc_slice(vec![]),
12575                                suppress_existential: suppress_existential2,
12576                                world: None,
12577                            })));
12578
12579                            let result = if is_negated {
12580                                self.ctx.exprs.alloc(LogicExpr::UnaryOp {
12581                                    op: TokenType::Not,
12582                                    operand: know_event,
12583                                })
12584                            } else {
12585                                know_event
12586                            };
12587
12588                            return self.wrap_with_definiteness_full(&subject, result);
12589                        }
12590                    }
12591                }
12592
12593                // Special handling for "exist" with negation
12594                if verb_lemma == "exist" && is_negated {
12595                    // "The King of France does not exist" -> ¬∃x(KingOfFrance(x))
12596                    let var_name = self.next_var_name();
12597                    let restriction = self.ctx.exprs.alloc(LogicExpr::Predicate {
12598                        name: subject.noun,
12599                        args: self.ctx.terms.alloc_slice([Term::Variable(var_name)]),
12600                        world: None,
12601                    });
12602                    let exists = self.ctx.exprs.alloc(LogicExpr::Quantifier {
12603                        kind: QuantifierKind::Existential,
12604                        variable: var_name,
12605                        body: restriction,
12606                        island_id: self.current_island,
12607                    });
12608                    return Ok(self.ctx.exprs.alloc(LogicExpr::UnaryOp {
12609                        op: TokenType::Not,
12610                        operand: exists,
12611                    }));
12612                }
12613
12614                // Regular do-support: "John does run" or "John does not run"
12615                // Also handles transitive: "John does not shave any man"
12616                let subject_term = self.noun_phrase_to_term(&subject);
12617                let modifiers: Vec<Symbol> = vec![];
12618
12619                // Check for reflexive object
12620                if self.check(&TokenType::Reflexive) {
12621                    self.advance();
12622                    let roles = vec![
12623                        (ThematicRole::Agent, subject_term.clone()),
12624                        (ThematicRole::Theme, subject_term),
12625                    ];
12626                    let event_var = self.get_event_var();
12627                    let suppress_existential = self.drs.in_conditional_antecedent();
12628                    if suppress_existential {
12629                        let event_class = self.interner.intern("Event");
12630                        self.drs.introduce_referent(event_var, event_class, Gender::Neuter, Number::Singular);
12631                    }
12632                    let neo_event = self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
12633                        event_var,
12634                        verb,
12635                        roles: self.ctx.roles.alloc_slice(roles),
12636                        modifiers: self.ctx.syms.alloc_slice(modifiers),
12637                        suppress_existential,
12638                        world: None,
12639                    })));
12640
12641                    let result = if is_negated {
12642                        self.ctx.exprs.alloc(LogicExpr::UnaryOp {
12643                            op: TokenType::Not,
12644                            operand: neo_event,
12645                        })
12646                    } else {
12647                        neo_event
12648                    };
12649                    return self.wrap_with_definiteness_full(&subject, result);
12650                }
12651
12652                // Check for quantified object: "does not shave any man"
12653                if self.check_npi_quantifier() || self.check_quantifier() || self.check_article() {
12654                    let (obj_quantifier, was_definite_article) = if self.check_npi_quantifier() {
12655                        // "any" is an NPI quantifier in negative contexts
12656                        let tok = self.advance().kind.clone();
12657                        (Some(tok), false)
12658                    } else if self.check_quantifier() {
12659                        (Some(self.advance().kind.clone()), false)
12660                    } else {
12661                        let art = self.advance().kind.clone();
12662                        if let TokenType::Article(def) = art {
12663                            if def == Definiteness::Indefinite {
12664                                (Some(TokenType::Some), false)
12665                            } else {
12666                                (None, true)
12667                            }
12668                        } else {
12669                            (None, false)
12670                        }
12671                    };
12672
12673                    let object_np = self.parse_noun_phrase(false)?;
12674                    let obj_var = self.next_var_name();
12675
12676                    let mut type_pred: &'a LogicExpr<'a> = self.ctx.exprs.alloc(LogicExpr::Predicate {
12677                        name: object_np.noun,
12678                        args: self.ctx.terms.alloc_slice([Term::Variable(obj_var)]),
12679                        world: None,
12680                    });
12681                    // A quantified object's adjectives and PP restrictors ("has a
12682                    // RED book", "has a maximum range OF 475 ft") are part of the
12683                    // object's description — dropping them is a meaning-loss parse.
12684                    for &adj in object_np.adjectives {
12685                        let adj_pred = self.adjective_restriction(adj, obj_var, object_np.noun);
12686                        type_pred = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
12687                            left: type_pred,
12688                            op: TokenType::And,
12689                            right: adj_pred,
12690                        });
12691                    }
12692                    for pp in object_np.pps {
12693                        let pp_sub = self.substitute_pp_placeholder(pp, obj_var);
12694                        type_pred = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
12695                            left: type_pred,
12696                            op: TokenType::And,
12697                            right: pp_sub,
12698                        });
12699                    }
12700
12701                    // Check for relative clause on object
12702                    let obj_restriction = if self.check(&TokenType::That) || self.check(&TokenType::Who) {
12703                        self.advance();
12704                        let rel_clause = self.parse_relative_clause(obj_var)?;
12705                        self.ctx.exprs.alloc(LogicExpr::BinaryOp {
12706                            left: type_pred,
12707                            op: TokenType::And,
12708                            right: rel_clause,
12709                        })
12710                    } else {
12711                        type_pred
12712                    };
12713
12714                    let event_var = self.get_event_var();
12715                    let suppress_existential = self.drs.in_conditional_antecedent();
12716                    if suppress_existential {
12717                        let event_class = self.interner.intern("Event");
12718                        self.drs.introduce_referent(event_var, event_class, Gender::Neuter, Number::Singular);
12719                    }
12720
12721                    let roles = vec![
12722                        (ThematicRole::Agent, subject_term),
12723                        (ThematicRole::Theme, Term::Variable(obj_var)),
12724                    ];
12725
12726                    let neo_event = self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
12727                        event_var,
12728                        verb,
12729                        roles: self.ctx.roles.alloc_slice(roles),
12730                        modifiers: self.ctx.syms.alloc_slice(modifiers),
12731                        suppress_existential,
12732                        world: None,
12733                    })));
12734
12735                    // Build quantified expression
12736                    // For "does not shave any man" with negation + any:
12737                    // ¬∃x(Man(x) ∧ Shave(barber, x)) = "there is no man the barber shaves"
12738                    let quantifier_kind = match &obj_quantifier {
12739                        Some(TokenType::Any) if is_negated => QuantifierKind::Existential,
12740                        Some(TokenType::All) => QuantifierKind::Universal,
12741                        Some(TokenType::No) => QuantifierKind::Universal,
12742                        _ => QuantifierKind::Existential,
12743                    };
12744
12745                    let obj_body = match &obj_quantifier {
12746                        Some(TokenType::All) => self.ctx.exprs.alloc(LogicExpr::BinaryOp {
12747                            left: obj_restriction,
12748                            op: TokenType::Implies,
12749                            right: neo_event,
12750                        }),
12751                        Some(TokenType::No) => {
12752                            let neg = self.ctx.exprs.alloc(LogicExpr::UnaryOp {
12753                                op: TokenType::Not,
12754                                operand: neo_event,
12755                            });
12756                            self.ctx.exprs.alloc(LogicExpr::BinaryOp {
12757                                left: obj_restriction,
12758                                op: TokenType::Implies,
12759                                right: neg,
12760                            })
12761                        }
12762                        _ => self.ctx.exprs.alloc(LogicExpr::BinaryOp {
12763                            left: obj_restriction,
12764                            op: TokenType::And,
12765                            right: neo_event,
12766                        }),
12767                    };
12768
12769                    let obj_quantified = self.ctx.exprs.alloc(LogicExpr::Quantifier {
12770                        kind: quantifier_kind,
12771                        variable: obj_var,
12772                        body: obj_body,
12773                        island_id: self.current_island,
12774                    });
12775
12776                    // Apply negation at sentence level for "does not ... any"
12777                    let result = if is_negated && matches!(obj_quantifier, Some(TokenType::Any)) {
12778                        self.ctx.exprs.alloc(LogicExpr::UnaryOp {
12779                            op: TokenType::Not,
12780                            operand: obj_quantified,
12781                        })
12782                    } else if is_negated {
12783                        // For other quantifiers, negate the whole thing
12784                        self.ctx.exprs.alloc(LogicExpr::UnaryOp {
12785                            op: TokenType::Not,
12786                            operand: obj_quantified,
12787                        })
12788                    } else {
12789                        obj_quantified
12790                    };
12791
12792                    return self.wrap_with_definiteness_full(&subject, result);
12793                }
12794
12795                // Remaining complement forms — bare-noun object ("contain clove"),
12796                // measure/money ("cost $29.99", "measure 60 inches"), trailing PPs
12797                // ("happen in May", "go to Kansas"), aspect — are folded by the
12798                // shared finite-verb builder, exactly as in the positive path. The
12799                // negative scope stays open across the complement so NPIs inside it
12800                // are licensed; the result is wrapped in ¬ when "not" was present.
12801                if is_negated {
12802                    self.negative_depth += 1;
12803                }
12804                let vp = self.build_verb_vp(
12805                    subject.noun,
12806                    subject_term,
12807                    false,
12808                    verb,
12809                    verb_time,
12810                    verb_aspect,
12811                    verb_class,
12812                )?;
12813                if is_negated {
12814                    self.negative_depth -= 1;
12815                }
12816                // Returned UNWRAPPED: a definite subject is not given a uniqueness
12817                // clause here, keeping negated do-support goals structurally clean
12818                // for the proof engine (matches the pre-existing intransitive form).
12819                let result = if is_negated {
12820                    self.ctx.exprs.alloc(LogicExpr::UnaryOp {
12821                        op: TokenType::Not,
12822                        operand: vp,
12823                    })
12824                } else {
12825                    vp
12826                };
12827                return Ok(result);
12828            }
12829        }
12830
12831        // Garden path detection: "The horse raced past the barn fell."
12832        // If we have a definite NP + past verb + more content + another verb,
12833        // try reduced relative interpretation
12834        // Skip if pending_time is set (auxiliary like "will" was just consumed)
12835        // Skip if verb is has/have/had (perfect aspect, not reduced relative)
12836        let is_perfect_aux = if self.check_verb() {
12837            let word = self.interner.resolve(self.peek().lexeme).to_lowercase();
12838            word == "has" || word == "have" || word == "had"
12839        } else {
12840            false
12841        };
12842        if subject.definiteness == Some(Definiteness::Definite) && self.check_verb() && self.pending_time.is_none() && !is_perfect_aux {
12843            let saved_pos = self.current;
12844
12845            // Try parsing as reduced relative: first verb is modifier, look for main verb after
12846            if let Some(garden_path_result) = self.try_parse(|p| {
12847                let (modifier_verb, _modifier_time, _, _) = p.consume_verb_with_metadata();
12848
12849                // Collect any PP modifiers on the reduced relative
12850                let mut pp_mods: Vec<&'a LogicExpr<'a>> = Vec::new();
12851                while p.check_preposition() {
12852                    let prep = if let TokenType::Preposition(prep) = p.advance().kind {
12853                        prep
12854                    } else {
12855                        break;
12856                    };
12857                    if p.check_article() || p.check_content_word() {
12858                        let pp_obj = p.parse_noun_phrase(false)?;
12859                        let pp_pred = p.ctx.exprs.alloc(LogicExpr::Predicate {
12860                            name: prep,
12861                            args: p.ctx.terms.alloc_slice([Term::Variable(p.interner.intern("x")), Term::Constant(pp_obj.noun)]),
12862                            world: None,
12863                        });
12864                        pp_mods.push(pp_pred);
12865                    }
12866                }
12867
12868                // Now check if there's ANOTHER verb (the real main verb)
12869                if !p.check_verb() {
12870                    return Err(ParseError {
12871                        kind: ParseErrorKind::ExpectedVerb { found: p.peek().kind.clone() },
12872                        span: p.current_span(),
12873                    });
12874                }
12875
12876                let (main_verb, main_time, _, _) = p.consume_verb_with_metadata();
12877
12878                // Adjective complement of the main verb ("proved FALSE") is
12879                // its predicated result.
12880                let main_complement = if let TokenType::Adjective(a) = p.peek().kind {
12881                    p.advance();
12882                    Some(a)
12883                } else {
12884                    None
12885                };
12886
12887                // The reanalysis must account for the WHOLE clause, or it is
12888                // not the right reading.
12889                if !p.at_clause_boundary() {
12890                    return Err(ParseError {
12891                        kind: ParseErrorKind::TrailingTokens {
12892                            found: p.peek().kind.clone(),
12893                        },
12894                        span: p.current_span(),
12895                    });
12896                }
12897
12898                // Build: ∃x((Horse(x) ∧ ∀y(Horse(y) → y=x)) ∧ Raced(x) ∧ Past(x, Barn) ∧ Fell(x))
12899                let var = p.interner.intern("x");
12900
12901                // Type predicate
12902                let type_pred = p.ctx.exprs.alloc(LogicExpr::Predicate {
12903                    name: subject.noun,
12904                    args: p.ctx.terms.alloc_slice([Term::Variable(var)]),
12905                    world: None,
12906                });
12907
12908                // Modifier verb predicate (reduced relative)
12909                let mod_pred = p.ctx.exprs.alloc(LogicExpr::Predicate {
12910                    name: modifier_verb,
12911                    args: p.ctx.terms.alloc_slice([Term::Variable(var)]),
12912                    world: None,
12913                });
12914
12915                // Main verb predicate
12916                let main_pred = p.ctx.exprs.alloc(LogicExpr::Predicate {
12917                    name: main_verb,
12918                    args: match main_complement {
12919                        Some(adj) => p
12920                            .ctx
12921                            .terms
12922                            .alloc_slice([Term::Variable(var), Term::Constant(adj)]),
12923                        None => p.ctx.terms.alloc_slice([Term::Variable(var)]),
12924                    },
12925                    world: None,
12926                });
12927
12928                // Combine type + modifier
12929                let mut body = p.ctx.exprs.alloc(LogicExpr::BinaryOp {
12930                    left: type_pred,
12931                    op: TokenType::And,
12932                    right: mod_pred,
12933                });
12934
12935                // Add PP modifiers
12936                for pp in pp_mods {
12937                    body = p.ctx.exprs.alloc(LogicExpr::BinaryOp {
12938                        left: body,
12939                        op: TokenType::And,
12940                        right: pp,
12941                    });
12942                }
12943
12944                // Add main predicate
12945                body = p.ctx.exprs.alloc(LogicExpr::BinaryOp {
12946                    left: body,
12947                    op: TokenType::And,
12948                    right: main_pred,
12949                });
12950
12951                // Wrap with temporal if needed
12952                let with_time = match main_time {
12953                    Time::Past => p.ctx.exprs.alloc(LogicExpr::Temporal {
12954                        operator: TemporalOperator::Past,
12955                        body,
12956                    }),
12957                    Time::Future => p.ctx.exprs.alloc(LogicExpr::Temporal {
12958                        operator: TemporalOperator::Future,
12959                        body,
12960                    }),
12961                    _ => body,
12962                };
12963
12964                // Wrap in existential quantifier for definite
12965                Ok(p.ctx.exprs.alloc(LogicExpr::Quantifier {
12966                    kind: QuantifierKind::Existential,
12967                    variable: var,
12968                    body: with_time,
12969                    island_id: p.current_island,
12970                }))
12971            }) {
12972                return Ok(garden_path_result);
12973            }
12974
12975            // Restore position if garden path didn't work
12976            self.current = saved_pos;
12977        }
12978
12979        if self.check_modal() {
12980            return self.parse_aspect_chain(subject.noun);
12981        }
12982
12983        // Handle "has/have/had" perfect aspect: "John has run"
12984        if self.check_content_word() {
12985            let word = self.interner.resolve(self.peek().lexeme).to_lowercase();
12986            if word == "has" || word == "have" || word == "had" {
12987                // Lookahead to distinguish perfect aspect ("has eaten") from possession ("has 3 children")
12988                let is_perfect_aspect = if self.current + 1 < self.tokens.len() {
12989                    let next_token = &self.tokens[self.current + 1].kind;
12990                    matches!(
12991                        next_token,
12992                        TokenType::Verb { .. } | TokenType::Not
12993                    ) && !matches!(next_token, TokenType::Number(_))
12994                } else {
12995                    false
12996                };
12997                if is_perfect_aspect {
12998                    return self.parse_aspect_chain(subject.noun);
12999                }
13000                // Otherwise fall through to verb parsing below
13001            }
13002        }
13003
13004        // "John had RUN" is the past perfect; "Bob had THE PORT" (an object, no
13005        // participle) is possessive HAVE in the PAST — re-tag "had" as the verb so
13006        // the normal verb+object grammar builds ∃e(Have(e) ∧ Agent ∧ Theme ∧ Past)
13007        // instead of mis-reading "the port" as a perfect participle.
13008        if self.check(&TokenType::Had) {
13009            let next_is_participle = matches!(
13010                self.tokens.get(self.current + 1).map(|t| &t.kind),
13011                Some(TokenType::Verb { .. })
13012            );
13013            if next_is_participle {
13014                return self.parse_aspect_chain(subject.noun);
13015            }
13016            let have_lemma = self.interner.intern("Have");
13017            self.tokens[self.current].kind = TokenType::Verb {
13018                lemma: have_lemma,
13019                time: Time::Past,
13020                aspect: Aspect::Simple,
13021                class: VerbClass::State,
13022            };
13023            // fall through to the normal verb+object handling
13024        }
13025
13026        // Handle "never" temporal negation: "John never runs"
13027        if self.check(&TokenType::Never) {
13028            self.advance();
13029            let verb = self.consume_verb();
13030            let subject_term = self.noun_phrase_to_term(&subject);
13031
13032            // Optional object: quantified ("two requests") raises past the
13033            // negation; a plain NP fills Theme. Bare "never V" keeps the
13034            // classical ¬Verb(subject) shape.
13035            let mut object_quant: Option<(QuantifierKind, Symbol, Symbol)> = None;
13036            let mut object_term: Option<Term<'a>> = None;
13037            if let TokenType::Cardinal(n) = self.peek().kind {
13038                self.advance();
13039                let np = self.parse_noun_phrase(false)?;
13040                let var = self.next_var_name();
13041                object_quant = Some((QuantifierKind::Cardinal(n), var, np.noun));
13042                object_term = Some(Term::Variable(var));
13043            } else if matches!(self.peek().kind, TokenType::All | TokenType::Some) {
13044                let universal = matches!(self.advance().kind, TokenType::All);
13045                let np = self.parse_noun_phrase(false)?;
13046                let var = self.next_var_name();
13047                let kind = if universal {
13048                    QuantifierKind::Universal
13049                } else {
13050                    QuantifierKind::Existential
13051                };
13052                object_quant = Some((kind, var, np.noun));
13053                object_term = Some(Term::Variable(var));
13054            } else if self.check_content_word() || self.check_article() {
13055                let np = self.parse_noun_phrase(false)?;
13056                object_term = Some(self.noun_phrase_to_term(&np));
13057            }
13058
13059            if object_term.is_none() {
13060                let verb_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
13061                    name: verb,
13062                    args: self.ctx.terms.alloc_slice([subject_term]),
13063                    world: None,
13064                });
13065                let result = self.ctx.exprs.alloc(LogicExpr::UnaryOp {
13066                    op: TokenType::Not,
13067                    operand: verb_pred,
13068                });
13069                return self.wrap_with_definiteness_full(&subject, result);
13070            }
13071
13072            let mut roles = vec![(crate::ast::ThematicRole::Agent, subject_term)];
13073            if let Some(t) = object_term {
13074                roles.push((crate::ast::ThematicRole::Theme, t));
13075            }
13076            let modifiers = self.collect_adverbs();
13077            let event_var = self.get_event_var();
13078            let suppress_existential = self.drs.in_conditional_antecedent();
13079            let event = self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
13080                event_var,
13081                verb,
13082                roles: self.ctx.roles.alloc_slice(roles),
13083                modifiers: self.ctx.syms.alloc_slice(modifiers),
13084                suppress_existential,
13085                world: None,
13086            })));
13087            let mut result = self.ctx.exprs.alloc(LogicExpr::UnaryOp {
13088                op: TokenType::Not,
13089                operand: event,
13090            });
13091            if let Some((kind, var, noun)) = object_quant {
13092                let restriction = self.ctx.exprs.alloc(LogicExpr::Predicate {
13093                    name: noun,
13094                    args: self.ctx.terms.alloc_slice([Term::Variable(var)]),
13095                    world: None,
13096                });
13097                let connective = if matches!(kind, QuantifierKind::Universal) {
13098                    TokenType::Implies
13099                } else {
13100                    TokenType::And
13101                };
13102                let body = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
13103                    left: restriction,
13104                    op: connective,
13105                    right: result,
13106                });
13107                result = self.ctx.exprs.alloc(LogicExpr::Quantifier {
13108                    kind,
13109                    variable: var,
13110                    body,
13111                    island_id: self.current_island,
13112                });
13113            }
13114            return self.wrap_with_definiteness_full(&subject, result);
13115        }
13116
13117        // Adverbs of quantification ("usually", "often", "always", "generally", …)
13118        // before the verb mark a characterizing/habitual clause ("John usually
13119        // smokes."). Consume the adverb so the predicate parse proceeds; the
13120        // habitual operator falls out of the simple-present eventive reading. Only
13121        // consumed when a verb follows, so it never strands other constructions.
13122        if let TokenType::Adverb(sym)
13123        | TokenType::ScopalAdverb(sym)
13124        | TokenType::TemporalAdverb(sym) = self.peek().kind
13125        {
13126            // Adverbs of quantification are a lexical category (tagged in the
13127            // lexicon), not a hardcoded parser list.
13128            let w = self.interner.resolve(sym).to_lowercase();
13129            if crate::lexicon::is_quantificational_adverb(&w)
13130                && self.current + 1 < self.tokens.len()
13131                && matches!(self.tokens[self.current + 1].kind, TokenType::Verb { .. })
13132            {
13133                self.advance();
13134            }
13135        }
13136
13137        if self.check_verb() {
13138            let (mut verb, verb_time, verb_aspect, verb_class) = self.consume_verb_with_metadata();
13139
13140            // Check for verb sort violation (metaphor detection)
13141            let subject_sort = lexicon::lookup_sort(self.interner.resolve(subject.noun));
13142            let verb_str = self.interner.resolve(verb);
13143            if let Some(s_sort) = subject_sort {
13144                if !crate::ontology::check_sort_compatibility(verb_str, s_sort) {
13145                    let metaphor = self.ctx.exprs.alloc(LogicExpr::Metaphor {
13146                        tenor: self.ctx.terms.alloc(Term::Constant(subject.noun)),
13147                        vehicle: self.ctx.terms.alloc(Term::Constant(verb)),
13148                    });
13149                    return self.wrap_with_definiteness_full(&subject, metaphor);
13150                }
13151            }
13152
13153            // Arithmetic / vague verbal comparative ("Tara scored 3 points lower
13154            // than Bessie"): the same construction parse_predicate_impl handles,
13155            // shared so a proper-name / definite subject parsed inline here keeps
13156            // the offset and the comparison instead of stranding the comparative.
13157            if let Some(cmp) =
13158                self.try_arithmetic_comparative(verb, Term::Constant(subject.noun), verb_time)?
13159            {
13160                return self.wrap_with_definiteness_full(&subject, cmp);
13161            }
13162
13163            // Temporal offset ("Tara performed 2 weeks after Bessie").
13164            if let Some(off) =
13165                self.try_temporal_offset(verb, Term::Constant(subject.noun), verb_time)?
13166            {
13167                return self.wrap_with_definiteness_full(&subject, off);
13168            }
13169
13170            // Ordinal-position offset ("finished 2 places ahead of Bob").
13171            if let Some(off) =
13172                self.try_positional_offset(verb, Term::Constant(subject.noun), verb_time)?
13173            {
13174                return self.wrap_with_definiteness_full(&subject, off);
13175            }
13176
13177            // Raising / evidential verbs (seem, appear, look — tagged `Raising` in
13178            // the lexicon): "John seems happy." marks an evidence source; the
13179            // complement is NOT asserted but embedded under the evidential:
13180            // Seem(⟨Happy(John)⟩). So "seems happy" does not entail "happy". This is
13181            // checked BEFORE the control-verb path because seem/appear are raising
13182            // (subject-to-subject), not control, even if also tagged control.
13183            // The EVIDENTIAL use has a SMALL-CLAUSE complement: "seems happy",
13184            // "seems to be happy", "seems a fool". A bare intransitive ("a cat
13185            // appeared.") and a raising-to-control "seems to <verb>" ("seems to want
13186            // to stay") both fall through to the existing verb handling.
13187            let is_raising =
13188                crate::lexicon::is_raising_verb(&self.interner.resolve(verb).to_lowercase());
13189            let next_is_to_be = self.check(&TokenType::To)
13190                && self.current + 1 < self.tokens.len()
13191                && matches!(self.tokens[self.current + 1].kind, TokenType::Be);
13192            let raising_with_complement = is_raising
13193                && (matches!(self.peek().kind, TokenType::Adjective(_))
13194                    || next_is_to_be
13195                    || (self.check_content_word() && !self.check(&TokenType::To)));
13196            if raising_with_complement {
13197                // Optional "to be": "seems to be happy".
13198                if self.check(&TokenType::To) {
13199                    self.advance();
13200                    if self.check(&TokenType::Be) {
13201                        self.advance();
13202                    }
13203                }
13204                let complement = if let TokenType::Adjective(adj) = self.peek().kind {
13205                    self.advance();
13206                    self.ctx.exprs.alloc(LogicExpr::Predicate {
13207                        name: adj,
13208                        args: self.ctx.terms.alloc_slice([Term::Constant(subject.noun)]),
13209                        world: None,
13210                    })
13211                } else if self.check_content_word() {
13212                    self.parse_predicate_with_subject(subject.noun)?
13213                } else {
13214                    self.ctx.exprs.alloc(LogicExpr::Atom(self.interner.intern("?")))
13215                };
13216                let evidential = self.ctx.exprs.alloc(LogicExpr::Modal {
13217                    vector: crate::ast::ModalVector::new(
13218                        crate::ast::ModalDomain::Alethic,
13219                        1.0,
13220                        crate::ast::ModalFlavor::Evidential,
13221                    )
13222                    .with_base(verb),
13223                    operand: complement,
13224                });
13225                return self.wrap_with_definiteness_full(&subject, evidential);
13226            }
13227
13228            // Perception complement (§3.2): a perception verb ("hear", "see" —
13229            // lexicon-tagged) takes an "NP bare-VP" small clause naming the PERCEIVED
13230            // EVENT: "Mary heard the bell ring." → Hear(Mary, ⟨Ring(bell)⟩). The bare
13231            // VP head is the LAST verb-capable token before the clause end (so the
13232            // perceived NP "the bell" is not greedily merged with "ring"); a clause
13233            // with no such trailing verb ("heard the bell") is an ordinary object.
13234            if crate::lexicon::is_perception_verb(&self.interner.resolve(verb).to_lowercase()) {
13235                let is_verb_capable = |kind: &TokenType| match kind {
13236                    TokenType::Verb { .. } => true,
13237                    TokenType::Ambiguous { primary, alternatives } => {
13238                        matches!(**primary, TokenType::Verb { .. })
13239                            || alternatives.iter().any(|t| matches!(t, TokenType::Verb { .. }))
13240                    }
13241                    _ => false,
13242                };
13243                let mut vp_idx = None;
13244                let mut i = self.current;
13245                while i < self.tokens.len() {
13246                    if matches!(
13247                        self.tokens[i].kind,
13248                        TokenType::Period | TokenType::EOF | TokenType::Comma
13249                    ) {
13250                        break;
13251                    }
13252                    // Verb-capable by token kind OR by lexicon (a word like "ring"
13253                    // may be tokenized as a noun after "the bell" yet head the VP).
13254                    let lex = self.interner.resolve(self.tokens[i].lexeme).to_lowercase();
13255                    if is_verb_capable(&self.tokens[i].kind)
13256                        || crate::lexicon::lookup_verb_db(&lex).is_some()
13257                    {
13258                        vp_idx = Some(i);
13259                    }
13260                    i += 1;
13261                }
13262                if let Some(vp_i) = vp_idx {
13263                    // The perceived subject is the noun/name immediately before the VP.
13264                    if vp_i > self.current {
13265                        let subj_kind = self.tokens[vp_i - 1].kind.clone();
13266                        let psubj = match subj_kind {
13267                            TokenType::Noun(n) | TokenType::ProperName(n) => Some(n),
13268                            // A function word cannot be the perceived subject.
13269                            TokenType::Article(_)
13270                            | TokenType::Preposition(_)
13271                            | TokenType::Period
13272                            | TokenType::Comma => None,
13273                            // An ADJECTIVE or NUMBER/CARDINAL before the candidate
13274                            // VP means this is NOT a small clause ("saw 6 previous
13275                            // JUMPS" — "previous" cannot be a clause subject); the
13276                            // verb-word is a deverbal noun head. Bail so the object
13277                            // NP path (counting NP / deverbal noun) handles it.
13278                            TokenType::Adjective(_)
13279                            | TokenType::NonIntersectiveAdjective(_)
13280                            | TokenType::Cardinal(_)
13281                            | TokenType::Number(_)
13282                            | TokenType::AtLeast(_)
13283                            | TokenType::AtMost(_) => None,
13284                            // A content word tokenized otherwise (e.g. a noun-or-verb
13285                            // ambiguity) — use its lexeme as the entity name.
13286                            _ => {
13287                                let lx = self
13288                                    .interner
13289                                    .resolve(self.tokens[vp_i - 1].lexeme)
13290                                    .to_lowercase();
13291                                let cap = lx
13292                                    .chars()
13293                                    .next()
13294                                    .map(|c| c.to_uppercase().collect::<String>() + &lx[1..])
13295                                    .unwrap_or(lx);
13296                                Some(self.interner.intern(&cap))
13297                            }
13298                        };
13299                        if let Some(psubj) = psubj {
13300                            let vlex = self.interner.resolve(self.tokens[vp_i].lexeme).to_lowercase();
13301                            let vname = vlex
13302                                .chars()
13303                                .next()
13304                                .map(|c| c.to_uppercase().collect::<String>() + &vlex[1..])
13305                                .unwrap_or(vlex);
13306                            let inner_verb = self.interner.intern(&vname);
13307                            self.current = vp_i + 1; // consume through the VP head
13308                            let perceived = self.ctx.exprs.alloc(LogicExpr::Predicate {
13309                                name: inner_verb,
13310                                args: self.ctx.terms.alloc_slice([Term::Constant(psubj)]),
13311                                world: None,
13312                            });
13313                            // A trailing adverb modifies the PERCEIVED event
13314                            // ("heard the bell ring LOUDLY").
13315                            let perceived_advs = self.collect_adverbs();
13316                            let perceived = if perceived_advs.is_empty() {
13317                                perceived
13318                            } else {
13319                                self.ctx.exprs.alloc(LogicExpr::Event {
13320                                    predicate: perceived,
13321                                    adverbs: self.ctx.syms.alloc_slice(perceived_advs),
13322                                })
13323                            };
13324                            let event_var = self.get_event_var();
13325                            let roles = vec![
13326                                (ThematicRole::Agent, Term::Constant(subject.noun)),
13327                                (ThematicRole::Theme, Term::Proposition(perceived)),
13328                            ];
13329                            let mut modifiers: Vec<Symbol> = Vec::new();
13330                            match verb_time {
13331                                Time::Past => modifiers.push(self.interner.intern("Past")),
13332                                Time::Future => modifiers.push(self.interner.intern("Future")),
13333                                _ => {}
13334                            }
13335                            let neo =
13336                                self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
13337                                    event_var,
13338                                    verb,
13339                                    roles: self.ctx.roles.alloc_slice(roles),
13340                                    modifiers: self.ctx.syms.alloc_slice(modifiers),
13341                                    suppress_existential: false,
13342                                    world: None,
13343                                })));
13344                            return self.wrap_with_definiteness_full(&subject, neo);
13345                        }
13346                    }
13347                }
13348            }
13349
13350            // Check for control verb + infinitive (raising verbs handled above).
13351            // A SUBJECT-control verb only takes the control structure when an
13352            // infinitival complement actually follows ("started TO run"). Used as a
13353            // plain eventive verb ("The outing started at Greektown"), it falls
13354            // through to the regular VP grammar so its PP adjuncts attach. An
13355            // OBJECT-control verb keeps the control path even without an immediate
13356            // "to" — its object comes first ("persuaded Mary to be examined").
13357            if self.is_control_verb(verb)
13358                && (self.check_to()
13359                    || lexicon::is_object_control_verb(
13360                        &self.interner.resolve(verb).to_lowercase(),
13361                    ))
13362            {
13363                return self.parse_control_structure(&subject, verb, verb_time);
13364            }
13365
13366            // If we have a relative clause, use variable binding
13367            if let Some((var_name, rel_clause)) = relative_clause {
13368                // A bare verb keeps the simple predication; material after it
13369                // ("The farmer who saw a cat CHASED IT.") takes the full VP
13370                // grammar over the relativized variable.
13371                let with_time = if self.at_clause_boundary() {
13372                    let main_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
13373                        name: verb,
13374                        args: self.ctx.terms.alloc_slice([Term::Variable(var_name)]),
13375                        world: None,
13376                    });
13377
13378                    let effective_time = self.pending_time.take().unwrap_or(verb_time);
13379                    match effective_time {
13380                        Time::Past => &*self.ctx.exprs.alloc(LogicExpr::Temporal {
13381                            operator: TemporalOperator::Past,
13382                            body: main_pred,
13383                        }),
13384                        Time::Future => &*self.ctx.exprs.alloc(LogicExpr::Temporal {
13385                            operator: TemporalOperator::Future,
13386                            body: main_pred,
13387                        }),
13388                        _ => main_pred,
13389                    }
13390                } else {
13391                    self.current -= 1; // hand the verb back to the VP parser
13392                    self.parse_predicate_with_subject_as_var(var_name)?
13393                };
13394
13395                // Build: ∃x(Type(x) ∧ RelClause(x) ∧ MainPred(x))
13396                let type_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
13397                    name: subject.noun,
13398                    args: self.ctx.terms.alloc_slice([Term::Variable(var_name)]),
13399                    world: None,
13400                });
13401
13402                let inner = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
13403                    left: type_pred,
13404                    op: TokenType::And,
13405                    right: rel_clause,
13406                });
13407
13408                let body = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
13409                    left: inner,
13410                    op: TokenType::And,
13411                    right: with_time,
13412                });
13413
13414                return Ok(self.ctx.exprs.alloc(LogicExpr::Quantifier {
13415                    kind: QuantifierKind::Existential,
13416                    variable: var_name,
13417                    body,
13418                    island_id: self.current_island,
13419                }));
13420            }
13421
13422            let subject_term = self.noun_phrase_to_term(&subject);
13423            let mut args = vec![subject_term.clone()];
13424
13425            let unknown = self.interner.intern("?");
13426
13427            // Check for embedded wh-clause: "I know who/what"
13428            if self.check_wh_word() {
13429                let wh_token = self.advance().kind.clone();
13430
13431                // Determine wh-type for slot matching
13432                let is_who = matches!(wh_token, TokenType::Who);
13433                let is_what = matches!(wh_token, TokenType::What);
13434
13435                // Check for sluicing: wh-word followed by terminator
13436                let is_sluicing = self.is_at_end() ||
13437                    self.check(&TokenType::Period) ||
13438                    self.check(&TokenType::Comma);
13439
13440                if is_sluicing {
13441                    // Reconstruct from template
13442                    if let Some(template) = self.last_event_template.clone() {
13443                        let wh_var = self.next_var_name();
13444
13445                        // Build roles with wh-variable in appropriate slot
13446                        let roles: Vec<_> = if is_who {
13447                            // "who" replaces Agent
13448                            std::iter::once((ThematicRole::Agent, Term::Variable(wh_var)))
13449                                .chain(template.non_agent_roles.iter().cloned())
13450                                .collect()
13451                        } else if is_what {
13452                            // "what" replaces Theme - use Agent from context, Theme is variable
13453                            vec![
13454                                (ThematicRole::Agent, subject_term.clone()),
13455                                (ThematicRole::Theme, Term::Variable(wh_var)),
13456                            ]
13457                        } else {
13458                            // Default: wh-variable as Agent
13459                            std::iter::once((ThematicRole::Agent, Term::Variable(wh_var)))
13460                                .chain(template.non_agent_roles.iter().cloned())
13461                                .collect()
13462                        };
13463
13464                        let event_var = self.get_event_var();
13465                        let suppress_existential = self.drs.in_conditional_antecedent();
13466                        if suppress_existential {
13467                            let event_class = self.interner.intern("Event");
13468                            self.drs.introduce_referent(event_var, event_class, Gender::Neuter, Number::Singular);
13469                        }
13470                        let reconstructed = self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
13471                            event_var,
13472                            verb: template.verb,
13473                            roles: self.ctx.roles.alloc_slice(roles),
13474                            modifiers: self.ctx.syms.alloc_slice(template.modifiers.clone()),
13475                            suppress_existential,
13476                            world: None,
13477                        })));
13478
13479                        let question = self.ctx.exprs.alloc(LogicExpr::Question {
13480                            wh_variable: wh_var,
13481                            body: reconstructed,
13482                        });
13483
13484                        // Build: Know(subject, question)
13485                        let know_event_var = self.get_event_var();
13486                        let suppress_existential2 = self.drs.in_conditional_antecedent();
13487                        if suppress_existential2 {
13488                            let event_class = self.interner.intern("Event");
13489                            self.drs.introduce_referent(know_event_var, event_class, Gender::Neuter, Number::Singular);
13490                        }
13491                        let know_event = self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
13492                            event_var: know_event_var,
13493                            verb,
13494                            roles: self.ctx.roles.alloc_slice(vec![
13495                                (ThematicRole::Agent, subject_term),
13496                                (ThematicRole::Theme, Term::Proposition(question)),
13497                            ]),
13498                            modifiers: self.ctx.syms.alloc_slice(vec![]),
13499                            suppress_existential: suppress_existential2,
13500                            world: None,
13501                        })));
13502
13503                        return self.wrap_with_definiteness_full(&subject, know_event);
13504                    }
13505                }
13506
13507                // Non-sluicing embedded question: "I know who runs"
13508                let embedded = self.parse_embedded_wh_clause()?;
13509                let question = self.ctx.exprs.alloc(LogicExpr::Question {
13510                    wh_variable: self.interner.intern("x"),
13511                    body: embedded,
13512                });
13513
13514                // Build: Know(subject, question)
13515                let know_event_var = self.get_event_var();
13516                let suppress_existential = self.drs.in_conditional_antecedent();
13517                if suppress_existential {
13518                    let event_class = self.interner.intern("Event");
13519                    self.drs.introduce_referent(know_event_var, event_class, Gender::Neuter, Number::Singular);
13520                }
13521                let know_event = self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
13522                    event_var: know_event_var,
13523                    verb,
13524                    roles: self.ctx.roles.alloc_slice(vec![
13525                        (ThematicRole::Agent, subject_term),
13526                        (ThematicRole::Theme, Term::Proposition(question)),
13527                    ]),
13528                    modifiers: self.ctx.syms.alloc_slice(vec![]),
13529                    suppress_existential,
13530                    world: None,
13531                })));
13532
13533                return self.wrap_with_definiteness_full(&subject, know_event);
13534            }
13535
13536            // Opaque attitude verbs take a finite clausal complement as a STRUCTURED
13537            // PROPOSITION (P3), not an extensional object: "John believes Mary left."
13538            // → Believe(John, ⟨Left(Mary)⟩). A pure-token lookahead detects an embedded
13539            // proper-name/pronoun subject directly followed by a verb (optionally after
13540            // the complementizer "that"); article-headed embedded clauses ("a spy
13541            // exists") keep their existing de re/de dicto treatment downstream.
13542            if crate::lexicon::is_opaque_verb(&self.interner.resolve(verb).to_lowercase()) {
13543                let mut i = self.current;
13544                if i < self.tokens.len() && matches!(self.tokens[i].kind, TokenType::That) {
13545                    i += 1;
13546                }
13547                let subj_ok = i < self.tokens.len()
13548                    && matches!(
13549                        self.tokens[i].kind,
13550                        TokenType::ProperName(_) | TokenType::Pronoun { .. }
13551                    );
13552                let verb_follows = subj_ok
13553                    && i + 1 < self.tokens.len()
13554                    && (matches!(
13555                        self.tokens[i + 1].kind,
13556                        TokenType::Verb { .. } | TokenType::Auxiliary(_)
13557                    )
13558                        // Noun/verb-ambiguous surface forms ("saw", "ring")
13559                        // tokenize as nouns; the irregular table identifies
13560                        // them as verb forms.
13561                        || crate::lexicon::lookup_irregular_verb(
13562                            &self
13563                                .interner
13564                                .resolve(self.tokens[i + 1].lexeme)
13565                                .to_lowercase(),
13566                        )
13567                        .is_some());
13568                if verb_follows {
13569                    if self.check(&TokenType::That) {
13570                        self.advance();
13571                    }
13572                    let embedded_subject = match self.peek().kind {
13573                        TokenType::ProperName(s) => {
13574                            self.advance();
13575                            s
13576                        }
13577                        TokenType::Pronoun { gender, number, .. } => {
13578                            self.advance();
13579                            match self.resolve_pronoun(gender, number)? {
13580                                ResolvedPronoun::Variable(s) | ResolvedPronoun::Constant(s) => s,
13581                            }
13582                        }
13583                        _ => unreachable!("guarded by subj_ok"),
13584                    };
13585                    let embedded_pred = self.parse_predicate_with_subject(embedded_subject)?;
13586                    let subject_term_for_event = self.coerce_agent(&subject);
13587                    let main_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
13588                        name: verb,
13589                        args: self.ctx.terms.alloc_slice([
13590                            subject_term_for_event,
13591                            Term::Proposition(embedded_pred),
13592                        ]),
13593                        world: None,
13594                    });
13595                    let effective_time = self.pending_time.take().unwrap_or(verb_time);
13596                    let with_time = match effective_time {
13597                        Time::Past => self.ctx.exprs.alloc(LogicExpr::Temporal {
13598                            operator: TemporalOperator::Past,
13599                            body: main_pred,
13600                        }),
13601                        Time::Future => self.ctx.exprs.alloc(LogicExpr::Temporal {
13602                            operator: TemporalOperator::Future,
13603                            body: main_pred,
13604                        }),
13605                        _ => main_pred,
13606                    };
13607                    return self.wrap_with_definiteness_full(&subject, with_time);
13608                }
13609            }
13610
13611            let mut object_term: Option<Term<'a>> = None;
13612            // A definite/constant object's own adjectives and PP restrictors ("ate
13613            // the RED apple", "the frat ON Holly Street") — predicated of the
13614            // object term in the shared event build below; dropping them loses a
13615            // constraint (meaning-loss).
13616            let mut object_adjectives: &[Symbol] = &[];
13617            let mut object_desc_pps: &[&'a LogicExpr<'a>] = &[];
13618            // Secondary predication (§3.3): a post-object adjective ("painted the
13619            // door red", "ate the meat raw") captured during object parsing and
13620            // attached as a Result (change-of-state verb) or Depictive role below.
13621            let mut secondary_adj: Option<Symbol> = None;
13622            // Resultative vs depictive is a LEXICAL property of the verb (causative
13623            // change-of-state verbs are tagged `Resultative` in the lexicon).
13624            let secondary_role = |verb_lemma: &str| -> ThematicRole {
13625                if crate::lexicon::is_resultative_verb(verb_lemma) {
13626                    ThematicRole::Result
13627                } else {
13628                    ThematicRole::Depictive
13629                }
13630            };
13631            let mut second_object_term: Option<Term<'a>> = None;
13632            let mut object_superlative: Option<(Symbol, Symbol)> = None; // (adjective, noun)
13633            if self.check(&TokenType::Reflexive) {
13634                self.advance();
13635                let term = self.noun_phrase_to_term(&subject);
13636                object_term = Some(term.clone());
13637                args.push(term);
13638
13639                // Check for distanced phrasal verb particle: "gave himself up"
13640                if let TokenType::Particle(particle_sym) = self.peek().kind {
13641                    let verb_str = self.interner.resolve(verb).to_lowercase();
13642                    let particle_str = self.interner.resolve(particle_sym).to_lowercase();
13643                    if let Some((phrasal_lemma, _class)) = crate::lexicon::lookup_phrasal_verb(&verb_str, &particle_str) {
13644                        self.advance();
13645                        verb = self.interner.intern(phrasal_lemma);
13646                    }
13647                }
13648            } else if self.check_pronoun() {
13649                let token = self.advance().clone();
13650                // See through an Ambiguous wrapper ("her" = object pronoun /
13651                // possessive): check_pronoun already ruled out the possessive
13652                // NP reading, so the primary pronoun analysis applies here.
13653                let pronoun_analysis = match &token.kind {
13654                    TokenType::Pronoun { gender, number, .. } => Some((*gender, *number)),
13655                    TokenType::Ambiguous { primary, .. } => match primary.as_ref() {
13656                        TokenType::Pronoun { gender, number, .. } => Some((*gender, *number)),
13657                        _ => None,
13658                    },
13659                    _ => None,
13660                };
13661                if let Some((gender, number)) = pronoun_analysis {
13662                    // Person deictics (§8.4) resolve to the discourse roles in ANY
13663                    // position: "Mary saw you." → Addressee, not a discourse referent.
13664                    let plex = self.interner.resolve(token.lexeme).to_lowercase();
13665                    let deictic_role = match plex.as_str() {
13666                        "you" | "yourself" => Some("Addressee"),
13667                        "i" | "me" | "myself" => Some("Speaker"),
13668                        _ => None,
13669                    };
13670                    if let Some(role) = deictic_role {
13671                        let term = Term::Constant(self.interner.intern(role));
13672                        object_term = Some(term.clone());
13673                        args.push(term);
13674                        // fall past the resolve-pronoun handling below
13675                    } else {
13676                    let resolved = self.resolve_pronoun(gender, number)?;
13677                    let resolved_sym = match resolved {
13678                        ResolvedPronoun::Variable(s) | ResolvedPronoun::Constant(s) => s,
13679                    };
13680                    // Binding Principle B: a plain (non-reflexive) object pronoun is
13681                    // free in its local clause — it must not corefer with the local
13682                    // subject. "John saw him." ⇒ him ≠ John. If resolution picked the
13683                    // subject, use a fresh distinct individual instead.
13684                    let term = if resolved_sym == subject.noun {
13685                        let deictic = match (gender, number) {
13686                            (Gender::Male, Number::Singular) => "Him",
13687                            (Gender::Female, Number::Singular) => "Her",
13688                            (Gender::Neuter, Number::Singular) => "It",
13689                            _ => "Them",
13690                        };
13691                        let sym = self.interner.intern(deictic);
13692                        self.drs.introduce_referent_with_source(
13693                            sym,
13694                            sym,
13695                            gender,
13696                            number,
13697                            crate::drs::ReferentSource::ProperName,
13698                        );
13699                        Term::Constant(sym)
13700                    } else {
13701                        match resolved {
13702                            ResolvedPronoun::Variable(s) => Term::Variable(s),
13703                            ResolvedPronoun::Constant(s) => Term::Constant(s),
13704                        }
13705                    };
13706                    object_term = Some(term.clone());
13707                    args.push(term);
13708
13709                    // Check for distanced phrasal verb particle: "gave it up"
13710                    if let TokenType::Particle(particle_sym) = self.peek().kind {
13711                        let verb_str = self.interner.resolve(verb).to_lowercase();
13712                        let particle_str = self.interner.resolve(particle_sym).to_lowercase();
13713                        if let Some((phrasal_lemma, _class)) = crate::lexicon::lookup_phrasal_verb(&verb_str, &particle_str) {
13714                            self.advance();
13715                            verb = self.interner.intern(phrasal_lemma);
13716                        }
13717                    }
13718                    } // end non-deictic pronoun branch
13719                }
13720            } else if self.check(&TokenType::Either) {
13721                // "Tara danced either the hustle or the lindy." — disjunctive theme
13722                self.advance(); // consume "either"
13723                let np1 = self.parse_noun_phrase(true)?;
13724                if self.check(&TokenType::Or) {
13725                    self.advance(); // consume "or"
13726                    let np2 = self.parse_noun_phrase(true)?;
13727
13728                    let effective_time = self.pending_time.take().unwrap_or(verb_time);
13729                    let subject_term1 = self.coerce_agent(&subject);
13730                    let subject_term2 = self.coerce_agent(&subject);
13731                    let suppress_existential = self.drs.in_conditional_antecedent();
13732
13733                    let past_sym = self.interner.intern("Past");
13734                    let future_sym = self.interner.intern("Future");
13735                    let mut mods1 = vec![];
13736                    let mut mods2 = vec![];
13737                    match effective_time {
13738                        Time::Past => { mods1.push(past_sym); mods2.push(past_sym); }
13739                        Time::Future => { mods1.push(future_sym); mods2.push(future_sym); }
13740                        _ => {}
13741                    }
13742
13743                    let event_var1 = self.get_event_var();
13744                    let event_var2 = self.get_event_var();
13745
13746                    let neo1 = self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
13747                        event_var: event_var1,
13748                        verb,
13749                        roles: self.ctx.roles.alloc_slice(vec![
13750                            (ThematicRole::Agent, subject_term1),
13751                            (ThematicRole::Theme, Term::Constant(np1.noun)),
13752                        ]),
13753                        modifiers: self.ctx.syms.alloc_slice(mods1),
13754                        suppress_existential,
13755                        world: None,
13756                    })));
13757                    let neo2 = self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
13758                        event_var: event_var2,
13759                        verb,
13760                        roles: self.ctx.roles.alloc_slice(vec![
13761                            (ThematicRole::Agent, subject_term2),
13762                            (ThematicRole::Theme, Term::Constant(np2.noun)),
13763                        ]),
13764                        modifiers: self.ctx.syms.alloc_slice(mods2),
13765                        suppress_existential,
13766                        world: None,
13767                    })));
13768
13769                    // Keep each disjunct object NP's possessor and PPs — "either
13770                    // the hustle from Spain or …" must not drop "from Spain";
13771                    // "either Tara's routine or …" must not drop the possessor.
13772                    let placeholder = self.interner.intern("_PP_SELF_");
13773                    let possesses = self.interner.intern("Possesses");
13774                    let mut augment = |p: &mut Self, neo: &'a LogicExpr<'a>, np: &crate::ast::NounPhrase<'a>| -> &'a LogicExpr<'a> {
13775                        let obj = Term::Constant(np.noun);
13776                        let mut e = neo;
13777                        if let Some(possessor) = np.possessor {
13778                            let poss = p.ctx.exprs.alloc(LogicExpr::Predicate {
13779                                name: possesses,
13780                                args: p
13781                                    .ctx
13782                                    .terms
13783                                    .alloc_slice([Term::Constant(possessor.noun), obj]),
13784                                world: None,
13785                            });
13786                            e = p.ctx.exprs.alloc(LogicExpr::BinaryOp {
13787                                left: e,
13788                                op: TokenType::And,
13789                                right: poss,
13790                            });
13791                        }
13792                        for pp in np.pps {
13793                            let pp_sub = match pp {
13794                                LogicExpr::Predicate { name, args, world } => {
13795                                    let new_args: Vec<Term<'a>> = args
13796                                        .iter()
13797                                        .map(|a| match a {
13798                                            Term::Variable(v) if *v == placeholder => obj,
13799                                            other => *other,
13800                                        })
13801                                        .collect();
13802                                    p.ctx.exprs.alloc(LogicExpr::Predicate {
13803                                        name: *name,
13804                                        args: p.ctx.terms.alloc_slice(new_args),
13805                                        world: *world,
13806                                    })
13807                                }
13808                                other => *other,
13809                            };
13810                            e = p.ctx.exprs.alloc(LogicExpr::BinaryOp {
13811                                left: e,
13812                                op: TokenType::And,
13813                                right: pp_sub,
13814                            });
13815                        }
13816                        e
13817                    };
13818                    let neo1 = augment(self, neo1, &np1);
13819                    let neo2 = augment(self, neo2, &np2);
13820                    let disj = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
13821                        left: neo1,
13822                        op: TokenType::Or,
13823                        right: neo2,
13824                    });
13825                    return self.wrap_with_definiteness_full(&subject, disj);
13826                }
13827                return Err(ParseError {
13828                    kind: ParseErrorKind::UnexpectedToken {
13829                        expected: TokenType::Or,
13830                        found: self.peek().kind.clone(),
13831                    },
13832                    span: self.current_span(),
13833                });
13834            } else if self.counting_np_lookahead().is_some()
13835                || self.check_quantifier()
13836                || self.check_article()
13837                || self.check_possessive_pronoun()
13838            {
13839                // Quantified object: "John loves every woman" or "John saw a dog"
13840                let (obj_quantifier, was_definite_article) = if let Some(n) =
13841                    self.counting_np_lookahead()
13842                {
13843                    // A digit-led counting NP object ("saw 6 brown manatees"):
13844                    // consume the integer and quantify the remaining
13845                    // "(adjective)+ noun" as ∃=n, reusing the canonical
13846                    // quantified-object construction below. Without this the
13847                    // adjective is mis-read as a measure unit (→ a bogus
13848                    // Recipient role and a dropped count).
13849                    self.advance();
13850                    (Some(TokenType::Cardinal(n)), false)
13851                } else if self.check_possessive_pronoun() {
13852                    // Possessive NP object ("her dog"): parse_noun_phrase
13853                    // consumes the possessor; semantically definite.
13854                    (None, true)
13855                } else if self.check_quantifier() {
13856                    (Some(self.advance().kind.clone()), false)
13857                } else {
13858                    let art = self.advance().kind.clone();
13859                    if let TokenType::Article(def) = art {
13860                        if def == Definiteness::Indefinite {
13861                            (Some(TokenType::Some), false)
13862                        } else {
13863                            (None, true)  // Was a definite article
13864                        }
13865                    } else {
13866                        (None, false)
13867                    }
13868                };
13869
13870                // The quantifier/article/cardinal just established this as an NP,
13871                // so a verb-word head after an adjective is a deverbal noun.
13872                self.nominal_np_context = true;
13873                let object_np_result = self.parse_noun_phrase(false);
13874                self.nominal_np_context = false;
13875                let object_np = object_np_result?;
13876
13877                // Capture superlative info for constraint generation
13878                if let Some(adj) = object_np.superlative {
13879                    object_superlative = Some((adj, object_np.noun));
13880                }
13881
13882                // Capture a post-object secondary-predicate adjective (consuming it).
13883                if let TokenType::Adjective(a) = self.peek().kind {
13884                    self.advance();
13885                    secondary_adj = Some(a);
13886                }
13887
13888                // Check for distanced phrasal verb particle: "gave the book up"
13889                if let TokenType::Particle(particle_sym) = self.peek().kind {
13890                    let verb_str = self.interner.resolve(verb).to_lowercase();
13891                    let particle_str = self.interner.resolve(particle_sym).to_lowercase();
13892                    if let Some((phrasal_lemma, _class)) = crate::lexicon::lookup_phrasal_verb(&verb_str, &particle_str) {
13893                        self.advance(); // consume the particle
13894                        verb = self.interner.intern(phrasal_lemma);
13895                    }
13896                }
13897
13898                if let Some(obj_q) = obj_quantifier {
13899                    // Check for opaque verb with indefinite object (de dicto reading)
13900                    // For verbs like "seek", "want", "believe" with indefinite objects,
13901                    // use Term::Intension to represent the intensional (concept) reading
13902                    let verb_str = self.interner.resolve(verb).to_lowercase();
13903                    let is_opaque = lexicon::lookup_verb_db(&verb_str)
13904                        .map(|meta| meta.features.contains(&lexicon::Feature::Opaque))
13905                        .unwrap_or(false);
13906
13907                    // A finite verb after the NP makes it the subject of a
13908                    // CLAUSAL complement, not an object: "believes a spy
13909                    // exists" → Believe(s, ⟨∃y(Spy(y) ∧ Exist(y))⟩).
13910                    if is_opaque && self.check_verb() {
13911                        let emb_var = self.next_var_name();
13912                        let emb_restriction = self.ctx.exprs.alloc(LogicExpr::Predicate {
13913                            name: object_np.noun,
13914                            args: self.ctx.terms.alloc_slice([Term::Variable(emb_var)]),
13915                            world: None,
13916                        });
13917                        let emb_vp = self.parse_predicate_with_subject_as_var(emb_var)?;
13918                        let (emb_kind, emb_op) = match obj_q {
13919                            TokenType::All => (QuantifierKind::Universal, TokenType::Implies),
13920                            _ => (QuantifierKind::Existential, TokenType::And),
13921                        };
13922                        let emb_body = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
13923                            left: emb_restriction,
13924                            op: emb_op,
13925                            right: emb_vp,
13926                        });
13927                        let embedded = self.ctx.exprs.alloc(LogicExpr::Quantifier {
13928                            kind: emb_kind,
13929                            variable: emb_var,
13930                            body: emb_body,
13931                            island_id: self.current_island,
13932                        });
13933                        let subject_term_for_event = self.coerce_agent(&subject);
13934                        let main_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
13935                            name: verb,
13936                            args: self.ctx.terms.alloc_slice([
13937                                subject_term_for_event,
13938                                Term::Proposition(embedded),
13939                            ]),
13940                            world: None,
13941                        });
13942                        let effective_time = self.pending_time.take().unwrap_or(verb_time);
13943                        let with_time = match effective_time {
13944                            Time::Past => self.ctx.exprs.alloc(LogicExpr::Temporal {
13945                                operator: TemporalOperator::Past,
13946                                body: main_pred,
13947                            }),
13948                            _ => main_pred,
13949                        };
13950                        return self.wrap_with_definiteness_full(&subject, with_time);
13951                    }
13952
13953                    if is_opaque && matches!(obj_q, TokenType::Some) {
13954                        // De dicto reading: use Term::Intension for the theme
13955                        let intension_term = Term::Intension(object_np.noun);
13956
13957                        // Register intensional entity for anaphora resolution
13958                        let event_var = self.get_event_var();
13959                        let mut modifiers = self.collect_adverbs();
13960                        let effective_time = self.pending_time.take().unwrap_or(verb_time);
13961                        match effective_time {
13962                            Time::Past => modifiers.push(self.interner.intern("Past")),
13963                            Time::Future => modifiers.push(self.interner.intern("Future")),
13964                            _ => {}
13965                        }
13966
13967                        let subject_term_for_event = self.coerce_agent(&subject);
13968                        let roles = vec![
13969                            (ThematicRole::Agent, subject_term_for_event),
13970                            (ThematicRole::Theme, intension_term),
13971                        ];
13972
13973                        let suppress_existential = self.drs.in_conditional_antecedent();
13974                        if suppress_existential {
13975                            let event_class = self.interner.intern("Event");
13976                            self.drs.introduce_referent(event_var, event_class, Gender::Neuter, Number::Singular);
13977                        }
13978                        let neo_event = self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
13979                            event_var,
13980                            verb,
13981                            roles: self.ctx.roles.alloc_slice(roles),
13982                            modifiers: self.ctx.syms.alloc_slice(modifiers),
13983                            suppress_existential,
13984                            world: None,
13985                        })));
13986
13987                        return self.wrap_with_definiteness_full(&subject, neo_event);
13988                    }
13989
13990                    let obj_var = self.next_var_name();
13991
13992                    // Introduce object referent in DRS for cross-sentence anaphora
13993                    let obj_gender = Self::infer_noun_gender(self.interner.resolve(object_np.noun));
13994                    let obj_number = if Self::is_plural_noun(self.interner.resolve(object_np.noun)) {
13995                        Number::Plural
13996                    } else {
13997                        Number::Singular
13998                    };
13999                    // Definite descriptions presuppose existence, so they should be globally accessible
14000                    if object_np.definiteness == Some(Definiteness::Definite) {
14001                        self.drs.introduce_referent_with_source(obj_var, object_np.noun, obj_gender, obj_number, ReferentSource::MainClause);
14002                    } else {
14003                        self.drs.introduce_referent(obj_var, object_np.noun, obj_gender, obj_number);
14004                    }
14005
14006                    let mut type_pred: &'a LogicExpr<'a> = self.ctx.exprs.alloc(LogicExpr::Predicate {
14007                        name: object_np.noun,
14008                        args: self.ctx.terms.alloc_slice([Term::Variable(obj_var)]),
14009                        world: None,
14010                    });
14011
14012                    // Mass→count coercion (§6.2): a count determiner on a mass noun
14013                    // ("a water", "two waters") introduces a PORTION of the stuff:
14014                    // ∃x(Portion(x) ∧ Water(x) ∧ …). The mass noun itself stays
14015                    // non-atomic; the portion is the atom the determiner counts.
14016                    if crate::lexicon::is_mass_noun(&self.interner.resolve(object_np.noun).to_lowercase()) {
14017                        let portion_sym = self.interner.intern("Portion");
14018                        let portion_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
14019                            name: portion_sym,
14020                            args: self.ctx.terms.alloc_slice([Term::Variable(obj_var)]),
14021                            world: None,
14022                        });
14023                        type_pred = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
14024                            left: portion_pred,
14025                            op: TokenType::And,
14026                            right: type_pred,
14027                        });
14028                    }
14029
14030                    // Preserve the object's adjectives and PP restrictors ("has a
14031                    // RED book", "has a maximum range OF 475 ft") — the description
14032                    // is part of the object, and dropping it is a meaning-loss parse.
14033                    for &adj in object_np.adjectives {
14034                        let adj_pred = self.adjective_restriction(adj, obj_var, object_np.noun);
14035                        type_pred = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
14036                            left: type_pred,
14037                            op: TokenType::And,
14038                            right: adj_pred,
14039                        });
14040                    }
14041                    for pp in object_np.pps {
14042                        let pp_sub = self.substitute_pp_placeholder(pp, obj_var);
14043                        type_pred = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
14044                            left: type_pred,
14045                            op: TokenType::And,
14046                            right: pp_sub,
14047                        });
14048                    }
14049
14050                    let obj_restriction = if self.check(&TokenType::That) || self.check(&TokenType::Who) {
14051                        self.advance();
14052                        let rel_clause = self.parse_relative_clause(obj_var)?;
14053                        self.ctx.exprs.alloc(LogicExpr::BinaryOp {
14054                            left: type_pred,
14055                            op: TokenType::And,
14056                            right: rel_clause,
14057                        })
14058                    } else {
14059                        type_pred
14060                    };
14061
14062                    // Continuations inside the object quantifier's scope: a
14063                    // recipient ("gave a book to Mary") or an object-control
14064                    // infinitive ("caused a fire to spread").
14065                    let mut recipient: Option<Term<'a>> = None;
14066                    let mut control_infinitive: Option<Symbol> = None;
14067                    if self.check_to_marker() {
14068                        let after_to = self.tokens.get(self.current + 1).map(|t| t.kind.clone());
14069                        match after_to {
14070                            Some(TokenType::Verb { lemma, .. }) => {
14071                                self.advance(); // to
14072                                self.advance(); // infinitive verb
14073                                control_infinitive = Some(lemma);
14074                            }
14075                            Some(TokenType::Noun(word))
14076                                if crate::lexicon::lookup_verb_db(
14077                                    &self.interner.resolve(word).to_lowercase(),
14078                                )
14079                                .is_some() =>
14080                            {
14081                                let lemma_str = crate::lexicon::lookup_verb_db(
14082                                    &self.interner.resolve(word).to_lowercase(),
14083                                )
14084                                .map(|m| m.lemma)
14085                                .unwrap();
14086                                self.advance(); // to
14087                                self.advance(); // infinitive verb (noun-classified)
14088                                control_infinitive = Some(self.interner.intern(lemma_str));
14089                            }
14090                            Some(kind)
14091                                if Lexer::is_ditransitive_verb(&self.interner.resolve(verb))
14092                                    && matches!(
14093                                        kind,
14094                                        TokenType::ProperName(_)
14095                                            | TokenType::Noun(_)
14096                                            | TokenType::Article(_)
14097                                    ) =>
14098                            {
14099                                self.advance(); // to
14100                                let r_np = self.parse_noun_phrase(false)?;
14101                                recipient = Some(Term::Constant(r_np.noun));
14102                            }
14103                            _ => {}
14104                        }
14105                    }
14106
14107                    let event_var = self.get_event_var();
14108                    let mut modifiers = self.collect_adverbs();
14109                    let effective_time = self.pending_time.take().unwrap_or(verb_time);
14110                    match effective_time {
14111                        Time::Past => modifiers.push(self.interner.intern("Past")),
14112                        Time::Future => modifiers.push(self.interner.intern("Future")),
14113                        _ => {}
14114                    }
14115
14116                    let subject_term_for_event = self.coerce_agent(&subject);
14117                    let mut roles = vec![
14118                        (ThematicRole::Agent, subject_term_for_event),
14119                        (ThematicRole::Theme, Term::Variable(obj_var)),
14120                    ];
14121                    if let Some(r) = recipient {
14122                        roles.push((ThematicRole::Recipient, r));
14123                    }
14124
14125                    // Secondary predication over the (quantified) object variable.
14126                    if let Some(adj) = secondary_adj {
14127                        let sec_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
14128                            name: adj,
14129                            args: self.ctx.terms.alloc_slice([Term::Variable(obj_var)]),
14130                            world: None,
14131                        });
14132                        let role = secondary_role(&self.interner.resolve(verb).to_lowercase());
14133                        roles.push((role, Term::Proposition(sec_pred)));
14134                    }
14135
14136                    // Capture template with object type for ellipsis reconstruction
14137                    // Use the object noun type instead of variable for reconstruction
14138                    let template_roles = vec![
14139                        (ThematicRole::Agent, subject_term_for_event),
14140                        (ThematicRole::Theme, Term::Constant(object_np.noun)),
14141                    ];
14142                    self.capture_event_template(verb, &template_roles, &modifiers);
14143
14144                    let suppress_existential = self.drs.in_conditional_antecedent();
14145                    if suppress_existential {
14146                        let event_class = self.interner.intern("Event");
14147                        self.drs.introduce_referent(event_var, event_class, Gender::Neuter, Number::Singular);
14148                    }
14149                    let neo_event = if let Some(inf) = control_infinitive {
14150                        let inf_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
14151                            name: inf,
14152                            args: self.ctx.terms.alloc_slice([Term::Variable(obj_var)]),
14153                            world: None,
14154                        });
14155                        let control = self.ctx.exprs.alloc(LogicExpr::Control {
14156                            verb,
14157                            subject: self.ctx.terms.alloc(subject_term_for_event),
14158                            object: Some(&*self.ctx.terms.alloc(Term::Variable(obj_var))),
14159                            infinitive: inf_pred,
14160                        });
14161                        match effective_time {
14162                            Time::Past => &*self.ctx.exprs.alloc(LogicExpr::Temporal {
14163                                operator: TemporalOperator::Past,
14164                                body: control,
14165                            }),
14166                            Time::Future => &*self.ctx.exprs.alloc(LogicExpr::Temporal {
14167                                operator: TemporalOperator::Future,
14168                                body: control,
14169                            }),
14170                            _ => control,
14171                        }
14172                    } else {
14173                        let plain = &*self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
14174                            event_var,
14175                            verb,
14176                            roles: self.ctx.roles.alloc_slice(roles),
14177                            modifiers: self.ctx.syms.alloc_slice(modifiers),
14178                            suppress_existential,
14179                            world: None,
14180                        })));
14181                        // Trailing event-PP adjuncts ("takes a holiday WITH a friend
14182                        // TO some location") attach to the event INSIDE the object's
14183                        // existential scope — the definite-object path consumes these
14184                        // inline; without this they strand after an indefinite object.
14185                        self.attach_trailing_event_pps(plain, event_var)?
14186                    };
14187
14188                    let obj_kind = match obj_q {
14189                        TokenType::All => QuantifierKind::Universal,
14190                        TokenType::Some => QuantifierKind::Existential,
14191                        TokenType::No => QuantifierKind::Universal,
14192                        TokenType::Most => QuantifierKind::Most,
14193                        TokenType::Few => QuantifierKind::Few,
14194                        TokenType::Many => QuantifierKind::Many,
14195                        TokenType::Cardinal(n) => QuantifierKind::Cardinal(n),
14196                        TokenType::AtLeast(n) => QuantifierKind::AtLeast(n),
14197                        TokenType::AtMost(n) => QuantifierKind::AtMost(n),
14198                        _ => QuantifierKind::Existential,
14199                    };
14200
14201                    let obj_body = match obj_q {
14202                        TokenType::All => self.ctx.exprs.alloc(LogicExpr::BinaryOp {
14203                            left: obj_restriction,
14204                            op: TokenType::Implies,
14205                            right: neo_event,
14206                        }),
14207                        TokenType::No => {
14208                            let neg = self.ctx.exprs.alloc(LogicExpr::UnaryOp {
14209                                op: TokenType::Not,
14210                                operand: neo_event,
14211                            });
14212                            self.ctx.exprs.alloc(LogicExpr::BinaryOp {
14213                                left: obj_restriction,
14214                                op: TokenType::Implies,
14215                                right: neg,
14216                            })
14217                        }
14218                        _ => self.ctx.exprs.alloc(LogicExpr::BinaryOp {
14219                            left: obj_restriction,
14220                            op: TokenType::And,
14221                            right: neo_event,
14222                        }),
14223                    };
14224
14225                    // Wrap object with its quantifier
14226                    let obj_quantified = self.ctx.exprs.alloc(LogicExpr::Quantifier {
14227                        kind: obj_kind,
14228                        variable: obj_var,
14229                        body: obj_body,
14230                        island_id: self.current_island,
14231                    });
14232
14233                    // A trailing temporal offset after an INDEFINITE/quantified
14234                    // object ("has a birthday 4 days before Rosemarie") — the
14235                    // constraint relates the SUBJECT's position to the standard's,
14236                    // so it conjoins OUTSIDE the object quantifier. Mirrors the
14237                    // constant-object temporal check; self-guards (no-op without
14238                    // an offset).
14239                    let obj_quantified = {
14240                        let subj = self.coerce_agent(&subject);
14241                        // A trailing temporal OFFSET ("4 days before X") or a BARE
14242                        // temporal ordering ("after Inez", "sometime before Guy") after
14243                        // the quantified object relates the SUBJECT to the standard, so
14244                        // it conjoins OUTSIDE the object quantifier. Without this, an
14245                        // indefinite object ("has an appointment after Inez") stranded
14246                        // the ordering. Both self-guard (no-op when absent).
14247                        let temporal = match self.parse_temporal_offset_constraint(subj)? {
14248                            Some(off) => Some(off),
14249                            None => self.parse_bare_temporal_constraint(subj)?,
14250                        };
14251                        match temporal {
14252                            Some(t) => self.ctx.exprs.alloc(LogicExpr::BinaryOp {
14253                                left: obj_quantified,
14254                                op: TokenType::And,
14255                                right: t,
14256                            }),
14257                            None => obj_quantified,
14258                        }
14259                    };
14260                    // Now wrap the SUBJECT (don't skip it with early return!)
14261                    return self.wrap_with_definiteness_full(&subject, obj_quantified);
14262                } else {
14263                    // Definite object NP (e.g., "the house")
14264                    // Introduce to DRS for cross-sentence bridging anaphora
14265                    // E.g., "John entered the house. The door was open." - door bridges to house
14266                    // Note: was_definite_article is true because the article was consumed before parse_noun_phrase
14267                    if was_definite_article {
14268                        let obj_gender = Self::infer_noun_gender(self.interner.resolve(object_np.noun));
14269                        let obj_number = if Self::is_plural_noun(self.interner.resolve(object_np.noun)) {
14270                            Number::Plural
14271                        } else {
14272                            Number::Singular
14273                        };
14274                        // Definite descriptions presuppose existence, so they should be globally accessible
14275                        self.drs.introduce_referent_with_source(object_np.noun, object_np.noun, obj_gender, obj_number, ReferentSource::MainClause);
14276                    }
14277
14278                    let term = self.noun_phrase_to_term(&object_np);
14279                    object_term = Some(term.clone());
14280                    object_adjectives = object_np.adjectives;
14281                    object_desc_pps = object_np.pps;
14282                    args.push(term);
14283
14284                    // Ditransitive with a DEFINITE indirect object: "gave the
14285                    // winner the prize" — the definite IO took this non-quantified
14286                    // branch (a definite article carries no obj_quantifier), so the
14287                    // direct object must still be picked up here. The double-object
14288                    // builder below then assigns Recipient(IO) ∧ Theme(DO); without
14289                    // this the DO strands as a trailing token.
14290                    let verb_str = self.interner.resolve(verb);
14291                    if Lexer::is_ditransitive_verb(verb_str)
14292                        && (self.check_content_word() || self.check_article())
14293                    {
14294                        let second_np = self.parse_noun_phrase(false)?;
14295                        let second_term = Term::Constant(second_np.noun);
14296                        second_object_term = Some(second_term);
14297                        args.push(second_term);
14298                    }
14299                }
14300            } else if self.check_focus() {
14301                let focus_kind = if let TokenType::Focus(k) = self.advance().kind {
14302                    k
14303                } else {
14304                    FocusKind::Only
14305                };
14306
14307                let event_var = self.get_event_var();
14308                let mut modifiers = self.collect_adverbs();
14309                let effective_time = self.pending_time.take().unwrap_or(verb_time);
14310                match effective_time {
14311                    Time::Past => modifiers.push(self.interner.intern("Past")),
14312                    Time::Future => modifiers.push(self.interner.intern("Future")),
14313                    _ => {}
14314                }
14315
14316                let subject_term_for_event = self.coerce_agent(&subject);
14317
14318                if self.check_preposition() {
14319                    let prep_token = self.advance().clone();
14320                    let prep_name = if let TokenType::Preposition(sym) = prep_token.kind {
14321                        sym
14322                    } else {
14323                        self.interner.intern("to")
14324                    };
14325                    let pp_obj = self.parse_noun_phrase(false)?;
14326                    let pp_obj_term = Term::Constant(pp_obj.noun);
14327
14328                    let roles = vec![(ThematicRole::Agent, subject_term_for_event)];
14329                    let suppress_existential = self.drs.in_conditional_antecedent();
14330                    if suppress_existential {
14331                        let event_class = self.interner.intern("Event");
14332                        self.drs.introduce_referent(event_var, event_class, Gender::Neuter, Number::Singular);
14333                    }
14334                    let neo_event = self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
14335                        event_var,
14336                        verb,
14337                        roles: self.ctx.roles.alloc_slice(roles),
14338                        modifiers: self.ctx.syms.alloc_slice(modifiers),
14339                        suppress_existential,
14340                        world: None,
14341                    })));
14342
14343                    let pp_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
14344                        name: prep_name,
14345                        args: self.ctx.terms.alloc_slice([Term::Variable(event_var), pp_obj_term]),
14346                        world: None,
14347                    });
14348
14349                    let with_pp = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
14350                        left: neo_event,
14351                        op: TokenType::And,
14352                        right: pp_pred,
14353                    });
14354
14355                    let focused_ref = self.ctx.terms.alloc(pp_obj_term);
14356                    return Ok(self.ctx.exprs.alloc(LogicExpr::Focus {
14357                        kind: focus_kind,
14358                        focused: focused_ref,
14359                        scope: with_pp,
14360                    }));
14361                }
14362
14363                let focused_np = self.parse_noun_phrase(false)?;
14364                let focused_term = self.noun_phrase_to_term(&focused_np);
14365                args.push(focused_term.clone());
14366
14367                let roles = vec![
14368                    (ThematicRole::Agent, subject_term_for_event),
14369                    (ThematicRole::Theme, focused_term.clone()),
14370                ];
14371
14372                let suppress_existential = self.drs.in_conditional_antecedent();
14373                if suppress_existential {
14374                    let event_class = self.interner.intern("Event");
14375                    self.drs.introduce_referent(event_var, event_class, Gender::Neuter, Number::Singular);
14376                }
14377                let neo_event = self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
14378                    event_var,
14379                    verb,
14380                    roles: self.ctx.roles.alloc_slice(roles),
14381                    modifiers: self.ctx.syms.alloc_slice(modifiers),
14382                    suppress_existential,
14383                    world: None,
14384                })));
14385
14386                let focused_ref = self.ctx.terms.alloc(focused_term);
14387                return Ok(self.ctx.exprs.alloc(LogicExpr::Focus {
14388                    kind: focus_kind,
14389                    focused: focused_ref,
14390                    scope: neo_event,
14391                }));
14392            } else if self.check_number() {
14393                // Handle "has 3 children" or "has cardinality aleph_0"
14394                let measure = self.parse_measure_phrase()?;
14395
14396                // If there's a noun after the measure (for "3 children" where
14397                // children wasn't a unit). An ARTICLE is NOT that noun — it begins
14398                // a rate denominator ("$700 A month", handled below) — so leave it.
14399                if self.check_content_word() && !self.check_article() {
14400                    let noun_sym = self.consume_content_word()?;
14401                    // Build: Has(Subject, 3, Children) where 3 is the count
14402                    let count_term = *measure;
14403                    object_term = Some(count_term.clone());
14404                    args.push(count_term);
14405                    second_object_term = Some(Term::Constant(noun_sym));
14406                    args.push(Term::Constant(noun_sym));
14407                } else {
14408                    // Just the measure: "has cardinality 5"
14409                    object_term = Some(*measure);
14410                    args.push(*measure);
14411                }
14412            } else if self.check_content_word() || self.check_article() {
14413                let object = self.parse_noun_phrase(false)?;
14414                if let Some(adj) = object.superlative {
14415                    object_superlative = Some((adj, object.noun));
14416                }
14417
14418                // Collect all objects for potential "respectively" handling
14419                let mut all_objects: Vec<Symbol> = vec![object.noun];
14420
14421                // Check for coordinated objects: "Tom and Jerry and Bob"
14422                while self.check(&TokenType::And) {
14423                    let saved = self.current;
14424                    self.advance(); // consume "and"
14425                    if self.check_content_word() || self.check_article() {
14426                        let next_obj = match self.parse_noun_phrase(false) {
14427                            Ok(np) => np,
14428                            Err(_) => {
14429                                self.current = saved;
14430                                break;
14431                            }
14432                        };
14433                        all_objects.push(next_obj.noun);
14434                    } else {
14435                        self.current = saved;
14436                        break;
14437                    }
14438                }
14439
14440                // Check for "respectively" with single subject
14441                if self.check(&TokenType::Respectively) {
14442                    let respectively_span = self.peek().span;
14443                    // Single subject with multiple objects + respectively = error
14444                    if all_objects.len() > 1 {
14445                        return Err(ParseError {
14446                            kind: ParseErrorKind::RespectivelyLengthMismatch {
14447                                subject_count: 1,
14448                                object_count: all_objects.len(),
14449                            },
14450                            span: respectively_span,
14451                        });
14452                    }
14453                    // Single subject, single object + respectively is valid (trivially pairwise)
14454                    self.advance(); // consume "respectively"
14455                }
14456
14457                // Use the first object (or only object) for normal processing
14458                let term = self.noun_phrase_to_term(&object);
14459                object_term = Some(term.clone());
14460                args.push(term.clone());
14461
14462                // For multiple objects without "respectively", use group semantics
14463                if all_objects.len() > 1 {
14464                    let obj_members: Vec<Term<'a>> = all_objects.iter()
14465                        .map(|o| Term::Constant(*o))
14466                        .collect();
14467                    let obj_group = Term::Group(self.ctx.terms.alloc_slice(obj_members));
14468                    // Replace the single object with the group
14469                    args.pop();
14470                    args.push(obj_group);
14471                }
14472
14473                // Check for distanced phrasal verb particle: "gave the book up"
14474                if let TokenType::Particle(particle_sym) = self.peek().kind {
14475                    let verb_str = self.interner.resolve(verb).to_lowercase();
14476                    let particle_str = self.interner.resolve(particle_sym).to_lowercase();
14477                    if let Some((phrasal_lemma, _class)) = crate::lexicon::lookup_phrasal_verb(&verb_str, &particle_str) {
14478                        self.advance(); // consume the particle
14479                        verb = self.interner.intern(phrasal_lemma);
14480                    }
14481                }
14482
14483                // Check for "has cardinality aleph_0" pattern: noun followed by number
14484                if self.check_number() {
14485                    let measure = self.parse_measure_phrase()?;
14486                    second_object_term = Some(*measure);
14487                    args.push(*measure);
14488                }
14489                // Check for ditransitive: "John gave Mary a book"
14490                else {
14491                    let verb_str = self.interner.resolve(verb);
14492                    if Lexer::is_ditransitive_verb(verb_str) && (self.check_content_word() || self.check_article()) {
14493                        let second_np = self.parse_noun_phrase(false)?;
14494                        let second_term = self.noun_phrase_to_term(&second_np);
14495                        second_object_term = Some(second_term.clone());
14496                        args.push(second_term);
14497                    }
14498                }
14499            }
14500
14501            let mut pp_predicates: Vec<&'a LogicExpr<'a>> = Vec::new();
14502
14503            // Rate denominator after a MEASURE object ("$700 A month", "5 miles
14504            // PER hour", "$2.50 PER pound") → Per(event, unit), a solver-ready
14505            // rate. "a"/"an" or "per" + a CalendarUnit/unit-noun. Guarded to a
14506            // measure object so a bare "a <noun>" elsewhere is not a false rate.
14507            if matches!(object_term, Some(Term::Value { .. })) {
14508                let rate = self.check_preposition_is("per")
14509                    || (matches!(
14510                        self.peek().kind,
14511                        TokenType::Article(Definiteness::Indefinite)
14512                    ) && matches!(
14513                        self.tokens.get(self.current + 1).map(|t| &t.kind),
14514                        Some(TokenType::CalendarUnit(_)) | Some(TokenType::Noun(_))
14515                    ));
14516                if rate {
14517                    self.advance(); // "per" / "a"
14518                    let unit_lexeme = self.peek().lexeme;
14519                    self.advance(); // the unit
14520                    let unit_cap = {
14521                        let s = self.interner.resolve(unit_lexeme).to_string();
14522                        let mut c = s.chars();
14523                        match c.next() {
14524                            Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
14525                            None => s,
14526                        }
14527                    };
14528                    let event_sym = self.get_event_var();
14529                    pp_predicates.push(self.ctx.exprs.alloc(LogicExpr::Predicate {
14530                        name: self.interner.intern("Per"),
14531                        args: self.ctx.terms.alloc_slice([
14532                            Term::Variable(event_sym),
14533                            Term::Constant(self.interner.intern(&unit_cap)),
14534                        ]),
14535                        world: None,
14536                    }));
14537                }
14538            }
14539
14540            while self.check_preposition() || self.check_to() {
14541                // "within N cycles" is a temporal bound, not a PP
14542                if self.check_preposition_is("within") && self.current + 1 < self.tokens.len()
14543                    && matches!(self.tokens[self.current + 1].kind, TokenType::Cardinal(_) | TokenType::Number(_))
14544                {
14545                    break;
14546                }
14547                let prep_token = self.advance().clone();
14548                let prep_name = if let TokenType::Preposition(sym) = prep_token.kind {
14549                    sym
14550                } else if matches!(prep_token.kind, TokenType::To) {
14551                    self.interner.intern("To")
14552                } else {
14553                    continue;
14554                };
14555
14556                let pp_obj_term = if self.check(&TokenType::Reflexive) {
14557                    self.advance();
14558                    self.noun_phrase_to_term(&subject)
14559                } else if self.check_pronoun() {
14560                    let token = self.advance().clone();
14561                    if let TokenType::Pronoun { gender, number, .. } = token.kind {
14562                        let resolved = self.resolve_pronoun(gender, number)?;
14563                        match resolved {
14564                            ResolvedPronoun::Variable(s) => Term::Variable(s),
14565                            ResolvedPronoun::Constant(s) => Term::Constant(s),
14566                        }
14567                    } else {
14568                        continue;
14569                    }
14570                } else if self.check_content_word() || self.check_article() {
14571                    let prep_obj = self.parse_noun_phrase(false)?;
14572                    self.noun_phrase_to_term(&prep_obj)
14573                } else if self.check_number() {
14574                    // Numeric PP object ("sold for $105", "sell for $2.60").
14575                    *self.parse_measure_phrase()?
14576                } else if self.at_clause_boundary()
14577                    && crate::lexicon::is_particle(
14578                        &self.interner.resolve(prep_name).to_lowercase(),
14579                    )
14580                {
14581                    // A clause-final object-less PARTICLE preposition is an
14582                    // intransitive directional ("walked in", "sat down") — a
14583                    // lexically listed class; "of"/"to" cannot end a clause.
14584                    let event_sym = self.get_event_var();
14585                    pp_predicates.push(self.ctx.exprs.alloc(LogicExpr::Predicate {
14586                        name: prep_name,
14587                        args: self.ctx.terms.alloc_slice([Term::Variable(event_sym)]),
14588                        world: None,
14589                    }));
14590                    continue;
14591                } else {
14592                    // A mid-clause preposition with no object is not a PP —
14593                    // hand it back so the sentence-level parse reports it
14594                    // instead of silently dropping it.
14595                    self.current -= 1;
14596                    break;
14597                };
14598
14599                if self.pp_attach_to_noun {
14600                    if let Some(ref obj) = object_term {
14601                        // NP-attachment: PP modifies the object noun
14602                        let pp_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
14603                            name: prep_name,
14604                            args: self.ctx.terms.alloc_slice([obj.clone(), pp_obj_term]),
14605                            world: None,
14606                        });
14607                        pp_predicates.push(pp_pred);
14608                    } else {
14609                        args.push(pp_obj_term);
14610                    }
14611                } else {
14612                    // VP-attachment: PP modifies the event (instrument/manner)
14613                    let event_sym = self.get_event_var();
14614                    let pp_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
14615                        name: prep_name,
14616                        args: self.ctx.terms.alloc_slice([Term::Variable(event_sym), pp_obj_term]),
14617                        world: None,
14618                    });
14619                    pp_predicates.push(pp_pred);
14620                }
14621
14622                // Manner adverbs interleaved with PPs ("with a key quietly in
14623                // the house") modify the same event; absorbing them here lets
14624                // the loop continue to the next PP.
14625                for adv in self.collect_adverbs() {
14626                    let event_sym = self.get_event_var();
14627                    pp_predicates.push(self.ctx.exprs.alloc(LogicExpr::Predicate {
14628                        name: adv,
14629                        args: self.ctx.terms.alloc_slice([Term::Variable(event_sym)]),
14630                        world: None,
14631                    }));
14632                }
14633            }
14634
14635            // Check for trailing relative clause on object NP: "the girl with the telescope that laughed"
14636            if self.check(&TokenType::That) || self.check(&TokenType::Who) {
14637                self.advance();
14638                let rel_var = self.next_var_name();
14639                let rel_pred = self.parse_relative_clause(rel_var)?;
14640                pp_predicates.push(rel_pred);
14641            }
14642
14643            // A trailing temporal offset on the VP ("has a birthday 4 days before
14644            // Rosemarie", "set sail 2 years after Glenda") — N <unit> after/before
14645            // STANDARD relates the subject's position to the standard's
14646            // (solver-ready add/sub). It trails the object, so it is checked here
14647            // after the object/PP loop. A BARE ordering with no count ("won the prize
14648            // before the winner who won in chemistry", "graduated before Orlando")
14649            // takes the same slot — the indefinite-object path already chains both, so
14650            // the definite/possessive-object path (which falls through here) must too,
14651            // or the ordering strands (TrailingTokens at Before/After). Both
14652            // self-guard (return None without consuming unless the pattern matches).
14653            {
14654                let subj = self.coerce_agent(&subject);
14655                let off = match self.parse_temporal_offset_constraint(subj)? {
14656                    Some(o) => Some(o),
14657                    None => self.parse_bare_temporal_constraint(subj)?,
14658                };
14659                if let Some(off) = off {
14660                    pp_predicates.push(off);
14661                }
14662            }
14663
14664            // Collect any trailing adverbs FIRST (before building NeoEvent)
14665            let mut modifiers = self.collect_adverbs();
14666
14667            // Add temporal modifier as part of event semantics
14668            let effective_time = self.pending_time.take().unwrap_or(verb_time);
14669            match effective_time {
14670                Time::Past => modifiers.push(self.interner.intern("Past")),
14671                Time::Future => modifiers.push(self.interner.intern("Future")),
14672                _ => {}
14673            }
14674
14675            // Add aspect modifier if applicable
14676            if verb_aspect == Aspect::Progressive {
14677                modifiers.push(self.interner.intern("Progressive"));
14678            } else if verb_aspect == Aspect::Perfect {
14679                modifiers.push(self.interner.intern("Perfect"));
14680            }
14681
14682            // Build thematic roles for Neo-Davidsonian event semantics
14683            let mut roles: Vec<(ThematicRole, Term<'a>)> = Vec::new();
14684
14685            // Check if verb is unaccusative (intransitive subject is Theme, not Agent)
14686            let verb_str_for_check = self.interner.resolve(verb).to_lowercase();
14687            let is_unaccusative = crate::lexicon::lookup_verb_db(&verb_str_for_check)
14688                .map(|meta| meta.features.contains(&crate::lexicon::Feature::Unaccusative))
14689                .unwrap_or(false);
14690
14691            // Unaccusative verbs used intransitively: subject is Theme
14692            let has_object = object_term.is_some() || second_object_term.is_some();
14693            let subject_role = if is_unaccusative && !has_object {
14694                ThematicRole::Theme
14695            } else {
14696                ThematicRole::Agent
14697            };
14698
14699            roles.push((subject_role, subject_term));
14700            if let Some(second_obj) = second_object_term {
14701                // Ditransitive: first object is Recipient, second is Theme
14702                if let Some(first_obj) = object_term {
14703                    roles.push((ThematicRole::Recipient, first_obj));
14704                }
14705                roles.push((ThematicRole::Theme, second_obj));
14706            } else if let Some(obj) = object_term {
14707                // Normal transitive: object is Theme
14708                roles.push((ThematicRole::Theme, obj));
14709            }
14710
14711            // Secondary predication: attach the captured post-object adjective as a
14712            // Result/Depictive role over the object ("painted the door red").
14713            if let (Some(adj), Some(obj)) = (secondary_adj, object_term) {
14714                let sec_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
14715                    name: adj,
14716                    args: self.ctx.terms.alloc_slice([obj]),
14717                    world: None,
14718                });
14719                let role = secondary_role(&self.interner.resolve(verb).to_lowercase());
14720                roles.push((role, Term::Proposition(sec_pred)));
14721            }
14722
14723            // Create event variable
14724            let event_var = self.get_event_var();
14725
14726            // Capture template for ellipsis reconstruction before consuming roles
14727            self.capture_event_template(verb, &roles, &modifiers);
14728
14729            // Create NeoEvent structure with all modifiers including time/aspect
14730            let suppress_existential = self.drs.in_conditional_antecedent();
14731            if suppress_existential {
14732                let event_class = self.interner.intern("Event");
14733                self.drs.introduce_referent(event_var, event_class, Gender::Neuter, Number::Singular);
14734            }
14735            let neo_event = self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
14736                event_var,
14737                verb,
14738                roles: self.ctx.roles.alloc_slice(roles),
14739                modifiers: self.ctx.syms.alloc_slice(modifiers),
14740                suppress_existential,
14741                world: None,
14742            })));
14743
14744            // Combine with PP predicates if any
14745            let with_pps = if pp_predicates.is_empty() {
14746                neo_event
14747            } else {
14748                let mut combined = neo_event;
14749                for pp in pp_predicates {
14750                    combined = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
14751                        left: combined,
14752                        op: TokenType::And,
14753                        right: pp,
14754                    });
14755                }
14756                combined
14757            };
14758
14759            // Predicate a definite/constant object's adjectives + PP restrictors
14760            // of it ("ate the RED apple" → Red(Apple); "is the frat ON Holly
14761            // Street" → On(·, Holly Street)). Dropping them is meaning loss.
14762            let with_pps = if let Some(obj_term) = object_term {
14763                let placeholder = self.interner.intern("_PP_SELF_");
14764                let mut combined = with_pps;
14765                for &adj in object_adjectives {
14766                    let adj_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
14767                        name: adj,
14768                        args: self.ctx.terms.alloc_slice([obj_term]),
14769                        world: None,
14770                    });
14771                    combined = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
14772                        left: combined,
14773                        op: TokenType::And,
14774                        right: adj_pred,
14775                    });
14776                }
14777                for pp in object_desc_pps {
14778                    if let LogicExpr::Predicate { name, args, world } = pp {
14779                        let new_args: Vec<Term<'a>> = args
14780                            .iter()
14781                            .map(|a| match a {
14782                                Term::Variable(v) if *v == placeholder => obj_term,
14783                                other => *other,
14784                            })
14785                            .collect();
14786                        let sub = self.ctx.exprs.alloc(LogicExpr::Predicate {
14787                            name: *name,
14788                            args: self.ctx.terms.alloc_slice(new_args),
14789                            world: *world,
14790                        });
14791                        combined = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
14792                            left: combined,
14793                            op: TokenType::And,
14794                            right: sub,
14795                        });
14796                    }
14797                }
14798                combined
14799            } else {
14800                with_pps
14801            };
14802
14803            // Apply aspectual operators based on verb class
14804            let with_aspect = if verb_aspect == Aspect::Progressive {
14805                // Semelfactive + Progressive → Iterative
14806                if verb_class == crate::lexicon::VerbClass::Semelfactive {
14807                    self.ctx.exprs.alloc(LogicExpr::Aspectual {
14808                        operator: AspectOperator::Iterative,
14809                        body: with_pps,
14810                    })
14811                } else {
14812                    // Other verbs + Progressive → Progressive
14813                    self.ctx.exprs.alloc(LogicExpr::Aspectual {
14814                        operator: AspectOperator::Progressive,
14815                        body: with_pps,
14816                    })
14817                }
14818            } else if verb_aspect == Aspect::Perfect {
14819                self.ctx.exprs.alloc(LogicExpr::Aspectual {
14820                    operator: AspectOperator::Perfect,
14821                    body: with_pps,
14822                })
14823            } else if effective_time == Time::Present && verb_aspect == Aspect::Simple {
14824                // Non-state verbs in simple present get Habitual reading
14825                if !verb_class.is_stative() {
14826                    self.ctx.exprs.alloc(LogicExpr::Aspectual {
14827                        operator: AspectOperator::Habitual,
14828                        body: with_pps,
14829                    })
14830                } else {
14831                    // State verbs in present: direct predication
14832                    with_pps
14833                }
14834            } else {
14835                with_pps
14836            };
14837
14838            let with_adverbs = with_aspect;
14839
14840            // Check for temporal anchor adverb at end of sentence
14841            let with_temporal = if self.check_temporal_adverb() {
14842                let anchor = if let TokenType::TemporalAdverb(adv) = self.advance().kind.clone() {
14843                    adv
14844                } else {
14845                    panic!("Expected temporal adverb");
14846                };
14847                self.ctx.exprs.alloc(LogicExpr::TemporalAnchor {
14848                    anchor,
14849                    body: with_adverbs,
14850                })
14851            } else {
14852                with_adverbs
14853            };
14854
14855            let wrapped = self.wrap_with_definiteness_full(&subject, with_temporal)?;
14856
14857            // Add superlative constraint for object NP if applicable
14858            if let Some((adj, noun)) = object_superlative {
14859                let superlative_expr = self.ctx.exprs.alloc(LogicExpr::Superlative {
14860                    adjective: adj,
14861                    subject: self.ctx.terms.alloc(Term::Constant(noun)),
14862                    domain: noun,
14863                });
14864                return Ok(self.ctx.exprs.alloc(LogicExpr::BinaryOp {
14865                    left: wrapped,
14866                    op: TokenType::And,
14867                    right: superlative_expr,
14868                }));
14869            }
14870
14871            return Ok(wrapped);
14872        }
14873
14874        Ok(self.ctx.exprs.alloc(LogicExpr::Atom(subject.noun)))
14875    }
14876
14877    fn check_preposition(&self) -> bool {
14878        matches!(self.peek().kind, TokenType::Preposition(_))
14879    }
14880
14881    fn check_by_preposition(&self) -> bool {
14882        if let TokenType::Preposition(p) = self.peek().kind {
14883            p.is(self.interner, "by")
14884        } else {
14885            false
14886        }
14887    }
14888
14889    fn check_preposition_is(&self, word: &str) -> bool {
14890        if let TokenType::Preposition(p) = self.peek().kind {
14891            p.is(self.interner, word)
14892        } else {
14893            false
14894        }
14895    }
14896
14897    /// Check if current token is a word (noun/adj/verb lexeme) matching the given string
14898    fn check_word(&self, word: &str) -> bool {
14899        let token = self.peek();
14900        // A string/char LITERAL is a value, never a keyword — a `"not"` literal
14901        // must not be mistaken for the `not` operator (which the lexeme match
14902        // would otherwise do, breaking `Show "not".`).
14903        if matches!(token.kind, TokenType::StringLiteral(_) | TokenType::CharLiteral(_)) {
14904            return false;
14905        }
14906        let lexeme = self.interner.resolve(token.lexeme);
14907        lexeme.eq_ignore_ascii_case(word)
14908    }
14909
14910    /// Like [`check_word`], but declines when the current token is a bound user
14911    /// variable — an English arithmetic operator word (`plus`, `times`, …) must
14912    /// yield to a variable of the same name (declarer-wins), so `Let times be 5`
14913    /// keeps `times` an identifier rather than the multiply operator.
14914    fn check_op_word(&self, word: &str) -> bool {
14915        self.check_word(word) && !self.user_bound.contains(&self.peek().lexeme)
14916    }
14917
14918    fn peek_word_at(&self, offset: usize, word: &str) -> bool {
14919        if self.current + offset >= self.tokens.len() {
14920            return false;
14921        }
14922        let token = &self.tokens[self.current + offset];
14923        if matches!(token.kind, TokenType::StringLiteral(_) | TokenType::CharLiteral(_)) {
14924            return false;
14925        }
14926        let lexeme = self.interner.resolve(token.lexeme);
14927        lexeme.eq_ignore_ascii_case(word)
14928    }
14929
14930    fn check_to_preposition(&self) -> bool {
14931        match self.peek().kind {
14932            TokenType::To => true,
14933            TokenType::Preposition(p) => p.is(self.interner, "to"),
14934            _ => false,
14935        }
14936    }
14937
14938    fn check_content_word(&self) -> bool {
14939        match &self.peek().kind {
14940            TokenType::Noun(_)
14941            | TokenType::Adjective(_)
14942            | TokenType::NonIntersectiveAdjective(_)
14943            | TokenType::Verb { .. }
14944            | TokenType::ProperName(_)
14945            | TokenType::Article(_)
14946            // A quoted string in NL prose is a name ("the \"Yellownose\"").
14947            | TokenType::StringLiteral(_)
14948            // "May" the MONTH collides with the modal token; in a content-word
14949            // position ("in May", "on May 3") it is the month noun.
14950            | TokenType::May
14951            // A numeric literal heads/fills a noun position ("equal to 42",
14952            // "2001, 2002, … are years") — consume_content_word already returns it.
14953            | TokenType::Number(_)
14954            | TokenType::Performative(_) => true,
14955            // "item"/"items" are LOGOS collection-index keywords, but in
14956            // declarative NL prose they are ordinary nouns ("three different
14957            // items", "the item is red"). The lexer keeps the keyword tokens (so
14958            // imperative index syntax still lexes); the declarative parser reads
14959            // them as nouns here.
14960            TokenType::Item | TokenType::Items if self.mode == ParserMode::Declarative => true,
14961            // Calendar-unit words ("year", "day", "week") are common nouns in a
14962            // noun position ("four different years", "the year was 2001"); the
14963            // lexer tokenizes them as CalendarUnit for the temporal grammar.
14964            TokenType::CalendarUnit(_) if self.mode == ParserMode::Declarative => true,
14965            TokenType::Ambiguous { primary, alternatives } => {
14966                Self::is_content_word_type(primary)
14967                    || alternatives.iter().any(Self::is_content_word_type)
14968            }
14969            _ => false,
14970        }
14971    }
14972
14973    fn is_content_word_type(t: &TokenType) -> bool {
14974        matches!(
14975            t,
14976            TokenType::Noun(_)
14977                | TokenType::Adjective(_)
14978                | TokenType::NonIntersectiveAdjective(_)
14979                | TokenType::Verb { .. }
14980                | TokenType::ProperName(_)
14981                | TokenType::Article(_)
14982                | TokenType::StringLiteral(_)
14983                | TokenType::Performative(_)
14984        )
14985    }
14986
14987    /// Check for trailing "within N cycles" and wrap in BoundedEventually.
14988    pub(super) fn try_wrap_bounded_delay(&mut self, expr: &'a LogicExpr<'a>) -> &'a LogicExpr<'a> {
14989        if !self.check_preposition_is("within") {
14990            return expr;
14991        }
14992        let has_number = if self.current + 1 < self.tokens.len() {
14993            matches!(self.tokens[self.current + 1].kind, TokenType::Cardinal(_) | TokenType::Number(_))
14994        } else {
14995            false
14996        };
14997        if !has_number {
14998            return expr;
14999        }
15000        self.advance(); // consume "within"
15001        let bound = match self.advance().kind {
15002            TokenType::Cardinal(n) => n,
15003            TokenType::Number(sym) => {
15004                let n_str = self.interner.resolve(sym);
15005                n_str.parse::<u32>().unwrap_or(1)
15006            }
15007            _ => 1,
15008        };
15009        // Consume optional "cycle" / "cycles"
15010        if self.check_content_word() {
15011            let word = self.interner.resolve(self.peek().lexeme).to_lowercase();
15012            if word == "cycle" || word == "cycles" {
15013                self.advance();
15014            }
15015        }
15016        self.ctx.exprs.alloc(LogicExpr::Temporal {
15017            operator: TemporalOperator::BoundedEventually(bound),
15018            body: expr,
15019        })
15020    }
15021
15022    /// Whether the preposition at the cursor introduces a CYCLE-temporal delay
15023    /// ("in the next cycle", "within 3 cycles", "after 2 cycles") rather than a
15024    /// locative/temporal PP adjunct. Such constructs are left for the SVA temporal
15025    /// wrappers (`try_wrap_next_cycle` / `try_wrap_within_cycles`), so the embedded
15026    /// VP parser's PP-adjunct loop must NOT swallow them.
15027    pub(super) fn pp_is_cycle_temporal(&self) -> bool {
15028        let prep = match &self.peek().kind {
15029            TokenType::Preposition(s) => self.interner.resolve(*s).to_lowercase(),
15030            _ => return false,
15031        };
15032        if !matches!(prep.as_str(), "in" | "within" | "after") {
15033            return false;
15034        }
15035        let mut i = self.current + 1;
15036        let end = (self.current + 5).min(self.tokens.len());
15037        while i < end {
15038            match &self.tokens[i].kind {
15039                TokenType::Period | TokenType::Comma | TokenType::EOF => return false,
15040                _ => {
15041                    let w = self.interner.resolve(self.tokens[i].lexeme).to_lowercase();
15042                    if matches!(w.as_str(), "cycle" | "cycles" | "tick" | "ticks") {
15043                        return true;
15044                    }
15045                }
15046            }
15047            i += 1;
15048        }
15049        false
15050    }
15051
15052    /// Check for a trailing "next cycle" — both "in the next cycle" and the bare adverbial form
15053    /// "…grant is high next cycle." — and wrap in Temporal::Next.
15054    pub(super) fn try_wrap_next_cycle(&mut self, expr: &'a LogicExpr<'a>) -> &'a LogicExpr<'a> {
15055        // Bare "next cycle" (no preposition): "next" immediately followed by "cycle".
15056        let bare_next_cycle = self.interner.resolve(self.peek().lexeme).eq_ignore_ascii_case("next")
15057            && self
15058                .tokens
15059                .get(self.current + 1)
15060                .map(|t| self.interner.resolve(t.lexeme).eq_ignore_ascii_case("cycle"))
15061                .unwrap_or(false);
15062        if bare_next_cycle {
15063            self.advance(); // next
15064            self.advance(); // cycle
15065            return self.ctx.exprs.alloc(LogicExpr::Temporal {
15066                operator: TemporalOperator::Next,
15067                body: expr,
15068            });
15069        }
15070        if !self.check_preposition_is("in") {
15071            return expr;
15072        }
15073        // Lookahead: "in" (the)? next cycle — bail untouched otherwise.
15074        let mut i = self.current + 1;
15075        if matches!(self.tokens.get(i).map(|t| &t.kind), Some(TokenType::Article(_))) {
15076            i += 1;
15077        }
15078        let next_matches = self
15079            .tokens
15080            .get(i)
15081            .map(|t| self.interner.resolve(t.lexeme).eq_ignore_ascii_case("next"))
15082            .unwrap_or(false);
15083        let cycle_matches = self
15084            .tokens
15085            .get(i + 1)
15086            .map(|t| self.interner.resolve(t.lexeme).eq_ignore_ascii_case("cycle"))
15087            .unwrap_or(false);
15088        if !next_matches || !cycle_matches {
15089            return expr;
15090        }
15091        while self.current <= i + 1 {
15092            self.advance();
15093        }
15094        self.ctx.exprs.alloc(LogicExpr::Temporal {
15095            operator: TemporalOperator::Next,
15096            body: expr,
15097        })
15098    }
15099
15100    fn check_verb(&self) -> bool {
15101        self.kind_is_verb(&self.peek().kind)
15102    }
15103
15104    /// Verb-ness of an arbitrary token kind, mirroring `check_verb` so a look-ahead
15105    /// token (e.g. the participle after a leading adverb) is judged the same way the
15106    /// current token is — an `Ambiguous` token counts when a verb reading is live and
15107    /// noun-priority is off.
15108    fn kind_is_verb(&self, kind: &TokenType) -> bool {
15109        match kind {
15110            TokenType::Verb { .. } => true,
15111            TokenType::Ambiguous { primary, alternatives } => {
15112                if self.noun_priority_mode {
15113                    return false;
15114                }
15115                matches!(**primary, TokenType::Verb { .. })
15116                    || alternatives.iter().any(|t| matches!(t, TokenType::Verb { .. }))
15117            }
15118            _ => false,
15119        }
15120    }
15121
15122    fn check_adverb(&self) -> bool {
15123        matches!(self.peek().kind, TokenType::Adverb(_))
15124    }
15125
15126    fn check_performative(&self) -> bool {
15127        matches!(self.peek().kind, TokenType::Performative(_))
15128    }
15129
15130    /// Resolve an optional direct object following a performative or its
15131    /// infinitival complement into a term. Deictic objects anchor to the
15132    /// discourse roles ("you" → Addressee, "me"/"myself" → Speaker); a proper
15133    /// name yields its constant. Returns `None` when no object NP follows.
15134    fn try_performative_object(&mut self) -> Option<Term<'a>> {
15135        match self.peek().kind {
15136            TokenType::Pronoun { .. } => {
15137                let text = self.interner.resolve(self.peek().lexeme).to_lowercase();
15138                let sym = match text.as_str() {
15139                    "you" => self.interner.intern("Addressee"),
15140                    "me" | "myself" => self.interner.intern("Speaker"),
15141                    other => {
15142                        let cap = other
15143                            .chars()
15144                            .next()
15145                            .map(|c| c.to_uppercase().collect::<String>() + &other[1..])
15146                            .unwrap_or_default();
15147                        self.interner.intern(&cap)
15148                    }
15149                };
15150                self.advance();
15151                Some(Term::Constant(sym))
15152            }
15153            TokenType::ProperName(s) => {
15154                self.advance();
15155                Some(Term::Constant(s))
15156            }
15157            _ => None,
15158        }
15159    }
15160
15161    /// Build the complement predicate `verb(subject [, object])` for a
15162    /// performative, capturing an optional direct object. Used both for the
15163    /// infinitival complement ("promise to call you" → `Call(speaker, hearer)`)
15164    /// and for the bare performative act ("thank you" → `Thank(speaker, hearer)`).
15165    fn performative_predicate(&mut self, verb: Symbol, subject: Symbol) -> &'a LogicExpr<'a> {
15166        if let Some(obj) = self.try_performative_object() {
15167            self.ctx.exprs.alloc(LogicExpr::Predicate {
15168                name: verb,
15169                args: self.ctx.terms.alloc_slice([Term::Constant(subject), obj]),
15170                world: None,
15171            })
15172        } else {
15173            self.ctx.exprs.alloc(LogicExpr::Predicate {
15174                name: verb,
15175                args: self.ctx.terms.alloc_slice([Term::Constant(subject)]),
15176                world: None,
15177            })
15178        }
15179    }
15180
15181    fn collect_adverbs(&mut self) -> Vec<Symbol> {
15182        let mut adverbs = Vec::new();
15183        loop {
15184            if self.check_adverb() {
15185                if let TokenType::Adverb(adv) = self.advance().kind.clone() {
15186                    adverbs.push(adv);
15187                }
15188                // Skip "and" between adverbs
15189                if self.check(&TokenType::And) {
15190                    self.advance();
15191                }
15192                continue;
15193            }
15194            // A trailing bare particle is a DIRECTIONAL event modifier
15195            // ("He sat down." → Sit(e) ∧ Down(e)). Phrasal-verb particles
15196            // ("gave the book up") are consumed by the phrasal lookup before
15197            // this point, so a particle still present here is unclaimed.
15198            if let TokenType::Particle(p) = self.peek().kind {
15199                if matches!(
15200                    self.tokens.get(self.current + 1).map(|t| &t.kind),
15201                    Some(TokenType::Period) | Some(TokenType::EOF) | None
15202                ) {
15203                    self.advance();
15204                    let raw = self.interner.resolve(p).to_string();
15205                    let mut capitalized = raw;
15206                    if let Some(first) = capitalized.get_mut(0..1) {
15207                        first.make_ascii_uppercase();
15208                    }
15209                    adverbs.push(self.interner.intern(&capitalized));
15210                    continue;
15211                }
15212            }
15213            break;
15214        }
15215        adverbs
15216    }
15217
15218    fn check_auxiliary(&self) -> bool {
15219        matches!(self.peek().kind, TokenType::Auxiliary(_))
15220    }
15221
15222    /// Check if the current auxiliary is being used as a true auxiliary (followed by "not" or verb)
15223    /// vs being used as a main verb (like "did" in "did it").
15224    ///
15225    /// "did not bark" → auxiliary usage (emphatic past + negation)
15226    /// "did run" → auxiliary usage (emphatic past)
15227    /// "did it" → main verb usage (past of "do" + object)
15228    fn is_true_auxiliary_usage(&self) -> bool {
15229        if self.current + 1 >= self.tokens.len() {
15230            return false;
15231        }
15232
15233        let next_token = &self.tokens[self.current + 1].kind;
15234
15235        // If followed by "not", it's auxiliary usage
15236        if matches!(next_token, TokenType::Not) {
15237            return true;
15238        }
15239
15240        // If followed by a verb, it's auxiliary usage
15241        if matches!(next_token, TokenType::Verb { .. }) {
15242            return true;
15243        }
15244
15245        // If followed by pronoun (it, him, her, etc.), article, or noun, it's main verb usage
15246        if matches!(
15247            next_token,
15248            TokenType::Pronoun { .. }
15249                | TokenType::Article(_)
15250                | TokenType::Noun(_)
15251                | TokenType::ProperName(_)
15252        ) {
15253            return false;
15254        }
15255
15256        // Default to auxiliary usage for backward compatibility
15257        true
15258    }
15259
15260    /// Check if we have an Auxiliary token that should be treated as a main verb.
15261    /// This is true for "did" when followed by an object (e.g., "the butler did it").
15262    fn check_auxiliary_as_main_verb(&self) -> bool {
15263        if let TokenType::Auxiliary(Time::Past) = self.peek().kind {
15264            // Check if followed by pronoun, article, or noun (object)
15265            if self.current + 1 < self.tokens.len() {
15266                let next = &self.tokens[self.current + 1].kind;
15267                matches!(
15268                    next,
15269                    TokenType::Pronoun { .. }
15270                        | TokenType::Article(_)
15271                        | TokenType::Noun(_)
15272                        | TokenType::ProperName(_)
15273                        // A numeric object ("did 49 previous jumps", "did 3
15274                        // routines") — "did" is the main verb, not do-support.
15275                        | TokenType::Number(_)
15276                )
15277            } else {
15278                false
15279            }
15280        } else {
15281            false
15282        }
15283    }
15284
15285    /// Parse "did" as the main verb "do" (past tense) with a transitive object.
15286    /// This handles constructions like "the butler did it" → Do(e) ∧ Agent(e, butler) ∧ Theme(e, it)
15287    fn parse_do_as_main_verb(&mut self, subject_term: Term<'a>) -> ParseResult<&'a LogicExpr<'a>> {
15288        // Consume the auxiliary token (we're treating it as past tense "do")
15289        let aux_token = self.advance();
15290        let verb_time = if let TokenType::Auxiliary(time) = aux_token.kind {
15291            time
15292        } else {
15293            Time::Past
15294        };
15295
15296        // Intern "Do" as the verb lemma
15297        let verb = self.interner.intern("Do");
15298
15299        let mut object_cardinal: Option<(u32, crate::intern::Symbol, &'a LogicExpr<'a>)> = None;
15300
15301        // Parse the object - handle pronouns specially
15302        let object_term = if let TokenType::Pronoun { .. } = self.peek().kind {
15303            // Pronoun object (like "it" in "did it")
15304            self.advance();
15305            // For "it", we use a generic placeholder or resolved referent
15306            // In the context of "did it", "it" often refers to "the crime" or "the act"
15307            let it_sym = self.interner.intern("it");
15308            Term::Constant(it_sym)
15309        } else if let Some(n) = self.counting_np_lookahead() {
15310            // "did 49 previous jumps" — a counting-NP object: bind ∃=n over the
15311            // deverbal noun + adjectives, wrapping the event below.
15312            self.advance(); // the number
15313            let obj_var = self.next_var_name();
15314            self.nominal_np_context = true;
15315            let obj_np_result = self.parse_noun_phrase(false);
15316            self.nominal_np_context = false;
15317            let obj_np = obj_np_result?;
15318            let mut restr: &'a LogicExpr<'a> = self.ctx.exprs.alloc(LogicExpr::Predicate {
15319                name: obj_np.noun,
15320                args: self.ctx.terms.alloc_slice([Term::Variable(obj_var)]),
15321                world: None,
15322            });
15323            for &adj in obj_np.adjectives {
15324                let adj_pred = self.adjective_restriction(adj, obj_var, obj_np.noun);
15325                restr = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
15326                    left: restr,
15327                    op: TokenType::And,
15328                    right: adj_pred,
15329                });
15330            }
15331            for pp in obj_np.pps {
15332                let pp_sub = self.substitute_pp_placeholder(pp, obj_var);
15333                restr = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
15334                    left: restr,
15335                    op: TokenType::And,
15336                    right: pp_sub,
15337                });
15338            }
15339            object_cardinal = Some((n, obj_var, restr));
15340            Term::Variable(obj_var)
15341        } else if self.check_number() {
15342            // A bare numeric object with no adjective ("did 49 jumps", "did 3
15343            // routines") is a measure-style Theme, like "made 49 jumps".
15344            *self.parse_measure_phrase()?
15345        } else {
15346            let object = self.parse_noun_phrase(false)?;
15347            self.noun_phrase_to_term(&object)
15348        };
15349
15350        // Build Neo-Davidsonian event structure
15351        let event_var = self.get_event_var();
15352        let suppress_existential = self.drs.in_conditional_antecedent();
15353
15354        let mut modifiers = Vec::new();
15355        if verb_time == Time::Past {
15356            modifiers.push(self.interner.intern("Past"));
15357        } else if verb_time == Time::Future {
15358            modifiers.push(self.interner.intern("Future"));
15359        }
15360
15361        let neo_event = self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
15362            event_var,
15363            verb,
15364            roles: self.ctx.roles.alloc_slice(vec![
15365                (ThematicRole::Agent, subject_term),
15366                (ThematicRole::Theme, object_term),
15367            ]),
15368            modifiers: self.ctx.syms.alloc_slice(modifiers),
15369            suppress_existential,
15370            world: None,
15371        })));
15372
15373        // A counting-NP object binds the event as ∃=n ("did 49 previous jumps"
15374        // → ∃=49 j(Jump(j) ∧ Previous(j) ∧ Do-event(…, j))).
15375        if let Some((n, obj_var, restr)) = object_cardinal {
15376            let body = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
15377                left: restr,
15378                op: TokenType::And,
15379                right: neo_event,
15380            });
15381            return Ok(self.ctx.exprs.alloc(LogicExpr::Quantifier {
15382                kind: QuantifierKind::Cardinal(n),
15383                variable: obj_var,
15384                body,
15385                island_id: self.current_island,
15386            }));
15387        }
15388
15389        Ok(neo_event)
15390    }
15391
15392    fn check_to(&self) -> bool {
15393        matches!(self.peek().kind, TokenType::To)
15394    }
15395
15396    /// The marker "to" regardless of how the lexer classified it. Context
15397    /// decides between `TokenType::To` and `Preposition("to")`, but the
15398    /// grammar concept — an infinitival or dative marker — is one thing.
15399    pub(super) fn check_to_marker(&self) -> bool {
15400        self.check_to() || self.check_preposition_is("to")
15401    }
15402
15403    /// Look ahead in the token stream for modal subordinating verbs (would, could, should, might).
15404    /// These verbs allow modal subordination: continuing a hypothetical context from a prior sentence.
15405    /// E.g., "A wolf might enter. It would eat you." - "would" subordinates to "might".
15406    fn has_modal_subordination_ahead(&self) -> bool {
15407        // Modal subordination verbs: would, could, should, might
15408        // These allow access to hypothetical entities from prior modal sentences
15409        for i in self.current..self.tokens.len() {
15410            match &self.tokens[i].kind {
15411                TokenType::Would | TokenType::Could | TokenType::Should | TokenType::Might => {
15412                    return true;
15413                }
15414                // Stop looking at sentence boundary
15415                TokenType::Period | TokenType::EOF => break,
15416                _ => {}
15417            }
15418        }
15419        false
15420    }
15421
15422    fn consume_verb(&mut self) -> Symbol {
15423        let t = self.advance().clone();
15424        match t.kind {
15425            TokenType::Verb { lemma, .. } => lemma,
15426            TokenType::Ambiguous { primary, .. } => match *primary {
15427                TokenType::Verb { lemma, .. } => lemma,
15428                _ => panic!("Expected verb in Ambiguous primary, got {:?}", primary),
15429            },
15430            _ => panic!("Expected verb, got {:?}", t.kind),
15431        }
15432    }
15433
15434    fn consume_verb_with_metadata(&mut self) -> (Symbol, Time, Aspect, VerbClass) {
15435        let t = self.advance().clone();
15436        match t.kind {
15437            TokenType::Verb { lemma, time, aspect, class } => (lemma, time, aspect, class),
15438            TokenType::Ambiguous { primary, .. } => match *primary {
15439                TokenType::Verb { lemma, time, aspect, class } => (lemma, time, aspect, class),
15440                _ => panic!("Expected verb in Ambiguous primary, got {:?}", primary),
15441            },
15442            _ => panic!("Expected verb, got {:?}", t.kind),
15443        }
15444    }
15445
15446    fn match_token(&mut self, types: &[TokenType]) -> bool {
15447        for t in types {
15448            if self.check(t) {
15449                self.advance();
15450                return true;
15451            }
15452        }
15453        false
15454    }
15455
15456    fn check_quantifier(&self) -> bool {
15457        matches!(
15458            self.peek().kind,
15459            TokenType::All
15460                | TokenType::No
15461                | TokenType::Some
15462                | TokenType::Any
15463                | TokenType::Most
15464                | TokenType::Few
15465                | TokenType::Many
15466                | TokenType::Cardinal(_)
15467                | TokenType::AtLeast(_)
15468                | TokenType::AtMost(_)
15469        )
15470    }
15471
15472    fn check_npi_quantifier(&self) -> bool {
15473        matches!(
15474            self.peek().kind,
15475            TokenType::Nobody | TokenType::Nothing | TokenType::NoOne
15476        )
15477    }
15478
15479    fn check_npi_object(&self) -> bool {
15480        matches!(
15481            self.peek().kind,
15482            TokenType::Anything | TokenType::Anyone
15483        )
15484    }
15485
15486    fn check_temporal_npi(&self) -> bool {
15487        matches!(
15488            self.peek().kind,
15489            TokenType::Ever | TokenType::Never
15490        )
15491    }
15492
15493    fn parse_npi_quantified(&mut self) -> ParseResult<&'a LogicExpr<'a>> {
15494        let npi_token = self.advance().kind.clone();
15495        let var_name = self.next_var_name();
15496
15497        let (restriction_name, is_person) = match npi_token {
15498            TokenType::Nobody | TokenType::NoOne => ("Person", true),
15499            TokenType::Nothing => ("Thing", false),
15500            _ => ("Thing", false),
15501        };
15502
15503        let restriction_sym = self.interner.intern(restriction_name);
15504        let subject_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
15505            name: restriction_sym,
15506            args: self.ctx.terms.alloc_slice([Term::Variable(var_name)]),
15507            world: None,
15508        });
15509
15510        self.negative_depth += 1;
15511
15512        let verb = self.consume_verb();
15513
15514        if self.check_npi_object() {
15515            let obj_npi_token = self.advance().kind.clone();
15516            let obj_var = self.next_var_name();
15517
15518            let obj_restriction_name = match obj_npi_token {
15519                TokenType::Anything => "Thing",
15520                TokenType::Anyone => "Person",
15521                _ => "Thing",
15522            };
15523
15524            let obj_restriction_sym = self.interner.intern(obj_restriction_name);
15525            let obj_restriction = self.ctx.exprs.alloc(LogicExpr::Predicate {
15526                name: obj_restriction_sym,
15527                args: self.ctx.terms.alloc_slice([Term::Variable(obj_var)]),
15528                world: None,
15529            });
15530
15531            let verb_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
15532                name: verb,
15533                args: self.ctx.terms.alloc_slice([Term::Variable(var_name), Term::Variable(obj_var)]),
15534                world: None,
15535            });
15536
15537            let verb_and_obj = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
15538                left: obj_restriction,
15539                op: TokenType::And,
15540                right: verb_pred,
15541            });
15542
15543            let inner_existential = self.ctx.exprs.alloc(LogicExpr::Quantifier {
15544                kind: crate::ast::QuantifierKind::Existential,
15545                variable: obj_var,
15546                body: verb_and_obj,
15547                island_id: self.current_island,
15548            });
15549
15550            self.negative_depth -= 1;
15551
15552            let negated = self.ctx.exprs.alloc(LogicExpr::UnaryOp {
15553                op: TokenType::Not,
15554                operand: inner_existential,
15555            });
15556
15557            let body = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
15558                left: subject_pred,
15559                op: TokenType::Implies,
15560                right: negated,
15561            });
15562
15563            return Ok(self.ctx.exprs.alloc(LogicExpr::Quantifier {
15564                kind: crate::ast::QuantifierKind::Universal,
15565                variable: var_name,
15566                body,
15567                island_id: self.current_island,
15568            }));
15569        }
15570
15571        let verb_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
15572            name: verb,
15573            args: self.ctx.terms.alloc_slice([Term::Variable(var_name)]),
15574            world: None,
15575        });
15576
15577        self.negative_depth -= 1;
15578
15579        let negated_verb = self.ctx.exprs.alloc(LogicExpr::UnaryOp {
15580            op: TokenType::Not,
15581            operand: verb_pred,
15582        });
15583
15584        let body = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
15585            left: subject_pred,
15586            op: TokenType::Implies,
15587            right: negated_verb,
15588        });
15589
15590        Ok(self.ctx.exprs.alloc(LogicExpr::Quantifier {
15591            kind: crate::ast::QuantifierKind::Universal,
15592            variable: var_name,
15593            body,
15594            island_id: self.current_island,
15595        }))
15596    }
15597
15598    fn parse_temporal_npi(&mut self) -> ParseResult<&'a LogicExpr<'a>> {
15599        let npi_token = self.advance().kind.clone();
15600        let is_never = matches!(npi_token, TokenType::Never);
15601
15602        let subject = self.parse_noun_phrase(true)?;
15603
15604        if is_never {
15605            self.negative_depth += 1;
15606        }
15607
15608        let verb = self.consume_verb();
15609        let verb_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
15610            name: verb,
15611            args: self.ctx.terms.alloc_slice([Term::Constant(subject.noun)]),
15612            world: None,
15613        });
15614
15615        if is_never {
15616            self.negative_depth -= 1;
15617            Ok(self.ctx.exprs.alloc(LogicExpr::UnaryOp {
15618                op: TokenType::Not,
15619                operand: verb_pred,
15620            }))
15621        } else {
15622            Ok(verb_pred)
15623        }
15624    }
15625
15626    fn check(&self, kind: &TokenType) -> bool {
15627        if self.is_at_end() {
15628            return false;
15629        }
15630        std::mem::discriminant(&self.peek().kind) == std::mem::discriminant(kind)
15631    }
15632
15633    fn check_any(&self, kinds: &[TokenType]) -> bool {
15634        if self.is_at_end() {
15635            return false;
15636        }
15637        let current = std::mem::discriminant(&self.peek().kind);
15638        kinds.iter().any(|k| std::mem::discriminant(k) == current)
15639    }
15640
15641    fn check_article(&self) -> bool {
15642        matches!(self.peek().kind, TokenType::Article(_))
15643    }
15644
15645    fn advance(&mut self) -> &Token {
15646        if !self.is_at_end() {
15647            self.current += 1;
15648        }
15649        self.previous()
15650    }
15651
15652    fn is_at_end(&self) -> bool {
15653        self.peek().kind == TokenType::EOF
15654    }
15655
15656    fn peek(&self) -> &Token {
15657        // Clamp to the EOF terminator (guaranteed by `Parser::new`): a walk
15658        // that overshoots reads EOF forever instead of panicking.
15659        self.tokens
15660            .get(self.current)
15661            .unwrap_or_else(|| self.tokens.last().expect("stream ends with EOF"))
15662    }
15663
15664    /// Phase 35: Check if the next token (after current) is a string literal.
15665    /// Used to distinguish causal `because` from Trust's `because "reason"`.
15666    fn peek_next_is_string_literal(&self) -> bool {
15667        self.tokens.get(self.current + 1)
15668            .map(|t| matches!(t.kind, TokenType::StringLiteral(_)))
15669            .unwrap_or(false)
15670    }
15671
15672    fn previous(&self) -> &Token {
15673        &self.tokens[self.current - 1]
15674    }
15675
15676    fn current_span(&self) -> crate::token::Span {
15677        self.peek().span
15678    }
15679
15680    fn consume(&mut self, kind: TokenType) -> ParseResult<&Token> {
15681        if self.check(&kind) {
15682            Ok(self.advance())
15683        } else {
15684            Err(ParseError {
15685                kind: ParseErrorKind::UnexpectedToken {
15686                    expected: kind,
15687                    found: self.peek().kind.clone(),
15688                },
15689                span: self.current_span(),
15690            })
15691        }
15692    }
15693
15694    /// After the first word of a proper name has been consumed, absorb any
15695    /// subsequent capitalized content words into one multi-word proper-name
15696    /// symbol ("Porcher" + "Place" → "Porcher_Place", "Highland" + "Drive" →
15697    /// "Highland_Drive"). The trailing words frequently lex as common
15698    /// nouns/verbs/adjectives, so the test is surface capitalization, not token
15699    /// class. Stops at the first lowercase word or non-content token.
15700    pub(super) fn absorb_multiword_proper_name(&mut self, first: Symbol) -> Symbol {
15701        let mut full = self.interner.resolve(first).to_string();
15702        loop {
15703            let next = self.peek();
15704            let is_content = matches!(
15705                next.kind,
15706                TokenType::ProperName(_)
15707                    | TokenType::Noun(_)
15708                    | TokenType::Adjective(_)
15709                    | TokenType::Verb { .. }
15710                    | TokenType::Ambiguous { .. }
15711            );
15712            let capitalized = self
15713                .interner
15714                .resolve(next.lexeme)
15715                .chars()
15716                .next()
15717                .map_or(false, |c| c.is_ascii_uppercase());
15718            if is_content && capitalized {
15719                full.push('_');
15720                full.push_str(&self.interner.resolve(self.peek().lexeme));
15721                self.advance();
15722            } else {
15723                break;
15724            }
15725        }
15726        self.interner.intern(&full)
15727    }
15728
15729    fn consume_content_word(&mut self) -> ParseResult<Symbol> {
15730        let t = self.advance().clone();
15731        match t.kind {
15732            TokenType::Noun(s) | TokenType::Adjective(s) | TokenType::NonIntersectiveAdjective(s) => Ok(s),
15733            // "item"/"items" are LOGOS index keywords but ordinary nouns in
15734            // declarative NL — read them as the noun via their (capitalized) lexeme.
15735            TokenType::Item | TokenType::Items if self.mode == ParserMode::Declarative => {
15736                let lex = self.interner.resolve(t.lexeme);
15737                let mut chars = lex.chars();
15738                let cap = match chars.next() {
15739                    Some(f) => f.to_uppercase().collect::<String>() + chars.as_str(),
15740                    None => String::new(),
15741                };
15742                Ok(self.interner.intern(&cap))
15743            }
15744            // A quoted string in NL prose names an entity ("the \"Yellownose\"").
15745            TokenType::StringLiteral(s) => {
15746                let s_str = self.interner.resolve(s);
15747                let gender = Self::infer_gender(s_str);
15748                self.drs.introduce_proper_name(s, s, gender);
15749                Ok(s)
15750            }
15751            // Phase 35: Allow single-letter articles (a, an) to be used as variable names
15752            TokenType::Article(_) => Ok(t.lexeme),
15753            // "May" the month (collides with the modal token) — use its lexeme.
15754            TokenType::May => Ok(self.interner.intern("May")),
15755            // Phase 35: Allow numeric literals as content words (e.g., "equal to 42")
15756            TokenType::Number(s) => Ok(s),
15757            // Calendar-unit words ("year", "day", "week") are common nouns (the
15758            // lexicon carries Year/Day/Week/… entries) that the lexer tokenizes as
15759            // CalendarUnit for the temporal grammar. In a noun position they are
15760            // their singular lemma, so "four different years" → Year(...), "the
15761            // year was 2001" → Year. The unit variant IS the lemma.
15762            TokenType::CalendarUnit(u) => {
15763                let lemma = match u {
15764                    crate::token::CalendarUnit::Second => "Second",
15765                    crate::token::CalendarUnit::Minute => "Minute",
15766                    crate::token::CalendarUnit::Hour => "Hour",
15767                    crate::token::CalendarUnit::Day => "Day",
15768                    crate::token::CalendarUnit::Week => "Week",
15769                    crate::token::CalendarUnit::Month => "Month",
15770                    crate::token::CalendarUnit::Year => "Year",
15771                };
15772                Ok(self.interner.intern(lemma))
15773            }
15774            TokenType::ProperName(s) => {
15775                // In imperative mode, proper names are variable references that must be defined
15776                if self.mode == ParserMode::Imperative {
15777                    if !self.drs.has_referent_by_variable(s) {
15778                        return Err(ParseError {
15779                            kind: ParseErrorKind::UndefinedVariable {
15780                                name: self.interner.resolve(s).to_string()
15781                            },
15782                            span: t.span,
15783                        });
15784                    }
15785                    return Ok(s);
15786                }
15787
15788                // Declarative mode: auto-register proper names as entities
15789                let s_str = self.interner.resolve(s);
15790                let gender = Self::infer_gender(s_str);
15791
15792                // Register in DRS for cross-sentence anaphora resolution
15793                self.drs.introduce_proper_name(s, s, gender);
15794
15795                Ok(s)
15796            }
15797            TokenType::Verb { lemma, .. } => Ok(lemma),
15798            TokenType::Performative(s) => Ok(s),
15799            TokenType::Ambiguous { primary, .. } => {
15800                match *primary {
15801                    TokenType::Noun(s) | TokenType::Adjective(s) | TokenType::NonIntersectiveAdjective(s) => Ok(s),
15802                    TokenType::Verb { lemma, .. } => Ok(lemma),
15803                    TokenType::Performative(s) => Ok(s),
15804                    TokenType::ProperName(s) => {
15805                        // In imperative mode, proper names must be defined
15806                        if self.mode == ParserMode::Imperative {
15807                            if !self.drs.has_referent_by_variable(s) {
15808                                return Err(ParseError {
15809                                    kind: ParseErrorKind::UndefinedVariable {
15810                                        name: self.interner.resolve(s).to_string()
15811                                    },
15812                                    span: t.span,
15813                                });
15814                            }
15815                            return Ok(s);
15816                        }
15817                        // Register proper name in DRS for ambiguous tokens too
15818                        let s_str = self.interner.resolve(s);
15819                        let gender = Self::infer_gender(s_str);
15820                        self.drs.introduce_proper_name(s, s, gender);
15821                        Ok(s)
15822                    }
15823                    _ => Err(ParseError {
15824                        kind: ParseErrorKind::ExpectedContentWord { found: *primary },
15825                        span: self.current_span(),
15826                    }),
15827                }
15828            }
15829            other => Err(ParseError {
15830                kind: ParseErrorKind::ExpectedContentWord { found: other },
15831                span: self.current_span(),
15832            }),
15833        }
15834    }
15835
15836    fn consume_copula(&mut self) -> ParseResult<()> {
15837        if self.match_token(&[TokenType::Is, TokenType::Are, TokenType::Was, TokenType::Were]) {
15838            Ok(())
15839        } else {
15840            Err(ParseError {
15841                kind: ParseErrorKind::ExpectedCopula,
15842                span: self.current_span(),
15843            })
15844        }
15845    }
15846
15847    fn check_comparative(&self) -> bool {
15848        matches!(self.peek().kind, TokenType::Comparative(_))
15849    }
15850
15851    fn is_contact_clause_pattern(&self) -> bool {
15852        // Detect "The cat [the dog chased] ran" pattern
15853        // Also handles nested: "The rat [the cat [the dog chased] ate] died"
15854        let mut pos = self.current;
15855
15856        // Skip the article we're at
15857        if pos < self.tokens.len() && matches!(self.tokens[pos].kind, TokenType::Article(_)) {
15858            pos += 1;
15859        } else {
15860            return false;
15861        }
15862
15863        // Skip adjectives
15864        while pos < self.tokens.len() && matches!(self.tokens[pos].kind, TokenType::Adjective(_)) {
15865            pos += 1;
15866        }
15867
15868        // Must have noun/proper name (embedded subject)
15869        if pos < self.tokens.len() && matches!(self.tokens[pos].kind, TokenType::Noun(_) | TokenType::ProperName(_) | TokenType::Adjective(_)) {
15870            pos += 1;
15871        } else {
15872            return false;
15873        }
15874
15875        // Must have verb OR another article (nested contact clause) after
15876        pos < self.tokens.len() && matches!(self.tokens[pos].kind, TokenType::Verb { .. } | TokenType::Article(_))
15877    }
15878
15879    fn check_superlative(&self) -> bool {
15880        matches!(self.peek().kind, TokenType::Superlative(_))
15881    }
15882
15883    fn check_scopal_adverb(&self) -> bool {
15884        matches!(self.peek().kind, TokenType::ScopalAdverb(_))
15885    }
15886
15887    fn check_temporal_adverb(&self) -> bool {
15888        matches!(self.peek().kind, TokenType::TemporalAdverb(_))
15889    }
15890
15891    fn check_non_intersective_adjective(&self) -> bool {
15892        matches!(self.peek().kind, TokenType::NonIntersectiveAdjective(_))
15893    }
15894
15895    fn check_focus(&self) -> bool {
15896        matches!(self.peek().kind, TokenType::Focus(_))
15897    }
15898
15899    fn check_measure(&self) -> bool {
15900        matches!(self.peek().kind, TokenType::Measure(_))
15901    }
15902
15903    fn check_presup_trigger(&self) -> bool {
15904        match &self.peek().kind {
15905            TokenType::PresupTrigger(_) => true,
15906            TokenType::Verb { lemma, .. } => {
15907                let s = self.interner.resolve(*lemma).to_lowercase();
15908                crate::lexicon::lookup_presup_trigger(&s).is_some()
15909            }
15910            TokenType::Ambiguous { primary, .. } => match primary.as_ref() {
15911                TokenType::Verb { lemma, .. } => {
15912                    let s = self.interner.resolve(*lemma).to_lowercase();
15913                    crate::lexicon::lookup_presup_trigger(&s).is_some()
15914                }
15915                _ => false,
15916            },
15917            _ => false,
15918        }
15919    }
15920
15921    fn consume_presup_trigger(&mut self) -> crate::token::PresupKind {
15922        let lemma = match self.advance().kind.clone() {
15923            TokenType::PresupTrigger(kind) => return kind,
15924            TokenType::Verb { lemma, .. } => lemma,
15925            TokenType::Ambiguous { primary, .. } => match *primary {
15926                TokenType::Verb { lemma, .. } => lemma,
15927                other => unreachable!("guarded by check_presup_trigger, got {other:?}"),
15928            },
15929            other => unreachable!("guarded by check_presup_trigger, got {other:?}"),
15930        };
15931        let s = self.interner.resolve(lemma).to_lowercase();
15932        crate::lexicon::lookup_presup_trigger(&s)
15933            .expect("Lexicon mismatch: Verb flagged as trigger but lookup failed")
15934    }
15935
15936    fn is_followed_by_np_object(&self) -> bool {
15937        if self.current + 1 >= self.tokens.len() {
15938            return false;
15939        }
15940        let next = &self.tokens[self.current + 1].kind;
15941        matches!(next,
15942            TokenType::ProperName(_) |
15943            TokenType::Article(_) |
15944            TokenType::Noun(_) |
15945            TokenType::Pronoun { .. } |
15946            TokenType::Reflexive |
15947            TokenType::Who |
15948            TokenType::What |
15949            TokenType::Where |
15950            TokenType::When |
15951            TokenType::Why
15952        )
15953    }
15954
15955    fn is_followed_by_gerund(&self) -> bool {
15956        if self.current + 1 >= self.tokens.len() {
15957            return false;
15958        }
15959        matches!(self.tokens[self.current + 1].kind, TokenType::Verb { .. })
15960    }
15961
15962    // =========================================================================
15963    // Phase 46: Agent System Parsing
15964    // =========================================================================
15965
15966    /// Parse spawn statement: "Spawn a Worker called 'w1'."
15967    fn parse_spawn_statement(&mut self) -> ParseResult<Stmt<'a>> {
15968        self.advance(); // consume "Spawn"
15969
15970        // Expect article (a/an)
15971        if !self.check_article() {
15972            return Err(ParseError {
15973                kind: ParseErrorKind::ExpectedKeyword { keyword: "a/an".to_string() },
15974                span: self.current_span(),
15975            });
15976        }
15977        self.advance(); // consume article
15978
15979        // Get agent type name (Noun or ProperName)
15980        let agent_type = match &self.tokens[self.current].kind {
15981            TokenType::Noun(sym) | TokenType::ProperName(sym) => {
15982                let s = *sym;
15983                self.advance();
15984                s
15985            }
15986            _ => {
15987                return Err(ParseError {
15988                    kind: ParseErrorKind::ExpectedKeyword { keyword: "agent type".to_string() },
15989                    span: self.current_span(),
15990                });
15991            }
15992        };
15993
15994        // Expect "called"
15995        if !self.check(&TokenType::Called) {
15996            return Err(ParseError {
15997                kind: ParseErrorKind::ExpectedKeyword { keyword: "called".to_string() },
15998                span: self.current_span(),
15999            });
16000        }
16001        self.advance(); // consume "called"
16002
16003        // Get agent name (string literal)
16004        let name = if let TokenType::StringLiteral(sym) = &self.tokens[self.current].kind {
16005            let s = *sym;
16006            self.advance();
16007            s
16008        } else {
16009            return Err(ParseError {
16010                kind: ParseErrorKind::ExpectedKeyword { keyword: "agent name".to_string() },
16011                span: self.current_span(),
16012            });
16013        };
16014
16015        Ok(Stmt::Spawn { agent_type, name })
16016    }
16017
16018    /// Parse send statement: "Send Ping to 'agent'."
16019    fn parse_send_statement(&mut self) -> ParseResult<Stmt<'a>> {
16020        self.advance(); // consume "Send"
16021
16022        // Optional `cached`, `unchecked`, and `compressed [with <codec>]` modifiers,
16023        // in any order. Bare `compressed` = deflate; `with deflate|lz4|zstd` picks it.
16024        let mut cached = false;
16025        let mut unchecked = false;
16026        let mut compression = None;
16027        let mut layout = None;
16028        let mut shared = false;
16029        let mut computed = false;
16030        let mut indexed = false;
16031        let mut deduped = false;
16032        loop {
16033            if !cached && self.check_word("cached") {
16034                self.advance();
16035                cached = true;
16036            } else if !shared && (self.check_word("shared") || self.check_word("known")) {
16037                self.advance();
16038                shared = true;
16039            } else if !indexed && (self.check_word("indexed") || self.check_word("addressable")) {
16040                self.advance();
16041                indexed = true;
16042            } else if !deduped && (self.check_word("deduped") || self.check_word("shared_refs")) {
16043                self.advance();
16044                deduped = true;
16045            } else if !computed && self.check_word("computed") {
16046                self.advance();
16047                computed = true;
16048            } else if !unchecked && self.check_word("unchecked") {
16049                self.advance();
16050                unchecked = true;
16051            } else if layout.is_none()
16052                && (self.check_word("fast") || self.check_word("quickly"))
16053            {
16054                self.advance();
16055                layout = Some(SendLayout::Fast);
16056            } else if layout.is_none()
16057                && (self.check_word("compact") || self.check_word("small"))
16058            {
16059                self.advance();
16060                layout = Some(SendLayout::Compact);
16061            } else if layout.is_none() && self.check_word("packed") {
16062                self.advance();
16063                layout = Some(SendLayout::Packed);
16064            } else if layout.is_none()
16065                && (self.check_word("smallest") || self.check_word("best"))
16066            {
16067                self.advance();
16068                layout = Some(SendLayout::Smallest);
16069            } else if layout.is_none()
16070                && (self.check_word("redundant") || self.check_word("tough"))
16071            {
16072                self.advance();
16073                layout = Some(SendLayout::Redundant);
16074            } else if compression.is_none() && self.check_word("compressed") {
16075                self.advance();
16076                let codec = if self.check_word("with") {
16077                    self.advance();
16078                    let name = self.interner.resolve(self.peek().lexeme).to_ascii_lowercase();
16079                    let c = match name.as_str() {
16080                        "deflate" => CompressionCodec::Deflate,
16081                        "lz4" => CompressionCodec::Lz4,
16082                        "zstd" => CompressionCodec::Zstd,
16083                        _ => {
16084                            return Err(ParseError {
16085                                kind: ParseErrorKind::ExpectedKeyword {
16086                                    keyword: "a codec (deflate, lz4, or zstd)".to_string(),
16087                                },
16088                                span: self.current_span(),
16089                            })
16090                        }
16091                    };
16092                    self.advance(); // consume the codec name
16093                    c
16094                } else {
16095                    CompressionCodec::Deflate
16096                };
16097                compression = Some(codec);
16098            } else {
16099                break;
16100            }
16101        }
16102
16103        // Parse message expression
16104        let message = self.parse_imperative_expr()?;
16105
16106        // Expect "to"
16107        if !self.check_preposition_is("to") {
16108            return Err(ParseError {
16109                kind: ParseErrorKind::ExpectedKeyword { keyword: "to".to_string() },
16110                span: self.current_span(),
16111            });
16112        }
16113        self.advance(); // consume "to"
16114
16115        // Parse destination expression
16116        let destination = self.parse_imperative_expr()?;
16117
16118        Ok(Stmt::SendMessage {
16119            message,
16120            destination,
16121            compression,
16122            cached,
16123            unchecked,
16124            layout,
16125            shared,
16126            computed,
16127            indexed,
16128            deduped,
16129        })
16130    }
16131
16132    /// Parse await statement: "Await response from 'agent' into result."
16133    fn parse_await_statement(&mut self) -> ParseResult<Stmt<'a>> {
16134        self.advance(); // consume "Await"
16135
16136        // Optional `view` knob: receive a record-list LAZILY (zero-copy, decode-on-touch).
16137        // Optional `stream` knob: receive a batch STREAM and deframe it into a list.
16138        let mut view = false;
16139        let mut stream = false;
16140        if self.check_word("view") || self.check_word("viewed") {
16141            view = true;
16142            self.advance();
16143        }
16144        if self.check_word("stream") {
16145            stream = true;
16146            self.advance();
16147        }
16148
16149        // Skip optional "response" word
16150        if self.check_word("response") {
16151            self.advance();
16152        }
16153
16154        // Expect "from" (can be keyword or preposition)
16155        if !self.check(&TokenType::From) && !self.check_preposition_is("from") {
16156            return Err(ParseError {
16157                kind: ParseErrorKind::ExpectedKeyword { keyword: "from".to_string() },
16158                span: self.current_span(),
16159            });
16160        }
16161        self.advance(); // consume "from"
16162
16163        // Parse source expression
16164        let source = self.parse_imperative_expr()?;
16165
16166        // Expect "into"
16167        if !self.check_word("into") {
16168            return Err(ParseError {
16169                kind: ParseErrorKind::ExpectedKeyword { keyword: "into".to_string() },
16170                span: self.current_span(),
16171            });
16172        }
16173        self.advance(); // consume "into"
16174
16175        // The bound variable: a binding position accepts any content word, so a
16176        // name that elsewhere lexes as a verb ("reply", "run") or another keyword
16177        // is taken as the identifier here — the same rule as every other `let`-like
16178        // binding (`expect_identifier`).
16179        let into = self.expect_identifier()?;
16180
16181        Ok(Stmt::AwaitMessage { source, into, view, stream })
16182    }
16183
16184    /// Parse a batch stream send. The transport is the one-word knob, exactly as `Send` chooses:
16185    ///   `Stream <values> into <pipe>` → an in-process channel send (`SendPipe`) — CROSS-TIER (runs
16186    ///                                   on the VM + AOT + tree-walker), the compute-speed pro.
16187    ///   `Stream <values> to <peer>`   → a relay batch stream (`StreamMessage`) — the NETWORK pro
16188    ///                                   (framed, zero-copy deframe), on the async tree-walker tier.
16189    fn parse_stream_statement(&mut self) -> ParseResult<Stmt<'a>> {
16190        self.advance(); // consume "Stream"
16191        let values = self.parse_imperative_expr()?;
16192        // `into` <pipe>: the cross-tier in-process transport (reuses the channel-send lowering).
16193        if self.check(&TokenType::Into) || self.check_word("into") {
16194            self.advance();
16195            let pipe = self.parse_imperative_expr()?;
16196            return Ok(Stmt::SendPipe { value: values, pipe });
16197        }
16198        // `to` <peer>: the relay network transport.
16199        if self.check(&TokenType::To) || self.check_preposition_is("to") || self.check_word("to") {
16200            self.advance();
16201            let destination = self.parse_imperative_expr()?;
16202            return Ok(Stmt::StreamMessage { values, destination });
16203        }
16204        Err(ParseError {
16205            kind: ParseErrorKind::ExpectedKeyword { keyword: "to (relay) or into (pipe)".to_string() },
16206            span: self.current_span(),
16207        })
16208    }
16209
16210    // =========================================================================
16211    // Phase 49: CRDT Statement Parsing
16212    // =========================================================================
16213
16214    /// Parse merge statement: "Merge remote into local." or "Merge remote's field into local's field."
16215    fn parse_merge_statement(&mut self) -> ParseResult<Stmt<'a>> {
16216        self.advance(); // consume "Merge"
16217
16218        // Parse source expression
16219        let source = self.parse_imperative_expr()?;
16220
16221        // Expect "into"
16222        if !self.check_word("into") {
16223            return Err(ParseError {
16224                kind: ParseErrorKind::ExpectedKeyword { keyword: "into".to_string() },
16225                span: self.current_span(),
16226            });
16227        }
16228        self.advance(); // consume "into"
16229
16230        // Parse target expression
16231        let target = self.parse_imperative_expr()?;
16232
16233        Ok(Stmt::MergeCrdt { source, target })
16234    }
16235
16236    /// Parse increase statement: "Increase local's points by 10."
16237    fn parse_increase_statement(&mut self) -> ParseResult<Stmt<'a>> {
16238        self.advance(); // consume "Increase"
16239
16240        // Parse object with field access (e.g., "local's points")
16241        let expr = self.parse_imperative_expr()?;
16242
16243        // Must be a field access
16244        let (object, field) = if let Expr::FieldAccess { object, field } = expr {
16245            (object, field)
16246        } else {
16247            return Err(ParseError {
16248                kind: ParseErrorKind::ExpectedKeyword { keyword: "field access (e.g., 'x's count')".to_string() },
16249                span: self.current_span(),
16250            });
16251        };
16252
16253        // Expect "by"
16254        if !self.check_preposition_is("by") {
16255            return Err(ParseError {
16256                kind: ParseErrorKind::ExpectedKeyword { keyword: "by".to_string() },
16257                span: self.current_span(),
16258            });
16259        }
16260        self.advance(); // consume "by"
16261
16262        // Parse amount
16263        let amount = self.parse_imperative_expr()?;
16264
16265        Ok(Stmt::IncreaseCrdt { object, field: *field, amount })
16266    }
16267
16268    /// Parse decrease statement: "Decrease game's score by 5."
16269    fn parse_decrease_statement(&mut self) -> ParseResult<Stmt<'a>> {
16270        self.advance(); // consume "Decrease"
16271
16272        // Parse object with field access (e.g., "game's score")
16273        let expr = self.parse_imperative_expr()?;
16274
16275        // Must be a field access
16276        let (object, field) = if let Expr::FieldAccess { object, field } = expr {
16277            (object, field)
16278        } else {
16279            return Err(ParseError {
16280                kind: ParseErrorKind::ExpectedKeyword { keyword: "field access (e.g., 'x's count')".to_string() },
16281                span: self.current_span(),
16282            });
16283        };
16284
16285        // Expect "by"
16286        if !self.check_preposition_is("by") {
16287            return Err(ParseError {
16288                kind: ParseErrorKind::ExpectedKeyword { keyword: "by".to_string() },
16289                span: self.current_span(),
16290            });
16291        }
16292        self.advance(); // consume "by"
16293
16294        // Parse amount
16295        let amount = self.parse_imperative_expr()?;
16296
16297        Ok(Stmt::DecreaseCrdt { object, field: *field, amount })
16298    }
16299
16300    /// Parse append statement: "Append value to sequence."
16301    fn parse_append_statement(&mut self) -> ParseResult<Stmt<'a>> {
16302        self.advance(); // consume "Append"
16303
16304        // Parse value to append
16305        let value = self.parse_imperative_expr()?;
16306
16307        // Expect "to" (can be TokenType::To or a preposition)
16308        if !self.check(&TokenType::To) && !self.check_preposition_is("to") {
16309            return Err(ParseError {
16310                kind: ParseErrorKind::ExpectedKeyword { keyword: "to".to_string() },
16311                span: self.current_span(),
16312            });
16313        }
16314        self.advance(); // consume "to"
16315
16316        // Parse sequence expression
16317        let sequence = self.parse_imperative_expr()?;
16318
16319        Ok(Stmt::AppendToSequence { sequence, value })
16320    }
16321
16322    /// Parse resolve statement: "Resolve page's title to value."
16323    fn parse_resolve_statement(&mut self) -> ParseResult<Stmt<'a>> {
16324        self.advance(); // consume "Resolve"
16325
16326        // Parse object with field access (e.g., "page's title")
16327        let expr = self.parse_imperative_expr()?;
16328
16329        // Must be a field access
16330        let (object, field) = if let Expr::FieldAccess { object, field } = expr {
16331            (object, field)
16332        } else {
16333            return Err(ParseError {
16334                kind: ParseErrorKind::ExpectedKeyword { keyword: "field access (e.g., 'x's title')".to_string() },
16335                span: self.current_span(),
16336            });
16337        };
16338
16339        // Expect "to" (can be TokenType::To or a preposition)
16340        if !self.check(&TokenType::To) && !self.check_preposition_is("to") {
16341            return Err(ParseError {
16342                kind: ParseErrorKind::ExpectedKeyword { keyword: "to".to_string() },
16343                span: self.current_span(),
16344            });
16345        }
16346        self.advance(); // consume "to"
16347
16348        // Parse value
16349        let value = self.parse_imperative_expr()?;
16350
16351        Ok(Stmt::ResolveConflict { object, field: *field, value })
16352    }
16353
16354}
16355