lua-users home
lua-l archive

table/function serialization

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


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('[%z1円-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;

AltStyle によって変換されたページ (->オリジナル) /