1use std::collections::HashMap;
2use std::hash::{Hash, Hasher};
3use std::sync::{Arc, Mutex, Weak};
4
5use crate::engine::ecs::ComponentId;
6use crate::scripting::ast::BlockStatement;
7use crate::scripting::block_effect_analyzer::BlockEffectAnalysis;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub enum BuiltinTableKind {
11 Math,
12 MusicNote,
13}
14
15#[derive(Debug, Clone, PartialEq)]
26pub struct RuntimeClosure {
27 pub body: BlockStatement,
28 pub captured_env: Arc<HashMap<String, Value>>,
29 pub heap: HeapHandle,
30 pub analysis: Option<BlockEffectAnalysis>,
31}
32
33#[derive(Debug, Clone, PartialEq)]
34pub struct MaterializedCE {
35 pub component_type: String,
37 pub component_property_assignment_only: bool,
40 pub ctor_method: Option<String>,
42 pub ctor_args: Vec<Value>,
44 pub calls: Vec<(String, Vec<Value>)>,
47 pub named: Vec<(String, Value)>,
49 pub positionals: Vec<Value>,
51 pub deferred_block: Option<RuntimeClosure>,
54 pub children: Vec<CeChild>,
57}
58
59#[derive(Debug, Clone, PartialEq)]
66pub enum CeChild {
67 Spawn(MaterializedCE),
68 Attach(ComponentId),
69}
70
71#[derive(Debug, Clone, PartialEq)]
77pub enum Value {
78 Null,
79 Bool(bool),
80 Number(f64),
81 Dimension {
86 value: f64,
87 unit: crate::scripting::token::Unit,
88 },
89 String(String),
90 Array(Vec<Value>),
91 Map(HashMap<String, Value>),
92
93 ComponentObject {
98 id: ComponentId,
99 component_type: String,
100 },
101
102 Object(ObjectId),
104
105 Identifier(String),
107
108 BuiltinTable(BuiltinTableKind),
110
111 ComponentExpr(Box<MaterializedCE>),
114
115 Function {
117 params: Vec<String>,
118 body: crate::scripting::ast::BlockStatement,
119 captured_env: Arc<HashMap<String, Value>>,
120 heap: HeapHandle,
121 },
122
123 Module {
125 named: HashMap<String, Value>,
126 sequence: Vec<MaterializedCE>,
127 heap: HeapHandle,
128 },
129}
130
131#[derive(Debug, Clone)]
136pub struct ObjectId {
137 slot: u32,
138 heap: Weak<Mutex<Heap>>,
139}
140
141impl ObjectId {
142 pub fn as_u32(&self) -> u32 {
143 self.slot
144 }
145
146 pub fn get(&self) -> Option<Object> {
147 let heap = self.heap.upgrade()?;
148 heap.lock().ok()?.get(self.clone()).cloned()
149 }
150
151 pub fn with_map<R>(&self, f: impl FnOnce(&HashMap<String, Value>) -> R) -> Option<R> {
152 let heap = self.heap.upgrade()?;
153 let heap = heap.lock().ok()?;
154 let Object::Map(map) = heap.get(self.clone())?;
155 Some(f(map))
156 }
157
158 pub fn with_map_mut<R>(&self, f: impl FnOnce(&mut HashMap<String, Value>) -> R) -> Option<R> {
159 let heap = self.heap.upgrade()?;
160 let mut heap = heap.lock().ok()?;
161 let Object::Map(map) = heap.get_mut(self.clone())?;
162 Some(f(map))
163 }
164}
165
166impl PartialEq for ObjectId {
167 fn eq(&self, other: &Self) -> bool {
168 self.slot == other.slot && Weak::as_ptr(&self.heap) == Weak::as_ptr(&other.heap)
169 }
170}
171
172impl Eq for ObjectId {}
173
174impl Hash for ObjectId {
175 fn hash<H: Hasher>(&self, state: &mut H) {
176 self.slot.hash(state);
177 Weak::as_ptr(&self.heap).hash(state);
178 }
179}
180
181#[derive(Debug, Clone, PartialEq)]
182pub enum Object {
183 Map(HashMap<String, Value>),
185}
186
187#[derive(Debug, Default)]
188pub struct Heap {
189 objects: Vec<Object>,
190}
191
192impl Heap {
193 pub fn new() -> Self {
194 Self::default()
195 }
196
197 fn alloc_in(handle: &HeapHandle, object: Object) -> ObjectId {
198 let slot = {
199 let mut heap = handle.0.lock().expect("heap lock poisoned");
200 let slot = heap
201 .objects
202 .len()
203 .try_into()
204 .expect("too many heap objects");
205 heap.objects.push(object);
206 slot
207 };
208 ObjectId {
209 slot,
210 heap: Arc::downgrade(&handle.0),
211 }
212 }
213
214 pub fn get(&self, id: ObjectId) -> Option<&Object> {
215 self.objects.get(id.slot as usize)
216 }
217
218 pub fn get_mut(&mut self, id: ObjectId) -> Option<&mut Object> {
219 self.objects.get_mut(id.slot as usize)
220 }
221
222 pub fn len(&self) -> usize {
223 self.objects.len()
224 }
225
226 pub fn is_empty(&self) -> bool {
227 self.objects.is_empty()
228 }
229}
230
231#[derive(Debug, Clone)]
232pub struct HeapHandle(Arc<Mutex<Heap>>);
233
234impl HeapHandle {
235 pub fn new() -> Self {
236 Self(Arc::new(Mutex::new(Heap::new())))
237 }
238
239 pub fn alloc(&self, object: Object) -> ObjectId {
240 Heap::alloc_in(self, object)
241 }
242}
243
244impl Default for HeapHandle {
245 fn default() -> Self {
246 Self::new()
247 }
248}
249
250impl PartialEq for HeapHandle {
251 fn eq(&self, other: &Self) -> bool {
252 Arc::ptr_eq(&self.0, &other.0)
253 }
254}
255
256#[derive(Debug, Clone, Copy, PartialEq, Eq)]
268pub enum FrameKind {
269 Block,
270 Function,
271}
272
273#[derive(Debug, Default)]
274struct Frame {
275 kind_or_root: Option<FrameKind>,
276 bindings: HashMap<String, Value>,
277 captured_bindings: Option<Arc<HashMap<String, Value>>>,
278}
279
280impl Frame {
281 fn new(kind: FrameKind) -> Self {
282 Self {
283 kind_or_root: Some(kind),
284 bindings: HashMap::new(),
285 captured_bindings: None,
286 }
287 }
288 fn root() -> Self {
289 Self {
290 kind_or_root: None,
291 bindings: HashMap::new(),
292 captured_bindings: None,
293 }
294 }
295 fn is_function_barrier(&self) -> bool {
296 matches!(self.kind_or_root, Some(FrameKind::Function))
297 }
298}
299
300#[derive(Debug)]
309pub struct ObjectWorld {
310 frames: Vec<Frame>,
311 heap: HeapHandle,
312 reset_requested: bool,
313}
314
315impl Default for ObjectWorld {
316 fn default() -> Self {
317 Self {
318 frames: vec![Frame::root()],
319 heap: HeapHandle::new(),
320 reset_requested: false,
321 }
322 }
323}
324
325impl ObjectWorld {
326 pub fn new() -> Self {
328 Self::default()
329 }
330
331 pub fn with_heap(heap: HeapHandle) -> Self {
332 Self {
333 frames: vec![Frame::root()],
334 heap,
335 reset_requested: false,
336 }
337 }
338
339 pub fn push_frame(&mut self, kind: FrameKind) {
342 self.frames.push(Frame::new(kind));
343 }
344
345 pub fn push_function_frame(&mut self, captured: Arc<HashMap<String, Value>>) {
348 self.frames.push(Frame {
349 kind_or_root: Some(FrameKind::Function),
350 bindings: HashMap::new(),
351 captured_bindings: Some(captured),
352 });
353 }
354
355 pub fn pop_frame(&mut self) {
356 if self.frames.len() > 1 {
358 self.frames.pop();
359 }
360 }
361
362 pub fn frame_depth(&self) -> usize {
363 self.frames.len()
364 }
365
366 pub fn request_reset(&mut self) {
367 self.reset_requested = true;
368 }
369
370 pub fn take_reset_requested(&mut self) -> bool {
371 std::mem::take(&mut self.reset_requested)
372 }
373
374 pub fn bind(&mut self, name: impl Into<String>, value: Value) {
378 let top = self.frames.last_mut().expect("ObjectWorld: no frames");
379 top.bindings.insert(name.into(), value);
380 }
381
382 pub fn lookup(&self, name: &str) -> Option<&Value> {
385 for frame in self.frames.iter().rev() {
386 if let Some(v) = frame.bindings.get(name) {
387 return Some(v);
388 }
389 if let Some(v) = frame
390 .captured_bindings
391 .as_ref()
392 .and_then(|captured| captured.get(name))
393 {
394 return Some(v);
395 }
396 if frame.is_function_barrier() {
397 return None;
398 }
399 }
400 None
401 }
402
403 pub fn has(&self, name: &str) -> bool {
405 self.lookup(name).is_some()
406 }
407
408 pub fn reassign(&mut self, name: &str, value: Value) -> Result<(), String> {
415 for frame in self.frames.iter_mut().rev() {
416 if frame.bindings.contains_key(name) {
417 frame.bindings.insert(name.to_string(), value);
418 return Ok(());
419 }
420 if frame
421 .captured_bindings
422 .as_ref()
423 .is_some_and(|captured| captured.contains_key(name))
424 {
425 frame.bindings.insert(name.to_string(), value);
426 return Ok(());
427 }
428 if matches!(frame.kind_or_root, Some(FrameKind::Function)) {
429 return Err(format!(
430 "cannot reassign '{}' from inside function (only its captured snapshot is visible)",
431 name
432 ));
433 }
434 }
435 Err(format!("reassignment: '{}' is not defined", name))
436 }
437
438 pub fn snapshot_visible(&self) -> HashMap<String, Value> {
443 let mut out: HashMap<String, Value> = HashMap::new();
444 for frame in self.frames.iter().rev() {
445 for (k, v) in &frame.bindings {
446 out.entry(k.clone()).or_insert_with(|| v.clone());
447 }
448 if let Some(captured) = &frame.captured_bindings {
449 for (k, v) in captured.iter() {
450 out.entry(k.clone()).or_insert_with(|| v.clone());
451 }
452 }
453 if frame.is_function_barrier() {
454 break;
455 }
456 }
457 out
458 }
459
460 pub fn heap(&self) -> &HeapHandle {
463 &self.heap
464 }
465
466 pub fn alloc_object(&self, object: Object) -> ObjectId {
467 self.heap.alloc(object)
468 }
469}
470
471#[cfg(test)]
476mod tests {
477 use super::*;
478 use std::sync::Arc;
479
480 fn n(x: f64) -> Value {
481 Value::Number(x)
482 }
483
484 #[test]
485 fn root_frame_bind_and_lookup() {
486 let mut ow = ObjectWorld::new();
487 ow.bind("x", n(1.0));
488 assert_eq!(ow.lookup("x"), Some(&n(1.0)));
489 assert!(ow.has("x"));
490 assert!(!ow.has("y"));
491 }
492
493 #[test]
494 fn block_frame_is_transparent_for_read_and_write() {
495 let mut ow = ObjectWorld::new();
496 ow.bind("x", n(1.0));
497 ow.push_frame(FrameKind::Block);
498 assert_eq!(ow.lookup("x"), Some(&n(1.0)));
500 ow.reassign("x", n(2.0)).unwrap();
502 ow.pop_frame();
503 assert_eq!(ow.lookup("x"), Some(&n(2.0)));
504 }
505
506 #[test]
507 fn block_frame_local_let_does_not_leak() {
508 let mut ow = ObjectWorld::new();
509 ow.push_frame(FrameKind::Block);
510 ow.bind("local", n(42.0));
511 assert_eq!(ow.lookup("local"), Some(&n(42.0)));
512 ow.pop_frame();
513 assert_eq!(ow.lookup("local"), None);
514 }
515
516 #[test]
517 fn function_frame_blocks_read_of_caller_locals() {
518 let mut ow = ObjectWorld::new();
519 ow.bind("caller_var", n(1.0));
520 let mut captured = HashMap::new();
521 captured.insert("captured_var".to_string(), n(99.0));
522 ow.push_function_frame(Arc::new(captured));
523 assert_eq!(ow.lookup("captured_var"), Some(&n(99.0)));
524 assert_eq!(ow.lookup("caller_var"), None);
525 }
526
527 #[test]
528 fn function_frame_blocks_reassign_of_caller_locals() {
529 let mut ow = ObjectWorld::new();
530 ow.bind("caller_var", n(1.0));
531 ow.push_function_frame(Arc::new(HashMap::new()));
532 let err = ow.reassign("caller_var", n(2.0)).unwrap_err();
533 assert!(err.contains("inside function"), "got: {}", err);
534 }
535
536 #[test]
537 fn reassign_undefined_errors() {
538 let mut ow = ObjectWorld::new();
539 let err = ow.reassign("nope", n(1.0)).unwrap_err();
540 assert!(err.contains("not defined"), "got: {}", err);
541 }
542
543 #[test]
544 fn nested_block_reassign_walks_to_declaring_frame() {
545 let mut ow = ObjectWorld::new();
546 ow.bind("sum", n(0.0));
547 ow.push_frame(FrameKind::Block);
548 ow.push_frame(FrameKind::Block);
549 ow.reassign("sum", n(6.0)).unwrap();
550 ow.pop_frame();
551 ow.pop_frame();
552 assert_eq!(ow.lookup("sum"), Some(&n(6.0)));
553 }
554
555 #[test]
556 fn snapshot_visible_flattens_with_inner_shadowing() {
557 let mut ow = ObjectWorld::new();
558 ow.bind("a", n(1.0));
559 ow.bind("b", n(2.0));
560 ow.push_frame(FrameKind::Block);
561 ow.bind("b", n(20.0)); ow.bind("c", n(3.0));
563 let snap = ow.snapshot_visible();
564 assert_eq!(snap.get("a"), Some(&n(1.0)));
565 assert_eq!(snap.get("b"), Some(&n(20.0))); assert_eq!(snap.get("c"), Some(&n(3.0)));
567 }
568
569 #[test]
570 fn snapshot_visible_stops_at_function_barrier() {
571 let mut ow = ObjectWorld::new();
572 ow.bind("caller", n(1.0));
573 let mut captured = HashMap::new();
574 captured.insert("cap".to_string(), n(2.0));
575 ow.push_function_frame(Arc::new(captured));
576 ow.bind("inner", n(3.0));
577 let snap = ow.snapshot_visible();
578 assert_eq!(snap.get("inner"), Some(&n(3.0)));
579 assert_eq!(snap.get("cap"), Some(&n(2.0)));
580 assert_eq!(snap.get("caller"), None);
581 }
582
583 #[test]
584 fn reassign_captured_binding_shadows_shared_snapshot_in_current_call() {
585 let mut ow = ObjectWorld::new();
586 let mut captured = HashMap::new();
587 captured.insert("cap".to_string(), n(2.0));
588 ow.push_function_frame(Arc::new(captured));
589 ow.reassign("cap", n(5.0)).unwrap();
590 assert_eq!(ow.lookup("cap"), Some(&n(5.0)));
591 }
592
593 #[test]
594 fn pop_frame_does_not_pop_root() {
595 let mut ow = ObjectWorld::new();
596 let depth0 = ow.frame_depth();
597 ow.pop_frame();
598 ow.pop_frame();
599 assert_eq!(ow.frame_depth(), depth0);
600 }
601
602 #[test]
603 fn bind_in_inner_frame_shadows_outer_for_lookup() {
604 let mut ow = ObjectWorld::new();
605 ow.bind("x", n(1.0));
606 ow.push_frame(FrameKind::Block);
607 ow.bind("x", n(2.0));
608 assert_eq!(ow.lookup("x"), Some(&n(2.0)));
609 ow.pop_frame();
610 assert_eq!(ow.lookup("x"), Some(&n(1.0)));
611 }
612}