1 --- Lua module to serialize values as Lua code.
2 -- From: https://github.com/appgurueu/modlib/blob/master/luon.lua
5 local next, rawget, pairs, pcall, error, type, setfenv, loadstring
6 = next, rawget, pairs, pcall, error, type, setfenv, loadstring
8 local table_concat, string_dump, string_format, string_match, math_huge
9 = table.concat, string.dump, string.format, string.match, math.huge
11 -- Recursively counts occurences of objects (non-primitives including strings) in a table.
12 local function count_objects(value)
15 -- Early return for nil; tables can't contain nil
18 local function count_values(val)
19 local type_ = type(val)
20 if type_ == "boolean" or type_ == "number" then
23 local count = counts[val]
24 counts[val] = (count or 0) + 1
25 if type_ == "table" then
27 for k, v in pairs(val) do
32 elseif type_ ~= "string" and type_ ~= "function" then
33 error("unsupported type: " .. type_)
40 -- Build a "set" of Lua keywords. These can't be used as short key names.
41 -- See https://www.lua.org/manual/5.1/manual.html#2.1
43 for _, keyword in pairs({
44 "and", "break", "do", "else", "elseif",
45 "end", "false", "for", "function", "if",
46 "in", "local", "nil", "not", "or",
47 "repeat", "return", "then", "true", "until", "while",
48 "goto" -- LuaJIT, Lua 5.2+
50 keywords[keyword] = true
53 local function quote(string)
54 return string_format("%q", string)
57 local function dump_func(func)
58 return string_format("loadstring(%q)", string_dump(func))
61 -- Serializes Lua nil, booleans, numbers, strings, tables and even functions
62 -- Tables are referenced by reference, strings are referenced by value. Supports circular tables.
63 local function serialize(value, write)
64 local reference, refnum = "r1", 1
65 -- [object] = reference string
67 -- Circular tables that must be filled using `table[key] = value` statements
69 for object, count in pairs(count_objects(value)) do
70 local type_ = type(object)
71 -- Object must appear more than once. If it is a string, the reference has to be shorter than the string.
72 if count >= 2 and (type_ ~= "string" or #reference + 2 < #object) then
75 if type_ == "table" then
77 elseif type_ == "function" then
78 write(dump_func(object))
79 elseif type_ == "string" then
83 references[object] = reference
84 if type_ == "table" then
85 to_fill[object] = reference
88 reference = ("r%X"):format(refnum)
91 -- Used to decide whether we should do "key=..."
92 local function use_short_key(key)
93 return not references[key] and type(key) == "string" and (not keywords[key]) and string_match(key, "^[%a_][%a%d_]*$")
95 local function dump(value)
100 if value == true then
103 if value == false then
104 return write("false")
106 local type_ = type(value)
107 if type_ == "number" then
108 return write(string_format("%.17g", value))
110 -- Reference types: table, function and string
111 local ref = references[value]
115 if type_ == "string" then
116 return write(quote(value))
118 if type_ == "function" then
119 return write(dump_func(value))
121 if type_ == "table" then
123 -- First write list keys:
124 -- Don't use the table length #value here as it may horribly fail
125 -- for tables which use large integers as keys in the hash part;
126 -- stop at the first "hole" (nil value) instead
128 local first = true -- whether this is the first entry, which may not have a leading comma
130 local v = rawget(value, len + 1) -- use rawget to avoid metatables like the vector metatable
131 if v == nil then break end
132 if first then first = false else write(",") end
136 -- Now write map keys ([key] = value)
137 for k, v in next, value do
138 -- We have written all non-float keys in [1, len] already
139 if type(k) ~= "number" or k % 1 ~= 0 or k < 1 or k > len then
140 if first then first = false else write(",") end
141 if use_short_key(k) then
156 -- Write the statements to fill circular tables
157 for table, ref in pairs(to_fill) do
158 for k, v in pairs(table) do
160 if use_short_key(k) then
177 function core.serialize(value)
179 serialize(value, function(text)
180 -- Faster than table.insert(rope, text) on PUC Lua 5.1
181 rope[#rope + 1] = text
183 return table_concat(rope)
186 local function dummy_func() end
188 local nan = (0/0)^1 -- +nan
190 function core.deserialize(str, safe)
191 -- Backwards compatibility
193 core.log("deprecated", "minetest.deserialize called with nil (expected string).")
194 return nil, "Invalid type: Expected a string, got nil"
197 if t ~= "string" then
198 error(("minetest.deserialize called with %s (expected string)."):format(t))
201 local func, err = loadstring(str)
202 if not func then return nil, err end
204 -- math.huge is serialized to inf, NaNs are serialized to nan by Lua
205 local env = {inf = math_huge, nan = nan}
207 env.loadstring = dummy_func
209 env.loadstring = function(str, ...)
210 local func, err = loadstring(str, ...)
219 local success, value_or_err = pcall(func)
223 return nil, value_or_err