luau 0.732.0

Safe lifetime-bound Rust embedding API for the Luau runtime
use core::marker::PhantomData;

use luau_vm::thread::StackGuard;

use super::Table;
use crate::error::Error;
use crate::value::{FromLua, Value};

/// An iterator over the key-value pairs of a [`Table`].
pub struct TablePairs<'table, 'lua, K, V> {
    table: &'table Table<'lua>,
    key: Option<Value<'lua>>,
    _marker: PhantomData<(K, V)>,
}

impl<'lua> Table<'lua> {
    /// Returns an iterator over this table's key-value pairs.
    pub fn pairs<K, V>(&self) -> TablePairs<'_, 'lua, K, V>
    where
        K: FromLua<'lua>,
        V: FromLua<'lua>,
    {
        TablePairs {
            table: self,
            key: Some(Value::Nil),
            _marker: PhantomData,
        }
    }

    /// Calls a function for each key-value pair without allocating an iterator.
    pub fn for_each<K, V>(
        &self,
        mut function: impl FnMut(K, V) -> Result<(), Error>,
    ) -> Result<(), Error>
    where
        K: FromLua<'lua>,
        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();
            vm_thread
                .push_nil()
                .map_err(|exit| Error::from_thread_exit(vm_thread, exit))?;
            while vm_thread
                .next(table_index)
                .map_err(|exit| Error::from_thread_exit(vm_thread, exit))?
                != 0
            {
                let key = K::from_stack(&thread, -2)?;
                let value = V::from_stack(&thread, -1)?;
                function(key, value)?;
                vm_thread.pop(1);
            }
        }
        Ok(())
    }
}

impl<'lua, K, V> Iterator for TablePairs<'_, 'lua, K, V>
where
    K: FromLua<'lua>,
    V: FromLua<'lua>,
{
    type Item = Result<(K, V), Error>;

    fn next(&mut self) -> Option<Self::Item> {
        let previous_key = self.key.take()?;
        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();
            previous_key.push_to(&thread)?;
            if vm_thread
                .next(table_index)
                .map_err(|exit| Error::from_thread_exit(vm_thread, exit))?
                == 0
            {
                return Ok(None);
            }

            let key = Value::from_stack(&thread, -2)?;
            let next_key = key.try_clone()?;
            let key = K::from_lua(key, thread.lua_ref())?;
            let value = V::from_stack(&thread, -1)?;
            Ok(Some((next_key, key, value)))
        })();

        match result {
            Ok(Some((next_key, key, value))) => {
                self.key = Some(next_key);
                Some(Ok((key, value)))
            }
            Ok(None) => None,
            Err(error) => Some(Err(error)),
        }
    }
}