logicaffeine_language/parser/
pragmatics.rs

1//! Pragmatic inference and focus-sensitive parsing.
2//!
3//! This module handles linguistic phenomena that go beyond pure syntax/semantics:
4//!
5//! - **Focus particles**: "only", "even", "also" with alternative semantics
6//! - **Presupposition triggers**: "stop", "continue", "too"
7//! - **Measure phrases**: Dimensional expressions with units
8//! - **Comparatives**: "taller than", "as tall as", degree semantics
9//! - **Superlatives**: "the tallest", unique maximal individuals
10//! - **Scopal adverbs**: "always", "never", "usually"
11//!
12//! Focus is represented using the `LogicExpr::Focus` variant with an alternatives
13//! set derived from the focus domain.
14
15use super::noun::NounParsing;
16use super::quantifier::QuantifierParsing;
17use super::{ParseResult, Parser};
18use crate::ast::{LogicExpr, NounPhrase, NumberKind, QuantifierKind, TemporalOperator, Term};
19use crate::error::{ParseError, ParseErrorKind};
20use crate::lexicon::{self, Time};
21use crate::token::{MeasureKind, PresupKind, TokenType};
22
23/// Trait for parsing pragmatic and focus-sensitive constructions.
24///
25/// Provides methods for parsing linguistic phenomena that go beyond pure
26/// syntax/semantics, including focus particles, presupposition triggers,
27/// measure phrases, degree expressions (comparatives/superlatives), and
28/// scopal adverbs.
29///
30/// Focus is represented using alternative semantics, where the focused
31/// element evokes a set of alternatives. Presuppositions project through
32/// various operators and represent background entailments.
33pub trait PragmaticsParsing<'a, 'ctx, 'int> {
34    /// Parses a focus particle construction: "only John runs", "even Mary left".
35    ///
36    /// Focus particles like "only", "even", and "also" introduce alternative
37    /// semantics. The focused element is contrasted with a contextually
38    /// determined set of alternatives.
39    ///
40    /// Returns a [`LogicExpr::Focus`] with the focus kind, focused element,
41    /// and the scope predicate.
42    fn parse_focus(&mut self) -> ParseResult<&'a LogicExpr<'a>>;
43
44    /// Parses a measure construction: "much water is cold", "little food arrived".
45    ///
46    /// Measure expressions quantify over amounts using "much", "little", etc.
47    /// The result is an existentially quantified formula binding the measured
48    /// entity with a measure predicate.
49    fn parse_measure(&mut self) -> ParseResult<&'a LogicExpr<'a>>;
50
51    /// Parses a presupposition-triggering verb: "stopped running", "regrets leaving".
52    ///
53    /// Presupposition triggers introduce background entailments that project
54    /// through negation and other operators:
55    ///
56    /// - "stop P" presupposes: previously P; asserts: now ¬P
57    /// - "start P" presupposes: previously ¬P; asserts: now P
58    /// - "regret P" presupposes: P happened; asserts: subject regrets it
59    /// - "continue P" presupposes: was P; asserts: still P
60    ///
61    /// Returns a [`LogicExpr::Presupposition`] separating assertion from presupposition.
62    fn parse_presupposition(
63        &mut self,
64        subject: &NounPhrase<'a>,
65        presup_kind: PresupKind,
66    ) -> ParseResult<&'a LogicExpr<'a>>;
67
68    /// Parses a simple predicate for a given subject noun phrase.
69    ///
70    /// Handles verb phrases with optional objects and focus-marked objects
71    /// like "eats only rice". Used as a helper for focus and other pragmatic
72    /// constructions.
73    fn parse_predicate_for_subject(&mut self, subject: &NounPhrase<'a>)
74        -> ParseResult<&'a LogicExpr<'a>>;
75
76    /// Parses a scopal adverb construction: "always runs", "never sleeps".
77    ///
78    /// Scopal adverbs like "always", "never", "usually", "sometimes" quantify
79    /// over times, events, or situations. They create a [`LogicExpr::Scopal`]
80    /// operator that scopes over the verb predicate.
81    fn parse_scopal_adverb(&mut self, subject: &NounPhrase<'a>) -> ParseResult<&'a LogicExpr<'a>>;
82
83    /// Parses a superlative construction: "is the tallest student".
84    ///
85    /// Superlatives identify the unique maximal individual along a gradable
86    /// dimension within a comparison class. Returns a [`LogicExpr::Superlative`]
87    /// with the adjective, subject, and domain restrictor.
88    fn parse_superlative(&mut self, subject: &NounPhrase<'a>) -> ParseResult<&'a LogicExpr<'a>>;
89
90    /// Parses a comparative construction: "is taller than Mary", "is greater than 0".
91    ///
92    /// Comparatives establish an ordering relation along a gradable dimension.
93    /// Supports both NP comparisons ("taller than Mary") and numeric comparisons
94    /// ("greater than 0"). The optional `difference` parameter handles differential
95    /// comparatives like "3 inches taller".
96    ///
97    /// Returns a [`LogicExpr::Comparative`] with adjective, subject, and object.
98    fn parse_comparative(
99        &mut self,
100        subject: &NounPhrase<'a>,
101        copula_time: Time,
102        difference: Option<&'a Term<'a>>,
103    ) -> ParseResult<&'a LogicExpr<'a>>;
104
105    /// Checks if the current token is a numeric literal.
106    ///
107    /// Used to distinguish numeric comparisons ("greater than 0") from
108    /// entity comparisons ("taller than John").
109    fn check_number(&self) -> bool;
110
111    /// Parses a measure phrase: "5 meters", "100 kilograms".
112    ///
113    /// Measure phrases combine a numeric value with an optional unit.
114    /// The unit is looked up in the lexicon to determine its dimension
115    /// (length, mass, time, etc.).
116    ///
117    /// Returns a [`Term::Value`] with the parsed number, unit, and dimension.
118    fn parse_measure_phrase(&mut self) -> ParseResult<&'a Term<'a>>;
119}
120
121impl<'a, 'ctx, 'int> PragmaticsParsing<'a, 'ctx, 'int> for Parser<'a, 'ctx, 'int> {
122    fn parse_focus(&mut self) -> ParseResult<&'a LogicExpr<'a>> {
123        let kind = if let TokenType::Focus(k) = self.advance().kind {
124            k
125        } else {
126            return Err(ParseError {
127                kind: ParseErrorKind::ExpectedFocusParticle,
128                span: self.current_span(),
129            });
130        };
131
132        if self.check_quantifier() {
133            self.advance();
134            let quantified = self.parse_quantified()?;
135            let focus_var = self.interner.intern("focus");
136            let focused = self.ctx.terms.alloc(Term::Variable(focus_var));
137            return Ok(self.ctx.exprs.alloc(LogicExpr::Focus {
138                kind,
139                focused,
140                scope: quantified,
141            }));
142        }
143
144        let focused_np = self.parse_noun_phrase(true)?;
145        let focused = self.ctx.terms.alloc(Term::Constant(focused_np.noun));
146
147        let scope = self.parse_predicate_for_subject(&focused_np)?;
148
149        Ok(self.ctx.exprs.alloc(LogicExpr::Focus {
150            kind,
151            focused,
152            scope,
153        }))
154    }
155
156    fn parse_measure(&mut self) -> ParseResult<&'a LogicExpr<'a>> {
157        let kind = if let TokenType::Measure(k) = self.advance().kind {
158            k
159        } else {
160            return Err(ParseError {
161                kind: ParseErrorKind::UnexpectedToken {
162                    expected: TokenType::Measure(MeasureKind::Much),
163                    found: self.peek().kind.clone(),
164                },
165                span: self.current_span(),
166            });
167        };
168
169        let np = self.parse_noun_phrase(true)?;
170        let var = self.next_var_name();
171
172        let noun_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
173            name: np.noun,
174            args: self.ctx.terms.alloc_slice([Term::Variable(var)]),
175            world: None,
176        });
177
178        let measure_sym = self.interner.intern("Measure");
179        let kind_sym = self.interner.intern(match kind {
180            MeasureKind::Much => "Much",
181            MeasureKind::Little => "Little",
182        });
183        let measure_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
184            name: measure_sym,
185            args: self
186                .ctx
187                .terms
188                .alloc_slice([Term::Variable(var), Term::Constant(kind_sym)]),
189            world: None,
190        });
191
192        let (pred_expr, verb_time) = if self.check(&TokenType::Is) {
193            let copula_time = if let TokenType::Is = self.advance().kind {
194                Time::Present
195            } else {
196                Time::Present
197            };
198
199            // Check for comparative: "is colder than"
200            if self.check_comparative() {
201                let subj_np = NounPhrase {
202                    noun: np.noun,
203                    definiteness: None,
204                    adjectives: &[],
205                    possessor: None,
206                    pps: &[],
207                    superlative: None,
208                };
209                let comp_expr = self.parse_comparative(&subj_np, copula_time, None)?;
210
211                let combined = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
212                    left: noun_pred,
213                    op: TokenType::And,
214                    right: self.ctx.exprs.alloc(LogicExpr::BinaryOp {
215                        left: measure_pred,
216                        op: TokenType::And,
217                        right: comp_expr,
218                    }),
219                });
220
221                return Ok(self.ctx.exprs.alloc(LogicExpr::Quantifier {
222                    kind: QuantifierKind::Existential,
223                    variable: var,
224                    body: combined,
225                    island_id: self.current_island,
226                }));
227            }
228
229            let adj = self.consume_content_word()?;
230            let adj_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
231                name: adj,
232                args: self.ctx.terms.alloc_slice([Term::Variable(var)]),
233                world: None,
234            });
235            (adj_pred, copula_time)
236        } else {
237            let (verb, verb_time, _, _) = self.consume_verb_with_metadata();
238            let verb_pred = self.ctx.exprs.alloc(LogicExpr::Predicate {
239                name: verb,
240                args: self.ctx.terms.alloc_slice([Term::Variable(var)]),
241                world: None,
242            });
243            (verb_pred, verb_time)
244        };
245
246        let combined = self.ctx.exprs.alloc(LogicExpr::BinaryOp {
247            left: noun_pred,
248            op: TokenType::And,
249            right: self.ctx.exprs.alloc(LogicExpr::BinaryOp {
250                left: measure_pred,
251                op: TokenType::And,
252                right: pred_expr,
253            }),
254        });
255
256        let with_time = match verb_time {
257            Time::Past => self.ctx.exprs.alloc(LogicExpr::Temporal {
258                operator: TemporalOperator::Past,
259                body: combined,
260            }),
261            Time::Future => self.ctx.exprs.alloc(LogicExpr::Temporal {
262                operator: TemporalOperator::Future,
263                body: combined,
264            }),
265            _ => combined,
266        };
267
268        Ok(self.ctx.exprs.alloc(LogicExpr::Quantifier {
269            kind: QuantifierKind::Existential,
270            variable: var,
271            body: with_time,
272            island_id: self.current_island,
273        }))
274    }
275
276    fn parse_presupposition(
277        &mut self,
278        subject: &NounPhrase<'a>,
279        presup_kind: PresupKind,
280    ) -> ParseResult<&'a LogicExpr<'a>> {
281        let subject_noun = subject.noun;
282
283        let unknown = self.interner.intern("?");
284        let complement = if self.check_verb() {
285            let verb = self.consume_verb();
286            self.ctx.exprs.alloc(LogicExpr::Predicate {
287                name: verb,
288                args: self.ctx.terms.alloc_slice([Term::Constant(subject_noun)]),
289                world: None,
290            })
291        } else {
292            self.ctx.exprs.alloc(LogicExpr::Atom(unknown))
293        };
294
295        let (assertion, presupposition) = match presup_kind {
296            PresupKind::Stop => {
297                let neg = self.ctx.exprs.alloc(LogicExpr::UnaryOp {
298                    op: TokenType::Not,
299                    operand: complement,
300                });
301                let past = self.ctx.exprs.alloc(LogicExpr::Temporal {
302                    operator: TemporalOperator::Past,
303                    body: complement,
304                });
305                (neg, past)
306            }
307            PresupKind::Start => {
308                let past = self.ctx.exprs.alloc(LogicExpr::Temporal {
309                    operator: TemporalOperator::Past,
310                    body: complement,
311                });
312                let neg_past = self.ctx.exprs.alloc(LogicExpr::UnaryOp {
313                    op: TokenType::Not,
314                    operand: past,
315                });
316                (complement, neg_past)
317            }
318            PresupKind::Regret => {
319                let regret_sym = self.interner.intern("Regret");
320                let regret = self.ctx.exprs.alloc(LogicExpr::Predicate {
321                    name: regret_sym,
322                    args: self.ctx.terms.alloc_slice([Term::Constant(subject_noun)]),
323                    world: None,
324                });
325                let past = self.ctx.exprs.alloc(LogicExpr::Temporal {
326                    operator: TemporalOperator::Past,
327                    body: complement,
328                });
329                (regret, past)
330            }
331            PresupKind::Continue | PresupKind::Realize | PresupKind::Know => {
332                let verb_name = match presup_kind {
333                    PresupKind::Continue => self.interner.intern("Continue"),
334                    PresupKind::Realize => self.interner.intern("Realize"),
335                    PresupKind::Know => self.interner.intern("Know"),
336                    _ => unknown,
337                };
338                let main = self.ctx.exprs.alloc(LogicExpr::Predicate {
339                    name: verb_name,
340                    args: self.ctx.terms.alloc_slice([Term::Constant(subject_noun)]),
341                    world: None,
342                });
343                (main, complement)
344            }
345        };
346
347        Ok(self.ctx.exprs.alloc(LogicExpr::Presupposition {
348            assertion,
349            presupposition,
350        }))
351    }
352
353    fn parse_predicate_for_subject(
354        &mut self,
355        subject: &NounPhrase<'a>,
356    ) -> ParseResult<&'a LogicExpr<'a>> {
357        if self.check_verb() {
358            let verb = self.consume_verb();
359
360            // Check for focused object: "eats only rice"
361            if self.check_focus() {
362                let focus_kind = if let TokenType::Focus(k) = self.advance().kind {
363                    k
364                } else {
365                    crate::token::FocusKind::Only
366                };
367
368                let object_np = self.parse_noun_phrase(false)?;
369                let object_term = Term::Constant(object_np.noun);
370
371                let predicate = self.ctx.exprs.alloc(LogicExpr::Predicate {
372                    name: verb,
373                    args: self.ctx.terms.alloc_slice([
374                        Term::Constant(subject.noun),
375                        object_term.clone(),
376                    ]),
377                    world: None,
378                });
379
380                return Ok(self.ctx.exprs.alloc(LogicExpr::Focus {
381                    kind: focus_kind,
382                    focused: self.ctx.terms.alloc(object_term),
383                    scope: predicate,
384                }));
385            }
386
387            let mut args = vec![Term::Constant(subject.noun)];
388
389            if self.check_content_word() || self.check_article() {
390                let object = self.parse_noun_phrase(false)?;
391                args.push(Term::Constant(object.noun));
392            }
393
394            Ok(self.ctx.exprs.alloc(LogicExpr::Predicate {
395                name: verb,
396                args: self.ctx.terms.alloc_slice(args),
397                world: None,
398            }))
399        } else {
400            Ok(self.ctx.exprs.alloc(LogicExpr::Atom(subject.noun)))
401        }
402    }
403
404    fn parse_scopal_adverb(&mut self, subject: &NounPhrase<'a>) -> ParseResult<&'a LogicExpr<'a>> {
405        let operator = if let TokenType::ScopalAdverb(adv) = self.advance().kind.clone() {
406            adv
407        } else {
408            return Err(ParseError {
409                kind: ParseErrorKind::ExpectedScopalAdverb,
410                span: self.current_span(),
411            });
412        };
413
414        if !self.check_verb() {
415            return Err(ParseError {
416                kind: ParseErrorKind::ExpectedVerb {
417                    found: self.peek().kind.clone(),
418                },
419                span: self.current_span(),
420            });
421        }
422
423        let (verb, verb_time, _verb_aspect, _) = self.consume_verb_with_metadata();
424
425        let predicate = self.ctx.exprs.alloc(LogicExpr::Predicate {
426            name: verb,
427            args: self.ctx.terms.alloc_slice([Term::Constant(subject.noun)]),
428            world: None,
429        });
430
431        let with_time = match verb_time {
432            Time::Past => self.ctx.exprs.alloc(LogicExpr::Temporal {
433                operator: TemporalOperator::Past,
434                body: predicate,
435            }),
436            Time::Future => self.ctx.exprs.alloc(LogicExpr::Temporal {
437                operator: TemporalOperator::Future,
438                body: predicate,
439            }),
440            _ => predicate,
441        };
442
443        Ok(self.ctx.exprs.alloc(LogicExpr::Scopal {
444            operator,
445            body: with_time,
446        }))
447    }
448
449    fn parse_superlative(&mut self, subject: &NounPhrase<'a>) -> ParseResult<&'a LogicExpr<'a>> {
450        let adj = if let TokenType::Superlative(adj) = self.advance().kind.clone() {
451            adj
452        } else {
453            return Err(ParseError {
454                kind: ParseErrorKind::ExpectedSuperlativeAdjective,
455                span: self.current_span(),
456            });
457        };
458
459        let domain = self.consume_content_word()?;
460
461        Ok(self.ctx.exprs.alloc(LogicExpr::Superlative {
462            adjective: adj,
463            subject: self.ctx.terms.alloc(Term::Constant(subject.noun)),
464            domain,
465        }))
466    }
467
468    fn parse_comparative(
469        &mut self,
470        subject: &NounPhrase<'a>,
471        _copula_time: Time,
472        difference: Option<&'a Term<'a>>,
473    ) -> ParseResult<&'a LogicExpr<'a>> {
474        let adj = if let TokenType::Comparative(adj) = self.advance().kind.clone() {
475            adj
476        } else {
477            return Err(ParseError {
478                kind: ParseErrorKind::ExpectedComparativeAdjective,
479                span: self.current_span(),
480            });
481        };
482
483        if !self.check(&TokenType::Than) {
484            return Err(ParseError {
485                kind: ParseErrorKind::ExpectedThan,
486                span: self.current_span(),
487            });
488        }
489        self.advance();
490
491        // Check if the comparison target is a number (e.g., "greater than 0")
492        let object_term = if self.check_number() {
493            // Parse number as the comparison target
494            let num_sym = if let TokenType::Number(sym) = self.advance().kind {
495                sym
496            } else {
497                unreachable!()
498            };
499            let num_str = self.interner.resolve(num_sym);
500            let num_val = num_str.parse::<i64>().unwrap_or(0);
501            self.ctx.terms.alloc(Term::Value {
502                kind: crate::ast::logic::NumberKind::Integer(num_val),
503                unit: None,
504                dimension: None,
505            })
506        } else {
507            // Parse noun phrase as the comparison target
508            let object = self.parse_noun_phrase(false)?;
509            let obj_term = self.ctx.terms.alloc(Term::Constant(object.noun));
510
511            let result = self.ctx.exprs.alloc(LogicExpr::Comparative {
512                adjective: adj,
513                subject: self.ctx.terms.alloc(Term::Constant(subject.noun)),
514                object: obj_term,
515                difference,
516            });
517
518            let result = self.wrap_with_definiteness(subject.definiteness, subject.noun, result)?;
519            return self.wrap_with_definiteness_for_object(object.definiteness, object.noun, result);
520        };
521
522        // For number comparisons, create a simple Comparative expression
523        Ok(self.ctx.exprs.alloc(LogicExpr::Comparative {
524            adjective: adj,
525            subject: self.ctx.terms.alloc(Term::Constant(subject.noun)),
526            object: object_term,
527            difference,
528        }))
529    }
530
531    fn check_number(&self) -> bool {
532        matches!(self.peek().kind, TokenType::Number(_))
533    }
534
535    fn parse_measure_phrase(&mut self) -> ParseResult<&'a Term<'a>> {
536        let num_sym = if let TokenType::Number(sym) = self.advance().kind {
537            sym
538        } else {
539            return Err(ParseError {
540                kind: ParseErrorKind::ExpectedNumber,
541                span: self.current_span(),
542            });
543        };
544
545        let num_str = self.interner.resolve(num_sym);
546        let kind = parse_number_kind(num_str, num_sym);
547
548        let (unit, dimension) = if self.check_content_word() {
549            let unit_word = self.consume_content_word()?;
550            let unit_str = self.interner.resolve(unit_word).to_lowercase();
551            let dim = lexicon::lookup_unit_dimension(&unit_str);
552            (Some(unit_word), dim)
553        } else {
554            (None, None)
555        };
556
557        Ok(self.ctx.terms.alloc(Term::Value { kind, unit, dimension }))
558    }
559}
560
561fn parse_number_kind(s: &str, sym: crate::intern::Symbol) -> NumberKind {
562    if s.contains('.') {
563        NumberKind::Real(s.parse().unwrap_or(0.0))
564    } else if s.chars().all(|c| c.is_ascii_digit() || c == '-') {
565        NumberKind::Integer(s.parse().unwrap_or(0))
566    } else {
567        NumberKind::Symbolic(sym)
568    }
569}