You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

122 lines
3.5 KiB

  1. local pairs, ipairs, tostring, type, concat, dump, floor, format = pairs, ipairs, tostring, type, table.concat, string.dump, math.floor, string.format
  2. local function getchr(c)
  3. return "\\" .. c:byte()
  4. end
  5. local function make_safe(text)
  6. return ("%q"):format(text):gsub('\n', 'n'):gsub("[\128-\255]", getchr)
  7. end
  8. local oddvals = {[tostring(1/0)] = '1/0', [tostring(-1/0)] = '-1/0', [tostring(-(0/0))] = '-(0/0)', [tostring(0/0)] = '0/0'}
  9. local function write(t, memo, rev_memo)
  10. local ty = type(t)
  11. if ty == 'number' then
  12. t = format("%.17g", t)
  13. return oddvals[t] or t
  14. elseif ty == 'boolean' or ty == 'nil' then
  15. return tostring(t)
  16. elseif ty == 'string' then
  17. return make_safe(t)
  18. elseif ty == 'table' or ty == 'function' then
  19. if not memo[t] then
  20. local index = #rev_memo + 1
  21. memo[t] = index
  22. rev_memo[index] = t
  23. end
  24. return '_[' .. memo[t] .. ']'
  25. else
  26. error("Trying to serialize unsupported type " .. ty)
  27. end
  28. end
  29. local kw = {['and'] = true, ['break'] = true, ['do'] = true, ['else'] = true,
  30. ['elseif'] = true, ['end'] = true, ['false'] = true, ['for'] = true,
  31. ['function'] = true, ['goto'] = true, ['if'] = true, ['in'] = true,
  32. ['local'] = true, ['nil'] = true, ['not'] = true, ['or'] = true,
  33. ['repeat'] = true, ['return'] = true, ['then'] = true, ['true'] = true,
  34. ['until'] = true, ['while'] = true}
  35. local function write_key_value_pair(k, v, memo, rev_memo, name)
  36. if type(k) == 'string' and k:match '^[_%a][_%w]*$' and not kw[k] then
  37. return (name and name .. '.' or '') .. k ..'=' .. write(v, memo, rev_memo)
  38. else
  39. return (name or '') .. '[' .. write(k, memo, rev_memo) .. ']=' .. write(v, memo, rev_memo)
  40. end
  41. end
  42. -- fun fact: this function is not perfect
  43. -- it has a few false positives sometimes
  44. -- but no false negatives, so that's good
  45. local function is_cyclic(memo, sub, super)
  46. local m = memo[sub]
  47. local p = memo[super]
  48. return m and p and m < p
  49. end
  50. local function write_table_ex(t, memo, rev_memo, srefs, name)
  51. if type(t) == 'function' then
  52. return '_[' .. name .. ']=loadstring' .. make_safe(dump(t))
  53. end
  54. local m = {}
  55. local mi = 1
  56. for i = 1, #t do -- don't use ipairs here, we need the gaps
  57. local v = t[i]
  58. if v == t or is_cyclic(memo, v, t) then
  59. srefs[#srefs + 1] = {name, i, v}
  60. m[mi] = 'nil'
  61. mi = mi + 1
  62. else
  63. m[mi] = write(v, memo, rev_memo)
  64. mi = mi + 1
  65. end
  66. end
  67. for k,v in pairs(t) do
  68. if type(k) ~= 'number' or floor(k) ~= k or k < 1 or k > #t then
  69. if v == t or k == t or is_cyclic(memo, v, t) or is_cyclic(memo, k, t) then
  70. srefs[#srefs + 1] = {name, k, v}
  71. else
  72. m[mi] = write_key_value_pair(k, v, memo, rev_memo)
  73. mi = mi + 1
  74. end
  75. end
  76. end
  77. return '_[' .. name .. ']={' .. concat(m, ',') .. '}'
  78. end
  79. return function(t)
  80. local memo = {[t] = 0}
  81. local rev_memo = {[0] = t}
  82. local srefs = {}
  83. local result = {}
  84. -- phase 1: recursively descend the table structure
  85. local n = 0
  86. while rev_memo[n] do
  87. result[n + 1] = write_table_ex(rev_memo[n], memo, rev_memo, srefs, n)
  88. n = n + 1
  89. end
  90. -- phase 2: reverse order
  91. for i = 1, n*.5 do
  92. local j = n - i + 1
  93. result[i], result[j] = result[j], result[i]
  94. end
  95. -- phase 3: add all the tricky cyclic stuff
  96. for i, v in ipairs(srefs) do
  97. n = n + 1
  98. result[n] = write_key_value_pair(v[2], v[3], memo, rev_memo, '_[' .. v[1] .. ']')
  99. end
  100. -- phase 4: add something about returning the main table
  101. if result[n]:sub(1, 5) == '_[0]=' then
  102. result[n] = 'return ' .. result[n]:sub(6)
  103. else
  104. result[n + 1] = 'return _[0]'
  105. end
  106. -- phase 5: just concatenate everything
  107. result = concat(result, '\n')
  108. return n > 1 and 'local _={}\n' .. result or result
  109. end