-- Light Unix I/O for Lua
-- Copyright 2012 Rob Kendrick <rjek+luxio@rjek.com>
--
-- Distributed under the same terms as Lua itself (MIT).
--
-- Simple serialiser that tries to make things small, for use for IPC.

local type = type
local format = string.format
local tostring = tostring
local error = error
local concat = table.concat
local dump = string.dump

local function a(t, x)
   t[#t+1] = x
end

local keywords = {
   ["and"] = true,
   ["break"] = true,
   ["do"] = true,
   ["else"] = true,
   ["elseif"] = true,
   ["end"] = true,
   ["false"] = true,
   ["for"] = true,
   ["function"] = 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 serialise(x)
   local t = type(x)
   if t == "string" then
      return format("%q", x)
   elseif t == "function" then
      return format("loadstring(%q)", dump(x))
   elseif t == "userdata" or t == "lightuserdata" then
      error "Cannot serialise userdata or lightuserdata"
   elseif t ~= "table" then
      return tostring(x)
   end

   local r = { "{" }

   -- first emit any sequence
   local k = 1
   repeat
      local v = x[k]
      if v ~= nil then
	 a(r, serialise(v))
	 a(r, ",")
      end

      k = k + 1
   until v == nil

   -- emit other numerical keys
   for i, j in pairs(x) do
      if type(i) == "number" and i >= k then
	 if r[#r] ~= "," then a(r, ",") end
	 a(r, format("[%d]=%s", i, serialise(j)))
	 a(r, ",")
      end
   end

   -- emit non-numeric keys
   for i, j in pairs(x) do
      local key
      local ti = type(i)
      if ti ~= "number" then
	 if ti == "string" then
	    key = format("%q", i)
	    if not keywords[i] and i == format("%q", i):sub(2, -2) then
	       key = i
	    else
	       key = "[" .. key .. "]"
	    end
	 elseif ti == "function" then
	    key = "[loadstring(" .. format("%q", dump(i)) .. ")]"
	 elseif ti == "table" then
	    key = "[" .. serialise(i) .. "]"
	 else
	    error "unhandled key type"
	 end
	       
	 a(r, format("%s=%s", key, serialise(j)))
	 a(r, ",")
      end
   end

   if r[#r] == "," then r[#r] = nil end
   a(r, "}")

   return concat(r)
end

return { serialise = serialise }

