mlua_sys/lua51/
compat.rs

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