Skip to content

Instantly share code, notes, and snippets.

@mentlerd
Created January 28, 2015 12:10
Show Gist options
  • Save mentlerd/d56ad9e6361f4b86af84 to your computer and use it in GitHub Desktop.
Save mentlerd/d56ad9e6361f4b86af84 to your computer and use it in GitHub Desktop.
Pretty printing for gmod13
-- Global 'nil' value
NIL = {}
-- Localise for faster access
local pcall = pcall
local string_len = string.len
local string_sub = string.sub
local string_find = string.find
local string_gsub = string.gsub
local table_concat = table.concat
local table_insert = table.insert
local table_sort = table.sort
-- Stream interface
local gSet -- Set grep pattern
local gMsgF -- Print fragment
local gMsgN -- Print newline
local gMsgC -- Set print color
do
local grep_color = Color(235, 70, 70)
-- Grep parameters (static between gBegin/gEnd)
local grep
local grep_raw
local grep_proximity
-- Current line parameters
local buffer
local colors
local markers
local baseColor
local currColor
local length
-- History
local history
local remain
-- Actual printing
local function gCheckMatch( buffer )
local raw = table_concat(buffer)
return raw, string_find(raw, grep, 0, grep_raw)
end
local function gFlushEx( raw, markers, colors, baseColor )
-- Print entire buffer
local len = string_len(raw)
-- Keep track of the current line properties
local index = 1
local marker = 1
local currColor = baseColor
-- Method to print to a preset area
local function printToIndex( limit, color )
local mark = markers and markers[marker]
-- Print all marker areas until we would overshoot
while mark and mark < limit do
-- Catch up to the marker
MsgC(color or currColor, string_sub(raw, index, mark))
index = mark +1
-- Set new color
currColor = colors[marker]
-- Select next marker
marker = marker +1
mark = markers[marker]
end
-- Print the remaining between the last marker and the limit
MsgC(color or currColor, string_sub(raw, index, limit))
index = limit +1
end
-- Grep!
local match, last = 1
local from, to = string_find(raw, grep, 0, grep_raw)
while from do
printToIndex(from -1)
printToIndex(to, grep_color)
last = to +1
from, to = string_find(raw, grep, last, grep_raw)
end
printToIndex(len)
MsgN()
end
local function gCommit()
if grep_proximity then
-- Check if the line has at least one match
local raw, match = gCheckMatch(buffer)
if match then
-- Divide matches
if history[grep_proximity] then
MsgN("...")
end
-- Flush history
if grep_proximity != 0 then
local len = #history
for index = len -1, 1, -1 do
local entry = history[index]
history[index] = nil
gFlushEx( entry[1], entry[2], entry[3], entry[4] )
end
history[len] = nil
end
-- Flush line, allow next X lines to get printed
gFlushEx( raw, markers, colors, baseColor )
remain = grep_proximity -1
history[grep_proximity +1] = nil
elseif remain > 0 then
-- Flush immediately
gFlushEx( raw, markers, colors, baseColor )
remain = remain -1
else
-- Store in history
table_insert(history, 1, {raw, markers, colors, baseColor})
history[grep_proximity +1] = nil
end
else
-- Flush anyway
gFlushEx( table_concat(buffer), markers, colors, baseColor )
end
-- Reset state
length = 0
buffer = {}
markers = nil
colors = nil
baseColor = nil
currColor = nil
end
-- State machine
function gBegin( new, prox )
grep = isstring(new) and new
if grep then
grep_raw = !pcall(string_find, ' ', grep)
grep_proximity = isnumber(prox) and prox
-- Reset everything
buffer = {}
history = {}
end
length = 0
remain = 0
baseColor = nil
currColor = nil
end
function gFinish()
if grep_proximity and history and history[1] then
MsgN("...")
end
-- Free memory
buffer = nil
markers = nil
colors = nil
history = nil
end
function gMsgC( color )
if grep then
-- Try to save some memory by not immediately allocating colors
if length == 0 then
baseColor = color
return
end
-- Record color change
if color != currColor then
if !markers then
markers = {}
colors = {}
end
-- Record color change
table_insert(markers, length)
table_insert(colors, color)
end
end
currColor = color
end
function gMsgF( str )
if grep then
-- Split multiline fragments to separate ones
local fragColor = currColor or baseColor
local last = 1
local from, to = string_find(str, '\n')
while from do
local frag = string_sub(str, last, from -1)
local len = from - last
-- Merge fragment to the line
length = length + len
table_insert(buffer, frag)
-- Print finished line
gCommit()
-- Assign base color as previous fragColor
baseColor = fragColor
-- Look for more
last = to +1
from, to = string_find(str, '\n', last)
end
-- Push last fragment
local frag = string_sub(str, last)
local len = string_len(str) - last +1
length = length + len
table_insert(buffer, frag)
else
-- Push immediately
MsgC(currColor or baseColor, str)
end
end
function gMsgN()
-- Print everything in the buffer
if grep then
gCommit()
else
MsgN()
end
baseColor = nil
currColor = nil
end
end
local type_weight = {
[TYPE_FUNCTION] = 1,
[TYPE_TABLE] = 2,
}
local type_colors = {
[TYPE_BOOL] = Color(175, 130, 255),
[TYPE_NUMBER] = Color(175, 130, 255),
[TYPE_STRING] = Color(230, 220, 115),
[TYPE_FUNCTION] = Color(100, 220, 240)
}
local color_neutral = Color(220, 220, 220)
local color_name = Color(260, 150, 30)
local color_reference = Color(150, 230, 50)
local color_comment = Color( 30, 210, 30)
local function InternalPrintValue( value )
-- 'nil' values can also be printed
if value == NIL then
gMsgC(color_comment)
gMsgF("nil")
return
end
local color = type_colors[ TypeID(value) ]
-- For strings, place quotes
if isstring(value) then
if string_len(value) <= 1 then
value = string.format([['%s']], value)
else
value = string.format([["%s"]], value)
end
gMsgC(color)
gMsgF(value)
return
end
-- Workaround for userdata not using MetaName
if string_sub(tostring(value), 0, 8) == "userdata" then
local meta = getmetatable(value)
if meta and meta.MetaName then
value = string.format("%s: %p", meta.MetaName, value)
end
end
-- General print
gMsgC(color)
gMsgF(tostring(value))
-- For functions append source info
if isfunction(value) then
local info = debug.getinfo(value, 'S')
local aux
if info.what == 'C' then
aux = "\t-- [C]: -1"
else
if info.linedefined != info.lastlinedefined then
aux = string.format("\t-- [%s]: %i-%i", info.short_src, info.linedefined, info.lastlinedefined)
else
aux = string.format("\t-- [%s]: %i", info.short_src, info.linedefined)
end
end
gMsgC(color_comment)
gMsgF(aux)
end
end
-- Associated to object keys
local objID
local function isprimitive( value )
local id = TypeID(value)
return id <= TYPE_FUNCTION and id != TYPE_TABLE
end
local function InternalPrintTable( table, path, prefix, names, todo )
-- Collect keys and some info about them
local keyList = {}
local keyStr = {}
local keyCount = 0
for key, value in pairs( table ) do
-- Add to key list for later sorting
table_insert(keyList, key)
-- Describe key as string
if isprimitive(key) then
keyStr[key] = tostring(key)
else
-- Lookup already known name
local name = names[key]
-- Assign a new unique identifier
if !name then
objID = objID +1
name = string.format("%s: obj #%i", tostring(key), objID)
names[key] = name
todo[key] = true
end
-- Substitute object with name
keyStr[key] = name
end
keyCount = keyCount +1
end
-- Exit early for empty tables
if keyCount == 0 then
return
end
-- Determine max key length
local keyLen = 4
for key, str in pairs(keyStr) do
keyLen = math.max(keyLen, string.len(str))
end
-- Sort table keys
if keyCount > 1 then
table_sort( keyList, function( A, B )
-- Sort numbers numerically correct
if isnumber(A) and isnumber(B) then
return A < B
end
-- Weight types
local wA = type_weight[ TypeID( table[A] ) ] or 0
local wB = type_weight[ TypeID( table[B] ) ] or 0
if wA != wB then
return wA < wB
end
-- Order by string representation
return keyStr[A] < keyStr[B]
end )
end
-- Determine the next level ident
local new_prefix = string.format( "%s║%s", prefix, string.rep(' ', keyLen) )
-- Mark object as done
todo[table] = nil
-- Start describing table
for index, key in ipairs(keyList) do
local value = table[key]
-- Assign names to already described keys/values
local kName = names[key]
local vName = names[value]
-- Decide to either fully describe, or print the value
local describe = !isprimitive(value) and ( !vName or todo[value] )
-- Ident
gMsgF(prefix)
-- Fancy table guides
local moreLines = (index != keyCount) or describe
if index == 1 then
gMsgF(moreLines and '╦ ' or '═ ')
else
gMsgF(moreLines and '╠ ' or '╚ ')
end
-- Print key
local sKey = kName or keyStr[key]
gMsgC(kName and color_reference or color_name)
gMsgF(sKey)
-- Describe non primitives
local describe = istable(value) and ( !names[value] or todo[value] ) and value != NIL
-- Print key postfix
local padding = keyLen - string.len(sKey)
local postfix = string.format(describe and ":%s" or "%s = ", string.rep(' ', padding))
gMsgC(color_neutral)
gMsgF(postfix)
-- Print the value
if describe then
gMsgN()
-- Expand access path
local new_path = sKey
if isnumber(key) or kName then
new_path = string.format("%s[%s]", path or '', key)
elseif path then
new_path = string.format("%s.%s", path, new_path)
end
-- Name the object to mark it done
names[value] = names[value] or new_path
-- Describe
InternalPrintTable(value, new_path, new_prefix, names, todo)
else
-- Print the value (or the reference name)
if vName and !todo[value] then
gMsgC(color_reference)
gMsgF(string.format("ref: %s",vName))
else
InternalPrintValue(value)
end
gMsgN()
end
end
end
function PrintTableGrep( table, grep, proximity )
local base = {
[_G] = "_G",
[table] = "root"
}
gBegin(grep, proximity)
objID = 0
InternalPrintTable(table, nil, "", base, {})
gFinish()
end
function PrintLocals( level )
local level = level or 2
local hash = {}
for index = 1, 255 do
local name, value = debug.getlocal(2, index)
if !name then
break
end
if value == nil then
value = NIL
end
hash[name] = value
end
PrintTableGrep( hash )
end
@wyozi
Copy link

wyozi commented Jan 28, 2015

Good stuff

@FPtje
Copy link

FPtje commented Apr 27, 2016

I pushed my variant to the FProfiler as a library, with credits in a comment. I know I got an OK for using this earlier, but are you still OK with this?
https://github.com/FPtje/FProfiler/blob/ui/lua/fprofiler/prettyprint.lua

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment