use crate::runtime::coroutine::Coro;
use crate::runtime::function::LuaClosure;
use crate::runtime::heap::Gc;
use crate::runtime::string::LuaStr;
use crate::runtime::table::Table;
use crate::runtime::userdata::Userdata;
pub type NativeFn =
fn(&mut crate::vm::Vm, func_slot: u32, nargs: u32) -> Result<u32, crate::vm::LuaError>;
use crate::runtime::function::NativeClosure;
#[derive(Clone, Copy, Debug)]
#[repr(C, u8)]
pub enum Value {
Nil,
Bool(
bool,
),
Int(
i64,
),
Float(
f64,
),
Str(
Gc<LuaStr>,
),
Table(
Gc<Table>,
),
Closure(
Gc<LuaClosure>,
),
Native(
Gc<NativeClosure>,
),
Coro(
Gc<Coro>,
),
Userdata(
Gc<Userdata>,
),
LightUserdata(
*const (),
),
}
impl Value {
pub fn type_name(self) -> &'static str {
match self {
Value::Nil => "nil",
Value::Bool(_) => "boolean",
Value::Int(_) | Value::Float(_) => "number",
Value::Str(_) => "string",
Value::Table(_) => "table",
Value::Closure(_) | Value::Native(_) => "function",
Value::Coro(_) => "thread",
Value::Userdata(_) | Value::LightUserdata(_) => "userdata",
}
}
pub fn is_nil(self) -> bool {
matches!(self, Value::Nil)
}
pub fn truthy(self) -> bool {
!matches!(self, Value::Nil | Value::Bool(false))
}
#[inline(always)]
pub fn tag_byte(&self) -> u8 {
unsafe { *(self as *const Value as *const u8) }
}
#[inline(always)]
pub fn is_callable(self) -> bool {
let t = self.tag_byte();
t == tag::CLOSURE || t == tag::NATIVE
}
#[doc(hidden)]
#[inline(always)]
pub unsafe fn as_closure_unchecked(self) -> Gc<crate::runtime::LuaClosure> {
debug_assert_eq!(self.tag_byte(), tag::CLOSURE);
unsafe {
let payload_ptr = (&self as *const Value as *const u8).add(8)
as *const Gc<crate::runtime::LuaClosure>;
*payload_ptr
}
}
#[doc(hidden)]
#[inline(always)]
pub unsafe fn as_int_unchecked(self) -> i64 {
debug_assert_eq!(self.tag_byte(), tag::INT);
unsafe {
let payload_ptr = (&self as *const Value as *const u8).add(8) as *const i64;
*payload_ptr
}
}
pub fn try_as_str(&self) -> Option<&str> {
match self {
Value::Str(s) => std::str::from_utf8(s.as_bytes()).ok(),
_ => None,
}
}
pub fn as_bytes(&self) -> Option<&[u8]> {
match self {
Value::Str(s) => Some(s.as_bytes()),
_ => None,
}
}
pub fn raw_eq(self, other: Value) -> bool {
match (self, other) {
(Value::Nil, Value::Nil) => true,
(Value::Bool(a), Value::Bool(b)) => a == b,
(Value::Int(a), Value::Int(b)) => a == b,
(Value::Float(a), Value::Float(b)) => a == b,
(Value::Int(a), Value::Float(b)) | (Value::Float(b), Value::Int(a)) => {
f2i_exact(b) == Some(a)
}
(Value::Str(a), Value::Str(b)) => str_eq(a, b),
(Value::Table(a), Value::Table(b)) => a.ptr_eq(b),
(Value::Closure(a), Value::Closure(b)) => a.ptr_eq(b),
(Value::Native(a), Value::Native(b)) => a.ptr_eq(b),
(Value::Coro(a), Value::Coro(b)) => a.ptr_eq(b),
(Value::Userdata(a), Value::Userdata(b)) => a.ptr_eq(b),
(Value::LightUserdata(a), Value::LightUserdata(b)) => a == b,
_ => false,
}
}
}
fn str_eq(a: Gc<LuaStr>, b: Gc<LuaStr>) -> bool {
if a.ptr_eq(b) {
return true;
}
if a.is_short() && b.is_short() {
return false; }
a.as_bytes() == b.as_bytes()
}
pub fn f2i_exact(f: f64) -> Option<i64> {
if f.trunc() == f && (-9_223_372_036_854_775_808.0..9_223_372_036_854_775_808.0).contains(&f) {
Some(f as i64)
} else {
None
}
}
pub mod tag {
pub const NIL: u8 = 0;
pub const BOOL: u8 = 1;
pub const INT: u8 = 2;
pub const FLOAT: u8 = 3;
pub const STR: u8 = 4;
pub const TABLE: u8 = 5;
pub const CLOSURE: u8 = 6;
pub const NATIVE: u8 = 7;
pub const CORO: u8 = 8;
pub const USERDATA: u8 = 9;
pub const LIGHTUSERDATA: u8 = 10;
}
#[doc(hidden)]
pub mod raw {
pub const NIL: u8 = 0;
pub const FALSE: u8 = 1;
pub const TRUE: u8 = 2;
pub const INT: u8 = 3;
pub const FLOAT: u8 = 4;
pub const STR: u8 = 5;
pub const TABLE: u8 = 6;
pub const CLOSURE: u8 = 7;
pub const NATIVE: u8 = 8;
pub const CORO: u8 = 9;
pub const USERDATA: u8 = 10;
pub const LIGHTUSERDATA: u8 = 11;
pub fn is_gc(tag: u8) -> bool {
(STR..LIGHTUSERDATA).contains(&tag)
}
}
#[derive(Clone, Copy)]
#[doc(hidden)]
pub union RawVal {
pub zero: u64,
pub i: i64,
pub f: f64,
pub s: *mut LuaStr,
pub t: *mut Table,
pub c: *mut LuaClosure,
pub n: *mut NativeClosure,
pub co: *mut Coro,
pub u: *mut Userdata,
pub lu: *const (),
}
impl RawVal {
pub(crate) const NIL: RawVal = RawVal { zero: 0 };
}
impl Value {
#[doc(hidden)]
pub fn unpack(self) -> (u8, RawVal) {
match self {
Value::Nil => (raw::NIL, RawVal::NIL),
Value::Bool(false) => (raw::FALSE, RawVal::NIL),
Value::Bool(true) => (raw::TRUE, RawVal::NIL),
Value::Int(i) => (raw::INT, RawVal { i }),
Value::Float(f) => (raw::FLOAT, RawVal { f }),
Value::Str(s) => (raw::STR, RawVal { s: s.as_ptr() }),
Value::Table(t) => (raw::TABLE, RawVal { t: t.as_ptr() }),
Value::Closure(c) => (raw::CLOSURE, RawVal { c: c.as_ptr() }),
Value::Native(n) => (raw::NATIVE, RawVal { n: n.as_ptr() }),
Value::Coro(co) => (raw::CORO, RawVal { co: co.as_ptr() }),
Value::Userdata(u) => (raw::USERDATA, RawVal { u: u.as_ptr() }),
Value::LightUserdata(p) => (raw::LIGHTUSERDATA, RawVal { lu: p }),
}
}
#[doc(hidden)]
pub unsafe fn pack(tag: u8, v: RawVal) -> Value {
unsafe {
match tag {
raw::NIL => Value::Nil,
raw::FALSE => Value::Bool(false),
raw::TRUE => Value::Bool(true),
raw::INT => Value::Int(v.i),
raw::FLOAT => Value::Float(v.f),
raw::NATIVE => Value::Native(Gc::from_ptr(v.n)),
raw::STR => Value::Str(Gc::from_ptr(v.s)),
raw::TABLE => Value::Table(Gc::from_ptr(v.t)),
raw::CLOSURE => Value::Closure(Gc::from_ptr(v.c)),
raw::CORO => Value::Coro(Gc::from_ptr(v.co)),
raw::USERDATA => Value::Userdata(Gc::from_ptr(v.u)),
raw::LIGHTUSERDATA => Value::LightUserdata(v.lu),
_ => unreachable!("bad raw value tag"),
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::runtime::heap::Heap;
#[test]
fn value_is_16_bytes() {
assert_eq!(size_of::<Value>(), 16);
}
#[test]
fn p17d_e1_tag_byte_matches_declaration_order() {
let mut heap = Heap::new();
assert_eq!(Value::Nil.tag_byte(), tag::NIL);
assert_eq!(Value::Bool(false).tag_byte(), tag::BOOL);
assert_eq!(Value::Bool(true).tag_byte(), tag::BOOL);
assert_eq!(Value::Int(0).tag_byte(), tag::INT);
assert_eq!(Value::Int(-1).tag_byte(), tag::INT);
assert_eq!(Value::Float(std::f64::consts::PI).tag_byte(), tag::FLOAT);
let s = heap.intern(b"hi");
assert_eq!(Value::Str(s).tag_byte(), tag::STR);
assert_eq!(
Value::LightUserdata(std::ptr::null()).tag_byte(),
tag::LIGHTUSERDATA
);
}
#[test]
fn p17d_e1_int_unchecked_roundtrip() {
for v in [0i64, 1, -1, i64::MAX, i64::MIN, 0x1234_5678_9abc_def0] {
let val = Value::Int(v);
let recovered = unsafe { val.as_int_unchecked() };
assert_eq!(recovered, v, "i64 payload round-trips for {}", v);
}
}
#[test]
fn p17d_e1_closure_unchecked_roundtrip() {
}
#[test]
fn p17d_e1_is_callable() {
let mut heap = Heap::new();
let s = heap.intern(b"x");
assert!(!Value::Nil.is_callable());
assert!(!Value::Int(0).is_callable());
assert!(!Value::Str(s).is_callable());
}
#[test]
fn raw_equality() {
assert!(Value::Nil.raw_eq(Value::Nil));
assert!(Value::Int(3).raw_eq(Value::Float(3.0)));
assert!(Value::Float(3.0).raw_eq(Value::Int(3)));
assert!(!Value::Int(3).raw_eq(Value::Float(3.5)));
assert!(!Value::Int(i64::MAX).raw_eq(Value::Float(i64::MAX as f64)));
assert!(!Value::Float(f64::NAN).raw_eq(Value::Float(f64::NAN)));
assert!(!Value::Nil.raw_eq(Value::Bool(false)));
assert!(Value::Int(0).raw_eq(Value::Float(-0.0)));
}
#[test]
fn string_equality_short_and_long() {
let mut heap = Heap::new();
let a = Value::Str(heap.intern(b"abc"));
let b = Value::Str(heap.intern(b"abc"));
let c = Value::Str(heap.intern(b"abd"));
assert!(a.raw_eq(b));
assert!(!a.raw_eq(c));
let long1 = Value::Str(heap.intern(&[7u8; 50]));
let long2 = Value::Str(heap.intern(&[7u8; 50]));
assert!(long1.raw_eq(long2));
}
#[test]
fn pack_roundtrip() {
let cases = [
Value::Nil,
Value::Bool(true),
Value::Bool(false),
Value::Int(-42),
Value::Float(0.5),
];
for v in cases {
let (t, b) = v.unpack();
assert!(unsafe { Value::pack(t, b) }.raw_eq(v));
}
}
#[test]
fn f2i_exact_boundaries() {
assert_eq!(f2i_exact(0.0), Some(0));
assert_eq!(f2i_exact(-0.0), Some(0));
assert_eq!(f2i_exact(9007199254740992.0), Some(1 << 53));
assert_eq!(f2i_exact(-9223372036854775808.0), Some(i64::MIN));
assert_eq!(f2i_exact(9223372036854775808.0), None); assert_eq!(f2i_exact(0.5), None);
assert_eq!(f2i_exact(f64::NAN), None);
assert_eq!(f2i_exact(f64::INFINITY), None);
}
}