use super::*;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct Scope<T> {
map: HashMap<String, T>,
}
impl<T> Default for Scope<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> Scope<T> {
pub fn new() -> Self {
Self {
map: HashMap::new(),
}
}
pub fn define(&mut self, name: String, val: T) {
self.map.insert(name, val);
}
pub fn get(&self, var: &str) -> Option<&T> {
self.map.get(var)
}
pub fn get_mut(&mut self, var: &str) -> Option<&mut T> {
self.map.get_mut(var)
}
}
#[derive(Debug, Default, Clone)]
pub struct Scopes<T> {
pub top: Scope<T>,
pub scopes: Vec<Scope<T>>,
}
impl<T> Scopes<T> {
pub fn new() -> Self {
Self {
top: Scope::new(),
scopes: vec![],
}
}
pub fn enter(&mut self) {
self.scopes.push(std::mem::take(&mut self.top));
}
pub fn exit(&mut self) {
self.top = self.scopes.pop().expect("no pushed scope");
}
pub fn get(&self, var: &str) -> Result<&T> {
std::iter::once(&self.top)
.chain(self.scopes.iter().rev())
.find_map(|scope| scope.get(var))
.ok_or_else(|| unknown_variable(var))
}
pub fn get_in_math(&self, var: &str) -> Result<&T> {
std::iter::once(&self.top)
.chain(self.scopes.iter().rev())
.find_map(|scope| scope.get(var))
.ok_or_else(|| unknown_variable(var))
}
pub fn get_mut(&mut self, var: &str) -> Result<&mut T> {
std::iter::once(&mut self.top)
.chain(&mut self.scopes.iter_mut().rev())
.find_map(|scope| scope.get_mut(var))
.ok_or_else(|| unknown_variable(var))
}
pub fn define(&mut self, arg: &str, v: impl Into<T>) {
self.top.define(arg.to_string(), v.into());
}
}
fn unknown_variable(var: &str) -> Error {
format!("unknown variable: {var}").into()
}