master ohmu/fastfetch-openbsd / src / common / impl / lua.c
ohmu  ·  2026-10-15
  1#if FF_HAVE_LUA
  2
  3    #include "common/lua.h"
  4    #include "common/debug.h"
  5    #include "common/mallocHelper.h"
  6
  7struct FFLuaData luaData;
  8
  9static yyjson_mut_val* lua2yyjson(lua_State* L, int idx, yyjson_mut_doc* doc, int depth) {
 10    if (__builtin_expect(depth > 15, false)) {
 11        yyjson_mut_doc_free(doc);
 12        lua_pushlstring(
 13            L, "yyjson: recursion depth exceeded; possible circular reference", strlen("yyjson: recursion depth exceeded; possible circular reference"));
 14        lua_error(L); // noreturn
 15        __builtin_unreachable();
 16    }
 17
 18    if (idx < 0) {
 19        idx = lua_gettop(L) + idx + 1;
 20    }
 21    int type = lua_type(L, idx);
 22
 23    switch (type) {
 24        case LUA_TNIL:
 25            return yyjson_mut_null(doc);
 26
 27        case LUA_TBOOLEAN:
 28            return yyjson_mut_bool(doc, lua_toboolean(L, idx));
 29
 30        case LUA_TNUMBER: {
 31            if (lua_isinteger(L, idx)) {
 32                lua_Integer i = lua_tointeger(L, idx);
 33                return yyjson_mut_sint(doc, (int64_t) i);
 34            } else {
 35                return yyjson_mut_real(doc, lua_tonumber(L, idx));
 36            }
 37        }
 38
 39        case LUA_TSTRING: {
 40            size_t len;
 41            const char* str = lua_tolstring(L, idx, &len);
 42            return yyjson_mut_strncpy(doc, str, len);
 43        }
 44
 45        case LUA_TTABLE: {
 46            lua_Unsigned len = lua_rawlen(L, idx);
 47            int is_array = 1;
 48
 49            if (len == 0) {
 50                lua_pushnil(L);
 51                is_array = 0;
 52                if (lua_next(L, idx) != 0) {
 53                    lua_pop(L, 2);
 54                }
 55            } else {
 56                lua_pushnil(L);
 57                while (lua_next(L, idx) != 0) {
 58                    if (lua_type(L, -2) != LUA_TNUMBER) {
 59                        is_array = 0;
 60                        lua_pop(L, 2);
 61                        break;
 62                    }
 63                    int isnum = false;
 64                    lua_Integer k = lua_tointegerx(L, -2, &isnum);
 65                    if (!isnum || (lua_Unsigned) k > len) {
 66                        is_array = 0;
 67                        lua_pop(L, 2);
 68                        break;
 69                    }
 70                    lua_pop(L, 1);
 71                }
 72            }
 73
 74            if (is_array) {
 75                yyjson_mut_val* arr = yyjson_mut_arr(doc);
 76                for (lua_Unsigned i = 1; i <= len; i++) {
 77                    lua_rawgeti(L, idx, (lua_Integer) i);
 78                    yyjson_mut_val* val = lua2yyjson(L, -1, doc, depth + 1);
 79                    yyjson_mut_arr_append(arr, val);
 80                    lua_pop(L, 1);
 81                }
 82                return arr;
 83            } else {
 84                yyjson_mut_val* obj = yyjson_mut_obj(doc);
 85                lua_pushnil(L);
 86                while (lua_next(L, idx) != 0) {
 87                    size_t klen;
 88                    const char* key_str = luaL_tolstring(L, -2, &klen);
 89                    yyjson_mut_val* key = yyjson_mut_strncpy(doc, key_str, klen);
 90                    lua_pop(L, 1);
 91
 92                    yyjson_mut_val* val = lua2yyjson(L, -1, doc, depth + 1);
 93                    yyjson_mut_obj_add(obj, key, val);
 94                    lua_pop(L, 1);
 95                }
 96                return obj;
 97            }
 98        }
 99
100        default:
101            return yyjson_mut_null(doc);
102    }
103}
104
105static int yyjsonEncode(lua_State* L) {
106    luaL_checkany(L, 1);
107
108    bool pretty = false;
109    if (lua_isboolean(L, 2)) {
110        pretty = (int) lua_toboolean(L, 2);
111    }
112
113    yyjson_mut_doc* doc = yyjson_mut_doc_new(nullptr);
114    if (__builtin_expect(!doc, false)) {
115        lua_pushlstring(L, "yyjson: yyjson_mut_doc_new() failed", strlen("yyjson: yyjson_mut_doc_new() failed"));
116        return lua_error(L);
117    }
118
119    yyjson_mut_val* root = lua2yyjson(L, 1, doc, 0);
120    yyjson_mut_doc_set_root(doc, root);
121
122    size_t jsonLen;
123    yyjson_write_err err = {};
124    FF_AUTO_FREE const char* jsonStr = yyjson_mut_write_opts(doc, YYJSON_WRITE_ALLOW_INF_AND_NAN | (pretty ? YYJSON_WRITE_PRETTY_TWO_SPACES : 0), nullptr, &jsonLen, &err);
125
126    if (__builtin_expect(jsonStr != nullptr, true)) {
127        lua_pushlstring(L, jsonStr, jsonLen);
128        yyjson_mut_doc_free(doc);
129        return 1;
130    } else {
131        yyjson_mut_doc_free(doc);
132        {
133            FF_STRBUF_AUTO_DESTROY errBuf = ffStrbufCreateStatic("yyjson: yyjson_mut_write_opts() failed: ");
134            ffStrbufAppendS(&errBuf, err.msg);
135            lua_pushlstring(L, errBuf.chars, errBuf.length);
136        }
137
138        return lua_error(L); // longjmp
139    }
140}
141
142const char* ffLuaLoadState(void) {
143    if (luaData.inited) {
144        if (luaData.L == nullptr) {
145            FF_DEBUG("The Lua state was already initialised and is gone");
146            return "Lua library is not available";
147        }
148        return nullptr;
149    }
150
151    luaData.inited = true;
152    // clang-format off
153    #ifdef _WIN32
154        #define FF_LOAD_LIBLUA(version) FF_LIBRARY_LOAD_MESSAGE(liblua, \
155            "lua5" #version FF_LIBRARY_EXTENSION, 0)
156    #else
157        #define FF_LOAD_LIBLUA(version) FF_LIBRARY_LOAD_MESSAGE(liblua, \
158            "liblua5." #version FF_LIBRARY_EXTENSION, 0, \
159            "liblua-5." #version FF_LIBRARY_EXTENSION, 0, \
160            "liblua5." #version FF_LIBRARY_EXTENSION ".5." #version, 0)
161    #endif
162    // clang-format on
163    #if LUA_VERSION_NUM == 505
164    FF_LOAD_LIBLUA(5)
165    #elif LUA_VERSION_NUM == 504
166    FF_LOAD_LIBLUA(4)
167    #elif LUA_VERSION_NUM == 503
168    FF_LOAD_LIBLUA(3)
169    #else
170        #error "Unsupported Lua version"
171    #endif
172    #undef FF_LOAD_LIBLUA
173    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(liblua, luaL_newstate)
174    #if LUA_VERSION_NUM >= 505
175    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(liblua, luaL_openselectedlibs)
176    #else
177    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(liblua, luaL_requiref)
178    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(liblua, luaopen_base)
179    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(liblua, luaopen_math)
180    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(liblua, luaopen_string)
181    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(liblua, luaopen_table)
182    #endif
183
184    #if !FF_DISABLE_DLOPEN
185    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, luaL_checkany)
186    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, luaL_loadbufferx)
187    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, luaL_tolstring)
188    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_callk)
189    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_createtable)
190    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_error)
191    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_gettop)
192    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_isinteger)
193    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_next)
194    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_pcallk)
195    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_pushboolean)
196    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_pushcclosure)
197    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_pushinteger)
198    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_pushlstring)
199    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_pushnil)
200    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_pushnumber)
201    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_pushvalue)
202    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_rawgeti)
203    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_rawlen)
204    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_setfield)
205    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_setglobal)
206    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_seti)
207    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_settop)
208    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_toboolean)
209    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_tointegerx)
210    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_tolstring)
211    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_tonumberx)
212    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_type)
213    #endif
214
215    lua_State* L = ffluaL_newstate();
216    if (L == nullptr) {
217        FF_DEBUG("luaL_newstate() could not allocate an interpreter");
218        return "luaL_newstate() failed";
219    }
220    #if LUA_VERSION_NUM >= 505
221    ffluaL_openselectedlibs(L, LUA_GLIBK | LUA_MATHLIBK | LUA_STRLIBK | LUA_TABLIBK, 0);
222    #else
223    ffluaL_requiref(L, LUA_GNAME, ffluaopen_base, 1);
224    ffluaL_requiref(L, LUA_MATHLIBNAME, ffluaopen_math, 1);
225    ffluaL_requiref(L, LUA_STRLIBNAME, ffluaopen_string, 1);
226    ffluaL_requiref(L, LUA_TABLIBNAME, ffluaopen_table, 1);
227    lua_settop(L, 0);
228    #endif
229    lua_pushcfunction(L, yyjsonEncode);
230    lua_setglobal(L, "json_encode");
231    luaData.L = L;
232    liblua = nullptr; // don't close lua
233    return nullptr;
234}
235
236#endif