logicaffeine_language/parser/
modal.rs

1//! Modal verb parsing with Kripke semantics support.
2//!
3//! This module handles modal auxiliaries (can, could, may, might, must, should, would)
4//! and their semantic interpretation using modal vectors that encode:
5//!
6//! - **Domain**: Alethic (possibility/necessity) vs Deontic (permission/obligation)
7//! - **Flavor**: Root (circumstantial) vs Epistemic (knowledge-based)
8//! - **Force**: Possibility (◇) vs Necessity (□)
9//!
10//! # Modal Vector Examples
11//!
12//! | Modal | Default Reading | Alternative Reading |
13//! |-------|-----------------|---------------------|
14//! | can   | Ability (Root)  | Permission (Deontic) |
15//! | may   | Permission (Deontic) | Possibility (Epistemic) |
16//! | must  | Necessity (Root) | Obligation (Deontic) |
17//! | might | Possibility (Epistemic) | - |
18//!
19//! The module also handles aspect chains (perfect "have", progressive "be -ing").
20
21use super::clause::ClauseParsing;
22use super::noun::NounParsing;
23use super::{ParseResult, Parser};
24use crate::ast::{AspectOperator, LogicExpr, ModalDomain, ModalFlavor, ModalVector, NeoEventData, ThematicRole, VoiceOperator, Term};
25use crate::drs::TimeRelation;
26use crate::error::{ParseError, ParseErrorKind};
27use logicaffeine_base::Symbol;
28use crate::lexicon::{Time, Aspect};
29use crate::token::TokenType;
30
31/// Trait for parsing modal verbs and aspect chains.
32///
33/// Provides methods for interpreting modal auxiliaries (can, must, etc.)
34/// with Kripke semantics and handling aspect markers (perfect, progressive).
35pub trait ModalParsing<'a, 'ctx, 'int> {
36    /// Parses a modal verb and its scope content.
37    fn parse_modal(&mut self) -> ParseResult<&'a LogicExpr<'a>>;
38    /// Parses perfect/progressive aspect chain with a symbol subject.
39    fn parse_aspect_chain(&mut self, subject_symbol: Symbol) -> ParseResult<&'a LogicExpr<'a>>;
40    /// Parses perfect/progressive aspect chain with a term subject.
41    fn parse_aspect_chain_with_term(&mut self, subject_term: Term<'a>) -> ParseResult<&'a LogicExpr<'a>>;
42    /// Converts a modal token to its semantic vector (domain, force, flavor).
43    fn token_to_vector(&self, token: &TokenType) -> ModalVector;
44}
45
46impl<'a, 'ctx, 'int> ModalParsing<'a, 'ctx, 'int> for Parser<'a, 'ctx, 'int> {
47    fn parse_modal(&mut self) -> ParseResult<&'a LogicExpr<'a>> {
48        use crate::drs::BoxType;
49
50        let vector = self.token_to_vector(&self.previous().kind.clone());
51
52        // Enter modal box in parser's DRS (not world_state - that's swapped at sentence boundaries)
53        self.drs.enter_box(BoxType::ModalScope);
54
55        if self.check(&TokenType::That) {
56            self.advance();
57        }
58
59        let content = self.parse_sentence()?;
60
61        // Exit modal box
62        self.drs.exit_box();
63
64        Ok(self.ctx.exprs.alloc(LogicExpr::Modal {
65            vector,
66            operand: content,
67        }))
68    }
69
70    fn parse_aspect_chain(&mut self, subject_symbol: Symbol) -> ParseResult<&'a LogicExpr<'a>> {
71        self.parse_aspect_chain_with_term(Term::Constant(subject_symbol))
72    }
73
74    fn parse_aspect_chain_with_term(&mut self, subject_term: Term<'a>) -> ParseResult<&'a LogicExpr<'a>> {
75        let mut has_modal = false;
76        let mut modal_vector = None;
77        let mut has_negation = false;
78        let mut has_perfect = false;
79        let mut has_passive = false;
80        let mut has_progressive = false;
81
82        if self.check(&TokenType::Would) || self.check(&TokenType::Could)
83            || self.check(&TokenType::Must) || self.check(&TokenType::Can)
84            || self.check(&TokenType::Should) || self.check(&TokenType::May)
85            || self.check(&TokenType::Cannot) || self.check(&TokenType::Might) {
86            let modal_token = self.peek().kind.clone();
87            self.advance();
88            has_modal = true;
89            let vector = self.token_to_vector(&modal_token);
90            modal_vector = Some(vector.clone());
91            // Enter modal box in DRS so any new referents are marked as hypothetical
92            // This ensures "A wolf might enter" puts the wolf in a modal scope
93            self.drs.enter_box(crate::drs::BoxType::ModalScope);
94            // Also set modal context on WorldState for cross-sentence tracking
95            // This is used by end_sentence() to mark telescope candidates as modal-sourced
96            let is_epistemic = matches!(vector.flavor, crate::ast::ModalFlavor::Epistemic);
97            self.world_state.enter_modal_context(is_epistemic, vector.force);
98        }
99
100        if self.check(&TokenType::Not) {
101            self.advance();
102            has_negation = true;
103        }
104
105        // Check for "be able to" periphrastic modal (= can)
106        // This creates a nested modal: "might be able to fly" → ◇◇Fly(x)
107        let mut nested_modal_vector = None;
108        if self.check_content_word() {
109            let word = self.interner.resolve(self.peek().lexeme).to_lowercase();
110            if word == "be" {
111                // Look ahead for "able to"
112                if let Some(next1) = self.tokens.get(self.current + 1) {
113                    let next1_word = self.interner.resolve(next1.lexeme).to_lowercase();
114                    if next1_word == "able" {
115                        if let Some(next2) = self.tokens.get(self.current + 2) {
116                            if matches!(next2.kind, TokenType::To) {
117                                // Consume "be able to" - it's a modal meaning "can" (ability)
118                                self.advance(); // consume "be"
119                                self.advance(); // consume "able"
120                                self.advance(); // consume "to"
121                                nested_modal_vector = Some(ModalVector {
122                                    domain: ModalDomain::Alethic,
123                                    force: 0.5, // ability = possibility
124                                    flavor: ModalFlavor::Root, // "be able to" = Root modal (ability)
125                                });
126                            }
127                        }
128                    }
129                }
130            }
131        }
132
133        if self.check_content_word() {
134            let word = self.interner.resolve(self.peek().lexeme).to_lowercase();
135            if word == "have" || word == "has" || word == "had" {
136                self.advance();
137                has_perfect = true;
138            }
139        }
140
141        if self.check(&TokenType::Had) {
142            self.advance();
143            has_perfect = true;
144            // "had" = past perfect: R < S (past reference time)
145            let r_var = self.world_state.next_reference_time();
146            self.world_state.add_time_constraint(r_var, TimeRelation::Precedes, "S".to_string());
147        }
148
149        if self.check_content_word() {
150            let word = self.interner.resolve(self.peek().lexeme).to_lowercase();
151            if word == "been" {
152                self.advance();
153
154                if self.check_verb() {
155                    match &self.peek().kind {
156                        TokenType::Verb { aspect: Aspect::Progressive, .. } => {
157                            has_progressive = true;
158                        }
159                        TokenType::Verb { .. } => {
160                            let next_word = self.interner.resolve(self.peek().lexeme);
161                            if next_word.ends_with("ing") {
162                                has_progressive = true;
163                            } else {
164                                has_passive = true;
165                            }
166                        }
167                        _ => {
168                            has_passive = true;
169                        }
170                    }
171                }
172            }
173        }
174
175        if self.check_content_word() {
176            let word = self.interner.resolve(self.peek().lexeme).to_lowercase();
177            if word == "being" {
178                self.advance();
179                has_progressive = true;
180            }
181        }
182
183        let verb = if self.check_verb() {
184            self.consume_verb()
185        } else if self.check_content_word() {
186            self.consume_content_word()?
187        } else {
188            return Err(ParseError {
189                kind: ParseErrorKind::ExpectedContentWord { found: self.peek().kind.clone() },
190                span: self.peek().span.clone(),
191            });
192        };
193
194        let subject_role = if has_passive {
195            ThematicRole::Theme
196        } else {
197            ThematicRole::Agent
198        };
199        let mut roles: Vec<(ThematicRole, Term<'a>)> = vec![(subject_role, subject_term)];
200
201        if has_passive && self.check_preposition() {
202            if let TokenType::Preposition(sym) = self.peek().kind {
203                if self.interner.resolve(sym) == "by" {
204                    self.advance();
205                    let agent_np = self.parse_noun_phrase(true)?;
206                    let agent_term = self.noun_phrase_to_term(&agent_np);
207                    roles.push((ThematicRole::Agent, agent_term));
208                }
209            }
210        } else if !has_passive && (self.check_content_word() || self.check_article()) {
211            let obj_np = self.parse_noun_phrase(false)?;
212            let obj_term = self.noun_phrase_to_term(&obj_np);
213            roles.push((ThematicRole::Theme, obj_term));
214        }
215
216        let event_var = self.get_event_var();
217        let mut modifiers: Vec<Symbol> = Vec::new();
218        if let Some(pending) = self.pending_time {
219            match pending {
220                Time::Past => modifiers.push(self.interner.intern("Past")),
221                Time::Future => modifiers.push(self.interner.intern("Future")),
222                _ => {}
223            }
224        }
225        let suppress_existential = self.drs.in_conditional_antecedent();
226        let base_pred = self.ctx.exprs.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
227            event_var,
228            verb,
229            roles: self.ctx.roles.alloc_slice(roles.clone()),
230            modifiers: self.ctx.syms.alloc_slice(modifiers.clone()),
231            suppress_existential,
232            world: None,
233        })));
234
235        // Capture template for ellipsis reconstruction
236        self.capture_event_template(verb, &roles, &modifiers);
237
238        let mut result: &'a LogicExpr<'a> = base_pred;
239
240        if has_progressive {
241            result = self.ctx.aspectual(AspectOperator::Progressive, result);
242        }
243
244        if has_passive {
245            result = self.ctx.voice(VoiceOperator::Passive, result);
246        }
247
248        if has_perfect {
249            result = self.ctx.aspectual(AspectOperator::Perfect, result);
250
251            // Check pending_time to set up reference time for tense
252            if let Some(pending) = self.pending_time.take() {
253                match pending {
254                    Time::Future => {
255                        // Future perfect: S < R
256                        let r_var = self.world_state.next_reference_time();
257                        self.world_state.add_time_constraint("S".to_string(), TimeRelation::Precedes, r_var);
258                    }
259                    Time::Past => {
260                        // Past perfect fallback (if not already set by "had")
261                        if self.world_state.current_reference_time() == "S" {
262                            let r_var = self.world_state.next_reference_time();
263                            self.world_state.add_time_constraint(r_var, TimeRelation::Precedes, "S".to_string());
264                        }
265                    }
266                    _ => {}
267                }
268            }
269
270            // Perfect: E < R (event before reference)
271            let e_var = format!("e{}", self.world_state.event_history().len().max(1));
272            let r_var = self.world_state.current_reference_time();
273            self.world_state.add_time_constraint(e_var, TimeRelation::Precedes, r_var);
274        }
275
276        if has_negation {
277            result = self.ctx.exprs.alloc(LogicExpr::UnaryOp {
278                op: TokenType::Not,
279                operand: result,
280            });
281        }
282
283        // Apply nested modal first (from "be able to" = ability)
284        if let Some(vector) = nested_modal_vector {
285            result = self.ctx.modal(vector, result);
286        }
287
288        // Then apply outer modal (e.g., "might")
289        if has_modal {
290            // Exit modal box in DRS (matches enter_box above)
291            self.drs.exit_box();
292            // Note: We do NOT exit_modal_context() here because we want the modal flag
293            // to persist until end_sentence() so telescope candidates are marked as modal.
294            // The modal context is cleared by end_sentence() → prior_modal_context.take()
295            if let Some(vector) = modal_vector {
296                result = self.ctx.modal(vector, result);
297            }
298        }
299
300        Ok(result)
301    }
302
303    fn token_to_vector(&self, token: &TokenType) -> ModalVector {
304        use crate::ast::ModalFlavor;
305        use super::ModalPreference;
306
307        match token {
308            // Root modals → Narrow Scope (De Re)
309            // These attach the modal to the predicate inside the quantifier
310            TokenType::Must => ModalVector {
311                domain: ModalDomain::Alethic,
312                force: 1.0,
313                flavor: ModalFlavor::Root,
314            },
315            TokenType::Cannot => ModalVector {
316                domain: ModalDomain::Alethic,
317                force: 0.0,
318                flavor: ModalFlavor::Root,
319            },
320
321            // Polysemous modal: CAN
322            // Default: Ability (Alethic, Root/Narrow)
323            // Deontic: Permission (Deontic, Root/Narrow)
324            TokenType::Can => {
325                match self.modal_preference {
326                    ModalPreference::Deontic => {
327                        // Permission: "You can go" (Deontic, Narrow Scope)
328                        ModalVector {
329                            domain: ModalDomain::Deontic,
330                            force: 0.5,
331                            flavor: ModalFlavor::Root,
332                        }
333                    }
334                    _ => {
335                        // Ability: "Birds can fly" (Alethic, Narrow Scope)
336                        ModalVector {
337                            domain: ModalDomain::Alethic,
338                            force: 0.5,
339                            flavor: ModalFlavor::Root,
340                        }
341                    }
342                }
343            },
344
345            // Polysemous modal: COULD
346            // Default: Past Ability (Alethic, Root/Narrow)
347            // Epistemic: Conditional Possibility (Alethic, Epistemic/Wide)
348            TokenType::Could => {
349                match self.modal_preference {
350                    ModalPreference::Epistemic => {
351                        // Conditional Possibility: "It could rain" (Alethic, Wide Scope)
352                        ModalVector {
353                            domain: ModalDomain::Alethic,
354                            force: 0.5,
355                            flavor: ModalFlavor::Epistemic,
356                        }
357                    }
358                    _ => {
359                        // Past Ability: "She could swim" (Alethic, Narrow Scope)
360                        ModalVector {
361                            domain: ModalDomain::Alethic,
362                            force: 0.5,
363                            flavor: ModalFlavor::Root,
364                        }
365                    }
366                }
367            },
368
369            TokenType::Would => ModalVector {
370                domain: ModalDomain::Alethic,
371                force: 0.5,
372                flavor: ModalFlavor::Root,
373            },
374            TokenType::Shall => ModalVector {
375                domain: ModalDomain::Deontic,
376                force: 0.9,
377                flavor: ModalFlavor::Root,
378            },
379            TokenType::Should => ModalVector {
380                domain: ModalDomain::Deontic,
381                force: 0.6,
382                flavor: ModalFlavor::Root,
383            },
384
385            // Epistemic modals → Wide Scope (De Dicto)
386            // These wrap the entire quantifier in the modal
387            TokenType::Might => ModalVector {
388                domain: ModalDomain::Alethic,
389                force: 0.3,
390                flavor: ModalFlavor::Epistemic,
391            },
392
393            // Polysemous modal: MAY
394            // Default: Permission (Deontic, Root/Narrow)
395            // Epistemic: Possibility (Alethic, Epistemic/Wide)
396            TokenType::May => {
397                match self.modal_preference {
398                    ModalPreference::Epistemic => {
399                        // Possibility: "It may rain" (Alethic, Wide Scope)
400                        ModalVector {
401                            domain: ModalDomain::Alethic,
402                            force: 0.5,
403                            flavor: ModalFlavor::Epistemic,
404                        }
405                    }
406                    _ => {
407                        // Permission: "Students may leave" (Deontic, Narrow Scope)
408                        ModalVector {
409                            domain: ModalDomain::Deontic,
410                            force: 0.5,
411                            flavor: ModalFlavor::Root,
412                        }
413                    }
414                }
415            },
416
417            _ => panic!("Unknown modal token: {:?}", token),
418        }
419    }
420}