mlua_sys/luau/
compat.rs

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