use crate::lua_value::{CClosureFunction, LuaUserdata, RClosureFunction, short_string_ptr_eq};
use crate::lua_vm::{CFunction, LuaState};
use crate::{
CClosurePtr, FunctionPtr, GcCClosure, GcFunction, GcObjectPtr, GcRClosure, GcString, GcTable,
GcThread, GcUserdata, LuaFunction, LuaTable, RClosurePtr, StringPtr, TablePtr, ThreadPtr,
UserdataPtr,
};
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: usize, pub i: i64, pub n: f64, }
impl Value {
#[inline(always)]
pub const fn 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: f as usize }
}
}
#[derive(Clone, Copy)]
#[repr(C)]
pub struct LuaValue {
pub(crate) value: Value, pub(crate) tt: u8, }
impl LuaValue {
#[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 empty() -> Self {
Self {
value: Value::nil(),
tt: LUA_VEMPTY,
}
}
#[inline(always)]
pub const fn abstkey() -> Self {
Self {
value: Value::nil(),
tt: LUA_VABSTKEY,
}
}
#[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())
}
#[allow(unused)]
#[inline(always)]
pub(crate) fn ttypetag(&self) -> u8 {
withvariant(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());
unsafe { self.value.i }
}
#[inline(always)]
pub(crate) fn fltvalue(&self) -> f64 {
debug_assert!(self.ttisfloat());
unsafe { self.value.n }
}
#[inline(always)]
pub(crate) fn nvalue(&self) -> f64 {
debug_assert!(self.ttisnumber());
if self.ttisinteger() {
unsafe { self.value.i as f64 }
} else {
unsafe { self.value.n }
}
}
#[allow(unused)]
#[inline(always)]
pub(crate) fn pvalue(&self) -> *mut std::ffi::c_void {
debug_assert!(self.ttislightuserdata());
unsafe { self.value.p }
}
#[inline(always)]
pub(crate) fn fvalue(&self) -> CFunction {
debug_assert!(self.ttiscfunction());
unsafe { std::mem::transmute(self.value.f) }
}
#[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_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() {
unsafe {
let ptr = self.value.ptr;
let s: &GcString = &*(ptr as *const GcString);
s.data.as_str()
}
} else {
None
}
}
#[inline(always)]
pub fn as_binary(&self) -> Option<&[u8]> {
if self.ttisstring() && self.as_str().is_none() {
Some(unsafe {
let ptr = self.value.ptr;
let v: &GcString = &*(ptr as *const GcString);
v.data.as_bytes()
})
} else {
None
}
}
#[inline(always)]
pub fn as_bytes(&self) -> Option<&[u8]> {
if self.ttisstring() {
Some(unsafe {
let ptr = self.value.ptr;
let s: &GcString = &*(ptr as *const GcString);
s.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<&LuaTable> {
if self.ttistable() {
let v = unsafe { &*(self.value.ptr as *const GcTable) };
Some(&v.data)
} else {
None
}
}
#[inline(always)]
pub(crate) fn hvalue(&self) -> &LuaTable {
unsafe { &(*(self.value.ptr as *const GcTable)).data }
}
#[allow(clippy::mut_from_ref)]
#[inline(always)]
pub(crate) fn hvalue_mut(&self) -> &mut LuaTable {
unsafe { &mut (*(self.value.ptr as *mut GcTable)).data }
}
#[allow(clippy::mut_from_ref)]
#[inline(always)]
pub fn as_table_mut(&self) -> Option<&mut LuaTable> {
if self.ttistable() {
let v = unsafe { &mut *(self.value.ptr as *mut GcTable) };
Some(&mut v.data)
} else {
None
}
}
#[inline(always)]
pub fn as_lua_function(&self) -> Option<&LuaFunction> {
if self.ttisluafunction() {
let func = unsafe { &*(self.value.ptr as *const GcFunction) };
Some(&func.data)
} else {
None
}
}
#[inline(always)]
pub unsafe fn as_lua_function_unchecked(&self) -> &LuaFunction {
debug_assert!(self.ttisluafunction());
let func = unsafe { &*(self.value.ptr as *const GcFunction) };
&func.data
}
#[allow(clippy::mut_from_ref)]
#[inline(always)]
pub fn as_lua_function_mut(&self) -> Option<&mut LuaFunction> {
if self.ttisluafunction() {
let func = unsafe { &mut *(self.value.ptr as *mut GcFunction) };
Some(&mut func.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() {
let gc = unsafe { &*(self.value.ptr as *const GcCClosure) };
Some(&gc.data)
} else {
None
}
}
#[allow(clippy::mut_from_ref)]
#[inline(always)]
pub fn as_cclosure_mut(&self) -> Option<&mut CClosureFunction> {
if self.is_cclosure() {
let gc = unsafe { &mut *(self.value.ptr as *mut GcCClosure) };
Some(&mut gc.data)
} else {
None
}
}
#[inline(always)]
pub fn as_rclosure(&self) -> Option<&RClosureFunction> {
if self.is_rclosure() {
let gc = unsafe { &*(self.value.ptr as *const GcRClosure) };
Some(&gc.data)
} else {
None
}
}
#[allow(clippy::mut_from_ref)]
#[inline(always)]
pub fn as_rclosure_mut(&self) -> Option<&mut RClosureFunction> {
if self.is_rclosure() {
let gc = unsafe { &mut *(self.value.ptr as *mut GcRClosure) };
Some(&mut gc.data)
} else {
None
}
}
#[inline(always)]
pub fn as_rclosure_ptr(&self) -> Option<RClosurePtr> {
if self.is_rclosure() {
Some(RClosurePtr::new(unsafe {
self.value.ptr as *mut GcRClosure
}))
} else {
None
}
}
#[allow(clippy::mut_from_ref)]
#[inline(always)]
pub fn as_userdata_mut(&self) -> Option<&mut LuaUserdata> {
if self.ttisfulluserdata() {
let gc = unsafe { &mut *(self.value.ptr as *mut GcUserdata) };
Some(&mut gc.data)
} else {
None
}
}
#[allow(clippy::mut_from_ref)]
#[inline(always)]
pub fn as_thread_mut(&self) -> Option<&mut LuaState> {
if self.ttisthread() {
let v = unsafe { &mut *(self.value.ptr as *mut GcThread) };
Some(&mut v.data)
} else {
None
}
}
pub fn as_table_ptr(&self) -> Option<TablePtr> {
if self.ttistable() {
Some(TablePtr::new(unsafe { self.value.ptr as *mut GcTable }))
} else {
None
}
}
#[inline(always)]
pub fn as_string_ptr(&self) -> Option<StringPtr> {
if self.ttisstring() {
Some(StringPtr::new(unsafe { self.value.ptr as *mut GcString }))
} else {
None
}
}
#[inline(always)]
pub fn as_function_ptr(&self) -> Option<FunctionPtr> {
if self.ttisfunction() {
Some(FunctionPtr::new(unsafe {
self.value.ptr as *mut GcFunction
}))
} else {
None
}
}
#[inline(always)]
pub fn as_cclosure_ptr(&self) -> Option<CClosurePtr> {
if self.is_cclosure() {
Some(CClosurePtr::new(unsafe {
self.value.ptr as *mut GcCClosure
}))
} else {
None
}
}
#[inline(always)]
pub fn as_userdata_ptr(&self) -> Option<UserdataPtr> {
if self.ttisfulluserdata() {
Some(UserdataPtr::new(unsafe {
self.value.ptr as *mut GcUserdata
}))
} else {
None
}
}
#[inline(always)]
pub fn as_thread_ptr(&self) -> Option<ThreadPtr> {
if self.ttisthread() {
Some(ThreadPtr::new(unsafe { self.value.ptr as *mut GcThread }))
} 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 unsafe fn as_gc_ptr_unchecked(&self) -> GcObjectPtr {
unsafe {
let ptr = self.value.ptr as u64;
match self.kind() {
LuaValueKind::Table => GcObjectPtr::from(TablePtr::new(ptr as *mut GcTable)),
LuaValueKind::Function => {
GcObjectPtr::from(FunctionPtr::new(ptr as *mut GcFunction))
}
LuaValueKind::CClosure => {
GcObjectPtr::from(CClosurePtr::new(ptr as *mut GcCClosure))
}
LuaValueKind::RClosure => {
GcObjectPtr::from(RClosurePtr::new(ptr as *mut GcRClosure))
}
LuaValueKind::String => GcObjectPtr::from(StringPtr::new(ptr as *mut GcString)),
LuaValueKind::Thread => GcObjectPtr::from(ThreadPtr::new(ptr as *mut GcThread)),
LuaValueKind::Userdata => {
GcObjectPtr::from(UserdataPtr::new(ptr as *mut GcUserdata))
}
_ => core::hint::unreachable_unchecked(),
}
}
}
#[inline(always)]
pub unsafe fn as_gc_ptr_table_unchecked(&self) -> GcObjectPtr {
unsafe { GcObjectPtr::from(TablePtr::new(self.value.ptr as *mut GcTable)) }
}
#[inline(always)]
pub unsafe fn as_table_ptr_unchecked(&self) -> TablePtr {
unsafe { TablePtr::new(self.value.ptr as *mut GcTable) }
}
#[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 {
unsafe { self.value.ptr }
}
#[inline(always)]
pub fn hash_value(&self) -> u64 {
let tt = self.tt();
if tt == LUA_VSHRSTR {
return unsafe { (*(self.value.ptr as *const GcString)).data.hash };
}
if tt == LUA_VLNGSTR {
let gs = unsafe { &*(self.value.ptr as *const GcString) };
let hash = gs.data.hash;
if hash != 0 {
return hash;
}
let computed = compute_long_string_hash(gs.data.str.as_bytes());
unsafe {
(*(self.value.ptr as *mut GcString)).data.hash = computed;
}
return computed;
}
if tt == LUA_VNUMINT {
return unsafe { self.value.i as u64 };
}
if tt == LUA_VNUMFLT {
let mut h = unsafe { self.value.i as u64 };
h ^= h >> 30;
h = h.wrapping_mul(0xbf58476d1ce4e5b9);
h ^= h >> 27;
h = h.wrapping_mul(0x94d049bb133111eb);
h ^= h >> 31;
return h;
}
unsafe { self.value.i as u64 }
}
#[inline(always)]
pub unsafe fn hash_string_unchecked(&self) -> u64 {
let gs = unsafe { &*(self.value.ptr as *const GcString) };
let hash = gs.data.hash;
if hash != 0 {
return hash;
}
let computed = compute_long_string_hash(gs.data.str.as_bytes());
unsafe {
(*(self.value.ptr as *mut GcString)).data.hash = computed;
}
computed
}
}
#[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 unsafe { self.value.n == other.value.n };
}
if unsafe { self.value.i == other.value.i } {
return true;
}
return match tt {
LUA_VSHRSTR => {
let left = StringPtr::new(unsafe { self.value.ptr as *mut GcString });
let right = StringPtr::new(unsafe { other.value.ptr as *mut GcString });
short_string_ptr_eq(left, right)
}
LUA_VLNGSTR => {
let s1 = unsafe { &*(self.value.ptr as *const GcString) };
let s2 = unsafe { &*(other.value.ptr as *const GcString) };
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}", unsafe { self.value.ptr as usize }),
LuaValueKind::Function => {
write!(f, "function: 0x{:x}", unsafe { self.value.ptr as usize })
}
LuaValueKind::CFunction => write!(f, "function: 0x{:x}", unsafe { self.value.f }),
LuaValueKind::CClosure => {
write!(f, "function: 0x{:x}", unsafe { self.value.ptr as usize })
}
LuaValueKind::RClosure => {
write!(f, "function: 0x{:x}", unsafe { self.value.ptr as usize })
}
LuaValueKind::Userdata => {
write!(f, "userdata: 0x{:x}", unsafe { self.value.ptr as usize })
}
LuaValueKind::Thread => {
write!(f, "thread: 0x{:x}", unsafe { self.value.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 = unsafe { &*(self.value.ptr as *const GcString) };
let mut hash = gs.data.hash;
if hash == 0 {
hash = compute_long_string_hash(gs.data.str.as_bytes());
unsafe {
(*(self.value.ptr as *mut GcString)).data.hash = hash;
}
}
hash.hash(state);
return;
}
if tt == LUA_VNUMINT || tt == LUA_VNUMFLT {
unsafe {
let n = if tt == LUA_VNUMINT {
self.value.i as f64
} else {
self.value.n
};
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_empty() {
let v = LuaValue::empty();
assert!(v.ttisnil()); assert!(v.ttisempty());
assert!(!v.ttisstrictnil());
}
#[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::lua_vm::SafeOption;
use crate::{GC, StringInterner};
let key = "0123456789abcdefghijklmnopqr";
let mut left_interner = StringInterner::new();
let mut right_interner = StringInterner::new();
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).unwrap();
let local_value = right_interner.intern(key, &mut right_gc).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());
}
}