diff options
Diffstat (limited to 'src/common/impl/lua.c')
| -rw-r--r-- | src/common/impl/lua.c | 233 |
1 files changed, 233 insertions, 0 deletions
diff --git a/src/common/impl/lua.c b/src/common/impl/lua.c new file mode 100644 index 0000000..ffc1d83 --- /dev/null +++ b/src/common/impl/lua.c @@ -0,0 +1,233 @@ +#if FF_HAVE_LUA + + #include "common/lua.h" + #include "common/mallocHelper.h" + +struct FFLuaData luaData; + +static yyjson_mut_val* lua2yyjson(lua_State* L, int idx, yyjson_mut_doc* doc, int depth) { + if (__builtin_expect(depth > 15, false)) { + yyjson_mut_doc_free(doc); + lua_pushlstring( + L, "yyjson: recursion depth exceeded; possible circular reference", strlen("yyjson: recursion depth exceeded; possible circular reference")); + lua_error(L); // noreturn + __builtin_unreachable(); + } + + if (idx < 0) { + idx = lua_gettop(L) + idx + 1; + } + int type = lua_type(L, idx); + + switch (type) { + case LUA_TNIL: + return yyjson_mut_null(doc); + + case LUA_TBOOLEAN: + return yyjson_mut_bool(doc, lua_toboolean(L, idx)); + + case LUA_TNUMBER: { + if (lua_isinteger(L, idx)) { + lua_Integer i = lua_tointeger(L, idx); + return yyjson_mut_sint(doc, (int64_t) i); + } else { + return yyjson_mut_real(doc, lua_tonumber(L, idx)); + } + } + + case LUA_TSTRING: { + size_t len; + const char* str = lua_tolstring(L, idx, &len); + return yyjson_mut_strncpy(doc, str, len); + } + + case LUA_TTABLE: { + lua_Unsigned len = lua_rawlen(L, idx); + int is_array = 1; + + if (len == 0) { + lua_pushnil(L); + is_array = 0; + if (lua_next(L, idx) != 0) { + lua_pop(L, 2); + } + } else { + lua_pushnil(L); + while (lua_next(L, idx) != 0) { + if (lua_type(L, -2) != LUA_TNUMBER) { + is_array = 0; + lua_pop(L, 2); + break; + } + int isnum = false; + lua_Integer k = lua_tointegerx(L, -2, &isnum); + if (!isnum || (lua_Unsigned) k > len) { + is_array = 0; + lua_pop(L, 2); + break; + } + lua_pop(L, 1); + } + } + + if (is_array) { + yyjson_mut_val* arr = yyjson_mut_arr(doc); + for (lua_Unsigned i = 1; i <= len; i++) { + lua_rawgeti(L, idx, (lua_Integer) i); + yyjson_mut_val* val = lua2yyjson(L, -1, doc, depth + 1); + yyjson_mut_arr_append(arr, val); + lua_pop(L, 1); + } + return arr; + } else { + yyjson_mut_val* obj = yyjson_mut_obj(doc); + lua_pushnil(L); + while (lua_next(L, idx) != 0) { + size_t klen; + const char* key_str = luaL_tolstring(L, -2, &klen); + yyjson_mut_val* key = yyjson_mut_strncpy(doc, key_str, klen); + lua_pop(L, 1); + + yyjson_mut_val* val = lua2yyjson(L, -1, doc, depth + 1); + yyjson_mut_obj_add(obj, key, val); + lua_pop(L, 1); + } + return obj; + } + } + + default: + return yyjson_mut_null(doc); + } +} + +static int yyjsonEncode(lua_State* L) { + luaL_checkany(L, 1); + + bool pretty = false; + if (lua_isboolean(L, 2)) { + pretty = (int) lua_toboolean(L, 2); + } + + yyjson_mut_doc* doc = yyjson_mut_doc_new(NULL); + if (__builtin_expect(!doc, false)) { + lua_pushlstring(L, "yyjson: yyjson_mut_doc_new() failed", strlen("yyjson: yyjson_mut_doc_new() failed")); + return lua_error(L); + } + + yyjson_mut_val* root = lua2yyjson(L, 1, doc, 0); + yyjson_mut_doc_set_root(doc, root); + + size_t jsonLen; + yyjson_write_err err = {}; + FF_AUTO_FREE const char* jsonStr = yyjson_mut_write_opts(doc, YYJSON_WRITE_ALLOW_INF_AND_NAN | (pretty ? YYJSON_WRITE_PRETTY_TWO_SPACES : 0), NULL, &jsonLen, &err); + + if (__builtin_expect(jsonStr != NULL, true)) { + lua_pushlstring(L, jsonStr, jsonLen); + yyjson_mut_doc_free(doc); + return 1; + } else { + yyjson_mut_doc_free(doc); + { + FF_STRBUF_AUTO_DESTROY errBuf = ffStrbufCreateStatic("yyjson: yyjson_mut_write_opts() failed: "); + ffStrbufAppendS(&errBuf, err.msg); + lua_pushlstring(L, errBuf.chars, errBuf.length); + } + + return lua_error(L); // longjmp + } +} + +const char* ffLuaLoadState(void) { + if (luaData.inited) { + if (luaData.L == NULL) { + return "Lua library is not available"; + } + return NULL; + } + + luaData.inited = true; + // clang-format off + #ifdef _WIN32 + #define FF_LOAD_LIBLUA(version) FF_LIBRARY_LOAD_MESSAGE(liblua, \ + "lua5" #version FF_LIBRARY_EXTENSION, 0) + #else + #define FF_LOAD_LIBLUA(version) FF_LIBRARY_LOAD_MESSAGE(liblua, \ + "liblua5." #version FF_LIBRARY_EXTENSION, 0, \ + "liblua-5." #version FF_LIBRARY_EXTENSION, 0, \ + "liblua5." #version FF_LIBRARY_EXTENSION ".5." #version, 0) + #endif + // clang-format on + #if LUA_VERSION_NUM == 505 + FF_LOAD_LIBLUA(5) + #elif LUA_VERSION_NUM == 504 + FF_LOAD_LIBLUA(4) + #elif LUA_VERSION_NUM == 503 + FF_LOAD_LIBLUA(3) + #else + #error "Unsupported Lua version" + #endif + #undef FF_LOAD_LIBLUA + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(liblua, luaL_newstate) + #if LUA_VERSION_NUM >= 505 + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(liblua, luaL_openselectedlibs) + #else + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(liblua, luaL_requiref) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(liblua, luaopen_base) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(liblua, luaopen_math) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(liblua, luaopen_string) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(liblua, luaopen_table) + #endif + + #if !FF_DISABLE_DLOPEN + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, luaL_checkany) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, luaL_loadbufferx) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, luaL_tolstring) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_callk) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_createtable) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_error) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_gettop) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_isinteger) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_next) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_pcallk) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_pushboolean) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_pushcclosure) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_pushinteger) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_pushlstring) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_pushnil) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_pushnumber) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_pushvalue) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_rawgeti) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_rawlen) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_setfield) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_setglobal) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_seti) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_settop) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_toboolean) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_tointegerx) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_tolstring) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_tonumberx) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(liblua, luaData, lua_type) + #endif + + lua_State* L = ffluaL_newstate(); + if (L == NULL) { + return "luaL_newstate() failed"; + } + #if LUA_VERSION_NUM >= 505 + ffluaL_openselectedlibs(L, LUA_GLIBK | LUA_MATHLIBK | LUA_STRLIBK | LUA_TABLIBK, 0); + #else + ffluaL_requiref(L, LUA_GNAME, ffluaopen_base, 1); + ffluaL_requiref(L, LUA_MATHLIBNAME, ffluaopen_math, 1); + ffluaL_requiref(L, LUA_STRLIBNAME, ffluaopen_string, 1); + ffluaL_requiref(L, LUA_TABLIBNAME, ffluaopen_table, 1); + lua_settop(L, 0); + #endif + lua_pushcfunction(L, yyjsonEncode); + lua_setglobal(L, "json_encode"); + luaData.L = L; + liblua = NULL; // don't close lua + return NULL; +} + +#endif |