use core::{fmt::{self, Debug, Display, Formatter, Write}, slice, str::Utf8Chunk};
use crate::{LuaState, LuaType, ffi};
struct ElementFormatter<'a, const RECURSIVE: bool> {
lua: &'a LuaState,
index: i32,
}
impl Display for ElementFormatter<'_, false> {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let Self { lua, index } = *self;
match lua.type_of(index) {
LuaType::None => f.write_str("(no value)"),
LuaType::Nil => f.write_str("nil"),
LuaType::Boolean => Display::fmt(&lua.to_boolean(index), f),
LuaType::Number => Display::fmt(&lua.to_number(index), f),
LuaType::String => unsafe { lua.fmt_string(index, |chunk| {
Display::fmt(chunk.valid(), f)?;
if !chunk.invalid().is_empty() {
f.write_char(char::REPLACEMENT_CHARACTER)?;
}
Ok(())
}) },
LuaType::Table => write!(f, "table: {:p}", lua.to_pointer(index)),
LuaType::Function => write!(f, "function: {:p}", lua.to_pointer(index)),
LuaType::Userdata | LuaType::LightUserdata => write!(f, "userdata: {:p}", lua.to_pointer(index)),
LuaType::Thread => write!(f, "thread: {:p}", lua.to_pointer(index)),
}
}
}
impl<const RECURSIVE: bool> Debug for ElementFormatter<'_, RECURSIVE> {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
const fn fmt_from_fn(f: impl Fn(&mut Formatter<'_>) -> fmt::Result) -> impl Debug {
#[cfg(feature = "no_msrv")]
#[clippy::msrv = "1.95.0"]
return fmt::from_fn(f);
#[cfg(not(feature = "no_msrv"))]
{
pub struct FmtFromFn<F>(F);
impl<F: Fn(&mut Formatter<'_>) -> fmt::Result> Debug for FmtFromFn<F> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
self.0(f)
}
}
FmtFromFn(f)
}
}
let Self { lua, index } = *self;
match lua.type_of(index) {
LuaType::None => f.write_str("None"),
LuaType::Nil => f.write_str("Nil"),
LuaType::Boolean => f.debug_tuple("Boolean").field(&lua.to_boolean(index)).finish(),
LuaType::LightUserdata => f.debug_tuple("LightUserdata").field(&lua.to_pointer(index)).finish(),
LuaType::Number => f.debug_tuple("Number").field(&lua.to_number(index)).finish(),
LuaType::String => f.debug_tuple("String").field(&fmt_from_fn(|f| {
f.write_str("b\"")?;
unsafe { lua.fmt_string(index, |chunk| {
Display::fmt(&chunk.valid().escape_debug(), f)?;
for byte in chunk.invalid() {
write!(f, "\\x{byte:X}")?;
}
Ok(())
})?; }
f.write_char('"')
})).finish(),
LuaType::Table if RECURSIVE => f.debug_tuple("Table").field(&fmt_from_fn(|f|
f.debug_map().entries(lua.pairs(index).map(|(k, v)| (
ElementFormatter::<false> { lua, index: k },
ElementFormatter::<false> { lua, index: v }
))).finish()
)).finish(),
LuaType::Table => f.debug_tuple("Table").field(&lua.to_pointer(index)).finish(),
LuaType::Function => f.debug_tuple("Function").field(&lua.to_pointer(index)).finish(),
LuaType::Userdata => f.debug_tuple("Userdata").field(&lua.to_pointer(index)).finish(),
LuaType::Thread => f.debug_tuple("Thread").field(&lua.to_pointer(index)).finish(),
}
}
}
impl LuaState {
#[inline]
unsafe fn fmt_string(&self, index: i32, f: impl FnMut(Utf8Chunk<'_>) -> fmt::Result) -> fmt::Result {
let string = unsafe {
let mut len = 0;
slice::from_raw_parts(ffi::lua_tolstring(self.as_ptr(), index, &raw mut len).cast(), len)
};
string.utf8_chunks().try_for_each(f)
}
#[inline]
#[must_use = "returns a type implementing `Display`, which does not have any effects unless consumed"]
pub fn display(&self, index: i32) -> impl Display + '_ {
ElementFormatter { lua: self, index }
}
#[inline]
#[must_use = "returns a type implementing `Debug`, which does not have any effects unless consumed"]
pub fn debug(&self, index: i32) -> impl Debug + '_ {
ElementFormatter::<true> { lua: self, index }
}
}