use crate::error::Result;
use crate::state::{Lua, LuaRef};
use crate::sync::{NotSync, XRc, NOT_SYNC};
use crate::sys::*;
use crate::traits::{FromLua, IntoLua};
use crate::value::Value;
#[derive(Clone)]
pub struct Table {
pub(crate) reference: XRc<LuaRef>,
pub(crate) _not_sync: NotSync,
}
impl Table {
pub(crate) fn from_ref(reference: LuaRef) -> Table {
Table {
reference: XRc::new(reference),
_not_sync: NOT_SYNC,
}
}
pub(crate) unsafe fn push_to_stack(&self) {
self.reference.push();
}
pub fn lua(&self) -> Lua {
self.reference.lua()
}
pub fn set<K: IntoLua, V: IntoLua>(&self, key: K, value: V) -> Result<()> {
let lua = self.lua();
let state = lua.state();
let k = key.into_lua(&lua)?;
let v = value.into_lua(&lua)?;
unsafe {
self.reference.push(); lua.push_value(&k)?; lua.push_value(&v)?; let status = protected_settable(state);
if status != 0 {
return Err(lua.pop_error(status));
}
}
Ok(())
}
pub fn get<V: FromLua>(&self, key: impl IntoLua) -> Result<V> {
let lua = self.lua();
let state = lua.state();
let k = key.into_lua(&lua)?;
let value = unsafe {
self.reference.push(); lua.push_value(&k)?; let status = protected_gettable(state);
if status != 0 {
return Err(lua.pop_error(status));
}
let v = lua.value_from_stack(-1)?;
lua_pop(state, 1); v
};
V::from_lua(value, &lua)
}
pub fn contains_key<K: IntoLua>(&self, key: K) -> Result<bool> {
let v: Value = self.get(key)?;
Ok(!v.is_nil())
}
pub fn raw_len(&self) -> usize {
let state = self.reference.state();
unsafe {
self.reference.push();
let n = lua_objlen(state, -1);
lua_pop(state, 1);
n.max(0) as usize
}
}
pub fn len(&self) -> Result<usize> {
let lua = self.lua();
if self.metatable().is_none() {
return Ok(self.raw_len());
}
let f = lua.load("local t = ...; return #t").into_function()?;
let n: i64 = f.call(self.clone())?;
Ok(n.max(0) as usize)
}
pub fn is_empty(&self) -> bool {
let state = self.reference.state();
unsafe {
self.reference.push(); lua_pushnil(state); if lua_next(state, -2) == 0 {
lua_pop(state, 1); return true;
}
lua_pop(state, 3);
false
}
}
pub fn pairs<K: FromLua, V: FromLua>(&self) -> TablePairs<K, V> {
TablePairs {
table: self.clone(),
next_key: Some(Value::Nil),
_phantom: std::marker::PhantomData,
}
}
pub fn pairs_vec<K: FromLua, V: FromLua>(&self) -> Result<Vec<(K, V)>> {
self.pairs().collect()
}
pub fn sequence_values<V: FromLua>(&self) -> TableSequence<V> {
TableSequence {
table: self.clone(),
index: 1,
_phantom: std::marker::PhantomData,
}
}
pub fn for_each<K: FromLua, V: FromLua>(
&self,
mut f: impl FnMut(K, V) -> Result<()>,
) -> Result<()> {
for pair in self.pairs::<K, V>() {
let (k, v) = pair?;
f(k, v)?;
}
Ok(())
}
pub fn for_each_value<V: FromLua>(&self, mut f: impl FnMut(V) -> Result<()>) -> Result<()> {
for v in self.sequence_values::<V>() {
f(v?)?;
}
Ok(())
}
pub fn raw_set<K: IntoLua, V: IntoLua>(&self, key: K, value: V) -> Result<()> {
let lua = self.lua();
let state = lua.state();
let k = key.into_lua(&lua)?;
let v = value.into_lua(&lua)?;
if self.is_readonly() {
return Err(crate::error::Error::RuntimeError(
"attempt to modify a readonly table".to_string(),
));
}
unsafe {
self.reference.push(); lua.push_value(&k)?; lua.push_value(&v)?; lua_rawset(state, -3);
lua_pop(state, 1); }
Ok(())
}
pub fn raw_get<V: FromLua>(&self, key: impl IntoLua) -> Result<V> {
let lua = self.lua();
let state = lua.state();
let k = key.into_lua(&lua)?;
let value = unsafe {
self.reference.push(); lua.push_value(&k)?; lua_rawget(state, -2); let v = lua.value_from_stack(-1)?;
lua_pop(state, 2); v
};
V::from_lua(value, &lua)
}
pub fn raw_push<V: IntoLua>(&self, value: V) -> Result<()> {
let n = self.raw_len();
self.raw_set((n + 1) as i64, value)
}
pub fn raw_pop<V: FromLua>(&self) -> Result<V> {
let lua = self.lua();
let n = self.raw_len();
if n == 0 {
return V::from_lua(Value::Nil, &lua);
}
if self.is_readonly() {
return Err(crate::error::Error::RuntimeError(
"attempt to modify a readonly table".to_string(),
));
}
let v: V = self.raw_get(n as i64)?;
self.raw_set(n as i64, Value::Nil)?;
Ok(v)
}
pub fn raw_insert<V: IntoLua>(&self, idx: i64, value: V) -> Result<()> {
let n = self.raw_len() as i64;
if idx < 1 || idx > n + 1 {
return Err(crate::error::Error::RuntimeError(format!(
"bad argument #2 to 'insert' (position out of bounds): {idx}"
)));
}
if self.is_readonly() {
return Err(crate::error::Error::RuntimeError(
"attempt to modify a readonly table".to_string(),
));
}
let mut i = n;
while i >= idx {
let moved: Value = self.raw_get(i)?;
self.raw_set(i + 1, moved)?;
i -= 1;
}
self.raw_set(idx, value.into_lua(&self.lua())?)
}
pub fn raw_remove(&self, idx: i64) -> Result<Value> {
let n = self.raw_len() as i64;
if n == 0 {
return Ok(Value::Nil);
}
if idx < 1 || idx > n {
return Err(crate::error::Error::RuntimeError(format!(
"bad argument #1 to 'remove' (position out of bounds): {idx}"
)));
}
if self.is_readonly() {
return Err(crate::error::Error::RuntimeError(
"attempt to modify a readonly table".to_string(),
));
}
let removed: Value = self.raw_get(idx)?;
let mut i = idx;
while i < n {
let moved: Value = self.raw_get(i + 1)?;
self.raw_set(i, moved)?;
i += 1;
}
self.raw_set(n, Value::Nil)?;
Ok(removed)
}
pub fn push<V: IntoLua>(&self, value: V) -> Result<()> {
let n = self.len()?;
self.set((n + 1) as i64, value)
}
pub fn pop<V: FromLua>(&self) -> Result<V> {
let lua = self.lua();
let n = self.len()?;
if n == 0 {
return V::from_lua(Value::Nil, &lua);
}
let v: V = self.get(n as i64)?;
self.set(n as i64, Value::Nil)?;
Ok(v)
}
pub fn clear(&self) -> Result<()> {
if self.is_readonly() {
return Err(crate::error::Error::RuntimeError(
"attempt to modify a readonly table".to_string(),
));
}
let lua = self.lua();
let state = lua.state();
let mut keys: Vec<Value> = Vec::new();
unsafe {
self.reference.push(); lua_pushnil(state); while lua_next(state, -2) != 0 {
let k = lua.value_from_stack(-2)?;
keys.push(k);
lua_pop(state, 1); }
lua_pop(state, 1); }
for k in keys {
self.raw_set(k, Value::Nil)?;
}
Ok(())
}
pub fn to_pointer(&self) -> *const std::ffi::c_void {
let state = self.reference.state();
unsafe {
self.reference.push();
let p = lua_topointer(state, -1);
lua_pop(state, 1);
p
}
}
pub fn equals(&self, other: &Table) -> Result<bool> {
let lua = self.lua();
let state = lua.state();
unsafe {
self.reference.push();
other.reference.push();
let eq = lua_equal(state, -2, -1);
lua_pop(state, 2);
Ok(eq != 0)
}
}
pub fn metatable(&self) -> Option<Table> {
let lua = self.lua();
let state = lua.state();
unsafe {
self.reference.push();
let has = lua_getmetatable(state, -1);
if has == 0 {
lua_pop(state, 1); return None;
}
let mt = Table::from_ref(lua.pop_ref());
lua_pop(state, 1); Some(mt)
}
}
pub fn set_metatable(&self, metatable: Option<Table>) -> Result<()> {
if self.is_readonly() {
return Err(crate::error::Error::RuntimeError(
"attempt to modify a readonly table".to_string(),
));
}
let lua = self.lua();
let state = lua.state();
unsafe {
self.reference.push(); match metatable {
Some(mt) => mt.push_to_stack(),
None => lua_pushnil(state),
}
lua_setmetatable(state, -2);
lua_pop(state, 1); }
Ok(())
}
pub fn is_readonly(&self) -> bool {
let state = self.reference.state();
unsafe {
self.reference.push();
let ro = lua_getreadonly(state, -1);
lua_pop(state, 1);
ro != 0
}
}
pub fn set_readonly(&self, enabled: bool) {
let state = self.reference.state();
unsafe {
self.reference.push();
lua_setreadonly(state, -1, enabled as c_int);
lua_pop(state, 1);
}
}
}
pub struct TableSequence<V> {
table: Table,
index: i64,
_phantom: std::marker::PhantomData<V>,
}
impl<V: FromLua> Iterator for TableSequence<V> {
type Item = Result<V>;
fn next(&mut self) -> Option<Self::Item> {
let lua = self.table.lua();
let value: Value = match self.table.raw_get(self.index) {
Ok(v) => v,
Err(e) => return Some(Err(e)),
};
if value.is_nil() {
return None;
}
self.index += 1;
Some(V::from_lua(value, &lua))
}
}
impl std::fmt::Debug for Table {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Table(len={})", self.raw_len())
}
}
impl PartialEq for Table {
fn eq(&self, other: &Self) -> bool {
self.to_pointer() == other.to_pointer()
}
}
impl<T> PartialEq<[T]> for Table
where
T: FromLua + PartialEq + Clone,
{
fn eq(&self, other: &[T]) -> bool {
let mut iter = self.sequence_values::<T>();
for expected in other.iter() {
match iter.next() {
Some(Ok(got)) if &got == expected => {}
_ => return false,
}
}
iter.next().is_none()
}
}
impl<T, const N: usize> PartialEq<[T; N]> for Table
where
T: FromLua + PartialEq + Clone,
{
fn eq(&self, other: &[T; N]) -> bool {
self == other.as_slice()
}
}
impl<T> PartialEq<&[T]> for Table
where
T: FromLua + PartialEq + Clone,
{
fn eq(&self, other: &&[T]) -> bool {
self == *other
}
}
pub struct TablePairs<K, V> {
table: Table,
next_key: Option<Value>,
_phantom: std::marker::PhantomData<(K, V)>,
}
impl<K: FromLua, V: FromLua> Iterator for TablePairs<K, V> {
type Item = Result<(K, V)>;
fn next(&mut self) -> Option<Self::Item> {
let key = self.next_key.take()?;
let lua = self.table.lua();
let state = lua.state();
unsafe {
self.table.reference.push(); if lua.push_value(&key).is_err() {
lua_pop(state, 1);
return None;
}
let has = lua_next(state, -2);
if has == 0 {
lua_pop(state, 1);
self.next_key = None;
return None;
}
let k_val = match lua.value_from_stack(-2) {
Ok(v) => v,
Err(e) => {
lua_pop(state, 3);
return Some(Err(e));
}
};
let v_val = match lua.value_from_stack(-1) {
Ok(v) => v,
Err(e) => {
lua_pop(state, 3);
return Some(Err(e));
}
};
self.next_key = Some(k_val.clone());
lua_pop(state, 3);
let k = match K::from_lua(k_val, &lua) {
Ok(k) => k,
Err(e) => return Some(Err(e)),
};
let v = match V::from_lua(v_val, &lua) {
Ok(v) => v,
Err(e) => return Some(Err(e)),
};
Some(Ok((k, v)))
}
}
}
pub(crate) fn create_table(lua: &Lua) -> Table {
let state = lua.state();
unsafe {
lua_createtable(state, 0, 0);
Table::from_ref(lua.pop_ref())
}
}
unsafe fn c_gettable(state: *mut lua_State) -> c_int {
unsafe {
lua_gettable(state, 1);
1
}
}
unsafe fn c_settable(state: *mut lua_State) -> c_int {
unsafe {
lua_settable(state, 1);
0
}
}
unsafe fn protected_gettable(state: *mut lua_State) -> c_int {
unsafe {
lua_pushcclosurek(
state,
Some(c_gettable),
c"luaur-rt-gettable".as_ptr(),
0,
None,
);
lua_insert(state, -3);
lua_pcall(state, 2, 1, 0)
}
}
unsafe fn protected_settable(state: *mut lua_State) -> c_int {
unsafe {
lua_pushcclosurek(
state,
Some(c_settable),
c"luaur-rt-settable".as_ptr(),
0,
None,
);
lua_insert(state, -4);
lua_pcall(state, 3, 0, 0)
}
}