I think @EchoReaper has a function that will print the content of a table like that. Sounds like a great thing to upload as a Model so it’s always a quick require away
I do have a pretty hastily developed function to print tables url=http://pastebin.com/X6y9cdzr[/url] that I ripped out of a plugin meant to read and generate tables from and to strings for the purpose of saving settings, but it’d be super useful if table contents were printed out by default.
How to handle cyclic tables though?
I once wrote something similar, that would change this:
local b = {'hi'}
b.loop = b
local a = {c={b=b},d={b=b}}
into:
{
c = {
b = {
'hi',
loop = c.b
}
},
d = {
b = c.b
}
}
(maybe “a.c.b” instead of “c.b”, I forgot) (It just prints the first reference when a table is seen multiple times) (well I would probably have “TABLE c.b” so I know it’s a table etc)
@Shedletsky: What about making this function a member of table, e.g. table.dump or table.debugString or table.dumpString? (that returns a string and/or prints it) I don’t think print by itself should provide this functionality, because everyone expects it to print one line containing just the memory address of the table. Then again, if we do that it’s just as convenient as using HttpService’s JSON encode function.
@einsteink: The function can keep track of the tables it has already printed, and if it encounters a table it has already printed it can simply just print the memory address and then be done with it. e.g.:
For my use case, which is debugging simple objects it would be sufficient to print out only the first 100-200 lines of the contents of the table, so in a cyclical table it would be ok to barf the whole thing out.
As you point out, though, it seems possible to detect cyclical tables and trim the display of those, using some graph traversal algo.
Even cooler would be a collapsible representation for nested tables similar to what the Visual Studio debugger provides. Then you just ignore this problem.
I’m just gonna say “yes, exactly” although I have no idea what that algorythm is.
It’s basicly “did I print this table already? k, only print reference or something”
Breadth-first search with a taboo list. The taboo list is all of the table references you have seen before. When you encounter a taboo node in the search, you ignore it or mark it as a cycle.
Any memory managed language with a garbage collector (Java, C#, Lua) solves this problem, so any of those algos would work.
local function tableToString(tab,a,b)
a,b = a or 0, b or {[tab]="ROOT"}
local name = b[tab]
local white = ("\t"):rep(a+1)
local res = {"{"}
for k,v in pairs(tab) do
local value
if b[v] then
value = b[v]
elseif type(v) == "table" then
b[v] = name.."."..tostring(k)
value = tableToString(v,a+1,b)
elseif type(v) == "string" then
value = '"'..v:gsub("\n","\\n"):gsub("\t","\\t")..'"'
else
value = tostring(v)
end
table.insert(res,white..tostring(k).." = "..value)
end white = white:sub(2)
table.insert(res,white.."}")
return table.concat(res,"\n")
end
local test = {
str = "Result:\n\t- Unknown";
number = 12345;
child = {a="b"};
cyclic = {};
}
test.child.cyclic = test.cyclic
test.another = test.child
print(tableToString(test))
Gives this:
{
number = 12345
another = {
a = "b"
cyclic = {
}
}
child = ROOT.another
cyclic = ROOT.another.cyclic
str = "Result:\n\t- Unknown"
}
Of course, naming the tables might not go… that well…
Now I understand what you mean:
First get all names of all the tables using breadth-first-magic.
I might write that in a minute.
local function getNames(tab,name,res,lev)
res = res or {[tab]="ROOT"}
local pls = {} lev = lev or 0
for k,v in pairs(tab) do
if type(v) == "table" and not res[v] then
local n = name.."."..tostring(k)
res[v] = n pls[v] = n
end
end
for k,v in pairs(pls) do
getNames(k,v,res)
pls[k] = lev
end return res,pls
end
local function tableToString(tab,a,b,c,d)
a,b = a or 0, b or {[tab]=true}
local name = b[tab]
local white = ("\t"):rep(a+1)
if not c then
c,d = getNames(tab,"ROOT")
end local res = {"{"}
for k,v in pairs(tab) do
local value
if type(v) == "table" then
if d[v] == a and not b[v] then
b[v] = true
value = tableToString(v,a+1,b,c,d)
else
value = c[v]
end
elseif type(v) == "string" then
value = '"'..v:gsub("\n","\\n"):gsub("\t","\\t")..'"'
else
value = tostring(v)
end
table.insert(res,white..tostring(k).." = "..value)
end white = white:sub(2)
table.insert(res,white.."}")
return table.concat(res,"\n")
end
local test = {
str = "Result:\n\t- Unknown";
number = 12345;
child = {a="b"};
cyclic = {};
}
test.child.cyclic = test.cyclic
test.another = test.child
print(tableToString(test))
Result:
{
number = 12345
another = {
a = "b"
cyclic = ROOT.cyclic
}
child = ROOT.another
cyclic = {
}
str = "Result:\n\t- Unknown"
}
I have thought about it a bit more and I believe that a depth first search would probably give more readable output most of the time.
I’m thinking about the case where your table has a lot of cycles. You’d want the “also this cycle” tags to all be at the bottom of your output instead of strewn throughout.
Well there is already HttpService:JSONEncode, so doing any more work here only makes sense if you are willing to change the default behavior of print I think.