dump.lua 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. --[[ file format
  2. document :
  3. int32 strtbloffset
  4. int32 n
  5. int32*n index table
  6. table*n
  7. strings
  8. table:
  9. int32 array
  10. int32 dict
  11. int8*(array+dict) type (align 4)
  12. value*array
  13. kvpair*dict
  14. kvpair:
  15. string k
  16. value v
  17. value: (union)
  18. int32 integer
  19. float real
  20. int32 boolean
  21. int32 table index
  22. int32 string offset
  23. type: (enum)
  24. 0 nil
  25. 1 integer
  26. 2 real
  27. 3 boolean
  28. 4 table
  29. 5 string
  30. ]]
  31. local ctd = {}
  32. local math = math
  33. local table = table
  34. local string = string
  35. function ctd.dump(root)
  36. local doc = {
  37. table_n = 0,
  38. table = {},
  39. strings = {},
  40. offset = 0,
  41. }
  42. local function dump_table(t)
  43. local index = doc.table_n + 1
  44. doc.table_n = index
  45. doc.table[index] = false -- place holder
  46. local array_n = 0
  47. local array = {}
  48. local kvs = {}
  49. local types = {}
  50. local function encode(v)
  51. local t = type(v)
  52. if t == "table" then
  53. local index = dump_table(v)
  54. return '\4', string.pack("<i4", index-1)
  55. elseif t == "number" then
  56. if math.tointeger(v) then
  57. return '\1', string.pack("<i4", v)
  58. else
  59. return '\2', string.pack("<f",v)
  60. end
  61. elseif t == "boolean" then
  62. if v then
  63. return '\3', "\0\0\0\1"
  64. else
  65. return '\3', "\0\0\0\0"
  66. end
  67. elseif t == "string" then
  68. local offset = doc.strings[v]
  69. if not offset then
  70. offset = doc.offset
  71. doc.offset = offset + #v + 1
  72. doc.strings[v] = offset
  73. table.insert(doc.strings, v)
  74. end
  75. return '\5', string.pack("<I4", offset)
  76. else
  77. error ("Unsupport value " .. tostring(v))
  78. end
  79. end
  80. for i,v in ipairs(t) do
  81. types[i], array[i] = encode(v)
  82. array_n = i
  83. end
  84. for k,v in pairs(t) do
  85. if type(k) == "string" then
  86. local _, kv = encode(k)
  87. local tv, ev = encode(v)
  88. table.insert(types, tv)
  89. table.insert(kvs, kv .. ev)
  90. else
  91. local ik = math.tointeger(k)
  92. assert(ik and ik > 0 and ik <= array_n)
  93. end
  94. end
  95. -- encode table
  96. local typeset = table.concat(types)
  97. local align = string.rep("\0", (4 - #typeset & 3) & 3)
  98. local tmp = {
  99. string.pack("<i4i4", array_n, #kvs),
  100. typeset,
  101. align,
  102. table.concat(array),
  103. table.concat(kvs),
  104. }
  105. doc.table[index] = table.concat(tmp)
  106. return index
  107. end
  108. dump_table(root)
  109. -- encode document
  110. local index = {}
  111. local offset = 0
  112. for i, v in ipairs(doc.table) do
  113. index[i] = string.pack("<I4", offset)
  114. offset = offset + #v
  115. end
  116. local tmp = {
  117. string.pack("<I4", 4 + 4 + 4 * doc.table_n + offset),
  118. string.pack("<I4", doc.table_n),
  119. table.concat(index),
  120. table.concat(doc.table),
  121. table.concat(doc.strings, "\0"),
  122. "\0",
  123. }
  124. return table.concat(tmp)
  125. end
  126. function ctd.undump(v)
  127. local stringtbl, n = string.unpack("<I4I4",v)
  128. local index = { string.unpack("<" .. string.rep("I4", n), v, 9) }
  129. local header = 4 + 4 + 4 * n + 1
  130. stringtbl = stringtbl + 1
  131. local tblidx = {}
  132. local function decode(n)
  133. local toffset = index[n+1] + header
  134. local array, dict = string.unpack("<I4I4", v, toffset)
  135. local types = { string.unpack(string.rep("B", (array+dict)), v, toffset + 8) }
  136. local offset = ((array + dict + 8 + 3) & ~3) + toffset
  137. local result = {}
  138. local function value(t)
  139. local off = offset
  140. offset = offset + 4
  141. if t == 1 then -- integer
  142. return (string.unpack("<i4", v, off))
  143. elseif t == 2 then -- float
  144. return (string.unpack("<f", v, off))
  145. elseif t == 3 then -- boolean
  146. return string.unpack("<i4", v, off) ~= 0
  147. elseif t == 4 then -- table
  148. local tindex = (string.unpack("<I4", v, off))
  149. return decode(tindex)
  150. elseif t == 5 then -- string
  151. local sindex = string.unpack("<I4", v, off)
  152. return (string.unpack("z", v, stringtbl + sindex))
  153. else
  154. error (string.format("Invalid data at %d (%d)", off, t))
  155. end
  156. end
  157. for i=1,array do
  158. table.insert(result, value(types[i]))
  159. end
  160. for i=1,dict do
  161. local sindex = string.unpack("<I4", v, offset)
  162. offset = offset + 4
  163. local key = string.unpack("z", v, stringtbl + sindex)
  164. result[key] = value(types[array + i])
  165. end
  166. tblidx[result] = n
  167. return result
  168. end
  169. return decode(0), tblidx
  170. end
  171. local function diffmap(last, current)
  172. local lastv, lasti = ctd.undump(last)
  173. local curv, curi = ctd.undump(current)
  174. local map = {} -- new(current index):old(last index)
  175. local function comp(lastr, curr)
  176. local old = lasti[lastr]
  177. local new = curi[curr]
  178. map[new] = old
  179. for k,v in pairs(lastr) do
  180. if type(v) == "table" then
  181. local newv = curr[k]
  182. if type(newv) == "table" then
  183. comp(v, newv)
  184. end
  185. end
  186. end
  187. end
  188. comp(lastv, curv)
  189. return map
  190. end
  191. function ctd.diff(last, current)
  192. local map = diffmap(last, current)
  193. local stringtbl, n = string.unpack("<I4I4",current)
  194. local _, lastn = string.unpack("<I4I4",last)
  195. local existn = 0
  196. for k,v in pairs(map) do
  197. existn = existn + 1
  198. end
  199. local newn = lastn
  200. for i = 0, n-1 do
  201. if not map[i] then
  202. map[i] = newn
  203. newn = newn + 1
  204. end
  205. end
  206. -- remap current
  207. local index = { string.unpack("<" .. string.rep("I4", n), current, 9) }
  208. local header = 4 + 4 + 4 * n + 1
  209. local function remap(n)
  210. local toffset = index[n+1] + header
  211. local array, dict = string.unpack("<I4I4", current, toffset)
  212. local types = { string.unpack(string.rep("B", (array+dict)), current, toffset + 8) }
  213. local hlen = (array + dict + 8 + 3) & ~3
  214. local hastable = false
  215. for _, v in ipairs(types) do
  216. if v == 4 then -- table
  217. hastable = true
  218. break
  219. end
  220. end
  221. if not hastable then
  222. return string.sub(current, toffset, toffset + hlen + (array + dict * 2) * 4 - 1)
  223. end
  224. local offset = hlen + toffset
  225. local pat = "<" .. string.rep("I4", array + dict * 2)
  226. local values = { string.unpack(pat, current, offset) }
  227. for i = 1, array do
  228. if types[i] == 4 then -- table
  229. values[i] = map[values[i]]
  230. end
  231. end
  232. for i = 1, dict do
  233. if types[i + array] == 4 then -- table
  234. values[array + i * 2] = map[values[array + i * 2]]
  235. end
  236. end
  237. return string.sub(current, toffset, toffset + hlen - 1) ..
  238. string.pack(pat, table.unpack(values))
  239. end
  240. -- rebuild
  241. local oldindex = { string.unpack("<" .. string.rep("I4", n), current, 9) }
  242. local index = {}
  243. for i = 1, newn do
  244. index[i] = 0xffffffff
  245. end
  246. for i = 0, #map do
  247. index[map[i]+1] = oldindex[i+1]
  248. end
  249. local tmp = {
  250. string.pack("<I4I4", stringtbl + (newn - n) * 4, newn), -- expand index table
  251. string.pack("<" .. string.rep("I4", newn), table.unpack(index)),
  252. }
  253. for i = 0, n - 1 do
  254. table.insert(tmp, remap(i))
  255. end
  256. table.insert(tmp, string.sub(current, stringtbl+1))
  257. return table.concat(tmp)
  258. end
  259. return ctd