use bstr::{BStr, ByteSlice};
use luau_vm::debug::LuaDebug;
use luau_vm::thread::StackGuard;
use super::{Lua, LuaRef};
use crate::{Error, LuaString};
#[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 {
pub fn inspect_stack<R>(
&self,
level: usize,
inspect: impl FnOnce(&StackInfo<'_>) -> R,
) -> Result<Option<R>, Error> {
self.lua_ref().inspect_stack(level, inspect)
}
pub fn traceback(&self, message: Option<&str>, level: usize) -> Result<LuaString<'_>, Error> {
self.lua_ref().traceback(message, level)
}
}
impl<'lua> LuaRef<'lua> {
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)))
}
}
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,
}
}
pub fn source(&self) -> &BStr {
self.source
}
pub fn short_source(&self) -> &BStr {
self.short_source
}
pub fn name(&self) -> Option<&BStr> {
self.name
}
pub fn what(&self) -> &BStr {
self.what
}
pub const fn current_line(&self) -> Option<usize> {
self.current_line
}
pub const fn line_defined(&self) -> Option<usize> {
self.line_defined
}
pub const fn parameter_count(&self) -> u8 {
self.parameter_count
}
pub const fn upvalue_count(&self) -> u8 {
self.upvalue_count
}
pub const fn is_vararg(&self) -> bool {
self.is_vararg
}
}