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