Skip to main content

luars/lua_value/
mod.rs

1// Lua 5.5 compatible value representation
2// 16 bytes, no pointer caching, all GC objects accessed via ID
3pub mod chunk_serializer;
4pub mod lua_convert;
5mod lua_string;
6mod lua_table;
7mod lua_value;
8pub mod userdata_builder;
9pub mod userdata_trait;
10
11use self::lua_value::Value;
12use std::any::Any;
13use std::fmt;
14
15pub use lua_string::*;
16pub use userdata_builder::UserDataBuilder;
17pub use userdata_trait::{UserDataTrait, lua_value_to_udvalue, udvalue_to_lua_value};
18
19// Re-export the optimized LuaValue and type enum for pattern matching
20pub use lua_table::LuaTable;
21pub use lua_value::{BIT_ISCOLLECTABLE, LUA_VNUMFLT, LUA_VNUMINT};
22pub use lua_value::{LuaValue, LuaValueKind};
23
24use crate::gc::{ProtoPtr, TablePtr, UpvaluePtr};
25use crate::lua_vm::CFunction;
26use crate::{Instruction, RefUserData};
27
28#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
29pub struct LuaValuePtr {
30    pub ptr: *mut LuaValue,
31}
32
33/// Runtime upvalue — pointer-based design matching C Lua's UpVal.
34///
35/// Like C Lua, `v` always points to the current value:
36/// - **Open**: `v` points to the stack slot in `register_stack`
37/// - **Closed**: `v` points to `self.closed_value`
38///
39/// This eliminates the match branch on every `get_value()`/`set_value()` call,
40/// replacing it with a single pointer dereference (zero branching).
41pub struct LuaUpvalue {
42    /// Always-valid pointer to the upvalue's current value.
43    /// Open → stack slot, Closed → &self.closed_value
44    v: *mut LuaValue,
45    /// Storage for the closed value. When closed, `v` points here.
46    closed_value: LuaValue,
47    /// Stack index (only meaningful when open)
48    stack_index: usize,
49}
50
51impl LuaUpvalue {
52    /// Create an open upvalue pointing to a stack location (absolute index).
53    /// `stack_ptr` must remain valid until the upvalue is closed or the pointer is updated.
54    #[inline(always)]
55    pub fn new_open(stack_index: usize, stack_ptr: LuaValuePtr) -> Self {
56        LuaUpvalue {
57            v: stack_ptr.ptr,
58            closed_value: LuaValue::nil(),
59            stack_index,
60        }
61    }
62
63    /// Create a closed upvalue with an owned value.
64    /// **IMPORTANT**: `v` is initially null. You MUST call `fix_closed_ptr()` after
65    /// the struct is placed at its final heap location (Box/Gc allocation).
66    #[inline(always)]
67    pub fn new_closed(value: LuaValue) -> Self {
68        LuaUpvalue {
69            v: std::ptr::null_mut(),
70            closed_value: value,
71            stack_index: 0,
72        }
73    }
74
75    /// Fix up the `v` pointer for a newly-created closed upvalue.
76    /// Must be called once after the struct is heap-allocated (won't move again).
77    /// No-op for open upvalues (where v is already a valid stack pointer).
78    #[inline(always)]
79    pub fn fix_closed_ptr(&mut self) {
80        if self.v.is_null() {
81            self.v = &mut self.closed_value as *mut LuaValue;
82        }
83    }
84
85    /// Check if this upvalue is open (like C Lua's `upisopen` macro).
86    /// Open ⟺ `v` does NOT point to our own `closed_value` field.
87    #[inline(always)]
88    pub fn is_open(&self) -> bool {
89        !std::ptr::eq(self.v, &self.closed_value)
90    }
91
92    /// Get the stack index (only meaningful when open).
93    #[inline(always)]
94    pub fn get_stack_index(&self) -> usize {
95        self.stack_index
96    }
97
98    /// Close this upvalue — copy value from stack into owned storage,
99    /// then redirect `v` to point to `self.closed_value`.
100    #[inline(always)]
101    pub fn close(&mut self, stack_value: LuaValue) {
102        self.closed_value = stack_value;
103        self.v = &mut self.closed_value as *mut LuaValue;
104    }
105
106    /// Update the cached stack pointer (called after stack reallocation).
107    #[inline(always)]
108    pub fn update_stack_ptr(&mut self, ptr: *mut LuaValue) {
109        self.v = ptr;
110    }
111
112    /// Get the raw v pointer (for caching in the execute loop).
113    #[inline(always)]
114    pub fn get_v_ptr(&self) -> *mut LuaValue {
115        self.v
116    }
117
118    /// Get the value with **zero branching** — single pointer dereference.
119    #[inline(always)]
120    pub fn get_value(&self) -> LuaValue {
121        debug_assert!(!self.v.is_null(), "upvalue get_value: null pointer");
122        debug_assert!(
123            (self.v as usize) > 0x10000,
124            "upvalue get_value: suspiciously low pointer {:p} (stack_index={})",
125            self.v,
126            self.stack_index
127        );
128        let val = unsafe { *self.v };
129        debug_assert!(
130            Self::is_valid_tt(val.tt()),
131            "upvalue get_value: INVALID type tag 0x{:02X} read from {:p} (stack_index={}, is_open={}). Likely dangling pointer!",
132            val.tt(),
133            self.v,
134            self.stack_index,
135            self.is_open()
136        );
137        val
138    }
139
140    /// Get reference to the value with **zero branching**.
141    #[inline(always)]
142    pub fn get_value_ref(&self) -> &LuaValue {
143        debug_assert!(!self.v.is_null(), "upvalue get_value_ref: null pointer");
144        unsafe { &*self.v }
145    }
146
147    /// Set the value with **zero branching** — single pointer write.
148    #[inline(always)]
149    pub fn set_value(&mut self, val: LuaValue) {
150        debug_assert!(!self.v.is_null(), "upvalue set_value: null pointer");
151        debug_assert!(
152            (self.v as usize) > 0x10000,
153            "upvalue set_value: suspiciously low pointer {:p} (stack_index={})",
154            self.v,
155            self.stack_index
156        );
157        unsafe { *self.v = val }
158    }
159
160    /// Set the value by raw parts to avoid constructing a temporary LuaValue.
161    #[inline(always)]
162    pub fn set_value_parts(&mut self, value: Value, tt: u8) {
163        debug_assert!(!self.v.is_null(), "upvalue set_value_parts: null pointer");
164        debug_assert!(
165            (self.v as usize) > 0x10000,
166            "upvalue set_value_parts: suspiciously low pointer {:p} (stack_index={})",
167            self.v,
168            self.stack_index
169        );
170        unsafe {
171            (*self.v).value = value;
172            (*self.v).tt = tt;
173        }
174    }
175
176    /// Check if a type tag is valid (used for dangling pointer detection)
177    fn is_valid_tt(tt: u8) -> bool {
178        use crate::lua_value::lua_value::*;
179        matches!(
180            tt,
181            LUA_VNIL
182                | LUA_VEMPTY
183                | LUA_VABSTKEY
184                | LUA_VFALSE
185                | LUA_VTRUE
186                | LUA_VNUMINT
187                | LUA_VNUMFLT
188                | LUA_VSHRSTR
189                | LUA_VLNGSTR
190                | LUA_VTABLE
191                | LUA_VFUNCTION
192                | LUA_CCLOSURE
193                | LUA_VLCF
194                | LUA_VLIGHTUSERDATA
195                | LUA_VUSERDATA
196                | LUA_VTHREAD
197        )
198    }
199
200    pub fn get_closed_value(&self) -> Option<&LuaValue> {
201        if !self.is_open() {
202            Some(&self.closed_value)
203        } else {
204            None
205        }
206    }
207}
208
209/// Userdata - arbitrary Rust data with optional metatable.
210///
211/// Uses `Box<dyn UserDataTrait>` for trait-based dispatch of field access,
212/// method calls, and metamethods. Falls back to metatable for Lua-level customization.
213pub struct LuaUserdata {
214    data: Box<dyn UserDataTrait>,
215    metatable: TablePtr,
216}
217
218impl LuaUserdata {
219    /// Create a new userdata wrapping a value that implements `UserDataTrait`.
220    pub fn new<T: UserDataTrait>(data: T) -> Self {
221        LuaUserdata {
222            data: Box::new(data),
223            metatable: TablePtr::null(),
224        }
225    }
226
227    /// Create a userdata from an already-boxed trait object.
228    ///
229    /// Used by the VM to convert `UdValue::UserdataOwned` results from
230    /// arithmetic trait methods into GC-managed userdata.
231    pub fn from_boxed(data: Box<dyn UserDataTrait>) -> Self {
232        LuaUserdata {
233            data,
234            metatable: TablePtr::null(),
235        }
236    }
237
238    /// Create a borrowed userdata from a mutable reference.
239    ///
240    /// The resulting userdata forwards all field/method/metamethod access through
241    /// a raw pointer — zero overhead, no ownership transfer.
242    ///
243    /// # Safety
244    /// The referenced object **must** outlive all Lua accesses to this userdata.
245    /// Accessing the userdata after the Rust object is dropped is **undefined behavior**.
246    #[inline]
247    pub unsafe fn from_ref<T: UserDataTrait>(reference: &mut T) -> Self {
248        LuaUserdata {
249            data: Box::new(unsafe { RefUserData::new(reference) }),
250            metatable: TablePtr::null(),
251        }
252    }
253
254    /// Create a borrowed userdata from a raw pointer.
255    ///
256    /// # Safety
257    /// The pointer must be valid and properly aligned for the entire duration
258    /// that Lua can access this userdata.
259    #[inline]
260    pub unsafe fn from_raw_ptr<T: UserDataTrait>(ptr: *mut T) -> Self {
261        LuaUserdata {
262            data: Box::new(unsafe { RefUserData::from_raw(ptr) }),
263            metatable: TablePtr::null(),
264        }
265    }
266
267    /// Create a new userdata with an initial metatable.
268    pub fn with_metatable<T: UserDataTrait>(data: T, metatable: TablePtr) -> Self {
269        LuaUserdata {
270            data: Box::new(data),
271            metatable,
272        }
273    }
274
275    // ==================== Trait-based access ====================
276
277    /// Get the trait object for direct field/method/metamethod dispatch.
278    #[inline]
279    pub fn get_trait(&self) -> &dyn UserDataTrait {
280        self.data.as_ref()
281    }
282
283    /// Get the mutable trait object.
284    #[inline]
285    pub fn get_trait_mut(&mut self) -> &mut dyn UserDataTrait {
286        self.data.as_mut()
287    }
288
289    /// Get the type name from the trait.
290    #[inline]
291    pub fn type_name(&self) -> &'static str {
292        self.data.type_name()
293    }
294
295    // ==================== Backward-compatible downcast access ====================
296
297    /// Downcast to a concrete type (immutable). Equivalent to old `get_data().downcast_ref::<T>()`.
298    #[inline]
299    pub fn downcast_ref<T: 'static>(&self) -> Option<&T> {
300        self.data.as_any().downcast_ref::<T>()
301    }
302
303    /// Downcast to a concrete type (mutable). Equivalent to old `get_data_mut().downcast_mut::<T>()`.
304    #[inline]
305    pub fn downcast_mut<T: 'static>(&mut self) -> Option<&mut T> {
306        self.data.as_any_mut().downcast_mut::<T>()
307    }
308
309    /// Get raw `&dyn Any` reference (backward compatibility).
310    pub fn get_data(&self) -> &dyn Any {
311        self.data.as_any()
312    }
313
314    /// Get raw `&mut dyn Any` reference (backward compatibility).
315    pub fn get_data_mut(&mut self) -> &mut dyn Any {
316        self.data.as_any_mut()
317    }
318
319    // ==================== Metatable ====================
320
321    pub fn get_metatable(&self) -> Option<LuaValue> {
322        if self.metatable.is_null() {
323            None
324        } else {
325            Some(LuaValue::table(self.metatable))
326        }
327    }
328
329    pub(crate) fn set_metatable(&mut self, metatable: LuaValue) {
330        if let Some(table_ptr) = metatable.as_table_ptr() {
331            self.metatable = table_ptr;
332        } else if metatable.is_nil() {
333            self.metatable = TablePtr::null();
334        } else {
335            debug_assert!(
336                false,
337                "Attempted to set userdata metatable to non-table, non-nil value"
338            );
339        }
340    }
341}
342
343impl fmt::Debug for LuaUserdata {
344    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
345        write!(
346            f,
347            "Userdata({}@{:p})",
348            self.data.type_name(),
349            self.data.as_any() as *const dyn Any
350        )
351    }
352}
353
354/// Upvalue descriptor
355#[derive(Debug, Clone)]
356pub struct UpvalueDesc {
357    pub name: String,   // upvalue name
358    pub is_local: bool, // true if captures parent local, false if captures parent upvalue
359    pub index: u32,     // index in parent's register or upvalue array
360}
361
362/// Local variable debug info (mirrors Lua 5.5's LocVar)
363#[derive(Debug, Clone)]
364pub struct LocVar {
365    pub name: String, // variable name
366    pub startpc: u32, // first point where variable is active
367    pub endpc: u32,   // first point where variable is dead
368}
369
370/// Compiled chunk (bytecode + metadata)
371#[derive(Debug, Clone)]
372pub struct LuaProto {
373    pub code: Vec<Instruction>,
374    pub constants: Vec<LuaValue>,
375    pub locals: Vec<LocVar>,
376    pub upvalue_count: usize,
377    pub param_count: usize,
378    pub is_vararg: bool,          // Whether function uses ... (varargs)
379    pub needs_vararg_table: bool, // Whether function needs vararg table (PF_VATAB in Lua 5.5)
380    pub use_hidden_vararg: bool,  // Whether function uses hidden vararg args (PF_VAHID in Lua 5.5)
381    pub max_stack_size: usize,
382    pub child_protos: Vec<ProtoPtr>,     // Nested function prototypes
383    pub upvalue_descs: Vec<UpvalueDesc>, // Upvalue descriptors
384    pub source_name: Option<String>,     // Source file/chunk name for debugging
385    pub line_info: Vec<u32>,             // Line number for each instruction (for debug)
386    pub linedefined: usize,              // Line where function starts (0 for main)
387    pub lastlinedefined: usize,          // Line where function ends (0 for main)
388    pub proto_data_size: u32,            // Cached size for GC (code+constants+children+lines)
389}
390
391impl Default for LuaProto {
392    fn default() -> Self {
393        Self::new()
394    }
395}
396
397impl LuaProto {
398    pub fn new() -> Self {
399        LuaProto {
400            code: Vec::new(),
401            constants: Vec::new(),
402            locals: Vec::new(),
403            upvalue_count: 0,
404            param_count: 0,
405            is_vararg: false,
406            needs_vararg_table: false,
407            use_hidden_vararg: false,
408            max_stack_size: 0,
409            child_protos: Vec::new(),
410            upvalue_descs: Vec::new(),
411            source_name: None,
412            line_info: Vec::new(),
413            linedefined: 0,
414            lastlinedefined: 0,
415            proto_data_size: 0,
416        }
417    }
418
419    /// Compute and cache proto_data_size. Call once after compilation is complete.
420    pub fn compute_proto_data_size(&mut self) {
421        use std::mem::size_of;
422        let instr_size = self.code.len() * size_of::<crate::lua_vm::Instruction>();
423        let const_size = self.constants.len() * size_of::<LuaValue>();
424        let child_size = self.child_protos.len() * size_of::<ProtoPtr>();
425        let line_size = self.line_info.len() * size_of::<u32>();
426        self.proto_data_size = (instr_size + const_size + child_size + line_size) as u32;
427    }
428
429    #[cfg(feature = "shared-proto")]
430    pub fn share_constant_strings(&mut self) -> usize {
431        let mut shared_count = 0;
432
433        for constant in &mut self.constants {
434            shared_count += usize::from(crate::gc::share_lua_value(constant));
435        }
436
437        shared_count
438    }
439
440    #[cfg(feature = "shared-proto")]
441    pub fn share_proto_strings(&mut self) -> usize {
442        let mut shared_count = self.share_constant_strings();
443
444        for child in &mut self.child_protos {
445            shared_count += child.as_mut_ref().data.share_proto_strings();
446        }
447
448        shared_count
449    }
450}
451
452/// Inline storage for upvalue pointers — avoids heap allocation for 0-1 upvalues.
453/// Most closures in Lua have 1 upvalue (_ENV), so this eliminates one allocation
454/// per closure creation on the most common path.
455pub enum UpvalueStore {
456    Empty,
457    One(UpvaluePtr),
458    Many(Box<[UpvaluePtr]>),
459}
460
461impl UpvalueStore {
462    #[inline(always)]
463    pub fn from_single(ptr: UpvaluePtr) -> Self {
464        UpvalueStore::One(ptr)
465    }
466
467    #[inline(always)]
468    pub fn from_vec(v: Vec<UpvaluePtr>) -> Self {
469        match v.len() {
470            0 => UpvalueStore::Empty,
471            1 => UpvalueStore::One(v[0]),
472            _ => UpvalueStore::Many(v.into_boxed_slice()),
473        }
474    }
475
476    #[inline(always)]
477    pub fn as_slice(&self) -> &[UpvaluePtr] {
478        match self {
479            UpvalueStore::Empty => &[],
480            UpvalueStore::One(p) => std::slice::from_ref(p),
481            UpvalueStore::Many(b) => b,
482        }
483    }
484
485    #[inline(always)]
486    pub fn as_mut_slice(&mut self) -> &mut [UpvaluePtr] {
487        match self {
488            UpvalueStore::Empty => &mut [],
489            UpvalueStore::One(p) => std::slice::from_mut(p),
490            UpvalueStore::Many(b) => b,
491        }
492    }
493
494    #[inline(always)]
495    pub fn len(&self) -> usize {
496        match self {
497            UpvalueStore::Empty => 0,
498            UpvalueStore::One(_) => 1,
499            UpvalueStore::Many(b) => b.len(),
500        }
501    }
502}
503
504pub struct LuaFunction {
505    chunk: ProtoPtr,
506    upvalue_store: UpvalueStore,
507}
508
509impl LuaFunction {
510    pub fn new(chunk: ProtoPtr, upvalue_store: UpvalueStore) -> Self {
511        LuaFunction {
512            chunk,
513            upvalue_store,
514        }
515    }
516
517    /// Get the chunk if this is a Lua function
518    #[inline(always)]
519    pub fn chunk(&self) -> &LuaProto {
520        &self.chunk.as_ref().data
521    }
522
523    #[inline(always)]
524    pub fn proto(&self) -> ProtoPtr {
525        self.chunk
526    }
527
528    /// Get upvalue pointers as a slice.
529    #[inline(always)]
530    pub fn upvalues(&self) -> &[UpvaluePtr] {
531        self.upvalue_store.as_slice()
532    }
533
534    /// Get mutable access to upvalue pointers (used by debug.upvaluejoin)
535    #[inline(always)]
536    pub fn upvalues_mut(&mut self) -> &mut [UpvaluePtr] {
537        self.upvalue_store.as_mut_slice()
538    }
539}
540
541pub struct CClosureFunction {
542    func: CFunction,
543    upvalues: Vec<LuaValue>,
544}
545
546impl CClosureFunction {
547    pub fn new(func: CFunction, upvalues: Vec<LuaValue>) -> Self {
548        CClosureFunction { func, upvalues }
549    }
550
551    /// Get the C function pointer
552    #[inline(always)]
553    pub fn func(&self) -> CFunction {
554        self.func
555    }
556
557    /// Get upvalues
558    #[inline(always)]
559    pub fn upvalues(&self) -> &Vec<LuaValue> {
560        &self.upvalues
561    }
562
563    /// Get mutable access to upvalues
564    #[inline(always)]
565    pub fn upvalues_mut(&mut self) -> &mut Vec<LuaValue> {
566        &mut self.upvalues
567    }
568}
569
570/// Rust closure callback — can capture arbitrary Rust state via Box<dyn Fn>
571pub type RustCallback = Box<dyn Fn(&mut crate::lua_vm::LuaState) -> crate::LuaResult<usize>>;
572
573/// RClosure: Rust closure function with optional LuaValue upvalues.
574/// Unlike CClosureFunction (which stores a bare fn pointer), this stores
575/// a heap-allocated trait object that can capture arbitrary Rust state.
576pub struct RClosureFunction {
577    func: RustCallback,
578    upvalues: Vec<LuaValue>,
579}
580
581impl RClosureFunction {
582    pub fn new(func: RustCallback, upvalues: Vec<LuaValue>) -> Self {
583        RClosureFunction { func, upvalues }
584    }
585
586    /// Call the Rust closure
587    #[inline(always)]
588    pub fn call(&self, state: &mut crate::lua_vm::LuaState) -> crate::LuaResult<usize> {
589        (self.func)(state)
590    }
591
592    /// Get upvalues
593    #[inline(always)]
594    pub fn upvalues(&self) -> &Vec<LuaValue> {
595        &self.upvalues
596    }
597
598    /// Get mutable access to upvalues
599    #[inline(always)]
600    pub fn upvalues_mut(&mut self) -> &mut Vec<LuaValue> {
601        &mut self.upvalues
602    }
603}
604
605#[cfg(test)]
606mod value_tests {
607    use super::*;
608
609    #[test]
610    fn test_integer_float_distinction() {
611        let int_val = LuaValue::integer(42);
612        let float_val = LuaValue::number(42.0);
613
614        assert!(int_val.is_integer());
615        assert!(!int_val.is_float());
616        assert!(!float_val.is_integer()); // 42.0 is a float, not an integer
617        assert!(float_val.is_float());
618
619        // Both are numbers
620        assert!(int_val.is_number());
621        assert!(float_val.is_number());
622    }
623
624    #[test]
625    fn test_integer_float_conversion() {
626        let int_val = LuaValue::integer(42);
627        let float_val = LuaValue::number(42.5);
628
629        // Integer can convert to float via as_float
630        assert_eq!(int_val.as_float(), Some(42.0));
631
632        // Float with fraction cannot convert to integer
633        assert_eq!(float_val.as_integer(), None);
634
635        // Float without fraction can convert to integer
636        let exact_float = LuaValue::number(42.0);
637        assert_eq!(exact_float.as_integer(), Some(42));
638    }
639
640    #[test]
641    fn test_as_number_unified() {
642        let int_val = LuaValue::integer(42);
643        let float_val = LuaValue::number(3.15);
644
645        // as_number works for both
646        assert_eq!(int_val.as_number(), Some(42.0));
647        assert_eq!(float_val.as_number(), Some(3.15));
648    }
649}