#![allow(non_camel_case_types, non_snake_case)] use bitflags::bitflags; use bstr::{BStr, BString, ByteSlice}; use luaffi::future::lua_pollable; use std::{ alloc::{Layout, alloc, dealloc, realloc}, ffi::{CString, NulError}, fmt, ops::{Deref, DerefMut}, os::raw::{c_char, c_int, c_void}, process, ptr::{self, NonNull}, rc::Rc, slice, str::Utf8Error, }; use thiserror::Error; include!(env!("LJ_BINDINGS")); // constants not exposed by lua.h pub const LUA_TPROTO: c_int = LUA_TTHREAD + 1; pub const LUA_TCDATA: c_int = LUA_TTHREAD + 2; // macros not translated by bindgen pub unsafe fn lua_upvalueindex(i: c_int) -> c_int { LUA_GLOBALSINDEX - i } pub unsafe fn lua_pop(L: *mut lua_State, n: c_int) { unsafe { lua_settop(L, -n - 1) } } pub unsafe fn lua_newtable(L: *mut lua_State) { unsafe { lua_createtable(L, 0, 0) } } pub unsafe fn lua_register(L: *mut lua_State, n: *const c_char, f: lua_CFunction) { unsafe { (lua_pushcfunction(L, f), lua_setglobal(L, n)) }; } pub unsafe fn lua_pushcfunction(L: *mut lua_State, f: lua_CFunction) { unsafe { lua_pushcclosure(L, f, 0) } } pub unsafe fn lua_strlen(L: *mut lua_State, i: c_int) -> usize { unsafe { lua_objlen(L, i) } } pub unsafe fn lua_isfunction(L: *mut lua_State, n: c_int) -> c_int { unsafe { (lua_type(L, n) == LUA_TFUNCTION) as c_int } } pub unsafe fn lua_istable(L: *mut lua_State, n: c_int) -> c_int { unsafe { (lua_type(L, n) == LUA_TTABLE) as c_int } } pub unsafe fn lua_islightuserdata(L: *mut lua_State, n: c_int) -> c_int { unsafe { (lua_type(L, n) == LUA_TLIGHTUSERDATA) as c_int } } pub unsafe fn lua_isnil(L: *mut lua_State, n: c_int) -> c_int { unsafe { (lua_type(L, n) == LUA_TNIL) as c_int } } pub unsafe fn lua_isboolean(L: *mut lua_State, n: c_int) -> c_int { unsafe { (lua_type(L, n) == LUA_TBOOLEAN) as c_int } } pub unsafe fn lua_isthread(L: *mut lua_State, n: c_int) -> c_int { unsafe { (lua_type(L, n) == LUA_TTHREAD) as c_int } } pub unsafe fn lua_isnone(L: *mut lua_State, n: c_int) -> c_int { unsafe { (lua_type(L, n) == LUA_TNONE) as c_int } } pub unsafe fn lua_isnoneornil(L: *mut lua_State, n: c_int) -> c_int { unsafe { (lua_type(L, n) <= LUA_TNIL) as c_int } } pub unsafe fn lua_pushliteral(L: *mut lua_State, s: impl AsRef<[u8]>) { unsafe { lua_pushlstring(L, s.as_ref().as_ptr().cast(), s.as_ref().len()) } } pub unsafe fn lua_setglobal(L: *mut lua_State, s: *const c_char) { unsafe { lua_setfield(L, LUA_GLOBALSINDEX, s) } } pub unsafe fn lua_getglobal(L: *mut lua_State, s: *const c_char) { unsafe { lua_getfield(L, LUA_GLOBALSINDEX, s) } } pub unsafe fn lua_tostring(L: *mut lua_State, i: c_int) -> *const c_char { unsafe { lua_tolstring(L, i, ptr::null_mut()) } } pub unsafe fn lua_open() -> *mut lua_State { unsafe { luaL_newstate() } } pub unsafe fn lua_getregistry(L: *mut lua_State) { unsafe { lua_pushvalue(L, LUA_REGISTRYINDEX) } } pub unsafe fn lua_getgccount(L: *mut lua_State) -> c_int { unsafe { lua_gc(L, LUA_GCCOUNT, 0) } } #[derive(Debug, Error)] pub enum Error { #[error("out of memory")] OutOfMemory, #[error("{msg}")] Syntax { chunk: BString, msg: BString }, #[error("bad chunk name: {0}")] BadChunkName(NulError), #[error("{msg}")] Call { msg: BString }, #[error("{msg}")] Resume { msg: BString, trace: BString }, #[error("{0} expected, got {1}")] InvalidType(&'static str, &'static str), #[error("{0}")] InvalidUtf8(#[from] Utf8Error), } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub enum Type { #[default] Nil, Boolean, Lightuserdata, Number, String, Table, Function, Userdata, Thread, Cdata, } impl Type { 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, }) } 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()) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub enum Status { #[default] Normal, Dead, Suspended, } impl Status { 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, }) } 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()) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum ResumeStatus { #[default] Ok, Suspended, } impl ResumeStatus { 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! { pub struct LoadMode: usize { const AUTO = 0b_00_11; const TEXT = 0b_00_01; const BINARY = 0b_00_10; const GC64 = 0b_01_10; const GC32 = 0b_10_10; } } impl Default for LoadMode { fn default() -> Self { Self::AUTO } } impl LoadMode { fn mode_str(&self) -> CString { let mut s = String::new(); if self.contains(Self::TEXT) { s.push_str("t"); } if self.contains(Self::BINARY) { s.push_str("b"); if self.contains(Self::GC64) { s.push_str("X"); } else if self.contains(Self::GC32) { s.push_str("W"); } } CString::new(s).unwrap() } } bitflags! { pub struct DumpMode: usize { const DEFAULT = 0b_00_1_0; const STRIP = 0b_00_0_1; const DETERMINISTIC = 0b_00_1_0; const GC64 = 0b_01_0_0; const GC32 = 0b_10_0_0; } } impl Default for DumpMode { fn default() -> Self { Self::DEFAULT } } impl DumpMode { fn mode_str(&self) -> CString { let mut s = String::new(); if self.contains(Self::STRIP) { s.push_str("s"); } if self.contains(Self::DETERMINISTIC) { s.push_str("d"); } if self.contains(Self::GC64) { s.push_str("X"); } else if self.contains(Self::GC32) { s.push_str("W"); } CString::new(s).unwrap() } } #[derive(Debug)] struct GlobalState { ptr: NonNull, } impl GlobalState { pub fn new() -> Result { unsafe { 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 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("alloc error: requested osize is too large"); let new_layout = Layout::from_size_align(nsize, ALIGNOF_MAX_ALIGN_T) .expect("alloc error: requested nsize is too large"); if nsize != 0 { if osize != 0 { unsafe { realloc(ptr.cast(), old_layout, nsize).cast() } } else { unsafe { alloc(new_layout).cast() } } } else { if osize != 0 { unsafe { dealloc(ptr.cast(), old_layout) } } ptr::null_mut() } } unsafe extern "C" fn panic_cb(L: *mut lua_State) -> c_int { // we cannot recover from uncaught lua exceptions; abort instead let stack = unsafe { Stack::new_unchecked(L) }; let msg = stack.string(-1).unwrap_or(b"unknown lua panic".into()); eprintln!("lua panicked: {msg}"); process::abort() } pub 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()) } } } #[derive(Debug)] pub struct State { stack: Stack, ref_key: c_int, global: Rc, } impl State { pub fn new() -> Result { let global = Rc::new(GlobalState::new()?); let mut state = Self { stack: unsafe { Stack::new_unchecked(global.as_ptr()) }, ref_key: LUA_NOREF, global, }; state.push_function(Some(Self::open_cb)); state.call(0, 0)?; Ok(state) } unsafe extern "C" fn open_cb(L: *mut lua_State) -> c_int { unsafe { luaL_openlibs(L) } 0 } pub fn new_thread(&self) -> Self { // SAFETY: lua_newthread never returns null, but may panic on oom self.ensure(1); Self { stack: unsafe { Stack::new_unchecked(lua_newthread(self.as_ptr())) }, ref_key: unsafe { luaL_ref(self.as_ptr(), LUA_REGISTRYINDEX) }, global: Rc::clone(&self.global), } } } 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 } } impl Drop for State { fn drop(&mut self) { unsafe { luaL_unref(self.global.as_ptr(), LUA_REGISTRYINDEX, self.ref_key) } } } pub struct Stack { ptr: NonNull, } impl Stack { unsafe fn new_unchecked(ptr: *mut lua_State) -> Self { let ptr = unsafe { NonNull::new_unchecked(ptr) }; Self { ptr } } pub fn as_ptr(&self) -> *mut lua_State { self.ptr.as_ptr() } pub fn top(&self) -> c_int { unsafe { lua_gettop(self.as_ptr()) } } pub fn set_top(&mut self, idx: c_int) { assert!(0 <= idx, "cannot resize to {idx}"); // lua_settop throws on oom when growing, so we call ensure first let top = self.top(); (top < idx).then(|| self.ensure(idx - top)); unsafe { lua_settop(self.as_ptr(), idx) } } pub fn ensure(&self, n: c_int) { // lua_checkstack throws on oom in puc lua 5.1, but it is fine in luajit unsafe { assert!(lua_checkstack(self.as_ptr(), n) != 0, "stack out of memory") } } pub fn pop(&mut self, n: c_int) { assert!(0 <= n && n <= self.top(), "cannot pop {n}: {self:?}"); unsafe { lua_pop(self.as_ptr(), n) } } pub fn pop_replace(&mut self, idx: c_int) { assert!(1 <= self.top(), "cannot pop 1: {self:?}"); unsafe { lua_replace(self.as_ptr(), self.index(idx).index()) } } pub fn status(&self) -> Status { Status::from_code(unsafe { lua_status(self.as_ptr()) }).unwrap() } pub fn iter<'s>(&'s self) -> StackIter<'s> { StackIter::new(self) } pub fn guard<'s>(&'s mut self) -> StackGuard<'s> { StackGuard::new(self) } pub fn index<'s>(&'s self, idx: c_int) -> Value<'s> { self.try_index(idx) .unwrap_or_else(|| panic!("invalid index {idx}: {self:?}")) } pub fn try_index<'s>(&'s self, idx: c_int) -> Option> { self.absindex(idx) .map(|idx| unsafe { Value::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 // luajit doesn't check out-of-bounds access for relative indices with assertions disabled let top = self.top(); 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) } } pub fn type_of(&self, idx: c_int) -> Type { self.index(idx).type_of() } pub fn parse<'s, T: Parse<'s>>(&'s self, idx: c_int) -> Result { self.index(idx).parse() } pub fn boolean(&self, idx: c_int) -> bool { self.index(idx).boolean() } pub fn lightuserdata(&self, idx: c_int) -> *mut T { self.index(idx).lightuserdata() } pub fn number(&self, idx: c_int) -> Option { self.index(idx).number() } pub fn integer(&self, idx: c_int) -> Option { self.index(idx).integer() } pub fn string(&self, idx: c_int) -> Option<&BStr> { self.index(idx).string() } pub fn function(&self, idx: c_int) -> lua_CFunction { self.index(idx).function() } pub fn cdata(&self, idx: c_int) -> *const T { self.index(idx).cdata() } pub fn pointer(&self, idx: c_int) -> *const c_void { self.index(idx).pointer() } pub fn push(&mut self, value: T) { value.push(self) } pub fn push_table(&mut self) { self.ensure(1); unsafe { lua_newtable(self.as_ptr()) } } pub fn push_function(&mut self, f: lua_CFunction) { assert!(f.is_some(), "function must not be null"); self.ensure(1); unsafe { lua_pushcfunction(self.as_ptr(), f) } } pub fn push_thread(&mut self) -> bool { self.ensure(1); unsafe { lua_pushthread(self.as_ptr()) != 0 } } pub fn push_index(&mut self, idx: c_int) { self.ensure(1); unsafe { lua_pushvalue(self.as_ptr(), self.index(idx).index()) } } pub fn get(&mut self, idx: c_int) { assert!(1 <= self.top(), "expected 1 value: {self:?}"); unsafe { lua_rawget(self.as_ptr(), self.index(idx).index()) } } pub fn geti(&mut self, idx: c_int, n: c_int) { self.ensure(1); unsafe { lua_rawgeti(self.as_ptr(), self.index(idx).index(), n) } } pub fn set(&mut self, idx: c_int) { assert!(2 <= self.top(), "expected 2 values: {self:?}"); unsafe { lua_rawset(self.as_ptr(), self.index(idx).index()) } } pub fn seti(&mut self, idx: c_int, n: c_int) { assert!(1 <= self.top(), "expected 1 value: {self:?}"); unsafe { lua_rawseti(self.as_ptr(), self.index(idx).index(), n) } } pub fn load( &mut self, name: Option>, chunk: impl AsRef<[u8]>, mode: LoadMode, ) -> Result<(), Error> { let mode = mode.mode_str(); let name = name .map(|s| CString::new(s.as_ref())) .transpose() .map_err(Error::BadChunkName)? .unwrap_or(c"?".into()); type State<'s> = Option<&'s [u8]>; let mut state: State = Some(chunk.as_ref()); 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.string(-1).unwrap_or(b"unknown error".into()).into(); self.pop(1); Err(Error::Syntax { chunk, msg }) } _ => unreachable!(), } } pub fn dump(&mut self, idx: c_int, mode: DumpMode) -> Result { let value = self.index(idx); let idx = match value.type_of() { Type::Function => value.index(), _ => panic!("expected function at index {}: {self:?}", value.index()), }; let mut s = self.guard(); s.push("string"); s.get(LUA_GLOBALSINDEX); s.push("dump"); s.get(-2); // local dump = string.dump s.push_index(idx); s.push(mode.mode_str().as_bytes()); s.call(2, 1)?; s.index(-1).parse() // return dump(idx, mode) } pub fn call(&mut self, narg: c_int, nret: c_int) -> Result { assert!(0 <= narg && (0 <= nret || nret == LUA_MULTRET)); let top = self.top(); let need = narg + 1; // need the function on the stack assert!(need <= top, "expected {need} values: {self:?}"); assert!( self.status() == Status::Normal, "thread {:p} called in wrong state", self.as_ptr() ); let base = top - need; match unsafe { lua_pcall(self.as_ptr(), narg, nret, 0) } { LUA_OK => { let n = self.top() - base; assert!( n == nret || nret == LUA_MULTRET, "expected {nret} values, got {n}: {self:?}" ); Ok(n) } LUA_ERRMEM => { self.pop(1); Err(Error::OutOfMemory) } LUA_ERRRUN | LUA_ERRERR => { let msg = self.string(-1).unwrap_or(b"unknown error".into()).into(); self.pop(1); Err(Error::Call { msg }) } _ => unreachable!(), } } pub async fn call_async(&mut self, mut narg: c_int) -> Result<(), Error> { loop { match self.resume(narg)? { ResumeStatus::Ok => return Ok(()), ResumeStatus::Suspended => { self.set_top(1); let value = self.index(1); let ptr = value.cdata::().cast_mut(); if ptr.is_null() { return Err(Error::InvalidType("cdata", value.type_of().name())); } else { unsafe { (&mut *ptr).await } narg = 1; } } } } } pub fn resume(&mut self, narg: c_int) -> Result { assert!(0 <= narg); let need = match self.status() { Status::Suspended => narg, _ => narg + 1, // need the function on the stack }; assert!(need <= self.top(), "expected {need} values: {self:?}"); 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 => { unsafe { luaL_traceback(self.as_ptr(), self.as_ptr(), ptr::null(), 0) } let msg = self.string(-2).unwrap_or(b"unknown error".into()).into(); let trace = self .string(-1) .map(|s| s.strip_prefix(b"stack traceback:\n").unwrap_or(s).as_bstr()) .filter(|s| !s.is_empty()) .unwrap_or(b"".into()) .into(); self.pop(2); Err(Error::Resume { msg, trace }) } _ => unreachable!(), } } } 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.ptr) .field("values", &Values(self)) .finish() } } #[derive(Debug)] pub struct StackGuard<'s> { stack: &'s mut Stack, idx: c_int, } impl<'s> StackGuard<'s> { pub fn new(stack: &'s mut Stack) -> Self { let idx = stack.top(); Self { stack, idx } } } 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 { self.stack } } impl<'s> Drop for StackGuard<'s> { fn drop(&mut self) { self.stack.set_top(self.idx); } } #[derive(Debug)] pub struct StackIter<'s> { stack: &'s Stack, idx: c_int, top: c_int, } impl<'s> StackIter<'s> { pub fn new(stack: &'s Stack) -> Self { let top = stack.top(); Self { stack, idx: 0, top } } } impl<'s> Iterator for StackIter<'s> { type Item = Value<'s>; fn next(&mut self) -> Option { (self.idx < self.top).then(|| { self.idx += 1; unsafe { Value::new_unchecked(self.stack, self.idx) } }) } } pub struct Value<'s> { stack: &'s Stack, idx: c_int, } impl<'s> Value<'s> { unsafe fn new_unchecked(stack: &'s Stack, idx: c_int) -> Self { Self { stack, idx } } pub fn index(&self) -> c_int { self.idx } pub fn type_of(&self) -> Type { Type::from_code(unsafe { lua_type(self.stack.as_ptr(), self.idx) }).unwrap_or(Type::Nil) } pub fn parse>(&self) -> Result { T::parse(self) } pub fn boolean(&self) -> bool { self.parse().unwrap_or(false) } pub fn lightuserdata(&self) -> *mut T { self.parse().unwrap_or(ptr::null_mut()) } pub fn number(&self) -> Option { self.parse().ok() } pub fn integer(&self) -> Option { self.parse().ok() } pub fn string(&self) -> Option<&'s BStr> { self.parse().ok() } pub fn function(&self) -> lua_CFunction { unsafe { lua_tocfunction(self.stack.as_ptr(), self.idx) } } pub fn cdata(&self) -> *const T { (self.type_of() == Type::Cdata) .then(|| self.pointer().cast()) .unwrap_or(ptr::null_mut()) } pub fn pointer(&self) -> *const c_void { unsafe { lua_topointer(self.stack.as_ptr(), self.idx).cast() } } } pub trait Push { 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); pub trait Parse<'s>: Sized { fn parse(value: &Value<'s>) -> Result; } impl Parse<'_> for () { fn parse(value: &Value) -> Result { match value.type_of() { Type::Nil => Ok(()), ty => Err(Error::InvalidType("nil", ty.name())), } } } impl Parse<'_> for bool { fn parse(value: &Value) -> Result { Ok(unsafe { lua_toboolean(value.stack.as_ptr(), value.idx) != 0 }) } } macro_rules! impl_parse_ptr { ($type:ty) => { impl Parse<'_> for $type { fn parse(value: &Value) -> Result { let ptr = unsafe { lua_touserdata(value.stack.as_ptr(), value.idx) }; if !ptr.is_null() { Ok(ptr as $type) } else { Err(Error::InvalidType("lightuserdata", value.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(value: &Value) -> Result { let mut isnum = 0; let n = unsafe { lua_tonumberx(value.stack.as_ptr(), value.idx, &raw mut isnum) }; if isnum != 0 { Ok(n as $type) } else { Err(Error::InvalidType("number", value.type_of().name())) } } } }; } impl_parse_num!(f32); impl_parse_num!(f64); macro_rules! impl_parse_int { ($type:ty) => { impl Parse<'_> for $type { fn parse(value: &Value) -> Result { let mut isnum = 0; let n = unsafe { lua_tointegerx(value.stack.as_ptr(), value.idx, &raw mut isnum) }; if isnum != 0 { Ok(n as $type) } else { Err(Error::InvalidType("number", value.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(value: &Value<'s>) -> Result { let mut len = 0; let ptr = unsafe { lua_tolstring(value.stack.as_ptr(), value.idx, &mut len) }; if !ptr.is_null() { Ok(unsafe { slice::from_raw_parts(ptr.cast(), len).into() }) } else { Err(Error::InvalidType("string", value.type_of().name())) } } } }; } macro_rules! impl_parse_str_utf8 { ($type:ty) => { impl<'s> Parse<'s> for $type { fn parse(value: &Value<'s>) -> Result { Ok(std::str::from_utf8(Parse::parse(value)?)?.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);