From 1d2c6c5c37761ba43ebc5892b5e4dfd359545265 Mon Sep 17 00:00:00 2001 From: Lars Mueller Date: Tue, 6 Jul 2021 21:56:20 +0200 Subject: [PATCH] Add experimental luon module --- init.lua | 1 + luon.lua | 165 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ test.lua | 14 +++++ 3 files changed, 180 insertions(+) create mode 100644 luon.lua diff --git a/init.lua b/init.lua index fdf5e80..4679998 100644 --- a/init.lua +++ b/init.lua @@ -50,6 +50,7 @@ for _, file in pairs{ "ranked_set", "binary", "b3d", + "luon", "bluon", "persistence", "debug" diff --git a/luon.lua b/luon.lua new file mode 100644 index 0000000..25ba639 --- /dev/null +++ b/luon.lua @@ -0,0 +1,165 @@ +local assert, next, pairs, pcall, error, type, table_insert, table_concat, string_format, setfenv, math_huge, loadfile, loadstring + = assert, next, pairs, pcall, error, type, table.insert, table.concat, string.format, setfenv, math.huge, loadfile, loadstring +local count_values = modlib.table.count_values + +-- Build a table with the succeeding character from A-Z +local succ = {} +for char = ("A"):byte(), ("Z"):byte() - 1 do + succ[string.char(char)] = string.char(char + 1) +end + +local function quote(string) + return string_format("%q", string) +end + +local _ENV = {} +setfenv(1, _ENV) + +function serialize(object, write) + local reference = {"A"} + local function increment_reference(place) + if not reference[place] then + reference[place] = "B" + elseif reference[place] == "Z" then + reference[place] = "A" + return increment_reference(place + 1) + else + reference[place] = succ[reference[place]] + end + end + local references = {} + local to_fill = {} + for value, count in pairs(count_values(object)) do + local type_ = type(value) + if count >= 2 and ((type_ == "string" and #reference + 2 >= #value) or type_ == "table") then + local ref = table_concat(reference) + write(ref) + write"=" + write(type_ == "table" and "{}" or quote(value)) + write";" + references[value] = ref + if type_ == "table" then + to_fill[value] = true + end + increment_reference(1) + end + end + local function is_short_key(key) + return not references[key] and type(key) == "string" and key:match"^[%a_][%a%d_]*$" + end + local function dump(value) + -- Primitive types + if value == nil then + return write"nil" + end + if value == true then + return write"true" + end + if value == false then + return write"false" + end + local type_ = type(value) + if type_ == "number" then + return write(("%.17g"):format(value)) + end + -- Reference types: table and string + local ref = references[value] + if ref then + -- Referenced + if not to_fill[value] then + return write(ref) + end + -- Fill table + to_fill[value] = false + for k, v in pairs(value) do + write(ref) + if is_short_key(k) then + write"." + write(k) + else + write"[" + dump(k) + write"]" + end + write"=" + dump(v) + write";" + end + elseif type_ == "string" then + return write(quote(value)) + elseif type_ == "table" then + local first = true + write"{" + local len = #value + for i = 1, len do + if not first then write";" end + dump(value[i]) + first = false + end + for k, v in next, value do + if type(k) ~= "number" or k % 1 ~= 0 or k < 1 or k > len then + if not first then write";" end + if is_short_key(k) then + write(k) + else + write"[" + dump(k) + write"]" + end + write"=" + dump(v) + first = false + end + end + write"}" + else + error("unsupported type: " .. type_) + end + end + local fill_root = to_fill[object] + if fill_root then + -- Root table is circular, must return by named reference + dump(object) + write"return " + write(references[object]) + else + -- Root table is not circular, can directly start writing + write"return " + dump(object) + end +end + +function serialize_file(object, file) + return serialize(object, function(text) + file:write(text) + end) +end + +function serialize_string(object) + local rope = {} + serialize(object, function(text) + table_insert(rope, text) + end) + return table_concat(rope) +end + +function deserialize(...) + local read = assert(...) + -- math.huge is serialized to inf, 0/0 is serialized to -nan + setfenv(read, {inf = math_huge, nan = 0/0}) + local success, value_or_err = pcall(read) + if success then + return value_or_err + end + return nil, value_or_err +end + +function deserialize_file(path) + return deserialize(loadfile(path)) +end + +function deserialize_string(string) + return deserialize(loadstring(string)) +end + +return _ENV \ No newline at end of file diff --git a/test.lua b/test.lua index 804d4f3..ee3b651 100644 --- a/test.lua +++ b/test.lua @@ -207,6 +207,20 @@ do assert_preserves(a) end +-- luon +do + local function assert_preserves(object) + local serialized = luon.serialize_string(object) + assert(table.equals_references(object, luon.deserialize_string(serialized)), serialized) + end + local tab = {} + tab[tab] = tab + tab.vec = {x = 1, y = 2, z = 3} + tab.vec2 = modlib.table.copy(tab.vec) + tab.blah = "blah" + assert_preserves(tab) +end + if not _G.minetest then return end -- colorspec