use std::cell::UnsafeCell;
use std::fmt;
use std::pin::Pin;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use anyhow::Result;
use env::Environment;
use indexmap::IndexMap;
use serde::Deserialize;
use serde::Serialize;
use crate::SourceRange;
use crate::errors::KclError;
use crate::errors::KclErrorDetails;
use crate::execution::KclValue;
pub(crate) const RETURN_NAME: &str = "__return";
pub(crate) const TYPE_PREFIX: &str = "__ty_";
pub(crate) const MODULE_PREFIX: &str = "__mod_";
pub(crate) const SKETCH_PREFIX: &str = "__sketch_";
#[derive(Debug)]
pub(crate) struct ProgramMemory {
environments: UnsafeCell<Vec<Pin<Box<Environment>>>>,
std: Option<EnvironmentRef>,
pub(crate) stats: MemoryStats,
next_stack_id: AtomicUsize,
epoch: AtomicUsize,
write_lock: AtomicBool,
}
unsafe impl Sync for ProgramMemory {}
#[derive(Debug, Clone)]
pub(crate) struct Stack {
pub(crate) memory: Arc<ProgramMemory>,
id: usize,
current_env: EnvironmentRef,
call_stack: Vec<EnvironmentRef>,
}
impl fmt::Display for ProgramMemory {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let envs: Vec<String> = self
.envs()
.iter()
.enumerate()
.map(|(i, env)| format!("{i}: {env}"))
.collect();
write!(
f,
"ProgramMemory (next stack: {})\nenvs:\n{}",
self.next_stack_id.load(Ordering::Relaxed),
envs.join("\n")
)
}
}
impl fmt::Display for Stack {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let stack: Vec<String> = self
.call_stack
.iter()
.chain(Some(&self.current_env))
.map(|e| format!("EnvRef({}, {})", e.0, e.1))
.collect();
write!(f, "Stack {}\nstack frames:\n{}", self.id, stack.join("\n"))
}
}
impl ProgramMemory {
#[allow(clippy::new_without_default)]
pub fn new() -> Arc<Self> {
Arc::new(Self {
environments: UnsafeCell::new(Vec::with_capacity(512)),
std: None,
stats: MemoryStats::default(),
next_stack_id: AtomicUsize::new(1),
epoch: AtomicUsize::new(1),
write_lock: AtomicBool::new(false),
})
}
fn deep_clone(&self) -> Self {
self.with_envs(|envs| Self {
environments: UnsafeCell::new(envs.clone()),
std: self.std,
stats: MemoryStats::default(),
next_stack_id: AtomicUsize::new(self.next_stack_id.load(Ordering::Relaxed)),
epoch: AtomicUsize::new(self.epoch.load(Ordering::Relaxed)),
write_lock: AtomicBool::new(false),
})
}
pub fn new_stack(self: Arc<Self>) -> Stack {
let id = self.next_stack_id.fetch_add(1, Ordering::Relaxed);
assert!(id > 0);
Stack {
id,
memory: self,
current_env: EnvironmentRef::dummy(),
call_stack: Vec::new(),
}
}
pub fn set_std(self: &mut Arc<Self>, std: EnvironmentRef) {
Arc::get_mut(self).unwrap().std = Some(std);
}
pub fn requires_std(&self) -> bool {
self.std.is_none()
}
pub fn get_from(
&self,
var: &str,
mut env_ref: EnvironmentRef,
source_range: SourceRange,
owner: usize,
) -> Result<&KclValue, KclError> {
loop {
let env = self.get_env(env_ref.index());
env_ref = match env.get(var, env_ref.1, owner) {
Ok(item) => return Ok(item),
Err(Some(parent)) => parent,
Err(None) => break,
};
}
let name = var.trim_start_matches(TYPE_PREFIX).trim_start_matches(MODULE_PREFIX);
Err(KclError::new_undefined_value(
KclErrorDetails::new(format!("`{name}` is not defined"), vec![source_range]),
Some(name.to_owned()),
))
}
fn find_all_in_env<'a>(
&'a self,
env: EnvironmentRef,
pred: impl Fn(&KclValue) -> bool + 'a,
owner: usize,
) -> impl Iterator<Item = (&'a String, &'a KclValue)> {
assert!(!env.skip_env());
self.get_env(env.index()).find_all_by(pred, owner)
}
fn envs(&self) -> &[Pin<Box<Environment>>] {
unsafe { self.environments.get().as_ref().unwrap() }
}
#[track_caller]
fn get_env(&self, index: usize) -> &Environment {
unsafe { &self.environments.get().as_ref().unwrap()[index] }
}
fn with_envs<T>(&self, f: impl FnOnce(&mut Vec<Pin<Box<Environment>>>) -> T) -> T {
while self.write_lock.swap(true, Ordering::AcqRel) {
self.stats.lock_waits.fetch_add(1, Ordering::Relaxed);
std::hint::spin_loop();
}
let envs = unsafe { self.environments.get().as_mut().unwrap() };
let result = f(envs);
let locked = self.write_lock.fetch_not(Ordering::AcqRel);
assert!(locked);
result
}
fn new_env(&self, parent: Option<EnvironmentRef>, is_root_env: bool, owner: usize) -> EnvironmentRef {
assert!(owner > 0);
self.stats.env_count.fetch_add(1, Ordering::Relaxed);
let new_env = Environment::new(parent, is_root_env, owner);
self.with_envs(|envs| {
let result = EnvironmentRef(envs.len(), usize::MAX);
envs.push(Box::pin(new_env));
result
})
}
fn pop_env(&self, old: EnvironmentRef, owner: usize) {
self.get_env(old.index()).compact(owner);
if self.get_env(old.index()).is_empty() {
self.with_envs(|envs| {
if old.index() == envs.len() - 1 {
self.stats.env_gcs.fetch_add(1, Ordering::Relaxed);
envs.pop();
} else {
self.stats.skipped_env_gcs.fetch_add(1, Ordering::Relaxed);
envs[old.index()].read_only();
}
});
} else {
self.stats.preserved_envs.fetch_add(1, Ordering::Relaxed);
self.get_env(old.index()).read_only();
}
}
fn take_env(&self, old: EnvironmentRef) -> Pin<Box<Environment>> {
self.with_envs(|envs| {
if old.index() == envs.len() - 1 {
self.stats.env_gcs.fetch_add(1, Ordering::Relaxed);
envs.pop().unwrap()
} else {
self.stats.skipped_env_gcs.fetch_add(1, Ordering::Relaxed);
std::mem::replace(&mut envs[old.index()], Box::pin(Environment::new(None, false, 0)))
}
})
}
#[cfg(test)]
pub fn get_from_unchecked(&self, var: &str, mut env_ref: EnvironmentRef) -> Result<&KclValue, KclError> {
loop {
let env = self.get_env(env_ref.index());
env_ref = match env.get_unchecked(var, env_ref.1) {
Ok(item) => return Ok(item),
Err(Some(parent)) => parent,
Err(None) => break,
};
}
Err(KclError::new_undefined_value(
KclErrorDetails::new(format!("`{var}` is not defined"), vec![]),
Some(var.to_owned()),
))
}
}
impl Stack {
pub fn deep_clone(&self) -> Stack {
let mem = self.memory.deep_clone();
let mut stack = self.clone();
stack.memory = Arc::new(mem);
stack
}
#[cfg(test)]
pub fn new_for_tests() -> Stack {
let mut stack = ProgramMemory::new().new_stack();
stack.push_new_root_env(false);
stack.memory.set_std(stack.current_env);
stack
}
pub fn current_epoch(&self) -> usize {
self.memory.epoch.load(Ordering::Relaxed)
}
pub fn push_new_env_for_call(&mut self, parent: EnvironmentRef) {
let env_ref = self.memory.new_env(Some(parent), false, self.id);
self.call_stack.push(self.current_env);
self.current_env = env_ref;
}
pub fn push_new_env_for_scope(&mut self) {
let snapshot = self.snapshot();
self.push_new_env_for_call(snapshot);
}
pub fn push_new_root_env(&mut self, include_prelude: bool) {
let parent = include_prelude.then(|| self.memory.std.unwrap());
let env_ref = self.memory.new_env(parent, true, self.id);
self.call_stack.push(self.current_env);
self.current_env = env_ref;
}
pub fn restore_env(&mut self, env: EnvironmentRef) {
self.call_stack.push(self.current_env);
self.memory.get_env(env.index()).restore_owner(self.id);
self.current_env = env;
}
pub fn pop_env(&mut self) -> EnvironmentRef {
let old = self.current_env;
self.current_env = self.call_stack.pop().unwrap();
if !old.skip_env() {
self.memory.pop_env(old, self.id);
}
old
}
pub fn pop_and_preserve_env(&mut self) -> EnvironmentRef {
let old = self.current_env;
self.current_env = self.call_stack.pop().unwrap();
if !old.skip_env() {
self.memory.get_env(old.index()).read_only();
}
old
}
pub fn squash_env(&mut self, old: EnvironmentRef) {
assert!(!old.skip_env());
if self.current_env.skip_env() {
return;
}
let mut old_env = self.memory.take_env(old);
if old_env.is_empty() {
return;
}
self.push_new_env_for_scope();
let env = self.memory.get_env(self.current_env.index());
for (k, (e, v)) in old_env.as_mut().take_bindings() {
env.insert(k, e, v.map_env_ref(old, self.current_env), self.id);
}
}
pub fn snapshot(&mut self) -> EnvironmentRef {
self.memory.stats.epoch_count.fetch_add(1, Ordering::Relaxed);
let env = self.memory.get_env(self.current_env.index());
env.mark_as_refed();
let prev_epoch = self.memory.epoch.fetch_add(1, Ordering::Relaxed);
EnvironmentRef(self.current_env.0, prev_epoch)
}
pub fn add(&mut self, key: String, value: KclValue, source_range: SourceRange) -> Result<(), KclError> {
let env = self.memory.get_env(self.current_env.index());
if env.contains_key(&key) {
return Err(KclError::new_value_already_defined(KclErrorDetails::new(
format!("Cannot redefine `{key}`"),
vec![source_range],
)));
}
self.memory.stats.mutation_count.fetch_add(1, Ordering::Relaxed);
env.insert(key, self.memory.epoch.load(Ordering::Relaxed), value, self.id);
Ok(())
}
pub fn add_recursive_closure(
&mut self,
key: String,
value: KclValue,
placeholder_env_ref: EnvironmentRef,
source_range: SourceRange,
) -> Result<KclValue, KclError> {
let original_env = self.current_env;
let env = self.memory.get_env(self.current_env.index());
if env.contains_key(&key) {
return Err(KclError::new_value_already_defined(KclErrorDetails::new(
format!("Cannot redefine `{key}`"),
vec![source_range],
)));
}
self.memory.stats.mutation_count.fetch_add(1, Ordering::Relaxed);
let epoch = self.current_epoch();
env.insert(key.clone(), epoch, value.clone(), self.id);
let fixed_env_ref = self.snapshot();
let fixed_closure = value.map_env_ref_and_epoch(placeholder_env_ref, fixed_env_ref);
let env = self.memory.get_env(original_env.index());
env.update(
&key,
|closure, _| {
*closure = fixed_closure.clone();
},
epoch,
self.id,
);
Ok(fixed_closure)
}
pub fn update(&mut self, key: &str, f: impl Fn(&mut KclValue, usize)) {
self.memory.stats.mutation_count.fetch_add(1, Ordering::Relaxed);
self.memory.get_env(self.current_env.index()).update(
key,
f,
self.memory.epoch.load(Ordering::Relaxed),
self.id,
);
}
pub fn get(&self, var: &str, source_range: SourceRange) -> Result<&KclValue, KclError> {
self.memory.get_from(var, self.current_env, source_range, self.id)
}
pub fn cur_frame_contains(&self, var: &str) -> bool {
let env = self.memory.get_env(self.current_env.index());
env.contains_key(var)
}
pub fn get_from_call_stack(&self, key: &str, source_range: SourceRange) -> Result<(usize, &KclValue), KclError> {
if !self.current_env.skip_env() {
return Ok((self.current_env.1, self.get(key, source_range)?));
}
for env in self.call_stack.iter().rev() {
if !env.skip_env() {
return Ok((env.1, self.memory.get_from(key, *env, source_range, self.id)?));
}
}
unreachable!("No frames on the stack?");
}
pub fn find_keys_in_current_env<'a>(
&'a self,
pred: impl Fn(&KclValue) -> bool + 'a,
) -> impl Iterator<Item = &'a String> {
self.memory
.find_all_in_env(self.current_env, pred, self.id)
.map(|(k, _)| k)
}
pub fn find_all_in_current_env(&self) -> impl Iterator<Item = (&String, &KclValue)> {
self.find_all_in_env(self.current_env)
}
pub fn find_all_in_env(&self, env: EnvironmentRef) -> impl Iterator<Item = (&String, &KclValue)> {
self.memory.find_all_in_env(env, |_| true, self.id)
}
pub fn walk_call_stack(&self) -> impl Iterator<Item = &KclValue> {
let mut cur_env = self.current_env;
let mut stack_index = self.call_stack.len();
while cur_env.skip_env() {
stack_index -= 1;
cur_env = self.call_stack[stack_index];
}
let mut result = CallStackIterator {
cur_env,
cur_values: None,
stack_index,
stack: self,
};
result.init_iter();
result
}
}
struct CallStackIterator<'a> {
stack: &'a Stack,
cur_env: EnvironmentRef,
cur_values: Option<Box<dyn Iterator<Item = &'a KclValue> + 'a>>,
stack_index: usize,
}
impl CallStackIterator<'_> {
fn init_iter(&mut self) {
self.cur_values = Some(self.stack.memory.get_env(self.cur_env.index()).values(self.cur_env.1));
}
}
impl<'a> Iterator for CallStackIterator<'a> {
type Item = &'a KclValue;
fn next(&mut self) -> Option<Self::Item> {
self.cur_values.as_ref()?;
'outer: loop {
loop {
let next = self.cur_values.as_mut().unwrap().next();
if next.is_some() {
return next;
}
if let Some(env_ref) = self.stack.memory.get_env(self.cur_env.index()).parent() {
self.cur_env = env_ref;
self.init_iter();
} else {
break;
}
}
if self.stack_index > 0 {
loop {
self.stack_index -= 1;
let env_ref = self.stack.call_stack[self.stack_index];
if !env_ref.skip_env() {
self.cur_env = env_ref;
self.init_iter();
break;
} else if self.stack_index == 0 {
break 'outer;
}
}
} else {
break;
}
}
self.cur_values = None;
None
}
}
#[cfg(test)]
impl PartialEq for Stack {
fn eq(&self, other: &Self) -> bool {
let vars: Vec<_> = self.find_keys_in_current_env(|_| true).collect();
let vars_other: Vec<_> = other.find_keys_in_current_env(|_| true).collect();
if vars != vars_other {
return false;
}
vars.iter()
.all(|k| self.get(k, SourceRange::default()).unwrap() == other.get(k, SourceRange::default()).unwrap())
}
}
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Hash, Eq, ts_rs::TS)]
pub struct EnvironmentRef(usize, usize);
impl EnvironmentRef {
pub fn dummy() -> Self {
Self(usize::MAX, 0)
}
fn is_regular(&self) -> bool {
self.0 < usize::MAX && self.1 > 0
}
fn index(&self) -> usize {
self.0
}
fn skip_env(&self) -> bool {
self.0 == usize::MAX
}
pub fn replace_env(&mut self, old: Self, new: Self) {
if self.0 == old.0 {
self.0 = new.0;
}
}
pub fn replace_env_and_epoch(&mut self, old: Self, new: Self) {
if self.0 == old.0 && self.1 == old.1 {
self.0 = new.0;
self.1 = new.1;
}
}
}
#[derive(Debug, Default)]
pub(crate) struct MemoryStats {
env_count: AtomicUsize,
epoch_count: AtomicUsize,
mutation_count: AtomicUsize,
env_gcs: AtomicUsize,
skipped_env_gcs: AtomicUsize,
preserved_envs: AtomicUsize,
lock_waits: AtomicUsize,
}
mod env {
use std::marker::PhantomPinned;
use super::*;
#[derive(Debug)]
pub(super) struct Environment {
bindings: UnsafeCell<IndexMap<String, (usize, KclValue)>>,
parent: Option<EnvironmentRef>,
might_be_refed: AtomicBool,
owner: AtomicUsize,
_unpin: PhantomPinned,
}
impl Clone for Environment {
fn clone(&self) -> Self {
assert!(self.owner.load(Ordering::Acquire) == 0);
Self {
bindings: UnsafeCell::new(self.get_bindings().clone()),
parent: self.parent,
might_be_refed: AtomicBool::new(self.might_be_refed.load(Ordering::Acquire)),
owner: AtomicUsize::new(0),
_unpin: PhantomPinned,
}
}
}
impl fmt::Display for Environment {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let parent = self
.parent
.map(|e| format!("EnvRef({}, {})", e.0, e.1))
.unwrap_or("_".to_owned());
let data: Vec<String> = self
.get_bindings()
.iter()
.map(|(k, v)| format!("{k}: {}@{}", v.1.human_friendly_type(), v.0))
.collect();
write!(
f,
"Env {{\n parent: {parent},\n owner: {},\n ref'ed?: {},\n bindings:\n {}\n}}",
self.owner.load(Ordering::Relaxed),
self.might_be_refed.load(Ordering::Relaxed),
data.join("\n "),
)
}
}
impl Environment {
pub(super) fn new(parent: Option<EnvironmentRef>, might_be_refed: bool, owner: usize) -> Self {
assert!(
parent.map(|p| p.is_regular()).unwrap_or(true),
"Parent env ref must be regular: {parent:?}"
);
Self {
bindings: UnsafeCell::new(IndexMap::new()),
parent,
might_be_refed: AtomicBool::new(might_be_refed),
owner: AtomicUsize::new(owner),
_unpin: PhantomPinned,
}
}
pub(super) fn read_only(&self) {
self.owner.store(0, Ordering::Release);
}
pub(super) fn restore_owner(&self, owner: usize) {
self.owner.store(owner, Ordering::Release);
}
pub(super) fn mark_as_refed(&self) {
self.might_be_refed.store(true, Ordering::Release);
}
fn get_bindings(&self) -> &IndexMap<String, (usize, KclValue)> {
unsafe { self.bindings.get().as_ref().unwrap() }
}
#[allow(clippy::mut_from_ref)]
fn get_mut_bindings(&self, owner: usize) -> &mut IndexMap<String, (usize, KclValue)> {
assert!(owner > 0 && self.owner.load(Ordering::Acquire) == owner);
unsafe { self.bindings.get().as_mut().unwrap() }
}
pub(super) fn is_empty(&self) -> bool {
self.get_bindings().is_empty() && !self.might_be_refed.load(Ordering::Acquire)
}
pub(super) fn compact(&self, owner: usize) {
if self.might_be_refed.load(Ordering::Acquire) {
return;
}
*self.get_mut_bindings(owner) = IndexMap::new();
}
pub(super) fn get(&self, key: &str, epoch: usize, owner: usize) -> Result<&KclValue, Option<EnvironmentRef>> {
let env_owner = self.owner.load(Ordering::Acquire);
assert!(env_owner == 0 || env_owner == owner);
self.get_unchecked(key, epoch)
}
pub(super) fn get_unchecked(&self, key: &str, epoch: usize) -> Result<&KclValue, Option<EnvironmentRef>> {
self.get_bindings()
.get(key)
.and_then(|(e, v)| if *e <= epoch { Some(v) } else { None })
.ok_or(self.parent)
}
pub(super) fn update(&self, key: &str, f: impl Fn(&mut KclValue, usize), epoch: usize, owner: usize) {
let Some((_, value)) = self.get_mut_bindings(owner).get_mut(key) else {
debug_assert!(false, "Missing memory entry for {key}");
return;
};
f(value, epoch);
}
pub(super) fn parent(&self) -> Option<EnvironmentRef> {
self.parent
}
pub(super) fn values<'a>(&'a self, epoch: usize) -> Box<dyn Iterator<Item = &'a KclValue> + 'a> {
Box::new(
self.get_bindings()
.values()
.filter_map(move |(e, v)| (*e <= epoch).then_some(v)),
)
}
pub(super) fn insert(&self, key: String, epoch: usize, value: KclValue, owner: usize) {
debug_assert!(!self.get_bindings().contains_key(&key));
self.get_mut_bindings(owner).insert(key, (epoch, value));
}
pub(super) fn contains_key(&self, key: &str) -> bool {
self.get_bindings().contains_key(key)
}
pub(super) fn find_all_by<'a>(
&'a self,
f: impl Fn(&KclValue) -> bool + 'a,
owner: usize,
) -> impl Iterator<Item = (&'a String, &'a KclValue)> {
let env_owner = self.owner.load(Ordering::Acquire);
assert!(env_owner == 0 || env_owner == owner);
self.get_bindings()
.iter()
.filter_map(move |(k, (_, v))| f(v).then_some((k, v)))
}
pub(super) fn take_bindings(self: Pin<&mut Self>) -> impl Iterator<Item = (String, (usize, KclValue))> + use<> {
let bindings = std::mem::take(unsafe { self.bindings.get().as_mut().unwrap() });
bindings.into_iter()
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::execution::kcl_value::FunctionSource;
use crate::execution::types::NumericType;
fn sr() -> SourceRange {
SourceRange::default()
}
fn val(value: i64) -> KclValue {
KclValue::Number {
value: value as f64,
ty: NumericType::count(),
meta: Vec::new(),
}
}
#[track_caller]
fn assert_get(mem: &Stack, key: &str, n: i64) {
match mem.get(key, sr()).unwrap() {
KclValue::Number { value, .. } => assert_eq!(*value as i64, n),
_ => unreachable!(),
}
}
#[track_caller]
fn assert_get_from(mem: &Stack, key: &str, n: i64, snapshot: EnvironmentRef) {
match mem.memory.get_from_unchecked(key, snapshot).unwrap() {
KclValue::Number { value, .. } => assert_eq!(*value as i64, n),
_ => unreachable!(),
}
}
#[test]
fn mem_smoke() {
let mem = &mut Stack::new_for_tests();
let transform = mem.snapshot();
mem.add("transform".to_owned(), val(1), sr()).unwrap();
let layer = mem.snapshot();
mem.add("layer".to_owned(), val(1), sr()).unwrap();
mem.add("x".to_owned(), val(1), sr()).unwrap();
mem.push_new_env_for_call(layer);
mem.pop_env();
mem.push_new_env_for_call(transform);
mem.get("x", sr()).unwrap_err();
mem.pop_env();
}
#[test]
fn simple_snapshot() {
let mem = &mut Stack::new_for_tests();
mem.add("a".to_owned(), val(1), sr()).unwrap();
assert_get(mem, "a", 1);
mem.add("a".to_owned(), val(2), sr()).unwrap_err();
assert_get(mem, "a", 1);
mem.get("b", sr()).unwrap_err();
let sn = mem.snapshot();
mem.add("a".to_owned(), val(2), sr()).unwrap_err();
assert_get(mem, "a", 1);
mem.add("b".to_owned(), val(3), sr()).unwrap();
assert_get(mem, "b", 3);
mem.memory.get_from_unchecked("b", sn).unwrap_err();
}
#[test]
fn multiple_snapshot() {
let mem = &mut Stack::new_for_tests();
mem.add("a".to_owned(), val(1), sr()).unwrap();
let sn1 = mem.snapshot();
mem.add("b".to_owned(), val(3), sr()).unwrap();
let sn2 = mem.snapshot();
mem.add("a".to_owned(), val(4), sr()).unwrap_err();
mem.add("b".to_owned(), val(5), sr()).unwrap_err();
mem.add("c".to_owned(), val(6), sr()).unwrap();
assert_get(mem, "a", 1);
assert_get(mem, "b", 3);
assert_get(mem, "c", 6);
assert_get_from(mem, "a", 1, sn1);
mem.memory.get_from_unchecked("b", sn1).unwrap_err();
mem.memory.get_from_unchecked("c", sn1).unwrap_err();
assert_get_from(mem, "a", 1, sn2);
assert_get_from(mem, "b", 3, sn2);
mem.memory.get_from_unchecked("c", sn2).unwrap_err();
}
#[test]
fn simple_call_env() {
let mem = &mut Stack::new_for_tests();
mem.add("a".to_owned(), val(1), sr()).unwrap();
mem.add("b".to_owned(), val(3), sr()).unwrap();
mem.push_new_env_for_call(mem.current_env);
assert_get(mem, "b", 3);
mem.add("b".to_owned(), val(4), sr()).unwrap();
mem.add("c".to_owned(), val(5), sr()).unwrap();
assert_get(mem, "b", 4);
assert_get(mem, "c", 5);
mem.snapshot();
let callee = mem.pop_env();
assert_get(mem, "b", 3);
mem.get("c", sr()).unwrap_err();
assert_get_from(mem, "b", 4, callee);
assert_get_from(mem, "c", 5, callee);
}
#[test]
fn multiple_call_env() {
let mem = &mut Stack::new_for_tests();
mem.add("a".to_owned(), val(1), sr()).unwrap();
mem.add("b".to_owned(), val(3), sr()).unwrap();
mem.push_new_env_for_call(mem.current_env);
assert_get(mem, "b", 3);
mem.add("b".to_owned(), val(4), sr()).unwrap();
mem.add("c".to_owned(), val(5), sr()).unwrap();
assert_get(mem, "b", 4);
assert_get(mem, "c", 5);
mem.pop_env();
mem.push_new_env_for_call(mem.current_env);
assert_get(mem, "b", 3);
mem.add("b".to_owned(), val(6), sr()).unwrap();
mem.add("d".to_owned(), val(7), sr()).unwrap();
assert_get(mem, "b", 6);
assert_get(mem, "d", 7);
mem.get("c", sr()).unwrap_err();
mem.pop_env();
}
#[test]
fn root_env() {
let mem = &mut Stack::new_for_tests();
mem.add("a".to_owned(), val(1), sr()).unwrap();
mem.add("b".to_owned(), val(3), sr()).unwrap();
mem.push_new_root_env(false);
mem.get("b", sr()).unwrap_err();
mem.add("b".to_owned(), val(4), sr()).unwrap();
mem.add("c".to_owned(), val(5), sr()).unwrap();
assert_get(mem, "b", 4);
assert_get(mem, "c", 5);
let callee = mem.pop_env();
assert_get(mem, "b", 3);
mem.get("c", sr()).unwrap_err();
assert_get_from(mem, "b", 4, callee);
assert_get_from(mem, "c", 5, callee);
}
#[test]
fn deep_call_env() {
let mem = &mut Stack::new_for_tests();
mem.add("a".to_owned(), val(1), sr()).unwrap();
mem.add("b".to_owned(), val(3), sr()).unwrap();
mem.push_new_env_for_call(mem.current_env);
assert_get(mem, "b", 3);
mem.add("b".to_owned(), val(4), sr()).unwrap();
mem.add("c".to_owned(), val(5), sr()).unwrap();
assert_get(mem, "b", 4);
assert_get(mem, "c", 5);
mem.push_new_env_for_call(mem.current_env);
assert_get(mem, "b", 4);
mem.add("b".to_owned(), val(6), sr()).unwrap();
mem.add("d".to_owned(), val(7), sr()).unwrap();
assert_get(mem, "b", 6);
assert_get(mem, "c", 5);
assert_get(mem, "d", 7);
mem.pop_env();
assert_get(mem, "b", 4);
assert_get(mem, "c", 5);
mem.get("d", sr()).unwrap_err();
mem.pop_env();
assert_get(mem, "b", 3);
mem.get("c", sr()).unwrap_err();
mem.get("d", sr()).unwrap_err();
}
#[test]
fn snap_env() {
let mem = &mut Stack::new_for_tests();
mem.add("a".to_owned(), val(1), sr()).unwrap();
let sn = mem.snapshot();
mem.add("b".to_owned(), val(3), sr()).unwrap();
mem.push_new_env_for_call(sn);
mem.get("b", sr()).unwrap_err();
mem.add("b".to_owned(), val(4), sr()).unwrap();
mem.add("c".to_owned(), val(5), sr()).unwrap();
assert_get(mem, "b", 4);
assert_get(mem, "c", 5);
mem.pop_env();
mem.memory.get_from_unchecked("b", sn).unwrap_err();
}
#[test]
fn snap_env2() {
let mem = &mut Stack::new_for_tests();
mem.add("a".to_owned(), val(1), sr()).unwrap();
let sn1 = mem.snapshot();
mem.add("b".to_owned(), val(3), sr()).unwrap();
mem.push_new_env_for_call(mem.current_env);
let sn2 = mem.snapshot();
mem.add("b".to_owned(), val(4), sr()).unwrap();
let sn3 = mem.snapshot();
assert_get_from(mem, "b", 3, sn2);
mem.add("c".to_owned(), val(5), sr()).unwrap();
assert_get(mem, "b", 4);
assert_get(mem, "c", 5);
mem.pop_env();
mem.memory.get_from_unchecked("b", sn1).unwrap_err();
assert_get_from(mem, "b", 3, sn2);
mem.memory.get_from_unchecked("c", sn2).unwrap_err();
assert_get_from(mem, "b", 4, sn3);
mem.memory.get_from_unchecked("c", sn3).unwrap_err();
}
#[test]
fn squash_env() {
let mem = &mut Stack::new_for_tests();
mem.add("a".to_owned(), val(1), sr()).unwrap();
mem.add("b".to_owned(), val(3), sr()).unwrap();
let sn1 = mem.snapshot();
mem.push_new_env_for_call(sn1);
mem.add("b".to_owned(), val(2), sr()).unwrap();
let sn2 = mem.snapshot();
mem.add(
"f".to_owned(),
KclValue::Function {
value: Box::new(FunctionSource::kcl(
crate::parsing::ast::types::FunctionExpression::dummy(),
sn2,
crate::execution::kcl_value::KclFunctionSourceParams {
is_std: false,
experimental: false,
include_in_feature_tree: false,
},
)),
meta: Vec::new(),
},
sr(),
)
.unwrap();
let old = mem.pop_and_preserve_env();
mem.squash_env(old);
assert_get(mem, "a", 1);
assert_get(mem, "b", 2);
match mem.get("f", SourceRange::default()).unwrap() {
KclValue::Function { value, .. } => match &**value {
FunctionSource {
body: crate::execution::kcl_value::FunctionBody::Kcl(memory),
..
} if memory.0 == mem.current_env.0 => {}
v => panic!("{v:#?}, expected {sn1:?}"),
},
v => panic!("{v:#?}, expected {sn1:?}"),
}
assert_eq!(mem.memory.envs().len(), 2);
}
#[test]
fn two_stacks() {
let stack1 = &mut Stack::new_for_tests();
let stack2 = &mut stack1.memory.clone().new_stack();
stack2.push_new_root_env(false);
stack1.add("a".to_owned(), val(1), sr()).unwrap();
stack1.push_new_env_for_call(stack1.current_env);
stack2.add("a".to_owned(), val(2), sr()).unwrap();
stack2.push_new_env_for_call(stack2.current_env);
stack2.add("a".to_owned(), val(4), sr()).unwrap();
stack2.push_new_env_for_call(stack2.current_env);
stack1.add("a".to_owned(), val(3), sr()).unwrap();
stack1.push_new_env_for_call(stack1.current_env);
stack1.add("a".to_owned(), val(5), sr()).unwrap();
stack1.push_new_env_for_call(stack1.current_env);
stack2.add("a".to_owned(), val(6), sr()).unwrap();
stack2.push_new_env_for_call(stack2.current_env);
stack1.add("a".to_owned(), val(7), sr()).unwrap();
stack2.add("a".to_owned(), val(8), sr()).unwrap();
assert_get(stack1, "a", 7);
assert_get(stack2, "a", 8);
stack1.pop_env();
assert_get(stack1, "a", 5);
assert_get(stack2, "a", 8);
stack2.pop_env();
assert_get(stack1, "a", 5);
assert_get(stack2, "a", 6);
stack2.pop_env();
assert_get(stack2, "a", 4);
stack2.pop_env();
assert_get(stack2, "a", 2);
stack1.pop_env();
assert_get(stack1, "a", 3);
stack1.pop_env();
assert_get(stack1, "a", 1);
}
}