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