[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: table/function serialization
- From: ketmar <ketmar@...>
- Date: Tue, 24 Mar 2009 16:26:20 +0200
by the steps of some ealier discussion i made serialization function
that can write out table with cross-references and (wow!) functions
with (wow!) upvalues. maybe somebody will find it useful. i'm using it
in my Fallout RL prototype to serialize items/critters with callback
functions. the code is PD, as usual.
btw: don't even ask for Fallout RL -- i can't publish it due to legal
issues with Fallout world and SPECIAL system (yes, it using .pro files
and battle system from the original Fallout 2).
local function quote (s)
return '"'..s:gsub('[%z\1-\31%\\%"\127]', function (ch)
local c = SPEC_CHARS[ch];
if c then return c;
elseif ch < " " then return formatStr("\\%03i", ch:byte());
else return "\\"..ch; --"
end;
end)..'"';
end;
string.quote = quote;
local formatStr = string.format;
local dump = string.dump;
local join = table.concat;
--[[
based on the code by Manuel Fernandez-Diaz, modified by Rici Lake
Formats tables with cycles recursively to any depth.
Can serialize Lua functions with proper upvalue serialization.
The output is returned as a string, suitable for passing to
loadstring(). Self references are indicated.
]]
local function serialize (t, knownfnlist)
local indent = "";
local name = "__";
local cart = {}; -- a container
local autoref = {}; -- for self references
local cache = {}; -- cross-refs, etc
local fnfixups = {}; -- post-fixups for upvalues
local badIdx = { "table", "function", "userdata" };
local basicSerialize;
local function isTableEmpty (t) return next(t) == nil; end;
local function serializeFunc (o, name, field)
if knownfnlist and knownfnlist[o] then
-- known function
return knownfnlist[o];
end;
local info = debug.getinfo(o, "Su");
if info.what == "C" then error("can't serialize C functions!"); end;
if info.nups ~= 0 then
local fnfix = {};
fnfix.name = name; -- TODO
fnfix.ups = {};
for f = 1, info.nups do
local n, v = debug.getupvalue(o, f);
fnfix.ups[#fnfix.ups+1] = {
name = n;
value = v;
};
end;
fnfixups[#fnfixups+1] = fnfix;
end;
local fn = dump(o);
return formatStr("loadstring(%s)", fn:quote());
end;
basicSerialize = function (o, name, field)
local to, so = type(o), tostring(o);
if to == "function" then return serializeFunc(o, name, field);
elseif to == "number" or to == "boolean" or o == nil then return so;
else return so:quote();
end;
end;
local function addToCart (value, name, indent, field)
indent = indent or "";
field = field or name;
cart[#cart+1] = indent;
cart[#cart+1] = field;
local tx = type(value);
if tx ~= "table" and tx ~= "function" then
cart[#cart+1] = "=";
cart[#cart+1] = basicSerialize(value, name, field);
cart[#cart+1] = ";\n";
return;
end;
-- cached?
if cache[value] then
cart[#cart] = "--";
cart[#cart+1] = field;
cart[#cart+1] = "=";
cart[#cart+1] = cache[value];
cart[#cart+1] = "\n";
autoref[#autoref+1] = name;
autoref[#autoref+1] = "=";
autoref[#autoref+1] = cache[value];
autoref[#autoref+1] = ";\n";
return;
end;
-- not cached, output it and cache
cache[value] = name;
if tx == "function" then
cart[#cart+1] = "=";
cart[#cart+1] = serializeFunc(value, name, field);
cart[#cart+1] = ";\n";
return;
end;
-- table
if isTableEmpty(value) then cart[#cart+1] = "={};\n";
else
cart[#cart+1] = "={\n";
for k, v in pairs(value) do
if badIdx[type(k)] then error("invalid table index type"); end;
k = basicSerialize(k);
local fname = formatStr("%s[%s]", name, k);
field = formatStr("[%s]", k);
-- one space between levels
addToCart(v, fname, indent.." ", field);
end;
cart[#cart+1] = indent;
cart[#cart+1] = "};\n";
end;
end;
local res;
if type(t) ~= "table" then
res = "do\nlocal "..name.." = "..basicSerialize(t, name)..";\n";
else
cart[1] = "do\nlocal ";
addToCart(t, name, indent);
res = join(cart)..join(autoref).."\n";
end;
-- has up-fixups?
if #fnfixups then
-- first, serialize all upvalues
--res = res.."-- serialize upvalues\n";
for n, fix in ipairs(fnfixups) do
local ups = fix.ups;
for fn, up in ipairs(ups) do
local uname = "__up_"..n.."_"..fn;
res = res.."local ";
cart = {}; autoref = {};
local v = up.value;
if (type(v) == "table" or type(v) == "function") and cache[v]
then res = res..uname.."="..cache[v]..";\n";
else
addToCart(up.value, uname, indent);
res = res..join(cart)..join(autoref);
end;
end;
end;
--res = res.."\n-- fix upvalues\n";
res = res.."\nlocal _dsu=debug.setupvalue;\n";
for n, fix in ipairs(fnfixups) do
local ups = fix.ups;
if n == 1 then res = res.."local "; end;
res = res.."_fn="..fix.name..";\n";
for fn, up in ipairs(ups) do
local uname = "__up_"..n.."_"..fn;
res = res.."_dsu(_fn, "..fn..", "..uname..");\n";
end;
end;
res = res.."\n";
end;
res = res.."return "..name..";\nend;";
return res;
end;
module.serialize = serialize;