~philho/+junk/Lua

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