use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use crate::value::{PromiseState, Value};
const MAX_VALUE_DEPTH: usize = 64;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Seal {
MacroExpansion,
Fork,
}
impl Seal {
#[must_use]
pub const fn refusal(self) -> &'static str {
match self {
Self::MacroExpansion => {
"it is bound outside the expansion and sealed. Macro expansion must be \
deterministic, so compile-time state cannot outlive the expansion. Use a \
local binding, or return the value in the expansion."
}
Self::Fork => {
"it belongs to the parent environment this one was forked from, which every \
sibling also shares. Writing it would be visible outside this child. Shadow \
it with a local `define` instead."
}
}
}
}
#[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>>,
write_floor: usize,
seal: Option<Seal>,
}
impl Default for Env {
fn default() -> Self {
Self::new()
}
}
impl Env {
pub fn new() -> Self {
Self {
frames: vec![Arc::new(Frame::new())],
write_floor: 0,
seal: None,
}
}
pub fn sealed_below_top(&self, reason: Seal) -> Self {
let mut env = self.clone();
env.write_floor = env.frames.len();
env.seal = Some(reason);
env.push();
env
}
#[must_use]
pub fn seal(&self) -> Option<Seal> {
self.seal
}
pub fn is_sealed_binding(&self, name: &str) -> bool {
if self.write_floor == 0 {
return false;
}
let writable_has_it = self.frames[self.write_floor..]
.iter()
.any(|f| f.bindings.lock().unwrap().contains_key(name));
if writable_has_it {
return false;
}
self.frames[..self.write_floor]
.iter()
.any(|f| f.bindings.lock().unwrap().contains_key(name))
}
pub fn push(&mut self) {
self.frames.push(Arc::new(Frame::new()));
}
pub fn pop(&mut self) {
if self.frames.len() > 1 && self.frames.len() > self.write_floor + 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[self.write_floor..].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 write_floor(&self) -> usize {
self.write_floor
}
pub fn release_own_frames(&mut self) -> usize {
let mut released = 0;
for frame in &self.frames[self.write_floor..] {
if Self::frame_is_exclusively_ours(frame) {
let drained = {
let mut bindings = frame.bindings.lock().unwrap();
std::mem::take(&mut *bindings)
};
drop(drained);
released += 1;
}
}
released
}
fn frame_is_exclusively_ours(frame: &Arc<Frame>) -> bool {
let Ok(bindings) = frame.bindings.lock() else {
return false;
};
let mut internal = 0usize;
for value in bindings.values() {
let Some(n) = handles_to_frame(frame, value, MAX_VALUE_DEPTH) else {
return false;
};
internal += n;
}
Arc::strong_count(frame) == 1 + internal
}
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()
}
}
}
fn frames_pointing_at(env: &Env, frame: &Arc<Frame>) -> usize {
env.frames.iter().filter(|f| Arc::ptr_eq(f, frame)).count()
}
fn handles_to_frame(frame: &Arc<Frame>, value: &Value, depth: usize) -> Option<usize> {
let next = depth.checked_sub(1)?;
Some(match value {
Value::Closure(c) if Arc::strong_count(c) == 1 => {
frames_pointing_at(&c.captured_env, frame)
}
Value::Promise(p) if Arc::strong_count(p) == 1 => {
let state = p.lock().ok()?;
match &*state {
PromiseState::Pending(thunk) if Arc::strong_count(thunk) == 1 => {
frames_pointing_at(&thunk.captured_env, frame)
}
PromiseState::Pending(_) => return None,
PromiseState::Forced(v) => handles_to_frame(frame, v, next)?,
}
}
Value::List(xs) if Arc::strong_count(xs) == 1 => {
let mut n = 0;
for x in xs.as_ref() {
n += handles_to_frame(frame, x, next)?;
}
n
}
Value::Map(m) if Arc::strong_count(m) == 1 => {
let mut n = 0;
for v in m.values() {
n += handles_to_frame(frame, v, next)?;
}
n
}
Value::Error(e) if Arc::strong_count(e) == 1 => {
let mut n = 0;
for (k, v) in &e.data {
n += handles_to_frame(frame, k, next)?;
n += handles_to_frame(frame, v, next)?;
}
n
}
Value::Foreign(any) => match any.downcast_ref::<crate::vm::run::CompiledClosure>() {
Some(cc) if Arc::strong_count(any) == 1 => {
let mut n = frames_pointing_at(&cc.globals, frame);
for cell in &cc.captures {
if Arc::strong_count(cell) != 1 {
return None;
}
let captured = cell.lock().ok()?;
n += handles_to_frame(frame, &captured, next)?;
}
n
}
_ => 0,
},
Value::Closure(_)
| Value::Promise(_)
| Value::List(_)
| Value::Map(_)
| Value::Error(_) => return None,
Value::Nil
| Value::Bool(_)
| Value::Int(_)
| Value::Float(_)
| Value::Str(_)
| Value::Symbol(_)
| Value::Keyword(_)
| Value::NativeFn(_)
| Value::Sexp(..) => 0,
})
}
#[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());
}
}