luau 0.732.0

Safe lifetime-bound Rust embedding API for the Luau runtime
use core::fmt;
use std::collections::HashSet;

use luau_vm::thread::StackGuard;

use super::Table;
use crate::error::Error;
use crate::value::Value;

impl<'lua> Table<'lua> {
    pub(crate) fn fmt_pretty(
        &self,
        formatter: &mut fmt::Formatter<'_>,
        indent: usize,
        visited: &mut HashSet<*const ()>,
    ) -> fmt::Result {
        let pointer = self.pointer();
        if !visited.insert(pointer) {
            return write!(formatter, "table: {pointer:p}");
        }

        let Ok(mut pairs) = self.pairs_for_debug() else {
            return write!(formatter, "table: {pointer:p}");
        };
        if pairs.is_empty() {
            return formatter.write_str("{}");
        }

        pairs.sort_by(|(left, _), (right, _)| left.sort_cmp(right));
        let is_sequence = pairs.iter().enumerate().all(|(index, (key, _))| {
            matches!(
                key.as_integer(),
                Some(key) if key == i64::try_from(index + 1).unwrap_or(i64::MAX)
            )
        });

        writeln!(formatter, "{{")?;
        if is_sequence {
            for (_, value) in pairs {
                write!(formatter, "{}", " ".repeat(indent + 2))?;
                value.fmt_pretty(formatter, true, indent + 2, visited)?;
                writeln!(formatter, ",")?;
            }
        } else {
            for (key, value) in pairs {
                match key {
                    Value::String(key) if is_simple_key(key.as_bytes().as_ref()) => {
                        write!(formatter, "{}{key}", " ".repeat(indent + 2))?;
                        formatter.write_str(" = ")?;
                    }
                    key => {
                        write!(formatter, "{}[", " ".repeat(indent + 2))?;
                        key.fmt_pretty(formatter, false, indent + 2, visited)?;
                        formatter.write_str("] = ")?;
                    }
                }
                value.fmt_pretty(formatter, true, indent + 2, visited)?;
                writeln!(formatter, ",")?;
            }
        }
        write!(formatter, "{}}}", " ".repeat(indent))
    }

    fn pairs_for_debug(&self) -> Result<Vec<(Value<'lua>, Value<'lua>)>, Error> {
        unsafe {
            let thread = self.thread();
            let vm_thread = thread.as_vm();
            let _stack = StackGuard::new(vm_thread);
            let mut pairs = Vec::new();

            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 value = Value::from_stack(&thread, -1)?;
                let key = Value::from_stack(&thread, -2)?;
                pairs.push((key, value));
                vm_thread.pop(1);
            }

            Ok(pairs)
        }
    }
}

impl fmt::Debug for Table<'_> {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        if formatter.alternate() {
            return self.fmt_pretty(formatter, 0, &mut HashSet::new());
        }

        formatter
            .debug_tuple("Table")
            .field(&self.reference)
            .finish()
    }
}

fn is_simple_key(key: &[u8]) -> bool {
    key.first()
        .is_some_and(|byte| byte.is_ascii_alphabetic() || *byte == b'_')
        && key
            .iter()
            .all(|byte| byte.is_ascii_alphanumeric() || *byte == b'_')
}