1use std::sync::{Arc, RwLock};
2
3use sim_kernel::{Cx, Error, Expr, Object, ObjectCompat, Result, Symbol, Value};
4use sim_lib_mutation::{
5 MutableRuntimeTable, RuntimeKey, RuntimeKeyPolicy, standard_mutate_capability,
6};
7use sim_lib_sequence::RuntimeIndexSource;
8
9use crate::number::lua_number_from_value;
10
11#[derive(Clone, Copy, Debug, Default)]
13pub struct LuaTablePolicy;
14
15impl RuntimeKeyPolicy for LuaTablePolicy {
16 fn key_for(&self, cx: &mut Cx, value: &Value) -> Result<Option<RuntimeKey>> {
17 match value.object().as_expr(cx)? {
18 Expr::Nil => Ok(None),
19 Expr::Number(_) => lua_number_key(cx, value),
20 Expr::Bool(value) => Ok(Some(RuntimeKey::Bool(value))),
21 Expr::String(value) => Ok(Some(RuntimeKey::Str(value))),
22 Expr::Symbol(symbol) => Ok(Some(RuntimeKey::Symbol(symbol))),
23 _ => RuntimeKey::from_value(cx, value),
24 }
25 }
26}
27
28pub struct LuaTable {
30 entries: MutableRuntimeTable<LuaTablePolicy>,
31 metatable: RwLock<Option<Value>>,
32}
33
34impl LuaTable {
35 pub fn new(cx: &mut Cx, entries: Vec<(Value, Value)>) -> Result<Self> {
37 Ok(Self {
38 entries: MutableRuntimeTable::with_entries(cx, LuaTablePolicy, entries)?,
39 metatable: RwLock::new(None),
40 })
41 }
42
43 pub fn raw_get(&self, cx: &mut Cx, key: &Value) -> Result<Option<Value>> {
45 self.entries.get(cx, key)
46 }
47
48 pub fn raw_set(&self, cx: &mut Cx, key: Value, value: Value) -> Result<()> {
50 if matches!(value.object().as_expr(cx)?, Expr::Nil) {
51 self.entries.del(cx, &key)?;
52 return Ok(());
53 }
54 self.entries.set(cx, key, value)
55 }
56
57 pub fn raw_del(&self, cx: &mut Cx, key: &Value) -> Result<Option<Value>> {
59 self.entries.del(cx, key)
60 }
61
62 pub fn get_symbol(&self, cx: &mut Cx, key: Symbol) -> Result<Option<Value>> {
64 let key = cx.factory().symbol(key)?;
65 self.raw_get(cx, &key)
66 }
67
68 pub fn set_symbol(&self, cx: &mut Cx, key: Symbol, value: Value) -> Result<()> {
70 let key = cx.factory().symbol(key)?;
71 self.raw_set(cx, key, value)
72 }
73
74 pub fn set_metatable(&self, cx: &mut Cx, metatable: Value) -> Result<()> {
76 cx.require(&standard_mutate_capability())?;
77 *self
78 .metatable
79 .write()
80 .map_err(|_| Error::PoisonedLock("lua table metatable"))? = Some(metatable);
81 Ok(())
82 }
83
84 pub fn metatable(&self) -> Result<Option<Value>> {
86 Ok(self
87 .metatable
88 .read()
89 .map_err(|_| Error::PoisonedLock("lua table metatable"))?
90 .clone())
91 }
92
93 pub fn len_border(&self, _cx: &mut Cx) -> Result<i64> {
95 let mut index = 1_i64;
96 loop {
97 if self
98 .entries
99 .get_runtime_key(&RuntimeKey::Integer(index))?
100 .is_none()
101 {
102 return Ok(index - 1);
103 }
104 index = index
105 .checked_add(1)
106 .ok_or_else(|| Error::Eval("lua table length overflow".to_owned()))?;
107 if index > 1_000_000 {
108 return Err(Error::Eval(
109 "lua table length exceeded bounded scan".to_owned(),
110 ));
111 }
112 }
113 }
114
115 pub fn entries_in_key_order(&self) -> Result<Vec<(RuntimeKey, Value)>> {
117 self.entries.entries_in_key_order()
118 }
119}
120
121impl RuntimeIndexSource for LuaTable {
122 fn value_at_runtime_index(&self, _cx: &mut Cx, index: i64) -> Result<Option<Value>> {
123 self.entries.get_runtime_key(&RuntimeKey::Integer(index))
124 }
125}
126
127impl Object for LuaTable {
128 fn display(&self, _cx: &mut Cx) -> Result<String> {
129 Ok(format!("#<lua-table {}>", self.entries.len()?))
130 }
131
132 fn as_any(&self) -> &dyn std::any::Any {
133 self
134 }
135}
136
137impl ObjectCompat for LuaTable {
138 fn as_expr(&self, cx: &mut Cx) -> Result<Expr> {
139 Ok(Expr::Map(
140 self.entries_in_key_order()?
141 .into_iter()
142 .map(|(key, value)| Ok((key.as_expr(), value.object().as_expr(cx)?)))
143 .collect::<Result<Vec<_>>>()?,
144 ))
145 }
146
147 fn truth(&self, _cx: &mut Cx) -> Result<bool> {
148 Ok(true)
149 }
150}
151
152pub fn lua_table_from_values(cx: &mut Cx, entries: Vec<(Value, Value)>) -> Result<Value> {
154 let table = LuaTable::new(cx, entries)?;
155 cx.factory().opaque(Arc::new(table))
156}
157
158pub fn lua_table(cx: &mut Cx, entries: Vec<(Symbol, Value)>) -> Result<Value> {
160 let mut value_entries = Vec::with_capacity(entries.len());
161 for (key, value) in entries {
162 value_entries.push((cx.factory().symbol(key)?, value));
163 }
164 lua_table_from_values(cx, value_entries)
165}
166
167pub fn lua_table_value(value: &Value) -> Result<&LuaTable> {
169 value
170 .object()
171 .downcast_ref::<LuaTable>()
172 .ok_or(Error::TypeMismatch {
173 expected: "lua table",
174 found: "non-table",
175 })
176}
177
178pub fn lua_rawget(cx: &mut Cx, table: &Value, key: &Value) -> Result<Option<Value>> {
180 lua_table_value(table)?.raw_get(cx, key)
181}
182
183pub fn lua_rawset(cx: &mut Cx, table: &Value, key: Value, value: Value) -> Result<()> {
185 lua_table_value(table)?.raw_set(cx, key, value)
186}
187
188pub fn lua_rawdel(cx: &mut Cx, table: &Value, key: &Value) -> Result<Option<Value>> {
190 lua_table_value(table)?.raw_del(cx, key)
191}
192
193pub fn lua_set_metatable(cx: &mut Cx, table: &Value, metatable: Value) -> Result<()> {
195 lua_table_value(table)?.set_metatable(cx, metatable)
196}
197
198pub fn lua_get_metatable(table: &Value) -> Result<Option<Value>> {
200 lua_table_value(table)?.metatable()
201}
202
203fn lua_number_key(cx: &mut Cx, value: &Value) -> Result<Option<RuntimeKey>> {
204 let Some(number) = lua_number_from_value(cx, value)? else {
205 return Ok(None);
206 };
207 match number {
208 crate::number::LuaNumber::Integer(value) => Ok(Some(RuntimeKey::Integer(value))),
209 crate::number::LuaNumber::Float(value) if value.is_nan() => Ok(None),
210 crate::number::LuaNumber::Float(value)
211 if value.fract() == 0.0 && value >= i64::MIN as f64 && value <= i64::MAX as f64 =>
212 {
213 Ok(Some(RuntimeKey::Integer(value as i64)))
214 }
215 crate::number::LuaNumber::Float(value) => Ok(Some(RuntimeKey::FloatBits(value.to_bits()))),
216 }
217}