use crate::error::{LambdustError, Result};
use crate::value::Value;
use std::collections::HashMap;
use std::rc::Rc;
use std::cell::RefCell;
#[derive(Debug, Clone)]
pub struct Environment {
bindings: Rc<RefCell<HashMap<String, Value>>>,
parent: Option<Rc<Environment>>,
}
impl Environment {
pub fn new() -> Self {
Environment {
bindings: Rc::new(RefCell::new(HashMap::new())),
parent: None,
}
}
pub fn with_parent(parent: Rc<Environment>) -> Self {
Environment {
bindings: Rc::new(RefCell::new(HashMap::new())),
parent: Some(parent),
}
}
pub fn with_bindings(bindings: HashMap<String, Value>) -> Self {
Environment {
bindings: Rc::new(RefCell::new(bindings)),
parent: None,
}
}
pub fn define(&self, name: String, value: Value) {
self.bindings.borrow_mut().insert(name, value);
}
pub fn set(&self, name: &str, value: Value) -> Result<()> {
if self.bindings.borrow().contains_key(name) {
self.bindings.borrow_mut().insert(name.to_string(), value);
return Ok(());
}
if let Some(ref parent) = self.parent {
parent.set(name, value)
} else {
Err(LambdustError::UndefinedVariable(name.to_string()))
}
}
pub fn get(&self, name: &str) -> Result<Value> {
if let Some(value) = self.bindings.borrow().get(name) {
return Ok(value.clone());
}
if let Some(ref parent) = self.parent {
parent.get(name)
} else {
Err(LambdustError::UndefinedVariable(name.to_string()))
}
}
pub fn contains(&self, name: &str) -> bool {
self.bindings.borrow().contains_key(name) ||
self.parent.as_ref().map_or(false, |p| p.contains(name))
}
pub fn extend(&self) -> Environment {
Environment::with_parent(Rc::new(self.clone()))
}
pub fn bind_parameters(&self, params: &[String], args: &[Value], variadic: bool) -> Result<Environment> {
let mut bindings = HashMap::new();
if variadic {
if params.is_empty() {
return Err(LambdustError::ArityError {
expected: 0,
actual: args.len(),
});
}
let required_params = params.len() - 1;
if args.len() < required_params {
return Err(LambdustError::ArityError {
expected: required_params,
actual: args.len(),
});
}
for (i, param) in params[..required_params].iter().enumerate() {
bindings.insert(param.clone(), args[i].clone());
}
let rest_args = args[required_params..].to_vec();
let rest_list = Value::from_vector(rest_args);
bindings.insert(params[required_params].clone(), rest_list);
} else {
if args.len() != params.len() {
return Err(LambdustError::ArityError {
expected: params.len(),
actual: args.len(),
});
}
for (param, arg) in params.iter().zip(args.iter()) {
bindings.insert(param.clone(), arg.clone());
}
}
Ok(Environment {
bindings: Rc::new(RefCell::new(bindings)),
parent: Some(Rc::new(self.clone())),
})
}
pub fn current_bindings(&self) -> HashMap<String, Value> {
self.bindings.borrow().clone()
}
pub fn global(&self) -> Rc<Environment> {
match &self.parent {
Some(parent) => parent.global(),
None => Rc::new(self.clone()),
}
}
pub fn with_builtins() -> Self {
let env = Environment::new();
let builtins = crate::builtins::create_builtins();
for (name, value) in builtins {
env.define(name, value);
}
env
}
}
impl Default for Environment {
fn default() -> Self {
Self::new()
}
}
impl PartialEq for Environment {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.bindings, &other.bindings)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_environment_define_get() {
let env = Environment::new();
env.define("x".to_string(), Value::from(42i64));
assert_eq!(env.get("x").unwrap(), Value::from(42i64));
assert!(env.get("y").is_err());
}
#[test]
fn test_environment_set() {
let env = Environment::new();
env.define("x".to_string(), Value::from(42i64));
env.set("x", Value::from(100i64)).unwrap();
assert_eq!(env.get("x").unwrap(), Value::from(100i64));
assert!(env.set("y", Value::from(200i64)).is_err());
}
#[test]
fn test_environment_scoping() {
let parent = Environment::new();
parent.define("x".to_string(), Value::from(42i64));
let child = Environment::with_parent(Rc::new(parent.clone()));
child.define("y".to_string(), Value::from(100i64));
assert_eq!(child.get("x").unwrap(), Value::from(42i64));
assert_eq!(child.get("y").unwrap(), Value::from(100i64));
assert!(parent.get("y").is_err());
}
#[test]
fn test_environment_shadowing() {
let parent = Environment::new();
parent.define("x".to_string(), Value::from(42i64));
let child = Environment::with_parent(Rc::new(parent.clone()));
child.define("x".to_string(), Value::from(100i64));
assert_eq!(child.get("x").unwrap(), Value::from(100i64));
assert_eq!(parent.get("x").unwrap(), Value::from(42i64));
}
#[test]
fn test_bind_parameters_fixed() {
let env = Environment::new();
let params = vec!["x".to_string(), "y".to_string()];
let args = vec![Value::from(1i64), Value::from(2i64)];
let new_env = env.bind_parameters(¶ms, &args, false).unwrap();
assert_eq!(new_env.get("x").unwrap(), Value::from(1i64));
assert_eq!(new_env.get("y").unwrap(), Value::from(2i64));
}
#[test]
fn test_bind_parameters_variadic() {
let env = Environment::new();
let params = vec!["x".to_string(), "rest".to_string()];
let args = vec![Value::from(1i64), Value::from(2i64), Value::from(3i64)];
let new_env = env.bind_parameters(¶ms, &args, true).unwrap();
assert_eq!(new_env.get("x").unwrap(), Value::from(1i64));
let rest = new_env.get("rest").unwrap();
assert!(rest.is_list());
assert_eq!(rest.list_length(), Some(2));
}
#[test]
fn test_bind_parameters_arity_error() {
let env = Environment::new();
let params = vec!["x".to_string(), "y".to_string()];
let args = vec![Value::from(1i64)];
let result = env.bind_parameters(¶ms, &args, false);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), LambdustError::ArityError { .. }));
}
#[test]
fn test_builtin_environment() {
let env = Environment::with_builtins();
assert!(env.contains("car"));
assert!(env.contains("cdr"));
assert!(env.contains("cons"));
assert!(env.contains("null?"));
assert!(env.contains("pair?"));
}
}