luau 0.732.0

Safe lifetime-bound Rust embedding API for the Luau runtime
use bstr::{BStr, ByteSlice};
use luau_vm::debug::LuaDebug;
use luau_vm::thread::StackGuard;

use super::{Lua, LuaRef};
use crate::{Error, LuaString};

/// Borrowed information about one active Luau call frame.
#[derive(Debug)]
pub struct StackInfo<'frame> {
    source: &'frame BStr,
    short_source: &'frame BStr,
    name: Option<&'frame BStr>,
    what: &'frame BStr,
    current_line: Option<usize>,
    line_defined: Option<usize>,
    parameter_count: u8,
    upvalue_count: u8,
    is_vararg: bool,
}

impl Lua {
    /// Inspects the active stack frame at `level`.
    ///
    /// Level zero is the currently executing Luau function and higher levels
    /// walk its callers. The borrowed [`StackInfo`] is valid only during
    /// `inspect`.
    pub fn inspect_stack<R>(
        &self,
        level: usize,
        inspect: impl FnOnce(&StackInfo<'_>) -> R,
    ) -> Result<Option<R>, Error> {
        self.lua_ref().inspect_stack(level, inspect)
    }

    /// Creates a traceback for the current Luau thread.
    pub fn traceback(&self, message: Option<&str>, level: usize) -> Result<LuaString<'_>, Error> {
        self.lua_ref().traceback(message, level)
    }
}

impl<'lua> LuaRef<'lua> {
    /// Inspects the active stack frame at `level`.
    ///
    /// Level zero is the currently executing Luau function and higher levels
    /// walk its callers. The borrowed [`StackInfo`] is valid only during
    /// `inspect`.
    pub fn inspect_stack<R>(
        &self,
        level: usize,
        inspect: impl FnOnce(&StackInfo<'_>) -> R,
    ) -> Result<Option<R>, Error> {
        let level =
            i32::try_from(level).map_err(|_| Error::runtime("stack level is out of range"))?;
        unsafe {
            let thread = self.as_vm();
            let _stack = StackGuard::new(thread);
            let mut debug = LuaDebug::default();
            if thread
                .get_info(level, "slnua", &mut debug)
                .map_err(|exit| Error::from_thread_exit(thread, exit))?
                == 0
            {
                return Ok(None);
            }
            let info = StackInfo::from_debug(&debug);
            Ok(Some(inspect(&info)))
        }
    }

    /// Creates a traceback for the current Luau thread.
    pub fn traceback(&self, message: Option<&str>, level: usize) -> Result<LuaString<'lua>, Error> {
        let level =
            i32::try_from(level).map_err(|_| Error::runtime("stack level is out of range"))?;
        unsafe {
            let thread = self.as_vm();
            let _stack = StackGuard::new(thread);
            thread
                .traceback(
                    Some(thread),
                    message.map(|value| value.as_bytes().as_bstr()),
                    level,
                )
                .map_err(|exit| Error::from_thread_exit(thread, exit))?;
            LuaString::from_stack(&self.current_thread(), -1)
                .map_err(|exit| Error::from_thread_exit(thread, exit))
        }
    }
}

impl<'frame> StackInfo<'frame> {
    fn from_debug(debug: &'frame LuaDebug) -> Self {
        Self {
            source: debug.source.as_bytes().as_bstr(),
            short_source: debug.short_src().as_bstr(),
            name: debug.name.as_ref().map(|name| name.as_bytes().as_bstr()),
            what: debug.what.as_bytes().as_bstr(),
            current_line: usize::try_from(debug.currentline).ok(),
            line_defined: usize::try_from(debug.linedefined).ok(),
            parameter_count: debug.nparams,
            upvalue_count: debug.nupvals,
            is_vararg: debug.is_vararg,
        }
    }

    /// Returns the function's source name.
    pub fn source(&self) -> &BStr {
        self.source
    }

    /// Returns a shortened form of the source name.
    pub fn short_source(&self) -> &BStr {
        self.short_source
    }

    /// Returns the function name when one is known.
    pub fn name(&self) -> Option<&BStr> {
        self.name
    }

    /// Returns the kind of function represented by this frame.
    pub fn what(&self) -> &BStr {
        self.what
    }

    /// Returns the current source line.
    pub const fn current_line(&self) -> Option<usize> {
        self.current_line
    }

    /// Returns the line where the function was defined.
    pub const fn line_defined(&self) -> Option<usize> {
        self.line_defined
    }

    /// Returns the number of fixed parameters.
    pub const fn parameter_count(&self) -> u8 {
        self.parameter_count
    }

    /// Returns the number of upvalues.
    pub const fn upvalue_count(&self) -> u8 {
        self.upvalue_count
    }

    /// Returns whether the function accepts variadic arguments.
    pub const fn is_vararg(&self) -> bool {
        self.is_vararg
    }
}