use core::{ffi::CStr, mem::{self, MaybeUninit}, ptr};
use crate::{LuaState, SafeFn, ffi::{self, LUA_IDSIZE, lua_Debug, lua_Hook}, map_ptr};
#[cfg(doc)]
use crate::CFunction;
pub type Hook = extern "C-unwind" fn(lua: LuaState, info: &mut DebugInfo);
impl SafeFn for Hook {
type RawFn = lua_Hook;
#[inline]
unsafe fn from_raw(raw: lua_Hook) -> Self {
unsafe { mem::transmute(raw) }
}
#[inline]
fn to_raw(self) -> lua_Hook {
unsafe { mem::transmute(self) }
}
}
#[repr(i32)]
#[derive(Debug, Clone, Copy)]
pub enum HookEvent {
Call = ffi::LUA_HOOKCALL,
Return = ffi::LUA_HOOKRET,
Line = ffi::LUA_HOOKLINE,
Count = ffi::LUA_HOOKCOUNT,
}
impl HookEvent {
pub const MASK_CALL: i32 = ffi::LUA_MASKCALL;
pub const MASK_RETURN: i32 = ffi::LUA_MASKRET;
pub const MASK_LINE: i32 = ffi::LUA_MASKLINE;
pub const MASK_COUNT: i32 = ffi::LUA_MASKCOUNT;
#[inline]
#[must_use]
pub const fn as_mask(self) -> i32 {
1 << self as i32
}
}
#[repr(transparent)]
#[derive(Debug, Clone)]
pub struct DebugInfo(lua_Debug);
macro_rules! get_maybe_uninit_field {
($self:ident.$zero:literal.$field:ident) => {
if $self.is_fully_init() {
unsafe { $self.0.$field.assume_init() }
} else {
return None;
}
};
(&raw $self:ident.$zero:literal.$field:ident) => {
if $self.is_fully_init() { $self.0.$field.as_ptr() } else { return None; }
};
}
impl DebugInfo {
#[inline]
#[must_use]
pub const fn new() -> Self {
Self(lua_Debug {
event: -1 | Self::IS_FULLY_INIT,
name: MaybeUninit::new(ptr::null()),
namewhat: MaybeUninit::new(ptr::null()),
what: MaybeUninit::new(ptr::null()),
source: MaybeUninit::new(ptr::null()),
currentline: -1,
nups: MaybeUninit::new(-1),
linedefined: MaybeUninit::new(-1),
lastlinedefined: MaybeUninit::new(-1),
short_src: MaybeUninit::new([1; ffi::LUA_IDSIZE]),
i_ci: 0,
})
}
#[inline]
#[must_use]
pub const unsafe fn from_raw(raw: lua_Debug) -> Self {
Self(raw)
}
#[inline]
#[must_use]
pub const fn into_raw(self) -> lua_Debug {
self.0
}
#[inline]
#[must_use]
pub const fn as_ptr(&self) -> *const lua_Debug {
&raw const self.0
}
#[inline]
#[must_use]
pub const fn as_mut_ptr(&mut self) -> *mut lua_Debug {
&raw mut self.0
}
const IS_FULLY_INIT: i32 = 1 << 31;
#[must_use]
const fn is_fully_init(&self) -> bool {
self.0.event & Self::IS_FULLY_INIT != 0
}
#[inline]
#[must_use]
pub const fn source(&self) -> Option<&CStr> {
let source = get_maybe_uninit_field!(self.0.source);
map_ptr!(source, unsafe { CStr::from_ptr(source) })
}
#[inline]
#[must_use]
pub const fn short_source(&self) -> Option<&CStr> {
let short_source = get_maybe_uninit_field!(&raw self.0.short_src);
match CStr::from_bytes_until_nul(unsafe { &*short_source.cast::<[u8; ffi::LUA_IDSIZE]>() }) {
Ok(short_src) => Some(short_src),
Err(_) => None,
}
}
#[inline]
#[must_use]
pub const fn line_defined(&self) -> Option<i32> {
match get_maybe_uninit_field!(self.0.linedefined) {
-1 => None,
linedefined => Some(linedefined),
}
}
#[inline]
#[must_use]
pub const fn last_line_defined(&self) -> Option<i32> {
match get_maybe_uninit_field!(self.0.lastlinedefined) {
-1 => None,
lastlinedefined => Some(lastlinedefined),
}
}
#[inline]
#[must_use]
pub const fn what(&self) -> Option<&CStr> {
let what = get_maybe_uninit_field!(self.0.what);
map_ptr!(what, unsafe { CStr::from_ptr(what) })
}
#[inline]
#[must_use]
pub const fn current_line(&self) -> Option<i32> {
match self.0.currentline {
-1 => None,
currentline => Some(currentline),
}
}
#[inline]
#[must_use]
pub const fn name(&self) -> Option<&CStr> {
let name = get_maybe_uninit_field!(self.0.name);
map_ptr!(name, unsafe { CStr::from_ptr(name) })
}
#[inline]
#[must_use]
pub const fn name_what(&self) -> Option<&CStr> {
let namewhat = get_maybe_uninit_field!(self.0.namewhat);
map_ptr!(namewhat, unsafe { CStr::from_ptr(namewhat) })
}
#[inline]
#[must_use]
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
pub const fn n_upvalues(&self) -> Option<u8> {
match get_maybe_uninit_field!(self.0.nups) {
-1 => None,
nups => Some(nups as u8),
}
}
#[inline]
#[must_use]
pub const fn event(&self) -> Option<HookEvent> {
match self.0.event {
-1 => None, event => Some(unsafe { mem::transmute::<i32, HookEvent>(event & !Self::IS_FULLY_INIT) })
}
}
}
impl Default for DebugInfo {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl LuaState {
#[inline]
pub fn get_info(&mut self, what: &CStr, info: &mut DebugInfo) -> bool {
assert!(info.0.i_ci != 0 || unsafe { *what.as_ptr().cast::<u8>() == b'>' }, "tried to get debug information without specifying a function");
if !info.is_fully_init() {
info.0.name.write(ptr::null());
info.0.namewhat.write(ptr::null());
info.0.what.write(ptr::null());
info.0.source.write(ptr::null());
info.0.nups.write(-1);
info.0.linedefined.write(-1);
info.0.lastlinedefined.write(-1);
info.0.short_src.write([1; LUA_IDSIZE]);
info.0.event |= DebugInfo::IS_FULLY_INIT;
}
unsafe { ffi::lua_getinfo(self.0, what.as_ptr(), info.as_mut_ptr()) != 0 }
}
#[inline]
pub fn get_stack(&self, level: i32, info: &mut DebugInfo) -> bool {
let success = unsafe { ffi::lua_getstack(self.0, level, info.as_mut_ptr()) != 0 };
if !success {
info.0.i_ci = 0;
}
success
}
#[inline]
pub fn get_local(&mut self, info: Option<&DebugInfo>, n: i32) -> Option<&CStr> {
if let Some(info) = info {
assert!(info.0.i_ci != 0, "tried to get a local without specifying a function");
}
let local = unsafe { ffi::lua_getlocal(self.0, info.map_or(ptr::null(), DebugInfo::as_ptr), n) };
map_ptr!(local, unsafe { CStr::from_ptr(local) })
}
pub fn set_local(&mut self, info: &DebugInfo, n: i32) -> Option<&CStr> {
assert!(self.get_top() > 0, "tried to set a local's value despite having an empty stack");
assert!(info.0.i_ci != 0, "tried to set a local's value without specifying a function");
let local = unsafe { ffi::lua_setlocal(self.0, info.as_ptr(), n) };
map_ptr!(local, unsafe { CStr::from_ptr(local) })
}
#[inline]
pub fn get_upvalue(&mut self, func_index: i32, n: u8) -> Option<&CStr> {
let upvalue = unsafe { ffi::lua_getupvalue(self.0, func_index, i32::from(n)) };
map_ptr!(upvalue, unsafe { CStr::from_ptr(upvalue) })
}
#[inline]
pub fn set_upvalue(&mut self, func_index: i32, n: u8) -> Option<&CStr> {
assert!(self.get_top() > 0, "tried to set an upvalue's value despite having an empty stack");
let upvalue = unsafe { ffi::lua_setupvalue(self.0, func_index, i32::from(n)) };
map_ptr!(upvalue, unsafe { CStr::from_ptr(upvalue) })
}
#[inline]
pub fn is_function_upvalue(&self, func_index: i32, n: u8) -> bool {
unsafe { self.access_mut(|lua| {
let exists = lua.get_upvalue(func_index, n).is_some();
if exists {
lua.pop(1);
}
exists
}) }
}
#[inline]
pub fn upvalue_id(&self, func_index: i32, n: u8) -> *mut () {
if self.is_function_upvalue(func_index, n) {
unsafe { ffi::lua_upvalueid(self.0, func_index, i32::from(n)).cast() }
} else {
ptr::null_mut()
}
}
#[inline]
pub fn get_hook(&self) -> Option<lua_Hook> {
unsafe { ffi::lua_gethook(self.0) }
}
#[inline]
pub fn set_hook(&self, hook: Hook, mask: i32, count: i32) {
unsafe { ffi::lua_sethook(self.0, Some(hook.to_raw()), mask, count); }
}
#[inline]
pub fn unset_hook(&self) {
unsafe { ffi::lua_sethook(self.0, None, 0, 0); }
}
#[inline]
pub fn get_hook_mask(&self) -> i32 {
unsafe { ffi::lua_gethookmask(self.0) }
}
#[inline]
pub fn set_hook_mask(&self, mask: i32) {
unsafe { ffi::lua_sethook(self.0, self.get_hook(), mask, self.get_hook_count()); }
}
#[inline]
pub fn get_hook_count(&self) -> i32 {
unsafe { ffi::lua_gethookcount(self.0) }
}
#[inline]
pub fn set_hook_count(&self, count: i32) {
unsafe { ffi::lua_sethook(self.0, self.get_hook(), self.get_hook_mask(), count); }
}
}