#![allow(non_local_definitions)]
use crate::scheme::value::Value;
use gc::{Gc, GcCell};
use std::collections::HashMap;
use std::rc::Rc;
#[derive(Debug, Clone, gc::Trace, gc::Finalize)]
pub struct Environment {
bindings: GcCell<HashMap<Rc<str>, Value>>,
parent: Option<Gc<Environment>>,
}
impl Environment {
pub fn new_global() -> Gc<Self> {
Gc::new(Environment {
bindings: GcCell::new(HashMap::new()),
parent: None,
})
}
pub fn extend(parent: Gc<Environment>) -> Gc<Self> {
Gc::new(Environment {
bindings: GcCell::new(HashMap::new()),
parent: Some(parent),
})
}
pub fn define(&self, name: &str, value: Value) {
let symbol = Rc::from(name);
self.bindings.borrow_mut().insert(symbol, value);
}
pub fn lookup(&self, name: &str) -> Option<Value> {
let symbol = Rc::from(name);
if let Some(value) = self.bindings.borrow().get(&symbol) {
return Some(value.clone());
}
if let Some(ref parent) = self.parent {
return parent.lookup(name);
}
None
}
pub fn set(&self, name: &str, value: Value) -> Result<(), String> {
let symbol = Rc::from(name);
if self.bindings.borrow().contains_key(&symbol) {
self.bindings.borrow_mut().insert(symbol, value);
return Ok(());
}
if let Some(ref parent) = self.parent {
return parent.set(name, value);
}
Err(format!("Undefined variable: {}", name))
}
pub fn is_defined(&self, name: &str) -> bool {
self.lookup(name).is_some()
}
pub fn parent(&self) -> Option<Gc<Environment>> {
self.parent.clone()
}
pub fn local_bindings(&self) -> Vec<(Rc<str>, Value)> {
self.bindings
.borrow()
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect()
}
pub fn all_bindings(&self) -> HashMap<String, Value> {
let mut result = HashMap::new();
if let Some(ref parent) = self.parent {
result = parent.all_bindings();
}
for (name, value) in self.bindings.borrow().iter() {
result.insert(name.to_string(), value.clone());
}
result
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_global_environment() {
let env = Environment::new_global();
env.define("x", Value::integer(42));
assert_eq!(
env.lookup("x").unwrap().is_integer(),
true
);
assert!(env.lookup("y").is_none());
}
#[test]
fn test_nested_environments() {
let global = Environment::new_global();
global.define("x", Value::integer(1));
global.define("y", Value::integer(2));
let local = Environment::extend(global.clone());
local.define("y", Value::integer(20)); local.define("z", Value::integer(30));
if let Value::Integer(n) = local.lookup("z").unwrap() {
assert_eq!(n, 30);
}
if let Value::Integer(n) = local.lookup("y").unwrap() {
assert_eq!(n, 20); }
if let Value::Integer(n) = local.lookup("x").unwrap() {
assert_eq!(n, 1); }
if let Value::Integer(n) = global.lookup("y").unwrap() {
assert_eq!(n, 2); }
}
#[test]
fn test_set_variable() {
let env = Environment::new_global();
env.define("x", Value::integer(1));
assert!(env.set("x", Value::integer(2)).is_ok());
if let Value::Integer(n) = env.lookup("x").unwrap() {
assert_eq!(n, 2);
}
assert!(env.set("y", Value::integer(3)).is_err());
}
#[test]
fn test_is_defined() {
let global = Environment::new_global();
global.define("x", Value::integer(1));
let local = Environment::extend(global.clone());
local.define("y", Value::integer(2));
assert!(local.is_defined("x")); assert!(local.is_defined("y")); assert!(!local.is_defined("z")); }
#[test]
fn test_deep_nesting() {
let global = Environment::new_global();
global.define("a", Value::integer(1));
let env1 = Environment::extend(global.clone());
env1.define("b", Value::integer(2));
let env2 = Environment::extend(env1.clone());
env2.define("c", Value::integer(3));
let env3 = Environment::extend(env2.clone());
env3.define("d", Value::integer(4));
assert!(env3.is_defined("a"));
assert!(env3.is_defined("b"));
assert!(env3.is_defined("c"));
assert!(env3.is_defined("d"));
}
}