serialize.lua 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. --- Lua module to serialize values as Lua code.
  2. -- From: https://github.com/appgurueu/modlib/blob/master/luon.lua
  3. -- License: MIT
  4. local next, rawget, pairs, pcall, error, type, setfenv, loadstring
  5. = next, rawget, pairs, pcall, error, type, setfenv, loadstring
  6. local table_concat, string_dump, string_format, string_match, math_huge
  7. = table.concat, string.dump, string.format, string.match, math.huge
  8. -- Recursively counts occurrences of objects (non-primitives including strings) in a table.
  9. local function count_objects(value)
  10. local counts = {}
  11. if value == nil then
  12. -- Early return for nil; tables can't contain nil
  13. return counts
  14. end
  15. local function count_values(val)
  16. local type_ = type(val)
  17. if type_ == "boolean" or type_ == "number" then
  18. return
  19. end
  20. local count = counts[val]
  21. counts[val] = (count or 0) + 1
  22. if type_ == "table" then
  23. if not count then
  24. for k, v in pairs(val) do
  25. count_values(k)
  26. count_values(v)
  27. end
  28. end
  29. elseif type_ ~= "string" and type_ ~= "function" then
  30. error("unsupported type: " .. type_)
  31. end
  32. end
  33. count_values(value)
  34. return counts
  35. end
  36. -- Build a "set" of Lua keywords. These can't be used as short key names.
  37. -- See https://www.lua.org/manual/5.1/manual.html#2.1
  38. local keywords = {}
  39. for _, keyword in pairs({
  40. "and", "break", "do", "else", "elseif",
  41. "end", "false", "for", "function", "if",
  42. "in", "local", "nil", "not", "or",
  43. "repeat", "return", "then", "true", "until", "while",
  44. "goto" -- LuaJIT, Lua 5.2+
  45. }) do
  46. keywords[keyword] = true
  47. end
  48. local function quote(string)
  49. return string_format("%q", string)
  50. end
  51. local function dump_func(func)
  52. return string_format("loadstring(%q)", string_dump(func))
  53. end
  54. -- Serializes Lua nil, booleans, numbers, strings, tables and even functions
  55. -- Tables are referenced by reference, strings are referenced by value. Supports circular tables.
  56. local function serialize(value, write)
  57. local reference, refnum = "1", 1
  58. -- [object] = reference
  59. local references = {}
  60. -- Circular tables that must be filled using `table[key] = value` statements
  61. local to_fill = {}
  62. for object, count in pairs(count_objects(value)) do
  63. local type_ = type(object)
  64. -- Object must appear more than once. If it is a string, the reference has to be shorter than the string.
  65. if count >= 2 and (type_ ~= "string" or #reference + 5 < #object) then
  66. if refnum == 1 then
  67. write"local _={};" -- initialize reference table
  68. end
  69. write"_["
  70. write(reference)
  71. write("]=")
  72. if type_ == "table" then
  73. write("{}")
  74. elseif type_ == "function" then
  75. write(dump_func(object))
  76. elseif type_ == "string" then
  77. write(quote(object))
  78. end
  79. write(";")
  80. references[object] = reference
  81. if type_ == "table" then
  82. to_fill[object] = reference
  83. end
  84. refnum = refnum + 1
  85. reference = ("%d"):format(refnum)
  86. end
  87. end
  88. -- Used to decide whether we should do "key=..."
  89. local function use_short_key(key)
  90. return not references[key] and type(key) == "string" and (not keywords[key]) and string_match(key, "^[%a_][%a%d_]*$")
  91. end
  92. local function dump(value)
  93. -- Primitive types
  94. if value == nil then
  95. return write("nil")
  96. end
  97. if value == true then
  98. return write("true")
  99. end
  100. if value == false then
  101. return write("false")
  102. end
  103. local type_ = type(value)
  104. if type_ == "number" then
  105. if value ~= value then -- nan
  106. return write"0/0"
  107. elseif value == math_huge then
  108. return write"1/0"
  109. elseif value == -math_huge then
  110. return write"-1/0"
  111. else
  112. return write(string_format("%.17g", value))
  113. end
  114. end
  115. -- Reference types: table, function and string
  116. local ref = references[value]
  117. if ref then
  118. write"_["
  119. write(ref)
  120. return write"]"
  121. end
  122. if type_ == "string" then
  123. return write(quote(value))
  124. end
  125. if type_ == "function" then
  126. return write(dump_func(value))
  127. end
  128. if type_ == "table" then
  129. write("{")
  130. -- First write list keys:
  131. -- Don't use the table length #value here as it may horribly fail
  132. -- for tables which use large integers as keys in the hash part;
  133. -- stop at the first "hole" (nil value) instead
  134. local len = 0
  135. local first = true -- whether this is the first entry, which may not have a leading comma
  136. while true do
  137. local v = rawget(value, len + 1) -- use rawget to avoid metatables like the vector metatable
  138. if v == nil then break end
  139. if first then first = false else write(",") end
  140. dump(v)
  141. len = len + 1
  142. end
  143. -- Now write map keys ([key] = value)
  144. for k, v in next, value do
  145. -- We have written all non-float keys in [1, len] already
  146. if type(k) ~= "number" or k % 1 ~= 0 or k < 1 or k > len then
  147. if first then first = false else write(",") end
  148. if use_short_key(k) then
  149. write(k)
  150. else
  151. write("[")
  152. dump(k)
  153. write("]")
  154. end
  155. write("=")
  156. dump(v)
  157. end
  158. end
  159. write("}")
  160. return
  161. end
  162. end
  163. -- Write the statements to fill circular tables
  164. for table, ref in pairs(to_fill) do
  165. for k, v in pairs(table) do
  166. write("_[")
  167. write(ref)
  168. write("]")
  169. if use_short_key(k) then
  170. write(".")
  171. write(k)
  172. else
  173. write("[")
  174. dump(k)
  175. write("]")
  176. end
  177. write("=")
  178. dump(v)
  179. write(";")
  180. end
  181. end
  182. write("return ")
  183. dump(value)
  184. end
  185. function core.serialize(value)
  186. local rope = {}
  187. serialize(value, function(text)
  188. -- Faster than table.insert(rope, text) on PUC Lua 5.1
  189. rope[#rope + 1] = text
  190. end)
  191. return table_concat(rope)
  192. end
  193. local function dummy_func() end
  194. function core.deserialize(str, safe)
  195. -- Backwards compatibility
  196. if str == nil then
  197. core.log("deprecated", "core.deserialize called with nil (expected string).")
  198. return nil, "Invalid type: Expected a string, got nil"
  199. end
  200. local t = type(str)
  201. if t ~= "string" then
  202. error(("core.deserialize called with %s (expected string)."):format(t))
  203. end
  204. local func, err = loadstring(str)
  205. if not func then return nil, err end
  206. -- math.huge was serialized to inf and NaNs to nan by Lua in engine version 5.6, so we have to support this here
  207. local env = {inf = math_huge, nan = 0/0}
  208. if safe then
  209. env.loadstring = dummy_func
  210. else
  211. env.loadstring = function(str, ...)
  212. local func, err = loadstring(str, ...)
  213. if func then
  214. setfenv(func, env)
  215. return func
  216. end
  217. return nil, err
  218. end
  219. end
  220. setfenv(func, env)
  221. local success, value_or_err = pcall(func)
  222. if success then
  223. return value_or_err
  224. end
  225. return nil, value_or_err
  226. end