low-luajit 0.4.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, fmt::{self, Display, Write}, hint::assert_unchecked};
use crate::{LuaState, LuaType, ffi};

/// Stack (& indices) related functions.
impl LuaState {
	/// Pops `amount` elements from the stack.
	///
	/// If `amount` is `0`, nothing happens.
	///
	/// Negative values of `amount` are allowed, behaving the same way as passing `abs(amount) - 1` to [`LuaState::set_top`].
	///
	/// # Panics
	///
	/// Panics if the stack contains less than `amount` values currently.
	#[inline]
	pub fn pop(&mut self, amount: i32) {
		assert!(amount <= self.get_top(), "tried to pop {amount} values but the stack currently holds {} values", self.get_top());

		// SAFETY: self.0 is always valid and the stack contains enough values to pop.
		unsafe { ffi::lua_pop(self.0, amount); }
	}

	/// Grow the stack to be large enough to hold `extra` more elements. Does nothing if the stack is already large enough.
	///
	/// If the stack failed to grow to the desired size, this function will error using `extra_msg` as the error message.
	///
	/// This function never shrinks the stack.
	///
	/// # Lua Errors
	///
	/// Intentionally errors if the stack fails to grow.
	pub fn check_stack(&mut self, extra: i32, extra_msg: &CStr) {
		// SAFETY: self.0 and extra_msg are always valid.
		unsafe { ffi::luaL_checkstack(self.0, extra, extra_msg.as_ptr()); }
	}

	/// Attempts to grow the stack to be large enough to hold `extra` more elements. Does nothing if the stack is already large enough.
	///
	/// Returns `false` if it failed to grow the stack to the desired size, `true` otherwise.
	///
	/// This function never shrinks the stack.
	#[inline]
	pub fn try_check_stack(&mut self, extra: i32) -> bool {
		// SAFETY: self.0 is always valid.
		unsafe { ffi::lua_checkstack(self.0, extra) != 0 }
	}

	/// Returns the index of the top element (last inserted) in the stack.
	///
	/// This also equals the amount of elements in the stack.
	#[inline]
	#[must_use]
	pub fn get_top(&self) -> i32 {
		// SAFETY: self.0 is always valid and there should never be a negative amount of elements.
		unsafe {
			let top = ffi::lua_gettop(self.0);
			assert_unchecked(top >= 0);
			top
		}
	}

	/// Sets the stack top to be `index`.
	///
	/// If the new top is higher than the previous, the new elements are filled with `nil`.
	///
	/// An `index` of `0` removes all stack elements.
	///
	/// # Panics
	///
	/// Panics if `index` is a negative number and `abs(index) > top`.
	///
	/// # Lua Errors
	///
	/// Really high `index` values may cause a stack overflow.
	#[inline]
	pub fn set_top(&mut self, index: i32) {
		assert!(index.is_positive() || self.is_index_ok(index), "cannot set the top to be {index}, as it is negative and out of bounds");

		// SAFETY: self.0 is always valid and index is not out of bounds.
		unsafe { ffi::lua_settop(self.0, index); }
	}

	/// Pseudo-index to the registry: a pre-defined table that C/Rust can use to store whatever it needs.
	///
	/// Other loaded C/Rust modules may use this table too, thus you should make your keys unique to avoid collisions
	/// (by using the module name itself for example).
	///
	/// The integer keys of the registry are used internally by Lua, and should not be touched.
	pub const REGISTRY_INDEX: i32 = ffi::LUA_REGISTRYINDEX;

	/// Pseudo-index to the environment of the running [`CFunction`].
	///
	/// [`CFunction`]: ./type.CFunction.html
	pub const ENVIRONMENT_INDEX: i32 = ffi::LUA_ENVIRONINDEX;

	/// Pseudo-index to the thread environment: the table containing all the global variables (`_G`).
	pub const GLOBALS_INDEX: i32 = ffi::LUA_GLOBALSINDEX;

	/// Returns the pseudo-index to the upvalue #`n` (i.e. `upvalue_index(1)` returns the pseudo-index to the first upvalue).
	///
	/// This function does *not* check if the upvalue exists, it just returns its hypothethical pseudo-index.
	///
	/// If `n` is `0`, this function will instead return [`LuaState::GLOBALS_INDEX`].
	#[inline]
	#[must_use]
	pub const fn upvalue_index(n: u8) -> i32 {
		ffi::lua_upvalueindex(n as i32)
	}

	/// Returns a boolean stating if `index` is a valid index.
	///
	/// `index` is valid if `1 ≤ abs(index) ≤ top`.
	///
	/// This function only checks if `index` is *valid*, not *acceptable*!
	#[inline]
	#[must_use]
	pub fn is_index_valid(&self, index: i32) -> bool {
		let abs_index = index.abs();
		abs_index >= 1 && abs_index <= self.get_top()
	}

	/// Returns a boolean stating if `index` is an "ok" index.
	///
	/// `index` is "ok" if `abs(index) ≤ top`.
	///
	/// This function is used internally for assertions.
	#[inline]
	#[must_use]
	pub fn is_index_ok(&self, index: i32) -> bool {
		index.unsigned_abs() <= self.get_top().cast_unsigned()
	}

