low-luajit 0.2.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::{ffi::CStr, mem, ptr};
use crate::{AmountReturned, CFunction, LuaState, ffi::{self, lua_CFunction, luaL_Reg}, map_ptr};

#[cfg(doc)]
use crate::reg;

/// Safe wrapper for [`luaL_Reg`], used in module creation.
///
/// This type is usually contained within a `&[Reg]`, these slices can be generated easily using the [`reg!`] macro or created manually
/// using [`Reg::new`] and [`Reg::END`].
///
/// *[See also the `reg!` macro](macro.reg.html)*.
#[repr(transparent)]
#[derive(Debug, Clone, Copy)]
pub struct Reg(luaL_Reg);

impl Reg {
	/// The terminator of a `Reg` slice. All slices must have the terminator as their last element.
	///
	/// [`reg!`] automatically appends the terminator to the generated slice.
	pub const END: Self = Self(luaL_Reg { name: ptr::null(), func: None });

	/// Manually creates a new `Reg` element intended to be inserted into a slice.
	///
	/// This function is internally called for you by [`reg!`].
	#[inline]
	#[must_use]
	pub const fn new(name: &'static CStr, func: CFunction) -> Self {
		Self(luaL_Reg {
			name: name.as_ptr(), // SAFETY: same as CFunction::to_raw, just cannot call the method directly since it's a const function.
			func: Some(unsafe { mem::transmute::<CFunction, lua_CFunction>(func) })
		})
	}

	/// Wraps a pre-existing raw [`luaL_Reg`].
	///
	/// # Safety
	///
	/// - `name` must either be null or a valid string pointer.
	/// - `func` must either be `None` or safe to call.
	///
	/// A non-modified [`luaL_Reg`] obtained from [`Reg::into_raw`] should always be safe to use.
	#[inline]
	#[must_use]
	pub const unsafe fn from_raw(raw: luaL_Reg) -> Self {
		Self(raw)
	}

	/// Returns the wrapped [`luaL_Reg`].
	///
	/// The raw [`luaL_Reg`] can be safely converted back into a `Reg` using [`Reg::from_raw`], as long as it is not modified.
	#[inline]
	#[must_use]
	pub const fn into_raw(self) -> luaL_Reg {
		self.0
	}

	/// Returns the stored function name.
	///
	/// [`Reg::END`] will return `None` instead.
	#[inline]
	#[must_use]
	pub const fn name(&self) -> Option<&'static CStr> {
		// SAFETY: self.0.name should always be a valid nul-terminated string pointer when not null.
		map_ptr!(self.0.name, unsafe { CStr::from_ptr(self.0.name) })
	}

	/// Returns the stored function.
	///
	/// [`Reg::END`] will return `None` instead.
	#[inline]
	#[must_use]
	pub const fn func(&self) -> Option<CFunction> {
		// SAFETY: self.0.func should be safe as it comes from a CFunction, but CFunction::from_raw cannot be called since it's a const function.
		match self.0.func {
			Some(func) => Some(unsafe { mem::transmute::<lua_CFunction, CFunction>(func) }),
			None => None,
		}
	}

	/// Returns `true` if this `Reg` is equal to [`Reg::END`], `false` otherwise.
	#[inline]
	#[must_use]
	pub const fn is_end(&self) -> bool {
		self.0.name.is_null() && self.0.func.is_none()
	}
}

/// Generates a list of named functions (`&[Reg]`) to be used in module creation.
///
/// The macro takes as input a list of the functions to put in the [`Reg`] slice. The list's elements can be:
/// - A tuple comprising of the function's name (as a `CStr`) and the function itself (as a [`CFunction`]):
/// ```
/// # use low_luajit::{LuaState, AmountReturned, reg};
/// # extern "C-unwind" fn foo(lua: LuaState) -> AmountReturned { AmountReturned::NONE }
/// # extern "C-unwind" fn bar(lua: LuaState) -> AmountReturned { AmountReturned::NONE }
/// # extern "C-unwind" fn baz(lua: LuaState) -> AmountReturned { AmountReturned::NONE }
/// # extern "C-unwind" fn example(lua: LuaState, condition: bool) -> AmountReturned {
/// lua.return_module(reg![
///     (c"foo", foo), // Any expression can be used inside the tuple!
///     (c"bar", if condition { bar } else { baz })
/// ])
/// # }
/// ```
/// - Just the function itself (as a [`CFunction`]), the function's name will be derived from the given identifier:
/// ```
/// # use low_luajit::{LuaState, AmountReturned, reg};
/// # extern "C-unwind" fn foo(lua: LuaState) -> AmountReturned { AmountReturned::NONE }
/// # extern "C-unwind" fn bar(lua: LuaState) -> AmountReturned { AmountReturned::NONE }
/// # extern "C-unwind" fn baz(lua: LuaState) -> AmountReturned { AmountReturned::NONE }
/// # extern "C-unwind" fn example(mut lua: LuaState, condition: bool) -> AmountReturned {
/// // Only identifiers (no expressions) can be used here.
/// lua.register_module(c"my_global_module", reg![foo, bar, baz]);
/// # AmountReturned::NONE }
/// ```
///
/// *(All elements of the input list must be specified the same way.)*
///
/// The generated slice can be passed to various functions used in module creation:
/// - [`LuaState::return_module`] returns to the caller a table containing the slice's functions.
/// - [`LuaState::register_module`] sets as a new global a table containing the slice's functions.
/// - [`LuaState::new_module`] pushes onto the stack a table containing the slice's functions.
/// - *(Etc.)*
#[macro_export]
macro_rules! reg {
	($(($name:expr, $func:expr)),* $(,)?) => {
		&[$($crate::Reg::new($name, $func)),*, $crate::Reg::END]
	};
	($($func:ident),* $(,)?) => {
		// SAFETY: function names should not be able to contain nul terminators, and a nul terminator is added at the end.
		$crate::reg![$((unsafe {core::ffi::CStr::from_bytes_with_nul_unchecked(concat!(stringify!($func), '\0').as_bytes())}, $func)),*]
	}
}

