Skip to main content

luaur_rt/
value.rs

1//! The [`Value`] enum and the stack <-> `Value` bridges.
2//!
3//! Mirrors `mlua::Value`. Luau (like Lua 5.x without the integer subtype)
4//! stores every number as an `f64`; there is no distinct integer tag at the VM
5//! level. We therefore reconstruct the [`Value::Integer`] vs [`Value::Number`]
6//! distinction on the way out by testing whether the `f64` is an exact,
7//! in-range integer — matching mlua's observable behavior closely enough for
8//! the high-level API.
9
10use crate::error::Result;
11use crate::function::Function;
12use crate::state::Lua;
13use crate::string::LuaString;
14use crate::sys::*;
15use crate::table::Table;
16
17/// The integer type exposed by the API. Mirrors `mlua::Integer` (`i64`).
18pub type Integer = i64;
19/// The float type exposed by the API. Mirrors `mlua::Number` (`f64`).
20pub type Number = f64;
21
22/// A dynamically typed Lua value.
23///
24/// Mirrors `mlua::Value`. Reference-typed variants ([`Value::String`],
25/// [`Value::Table`], [`Value::Function`]) carry handles that keep both the
26/// value and the VM alive.
27#[derive(Clone, Debug)]
28pub enum Value {
29    /// `nil`.
30    Nil,
31    /// A boolean.
32    Boolean(bool),
33    /// An integer (an `f64` that is an exact, in-range whole number).
34    Integer(Integer),
35    /// A floating-point number.
36    Number(Number),
37    /// A string.
38    String(LuaString),
39    /// A table.
40    Table(Table),
41    /// A function (Lua or Rust).
42    Function(Function),
43    /// A raw-pointer light userdata. Mirrors `mlua::Value::LightUserData`.
44    LightUserData(crate::light_userdata::LightUserData),
45    /// A userdata value with typed Rust-side borrowing.
46    UserData(crate::userdata::AnyUserData),
47    /// A thread (coroutine).
48    Thread(crate::thread::Thread),
49    /// A Luau vector (3 `f32` components). Mirrors `mlua::Value::Vector`.
50    Vector(crate::vector::Vector),
51    /// A Luau buffer (mutable, fixed-size byte array). Mirrors
52    /// `mlua::Value::Buffer`.
53    Buffer(crate::buffer::Buffer),
54    /// A boxed Lua/Rust error carried as a first-class value (mirrors
55    /// `mlua::Value::Error`). Produced when a Rust error is returned to Lua.
56    Error(Box<crate::error::Error>),
57}
58
59impl Value {
60    /// `Value::Nil`. Mirrors `mlua::Nil`.
61    pub const NIL: Value = Value::Nil;
62
63    /// The Lua type name of this value (e.g. `"nil"`, `"number"`, `"table"`).
64    pub fn type_name(&self) -> &'static str {
65        match self {
66            Value::Nil => "nil",
67            Value::Boolean(_) => "boolean",
68            Value::Integer(_) | Value::Number(_) => "number",
69            Value::String(_) => "string",
70            Value::Table(_) => "table",
71            Value::Function(_) => "function",
72            Value::LightUserData(_) => "userdata",
73            Value::UserData(_) => "userdata",
74            Value::Thread(_) => "thread",
75            Value::Vector(_) => "vector",
76            Value::Buffer(_) => "buffer",
77            Value::Error(_) => "error",
78        }
79    }
80
81    /// Whether this is an error value.
82    pub fn is_error(&self) -> bool {
83        matches!(self, Value::Error(_))
84    }
85
86    /// View as a reference to the contained error, if any.
87    pub fn as_error(&self) -> Option<&crate::error::Error> {
88        match self {
89            Value::Error(e) => Some(e),
90            _ => None,
91        }
92    }
93
94    /// Whether this is `nil`.
95    pub fn is_nil(&self) -> bool {
96        matches!(self, Value::Nil)
97    }
98    /// Whether this is a boolean.
99    pub fn is_boolean(&self) -> bool {
100        matches!(self, Value::Boolean(_))
101    }
102    /// Whether this is a number of either subtype.
103    pub fn is_number(&self) -> bool {
104        matches!(self, Value::Number(_) | Value::Integer(_))
105    }
106    /// Whether this is the integer subtype.
107    pub fn is_integer(&self) -> bool {
108        matches!(self, Value::Integer(_))
109    }
110    /// Whether this is a string.
111    pub fn is_string(&self) -> bool {
112        matches!(self, Value::String(_))
113    }
114    /// Whether this is a table.
115    pub fn is_table(&self) -> bool {
116        matches!(self, Value::Table(_))
117    }
118    /// Whether this is a function.
119    pub fn is_function(&self) -> bool {
120        matches!(self, Value::Function(_))
121    }
122
123    /// Lua truthiness: everything except `nil` and `false` is truthy.
124    pub fn as_boolean(&self) -> Option<bool> {
125        match self {
126            Value::Boolean(b) => Some(*b),
127            _ => None,
128        }
129    }
130    /// View as an integer if it is one.
131    pub fn as_integer(&self) -> Option<Integer> {
132        match self {
133            Value::Integer(i) => Some(*i),
134            _ => None,
135        }
136    }
137    /// View as an `f64` if it is a number of either subtype.
138    pub fn as_number(&self) -> Option<Number> {
139        match self {
140            Value::Number(n) => Some(*n),
141            Value::Integer(i) => Some(*i as f64),
142            _ => None,
143        }
144    }
145    /// View as a string handle.
146    pub fn as_string(&self) -> Option<&LuaString> {
147        match self {
148            Value::String(s) => Some(s),
149            _ => None,
150        }
151    }
152    /// View as a table handle.
153    pub fn as_table(&self) -> Option<&Table> {
154        match self {
155            Value::Table(t) => Some(t),
156            _ => None,
157        }
158    }
159    /// View as a function handle.
160    pub fn as_function(&self) -> Option<&Function> {
161        match self {
162            Value::Function(f) => Some(f),
163            _ => None,
164        }
165    }
166
167    /// Whether this is a userdata value.
168    pub fn is_userdata(&self) -> bool {
169        matches!(self, Value::UserData(_))
170    }
171
172    /// View as a userdata handle.
173    pub fn as_userdata(&self) -> Option<&crate::userdata::AnyUserData> {
174        match self {
175            Value::UserData(u) => Some(u),
176            _ => None,
177        }
178    }
179
180    /// Whether this is a thread (coroutine) value.
181    pub fn is_thread(&self) -> bool {
182        matches!(self, Value::Thread(_))
183    }
184
185    /// View as a thread handle.
186    pub fn as_thread(&self) -> Option<&crate::thread::Thread> {
187        match self {
188            Value::Thread(t) => Some(t),
189            _ => None,
190        }
191    }
192
193    /// Whether this is a Luau vector value. Mirrors `mlua::Value::is_vector`.
194    pub fn is_vector(&self) -> bool {
195        matches!(self, Value::Vector(_))
196    }
197
198    /// View as a vector. Mirrors `mlua::Value::as_vector`.
199    pub fn as_vector(&self) -> Option<&crate::vector::Vector> {
200        match self {
201            Value::Vector(v) => Some(v),
202            _ => None,
203        }
204    }
205
206    /// Whether this is a Luau buffer value. Mirrors `mlua::Value::is_buffer`.
207    pub fn is_buffer(&self) -> bool {
208        matches!(self, Value::Buffer(_))
209    }
210
211    /// View as a buffer handle. Mirrors `mlua::Value::as_buffer`.
212    pub fn as_buffer(&self) -> Option<&crate::buffer::Buffer> {
213        match self {
214            Value::Buffer(b) => Some(b),
215            _ => None,
216        }
217    }
218
219    /// View as an `i32` if it is an in-range integer.
220    pub fn as_i32(&self) -> Option<i32> {
221        self.as_integer().and_then(|i| i32::try_from(i).ok())
222    }
223    /// View as a `u32` if it is an in-range integer.
224    pub fn as_u32(&self) -> Option<u32> {
225        self.as_integer().and_then(|i| u32::try_from(i).ok())
226    }
227    /// View as an `i64` if it is an integer.
228    pub fn as_i64(&self) -> Option<i64> {
229        self.as_integer()
230    }
231    /// View as a `u64` if it is an in-range integer.
232    pub fn as_u64(&self) -> Option<u64> {
233        self.as_integer().and_then(|i| u64::try_from(i).ok())
234    }
235    /// View as an `isize` if it is an in-range integer.
236    pub fn as_isize(&self) -> Option<isize> {
237        self.as_integer().and_then(|i| isize::try_from(i).ok())
238    }
239    /// View as a `usize` if it is an in-range integer.
240    pub fn as_usize(&self) -> Option<usize> {
241        self.as_integer().and_then(|i| usize::try_from(i).ok())
242    }
243    /// View as an `f32`.
244    pub fn as_f32(&self) -> Option<f32> {
245        self.as_number().map(|n| n as f32)
246    }
247    /// View as an `f64`.
248    pub fn as_f64(&self) -> Option<f64> {
249        self.as_number()
250    }
251
252    /// A raw pointer identifying reference-typed values (tables, functions,
253    /// strings, userdata). Returns null for value-typed (nil/bool/number)
254    /// values. Mirrors `mlua::Value::to_pointer`.
255    pub fn to_pointer(&self) -> *const std::ffi::c_void {
256        match self {
257            Value::LightUserData(lud) => lud.0 as *const std::ffi::c_void,
258            Value::String(s) => s.to_pointer(),
259            Value::Table(t) => t.to_pointer(),
260            Value::Function(f) => f.to_pointer(),
261            Value::UserData(u) => u.to_pointer(),
262            Value::Thread(t) => t.to_pointer(),
263            // Buffers are GC objects (reference-typed); vectors are inline
264            // value types (no pointer identity), matching mlua.
265            Value::Buffer(b) => b.to_pointer(),
266            _ => core::ptr::null(),
267        }
268    }
269
270    /// Compare two values for equality honoring `__eq` metamethods.
271    /// Mirrors `mlua::Value::equals`.
272    pub fn equals(&self, other: &Value) -> Result<bool> {
273        // For reference types, route through the VM's `lua_equal` (which runs
274        // `__eq`). For value types, structural equality matches Lua semantics.
275        match (self, other) {
276            (Value::Table(a), Value::Table(b)) => a.equals(b),
277            (Value::UserData(a), Value::UserData(b)) => a.equals(b),
278            _ => Ok(self == other),
279        }
280    }
281
282    /// The metatable-aware string form of this value (honors `__tostring`).
283    /// Mirrors `mlua::Value::to_string`.
284    #[allow(clippy::inherent_to_string_shadow_display)]
285    pub fn to_string(&self) -> Result<String> {
286        match self {
287            Value::Nil => Ok("nil".to_string()),
288            Value::Boolean(b) => Ok(b.to_string()),
289            Value::Integer(i) => Ok(i.to_string()),
290            Value::Number(n) => Ok(crate::value::format_number(*n)),
291            Value::Error(e) => Ok(e.to_string()),
292            // Vector is an inline value type: format it directly (mlua does the
293            // same, via `Vector`'s `Display`).
294            Value::Vector(v) => Ok(v.to_string()),
295            // Light userdata: format the pointer, matching Lua's `tostring`
296            // (`userdata: 0x...`).
297            Value::LightUserData(lud) => Ok(format!("userdata: {:p}", lud.0)),
298            // Reference types: ask the VM (honors `__tostring`).
299            Value::String(s) => s.to_str(),
300            other => {
301                // Find the owning Lua via the handle and use luaL_tolstring.
302                let lua = match other {
303                    Value::Table(t) => t.lua(),
304                    Value::Function(f) => f.lua(),
305                    Value::UserData(u) => u.lua(),
306                    Value::Thread(t) => t.lua(),
307                    Value::Buffer(b) => b.lua(),
308                    _ => unreachable!(),
309                };
310                lua.value_to_string(other)
311            }
312        }
313    }
314}
315
316/// Format an `f64` the way Lua's `tostring` does for the common cases the
317/// tests exercise (e.g. `34.59`, integral floats as plain integers).
318pub(crate) fn format_number(n: f64) -> String {
319    if n.fract() == 0.0 && n.is_finite() && n.abs() < 1e15 {
320        format!("{}", n as i64)
321    } else {
322        // Rust's default float formatting matches Lua's "%.14g" closely enough
323        // for the values under test.
324        let s = format!("{n}");
325        s
326    }
327}
328
329impl PartialEq for Value {
330    fn eq(&self, other: &Self) -> bool {
331        match (self, other) {
332            (Value::Nil, Value::Nil) => true,
333            (Value::Boolean(a), Value::Boolean(b)) => a == b,
334            (Value::LightUserData(a), Value::LightUserData(b)) => a == b,
335            // Numbers compare by value across the integer/float subtypes,
336            // matching Lua's `==` (1 == 1.0).
337            (Value::Integer(a), Value::Integer(b)) => a == b,
338            (Value::Number(a), Value::Number(b)) => a == b,
339            (Value::Integer(a), Value::Number(b)) | (Value::Number(b), Value::Integer(a)) => {
340                (*a as f64) == *b
341            }
342            (Value::String(a), Value::String(b)) => a == b,
343            // Reference types: identity (NOT `__eq`); use `equals` for `__eq`.
344            (Value::Table(a), Value::Table(b)) => a.to_pointer() == b.to_pointer(),
345            (Value::Function(a), Value::Function(b)) => a.to_pointer() == b.to_pointer(),
346            (Value::UserData(a), Value::UserData(b)) => a.to_pointer() == b.to_pointer(),
347            (Value::Thread(a), Value::Thread(b)) => a.to_pointer() == b.to_pointer(),
348            // Vectors compare component-wise (value type); buffers by object
349            // identity (reference type), matching mlua/Luau `==`.
350            (Value::Vector(a), Value::Vector(b)) => a == b,
351            (Value::Buffer(a), Value::Buffer(b)) => a.to_pointer() == b.to_pointer(),
352            (Value::Error(a), Value::Error(b)) => a.to_string() == b.to_string(),
353            _ => false,
354        }
355    }
356}
357
358/// True if the `f64` is an exact integer within `i64` range (so it can be
359/// presented as [`Value::Integer`]).
360fn is_exact_integer(n: f64) -> bool {
361    n.fract() == 0.0 && n.is_finite() && n >= i64::MIN as f64 && n <= i64::MAX as f64
362}
363
364/// Push a [`Value`] onto the top of the Lua stack.
365pub(crate) fn push_value(lua: &Lua, value: &Value) -> Result<()> {
366    let state = lua.state();
367    unsafe {
368        match value {
369            Value::Nil => lua_pushnil(state),
370            Value::Boolean(b) => lua_pushboolean(state, *b as c_int),
371            // Light userdata: push the raw pointer with tag 0.
372            Value::LightUserData(lud) => lua_pushlightuserdatatagged(state, lud.0, 0),
373            Value::Integer(i) => lua_pushnumber(state, *i as f64),
374            Value::Number(n) => lua_pushnumber(state, *n),
375            Value::String(s) => s.push_to_stack(),
376            Value::Table(t) => t.push_to_stack(),
377            Value::Function(f) => f.push_to_stack(),
378            Value::UserData(u) => u.push_to_stack(),
379            Value::Thread(t) => t.push_to_stack(),
380            Value::Buffer(b) => b.push_to_stack(),
381            // luaur is a 3-wide vector build; the 4th component is ignored by
382            // the VM. Push x/y/z (w = 0).
383            Value::Vector(v) => {
384                lua_pushvector_lua_state_f32_f32_f32_f32(state, v.x(), v.y(), v.z(), 0.0)
385            }
386            // An error value pushes as its message string (so Lua code that
387            // receives it can `tostring(err)` it). This matches how a Rust
388            // callback's `Err` surfaces to Lua as a string error object.
389            Value::Error(e) => {
390                let msg = e.to_string();
391                lua_pushlstring(state, msg.as_ptr() as *const c_char, msg.len());
392            }
393        }
394    }
395    Ok(())
396}
397
398/// Build a [`Value`] from the value at stack index `idx` (does not pop). For
399/// reference types this registers a registry reference.
400pub(crate) fn value_from_stack(lua: &Lua, idx: c_int) -> Result<Value> {
401    let state = lua.state();
402    unsafe {
403        let t = lua_type(state, idx);
404        let value = match t {
405            x if x == ttype::NIL || x == ttype::NONE => Value::Nil,
406            x if x == ttype::BOOLEAN => Value::Boolean(lua_toboolean(state, idx) != 0),
407            x if x == ttype::LIGHTUSERDATA => {
408                let p = lua_tolightuserdata(state, idx);
409                Value::LightUserData(crate::light_userdata::LightUserData(p))
410            }
411            x if x == ttype::NUMBER => {
412                let n = lua_tonumberx(state, idx, core::ptr::null_mut());
413                if is_exact_integer(n) {
414                    Value::Integer(n as i64)
415                } else {
416                    Value::Number(n)
417                }
418            }
419            x if x == ttype::STRING => {
420                lua_pushvalue(state, idx);
421                Value::String(LuaString::from_ref(lua.pop_ref()))
422            }
423            x if x == ttype::TABLE => {
424                lua_pushvalue(state, idx);
425                Value::Table(Table::from_ref(lua.pop_ref()))
426            }
427            x if x == ttype::FUNCTION => {
428                lua_pushvalue(state, idx);
429                Value::Function(Function::from_ref(lua.pop_ref()))
430            }
431            x if x == ttype::USERDATA => {
432                lua_pushvalue(state, idx);
433                Value::UserData(crate::userdata::AnyUserData::from_ref(lua.pop_ref()))
434            }
435            x if x == ttype::THREAD => {
436                lua_pushvalue(state, idx);
437                Value::Thread(crate::thread::Thread::from_ref(lua.pop_ref()))
438            }
439            x if x == ttype::VECTOR => {
440                // luaur is a 3-wide vector build: read the three components from
441                // the inline `TValue` via `lua_tovector`.
442                let p = lua_tovector(state, idx);
443                if p.is_null() {
444                    Value::Nil
445                } else {
446                    let comps = core::slice::from_raw_parts(p, crate::vector::Vector::SIZE);
447                    Value::Vector(crate::vector::Vector::new(comps[0], comps[1], comps[2]))
448                }
449            }
450            x if x == ttype::BUFFER => {
451                lua_pushvalue(state, idx);
452                Value::Buffer(crate::buffer::Buffer::from_ref(lua.pop_ref()))
453            }
454            // Any other exotic tags collapse to Nil.
455            _ => Value::Nil,
456        };
457        Ok(value)
458    }
459}