Skip to main content

low_luajit/lua/
fmt.rs

1use core::{fmt::{self, Debug, Display, Formatter, Write}, slice, str::Utf8Chunk};
2use crate::{LuaState, LuaType, ffi};
3
4/// Internal formatter for Lua values.
5///
6/// This struct is private as the implementation of the formatting functions does not specify the returned type.
7struct 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			// SAFETY: The element is a string.
24			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				// SAFETY: The element is a string.
77				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
106/// Value formatting functions.
107impl LuaState {
108	/// Formats the string at the given `index` using `f` as the formatter function.
109	///
110	/// # Safety
111	///
112	/// The element at the given `index` must be a string.
113	#[inline]
114	unsafe fn fmt_string(&self, index: i32, f: impl FnMut(Utf8Chunk<'_>) -> fmt::Result) -> fmt::Result {
115		// SAFETY: self.as_ptr() is always valid, len is initialized right before passing its pointer,
116		// and lua_tolstring returns a valid for len amount of values string pointer as the element should always be a string.
117		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	/// Returns a printable representation of the element at the given `index`.
126	///
127	/// The formatting is unspecified but it resembles the results of Lua's `tostring` function. This may change in the future.
128	///
129	/// For a format that's more Rust-like, see [`LuaState::debug`].
130	#[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	/// Returns a printable representation of the element at the given `index`.
137	///
138	/// The formatting is unspecified but it resembles a Rust enum's `Debug` implementation. This may change in the future.
139	///
140	/// For a format that's more Lua-like, see [`LuaState::display`].
141	#[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}