use crate::types::MoltList;
use crate::value::Value;
use std::collections::HashMap;
enum Var {
Value(Value),
Level(usize),
}
#[derive(Default)]
struct Scope {
map: HashMap<String, Var>,
}
impl Scope {
pub fn new() -> Self {
Scope {
map: HashMap::new(),
}
}
}
#[derive(Default)]
pub(crate) struct ScopeStack {
stack: Vec<Scope>,
}
impl ScopeStack {
pub fn new() -> Self {
let mut ss = Self { stack: Vec::new() };
ss.stack.push(Scope::new());
ss
}
pub fn set(&mut self, name: &str, value: Value) -> Value {
let top = self.stack.len() - 1;
self.set_at(top, name, value.clone());
value
}
pub fn get(&self, name: &str) -> Option<Value> {
let top = self.stack.len() - 1;
self.get_at(top, name)
}
pub fn unset(&mut self, name: &str) {
let top = self.stack.len() - 1;
self.unset_at(top, name);
}
fn get_at(&self, level: usize, name: &str) -> Option<Value> {
match self.stack[level].map.get(name) {
Some(Var::Value(value)) => Some(value.clone()),
Some(Var::Level(at)) => self.get_at(*at, name),
_ => None,
}
}
fn set_at(&mut self, level: usize, name: &str, value: Value) {
match self.stack[level].map.get(name) {
Some(Var::Level(at)) => {
let true_level = *at;
self.set_at(true_level, name, value);
}
_ => {
self.stack[level].map.insert(name.into(), Var::Value(value));
}
}
}
fn unset_at(&mut self, level: usize, name: &str) {
if let Some(Var::Level(at)) = self.stack[level].map.get(name) {
let true_level = *at;
self.unset_at(true_level, name);
}
self.stack[level].map.remove(name);
}
pub fn upvar(&mut self, level: usize, name: &str) {
assert!(level < self.current(), "Can't upvar to current stack level");
let top = self.current();
self.stack[top].map.insert(name.into(), Var::Level(level));
}
pub fn current(&self) -> usize {
self.stack.len() - 1
}
pub fn push(&mut self) {
self.stack.push(Scope::new());
}
pub fn pop(&mut self) {
self.stack.pop();
assert!(!self.stack.is_empty(), "Popped global scope!");
}
pub fn vars_in_scope(&self) -> MoltList {
let top = self.stack.len() - 1;
self.stack[top]
.map
.keys()
.cloned()
.map(|x| Value::from(&x))
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new() {
let ss = ScopeStack::new();
assert_eq!(ss.stack.len(), 1);
assert_eq!(ss.current(), 0);
}
#[test]
fn test_set_get_basic() {
let mut ss = ScopeStack::new();
ss.set("a", Value::from("1"));
let out = ss.get("a");
assert!(out.is_some());
assert_eq!(out.unwrap().as_str(), "1");
ss.set("b", Value::from("2"));
let out = ss.get("b");
assert!(out.is_some());
assert_eq!(out.unwrap().as_str(), "2");
assert!(ss.get("c").is_none());
}
#[test]
fn test_unset_basic() {
let mut ss = ScopeStack::new();
ss.set("a", Value::from("1"));
assert!(ss.get("a").is_some());
ss.unset("a");
assert!(ss.get("a").is_none());
}
#[test]
fn test_push() {
let mut ss = ScopeStack::new();
ss.push();
assert_eq!(ss.stack.len(), 2);
ss.push();
assert_eq!(ss.stack.len(), 3);
}
#[test]
fn test_pop() {
let mut ss = ScopeStack::new();
ss.push();
ss.push();
assert_eq!(ss.stack.len(), 3);
ss.pop();
assert_eq!(ss.stack.len(), 2);
ss.pop();
assert_eq!(ss.stack.len(), 1);
}
#[test]
#[should_panic]
fn test_pop_global_scope() {
let mut ss = ScopeStack::new();
assert_eq!(ss.stack.len(), 1);
ss.pop();
}
#[test]
fn test_current() {
let mut ss = ScopeStack::new();
assert_eq!(ss.current(), 0);
ss.push();
assert_eq!(ss.current(), 1);
ss.push();
assert_eq!(ss.current(), 2);
ss.pop();
assert_eq!(ss.current(), 1);
ss.pop();
assert_eq!(ss.current(), 0);
}
#[test]
fn test_set_levels() {
let mut ss = ScopeStack::new();
ss.set("a", Value::from("1"));
ss.set("b", Value::from("2"));
ss.push();
assert!(ss.get("a").is_none());
assert!(ss.get("b").is_none());
assert!(ss.get("c").is_none());
ss.set("a", Value::from("3"));
ss.set("b", Value::from("4"));
ss.set("c", Value::from("5"));
assert_eq!(ss.get("a").unwrap().as_str(), "3");
assert_eq!(ss.get("b").unwrap().as_str(), "4");
assert_eq!(ss.get("c").unwrap().as_str(), "5");
ss.pop();
assert_eq!(ss.get("a").unwrap().as_str(), "1");
assert_eq!(ss.get("b").unwrap().as_str(), "2");
assert!(ss.get("c").is_none());
}
#[test]
fn test_set_get_upvar() {
let mut ss = ScopeStack::new();
ss.set("a", Value::from("1"));
ss.set("b", Value::from("2"));
ss.push();
ss.upvar(0, "a");
assert_eq!(ss.get("a").unwrap().as_str(), "1");
assert!(ss.get("b").is_none());
ss.set("a", Value::from("3"));
ss.set("b", Value::from("4"));
assert_eq!(ss.get("a").unwrap().as_str(), "3");
assert_eq!(ss.get("b").unwrap().as_str(), "4");
ss.pop();
assert_eq!(ss.get("a").unwrap().as_str(), "3");
assert_eq!(ss.get("b").unwrap().as_str(), "2");
}
#[test]
fn test_unset_levels() {
let mut ss = ScopeStack::new();
ss.set("a", Value::from("1"));
ss.set("b", Value::from("2"));
ss.push();
ss.set("a", Value::from("3"));
ss.unset("a"); ss.unset("b");
ss.pop();
assert_eq!(ss.get("a").unwrap().as_str(), "1");
assert_eq!(ss.get("b").unwrap().as_str(), "2");
}
#[test]
fn test_unset_upvar() {
let mut ss = ScopeStack::new();
ss.set("a", Value::from("1"));
assert!(ss.get("a").is_some());
ss.push();
assert!(ss.get("a").is_none());
ss.upvar(0, "a");
assert!(ss.get("a").is_some());
ss.unset("a");
assert!(ss.get("a").is_none());
ss.pop();
assert!(ss.get("a").is_none());
}
#[test]
fn test_vars_in_scope() {
let mut ss = ScopeStack::new();
assert_eq!(ss.vars_in_scope().len(), 0);
ss.set("a", Value::from("1"));
ss.set("b", Value::from("2"));
assert_eq!(ss.vars_in_scope().len(), 2);
assert!(ss.vars_in_scope().contains(&Value::from("a")));
assert!(ss.vars_in_scope().contains(&Value::from("b")));
ss.push();
assert_eq!(ss.vars_in_scope().len(), 0);
ss.set("c", Value::from("3"));
assert_eq!(ss.vars_in_scope().len(), 1);
assert!(ss.vars_in_scope().contains(&Value::from("c")));
ss.upvar(0, "a");
assert_eq!(ss.vars_in_scope().len(), 2);
assert!(ss.vars_in_scope().contains(&Value::from("a")));
ss.pop();
assert_eq!(ss.vars_in_scope().len(), 2);
assert!(!ss.vars_in_scope().contains(&Value::from("c")));
ss.unset("b");
assert_eq!(ss.vars_in_scope().len(), 1);
assert!(!ss.vars_in_scope().contains(&Value::from("b")));
}
}