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