use std::collections::hash_map::Entry::Occupied;
use std::collections::hash_map::Entry::Vacant;
use std::collections::HashMap;
use thiserror::Error;
use crate::graph::Value;
use crate::Identifier;
#[derive(Debug, Error)]
pub enum VariableError {
#[error("Cannot assign immutable variable")]
CannotAssignImmutableVariable(String),
#[error("Variable already defined")]
VariableAlreadyDefined(String),
#[error("Undefined variable")]
UndefinedVariable(String),
}
pub(crate) trait Variables<V> {
fn get(&self, name: &Identifier) -> Option<&V>;
}
pub(crate) trait MutVariables<V>: Variables<V> {
fn add(&mut self, name: Identifier, value: V, mutable: bool) -> Result<(), VariableError>;
fn set(&mut self, name: Identifier, value: V) -> Result<(), VariableError>;
}
pub(crate) struct VariableMap<'a, V> {
context: Option<&'a mut dyn MutVariables<V>>,
values: HashMap<Identifier, Variable<V>>,
}
struct Variable<V> {
value: V,
mutable: bool,
}
impl<'a, V> VariableMap<'a, V> {
pub(crate) fn new() -> Self {
Self {
context: None,
values: HashMap::new(),
}
}
pub(crate) fn nested(context: &'a mut dyn MutVariables<V>) -> Self {
Self {
context: Some(context),
values: HashMap::new(),
}
}
pub(crate) fn clear(&mut self) {
self.values.clear();
}
}
impl<V> Variables<V> for VariableMap<'_, V> {
fn get(&self, name: &Identifier) -> Option<&V> {
self.values
.get(name)
.map(|v| &v.value)
.or_else(|| self.context.as_ref().map(|p| p.get(name)).flatten())
}
}
impl<V> MutVariables<V> for VariableMap<'_, V> {
fn add(&mut self, name: Identifier, value: V, mutable: bool) -> Result<(), VariableError> {
match self.values.entry(name) {
Vacant(v) => {
let variable = Variable { value, mutable };
v.insert(variable);
Ok(())
}
Occupied(o) => Err(VariableError::VariableAlreadyDefined(o.key().to_string())),
}
}
fn set(&mut self, name: Identifier, value: V) -> Result<(), VariableError> {
match self.values.entry(name) {
Vacant(v) => self
.context
.as_mut()
.map(|context| context.set(v.key().clone(), value))
.unwrap_or(Err(VariableError::UndefinedVariable(
v.into_key().to_string(),
))),
Occupied(mut o) => {
let variable = o.get_mut();
if variable.mutable {
variable.value = value;
Ok(())
} else {
Err(VariableError::CannotAssignImmutableVariable(
o.key().to_string(),
))
}
}
}
}
}
pub struct Globals<'a> {
context: Option<&'a dyn Variables<Value>>,
values: HashMap<Identifier, Value>,
}
impl<'a> Globals<'a> {
pub fn new() -> Self {
Self {
context: None,
values: HashMap::new(),
}
}
pub fn nested(context: &'a Globals<'a>) -> Self {
Self {
context: Some(context),
values: HashMap::new(),
}
}
pub fn add(&mut self, name: Identifier, value: Value) -> Result<(), VariableError> {
match self.values.entry(name) {
Vacant(v) => {
v.insert(value);
Ok(())
}
Occupied(o) => Err(VariableError::VariableAlreadyDefined(o.key().to_string())),
}
}
pub fn get(&self, name: &Identifier) -> Option<&Value> {
self.values
.get(name)
.or_else(|| self.context.as_ref().map(|p| p.get(name)).flatten())
}
pub fn remove(&mut self, name: &Identifier) {
self.values.remove(name);
}
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
pub fn iter<'b>(&'b self) -> Iter<'b> {
Iter(self.values.iter())
}
pub fn clear(&mut self) {
self.values.clear();
}
}
pub struct Iter<'a>(std::collections::hash_map::Iter<'a, Identifier, Value>);
impl<'a> std::iter::Iterator for Iter<'a> {
type Item = (&'a Identifier, &'a Value);
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}
impl Variables<Value> for Globals<'_> {
fn get(&self, name: &Identifier) -> Option<&Value> {
self.get(name)
}
}