-- http://lua-users.org/lists/lua-l/2009-11/msg00533.html

local szt = {}

local function char(c)
  return ("\\%3d"):format(c:byte())
end

local function szstr(s)
  -- TODO: this logic occurs errors, so use naive implementation by Neovim
  --return ('"%s"'):format(s:gsub("[^ !#-~]", char))
  return vim.inspect(s)
end

local function szfun(f)
  return "loadstring" .. szstr(string.dump(f))
end

local function szany(...)
  return szt[type(...)](...)
end

local function sztbl(t, code, var)
  for k, v in pairs(t) do
    local ks = szany(k, code, var)
    local vs = szany(v, code, var)
    code[#code + 1] = ("%s[%s]=%s"):format(var[t], ks, vs)
  end
  return "{}"
end

local function memo(sz)
  return function(d, code, var)
    if var[d] == nil then
      var[1] = var[1] + 1
      var[d] = ("_[%d]"):format(var[1])
      local index = #code + 1
      code[index] = "" -- reserve place during recursion
      code[index] = ("%s=%s"):format(var[d], sz(d, code, var))
    end
    return var[d]
  end
end

szt["nil"] = tostring
szt["boolean"] = tostring
szt["number"] = tostring
szt["string"] = szstr
szt["function"] = memo(szfun)
szt["table"] = memo(sztbl)

return function(d)
  local code = { "local _ = {}" }
  local value = szany(d, code, { 0 })
  code[#code + 1] = "return " .. value
  if #code == 2 then
    return code[2]
  else
    return table.concat(code, "\n")
  end
end