	/// Converts the value at the given acceptable `index` to a generic pointer and returns it.
	///
	/// Only userdata, tables, and functions are accepted. Anything else will return a null pointer.
	///
	/// There is no intended way to convert the pointer back to its original value.
	#[inline]
	#[must_use]
	pub fn to_pointer(&self, index: i32) -> *const () {
		// SAFETY: self.0 is always valid.
		unsafe { ffi::lua_topointer(self.0, index).cast() }
	}

	/// Pushes a copy of the value at the given valid `index` onto the stack.
	///
	/// If `index` is out of bounds, `nil` is pushed onto the stack.
	#[inline]
	pub fn push_value(&mut self, index: i32) {
		// SAFETY: self.0 is always valid.
		unsafe { ffi::lua_pushvalue(self.0, index); }
	}

	/// Pops the top element and inserts it at the given valid `index`, shifting up the other elements to make space.
	///
	/// This function will do nothing if `index` is 0.
	///
	/// To replace the element located at `index` instead of shifting them up, see [`LuaState::replace`].
	///
	/// # Panics
	///
	/// Panics if `index` is out of bounds.
	/// (Pseudo-indices are out of bounds as they are not real indices.)
	#[inline]
	pub fn insert(&mut self, index: i32) {
		assert!(self.is_index_ok(index), "cannot insert the top element at index {index}, as there are only {} elements", self.get_top());

		// SAFETY: self.0 is always valid and index is not out of bounds.
		unsafe { ffi::lua_insert(self.0, index); }
	}

	/// Pops the top element and inserts it at the given valid `index`, replacing the element that was previously located at `index`.
	///
	/// If `index` is zero, the top element is effectively removed.
	///
	/// To shift up the other elements instead of replacing it, see [`LuaState::insert`].
	///
	/// # Panics
	///
	/// Panics if `index` is out of bounds.
	/// (Pseudo-indices are out of bounds as they are not real indices.)
	#[inline]
	pub fn replace(&mut self, index: i32) {
		assert!(self.is_index_ok(index), "cannot replace the element at index {index} with the top element, as there are only {} elements", self.get_top());

		// SAFETY: self.0 is always valid and index is not out of bounds.
		unsafe { ffi::lua_replace(self.0, index); }
	}

	/// Removes the element at the given valid `index`, shifting down the other elements to fill the gap.
	///
	/// # Panics
	///
	/// Panics if `index` is out of bounds or `0`.
	/// (Pseudo-indices are out of bounds as they are not real indices.)
	#[inline]
	pub fn remove(&mut self, index: i32) {
		assert!(self.is_index_valid(index), "cannot delete an element at invalid index {index}");

		// SAFETY: self.0 is always valid and index is not out of bounds.
		unsafe { ffi::lua_remove(self.0, index); }
	}

	/// Copies the element at index `from_index` into the valid index `to_index`, replacing the element that was previously located at `to_index`.
	///
	/// If `from_index` is out of bounds, the element located at `to_index` will be replaced with `nil`.
	///
	/// # Panics
	///
	/// Panics if `to_index` is out of bounds.
	#[inline]
	pub fn copy(&mut self, from_index: i32, to_index: i32) {
		assert!(
			self.is_index_ok(to_index),
			"cannot replace the element at index {to_index} with the element at index {from_index}, as there are only {} elements",
			self.get_top()
		);

		unsafe { ffi::lua_copy(self.0, from_index, to_index); }
	}

	/// Checks if there's an element of any type (including `nil`) at the given index `arg_n`.
	///
	/// If the check fails, `LuaState::arg_error` is called using `arg_n`.
	///
	/// # Lua Errors
	///
	/// Intentionally errors if the check fails.
	#[inline]
	pub fn check_any(&self, arg_n: i32) {
		// SAFETY: self.0 is always valid.
		unsafe { ffi::luaL_checkany(self.0, arg_n); }
	}

	/// Returns `default` when the element at the given index `arg_n` is `nil` (or non-existing).
	///
	/// Otherwise, `func` is called with `self` and `arg_n` as its arguments, returning the value `func` returns.
	#[inline]
	pub fn opt<T>(&self, func: impl FnOnce(&LuaState, i32) -> T, arg_n: i32, default: T) -> T {
		if self.is_none_or_nil(arg_n) {
			default
		} else {
			func(self, arg_n)
		}
	}

	/// Returns a printable representation of the element at the given `index`.
	///
	/// The formatting is unspecified but it loosely mimics the formatting of Lua's `tostring` function. This may change in the future.
	#[inline]
	pub fn display(&self, index: i32) -> impl Display + '_ {
		struct ElementDisplay<'a> {
			lua: &'a LuaState,
			index: i32,
		}

		impl Display for ElementDisplay<'_> {
			#[inline]
			fn fmt(&self, f: &mut fmt::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 => lua.to_boolean(index).fmt(f),
					LuaType::Number => lua.to_number(index).fmt(f),
					LuaType::String => {
						// SAFETY: The element can only be a string.
						let bytes = unsafe { lua.to_byte_string(index).unwrap_unchecked() };
						for chunk in bytes.utf8_chunks() {
							chunk.valid().fmt(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)),
				}
			}
		}

		ElementDisplay { lua: self, index }
	}
}