serialize.lua 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. -- Minetest: builtin/serialize.lua
  2. -- https://github.com/fab13n/metalua/blob/no-dll/src/lib/serialize.lua
  3. -- Copyright (c) 2006-2997 Fabien Fleutot <metalua@gmail.com>
  4. -- License: MIT
  5. --------------------------------------------------------------------------------
  6. -- Serialize an object into a source code string. This string, when passed as
  7. -- an argument to deserialize(), returns an object structurally identical
  8. -- to the original one. The following are currently supported:
  9. -- * strings, numbers, booleans, nil
  10. -- * tables thereof. Tables can have shared part, but can't be recursive yet.
  11. -- Caveat: metatables and environments aren't saved.
  12. --------------------------------------------------------------------------------
  13. local no_identity = { number=1, boolean=1, string=1, ['nil']=1 }
  14. function core.serialize(x)
  15. local gensym_max = 0 -- index of the gensym() symbol generator
  16. local seen_once = { } -- element->true set of elements seen exactly once in the table
  17. local multiple = { } -- element->varname set of elements seen more than once
  18. local nested = { } -- transient, set of elements currently being traversed
  19. local nest_points = { }
  20. local nest_patches = { }
  21. local function gensym()
  22. gensym_max = gensym_max + 1 ; return gensym_max
  23. end
  24. -----------------------------------------------------------------------------
  25. -- nest_points are places where a table appears within itself, directly or not.
  26. -- for instance, all of these chunks create nest points in table x:
  27. -- "x = { }; x[x] = 1", "x = { }; x[1] = x", "x = { }; x[1] = { y = { x } }".
  28. -- To handle those, two tables are created by mark_nest_point:
  29. -- * nest_points [parent] associates all keys and values in table parent which
  30. -- create a nest_point with boolean `true'
  31. -- * nest_patches contain a list of { parent, key, value } tuples creating
  32. -- a nest point. They're all dumped after all the other table operations
  33. -- have been performed.
  34. --
  35. -- mark_nest_point (p, k, v) fills tables nest_points and nest_patches with
  36. -- informations required to remember that key/value (k,v) create a nest point
  37. -- in table parent. It also marks `parent' as occuring multiple times, since
  38. -- several references to it will be required in order to patch the nest
  39. -- points.
  40. -----------------------------------------------------------------------------
  41. local function mark_nest_point (parent, k, v)
  42. local nk, nv = nested[k], nested[v]
  43. assert (not nk or seen_once[k] or multiple[k])
  44. assert (not nv or seen_once[v] or multiple[v])
  45. local mode = (nk and nv and "kv") or (nk and "k") or ("v")
  46. local parent_np = nest_points [parent]
  47. local pair = { k, v }
  48. if not parent_np then parent_np = { }; nest_points [parent] = parent_np end
  49. parent_np [k], parent_np [v] = nk, nv
  50. table.insert (nest_patches, { parent, k, v })
  51. seen_once [parent], multiple [parent] = nil, true
  52. end
  53. -----------------------------------------------------------------------------
  54. -- First pass, list the tables and functions which appear more than once in x
  55. -----------------------------------------------------------------------------
  56. local function mark_multiple_occurences (x)
  57. if no_identity [type(x)] then return end
  58. if seen_once [x] then seen_once [x], multiple [x] = nil, true
  59. elseif multiple [x] then -- pass
  60. else seen_once [x] = true end
  61. if type (x) == 'table' then
  62. nested [x] = true
  63. for k, v in pairs (x) do
  64. if nested[k] or nested[v] then mark_nest_point (x, k, v) else
  65. mark_multiple_occurences (k)
  66. mark_multiple_occurences (v)
  67. end
  68. end
  69. nested [x] = nil
  70. end
  71. end
  72. local dumped = { } -- multiply occuring values already dumped in localdefs
  73. local localdefs = { } -- already dumped local definitions as source code lines
  74. -- mutually recursive functions:
  75. local dump_val, dump_or_ref_val
  76. --------------------------------------------------------------------
  77. -- if x occurs multiple times, dump the local var rather than the
  78. -- value. If it's the first time it's dumped, also dump the content
  79. -- in localdefs.
  80. --------------------------------------------------------------------
  81. function dump_or_ref_val (x)
  82. if nested[x] then return 'false' end -- placeholder for recursive reference
  83. if not multiple[x] then return dump_val (x) end
  84. local var = dumped [x]
  85. if var then return "_[" .. var .. "]" end -- already referenced
  86. local val = dump_val(x) -- first occurence, create and register reference
  87. var = gensym()
  88. table.insert(localdefs, "_["..var.."]="..val)
  89. dumped [x] = var
  90. return "_[" .. var .. "]"
  91. end
  92. -----------------------------------------------------------------------------
  93. -- Second pass, dump the object; subparts occuring multiple times are dumped
  94. -- in local variables which can be referenced multiple times;
  95. -- care is taken to dump locla vars in asensible order.
  96. -----------------------------------------------------------------------------
  97. function dump_val(x)
  98. local t = type(x)
  99. if x==nil then return 'nil'
  100. elseif t=="number" then return tostring(x)
  101. elseif t=="string" then return string.format("%q", x)
  102. elseif t=="boolean" then return x and "true" or "false"
  103. elseif t=="function" then
  104. return "loadstring("..string.format("%q", string.dump(x))..")"
  105. elseif t=="table" then
  106. local acc = { }
  107. local idx_dumped = { }
  108. local np = nest_points [x]
  109. for i, v in ipairs(x) do
  110. if np and np[v] then
  111. table.insert (acc, 'false') -- placeholder
  112. else
  113. table.insert (acc, dump_or_ref_val(v))
  114. end
  115. idx_dumped[i] = true
  116. end
  117. for k, v in pairs(x) do
  118. if np and (np[k] or np[v]) then
  119. --check_multiple(k); check_multiple(v) -- force dumps in localdefs
  120. elseif not idx_dumped[k] then
  121. table.insert (acc, "[" .. dump_or_ref_val(k) .. "] = " .. dump_or_ref_val(v))
  122. end
  123. end
  124. return "{ "..table.concat(acc,", ").." }"
  125. else
  126. error ("Can't serialize data of type "..t)
  127. end
  128. end
  129. local function dump_nest_patches()
  130. for _, entry in ipairs(nest_patches) do
  131. local p, k, v = unpack (entry)
  132. assert (multiple[p])
  133. local set = dump_or_ref_val (p) .. "[" .. dump_or_ref_val (k) .. "] = " ..
  134. dump_or_ref_val (v) .. " -- rec "
  135. table.insert (localdefs, set)
  136. end
  137. end
  138. mark_multiple_occurences (x)
  139. local toplevel = dump_or_ref_val (x)
  140. dump_nest_patches()
  141. if next (localdefs) then
  142. return "local _={ }\n" ..
  143. table.concat (localdefs, "\n") ..
  144. "\nreturn " .. toplevel
  145. else
  146. return "return " .. toplevel
  147. end
  148. end
  149. -- Deserialization.
  150. -- http://stackoverflow.com/questions/5958818/loading-serialized-data-into-a-table
  151. --
  152. local env = {
  153. loadstring = loadstring,
  154. }
  155. local function noop() end
  156. local safe_env = {
  157. loadstring = noop,
  158. }
  159. local function stringtotable(sdata, safe)
  160. if sdata:byte(1) == 27 then return nil, "binary bytecode prohibited" end
  161. local f, message = assert(loadstring(sdata))
  162. if not f then return nil, message end
  163. if safe then
  164. setfenv(f, safe_env)
  165. else
  166. setfenv(f, env)
  167. end
  168. return f()
  169. end
  170. function core.deserialize(sdata, safe)
  171. local table = {}
  172. local okay, results = pcall(stringtotable, sdata, safe)
  173. if okay then
  174. return results
  175. end
  176. core.log('error', 'core.deserialize(): '.. results)
  177. return nil
  178. end
  179. -- Run some unit tests
  180. local function unit_test()
  181. function unitTest(name, success)
  182. if not success then
  183. error(name .. ': failed')
  184. end
  185. end
  186. unittest_input = {cat={sound="nyan", speed=400}, dog={sound="woof"}}
  187. unittest_output = core.deserialize(core.serialize(unittest_input))
  188. unitTest("test 1a", unittest_input.cat.sound == unittest_output.cat.sound)
  189. unitTest("test 1b", unittest_input.cat.speed == unittest_output.cat.speed)
  190. unitTest("test 1c", unittest_input.dog.sound == unittest_output.dog.sound)
  191. unittest_input = {escapechars="\n\r\t\v\\\"\'", noneuropean="θשׁ٩∂"}
  192. unittest_output = core.deserialize(core.serialize(unittest_input))
  193. unitTest("test 3a", unittest_input.escapechars == unittest_output.escapechars)
  194. unitTest("test 3b", unittest_input.noneuropean == unittest_output.noneuropean)
  195. end
  196. unit_test() -- Run it
  197. unit_test = nil -- Hide it