use crate::gc::{
CClosurePtr, FunctionPtr, GcCClosure, GcFunction, GcObjectPtr, GcRClosure, GcString, GcTable,
GcThread, GcUserdata, RClosurePtr, StringPtr, TablePtr, ThreadPtr, UserdataPtr,
};
use crate::lua_value::{CClosureFunction, LuaUserdata, RClosureFunction, short_string_ptr_eq};
use crate::lua_vm::{CFunction, LuaState};
use crate::{LuaRawFunction, LuaRawTable};
pub const LUA_TNIL: u8 = 0;
pub const LUA_TBOOLEAN: u8 = 1;
pub const LUA_TLIGHTUSERDATA: u8 = 2;
pub const LUA_TNUMBER: u8 = 3;
pub const LUA_TSTRING: u8 = 4;
pub const LUA_TTABLE: u8 = 5;
pub const LUA_TFUNCTION: u8 = 6;
pub const LUA_TUSERDATA: u8 = 7;
pub const LUA_TTHREAD: u8 = 8;
macro_rules! makevariant {
($base:expr, $variant:expr) => {
$base | ($variant << 4)
};
}
pub const LUA_VNIL: u8 = makevariant!(LUA_TNIL, 0);
pub const LUA_VEMPTY: u8 = makevariant!(LUA_TNIL, 1); pub const LUA_VABSTKEY: u8 = makevariant!(LUA_TNIL, 2);
pub const LUA_VFALSE: u8 = makevariant!(LUA_TBOOLEAN, 0);
pub const LUA_VTRUE: u8 = makevariant!(LUA_TBOOLEAN, 1);
pub const LUA_VNUMINT: u8 = makevariant!(LUA_TNUMBER, 0); pub const LUA_VNUMFLT: u8 = makevariant!(LUA_TNUMBER, 1);
pub const LUA_VLIGHTUSERDATA: u8 = makevariant!(LUA_TLIGHTUSERDATA, 0);
pub const BIT_ISCOLLECTABLE: u8 = 1 << 6;
pub const LUA_VSHRSTR: u8 = LUA_TSTRING | BIT_ISCOLLECTABLE; pub const LUA_VLNGSTR: u8 = makevariant!(LUA_TSTRING, 1) | BIT_ISCOLLECTABLE;
pub const LUA_VTABLE: u8 = LUA_TTABLE | BIT_ISCOLLECTABLE;
pub const LUA_VFUNCTION: u8 = makevariant!(LUA_TFUNCTION, 0) | BIT_ISCOLLECTABLE; pub const LUA_CCLOSURE: u8 = makevariant!(LUA_TFUNCTION, 1) | BIT_ISCOLLECTABLE; pub const LUA_VLCF: u8 = makevariant!(LUA_TFUNCTION, 2); pub const LUA_VRCLOSURE: u8 = makevariant!(LUA_TFUNCTION, 3) | BIT_ISCOLLECTABLE;
pub const LUA_VUSERDATA: u8 = LUA_TUSERDATA | BIT_ISCOLLECTABLE; pub const LUA_VTHREAD: u8 = LUA_TTHREAD | BIT_ISCOLLECTABLE;
#[inline(always)]
pub const fn novariant(tt: u8) -> u8 {
tt & 0x0F
}
#[allow(unused)]
#[inline(always)]
pub const fn withvariant(tt: u8) -> u8 {
tt & 0x3F
}
#[derive(Clone, Copy)]
#[repr(C)]
pub union Value {
pub ptr: *const u8, pub p: *mut std::ffi::c_void, pub f: CFunction, pub i: i64, pub n: f64, }
impl Value {
pub const NIL: Self = Value { i: 0 };
#[inline(always)]
pub const fn integer(i: i64) -> Self {
Value { i }
}
#[inline(always)]
pub fn float(n: f64) -> Self {
Value { n }
}
#[inline(always)]
pub fn lightuserdata(p: *mut std::ffi::c_void) -> Self {
Value { p }
}
#[inline(always)]
pub fn cfunction(f: CFunction) -> Self {
Value { f }
}
}
#[derive(Clone, Copy)]
#[repr(C)]
pub struct LuaValue {
pub(crate) value: Value, pub(crate) tt: u8, }
impl LuaValue {
#[inline(always)]
fn raw_ptr(&self) -> *const u8 {
unsafe { self.value.ptr }
}
#[inline(always)]
fn raw_i64(&self) -> i64 {
unsafe { self.value.i }
}
#[inline(always)]
fn raw_f64(&self) -> f64 {
unsafe { self.value.n }
}
#[inline(always)]
fn raw_lightuserdata(&self) -> *mut std::ffi::c_void {
unsafe { self.value.p }
}
#[inline(always)]
fn raw_cfunction(&self) -> CFunction {
unsafe { self.value.f }
}
#[inline(always)]
fn raw_gc_const<T>(&self) -> *const T {
self.raw_ptr() as *const T
}
#[inline(always)]
fn raw_gc_mut<T>(&self) -> *mut T {
self.raw_ptr() as *mut T
}
#[inline(always)]
fn gc_ref<T>(&self) -> &T {
unsafe { &*self.raw_gc_const::<T>() }
}
#[allow(clippy::mut_from_ref)]
#[inline(always)]
fn gc_mut<T>(&self) -> &mut T {
unsafe { &mut *self.raw_gc_mut::<T>() }
}
#[inline(always)]
fn gc_string(&self) -> &GcString {
debug_assert!(self.ttisstring());
self.gc_ref::<GcString>()
}
#[allow(clippy::mut_from_ref)]
#[inline(always)]
fn gc_string_mut(&self) -> &mut GcString {
debug_assert!(self.ttisstring());
self.gc_mut::<GcString>()
}
#[inline(always)]
fn gc_table(&self) -> &GcTable {
debug_assert!(self.ttistable());
self.gc_ref::<GcTable>()
}
#[allow(clippy::mut_from_ref)]
#[inline(always)]
fn gc_table_mut(&self) -> &mut GcTable {
debug_assert!(self.ttistable());
self.gc_mut::<GcTable>()
}
#[inline(always)]
fn gc_function(&self) -> &GcFunction {
debug_assert!(self.ttisluafunction() || self.ttisfunction());
self.gc_ref::<GcFunction>()
}
#[allow(clippy::mut_from_ref)]
#[inline(always)]
fn gc_function_mut(&self) -> &mut GcFunction {
debug_assert!(self.ttisluafunction() || self.ttisfunction());
self.gc_mut::<GcFunction>()
}
#[inline(always)]
fn gc_cclosure(&self) -> &GcCClosure {
debug_assert!(self.is_cclosure());
self.gc_ref::<GcCClosure>()
}
#[allow(clippy::mut_from_ref)]
#[inline(always)]
fn gc_cclosure_mut(&self) -> &mut GcCClosure {
debug_assert!(self.is_cclosure());
self.gc_mut::<GcCClosure>()
}
#[inline(always)]
fn gc_rclosure(&self) -> &GcRClosure {
debug_assert!(self.is_rclosure());
self.gc_ref::<GcRClosure>()
}
#[allow(clippy::mut_from_ref)]
#[inline(always)]
fn gc_rclosure_mut(&self) -> &mut GcRClosure {
debug_assert!(self.is_rclosure());
self.gc_mut::<GcRClosure>()
}
#[allow(clippy::mut_from_ref)]
#[inline(always)]
fn gc_userdata_mut(&self) -> &mut GcUserdata {
debug_assert!(self.ttisfulluserdata());
self.gc_mut::<GcUserdata>()
}
#[allow(clippy::mut_from_ref)]
#[inline(always)]
fn gc_thread_mut(&self) -> &mut GcThread {
debug_assert!(self.ttisthread());
self.gc_mut::<GcThread>()
}
#[inline(always)]
pub(crate) fn table_ptr_raw(&self) -> TablePtr {
TablePtr::new(self.raw_gc_mut::<GcTable>())
}
#[inline(always)]
pub(crate) fn string_ptr_raw(&self) -> StringPtr {
StringPtr::new(self.raw_gc_mut::<GcString>())
}
#[inline(always)]
fn function_ptr_raw(&self) -> FunctionPtr {
FunctionPtr::new(self.raw_gc_mut::<GcFunction>())
}
#[inline(always)]
fn cclosure_ptr_raw(&self) -> CClosurePtr {
CClosurePtr::new(self.raw_gc_mut::<GcCClosure>())
}
#[inline(always)]
fn rclosure_ptr_raw(&self) -> RClosurePtr {
RClosurePtr::new(self.raw_gc_mut::<GcRClosure>())
}
#[inline(always)]
fn userdata_ptr_raw(&self) -> UserdataPtr {
UserdataPtr::new(self.raw_gc_mut::<GcUserdata>())
}
#[inline(always)]
fn thread_ptr_raw(&self) -> ThreadPtr {
ThreadPtr::new(self.raw_gc_mut::<GcThread>())
}
#[inline(always)]
pub fn tt(&self) -> u8 {
self.tt
}
#[inline(always)]
pub const fn from_raw(value: Value, tt: u8) -> Self {
Self { value, tt }
}
#[inline(always)]
pub const fn nil() -> Self {
Self {
value: Value::NIL,
tt: LUA_VNIL,
}
}
#[inline(always)]
pub const fn boolean(b: bool) -> Self {
Self {
value: Value::NIL,
tt: if b { LUA_VTRUE } else { LUA_VFALSE },
}
}
#[inline(always)]
pub const fn integer(i: i64) -> Self {
Self {
value: Value::integer(i),
tt: LUA_VNUMINT,
}
}
#[inline(always)]
pub fn float(n: f64) -> Self {
Self {
value: Value::float(n),
tt: LUA_VNUMFLT,
}
}
#[inline(always)]
pub fn number(n: f64) -> Self {
Self::float(n)
}
#[inline(always)]
pub fn shortstring(ptr: StringPtr) -> Self {
Self {
value: Value {
ptr: ptr.as_ptr() as *const u8,
},
tt: LUA_VSHRSTR,
}
}
#[inline(always)]
pub fn longstring(ptr: StringPtr) -> Self {
Self {
value: Value {
ptr: ptr.as_ptr() as *const u8,
},
tt: LUA_VLNGSTR,
}
}
#[inline(always)]
pub fn table(ptr: TablePtr) -> Self {
Self {
value: Value {
ptr: ptr.as_ptr() as *const u8,
},
tt: LUA_VTABLE,
}
}
#[inline(always)]
pub fn function(ptr: FunctionPtr) -> Self {
Self {
value: Value {
ptr: ptr.as_ptr() as *const u8,
},
tt: LUA_VFUNCTION,
}
}
#[inline(always)]
pub fn cfunction(f: CFunction) -> Self {
Self {
value: Value::cfunction(f),
tt: LUA_VLCF,
}
}
#[inline(always)]
pub fn cclosure(ptr: CClosurePtr) -> Self {
Self {
value: Value {
ptr: ptr.as_ptr() as *const u8,
},
tt: LUA_CCLOSURE,
}
}
#[inline(always)]
pub fn rclosure(ptr: RClosurePtr) -> Self {
Self {
value: Value {
ptr: ptr.as_ptr() as *const u8,
},
tt: LUA_VRCLOSURE,
}
}
#[inline(always)]
pub fn lightuserdata(p: *mut std::ffi::c_void) -> Self {
Self {
value: Value::lightuserdata(p),
tt: LUA_VLIGHTUSERDATA,
}
}
#[inline(always)]
pub fn userdata(ptr: UserdataPtr) -> Self {
Self {
value: Value {
ptr: ptr.as_ptr() as *const u8,
},
tt: LUA_VUSERDATA,
}
}
#[inline(always)]
pub fn thread(ptr: ThreadPtr) -> Self {
Self {
value: Value {
ptr: ptr.as_ptr() as *const u8,
},
tt: LUA_VTHREAD,
}
}
#[inline(always)]
pub(crate) fn ttype(&self) -> u8 {
novariant(self.tt())
}
#[inline(always)]
pub(crate) fn checktag(&self, t: u8) -> bool {
self.tt() == t
}
#[inline(always)]
pub(crate) fn checktype(&self, t: u8) -> bool {
novariant(self.tt()) == t
}
#[inline(always)]
pub(crate) fn iscollectable(&self) -> bool {
(self.tt() & BIT_ISCOLLECTABLE) != 0
}
#[inline(always)]
pub(crate) fn is_collectable(&self) -> bool {
self.iscollectable()
}
#[inline(always)]
pub(crate) fn ttisnil(&self) -> bool {
self.checktype(LUA_TNIL)
}
#[allow(unused)]
#[inline(always)]
pub(crate) fn ttisstrictnil(&self) -> bool {
self.checktag(LUA_VNIL)
}
#[allow(unused)]
#[inline(always)]
pub(crate) fn ttisempty(&self) -> bool {
self.checktag(LUA_VEMPTY)
}
#[allow(unused)]
#[inline(always)]
pub(crate) fn isabstkey(&self) -> bool {
self.checktag(LUA_VABSTKEY)
}
#[inline(always)]
pub(crate) fn ttisboolean(&self) -> bool {
self.checktype(LUA_TBOOLEAN)
}
#[inline(always)]
pub(crate) fn ttisfalse(&self) -> bool {
self.checktag(LUA_VFALSE)
}
#[allow(unused)]
#[inline(always)]
pub(crate) fn ttistrue(&self) -> bool {
self.checktag(LUA_VTRUE)
}
#[inline(always)]
pub(crate) fn ttisnumber(&self) -> bool {
self.checktype(LUA_TNUMBER)
}
#[inline(always)]
pub(crate) fn ttisinteger(&self) -> bool {
self.checktag(LUA_VNUMINT)
}
#[inline(always)]
pub(crate) fn ttisfloat(&self) -> bool {
self.checktag(LUA_VNUMFLT)
}
#[inline(always)]
pub(crate) fn ttisstring(&self) -> bool {
self.checktype(LUA_TSTRING)
}
#[inline(always)]
pub(crate) fn ttistable(&self) -> bool {
self.checktag(LUA_VTABLE)
}
#[inline(always)]
pub(crate) fn ttisfunction(&self) -> bool {
self.checktype(LUA_TFUNCTION)
}
#[inline(always)]
pub(crate) fn ttisluafunction(&self) -> bool {
self.checktag(LUA_VFUNCTION)
}
#[inline(always)]
pub(crate) fn ttiscfunction(&self) -> bool {
self.checktag(LUA_VLCF)
}
#[allow(unused)]
#[inline(always)]
pub(crate) fn ttislightuserdata(&self) -> bool {
self.checktag(LUA_VLIGHTUSERDATA)
}
#[inline(always)]
pub(crate) fn ttisfulluserdata(&self) -> bool {
self.checktag(LUA_VUSERDATA)
}
#[inline(always)]
pub(crate) fn ttisthread(&self) -> bool {
self.checktag(LUA_VTHREAD)
}
#[inline(always)]
pub(crate) fn bvalue(&self) -> bool {
debug_assert!(self.ttisboolean());
self.tt() == LUA_VTRUE
}
#[inline(always)]
pub(crate) fn ivalue(&self) -> i64 {
debug_assert!(self.ttisinteger());
self.raw_i64()
}
#[inline(always)]
pub(crate) fn fltvalue(&self) -> f64 {
debug_assert!(self.ttisfloat());
self.raw_f64()
}
#[inline(always)]
pub(crate) fn nvalue(&self) -> f64 {
debug_assert!(self.ttisnumber());
if self.ttisinteger() {
self.raw_i64() as f64
} else {
self.raw_f64()
}
}
#[allow(unused)]
#[inline(always)]
pub(crate) fn pvalue(&self) -> *mut std::ffi::c_void {
debug_assert!(self.ttislightuserdata());
self.raw_lightuserdata()
}
#[inline(always)]
pub(crate) fn fvalue(&self) -> CFunction {
debug_assert!(self.ttiscfunction());
self.raw_cfunction()
}
#[inline(always)]
pub fn is_nil(&self) -> bool {
self.ttisnil()
}
#[inline(always)]
pub fn is_boolean(&self) -> bool {
self.ttisboolean()
}
#[inline(always)]
pub fn is_integer(&self) -> bool {
self.ttisinteger()
}
#[inline(always)]
pub fn is_float(&self) -> bool {
self.ttisfloat()
}
#[inline(always)]
pub fn is_number(&self) -> bool {
self.ttisnumber()
}
#[inline(always)]
pub fn is_string(&self) -> bool {
self.ttisstring()
}
#[inline(always)]
pub fn is_short_string(&self) -> bool {
self.checktag(LUA_VSHRSTR)
}
#[inline(always)]
pub fn is_binary(&self) -> bool {
self.ttisstring() && self.as_str().is_none()
}
#[inline(always)]
pub fn is_table(&self) -> bool {
self.ttistable()
}
#[inline(always)]
pub fn is_function(&self) -> bool {
self.ttisfunction()
}
#[inline(always)]
pub fn is_lua_function(&self) -> bool {
self.ttisluafunction()
}
#[inline(always)]
pub fn is_cfunction(&self) -> bool {
self.ttiscfunction()
}
#[inline(always)]
pub fn is_cclosure(&self) -> bool {
self.checktag(LUA_CCLOSURE)
}
#[inline(always)]
pub fn is_rclosure(&self) -> bool {
self.checktag(LUA_VRCLOSURE)
}
#[inline(always)]
pub fn is_c_callable(&self) -> bool {
self.is_cfunction() || self.is_cclosure() || self.is_rclosure()
}
#[inline(always)]
pub fn is_userdata(&self) -> bool {
self.ttisfulluserdata()
}
#[inline(always)]
pub fn is_lightuserdata(&self) -> bool {
self.ttislightuserdata()
}
#[inline(always)]
pub fn is_thread(&self) -> bool {
self.ttisthread()
}
#[inline(always)]
pub fn is_callable(&self) -> bool {
self.ttisfunction()
}
#[inline(always)]
pub fn as_boolean(&self) -> Option<bool> {
if self.ttisboolean() {
Some(self.bvalue())
} else {
None
}
}
#[inline(always)]
pub fn as_bool(&self) -> Option<bool> {
self.as_boolean()
}
#[inline(always)]
pub fn as_integer_strict(&self) -> Option<i64> {
if self.ttisinteger() {
Some(self.ivalue())
} else {
None
}
}
#[inline(always)]
pub fn as_integer(&self) -> Option<i64> {
if self.ttisinteger() {
Some(self.ivalue())
} else if self.ttisfloat() {
let f = self.fltvalue();
if f >= (i64::MIN as f64) && f < -(i64::MIN as f64) && f == (f as i64 as f64) {
Some(f as i64)
} else {
None
}
} else {
None
}
}
#[inline(always)]
pub fn as_float(&self) -> Option<f64> {
if self.ttisfloat() {
Some(self.fltvalue())
} else if self.ttisinteger() {
Some(self.ivalue() as f64)
} else {
None
}
}
#[inline(always)]
pub fn as_number(&self) -> Option<f64> {
if self.ttisnumber() {
Some(self.nvalue())
} else {
None
}
}
#[inline(always)]
pub fn as_str(&self) -> Option<&str> {
if self.ttisstring() {
self.gc_string().data.as_str()
} else {
None
}
}
#[inline(always)]
pub fn as_binary(&self) -> Option<&[u8]> {
if self.ttisstring() && self.as_str().is_none() {
Some(self.gc_string().data.as_bytes())
} else {
None
}
}
#[inline(always)]
pub fn as_bytes(&self) -> Option<&[u8]> {
if self.ttisstring() {
Some(self.gc_string().data.as_bytes())
} else {
None
}
}
#[inline(always)]
pub fn as_str_bytes(&self) -> Option<&[u8]> {
self.as_bytes()
}
#[inline(always)]
pub fn as_table(&self) -> Option<&LuaRawTable> {
if self.ttistable() {
Some(&self.gc_table().data)
} else {
None
}
}
#[inline(always)]
pub(crate) fn hvalue(&self) -> &LuaRawTable {
&self.gc_table().data
}
#[allow(clippy::mut_from_ref)]
#[inline(always)]
pub(crate) fn hvalue_mut(&self) -> &mut LuaRawTable {
&mut self.gc_table_mut().data
}
#[allow(clippy::mut_from_ref)]
#[inline(always)]
pub fn as_table_mut(&self) -> Option<&mut LuaRawTable> {
if self.ttistable() {
Some(&mut self.gc_table_mut().data)
} else {
None
}
}
#[inline(always)]
pub fn as_lua_function(&self) -> Option<&LuaRawFunction> {
if self.ttisluafunction() {
Some(&self.gc_function().data)
} else {
None
}
}
#[inline(always)]
pub(crate) unsafe fn as_lua_function_unchecked(&self) -> &LuaRawFunction {
&self.gc_function().data
}
#[allow(clippy::mut_from_ref)]
#[inline(always)]
pub fn as_lua_function_mut(&self) -> Option<&mut LuaRawFunction> {
if self.ttisluafunction() {
Some(&mut self.gc_function_mut().data)
} else {
None
}
}
#[inline(always)]
pub fn as_cfunction(&self) -> Option<CFunction> {
if self.ttiscfunction() {
Some(self.fvalue())
} else {
None
}
}
#[inline(always)]
pub fn as_cclosure(&self) -> Option<&CClosureFunction> {
if self.is_cclosure() {
Some(&self.gc_cclosure().data)
} else {
None
}
}
#[allow(clippy::mut_from_ref)]
#[inline(always)]
pub fn as_cclosure_mut(&self) -> Option<&mut CClosureFunction> {
if self.is_cclosure() {
Some(&mut self.gc_cclosure_mut().data)
} else {
None
}
}
#[inline(always)]
pub fn as_rclosure(&self) -> Option<&RClosureFunction> {
if self.is_rclosure() {
Some(&self.gc_rclosure().data)
} else {
None
}
}
#[allow(clippy::mut_from_ref)]
#[inline(always)]
pub fn as_rclosure_mut(&self) -> Option<&mut RClosureFunction> {
if self.is_rclosure() {
Some(&mut self.gc_rclosure_mut().data)
} else {
None
}
}
#[inline(always)]
pub fn as_rclosure_ptr(&self) -> Option<RClosurePtr> {
if self.is_rclosure() {
Some(self.rclosure_ptr_raw())
} else {
None
}
}
#[allow(clippy::mut_from_ref)]
#[inline(always)]
pub fn as_userdata_mut(&self) -> Option<&mut LuaUserdata> {
if self.ttisfulluserdata() {
Some(&mut self.gc_userdata_mut().data)
} else {
None
}
}
#[inline(always)]
pub fn as_lightuserdata(&self) -> Option<*mut std::ffi::c_void> {
if self.ttislightuserdata() {
Some(self.pvalue())
} else {
None
}
}
#[allow(clippy::mut_from_ref)]
#[inline(always)]
pub fn as_thread_mut(&self) -> Option<&mut LuaState> {
if self.ttisthread() {
Some(&mut self.gc_thread_mut().data)
} else {
None
}
}
pub fn as_table_ptr(&self) -> Option<TablePtr> {
if self.ttistable() {
Some(self.table_ptr_raw())
} else {
None
}
}
#[inline(always)]
pub fn as_string_ptr(&self) -> Option<StringPtr> {
if self.ttisstring() {
Some(self.string_ptr_raw())
} else {
None
}
}
#[inline(always)]
pub fn as_function_ptr(&self) -> Option<FunctionPtr> {
if self.ttisfunction() {
Some(self.function_ptr_raw())
} else {
None
}
}
#[inline(always)]
pub fn as_cclosure_ptr(&self) -> Option<CClosurePtr> {
if self.is_cclosure() {
Some(self.cclosure_ptr_raw())
} else {
None
}
}
#[inline(always)]
pub fn as_userdata_ptr(&self) -> Option<UserdataPtr> {
if self.ttisfulluserdata() {
Some(self.userdata_ptr_raw())
} else {
None
}
}
#[inline(always)]
pub fn as_thread_ptr(&self) -> Option<ThreadPtr> {
if self.ttisthread() {
Some(self.thread_ptr_raw())
} else {
None
}
}
#[inline(always)]
pub fn as_gc_ptr(&self) -> Option<GcObjectPtr> {
match self.kind() {
LuaValueKind::Table => self.as_table_ptr().map(GcObjectPtr::from),
LuaValueKind::Function => self.as_function_ptr().map(GcObjectPtr::from),
LuaValueKind::CClosure => self.as_cclosure_ptr().map(GcObjectPtr::from),
LuaValueKind::RClosure => self.as_rclosure_ptr().map(GcObjectPtr::from),
LuaValueKind::String => self.as_string_ptr().map(GcObjectPtr::from),
LuaValueKind::Thread => self.as_thread_ptr().map(GcObjectPtr::from),
LuaValueKind::Userdata => self.as_userdata_ptr().map(GcObjectPtr::from),
_ => None,
}
}
#[inline(always)]
pub(crate) fn as_gc_ptr_unchecked(&self) -> GcObjectPtr {
let tag = match self.tt {
LUA_VTABLE => GcObjectPtr::TAG_TABLE,
LUA_VFUNCTION => GcObjectPtr::TAG_FUNCTION,
LUA_CCLOSURE => GcObjectPtr::TAG_CCLOSURE,
LUA_VRCLOSURE => GcObjectPtr::TAG_RCLOSURE,
LUA_VSHRSTR | LUA_VLNGSTR => GcObjectPtr::TAG_STRING,
LUA_VTHREAD => GcObjectPtr::TAG_THREAD,
LUA_VUSERDATA => GcObjectPtr::TAG_USERDATA,
_ => GcObjectPtr::TAG_NONE,
};
GcObjectPtr::new_tagged(self.raw_ptr() as u64, tag)
}
#[inline(always)]
pub fn is_truthy(&self) -> bool {
!self.is_falsy()
}
#[inline(always)]
pub fn is_falsy(&self) -> bool {
self.ttisnil() || self.ttisfalse()
}
pub fn type_name(&self) -> &'static str {
match self.ttype() {
LUA_TNIL => "nil",
LUA_TBOOLEAN => "boolean",
LUA_TNUMBER => "number",
LUA_TSTRING => "string",
LUA_TTABLE => "table",
LUA_TFUNCTION => "function",
LUA_TLIGHTUSERDATA => "userdata",
LUA_TUSERDATA => "userdata",
LUA_TTHREAD => "thread",
_ => "unknown",
}
}
pub fn kind(&self) -> LuaValueKind {
match self.ttype() {
LUA_TNIL => LuaValueKind::Nil,
LUA_TBOOLEAN => LuaValueKind::Boolean,
LUA_TNUMBER => {
if self.ttisinteger() {
LuaValueKind::Integer
} else {
LuaValueKind::Float
}
}
LUA_TSTRING => LuaValueKind::String,
LUA_TTABLE => LuaValueKind::Table,
LUA_TFUNCTION => {
if self.ttiscfunction() {
LuaValueKind::CFunction
} else if self.is_cclosure() {
LuaValueKind::CClosure
} else if self.is_rclosure() {
LuaValueKind::RClosure
} else {
LuaValueKind::Function
}
}
LUA_TLIGHTUSERDATA | LUA_TUSERDATA => LuaValueKind::Userdata,
LUA_TTHREAD => LuaValueKind::Thread,
_ => LuaValueKind::Nil,
}
}
pub fn raw_ptr_repr(&self) -> *const u8 {
self.raw_ptr()
}
#[inline(always)]
pub fn hash_value(&self) -> u64 {
let tt = self.tt();
if tt == LUA_VSHRSTR {
return self.gc_string().data.hash;
}
if tt == LUA_VLNGSTR {
let gs = self.gc_string();
let hash = gs.data.hash;
if hash != 0 {
return hash;
}
let computed = compute_long_string_hash(gs.data.str.as_bytes());
self.gc_string_mut().data.hash = computed;
return computed;
}
if tt == LUA_VNUMINT {
return self.raw_i64() as u64;
}
if tt == LUA_VNUMFLT {
let mut h = self.raw_f64().to_bits();
h ^= h >> 30;
h = h.wrapping_mul(0xbf58476d1ce4e5b9);
h ^= h >> 27;
h = h.wrapping_mul(0x94d049bb133111eb);
h ^= h >> 31;
return h;
}
self.raw_i64() as u64
}
}
#[inline(always)]
fn lua_float_eq_int(f: f64, i: i64) -> bool {
if !f.is_finite() {
return false;
}
if f != f.floor() {
return false;
}
if f < i64::MIN as f64 || f >= (i64::MAX as f64) + 1.0 {
return false;
}
(f as i64) == i
}
impl PartialEq for LuaValue {
#[inline(always)]
fn eq(&self, other: &Self) -> bool {
let tt = self.tt();
let other_tt = other.tt();
if tt == other_tt {
if tt == LUA_VNUMFLT {
return self.raw_f64() == other.raw_f64();
}
if self.raw_i64() == other.raw_i64() {
return true;
}
return match tt {
LUA_VSHRSTR => {
let left = self.string_ptr_raw();
let right = other.string_ptr_raw();
short_string_ptr_eq(left, right)
}
LUA_VLNGSTR => {
let s1 = self.gc_string();
let s2 = other.gc_string();
s1.data.str == s2.data.str
}
_ => false,
};
} else if tt == LUA_VNUMINT && other_tt == LUA_VNUMFLT {
let f = other.fltvalue();
let i = self.ivalue();
return lua_float_eq_int(f, i);
} else if tt == LUA_VNUMFLT && other_tt == LUA_VNUMINT {
let f = self.fltvalue();
let i = other.ivalue();
return lua_float_eq_int(f, i);
}
false
}
}
impl Eq for LuaValue {}
#[inline]
fn compute_long_string_hash(bytes: &[u8]) -> u64 {
let l = bytes.len();
let mut h = l as u64 ^ 0x5bd1e995; for &b in bytes.iter() {
h = h ^ ((h << 5).wrapping_add(h >> 2).wrapping_add(b as u64));
}
if h == 0 { 1 } else { h }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum LuaValueKind {
Nil,
Boolean,
Integer,
Float,
String,
Table,
Function,
CFunction,
CClosure,
RClosure,
Userdata,
Thread,
}
impl Default for LuaValue {
#[inline(always)]
fn default() -> Self {
Self::nil()
}
}
fn fmt_binary_bytes(
f: &mut std::fmt::Formatter<'_>,
bytes: &[u8],
prefix: &str,
) -> std::fmt::Result {
write!(f, "{}(len={}, [", prefix, bytes.len())?;
for (i, &b) in bytes.iter().enumerate() {
if i > 0 {
write!(f, " ")?;
}
write!(f, "{:02x}", b)?;
}
write!(f, "])")
}
impl std::fmt::Debug for LuaValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.kind() {
LuaValueKind::Nil => write!(f, "nil"),
LuaValueKind::Boolean => write!(f, "{}", self.bvalue()),
LuaValueKind::Integer => write!(f, "{}", self.ivalue()),
LuaValueKind::Float => write!(f, "{}", self.fltvalue()),
LuaValueKind::String => {
if let Some(s) = self.as_str() {
write!(f, "{}", s)
} else {
fmt_binary_bytes(f, self.as_bytes().unwrap_or(&[]), "string")
}
}
LuaValueKind::Table => write!(f, "table(0x{:#x})", self.raw_ptr_repr() as usize),
LuaValueKind::Function => write!(f, "function(0x{:#x})", self.raw_ptr_repr() as usize),
LuaValueKind::CFunction => {
write!(f, "cfunction(0x{:#x})", self.raw_ptr_repr() as usize)
}
LuaValueKind::CClosure => {
write!(f, "cclosure(0x{:#x})", self.raw_ptr_repr() as usize)
}
LuaValueKind::RClosure => {
write!(f, "rclosure(0x{:#x})", self.raw_ptr_repr() as usize)
}
LuaValueKind::Userdata => write!(f, "userdata(0x{:#x})", self.raw_ptr_repr() as usize),
LuaValueKind::Thread => write!(f, "thread(0x{:#x})", self.raw_ptr_repr() as usize),
}
}
}
impl std::fmt::Display for LuaValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.kind() {
LuaValueKind::Nil => write!(f, "nil"),
LuaValueKind::Boolean => write!(f, "{}", self.bvalue()),
LuaValueKind::Integer => write!(f, "{}", self.ivalue()),
LuaValueKind::Float => {
let n = self.fltvalue();
if n.floor() == n && n.abs() < 1e14 {
write!(f, "{:.0}", n)
} else {
write!(f, "{}", n)
}
}
LuaValueKind::String => {
if let Some(s) = self.as_str() {
write!(f, "{}", s)
} else {
fmt_binary_bytes(f, self.as_bytes().unwrap_or(&[]), "string")
}
}
LuaValueKind::Table => write!(f, "table: 0x{:x}", self.raw_ptr() as usize),
LuaValueKind::Function => {
write!(f, "function: 0x{:x}", self.raw_ptr() as usize)
}
LuaValueKind::CFunction => {
write!(
f,
"function: 0x{:x}",
self.raw_cfunction() as *const () as usize
)
}
LuaValueKind::CClosure => {
write!(f, "function: 0x{:x}", self.raw_ptr() as usize)
}
LuaValueKind::RClosure => {
write!(f, "function: 0x{:x}", self.raw_ptr() as usize)
}
LuaValueKind::Userdata => {
write!(f, "userdata: 0x{:x}", self.raw_ptr() as usize)
}
LuaValueKind::Thread => {
write!(f, "thread: 0x{:x}", self.raw_ptr() as usize)
}
}
}
}
impl std::hash::Hash for LuaValue {
#[inline(always)]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
let tt = self.tt();
if tt == LUA_VSHRSTR || tt == LUA_VLNGSTR {
let gs = self.gc_string();
let mut hash = gs.data.hash;
if hash == 0 {
hash = compute_long_string_hash(gs.data.str.as_bytes());
self.gc_string_mut().data.hash = hash;
}
hash.hash(state);
return;
}
if tt == LUA_VNUMINT || tt == LUA_VNUMFLT {
let n = if tt == LUA_VNUMINT {
self.raw_i64() as f64
} else {
self.raw_f64()
};
LUA_TNUMBER.hash(state);
n.to_bits().hash(state);
} else if tt <= LUA_VFALSE {
tt.hash(state);
} else {
tt.hash(state);
self.raw_ptr_repr().hash(state);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_size() {
assert_eq!(std::mem::size_of::<LuaValue>(), 16);
assert_eq!(std::mem::size_of::<Value>(), 8);
}
#[test]
fn test_nil() {
let v = LuaValue::nil();
assert!(v.ttisnil());
assert!(v.ttisstrictnil());
assert!(v.is_falsy());
assert_eq!(v.type_name(), "nil");
}
#[test]
fn test_boolean() {
let t = LuaValue::boolean(true);
let f = LuaValue::boolean(false);
assert!(t.ttisboolean());
assert!(f.ttisboolean());
assert!(t.ttistrue());
assert!(f.ttisfalse());
assert!(t.bvalue());
assert!(!f.bvalue());
assert!(t.is_truthy());
assert!(f.is_falsy());
}
#[test]
fn test_integer() {
let v = LuaValue::integer(42);
assert!(v.ttisnumber());
assert!(v.ttisinteger());
assert_eq!(v.ivalue(), 42);
assert_eq!(v.nvalue(), 42.0);
let neg = LuaValue::integer(-100);
assert_eq!(neg.ivalue(), -100);
}
#[test]
fn test_float() {
let v = LuaValue::float(3.15);
assert!(v.ttisnumber());
assert!(v.ttisfloat());
assert!((v.fltvalue() - 3.15).abs() < f64::EPSILON);
assert!((v.nvalue() - 3.15).abs() < f64::EPSILON);
}
#[test]
fn test_equality() {
assert_eq!(LuaValue::nil(), LuaValue::nil());
assert_eq!(LuaValue::integer(42), LuaValue::integer(42));
assert_ne!(LuaValue::integer(42), LuaValue::integer(43));
}
#[cfg(feature = "shared-proto")]
#[test]
fn test_shared_short_string_equals_local_short_string() {
use crate::gc::share_lua_value;
use crate::gc::{GC, GcString, PagedPool, StringInterner};
use crate::lua_vm::SafeOption;
let key = "0123456789abcdefghijklmnopqr";
let mut left_interner = StringInterner::new();
let mut right_interner = StringInterner::new();
let mut left_pool = PagedPool::<GcString>::default();
let mut right_pool = PagedPool::<GcString>::default();
let mut left_gc = GC::new(SafeOption::default());
let mut right_gc = GC::new(SafeOption::default());
let mut shared_value = left_interner
.intern(key, &mut left_gc, &mut left_pool)
.unwrap();
let local_value = right_interner
.intern(key, &mut right_gc, &mut right_pool)
.unwrap();
assert!(share_lua_value(&mut shared_value));
assert_eq!(shared_value, local_value);
assert_eq!(local_value, shared_value);
let local_ptr = local_value.as_string_ptr().unwrap();
assert_eq!(
local_ptr.as_ref().data.short_id(),
shared_value
.as_string_ptr()
.unwrap()
.as_ref()
.data
.short_id()
);
}
#[test]
fn test_type_tags() {
assert_eq!(novariant(LUA_VNUMINT), LUA_TNUMBER);
assert_eq!(novariant(LUA_VNUMFLT), LUA_TNUMBER);
assert_eq!(withvariant(LUA_VNUMINT), LUA_VNUMINT);
assert_eq!(makevariant!(LUA_TNUMBER, 0), LUA_VNUMINT);
assert_eq!(makevariant!(LUA_TNUMBER, 1), LUA_VNUMFLT);
}
#[test]
fn test_collectable_bit() {
let nil = LuaValue::nil();
let int = LuaValue::integer(42);
assert!(!nil.iscollectable());
assert!(!int.iscollectable());
}
}