Something went wrong. Try again.
Personal fork of prime agent. Mirror of: https://mnemosyne.sigilzero.dev/sigilzero/prime-agent
Something went wrong. Try again.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252use std::collections::HashSet;use std::fmt;use std::marker::PhantomData;use std::os::raw::c_void;use std::string::String as StdString;
use crate::error::{Error, Result};use crate::function::Function;use crate::state::{LuaGuard, RawLua, WeakLua};use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, ObjectLike};use crate::types::{Integer, ValueRef};use crate::util::{assert_stack, check_stack, get_metatable_ptr, StackGuard};use crate::value::{Nil, Value};
#[cfg(feature = "async")]use crate::function::AsyncCallFuture;
#[cfg(feature = "serde")]use { rustc_hash::FxHashSet, serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer}, std::{cell::RefCell, rc::Rc, result::Result as StdResult},};
/// Handle to an internal Lua table.#[derive(Clone, PartialEq)]pub struct Table(pub(crate) ValueRef);
impl Table { /// Sets a key-value pair in the table. /// /// If the value is `nil`, this will effectively remove the pair. /// /// This might invoke the `__newindex` metamethod. Use the [`raw_set`] method if that is not /// desired. /// /// # Examples /// /// Export a value as a global to make it usable from Lua: /// /// ``` /// # use mlua::{Lua, Result}; /// # fn main() -> Result<()> { /// # let lua = Lua::new(); /// let globals = lua.globals(); /// /// globals.set("assertions", cfg!(debug_assertions))?; /// /// lua.load(r#" /// if assertions == true then /// -- ... /// elseif assertions == false then /// -- ... /// else /// error("assertions neither on nor off?") /// end /// "#).exec()?; /// # Ok(()) /// # } /// ``` /// /// [`raw_set`]: Table::raw_set pub fn set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> { // Fast track (skip protected call) if !self.has_metatable() { return self.raw_set(key, value); }
self.set_protected(key, value) }
pub(crate) fn set_protected(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> { let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); check_stack(state, 5)?;
lua.push_ref(&self.0); key.push_into_stack(&lua)?; value.push_into_stack(&lua)?; protect_lua!(state, 3, 0, fn(state) ffi::lua_settable(state, -3)) } }
/// Gets the value associated to `key` from the table. /// /// If no value is associated to `key`, returns the `nil` value. /// /// This might invoke the `__index` metamethod. Use the [`raw_get`] method if that is not /// desired. /// /// # Examples /// /// Query the version of the Lua interpreter: /// /// ``` /// # use mlua::{Lua, Result}; /// # fn main() -> Result<()> { /// # let lua = Lua::new(); /// let globals = lua.globals(); /// /// let version: String = globals.get("_VERSION")?; /// println!("Lua version: {}", version); /// # Ok(()) /// # } /// ``` /// /// [`raw_get`]: Table::raw_get pub fn get<V: FromLua>(&self, key: impl IntoLua) -> Result<V> { // Fast track (skip protected call) if !self.has_metatable() { return self.raw_get(key); }
self.get_protected(key) }
pub(crate) fn get_protected<V: FromLua>(&self, key: impl IntoLua) -> Result<V> { let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); check_stack(state, 4)?;
lua.push_ref(&self.0); key.push_into_stack(&lua)?; protect_lua!(state, 2, 1, fn(state) ffi::lua_gettable(state, -2))?;
V::from_stack(-1, &lua) } }
/// Checks whether the table contains a non-nil value for `key`. /// /// This might invoke the `__index` metamethod. pub fn contains_key(&self, key: impl IntoLua) -> Result<bool> { Ok(self.get::<Value>(key)? != Value::Nil) }
/// Appends a value to the back of the table. /// /// This might invoke the `__len` and `__newindex` metamethods. pub fn push(&self, value: impl IntoLua) -> Result<()> { // Fast track (skip protected call) if !self.has_metatable() { return self.raw_push(value); }
let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); check_stack(state, 4)?;
lua.push_ref(&self.0); value.push_into_stack(&lua)?; protect_lua!(state, 2, 0, fn(state) { let len = ffi::luaL_len(state, -2) as Integer; ffi::lua_seti(state, -2, len + 1); })? } Ok(()) }
/// Removes the last element from the table and returns it. /// /// This might invoke the `__len` and `__newindex` metamethods. pub fn pop<V: FromLua>(&self) -> Result<V> { // Fast track (skip protected call) if !self.has_metatable() { return self.raw_pop(); }
let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); check_stack(state, 4)?;
lua.push_ref(&self.0); protect_lua!(state, 1, 1, fn(state) { let len = ffi::luaL_len(state, -1) as Integer; ffi::lua_geti(state, -1, len); ffi::lua_pushnil(state); ffi::lua_seti(state, -3, len); })?; V::from_stack(-1, &lua) } }
/// Compares two tables for equality. /// /// Tables are compared by reference first. /// If they are not primitively equals, then mlua will try to invoke the `__eq` metamethod. /// mlua will check `self` first for the metamethod, then `other` if not found. /// /// # Examples /// /// Compare two tables using `__eq` metamethod: /// /// ``` /// # use mlua::{Lua, Result, Table}; /// # fn main() -> Result<()> { /// # let lua = Lua::new(); /// let table1 = lua.create_table()?; /// table1.set(1, "value")?; /// /// let table2 = lua.create_table()?; /// table2.set(2, "value")?; /// /// let always_equals_mt = lua.create_table()?; /// always_equals_mt.set("__eq", lua.create_function(|_, (_t1, _t2): (Table, Table)| Ok(true))?)?; /// table2.set_metatable(Some(always_equals_mt))?; /// /// assert!(table1.equals(&table1.clone())?); /// assert!(table1.equals(&table2)?); /// # Ok(()) /// # } /// ``` pub fn equals(&self, other: &Self) -> Result<bool> { if self == other { return Ok(true); }
// Compare using `__eq` metamethod if exists // First, check the self for the metamethod. // If self does not define it, then check the other table. if let Some(mt) = self.metatable() { if mt.contains_key("__eq")? { return mt.get::<Function>("__eq")?.call((self, other)); } } if let Some(mt) = other.metatable() { if mt.contains_key("__eq")? { return mt.get::<Function>("__eq")?.call((self, other)); } }
Ok(false) }
/// Sets a key-value pair without invoking metamethods. pub fn raw_set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> { let lua = self.0.lua.lock(); let state = lua.state(); unsafe { #[cfg(feature = "luau")] self.check_readonly_write(&lua)?;
let _sg = StackGuard::new(state); check_stack(state, 5)?;
lua.push_ref(&self.0); key.push_into_stack(&lua)?; value.push_into_stack(&lua)?;
if lua.unlikely_memory_error() { ffi::lua_rawset(state, -3); ffi::lua_pop(state, 1); Ok(()) } else { protect_lua!(state, 3, 0, fn(state) ffi::lua_rawset(state, -3)) } } }
/// Gets the value associated to `key` without invoking metamethods. pub fn raw_get<V: FromLua>(&self, key: impl IntoLua) -> Result<V> { let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); check_stack(state, 3)?;
lua.push_ref(&self.0); key.push_into_stack(&lua)?; ffi::lua_rawget(state, -2);
V::from_stack(-1, &lua) } }
/// Inserts element value at position `idx` to the table, shifting up the elements from /// `table[idx]`. /// /// The worst case complexity is O(n), where n is the table length. pub fn raw_insert(&self, idx: Integer, value: impl IntoLua) -> Result<()> { let size = self.raw_len() as Integer; if idx < 1 || idx > size + 1 { return Err(Error::runtime("index out of bounds")); }
let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); check_stack(state, 5)?;
lua.push_ref(&self.0); value.push_into_stack(&lua)?; protect_lua!(state, 2, 0, |state| { for i in (idx..=size).rev() { // table[i+1] = table[i] ffi::lua_rawgeti(state, -2, i); ffi::lua_rawseti(state, -3, i + 1); } ffi::lua_rawseti(state, -2, idx) }) } }
/// Appends a value to the back of the table without invoking metamethods. pub fn raw_push(&self, value: impl IntoLua) -> Result<()> { let lua = self.0.lua.lock(); let state = lua.state(); unsafe { #[cfg(feature = "luau")] self.check_readonly_write(&lua)?;
let _sg = StackGuard::new(state); check_stack(state, 4)?;
lua.push_ref(&self.0); value.push_into_stack(&lua)?;
unsafe fn callback(state: *mut ffi::lua_State) { let len = ffi::lua_rawlen(state, -2) as Integer; ffi::lua_rawseti(state, -2, len + 1); }
if lua.unlikely_memory_error() { callback(state); } else { protect_lua!(state, 2, 0, fn(state) callback(state))?; } } Ok(()) }
/// Removes the last element from the table and returns it, without invoking metamethods. pub fn raw_pop<V: FromLua>(&self) -> Result<V> { let lua = self.0.lua.lock(); let state = lua.state(); unsafe { #[cfg(feature = "luau")] self.check_readonly_write(&lua)?;
let _sg = StackGuard::new(state); check_stack(state, 3)?;
lua.push_ref(&self.0); let len = ffi::lua_rawlen(state, -1) as Integer; ffi::lua_rawgeti(state, -1, len); // Set slot to nil (it must be safe to do) ffi::lua_pushnil(state); ffi::lua_rawseti(state, -3, len);
V::from_stack(-1, &lua) } }
/// Removes a key from the table. /// /// If `key` is an integer, mlua shifts down the elements from `table[key+1]`, /// and erases element `table[key]`. The complexity is `O(n)` in the worst case, /// where `n` is the table length. /// /// For other key types this is equivalent to setting `table[key] = nil`. pub fn raw_remove(&self, key: impl IntoLua) -> Result<()> { let lua = self.0.lua.lock(); let state = lua.state(); let key = key.into_lua(lua.lua())?; match key { Value::Integer(idx) => { let size = self.raw_len() as Integer; if idx < 1 || idx > size { return Err(Error::runtime("index out of bounds")); } unsafe { let _sg = StackGuard::new(state); check_stack(state, 4)?;
lua.push_ref(&self.0); protect_lua!(state, 1, 0, |state| { for i in idx..size { ffi::lua_rawgeti(state, -1, i + 1); ffi::lua_rawseti(state, -2, i); } ffi::lua_pushnil(state); ffi::lua_rawseti(state, -2, size); }) } } _ => self.raw_set(key, Nil), } }
/// Clears the table, removing all keys and values from array and hash parts, /// without invoking metamethods. /// /// This method is useful to clear the table while keeping its capacity. pub fn clear(&self) -> Result<()> { let lua = self.0.lua.lock(); unsafe { #[cfg(feature = "luau")] { self.check_readonly_write(&lua)?; ffi::lua_cleartable(lua.ref_thread(), self.0.index); }
#[cfg(not(feature = "luau"))] { let state = lua.state(); check_stack(state, 4)?;
lua.push_ref(&self.0);
// This is safe as long as we don't assign new keys ffi::lua_pushnil(state); while ffi::lua_next(state, -2) != 0 { ffi::lua_pop(state, 1); // pop value ffi::lua_pushvalue(state, -1); // copy key ffi::lua_pushnil(state); ffi::lua_rawset(state, -4); } } }
Ok(()) }
/// Returns the result of the Lua `#` operator. /// /// This might invoke the `__len` metamethod. Use the [`Table::raw_len`] method if that is not /// desired. pub fn len(&self) -> Result<Integer> { // Fast track (skip protected call) if !self.has_metatable() { return Ok(self.raw_len() as Integer); }
let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); check_stack(state, 4)?;
lua.push_ref(&self.0); protect_lua!(state, 1, 0, |state| ffi::luaL_len(state, -1)) } }
/// Returns the result of the Lua `#` operator, without invoking the `__len` metamethod. pub fn raw_len(&self) -> usize { let lua = self.0.lua.lock(); unsafe { ffi::lua_rawlen(lua.ref_thread(), self.0.index) } }
/// Returns `true` if the table is empty, without invoking metamethods. /// /// It checks both the array part and the hash part. pub fn is_empty(&self) -> bool { let lua = self.0.lua.lock(); let ref_thread = lua.ref_thread(); unsafe { ffi::lua_pushnil(ref_thread); if ffi::lua_next(ref_thread, self.0.index) == 0 { return true; } ffi::lua_pop(ref_thread, 2); } false }
/// Returns a reference to the metatable of this table, or `None` if no metatable is set. /// /// Unlike the [`getmetatable`] Lua function, this method ignores the `__metatable` field. /// /// [`getmetatable`]: https://www.lua.org/manual/5.4/manual.html#pdf-getmetatable pub fn metatable(&self) -> Option<Table> { let lua = self.0.lua.lock(); let ref_thread = lua.ref_thread(); unsafe { if ffi::lua_getmetatable(ref_thread, self.0.index) == 0 { None } else { Some(Table(lua.pop_ref_thread())) } } }
/// Sets or removes the metatable of this table. /// /// If `metatable` is `None`, the metatable is removed (if no metatable is set, this does /// nothing). pub fn set_metatable(&self, metatable: Option<Table>) -> Result<()> { #[cfg(feature = "luau")] if self.is_readonly() { return Err(Error::runtime("attempt to modify a readonly table")); }
let lua = self.0.lua.lock(); let ref_thread = lua.ref_thread(); unsafe { if let Some(metatable) = &metatable { ffi::lua_pushvalue(ref_thread, metatable.0.index); } else { ffi::lua_pushnil(ref_thread); } ffi::lua_setmetatable(ref_thread, self.0.index); } Ok(()) }
/// Returns true if the table has metatable attached. #[doc(hidden)] #[inline] pub fn has_metatable(&self) -> bool { let lua = self.0.lua.lock(); unsafe { !get_metatable_ptr(lua.ref_thread(), self.0.index).is_null() } }
/// Sets `readonly` attribute on the table. #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] pub fn set_readonly(&self, enabled: bool) { let lua = self.0.lua.lock(); let ref_thread = lua.ref_thread(); unsafe { ffi::lua_setreadonly(ref_thread, self.0.index, enabled as _); if !enabled { // Reset "safeenv" flag ffi::lua_setsafeenv(ref_thread, self.0.index, 0); } } }
/// Returns `readonly` attribute of the table. #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] pub fn is_readonly(&self) -> bool { let lua = self.0.lua.lock(); let ref_thread = lua.ref_thread(); unsafe { ffi::lua_getreadonly(ref_thread, self.0.index) != 0 } }
/// Controls `safeenv` attribute on the table. /// /// This a special flag that activates some performance optimizations for environment tables. /// In particular, it controls: /// - Optimization of import resolution (cache values of constant keys). /// - Fast-path for built-in iteration with pairs/ipairs. /// - Fast-path for some built-in functions (fastcall). /// /// For `safeenv` environments, monkey patching or modifying values may not work as expected. #[cfg(any(feature = "luau", doc))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))] pub fn set_safeenv(&self, enabled: bool) { let lua = self.0.lua.lock(); unsafe { ffi::lua_setsafeenv(lua.ref_thread(), self.0.index, enabled as _) }; }
/// Converts this table to a generic C pointer. /// /// Different tables will give different pointers. /// There is no way to convert the pointer back to its original value. /// /// Typically this function is used only for hashing and debug information. #[inline] pub fn to_pointer(&self) -> *const c_void { self.0.to_pointer() }
/// Returns an iterator over the pairs of the table. /// /// This works like the Lua `pairs` function, but does not invoke the `__pairs` metamethod. /// /// The pairs are wrapped in a [`Result`], since they are lazily converted to `K` and `V` types. /// /// # Examples /// /// Iterate over all globals: /// /// ``` /// # use mlua::{Lua, Result, Value}; /// # fn main() -> Result<()> { /// # let lua = Lua::new(); /// let globals = lua.globals(); /// /// for pair in globals.pairs::<Value, Value>() { /// let (key, value) = pair?; /// # let _ = (key, value); // used /// // ... /// } /// # Ok(()) /// # } /// ``` /// /// [Lua manual]: http://www.lua.org/manual/5.4/manual.html#pdf-next pub fn pairs<K: FromLua, V: FromLua>(&self) -> TablePairs<'_, K, V> { TablePairs { guard: self.0.lua.lock(), table: self, key: Some(Nil), _phantom: PhantomData, } }
/// Iterates over the pairs of the table, invoking the given closure on each pair. /// /// This method is similar to [`Table::pairs`], but optimized for performance. /// It does not invoke the `__pairs` metamethod. pub fn for_each<K, V>(&self, mut f: impl FnMut(K, V) -> Result<()>) -> Result<()> where K: FromLua, V: FromLua, { let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); check_stack(state, 5)?;
lua.push_ref(&self.0); ffi::lua_pushnil(state); while ffi::lua_next(state, -2) != 0 { let k = K::from_stack(-2, &lua)?; let v = V::from_stack(-1, &lua)?; f(k, v)?; // Keep key for next iteration ffi::lua_pop(state, 1); } } Ok(()) }
/// Returns an iterator over all values in the sequence part of the table. /// /// The iterator will yield all values `t[1]`, `t[2]` and so on, until a `nil` value is /// encountered. This mirrors the behavior of Lua's `ipairs` function but does not invoke /// any metamethods. /// /// # Examples /// /// ``` /// # use mlua::{Lua, Result, Table}; /// # fn main() -> Result<()> { /// # let lua = Lua::new(); /// let my_table: Table = lua.load(r#" /// { /// [1] = 4, /// [2] = 5, /// [4] = 7, /// key = 2 /// } /// "#).eval()?; /// /// let expected = [4, 5]; /// for (&expected, got) in expected.iter().zip(my_table.sequence_values::<u32>()) { /// assert_eq!(expected, got?); /// } /// # Ok(()) /// # } /// ``` pub fn sequence_values<V: FromLua>(&self) -> TableSequence<'_, V> { TableSequence { guard: self.0.lua.lock(), table: self, index: 1, len: None, _phantom: PhantomData, } }
/// Iterates over the sequence part of the table, invoking the given closure on each value. /// /// This methods is similar to [`Table::sequence_values`], but optimized for performance. #[doc(hidden)] pub fn for_each_value<V: FromLua>(&self, f: impl FnMut(V) -> Result<()>) -> Result<()> { self.for_each_value_by_len(None, f) }
fn for_each_value_by_len<V: FromLua>( &self, len: impl Into<Option<usize>>, mut f: impl FnMut(V) -> Result<()>, ) -> Result<()> { let len = len.into(); let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); check_stack(state, 4)?;
lua.push_ref(&self.0); for i in 1.. { if len.map(|len| i > len).unwrap_or(false) { break; } let t = ffi::lua_rawgeti(state, -1, i as _); if len.is_none() && t == ffi::LUA_TNIL { break; } f(V::from_stack(-1, &lua)?)?; ffi::lua_pop(state, 1); } } Ok(()) }
/// Sets element value at position `idx` without invoking metamethods. #[doc(hidden)] pub fn raw_seti(&self, idx: usize, value: impl IntoLua) -> Result<()> { let lua = self.0.lua.lock(); let state = lua.state(); unsafe { #[cfg(feature = "luau")] self.check_readonly_write(&lua)?;
let _sg = StackGuard::new(state); check_stack(state, 5)?;
lua.push_ref(&self.0); value.push_into_stack(&lua)?;
let idx = idx.try_into().unwrap(); if lua.unlikely_memory_error() { ffi::lua_rawseti(state, -2, idx); } else { protect_lua!(state, 2, 0, |state| ffi::lua_rawseti(state, -2, idx))?; } } Ok(()) }
/// Checks if the table has the array metatable attached. #[cfg(feature = "serde")] fn has_array_metatable(&self) -> bool { let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); assert_stack(state, 3);
lua.push_ref(&self.0); if ffi::lua_getmetatable(state, -1) == 0 { return false; } crate::serde::push_array_metatable(state); ffi::lua_rawequal(state, -1, -2) != 0 } }
/// If the table is an array, returns the number of non-nil elements and max index. /// /// Returns `None` if the table is not an array. /// /// This operation has O(n) complexity. #[cfg(feature = "serde")] fn find_array_len(&self) -> Option<(usize, usize)> { let lua = self.0.lua.lock(); let ref_thread = lua.ref_thread(); unsafe { let _sg = StackGuard::new(ref_thread);
let (mut count, mut max_index) = (0, 0); ffi::lua_pushnil(ref_thread); while ffi::lua_next(ref_thread, self.0.index) != 0 { if ffi::lua_type(ref_thread, -2) != ffi::LUA_TNUMBER { return None; }
let k = ffi::lua_tonumber(ref_thread, -2); if k.trunc() != k || k < 1.0 { return None; } max_index = std::cmp::max(max_index, k as usize); count += 1; ffi::lua_pop(ref_thread, 1); } Some((count, max_index)) } }
/// Determines if the table should be encoded as an array or a map. /// /// The algorithm is the following: /// 1. If `detect_mixed_tables` is enabled, iterate over all keys in the table checking is they /// all are positive integers. If non-array key is found, return `None` (encode as map). /// Otherwise check the sparsity of the array. Too sparse arrays are encoded as maps. /// /// 2. If `detect_mixed_tables` is disabled, check if the table has a positive length or has the /// array metatable. If so, encode as array. If the table is empty and /// `encode_empty_tables_as_array` is enabled, encode as array. /// /// Returns the length of the array if it should be encoded as an array. #[cfg(feature = "serde")] pub(crate) fn encode_as_array(&self, options: crate::serde::de::Options) -> Option<usize> { if options.detect_mixed_tables { if let Some((len, max_idx)) = self.find_array_len() { // If the array is too sparse, serialize it as a map instead if len < 10 || len * 2 >= max_idx { return Some(max_idx); } } } else { let len = self.raw_len(); if len > 0 || self.has_array_metatable() { return Some(len); } if options.encode_empty_tables_as_array && self.is_empty() { return Some(0); } } None }
#[cfg(feature = "luau")] #[inline(always)] fn check_readonly_write(&self, lua: &RawLua) -> Result<()> { if unsafe { ffi::lua_getreadonly(lua.ref_thread(), self.0.index) != 0 } { return Err(Error::runtime("attempt to modify a readonly table")); } Ok(()) }
pub(crate) fn fmt_pretty( &self, fmt: &mut fmt::Formatter, ident: usize, visited: &mut HashSet<*const c_void>, ) -> fmt::Result { visited.insert(self.to_pointer());
// Collect key/value pairs into a vector so we can sort them let mut pairs = self.pairs::<Value, Value>().flatten().collect::<Vec<_>>(); // Sort keys pairs.sort_by(|(a, _), (b, _)| a.sort_cmp(b)); let is_sequence = (pairs.iter().enumerate()) .all(|(i, (k, _))| matches!(k, Value::Integer(n) if *n == (i + 1) as Integer)); if pairs.is_empty() { return write!(fmt, "{{}}"); } writeln!(fmt, "{{")?; if is_sequence { // Format as list for (_, value) in pairs { write!(fmt, "{}", " ".repeat(ident + 2))?; value.fmt_pretty(fmt, true, ident + 2, visited)?; writeln!(fmt, ",")?; } } else { fn is_simple_key(key: &[u8]) -> bool { key.iter().take(1).all(|c| c.is_ascii_alphabetic() || *c == b'_') && key.iter().all(|c| c.is_ascii_alphanumeric() || *c == b'_') }
for (key, value) in pairs { match key { Value::String(key) if is_simple_key(&key.as_bytes()) => { write!(fmt, "{}{}", " ".repeat(ident + 2), key.display())?; write!(fmt, " = ")?; } _ => { write!(fmt, "{}[", " ".repeat(ident + 2))?; key.fmt_pretty(fmt, false, ident + 2, visited)?; write!(fmt, "] = ")?; } } value.fmt_pretty(fmt, true, ident + 2, visited)?; writeln!(fmt, ",")?; } } write!(fmt, "{}}}", " ".repeat(ident)) }}
impl fmt::Debug for Table { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { if fmt.alternate() { return self.fmt_pretty(fmt, 0, &mut HashSet::new()); } fmt.debug_tuple("Table").field(&self.0).finish() }}
impl<T> PartialEq<[T]> for Tablewhere T: IntoLua + Clone,{ fn eq(&self, other: &[T]) -> bool { let lua = self.0.lua.lock(); let state = lua.state(); unsafe { let _sg = StackGuard::new(state); assert_stack(state, 4);
lua.push_ref(&self.0);
let len = ffi::lua_rawlen(state, -1); for i in 0..len { ffi::lua_rawgeti(state, -1, (i + 1) as _); let val = lua.pop_value(); if val == Nil { return i == other.len(); } match other.get(i).map(|v| v.clone().into_lua(lua.lua())) { Some(Ok(other_val)) if val == other_val => continue, _ => return false, } } } true }}
impl<T> PartialEq<&[T]> for Tablewhere T: IntoLua + Clone,{ #[inline] fn eq(&self, other: &&[T]) -> bool { self == *other }}
impl<T, const N: usize> PartialEq<[T; N]> for Tablewhere T: IntoLua + Clone,{ #[inline] fn eq(&self, other: &[T; N]) -> bool { self == &other[..] }}
impl ObjectLike for Table { #[inline] fn get<V: FromLua>(&self, key: impl IntoLua) -> Result<V> { self.get(key) }
#[inline] fn set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> { self.set(key, value) }
#[inline] fn call<R>(&self, args: impl IntoLuaMulti) -> Result<R> where R: FromLuaMulti, { // Convert table to a function and call via pcall that respects the `__call` metamethod. Function(self.0.clone()).call(args) }
#[cfg(feature = "async")] #[inline] fn call_async<R>(&self, args: impl IntoLuaMulti) -> AsyncCallFuture<R> where R: FromLuaMulti, { Function(self.0.clone()).call_async(args) }
#[inline] fn call_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> Result<R> where R: FromLuaMulti, { self.call_function(name, (self, args)) }
#[cfg(feature = "async")] fn call_async_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R> where R: FromLuaMulti, { self.call_async_function(name, (self, args)) }
#[inline] fn call_function<R: FromLuaMulti>(&self, name: &str, args: impl IntoLuaMulti) -> Result<R> { match self.get(name)? { Value::Function(func) => func.call(args), val => { let msg = format!("attempt to call a {} value (function '{name}')", val.type_name()); Err(Error::runtime(msg)) } } }
#[cfg(feature = "async")] #[inline] fn call_async_function<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R> where R: FromLuaMulti, { match self.get(name) { Ok(Value::Function(func)) => func.call_async(args), Ok(val) => { let msg = format!("attempt to call a {} value (function '{name}')", val.type_name()); AsyncCallFuture::error(Error::RuntimeError(msg)) } Err(err) => AsyncCallFuture::error(err), } }
#[inline] fn to_string(&self) -> Result<StdString> { Value::Table(Table(self.0.clone())).to_string() }
#[inline] fn to_value(&self) -> Value { Value::Table(self.clone()) }
#[inline] fn weak_lua(&self) -> &WeakLua { &self.0.lua }}
/// A wrapped [`Table`] with customized serialization behavior.#[cfg(feature = "serde")]pub(crate) struct SerializableTable<'a> { table: &'a Table, options: crate::serde::de::Options, visited: Rc<RefCell<FxHashSet<*const c_void>>>,}
#[cfg(feature = "serde")]impl Serialize for Table { #[inline] fn serialize<S: Serializer>(&self, serializer: S) -> StdResult<S::Ok, S::Error> { SerializableTable::new(self, Default::default(), Default::default()).serialize(serializer) }}
#[cfg(feature = "serde")]impl<'a> SerializableTable<'a> { #[inline] pub(crate) fn new( table: &'a Table, options: crate::serde::de::Options, visited: Rc<RefCell<FxHashSet<*const c_void>>>, ) -> Self { Self { table, options, visited, } }}
impl<V> TableSequence<'_, V> { /// Sets the length (hint) of the sequence. #[cfg(feature = "serde")] pub(crate) fn with_len(mut self, len: usize) -> Self { self.len = Some(len); self }}
#[cfg(feature = "serde")]impl Serialize for SerializableTable<'_> { fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error> where S: Serializer, { use crate::serde::de::{check_value_for_skip, MapPairs, RecursionGuard}; use crate::value::SerializableValue;
let convert_result = |res: Result<()>, serialize_err: Option<S::Error>| match res { Ok(v) => Ok(v), Err(Error::SerializeError(_)) if serialize_err.is_some() => Err(serialize_err.unwrap()), Err(Error::SerializeError(msg)) => Err(serde::ser::Error::custom(msg)), Err(err) => Err(serde::ser::Error::custom(err.to_string())), };
let options = self.options; let visited = &self.visited; let _guard = RecursionGuard::new(self.table, visited);
// Array if let Some(len) = self.table.encode_as_array(self.options) { let mut seq = serializer.serialize_seq(Some(len))?; let mut serialize_err = None; let res = self.table.for_each_value_by_len::<Value>(len, |value| { let skip = check_value_for_skip(&value, self.options, visited) .map_err(|err| Error::SerializeError(err.to_string()))?; if skip { // continue iteration return Ok(()); } seq.serialize_element(&SerializableValue::new(&value, options, Some(visited))) .map_err(|err| { serialize_err = Some(err); Error::SerializeError(StdString::new()) }) }); convert_result(res, serialize_err)?; return seq.end(); }
// HashMap let mut map = serializer.serialize_map(None)?; let mut serialize_err = None; let mut process_pair = |key, value| { let skip_key = check_value_for_skip(&key, self.options, visited) .map_err(|err| Error::SerializeError(err.to_string()))?; let skip_value = check_value_for_skip(&value, self.options, visited) .map_err(|err| Error::SerializeError(err.to_string()))?; if skip_key || skip_value { // continue iteration return Ok(()); } map.serialize_entry( &SerializableValue::new(&key, options, Some(visited)), &SerializableValue::new(&value, options, Some(visited)), ) .map_err(|err| { serialize_err = Some(err); Error::SerializeError(StdString::new()) }) };
let res = if !self.options.sort_keys { // Fast track self.table.for_each(process_pair) } else { MapPairs::new(self.table, self.options.sort_keys) .map_err(serde::ser::Error::custom)? .try_for_each(|kv| { let (key, value) = kv?; process_pair(key, value) }) }; convert_result(res, serialize_err)?; map.end() }}
/// An iterator over the pairs of a Lua table.////// This struct is created by the [`Table::pairs`] method.////// [`Table::pairs`]: crate::Table::pairspub struct TablePairs<'a, K, V> { guard: LuaGuard, table: &'a Table, key: Option<Value>, _phantom: PhantomData<(K, V)>,}
impl<K, V> Iterator for TablePairs<'_, K, V>where K: FromLua, V: FromLua,{ type Item = Result<(K, V)>;
fn next(&mut self) -> Option<Self::Item> { if let Some(prev_key) = self.key.take() { let lua: &RawLua = &self.guard; let state = lua.state();
let res = (|| unsafe { let _sg = StackGuard::new(state); check_stack(state, 5)?;
lua.push_ref(&self.table.0); lua.push_value(&prev_key)?;
// It must be safe to call `lua_next` unprotected as deleting a key from a table is // a permitted operation. // It fails only if the key is not found (never existed) which seems impossible scenario. if ffi::lua_next(state, -2) != 0 { let key = lua.stack_value(-2, None); Ok(Some(( key.clone(), K::from_lua(key, lua.lua())?, V::from_stack(-1, lua)?, ))) } else { Ok(None) } })();
match res { Ok(Some((key, ret_key, value))) => { self.key = Some(key); Some(Ok((ret_key, value))) } Ok(None) => None, Err(e) => Some(Err(e)), } } else { None } }}
/// An iterator over the sequence part of a Lua table.////// This struct is created by the [`Table::sequence_values`] method.////// [`Table::sequence_values`]: crate::Table::sequence_valuespub struct TableSequence<'a, V> { guard: LuaGuard, table: &'a Table, index: Integer, len: Option<usize>, _phantom: PhantomData<V>,}
impl<V: FromLua> Iterator for TableSequence<'_, V> { type Item = Result<V>;
fn next(&mut self) -> Option<Self::Item> { let lua: &RawLua = &self.guard; let state = lua.state(); unsafe { let _sg = StackGuard::new(state); if let Err(err) = check_stack(state, 1) { return Some(Err(err)); }
lua.push_ref(&self.table.0); match ffi::lua_rawgeti(state, -1, self.index) { ffi::LUA_TNIL if self.index as usize > self.len.unwrap_or(0) => None, _ => { self.index += 1; Some(V::from_stack(-1, lua)) } } } }}
#[cfg(test)]mod assertions { use super::*;
#[cfg(not(feature = "send"))] static_assertions::assert_not_impl_any!(Table: Send); #[cfg(feature = "send")] static_assertions::assert_impl_all!(Table: Send, Sync);}