low-luajit 0.5.0

Low level but safe bindings to the LuaJIT C API with no dependencies and no-std support to create Lua modules using Rust.
Documentation
use core::{fmt::{self, Debug, Display, Formatter, Write}, slice, str::Utf8Chunk};
use crate::{LuaState, LuaType, ffi};

/// Internal formatter for Lua values.
///
/// This struct is private as the implementation of the formatting functions does not specify the returned type.
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),

			// SAFETY: The element is a string.
			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\"")?;

				// SAFETY: The element is a string.
				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(),
		}
	}
}

/// Value formatting functions.
impl LuaState {
	/// Formats the string at the given `index` using `f` as the formatter function.
	///
	/// # Safety
	///
	/// The element at the given `index` must be a string.
	#[inline]
	unsafe fn fmt_string(&self, index: i32, f: impl FnMut(Utf8Chunk<'_>) -> fmt::Result) -> fmt::Result {
		// SAFETY: self.as_ptr() is always valid, len is initialized right before passing its pointer,
		// and lua_tolstring returns a valid for len amount of values string pointer as the element should always be a string.
		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)
	}

	/// Returns a printable representation of the element at the given `index`.
	///
	/// The formatting is unspecified but it resembles the results of Lua's `tostring` function. This may change in the future.
	///
	/// For a format that's more Rust-like, see [`LuaState::debug`].
	#[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 }
	}

	/// Returns a printable representation of the element at the given `index`.
	///
	/// The formatting is unspecified but it resembles a Rust enum's `Debug` implementation. This may change in the future.
	///
	/// For a format that's more Lua-like, see [`LuaState::display`].
	#[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 }
	}
}