Skip to main content

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    /// Switch the output format mid-session.
73    ///
74    /// Affects only how subsequent sentences are rendered; discourse state
75    /// (referents, event counters) is untouched, so a session switched to
76    /// LaTeX renders exactly as one that used LaTeX from the start.
77    pub fn set_format(&mut self, format: OutputFormat) {
78        self.format = format;
79    }
80
81    /// Create a new session with a specific output format.
82    pub fn with_format(format: OutputFormat) -> Self {
83        Session {
84            world_state: WorldState::new(),
85            interner: Interner::new(),
86            registry: SymbolRegistry::new(),
87            mwe_trie: mwe::build_mwe_trie(),
88            history: Vec::new(),
89            format,
90        }
91    }
92
93    /// Evaluate a single sentence, updating the session state.
94    ///
95    /// Returns the transpiled logic for just this sentence.
96    /// Pronouns in this sentence can resolve to entities from previous sentences.
97    pub fn eval(&mut self, input: &str) -> Result<String, ParseError> {
98        // Generate event variable for this turn
99        let event_var_name = self.world_state.next_event_var();
100        let event_var_symbol = self.interner.intern(&event_var_name);
101
102        // Tokenize
103        let mut lexer = Lexer::new(input, &mut self.interner);
104        let tokens = lexer.tokenize();
105
106        // Apply MWE collapsing
107        let tokens = mwe::apply_mwe_pipeline(tokens, &self.mwe_trie, &mut self.interner);
108
109        // Pass 1: Discovery - scan for type definitions
110        let type_registry = {
111            let mut discovery = analysis::DiscoveryPass::new(&tokens, &mut self.interner);
112            discovery.run()
113        };
114
115        // Create arenas for this parse (fresh each sentence)
116        let expr_arena = Arena::new();
117        let term_arena = Arena::new();
118        let np_arena = Arena::new();
119        let sym_arena = Arena::new();
120        let role_arena = Arena::new();
121        let pp_arena = Arena::new();
122
123        let ast_ctx = AstContext::new(
124            &expr_arena,
125            &term_arena,
126            &np_arena,
127            &sym_arena,
128            &role_arena,
129            &pp_arena,
130        );
131
132        // Pass 2: Parse with WorldState (DRS persists across sentences)
133        let mut parser = Parser::new(
134            tokens,
135            &mut self.world_state,
136            &mut self.interner,
137            ast_ctx,
138            type_registry,
139        );
140        parser.set_discourse_event_var(event_var_symbol);
141
142        // Swap DRS from WorldState into Parser at start
143        parser.swap_drs_with_world_state();
144        let ast = parser.parse()?;
145        // Swap DRS back to WorldState at end
146        parser.swap_drs_with_world_state();
147
148        // Mark sentence boundary - collect telescope candidates for cross-sentence anaphora
149        self.world_state.end_sentence();
150
151        // Apply semantic axioms
152        let ast = semantics::apply_axioms(ast, ast_ctx.exprs, ast_ctx.terms, &mut self.interner);
153
154        // Transpile
155        let output = ast.transpile(&mut self.registry, &self.interner, self.format);
156
157        // Store in history
158        self.history.push(output.clone());
159
160        Ok(output)
161    }
162
163    /// Get the full accumulated logic from all sentences.
164    ///
165    /// Includes temporal ordering constraints (Precedes relations).
166    pub fn history(&self) -> String {
167        if self.history.is_empty() {
168            return String::new();
169        }
170
171        let event_history = self.world_state.event_history();
172        let mut precedes = Vec::new();
173        for i in 0..event_history.len().saturating_sub(1) {
174            precedes.push(format!("Precedes({}, {})", event_history[i], event_history[i + 1]));
175        }
176
177        if precedes.is_empty() {
178            self.history.join(" ∧ ")
179        } else {
180            format!("{} ∧ {}", self.history.join(" ∧ "), precedes.join(" ∧ "))
181        }
182    }
183
184    /// Get the number of sentences processed.
185    pub fn turn_count(&self) -> usize {
186        self.history.len()
187    }
188
189    /// Get direct access to the world state (for advanced use).
190    pub fn world_state(&self) -> &WorldState {
191        &self.world_state
192    }
193
194    /// Get mutable access to the world state (for advanced use).
195    pub fn world_state_mut(&mut self) -> &mut WorldState {
196        &mut self.world_state
197    }
198
199    /// Reset the session to initial state.
200    pub fn reset(&mut self) {
201        self.world_state = WorldState::new();
202        self.history.clear();
203        // Keep interner and registry - symbols are still valid
204    }
205}
206
207#[cfg(test)]
208mod tests {
209    use super::*;
210
211    #[test]
212    fn test_session_basic() {
213        let mut session = Session::new();
214        let out = session.eval("John walked.").unwrap();
215        assert!(out.contains("Walk"), "Should have Walk predicate");
216    }
217
218    #[test]
219    fn test_session_multiple_sentences() {
220        let mut session = Session::new();
221        session.eval("John walked.").unwrap();
222        session.eval("Mary ran.").unwrap();
223
224        assert_eq!(session.turn_count(), 2);
225
226        let history = session.history();
227        assert!(history.contains("Walk"));
228        assert!(history.contains("Run"));
229        assert!(history.contains("Precedes"));
230    }
231}