2025-06-30 17:22:12 +10:00

2490 lines
77 KiB
Rust

//! Safe LuaJIT bindings for Rust.
#![allow(non_snake_case)]
use bitflags::bitflags;
use bstr::{BStr, BString, ByteSlice};
use luaffi::future::lua_pollable;
use luajit_sys::*;
use std::{
alloc::{Layout, alloc, dealloc, realloc},
cell::UnsafeCell,
ffi::CString,
fmt,
marker::PhantomData,
mem::{self, ManuallyDrop},
num::NonZero,
ops::{self, Deref, DerefMut},
os::raw::{c_char, c_int, c_void},
pin::Pin,
process,
ptr::{self, NonNull},
rc::Rc,
slice,
};
/// LuaJIT version string.
pub fn version() -> &'static str {
LUAJIT_VERSION.to_str().unwrap()
}
/// LuaJIT copyright string.
pub fn copyright() -> &'static str {
LUAJIT_COPYRIGHT.to_str().unwrap()
}
/// LuaJIT URL string.
pub fn url() -> &'static str {
LUAJIT_URL.to_str().unwrap()
}
/// Lua result.
pub type Result<T> = std::result::Result<T, Error>;
/// Lua error.
#[derive(Debug)]
pub struct Error(ErrorInner);
#[derive(Debug)]
enum ErrorInner {
Memory,
Syntax {
msg: BString,
_chunk: Chunk,
},
Call {
msg: BString,
kind: ErrorKind,
trace: Option<BString>,
},
_Argument {
index: u32,
func: BString,
err: Box<Error>,
},
_Slot {
index: i32,
err: Box<Error>,
},
Type {
expected: &'static str,
got: &'static str,
},
Other(Box<dyn std::error::Error + Send + Sync>),
}
impl Error {
/// Creates a new error with an arbitrary error payload.
pub fn new(error: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> Self {
Self(ErrorInner::Other(error.into()))
}
/// Creates a new error for a type mismatch.
pub fn invalid_type(expected: &'static str, got: &'static str) -> Self {
Self(ErrorInner::Type { expected, got })
}
fn memory() -> Self {
Self(ErrorInner::Memory)
}
fn syntax(msg: impl AsRef<[u8]>, chunk: Chunk) -> Self {
Self(ErrorInner::Syntax {
msg: msg.as_ref().into(),
_chunk: chunk,
})
}
fn call(kind: ErrorKind, msg: impl AsRef<[u8]>, trace: Option<BString>) -> Self {
Self(ErrorInner::Call {
kind,
msg: msg.as_ref().into(),
trace,
})
}
/// Kind of this error.
pub fn kind(&self) -> ErrorKind {
match self.0 {
ErrorInner::Memory => ErrorKind::Memory,
ErrorInner::Syntax { .. } => ErrorKind::Syntax,
ErrorInner::Call { kind, .. } => kind,
_ => std::error::Error::source(self)
.and_then(|err| err.downcast_ref::<Error>())
.map(|err| err.kind())
.unwrap_or(ErrorKind::Runtime),
}
}
/// Stack trace, or [`None`] if it was not collected.
pub fn trace(&self) -> Option<&BStr> {
match self.0 {
ErrorInner::Call { ref trace, .. } => trace.as_ref().map(|s| s.as_ref()),
_ => None,
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self.0 {
ErrorInner::Syntax { msg, .. } | ErrorInner::Call { msg, .. } => write!(f, "{msg}"),
ErrorInner::_Argument { index, func, err } => {
write!(f, "bad argument #{index} to '{func}': {err}")
}
ErrorInner::_Slot { index, err } => write!(f, "bad stack slot #{index}: {err}"),
ErrorInner::Type { expected, got } => {
write!(f, "{expected} expected, got {got}")
}
_ => match std::error::Error::source(self) {
Some(err) => write!(f, "{err}"),
None => write!(f, "{}", self.kind()),
},
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match &self.0 {
ErrorInner::_Argument { err, .. } | ErrorInner::_Slot { err, .. } => Some(err),
ErrorInner::Other(err) => Some(err.as_ref()),
_ => None,
}
}
}
/// Lua error kind.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ErrorKind {
/// Runtime error.
Runtime,
/// Syntax error.
Syntax,
/// Memory allocation error.
Memory,
/// Error while running the error handler function.
Error,
}
impl ErrorKind {
fn from_raw(code: c_int) -> Option<Self> {
Some(match code {
LUA_ERRRUN => Self::Runtime,
LUA_ERRSYNTAX => Self::Syntax,
LUA_ERRMEM => Self::Memory,
LUA_ERRERR => Self::Error,
_ => return None,
})
}
/// Name of this error kind, like `"runtime"` or `"syntax"`.
pub fn name(self) -> &'static str {
match self {
Self::Runtime => "runtime",
Self::Syntax => "syntax",
Self::Memory => "memory",
Self::Error => "error",
}
}
}
impl fmt::Display for ErrorKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ErrorKind::Runtime => write!(f, "runtime error"),
ErrorKind::Syntax => write!(f, "syntax error"),
ErrorKind::Memory => write!(f, "memory allocation error"),
ErrorKind::Error => write!(f, "error while running the error handler function"),
}
}
}
/// Lua type.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Type {
/// `nil` type.
#[default]
Nil,
/// `boolean` type.
Boolean,
/// `lightuserdata` type.
Lightuserdata,
/// `number` type.
Number,
/// `string` type.
String,
/// `table` type.
Table,
/// `function` type.
Function,
/// `userdata` type.
Userdata,
/// `thread` type.
Thread,
/// `cdata` type.
Cdata,
}
impl Type {
fn from_raw(code: c_int) -> Option<Self> {
Some(match code {
LUA_TNIL => Self::Nil,
LUA_TBOOLEAN => Self::Boolean,
LUA_TLIGHTUSERDATA => Self::Lightuserdata,
LUA_TNUMBER => Self::Number,
LUA_TSTRING => Self::String,
LUA_TTABLE => Self::Table,
LUA_TFUNCTION => Self::Function,
LUA_TUSERDATA => Self::Userdata,
LUA_TTHREAD => Self::Thread,
LUA_TCDATA => Self::Cdata,
_ => return None,
})
}
/// Name of this type, like `"nil"` or `"string"`.
pub fn name(&self) -> &'static str {
match self {
Self::Nil => "nil",
Self::Boolean => "boolean",
Self::Lightuserdata => "lightuserdata",
Self::Number => "number",
Self::String => "string",
Self::Table => "table",
Self::Function => "function",
Self::Userdata => "userdata",
Self::Thread => "thread",
Self::Cdata => "cdata",
}
}
}
impl fmt::Display for Type {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.name())
}
}
/// Lua thread status.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Status {
/// Thread is alive (currently running or can run a function).
#[default]
Normal,
/// Thread terminated with an error and can no longer be used.
Dead(
/// Why this thread is dead.
ErrorKind,
),
/// Thread suspended with `coroutine.yield(...)` and is awaiting resume.
Suspended,
}
impl Status {
fn from_raw(code: c_int) -> Option<Self> {
Some(match code {
LUA_OK => Self::Normal,
LUA_YIELD => Self::Suspended,
_ => Self::Dead(ErrorKind::from_raw(code)?),
})
}
/// Name of this status, like `"normal"` or `"suspended"`.
pub fn name(&self) -> &'static str {
match self {
Status::Normal => "normal",
Status::Dead(_) => "dead",
Status::Suspended => "suspended",
}
}
}
impl fmt::Display for Status {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Status::Normal => write!(f, "normal"),
Status::Dead(why) => write!(f, "dead ({why})"),
Status::Suspended => write!(f, "suspended"),
}
}
}
/// Result of [`resume`](Stack::resume).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ResumeStatus {
/// Thread completed successfully and can run another function.
#[default]
Ok,
/// Thread suspended with `coroutine.yield(...)` and is awaiting resume.
Suspended,
}
impl ResumeStatus {
/// Name of this status, like `"ok"` or `"suspended"`.
pub fn name(&self) -> &'static str {
match self {
ResumeStatus::Ok => "ok",
ResumeStatus::Suspended => "suspended",
}
}
}
impl fmt::Display for ResumeStatus {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.name())
}
}
bitflags! {
/// Flags for [`Stack::load`].
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct LoadMode: usize {
/// No flags.
const NONE = 0b_00_00;
/// Auto-detect chunk type (flag `bt`).
const AUTO = 0b_00_11;
/// Text chunk only (flag `t`).
const TEXT = 0b_00_01;
/// Binary chunk only (flag `b`).
const BINARY = 0b_00_10;
/// Force 32-bit binary format (flag `W`, implies `b`).
const GC32 = 0b_10_10;
/// Force 64-bit binary format (flag `X`, implies `b`).
const GC64 = 0b_01_10;
}
}
impl Default for LoadMode {
fn default() -> Self {
Self::AUTO
}
}
impl LoadMode {
fn mode_str(&self) -> CString {
let mut s = String::new();
self.contains(Self::TEXT).then(|| s.push_str("t"));
self.contains(Self::BINARY).then(|| s.push_str("b"));
self.contains(Self::GC32).then(|| s.push_str("W"));
self.contains(Self::GC64).then(|| s.push_str("X"));
CString::new(s).unwrap()
}
}
bitflags! {
/// Flags for [`Stack::dump`].
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct DumpMode: usize {
/// No flags.
const NONE = 0b_00_0_0;
/// Strip debug information (flag `s`).
const STRIP = 0b_00_0_1;
/// Deterministic mode (flag `d`).
const DETERMINISTIC = 0b_00_1_0;
/// Force 32-bit binary format (flag `W`).
const GC32 = 0b_10_0_0;
/// Force 64-bit binary format (flag `X`).
const GC64 = 0b_01_0_0;
}
}
impl Default for DumpMode {
fn default() -> Self {
Self::DETERMINISTIC
}
}
impl DumpMode {
fn mode_str(&self) -> CString {
let mut s = String::new();
self.contains(Self::STRIP).then(|| s.push_str("s"));
self.contains(Self::DETERMINISTIC).then(|| s.push_str("d"));
self.contains(Self::GC32).then(|| s.push_str("W"));
self.contains(Self::GC64).then(|| s.push_str("X"));
CString::new(s).unwrap()
}
}
/// Lua chunk data.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Chunk {
name: BString,
content: BString,
mode: LoadMode,
}
impl Chunk {
/// Creates a new chunk with the given content.
pub fn new(content: impl Into<BString>) -> Self {
Self {
name: "?".into(),
content: content.into(),
mode: LoadMode::AUTO,
}
}
/// Name of this chunk, like `"?"` or `"@path/to/file.lua"`.
pub fn name(&self) -> &BStr {
self.name.as_ref()
}
/// Path of this chunk, if the name of this chunk starts with `@`.
pub fn path(&self) -> Option<&BStr> {
self.name.strip_prefix(b"@").map(|s| s.as_ref())
}
/// Mode flag for loading this chunk.
pub fn mode(&self) -> LoadMode {
self.mode
}
/// Assigns a name to this chunk.
pub fn with_name(self, name: impl AsRef<[u8]>) -> Self {
Self {
name: name.as_ref().into(),
..self
}
}
/// Assigns a name to this chunk as a path.
///
/// This sets the name of this chunk to `"@path"`, where `path` is the given path.
pub fn with_path(self, path: impl AsRef<[u8]>) -> Self {
let mut name = BString::from(b"@");
name.extend_from_slice(path.as_ref());
Self { name, ..self }
}
/// Assigns a mode flag for loading this chunk.
pub fn with_mode(self, mode: LoadMode) -> Self {
Self { mode, ..self }
}
}
impl Deref for Chunk {
type Target = BString;
fn deref(&self) -> &Self::Target {
&self.content
}
}
impl DerefMut for Chunk {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.content
}
}
impl fmt::Display for Chunk {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.content)
}
}
impl<T: Into<BString>> From<T> for Chunk {
fn from(value: T) -> Self {
Self::new(value)
}
}
macro_rules! assert_slots {
($stack:expr, $n:expr) => {{
let size = $stack.size();
let n = $n;
assert!(
n <= size,
"stack underflow: expected at least {n} values, got {size}: {stack:?}",
stack = $stack,
);
let _base = size - n;
_base
}};
}
macro_rules! assert_type {
($stack:expr, $slot:expr, $expected:expr, $pat:pat) => {{
let slot = $stack.slot($slot);
let expected = $expected;
let idx = slot.index();
let got = slot.ty();
assert!(
matches!(got, $pat),
"expected {expected} at index {idx}, got {got}: {stack:?}",
stack = $stack,
);
slot
}};
}
#[derive(Debug)]
struct GlobalState {
ptr: NonNull<lua_State>,
_alloc: Box<UnsafeCell<AllocatorState>>,
}
#[derive(Debug, Default)]
struct AllocatorState {
alloc: usize,
dealloc: usize,
}
impl GlobalState {
pub fn new() -> Result<Self> {
unsafe {
// SAFETY: lua_newstate may return a null pointer if allocation fails
let mut alloc = Box::new(UnsafeCell::new(AllocatorState::default()));
let ud = alloc.get_mut() as *mut _ as *mut c_void;
let ptr = NonNull::new(lua_newstate(Some(Self::alloc_cb), ud)).ok_or(Error::memory())?;
lua_atpanic(ptr.as_ptr(), Some(Self::panic_cb));
Ok(Self { ptr, _alloc: alloc })
}
}
fn as_ptr(&self) -> *mut lua_State {
self.ptr.as_ptr()
}
unsafe extern "C" fn alloc_cb(
ud: *mut c_void,
ptr: *mut c_void,
osize: usize,
nsize: usize,
) -> *mut c_void {
unsafe {
// https://github.com/tikv/jemallocator/blob/main/jemallocator/src/lib.rs
#[cfg(any(target_arch = "arm", target_arch = "mips", target_arch = "powerpc"))]
const ALIGNOF_MAX_ALIGN_T: usize = 8;
#[cfg(any(
target_arch = "x86",
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "powerpc64",
target_arch = "loongarch64",
target_arch = "mips64",
target_arch = "riscv64",
target_arch = "s390x",
target_arch = "sparc64"
))]
const ALIGNOF_MAX_ALIGN_T: usize = 16;
let old = Layout::from_size_align(osize, ALIGNOF_MAX_ALIGN_T)
.expect("lua alloc error: requested osize is too large");
let new = Layout::from_size_align(nsize, ALIGNOF_MAX_ALIGN_T)
.expect("lua alloc error: requested nsize is too large");
let state = &mut *(ud as *mut AllocatorState);
let ptr = ptr as *mut u8;
// SAFETY: from lua documentation:
//
// When nsize is zero, the allocator must return NULL; if osize is not zero, it should
// free the block pointed to by ptr. When nsize is not zero, the allocator returns
// NULL if and only if it cannot fill the request. When nsize is not zero and osize is
// zero, the allocator should behave like malloc. When nsize and osize are not zero,
// the allocator behaves like realloc. Lua assumes that the allocator never fails when
// osize >= nsize.
(if new.size() == 0 {
if old.size() != 0 {
dealloc(ptr, old);
state.dealloc += old.size();
}
ptr::null_mut()
} else {
let ptr = if old.size() == 0 {
alloc(new)
} else {
realloc(ptr, old, new.size())
};
if !ptr.is_null() {
state.alloc += new.size();
state.dealloc += old.size();
}
ptr
}) as *mut c_void
}
}
unsafe extern "C" fn panic_cb(L: *mut lua_State) -> c_int {
// SAFETY: we cannot recover from uncaught lua exceptions; abort instead
assert!(!L.is_null());
let stack = unsafe { Stack::new_unchecked(L) };
let msg = stack
.top()
.and_then(|s| s.string())
.unwrap_or(b"unknown error".into());
eprintln!("lua panicked: {msg}");
process::abort()
}
}
impl Drop for GlobalState {
fn drop(&mut self) {
unsafe { lua_close(self.as_ptr()) }
}
}
/// Key for a [`Ref`] that represents no-reference.
///
/// This key can always be used safely in [`from_raw`](Ref::from_raw) to create a [`Ref`] that does
/// not reference any value in the registry. If dereferenced, it will always evaluate to `nil`.
pub use luajit_sys::LUA_NOREF;
/// Lua value handle into the registry.
///
/// Equivalent to [`luaL_ref`] and [`luaL_unref`] with [`LUA_REGISTRYINDEX`] as the target table.
#[derive(Debug)]
pub struct Ref {
state: Rc<GlobalState>,
key: c_int,
}
impl Ref {
/// Creates a new ref by popping the value at the top of the given stack.
pub fn new(stack: &mut State) -> Self {
// SAFETY: luaL_ref always returns a unique key
assert_slots!(stack, 1);
unsafe { Self::from_raw(stack, luaL_ref(stack.as_ptr(), LUA_REGISTRYINDEX)) }
}
/// Creates a new ref from the given key.
///
/// # Safety
///
/// `key` must have been created by [`into_raw`](Self::into_raw) or a previous call to
/// [`luaL_ref`] with [`LUA_REGISTRYINDEX`] as the target table. It must also be unique; no two
/// [`Ref`] instances with the same key should exist at the same time.
pub unsafe fn from_raw(stack: &State, key: c_int) -> Self {
let state = Rc::clone(&stack.thread_ref.state);
Self { state, key }
}
/// Consumes this ref and returns the original key used to create the ref.
///
/// This key can be used to index into the registry table ([`LUA_REGISTRYINDEX`]) to retrieve
/// the referenced value. The key can be converted back into a ref using
/// [`from_raw`](Ref::from_raw).
pub fn into_raw(self) -> c_int {
let Self { ref mut state, key } = *ManuallyDrop::new(self);
unsafe { ptr::drop_in_place(state) }
key
}
}
impl Clone for Ref {
fn clone(&self) -> Self {
let state = Rc::clone(&self.state);
let key = unsafe {
// SAFETY: luaL_ref always returns a unique key
let mut stack = Stack::new_unchecked(state.as_ptr());
stack.geti(PseudoIndex::Registry, self.key as isize);
luaL_ref(stack.as_ptr(), LUA_REGISTRYINDEX)
};
Self { state, key }
}
}
impl Drop for Ref {
fn drop(&mut self) {
unsafe { luaL_unref(self.state.as_ptr(), LUA_REGISTRYINDEX, self.key) }
}
}
/// Lua state handle.
///
/// A state instance can be manipulated using the [`Stack`] object that it mutably dereferences to.
#[derive(Debug)]
pub struct State {
thread_ref: Ref,
stack: Stack,
}
impl State {
/// Creates a new [`State`].
///
/// All built-in libraries are opened by default.
///
/// This may return an error if allocation or library initialisation fails.
pub fn new() -> Result<Self> {
let state = Rc::new(GlobalState::new()?);
let mut state = Self {
stack: unsafe { Stack::new_unchecked(state.as_ptr()) },
thread_ref: Ref {
// SAFETY: the main thread doesn't need a ref, it doesn't get garbage collected
state,
key: LUA_NOREF,
},
};
state.push(Function::Bare(Self::open_cb));
state.call(0, Some(0))?;
Ok(state)
}
/// Creates a new empty thread (coroutine) associated with the given state.
pub fn new_thread(state: &State) -> Self {
Self {
// SAFETY: must call lua_newthread first before ref'ing it
stack: unsafe {
state.ensure(1);
Stack::new_unchecked(lua_newthread(state.as_ptr()))
},
thread_ref: Ref {
state: Rc::clone(&state.thread_ref.state),
key: unsafe { luaL_ref(state.as_ptr(), LUA_REGISTRYINDEX) },
},
}
}
fn open_cb(stack: &mut Stack) -> c_int {
unsafe {
// SAFETY: this is only ever called once on state initialisation
luaL_openlibs(stack.as_ptr()); // lua base libraries
luaJIT_openlibs(stack.as_ptr()); // luajit-sys extension to open jitlibs
0
}
}
}
impl Deref for State {
type Target = Stack;
fn deref(&self) -> &Self::Target {
&self.stack
}
}
impl DerefMut for State {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.stack
}
}
/// Lua stack handle.
///
/// This simply wraps a pointer to the actual [`lua_State`] object and provides safe methods to
/// manipulate the state. Lua states can only be manipulated if the Rust caller holds a mutable
/// reference to its [`Stack`].
///
/// A mutable [`State`] can be dereferenced to obtain a mutable reference to its inner [`Stack`]
/// safely.
///
/// # Indexing
///
/// All indices are 1-based as in Lua convention. Positive values are absolute indices from the
/// bottom of the stack, where index `1` is the first value, index `2` is the second value, and so
/// on. Negative values are relative indices from the top of the stack, where index `-1` is the last
/// value, index `-2` is the second last value, and so on. Index `0` is either invalid or indicates
/// an empty stack.
///
/// # Mutability
///
/// All methods which mutate the *visible contents* of the stack require a mutable reference. For
/// example, pushing values to or popping values from the stack require `&mut Stack`, while parsing
/// a value on the stack into a Rust value requires `&Stack`.
///
/// Although a [`lua_State`] as a C object is internally mutable, this limitation exists to allow
/// for zero-copy marshalling of values like Lua `string`s into Rust `&str` without any allocations.
/// It is guaranteed that the lifetime of an `&str` will not outlive the lifetime of the original
/// `string` while the stack is immutable and the original string cannot be removed from the stack,
/// preventing it from being garbage-collected by Lua.
#[derive(PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct Stack(NonNull<lua_State>);
impl Stack {
unsafe fn new_unchecked(ptr: *mut lua_State) -> Self {
debug_assert!(!ptr.is_null(), "attempt to create stack with null pointer");
Self(unsafe { NonNull::new_unchecked(ptr) })
}
/// Pointer to the underlying [`lua_State`].
pub fn as_ptr(&self) -> *mut lua_State {
self.0.as_ptr()
}
/// Size of the stack.
///
/// Equivalent to [`lua_gettop`].
pub fn size(&self) -> u32 {
unsafe { lua_gettop(self.as_ptr()) as u32 }
}
/// Resizes the stack to fit exactly `n` values, reallocating the stack and popping any
/// extraneous values or pushing `nil`s to fill the space as necessary.
///
/// Equivalent to [`lua_settop`].
///
/// # Panics
///
/// Panics if the stack could not be resized.
pub fn resize(&mut self, n: u32) {
assert!(
n <= (LUAI_MAXCSTACK as u32),
"stack overflow: cannot resize stack to size > {LUAI_MAXCSTACK}"
);
// SAFETY: lua_settop can throw on oom (calls growstack not cpgrowstack) when growing, so we
// need to call ensure first if we might reallocate
let size = self.size();
n.checked_sub(size).map(|n| self.ensure(n));
unsafe { lua_settop(self.as_ptr(), n as c_int) }
}
/// Reallocates the stack to fit `n` more values, if necessary.
///
/// This does not resize the stack and thus does not require a mutable reference as it does not
/// mutate the *visible contents* of the stack.
///
/// Equivalent to [`lua_checkstack`].
///
/// # Panics
///
/// Panics if reallocation fails.
pub fn ensure(&self, n: u32) {
if n != 0 {
// lua_checkstack throws on oom in PUC lua 5.1, but it is fine in luajit
assert!(
n <= (LUAI_MAXCSTACK as u32),
"stack overflow: cannot reallocate stack to size > {LUAI_MAXCSTACK}"
);
assert!(
unsafe { lua_checkstack(self.as_ptr(), n as c_int) != 0 },
"stack overflow: failed to reallocate stack to size {len}",
len = self.size() + n,
)
}
}
/// Pops `n` values at the top of the stack.
///
/// Equivalent to [`lua_pop`].
///
/// # Panics
///
/// Panics if there are less than `n` values on the stack.
pub fn pop(&mut self, n: u32) {
if n != 0 {
assert_slots!(self, n);
unsafe { lua_pop(self.as_ptr(), n as c_int) }
}
}
/// Pops the value at the top of the stack and inserts it at index `idx` by shifting up existing
/// values, and returns the slot for that index.
///
/// If the index `idx` points to the top of the stack, this does not pop anything and keeps the
/// stack untouched.
///
/// Index `idx` cannot be a pseudo-index.
///
/// Equivalent to [`lua_insert`].
///
/// # Panics
///
/// Panics if the stack is empty or the index `idx` is invalid.
pub fn pop_insert<'s>(&'s mut self, idx: impl ToSlot) -> Slot<'s> {
let top = self.slot(-1).index();
let slot = self.slot(idx);
let idx = slot.index();
if let Index::Stack(_) = idx {
unsafe { (idx != top).then(|| lua_insert(self.as_ptr(), idx.into_raw())) };
} else {
panic!("cannot insert into pseudo-index {idx}");
}
slot
}
/// Pops the value at the top of the stack, replaces the value at index `idx` with it, and
/// returns the slot for that index.
///
/// If the index `idx` points to the top of the stack, this does not pop anything and keeps the
/// stack untouched.
///
/// Equivalent to [`lua_replace`].
///
/// # Panics
///
/// Panics if the stack is empty or the index `idx` is invalid.
pub fn pop_replace<'s>(&'s mut self, idx: impl ToSlot) -> Slot<'s> {
let top = self.slot(-1).index();
let slot = self.slot(idx);
let idx = slot.index();
unsafe { (idx != top).then(|| lua_replace(self.as_ptr(), idx.into_raw())) };
slot
}
/// Status of the current thread.
///
/// Equivalent to [`lua_status`].
pub fn status(&self) -> Status {
Status::from_raw(unsafe { lua_status(self.as_ptr()) }).unwrap()
}
/// Iterator over all values on the stack.
pub fn iter<'s>(&'s self) -> StackIter<'s> {
StackIter::new(self)
}
/// Returns a guard that resets the stack to the current size when dropped.
///
/// This is useful for implementing idempotent stack operations which must keep the stack size
/// consistent.
pub fn guard<'s>(&'s mut self) -> StackGuard<'s> {
StackGuard::new(self, false)
}
/// Similar to [`guard`](Self::guard) but takes an immutable reference to [`Stack`].
///
/// # Safety
///
/// The caller must ensure that the stack from this point onwards until the returned
/// [`StackGuard`] is dropped never mutates any values already on the stack *before* the guard
/// was created. The caller may push new values onto the guarded stack and mutate or pop values
/// that they themselves have pushed, but mutating or popping any values that were already on
/// the stack before the guard was created is **undefined behaviour**.
pub unsafe fn guard_unchecked<'s>(&'s self) -> StackGuard<'s> {
StackGuard::new(self, true)
}
/// Slot for the value at the top of the stack, or [`None`] if the stack is empty.
pub fn top<'s>(&'s self) -> Option<Slot<'s>> {
let size = self.size();
(size != 0).then(|| unsafe { self.slot_unchecked(Index::stack(size)) })
}
/// Slot for the value at index `idx`.
///
/// # Panics
///
/// Panics if the index `idx` is invalid.
pub fn slot<'s>(&'s self, idx: impl ToSlot) -> Slot<'s> {
idx.to_slot(self)
}
/// Slot for the value at index `idx`, without checking if the index is valid.
///
/// # Safety
///
/// The caller must ensure that the index is valid, i.e. within the bounds of the stack.
pub unsafe fn slot_unchecked<'s>(&'s self, idx: Index) -> Slot<'s> {
// SAFETY: the caller must ensure that the index is valid
unsafe { Slot::new_unchecked(self, idx) }
}
/// Slot for the upvalue of the current function at index `idx`.
pub fn upvalue<'s>(&'s self, idx: u32) -> Slot<'s> {
self.slot(Index::upvalue(idx))
}
/// Slot for the registry table.
pub fn registry<'s>(&'s self) -> Slot<'s> {
self.slot(PseudoIndex::Registry)
}
/// Slot for the environment table of the current function.
pub fn environment<'s>(&'s self) -> Slot<'s> {
self.slot(PseudoIndex::Environment)
}
/// Slot for the global environment table of the current thread.
pub fn globals<'s>(&'s self) -> Slot<'s> {
self.slot(PseudoIndex::Globals)
}
/// Pushes the given value to the top of the stack.
///
/// Equivalent to the `lua_push*` family of functions depending on the type of `T`.
pub fn push<'s, T: Push>(&'s mut self, value: T) {
value.push(self);
}
/// Pushes the value at index `idx` to the top of the stack.
///
/// Equivalent to [`lua_pushvalue`].
pub fn push_idx(&mut self, idx: impl ToSlot) {
self.push(self.slot(idx).index());
}
/// Gets a field of the table at index `idx` using the value at the top of the stack as the key,
/// replaces the key with the field value, and returns the slot for it.
///
/// This function does not invoke the `__index` metamethod.
///
/// Equivalent to [`lua_rawget`].
///
/// # Panics
///
/// Panics if the value at index `idx` is not a table.
pub fn get<'s>(&'s mut self, idx: impl ToSlot) -> Slot<'s> {
let slot = assert_type!(self, idx, "table", Type::Table);
assert_slots!(self, 1);
unsafe { lua_rawget(self.as_ptr(), slot.index().into_raw()) }
slot
}
/// Gets a field of the table at index `idx` using `n` as the key, pushes the field value at the
/// top of the stack, and returns the slot for it.
///
/// This function does not invoke the `__index` metamethod.
///
/// Equivalent to [`lua_rawgeti`].
///
/// # Panics
///
/// Panics if the value at index `idx` is not a table.
pub fn geti<'s>(&'s mut self, idx: impl ToSlot, n: isize) -> Slot<'s> {
let slot = assert_type!(self, idx, "table", Type::Table);
self.ensure(1);
match n.try_into() {
Ok(n) => unsafe { lua_rawgeti(self.as_ptr(), slot.index().into_raw(), n) },
Err(_) => unsafe {
lua_pushinteger(self.as_ptr(), n as lua_Integer);
lua_rawget(self.as_ptr(), slot.index().into_raw());
},
}
slot
}
/// Sets a field of the table at index `idx` using two values at the top of the stack as the key
/// and value.
///
/// Both the key and value are popped from the stack.
///
/// This function does not invoke the `__newindex` metamethod.
///
/// Equivalent to [`lua_rawset`].
///
/// # Panics
///
/// Panics if the value at index `idx` is not a table.
pub fn set(&mut self, idx: impl ToSlot) {
let slot = assert_type!(self, idx, "table", Type::Table);
assert_slots!(self, 2);
unsafe { lua_rawset(self.as_ptr(), slot.index().into_raw()) }
}
/// Sets a field of the table at index `idx` using `n` is the key and the value at the top of
/// the stack as the value.
///
/// The value is popped from the stack.
///
/// This function does not invoke the `__newindex` metamethod.
///
/// Equivalent to [`lua_rawseti`].
///
/// # Panics
///
/// Panics if the value at index `idx` is not a table.
pub fn seti(&mut self, idx: impl ToSlot, n: isize) {
let slot = assert_type!(self, idx, "table", Type::Table);
assert_slots!(self, 1);
match n.try_into() {
Ok(n) => unsafe { lua_rawseti(self.as_ptr(), slot.index().into_raw(), n) },
Err(_) => unsafe {
self.ensure(1);
lua_pushinteger(self.as_ptr(), n as lua_Integer);
lua_insert(self.as_ptr(), -2);
lua_rawset(self.as_ptr(), slot.index().into_raw());
},
}
}
/// Gets the metatable of the value at index `idx`, pushes it onto the stack, and returns the
/// slot for that metatable.
///
/// If the value at index `idx` does not have a metatable, then nothing is pushed onto the stack
/// and [`None`] is returned.
///
/// Metatables can be attached to non-table values including primitives like numbers, strings,
/// and booleans.
pub fn get_metatable<'s>(&'s mut self, idx: impl ToSlot) -> Option<Slot<'s>> {
let slot = self.slot(idx);
self.ensure(1);
unsafe { lua_getmetatable(self.as_ptr(), slot.index().into_raw()) != 0 }
.then(|| self.slot(-1))
}
/// Sets the metatable of the value at index `idx` to the value at the top of the stack, and
/// returns the slot for that value.
///
/// The value at the top of the stack can be `nil` to remove the metatable, or a table to set it
/// as the new metatable. This value is popped from the stack and set as the metatable of the
/// value at index `idx`.
///
/// Metatables can be attached to non-table values including primitives like numbers, strings,
/// and booleans.
pub fn set_metatable<'s>(&'s mut self, idx: impl ToSlot) -> Slot<'s> {
let slot = self.slot(idx);
assert_type!(self, -1, "table or nil", Type::Table | Type::Nil);
unsafe { lua_setmetatable(self.as_ptr(), slot.index().into_raw()) }; // always returns 1
slot
}
/// Packs the array-part of the table at index `idx` from the stack.
///
/// This pops `n` values at the top of the stack, sets them as the fields of the table at index
/// `idx` at indices `1` to `n` inclusive, then sets the field `"n"` to `n`. Any existing values
/// in the table are overwritten. If the table already has more than `n` values in its
/// array-part, these values are **not** cleared. The number of values popped is returned, which
/// is always equal to `n`.
///
/// This method does not invoke any metamethods. The table is not popped from the stack.
///
/// Equivalent to `table.pack(...)`.
///
/// # Panics
///
/// Panics if `n` is negative, there are not enough values on the stack, or the value at index
/// `idx` is not a table.
pub fn pack(&mut self, idx: impl ToSlot, n: u32) -> u32 {
let slot = assert_type!(self, idx, "table", Type::Table);
let base = assert_slots!(self, n);
if let Index::Stack(idx) = slot.index() {
assert!(
idx.get() <= base,
"cannot pack a table into itself: {self:?}"
);
}
unsafe {
for i in 0..n {
lua_rawseti(self.as_ptr(), slot.index().into_raw(), (n - i) as c_int);
}
self.ensure(2);
lua_pushliteral(self.as_ptr(), "n");
lua_pushinteger(self.as_ptr(), n as lua_Integer);
lua_rawset(self.as_ptr(), slot.index().into_raw());
n
}
}
/// Unpacks the array-part of the table at index `idx` onto the stack, and returns the number of
/// values pushed.
///
/// If `j` is [`None`], then it is set to be the value of the field `"n"` interpreted as an
/// integer. If this field does not exist, then it is set to the be length of the table as
/// defined by the Lua `#` length operator. All values in indices `i` to `j` inclusive are
/// pushed to the top of the stack in ascending order. If `i > j`, then nothing is pushed.
/// Otherwise, `j - i + 1` values are pushed, and the number of values pushed is returned.
///
/// This method does not invoke any metamethods. The table is not popped from the stack or
/// altered in any way.
///
/// Equivalent to `table.unpack(list, i, j)`.
///
/// # Panics
///
/// Panics if the value at index `idx` is not a table.
pub fn unpack(&mut self, idx: impl ToSlot, i: isize, j: Option<isize>) -> u32 {
let slot = assert_type!(self, idx, "table", Type::Table);
let j = match j {
Some(j) => j,
None => unsafe {
self.ensure(1);
lua_pushliteral(self.as_ptr(), "n");
lua_rawget(self.as_ptr(), slot.index().into_raw());
let mut isnum = 0;
let n = lua_tointegerx(self.as_ptr(), -1, &raw mut isnum);
lua_pop(self.as_ptr(), 1);
(isnum != 0)
.then_some(n as isize)
.unwrap_or_else(|| slot.length() as isize)
},
};
let n = (j - i + 1)
.max(0)
.try_into()
.expect("too many values to unpack");
if n > 0 {
self.ensure(n);
for k in i..=j {
match k.try_into() {
Ok(n) => unsafe { lua_rawgeti(self.as_ptr(), slot.index().into_raw(), n) },
Err(_) => unsafe {
lua_pushinteger(self.as_ptr(), n as lua_Integer);
lua_rawget(self.as_ptr(), slot.index().into_raw());
},
}
}
}
n
}
/// Pushes the result of a Lua `require(...)` call onto the stack.
///
/// Any return values from the library `name` are pushed. Lua libraries are allowed to return
/// multiple values. If `nret` is not [`None`], then the number of return values pushed will be
/// exactly `nret`, filling with nils if necessary. The number values pushed to the stack is
/// returned.
///
/// Equivalent to `require(name)`.
pub fn require(&mut self, name: impl AsRef<[u8]>, nret: Option<u32>) -> Result<u32> {
self.push("require");
self.get(PseudoIndex::Globals);
self.push(name.as_ref());
self.call(1, nret)
}
/// Pushes the given chunk as a function to the top of the stack and returns the slot for the
/// new function.
///
/// Equivalent to [`lua_loadx`].
pub fn load(&mut self, chunk: &Chunk) -> Result<()> {
type State<'s> = Option<&'s [u8]>;
let mut state: State = Some(chunk.content.as_ref());
unsafe extern "C" fn read_cb(
_L: *mut lua_State,
state: *mut c_void,
size: *mut usize,
) -> *const c_char {
unsafe {
if let Some(chunk) = (*(state as *mut State)).take() {
*size = chunk.len();
chunk.as_ptr().cast()
} else {
*size = 0;
ptr::null()
}
}
}
let name = CString::new(chunk.name.to_vec())
.map_err(|err| Error::syntax(format!("invalid chunk name: {err}"), chunk.clone()))?;
let mode = chunk.mode.mode_str();
self.ensure(1);
match unsafe {
lua_loadx(
self.as_ptr(),
Some(read_cb),
&raw mut state as *mut c_void,
name.as_ptr(),
mode.as_ptr(),
)
} {
LUA_OK => Ok(()),
LUA_ERRMEM => {
self.pop(1);
Err(Error::memory())
}
LUA_ERRSYNTAX => {
let msg = self
.slot(-1)
.string()
.unwrap_or(b"unknown error".into())
.to_owned();
self.pop(1);
Err(Error::syntax(msg, chunk.clone()))
}
_ => unreachable!(),
}
}
/// Dumps the function at index `idx` into bytecode.
///
/// Equivalent to `string.dump(f, mode)`.
///
/// # Panics
///
/// Panics if the value at index `idx` is not a function.
pub fn dump(&self, idx: impl ToSlot, mode: DumpMode) -> Result<BString> {
let idx = assert_type!(self, idx, "function", Type::Function).index();
unsafe {
let mut s = self.guard_unchecked();
s.push("string");
s.get(PseudoIndex::Globals);
s.push("dump");
s.get(-2); // local dump = string.dump
s.push(idx);
s.push(mode.mode_str().to_bytes());
s.call(2, Some(1))?;
s.slot(-1).parse() // return dump(idx, mode)
}
}
/// Evaluates the given chunk on the stack synchronously with `narg` values at the top of the
/// stack as arguments.
///
/// Equivalent to calling [`load`](Self::load) on the chunk and then [`call`](Self::call) on the
/// loaded function.
///
/// # Panics
///
/// Panics if there are not enough values on the stack or thread status is invalid.
pub fn eval(&mut self, chunk: &Chunk, narg: u32, nret: Option<u32>) -> Result<u32> {
let base = assert_slots!(self, narg);
self.load(chunk)?;
self.pop_insert(base + 1);
self.call(narg, nret)
}
/// Calls a function on the stack synchronously with `narg` values at the top of the stack as
/// arguments.
///
/// There must be `narg + 1` values at the top of the stack, including the function to call at
/// the index `top - narg` (i.e. the function is pushed first and then `narg` values as
/// arguments). All arguments and the function are popped from the stack and then any return
/// values are pushed. If `nret` is not [`None`], then the number of return values pushed will
/// be exactly `nret`, filling with nils if necessary. Finally, the number of values pushed to
/// the stack is returned.
///
/// The current thread status must not be suspended or dead.
///
/// Equivalent to [`lua_pcall`].
///
/// # Panics
///
/// Panics if there are not enough values on the stack, the function to call is not on the
/// stack, or thread status is invalid.
pub fn call(&mut self, narg: u32, nret: Option<u32>) -> Result<u32> {
let base = match self.status() {
Status::Normal => {
// need the function on the stack
let base = assert_slots!(self, narg + 1);
assert_type!(self, base + 1, "function", Type::Function);
base
}
status => panic!("thread {self:p} called in wrong state: {status}"),
};
// TODO: use error handler to collect backtrace
match unsafe {
lua_pcall(
self.as_ptr(),
narg as c_int,
nret.map_or(LUA_MULTRET, |n| {
n.try_into().expect("too many return values")
}),
0,
)
} {
LUA_OK => Ok(self.size() - base),
LUA_ERRMEM => {
self.pop(1);
Err(Error::memory())
}
code => {
let msg = self
.slot(-1)
.string()
.unwrap_or(b"unknown error".into())
.to_owned();
self.pop(1);
Err(Error::call(ErrorKind::from_raw(code).unwrap(), msg, None))
}
}
}
/// Calls a function on the stack asynchronously with `narg` values at the top of the stack as
/// arguments.
///
/// There must be `narg + 1` values at the top of the stack, including the function to call at
/// the index `top - narg` (i.e. the function is pushed first and then `narg` values as
/// arguments). All arguments and the function are popped from the stack and then any return
/// values are pushed. If `nret` is not [`None`], then the number of return values pushed will
/// be exactly `nret`, filling with nils if necessary. Finally, the number values pushed to the
/// stack is returned.
///
/// If the thread yields a Rust [`Future`] value, then it will be polled to completion before
/// the thread is resumed with the output of the [`Future`] as the argument. If the thread
/// yields any other value, the thread is resumed immediately with no arguments.
///
/// The current thread status must not be suspended or dead.
///
/// Equivalent to multiple calls to [`lua_resume`] until the thread completes with a normal
/// result.
///
/// # Panics
///
/// Panics if there are not enough values on the stack, the function to call is not on the
/// stack, or thread status is invalid.
pub async fn call_async(&mut self, mut narg: u32, nret: Option<u32>) -> Result<u32> {
let base = match self.status() {
Status::Normal => {
// need the function on the stack
let base = assert_slots!(self, narg + 1);
assert_type!(self, base + 1, "function", Type::Function);
base
}
status => panic!("thread {self:p} called in wrong state: {status}"),
};
loop {
match self.resume(narg)? {
ResumeStatus::Ok => {
break Ok(match nret {
Some(n) => {
self.resize(base + n);
n
}
None => self.size() - base,
});
}
ResumeStatus::Suspended => unsafe {
narg = 0;
self.resize(1);
// SAFETY: Rust futures boxed in cdata payloads are never moved by the GC so we
// can safely make a Pin out of this pointer. See also comments in
// `luaffi::future::lua_future<T>`.
if let Some(fut) = self
.slot(1)
.cdata_mut::<lua_pollable>()
.map(|ptr| Pin::new_unchecked(ptr))
&& fut.is_valid()
{
fut.await;
}
},
}
}
}
/// Resumes the current thread with `narg` values on the stack as arguments.
///
/// If the current thread status is normal, then there must be `narg + 1` values at the top of
/// the stack, including the function to call at the index `top - narg` (i.e. the function is
/// pushed first and then `narg` values as arguments). If the current thread status is
/// suspended, then there must be `narg` values at the top of the stack. All arguments and the
/// function are popped from the stack and then any yielded values are pushed. Finally, the new
/// status of the thread indicating whether the thread had completed or suspended is returned.
///
/// The current thread status must not be dead.
///
/// Equivalent to [`lua_resume`].
///
/// # Panics
///
/// Panics if there are not enough values on the stack, the function to call is not on the
/// stack, or thread status is invalid.
pub fn resume(&mut self, narg: u32) -> Result<ResumeStatus> {
match self.status() {
Status::Normal => {
// need the function on the stack
let base = assert_slots!(self, narg + 1);
assert_type!(self, base + 1, "function", Type::Function);
base
}
Status::Suspended => assert_slots!(self, narg),
status => panic!("thread {self:p} resumed in wrong state: {status}"),
};
match unsafe { lua_resume(self.as_ptr(), narg as c_int) } {
LUA_OK => Ok(ResumeStatus::Ok),
LUA_YIELD => Ok(ResumeStatus::Suspended),
LUA_ERRMEM => {
self.pop(1);
Err(Error::memory())
}
code => {
let msg = self
.slot(-1)
.string()
.unwrap_or(b"unknown error".into())
.to_owned();
self.pop(1);
Err(Error::call(
ErrorKind::from_raw(code).unwrap(),
msg,
self.backtrace(0),
))
}
}
}
/// Captures a stack backtrace of the current thread.
///
/// This does not return any useful data unless the thread terminated with an error without
/// unwinding, which only happens when resuming a thread, or it is called inside the error
/// handler of a protected call. Both of these cases are already handled by this library
/// which automatically provides the backtrace with [`Error::trace`] if it is available.
///
/// Equivalent to [`luaL_traceback`].
pub fn backtrace(&self, level: u32) -> Option<BString> {
unsafe {
self.ensure(LUA_MINSTACK as u32);
luaL_traceback(
self.as_ptr(), // thread to push the trace onto
self.as_ptr(), // thread to trace
ptr::null(), // message prefixed to trace string
level.try_into().expect("invalid trace level"),
);
// SAFETY: must clone the trace string here before popping it off the stack
let trace = self
.slot(-1)
.string()
.map(|s| s.strip_prefix(b"stack traceback:\n").unwrap_or(s).as_bstr())
.filter(|s| !s.is_empty())
.map(|s| s.to_owned());
lua_pop(self.as_ptr(), 1);
trace
}
}
}
impl fmt::Debug for Stack {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
struct Slots<'s>(&'s Stack);
impl<'s> fmt::Debug for Slots<'s> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_list().entries(self.0.iter()).finish()
}
}
f.debug_struct("Stack")
.field("ptr", &self.0)
.field("size", &self.size())
.field("slots", &Slots(self))
.finish()
}
}
/// Guarded Lua stack handle.
///
/// This value resets the stack to the original size when dropped. This is useful for implementing
/// idempotent stack operations which must keep the stack size consistent.
///
/// Can be obtained by [`Stack::guard`].
#[derive(Debug)]
pub struct StackGuard<'s> {
parent: PhantomData<&'s mut Stack>,
stack: Stack,
base: u32,
check_overpop: bool,
}
impl<'s> StackGuard<'s> {
fn new(stack: &'s Stack, check_overpop: bool) -> Self {
Self {
parent: PhantomData,
stack: unsafe { Stack::new_unchecked(stack.as_ptr()) }, // SAFETY: stack.as_ptr() is never null
base: stack.size(),
check_overpop,
}
}
}
impl<'s> Deref for StackGuard<'s> {
type Target = Stack;
fn deref(&self) -> &Self::Target {
&self.stack
}
}
impl<'s> DerefMut for StackGuard<'s> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.stack
}
}
impl<'s> Drop for StackGuard<'s> {
fn drop(&mut self) {
if cfg!(debug_assertions) && self.check_overpop {
let new_size = self.stack.size();
assert!(
self.base <= new_size,
"StackGuard detected over-popping by {n} values (this is UB!!)",
n = self.base - new_size,
);
}
self.stack.resize(self.base);
}
}
/// Iterator over the values in a [`Stack`].
///
/// Can be obtained by [`Stack::iter`].
#[derive(Debug)]
pub struct StackIter<'s> {
stack: &'s Stack,
idx: u32,
size: u32,
}
impl<'s> StackIter<'s> {
fn new(stack: &'s Stack) -> Self {
Self {
stack,
idx: 0,
size: stack.size(),
}
}
}
impl<'s> Iterator for StackIter<'s> {
type Item = Slot<'s>;
fn next(&mut self) -> Option<Self::Item> {
(self.idx < self.size).then(|| {
self.idx += 1;
unsafe { self.stack.slot_unchecked(Index::stack(self.idx)) }
})
}
}
/// Index of a value in a [`Stack`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Index {
/// Absolute stack index.
Stack(NonZero<u32>),
/// Upvalue index.
Upvalue(NonZero<u32>),
/// Pseudo-index.
Pseudo(PseudoIndex),
}
impl Index {
/// Creates a new absolute stack index.
///
/// # Panics
///
/// The index must be non-zero.
pub fn stack(idx: u32) -> Self {
Self::Stack(NonZero::new(idx).expect("stack index cannot be zero"))
}
/// Creates a new upvalue index.
///
/// # Panics
///
/// The index must be non-zero.
pub fn upvalue(idx: u32) -> Self {
Self::Upvalue(NonZero::new(idx).expect("upvalue index cannot be zero"))
}
/// Creates a new registry table pseudo-index.
pub fn registry() -> Self {
Self::Pseudo(PseudoIndex::Registry)
}
/// Creates a new environment table pseudo-index.
pub fn environment() -> Self {
Self::Pseudo(PseudoIndex::Environment)
}
/// Creates a new globals table pseudo-index.
pub fn globals() -> Self {
Self::Pseudo(PseudoIndex::Globals)
}
fn _from_raw(idx: c_int) -> Self {
match idx {
0 => panic!("index cannot be zero"),
idx if idx > 0 => Self::stack(idx as u32),
idx if idx < LUA_GLOBALSINDEX => Self::upvalue((LUA_GLOBALSINDEX - idx) as u32),
_ => match PseudoIndex::_from_raw(idx) {
Some(idx) => Self::Pseudo(idx),
None => panic!("invalid pseudo-index {idx}"),
},
}
}
fn into_raw(self) -> c_int {
match self {
Self::Stack(idx) => idx.get().try_into().expect("stack index overflow"),
Self::Upvalue(idx) => idx
.get()
.try_into()
.ok()
.and_then(|idx| LUA_GLOBALSINDEX.checked_sub(idx))
.expect("upvalue index overflow"),
Self::Pseudo(idx) => idx.into_raw(),
}
}
}
impl ops::Add<u32> for Index {
type Output = Index;
fn add(self, rhs: u32) -> Self::Output {
match self {
Self::Stack(idx) => {
Self::stack(idx.get().checked_add(rhs).expect("stack index overflow"))
}
Self::Upvalue(idx) => {
Self::upvalue(idx.get().checked_add(rhs).expect("upvalue index overflow"))
}
Self::Pseudo(idx) => panic!("cannot add offset to {idx} pseudo-index"),
}
}
}
impl ops::Sub<u32> for Index {
type Output = Index;
fn sub(self, rhs: u32) -> Self::Output {
match self {
Self::Stack(idx) => {
Self::stack(idx.get().checked_sub(rhs).expect("stack index underflow"))
}
Self::Upvalue(idx) => {
Self::upvalue(idx.get().checked_sub(rhs).expect("upvalue index underflow"))
}
Self::Pseudo(idx) => panic!("cannot subtract offset from {idx} pseudo-index"),
}
}
}
impl fmt::Display for Index {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Stack(idx) => write!(f, "stack#{idx}"),
Self::Upvalue(idx) => write!(f, "upvalue#{idx}"),
Self::Pseudo(idx) => write!(f, "{idx}"),
}
}
}
/// Pseudo-index in a [`Stack`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum PseudoIndex {
/// Registry table index.
Registry,
/// Environment table index.
Environment,
/// Globals table index.
Globals,
}
impl PseudoIndex {
fn _from_raw(idx: c_int) -> Option<Self> {
Some(match idx {
LUA_REGISTRYINDEX => Self::Registry,
LUA_ENVIRONINDEX => Self::Environment,
LUA_GLOBALSINDEX => Self::Globals,
_ => return None,
})
}
fn into_raw(self) -> c_int {
match self {
Self::Registry => LUA_REGISTRYINDEX,
Self::Environment => LUA_ENVIRONINDEX,
Self::Globals => LUA_GLOBALSINDEX,
}
}
}
impl fmt::Display for PseudoIndex {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Registry => write!(f, "registry"),
Self::Environment => write!(f, "environment"),
Self::Globals => write!(f, "globals"),
}
}
}
/// Lua value handle into the stack.
#[derive(Clone, Copy)]
pub struct Slot<'s> {
stack: &'s Stack,
idx: Index,
}
impl<'s> Slot<'s> {
unsafe fn new_unchecked(stack: &'s Stack, idx: Index) -> Self {
debug_assert!(
unsafe { lua_type(stack.as_ptr(), idx.into_raw()) != LUA_TNONE },
"invalid stack index {idx}: {stack:?}"
);
Self { stack, idx }
}
/// Index of this slot.
pub fn index(&self) -> Index {
self.idx
}
/// Type of the value in this slot.
pub fn ty(&self) -> Type {
Type::from_raw(unsafe { lua_type(self.stack.as_ptr(), self.idx.into_raw()) })
.unwrap_or(Type::Nil)
}
/// Parses the value in this slot as the type `T`.
pub fn parse<T: Parse<'s>>(&self) -> Result<T> {
T::parse(self)
}
/// Parses the value in this slot as a [`bool`].
///
/// If the value is not a `boolean`, then this returns false. `nil` is always considered false.
///
/// Equivalent to [`lua_toboolean`].
pub fn boolean(&self) -> bool {
self.parse().unwrap_or(false)
}
/// Parses the value in this slot as a `lightuserdata` pointer.
///
/// If the value is not a `lightuserdata`, then this returns a null pointer.
///
/// Equivalent to [`lua_touserdata`].
pub fn lightuserdata<T>(&self) -> *mut T {
(self.ty() == Type::Lightuserdata)
.then(|| self.parse().ok())
.flatten()
.unwrap_or(ptr::null_mut())
}
/// Parses the value in this slot as a [`lua_Number`].
///
/// If the value is not a `number` or a `string` that can be parsed as a number, then this
/// returns [`None`].
///
/// Equivalent to [`lua_tonumberx`].
pub fn number(&self) -> Option<lua_Number> {
self.parse().ok()
}
/// Parses the value in this slot as a [`lua_Integer`].
///
/// If the value is not a `number` or a `string` that can be parsed as an integer, then this
/// returns [`None`].
///
/// Equivalent to [`lua_tointegerx`].
pub fn integer(&self) -> Option<lua_Integer> {
self.parse().ok()
}
/// Parses the value in this slot as a binary string.
///
/// If the value is a `number`, then it is converted in-place into a `string` representation of
/// the value first.
///
/// If the value is not a `string`, then this returns [`None`].
///
/// Equivalent to [`lua_tolstring`].
pub fn string(&self) -> Option<&'s BStr> {
self.parse().ok()
}
/// Parses the value in this slot as a UTF-8 string.
///
/// If the value is a `number`, then it is converted in-place into a `string` representation
/// first.
///
/// Equivalent to [`lua_tolstring`].
pub fn string_utf8(&self) -> Option<&'s str> {
self.parse().ok()
}
/// Parses the value in this slot as a [`lua_CFunction`].
///
/// If the value is not a raw C function, then this returns [`None`].
///
/// Equivalent to [`lua_tocfunction`].
pub fn function_raw(&self) -> lua_CFunction {
unsafe { lua_tocfunction(self.stack.as_ptr(), self.idx.into_raw()) }
}
/// Parses the value in this slot as a `cdata` and returns an immutable reference to its
/// payload.
///
/// See [`cdata_ptr`](Self::cdata_ptr) regarding safety.
pub unsafe fn cdata<T>(&self) -> Option<&T> {
let ptr = self.cdata_ptr::<T>();
(!ptr.is_null()).then(|| unsafe { &*ptr })
}
/// Parses the value in this slot as a `cdata` and returns a mutable reference to its payload.
///
/// See [`cdata_ptr_mut`](Self::cdata_ptr_mut) regarding safety.
pub unsafe fn cdata_mut<T>(&self) -> Option<&mut T> {
let ptr = self.cdata_ptr_mut::<T>();
(!ptr.is_null()).then(|| unsafe { &mut *ptr })
}
/// Parses the value in this slot as a `cdata` and returns a mutable pointer to its payload.
///
/// If the value is a `cdata`, then the returned pointer is the address of the base of the its
/// payload. Otherwise, this returns a null pointer.
///
/// Nothing is done to ensure that the payload is of the type `T`. If only the pointer is needed
/// and not its payload, then it is recommended for `T` to be [`c_void`].
///
/// Refer to LuaJIT's [FFI semantics](https://luajit.org/ext_ffi_semantics.html) more
/// documentation regarding cdata objects.
///
/// Equivalent to [`lua_topointer`].
pub fn cdata_ptr<T>(&self) -> *const T {
(self.ty() == Type::Cdata)
.then(|| self.pointer().cast())
.unwrap_or(ptr::null_mut())
}
/// Parses the value in this slot as a `cdata` and returns a mutable pointer to its payload.
///
/// If the value is a `cdata`, then the returned pointer is the address of the base of the its
/// payload. Otherwise, this returns a null pointer.
///
/// Nothing is done to ensure that the payload is of the type `T`. If only the pointer is needed
/// and not its payload, then it is recommended for `T` to be [`c_void`].
///
/// Refer to LuaJIT's [FFI semantics](https://luajit.org/ext_ffi_semantics.html) more
/// documentation regarding cdata objects. In particular, reference cdata objects are immutable
/// after initialisation and must not be modified ("no re-seating of references").
///
/// Equivalent to [`lua_topointer`].
pub fn cdata_ptr_mut<T>(&self) -> *mut T {
self.cdata_ptr::<T>().cast_mut()
}
/// Parses the value in this slot as a generic pointer.
///
/// If the value is not a garbage-collected object that can be represented by a pointer (i.e. a
/// non-`nil` primitive value), then this returns a null pointer.
///
/// Equivalent to [`lua_topointer`].
pub fn pointer(&self) -> *const c_void {
unsafe { lua_topointer(self.stack.as_ptr(), self.idx.into_raw()).cast() }
}
/// Returns the length of the value in this slot.
///
/// For strings, this is the byte-length of the string. For tables, this is the length of the
/// table defined by the Lua `#` operator. For userdata, this is the size of its payload in
/// bytes. Otherwise, this returns 0.
///
/// This function does not invoke the `__len` metamethod.
///
/// Equivalent to [`lua_objlen`].
pub fn length(&self) -> usize {
matches!(self.ty(), Type::String | Type::Table | Type::Userdata)
.then(|| unsafe { lua_objlen(self.stack.as_ptr(), self.idx.into_raw()) })
.unwrap_or(0)
}
}
impl<'s> fmt::Debug for Slot<'s> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.ty() {
Type::Nil => write!(f, "nil"),
Type::Boolean => write!(f, "{}", self.boolean()),
Type::Number => write!(f, "{}", self.number().unwrap()),
Type::String => fmt::Debug::fmt(self.string().unwrap(), f),
ty => write!(f, "{ty} {:p}", self.pointer()),
}
}
}
/// Converts a value to a [`Slot`].
pub trait ToSlot {
/// Converts this value to a [`Slot`] on the given stack.
fn to_slot<'s>(&self, stack: &'s Stack) -> Slot<'s>;
}
impl<T> ToSlot for &T
where
T: ToSlot,
{
fn to_slot<'s>(&self, stack: &'s Stack) -> Slot<'s> {
(*self).to_slot(stack)
}
}
impl ToSlot for Slot<'_> {
fn to_slot<'s>(&self, stack: &'s Stack) -> Slot<'s> {
assert!(self.stack == stack);
unsafe { stack.slot_unchecked(self.idx) }
}
}
impl ToSlot for Index {
fn to_slot<'s>(&self, stack: &'s Stack) -> Slot<'s> {
match self {
Self::Pseudo(_) => {}
Self::Stack(idx) => {
assert!(
idx.get() <= stack.size(),
"stack underflow: expected at least {idx} values: {stack:?}"
);
}
Self::Upvalue(idx) => {
assert!(
unsafe { lua_type(stack.as_ptr(), self.into_raw()) != LUA_TNONE },
"stack underflow: expected at least {idx} upvalues"
);
}
}
unsafe { stack.slot_unchecked(*self) }
}
}
impl ToSlot for PseudoIndex {
fn to_slot<'s>(&self, stack: &'s Stack) -> Slot<'s> {
unsafe { stack.slot_unchecked(Index::Pseudo(*self)) }
}
}
impl ToSlot for u32 {
fn to_slot<'s>(&self, stack: &'s Stack) -> Slot<'s> {
stack.slot(Index::stack(*self))
}
}
impl ToSlot for i32 {
fn to_slot<'s>(&self, stack: &'s Stack) -> Slot<'s> {
let idx = *self;
if idx >= 0 {
return stack.slot(idx as u32);
}
let size = stack.size();
let offset = (-idx) as u32;
assert!(
offset <= size,
"stack underflow: expected at least {offset} values: {stack:?}"
);
unsafe { stack.slot_unchecked(Index::stack(size - offset + 1)) }
}
}
impl<'s> ops::Add<u32> for Slot<'s> {
type Output = Slot<'s>;
fn add(self, n: u32) -> Self::Output {
if n == 0 {
self
} else {
self.stack.slot(self.idx + n)
}
}
}
impl<'s> ops::Sub<u32> for Slot<'s> {
type Output = Slot<'s>;
fn sub(self, n: u32) -> Self::Output {
if n == 0 {
self
} else {
// SAFETY: subtracted index is guaranteed to be valid
unsafe { self.stack.slot_unchecked(self.idx - n) }
}
}
}
/// Pushes a value onto a [`Stack`].
///
/// This trait can be implemented by any type that can be reasonably converted to an equivalent Lua
/// value. Any type implementing this trait can be passed as an argument to [`Stack::push`].
pub trait Push {
/// Pushes this value to the given stack.
fn push(&self, stack: &mut Stack);
}
impl<T: Push> Push for &T {
fn push(&self, stack: &mut Stack) {
T::push(self, stack)
}
}
impl<T: Push> Push for &mut T {
fn push(&self, stack: &mut Stack) {
T::push(self, stack)
}
}
impl<T: Push> Push for Option<T> {
fn push(&self, stack: &mut Stack) {
match self {
Some(value) => value.push(stack),
None => ().push(stack),
}
}
}
impl Push for () {
fn push(&self, stack: &mut Stack) {
stack.ensure(1);
unsafe { lua_pushnil(stack.as_ptr()) }
}
}
impl Push for bool {
fn push(&self, stack: &mut Stack) {
stack.ensure(1);
unsafe { lua_pushboolean(stack.as_ptr(), *self as c_int) }
}
}
macro_rules! impl_push_ptr {
($type:ty) => {
impl<T> Push for $type {
fn push(&self, stack: &mut Stack) {
stack.ensure(1);
unsafe { lua_pushlightuserdata(stack.as_ptr(), *self as *mut c_void) }
}
}
};
}
impl_push_ptr!(*const T);
impl_push_ptr!(*mut T);
macro_rules! impl_push_num {
($type:ty) => {
impl Push for $type {
fn push(&self, stack: &mut Stack) {
stack.ensure(1);
unsafe { lua_pushnumber(stack.as_ptr(), *self as lua_Number) }
}
}
};
}
impl_push_num!(f32);
impl_push_num!(f64);
macro_rules! impl_push_int {
($type:ty) => {
impl Push for $type {
fn push(&self, stack: &mut Stack) {
stack.ensure(1);
unsafe { lua_pushinteger(stack.as_ptr(), *self as lua_Integer) }
}
}
};
}
impl_push_int!(u8);
impl_push_int!(u16);
impl_push_int!(u32);
impl_push_int!(u64);
impl_push_int!(usize);
impl_push_int!(i8);
impl_push_int!(i16);
impl_push_int!(i32);
impl_push_int!(i64);
impl_push_int!(isize);
macro_rules! impl_push_str {
($type:ty) => {
impl Push for $type {
fn push(&self, stack: &mut Stack) {
stack.ensure(1);
unsafe { lua_pushliteral(stack.as_ptr(), self) }
}
}
};
}
impl_push_str!(&[u8]);
impl_push_str!(Vec<u8>);
impl_push_str!(&BStr);
impl_push_str!(BString);
impl_push_str!(&str);
impl_push_str!(String);
impl Push for Ref {
fn push(&self, stack: &mut Stack) {
stack.ensure(1);
unsafe { lua_rawgeti(stack.as_ptr(), LUA_REGISTRYINDEX, self.key) }
}
}
impl Push for Index {
fn push(&self, stack: &mut Stack) {
match self {
Self::Pseudo(_) => {}
Self::Stack(idx) => {
assert!(
idx.get() <= stack.size(),
"stack underflow: expected at least {idx} values: {stack:?}"
);
}
Self::Upvalue(idx) => {
assert!(
unsafe { lua_type(stack.as_ptr(), self.into_raw()) != LUA_TNONE },
"stack underflow: expected at least {idx} upvalues"
)
}
}
stack.ensure(1);
unsafe { lua_pushvalue(stack.as_ptr(), self.into_raw()) };
}
}
impl Push for PseudoIndex {
fn push(&self, stack: &mut Stack) {
Index::Pseudo(*self).push(stack);
}
}
impl<'s> Push for Slot<'s> {
fn push(&self, stack: &mut Stack) {
assert!(self.stack == stack); // TODO: check global_State are equal and xmove instead
stack.ensure(1);
unsafe { lua_pushvalue(stack.as_ptr(), self.idx.into_raw()) };
}
}
/// [`Push`]es a new table onto a [`Stack`].
///
/// This value can be used as an argument to [`Stack::push`] to create a new empty table onto the
/// stack.
///
/// Equivalent to [`lua_createtable`].
#[derive(Debug, Default, Clone, Copy, Hash)]
pub struct NewTable {
/// Size of the preallocated array part.
pub narr: u32,
/// Size of the preallocated hash part.
pub nrec: u32,
}
impl NewTable {
/// Creates a new [`NewTable`] with no preallocations defined.
pub fn new() -> Self {
Self::sized(0, 0)
}
/// Creates a new [`NewTable`] with the array part set to preallocate `size`.
pub fn array(size: u32) -> Self {
Self::sized(size, 0)
}
/// Creates a new [`NewTable`] with the hash part set to preallocate `size`.
pub fn record(size: u32) -> Self {
Self::sized(0, size)
}
/// Creates a new [`NewTable`] with the array and hash parts set to preallocate `narr` and
/// `nrec` respectively.
pub fn sized(narr: u32, nrec: u32) -> Self {
Self { narr, nrec }
}
}
impl Push for NewTable {
fn push(&self, stack: &mut Stack) {
let Self { narr, nrec } = *self;
stack.ensure(1);
unsafe {
lua_createtable(
stack.as_ptr(),
narr.try_into().expect("table narr too big"),
nrec.try_into().expect("table nrec too big"),
)
}
}
}
/// Bare Rust function that can be called from Lua.
pub type BareFunction = fn(&mut Stack) -> c_int;
/// [`Push`]es a new function onto a [`Stack`].
#[derive(Debug, Clone, Copy, Hash)]
pub enum Function {
/// Bare Rust function.
Bare(BareFunction),
/// Raw C function.
Raw(lua_CFunction),
}
impl Push for Function {
fn push(&self, stack: &mut Stack) {
match *self {
Function::Bare(f) => unsafe {
unsafe extern "C" fn cb(L: *mut lua_State) -> c_int {
unsafe {
let mut stack = Stack::new_unchecked(L);
let f = mem::transmute::<*mut c_void, BareFunction>(
stack.slot(Index::upvalue(1)).lightuserdata(),
);
f(&mut stack)
}
}
stack.ensure(2);
lua_pushlightuserdata(stack.as_ptr(), f as *mut c_void);
lua_pushcclosure(stack.as_ptr(), Some(cb), 1);
},
Function::Raw(f) => unsafe {
assert!(f.is_some(), "raw function cannot be null");
stack.ensure(1);
lua_pushcfunction(stack.as_ptr(), f);
},
}
}
}
impl Push for BareFunction {
fn push(&self, stack: &mut Stack) {
Function::Bare(*self).push(stack);
}
}
/// [`Push`]es the current thread onto a [`Stack`].
///
/// This value can be used as an argument to [`Stack::push`] to push the current thread as a
/// `coroutine` object onto the stack.
///
/// Equivalent to [`lua_pushthread`].
#[derive(Debug, Default, Clone, Copy, Hash)]
pub struct CurrentThread;
impl Push for CurrentThread {
fn push(&self, stack: &mut Stack) {
stack.ensure(1);
unsafe { lua_pushthread(stack.as_ptr()) };
}
}
/// Parses a value on a [`Stack`].
///
/// This trait can be implemented by any type that can be reasonably converted from an equivalent
/// Lua value. Any type implementing this trait can be passed as a generic argument to
/// [`Slot::parse`].
pub trait Parse<'s>: Sized {
/// Parses the value in the given slot.
fn parse(slot: &Slot<'s>) -> Result<Self>;
}
impl Parse<'_> for () {
fn parse(slot: &Slot) -> Result<Self> {
match slot.ty() {
Type::Nil => Ok(()),
ty => Err(Error::invalid_type("nil", ty.name())),
}
}
}
impl Parse<'_> for bool {
fn parse(slot: &Slot) -> Result<Self> {
Ok(unsafe { lua_toboolean(slot.stack.as_ptr(), slot.index().into_raw()) != 0 })
}
}
macro_rules! impl_parse_ptr {
($type:ty) => {
impl<T> Parse<'_> for $type {
fn parse(slot: &Slot) -> Result<Self> {
let ptr = unsafe { lua_touserdata(slot.stack.as_ptr(), slot.index().into_raw()) };
if !ptr.is_null() {
Ok(ptr as $type)
} else {
Err(Error::invalid_type("userdata", slot.ty().name()))
}
}
}
};
}
impl_parse_ptr!(*mut T);
impl_parse_ptr!(*const T);
macro_rules! impl_parse_num {
($type:ty) => {
impl Parse<'_> for $type {
fn parse(slot: &Slot) -> Result<Self> {
let mut isnum = 0;
let n = unsafe {
lua_tonumberx(slot.stack.as_ptr(), slot.index().into_raw(), &raw mut isnum)
};
if isnum != 0 {
Ok(n as $type)
} else {
Err(Error::invalid_type("number", slot.ty().name()))
}
}
}
};
}
impl_parse_num!(f32);
impl_parse_num!(f64);
macro_rules! impl_parse_int {
($type:ty) => {
impl Parse<'_> for $type {
fn parse(slot: &Slot) -> Result<Self> {
let mut isnum = 0;
let n = unsafe {
lua_tointegerx(slot.stack.as_ptr(), slot.index().into_raw(), &raw mut isnum)
};
if isnum != 0 {
Ok(n as $type)
} else {
Err(Error::invalid_type("number", slot.ty().name()))
}
}
}
};
}
impl_parse_int!(u8);
impl_parse_int!(u16);
impl_parse_int!(u32);
impl_parse_int!(u64);
impl_parse_int!(usize);
impl_parse_int!(i8);
impl_parse_int!(i16);
impl_parse_int!(i32);
impl_parse_int!(i64);
impl_parse_int!(isize);
macro_rules! impl_parse_str {
($type:ty) => {
impl<'s> Parse<'s> for $type {
fn parse(slot: &Slot<'s>) -> Result<Self> {
let mut len = 0;
let ptr = unsafe {
lua_tolstring(slot.stack.as_ptr(), slot.index().into_raw(), &mut len)
};
if !ptr.is_null() {
Ok(unsafe { slice::from_raw_parts(ptr.cast(), len).into() })
} else {
Err(Error::invalid_type("string", slot.ty().name()))
}
}
}
};
}
macro_rules! impl_parse_str_utf8 {
($type:ty) => {
impl<'s> Parse<'s> for $type {
fn parse(slot: &Slot<'s>) -> Result<Self> {
Ok(std::str::from_utf8(Parse::parse(slot)?)
.map_err(Error::new)?
.into())
}
}
};
}
impl_parse_str!(&'s [u8]);
impl_parse_str!(&'s BStr);
impl_parse_str!(Vec<u8>);
impl_parse_str!(BString);
impl_parse_str_utf8!(&'s str);
impl_parse_str_utf8!(String);