local pairs, ipairs, tostring, type, concat, dump, floor = pairs, ipairs, tostring, type, table.concat, string.dump, math.floor local function getchr(c) return "\\" .. c:byte() end local function make_safe(text) return ("%q"):format(text):gsub('\n', 'n'):gsub("[\128-\255]", getchr) end local oddvals = {inf = '1/0', ['-inf'] = '-1/0', [tostring(0/0)] = '0/0'} local function write(t, memo, rev_memo) local ty = type(t) if ty == 'number' or ty == 'boolean' or ty == 'nil' then t = tostring(t) return oddvals[t] or t elseif ty == 'string' then return make_safe(t) elseif ty == 'table' or ty == 'function' then if not memo[t] then local index = #rev_memo + 1 memo[t] = index rev_memo[index] = t end return '_' .. memo[t] else error("Trying to serialize unsupported type " .. ty) end end local kw = {['and'] = true, ['break'] = true, ['do'] = true, ['else'] = true, ['elseif'] = true, ['end'] = true, ['false'] = true, ['for'] = true, ['function'] = true, ['goto'] = true, ['if'] = true, ['in'] = true, ['local'] = true, ['nil'] = true, ['not'] = true, ['or'] = true, ['repeat'] = true, ['return'] = true, ['then'] = true, ['true'] = true, ['until'] = true, ['while'] = true} local function write_key_value_pair(k, v, memo, rev_memo, name) if type(k) == 'string' and k:match '^[_%a][_%w]*$' and not kw[k] then return (name and name .. '.' or '') .. k ..' = ' .. write(v, memo, rev_memo) else return (name or '') .. '[' .. write(k, memo, rev_memo) .. '] = ' .. write(v, memo, rev_memo) end end -- fun fact: this function is not perfect -- it has a few false positives sometimes -- but no false negatives, so that's good local function is_cyclic(memo, sub, super) local m = memo[sub] local p = memo[super] return m and p and m < p end local function write_table_ex(t, memo, rev_memo, srefs, name) if type(t) == 'function' then return 'local _' .. name .. ' = loadstring ' .. make_safe(dump(t)) end local m = {'local _', name, ' = {'} local mi = 3 for i = 1, #t do -- don't use ipairs here, we need the gaps local v = t[i] if v == t or is_cyclic(memo, v, t) then srefs[#srefs + 1] = {name, i, v} m[mi + 1] = 'nil, ' mi = mi + 1 else m[mi + 1] = write(v, memo, rev_memo) m[mi + 2] = ', ' mi = mi + 2 end end for k,v in pairs(t) do if type(k) ~= 'number' or floor(k) ~= k or k < 1 or k > #t then if v == t or k == t or is_cyclic(memo, v, t) or is_cyclic(memo, k, t) then srefs[#srefs + 1] = {name, k, v} else m[mi + 1] = write_key_value_pair(k, v, memo, rev_memo) m[mi + 2] = ', ' mi = mi + 2 end end end m[mi > 3 and mi or mi + 1] = '}' return concat(m) end return function(t) local memo = {[t] = 0} local rev_memo = {[0] = t} local srefs = {} local result = {} -- phase 1: recursively descend the table structure local n = 0 while rev_memo[n] do result[n + 1] = write_table_ex(rev_memo[n], memo, rev_memo, srefs, n) n = n + 1 end -- phase 2: reverse order for i = 1, n*.5 do local j = n - i + 1 result[i], result[j] = result[j], result[i] end -- phase 3: add all the tricky cyclic stuff for i, v in ipairs(srefs) do n = n + 1 result[n] = write_key_value_pair(v[2], v[3], memo, rev_memo, '_' .. v[1]) end -- phase 4: add something about returning the main table if result[n]:sub(1, 8) == 'local _0' then result[n] = 'return' .. result[n]:sub(11) else result[n + 1] = 'return _0' end -- phase 5: just concatenate everything return concat(result, '\n') end