Skip to main content

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