From 4f5640972e11116da9e460c8d5136f27a2eb8e67 Mon Sep 17 00:00:00 2001 From: Alexander Turenko Date: Mon, 16 Jan 2023 18:58:48 +0300 Subject: [PATCH] lua: add built-in module registration function This commit starts a series, which refactors built-in module registration mechanisms. The series aims several goals: * Offer a standard tarantool specific way to register a built-in module. It allows to change the registration process for all built-in modules at once. * Make the API of such functions simple and clean. * Eliminate known problems of the existing approach to register built-in modules. This particular commit offers two new functions: * luaT_setfuncs() * luaT_newmodule() The former replaces `luaL_register(L, NULL, funcs)`. The latter replaces `luaL_register()` and `luaL_register_module()` to some extent. Let's look on examples below. ## How it works now First of all, consider the [documentation][1] about the `luaL_register()` function. Then look on the examples. ```c luaL_register(L, "foo.bar", funcs); ``` Creates a table `package.loaded['foo.bar']` and `_G.foo.bar` (if neither of them exists), fill it with the functions and pushes it to the stack. What is not ok: * Pollutes `_G`. * `package.loaded` is written flat, but `_G` is written deeply. * No control over (un)intended module rewritting. It also worth to note that we can't change this function, because it is part of the Lua API. So we need another function for our registration process. Another usage of the function makes it even more confusing: ```c luaL_register(L, NULL, funcs); ``` Does it creates a table, fills it with the functions and pushes it to the stack? No. Unlike other usages, it fills a table on the top of the stack with the functions and leaves it on the stack. And it actually has no relation to module registration. There is tarantool specific function `luaL_register_module()`, which looks as an attempt to solve some of the problems. ```c luaL_register_module(L, "foo.bar", funcs); ``` It doesn't touch `_G` and, surprisingly, changes how a module is written to `package.loaded`. The call creates a table (or finds it) in `package.loaded.foo.bar`, fills it with the functions and pushes to the stack. *This* function writes `package.loaded` deeply. It leaves us with `luaL_register()` for assigning `_G.foo.bar` (with pollution of `package.loaded`). It leaves us with `lua_register()` for setting functions into a table on the stack (with confusing name in this context and confusing contract). And we has no a function to just assign `package.loaded[modname]` without the deep tables creation and with appropriate checks. ## How it will work ```c luaT_newmodule(L, "foo.bar", funcs); ``` Create a table, set it to `package.loaded['foo.bar']` and push into the stack. Assert that the table doesn't exist. ```c luaT_setfuncs(L, funcs); ``` Add functions into a table on top of the stack. ```c luaL_findtable(L, LUA_GLOBALSINDEX, "foo.bar", 0); luaT_setfuncs(L, funcs); ``` Find/create a table `_G.foo.bar` and add functions to it. [1]: https://www.lua.org/manual/5.1/manual.html#luaL_register Part of #7774 NO_DOC=user visible behavior is unchanged, pure refactoring change NO_TEST=see NO_DOC NO_CHANGELOG=see NO_DOC --- src/lua/utils.c | 54 +++++++++++++++++++++++++++++++++++++++++++++++-- src/lua/utils.h | 33 ++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/src/lua/utils.c b/src/lua/utils.c index ccaa94e2c837f..73797986129a2 100644 --- a/src/lua/utils.c +++ b/src/lua/utils.c @@ -40,6 +40,7 @@ #include #include "tt_uuid.h" #include "core/datetime.h" +#include "core/say.h" int luaL_nil_ref = LUA_REFNIL; @@ -285,6 +286,19 @@ luaL_setcdatagc(struct lua_State *L, int idx) lua_pop(L, 1); } +int +luaT_setfuncs(struct lua_State *L, const struct luaL_Reg *funcs) +{ + assert(lua_gettop(L) >= 1); + assert(lua_istable(L, -1)); + + for (const struct luaL_Reg *f = funcs; f->name != NULL; ++f) { + lua_pushcclosure(L, f->func, 0); + lua_setfield(L, -2, f->name); + } + + return 0; +} /** * A helper to register a single type metatable. @@ -303,7 +317,7 @@ luaL_register_type(struct lua_State *L, const char *type_name, lua_setfield(L, -2, "__index"); lua_pushstring(L, type_name); lua_setfield(L, -2, "__metatable"); - luaL_register(L, NULL, methods); + luaT_setfuncs(L, methods); lua_pop(L, 1); } @@ -328,7 +342,43 @@ luaL_register_module(struct lua_State *L, const char *modname, luaL_error(L, "Failed to register library"); } lua_remove(L, -2); /* remove _LOADED table */ - luaL_register(L, NULL, methods); + luaT_setfuncs(L, methods); +} + +int +luaT_newmodule(struct lua_State *L, const char *modname, + const struct luaL_Reg *funcs) +{ + say_debug("%s(%s)", __func__, modname); + assert(modname != NULL && funcs != NULL); + + /* Get package.loaded. */ + lua_getfield(L, LUA_REGISTRYINDEX, "_LOADED"); + + /* Verify that the module is not already registered. */ + lua_getfield(L, -1, modname); + assert(lua_isnil(L, -1)); + lua_pop(L, 1); + + /* Create a module table. */ + lua_newtable(L); + + /* Fill the module table with functions. */ + luaT_setfuncs(L, funcs); + + /* Copy the module table. */ + lua_pushvalue(L, -1); + + /* package.loaded[modname] = */ + lua_setfield(L, -3, modname); + + /* Stack: package.loaded, module table. */ + + /* Drop package.loaded. Stack: module table. */ + lua_remove(L, -2); + + say_debug("%s(%s): success", __func__, modname); + return 1; } /* diff --git a/src/lua/utils.h b/src/lua/utils.h index ce90b977561a0..f6ac95bd07330 100644 --- a/src/lua/utils.h +++ b/src/lua/utils.h @@ -249,6 +249,14 @@ luaL_maplen(struct lua_State *L, int idx) extern int luaL_nil_ref; +/** + * Fill a table on top of the Lua stack with functions. + * + * Leave the table on top of the stack. + */ +int +luaT_setfuncs(struct lua_State *L, const struct luaL_Reg *funcs); + void luaL_register_type(struct lua_State *L, const char *type_name, const struct luaL_Reg *methods); @@ -258,6 +266,31 @@ void luaL_register_module(struct lua_State *L, const char *modname, const struct luaL_Reg *methods); +/** + * Create a table with functions and register it as a built-in + * tarantool module. + * + * Leave the table on top of the stack. + * + * Pseudocode: + * + * | local function newmodule(modname, funcs) + * | assert(modname ~= nil and funcs ~= nil) + * | assert(package.loaded[modname] == nil) + * | local mod = {} + * | setfuncs(mod, funcs) + * | package.loaded[modname] = mod + * | return mod + * | end + * + * Unlike luaL_register() it is very straightforward: no recursive + * search, no _G pollution, no branching around using a stack + * top/find a table/create a new table. + */ +int +luaT_newmodule(struct lua_State *L, const char *modname, + const struct luaL_Reg *funcs); + /** \cond public */ /**