Skip to content

Commit

Permalink
lua: add built-in module registration function
Browse files Browse the repository at this point in the history
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 tarantool#7774

NO_DOC=user visible behavior is unchanged, pure refactoring change
NO_TEST=see NO_DOC
NO_CHANGELOG=see NO_DOC
  • Loading branch information
Totktonada committed Jan 17, 2023
1 parent 457b293 commit 4f56409
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 2 deletions.
54 changes: 52 additions & 2 deletions src/lua/utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
#include <fiber.h>
#include "tt_uuid.h"
#include "core/datetime.h"
#include "core/say.h"

int luaL_nil_ref = LUA_REFNIL;

Expand Down Expand Up @@ -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.
Expand All @@ -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);
}

Expand All @@ -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] = <module table> */
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;
}

/*
Expand Down
33 changes: 33 additions & 0 deletions src/lua/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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 */

/**
Expand Down

0 comments on commit 4f56409

Please sign in to comment.