/// Module related functions.
impl LuaState {
	/// Pushes onto the stack a table filled with all the functions contained in `list` and finishes the currently called [`CFunction`].
	///
	/// The returned [`AmountReturned`] will always be `1`, which will make Lua return the pushed table to the caller.
	///
	/// This function can be handy to quickly return your module's functions from your `luaopen_crate_name` function.
	///
	/// # Panics
	///
	/// Panics if `list` does not have the [`Reg::END`] terminator at its end.
	/// *([`reg!`] automatically appends the terminator for you.)*
	///
	/// # Examples
	///
	/// ```
	/// # use low_luajit::{LuaState, AmountReturned, reg};
	/// extern "C-unwind" fn foo(lua: LuaState) -> AmountReturned {
	///     // ...
	/// # AmountReturned::NONE
	/// }
	///
	/// extern "C-unwind" fn bar(lua: LuaState) -> AmountReturned {
	///     // ...
	/// # AmountReturned::NONE
	/// }
	///
	/// #[unsafe(no_mangle)]
	/// extern "C-unwind" fn luaopen_crate_name(lua: LuaState) -> AmountReturned {
	///     lua.return_module(reg![foo, bar])
	/// }
	/// ```
	#[inline]
	pub fn return_module(mut self, list: &[Reg]) -> AmountReturned {
		self.new_module(list);

		// SAFETY: There should always be at least one element in the stack.
		unsafe { AmountReturned::unchecked(1) }
	}

	/// Pushes onto the stack a new table filled with all the functions contained in `list`.
	/// This table is then set as the value of a global named `name` and of `package.loaded[name]`.
	///
	/// If `package.loaded[name]` already contains a table, said table will be used instead of creating a new table
	/// (it will still be pushed onto the stack).
	///
	/// If `name` is `None`, this function will instead simply use the (pre-existing) top element as the table to fill.
	/// (In this case the table will *not* be set as the value of neither a global nor of `package.loaded[name]`.)
	///
	/// All paths will result in the filled table being the top element.
	///
	/// # Panics
	///
	/// Panics if `list` does not have the [`Reg::END`] terminator at its end.
	/// *([`reg!`] automatically appends the terminator for you.)*
	///
	/// # Lua Errors
	///
	/// Indexing most of the aformentioned tables may trigger the `__newindex` metamethod which could error.
	#[inline]
	pub fn register_module<'a>(&mut self, name: impl Into<Option<&'a CStr>>, list: &[Reg]) {
		assert!(list.last().is_some_and(Reg::is_end), "tried to set a global module using an unterminated named function list");

		// SAFETY: self.0 is always valid,
		// name should always be a valid nul-terminated string pointer or a null pointer which luaL_register accepts,
		// and list should always be valid and terminated.
		unsafe { ffi::luaL_register(self.0, name.into().map_or(ptr::null(), CStr::as_ptr), list.as_ptr().cast()); }
	}

	/// Creates a new table and pushes it onto the stack.
	///
	/// This table will be filled with all the functions contained in `list`.
	///
	/// To automatically return this table to the caller, see [`LuaState::return_module`].
	///
	/// For a function that only pushes an empty pre-allocated (for `list`) table to the stack, see [`LuaState::new_module_table`].
	///
	/// # Panics
	///
	/// Panics if `list` does not have the [`Reg::END`] terminator at its end.
	/// *([`reg!`] automatically appends the terminator for you.)*
	#[inline]
	pub fn new_module(&mut self, list: &[Reg]) {
		assert!(list.last().is_some_and(Reg::is_end), "tried to create a module using an unterminated named function list");

		// SAEFTY: self.0 is always valid and list should always be valid and terminated.
		unsafe { ffi::luaL_newlib(self.0, list.as_ptr().cast()); }
	}

	/// Creates a new empty table and pushes it onto the stack.
	///
	/// This table will have as many elements as there are function in `list` pre-allocated.
	///
	// To fill this empty pre-allocated table with `list`'s functions, see [`LuaState::set_functions`]. (TODO)
	///
	/// For a function that both pushes a pre-allocated table to the stack and fills it with `list`'s functions, see [`LuaState::new_module`].
	///
	/// # Panics
	///
	/// Panics if `list` does not have the [`Reg::END`] terminator at its end.
	/// *([`reg!`] automatically appends the terminator for you.)*
	#[inline]
	pub fn new_module_table(&mut self, list: &[Reg]) {
		assert!(list.last().is_some_and(Reg::is_end), "tried to create a module's table using an unterminated named function list");

		// SAEFTY: self.0 is always valid and list should always be valid and terminated.
		unsafe { ffi::luaL_newlibtable(self.0, list.as_ptr().cast()); }
	}
}