Skip to main content

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