[library] inspect.lua - print tables and debug beautifully
- kikito
- Inner party member
- Posts: 3153
- Joined: Sat Oct 03, 2009 5:22 pm
- Location: Madrid, Spain
- Contact:
[library] inspect.lua - print tables and debug beautifully
Hi there!
I've created another utility function:
https://github.com/kikito/inspect.lua
It is mainly useful for debugging / logging errors; tables are displayed hierarchically. The main objective here is being easy to read for a human; It does not produce 100% lua-compatible code, so you should not use it for saving/restoring tables.
The way it handles "infinite recursion" (table t has a reference to table q, and table q references back to t) by simply limiting the "maximum depth" that will be used (this can be changed on a second value). EDIT: Nowadays it handles recursion much more awesomely.
I know that some of you have already developed your own "recursive printers". I hope this is useful for those who haven't.
The library does some fancy things with metatables. This was implemented to appease Taehl, who seems to be into that
You may find examples and instructions on the readme, which should be visible on the link above.
Regards!
I've created another utility function:
https://github.com/kikito/inspect.lua
It is mainly useful for debugging / logging errors; tables are displayed hierarchically. The main objective here is being easy to read for a human; It does not produce 100% lua-compatible code, so you should not use it for saving/restoring tables.
The way it handles "infinite recursion" (table t has a reference to table q, and table q references back to t) by simply limiting the "maximum depth" that will be used (this can be changed on a second value). EDIT: Nowadays it handles recursion much more awesomely.
I know that some of you have already developed your own "recursive printers". I hope this is useful for those who haven't.
The library does some fancy things with metatables. This was implemented to appease Taehl, who seems to be into that
You may find examples and instructions on the readme, which should be visible on the link above.
Regards!
Last edited by kikito on Mon Oct 29, 2012 11:23 pm, edited 3 times in total.
When I write def I mean function.
- TechnoCat
- Inner party member
- Posts: 1611
- Joined: Thu Jul 30, 2009 12:31 am
- Location: Denver, CO
- Contact:
Re: inspect.lua
It is like PrettyPrinter for Lua. Thanks.
Re: inspect.lua
So much better than the basic functionality I've been using. Well done.
- ishkabible
- Party member
- Posts: 241
- Joined: Sat Oct 23, 2010 7:34 pm
- Location: Kansas USA
Re: inspect.lua
i made a small improvement to it. with my version you can handle infinite recursion a bit differently. rather than only allowing for a limited depth my version allows you to pass a negative number to tell the 'putTable' method to keep going forever. this brings up the issue of infinite recursion, so it uses another table to keep a record of all parent tables and if the 'putTable' method is passed a table which has a parent of the same value then "<table: (hex value)" is printed instead.
example:
will print
entire code: (changes only made to Inspector:putTable and Inspector:new so that's all i posted)
example:
Code: Select all
inspect = require('inspect')
local t = {}
t.t = t
print("t = "..inspect(t,-1))
i like this better becuase its more flexible. i kinda want to add a way to show table that it recurs to but im not sure on how i want to do that yet.t = {
t = <table: 00B7B928>
}
entire code: (changes only made to Inspector:putTable and Inspector:new so that's all i posted)
Code: Select all
function Inspector:new(v, depth)
local inspector = setmetatable( { buffer = {}, depth = depth, level = 0, RefTbl = {} }, {
__index = Inspector,
__tostring = function(instance) return table.concat(instance.buffer) end
} )
return inspector:putValue(v)
end
function Inspector:putTable(t)
if self.RefTbl[t] then
self:puts('<',tostring(t),'>')
elseif self.depth > 0 and self.level >= self.depth then
self:puts('{...}')
else
self.RefTbl[t] = true
self:puts('{')
self:down()
local length = #t
local mt = getmetatable(t)
local __tostring = type(mt) == 'table' and mt.__tostring
local string = type(__tostring) == 'function' and __tostring(t)
if type(string) == 'string' and #string > 0 then
self:puts(' -- ', unescape(string))
if length >= 1 then self:tabify() end -- tabify the array values
end
local comma = false
for i=1, length do
comma = self:putComma(comma)
self:puts(' '):putValue(t[i])
end
local dictKeys = getDictionaryKeys(t)
for _,k in ipairs(dictKeys) do
comma = self:putComma(comma)
self:tabify():putKey(k):puts(' = '):putValue(t[k])
end
if mt then
comma = self:putComma(comma)
self:tabify():puts('<metatable> = '):putValue(mt)
end
self:up()
if #dictKeys > 0 or mt then -- dictionary table. Justify closing }
self:tabify()
elseif length > 0 then -- array tables have one extra space before closing }
self:puts(' ')
end
self:puts('}')
self.RefTbl[t] = nil
end
return self
end
- kikito
- Inner party member
- Posts: 3153
- Joined: Sat Oct 03, 2009 5:22 pm
- Location: Madrid, Spain
- Contact:
Re: inspect.lua
mm. I might steal your idea.
I'll do a rundown on my libs this weekend, there are some things I must change. Recursion handling on inspect might be one of the things that I change.
I like having a depth limit though. IMHO it increases readability.
I'll do a rundown on my libs this weekend, there are some things I must change. Recursion handling on inspect might be one of the things that I change.
I like having a depth limit though. IMHO it increases readability.
When I write def I mean function.
- BlackBulletIV
- Inner party member
- Posts: 1261
- Joined: Wed Dec 29, 2010 8:19 pm
- Location: Queensland, Australia
- Contact:
Re: inspect.lua
I like the idea of table IDs, that helps to distinguish one from another.
- Robin
- The Omniscient
- Posts: 6506
- Joined: Fri Feb 20, 2009 4:29 pm
- Location: The Netherlands
- Contact:
Re: inspect.lua
Perhaps something likeBlackBulletIV wrote:I like the idea of table IDs, that helps to distinguish one from another.
Code: Select all
t = {
t = <table: 00B7B928>
} -- 00B7B928
Help us help you: attach a .love.
- ishkabible
- Party member
- Posts: 241
- Joined: Sat Oct 23, 2010 7:34 pm
- Location: Kansas USA
Re: inspect.lua
my alteration of your code keeps the depth as it is more readable, it just allows you to specify infinite depth as well.I like having a depth limit though. IMHO it increases readability.
ill try robins idea real quick to see how it looks with larger tables. do you know of anyway to get the hex value of the table with out just chopping of the first part of the tostring() output?
edit: i implemented robins idea, i looks pretty nice actually
added a function to get the hex string of a table
Code: Select all
local function tableHexStr(t)
return string.sub(tostring(t), 7)
end
Code: Select all
self:puts('}')
Code: Select all
self:puts('} -- ', tableHexStr(t))
just encase anyone is confused on where I am (e.g. i refer to line 147) here is the entier code
Code: Select all
-----------------------------------------------------------------------------------------------------------------------
-- inspect.lua - v1.0 (2011-04)
-- Enrique García Cota - enrique.garcia.cota [AT] gmail [DOT] com
-- human-readable representations of tables.
-- inspired by http://lua-users.org/wiki/TableSerialization
-----------------------------------------------------------------------------------------------------------------------
-- Apostrophizes the string if it has quotes, but not aphostrophes
-- Otherwise, it returns a regular quoted string
local function smartQuote(str)
if string.match( string.gsub(str,"[^'\"]",""), '^"+$' ) then
return "'" .. str .. "'"
end
return string.format("%q", str )
end
local function tableHexStr(t)
return string.sub(tostring(t), 7)
end
local controlCharsTranslation = {
["\a"] = "\\a", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n",
["\r"] = "\\r", ["\t"] = "\\t", ["\v"] = "\\v", ["\\"] = "\\\\"
}
local function unescapeChar(c) return controlCharsTranslation[c] end
local function unescape(str)
local result, _ = string.gsub( str, "(%c)", unescapeChar )
return result
end
local function isIdentifier(str)
return string.match( str, "^[_%a][_%a%d]*$" )
end
local function isArrayKey(k, length)
return type(k)=='number' and 1 <= k and k <= length
end
local function isDictionaryKey(k, length)
return not isArrayKey(k, length)
end
local sortOrdersByType = {
['number'] = 1, ['boolean'] = 2, ['string'] = 3, ['table'] = 4,
['function'] = 5, ['userdata'] = 6, ['thread'] = 7
}
function sortKeys(a,b)
local ta, tb = type(a), type(b)
if ta ~= tb then return sortOrdersByType[ta] < sortOrdersByType[tb] end
if ta == 'string' or ta == 'number' then return a < b end
return false
end
local function getDictionaryKeys(t)
local length = #t
local keys = {}
for k,_ in pairs(t) do
if isDictionaryKey(k, length) then table.insert(keys,k) end
end
table.sort(keys, sortKeys)
return keys
end
local Inspector = {}
function Inspector:new(v, depth)
local inspector = setmetatable( { buffer = {}, depth = depth, level = 0, RefTbl = {} }, {
__index = Inspector,
__tostring = function(instance) return table.concat(instance.buffer) end
} )
return inspector:putValue(v)
end
function Inspector:puts(...)
local args = {...}
for i=1, #args do
table.insert(self.buffer, tostring(args[i]))
end
return self
end
function Inspector:tabify()
self:puts("\n", string.rep(" ", self.level))
return self
end
function Inspector:up()
self.level = self.level - 1
end
function Inspector:down()
self.level = self.level + 1
end
function Inspector:putComma(comma)
if comma then self:puts(',') end
return true
end
function Inspector:putTable(t)
if self.RefTbl[t] then
self:puts('<',tostring(t),'>')
elseif self.depth > 0 and self.level >= self.depth then
self:puts('{...}')
else
self.RefTbl[t] = true
self:puts('{')
self:down()
local length = #t
local mt = getmetatable(t)
local __tostring = type(mt) == 'table' and mt.__tostring
local string = type(__tostring) == 'function' and __tostring(t)
if type(string) == 'string' and #string > 0 then
self:puts(' -- ', unescape(string))
if length >= 1 then self:tabify() end -- tabify the array values
end
local comma = false
for i=1, length do
comma = self:putComma(comma)
self:puts(' '):putValue(t[i])
end
local dictKeys = getDictionaryKeys(t)
for _,k in ipairs(dictKeys) do
comma = self:putComma(comma)
self:tabify():putKey(k):puts(' = '):putValue(t[k])
end
if mt then
comma = self:putComma(comma)
self:tabify():puts('<metatable> = '):putValue(mt)
end
self:up()
if #dictKeys > 0 or mt then -- dictionary table. Justify closing }
self:tabify()
elseif length > 0 then -- array tables have one extra space before closing }
self:puts(' ')
end
self:puts('} -- ', tableHexStr(t))
self.RefTbl[t] = nil
end
return self
end
function Inspector:putValue(v)
local tv = type(v)
if tv == 'string' then
self:puts(smartQuote(unescape(v)))
elseif tv == 'number' or tv == 'boolean' then
self:puts(tostring(v))
elseif tv == 'table' then
self:putTable(v)
else
self:puts('<',tv,'>')
end
return self
end
function Inspector:putKey(k)
if type(k) == "string" and isIdentifier(k) then
return self:puts(k)
end
return self:puts( "[" ):putValue(k):puts("]")
end
local function inspect(t, depth)
depth = depth or 4
return tostring(Inspector:new(t, depth))
end
return inspect
- Robin
- The Omniscient
- Posts: 6506
- Joined: Fri Feb 20, 2009 4:29 pm
- Location: The Netherlands
- Contact:
Re: inspect.lua
Wouldn't it make more sense to use this:Code: Select all
if string.match( string.gsub(str,"[^'\"]",""), '^"+$' ) then
Code: Select all
if str:find('"', 1, true) and not str:find("'", 1, true) then
Help us help you: attach a .love.
Who is online
Users browsing this forum: Ahrefs [Bot] and 4 guests