factorio_mlua_sys/luau/
compat.rs

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