use crate::Real;
use crate::context::EvalContext;
use crate::error::ExprError;
use crate::types::HString;
use alloc::rc::Rc;
use alloc::vec::Vec;
use heapless::FnvIndexMap;
const MAX_CONTEXTS: usize = 128;
pub struct ContextStack {
contexts: Vec<Option<ContextWrapper>>,
next_id: usize,
parent_map: FnvIndexMap<usize, Option<usize>, MAX_CONTEXTS>,
}
struct ContextWrapper {
context: Rc<EvalContext>,
#[allow(dead_code)]
is_owned: bool,
}
impl Default for ContextStack {
fn default() -> Self {
Self::new()
}
}
impl ContextStack {
pub fn new() -> Self {
Self {
contexts: Vec::with_capacity(8),
next_id: 0,
parent_map: FnvIndexMap::new(),
}
}
pub fn clear(&mut self) {
self.contexts.clear();
self.next_id = 0;
self.parent_map.clear();
}
pub fn push_context(&mut self, ctx: Option<Rc<EvalContext>>) -> Result<usize, ExprError> {
let id = self.next_id;
if id >= MAX_CONTEXTS {
return Err(ExprError::CapacityExceeded("context stack"));
}
self.next_id += 1;
if self.contexts.len() <= id {
self.contexts.resize_with(id + 1, || None);
}
if let Some(ctx) = ctx {
self.contexts[id] = Some(ContextWrapper {
context: ctx,
is_owned: false,
});
} else {
self.contexts[id] = Some(ContextWrapper {
context: Rc::new(EvalContext::default()),
is_owned: true,
});
}
self.parent_map
.insert(id, None)
.map_err(|_| ExprError::CapacityExceeded("parent map"))?;
Ok(id)
}
pub fn push_context_with_parent(
&mut self,
ctx: EvalContext,
parent_id: usize,
) -> Result<usize, ExprError> {
let id = self.next_id;
if id >= MAX_CONTEXTS {
return Err(ExprError::CapacityExceeded("context stack"));
}
self.next_id += 1;
if self.contexts.len() <= id {
self.contexts.resize_with(id + 1, || None);
}
self.contexts[id] = Some(ContextWrapper {
context: Rc::new(ctx),
is_owned: true,
});
self.parent_map
.insert(id, Some(parent_id))
.map_err(|_| ExprError::CapacityExceeded("parent map"))?;
Ok(id)
}
pub fn get_context(&self, id: usize) -> Option<&Rc<EvalContext>> {
self.contexts
.get(id)
.and_then(|opt| opt.as_ref())
.map(|wrapper| &wrapper.context)
}
pub fn lookup_variable(&self, ctx_id: usize, name: &HString) -> Option<Real> {
let mut current_id = Some(ctx_id);
let mut visited_contexts = Vec::new();
while let Some(id) = current_id {
if let Some(ctx) = self.get_context(id) {
if let Some(&value) = ctx.variables.get(name) {
return Some(value);
}
if let Some(&value) = ctx.constants.get(name) {
return Some(value);
}
visited_contexts.push(id);
if let Some(ref parent_ctx) = ctx.parent {
return self.lookup_in_context_chain(
parent_ctx.as_ref(),
name,
&visited_contexts,
);
}
}
current_id = self.parent_map.get(&id).and_then(|&parent| parent);
}
None
}
fn lookup_in_context_chain(
&self,
ctx: &EvalContext,
name: &HString,
visited: &[usize],
) -> Option<Real> {
if let Some(&value) = ctx.variables.get(name) {
return Some(value);
}
if let Some(&value) = ctx.constants.get(name) {
return Some(value);
}
if let Some(ref parent) = ctx.parent {
return self.lookup_in_context_chain(parent.as_ref(), name, visited);
}
None
}
pub fn get_parent_id(&self, ctx_id: usize) -> Option<usize> {
self.parent_map.get(&ctx_id).and_then(|&parent| parent)
}
}