diff --git a/lib/serialization.lua b/lib/serialization.lua index 34425e9..675f8cd 100644 --- a/lib/serialization.lua +++ b/lib/serialization.lua @@ -1,145 +1,49 @@ --- serialization lib borrowed from OpenOS -local serialization = {} - --- delay loaded tables fail to deserialize cross [C] boundaries (such as when having to read files that cause yields) -local local_pairs = function(tbl) - local mt = getmetatable(tbl) - return (mt and mt.__pairs or pairs)(tbl) +local serial = {} +local local_pairs=function(tbl) + local mt=getmetatable(tbl) + return (mt and mt.__pairs or pairs)(tbl) end - --- Important: pretty formatting will allow presenting non-serializable values --- but may generate output that cannot be unserialized back. -function serialization.serialize(value, pretty) -- serialize *value* into a string, optionally in a nicely formatted manner when *pretty* is set - local kw = {["and"]=true, ["break"]=true, ["do"]=true, ["else"]=true, - ["elseif"]=true, ["end"]=true, ["false"]=true, ["for"]=true, - ["function"]=true, ["goto"]=true, ["if"]=true, ["in"]=true, - ["local"]=true, ["nil"]=true, ["not"]=true, ["or"]=true, - ["repeat"]=true, ["return"]=true, ["then"]=true, ["true"]=true, - ["until"]=true, ["while"]=true} - local id = "^[%a_][%w_]*$" - local ts = {} - local result_pack = {} - local function recurse(current_value, depth) - local t = type(current_value) - if t == "number" then - if current_value ~= current_value then - table.insert(result_pack, "0/0") - elseif current_value == math.huge then - table.insert(result_pack, "math.huge") - elseif current_value == -math.huge then - table.insert(result_pack, "-math.huge") - else - table.insert(result_pack, tostring(current_value)) - end - elseif t == "string" then - table.insert(result_pack, (string.format("%q", current_value):gsub("\\\n","\\n"))) - elseif - t == "nil" or - t == "boolean" or - pretty and (t ~= "table" or (getmetatable(current_value) or {}).__tostring) then - table.insert(result_pack, tostring(current_value)) - elseif t == "table" then - if ts[current_value] then - if pretty then - table.insert(result_pack, "recursion") - return - else - error("tables with cycles are not supported") - end - end - ts[current_value] = true - local f - if pretty then - local ks, sks, oks = {}, {}, {} - for k in local_pairs(current_value) do - if type(k) == "number" then - table.insert(ks, k) - elseif type(k) == "string" then - table.insert(sks, k) - else - table.insert(oks, k) - end - end - table.sort(ks) - table.sort(sks) - for _, k in ipairs(sks) do - table.insert(ks, k) - end - for _, k in ipairs(oks) do - table.insert(ks, k) - end - local n = 0 - f = table.pack(function() - n = n + 1 - local k = ks[n] - if k ~= nil then - return k, current_value[k] - else - return nil - end - end) - else - f = table.pack(local_pairs(current_value)) - end - local i = 1 - local first = true - table.insert(result_pack, "{") - for k, v in table.unpack(f) do - if not first then - table.insert(result_pack, ",") - if pretty then - table.insert(result_pack, "\n" .. string.rep(" ", depth)) - end - end - first = nil - local tk = type(k) - if tk == "number" and k == i then - i = i + 1 - recurse(v, depth + 1) - else - if tk == "string" and not kw[k] and string.match(k, id) then - table.insert(result_pack, k) - else - table.insert(result_pack, "[") - recurse(k, depth + 1) - table.insert(result_pack, "]") - end - table.insert(result_pack, "=") - recurse(v, depth + 1) - end - end - ts[current_value] = nil -- allow writing same table more than once - table.insert(result_pack, "}") +function serial.serialize(value) + local kw={["and"]=true,["break"]=true,["do"]=true,["else"]=true,["elseif"]=true,["end"]=true,["false"]=true,["for"]=true,["function"]=true,["goto"]=true,["if"]=true,["in"]=true,["local"]=true,["nil"]=true,["not"]=true,["or"]=true,["repeat"]=true,["return"]=true,["then"]=true,["true"]=true,["until"]=true,["while"]=true} + local id="^[%a_][%w_]*$" + local ts={} + local function s(v,l) + local t=type(v) + if t=="nil" then return "nil" + elseif t=="boolean" then return v and "true" or "false" + elseif t=="number" then + if v~=v then return "0/0" + elseif v==math.huge then return "math.huge" + elseif v==-math.huge then return "-math.huge" + else return tostring(v) end + elseif t=="string" then return string.format("%q",v):gsub("\\\n","\\n") + elseif t=="table" then + if ts[v] then error("tcyc") end + ts[v]=true + local i,r=1, nil + local f=table.pack(local_pairs(v)) + for k,v in table.unpack(f) do + if r then r=r..","..(("\n"..string.rep(" ",l)) or "") + else r="{" end + local tk=type(k) + if tk=="number" and k==i then + i=i+1 + r=r..s(v,l+1) else - error("unsupported type: " .. t) - end - end - recurse(value, 1) - local result = table.concat(result_pack) - if pretty then - local limit = type(pretty) == "number" and pretty or 10 - local truncate = 0 - while limit > 0 and truncate do - truncate = string.find(result, "\n", truncate + 1, true) - limit = limit - 1 - end - if truncate then - return result:sub(1, truncate) .. "..." - end - end - return result + if tk == "string" and not kw[k] and string.match(k,id) then r=r..k + else r=r.."["..s(k,l+1).."]" end + r=r.."="..s(v,l+1) end end + ts[v]=nil + return (r or "{").."}" + else error("ut "..t) end end + return s(value, 1) end - -function serialization.unserialize(data) -- returns the data contained in serialized string *data* - local result, reason = load("return " .. data, "=data", nil, {math={huge=math.huge}}) - if not result then - return nil, reason - end - local ok, output = pcall(result) - if not ok then - return nil, output - end - return output +function serial.unserialize(data) + checkArg(1, data, "string") + local result, reason = load("return " .. data, "=data", _, {math={huge=math.huge}}) + if not result then return nil, reason end + local ok, output = pcall(result) + if not ok then return nil, output end + return output end - -return serialization +return serial