use crate::error::{Error, Result};
use crate::model::{Object, ObjectView, Scalar, ScalarCow, Value, ValueCow, ValueView};
#[derive(Clone, Default, Debug)]
struct Frame {
name: Option<kstring::KString>,
data: Object,
}
impl Frame {
fn new() -> Self {
Default::default()
}
fn with_name<S: Into<kstring::KString>>(name: S) -> Self {
Self {
name: Some(name.into()),
data: Object::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct Stack<'g> {
globals: Option<&'g dyn ObjectView>,
stack: Vec<Frame>,
indexes: Object,
}
impl<'g> Stack<'g> {
pub fn empty() -> Self {
Self {
globals: None,
indexes: Object::new(),
stack: vec![Frame::new()],
}
}
pub fn with_globals(globals: &'g dyn ObjectView) -> Self {
let mut stack = Self::empty();
stack.globals = Some(globals);
stack
}
pub(crate) fn push_frame(&mut self) {
self.stack.push(Frame::new());
}
pub(crate) fn push_named_frame<S: Into<kstring::KString>>(&mut self, name: S) {
self.stack.push(Frame::with_name(name));
}
pub(crate) fn pop_frame(&mut self) {
if self.stack.pop().is_none() {
panic!("Unbalanced push/pop, leaving the stack empty.")
};
}
pub fn frame_name(&self) -> Option<kstring::KStringRef<'_>> {
self.stack
.iter()
.rev()
.find_map(|f| f.name.as_ref().map(|s| s.as_ref()))
}
pub fn try_get(&self, path: &[ScalarCow<'_>]) -> Option<ValueCow<'_>> {
let frame = self.find_path_frame(path)?;
crate::model::find::try_find(frame.as_value(), path)
}
pub fn get(&self, path: &[ScalarCow<'_>]) -> Result<ValueCow<'_>> {
let frame = self.find_path_frame(path).ok_or_else(|| {
let key = path
.iter()
.next()
.cloned()
.unwrap_or_else(|| Scalar::new("nil"));
let globals = itertools::join(self.roots().iter(), ", ");
Error::with_msg("Unknown variable")
.context("requested variable", key.to_kstr())
.context("available variables", globals)
})?;
crate::model::find::find(frame.as_value(), path)
}
fn roots(&self) -> Vec<kstring::KStringCow<'_>> {
let mut roots = Vec::new();
if let Some(globals) = self.globals {
roots.extend(globals.keys());
}
for frame in self.stack.iter() {
roots.extend(frame.data.keys().map(kstring::KStringCow::from));
}
roots.sort();
roots.dedup();
roots
}
fn find_path_frame<'a>(&'a self, path: &[ScalarCow<'_>]) -> Option<&'a dyn ObjectView> {
let key = path.iter().next()?;
let key = key.to_kstr();
self.find_frame(key.as_str())
}
fn find_frame<'a>(&'a self, name: &str) -> Option<&'a dyn ObjectView> {
for frame in self.stack.iter().rev() {
if frame.data.contains_key(name) {
return Some(&frame.data);
}
}
if self.globals.map(|g| g.contains_key(name)).unwrap_or(false) {
return self.globals;
}
if self.indexes.contains_key(name) {
return Some(&self.indexes);
}
None
}
pub fn set_index<S>(&mut self, name: S, val: Value) -> Option<Value>
where
S: Into<kstring::KString>,
{
self.indexes.insert(name.into(), val)
}
pub fn get_index<'a>(&'a self, name: &str) -> Option<&'a Value> {
self.indexes.get(name)
}
pub fn set_global<S>(&mut self, name: S, val: Value) -> Option<Value>
where
S: Into<kstring::KString>,
{
let name = name.into();
self.global_frame().insert(name, val)
}
pub fn set<S>(&mut self, name: S, val: Value) -> Option<Value>
where
S: Into<kstring::KString>,
{
self.current_frame().insert(name.into(), val)
}
fn current_frame(&mut self) -> &mut Object {
match self.stack.last_mut() {
Some(frame) => &mut frame.data,
None => panic!("Global frame removed."),
}
}
fn global_frame(&mut self) -> &mut Object {
match self.stack.first_mut() {
Some(frame) => &mut frame.data,
None => panic!("Global frame removed."),
}
}
}
impl<'g> Default for Stack<'g> {
fn default() -> Self {
Self::empty()
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::model::value::ValueViewCmp;
#[test]
fn stack_find_frame() {
let mut stack = Stack::empty();
stack.set_global("number", Value::scalar(42f64));
assert!(stack.find_frame("number").is_some(),);
}
#[test]
fn stack_find_frame_failure() {
let mut stack = Stack::empty();
let mut post = Object::new();
post.insert("number".into(), Value::scalar(42f64));
stack.set_global("post", Value::Object(post));
assert!(stack.find_frame("post.number").is_none());
}
#[test]
fn stack_get() {
let mut stack = Stack::empty();
let mut post = Object::new();
post.insert("number".into(), Value::scalar(42f64));
stack.set_global("post", Value::Object(post));
let indexes = [Scalar::new("post"), Scalar::new("number")];
assert_eq!(&stack.get(&indexes).unwrap(), &ValueViewCmp::new(&42f64));
}
}