1823 lines
57 KiB
Rust
1823 lines
57 KiB
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},
|
|
borrow::Cow,
|
|
ffi::{CStr, CString, NulError},
|
|
fmt,
|
|
marker::PhantomData,
|
|
ops::{Deref, DerefMut},
|
|
os::raw::{c_char, c_int, c_void},
|
|
pin::Pin,
|
|
process,
|
|
ptr::{self, NonNull},
|
|
rc::Rc,
|
|
slice,
|
|
str::Utf8Error,
|
|
};
|
|
use thiserror::Error;
|
|
|
|
/// LuaJIT version string.
|
|
pub fn version() -> &'static str {
|
|
LUAJIT_VERSION.to_str().unwrap()
|
|
}
|
|
|
|
/// Lua error.
|
|
#[derive(Debug, Error)]
|
|
#[non_exhaustive]
|
|
pub enum Error {
|
|
/// Out of memory error.
|
|
#[error("out of memory")]
|
|
OutOfMemory,
|
|
/// Lua syntax error returned by [`Stack::load`].
|
|
#[error("{msg}")]
|
|
Syntax {
|
|
/// Content of the chunk which had errors.
|
|
chunk: BString,
|
|
/// Lua error message.
|
|
msg: BString,
|
|
},
|
|
/// Lua chunk name error returned by [`Stack::load`].
|
|
#[error("bad chunk name: {0}")]
|
|
BadChunkName(NulError),
|
|
/// Lua error returned by [`Stack::call`].
|
|
#[error("{msg}")]
|
|
Call {
|
|
/// Lua error message.
|
|
msg: BString,
|
|
},
|
|
/// Lua error returned by [`Stack::resume`].
|
|
#[error("{msg}")]
|
|
Resume {
|
|
/// Lua error message.
|
|
msg: BString,
|
|
/// Lua stack trace.
|
|
trace: Option<BString>,
|
|
},
|
|
/// Type mismatch type error.
|
|
#[error("{0} expected, got {1}")]
|
|
InvalidType(
|
|
/// The expected type.
|
|
&'static str,
|
|
/// The actual type.
|
|
&'static str,
|
|
),
|
|
/// Invalid UTF-8 string error.
|
|
#[error("{0}")]
|
|
InvalidUtf8(#[from] Utf8Error),
|
|
}
|
|
|
|
impl Error {
|
|
/// Lua stack trace, if it was collected.
|
|
///
|
|
/// Currently this is only available for [`Error::Resume`].
|
|
pub fn trace(&self) -> Option<&BStr> {
|
|
match self {
|
|
Self::Resume { trace, .. } => trace.as_ref().map(|s| s.as_ref()),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Lua result.
|
|
pub type Result<T> = ::std::result::Result<T, Error>;
|
|
|
|
/// 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 {
|
|
/// Converts a raw Lua type code to [`Type`], returning [`None`] if the value is invalid.
|
|
pub fn from_code(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,
|
|
/// Thread suspended with `coroutine.yield(...)` and is awaiting resume.
|
|
Suspended,
|
|
}
|
|
|
|
impl Status {
|
|
/// Converts a raw Lua status code to [`Status`], returning [`None`] if the value is invalid.
|
|
pub fn from_code(code: c_int) -> Option<Self> {
|
|
Some(match code {
|
|
LUA_OK => Self::Normal,
|
|
LUA_YIELD => Self::Suspended,
|
|
LUA_ERRRUN | LUA_ERRSYNTAX | LUA_ERRMEM | LUA_ERRERR => Self::Dead,
|
|
_ => return None,
|
|
})
|
|
}
|
|
|
|
/// 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 {
|
|
write!(f, "{}", self.name())
|
|
}
|
|
}
|
|
|
|
/// Result of [`Stack::resume`].
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
|
pub enum ResumeStatus {
|
|
/// Thread returned 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 LoadMode {
|
|
fn to_mode_str(&self) -> Cow<'static, CStr> {
|
|
Cow::Borrowed(match *self {
|
|
Self::NONE => c"",
|
|
Self::AUTO => c"bt",
|
|
Self::TEXT => c"t",
|
|
Self::BINARY => c"b",
|
|
_ => {
|
|
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"));
|
|
return Cow::Owned(CString::new(s).unwrap());
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Default for LoadMode {
|
|
fn default() -> Self {
|
|
Self::AUTO
|
|
}
|
|
}
|
|
|
|
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 DumpMode {
|
|
fn to_mode_str(&self) -> Cow<'static, CStr> {
|
|
Cow::Borrowed(match *self {
|
|
Self::NONE => c"",
|
|
Self::STRIP => c"s",
|
|
Self::DETERMINISTIC => c"d",
|
|
_ => {
|
|
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"));
|
|
return Cow::Owned(CString::new(s).unwrap());
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Default for DumpMode {
|
|
fn default() -> Self {
|
|
Self::DETERMINISTIC
|
|
}
|
|
}
|
|
|
|
/// 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,
|
|
}
|
|
}
|
|
|
|
/// Sets the name of this chunk as `name`.
|
|
pub fn name(&mut self, name: impl AsRef<[u8]>) -> &mut Self {
|
|
self.name = name.as_ref().into();
|
|
self
|
|
}
|
|
|
|
/// Sets the name of this chunk as the path `path`.
|
|
pub fn path(&mut self, path: impl AsRef<[u8]>) -> &mut Self {
|
|
let mut name = BString::from(b"@");
|
|
name.extend_from_slice(path.as_ref());
|
|
self.name = name;
|
|
self
|
|
}
|
|
|
|
/// Sets the mode flag for loading this chunk.
|
|
pub fn mode(&mut self, mode: LoadMode) -> &mut Self {
|
|
self.mode = 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)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct GlobalState {
|
|
ptr: NonNull<lua_State>,
|
|
}
|
|
|
|
impl GlobalState {
|
|
pub fn new() -> Result<Self> {
|
|
unsafe {
|
|
// SAFETY: lua_newstate may return a null pointer if allocation fails
|
|
let ptr = NonNull::new(lua_newstate(Some(Self::alloc_cb), ptr::null_mut()))
|
|
.ok_or(Error::OutOfMemory)?;
|
|
|
|
lua_atpanic(ptr.as_ptr(), Some(Self::panic_cb));
|
|
Ok(Self { ptr })
|
|
}
|
|
}
|
|
|
|
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
|
|
.slot(-1)
|
|
.string()
|
|
.unwrap_or(b"unknown lua panic".into());
|
|
|
|
eprintln!("lua panicked: {msg}");
|
|
process::abort()
|
|
}
|
|
|
|
unsafe extern "C" fn alloc_cb(
|
|
_ud: *mut c_void,
|
|
ptr: *mut c_void,
|
|
osize: usize,
|
|
nsize: usize,
|
|
) -> *mut c_void {
|
|
// 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 = Layout::from_size_align(osize, ALIGNOF_MAX_ALIGN_T)
|
|
.expect("lua alloc error: requested osize is too large");
|
|
let new_layout = Layout::from_size_align(nsize, ALIGNOF_MAX_ALIGN_T)
|
|
.expect("lua alloc error: requested nsize is too large");
|
|
|
|
// 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 nsize == 0 {
|
|
if osize != 0 {
|
|
unsafe { dealloc(ptr as *mut u8, old_layout) }
|
|
}
|
|
ptr::null_mut()
|
|
} else {
|
|
if osize == 0 {
|
|
unsafe { alloc(new_layout) as *mut c_void }
|
|
} else {
|
|
unsafe { realloc(ptr as *mut u8, old_layout, nsize) as *mut c_void }
|
|
}
|
|
}
|
|
}
|
|
|
|
fn as_ptr(&self) -> *mut lua_State {
|
|
self.ptr.as_ptr()
|
|
}
|
|
}
|
|
|
|
impl Drop for GlobalState {
|
|
fn drop(&mut self) {
|
|
unsafe { lua_close(self.as_ptr()) }
|
|
}
|
|
}
|
|
|
|
/// 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 Drop for Ref {
|
|
fn drop(&mut self) {
|
|
// SAFETY: luaL_unref is guaranteed to not fail
|
|
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,
|
|
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 {
|
|
state,
|
|
key: LUA_NOREF,
|
|
},
|
|
};
|
|
|
|
state.push_function_raw(Some(Self::open_cb), 0);
|
|
state.call(0, 0)?;
|
|
Ok(state)
|
|
}
|
|
|
|
unsafe extern "C" fn open_cb(L: *mut lua_State) -> c_int {
|
|
// SAFETY: we wrap luaL_openlibs so we can potentially catch any library open errors
|
|
unsafe { luaL_openlibs(L) }
|
|
0
|
|
}
|
|
|
|
/// Creates a new empty thread (coroutine) associated with this state.
|
|
pub fn new_thread(&self) -> Self {
|
|
self.ensure(1);
|
|
Self {
|
|
// SAFETY: lua_newthread never returns null, but may panic on oom
|
|
stack: unsafe { Stack::new_unchecked(lua_newthread(self.as_ptr())) },
|
|
thread: Ref {
|
|
state: Rc::clone(&self.thread.state),
|
|
key: unsafe { luaL_ref(self.as_ptr(), LUA_REGISTRYINDEX) },
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Creates a new [`Ref`] with the given key.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// The caller must ensure that the given ref key is unique and not already used by any other
|
|
/// instances of [`Ref`].
|
|
pub unsafe fn new_ref_unchecked(&self, key: c_int) -> Ref {
|
|
Ref {
|
|
state: Rc::clone(&self.thread.state),
|
|
key,
|
|
}
|
|
}
|
|
}
|
|
|
|
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.
|
|
#[repr(transparent)]
|
|
pub struct Stack(NonNull<lua_State>);
|
|
|
|
impl Stack {
|
|
/// Creates a new [`Stack`] from a raw pointer.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// The pointer must not be null. See also mutability guarantees on [`Stack`](Stack#mutability).
|
|
pub unsafe fn new_unchecked(ptr: *mut lua_State) -> Self {
|
|
assert!(!ptr.is_null(), "attempt to create Stack with null pointer");
|
|
Self(unsafe { NonNull::new_unchecked(ptr) })
|
|
}
|
|
|
|
/// Pointer to the [`lua_State`].
|
|
pub fn as_ptr(&self) -> *mut lua_State {
|
|
self.0.as_ptr()
|
|
}
|
|
|
|
/// Size of the stack.
|
|
///
|
|
/// This is the number of values on the stack and points to the value at the top of the stack
|
|
/// when used as an index.
|
|
///
|
|
/// Equivalent to [`lua_gettop`].
|
|
pub fn size(&self) -> c_int {
|
|
unsafe { lua_gettop(self.as_ptr()) }
|
|
}
|
|
|
|
/// Resizes the stack to fit exactly `n` values, reallocating the stack and popping any
|
|
/// extraneous values or pushing nils to fill the space as necessary.
|
|
///
|
|
/// Equivalent to [`lua_settop`].
|
|
///
|
|
/// # Panic
|
|
///
|
|
/// Panics if `n` is negative.
|
|
pub fn resize(&mut self, n: c_int) {
|
|
// SAFETY: lua_settop can throw on oom (doesn't cpgrowstack) when growing, so we call ensure
|
|
// first
|
|
assert!(0 <= n, "cannot resize to size {n}");
|
|
let size = self.size();
|
|
(n > size).then(|| self.ensure(n - size));
|
|
unsafe { lua_settop(self.as_ptr(), n) }
|
|
}
|
|
|
|
/// 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`].
|
|
///
|
|
/// # Panic
|
|
///
|
|
/// Panics if `n` is negative or reallocation fails.
|
|
pub fn ensure(&self, n: c_int) {
|
|
// lua_checkstack throws on oom in puc lua 5.1, but it is fine in luajit
|
|
assert!(n >= 0, "ensure called with a negative value");
|
|
unsafe { assert!(lua_checkstack(self.as_ptr(), n) != 0, "stack out of memory") }
|
|
}
|
|
|
|
/// Pops `n` values at the top of the stack.
|
|
///
|
|
/// Equivalent to [`lua_pop`].
|
|
///
|
|
/// # Panic
|
|
///
|
|
/// Panics if there are less than `n` values on the stack.
|
|
pub fn pop(&mut self, n: c_int) {
|
|
assert!(0 <= n && n <= self.size(), "cannot pop {n}: {self:?}");
|
|
unsafe { lua_pop(self.as_ptr(), n) }
|
|
}
|
|
|
|
/// Pops the value at the top of the stack and inserts it at index `idx` by shifting up existing
|
|
/// values.
|
|
///
|
|
/// Index `idx` cannot be a pseudo-index.
|
|
///
|
|
/// Equivalent to [`lua_insert`].
|
|
///
|
|
/// # Panic
|
|
///
|
|
/// Panics if the stack is empty or the index `idx` is invalid.
|
|
pub fn pop_insert(&mut self, idx: c_int) {
|
|
assert!(self.size() >= 1, "cannot pop 1: {self:?}");
|
|
let idx = self.slot(idx).index();
|
|
assert!(idx > 0, "cannot insert into pseudo-index {idx}");
|
|
unsafe { lua_insert(self.as_ptr(), idx) }
|
|
}
|
|
|
|
/// Pops the value at the top of the stack and replaces the value at index `idx` with it.
|
|
///
|
|
/// If the index `idx` points to the top of the stack, this still pops the value and is
|
|
/// functionally equivalent to [`Stack::pop`] in that case.
|
|
///
|
|
/// Equivalent to [`lua_replace`].
|
|
///
|
|
/// # Panic
|
|
///
|
|
/// Panics if the stack is empty or the index `idx` is invalid.
|
|
pub fn pop_replace(&mut self, idx: c_int) {
|
|
assert!(self.size() >= 1, "cannot pop 1: {self:?}");
|
|
unsafe { lua_replace(self.as_ptr(), self.slot(idx).index()) }
|
|
}
|
|
|
|
/// Status of the current thread.
|
|
///
|
|
/// Equivalent to [`lua_status`].
|
|
pub fn status(&self) -> Status {
|
|
Status::from_code(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)
|
|
}
|
|
|
|
/// Handle for the value at index `idx`.
|
|
///
|
|
/// # Panic
|
|
///
|
|
/// Panics if the index `idx` is invalid.
|
|
pub fn slot<'s>(&'s self, idx: c_int) -> Slot<'s> {
|
|
self.try_slot(idx)
|
|
.unwrap_or_else(|| panic!("invalid index {idx}: {self:?}"))
|
|
}
|
|
|
|
/// Handle for the value at index `idx`, or [`None`] if there is no value at that index.
|
|
pub fn try_slot<'s>(&'s self, idx: c_int) -> Option<Slot<'s>> {
|
|
self.absindex(idx)
|
|
.map(|idx| unsafe { Slot::new_unchecked(self, idx) })
|
|
}
|
|
|
|
fn absindex(&self, idx: c_int) -> Option<c_int> {
|
|
if LUA_REGISTRYINDEX < idx && idx <= 0 {
|
|
// SAFETY: must check any relative index that gets passed to index2adr in lj_api.c
|
|
// because luajit doesn't check for out-of-bounds access for relative indices with
|
|
// assertions disabled
|
|
let top = self.size();
|
|
let idx = top + idx + 1;
|
|
(0 < idx && idx <= top).then_some(idx)
|
|
} else {
|
|
unsafe { lua_type(self.as_ptr(), idx) != LUA_TNONE }.then_some(idx)
|
|
}
|
|
}
|
|
|
|
/// Pushes the given value at the top of the stack.
|
|
///
|
|
/// Equivalent to the `lua_push*` family of functions depending on the type of `T`.
|
|
pub fn push<T: Push>(&mut self, value: T) {
|
|
value.push(self)
|
|
}
|
|
|
|
/// Pushes the given C function at the top of the stack.
|
|
///
|
|
/// Equivalent to [`lua_pushcclosure`].
|
|
///
|
|
/// # Panic
|
|
///
|
|
/// Panics if the given function pointer is null.
|
|
pub fn push_function_raw(&mut self, f: lua_CFunction, upvals: c_int) {
|
|
assert!(f.is_some(), "function must not be null");
|
|
self.ensure(1);
|
|
unsafe { lua_pushcclosure(self.as_ptr(), f, upvals) }
|
|
}
|
|
|
|
/// Gets a field of the table at index `idx` using the value at the top of the stack as the key,
|
|
/// and replaces the key with the retrieved value.
|
|
///
|
|
/// This function does not invoke the `__index` metamethod.
|
|
///
|
|
/// Equivalent to [`lua_rawget`].
|
|
///
|
|
/// # Panic
|
|
///
|
|
/// Panics if the value at index `idx` is not a table.
|
|
pub fn get(&mut self, idx: c_int) {
|
|
assert!(self.size() >= 1, "expected 1 value: {self:?}");
|
|
let table = self.slot(idx);
|
|
assert!(
|
|
table.type_of() == Type::Table,
|
|
"expected table at index {idx}: {self:?}"
|
|
);
|
|
unsafe { lua_rawget(self.as_ptr(), table.index()) }
|
|
}
|
|
|
|
/// Gets a field of the table at index `idx` using `n` as the key, and pushes the retrieved
|
|
/// value at the top of the stack.
|
|
///
|
|
/// This function does not invoke the `__index` metamethod.
|
|
///
|
|
/// Equivalent to [`lua_rawgeti`].
|
|
///
|
|
/// # Panic
|
|
///
|
|
/// Panics if the value at index `idx` is not a table.
|
|
pub fn geti(&mut self, idx: c_int, n: c_int) {
|
|
let table = self.slot(idx);
|
|
assert!(
|
|
table.type_of() == Type::Table,
|
|
"expected table at index {idx}: {self:?}"
|
|
);
|
|
self.ensure(1);
|
|
unsafe { lua_rawgeti(self.as_ptr(), table.index(), n) }
|
|
}
|
|
|
|
/// 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`].
|
|
///
|
|
/// # Panic
|
|
///
|
|
/// Panics if the value at index `idx` is not a table.
|
|
pub fn set(&mut self, idx: c_int) {
|
|
assert!(self.size() >= 2, "expected 2 values: {self:?}");
|
|
let table = self.slot(idx);
|
|
assert!(
|
|
table.type_of() == Type::Table,
|
|
"expected table at index {idx}: {self:?}"
|
|
);
|
|
unsafe { lua_rawset(self.as_ptr(), table.index()) }
|
|
}
|
|
|
|
/// 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`].
|
|
///
|
|
/// # Panic
|
|
///
|
|
/// Panics if the value at index `idx` is not a table.
|
|
pub fn seti(&mut self, idx: c_int, n: c_int) {
|
|
assert!(self.size() >= 1, "expected 1 value: {self:?}");
|
|
let table = self.slot(idx);
|
|
assert!(
|
|
table.type_of() == Type::Table,
|
|
"expected table at index {idx}: {self:?}"
|
|
);
|
|
unsafe { lua_rawseti(self.as_ptr(), table.index(), n) }
|
|
}
|
|
|
|
/// 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.
|
|
///
|
|
/// Equivalent to `table.pack(...)`.
|
|
///
|
|
/// # Panic
|
|
///
|
|
/// 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: c_int, n: c_int) -> c_int {
|
|
assert!(n >= 0, "n must be nonnegative");
|
|
let size = self.size();
|
|
let table = self.slot(idx);
|
|
assert!(
|
|
table.type_of() == Type::Table,
|
|
"expected table at index {idx}: {self:?}"
|
|
);
|
|
assert!(n <= size, "expected {n} values: {self:?}");
|
|
assert!(idx <= size - n, "cannot pack a table into itself: {self:?}");
|
|
unsafe {
|
|
(0..n).for_each(|i| lua_rawseti(self.as_ptr(), table.index(), n - i));
|
|
self.ensure(2);
|
|
lua_pushliteral(self.as_ptr(), "n");
|
|
lua_pushinteger(self.as_ptr(), n as lua_Integer);
|
|
lua_rawset(self.as_ptr(), table.index());
|
|
n
|
|
}
|
|
}
|
|
|
|
/// Unpacks the array-part of the table at index `idx` onto the stack.
|
|
///
|
|
/// 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 at 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.
|
|
///
|
|
/// Equivalent to `table.unpack(list, i, j)`.
|
|
///
|
|
/// # Panic
|
|
///
|
|
/// Panics if the value at index `idx` is not a table.
|
|
pub fn unpack(&mut self, idx: c_int, i: c_int, j: Option<c_int>) -> c_int {
|
|
let table = self.slot(idx);
|
|
assert!(
|
|
table.type_of() == Type::Table,
|
|
"expected table at index {idx}: {self:?}"
|
|
);
|
|
let j = match j {
|
|
Some(j) => j,
|
|
None => unsafe {
|
|
self.ensure(1);
|
|
lua_pushliteral(self.as_ptr(), "n");
|
|
lua_rawget(self.as_ptr(), table.index());
|
|
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 c_int)
|
|
.unwrap_or_else(|| lua_objlen(self.as_ptr(), table.index()) as c_int)
|
|
},
|
|
};
|
|
let n = (j - i + 1).max(0);
|
|
if n > 0 {
|
|
self.ensure(n);
|
|
(0..n).for_each(|n| unsafe { lua_rawgeti(self.as_ptr(), table.index(), i + n) });
|
|
}
|
|
n
|
|
}
|
|
|
|
/// Pushes the given chunk as a function at the top of the stack.
|
|
///
|
|
/// 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());
|
|
let name = CString::new(chunk.name.to_vec()).map_err(Error::BadChunkName)?;
|
|
let mode = chunk.mode.to_mode_str();
|
|
|
|
unsafe extern "C" fn reader_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()
|
|
}
|
|
}
|
|
}
|
|
|
|
self.ensure(1);
|
|
|
|
match unsafe {
|
|
lua_loadx(
|
|
self.as_ptr(),
|
|
Some(reader_cb),
|
|
&raw mut state as *mut c_void,
|
|
name.as_ptr(),
|
|
mode.as_ptr(),
|
|
)
|
|
} {
|
|
LUA_OK => Ok(()),
|
|
LUA_ERRMEM => {
|
|
self.pop(1);
|
|
Err(Error::OutOfMemory)
|
|
}
|
|
LUA_ERRSYNTAX => {
|
|
let chunk = name.into_bytes().into();
|
|
let msg = self
|
|
.slot(-1)
|
|
.string()
|
|
.unwrap_or(b"unknown error".into())
|
|
.into();
|
|
|
|
self.pop(1);
|
|
Err(Error::Syntax { chunk, msg })
|
|
}
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
/// Dumps the function at index `idx` into bytecode.
|
|
///
|
|
/// Equivalent to `string.dump(f, mode)`.
|
|
///
|
|
/// # Panic
|
|
///
|
|
/// Panics if the value at index `idx` is not a function.
|
|
pub fn dump(&self, idx: c_int, mode: DumpMode) -> Result<BString> {
|
|
let func = self.slot(idx);
|
|
assert!(
|
|
func.type_of() == Type::Function,
|
|
"expected function at index {idx}: {self:?}"
|
|
);
|
|
|
|
unsafe {
|
|
let idx = func.index();
|
|
let mut s = self.guard_unchecked();
|
|
s.push("string");
|
|
s.get(LUA_GLOBALSINDEX);
|
|
s.push("dump");
|
|
s.get(-2); // local dump = string.dump
|
|
s.push(Index(idx));
|
|
s.push(mode.to_mode_str().to_bytes());
|
|
s.call(2, 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.
|
|
///
|
|
/// # Panic
|
|
///
|
|
/// Panics if there are not enough values on the stack or thread status is invalid.
|
|
pub fn eval(&mut self, chunk: &Chunk, narg: c_int, nret: c_int) -> Result<c_int> {
|
|
assert!(0 <= narg && (0 <= nret || nret == LUA_MULTRET));
|
|
let base = self.size() - narg;
|
|
assert!(base >= 0, "expected {narg} values: {self:?}");
|
|
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 [`LUA_MULTRET`], then the number of return values pushed
|
|
/// will be exactly `nret`, filling with nils if necessary. Finally, the number of returned
|
|
/// values pushed to the stack is returned.
|
|
///
|
|
/// The current thread status must not be suspended or dead.
|
|
///
|
|
/// Equivalent to [`lua_pcall`].
|
|
///
|
|
/// # Panic
|
|
///
|
|
/// 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: c_int, nret: c_int) -> Result<c_int> {
|
|
assert!(0 <= narg && (0 <= nret || nret == LUA_MULTRET));
|
|
|
|
let top = self.size();
|
|
let need = narg + 1; // need the function on the stack
|
|
let base = top - need;
|
|
|
|
assert!(base >= 0, "expected {need} values: {self:?}");
|
|
assert!(
|
|
self.slot(base + 1).type_of() == Type::Function,
|
|
"expected function at index {}: {self:?}",
|
|
base + 1
|
|
);
|
|
assert!(
|
|
self.status() == Status::Normal,
|
|
"thread {self:p} called in wrong state"
|
|
);
|
|
|
|
// TODO: use error handler to collect backtrace
|
|
match unsafe { lua_pcall(self.as_ptr(), narg, nret, 0) } {
|
|
LUA_OK => Ok(self.size() - base),
|
|
LUA_ERRMEM => {
|
|
self.pop(1);
|
|
Err(Error::OutOfMemory)
|
|
}
|
|
LUA_ERRRUN | LUA_ERRERR => {
|
|
let msg = self
|
|
.slot(-1)
|
|
.string()
|
|
.unwrap_or(b"unknown error".into())
|
|
.into();
|
|
|
|
self.pop(1);
|
|
Err(Error::Call { msg })
|
|
}
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
/// 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 [`LUA_MULTRET`], then the number of return values pushed
|
|
/// will be exactly `nret`, filling with nils if necessary. Finally, the number of returned
|
|
/// 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.
|
|
///
|
|
/// # Panic
|
|
///
|
|
/// 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: c_int, nret: c_int) -> Result<c_int> {
|
|
assert!(0 <= narg && (0 <= nret || nret == LUA_MULTRET));
|
|
|
|
let top = self.size();
|
|
let need = narg + 1; // need the function on the stack
|
|
let base = top - need;
|
|
|
|
assert!(base >= 0, "expected {need} values: {self:?}");
|
|
assert!(
|
|
self.slot(base + 1).type_of() == Type::Function,
|
|
"expected function at index {}: {self:?}",
|
|
base + 1
|
|
);
|
|
assert!(
|
|
self.status() == Status::Normal,
|
|
"thread {self:p} called in wrong state"
|
|
);
|
|
|
|
loop {
|
|
match self.resume(narg)? {
|
|
ResumeStatus::Ok => {
|
|
if nret == LUA_MULTRET {
|
|
break Ok(self.size() - base);
|
|
} else {
|
|
self.resize(base + nret);
|
|
break Ok(nret);
|
|
}
|
|
}
|
|
ResumeStatus::Suspended => {
|
|
narg = 0;
|
|
self.resize(1);
|
|
|
|
let ptr = self.slot(1).cdata::<lua_pollable>().cast_mut();
|
|
if !ptr.is_null() {
|
|
// 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>`.
|
|
let fut = unsafe { Pin::new_unchecked(&mut *ptr) };
|
|
if 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`].
|
|
///
|
|
/// # Panic
|
|
///
|
|
/// 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: c_int) -> Result<ResumeStatus> {
|
|
assert!(0 <= narg);
|
|
let status = self.status();
|
|
let need = match status {
|
|
Status::Normal => narg + 1, // need the function on the stack
|
|
Status::Suspended => narg,
|
|
Status::Dead => panic!("thread {self:p} resumed in wrong state"),
|
|
};
|
|
let base = self.size() - need;
|
|
assert!(base >= 0, "expected {need} values: {self:?}");
|
|
assert!(
|
|
status == Status::Suspended || self.slot(base + 1).type_of() == Type::Function,
|
|
"expected function at index {}: {self:?}",
|
|
base + 1
|
|
);
|
|
|
|
match unsafe { lua_resume(self.as_ptr(), narg) } {
|
|
LUA_OK => Ok(ResumeStatus::Ok),
|
|
LUA_YIELD => Ok(ResumeStatus::Suspended),
|
|
LUA_ERRMEM => {
|
|
self.pop(1);
|
|
Err(Error::OutOfMemory)
|
|
}
|
|
LUA_ERRRUN | LUA_ERRERR => {
|
|
let msg = self
|
|
.slot(-1)
|
|
.string()
|
|
.unwrap_or(b"unknown error".into())
|
|
.into();
|
|
|
|
self.pop(1);
|
|
let trace = self.backtrace(0);
|
|
Err(Error::Resume { msg, trace })
|
|
}
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
/// 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: c_int) -> Option<BString> {
|
|
assert!(level >= 0, "level must be nonnegative");
|
|
self.ensure(LUA_MINSTACK);
|
|
unsafe {
|
|
luaL_traceback(self.as_ptr(), self.as_ptr(), ptr::null(), 0);
|
|
|
|
// 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.into());
|
|
|
|
lua_pop(self.as_ptr(), 1);
|
|
trace
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for Stack {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
struct PointerValue(&'static str, *const c_void);
|
|
impl<'s> fmt::Debug for PointerValue {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
write!(f, "{} {:p}", self.0, self.1)
|
|
}
|
|
}
|
|
|
|
struct Values<'s>(&'s Stack);
|
|
impl<'s> fmt::Debug for Values<'s> {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
let mut list = f.debug_list();
|
|
for value in self.0.iter() {
|
|
match value.type_of() {
|
|
Type::Nil => list.entry(&"nil"),
|
|
Type::Boolean => list.entry(&value.boolean()),
|
|
Type::Number => list.entry(&value.number()),
|
|
Type::String => list.entry(&value.string().unwrap()),
|
|
ty => list.entry(&PointerValue(ty.name(), value.pointer())),
|
|
};
|
|
}
|
|
list.finish()
|
|
}
|
|
}
|
|
|
|
f.debug_struct("Stack")
|
|
.field("ptr", &self.0)
|
|
.field("values", &Values(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,
|
|
size: c_int,
|
|
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
|
|
size: 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) {
|
|
#[cfg(debug_assertions)]
|
|
if self.check_overpop {
|
|
let new_size = self.stack.size();
|
|
assert!(
|
|
self.size <= new_size,
|
|
"StackGuard detected over-popping by {} values (this is UB!)",
|
|
self.size - new_size
|
|
);
|
|
}
|
|
|
|
self.stack.resize(self.size);
|
|
}
|
|
}
|
|
|
|
/// Iterator over the values in a [`Stack`].
|
|
///
|
|
/// Can be obtained by [`Stack::iter`].
|
|
#[derive(Debug)]
|
|
pub struct StackIter<'s> {
|
|
stack: &'s Stack,
|
|
idx: c_int,
|
|
top: c_int,
|
|
}
|
|
|
|
impl<'s> StackIter<'s> {
|
|
fn new(stack: &'s Stack) -> Self {
|
|
let top = stack.size();
|
|
Self { stack, idx: 0, top }
|
|
}
|
|
}
|
|
|
|
impl<'s> Iterator for StackIter<'s> {
|
|
type Item = Slot<'s>;
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
(self.idx < self.top).then(|| {
|
|
self.idx += 1;
|
|
unsafe { Slot::new_unchecked(self.stack, self.idx) }
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Lua value handle into the stack.
|
|
pub struct Slot<'s> {
|
|
stack: &'s Stack,
|
|
idx: c_int,
|
|
}
|
|
|
|
impl<'s> Slot<'s> {
|
|
/// Creates a new [`Slot`] for given index into the stack.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// Index `idx` must be a valid absolute index.
|
|
pub unsafe fn new_unchecked(stack: &'s Stack, idx: c_int) -> Self {
|
|
Self { stack, idx }
|
|
}
|
|
|
|
/// Index of this slot within the stack.
|
|
///
|
|
/// This value is always a valid absolute positive index or a negative pseudo-index. It is never
|
|
/// a negative relative index.
|
|
pub fn index(&self) -> c_int {
|
|
self.idx
|
|
}
|
|
|
|
/// Type of the value in this slot.
|
|
pub fn type_of(&self) -> Type {
|
|
Type::from_code(unsafe { lua_type(self.stack.as_ptr(), self.idx) }).unwrap_or(Type::Nil)
|
|
}
|
|
|
|
/// Parses the value in this slot as a `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.
|
|
///
|
|
/// 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.type_of() == 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 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) }
|
|
}
|
|
|
|
/// Parses the value in this slot as a `cdata` pointer.
|
|
///
|
|
/// If the value is a `cdata`, then the returned pointer is the address of the base of the cdata
|
|
/// payload. Otherwise this returns a null pointer.
|
|
///
|
|
/// Equivalent to [`lua_topointer`].
|
|
pub fn cdata<T>(&self) -> *const T {
|
|
(self.type_of() == Type::Cdata)
|
|
.then(|| self.pointer().cast())
|
|
.unwrap_or(ptr::null_mut())
|
|
}
|
|
|
|
/// Parses the value in this slot as a generic pointer.
|
|
///
|
|
/// If the value is not a GC-managed object that can be represented by a pointer, 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).cast() }
|
|
}
|
|
|
|
/// Returns the length of the value in this slot.
|
|
///
|
|
/// For strings, this is the byte-length of the contents 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. For numbers, this is equivalent to converting the value in-place into a
|
|
/// `string` representation before calculating its length. Otherwise, this returns 0.
|
|
///
|
|
/// This function does not invoke the `__len` metamethod.
|
|
///
|
|
/// Equivalent to [`lua_objlen`].
|
|
pub fn length(&self) -> usize {
|
|
unsafe { lua_objlen(self.stack.as_ptr(), self.idx) }
|
|
}
|
|
}
|
|
|
|
/// 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) }
|
|
}
|
|
}
|
|
|
|
/// [`Push`]es a copy of the value at an index onto a [`Stack`].
|
|
///
|
|
/// Equivalent to [`lua_pushvalue`].
|
|
#[derive(Debug, Default, Clone, Copy, Hash)]
|
|
pub struct Index(
|
|
/// Index of the value to copy.
|
|
pub c_int,
|
|
);
|
|
|
|
impl Push for Index {
|
|
fn push(&self, stack: &mut Stack) {
|
|
stack.ensure(1);
|
|
unsafe { lua_pushvalue(stack.as_ptr(), stack.slot(self.0).index()) }
|
|
}
|
|
}
|
|
|
|
/// [`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: c_int,
|
|
/// Size of the preallocated hash part.
|
|
pub nrec: c_int,
|
|
}
|
|
|
|
impl NewTable {
|
|
/// Creates a new [`NewTable`] with no preallocations defined.
|
|
pub fn new() -> Self {
|
|
Self::new_sized(0, 0)
|
|
}
|
|
|
|
/// Creates a new [`NewTable`] with the array part set to preallocate `size`.
|
|
pub fn new_array(size: c_int) -> Self {
|
|
Self::new_sized(size, 0)
|
|
}
|
|
|
|
/// Creates a new [`NewTable`] with the hash part set to preallocate `size`.
|
|
pub fn new_record(size: c_int) -> Self {
|
|
Self::new_sized(0, size)
|
|
}
|
|
|
|
/// Creates a new [`NewTable`] with the array and hash parts set to preallocate `narr` and
|
|
/// `rec`.
|
|
pub fn new_sized(narr: c_int, nrec: c_int) -> Self {
|
|
Self { narr, nrec }
|
|
}
|
|
}
|
|
|
|
impl Push for NewTable {
|
|
fn push(&self, stack: &mut Stack) {
|
|
let Self { narr, nrec } = *self;
|
|
assert!(0 <= narr, "narr must be nonnegative");
|
|
assert!(0 <= nrec, "nrec must be nonnegative");
|
|
stack.ensure(1);
|
|
unsafe { lua_createtable(stack.as_ptr(), narr, nrec) }
|
|
}
|
|
}
|
|
|
|
/// [`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.type_of() {
|
|
Type::Nil => Ok(()),
|
|
ty => Err(Error::InvalidType("nil", ty.name())),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Parse<'_> for bool {
|
|
fn parse(slot: &Slot) -> Result<Self> {
|
|
Ok(unsafe { lua_toboolean(slot.stack.as_ptr(), slot.index()) != 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.idx) };
|
|
if !ptr.is_null() {
|
|
Ok(ptr as $type)
|
|
} else {
|
|
Err(Error::InvalidType("userdata", slot.type_of().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.idx, &raw mut isnum) };
|
|
if isnum != 0 {
|
|
Ok(n as $type)
|
|
} else {
|
|
Err(Error::InvalidType("number", slot.type_of().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.idx, &raw mut isnum) };
|
|
if isnum != 0 {
|
|
Ok(n as $type)
|
|
} else {
|
|
Err(Error::InvalidType("number", slot.type_of().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.idx, &mut len) };
|
|
if !ptr.is_null() {
|
|
Ok(unsafe { slice::from_raw_parts(ptr.cast(), len).into() })
|
|
} else {
|
|
Err(Error::InvalidType("string", slot.type_of().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)?)?.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);
|