local function append(t, a, b, c)
  if a and a ~= "" then
    t[#t + 1] = a
  end
  if b and b ~= "" then
    t[#t + 1] = b
  end
  if c and c ~= "" then
    t[#t + 1] = c
  end
end

local function desFun(index, t)
   local old = _G[index]
   if old then
      if t then
	 for k, v in pairs(t) do
	    old[k] = v
	 end
      end
      return old
   else
      t = t or {}
      _G[index] = t
      return t
   end
end

local desEnv = {}
setmetatable(desEnv, {__newindex = function() end})
local next = next

function deserialize(str)
   -- for lua 5.0 (and 5.1)
   local f = assert(loadstring("return function(_) return " .. str .. " end"))
   setfenv(f, desEnv)
   return f()(desFun)

   -- for lua 5.1
--[[
   local f = assert(loadstring("local _ = ... return " .. str .. " end"))
   setfenv(f, desEnv)
   return f(desFun)
]]
end

local serialize_pass2

local typeSerialize = {}
local function nilSerialize(buf, value)
  append(buf, tostring(value))
end

local function stringSerialize(buf, value)
   append(buf, string.format("%q", value))
end
typeSerialize["string"] = stringSerialize

local function numberSerialize(buf, value)
   append(buf, string.format("%.17g", value))
end
typeSerialize["number"] = numberSerialize

local keywords = {}
keywords["and"] = 1
keywords["or"] = 1
keywords["not"] = 1
keywords["do"] = 1
keywords["end"] = 1
keywords["if"] = 1
keywords["then"] = 1
keywords["while"] = 1
keywords["repeat"] = 1
keywords["local"] = 1
keywords["function"] = 1
keywords["break"] = 1
keywords["return"] = 1
keywords["else"] = 1
keywords["elseif"] = 1
keywords["until"] = 1
keywords["true"] = 1
keywords["false"] = 1
keywords["nil"] = 1
keywords["in"] = 1
keywords["for"] = 1

local function tableKey2(buf, key, vars, multiline, indent)
      append(buf, "[")
      serialize_pass2(buf, key, vars, multiline, indent)
      append(buf, "]")
end

local function tableKey(buf, key, vars, multiline, indent)
   if type(key) ~= "string" then
      return tableKey2(buf, key, vars, multiline, indent)
   end
   if not keywords[key] and string.find(key, "^[%a_][%a%d_]*$") then
      return append(buf, key)
   else
      return tableKey2(buf, key, vars, multiline, indent)
   end
end

local function tableSerialize(buf, value, vars, multiline, indent)
   local var = vars[value]

   if var then
      local touched = vars[var]
      if touched then
          return append(buf, "_(", var, ")")
      else
	     vars[var] = true
      end
   end

   if var then
      append(buf, "_(", var, ",{")
   else
      append(buf, "{")
   end
   local wantComma
   local lastInt = 0
   local nextIndent = indent .. " "
   for k, v in ipairs(value) do
      if v == nil then
        break
      end
      if wantComma then
         append(buf, ",")
      end
      append(buf, multiline, nextIndent)
      serialize_pass2(buf, v, vars, multiline, nextIndent)
      wantComma = true

      lastInt = k
   end
   for k, v in pairs(value) do
      if type(k) == "number" and k == math.floor(k) and 1 <= k and k <= lastInt then
    	 -- ignore this, it's already in the table
      else
         if k ~= nil and v ~= nil then
              if wantComma then
                append(buf, ",")
              end
              append(buf, multiline, nextIndent)
              tableKey(buf, k, vars, multiline, nextIndent)
              append(buf, "=")
              serialize_pass2(buf, v, vars, multiline, nextIndent)
              wantComma = true
          end
       end
   end
   append(buf, multiline, indent)
   if var then
       append(buf, "})")
   else
       append(buf, "}")
   end
end
typeSerialize["table"] = tableSerialize


serialize_pass2 = function(buf, value, vars, multiline, indent)
   local f = typeSerialize[type(value)] or nilSerialize
   f(buf, value, vars, multiline, indent)
end

local function getVars(value, t, curVar)
   t = t or {}
   if type(value) == "table" then
      if t[value] then
	 if t[value] == 0 then
	    t[value] = curVar
	    curVar = curVar + 1
	 end
      else
	 t[value] = 0
	 for k, v in pairs(value) do
	    local t2, curVar2 = getVars(k, t, curVar)
	    curVar = curVar2
	    local t2, curVar2 = getVars(v, t, curVar)
	    curVar = curVar2
	 end
      end
   end
   return t, curVar
end

function serialize(value, multiline, indent)
   multiline = multiline or ""
   indent = indent or ""
   local vars = getVars(value, nil, 1)
   for k, v in pairs(vars) do
      if v == 0 then
	 vars[k] = nil
      end
   end
   local buf = {}
   serialize_pass2(buf, value, vars, multiline, indent)
   return table.concat(buf)
end

function pp(value)
    return serialize(value, "\n")
end
