use core::str;
use luau_common::ByteSlice;
use crate::debug::LuaDebug;
use crate::native::{NativeCallContext, NativeCallResult, NativeFunction};
use crate::thread::Thread;
use crate::types::LUA_TFUNCTION;
static DEBUG_LIB: [NativeFunction; 2] = [
NativeFunction {
name: "info",
function: db_info,
},
NativeFunction {
name: "traceback",
function: db_traceback,
},
];
fn get_thread(thread: &Thread) -> (Option<Thread>, i32) {
if let Some(other) = unsafe { thread.to_thread(1) } {
(Some(other), 1)
} else {
(None, 0)
}
}
fn db_info(ctx: NativeCallContext) -> NativeCallResult {
let thread = ctx.raw_thread();
unsafe {
let (source, arg) = get_thread(thread);
let source = source.as_ref().unwrap_or(thread);
let mut source_top = 0;
if source != thread {
source.raw_check_stack(1)?;
source_top = source.get_top();
}
let level = if thread.is_number(arg + 1) != 0 {
let level = thread.check_integer(arg + 1)?;
if level < 0 {
return thread
.lua_arg_error(arg + 1, "level can't be negative")
.map_err(Into::into);
}
level
} else if arg == 0 && thread.type_of(1) == LUA_TFUNCTION {
-thread.get_top()
} else {
return thread
.lua_arg_error(arg + 1, "function or level expected")
.map_err(Into::into);
};
let options = thread.check_string(arg + 2)?;
let options = match str::from_utf8(options) {
Ok(options) => options,
Err(_) => {
return thread
.lua_arg_error(arg + 2, "invalid option")
.map_err(Into::into);
}
};
let mut ar = LuaDebug::default();
if source.get_info(level, options, &mut ar)? == 0 {
return Ok(0);
}
let mut results = 0;
let mut occurs = [false; 26];
for &byte in options.as_bytes() {
if byte.is_ascii_lowercase() {
let slot = (byte - b'a') as usize;
if occurs[slot] {
if source != thread {
source.restore_top(source_top);
}
return thread
.lua_arg_error(arg + 2, "duplicate option")
.map_err(Into::into);
}
occurs[slot] = true;
}
match byte {
b's' => {
thread.push_string(ar.short_src())?;
results += 1;
}
b'l' => {
thread.push_integer(ar.currentline)?;
results += 1;
}
b'n' => {
if let Some(name) = &ar.name {
thread.push_string(name)?;
} else {
thread.push_string("")?;
}
results += 1;
}
b'f' => {
if source == thread {
thread.push_value(-1 - results)?;
} else {
source.x_move(thread, 1)?;
}
results += 1;
}
b'a' => {
thread.push_integer(ar.nparams as i32)?;
thread.push_boolean(i32::from(ar.is_vararg))?;
results += 2;
}
_ => {
if source != thread {
source.restore_top(source_top);
}
return thread
.lua_arg_error(arg + 2, "invalid option")
.map_err(Into::into);
}
}
}
Ok(results as usize)
}
}
fn db_traceback(ctx: NativeCallContext) -> NativeCallResult {
let thread = ctx.raw_thread();
unsafe {
let (source, arg) = get_thread(thread);
let source = source.as_ref().unwrap_or(thread);
let message = thread.opt_string(arg + 1)?.map(|value| value.as_bstr());
let level = thread.opt_integer(arg + 2, if source == thread { 1 } else { 0 })?;
if level < 0 {
return thread
.lua_arg_error(arg + 2, "level can't be negative")
.map_err(Into::into);
}
thread.traceback(Some(source), message, level)?;
Ok(1)
}
}
impl Thread {
pub unsafe fn open_debug(&self) -> NativeCallResult {
unsafe { self.register(Some(super::LUA_DBLIB_NAME), &DEBUG_LIB[..])? };
Ok(1)
}
}