Skip to content
Permalink
master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
--------------------------------------------------------------------------------
--- 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, tostring, select
= pairs, type, tostring, select
local table_concat, table_remove = table.concat, table.remove
local string_format, string_match = string.format, string.match
local lua_keywords = import 'lua-nucleo/language.lua' { 'lua_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 general_iterator(tbl, i)
i = i + 1
local v = tbl[i]
if v ~= nil then
return i, v
end
end
local function metatable_iterator(tbl, i)
i = i + 1
local v = rawget(tbl, i)
if v ~= nil then
return i, v
end
end
-- Starting from Lua 5.3, size of the array tables with holes is different
-- that in older versions and is really undetermined as the Lua manual
-- always been said. This affects native `ipair` iterator and breaks
-- serialization of some tables with holes in Lua >= 5.3. To resolve it,
-- we introduce modified ipairs iterator that gets rid of that uncertainty.
-- `ipairs_raw` iterates the table starting from the first element until
-- the `nil` element is found. Access is made via `rawget` if the input table
-- has metatable, and via table key accessor otherwise.
local function ipairs_raw(list)
if getmetatable(list) then
return metatable_iterator, list, 0
end
return general_iterator, list, 0
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_raw(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 lua_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_raw(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 lua_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;
}