luna_core/vm/table_builder.rs
1//! `TableBuilder` + `vm.table_of` (B3, Phase 2 P2-B) โ replace the
2//! dogfood ยง4.1 `unsafe { t.as_mut() }.set(...)` dance with a safe
3//! one-line builder.
4//!
5//! ```
6//! use luna_core::vm::Vm;
7//! use luna_core::version::LuaVersion;
8//! use luna_core::runtime::Value;
9//!
10//! let mut vm = Vm::sandbox(LuaVersion::Lua55).open_base().build();
11//!
12//! // One-shot: table_of
13//! let t = vm.table_of([("answer", 42_i64), ("year", 2026)]);
14//! vm.set_global("constants", Value::Table(t)).unwrap();
15//!
16//! // Multi-step: new_table builder
17//! let t = vm.new_table()
18//! .with("name", "luna")
19//! .with("major", 1_i64)
20//! .with("minor", 1_i64)
21//! .build();
22//! vm.set_global("info", Value::Table(t)).unwrap();
23//!
24//! let r = vm.eval("return constants.answer + info.minor").unwrap();
25//! assert_eq!(r.len(), 1);
26//! ```
27//!
28//! The unsafe `Gc::as_mut` lives inside the builder; embedders never
29//! write it.
30
31use crate::runtime::Table;
32use crate::runtime::heap::Gc;
33use crate::vm::error::LuaError;
34use crate::vm::exec::Vm;
35use crate::vm::into_value::IntoValue;
36
37/// Multi-step table construction. Borrows `&mut Vm` for the whole
38/// builder window so no other Vm operation can interleave (which
39/// might trigger GC mid-build). Consume with [`TableBuilder::build`].
40pub struct TableBuilder<'vm> {
41 vm: &'vm mut Vm,
42 t: Gc<Table>,
43}
44
45impl<'vm> TableBuilder<'vm> {
46 /// Add a `(key, value)` entry. Both may be any [`IntoValue`].
47 /// Panics if the table overflows (`MAX_ASIZE = 1<<27`; unreachable
48 /// in practice โ embedders building tables that large have other
49 /// problems).
50 pub fn with<K, V>(self, k: K, v: V) -> Self
51 where
52 K: IntoValue,
53 V: IntoValue,
54 {
55 let TableBuilder { vm, t } = self;
56 let k = k.into_value(vm);
57 let v = v.into_value(vm);
58 // SAFETY: Gc<T> is NonNull<T> over the single-threaded GC heap
59 // (see heap.rs:5-7); the TableBuilder's exclusive &mut Vm borrow
60 // guarantees no concurrent access to the table.
61 unsafe { t.as_mut() }
62 .set(&mut vm.heap, k, v)
63 .expect("table builder overflow");
64 TableBuilder { vm, t }
65 }
66
67 /// Fallible variant of [`TableBuilder::with`] โ propagates
68 /// `TableError::Overflow` as `LuaError` instead of panicking.
69 pub fn try_with<K, V>(self, k: K, v: V) -> Result<Self, LuaError>
70 where
71 K: IntoValue,
72 V: IntoValue,
73 {
74 let TableBuilder { vm, t } = self;
75 let k = k.into_value(vm);
76 let v = v.into_value(vm);
77 // SAFETY: same as with().
78 unsafe { t.as_mut() }.set(&mut vm.heap, k, v)?;
79 Ok(TableBuilder { vm, t })
80 }
81
82 /// Finalize: emit a GC write barrier (so any newly-rooted children
83 /// are visible to the collector) and return the table handle.
84 pub fn build(self) -> Gc<Table> {
85 let TableBuilder { vm, t } = self;
86 vm.heap
87 .barrier_back(t.as_ptr() as *mut crate::runtime::heap::GcHeader);
88 t
89 }
90}
91
92impl Vm {
93 /// Allocate a fresh table and return a builder for in-place population.
94 pub fn new_table(&mut self) -> TableBuilder<'_> {
95 let t = self.heap.new_table();
96 TableBuilder { vm: self, t }
97 }
98
99 /// Allocate a fresh table populated from a fixed-size slice of
100 /// `(key, value)` pairs. Equivalent to chained `new_table().with(...)`
101 /// calls, but more concise for static tables (stdlib registration,
102 /// embedder-side constants).
103 ///
104 /// Panics on table overflow (unreachable for `N` small).
105 pub fn table_of<K, V, const N: usize>(&mut self, entries: [(K, V); N]) -> Gc<Table>
106 where
107 K: IntoValue,
108 V: IntoValue,
109 {
110 let mut b = self.new_table();
111 for (k, v) in entries {
112 b = b.with(k, v);
113 }
114 b.build()
115 }
116}