175 lines
3.9 KiB
Lua
175 lines
3.9 KiB
Lua
----------------------------------------------------------------
|
|
-- serialize.lua
|
|
--
|
|
-- Exports:
|
|
--
|
|
-- orderedPairs : deterministically ordered version of pairs()
|
|
--
|
|
-- serialize : convert Lua value to string in Lua syntax
|
|
--
|
|
----------------------------------------------------------------
|
|
|
|
|
|
-- orderedPairs: iterate over table elements in deterministic order. First,
|
|
-- array elements are returned, then remaining elements sorted by the key's
|
|
-- type and value.
|
|
|
|
-- compare any two Lua values, establishing a complete ordering
|
|
local function ltAny(a,b)
|
|
local ta, tb = type(a), type(b)
|
|
if ta ~= tb then
|
|
return ta < tb
|
|
end
|
|
if ta == "string" or ta == "number" then
|
|
return a < b
|
|
end
|
|
return tostring(a) < tostring(b)
|
|
end
|
|
|
|
local inext = ipairs{}
|
|
|
|
local function orderedPairs(t)
|
|
local keys = {}
|
|
local keyIndex = 1
|
|
local counting = true
|
|
|
|
local function _next(seen, s)
|
|
local v
|
|
|
|
if counting then
|
|
-- return next array index
|
|
s, v = inext(t, s)
|
|
if s ~= nil then
|
|
seen[s] = true
|
|
return s,v
|
|
end
|
|
counting = false
|
|
|
|
-- construct sorted unseen keys
|
|
for k,v in pairs(t) do
|
|
if not seen[k] then
|
|
table.insert(keys, k)
|
|
end
|
|
end
|
|
table.sort(keys, ltAny)
|
|
end
|
|
|
|
-- return next unseen table element
|
|
s = keys[keyIndex]
|
|
if s ~= nil then
|
|
keyIndex = keyIndex + 1
|
|
v = t[s]
|
|
end
|
|
return s, v
|
|
end
|
|
|
|
return _next, {}, 0
|
|
end
|
|
|
|
|
|
-- avoid 'nan', 'inf', and '-inf'
|
|
local numtostring = {
|
|
[tostring(-1/0)] = "-1/0",
|
|
[tostring(1/0)] = "1/0",
|
|
[tostring(0/0)] = "0/0"
|
|
}
|
|
|
|
setmetatable(numtostring, { __index = function (t, k) return k end })
|
|
|
|
-- serialize: Serialize a Lua data structure
|
|
--
|
|
-- x = value to serialize
|
|
-- out = function to be called repeatedly with strings, or
|
|
-- table into which strings should be inserted, or
|
|
-- nil => return a string
|
|
-- iter = function to iterate over table elements, or
|
|
-- "s" to sort elements by key, or
|
|
-- nil for default (fastest)
|
|
--
|
|
-- Notes:
|
|
-- * Does not support self-referential data structures.
|
|
-- * Does not optimize for repeated sub-expressions.
|
|
-- * Does not preserve topology; only values.
|
|
-- * Does not handle types other than nil, number, boolean, string, table
|
|
--
|
|
local function serialize(x, out, iter)
|
|
local visited = {}
|
|
local iter = iter=="s" and orderedPairs or iter or pairs
|
|
assert(type(iter) == "function")
|
|
|
|
local function _serialize(x)
|
|
if type(x) == "string" then
|
|
|
|
out(string.format("%q", x))
|
|
|
|
elseif type(x) == "number" then
|
|
|
|
out(numtostring[tostring(x)])
|
|
|
|
elseif type(x) == "boolean" or
|
|
type(x) == "nil" then
|
|
|
|
out(tostring(x))
|
|
|
|
elseif type(x) == "table" then
|
|
|
|
if visited[x] then
|
|
error("serialize: recursive structure")
|
|
end
|
|
visited[x] = true
|
|
local first, nextIndex = true, 1
|
|
|
|
out "{"
|
|
|
|
for k,v in iter(x) do
|
|
if first then
|
|
first = false
|
|
else
|
|
out ","
|
|
end
|
|
if k == nextIndex then
|
|
nextIndex = nextIndex + 1
|
|
else
|
|
if type(k) == "string" and k:match("^[%a_][%w_]*$") then
|
|
out(k.."=")
|
|
else
|
|
out "["
|
|
_serialize(k)
|
|
out "]="
|
|
end
|
|
end
|
|
_serialize(v)
|
|
end
|
|
|
|
out "}"
|
|
visited[x] = false
|
|
else
|
|
error("serialize: unsupported type")
|
|
end
|
|
end
|
|
|
|
local result
|
|
if not out then
|
|
result = {}
|
|
out = result
|
|
end
|
|
|
|
if type(out) == "table" then
|
|
local t = out
|
|
function out(s)
|
|
table.insert(t,s)
|
|
end
|
|
end
|
|
|
|
_serialize(x)
|
|
|
|
if result then
|
|
return table.concat(result)
|
|
end
|
|
end
|
|
|
|
return {
|
|
orderedPairs = orderedPairs,
|
|
serialize = serialize
|
|
}
|