glitchdetector

Inspect

Oct 10th, 2018
149
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 9.64 KB | None | 0 0
  1. local inspect ={
  2.   _VERSION = 'inspect.lua 3.1.0',
  3.   _URL     = 'http://github.com/kikito/inspect.lua',
  4.   _DESCRIPTION = 'human-readable representations of tables',
  5.   _LICENSE = [[
  6.     MIT LICENSE
  7.  
  8.     Copyright (c) 2013 Enrique García Cota
  9.  
  10.     Permission is hereby granted, free of charge, to any person obtaining a
  11.     copy of this software and associated documentation files (the
  12.     "Software"), to deal in the Software without restriction, including
  13.     without limitation the rights to use, copy, modify, merge, publish,
  14.     distribute, sublicense, and/or sell copies of the Software, and to
  15.     permit persons to whom the Software is furnished to do so, subject to
  16.     the following conditions:
  17.  
  18.     The above copyright notice and this permission notice shall be included
  19.     in all copies or substantial portions of the Software.
  20.  
  21.     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  22.     OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  23.     MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  24.     IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
  25.     CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
  26.     TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
  27.     SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  28.   ]]
  29. }
  30.  
  31. local tostring = tostring
  32.  
  33. inspect.KEY       = setmetatable({}, {__tostring = function() return 'inspect.KEY' end})
  34. inspect.METATABLE = setmetatable({}, {__tostring = function() return 'inspect.METATABLE' end})
  35.  
  36. local function rawpairs(t)
  37.   return next, t, nil
  38. end
  39.  
  40. -- Apostrophizes the string if it has quotes, but not aphostrophes
  41. -- Otherwise, it returns a regular quoted string
  42. local function smartQuote(str)
  43.   if str:match('"') and not str:match("'") then
  44.     return "'" .. str .. "'"
  45.   end
  46.   return '"' .. str:gsub('"', '\\"') .. '"'
  47. end
  48.  
  49. -- \a => '\\a', \0 => '\\0', 31 => '\31'
  50. local shortControlCharEscapes = {
  51.   ["\a"] = "\\a",  ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n",
  52.   ["\r"] = "\\r",  ["\t"] = "\\t", ["\v"] = "\\v"
  53. }
  54. local longControlCharEscapes = {} -- \a => nil, \0 => \000, 31 => \031
  55. for i=0, 31 do
  56.   local ch = string.char(i)
  57.   if not shortControlCharEscapes[ch] then
  58.     shortControlCharEscapes[ch] = "\\"..i
  59.     longControlCharEscapes[ch]  = string.format("\\%03d", i)
  60.   end
  61. end
  62.  
  63. local function escape(str)
  64.   return (str:gsub("\\", "\\\\")
  65.              :gsub("(%c)%f[0-9]", longControlCharEscapes)
  66.              :gsub("%c", shortControlCharEscapes))
  67. end
  68.  
  69. local function isIdentifier(str)
  70.   return type(str) == 'string' and str:match( "^[_%a][_%a%d]*$" )
  71. end
  72.  
  73. local function isSequenceKey(k, sequenceLength)
  74.   return type(k) == 'number'
  75.      and 1 <= k
  76.      and k <= sequenceLength
  77.      and math.floor(k) == k
  78. end
  79.  
  80. local defaultTypeOrders = {
  81.   ['number']   = 1, ['boolean']  = 2, ['string'] = 3, ['table'] = 4,
  82.   ['function'] = 5, ['userdata'] = 6, ['thread'] = 7
  83. }
  84.  
  85. local function sortKeys(a, b)
  86.   local ta, tb = type(a), type(b)
  87.  
  88.   -- strings and numbers are sorted numerically/alphabetically
  89.   if ta == tb and (ta == 'string' or ta == 'number') then return a < b end
  90.  
  91.   local dta, dtb = defaultTypeOrders[ta], defaultTypeOrders[tb]
  92.   -- Two default types are compared according to the defaultTypeOrders table
  93.   if dta and dtb then return defaultTypeOrders[ta] < defaultTypeOrders[tb]
  94.   elseif dta     then return true  -- default types before custom ones
  95.   elseif dtb     then return false -- custom types after default ones
  96.   end
  97.  
  98.   -- custom types are sorted out alphabetically
  99.   return ta < tb
  100. end
  101.  
  102. -- For implementation reasons, the behavior of rawlen & # is "undefined" when
  103. -- tables aren't pure sequences. So we implement our own # operator.
  104. local function getSequenceLength(t)
  105.   local len = 1
  106.   local v = rawget(t,len)
  107.   while v ~= nil do
  108.     len = len + 1
  109.     v = rawget(t,len)
  110.   end
  111.   return len - 1
  112. end
  113.  
  114. local function getNonSequentialKeys(t)
  115.   local keys, keysLength = {}, 0
  116.   local sequenceLength = getSequenceLength(t)
  117.   for k,_ in rawpairs(t) do
  118.     if not isSequenceKey(k, sequenceLength) then
  119.       keysLength = keysLength + 1
  120.       keys[keysLength] = k
  121.     end
  122.   end
  123.   table.sort(keys, sortKeys)
  124.   return keys, keysLength, sequenceLength
  125. end
  126.  
  127. local function countTableAppearances(t, tableAppearances)
  128.   tableAppearances = tableAppearances or {}
  129.  
  130.   if type(t) == 'table' then
  131.     if not tableAppearances[t] then
  132.       tableAppearances[t] = 1
  133.       for k,v in rawpairs(t) do
  134.         countTableAppearances(k, tableAppearances)
  135.         countTableAppearances(v, tableAppearances)
  136.       end
  137.       countTableAppearances(getmetatable(t), tableAppearances)
  138.     else
  139.       tableAppearances[t] = tableAppearances[t] + 1
  140.     end
  141.   end
  142.  
  143.   return tableAppearances
  144. end
  145.  
  146. local copySequence = function(s)
  147.   local copy, len = {}, #s
  148.   for i=1, len do copy[i] = s[i] end
  149.   return copy, len
  150. end
  151.  
  152. local function makePath(path, ...)
  153.   local keys = {...}
  154.   local newPath, len = copySequence(path)
  155.   for i=1, #keys do
  156.     newPath[len + i] = keys[i]
  157.   end
  158.   return newPath
  159. end
  160.  
  161. local function processRecursive(process, item, path, visited)
  162.   if item == nil then return nil end
  163.   if visited[item] then return visited[item] end
  164.  
  165.   local processed = process(item, path)
  166.   if type(processed) == 'table' then
  167.     local processedCopy = {}
  168.     visited[item] = processedCopy
  169.     local processedKey
  170.  
  171.     for k,v in rawpairs(processed) do
  172.       processedKey = processRecursive(process, k, makePath(path, k, inspect.KEY), visited)
  173.       if processedKey ~= nil then
  174.         processedCopy[processedKey] = processRecursive(process, v, makePath(path, processedKey), visited)
  175.       end
  176.     end
  177.  
  178.     local mt  = processRecursive(process, getmetatable(processed), makePath(path, inspect.METATABLE), visited)
  179.     if type(mt) ~= 'table' then mt = nil end -- ignore not nil/table __metatable field
  180.     setmetatable(processedCopy, mt)
  181.     processed = processedCopy
  182.   end
  183.   return processed
  184. end
  185.  
  186.  
  187.  
  188. -------------------------------------------------------------------
  189.  
  190. local Inspector = {}
  191. local Inspector_mt = {__index = Inspector}
  192.  
  193. function Inspector:puts(...)
  194.   local args   = {...}
  195.   local buffer = self.buffer
  196.   local len    = #buffer
  197.   for i=1, #args do
  198.     len = len + 1
  199.     buffer[len] = args[i]
  200.   end
  201. end
  202.  
  203. function Inspector:down(f)
  204.   self.level = self.level + 1
  205.   f()
  206.   self.level = self.level - 1
  207. end
  208.  
  209. function Inspector:tabify()
  210.   self:puts(self.newline, string.rep(self.indent, self.level))
  211. end
  212.  
  213. function Inspector:alreadyVisited(v)
  214.   return self.ids[v] ~= nil
  215. end
  216.  
  217. function Inspector:getId(v)
  218.   local id = self.ids[v]
  219.   if not id then
  220.     local tv = type(v)
  221.     id              = (self.maxIds[tv] or 0) + 1
  222.     self.maxIds[tv] = id
  223.     self.ids[v]     = id
  224.   end
  225.   return tostring(id)
  226. end
  227.  
  228. function Inspector:putKey(k)
  229.   if isIdentifier(k) then return self:puts(k) end
  230.   self:puts("[")
  231.   self:putValue(k)
  232.   self:puts("]")
  233. end
  234.  
  235. function Inspector:putTable(t)
  236.   if t == inspect.KEY or t == inspect.METATABLE then
  237.     self:puts(tostring(t))
  238.   elseif self:alreadyVisited(t) then
  239.     self:puts('<table ', self:getId(t), '>')
  240.   elseif self.level >= self.depth then
  241.     self:puts('{...}')
  242.   else
  243.     if self.tableAppearances[t] > 1 then self:puts('<', self:getId(t), '>') end
  244.  
  245.     local nonSequentialKeys, nonSequentialKeysLength, sequenceLength = getNonSequentialKeys(t)
  246.     local mt                = getmetatable(t)
  247.  
  248.     self:puts('{')
  249.     self:down(function()
  250.       local count = 0
  251.       for i=1, sequenceLength do
  252.         if count > 0 then self:puts(',') end
  253.         self:puts(' ')
  254.         self:putValue(t[i])
  255.         count = count + 1
  256.       end
  257.  
  258.       for i=1, nonSequentialKeysLength do
  259.         local k = nonSequentialKeys[i]
  260.         if count > 0 then self:puts(',') end
  261.         self:tabify()
  262.         self:putKey(k)
  263.         self:puts(' = ')
  264.         self:putValue(t[k])
  265.         count = count + 1
  266.       end
  267.  
  268.       if type(mt) == 'table' then
  269.         if count > 0 then self:puts(',') end
  270.         self:tabify()
  271.         self:puts('<metatable> = ')
  272.         self:putValue(mt)
  273.       end
  274.     end)
  275.  
  276.     if nonSequentialKeysLength > 0 or type(mt) == 'table' then -- result is multi-lined. Justify closing }
  277.       self:tabify()
  278.     elseif sequenceLength > 0 then -- array tables have one extra space before closing }
  279.       self:puts(' ')
  280.     end
  281.  
  282.     self:puts('}')
  283.   end
  284. end
  285.  
  286. function Inspector:putValue(v)
  287.   local tv = type(v)
  288.  
  289.   if tv == 'string' then
  290.     self:puts(smartQuote(escape(v)))
  291.   elseif tv == 'number' or tv == 'boolean' or tv == 'nil' or
  292.          tv == 'cdata' or tv == 'ctype' then
  293.     self:puts(tostring(v))
  294.   elseif tv == 'table' then
  295.     self:putTable(v)
  296.   else
  297.     self:puts('<', tv, ' ', self:getId(v), '>')
  298.   end
  299. end
  300.  
  301. -------------------------------------------------------------------
  302.  
  303. function inspect.inspect(root, options)
  304.   options       = options or {}
  305.  
  306.   local depth   = options.depth   or math.huge
  307.   local newline = options.newline or '\n'
  308.   local indent  = options.indent  or '  '
  309.   local process = options.process
  310.  
  311.   if process then
  312.     root = processRecursive(process, root, {}, {})
  313.   end
  314.  
  315.   local inspector = setmetatable({
  316.     depth            = depth,
  317.     level            = 0,
  318.     buffer           = {},
  319.     ids              = {},
  320.     maxIds           = {},
  321.     newline          = newline,
  322.     indent           = indent,
  323.     tableAppearances = countTableAppearances(root)
  324.   }, Inspector_mt)
  325.  
  326.   inspector:putValue(root)
  327.  
  328.   return table.concat(inspector.buffer)
  329. end
  330.  
  331. setmetatable(inspect, { __call = function(_, ...) return inspect.inspect(...) end })
  332.  
  333. return inspect
Add Comment
Please, Sign In to add comment