use std::cell::RefCell;
pub const ZCONTEXT_HIST: u32 = 1;
pub const ZCONTEXT_LEX: u32 = 2;
pub const ZCONTEXT_PARSE: u32 = 4;
#[derive(Clone, Default)]
pub struct HistStack {
pub curhist: usize,
pub histsiz: usize,
pub savehistsiz: usize,
}
#[derive(Clone, Default)]
pub struct LexStack {
pub tok: i32,
pub tokstr: Option<String>,
pub zsession: Option<String>,
}
#[derive(Clone, Default)]
pub struct ParseStack {
pub ecused: usize,
pub ecnpats: usize,
}
#[derive(Clone, Default)]
pub struct ContextStack {
pub hist_stack: HistStack,
pub lex_stack: LexStack,
pub parse_stack: ParseStack,
}
pub struct ContextManager {
stack: Vec<ContextStack>,
}
impl Default for ContextManager {
fn default() -> Self {
Self::new()
}
}
impl ContextManager {
pub fn new() -> Self {
ContextManager { stack: Vec::new() }
}
pub fn is_empty(&self) -> bool {
self.stack.is_empty()
}
pub fn save_partial(
&mut self,
parts: u32,
hist: &HistStack,
lex: &LexStack,
parse: &ParseStack,
) {
let mut ctx = ContextStack::default();
if (parts & ZCONTEXT_HIST) != 0 {
ctx.hist_stack = hist.clone();
}
if (parts & ZCONTEXT_LEX) != 0 {
ctx.lex_stack = lex.clone();
}
if (parts & ZCONTEXT_PARSE) != 0 {
ctx.parse_stack = parse.clone();
}
self.stack.push(ctx);
}
pub fn save(&mut self, hist: &HistStack, lex: &LexStack, parse: &ParseStack) {
self.save_partial(
ZCONTEXT_HIST | ZCONTEXT_LEX | ZCONTEXT_PARSE,
hist,
lex,
parse,
);
}
pub fn restore_partial(&mut self, parts: u32) -> Option<ContextStack> {
let ctx = self.stack.pop()?;
let mut result = ContextStack::default();
if (parts & ZCONTEXT_HIST) != 0 {
result.hist_stack = ctx.hist_stack;
}
if (parts & ZCONTEXT_LEX) != 0 {
result.lex_stack = ctx.lex_stack;
}
if (parts & ZCONTEXT_PARSE) != 0 {
result.parse_stack = ctx.parse_stack;
}
Some(result)
}
pub fn restore(&mut self) -> Option<ContextStack> {
self.restore_partial(ZCONTEXT_HIST | ZCONTEXT_LEX | ZCONTEXT_PARSE)
}
pub fn depth(&self) -> usize {
self.stack.len()
}
}
thread_local! {
static CONTEXT_STACK: RefCell<ContextManager> = RefCell::new(ContextManager::new());
}
pub fn zcontext_save(hist: &HistStack, lex: &LexStack, parse: &ParseStack) {
CONTEXT_STACK.with(|cs| {
cs.borrow_mut().save(hist, lex, parse);
});
}
pub fn zcontext_save_partial(parts: u32, hist: &HistStack, lex: &LexStack, parse: &ParseStack) {
CONTEXT_STACK.with(|cs| {
cs.borrow_mut().save_partial(parts, hist, lex, parse);
});
}
pub fn zcontext_restore() -> Option<ContextStack> {
CONTEXT_STACK.with(|cs| cs.borrow_mut().restore())
}
pub fn zcontext_restore_partial(parts: u32) -> Option<ContextStack> {
CONTEXT_STACK.with(|cs| cs.borrow_mut().restore_partial(parts))
}
pub fn zcontext_is_toplevel() -> bool {
CONTEXT_STACK.with(|cs| cs.borrow().is_empty())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_context_save_restore() {
let mut mgr = ContextManager::new();
let hist = HistStack {
curhist: 100,
histsiz: 1000,
savehistsiz: 500,
};
let lex = LexStack {
tok: 42,
tokstr: Some("test".to_string()),
zsession: None,
};
let parse = ParseStack {
ecused: 10,
ecnpats: 5,
};
mgr.save(&hist, &lex, &parse);
assert_eq!(mgr.depth(), 1);
let restored = mgr.restore().unwrap();
assert_eq!(restored.hist_stack.curhist, 100);
assert_eq!(restored.lex_stack.tok, 42);
assert_eq!(restored.parse_stack.ecused, 10);
assert_eq!(mgr.depth(), 0);
}
#[test]
fn test_context_partial_save() {
let mut mgr = ContextManager::new();
let hist = HistStack {
curhist: 50,
histsiz: 500,
savehistsiz: 250,
};
let lex = LexStack::default();
let parse = ParseStack::default();
mgr.save_partial(ZCONTEXT_HIST, &hist, &lex, &parse);
let restored = mgr.restore_partial(ZCONTEXT_HIST).unwrap();
assert_eq!(restored.hist_stack.curhist, 50);
}
#[test]
fn test_nested_contexts() {
let mut mgr = ContextManager::new();
let hist1 = HistStack {
curhist: 1,
histsiz: 100,
savehistsiz: 50,
};
let hist2 = HistStack {
curhist: 2,
histsiz: 200,
savehistsiz: 100,
};
let lex = LexStack::default();
let parse = ParseStack::default();
mgr.save(&hist1, &lex, &parse);
mgr.save(&hist2, &lex, &parse);
assert_eq!(mgr.depth(), 2);
let restored2 = mgr.restore().unwrap();
assert_eq!(restored2.hist_stack.curhist, 2);
let restored1 = mgr.restore().unwrap();
assert_eq!(restored1.hist_stack.curhist, 1);
assert!(mgr.is_empty());
}
}