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, ptr, slice, str};
use crate::{AmountReturned, LuaState, ffi, map_ptr};

/// String related functions.
impl LuaState {
	/// Returns `true` if the value at the given acceptable `index` is a string, `false` otherwise.
	#[inline]
	#[must_use]
	pub fn is_string(&self, index: i32) -> bool {
		// SAFETY: self.0 is always valid.
		unsafe { ffi::lua_isstring(self.0, index) != 0 }
	}

	/// Converts the value at the given acceptable `index` to a string and returns it as a string slice.
	///
	/// Only valid UTF-8 strings or numbers are accepted. Anything else will return `None`.
	///
	/// This function *does* affect the element in the stack itself:
	/// numbers will be replaced by the converted string of said number.
	#[inline]
	#[must_use]
	pub fn to_string(&self, index: i32) -> Option<&str> {
		str::from_utf8(self.to_byte_string(index)?).ok()
	}

	/// Converts the value at the given acceptable `index` to a string and returns it as a byte slice.
	///
	/// Only strings or numbers are accepted. Anything else will return `None`.
	///
	/// This function *does* affect the element in the stack itself:
	/// numbers will be replaced by the converted string of said number.
	#[inline]
	#[must_use]
	pub fn to_byte_string(&self, index: i32) -> Option<&[u8]> {
		// SAFETY: self.0 is always valid, len is initialized right before passing its pointer,
		// and lua_tolstring returns valid for len amount of values string pointers when not null.
		let mut len = 0;
		let string = unsafe { ffi::lua_tolstring(self.0, index, &raw mut len) };
		map_ptr!(string, unsafe { slice::from_raw_parts(string.cast(), len) })
	}

	/// Converts the value at the given acceptable `index` to a string and returns it as a `CStr`.
	///
	/// Only strings or numbers are accepted. Anything else will return `None`.
	///
	/// This function *does* affect the element in the stack itself:
	/// numbers will be replaced by the converted string of said number.
	#[inline]
	#[must_use]
	pub fn to_cstring(&self, index: i32) -> Option<&CStr> {
		// SAFETY: self.0 is always valid and lua_tostring returns valid nul-terminated string pointers when not null.
		let cstring = unsafe { ffi::lua_tostring(self.0, index) };
		map_ptr!(cstring, unsafe { CStr::from_ptr(cstring) })
	}

	/// Pushes a copy of the given `string` onto the stack.
	#[inline]
	pub fn push_string(&mut self, string: impl AsRef<[u8]>) {
		self.push_byte_string(string.as_ref());
	}

	/// Pushes a copy of the given `byte_string` onto the stack.
	#[inline]
	pub fn push_byte_string(&mut self, byte_string: &[u8]) {
		// SAFETY: self.0is always valid, the pointer remains valid for the duration of this function,
		// and the length is correct as it's returned from the array itself.
		unsafe { ffi::lua_pushlstring(self.0, byte_string.as_ptr().cast(), byte_string.len()); }
	}

