use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use crate::value::{Closure, 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."
}
}
}
}
pub struct Frame {
bindings: Mutex<HashMap<Arc<str>, Value>>,
scope_open: AtomicBool,
}
impl Default for Frame {
fn default() -> Self {
Self {
bindings: Mutex::default(),
scope_open: AtomicBool::new(true),
}
}
}
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 release_if_closed(frame, frames_pointing_at(self, frame)) {
released += 1;
}
}
released
}
pub fn release_scope_frames(&self, keep: usize) -> usize {
let floor = keep.max(self.write_floor + 1);
let mut released = 0;
for frame in self.frames.iter().skip(floor).rev() {
frame.scope_open.store(false, Ordering::Release);
if release_if_closed(frame, frames_pointing_at(self, frame)) {
released += 1;
}
}
released
}
pub fn release_scope(&self) -> bool {
self.release_scope_frames(self.frames.len().saturating_sub(1)) == 1
}
pub fn pop_scope(&mut self) {
self.release_scope();
self.pop();
}
pub fn drop_releasing_defining_frame(closure: Arc<Closure>) {
if Arc::strong_count(&closure) > 2 {
return;
}
let env = &closure.captured_env;
if env.frames.len() <= env.write_floor + 1 {
return;
}
let Some(frame) = env.frames.last().map(Arc::clone) else {
return;
};
if frame.scope_open.load(Ordering::Acquire) {
return;
}
drop(closure);
release_if_closed(&frame, 1);
}
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 release_if_closed(frame: &Arc<Frame>, held: usize) -> bool {
let strong = Arc::strong_count(frame);
let Ok(bindings) = frame.bindings.lock() else {
return false;
};
if strong != held {
let internal: usize = bindings
.values()
.map(|value| handles_to_frame(frame, value, MAX_VALUE_DEPTH))
.sum();
if strong != held + internal {
return false;
}
}
let mut bindings = bindings;
let drained = std::mem::take(&mut *bindings);
drop(bindings);
drop(drained);
true
}
fn handles_to_frame(frame: &Arc<Frame>, value: &Value, depth: usize) -> usize {
let Some(next) = depth.checked_sub(1) else {
return 0;
};
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 Ok(state) = p.lock() else {
return 0;
};
match &*state {
PromiseState::Pending(thunk) if Arc::strong_count(thunk) == 1 => {
frames_pointing_at(&thunk.captured_env, frame)
}
PromiseState::Pending(_) => 0,
PromiseState::Forced(v) => handles_to_frame(frame, v, next),
}
}
Value::Error(e) if Arc::strong_count(e) == 1 => e
.data
.iter()
.map(|(k, v)| handles_to_frame(frame, k, next) + handles_to_frame(frame, v, next))
.sum(),
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 {
if let Ok(captured) = cell.lock() {
n += handles_to_frame(frame, &captured, next);
}
}
}
n
}
_ => 0,
},
Value::Closure(_)
| Value::Promise(_)
| Value::List(_)
| Value::Map(_)
| Value::Error(_)
| 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());
}
}