1---
2-- DataDumper.lua
3-- Copyright (c) 2007 Olivetti-Engineering SA
4--
5-- Permission is hereby granted, free of charge, to any person
6-- obtaining a copy of this software and associated documentation
7-- files (the "Software"), to deal in the Software without
8-- restriction, including without limitation the rights to use,
9-- copy, modify, merge, publish, distribute, sublicense, and/or sell
10-- copies of the Software, and to permit persons to whom the
11-- Software is furnished to do so, subject to the following
12-- conditions:
13--
14-- The above copyright notice and this permission notice shall be
15-- included in all copies or substantial portions of the Software.
16--
17-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
19-- OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20-- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
21-- HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22-- WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23-- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24-- OTHER DEALINGS IN THE SOFTWARE.
25
26local dumplua_closure = [[
27local closures = {}
28local function closure(t)
29  closures[#closures+1] = t
30  t[1] = assert(loadstring(t[1]))
31  return t[1]
32end
33
34for _,t in pairs(closures) do
35  for i = 2,#t do
36    debug.setupvalue(t[1], i-1, t[i])
37  end
38end
39]]
40
41local lua_reserved_keywords = {
42  'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for',
43  'function', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat',
44  'return', 'then', 'true', 'until', 'while' }
45
46local function keys(t)
47  local res = {}
48  local oktypes = { stringstring = true, numbernumber = true }
49  local function cmpfct(a,b)
50    if oktypes[type(a)..type(b)] then
51      return a < b
52    else
53      return type(a) < type(b)
54    end
55  end
56  for k in pairs(t) do
57    res[#res+1] = k
58  end
59  table.sort(res, cmpfct)
60  return res
61end
62
63local c_functions = {}
64for _,lib in pairs{'_G', 'string', 'table', 'math',
65    'io', 'os', 'coroutine', 'package', 'debug'} do
66  local t = _G[lib] or {}
67  lib = lib .. "."
68  if lib == "_G." then lib = "" end
69  for k,v in pairs(t) do
70    if type(v) == 'function' and not pcall(string.dump, v) then
71      c_functions[v] = lib..k
72    end
73  end
74end
75
76function DataDumper(value, varname, fastmode, ident)
77  local defined, dumplua = {}
78  -- Local variables for speed optimization
79  local string_format, type, string_dump, string_rep =
80        string.format, type, string.dump, string.rep
81  local tostring, pairs, table_concat =
82        tostring, pairs, table.concat
83  local keycache, strvalcache, out, closure_cnt = {}, {}, {}, 0
84  setmetatable(strvalcache, {__index = function(t,value)
85    local res = string_format('%q', value)
86    t[value] = res
87    return res
88  end})
89  local fcts = {
90    string = function(value) return strvalcache[value] end,
91    number = function(value) return value end,
92    boolean = function(value) return tostring(value) end,
93    ['nil'] = function(value) return 'nil' end,
94    ['function'] = function(value)
95      return string_format("loadstring(%q)", string_dump(value))
96    end,
97    userdata = function() error("Cannot dump userdata") end,
98    thread = function() error("Cannot dump threads") end,
99  }
100  local function test_defined(value, path)
101    if defined[value] then
102      if path:match("^getmetatable.*%)$") then
103        out[#out+1] = string_format("s%s, %s)\n", path:sub(2,-2), defined[value])
104      else
105        out[#out+1] = path .. " = " .. defined[value] .. "\n"
106      end
107      return true
108    end
109    defined[value] = path
110  end
111  local function make_key(t, key)
112    local s
113    if type(key) == 'string' and key:match('^[_%a][_%w]*$') then
114      s = key .. "="
115    else
116      s = "[" .. dumplua(key, 0) .. "]="
117    end
118    t[key] = s
119    return s
120  end
121  for _,k in ipairs(lua_reserved_keywords) do
122    keycache[k] = '["'..k..'"] = '
123  end
124  if fastmode then
125    fcts.table = function (value)
126      -- Table value
127      local numidx = 1
128      out[#out+1] = "{"
129      for key,val in pairs(value) do
130        if key == numidx then
131          numidx = numidx + 1
132        else
133          out[#out+1] = keycache[key]
134        end
135        local str = dumplua(val)
136        out[#out+1] = str..","
137      end
138      if string.sub(out[#out], -1) == "," then
139        out[#out] = string.sub(out[#out], 1, -2);
140      end
141      out[#out+1] = "}"
142      return ""
143    end
144  else
145    fcts.table = function (value, ident, path)
146      if test_defined(value, path) then return "nil" end
147      -- Table value
148      local sep, str, numidx, totallen = " ", {}, 1, 0
149      local meta, metastr = (debug or getfenv()).getmetatable(value)
150      if meta then
151        ident = ident + 1
152        metastr = dumplua(meta, ident, "getmetatable("..path..")")
153        totallen = totallen + #metastr + 16
154      end
155      for _,key in pairs(keys(value)) do
156        local val = value[key]
157        local s = ""
158        local subpath = path
159        if key == numidx then
160          subpath = subpath .. "[" .. numidx .. "]"
161          numidx = numidx + 1
162        else
163          s = keycache[key]
164          if not s:match "^%[" then subpath = subpath .. "." end
165          subpath = subpath .. s:gsub("%s*=%s*$","")
166        end
167        s = s .. dumplua(val, ident+1, subpath)
168        str[#str+1] = s
169        totallen = totallen + #s + 2
170      end
171      if totallen > 80 then
172        sep = "\n" .. string_rep("  ", ident+1)
173      end
174      str = "{"..sep..table_concat(str, ","..sep).." "..sep:sub(1,-3).."}"
175      if meta then
176        sep = sep:sub(1,-3)
177        return "setmetatable("..sep..str..","..sep..metastr..sep:sub(1,-3)..")"
178      end
179      return str
180    end
181    fcts['function'] = function (value, ident, path)
182      if test_defined(value, path) then return "nil" end
183      if c_functions[value] then
184        return c_functions[value]
185      elseif debug == nil or debug.getupvalue(value, 1) == nil then
186        return string_format("loadstring(%q)", string_dump(value))
187      end
188      closure_cnt = closure_cnt + 1
189      local res = {string.dump(value)}
190      for i = 1,math.huge do
191        local name, v = debug.getupvalue(value,i)
192        if name == nil then break end
193        res[i+1] = v
194      end
195      return "closure " .. dumplua(res, ident, "closures["..closure_cnt.."]")
196    end
197  end
198  function dumplua(value, ident, path)
199    return fcts[type(value)](value, ident, path)
200  end
201  if varname == nil then
202    varname = "return "
203  elseif varname:match("^[%a_][%w_]*$") then
204    varname = varname .. " = "
205  end
206  if fastmode then
207    setmetatable(keycache, {__index = make_key })
208    out[1] = varname
209    table.insert(out,dumplua(value, 0))
210    return table.concat(out)
211  else
212    setmetatable(keycache, {__index = make_key })
213    local items = {}
214    for i=1,10 do items[i] = '' end
215    items[3] = dumplua(value, ident or 0, "t")
216    if closure_cnt > 0 then
217      items[1], items[6] = dumplua_closure:match("(.*\n)\n(.*)")
218      out[#out+1] = ""
219    end
220    if #out > 0 then
221      items[2], items[4] = "local t = ", "\n"
222      items[5] = table.concat(out)
223      items[7] = varname .. "t"
224    else
225      items[2] = varname
226    end
227    return table.concat(items)
228  end
229end
230