Skip to main content

dellingr/vm/
lua_val.rs

1use super::Result;
2use super::State;
3use super::object::{Closure, GcHeap, ObjectPtr, StringPtr};
4
5use std::borrow::Cow;
6use std::fmt;
7use std::hash::{Hash, Hasher};
8
9/// Signature for a host-provided Rust function callable from Lua.
10///
11/// The function reads its arguments from the [`State`]'s stack (1-based
12/// indexing for host calls), pushes any return values, and returns the
13/// number of return values (or an error).
14pub type RustFunc = fn(&mut State) -> Result<u8>;
15
16#[derive(Clone, Copy, Default)]
17pub(crate) enum Val {
18    #[default]
19    Nil,
20    Bool(bool),
21    Num(f64),
22    Str(StringPtr),
23    RustFn(RustFunc),
24    Obj(ObjectPtr),
25}
26use Val::*;
27
28impl Val {
29    /// Get this value as a Lua function (closure), if it is one.
30    /// Requires heap access since the function data is stored in the GC heap.
31    pub(super) fn as_lua_function(&self, heap: &GcHeap) -> Option<Closure> {
32        if let Obj(o) = self {
33            heap.as_lua_function(*o)
34        } else {
35            None
36        }
37    }
38
39    pub(super) fn as_num(&self) -> Option<f64> {
40        match self {
41            Num(f) => Some(*f),
42            _ => None,
43        }
44    }
45
46    /// Get this value as Lua string bytes, if it is a string.
47    /// Requires heap access since strings are stored in the GC heap.
48    pub(super) fn as_string<'a>(&self, heap: &'a GcHeap) -> Option<&'a [u8]> {
49        if let Str(s) = self {
50            Some(heap.get_string(*s))
51        } else {
52            None
53        }
54    }
55
56    /// Get the StringPtr if this is a string value.
57    pub(super) fn as_string_ptr(&self) -> Option<StringPtr> {
58        if let Str(s) = self { Some(*s) } else { None }
59    }
60
61    /// Get the ObjectPtr if this is an object, without checking what kind.
62    /// Use heap.as_table() or heap.as_lua_function() to check the actual type.
63    pub(super) fn as_object_ptr(&self) -> Option<ObjectPtr> {
64        if let Obj(o) = self { Some(*o) } else { None }
65    }
66
67    pub(super) fn truthy(&self) -> bool {
68        !matches!(self, Nil | Bool(false))
69    }
70
71    /// Returns the value's type.
72    /// Requires heap access to determine if an Object is a Table or Function.
73    pub(super) fn typ(&self, heap: &GcHeap) -> LuaType {
74        match self {
75            Nil => LuaType::Nil,
76            Bool(_) => LuaType::Boolean,
77            Num(_) => LuaType::Number,
78            RustFn(_) => LuaType::Function,
79            Str(_) => LuaType::String,
80            Obj(o) => o.typ(heap),
81        }
82    }
83
84    /// Returns the value's type for non-object types.
85    /// For objects, this is unsafe to call - use typ() with heap access instead.
86    /// This is useful for error messages where we already know it's not an object.
87    pub(super) fn typ_simple(&self) -> LuaType {
88        match self {
89            Nil => LuaType::Nil,
90            Bool(_) => LuaType::Boolean,
91            Num(_) => LuaType::Number,
92            RustFn(_) => LuaType::Function,
93            Str(_) => LuaType::String,
94            Obj(_) => LuaType::Table, // Assume table for display purposes
95        }
96    }
97
98    /// Convert this value to a string representation.
99    /// Requires heap access to get string and object contents.
100    pub(super) fn to_string_with_heap(self, heap: &GcHeap) -> String {
101        match self {
102            Nil => "nil".to_string(),
103            Bool(b) => b.to_string(),
104            Num(n) => n.to_string(),
105            RustFn(func) => format!("<function: {func:p}>"),
106            Obj(o) => format!("{o}"),
107            Str(s) => String::from_utf8_lossy(heap.get_string(s)).into_owned(),
108        }
109    }
110
111    pub(super) fn to_bytes_with_heap(self, heap: &GcHeap) -> Cow<'_, [u8]> {
112        match self {
113            Nil => Cow::Borrowed(b"nil"),
114            Bool(false) => Cow::Borrowed(b"false"),
115            Bool(true) => Cow::Borrowed(b"true"),
116            Num(n) => Cow::Owned(n.to_string().into_bytes()),
117            RustFn(func) => Cow::Owned(format!("<function: {func:p}>").into_bytes()),
118            Obj(o) => Cow::Owned(format!("{o}").into_bytes()),
119            Str(s) => Cow::Borrowed(heap.get_string(s)),
120        }
121    }
122}
123
124impl fmt::Debug for Val {
125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        match self {
127            Nil => write!(f, "nil"),
128            Bool(b) => b.fmt(f),
129            Num(n) => n.fmt(f),
130            RustFn(func) => write!(f, "<function: {func:p}>"),
131            Obj(o) => o.fmt(f),
132            Str(s) => s.fmt(f),
133        }
134    }
135}
136
137impl fmt::Display for Val {
138    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139        match self {
140            Nil => write!(f, "nil"),
141            Bool(b) => b.fmt(f),
142            Num(n) => n.fmt(f),
143            Obj(o) => o.fmt(f),
144            Str(s) => s.fmt(f),
145            RustFn(func) => write!(f, "<function: {func:p}>"),
146        }
147    }
148}
149
150/// This is very dangerous, since f64 doesn't implement Eq.
151impl Eq for Val {}
152
153impl Hash for Val {
154    fn hash<H: Hasher>(&self, hasher: &mut H) {
155        match self {
156            Nil => (),
157            Bool(b) => b.hash(hasher),
158            Obj(o) => o.hash(hasher),
159            Num(n) => {
160                // NaN breaks HashMap invariants (compares unequal to itself but may hash same)
161                // This must be a hard assert, not debug_assert, to prevent undefined behavior
162                assert!(!n.is_nan(), "Cannot use NaN as table key");
163                let mut bits = n.to_bits();
164                if bits == 1 << 63 {
165                    bits = 0;
166                }
167                bits.hash(hasher);
168            }
169            RustFn(func) => {
170                let f: *const RustFunc = func;
171                f.hash(hasher);
172            }
173            Str(s) => s.hash(hasher),
174        }
175    }
176}
177
178impl PartialEq for Val {
179    fn eq(&self, other: &Val) -> bool {
180        match (self, other) {
181            (Nil, Nil) => true,
182            (Bool(a), Bool(b)) => a == b,
183            (Num(a), Num(b)) => a == b,
184            (RustFn(a), RustFn(b)) => {
185                let x: *const RustFunc = a;
186                let y: *const RustFunc = b;
187                x == y
188            }
189            (Obj(a), Obj(b)) => a == b,
190            // String pointer equality works because strings are interned
191            (Str(a), Str(b)) => a == b,
192            _ => false,
193        }
194    }
195}
196
197// Markable impl for Val is in object.rs
198
199/// The runtime type of a Lua value, as exposed to host code via [`State`].
200#[derive(Debug, Eq, PartialEq)]
201pub enum LuaType {
202    /// `nil`
203    Nil,
204    /// `true` or `false`
205    Boolean,
206    /// IEEE-754 double-precision number
207    Number,
208    /// Interned UTF-8 string (or arbitrary byte string)
209    String,
210    /// Mutable Lua table (array + hash, with optional metatable)
211    Table,
212    /// Lua closure or host-provided [`RustFunc`]
213    Function,
214}
215
216impl LuaType {
217    /// Lowercase name used by Lua's `type()` builtin and in error messages.
218    pub fn as_str(&self) -> &'static str {
219        use LuaType::*;
220        match self {
221            Nil => "nil",
222            Boolean => "boolean",
223            Number => "number",
224            String => "string",
225            Table => "table",
226            Function => "function",
227        }
228    }
229}
230
231impl fmt::Display for LuaType {
232    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233        self.as_str().fmt(f)
234    }
235}