Permalink
Cannot retrieve contributors at this time
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?
lua-nucleo/lua-nucleo/tserialize.lua
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
375 lines (347 sloc)
10.5 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| -------------------------------------------------------------------------------- | |
| --- 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; | |
| } |