use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use std::sync::{Arc, Mutex, Weak};
use crate::engine::ecs::ComponentId;
use crate::scripting::ast::BlockStatement;
use crate::scripting::block_effect_analyzer::BlockEffectAnalysis;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BuiltinTableKind {
Math,
MusicNote,
}
#[derive(Debug, Clone, PartialEq)]
pub struct RuntimeClosure {
pub body: BlockStatement,
pub captured_env: Arc<HashMap<String, Value>>,
pub heap: HeapHandle,
pub analysis: Option<BlockEffectAnalysis>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct MaterializedCE {
pub component_type: String,
pub component_property_assignment_only: bool,
pub ctor_method: Option<String>,
pub ctor_args: Vec<Value>,
pub calls: Vec<(String, Vec<Value>)>,
pub named: Vec<(String, Value)>,
pub positionals: Vec<Value>,
pub deferred_block: Option<RuntimeClosure>,
pub children: Vec<CeChild>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum CeChild {
Spawn(MaterializedCE),
Attach(ComponentId),
}
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
Null,
Bool(bool),
Number(f64),
Dimension {
value: f64,
unit: crate::scripting::token::Unit,
},
String(String),
Array(Vec<Value>),
Map(HashMap<String, Value>),
ComponentObject {
id: ComponentId,
component_type: String,
},
Object(ObjectId),
Identifier(String),
BuiltinTable(BuiltinTableKind),
ComponentExpr(Box<MaterializedCE>),
Function {
params: Vec<String>,
body: crate::scripting::ast::BlockStatement,
captured_env: Arc<HashMap<String, Value>>,
heap: HeapHandle,
},
Module {
named: HashMap<String, Value>,
sequence: Vec<MaterializedCE>,
heap: HeapHandle,
},
}
#[derive(Debug, Clone)]
pub struct ObjectId {
slot: u32,
heap: Weak<Mutex<Heap>>,
}
impl ObjectId {
pub fn as_u32(&self) -> u32 {
self.slot
}
pub fn get(&self) -> Option<Object> {
let heap = self.heap.upgrade()?;
heap.lock().ok()?.get(self.clone()).cloned()
}
pub fn with_map<R>(&self, f: impl FnOnce(&HashMap<String, Value>) -> R) -> Option<R> {
let heap = self.heap.upgrade()?;
let heap = heap.lock().ok()?;
let Object::Map(map) = heap.get(self.clone())?;
Some(f(map))
}
pub fn with_map_mut<R>(&self, f: impl FnOnce(&mut HashMap<String, Value>) -> R) -> Option<R> {
let heap = self.heap.upgrade()?;
let mut heap = heap.lock().ok()?;
let Object::Map(map) = heap.get_mut(self.clone())?;
Some(f(map))
}
}
impl PartialEq for ObjectId {
fn eq(&self, other: &Self) -> bool {
self.slot == other.slot && Weak::as_ptr(&self.heap) == Weak::as_ptr(&other.heap)
}
}
impl Eq for ObjectId {}
impl Hash for ObjectId {
fn hash<H: Hasher>(&self, state: &mut H) {
self.slot.hash(state);
Weak::as_ptr(&self.heap).hash(state);
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Object {
Map(HashMap<String, Value>),
}
#[derive(Debug, Default)]
pub struct Heap {
objects: Vec<Object>,
}
impl Heap {
pub fn new() -> Self {
Self::default()
}
fn alloc_in(handle: &HeapHandle, object: Object) -> ObjectId {
let slot = {
let mut heap = handle.0.lock().expect("heap lock poisoned");
let slot = heap
.objects
.len()
.try_into()
.expect("too many heap objects");
heap.objects.push(object);
slot
};
ObjectId {
slot,
heap: Arc::downgrade(&handle.0),
}
}
pub fn get(&self, id: ObjectId) -> Option<&Object> {
self.objects.get(id.slot as usize)
}
pub fn get_mut(&mut self, id: ObjectId) -> Option<&mut Object> {
self.objects.get_mut(id.slot as usize)
}
pub fn len(&self) -> usize {
self.objects.len()
}
pub fn is_empty(&self) -> bool {
self.objects.is_empty()
}
}
#[derive(Debug, Clone)]
pub struct HeapHandle(Arc<Mutex<Heap>>);
impl HeapHandle {
pub fn new() -> Self {
Self(Arc::new(Mutex::new(Heap::new())))
}
pub fn alloc(&self, object: Object) -> ObjectId {
Heap::alloc_in(self, object)
}
}
impl Default for HeapHandle {
fn default() -> Self {
Self::new()
}
}
impl PartialEq for HeapHandle {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FrameKind {
Block,
Function,
}
#[derive(Debug, Default)]
struct Frame {
kind_or_root: Option<FrameKind>,
bindings: HashMap<String, Value>,
captured_bindings: Option<Arc<HashMap<String, Value>>>,
}
impl Frame {
fn new(kind: FrameKind) -> Self {
Self {
kind_or_root: Some(kind),
bindings: HashMap::new(),
captured_bindings: None,
}
}
fn root() -> Self {
Self {
kind_or_root: None,
bindings: HashMap::new(),
captured_bindings: None,
}
}
fn is_function_barrier(&self) -> bool {
matches!(self.kind_or_root, Some(FrameKind::Function))
}
}
#[derive(Debug)]
pub struct ObjectWorld {
frames: Vec<Frame>,
heap: HeapHandle,
reset_requested: bool,
}
impl Default for ObjectWorld {
fn default() -> Self {
Self {
frames: vec![Frame::root()],
heap: HeapHandle::new(),
reset_requested: false,
}
}
}
impl ObjectWorld {
pub fn new() -> Self {
Self::default()
}
pub fn with_heap(heap: HeapHandle) -> Self {
Self {
frames: vec![Frame::root()],
heap,
reset_requested: false,
}
}
pub fn push_frame(&mut self, kind: FrameKind) {
self.frames.push(Frame::new(kind));
}
pub fn push_function_frame(&mut self, captured: Arc<HashMap<String, Value>>) {
self.frames.push(Frame {
kind_or_root: Some(FrameKind::Function),
bindings: HashMap::new(),
captured_bindings: Some(captured),
});
}
pub fn pop_frame(&mut self) {
if self.frames.len() > 1 {
self.frames.pop();
}
}
pub fn frame_depth(&self) -> usize {
self.frames.len()
}
pub fn request_reset(&mut self) {
self.reset_requested = true;
}
pub fn take_reset_requested(&mut self) -> bool {
std::mem::take(&mut self.reset_requested)
}
pub fn bind(&mut self, name: impl Into<String>, value: Value) {
let top = self.frames.last_mut().expect("ObjectWorld: no frames");
top.bindings.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.get(name) {
return Some(v);
}
if let Some(v) = frame
.captured_bindings
.as_ref()
.and_then(|captured| captured.get(name))
{
return Some(v);
}
if frame.is_function_barrier() {
return None;
}
}
None
}
pub fn has(&self, name: &str) -> bool {
self.lookup(name).is_some()
}
pub fn reassign(&mut self, name: &str, value: Value) -> Result<(), String> {
for frame in self.frames.iter_mut().rev() {
if frame.bindings.contains_key(name) {
frame.bindings.insert(name.to_string(), value);
return Ok(());
}
if frame
.captured_bindings
.as_ref()
.is_some_and(|captured| captured.contains_key(name))
{
frame.bindings.insert(name.to_string(), value);
return Ok(());
}
if matches!(frame.kind_or_root, Some(FrameKind::Function)) {
return Err(format!(
"cannot reassign '{}' from inside function (only its captured snapshot is visible)",
name
));
}
}
Err(format!("reassignment: '{}' is not defined", name))
}
pub fn snapshot_visible(&self) -> HashMap<String, Value> {
let mut out: HashMap<String, Value> = HashMap::new();
for frame in self.frames.iter().rev() {
for (k, v) in &frame.bindings {
out.entry(k.clone()).or_insert_with(|| v.clone());
}
if let Some(captured) = &frame.captured_bindings {
for (k, v) in captured.iter() {
out.entry(k.clone()).or_insert_with(|| v.clone());
}
}
if frame.is_function_barrier() {
break;
}
}
out
}
pub fn heap(&self) -> &HeapHandle {
&self.heap
}
pub fn alloc_object(&self, object: Object) -> ObjectId {
self.heap.alloc(object)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
fn n(x: f64) -> Value {
Value::Number(x)
}
#[test]
fn root_frame_bind_and_lookup() {
let mut ow = ObjectWorld::new();
ow.bind("x", n(1.0));
assert_eq!(ow.lookup("x"), Some(&n(1.0)));
assert!(ow.has("x"));
assert!(!ow.has("y"));
}
#[test]
fn block_frame_is_transparent_for_read_and_write() {
let mut ow = ObjectWorld::new();
ow.bind("x", n(1.0));
ow.push_frame(FrameKind::Block);
assert_eq!(ow.lookup("x"), Some(&n(1.0)));
ow.reassign("x", n(2.0)).unwrap();
ow.pop_frame();
assert_eq!(ow.lookup("x"), Some(&n(2.0)));
}
#[test]
fn block_frame_local_let_does_not_leak() {
let mut ow = ObjectWorld::new();
ow.push_frame(FrameKind::Block);
ow.bind("local", n(42.0));
assert_eq!(ow.lookup("local"), Some(&n(42.0)));
ow.pop_frame();
assert_eq!(ow.lookup("local"), None);
}
#[test]
fn function_frame_blocks_read_of_caller_locals() {
let mut ow = ObjectWorld::new();
ow.bind("caller_var", n(1.0));
let mut captured = HashMap::new();
captured.insert("captured_var".to_string(), n(99.0));
ow.push_function_frame(Arc::new(captured));
assert_eq!(ow.lookup("captured_var"), Some(&n(99.0)));
assert_eq!(ow.lookup("caller_var"), None);
}
#[test]
fn function_frame_blocks_reassign_of_caller_locals() {
let mut ow = ObjectWorld::new();
ow.bind("caller_var", n(1.0));
ow.push_function_frame(Arc::new(HashMap::new()));
let err = ow.reassign("caller_var", n(2.0)).unwrap_err();
assert!(err.contains("inside function"), "got: {}", err);
}
#[test]
fn reassign_undefined_errors() {
let mut ow = ObjectWorld::new();
let err = ow.reassign("nope", n(1.0)).unwrap_err();
assert!(err.contains("not defined"), "got: {}", err);
}
#[test]
fn nested_block_reassign_walks_to_declaring_frame() {
let mut ow = ObjectWorld::new();
ow.bind("sum", n(0.0));
ow.push_frame(FrameKind::Block);
ow.push_frame(FrameKind::Block);
ow.reassign("sum", n(6.0)).unwrap();
ow.pop_frame();
ow.pop_frame();
assert_eq!(ow.lookup("sum"), Some(&n(6.0)));
}
#[test]
fn snapshot_visible_flattens_with_inner_shadowing() {
let mut ow = ObjectWorld::new();
ow.bind("a", n(1.0));
ow.bind("b", n(2.0));
ow.push_frame(FrameKind::Block);
ow.bind("b", n(20.0)); ow.bind("c", n(3.0));
let snap = ow.snapshot_visible();
assert_eq!(snap.get("a"), Some(&n(1.0)));
assert_eq!(snap.get("b"), Some(&n(20.0))); assert_eq!(snap.get("c"), Some(&n(3.0)));
}
#[test]
fn snapshot_visible_stops_at_function_barrier() {
let mut ow = ObjectWorld::new();
ow.bind("caller", n(1.0));
let mut captured = HashMap::new();
captured.insert("cap".to_string(), n(2.0));
ow.push_function_frame(Arc::new(captured));
ow.bind("inner", n(3.0));
let snap = ow.snapshot_visible();
assert_eq!(snap.get("inner"), Some(&n(3.0)));
assert_eq!(snap.get("cap"), Some(&n(2.0)));
assert_eq!(snap.get("caller"), None);
}
#[test]
fn reassign_captured_binding_shadows_shared_snapshot_in_current_call() {
let mut ow = ObjectWorld::new();
let mut captured = HashMap::new();
captured.insert("cap".to_string(), n(2.0));
ow.push_function_frame(Arc::new(captured));
ow.reassign("cap", n(5.0)).unwrap();
assert_eq!(ow.lookup("cap"), Some(&n(5.0)));
}
#[test]
fn pop_frame_does_not_pop_root() {
let mut ow = ObjectWorld::new();
let depth0 = ow.frame_depth();
ow.pop_frame();
ow.pop_frame();
assert_eq!(ow.frame_depth(), depth0);
}
#[test]
fn bind_in_inner_frame_shadows_outer_for_lookup() {
let mut ow = ObjectWorld::new();
ow.bind("x", n(1.0));
ow.push_frame(FrameKind::Block);
ow.bind("x", n(2.0));
assert_eq!(ow.lookup("x"), Some(&n(2.0)));
ow.pop_frame();
assert_eq!(ow.lookup("x"), Some(&n(1.0)));
}
}