use core::marker::PhantomData;
use luau_vm::internal::RawHandle;
use luau_vm::thread::StackGuard;
use super::Table;
use crate::error::Error;
use crate::value::{FromLua, IntoLua, Value};
pub struct TableSequence<'table, 'lua, V> {
table: &'table Table<'lua>,
index: usize,
_marker: PhantomData<V>,
}
impl<'lua> Table<'lua> {
pub fn len(&self) -> Result<i64, Error> {
unsafe {
let thread = self.reference.thread();
let vm_thread = thread.as_vm();
let _stack = StackGuard::new(vm_thread);
self.push_to(&thread)?;
let length = vm_thread
.len(-1)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))?;
if !length.is_finite() || length.fract() != 0.0 {
return Err(Error::runtime("object length is not an integer"));
}
num_traits::cast(length)
.ok_or_else(|| Error::runtime("object length is not an integer"))
}
}
pub fn raw_len(&self) -> usize {
self.raw_len_cached()
}
pub fn is_empty(&self) -> bool {
self.is_empty_cached()
}
pub fn push(&self, value: impl IntoLua<'lua>) -> Result<(), Error> {
self.set(
self.len()?
.checked_add(1)
.ok_or_else(Error::index_out_of_bounds)?,
value,
)
}
pub fn pop<V>(&self) -> Result<V, Error>
where
V: FromLua<'lua>,
{
let index = self.len()?;
let value = self.get(index)?;
self.set(index, Value::Nil)?;
Ok(value)
}
pub fn remove(&self, key: impl IntoLua<'lua>) -> Result<(), Error> {
let thread = self.thread();
let key = key.into_lua(thread.lua_ref())?;
let Some(index) = key.as_table_array_index()? else {
return self.set(key, Value::Nil);
};
let size = self.len()?;
if index > size {
return Err(Error::index_out_of_bounds());
}
for current in index..size {
let value: Value<'lua> = self.get(current + 1)?;
self.set(current, value)?;
}
self.set(size, Value::Nil)
}
pub fn raw_seti(&self, index: usize, value: impl IntoLua<'lua>) -> Result<(), Error> {
let index = raw_array_index(index)?;
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)?;
value.push_into_stack(&thread)?;
vm_thread
.raw_seti(-2, index)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))
}
}
pub fn raw_push(&self, value: impl IntoLua<'lua>) -> Result<(), Error> {
self.raw_seti(self.raw_len_cached() + 1, value)
}
pub fn raw_pop<V>(&self) -> Result<V, Error>
where
V: FromLua<'lua>,
{
let index = raw_array_index(self.raw_len_cached())?;
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();
vm_thread
.raw_geti(table_index, index)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))?;
vm_thread
.push_nil()
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))?;
vm_thread
.raw_seti(table_index, index)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))?;
V::from_stack(&thread, -1)
}
}
pub fn raw_insert(&self, index: usize, value: impl IntoLua<'lua>) -> Result<(), Error> {
let size = self.raw_len_cached();
if index < 1 || index > size + 1 {
return Err(Error::index_out_of_bounds());
}
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();
for current in (index..=size).rev() {
vm_thread
.raw_geti(table_index, raw_array_index(current)?)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))?;
vm_thread
.raw_seti(table_index, raw_array_index(current + 1)?)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))?;
}
value.push_into_stack(&thread)?;
vm_thread
.raw_seti(table_index, raw_array_index(index)?)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))
}
}
pub fn raw_remove(&self, key: impl IntoLua<'lua>) -> Result<(), Error> {
let thread = self.thread();
let key = key.into_lua(thread.lua_ref())?;
let Some(index) = key.as_table_array_index()? else {
return self.raw_set(key, Value::Nil);
};
let size = raw_array_index(self.raw_len_cached())?;
if index > i64::from(size) {
return Err(Error::index_out_of_bounds());
}
let index = i32::try_from(index).map_err(|_| Error::index_out_of_bounds())?;
self.invalidate_safe_env();
unsafe {
let vm_thread = thread.as_vm();
let _stack = StackGuard::new(vm_thread);
self.push_to(&thread)?;
let table_index = vm_thread.get_top();
for current in index..size {
vm_thread
.raw_geti(table_index, current + 1)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))?;
vm_thread
.raw_seti(table_index, current)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))?;
}
vm_thread
.push_nil()
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))?;
vm_thread
.raw_seti(table_index, size)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))
}
}
pub fn sequence_values<V>(&self) -> TableSequence<'_, 'lua, V>
where
V: FromLua<'lua>,
{
TableSequence {
table: self,
index: 1,
_marker: PhantomData,
}
}
pub fn for_each_value<V>(
&self,
mut function: impl FnMut(V) -> Result<(), Error>,
) -> Result<(), Error>
where
V: FromLua<'lua>,
{
for value in self.sequence_values() {
function(value?)?;
}
Ok(())
}
fn raw_len_cached(&self) -> usize {
unsafe { self.table.getn() as usize }
}
fn is_empty_cached(&self) -> bool {
unsafe {
for index in 0..(*self.table.as_ptr()).size_array as usize {
if !self.table.array_slot(index).is_nil() {
return false;
}
}
if !self.table.has_dummy_node() {
for index in 0..self.table.node_count() {
if !self.table.node(index as i32).value_unchecked().is_nil() {
return false;
}
}
}
true
}
}
}
impl<'lua, V> Iterator for TableSequence<'_, 'lua, V>
where
V: FromLua<'lua>,
{
type Item = Result<V, Error>;
fn next(&mut self) -> Option<Self::Item> {
let result = (|| unsafe {
let thread = self.table.thread();
let vm_thread = thread.as_vm();
let _stack = StackGuard::new(vm_thread);
self.table.push_to(&thread)?;
let table_index = vm_thread.get_top();
let tag = vm_thread
.raw_geti(table_index, raw_array_index(self.index)?)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))?;
if tag == luau_vm::types::LUA_TNIL {
return Ok(None);
}
self.index += 1;
V::from_stack(&thread, -1).map(Some)
})();
match result {
Ok(Some(value)) => Some(Ok(value)),
Ok(None) => None,
Err(error) => Some(Err(error)),
}
}
}
fn raw_array_index(index: usize) -> Result<i32, Error> {
i32::try_from(index).map_err(|_| Error::index_out_of_bounds())
}