logicaffeine_language/
arena_ctx.rs

1//! Arena context for AST allocation.
2//!
3//! This module provides [`AstContext`], a collection of typed arenas used during
4//! parsing to allocate AST nodes. All nodes are bump-allocated for efficiency,
5//! with the `'a` lifetime tracking the arena's scope.
6//!
7//! The context contains separate arenas for:
8//! - Logical expressions ([`LogicExpr`])
9//! - Terms ([`Term`])
10//! - Noun phrases ([`NounPhrase`])
11//! - Symbols, thematic roles, and prepositional phrases
12//! - Imperative statements (optional, for LOGOS mode)
13
14use logicaffeine_base::Arena;
15use crate::ast::{AspectOperator, LogicExpr, ModalVector, NounPhrase, QuantifierKind, TemporalOperator, VoiceOperator, Term, ThematicRole, Stmt, Expr, TypeExpr};
16use logicaffeine_base::Symbol;
17use crate::token::TokenType;
18
19/// Collection of typed arenas for AST allocation during parsing.
20///
21/// The context holds references to multiple arenas, each specialized for
22/// a particular AST node type. This separation allows efficient allocation
23/// while maintaining type safety.
24///
25/// # Modes
26///
27/// The context supports two modes:
28/// - **Declarative** (default): For natural language parsing to logic
29/// - **Imperative**: Adds statement and expression arenas for LOGOS programs
30#[derive(Clone, Copy)]
31pub struct AstContext<'a> {
32    /// Arena for logical expressions ([`LogicExpr`]).
33    pub exprs: &'a Arena<LogicExpr<'a>>,
34    /// Arena for first-order terms ([`Term`]).
35    pub terms: &'a Arena<Term<'a>>,
36    /// Arena for noun phrases ([`NounPhrase`]).
37    pub nps: &'a Arena<NounPhrase<'a>>,
38    /// Arena for interned symbols.
39    pub syms: &'a Arena<Symbol>,
40    /// Arena for thematic role assignments.
41    pub roles: &'a Arena<(ThematicRole, Term<'a>)>,
42    /// Arena for prepositional phrase modifiers.
43    pub pps: &'a Arena<&'a LogicExpr<'a>>,
44    /// Optional arena for imperative statements (LOGOS mode).
45    pub stmts: Option<&'a Arena<Stmt<'a>>>,
46    /// Optional arena for imperative expressions (LOGOS mode).
47    pub imperative_exprs: Option<&'a Arena<Expr<'a>>>,
48    /// Optional arena for type expressions (LOGOS mode).
49    pub type_exprs: Option<&'a Arena<TypeExpr<'a>>>,
50}
51
52impl<'a> AstContext<'a> {
53    /// Creates a new context for declarative (natural language) parsing.
54    pub fn new(
55        exprs: &'a Arena<LogicExpr<'a>>,
56        terms: &'a Arena<Term<'a>>,
57        nps: &'a Arena<NounPhrase<'a>>,
58        syms: &'a Arena<Symbol>,
59        roles: &'a Arena<(ThematicRole, Term<'a>)>,
60        pps: &'a Arena<&'a LogicExpr<'a>>,
61    ) -> Self {
62        AstContext { exprs, terms, nps, syms, roles, pps, stmts: None, imperative_exprs: None, type_exprs: None }
63    }
64
65    /// Creates a context with imperative statement arenas for LOGOS programs.
66    pub fn with_imperative(
67        exprs: &'a Arena<LogicExpr<'a>>,
68        terms: &'a Arena<Term<'a>>,
69        nps: &'a Arena<NounPhrase<'a>>,
70        syms: &'a Arena<Symbol>,
71        roles: &'a Arena<(ThematicRole, Term<'a>)>,
72        pps: &'a Arena<&'a LogicExpr<'a>>,
73        stmts: &'a Arena<Stmt<'a>>,
74        imperative_exprs: &'a Arena<Expr<'a>>,
75    ) -> Self {
76        AstContext { exprs, terms, nps, syms, roles, pps, stmts: Some(stmts), imperative_exprs: Some(imperative_exprs), type_exprs: None }
77    }
78
79    /// Creates a context with type expression arena for typed LOGOS programs.
80    pub fn with_types(
81        exprs: &'a Arena<LogicExpr<'a>>,
82        terms: &'a Arena<Term<'a>>,
83        nps: &'a Arena<NounPhrase<'a>>,
84        syms: &'a Arena<Symbol>,
85        roles: &'a Arena<(ThematicRole, Term<'a>)>,
86        pps: &'a Arena<&'a LogicExpr<'a>>,
87        stmts: &'a Arena<Stmt<'a>>,
88        imperative_exprs: &'a Arena<Expr<'a>>,
89        type_exprs: &'a Arena<TypeExpr<'a>>,
90    ) -> Self {
91        AstContext {
92            exprs, terms, nps, syms, roles, pps,
93            stmts: Some(stmts),
94            imperative_exprs: Some(imperative_exprs),
95            type_exprs: Some(type_exprs),
96        }
97    }
98
99    /// Allocates an imperative statement.
100    ///
101    /// # Panics
102    /// Panics if imperative arenas were not initialized.
103    pub fn alloc_stmt(&self, stmt: Stmt<'a>) -> &'a Stmt<'a> {
104        self.stmts.expect("imperative arenas not initialized").alloc(stmt)
105    }
106
107    /// Allocates an imperative expression.
108    ///
109    /// # Panics
110    /// Panics if imperative arenas were not initialized.
111    pub fn alloc_imperative_expr(&self, expr: Expr<'a>) -> &'a Expr<'a> {
112        self.imperative_exprs.expect("imperative arenas not initialized").alloc(expr)
113    }
114
115    /// Allocates a type expression.
116    ///
117    /// # Panics
118    /// Panics if type expression arena was not initialized.
119    pub fn alloc_type_expr(&self, ty: TypeExpr<'a>) -> &'a TypeExpr<'a> {
120        self.type_exprs.expect("type_exprs arena not initialized").alloc(ty)
121    }
122
123    /// Allocates a slice of type expressions.
124    ///
125    /// # Panics
126    /// Panics if type expression arena was not initialized.
127    pub fn alloc_type_exprs<I>(&self, types: I) -> &'a [TypeExpr<'a>]
128    where
129        I: IntoIterator<Item = TypeExpr<'a>>,
130        I::IntoIter: ExactSizeIterator,
131    {
132        self.type_exprs.expect("type_exprs arena not initialized").alloc_slice(types)
133    }
134
135    /// Allocates a logical expression.
136    pub fn alloc_expr(&self, expr: LogicExpr<'a>) -> &'a LogicExpr<'a> {
137        self.exprs.alloc(expr)
138    }
139
140    /// Allocates a first-order term.
141    pub fn alloc_term(&self, term: Term<'a>) -> &'a Term<'a> {
142        self.terms.alloc(term)
143    }
144
145    /// Allocates a slice of terms from an iterator.
146    pub fn alloc_terms<I>(&self, terms: I) -> &'a [Term<'a>]
147    where
148        I: IntoIterator<Item = Term<'a>>,
149        I::IntoIter: ExactSizeIterator,
150    {
151        self.terms.alloc_slice(terms)
152    }
153
154    /// Allocates a noun phrase.
155    pub fn alloc_np(&self, np: NounPhrase<'a>) -> &'a NounPhrase<'a> {
156        self.nps.alloc(np)
157    }
158
159    /// Allocates a slice of symbols from an iterator.
160    pub fn alloc_syms<I>(&self, syms: I) -> &'a [Symbol]
161    where
162        I: IntoIterator<Item = Symbol>,
163        I::IntoIter: ExactSizeIterator,
164    {
165        self.syms.alloc_slice(syms)
166    }
167
168    /// Allocates a slice of thematic role assignments.
169    pub fn alloc_roles<I>(&self, roles: I) -> &'a [(ThematicRole, Term<'a>)]
170    where
171        I: IntoIterator<Item = (ThematicRole, Term<'a>)>,
172        I::IntoIter: ExactSizeIterator,
173    {
174        self.roles.alloc_slice(roles)
175    }
176
177    /// Allocates a slice of prepositional phrase modifiers.
178    pub fn alloc_pps<I>(&self, pps: I) -> &'a [&'a LogicExpr<'a>]
179    where
180        I: IntoIterator<Item = &'a LogicExpr<'a>>,
181        I::IntoIter: ExactSizeIterator,
182    {
183        self.pps.alloc_slice(pps)
184    }
185
186    /// Creates an atomic predicate: `P(t1, t2, ...)`.
187    pub fn predicate(&self, name: Symbol, args: &'a [Term<'a>]) -> &'a LogicExpr<'a> {
188        self.exprs.alloc(LogicExpr::Predicate { name, args, world: None })
189    }
190
191    /// Creates a binary operation: `left op right`.
192    #[inline(always)]
193    pub fn binary(&self, left: &'a LogicExpr<'a>, op: TokenType, right: &'a LogicExpr<'a>) -> &'a LogicExpr<'a> {
194        self.exprs.alloc(LogicExpr::BinaryOp { left, op, right })
195    }
196
197    /// Creates a unary operation: `op operand`.
198    #[inline(always)]
199    pub fn unary(&self, op: TokenType, operand: &'a LogicExpr<'a>) -> &'a LogicExpr<'a> {
200        self.exprs.alloc(LogicExpr::UnaryOp { op, operand })
201    }
202
203    /// Creates a quantified formula: `∀x.body` or `∃x.body`.
204    #[inline(always)]
205    pub fn quantifier(&self, kind: QuantifierKind, variable: Symbol, body: &'a LogicExpr<'a>, island_id: u32) -> &'a LogicExpr<'a> {
206        self.exprs.alloc(LogicExpr::Quantifier { kind, variable, body, island_id })
207    }
208
209    /// Creates a temporal operator: `PAST(body)` or `FUTURE(body)`.
210    #[inline(always)]
211    pub fn temporal(&self, operator: TemporalOperator, body: &'a LogicExpr<'a>) -> &'a LogicExpr<'a> {
212        self.exprs.alloc(LogicExpr::Temporal { operator, body })
213    }
214
215    /// Creates an aspectual operator: `PROG(body)`, `PERF(body)`, etc.
216    #[inline(always)]
217    pub fn aspectual(&self, operator: AspectOperator, body: &'a LogicExpr<'a>) -> &'a LogicExpr<'a> {
218        self.exprs.alloc(LogicExpr::Aspectual { operator, body })
219    }
220
221    /// Creates a voice operator: `PASSIVE(body)`.
222    #[inline(always)]
223    pub fn voice(&self, operator: VoiceOperator, body: &'a LogicExpr<'a>) -> &'a LogicExpr<'a> {
224        self.exprs.alloc(LogicExpr::Voice { operator, body })
225    }
226
227    /// Creates a modal operator: `□operand` or `◇operand`.
228    #[inline(always)]
229    pub fn modal(&self, vector: ModalVector, operand: &'a LogicExpr<'a>) -> &'a LogicExpr<'a> {
230        self.exprs.alloc(LogicExpr::Modal { vector, operand })
231    }
232}
233
234#[cfg(test)]
235mod tests {
236    use super::*;
237    use crate::ast::{QuantifierKind, TemporalOperator, AspectOperator, ModalVector, ModalDomain};
238    use logicaffeine_base::Interner;
239    use crate::token::TokenType;
240
241    fn setup<'a>(
242        expr_arena: &'a Arena<LogicExpr<'a>>,
243        term_arena: &'a Arena<Term<'a>>,
244        np_arena: &'a Arena<NounPhrase<'a>>,
245        sym_arena: &'a Arena<Symbol>,
246        role_arena: &'a Arena<(ThematicRole, Term<'a>)>,
247        pp_arena: &'a Arena<&'a LogicExpr<'a>>,
248    ) -> AstContext<'a> {
249        AstContext::new(expr_arena, term_arena, np_arena, sym_arena, role_arena, pp_arena)
250    }
251
252    #[test]
253    fn binary_builder_creates_binary_op() {
254        let expr_arena: Arena<LogicExpr> = Arena::new();
255        let term_arena: Arena<Term> = Arena::new();
256        let np_arena: Arena<NounPhrase> = Arena::new();
257        let sym_arena: Arena<Symbol> = Arena::new();
258        let role_arena: Arena<(ThematicRole, Term)> = Arena::new();
259        let pp_arena: Arena<&LogicExpr> = Arena::new();
260        let ctx = setup(&expr_arena, &term_arena, &np_arena, &sym_arena, &role_arena, &pp_arena);
261
262        let mut interner = Interner::new();
263        let p = interner.intern("P");
264        let q = interner.intern("Q");
265
266        let left = ctx.alloc_expr(LogicExpr::Atom(p));
267        let right = ctx.alloc_expr(LogicExpr::Atom(q));
268        let result = ctx.binary(left, TokenType::And, right);
269
270        assert!(matches!(result, LogicExpr::BinaryOp { op: TokenType::And, .. }));
271    }
272
273    #[test]
274    fn unary_builder_creates_unary_op() {
275        let expr_arena: Arena<LogicExpr> = Arena::new();
276        let term_arena: Arena<Term> = Arena::new();
277        let np_arena: Arena<NounPhrase> = Arena::new();
278        let sym_arena: Arena<Symbol> = Arena::new();
279        let role_arena: Arena<(ThematicRole, Term)> = Arena::new();
280        let pp_arena: Arena<&LogicExpr> = Arena::new();
281        let ctx = setup(&expr_arena, &term_arena, &np_arena, &sym_arena, &role_arena, &pp_arena);
282
283        let mut interner = Interner::new();
284        let p = interner.intern("P");
285        let operand = ctx.alloc_expr(LogicExpr::Atom(p));
286        let result = ctx.unary(TokenType::Not, operand);
287
288        assert!(matches!(result, LogicExpr::UnaryOp { op: TokenType::Not, .. }));
289    }
290
291    #[test]
292    fn quantifier_builder_creates_quantifier() {
293        let expr_arena: Arena<LogicExpr> = Arena::new();
294        let term_arena: Arena<Term> = Arena::new();
295        let np_arena: Arena<NounPhrase> = Arena::new();
296        let sym_arena: Arena<Symbol> = Arena::new();
297        let role_arena: Arena<(ThematicRole, Term)> = Arena::new();
298        let pp_arena: Arena<&LogicExpr> = Arena::new();
299        let ctx = setup(&expr_arena, &term_arena, &np_arena, &sym_arena, &role_arena, &pp_arena);
300
301        let mut interner = Interner::new();
302        let x = interner.intern("x");
303        let p = interner.intern("P");
304        let body = ctx.alloc_expr(LogicExpr::Atom(p));
305        let result = ctx.quantifier(QuantifierKind::Universal, x, body, 0);
306
307        assert!(matches!(result, LogicExpr::Quantifier { kind: QuantifierKind::Universal, .. }));
308    }
309
310    #[test]
311    fn temporal_builder_creates_temporal() {
312        let expr_arena: Arena<LogicExpr> = Arena::new();
313        let term_arena: Arena<Term> = Arena::new();
314        let np_arena: Arena<NounPhrase> = Arena::new();
315        let sym_arena: Arena<Symbol> = Arena::new();
316        let role_arena: Arena<(ThematicRole, Term)> = Arena::new();
317        let pp_arena: Arena<&LogicExpr> = Arena::new();
318        let ctx = setup(&expr_arena, &term_arena, &np_arena, &sym_arena, &role_arena, &pp_arena);
319
320        let mut interner = Interner::new();
321        let p = interner.intern("P");
322        let body = ctx.alloc_expr(LogicExpr::Atom(p));
323        let result = ctx.temporal(TemporalOperator::Past, body);
324
325        assert!(matches!(result, LogicExpr::Temporal { operator: TemporalOperator::Past, .. }));
326    }
327
328    #[test]
329    fn aspectual_builder_creates_aspectual() {
330        let expr_arena: Arena<LogicExpr> = Arena::new();
331        let term_arena: Arena<Term> = Arena::new();
332        let np_arena: Arena<NounPhrase> = Arena::new();
333        let sym_arena: Arena<Symbol> = Arena::new();
334        let role_arena: Arena<(ThematicRole, Term)> = Arena::new();
335        let pp_arena: Arena<&LogicExpr> = Arena::new();
336        let ctx = setup(&expr_arena, &term_arena, &np_arena, &sym_arena, &role_arena, &pp_arena);
337
338        let mut interner = Interner::new();
339        let p = interner.intern("P");
340        let body = ctx.alloc_expr(LogicExpr::Atom(p));
341        let result = ctx.aspectual(AspectOperator::Progressive, body);
342
343        assert!(matches!(result, LogicExpr::Aspectual { operator: AspectOperator::Progressive, .. }));
344    }
345
346    #[test]
347    fn modal_builder_creates_modal() {
348        let expr_arena: Arena<LogicExpr> = Arena::new();
349        let term_arena: Arena<Term> = Arena::new();
350        let np_arena: Arena<NounPhrase> = Arena::new();
351        let sym_arena: Arena<Symbol> = Arena::new();
352        let role_arena: Arena<(ThematicRole, Term)> = Arena::new();
353        let pp_arena: Arena<&LogicExpr> = Arena::new();
354        let ctx = setup(&expr_arena, &term_arena, &np_arena, &sym_arena, &role_arena, &pp_arena);
355
356        let mut interner = Interner::new();
357        let p = interner.intern("P");
358        let operand = ctx.alloc_expr(LogicExpr::Atom(p));
359        let vector = ModalVector { domain: ModalDomain::Alethic, force: 1.0, flavor: crate::ast::ModalFlavor::Root };
360        let result = ctx.modal(vector, operand);
361
362        assert!(matches!(result, LogicExpr::Modal { .. }));
363    }
364}