[Tarantool-patches] [PATCH 6/7] box/console: switch to new lua serializer
Cyrill Gorcunov
gorcunov at gmail.com
Fri May 8 14:47:13 MSK 2020
I do not drop the serpent module for a while
since I need to make sure that everything work
as expected as we've not break backward compatibility
significantly (though we didn't claim the lua mode
output is stable enough).
Fixes #4682
Signed-off-by: Cyrill Gorcunov <gorcunov at gmail.com>
---
src/box/lua/console.c | 77 +++++++++++++++++++++++++++++++
src/box/lua/console.lua | 71 ++++++++++++++--------------
test/app-tap/console_lua.test.lua | 18 ++++----
3 files changed, 120 insertions(+), 46 deletions(-)
diff --git a/src/box/lua/console.c b/src/box/lua/console.c
index c8e0dea58..d1bf7949c 100644
--- a/src/box/lua/console.c
+++ b/src/box/lua/console.c
@@ -39,6 +39,7 @@
#include "coio.h"
#include "lua/msgpack.h"
#include "lua-yaml/lyaml.h"
+#include "serialize_lua.h"
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
@@ -50,6 +51,7 @@
extern char serpent_lua[];
static struct luaL_serializer *serializer_yaml;
+static struct luaL_serializer *serializer_lua;
/*
* Completion engine (Mike Paul's).
@@ -68,6 +70,54 @@ lua_rl_complete(lua_State *L, const char *text, int start, int end);
*/
static struct lua_State *readline_L;
+/**
+ * Encode Lua object into Lua form.
+ */
+static int
+lbox_console_format_lua(struct lua_State *L)
+{
+ lua_dumper_opts_t opts;
+ int arg_count;
+
+ /* Parse options and remove them */
+ lua_parse_opts(L, &opts);
+ lua_remove(L, 1);
+
+ arg_count = lua_gettop(L);
+
+ /* If nothing to process just exit early */
+ if (arg_count == 0)
+ return 0;
+
+ /*
+ * When processing arguments we might
+ * need to modify reference (for example
+ * when __index references to object itself)
+ * thus make a copy of incoming data.
+ *
+ * Note that in yaml there is no map for
+ * Lua's "nil" value so for yaml encoder
+ * we do
+ *
+ * if (lua_isnil(L, i + 1))
+ * luaL_pushnull(L);
+ * else
+ * lua_pushvalue(L, i + 1);
+ *
+ *
+ * For lua mode we have to preserve "nil".
+ */
+ lua_createtable(L, arg_count, 0);
+ for (int i = 0; i < arg_count; ++i) {
+ lua_pushvalue(L, i + 1);
+ lua_rawseti(L, -2, i + 1);
+ }
+
+ lua_replace(L, 1);
+ lua_settop(L, 1);
+ return lua_encode(L, serializer_lua, &opts);
+}
+
/*
* console_completion_handler()
* Called by readline to collect plausible completions;
@@ -557,6 +607,7 @@ tarantool_lua_console_init(struct lua_State *L)
{"add_history", lbox_console_add_history},
{"completion_handler", lbox_console_completion_handler},
{"format_yaml", lbox_console_format_yaml},
+ {"format_lua", lbox_console_format_lua},
{NULL, NULL}
};
luaL_register_module(L, "console", consolelib);
@@ -581,6 +632,32 @@ tarantool_lua_console_init(struct lua_State *L)
* load_history work the same way.
*/
lua_setfield(L, -2, "formatter");
+
+ /*
+ * We don't export it as a module
+ * for a while, so the library
+ * is kept empty.
+ */
+ static const luaL_Reg lualib[] = {
+ { },
+ };
+
+ serializer_lua = luaL_newserializer(L, NULL, lualib);
+ serializer_lua->has_compact = 1;
+ serializer_lua->encode_invalid_numbers = 1;
+ serializer_lua->encode_load_metatables = 1;
+ serializer_lua->encode_use_tostring = 1;
+ serializer_lua->encode_invalid_as_nil = 1;
+
+ /*
+ * Keep a reference to this module so it
+ * won't be unloaded.
+ */
+ lua_setfield(L, -2, "formatter_lua");
+
+ /* Output formatter in Lua mode */
+ lua_serializer_init(L);
+
struct session_vtab console_session_vtab = {
.push = console_session_push,
.fd = console_session_fd,
diff --git a/src/box/lua/console.lua b/src/box/lua/console.lua
index 5642ca956..e6e471a7d 100644
--- a/src/box/lua/console.lua
+++ b/src/box/lua/console.lua
@@ -69,47 +69,35 @@ output_handlers["yaml"] = function(status, opts, ...)
return internal.format_yaml({ error = err })
end
--- A map for internal symbols in case if they
--- are not inside tables and serpent won't be
--- able to handle them properly.
-local lua_map_direct_symbols = {
- [box.NULL] = 'box.NULL',
-}
-
--- A map for internal symbols in case if they
--- are coming from tables and we need to depict
--- them into user known values.
-local lua_map_table_symbols = {
- ['"cdata<void %*>: NULL"'] = 'box.NULL'
-}
-
--
--- Map internal symbols which serpent doesn't
--- know about to a known representation.
-local serpent_map_symbols = function(tag, head, body, tail, level)
- for k,v in pairs(lua_map_table_symbols) do
- body = body:gsub(k, v)
+-- Format a Lua value.
+local function format_lua_value(status, internal_opts, value)
+ local err
+ if status then
+ status, err = pcall(internal.format_lua, internal_opts, value)
+ if status then
+ return err
+ else
+ local m = 'console: exception while formatting output: "%s"'
+ err = m:format(tostring(err))
+ end
+ else
+ err = value
+ if err == nil then
+ err = box.NULL
+ end
end
- return tag..head..body..tail
+ return internal.format_lua(internal_opts, { error = err })
end
--
--- Format a Lua value.
-local function format_lua_value(value, opts)
- for k,v in pairs(lua_map_direct_symbols) do
- if k == value then
- return v
- end
- end
- local serpent_opts = {
- custom = serpent_map_symbols,
- comment = false,
- nocode = true,
- }
+-- Convert options from user form to the internal format.
+local function gen_lua_opts(opts)
if opts == "block" then
- return serpent.block(value, serpent_opts)
+ return {block=true, indent=2}
+ else
+ return {block=false, indent=2}
end
- return serpent.line(value, serpent_opts)
end
output_handlers["lua"] = function(status, opts, ...)
@@ -121,8 +109,17 @@ output_handlers["lua"] = function(status, opts, ...)
if not ... then
return output_eos["lua"]
end
+ --
+ -- Convert options to internal dictionary.
+ local internal_opts = gen_lua_opts(opts)
for i = 1, select('#', ...) do
- collect[i] = format_lua_value(select(i, ...), opts)
+ collect[i] = format_lua_value(status, internal_opts, select(i, ...))
+ if collect[i] == nil then
+ --
+ -- table.concat doesn't work for nil
+ -- so convert it explicitly.
+ collect[i] = "nil"
+ end
end
return table.concat(collect, ', ') .. output_eos["lua"]
end
@@ -210,8 +207,8 @@ end
-- Used by console_session_push.
box_internal.format_lua_push = function(value)
- local opts = current_output()["opts"]
- value = format_lua_value(value, opts)
+ local internal_opts = gen_lua_opts(current_output()["opts"])
+ value = format_lua_value(true, internal_opts, value)
return '-- Push\n' .. value .. ';'
end
diff --git a/test/app-tap/console_lua.test.lua b/test/app-tap/console_lua.test.lua
index 3ed6aad97..8bb6eb0b3 100755
--- a/test/app-tap/console_lua.test.lua
+++ b/test/app-tap/console_lua.test.lua
@@ -102,29 +102,29 @@ local cases = {
prepare = 'a = {1, 2, 3}',
opts = {block = false},
input = '1, 2, nil, a',
- expected = '1, 2, box.NULL, {1, 2, 3}',
+ expected = '1, 2, nil, {1, 2, 3}',
cleanup = 'a = nil',
}, {
name = 'multireturn block mode',
prepare = 'a = {1, 2, 3}',
opts = {block = true},
input = '1, 2, nil, a',
- expected = '1, 2, box.NULL, {\n 1,\n 2,\n 3\n}',
+ expected = '1, 2, nil, {\n 1,\n 2,\n 3\n}',
cleanup = 'a = nil',
}, {
- name = 'trailing nils, line mode',
+ name = 'trailing nils, box.NULL, line mode',
opts = {block = false},
- input = '1, nil, nil, nil',
- expected = '1, box.NULL, box.NULL, box.NULL',
+ input = '1, nil, box.NULL, nil',
+ expected = '1, nil, box.NULL, nil',
}, {
- name = 'trailing nils, block mode',
+ name = 'trailing nils, box.NULL, block mode',
opts = {block = true},
- input = '1, nil, nil, nil',
- expected = '1, box.NULL, box.NULL, box.NULL',
+ input = '1, nil, box.NULL, nil',
+ expected = '1, nil, box.NULL, nil',
}, {
name = 'empty output',
input = '\\set output',
- expected = '"Specify output format: lua or yaml."',
+ expected = '{error = \"Specify output format: lua or yaml.\"}',
}
}
--
2.26.2
More information about the Tarantool-patches
mailing list