serialize.lua 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. --- Lua module to serialize values as Lua code.
  2. -- From: https://github.com/fab13n/metalua/blob/no-dll/src/lib/serialize.lua
  3. -- License: MIT
  4. -- @copyright 2006-2997 Fabien Fleutot <metalua@gmail.com>
  5. -- @author Fabien Fleutot <metalua@gmail.com>
  6. -- @author ShadowNinja <shadowninja@minetest.net>
  7. --------------------------------------------------------------------------------
  8. --- Serialize an object into a source code string. This string, when passed as
  9. -- an argument to deserialize(), returns an object structurally identical to
  10. -- the original one. The following are currently supported:
  11. -- * Booleans, numbers, strings, and nil.
  12. -- * Functions; uses interpreter-dependent (and sometimes platform-dependent) bytecode!
  13. -- * Tables; they can cantain multiple references and can be recursive, but metatables aren't saved.
  14. -- This works in two phases:
  15. -- 1. Recursively find and record multiple references and recursion.
  16. -- 2. Recursively dump the value into a string.
  17. -- @param x Value to serialize (nil is allowed).
  18. -- @return load()able string containing the value.
  19. function core.serialize(x)
  20. local local_index = 1 -- Top index of the "_" local table in the dump
  21. -- table->nil/1/2 set of tables seen.
  22. -- nil = not seen, 1 = seen once, 2 = seen multiple times.
  23. local seen = {}
  24. -- nest_points are places where a table appears within itself, directly
  25. -- or not. For instance, all of these chunks create nest points in
  26. -- table x: "x = {}; x[x] = 1", "x = {}; x[1] = x",
  27. -- "x = {}; x[1] = {y = {x}}".
  28. -- To handle those, two tables are used by mark_nest_point:
  29. -- * nested - Transient set of tables being currently traversed.
  30. -- Used for detecting nested tables.
  31. -- * nest_points - parent->{key=value, ...} table cantaining the nested
  32. -- keys and values in the parent. They're all dumped after all the
  33. -- other table operations have been performed.
  34. --
  35. -- mark_nest_point(p, k, v) fills nest_points with information required
  36. -- to remember that key/value (k, v) creates a nest point in table
  37. -- parent. It also marks "parent" and the nested item(s) as occuring
  38. -- multiple times, since several references to it will be required in
  39. -- order to patch the nest points.
  40. local nest_points = {}
  41. local nested = {}
  42. local function mark_nest_point(parent, k, v)
  43. local nk, nv = nested[k], nested[v]
  44. local np = nest_points[parent]
  45. if not np then
  46. np = {}
  47. nest_points[parent] = np
  48. end
  49. np[k] = v
  50. seen[parent] = 2
  51. if nk then seen[k] = 2 end
  52. if nv then seen[v] = 2 end
  53. end
  54. -- First phase, list the tables and functions which appear more than
  55. -- once in x.
  56. local function mark_multiple_occurences(x)
  57. local tp = type(x)
  58. if tp ~= "table" and tp ~= "function" then
  59. -- No identity (comparison is done by value, not by instance)
  60. return
  61. end
  62. if seen[x] == 1 then
  63. seen[x] = 2
  64. elseif seen[x] ~= 2 then
  65. seen[x] = 1
  66. end
  67. if tp == "table" then
  68. nested[x] = true
  69. for k, v in pairs(x) do
  70. if nested[k] or nested[v] then
  71. mark_nest_point(x, k, v)
  72. else
  73. mark_multiple_occurences(k)
  74. mark_multiple_occurences(v)
  75. end
  76. end
  77. nested[x] = nil
  78. end
  79. end
  80. local dumped = {} -- object->varname set
  81. local local_defs = {} -- Dumped local definitions as source code lines
  82. -- Mutually recursive local functions:
  83. local dump_val, dump_or_ref_val
  84. -- If x occurs multiple times, dump the local variable rather than
  85. -- the value. If it's the first time it's dumped, also dump the
  86. -- content in local_defs.
  87. function dump_or_ref_val(x)
  88. if seen[x] ~= 2 then
  89. return dump_val(x)
  90. end
  91. local var = dumped[x]
  92. if var then -- Already referenced
  93. return var
  94. end
  95. -- First occurence, create and register reference
  96. local val = dump_val(x)
  97. local i = local_index
  98. local_index = local_index + 1
  99. var = "_["..i.."]"
  100. local_defs[#local_defs + 1] = var.." = "..val
  101. dumped[x] = var
  102. return var
  103. end
  104. -- Second phase. Dump the object; subparts occuring multiple times
  105. -- are dumped in local variables which can be referenced multiple
  106. -- times. Care is taken to dump local vars in a sensible order.
  107. function dump_val(x)
  108. local tp = type(x)
  109. if x == nil then return "nil"
  110. elseif tp == "string" then return string.format("%q", x)
  111. elseif tp == "boolean" then return x and "true" or "false"
  112. elseif tp == "function" then
  113. return string.format("loadstring(%q)", string.dump(x))
  114. elseif tp == "number" then
  115. -- Serialize integers with string.format to prevent
  116. -- scientific notation, which doesn't preserve
  117. -- precision and breaks things like node position
  118. -- hashes. Serialize floats normally.
  119. if math.floor(x) == x then
  120. return string.format("%d", x)
  121. else
  122. return tostring(x)
  123. end
  124. elseif tp == "table" then
  125. local vals = {}
  126. local idx_dumped = {}
  127. local np = nest_points[x]
  128. for i, v in ipairs(x) do
  129. if not np or not np[i] then
  130. vals[#vals + 1] = dump_or_ref_val(v)
  131. end
  132. idx_dumped[i] = true
  133. end
  134. for k, v in pairs(x) do
  135. if (not np or not np[k]) and
  136. not idx_dumped[k] then
  137. vals[#vals + 1] = "["..dump_or_ref_val(k).."] = "
  138. ..dump_or_ref_val(v)
  139. end
  140. end
  141. return "{"..table.concat(vals, ", ").."}"
  142. else
  143. error("Can't serialize data of type "..tp)
  144. end
  145. end
  146. local function dump_nest_points()
  147. for parent, vals in pairs(nest_points) do
  148. for k, v in pairs(vals) do
  149. local_defs[#local_defs + 1] = dump_or_ref_val(parent)
  150. .."["..dump_or_ref_val(k).."] = "
  151. ..dump_or_ref_val(v)
  152. end
  153. end
  154. end
  155. mark_multiple_occurences(x)
  156. local top_level = dump_or_ref_val(x)
  157. dump_nest_points()
  158. if next(local_defs) then
  159. return "local _ = {}\n"
  160. ..table.concat(local_defs, "\n")
  161. .."\nreturn "..top_level
  162. else
  163. return "return "..top_level
  164. end
  165. end
  166. -- Deserialization
  167. local env = {
  168. loadstring = loadstring,
  169. }
  170. local safe_env = {
  171. loadstring = function() end,
  172. }
  173. function core.deserialize(str, safe)
  174. if type(str) ~= "string" then
  175. return nil, "Cannot deserialize type '"..type(str)
  176. .."'. Argument must be a string."
  177. end
  178. if str:byte(1) == 0x1B then
  179. return nil, "Bytecode prohibited"
  180. end
  181. local f, err = loadstring(str)
  182. if not f then return nil, err end
  183. setfenv(f, safe and safe_env or env)
  184. local good, data = pcall(f)
  185. if good then
  186. return data
  187. else
  188. return nil, data
  189. end
  190. end
  191. -- Unit tests
  192. local test_in = {cat={sound="nyan", speed=400}, dog={sound="woof"}}
  193. local test_out = core.deserialize(core.serialize(test_in))
  194. assert(test_in.cat.sound == test_out.cat.sound)
  195. assert(test_in.cat.speed == test_out.cat.speed)
  196. assert(test_in.dog.sound == test_out.dog.sound)
  197. test_in = {escape_chars="\n\r\t\v\\\"\'", non_european="θשׁ٩∂"}
  198. test_out = core.deserialize(core.serialize(test_in))
  199. assert(test_in.escape_chars == test_out.escape_chars)
  200. assert(test_in.non_european == test_out.non_european)