lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


Silly me, I forgot to include the files...
I have put them in the Wiki, anyway.

--
Philippe Lhoste
--  (near) Paris -- France
--  http://Phi.Lho.free.fr
--  --  --  --  --  --  --  --  --  --  --  --  --  --
#!Lua-5.0.exe
-- String dump of any Lua object.
--
-- v. 1.0 -- 2006/07/13 -- Creation of this version.
-- v. 0.1 -- 2003/??/?? -- First version?

--[[
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)
	-- The string used to indent
	-- Give an empty string for no indentation
	if indentUnit == nil then
		indentUnit = "  " -- 2 spaces
	end
	-- Give an empty string 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 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 [ [[...]] ] 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't 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

	-- 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...
				openBrace = "[ {" .. newline
				closeBrace = newline .. indent .. "} ]"
			else
				openBrace = newline .. indent .. "{" .. newline
				closeBrace = newline .. indent .. "}"
			end
			-- Start of table dump
			local td = { openBrace }
			local tdi = 1
			-- Indent more
			indent = indent .. indentUnit
			-- Walk the table
			local k, v = nil, nil
			repeat
				k, v = next(object, k)
				if k ~= nil then
					if tdi ~= 1 then
						-- Add field separator
						tdi = tdi + 1
						td[tdi] = "," .. newline
					end
					-- Walk the key and the value
					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
						-- Add this field
						tdi = tdi + 1
						td[tdi] = indent .. left .. " = " .. right
					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)
	local fh = io.open(fileName, "w")
	fh:write("do\n" .. DumpObject(object) .. "\nreturn T\n\nend\n")
	fh:close()
end
dofile[[../Lua/DumpObject.lua]]

function Foo(a, b)
	return (a + b) / a * b
end
a = { _ = true, __ = false }
b = { 'a', 'b' }

t =
{
	a =
	{
		x = 5.0,
		y = 104.3,
	},
	b =
	{
		-- Function as key
		[Foo] = 'Function!',
		-- Numerical index
		[5] = 42,
		-- Simple string index ('simple' means having the syntax of variables)
		ls = [[
Multiline
Value
]],
		-- Complex string index
		['Regular Expression'] = [[(?:\d{1,3}\.){3}\d{1,3}]],
		-- A simple array
		sar =
		{
			"one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten",
		},
		-- An array of tables
		tar =
		{
			{
				kw1 = 'and',
				kw2 = 'or',
				kw3 = 'not',
			},
			{
				op1 = '*',
				op2 = '+',
				op3 = '-',
			},
		},
	},
	others =
	{
		-- Function storage
		functions =
		{
			f = function () return math.random(100) end,
			ff = Foo,
		},
		-- Table as key
		tables =
		{
			[a] = '',
			[b] = "",
			-- This one is different of the previous one because the created
			-- table is different of b. You cannot reference it directly,
			-- it is reachable only by iterating on all keys/values...
			[ { 'a', 'b' } ] = { "aa", "bb" },
		},
		-- Complex strings as key
		strings =
		{
			[ [[C:\Lua\Lua.exe]] ] = true,
			[ "O'Neil" ] = false,
			[ 'Will "Liam" Cheese Pear' ] = not nil,
		},
		-- Boolean as key
		[true] = a,
	}
}
-- Réferences in the table
t.others.tables.reference = t.a
t.others.tables.otherRef = b
t[ { t.a, t.b } ] = t.others.functions

--~ print(t.f(), t.f(), t.f())
--~ print(t.reference.x, t.b[5], t.b.ls, t[true])

print("\ndo\n" .. DumpObject(t) .. "\nreturn T\n\nend\n")
print("\n" .. DumpObject(t, '', ' '))

-- This one doesn't work, I don't handle cyclic references!

a =
{
	x = 5,
	y = 42
}
b =
{
	a,
	"Foo"
}
a.ref = b

print("\ndo\n" .. DumpObject(b) .. "\nreturn T\n\nend\n")

print(t.b.tar[2].op1)