mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-17 13:53:45 +03:00
Upload
This commit is contained in:
594
lua/fprofiler/prettyprint.lua
Normal file
594
lua/fprofiler/prettyprint.lua
Normal file
@@ -0,0 +1,594 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
-- Based on MDave's thing
|
||||
-- https://gist.github.com/mentlerd/d56ad9e6361f4b86af84
|
||||
if SERVER then AddCSLuaFile() 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)
|
||||
|
||||
-- 'nil' value
|
||||
local NIL = {}
|
||||
|
||||
-- Localise for faster access
|
||||
local pcall = pcall
|
||||
|
||||
local string_len = string.len
|
||||
local string_sub = string.sub
|
||||
local string_find = string.find
|
||||
|
||||
local table_concat = table.concat
|
||||
local table_insert = table.insert
|
||||
local table_sort = table.sort
|
||||
|
||||
|
||||
-- Stream interface
|
||||
local gMsgF -- Print fragment
|
||||
local gMsgN -- Print newline
|
||||
local gMsgC -- Set print color
|
||||
|
||||
local PrintLocals, gBegin, gFinish, PrintTableGrep
|
||||
|
||||
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 or color_neutral, 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 or color_neutral, 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 = not 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 not 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 or color_neutral, str)
|
||||
end
|
||||
end
|
||||
|
||||
function gMsgN()
|
||||
-- Print everything in the buffer
|
||||
if grep then
|
||||
gCommit()
|
||||
else
|
||||
MsgN()
|
||||
end
|
||||
|
||||
baseColor = nil
|
||||
currColor = nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
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 not 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 = not isprimitive(value) and ( not 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
|
||||
describe = istable(value) and ( not 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 not 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 not name then
|
||||
break
|
||||
end
|
||||
|
||||
if value == nil then
|
||||
value = NIL
|
||||
end
|
||||
|
||||
hash[name] = value
|
||||
end
|
||||
|
||||
PrintTableGrep( hash )
|
||||
end
|
||||
|
||||
function show(...)
|
||||
local n = select('#', ...)
|
||||
local tbl = {...}
|
||||
|
||||
for i = 1, n do
|
||||
if istable(tbl[i]) then MsgN(tostring(tbl[i])) PrintTableGrep(tbl[i])
|
||||
else InternalPrintValue(tbl[i]) MsgN() end
|
||||
end
|
||||
end
|
||||
|
||||
-- Hacky way of creating a pretty string from the above code
|
||||
-- because I don't feel like refactoring the entire thing
|
||||
local strResult
|
||||
local toStringMsgF = function(txt)
|
||||
table.insert(strResult, txt)
|
||||
end
|
||||
|
||||
local toStringMsgN = function()
|
||||
table.insert(strResult, "\n")
|
||||
end
|
||||
|
||||
local toStringMsgC = function(_, txt)
|
||||
table.insert(strResult, txt)
|
||||
end
|
||||
|
||||
function showStr(...)
|
||||
local oldF, oldN, oldMsgC, oldMsgN = gMsgF, gMsgN, MsgC, MsgN
|
||||
gMsgF, gMsgN, MsgC, MsgN = toStringMsgF, toStringMsgN, toStringMsgC, toStringMsgN
|
||||
|
||||
strResult = {}
|
||||
show(...)
|
||||
|
||||
gMsgF, gMsgN, MsgC, MsgN = oldF, oldN, oldMsgC, oldMsgN
|
||||
|
||||
return table.concat(strResult, "")
|
||||
end
|
||||
Reference in New Issue
Block a user