luby/crates/luajit/lib.rs
2025-06-19 21:54:42 +10:00

1139 lines
30 KiB
Rust

#![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<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,
})
}
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<Self> {
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<lua_State>,
}
impl GlobalState {
pub fn new() -> Result<Self, Error> {
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<GlobalState>,
}
impl State {
pub fn new() -> Result<Self, Error> {
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<lua_State>,
}
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<Value<'s>> {
self.absindex(idx)
.map(|idx| unsafe { Value::new_unchecked(self, idx) })
}
fn absindex(&self, idx: c_int) -> Option<c_int> {
if LUA_REGISTRYINDEX < idx && idx <= 0 {
// SAFETY: must check any relative index that gets passed to index2adr in lj_api.c
// 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<T, Error> {
self.index(idx).parse()
}
pub fn boolean(&self, idx: c_int) -> bool {
self.index(idx).boolean()
}
pub fn lightuserdata<T>(&self, idx: c_int) -> *mut T {
self.index(idx).lightuserdata()
}
pub fn number(&self, idx: c_int) -> Option<lua_Number> {
self.index(idx).number()
}
pub fn integer(&self, idx: c_int) -> Option<lua_Integer> {
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<T>(&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<T: 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<impl AsRef<[u8]>>,
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<BString, Error> {
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<c_int, Error> {
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::<lua_pollable>().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<ResumeStatus, Error> {
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"<no stack trace>".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::Item> {
(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<T: Parse<'s>>(&self) -> Result<T, Error> {
T::parse(self)
}
pub fn boolean(&self) -> bool {
self.parse().unwrap_or(false)
}
pub fn lightuserdata<T>(&self) -> *mut T {
self.parse().unwrap_or(ptr::null_mut())
}
pub fn number(&self) -> Option<lua_Number> {
self.parse().ok()
}
pub fn integer(&self) -> Option<lua_Integer> {
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<T>(&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<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);
pub trait Parse<'s>: Sized {
fn parse(value: &Value<'s>) -> Result<Self, Error>;
}
impl Parse<'_> for () {
fn parse(value: &Value) -> Result<Self, Error> {
match value.type_of() {
Type::Nil => Ok(()),
ty => Err(Error::InvalidType("nil", ty.name())),
}
}
}
impl Parse<'_> for bool {
fn parse(value: &Value) -> Result<Self, Error> {
Ok(unsafe { lua_toboolean(value.stack.as_ptr(), value.idx) != 0 })
}
}
macro_rules! impl_parse_ptr {
($type:ty) => {
impl<T> Parse<'_> for $type {
fn parse(value: &Value) -> Result<Self, Error> {
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<Self, Error> {
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<Self, Error> {
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<Self, Error> {
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<Self, Error> {
Ok(std::str::from_utf8(Parse::parse(value)?)?.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);