#![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, }, /// 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 = ::std::result::Result; /// 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 { 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 { 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) -> 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> From for Chunk { fn from(value: T) -> Self { Self::new(value) } } #[derive(Debug)] struct GlobalState { ptr: NonNull, } impl GlobalState { pub fn new() -> Result { 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, 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 { 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); 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> { self.absindex(idx) .map(|idx| unsafe { Slot::new_unchecked(self, idx) }) } fn absindex(&self, idx: c_int) -> Option { 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(&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 { 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 { 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 { 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 { 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 { 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::().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`. 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 { 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 { 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.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>(&self) -> Result { T::parse(self) } /// Parses the value in this slot as a [`bool`]. /// /// If the value is not a `boolean`, then this returns false. /// /// Equivalent to [`lua_toboolean`]. pub fn boolean(&self) -> bool { self.parse().unwrap_or(false) } /// Parses the value in this slot as a `lightuserdata` pointer. /// /// If the value is not a `lightuserdata`, then this returns a null pointer. /// /// Equivalent to [`lua_touserdata`]. pub fn lightuserdata(&self) -> *mut T { (self.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 { self.parse().ok() } /// Parses the value in this slot as a [`lua_Integer`]. /// /// If the value is not a `number` or a `string` that can be parsed as an integer, then this /// returns [`None`]. /// /// Equivalent to [`lua_tointegerx`]. pub fn integer(&self) -> Option { self.parse().ok() } /// Parses the value in this slot as a binary string. /// /// If the value is a `number`, then it is converted in-place into a `string` representation of /// the value first. /// /// If the value is not a `string`, then this returns [`None`]. /// /// Equivalent to [`lua_tolstring`]. pub fn string(&self) -> Option<&'s BStr> { self.parse().ok() } /// Parses the value in this slot as a UTF-8 string. /// /// If the value is a `number`, then it is converted in-place into a `string` representation /// first. /// /// Equivalent to [`lua_tolstring`]. pub fn string_utf8(&self) -> Option<&'s str> { self.parse().ok() } /// Parses the value in this slot as a [`lua_CFunction`]. /// /// If the value is not a 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(&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 Push for &T { fn push(&self, stack: &mut Stack) { T::push(self, stack) } } impl Push for &mut T { fn push(&self, stack: &mut Stack) { T::push(self, stack) } } impl Push for Option { fn push(&self, stack: &mut Stack) { match self { Some(value) => value.push(stack), None => ().push(stack), } } } impl Push for () { fn push(&self, stack: &mut Stack) { stack.ensure(1); unsafe { lua_pushnil(stack.as_ptr()) } } } impl Push for bool { fn push(&self, stack: &mut Stack) { stack.ensure(1); unsafe { lua_pushboolean(stack.as_ptr(), *self as c_int) } } } macro_rules! impl_push_ptr { ($type:ty) => { impl Push for $type { fn push(&self, stack: &mut Stack) { stack.ensure(1); unsafe { lua_pushlightuserdata(stack.as_ptr(), *self as *mut c_void) } } } }; } impl_push_ptr!(*const T); impl_push_ptr!(*mut T); macro_rules! impl_push_num { ($type:ty) => { impl Push for $type { fn push(&self, stack: &mut Stack) { stack.ensure(1); unsafe { lua_pushnumber(stack.as_ptr(), *self as lua_Number) } } } }; } impl_push_num!(f32); impl_push_num!(f64); macro_rules! impl_push_int { ($type:ty) => { impl Push for $type { fn push(&self, stack: &mut Stack) { stack.ensure(1); unsafe { lua_pushinteger(stack.as_ptr(), *self as lua_Integer) } } } }; } impl_push_int!(u8); impl_push_int!(u16); impl_push_int!(u32); impl_push_int!(u64); impl_push_int!(usize); impl_push_int!(i8); impl_push_int!(i16); impl_push_int!(i32); impl_push_int!(i64); impl_push_int!(isize); macro_rules! impl_push_str { ($type:ty) => { impl Push for $type { fn push(&self, stack: &mut Stack) { stack.ensure(1); unsafe { lua_pushliteral(stack.as_ptr(), self) } } } }; } impl_push_str!(&[u8]); impl_push_str!(Vec); impl_push_str!(&BStr); impl_push_str!(BString); impl_push_str!(&str); impl_push_str!(String); impl Push for Ref { fn push(&self, stack: &mut Stack) { stack.ensure(1); unsafe { lua_rawgeti(stack.as_ptr(), LUA_REGISTRYINDEX, self.key) } } } /// [`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; } impl Parse<'_> for () { fn parse(slot: &Slot) -> Result { match slot.type_of() { Type::Nil => Ok(()), ty => Err(Error::InvalidType("nil", ty.name())), } } } impl Parse<'_> for bool { fn parse(slot: &Slot) -> Result { Ok(unsafe { lua_toboolean(slot.stack.as_ptr(), slot.index()) != 0 }) } } macro_rules! impl_parse_ptr { ($type:ty) => { impl Parse<'_> for $type { fn parse(slot: &Slot) -> Result { let ptr = unsafe { lua_touserdata(slot.stack.as_ptr(), slot.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 { 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 { 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 { 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 { Ok(std::str::from_utf8(Parse::parse(slot)?)?.into()) } } }; } impl_parse_str!(&'s [u8]); impl_parse_str!(&'s BStr); impl_parse_str!(Vec); impl_parse_str!(BString); impl_parse_str_utf8!(&'s str); impl_parse_str_utf8!(String);