Skip to main content

sim_lib_lang_lua/
table.rs

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/// Lua table key policy over the shared runtime-keyed mutation table.
12#[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
28/// Lua table handle backed by the mutation organ's runtime-keyed table.
29pub struct LuaTable {
30    entries: MutableRuntimeTable<LuaTablePolicy>,
31    metatable: RwLock<Option<Value>>,
32}
33
34impl LuaTable {
35    /// Builds a Lua table from already-evaluated key/value entries.
36    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    /// Reads a raw entry without consulting `__index`.
44    pub fn raw_get(&self, cx: &mut Cx, key: &Value) -> Result<Option<Value>> {
45        self.entries.get(cx, key)
46    }
47
48    /// Writes a raw entry after checking mutation authority.
49    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    /// Deletes a raw entry after checking mutation authority.
58    pub fn raw_del(&self, cx: &mut Cx, key: &Value) -> Result<Option<Value>> {
59        self.entries.del(cx, key)
60    }
61
62    /// Reads a symbol-keyed raw entry.
63    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    /// Writes a symbol-keyed raw entry.
69    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    /// Installs the table's metatable after checking mutation authority.
75    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    /// Returns the current metatable, if any.
85    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    /// Returns the Lua length border by walking contiguous integer keys from 1.
94    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    /// Returns entries in deterministic key order.
116    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
152/// Constructs a Lua table from value-keyed entries.
153pub 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
158/// Constructs a Lua table from symbol-keyed entries.
159pub 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
167/// Borrows the Lua table behind `value`.
168pub 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
178/// Performs a raw Lua table read without consulting `__index`.
179pub fn lua_rawget(cx: &mut Cx, table: &Value, key: &Value) -> Result<Option<Value>> {
180    lua_table_value(table)?.raw_get(cx, key)
181}
182
183/// Performs a raw Lua table write without consulting `__newindex`.
184pub 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
188/// Deletes a raw Lua table entry without consulting `__newindex`.
189pub fn lua_rawdel(cx: &mut Cx, table: &Value, key: &Value) -> Result<Option<Value>> {
190    lua_table_value(table)?.raw_del(cx, key)
191}
192
193/// Installs a Lua table metatable.
194pub fn lua_set_metatable(cx: &mut Cx, table: &Value, metatable: Value) -> Result<()> {
195    lua_table_value(table)?.set_metatable(cx, metatable)
196}
197
198/// Returns a Lua table metatable, if one is installed.
199pub 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}