	/// Pushes a copy of the given `cstring` onto the stack.
	///
	/// If `cstring` is `None`, `nil` will be pushed onto the stack instead.
	#[inline]
	pub fn push_cstring<'a>(&mut self, cstring: impl Into<Option<&'a CStr>>) {
		// SAFETY: self.0 is always valid and the pointer either remains valid for the duration of this function
		// or is null which lua_pushstring accepts.
		unsafe { ffi::lua_pushstring(self.0, cstring.into().map_or(core::ptr::null(), CStr::as_ptr)); }
	}

	/// Checks if there's a valid UTF-8 string (or number convertible to string) at the given index `arg_n` and returns it as a string slice.
	///
	/// If the check fails, `LuaState::type_error` is called using `arg_n`.
	///
	/// This function has the same caveats as [`LuaState::to_string`].
	///
	/// # Lua Errors
	///
	/// Intentionally errors if the check fails.
	#[inline]
	pub fn check_string(&self, arg_n: i32) -> &str {
		match str::from_utf8(self.check_byte_string(arg_n)) {
			Ok(string) => string,
			Err(_) => self.arg_error(arg_n, c"string contains invalid UTF-8"),
		}
	}

	/// Checks if there's a string (or number convertible to string) at the given index `arg_n` and returns it as a byte slice.
	///
	/// If the check fails, `LuaState::type_error` is called using `arg_n`.
	///
	/// This function has the same caveats as [`LuaState::to_cstring`].
	///
	/// # Lua Errors
	///
	/// Intentionally errors if the check fails.
	#[inline]
	pub fn check_byte_string(&self, arg_n: i32) -> &[u8] {
		// SAFETY: self.0 is always valid, len is initialized right before passing its pointer,
		// and string should always be non-null and valid for len amount of values.
		let mut len = 0;
		let string = unsafe { ffi::luaL_checklstring(self.0, arg_n, &raw mut len) };
		unsafe { slice::from_raw_parts(string.cast(), len) }
	}

	/// Checks if there's a string (or number convertible to string) at the given index `arg_n` and returns it as a `CStr`.
	///
	/// If the check fails, `LuaState::type_error` is called using `arg_n`.
	///
	/// This function has the same caveats as [`LuaState::to_byte_string`].
	///
	/// # Lua Errors
	///
	/// Intentionally errors if the check fails.
	#[inline]
	pub fn check_cstring(&self, arg_n: i32) -> &CStr {
		// SAFETY: self.0 is always valid and the returned pointer should be non-null, nul terminated, and valid for all values.
		unsafe { CStr::from_ptr(ffi::luaL_checkstring(self.0, arg_n)) }
	}

	/// Behaves exactly as [`LuaState::check_string`],
	/// but returns `default` instead of erroring when the element at the given index `arg_n` is `nil` (or non-existing).
	#[inline]
	pub fn opt_string<'a>(&'a self, arg_n: i32, default: &'a str) -> &'a str {
		// SAFETY: self.0 is always valid, luaL_optlstring accepts a null pointer for the default value,
		// and len is initialized right before passing its pointer.
		let mut len = 0;
		let string = unsafe { ffi::luaL_optlstring(self.0, arg_n, ptr::null(), &raw mut len) };

		if string.is_null() {
			return default;
		}

		// SAFETY: string should always be non-null and valid for len amount of values.
		match str::from_utf8(unsafe { slice::from_raw_parts(string.cast(), len) }) {
			Ok(string) => string,
			Err(_) => self.arg_error(arg_n, c"string contains invalid UTF-8"),
		}
	}

	/// Behaves exactly as [`LuaState::check_byte_string`],
	/// but returns `default` instead of erroring when the element at the given index `arg_n` is `nil` (or non-existing).
	#[inline]
	pub fn opt_byte_string<'a>(&'a self, arg_n: i32, default: &'a CStr) -> &'a [u8] {
		// SAFETY: self.0 and default are always valid, len is initialized right before passing its pointer,
		// and string should always be non-null and valid for len amount of values.
		let mut len = 0;
		let string = unsafe { ffi::luaL_optlstring(self.0, arg_n, default.as_ptr(), &raw mut len) };
		unsafe { slice::from_raw_parts(string.cast(), len) }
	}

	/// Behaves exactly as [`LuaState::check_cstring`],
	/// but returns `default` instead of erroring when the element at the given index `arg_n` is `nil` (or non-existing).
	#[inline]
	pub fn opt_cstring<'a>(&'a self, arg_n: i32, default: &'a CStr) -> &'a CStr {
		// SAFETY: self.0 and default are always valid, len is initialized right before passing its pointer,
		// and string should always be non-null and valid for len amount of values.
		unsafe { CStr::from_ptr(ffi::luaL_optstring(self.0, arg_n, default.as_ptr())) }
	}

	/// Finishes the currently called [`CFunction`], returning the given `string` to the caller.
	///
	/// The `LuaState` wrapper is consumed to prevent invalidating the `AmountReturned` (the Lua state itself is left intact).
	///
	/// [`CFunction`]: ./type.CFunction.html
	#[inline]
	pub fn return_string(self, string: impl AsRef<[u8]>) -> AmountReturned {
		self.return_byte_string(string.as_ref())
	}

	/// Finishes the currently called [`CFunction`], returning the given `byte_string` to the caller.
	///
	/// The `LuaState` wrapper is consumed to prevent invalidating the `AmountReturned` (the Lua state itself is left intact).
	///
	/// [`CFunction`]: ./type.CFunction.html
	#[inline]
	pub fn return_byte_string(mut self, byte_string: &[u8]) -> AmountReturned {
		self.push_byte_string(byte_string);

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

	/// Finishes the currently called [`CFunction`], returning the given `cstring` to the caller.
	///
	/// If `cstring` is `None`, `nil` will be returned to the caller instead.
	///
	/// The `LuaState` wrapper is consumed to prevent invalidating the `AmountReturned` (the Lua state itself is left intact).
	///
	/// [`CFunction`]: ./type.CFunction.html
	#[inline]
	pub fn return_cstring<'a>(mut self, cstring: impl Into<Option<&'a CStr>>) -> AmountReturned {
		self.push_cstring(cstring);

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

	/// Creates a copy of `string` where every occurrence of `pattern` is replaced by `replacement`.
	///
	/// The resulting string is both pushed onto the stack and returned.
	#[inline]
	pub fn gsub(&mut self, string: &CStr, pattern: &CStr, replacement: &CStr) -> &CStr {
		// SAFETY: self.0 and string, pattern, and replacement's pointers are always valid and luaL_gsub always returns a valid string pointer.
		unsafe { CStr::from_ptr(ffi::luaL_gsub(self.0, string.as_ptr(), pattern.as_ptr(), replacement.as_ptr())) }
	}
}