use crate::{Cell, Executor, SessionState, Store};
use anyhow::{Context, Result};
use std::collections::HashMap;
pub struct Session {
executor: Box<dyn Executor>,
store: Box<dyn Store>,
defs_text: String,
snapshots: HashMap<usize, Vec<Cell>>,
line_no: usize,
}
impl Session {
pub fn new(executor: Box<dyn Executor>, store: Box<dyn Store>) -> Session {
let state = store.load().unwrap_or_default();
let line_no = state.line_no.max(1);
Session {
executor,
store,
defs_text: state.defs_text,
snapshots: HashMap::new(),
line_no,
}
}
pub fn set_executor(&mut self, executor: Box<dyn Executor>) {
self.executor = executor;
}
pub fn seed_defs(&mut self, text: &str) -> Result<()> {
quarb::parse_defs(text).context("parsing --defs")?;
self.defs_text = format!("{}\n", quarb::strip_defs_comments(text));
self.persist();
Ok(())
}
pub fn add_def(&mut self, line: &str) -> Result<()> {
let candidate = format!("{}{}\n", self.defs_text, line);
quarb::parse_defs(&candidate).context("parsing definition")?;
self.defs_text = candidate;
self.persist();
Ok(())
}
fn combined(&self, line: &str) -> String {
if self.defs_text.is_empty() {
line.to_string()
} else {
format!("{}\n{line}", self.defs_text)
}
}
pub fn eval(&self, line: &str) -> Result<Vec<Cell>> {
self.executor.run(&self.combined(line))
}
pub fn eval_fresh(&self, line: &str) -> Result<Vec<Cell>> {
self.executor.run_fresh(&self.combined(line))
}
pub fn commit(&mut self, line: &str, snapshot: Vec<Cell>) -> bool {
self.snapshots.insert(self.line_no, snapshot);
let candidate = format!("{}def &{}: {} ;\n", self.defs_text, self.line_no, line);
let referenceable = quarb::parse_defs(&candidate).is_ok();
if referenceable {
self.defs_text = candidate;
}
self.line_no += 1;
self.persist();
referenceable
}
pub fn frozen(&self, n: usize) -> Option<&Vec<Cell>> {
self.snapshots.get(&n)
}
pub fn record_frozen(&mut self, snapshot: Vec<Cell>) {
self.snapshots.insert(self.line_no, snapshot);
self.line_no += 1;
self.persist();
}
fn persist(&self) {
let state = SessionState {
defs_text: self.defs_text.clone(),
line_no: self.line_no,
};
let _ = self.store.save(&state);
}
pub fn line_no(&self) -> usize {
self.line_no
}
pub fn history(&self) -> &str {
&self.defs_text
}
pub fn restore(&mut self, defs_text: String, line_no: usize) {
self.defs_text = defs_text;
self.line_no = line_no.max(1);
}
pub fn reset(&mut self) {
self.defs_text.clear();
self.snapshots.clear();
self.line_no = 1;
self.persist();
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::MemStore;
struct NullExec;
impl Executor for NullExec {
fn run(&self, _query: &str) -> anyhow::Result<Vec<Cell>> {
Ok(vec![])
}
}
#[test]
fn seed_defs_strips_comments() {
let mut s = Session::new(Box::new(NullExec), Box::new(MemStore));
s.seed_defs("# a library header\ndef &a: /x;\n# and a note\ndef &b: /y;")
.unwrap();
assert!(!s.history().contains('#'), "history: {}", s.history());
assert!(s.history().contains("def &a"));
assert!(s.history().contains("def &b"));
}
}