diff options
author | ShadowNinja <shadowninja@minetest.net> | 2014-04-27 17:55:49 -0400 |
---|---|---|
committer | ShadowNinja <shadowninja@minetest.net> | 2014-05-07 17:14:23 -0400 |
commit | 1cd512913e4d4ad1fb43d4b6e3d7971bb6c67528 (patch) | |
tree | 8c1e2c708f567656684e89faee4f7c8e6c5ec673 /builtin/common/serialize.lua | |
parent | fef2729fd0945601e6772780514ee55fec35b068 (diff) | |
download | minetest-1cd512913e4d4ad1fb43d4b6e3d7971bb6c67528.tar.gz minetest-1cd512913e4d4ad1fb43d4b6e3d7971bb6c67528.tar.bz2 minetest-1cd512913e4d4ad1fb43d4b6e3d7971bb6c67528.zip |
Organize builtin into subdirectories
Diffstat (limited to 'builtin/common/serialize.lua')
-rw-r--r-- | builtin/common/serialize.lua | 223 |
1 files changed, 223 insertions, 0 deletions
diff --git a/builtin/common/serialize.lua b/builtin/common/serialize.lua new file mode 100644 index 000000000..93fffe80d --- /dev/null +++ b/builtin/common/serialize.lua @@ -0,0 +1,223 @@ +-- Minetest: builtin/serialize.lua + +-- https://github.com/fab13n/metalua/blob/no-dll/src/lib/serialize.lua +-- Copyright (c) 2006-2997 Fabien Fleutot <metalua@gmail.com> +-- License: MIT +-------------------------------------------------------------------------------- +-- Serialize an object into a source code string. This string, when passed as +-- an argument to deserialize(), returns an object structurally identical +-- to the original one. The following are currently supported: +-- * strings, numbers, booleans, nil +-- * tables thereof. Tables can have shared part, but can't be recursive yet. +-- Caveat: metatables and environments aren't saved. +-------------------------------------------------------------------------------- + +local no_identity = { number=1, boolean=1, string=1, ['nil']=1 } + +function minetest.serialize(x) + + local gensym_max = 0 -- index of the gensym() symbol generator + local seen_once = { } -- element->true set of elements seen exactly once in the table + local multiple = { } -- element->varname set of elements seen more than once + local nested = { } -- transient, set of elements currently being traversed + local nest_points = { } + local nest_patches = { } + + local function gensym() + gensym_max = gensym_max + 1 ; return gensym_max + end + + ----------------------------------------------------------------------------- + -- nest_points are places where a table appears within itself, directly or not. + -- for instance, all of these chunks create nest points in table x: + -- "x = { }; x[x] = 1", "x = { }; x[1] = x", "x = { }; x[1] = { y = { x } }". + -- To handle those, two tables are created by mark_nest_point: + -- * nest_points [parent] associates all keys and values in table parent which + -- create a nest_point with boolean `true' + -- * nest_patches contain a list of { parent, key, value } tuples creating + -- a nest point. They're all dumped after all the other table operations + -- have been performed. + -- + -- mark_nest_point (p, k, v) fills tables nest_points and nest_patches with + -- informations required to remember that key/value (k,v) create a nest point + -- in table parent. It also marks `parent' as occuring multiple times, since + -- several references to it will be required in order to patch the nest + -- points. + ----------------------------------------------------------------------------- + local function mark_nest_point (parent, k, v) + local nk, nv = nested[k], nested[v] + assert (not nk or seen_once[k] or multiple[k]) + assert (not nv or seen_once[v] or multiple[v]) + local mode = (nk and nv and "kv") or (nk and "k") or ("v") + local parent_np = nest_points [parent] + local pair = { k, v } + if not parent_np then parent_np = { }; nest_points [parent] = parent_np end + parent_np [k], parent_np [v] = nk, nv + table.insert (nest_patches, { parent, k, v }) + seen_once [parent], multiple [parent] = nil, true + end + + ----------------------------------------------------------------------------- + -- First pass, list the tables and functions which appear more than once in x + ----------------------------------------------------------------------------- + local function mark_multiple_occurences (x) + if no_identity [type(x)] then return end + if seen_once [x] then seen_once [x], multiple [x] = nil, true + elseif multiple [x] then -- pass + else seen_once [x] = true end + + if type (x) == 'table' then + nested [x] = true + for k, v in pairs (x) do + if nested[k] or nested[v] then mark_nest_point (x, k, v) else + mark_multiple_occurences (k) + mark_multiple_occurences (v) + end + end + nested [x] = nil + end + end + + local dumped = { } -- multiply occuring values already dumped in localdefs + local localdefs = { } -- already dumped local definitions as source code lines + + -- mutually recursive functions: + local dump_val, dump_or_ref_val + + -------------------------------------------------------------------- + -- if x occurs multiple times, dump the local var rather than the + -- value. If it's the first time it's dumped, also dump the content + -- in localdefs. + -------------------------------------------------------------------- + function dump_or_ref_val (x) + if nested[x] then return 'false' end -- placeholder for recursive reference + if not multiple[x] then return dump_val (x) end + local var = dumped [x] + if var then return "_[" .. var .. "]" end -- already referenced + local val = dump_val(x) -- first occurence, create and register reference + var = gensym() + table.insert(localdefs, "_["..var.."]="..val) + dumped [x] = var + return "_[" .. var .. "]" + end + + ----------------------------------------------------------------------------- + -- Second pass, dump the object; subparts occuring multiple times are dumped + -- in local variables which can be referenced multiple times; + -- care is taken to dump locla vars in asensible order. + ----------------------------------------------------------------------------- + function dump_val(x) + local t = type(x) + if x==nil then return 'nil' + elseif t=="number" then return tostring(x) + elseif t=="string" then return string.format("%q", x) + elseif t=="boolean" then return x and "true" or "false" + elseif t=="function" then + return "loadstring("..string.format("%q", string.dump(x))..")" + elseif t=="table" then + local acc = { } + local idx_dumped = { } + local np = nest_points [x] + for i, v in ipairs(x) do + if np and np[v] then + table.insert (acc, 'false') -- placeholder + else + table.insert (acc, dump_or_ref_val(v)) + end + idx_dumped[i] = true + end + for k, v in pairs(x) do + if np and (np[k] or np[v]) then + --check_multiple(k); check_multiple(v) -- force dumps in localdefs + elseif not idx_dumped[k] then + table.insert (acc, "[" .. dump_or_ref_val(k) .. "] = " .. dump_or_ref_val(v)) + end + end + return "{ "..table.concat(acc,", ").." }" + else + error ("Can't serialize data of type "..t) + end + end + + local function dump_nest_patches() + for _, entry in ipairs(nest_patches) do + local p, k, v = unpack (entry) + assert (multiple[p]) + local set = dump_or_ref_val (p) .. "[" .. dump_or_ref_val (k) .. "] = " .. + dump_or_ref_val (v) .. " -- rec " + table.insert (localdefs, set) + end + end + + mark_multiple_occurences (x) + local toplevel = dump_or_ref_val (x) + dump_nest_patches() + + if next (localdefs) then + return "local _={ }\n" .. + table.concat (localdefs, "\n") .. + "\nreturn " .. toplevel + else + return "return " .. toplevel + end +end + +-- Deserialization. +-- http://stackoverflow.com/questions/5958818/loading-serialized-data-into-a-table +-- + +local env = { + loadstring = loadstring, +} + +local function noop() end + +local safe_env = { + loadstring = noop, +} + +local function stringtotable(sdata, safe) + if sdata:byte(1) == 27 then return nil, "binary bytecode prohibited" end + local f, message = assert(loadstring(sdata)) + if not f then return nil, message end + if safe then + setfenv(f, safe_env) + else + setfenv(f, env) + end + return f() +end + +function minetest.deserialize(sdata, safe) + local table = {} + local okay, results = pcall(stringtotable, sdata, safe) + if okay then + return results + end + minetest.log('error', 'minetest.deserialize(): '.. results) + return nil +end + +-- Run some unit tests +local function unit_test() + function unitTest(name, success) + if not success then + error(name .. ': failed') + end + end + + unittest_input = {cat={sound="nyan", speed=400}, dog={sound="woof"}} + unittest_output = minetest.deserialize(minetest.serialize(unittest_input)) + + unitTest("test 1a", unittest_input.cat.sound == unittest_output.cat.sound) + unitTest("test 1b", unittest_input.cat.speed == unittest_output.cat.speed) + unitTest("test 1c", unittest_input.dog.sound == unittest_output.dog.sound) + + unittest_input = {escapechars="\n\r\t\v\\\"\'", noneuropean="θשׁ٩∂"} + unittest_output = minetest.deserialize(minetest.serialize(unittest_input)) + unitTest("test 3a", unittest_input.escapechars == unittest_output.escapechars) + unitTest("test 3b", unittest_input.noneuropean == unittest_output.noneuropean) +end +unit_test() -- Run it +unit_test = nil -- Hide it + |