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 occurrences 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 = "1", 1
65 -- [object] = reference
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 + 5 < #object) then
74 write"local _={};" -- initialize reference table
79 if type_ == "table" then
81 elseif type_ == "function" then
82 write(dump_func(object))
83 elseif type_ == "string" then
87 references[object] = reference
88 if type_ == "table" then
89 to_fill[object] = reference
92 reference = ("%d"):format(refnum)
95 -- Used to decide whether we should do "key=..."
96 local function use_short_key(key)
97 return not references[key] and type(key) == "string" and (not keywords[key]) and string_match(key, "^[%a_][%a%d_]*$")
99 local function dump(value)
104 if value == true then
107 if value == false then
108 return write("false")
110 local type_ = type(value)
111 if type_ == "number" then
112 if value ~= value then -- nan
114 elseif value == math_huge then
116 elseif value == -math_huge then
119 return write(string_format("%.17g", value))
122 -- Reference types: table, function and string
123 local ref = references[value]
129 if type_ == "string" then
130 return write(quote(value))
132 if type_ == "function" then
133 return write(dump_func(value))
135 if type_ == "table" then
137 -- First write list keys:
138 -- Don't use the table length #value here as it may horribly fail
139 -- for tables which use large integers as keys in the hash part;
140 -- stop at the first "hole" (nil value) instead
142 local first = true -- whether this is the first entry, which may not have a leading comma
144 local v = rawget(value, len + 1) -- use rawget to avoid metatables like the vector metatable
145 if v == nil then break end
146 if first then first = false else write(",") end
150 -- Now write map keys ([key] = value)
151 for k, v in next, value do
152 -- We have written all non-float keys in [1, len] already
153 if type(k) ~= "number" or k % 1 ~= 0 or k < 1 or k > len then
154 if first then first = false else write(",") end
155 if use_short_key(k) then
170 -- Write the statements to fill circular tables
171 for table, ref in pairs(to_fill) do
172 for k, v in pairs(table) do
176 if use_short_key(k) then
193 function core.serialize(value)
195 serialize(value, function(text)
196 -- Faster than table.insert(rope, text) on PUC Lua 5.1
197 rope[#rope + 1] = text
199 return table_concat(rope)
202 local function dummy_func() end
204 function core.deserialize(str, safe)
205 -- Backwards compatibility
207 core.log("deprecated", "minetest.deserialize called with nil (expected string).")
208 return nil, "Invalid type: Expected a string, got nil"
211 if t ~= "string" then
212 error(("minetest.deserialize called with %s (expected string)."):format(t))
215 local func, err = loadstring(str)
216 if not func then return nil, err end
218 -- math.huge was serialized to inf and NaNs to nan by Lua in Minetest 5.6, so we have to support this here
219 local env = {inf = math_huge, nan = 0/0}
221 env.loadstring = dummy_func
223 env.loadstring = function(str, ...)
224 local func, err = loadstring(str, ...)
233 local success, value_or_err = pcall(func)
237 return nil, value_or_err