use luau_common::ByteSlice;
use luau_vm::VmErrorResult;
use luau_vm::internal::RawHandle;
use luau_vm::internal::api::RawStackAccess;
use luau_vm::internal::table::Table as VmTable;
use luau_vm::thread::{LUA_GLOBALS_INDEX, LUA_MULTRET, StackGuard, Thread as VmThread};
use crate::error::Error;
use crate::function::Function;
use crate::lua::{Lua, LuaRef};
use crate::object::{self, ObjectLike};
use crate::thread::Thread;
use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value, ValueRef};
mod formatting;
mod iteration;
mod sequence;
pub use iteration::TablePairs;
pub use sequence::TableSequence;
pub struct Table<'lua> {
reference: ValueRef<'lua>,
table: VmTable,
}
impl Lua {
pub fn globals(&self) -> Result<Table<'_>, Error> {
self.lua_ref().globals()
}
pub fn create_table(&self) -> Result<Table<'_>, Error> {
self.lua_ref().create_table()
}
pub fn create_table_with_capacity(
&self,
array_size: usize,
record_size: usize,
) -> Result<Table<'_>, Error> {
self.lua_ref()
.create_table_with_capacity(array_size, record_size)
}
pub fn create_table_from<'lua, K, V>(
&'lua self,
values: impl IntoIterator<Item = (K, V)>,
) -> Result<Table<'lua>, Error>
where
K: IntoLua<'lua>,
V: IntoLua<'lua>,
{
self.lua_ref().create_table_from(values)
}
pub fn create_sequence_from<'lua, T>(
&'lua self,
values: impl IntoIterator<Item = T>,
) -> Result<Table<'lua>, Error>
where
T: IntoLua<'lua>,
{
self.lua_ref().create_sequence_from(values)
}
}
impl<'lua> LuaRef<'lua> {
pub fn globals(&self) -> Result<Table<'lua>, Error> {
unsafe {
let thread = self.as_vm();
let _stack = StackGuard::new(thread);
let safe_thread = self.current_thread();
thread
.push_value(LUA_GLOBALS_INDEX)
.map_err(|exit| Error::from_thread_exit(thread, exit))?;
Table::from_stack(&safe_thread, -1)
.map_err(|exit| Error::from_thread_exit(thread, exit))
}
}
pub fn create_table(&self) -> Result<Table<'lua>, Error> {
self.current_thread().create_table()
}
pub fn create_table_with_capacity(
&self,
array_size: usize,
record_size: usize,
) -> Result<Table<'lua>, Error> {
self.current_thread()
.create_table_with_capacity(array_size, record_size)
}
pub fn create_table_from<K, V>(
&self,
values: impl IntoIterator<Item = (K, V)>,
) -> Result<Table<'lua>, Error>
where
K: IntoLua<'lua>,
V: IntoLua<'lua>,
{
let values = values.into_iter();
let table = self.create_table_with_capacity(0, values.size_hint().0)?;
for (key, value) in values {
table.raw_set(key, value)?;
}
Ok(table)
}
pub fn create_sequence_from<T>(
&self,
values: impl IntoIterator<Item = T>,
) -> Result<Table<'lua>, Error>
where
T: IntoLua<'lua>,
{
let values = values.into_iter();
let table = self.create_table_with_capacity(values.size_hint().0, 0)?;
for (index, value) in values.enumerate() {
table.raw_seti(
index
.checked_add(1)
.ok_or_else(Error::index_out_of_bounds)?,
value,
)?;
}
Ok(table)
}
}
impl<'lua> Thread<'lua> {
pub(crate) fn create_table(&self) -> Result<Table<'lua>, Error> {
self.create_table_with_capacity(0, 0)
}
pub(crate) fn create_table_with_capacity(
&self,
array_size: usize,
record_size: usize,
) -> Result<Table<'lua>, Error> {
unsafe {
let thread = self.as_vm();
let _stack = StackGuard::new(thread);
thread
.create_table(array_size, record_size)
.map_err(|exit| Error::from_thread_exit(thread, exit))?;
Table::from_stack(self, -1).map_err(|exit| Error::from_thread_exit(thread, exit))
}
}
}
impl<'lua> Table<'lua> {
pub(crate) unsafe fn from_stack(thread: &Thread<'lua>, index: i32) -> VmErrorResult<Self> {
let stack_thread = thread.as_vm();
debug_assert_ne!(unsafe { stack_thread.is_table(index) }, 0);
let table = unsafe {
stack_thread
.to_object(index)
.expect("table stack slot should contain a table")
.table_value()
};
Ok(Self {
reference: ValueRef::from_stack(thread, index)?,
table,
})
}
pub fn try_clone(&self) -> Result<Self, Error> {
Ok(Self {
reference: self.reference.try_clone()?,
table: self.table,
})
}
pub fn equals(&self, other: &Self) -> Result<bool, Error> {
unsafe {
let thread = self.thread();
let vm_thread = thread.as_vm();
let _stack = StackGuard::new(vm_thread);
self.push_to(&thread)?;
other.push_to(&thread)?;
vm_thread
.equal(-2, -1)
.map(|equal| equal != 0)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))
}
}
pub fn get<V>(&self, key: impl IntoLua<'lua>) -> Result<V, Error>
where
V: FromLua<'lua>,
{
unsafe {
let thread = self.thread();
let vm_thread = thread.as_vm();
let _stack = StackGuard::new(vm_thread);
self.push_to(&thread)?;
let table_index = vm_thread.get_top();
key.push_into_stack(&thread)?;
vm_thread
.get_table(table_index)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))?;
V::from_stack(&thread, -1)
}
}
pub fn raw_get<V>(&self, key: impl IntoLua<'lua>) -> Result<V, Error>
where
V: FromLua<'lua>,
{
unsafe {
let thread = self.reference.thread();
let vm_thread = thread.as_vm();
let _stack = StackGuard::new(vm_thread);
self.push_to(&thread)?;
let table_index = vm_thread.get_top();
key.push_into_stack(&thread)?;
vm_thread.raw_get(table_index);
V::from_stack(&thread, -1)
}
}
pub fn contains_key(&self, key: impl IntoLua<'lua>) -> Result<bool, Error> {
self.get::<Value<'lua>>(key)
.map(|value| value != Value::Nil)
}
pub fn set(&self, key: impl IntoLua<'lua>, value: impl IntoLua<'lua>) -> Result<(), Error> {
self.invalidate_safe_env();
unsafe {
let thread = self.thread();
let vm_thread = thread.as_vm();
let _stack = StackGuard::new(vm_thread);
self.push_to(&thread)?;
let table_index = vm_thread.get_top();
key.push_into_stack(&thread)?;
value.push_into_stack(&thread)?;
vm_thread
.set_table(table_index)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))?;
}
Ok(())
}
pub fn raw_set(&self, key: impl IntoLua<'lua>, value: impl IntoLua<'lua>) -> Result<(), Error> {
self.invalidate_safe_env();
unsafe {
let thread = self.reference.thread();
let vm_thread = thread.as_vm();
let _stack = StackGuard::new(vm_thread);
self.push_to(&thread)?;
let table_index = vm_thread.get_top();
key.push_into_stack(&thread)?;
value.push_into_stack(&thread)?;
vm_thread
.raw_set(table_index)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))?;
}
Ok(())
}
pub fn clear(&self) -> Result<(), Error> {
self.invalidate_safe_env();
unsafe {
let thread = self.reference.thread();
let vm_thread = thread.as_vm();
let _stack = StackGuard::new(vm_thread);
self.push_to(&thread)?;
vm_thread
.clear_table(-1)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))?;
}
Ok(())
}
pub fn metatable(&self) -> Result<Option<Self>, Error> {
unsafe {
let thread = self.reference.thread();
let vm_thread = thread.as_vm();
let _stack = StackGuard::new(vm_thread);
let Some(metatable) = self.table.metatable() else {
return Ok(None);
};
vm_thread
.push_table(metatable)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))?;
Table::from_stack(&thread, -1)
.map(Some)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))
}
}
pub fn set_metatable(&self, metatable: Option<&Self>) -> Result<(), Error> {
self.invalidate_safe_env();
unsafe {
let thread = self.reference.thread();
let vm_thread = thread.as_vm();
let _stack = StackGuard::new(vm_thread);
self.push_to(&thread)?;
let table_index = vm_thread.get_top();
match metatable {
Some(metatable) => metatable.push_to(&thread)?,
None => vm_thread
.push_nil()
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))?,
}
vm_thread
.set_metatable(table_index)
.map(drop)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))?;
}
Ok(())
}
pub fn has_metatable(&self) -> bool {
unsafe { self.table.metatable().is_some() }
}
pub fn set_readonly(&self, enabled: bool) {
unsafe {
(*self.table.as_ptr()).readonly = u8::from(enabled);
}
if !enabled {
self.invalidate_safe_env();
}
}
pub fn is_readonly(&self) -> bool {
unsafe { (*self.table.as_ptr()).readonly != 0 }
}
pub fn set_safe_env(&self, enabled: bool) {
unsafe { (*self.table.as_ptr()).safe_env = u8::from(enabled) };
}
pub fn to_pointer(&self) -> *const () {
self.pointer()
}
pub(crate) fn push_to(&self, target: impl AsRef<VmThread>) -> Result<(), Error> {
unsafe {
let target = target.as_ref();
if !target.same_vm(self.reference.reference_thread()) {
return Err(Error::foreign_lua_handle());
}
target
.push_table(self.table)
.map_err(|exit| Error::from_thread_exit(target, exit))
}
}
pub(crate) fn thread(&self) -> Thread<'lua> {
self.reference.thread()
}
pub(crate) fn pointer(&self) -> *const () {
self.table.as_ptr().cast()
}
fn invalidate_safe_env(&self) {
self.reference.runtime().invalidate_managed_safe_env();
self.set_safe_env(false);
}
}
impl PartialEq for Table<'_> {
fn eq(&self, other: &Self) -> bool {
self.reference == other.reference
}
}
impl Eq for Table<'_> {}
impl object::private::Sealed for Table<'_> {}
impl<'lua> ObjectLike<'lua> for Table<'lua> {
fn get<V>(&self, key: impl IntoLua<'lua>) -> Result<V, Error>
where
V: FromLua<'lua>,
{
Table::get(self, key)
}
fn set(&self, key: impl IntoLua<'lua>, value: impl IntoLua<'lua>) -> Result<(), Error> {
Table::set(self, key, value)
}
fn call<R>(&self, args: impl IntoLuaMulti<'lua>) -> Result<R, Error>
where
R: FromLuaMulti<'lua>,
{
unsafe {
let thread = self.thread();
let vm_thread = thread.as_vm();
let stack = StackGuard::new(vm_thread);
self.push_to(&thread)?;
let arg_count = i32::try_from(args.push_into_stack_multi(&thread)?)
.map_err(|_| Error::StackError)?;
vm_thread
.protected_call(arg_count, LUA_MULTRET, 0)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))?;
let result_count = vm_thread.get_top() - stack.top();
R::from_stack_multi(&thread, stack.top(), result_count)
}
}
fn call_method<R>(&self, name: &str, args: impl IntoLuaMulti<'lua>) -> Result<R, Error>
where
R: FromLuaMulti<'lua>,
{
let function = self.get::<Function<'lua>>(name)?;
unsafe {
let thread = self.thread();
let vm_thread = thread.as_vm();
let stack = StackGuard::new(vm_thread);
function.push_to(&thread)?;
self.push_to(&thread)?;
let arg_count = args
.push_into_stack_multi(&thread)?
.checked_add(1)
.ok_or(Error::StackError)?;
let arg_count = i32::try_from(arg_count).map_err(|_| Error::StackError)?;
vm_thread
.protected_call(arg_count, LUA_MULTRET, 0)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))?;
let result_count = vm_thread.get_top() - stack.top();
R::from_stack_multi(&thread, stack.top(), result_count)
}
}
fn call_function<R>(&self, name: &str, args: impl IntoLuaMulti<'lua>) -> Result<R, Error>
where
R: FromLuaMulti<'lua>,
{
self.get::<Function<'lua>>(name)?.call(args)
}
fn to_string(&self) -> Result<String, Error> {
unsafe {
let thread = self.thread();
let vm_thread = thread.as_vm();
let _stack = StackGuard::new(vm_thread);
self.push_to(&thread)?;
let bytes = vm_thread
.lua_to_string(-1)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))?;
bytes.to_str().map(str::to_owned).map_err(|error| {
let message = error.to_string();
Error::from_lua_conversion("string", "String", Some(message.as_str()))
})
}
}
fn to_value(&self) -> Result<Value<'lua>, Error> {
self.try_clone().map(Value::Table)
}
}
impl<'lua, 'table> IntoLua<'lua> for Table<'table>
where
'table: 'lua,
{
fn into_lua(self, _: crate::LuaRef<'lua>) -> Result<Value<'lua>, Error> {
Ok(Value::Table(self))
}
unsafe fn push_into_stack(self, thread: &Thread<'lua>) -> Result<(), Error> {
self.push_to(thread)
}
}
impl<'lua, 'table> IntoLua<'lua> for &Table<'table>
where
'table: 'lua,
{
fn into_lua(self, _: crate::LuaRef<'lua>) -> Result<Value<'lua>, Error> {
self.try_clone().map(Value::Table)
}
unsafe fn push_into_stack(self, thread: &Thread<'lua>) -> Result<(), Error> {
self.push_to(thread)
}
}
impl<'lua> FromLua<'lua> for Table<'lua> {
fn from_lua(value: Value<'lua>, _: crate::LuaRef<'lua>) -> Result<Self, Error> {
match value {
Value::Table(table) => Ok(table),
value => Err(Error::from_lua_conversion(value.type_name(), "table", None)),
}
}
}