logicaffeine_language/
session.rs

1//! Session Manager for Incremental Evaluation
2//!
3//! Provides a REPL-style interface for parsing sentences one at a time
4//! while maintaining persistent discourse state across turns.
5//!
6//! # Example
7//!
8//! ```
9//! use logicaffeine_language::Session;
10//!
11//! let mut session = Session::new();
12//! let out1 = session.eval("The boys lifted the piano.").unwrap();
13//! let out2 = session.eval("They smiled.").unwrap();  // "They" resolves to "the boys"
14//! ```
15
16use crate::analysis;
17use logicaffeine_base::Arena;
18use crate::arena_ctx::AstContext;
19use crate::drs::WorldState;
20use crate::error::ParseError;
21use logicaffeine_base::Interner;
22use crate::lexer::Lexer;
23use crate::mwe;
24use crate::parser::Parser;
25use crate::registry::SymbolRegistry;
26use crate::semantics;
27use crate::OutputFormat;
28
29/// A persistent session for incremental sentence evaluation.
30///
31/// Maintains discourse state across multiple `eval()` calls, enabling
32/// cross-sentence anaphora resolution and temporal ordering.
33pub struct Session {
34    /// Persistent discourse state (DRS tree, referents, modal contexts)
35    world_state: WorldState,
36
37    /// Symbol interner for string interning across all sentences
38    interner: Interner,
39
40    /// Symbol registry for transpilation
41    registry: SymbolRegistry,
42
43    /// MWE trie for multi-word expression detection
44    mwe_trie: mwe::MweTrie,
45
46    /// Accumulated transpiled outputs from each sentence
47    history: Vec<String>,
48
49    /// Output format for transpilation
50    format: OutputFormat,
51}
52
53impl Default for Session {
54    fn default() -> Self {
55        Self::new()
56    }
57}
58
59impl Session {
60    /// Create a new session with default settings.
61    pub fn new() -> Self {
62        Session {
63            world_state: WorldState::new(),
64            interner: Interner::new(),
65            registry: SymbolRegistry::new(),
66            mwe_trie: mwe::build_mwe_trie(),
67            history: Vec::new(),
68            format: OutputFormat::Unicode,
69        }
70    }
71
72    /// Create a new session with a specific output format.
73    pub fn with_format(format: OutputFormat) -> Self {
74        Session {
75            world_state: WorldState::new(),
76            interner: Interner::new(),
77            registry: SymbolRegistry::new(),
78            mwe_trie: mwe::build_mwe_trie(),
79            history: Vec::new(),
80            format,
81        }
82    }
83
84    /// Evaluate a single sentence, updating the session state.
85    ///
86    /// Returns the transpiled logic for just this sentence.
87    /// Pronouns in this sentence can resolve to entities from previous sentences.
88    pub fn eval(&mut self, input: &str) -> Result<String, ParseError> {
89        // Generate event variable for this turn
90        let event_var_name = self.world_state.next_event_var();
91        let event_var_symbol = self.interner.intern(&event_var_name);
92
93        // Tokenize
94        let mut lexer = Lexer::new(input, &mut self.interner);
95        let tokens = lexer.tokenize();
96
97        // Apply MWE collapsing
98        let tokens = mwe::apply_mwe_pipeline(tokens, &self.mwe_trie, &mut self.interner);
99
100        // Pass 1: Discovery - scan for type definitions
101        let type_registry = {
102            let mut discovery = analysis::DiscoveryPass::new(&tokens, &mut self.interner);
103            discovery.run()
104        };
105
106        // Create arenas for this parse (fresh each sentence)
107        let expr_arena = Arena::new();
108        let term_arena = Arena::new();
109        let np_arena = Arena::new();
110        let sym_arena = Arena::new();
111        let role_arena = Arena::new();
112        let pp_arena = Arena::new();
113
114        let ast_ctx = AstContext::new(
115            &expr_arena,
116            &term_arena,
117            &np_arena,
118            &sym_arena,
119            &role_arena,
120            &pp_arena,
121        );
122
123        // Pass 2: Parse with WorldState (DRS persists across sentences)
124        let mut parser = Parser::new(
125            tokens,
126            &mut self.world_state,
127            &mut self.interner,
128            ast_ctx,
129            type_registry,
130        );
131        parser.set_discourse_event_var(event_var_symbol);
132
133        // Swap DRS from WorldState into Parser at start
134        parser.swap_drs_with_world_state();
135        let ast = parser.parse()?;
136        // Swap DRS back to WorldState at end
137        parser.swap_drs_with_world_state();
138
139        // Mark sentence boundary - collect telescope candidates for cross-sentence anaphora
140        self.world_state.end_sentence();
141
142        // Apply semantic axioms
143        let ast = semantics::apply_axioms(ast, ast_ctx.exprs, ast_ctx.terms, &mut self.interner);
144
145        // Transpile
146        let output = ast.transpile(&mut self.registry, &self.interner, self.format);
147
148        // Store in history
149        self.history.push(output.clone());
150
151        Ok(output)
152    }
153
154    /// Get the full accumulated logic from all sentences.
155    ///
156    /// Includes temporal ordering constraints (Precedes relations).
157    pub fn history(&self) -> String {
158        if self.history.is_empty() {
159            return String::new();
160        }
161
162        let event_history = self.world_state.event_history();
163        let mut precedes = Vec::new();
164        for i in 0..event_history.len().saturating_sub(1) {
165            precedes.push(format!("Precedes({}, {})", event_history[i], event_history[i + 1]));
166        }
167
168        if precedes.is_empty() {
169            self.history.join(" ∧ ")
170        } else {
171            format!("{} ∧ {}", self.history.join(" ∧ "), precedes.join(" ∧ "))
172        }
173    }
174
175    /// Get the number of sentences processed.
176    pub fn turn_count(&self) -> usize {
177        self.history.len()
178    }
179
180    /// Get direct access to the world state (for advanced use).
181    pub fn world_state(&self) -> &WorldState {
182        &self.world_state
183    }
184
185    /// Get mutable access to the world state (for advanced use).
186    pub fn world_state_mut(&mut self) -> &mut WorldState {
187        &mut self.world_state
188    }
189
190    /// Reset the session to initial state.
191    pub fn reset(&mut self) {
192        self.world_state = WorldState::new();
193        self.history.clear();
194        // Keep interner and registry - symbols are still valid
195    }
196}
197
198#[cfg(test)]
199mod tests {
200    use super::*;
201
202    #[test]
203    fn test_session_basic() {
204        let mut session = Session::new();
205        let out = session.eval("John walked.").unwrap();
206        assert!(out.contains("Walk"), "Should have Walk predicate");
207    }
208
209    #[test]
210    fn test_session_multiple_sentences() {
211        let mut session = Session::new();
212        session.eval("John walked.").unwrap();
213        session.eval("Mary ran.").unwrap();
214
215        assert_eq!(session.turn_count(), 2);
216
217        let history = session.history();
218        assert!(history.contains("Walk"));
219        assert!(history.contains("Run"));
220        assert!(history.contains("Precedes"));
221    }
222}