[PATCH v3 2/7] Add functions to ease using Lua iterators from C
Alexander Turenko
alexander.turenko at tarantool.org
Wed Apr 10 18:21:20 MSK 2019
Needed for #3276.
---
src/lua/utils.c | 92 +++++++++++++++
src/lua/utils.h | 37 ++++++
test/unit/CMakeLists.txt | 4 +
test/unit/luaL_iterator.c | 208 +++++++++++++++++++++++++++++++++
test/unit/luaL_iterator.result | 89 ++++++++++++++
5 files changed, 430 insertions(+)
create mode 100644 test/unit/luaL_iterator.c
create mode 100644 test/unit/luaL_iterator.result
diff --git a/src/lua/utils.c b/src/lua/utils.c
index 83bc1695e..192912ab8 100644
--- a/src/lua/utils.c
+++ b/src/lua/utils.c
@@ -1065,6 +1065,98 @@ luaT_state(void)
return tarantool_L;
}
+/* {{{ Helper functions to interact with a Lua iterator from C */
+
+struct luaL_iterator {
+ int gen;
+ int param;
+ int state;
+};
+
+struct luaL_iterator *
+luaL_iterator_new(lua_State *L, int idx)
+{
+ struct luaL_iterator *it = malloc(sizeof(struct luaL_iterator));
+ if (it == NULL) {
+ diag_set(OutOfMemory, sizeof(struct luaL_iterator),
+ "malloc", "luaL_iterator");
+ return NULL;
+ }
+
+ if (idx == 0) {
+ /* gen, param, state are on top of a Lua stack. */
+ lua_pushvalue(L, -3); /* Popped by luaL_ref(). */
+ it->gen = luaL_ref(L, LUA_REGISTRYINDEX);
+ lua_pushvalue(L, -2); /* Popped by luaL_ref(). */
+ it->param = luaL_ref(L, LUA_REGISTRYINDEX);
+ lua_pushvalue(L, -1); /* Popped by luaL_ref(). */
+ it->state = luaL_ref(L, LUA_REGISTRYINDEX);
+ } else {
+ /*
+ * {gen, param, state} table is at idx in a Lua
+ * stack.
+ */
+ lua_rawgeti(L, idx, 1); /* Popped by luaL_ref(). */
+ it->gen = luaL_ref(L, LUA_REGISTRYINDEX);
+ lua_rawgeti(L, idx, 2); /* Popped by luaL_ref(). */
+ it->param = luaL_ref(L, LUA_REGISTRYINDEX);
+ lua_rawgeti(L, idx, 3); /* Popped by luaL_ref(). */
+ it->state = luaL_ref(L, LUA_REGISTRYINDEX);
+ }
+
+ return it;
+}
+
+int
+luaL_iterator_next(lua_State *L, struct luaL_iterator *it)
+{
+ int frame_start = lua_gettop(L);
+
+ /* Call gen(param, state). */
+ lua_rawgeti(L, LUA_REGISTRYINDEX, it->gen);
+ lua_rawgeti(L, LUA_REGISTRYINDEX, it->param);
+ lua_rawgeti(L, LUA_REGISTRYINDEX, it->state);
+ if (luaT_call(L, 2, LUA_MULTRET) != 0) {
+ /*
+ * Pop garbage from the call (a gen function
+ * likely will not leave the stack even when raise
+ * an error), pop a returned error.
+ */
+ lua_settop(L, frame_start);
+ return -1;
+ }
+ int nresults = lua_gettop(L) - frame_start;
+
+ /*
+ * gen() function can either return nil when the iterator
+ * ends or return zero count of values.
+ *
+ * In LuaJIT pairs() returns nil, but ipairs() returns
+ * nothing when ends.
+ */
+ if (nresults == 0 || lua_isnil(L, frame_start + 1)) {
+ lua_settop(L, frame_start);
+ return 0;
+ }
+
+ /* Save the first result to it->state. */
+ luaL_unref(L, LUA_REGISTRYINDEX, it->state);
+ lua_pushvalue(L, frame_start + 1); /* Popped by luaL_ref(). */
+ it->state = luaL_ref(L, LUA_REGISTRYINDEX);
+
+ return nresults;
+}
+
+void luaL_iterator_delete(struct luaL_iterator *it)
+{
+ luaL_unref(tarantool_L, LUA_REGISTRYINDEX, it->gen);
+ luaL_unref(tarantool_L, LUA_REGISTRYINDEX, it->param);
+ luaL_unref(tarantool_L, LUA_REGISTRYINDEX, it->state);
+ free(it);
+}
+
+/* }}} */
+
int
tarantool_lua_utils_init(struct lua_State *L)
{
diff --git a/src/lua/utils.h b/src/lua/utils.h
index cf8c8d09d..a22492227 100644
--- a/src/lua/utils.h
+++ b/src/lua/utils.h
@@ -536,6 +536,43 @@ luaL_checkfinite(struct lua_State *L, struct luaL_serializer *cfg,
luaL_error(L, "number must not be NaN or Inf");
}
+/* {{{ Helper functions to interact with a Lua iterator from C */
+
+/**
+ * Holds iterator state (references to Lua objects).
+ */
+struct luaL_iterator;
+
+/**
+ * Create a Lua iterator from a gen, param, state triplet.
+ *
+ * If idx == 0, then three top stack values are used as the
+ * triplet. Note: they are not popped.
+ *
+ * Otherwise idx is index on Lua stack points to a
+ * {gen, param, state} table.
+ */
+struct luaL_iterator *
+luaL_iterator_new(lua_State *L, int idx);
+
+/**
+ * Move iterator to the next value. Push values returned by
+ * gen(param, state).
+ *
+ * Return count of pushed values. Zero means no more results
+ * available. In case of a Lua error in a gen function return -1
+ * and set a diag.
+ */
+int
+luaL_iterator_next(lua_State *L, struct luaL_iterator *it);
+
+/**
+ * Free all resources held by the iterator.
+ */
+void luaL_iterator_delete(struct luaL_iterator *it);
+
+/* }}} */
+
int
tarantool_lua_utils_init(struct lua_State *L);
diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt
index 2bcb6e0a8..64739ab09 100644
--- a/test/unit/CMakeLists.txt
+++ b/test/unit/CMakeLists.txt
@@ -144,6 +144,10 @@ add_executable(luaT_tuple_new.test luaT_tuple_new.c)
target_link_libraries(luaT_tuple_new.test unit box server core misc
${CURL_LIBRARIES} ${LIBYAML_LIBRARIES} ${READLINE_LIBRARIES}
${ICU_LIBRARIES} ${LUAJIT_LIBRARIES})
+add_executable(luaL_iterator.test luaL_iterator.c)
+target_link_libraries(luaL_iterator.test unit server coll core misc
+ ${CURL_LIBRARIES} ${LIBYAML_LIBRARIES} ${READLINE_LIBRARIES}
+ ${ICU_LIBRARIES} ${LUAJIT_LIBRARIES} dl)
add_executable(say.test say.c)
target_link_libraries(say.test core unit)
diff --git a/test/unit/luaL_iterator.c b/test/unit/luaL_iterator.c
new file mode 100644
index 000000000..8d25a0062
--- /dev/null
+++ b/test/unit/luaL_iterator.c
@@ -0,0 +1,208 @@
+#include <lua.h> /* lua_*() */
+#include <lauxlib.h> /* luaL_*() */
+#include <lualib.h> /* luaL_openlibs() */
+#include "unit.h" /* plan, header, footer, is */
+#include "memory.h" /* memory_init() */
+#include "fiber.h" /* fiber_init() */
+#include "diag.h" /* struct error, diag_*() */
+#include "exception.h" /* type_LuajitError */
+#include "lua/utils.h" /* luaL_iterator_*() */
+#include "lua/error.h" /* tarantool_lua_error_init() */
+
+extern char fun_lua[];
+
+int
+main()
+{
+ struct {
+ /* A string to output with a test case. */
+ const char *description;
+ /* A string with Lua code to push an iterator. */
+ const char *init;
+ /*
+ * How much values are pushed by the Lua code
+ * above.
+ */
+ int init_retvals;
+ /*
+ * Start values from this number to distinguish
+ * them from its ordinal.
+ */
+ int first_value;
+ /*
+ * Lua stack index where {gen, param, state} is
+ * placed or zero.
+ */
+ int idx;
+ /* How much values are in the iterator. */
+ int iterations;
+ /* Expected error (if any). */
+ const char *exp_err;
+ } cases[] = {
+ {
+ .description = "pairs, zero idx",
+ .init = "return pairs({42})",
+ .init_retvals = 3,
+ .first_value = 42,
+ .idx = 0,
+ .iterations = 1,
+ .exp_err = NULL,
+ },
+ {
+ .description = "ipairs, zero idx",
+ .init = "return ipairs({42, 43, 44})",
+ .init_retvals = 3,
+ .first_value = 42,
+ .idx = 0,
+ .iterations = 3,
+ .exp_err = NULL,
+ },
+ {
+ .description = "luafun iterator, zero idx",
+ .init = "return fun.wrap(ipairs({42, 43, 44}))",
+ .init_retvals = 3,
+ .first_value = 42,
+ .idx = 0,
+ .iterations = 3,
+ .exp_err = NULL,
+ },
+ {
+ .description = "pairs, from table",
+ .init = "return {pairs({42})}",
+ .init_retvals = 1,
+ .first_value = 42,
+ .idx = -1,
+ .iterations = 1,
+ .exp_err = NULL,
+ },
+ {
+ .description = "ipairs, from table",
+ .init = "return {ipairs({42, 43, 44})}",
+ .init_retvals = 1,
+ .first_value = 42,
+ .idx = -1,
+ .iterations = 3,
+ .exp_err = NULL,
+ },
+ {
+ .description = "luafun iterator, from table",
+ .init = "return {fun.wrap(ipairs({42, 43, 44}))}",
+ .init_retvals = 1,
+ .first_value = 42,
+ .idx = -1,
+ .iterations = 3,
+ .exp_err = NULL,
+ },
+ {
+ .description = "lua error",
+ .init = "return error, 'I am the error', 0",
+ .init_retvals = 3,
+ .first_value = 0,
+ .idx = 0,
+ .iterations = 0,
+ .exp_err = "I am the error",
+ },
+ };
+
+ int cases_cnt = (int) (sizeof(cases) / sizeof(cases[0]));
+ /*
+ * * 4 checks per iteration.
+ * * 3 checks of a stack size.
+ * * 1 check that values ends (for success cases).
+ * * 1 check for an iterator error (for error cases).
+ * * 1 check for an error type (for error cases).
+ * * 1 check for an error message (for error cases).
+ */
+ int planned = 0;
+ for (int i = 0; i < cases_cnt; ++i) {
+ planned += cases[i].iterations * 4 + 4;
+ if (cases[i].exp_err != NULL)
+ planned += 2;
+ }
+
+ plan(planned);
+ header();
+
+ struct lua_State *L = luaL_newstate();
+ luaL_openlibs(L);
+ tarantool_L = L;
+
+ memory_init();
+ fiber_init(fiber_c_invoke);
+ tarantool_lua_error_init(L);
+
+ /*
+ * Check that everything works fine in a thread (a fiber)
+ * other then the main one.
+ */
+ L = lua_newthread(L);
+
+ /*
+ * Expose luafun.
+ *
+ * Don't register it in package.loaded for simplicity.
+ */
+ luaL_loadstring(L, fun_lua);
+ lua_call(L, 0, 1);
+ lua_setglobal(L, "fun");
+
+ for (int i = 0; i < cases_cnt; ++i) {
+ const char *description = cases[i].description;
+ int top = lua_gettop(L);
+
+ /* Push an iterator to the Lua stack. */
+ luaL_loadstring(L, cases[i].init);
+ lua_call(L, 0, cases[i].init_retvals);
+
+ /* Create the luaL_iterator structure. */
+ struct luaL_iterator *it = luaL_iterator_new(L, cases[i].idx);
+ lua_pop(L, cases[i].init_retvals);
+
+ /* Check stack size. */
+ is(lua_gettop(L) - top, 0, "%s: stack size", description);
+
+ /* Iterate over values and check them. */
+ for (int j = 0; j < cases[i].iterations; ++j) {
+ int top = lua_gettop(L);
+ int rc = luaL_iterator_next(L, it);
+ is(rc, 2, "%s: iter %d: gen() retval count",
+ description, j);
+ is(luaL_checkinteger(L, -2), j + 1,
+ "%s: iter %d: gen() 1st retval",
+ description, j);
+ is(luaL_checkinteger(L, -1), j + cases[i].first_value,
+ "%s: iter %d: gen() 2nd retval",
+ description, j);
+ lua_pop(L, 2);
+ is(lua_gettop(L) - top, 0, "%s: iter: %d: stack size",
+ description, j);
+ }
+
+ if (cases[i].exp_err == NULL) {
+ /* Check the iterator ends when expected. */
+ int rc = luaL_iterator_next(L, it);
+ is(rc, 0, "%s: iterator ends", description);
+ } else {
+ /* Check expected error. */
+ int rc = luaL_iterator_next(L, it);
+ is(rc, -1, "%s: iterator error", description);
+ struct error *e = diag_last_error(diag_get());
+ is(e->type, &type_LuajitError, "%s: check error type",
+ description);
+ ok(!strcmp(e->errmsg, cases[i].exp_err),
+ "%s: check error message", description);
+ }
+
+ /* Check stack size. */
+ is(lua_gettop(L) - top, 0, "%s: stack size", description);
+
+ /* Free the luaL_iterator structure. */
+ luaL_iterator_delete(it);
+
+ /* Check stack size. */
+ is(lua_gettop(L) - top, 0, "%s: stack size", description);
+ }
+
+ footer();
+ return check_plan();
+}
diff --git a/test/unit/luaL_iterator.result b/test/unit/luaL_iterator.result
new file mode 100644
index 000000000..2472eedcf
--- /dev/null
+++ b/test/unit/luaL_iterator.result
@@ -0,0 +1,89 @@
+1..86
+ *** main ***
+ok 1 - pairs, zero idx: stack size
+ok 2 - pairs, zero idx: iter 0: gen() retval count
+ok 3 - pairs, zero idx: iter 0: gen() 1st retval
+ok 4 - pairs, zero idx: iter 0: gen() 2nd retval
+ok 5 - pairs, zero idx: iter: 0: stack size
+ok 6 - pairs, zero idx: iterator ends
+ok 7 - pairs, zero idx: stack size
+ok 8 - pairs, zero idx: stack size
+ok 9 - ipairs, zero idx: stack size
+ok 10 - ipairs, zero idx: iter 0: gen() retval count
+ok 11 - ipairs, zero idx: iter 0: gen() 1st retval
+ok 12 - ipairs, zero idx: iter 0: gen() 2nd retval
+ok 13 - ipairs, zero idx: iter: 0: stack size
+ok 14 - ipairs, zero idx: iter 1: gen() retval count
+ok 15 - ipairs, zero idx: iter 1: gen() 1st retval
+ok 16 - ipairs, zero idx: iter 1: gen() 2nd retval
+ok 17 - ipairs, zero idx: iter: 1: stack size
+ok 18 - ipairs, zero idx: iter 2: gen() retval count
+ok 19 - ipairs, zero idx: iter 2: gen() 1st retval
+ok 20 - ipairs, zero idx: iter 2: gen() 2nd retval
+ok 21 - ipairs, zero idx: iter: 2: stack size
+ok 22 - ipairs, zero idx: iterator ends
+ok 23 - ipairs, zero idx: stack size
+ok 24 - ipairs, zero idx: stack size
+ok 25 - luafun iterator, zero idx: stack size
+ok 26 - luafun iterator, zero idx: iter 0: gen() retval count
+ok 27 - luafun iterator, zero idx: iter 0: gen() 1st retval
+ok 28 - luafun iterator, zero idx: iter 0: gen() 2nd retval
+ok 29 - luafun iterator, zero idx: iter: 0: stack size
+ok 30 - luafun iterator, zero idx: iter 1: gen() retval count
+ok 31 - luafun iterator, zero idx: iter 1: gen() 1st retval
+ok 32 - luafun iterator, zero idx: iter 1: gen() 2nd retval
+ok 33 - luafun iterator, zero idx: iter: 1: stack size
+ok 34 - luafun iterator, zero idx: iter 2: gen() retval count
+ok 35 - luafun iterator, zero idx: iter 2: gen() 1st retval
+ok 36 - luafun iterator, zero idx: iter 2: gen() 2nd retval
+ok 37 - luafun iterator, zero idx: iter: 2: stack size
+ok 38 - luafun iterator, zero idx: iterator ends
+ok 39 - luafun iterator, zero idx: stack size
+ok 40 - luafun iterator, zero idx: stack size
+ok 41 - pairs, from table: stack size
+ok 42 - pairs, from table: iter 0: gen() retval count
+ok 43 - pairs, from table: iter 0: gen() 1st retval
+ok 44 - pairs, from table: iter 0: gen() 2nd retval
+ok 45 - pairs, from table: iter: 0: stack size
+ok 46 - pairs, from table: iterator ends
+ok 47 - pairs, from table: stack size
+ok 48 - pairs, from table: stack size
+ok 49 - ipairs, from table: stack size
+ok 50 - ipairs, from table: iter 0: gen() retval count
+ok 51 - ipairs, from table: iter 0: gen() 1st retval
+ok 52 - ipairs, from table: iter 0: gen() 2nd retval
+ok 53 - ipairs, from table: iter: 0: stack size
+ok 54 - ipairs, from table: iter 1: gen() retval count
+ok 55 - ipairs, from table: iter 1: gen() 1st retval
+ok 56 - ipairs, from table: iter 1: gen() 2nd retval
+ok 57 - ipairs, from table: iter: 1: stack size
+ok 58 - ipairs, from table: iter 2: gen() retval count
+ok 59 - ipairs, from table: iter 2: gen() 1st retval
+ok 60 - ipairs, from table: iter 2: gen() 2nd retval
+ok 61 - ipairs, from table: iter: 2: stack size
+ok 62 - ipairs, from table: iterator ends
+ok 63 - ipairs, from table: stack size
+ok 64 - ipairs, from table: stack size
+ok 65 - luafun iterator, from table: stack size
+ok 66 - luafun iterator, from table: iter 0: gen() retval count
+ok 67 - luafun iterator, from table: iter 0: gen() 1st retval
+ok 68 - luafun iterator, from table: iter 0: gen() 2nd retval
+ok 69 - luafun iterator, from table: iter: 0: stack size
+ok 70 - luafun iterator, from table: iter 1: gen() retval count
+ok 71 - luafun iterator, from table: iter 1: gen() 1st retval
+ok 72 - luafun iterator, from table: iter 1: gen() 2nd retval
+ok 73 - luafun iterator, from table: iter: 1: stack size
+ok 74 - luafun iterator, from table: iter 2: gen() retval count
+ok 75 - luafun iterator, from table: iter 2: gen() 1st retval
+ok 76 - luafun iterator, from table: iter 2: gen() 2nd retval
+ok 77 - luafun iterator, from table: iter: 2: stack size
+ok 78 - luafun iterator, from table: iterator ends
+ok 79 - luafun iterator, from table: stack size
+ok 80 - luafun iterator, from table: stack size
+ok 81 - lua error: stack size
+ok 82 - lua error: iterator error
+ok 83 - lua error: check error type
+ok 84 - lua error: check error message
+ok 85 - lua error: stack size
+ok 86 - lua error: stack size
+ *** main: done ***
--
2.20.1
More information about the Tarantool-patches
mailing list