use crate::language::syntax::ast::Value;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BindingType {
Let,
Var,
Const,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Binding {
pub value: Value,
pub binding_type: BindingType,
pub is_initialized: bool,
}
impl Binding {
pub fn new(value: Value, binding_type: BindingType) -> Self {
Self {
value,
binding_type,
is_initialized: true,
}
}
pub fn can_reassign(&self) -> bool {
matches!(self.binding_type, BindingType::Let | BindingType::Var)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct VariableTable {
bindings: HashMap<String, Binding>,
parent: Option<Box<VariableTable>>,
}
impl Default for VariableTable {
fn default() -> Self {
Self::new()
}
}
impl VariableTable {
pub fn new() -> Self {
Self {
bindings: HashMap::new(),
parent: None,
}
}
pub fn with_parent(parent: VariableTable) -> Self {
Self {
bindings: HashMap::new(),
parent: Some(Box::new(parent)),
}
}
pub fn set(&mut self, name: String, value: Value) {
self.set_with_type(name, value, BindingType::Let);
}
pub fn set_with_type(&mut self, name: String, value: Value, binding_type: BindingType) {
if binding_type == BindingType::Var {
if self.has_var_in_chain(&name) {
self.update_var_in_chain(name, value);
return;
}
}
self.bindings
.insert(name, Binding::new(value, binding_type));
}
pub fn update(&mut self, name: &str, value: Value) -> Result<(), String> {
if let Some(binding) = self.bindings.get_mut(name) {
if !binding.can_reassign() {
return Err(format!("Cannot reassign const variable '{}'", name));
}
binding.value = value;
return Ok(());
}
if let Some(parent) = &mut self.parent {
return parent.update(name, value);
}
Err(format!("Variable '{}' not found", name))
}
pub fn get(&self, name: &str) -> Option<&Value> {
if let Some(binding) = self.bindings.get(name) {
return Some(&binding.value);
}
let mut current = &self.parent;
while let Some(boxed) = current {
if let Some(binding) = boxed.bindings.get(name) {
return Some(&binding.value);
}
current = &boxed.parent;
}
None
}
pub fn get_binding(&self, name: &str) -> Option<&Binding> {
if let Some(binding) = self.bindings.get(name) {
return Some(binding);
}
let mut current = &self.parent;
while let Some(boxed) = current {
if let Some(binding) = boxed.bindings.get(name) {
return Some(binding);
}
current = &boxed.parent;
}
None
}
pub fn has_local(&self, name: &str) -> bool {
self.bindings.contains_key(name)
}
pub fn has(&self, name: &str) -> bool {
self.get(name).is_some()
}
pub fn remove(&mut self, name: &str) -> Option<Value> {
self.bindings.remove(name).map(|b| b.value)
}
pub fn local_variables(&self) -> Vec<String> {
self.bindings.keys().cloned().collect()
}
fn has_var_in_chain(&self, name: &str) -> bool {
if let Some(binding) = self.bindings.get(name) {
return binding.binding_type == BindingType::Var;
}
if let Some(parent) = &self.parent {
return parent.has_var_in_chain(name);
}
false
}
fn update_var_in_chain(&mut self, name: String, value: Value) {
if let Some(binding) = self.bindings.get_mut(&name) {
if binding.binding_type == BindingType::Var {
binding.value = value;
return;
}
}
if let Some(parent) = &mut self.parent {
parent.update_var_in_chain(name, value);
}
}
}
#[cfg(test)]
#[path = "test_variables.rs"]
mod tests;