1use core::{fmt::{self, Debug, Display, Formatter, Write}, slice, str::Utf8Chunk};
2use crate::{LuaState, LuaType, ffi};
3
4struct ElementFormatter<'a, const RECURSIVE: bool> {
8 lua: &'a LuaState,
9 index: i32,
10}
11
12impl Display for ElementFormatter<'_, false> {
13 #[inline]
14 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
15 let Self { lua, index } = *self;
16
17 match lua.type_of(index) {
18 LuaType::None => f.write_str("(no value)"),
19 LuaType::Nil => f.write_str("nil"),
20 LuaType::Boolean => Display::fmt(&lua.to_boolean(index), f),
21 LuaType::Number => Display::fmt(&lua.to_number(index), f),
22
23 LuaType::String => unsafe { lua.fmt_string(index, |chunk| {
25 Display::fmt(chunk.valid(), f)?;
26
27 if !chunk.invalid().is_empty() {
28 f.write_char(char::REPLACEMENT_CHARACTER)?;
29 }
30
31 Ok(())
32 }) },
33
34 LuaType::Table => write!(f, "table: {:p}", lua.to_pointer(index)),
35 LuaType::Function => write!(f, "function: {:p}", lua.to_pointer(index)),
36 LuaType::Userdata | LuaType::LightUserdata => write!(f, "userdata: {:p}", lua.to_pointer(index)),
37 LuaType::Thread => write!(f, "thread: {:p}", lua.to_pointer(index)),
38 }
39 }
40}
41
42impl<const RECURSIVE: bool> Debug for ElementFormatter<'_, RECURSIVE> {
43 #[inline]
44 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
45 const fn fmt_from_fn(f: impl Fn(&mut Formatter<'_>) -> fmt::Result) -> impl Debug {
46 #[cfg(feature = "no_msrv")]
47 #[clippy::msrv = "1.95.0"]
48 return fmt::from_fn(f);
49
50 #[cfg(not(feature = "no_msrv"))]
51 {
52 pub struct FmtFromFn<F>(F);
53
54 impl<F: Fn(&mut Formatter<'_>) -> fmt::Result> Debug for FmtFromFn<F> {
55 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
56 self.0(f)
57 }
58 }
59
60 FmtFromFn(f)
61 }
62 }
63
64 let Self { lua, index } = *self;
65
66 match lua.type_of(index) {
67 LuaType::None => f.write_str("None"),
68 LuaType::Nil => f.write_str("Nil"),
69 LuaType::Boolean => f.debug_tuple("Boolean").field(&lua.to_boolean(index)).finish(),
70 LuaType::LightUserdata => f.debug_tuple("LightUserdata").field(&lua.to_pointer(index)).finish(),
71 LuaType::Number => f.debug_tuple("Number").field(&lua.to_number(index)).finish(),
72
73 LuaType::String => f.debug_tuple("String").field(&fmt_from_fn(|f| {
74 f.write_str("b\"")?;
75
76 unsafe { lua.fmt_string(index, |chunk| {
78 Display::fmt(&chunk.valid().escape_debug(), f)?;
79
80 for byte in chunk.invalid() {
81 write!(f, "\\x{byte:X}")?;
82 }
83
84 Ok(())
85 })?; }
86
87 f.write_char('"')
88 })).finish(),
89
90
91 LuaType::Table if RECURSIVE => f.debug_tuple("Table").field(&fmt_from_fn(|f|
92 f.debug_map().entries(lua.pairs(index).map(|(k, v)| (
93 ElementFormatter::<false> { lua, index: k },
94 ElementFormatter::<false> { lua, index: v }
95 ))).finish()
96 )).finish(),
97
98 LuaType::Table => f.debug_tuple("Table").field(&lua.to_pointer(index)).finish(),
99 LuaType::Function => f.debug_tuple("Function").field(&lua.to_pointer(index)).finish(),
100 LuaType::Userdata => f.debug_tuple("Userdata").field(&lua.to_pointer(index)).finish(),
101 LuaType::Thread => f.debug_tuple("Thread").field(&lua.to_pointer(index)).finish(),
102 }
103 }
104}
105
106impl LuaState {
108 #[inline]
114 unsafe fn fmt_string(&self, index: i32, f: impl FnMut(Utf8Chunk<'_>) -> fmt::Result) -> fmt::Result {
115 let string = unsafe {
118 let mut len = 0;
119 slice::from_raw_parts(ffi::lua_tolstring(self.as_ptr(), index, &raw mut len).cast(), len)
120 };
121
122 string.utf8_chunks().try_for_each(f)
123 }
124
125 #[inline]
131 #[must_use = "returns a type implementing `Display`, which does not have any effects unless consumed"]
132 pub fn display(&self, index: i32) -> impl Display + '_ {
133 ElementFormatter { lua: self, index }
134 }
135
136 #[inline]
142 #[must_use = "returns a type implementing `Debug`, which does not have any effects unless consumed"]
143 pub fn debug(&self, index: i32) -> impl Debug + '_ {
144 ElementFormatter::<true> { lua: self, index }
145 }
146}