use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use crate::value::Value;
#[derive(Default)]
pub struct Frame {
bindings: Mutex<HashMap<Arc<str>, Value>>,
}
impl Frame {
fn new() -> Self {
Self::default()
}
}
impl std::fmt::Debug for Frame {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Frame")
.field("len", &self.bindings.lock().unwrap().len())
.finish()
}
}
#[derive(Clone, Debug)]
pub struct Env {
frames: Vec<Arc<Frame>>,
}
impl Default for Env {
fn default() -> Self {
Self::new()
}
}
impl Env {
pub fn new() -> Self {
Self {
frames: vec![Arc::new(Frame::new())],
}
}
pub fn push(&mut self) {
self.frames.push(Arc::new(Frame::new()));
}
pub fn pop(&mut self) {
if self.frames.len() > 1 {
self.frames.pop();
}
}
pub fn define(&self, name: impl Into<Arc<str>>, value: Value) {
if let Some(top) = self.frames.last() {
top.bindings.lock().unwrap().insert(name.into(), value);
}
}
pub fn lookup(&self, name: &str) -> Option<Value> {
for frame in self.frames.iter().rev() {
if let Some(v) = frame.bindings.lock().unwrap().get(name) {
return Some(v.clone());
}
}
None
}
pub fn set(&self, name: &str, value: Value) -> bool {
for frame in self.frames.iter().rev() {
let mut bindings = frame.bindings.lock().unwrap();
if let Some(slot) = bindings.get_mut(name) {
*slot = value;
return true;
}
}
false
}
pub fn frame_depth(&self) -> usize {
self.frames.len()
}
pub fn iter_top_level(&self) -> Vec<(Arc<str>, Value)> {
if let Some(root) = self.frames.first() {
root.bindings
.lock()
.unwrap()
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect()
} else {
Vec::new()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lookup_walks_chain() {
let mut env = Env::new();
env.define("x", Value::Int(1));
env.push();
env.define("y", Value::Int(2));
assert!(matches!(env.lookup("x"), Some(Value::Int(1))));
assert!(matches!(env.lookup("y"), Some(Value::Int(2))));
env.pop();
assert!(env.lookup("y").is_none());
}
#[test]
fn set_mutates_existing_binding() {
let env = Env::new();
env.define("x", Value::Int(1));
assert!(env.set("x", Value::Int(99)));
assert!(matches!(env.lookup("x"), Some(Value::Int(99))));
assert!(!env.set("no-such", Value::Nil));
}
#[test]
fn cloned_env_shares_frame_state() {
let env_a = Env::new();
let env_b = env_a.clone();
env_a.define("x", Value::Int(42));
assert!(matches!(env_b.lookup("x"), Some(Value::Int(42))));
}
#[test]
fn push_after_clone_diverges() {
let mut env_a = Env::new();
let env_b = env_a.clone();
env_a.push();
env_a.define("only-in-a", Value::Int(7));
assert!(matches!(env_a.lookup("only-in-a"), Some(Value::Int(7))));
assert!(env_b.lookup("only-in-a").is_none());
}
}