//! 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 = std::result::Result; /// 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, }, _Argument { index: u32, func: BString, err: Box, }, _Slot { index: i32, err: Box, }, Type { expected: &'static str, got: &'static str, }, Other(Box), } impl Error { /// Creates a new error with an arbitrary error payload. pub fn new(error: impl Into>) -> 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) -> 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::()) .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 { 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 { 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 { 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) -> 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> From 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, _alloc: Box>, } #[derive(Debug, Default)] struct AllocatorState { alloc: usize, dealloc: usize, } impl GlobalState { pub fn new() -> Result { 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, 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 { 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); 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> { 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> { 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) -> 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) -> Result { 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 { 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) -> Result { 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) -> Result { 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) -> Result { 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`. if let Some(fut) = self .slot(1) .cdata_mut::() .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 { 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 { 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.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), /// Upvalue index. Upvalue(NonZero), /// 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 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 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 { 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>(&self) -> Result { 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(&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 { 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 { 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(&self) -> Option<&T> { let ptr = self.cdata_ptr::(); (!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(&self) -> Option<&mut T> { let ptr = self.cdata_ptr_mut::(); (!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(&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(&self) -> *mut T { self.cdata_ptr::().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 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 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 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 Push for &T { fn push(&self, stack: &mut Stack) { T::push(self, stack) } } impl Push for &mut T { fn push(&self, stack: &mut Stack) { T::push(self, stack) } } impl Push for Option { 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 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); 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; } impl Parse<'_> for () { fn parse(slot: &Slot) -> Result { match slot.ty() { Type::Nil => Ok(()), ty => Err(Error::invalid_type("nil", ty.name())), } } } impl Parse<'_> for bool { fn parse(slot: &Slot) -> Result { Ok(unsafe { lua_toboolean(slot.stack.as_ptr(), slot.index().into_raw()) != 0 }) } } macro_rules! impl_parse_ptr { ($type:ty) => { impl Parse<'_> for $type { fn parse(slot: &Slot) -> Result { 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 { 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 { 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 { 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 { 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); impl_parse_str!(BString); impl_parse_str_utf8!(&'s str); impl_parse_str_utf8!(String);