Permalink
Fetching contributors…
Cannot retrieve contributors at this time
344 lines (319 sloc) 9.53 KB
--------------------------------------------------------------------------------
--- Serialize arbitrary Lua data to Lua code
-- @module lua-nucleo.tserialize
-- This file is a part of lua-nucleo library
-- @copyright lua-nucleo authors (see file `COPYRIGHT` for the license)
--------------------------------------------------------------------------------
-- Serializes arbitrary lua tables to lua code
-- that can be loaded back via loadstring()
-- Functions, threads, userdata are not supported
-- Metatables are ignored
-- Usage:
-- str = tserialize(explist) --> to serialize data
-- =(loadstring(str)()) --> to load it back
local tserialize
do
local pairs, type, ipairs, tostring, select
= pairs, type, ipairs, tostring, select
local table_concat, table_remove = table.concat, table.remove
local string_format, string_match = string.format, string.match
local lua51_keywords = import 'lua-nucleo/language.lua' { 'lua51_keywords' }
local serialize_number = import 'lua-nucleo/string.lua' { 'serialize_number' }
local cur_buf
local cat = function(v)
cur_buf[#cur_buf + 1] = v
end
local function explode_rec(t, add, vis, added)
local t_type = type(t)
if t_type == "table" then
if not (added[t] or vis[t]) then
vis[t] = true
for k,v in pairs(t) do
explode_rec(k, add, vis, added)
explode_rec(v, add, vis, added)
end
else
if not added[t] and vis[t] then
add[#add+1] = t
added[t] = { name = "_["..#add.."]", num = #add}
end
end
end
end
local parse_rec
do
local started
local function impl(t, added, rec_info)
local t_type = type(t)
local rec = false
if t_type == "table" then
if not added[t]or not started then
started = true
for k, v in pairs(t) do
if impl(k, added, rec_info) or impl(v, added, rec_info) then
rec = true
if type(k) == "table" then
rec_info[k] = true
end
if type(v) == "table" then
rec_info[v] = true
end
end
end
else
return true
end
end
return rec
end
parse_rec = function(t, added, rec_info)
started = false
rec_info[t] = true
impl(t, added, rec_info)
end
end
local function recursive_proceed(t, added, rec_info, after, started)
local t_type = type(t)
if t_type == "table" then
if not started or not added[t] then
local started = true
cat("{")
-- Serialize numeric indices
local next_i = 0
for i, v in ipairs(t) do
next_i = i
if not (rec_info[i] or rec_info[v]) then
if i ~= 1 then cat(",") end
recursive_proceed(v, added, rec_info, after, started)
else
next_i = i - 1
break
end
end
next_i = next_i + 1
-- Serialize hash part
-- Skipping comma only at first element if there is no numeric part.
local comma = (next_i > 1) and "," or ""
for k, v in pairs(t) do
local k_type = type(k)
if not (rec_info[k] or rec_info[v]) then
--that means, if the value does not contain a recursive link
-- to the table itself
--and the index does not contain a recursive link...
if k_type == "string" then
cat(comma)
comma = ","
--check if we can use the short notation
-- eg {a=3,b=5} istead of {["a"]=3,["b"]=5}
if
not lua51_keywords[k] and string_match(k, "^[%a_][%a%d_]*$")
then
cat(k); cat("=")
else
cat(string_format("[%q]=", k))
end
recursive_proceed(v, added, rec_info, after, started)
elseif
k_type ~= "number" or -- non-string non-number
k >= next_i or k < 1 or -- integer key in hash part of the table
k % 1 ~= 0 -- non-integral key.
then
cat(comma)
comma = ","
cat("[")
recursive_proceed(k, added, rec_info, after, started)
cat("]")
cat("=")
recursive_proceed(v, added, rec_info, after, started)
end
else
after[#after + 1] = {k,v}
end
end
cat("}")
else -- already visited!
cat(added[t].name)
end
elseif t_type == "string" then
cat(string_format("%q", t))
elseif t_type == "number" then
cat(serialize_number(t))
elseif t_type == "boolean" then
cat(tostring(t))
elseif t == nil then
cat("nil")
else
return nil
end
return true
end
local function recursive_proceed_simple(t, added)
local t_type = type(t)
if t_type == "table" then
if not added[t] then
cat("{")
-- Serialize numeric indices
local next_i = 0
for i, v in ipairs(t) do
next_i = i
if i ~= 1 then cat(",") end
recursive_proceed_simple(v, added)
end
next_i = next_i + 1
-- Serialize hash part
-- Skipping comma only at first element if there is no numeric part.
local comma = (next_i > 1) and "," or ""
for k, v in pairs(t) do
local k_type = type(k)
if k_type == "string" then
cat(comma)
comma = ","
--check if we can use the short notation
-- eg {a=3,b=5} istead of {["a"]=3,["b"]=5}
if
not lua51_keywords[k] and string_match(k, "^[%a_][%a%d_]*$")
then
cat(k); cat("=")
else
cat(string_format("[%q]=", k))
end
recursive_proceed_simple(v, added)
elseif
k_type ~= "number" or -- non-string non-number
k >= next_i or k < 1 or -- integer key in hash part of the table
k % 1 ~= 0 -- non-integral key.
then
cat(comma)
comma = ","
cat("[")
recursive_proceed_simple(k, added)
cat("]")
cat("=")
recursive_proceed_simple(v, added)
end
end
cat("}")
else -- already visited!
cat(added[t].name)
end
elseif t_type == "string" then
cat(string_format("%q", t))
elseif t_type == "number" then
cat(serialize_number(t))
elseif t_type == "boolean" then
cat(tostring(t))
elseif t == nil then
cat("nil")
else
return nil
end
return true
end
local afterwork = function(k, v, buf, name, added)
cur_buf = buf
cat(" ")
cat(name)
cat("[")
local after = buf.afterwork
if not recursive_proceed_simple(k, added) then
return false
end
cat("]=")
if not recursive_proceed_simple(v, added) then
return false
end
cat(" ")
return true
end
tserialize = function (...)
--===================================--
--===========THE MAIN PART===========--
--===================================--
--PREPARATORY WORK: LOCATE THE RECURSIVE AND SHARED PARTS--
local narg = select("#", ...)
local visited = {}
-- table, containing recursive parts of our variables
local additional_vars = { }
local added = {}
for i = 1, narg do
local v = select(i, ...)
explode_rec(v, additional_vars, visited, added) -- discover recursive subtables
end
visited = nil -- no more needed
local nadd = #additional_vars
--SERIALIZE ADDITIONAL FIRST--
local rec_info = {}
for i = 1, nadd do
local v = additional_vars[i]
parse_rec(v, added, rec_info)
end
local buf = {}
for i = 1, nadd do
local v = additional_vars[i]
buf[i] = {afterwork = {}}
local after = buf[i].afterwork
cur_buf = buf[i]
if not recursive_proceed(v, added, rec_info, after) then
return nil, "Unserializable data in parameter #" .. i
end
end
rec_info = {}
for i = 1, nadd do
local v = additional_vars[i]
buf[i].afterstart = #buf[i]
for j = 1, #(buf[i].afterwork) do
if not afterwork(
buf[i].afterwork[j][1],
buf[i].afterwork[j][2],
buf[i],
added[v].name,
added
)
then
return nil, "Unserializable data in parameter #" .. i
end
end
end
--SERIALIZE GIVEN VARS--
for i = 1, narg do
local v = select(i, ...)
buf[i + nadd] = {}
cur_buf = buf[i + nadd]
if not recursive_proceed_simple(v, added) then
return nil, "Unserializable data in parameter #" .. i
end
end
--DECLARE ADDITIONAL VARS--
local prevbuf = {}
for v, inf in pairs(added) do
prevbuf[ #prevbuf + 1] =
"[" .. inf.num .. "]=" .. table_concat(buf[inf.num], "", 1, buf[inf.num].afterstart)..";"
end
--CONCAT PARTS--
for i = 1, nadd do
buf[i] = table_concat(buf[i], "", buf[i].afterstart + 1)
end
for i = nadd + 1, nadd+narg do
buf[i] = table_concat(buf[i])
end
--RETURN THE RESULT--
if nadd == 0 then
return "return " .. table_concat(buf,",")
else
local rez = {
"do local _={";
table_concat(prevbuf, " ");
'}';
table_concat(buf, " ", 1, nadd);
" return ";
table_concat(buf, ",", nadd+1);
" end";
}
return table_concat(rez)
end
end
end
return
{
tserialize = tserialize;
}