mlua_sys/luau/
compat.rs

1//! MLua compatibility layer for Roblox Luau.
2//!
3//! Based on github.com/keplerproject/lua-compat-5.3
4
5use std::ffi::CStr;
6use std::os::raw::{c_char, c_int, c_void};
7use std::{mem, ptr};
8
9use super::lauxlib::*;
10use super::lua::*;
11use super::luacode::*;
12
13unsafe fn compat53_reverse(L: *mut lua_State, mut a: c_int, mut b: c_int) {
14    while a < b {
15        lua_pushvalue(L, a);
16        lua_pushvalue(L, b);
17        lua_replace(L, a);
18        lua_replace(L, b);
19        a += 1;
20        b -= 1;
21    }
22}
23
24const COMPAT53_LEVELS1: c_int = 12; // size of the first part of the stack
25const COMPAT53_LEVELS2: c_int = 10; // size of the second part of the stack
26
27unsafe fn compat53_findfield(L: *mut lua_State, objidx: c_int, level: c_int) -> c_int {
28    if level == 0 || lua_istable(L, -1) == 0 {
29        return 0; // not found
30    }
31
32    lua_pushnil(L); // start 'next' loop
33    while lua_next(L, -2) != 0 {
34        // for each pair in table
35        if lua_type(L, -2) == LUA_TSTRING {
36            // ignore non-string keys
37            if lua_rawequal(L, objidx, -1) != 0 {
38                // found object?
39                lua_pop(L, 1); // remove value (but keep name)
40                return 1;
41            } else if compat53_findfield(L, objidx, level - 1) != 0 {
42                // try recursively
43                lua_remove(L, -2); // remove table (but keep name)
44                lua_pushliteral(L, ".");
45                lua_insert(L, -2); // place '.' between the two names
46                lua_concat(L, 3);
47                return 1;
48            }
49        }
50        lua_pop(L, 1); // remove value
51    }
52    0 // not found
53}
54
55unsafe fn compat53_pushglobalfuncname(L: *mut lua_State, level: c_int, ar: *mut lua_Debug) -> c_int {
56    let top = lua_gettop(L);
57    // push function
58    lua_getinfo(L, level, cstr!("f"), ar);
59    lua_pushvalue(L, LUA_GLOBALSINDEX);
60    if compat53_findfield(L, top + 1, 2) != 0 {
61        lua_copy(L, -1, top + 1); // move name to proper place
62        lua_pop(L, 2); // remove pushed values
63        1
64    } else {
65        lua_settop(L, top); // remove function and global table
66        0
67    }
68}
69
70unsafe fn compat53_pushfuncname(L: *mut lua_State, level: c_int, ar: *mut lua_Debug) {
71    if !(*ar).name.is_null() {
72        // is there a name?
73        lua_pushfstring(L, cstr!("function '%s'"), (*ar).name);
74    } else if compat53_pushglobalfuncname(L, level, ar) != 0 {
75        lua_pushfstring(L, cstr!("function '%s'"), lua_tostring(L, -1));
76        lua_remove(L, -2); // remove name
77    } else {
78        lua_pushliteral(L, "?");
79    }
80}
81
82//
83// lua ported functions
84//
85
86pub unsafe fn lua_rotate(L: *mut lua_State, mut idx: c_int, mut n: c_int) {
87    idx = lua_absindex(L, idx);
88    if n > 0 {
89        // Faster version
90        for _ in 0..n {
91            lua_insert(L, idx);
92        }
93        return;
94    }
95    let n_elems = lua_gettop(L) - idx + 1;
96    if n < 0 {
97        n += n_elems;
98    }
99    if n > 0 && n < n_elems {
100        luaL_checkstack(L, 2, cstr!("not enough stack slots available"));
101        n = n_elems - n;
102        compat53_reverse(L, idx, idx + n - 1);
103        compat53_reverse(L, idx + n, idx + n_elems - 1);
104        compat53_reverse(L, idx, idx + n_elems - 1);
105    }
106}
107
108#[inline(always)]
109pub unsafe fn lua_copy(L: *mut lua_State, fromidx: c_int, toidx: c_int) {
110    let abs_to = lua_absindex(L, toidx);
111    luaL_checkstack(L, 1, cstr!("not enough stack slots available"));
112    lua_pushvalue(L, fromidx);
113    lua_replace(L, abs_to);
114}
115
116#[inline(always)]
117pub unsafe fn lua_isinteger(L: *mut lua_State, idx: c_int) -> c_int {
118    if lua_type(L, idx) == LUA_TNUMBER {
119        let n = lua_tonumber(L, idx);
120        let i = lua_tointeger(L, idx);
121        if (n - i as lua_Number).abs() < lua_Number::EPSILON {
122            return 1;
123        }
124    }
125    0
126}
127
128#[inline(always)]
129pub unsafe fn lua_tointeger(L: *mut lua_State, i: c_int) -> lua_Integer {
130    lua_tointegerx(L, i, ptr::null_mut())
131}
132
133pub unsafe fn lua_tointegerx(L: *mut lua_State, i: c_int, isnum: *mut c_int) -> lua_Integer {
134    let mut ok = 0;
135    let n = lua_tonumberx(L, i, &mut ok);
136    let n_int = n as lua_Integer;
137    if ok != 0 && (n - n_int as lua_Number).abs() < lua_Number::EPSILON {
138        if !isnum.is_null() {
139            *isnum = 1;
140        }
141        return n_int;
142    }
143    if !isnum.is_null() {
144        *isnum = 0;
145    }
146    0
147}
148
149#[inline(always)]
150pub unsafe fn lua_rawlen(L: *mut lua_State, idx: c_int) -> usize {
151    lua_objlen(L, idx)
152}
153
154#[inline(always)]
155pub unsafe fn lua_pushlstring(L: *mut lua_State, s: *const c_char, l: usize) -> *const c_char {
156    if l == 0 {
157        lua_pushlstring_(L, cstr!(""), 0);
158    } else {
159        lua_pushlstring_(L, s, l);
160    }
161    lua_tostring(L, -1)
162}
163
164#[inline(always)]
165pub unsafe fn lua_pushstring(L: *mut lua_State, s: *const c_char) -> *const c_char {
166    lua_pushstring_(L, s);
167    lua_tostring(L, -1)
168}
169
170#[inline(always)]
171pub unsafe fn lua_geti(L: *mut lua_State, mut idx: c_int, n: lua_Integer) -> c_int {
172    idx = lua_absindex(L, idx);
173    lua_pushinteger(L, n);
174    lua_gettable(L, idx)
175}
176
177#[inline(always)]
178pub unsafe fn lua_rawgeti(L: *mut lua_State, idx: c_int, n: lua_Integer) -> c_int {
179    lua_rawgeti_(L, idx, n)
180}
181
182#[inline(always)]
183pub unsafe fn lua_rawgetp(L: *mut lua_State, idx: c_int, p: *const c_void) -> c_int {
184    let abs_i = lua_absindex(L, idx);
185    lua_pushlightuserdata(L, p as *mut c_void);
186    lua_rawget(L, abs_i)
187}
188
189#[inline(always)]
190pub unsafe fn lua_getuservalue(L: *mut lua_State, mut idx: c_int) -> c_int {
191    luaL_checkstack(L, 2, cstr!("not enough stack slots available"));
192    idx = lua_absindex(L, idx);
193    lua_pushliteral(L, "__mlua_uservalues");
194    if lua_rawget(L, LUA_REGISTRYINDEX) != LUA_TTABLE {
195        return LUA_TNIL;
196    }
197    lua_pushvalue(L, idx);
198    lua_rawget(L, -2);
199    lua_remove(L, -2);
200    lua_type(L, -1)
201}
202
203#[inline(always)]
204pub unsafe fn lua_seti(L: *mut lua_State, mut idx: c_int, n: lua_Integer) {
205    luaL_checkstack(L, 1, cstr!("not enough stack slots available"));
206    idx = lua_absindex(L, idx);
207    lua_pushinteger(L, n);
208    lua_insert(L, -2);
209    lua_settable(L, idx);
210}
211
212#[inline(always)]
213pub unsafe fn lua_rawseti(L: *mut lua_State, idx: c_int, n: lua_Integer) {
214    lua_rawseti_(L, idx, n)
215}
216
217#[inline(always)]
218pub unsafe fn lua_rawsetp(L: *mut lua_State, idx: c_int, p: *const c_void) {
219    let abs_i = lua_absindex(L, idx);
220    luaL_checkstack(L, 1, cstr!("not enough stack slots available"));
221    lua_pushlightuserdata(L, p as *mut c_void);
222    lua_insert(L, -2);
223    lua_rawset(L, abs_i);
224}
225
226#[inline(always)]
227pub unsafe fn lua_setuservalue(L: *mut lua_State, mut idx: c_int) {
228    luaL_checkstack(L, 4, cstr!("not enough stack slots available"));
229    idx = lua_absindex(L, idx);
230    lua_pushliteral(L, "__mlua_uservalues");
231    lua_pushvalue(L, -1);
232    if lua_rawget(L, LUA_REGISTRYINDEX) != LUA_TTABLE {
233        lua_pop(L, 1);
234        lua_createtable(L, 0, 2); // main table
235        lua_createtable(L, 0, 1); // metatable
236        lua_pushliteral(L, "k");
237        lua_setfield(L, -2, cstr!("__mode"));
238        lua_setmetatable(L, -2);
239        lua_pushvalue(L, -2);
240        lua_pushvalue(L, -2);
241        lua_rawset(L, LUA_REGISTRYINDEX);
242    }
243    lua_replace(L, -2);
244    lua_pushvalue(L, idx);
245    lua_pushvalue(L, -3);
246    lua_remove(L, -4);
247    lua_rawset(L, -3);
248    lua_pop(L, 1);
249}
250
251#[inline(always)]
252pub unsafe fn lua_len(L: *mut lua_State, idx: c_int) {
253    match lua_type(L, idx) {
254        LUA_TSTRING => {
255            lua_pushnumber(L, lua_objlen(L, idx) as lua_Number);
256        }
257        LUA_TTABLE => {
258            if luaL_callmeta(L, idx, cstr!("__len")) == 0 {
259                lua_pushnumber(L, lua_objlen(L, idx) as lua_Number);
260            }
261        }
262        LUA_TUSERDATA if luaL_callmeta(L, idx, cstr!("__len")) != 0 => {}
263        _ => {
264            luaL_error(
265                L,
266                cstr!("attempt to get length of a %s value"),
267                lua_typename(L, lua_type(L, idx)),
268            );
269        }
270    }
271}
272
273#[inline(always)]
274pub unsafe fn lua_pushglobaltable(L: *mut lua_State) {
275    lua_pushvalue(L, LUA_GLOBALSINDEX);
276}
277
278#[inline(always)]
279pub unsafe fn lua_resume(L: *mut lua_State, from: *mut lua_State, narg: c_int, nres: *mut c_int) -> c_int {
280    let ret = lua_resume_(L, from, narg);
281    if (ret == LUA_OK || ret == LUA_YIELD) && !(nres.is_null()) {
282        *nres = lua_gettop(L);
283    }
284    ret
285}
286
287//
288// lauxlib ported functions
289//
290
291#[inline(always)]
292pub unsafe fn luaL_checkstack(L: *mut lua_State, sz: c_int, msg: *const c_char) {
293    if lua_checkstack(L, sz + LUA_MINSTACK) == 0 {
294        if !msg.is_null() {
295            luaL_error(L, cstr!("stack overflow (%s)"), msg);
296        } else {
297            lua_pushliteral(L, "stack overflow");
298            lua_error(L);
299        }
300    }
301}
302
303#[inline(always)]
304pub unsafe fn luaL_getmetafield(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int {
305    if luaL_getmetafield_(L, obj, e) != 0 {
306        lua_type(L, -1)
307    } else {
308        LUA_TNIL
309    }
310}
311
312#[inline(always)]
313pub unsafe fn luaL_newmetatable(L: *mut lua_State, tname: *const c_char) -> c_int {
314    if luaL_newmetatable_(L, tname) != 0 {
315        lua_pushstring(L, tname);
316        lua_setfield(L, -2, cstr!("__type"));
317        1
318    } else {
319        0
320    }
321}
322
323pub unsafe fn luaL_loadbufferenv(
324    L: *mut lua_State,
325    data: *const c_char,
326    mut size: usize,
327    name: *const c_char,
328    mode: *const c_char,
329    mut env: c_int,
330) -> c_int {
331    extern "C" {
332        fn free(p: *mut c_void);
333    }
334
335    unsafe extern "C-unwind" fn data_dtor(data: *mut c_void) {
336        free(*(data as *mut *mut c_char) as *mut c_void);
337    }
338
339    let chunk_is_text = size == 0 || (*data as u8) >= b'\t';
340    if !mode.is_null() {
341        let modeb = CStr::from_ptr(mode).to_bytes();
342        if !chunk_is_text && !modeb.contains(&b'b') {
343            lua_pushfstring(L, cstr!("attempt to load a binary chunk (mode is '%s')"), mode);
344            return LUA_ERRSYNTAX;
345        } else if chunk_is_text && !modeb.contains(&b't') {
346            lua_pushfstring(L, cstr!("attempt to load a text chunk (mode is '%s')"), mode);
347            return LUA_ERRSYNTAX;
348        }
349    }
350
351    if chunk_is_text {
352        if env < 0 {
353            env -= 1;
354        }
355        let data_ud = lua_newuserdatadtor(L, mem::size_of::<*mut c_char>(), data_dtor) as *mut *mut c_char;
356        let data = luau_compile_(data, size, ptr::null_mut(), &mut size);
357        ptr::write(data_ud, data);
358        // By deferring the `free(data)` to the userdata destructor, we ensure that
359        // even if `luau_load` throws an error, the `data` is still released.
360        let ok = luau_load(L, name, data, size, env) == 0;
361        lua_replace(L, -2); // replace data with the result
362        if !ok {
363            return LUA_ERRSYNTAX;
364        }
365    } else if luau_load(L, name, data, size, env) != 0 {
366        return LUA_ERRSYNTAX;
367    }
368    LUA_OK
369}
370
371#[inline(always)]
372pub unsafe fn luaL_loadbufferx(
373    L: *mut lua_State,
374    data: *const c_char,
375    size: usize,
376    name: *const c_char,
377    mode: *const c_char,
378) -> c_int {
379    luaL_loadbufferenv(L, data, size, name, mode, 0)
380}
381
382#[inline(always)]
383pub unsafe fn luaL_loadbuffer(
384    L: *mut lua_State,
385    data: *const c_char,
386    size: usize,
387    name: *const c_char,
388) -> c_int {
389    luaL_loadbufferenv(L, data, size, name, ptr::null(), 0)
390}
391
392#[inline(always)]
393pub unsafe fn luaL_len(L: *mut lua_State, idx: c_int) -> lua_Integer {
394    let mut isnum = 0;
395    luaL_checkstack(L, 1, cstr!("not enough stack slots available"));
396    lua_len(L, idx);
397    let res = lua_tointegerx(L, -1, &mut isnum);
398    lua_pop(L, 1);
399    if isnum == 0 {
400        luaL_error(L, cstr!("object length is not an integer"));
401    }
402    res
403}
404
405pub unsafe fn luaL_traceback(L: *mut lua_State, L1: *mut lua_State, msg: *const c_char, mut level: c_int) {
406    let mut ar: lua_Debug = mem::zeroed();
407    let top = lua_gettop(L);
408    let numlevels = lua_stackdepth(L);
409    let mark = if numlevels > COMPAT53_LEVELS1 + COMPAT53_LEVELS2 {
410        COMPAT53_LEVELS1
411    } else {
412        0
413    };
414
415    if !msg.is_null() {
416        lua_pushfstring(L, cstr!("%s\n"), msg);
417    }
418    lua_pushliteral(L, "stack traceback:");
419    while lua_getinfo(L1, level, cstr!(""), &mut ar) != 0 {
420        if level + 1 == mark {
421            // too many levels?
422            lua_pushliteral(L, "\n\t..."); // add a '...'
423            level = numlevels - COMPAT53_LEVELS2; // and skip to last ones
424        } else {
425            lua_getinfo(L1, level, cstr!("sln"), &mut ar);
426            lua_pushfstring(L, cstr!("\n\t%s:"), ar.short_src);
427            if ar.currentline > 0 {
428                lua_pushfstring(L, cstr!("%d:"), ar.currentline);
429            }
430            lua_pushliteral(L, " in ");
431            compat53_pushfuncname(L, level, &mut ar);
432            lua_concat(L, lua_gettop(L) - top);
433        }
434        level += 1;
435    }
436    lua_concat(L, lua_gettop(L) - top);
437}
438
439pub unsafe fn luaL_tolstring(L: *mut lua_State, mut idx: c_int, len: *mut usize) -> *const c_char {
440    idx = lua_absindex(L, idx);
441    if luaL_callmeta(L, idx, cstr!("__tostring")) == 0 {
442        match lua_type(L, idx) {
443            LUA_TNIL => {
444                lua_pushliteral(L, "nil");
445            }
446            LUA_TSTRING | LUA_TNUMBER => {
447                lua_pushvalue(L, idx);
448            }
449            LUA_TBOOLEAN => {
450                if lua_toboolean(L, idx) == 0 {
451                    lua_pushliteral(L, "false");
452                } else {
453                    lua_pushliteral(L, "true");
454                }
455            }
456            t => {
457                let tt = luaL_getmetafield(L, idx, cstr!("__type"));
458                let name = if tt == LUA_TSTRING {
459                    lua_tostring(L, -1)
460                } else {
461                    lua_typename(L, t)
462                };
463                lua_pushfstring(L, cstr!("%s: %p"), name, lua_topointer(L, idx));
464                if tt != LUA_TNIL {
465                    lua_replace(L, -2); // remove '__type'
466                }
467            }
468        };
469    } else if lua_isstring(L, -1) == 0 {
470        luaL_error(L, cstr!("'__tostring' must return a string"));
471    }
472    lua_tolstring(L, -1, len)
473}
474
475#[inline(always)]
476pub unsafe fn luaL_setmetatable(L: *mut lua_State, tname: *const c_char) {
477    luaL_checkstack(L, 1, cstr!("not enough stack slots available"));
478    luaL_getmetatable(L, tname);
479    lua_setmetatable(L, -2);
480}
481
482pub unsafe fn luaL_getsubtable(L: *mut lua_State, idx: c_int, fname: *const c_char) -> c_int {
483    let abs_i = lua_absindex(L, idx);
484    luaL_checkstack(L, 3, cstr!("not enough stack slots available"));
485    lua_pushstring_(L, fname);
486    if lua_gettable(L, abs_i) == LUA_TTABLE {
487        return 1;
488    }
489    lua_pop(L, 1);
490    lua_newtable(L);
491    lua_pushstring_(L, fname);
492    lua_pushvalue(L, -2);
493    lua_settable(L, abs_i);
494    0
495}
496
497pub unsafe fn luaL_requiref(L: *mut lua_State, modname: *const c_char, openf: lua_CFunction, glb: c_int) {
498    luaL_checkstack(L, 3, cstr!("not enough stack slots available"));
499    luaL_getsubtable(L, LUA_REGISTRYINDEX, cstr!("_LOADED"));
500    if lua_getfield(L, -1, modname) == LUA_TNIL {
501        lua_pop(L, 1);
502        lua_pushcfunction(L, openf);
503        lua_pushstring(L, modname);
504        lua_call(L, 1, 1);
505        lua_pushvalue(L, -1);
506        lua_setfield(L, -3, modname);
507    }
508    if glb != 0 {
509        lua_pushvalue(L, -1);
510        lua_setglobal(L, modname);
511    } else {
512        lua_pushnil(L);
513        lua_setglobal(L, modname);
514    }
515    lua_replace(L, -2);
516}