serialize.lua 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  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 minetest.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=="table" then
  104. local acc = { }
  105. local idx_dumped = { }
  106. local np = nest_points [x]
  107. for i, v in ipairs(x) do
  108. if np and np[v] then
  109. table.insert (acc, 'false') -- placeholder
  110. else
  111. table.insert (acc, dump_or_ref_val(v))
  112. end
  113. idx_dumped[i] = true
  114. end
  115. for k, v in pairs(x) do
  116. if np and (np[k] or np[v]) then
  117. --check_multiple(k); check_multiple(v) -- force dumps in localdefs
  118. elseif not idx_dumped[k] then
  119. table.insert (acc, "[" .. dump_or_ref_val(k) .. "] = " .. dump_or_ref_val(v))
  120. end
  121. end
  122. return "{ "..table.concat(acc,", ").." }"
  123. else
  124. error ("Can't serialize data of type "..t)
  125. end
  126. end
  127. local function dump_nest_patches()
  128. for _, entry in ipairs(nest_patches) do
  129. local p, k, v = unpack (entry)
  130. assert (multiple[p])
  131. local set = dump_or_ref_val (p) .. "[" .. dump_or_ref_val (k) .. "] = " ..
  132. dump_or_ref_val (v) .. " -- rec "
  133. table.insert (localdefs, set)
  134. end
  135. end
  136. mark_multiple_occurences (x)
  137. local toplevel = dump_or_ref_val (x)
  138. dump_nest_patches()
  139. if next (localdefs) then
  140. return "local _={ }\n" ..
  141. table.concat (localdefs, "\n") ..
  142. "\nreturn " .. toplevel
  143. else
  144. return "return " .. toplevel
  145. end
  146. end
  147. -- Deserialization.
  148. -- http://stackoverflow.com/questions/5958818/loading-serialized-data-into-a-table
  149. --
  150. local function stringtotable(sdata)
  151. if sdata:byte(1) == 27 then return nil, "binary bytecode prohibited" end
  152. local f, message = assert(loadstring(sdata))
  153. if not f then return nil, message end
  154. setfenv(f, table)
  155. return f()
  156. end
  157. function minetest.deserialize(sdata)
  158. local table = {}
  159. local okay,results = pcall(stringtotable, sdata)
  160. if okay then
  161. return results
  162. end
  163. print('error:'.. results)
  164. return nil
  165. end
  166. -- Run some unit tests
  167. local function unit_test()
  168. function unitTest(name, success)
  169. if not success then
  170. error(name .. ': failed')
  171. end
  172. end
  173. unittest_input = {cat={sound="nyan", speed=400}, dog={sound="woof"}}
  174. unittest_output = minetest.deserialize(minetest.serialize(unittest_input))
  175. unitTest("test 1a", unittest_input.cat.sound == unittest_output.cat.sound)
  176. unitTest("test 1b", unittest_input.cat.speed == unittest_output.cat.speed)
  177. unitTest("test 1c", unittest_input.dog.sound == unittest_output.dog.sound)
  178. unittest_input = {escapechars="\n\r\t\v\\\"\'\[\]", noneuropean="θשׁ٩∂"}
  179. unittest_output = minetest.deserialize(minetest.serialize(unittest_input))
  180. unitTest("test 3a", unittest_input.escapechars == unittest_output.escapechars)
  181. unitTest("test 3b", unittest_input.noneuropean == unittest_output.noneuropean)
  182. end
  183. unit_test() -- Run it
  184. unit_test = nil -- Hide it