1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
|
#!Lua5.1.exe
--[[
String dump of any Lua object.
File/Project history:
1.03 -- 2010/03/18 (PL) -- I dumped { 'a', 'b', 'c' } to { [1] = 'a', [2] = 'b', [3] = 'c' }
which wasn't very nice. Now if I find a key of 1, I walk the array part of the table
first then I dump the other keys.
1.02 -- 2007/03/15 (PL) -- Special, compact handling of empty tables.
Also corrected a bug in quotes around tostring result ("foo')
1.01 -- 2006/11/07 (PL) -- Exclude keywords from "naked" string keys.
1.00 -- 2006/07/13 (PL) -- Creation of this version.
0.01 -- 2003/??/?? (PL) -- First version?
]]
--[[
Author: Philippe Lhoste <PhiLho(a)GMX.net> http://Phi.Lho.free.fr
Copyright notice: For details, see the following file:
http://Phi.Lho.free.fr/softwares/PhiLhoSoft/PhiLhoSoftLicense.txt
This program is distributed under the zlib/libpng license.
Copyright (c) 2006-2010 Philippe Lhoste / PhiLhoSoft
]]
--[[
Returns a textual representation of "any" Lua object,
including tables (and nested tables).
Functions are not decompiled (of course).
It sticks strictly to the real table constructor syntax,
so the result can be reparsed by Lua to recreate the object,
at least if there are NO complex objects (functions, userdata...).
It handles references correctly, except for cycles (a ref. b which ref. a).
Example of use:
For an indented dump:
print(DumpObject(tableName))
For an inline dump:
print(DumpObject(t, '', ' '))
]]
function DumpObject(objectToDump, indentUnit, newline)
local function KeywordList(list)
local kl = {}
for i, v in ipairs(list) do
kl[v] = i
end
return kl
end
-- We can write a = 1 in table constructor, but not for = 1 because for is a keyword.
-- So we have to list all keywords to make a special rule for them.
local keywords = KeywordList
{
'and', 'break', 'do', 'else', 'elseif', 'end', 'false',
'for', 'function', 'if', 'in', 'local', 'nil', 'not',
'or', 'repeat', 'return', 'then', 'true', 'until', 'while'
}
-- The string used to indent
-- Give an empty string for no indentation
if indentUnit == nil then
indentUnit = " " -- 2 spaces
end
-- Give an empty string or space to put all the dump in one line
if newline == nil then
newline = "\n"
end
-- Current indent level/string
local indent = ""
-- Table of visited tables, to handle cross-references
local visited = {}
-- Number of the currently visited table
local tableIndex = 0
-- We put the dump string fragments here, to concatenate them later
local dump = {}
-- Half backed tentative to handle cyclic references...
local dumpAfter, daIndex = {}, 0
-- In the following functions, the bOnLeft parameter
-- is true if we are on the left side of an assignment (=), false on the right side
-- If on left of assignment, add brackets around the string,
-- otherwise, leave it as is.
local function GetLeftOrRight(str, bOnLeft)
if bOnLeft then
-- Index syntax
return '[' .. str .. ']'
else
-- Plain object
return str
end
end
-- Add the right quotes around the string, depending on what we
-- find in it, and if we are on left or on right of assignment.
local function QuoteString(str, bOnLeft)
if bOnLeft and string.find(str, '[^%w_]') == nil and not keywords[str] then
-- String with variable syntax on the left side of '=': can be left unquoted
return str
end
local qs
if string.find(str, '[\\\r\n"]') == nil then
-- No special chars, can just use double quotes
qs = '"' .. str .. '"'
elseif string.find(str, "[\\\r\n']") == nil then
-- No special chars, can just use single quotes
qs = "'" .. str .. "'"
else
-- Backslash or newline or double quotes in string, must use literal string
if bOnLeft then
-- We have to write [ [[...]] ] (with spaces) to avoid ambiguity
qs = ' [[' .. str .. ']] '
else
qs = '[[' .. str .. ']]'
end
end
return GetLeftOrRight(qs, bOnLeft)
end
-- Return a correctly quoted (or not) string representation
-- of the given object.
-- Must not be called with a table, which is processed separately.
local function DumpItem(object, bOnLeft)
local di = "UH?"
local to = type(object)
if to == "string" then
di = QuoteString(object, bOnLeft)
elseif to == "number" or to == "boolean" then
di = GetLeftOrRight(tostring(object), bOnLeft)
elseif to == "nil" then -- If the first object is nil itself...
return 'nil' -- Cannot happen on left, can it?
elseif to == "table" then
di = "TABLE!" -- Should be tested before...
else
-- function, userdata, thread, ...
-- Put the tostring result between double quotes
-- It won't be restored correctly, but it won't give a syntax error...
di = GetLeftOrRight('"' .. tostring(object) .. '"', bOnLeft)
end
return di
end
local function AddKeyValue(td, tdi, left, right)
if tdi ~= 1 then
-- Add field separator
tdi = tdi + 1
td[tdi] = "," .. newline
end
-- Add this field
tdi = tdi + 1
if left == nil then
-- Successive numerical index, don't put a key
td[tdi] = indent .. right
else
td[tdi] = indent .. left .. " = " .. right
end
return tdi
end
-- Walk the object (if it is a table) and build
-- a string representation of the found objects.
-- bForce bypasses the reference mechanism,
-- forcing to dump the table (for referenced tables).
local function DumpRecursively(object, bOnLeft, bForce)
if type(object) == "table" then
if visited[object].visits > 1 and not bForce then
-- Cross-reference
if object == objectToDump then
-- Don't reference the root table! (trying to handle cycles)
return nil
else
-- We have a referenced table, return its variable name
return GetLeftOrRight("T" .. visited[object].idx, bOnLeft)
end
end
local openBrace, closeBrace
if bOnLeft then
-- Using a raw table constructor as key!
-- Only accessible by iteration...
if next(object) == nil then -- Empty table
-- Use a lighter traditional representation
openBrace = "[ {"; closeBrace = "} ]"
else
openBrace = "[ {" .. newline
closeBrace = newline .. indent .. "} ]"
end
else
if next(object) == nil then -- Empty table
-- Use a lighter traditional representation
openBrace = "{"; closeBrace = "}"
else
openBrace = newline .. indent .. "{" .. newline
closeBrace = newline .. indent .. "}"
end
end
-- Start of table dump
local td = { openBrace }
local tdi = 1
-- Indent more
indent = indent .. indentUnit
--Handle the array part, if any
local lastNumericalIndex = 0
if object[1] ~= nil then
-- This table as an array section, perhaps with increasing numerical indices
for i, v in ipairs(object) do
lastNumericalIndex = i
-- Add value without key
local right = DumpRecursively(v, false, false)
tdi = AddKeyValue(td, tdi, nil, string.gsub(right, '^\n%s*', '')) -- Skip initial newline if any
--~ tdi = AddKeyValue(td, tdi, nil, right)
end
end
-- Walk the table
local k, v = nil, nil
repeat
k, v = next(object, k)
if k ~= nil then
local bIsNumberKey = type(k) == "number"
if not bIsNumberKey or bIsNumberKey and k > lastNumericalIndex then
local left = DumpRecursively(k, true, false)
local right = DumpRecursively(v, false, false)
if right == nil then
-- Tentative to handle cycles
daIndex = daIndex + 1
-- Incorrect, it must not be 'left' but the full path!
dumpAfter[daIndex] = left .. " = T" .. visited[v].idx
else
tdi = AddKeyValue(td, tdi, left, right)
end
end
end
until k == nil
-- Deindent
indent = string.sub(indent, string.len(indentUnit) + 1)
-- End of table
tdi = tdi + 1
td[tdi] = closeBrace
return table.concat(td)
else -- Not a table
-- Return a string equivalence
return DumpItem(object, bOnLeft)
end
end
-- List cross references of the given object (must be a table).
local function ListCrossReferences(object)
if type(object) == "table" then
if visited[object] ~= nil then
-- Already seen, cross-reference
visited[object].visits = visited[object].visits + 1
return
end
-- First seen, number it
tableIndex = tableIndex + 1
visited[object] = { visits = 1, idx = tableIndex }
-- Walk this table and see if it has other tables inside
local k, v = nil, nil
repeat
k, v = next(object, k)
if k ~= nil then
ListCrossReferences(k)
ListCrossReferences(v)
end
until k == nil
end
end
-- Dump the tables referenced more than once,
-- so they make easy to reference variables.
local function DumpCrossReferences()
local k, v = nil, nil
repeat
k, v = next(visited, k)
if k ~= nil and v.visits > 1 and k ~= objectToDump then
dump[v.idx] = "\nlocal T" .. v.idx .. " = " .. DumpRecursively(k, false, true)
end
until k == nil
end
-- List all cross-references of the (possibly) table
ListCrossReferences(objectToDump)
-- Dump the tables used in cross-references
DumpCrossReferences()
-- Add the main table
dump[tableIndex + 1] = "\nlocal T = " .. DumpRecursively(objectToDump, false, true)
-- Fill in the gaps, so concat could work
for i = 1, tableIndex do
if dump[i] == nil then
dump[i] = ''
end
end
return table.concat(dump) .. "\n" .. table.concat(dumpAfter)
end
--[[
Write a dump of the object (likely to be a table...)
to the given file.
It can be used with:
table = dofile("DumpedTable.lua")
]]
function DumpObjectToFile(object, fileName, indentUnit, newline)
local fh = io.open(fileName, "w")
fh:write("do\n" .. DumpObject(object, indentUnit, newline) .. "\nreturn T\n\nend\n")
fh:close()
end
--[[
For a quick in-line dump of a simple table (no cross-references)
without the initialization stuff.
]]
function FormatSimpleTable(t)
return (string.gsub(string.gsub(DumpObject(t, '', ' '), "^\nlocal T = ", ""), "\n$", ""))
end
|