Skip to content

Instantly share code, notes, and snippets.

@Necktrox
Last active May 4, 2017 06:17
Show Gist options
  • Save Necktrox/2387ac6aea23bbff7b71 to your computer and use it in GitHub Desktop.
Save Necktrox/2387ac6aea23bbff7b71 to your computer and use it in GitHub Desktop.
Debug function for formatted output with variable names
DEBUG_MODE = true
DEBUG_PIPE = print
SHOW_ADDRESS = true
MTA_COMPATIBLE = false
local function getVariableValue(level, variable)
local index, name, value, skip = 1
-- Check if variable name contains a dot (table access)
if variable:find("%.") ~= nil then
-- Get the table name
local vtable = variable:sub(1, variable:find("%.") - 1)
-- Get the table reference
local vt = getVariableValue(level, vtable)
-- Abort if reference is not a table
if type(vt) ~= "table" then
return nil
end
-- Remove the table name from the variable name
variable = variable:sub(variable:find("%.") + 1, variable:len())
-- Dig into the table to get the requested field
for var in variable:gmatch("[^%.]+") do
if type(vt) ~= "table" then
return nil
else
vt = vt[var]
end
end
-- Return found value
return vt
end
-- Search for variable in local environment
repeat
name, value = debug.getlocal(level + 1, index)
if name ~= nil and name == variable then
return value
else
index = index + 1
end
until name == nil
index = 1
local ref = debug.getinfo(level + 1, "f").func
-- Check if ref points to a valid function
while type(ref) ~= "function" and level > 0 do
ref = debug.getinfo(level, "f").func
level = level - 1
end
-- Search for variable in global environment
if type(ref) == "function" then
repeat
name, value = debug.getupvalue(ref, index)
if name ~= nil and name == variable then
return value
else
index = index + 1
end
until name == nil
end
if MTA_COMPATIBLE then
-- Last attempt is to return a value from _G
return _G[variable]
else
-- Last attempt is to return a value from function environment
return getfenv(ref)[variable]
end
end
local function replace(where, what, with)
-- Replace each finding
repeat
local start, stop = where:find(what, 1, true)
if start ~= nil then
where = where:sub(1, start - 1) .. with .. where:sub(stop + 1, where:len())
end
until start == nil
-- Return the replaced string
return where
end
local function size(tbl)
-- Verify that we handle only tables
if type(tbl) ~= "table" then
return 0
end
local size = 0
-- Calculate size
for index in pairs(tbl) do
size = size + 1
end
return size
end
local function trimAddress(value)
-- Convert value to string
if type(value) ~= "string" then
value = tostring(value)
end
-- Remove any description and leading zeros from address
return value:gsub(".-: [0]*", "")
end
local function trimPlayerName(name)
-- Convert value to string
if type(name) ~= "string" then
name = tostring(name)
end
-- Check if name contains only color codes
if name:gsub("#%x%x%x%x%x%x", ""):len() == 0 then
-- Add invisible character to show color codes
return name:gsub("#(%x%x%x%x%x%x)", "#\1%1")
else
-- Remove color codes
return name:gsub("#%x%x%x%x%x%x", "")
end
end
local function format(name, value, modifier)
-- Verify that modifier is a string
modifier = type(modifier) == "string" and modifier or ""
-- Cache the value type
local vtype = type(value)
-- Number (rounded)
if (vtype == "number" and modifier == "") or modifier == "number" then
if vtype ~= "number" then
value = tonumber(value) or 0
end
local integral, fractional = math.modf(value)
local formatter = (fractional ~= 0) and "%.3f" or "%d"
return formatter:format(value)
-- Natural (not rounded)
elseif modifier == "natural" then
if vtype ~= "number" then
value = tonumber(value) or 0
end
return tostring(value)
-- Nil
elseif (vtype == "nil" and modifier == "") or modifier == "nil" then
return "nil"
-- String
elseif (vtype == "string" and modifier == "") or modifier == "string" then
return tostring(value)
-- Boolean
elseif (vtype == "boolean" and modifier == "") or modifier == "boolean" then
if vtype ~= "boolean" then
if vtype == "string" then
return (value ~= "false") and "true" or "false"
elseif vtype == "number" then
return (value ~= 0) and "true" or "false"
end
end
return (value and "true" or "false")
-- Table
elseif (vtype == "table" and modifier == "") or modifier == "table" then
if vtype ~= "table" then
value = {value}
end
if SHOW_ADDRESS then
return name .. " [table(" .. size(value) .."), 0x".. trimAddress(value) .."]"
else
return name .. " [table(" .. size(value) ..")]"
end
-- Function
elseif (vtype == "function" and modifier == "") or modifier == "function" then
if vtype ~= "function" then
return "non-function"
end
if SHOW_ADDRESS then
return name .. " [function, 0x".. trimAddress(value) .."]"
else
return name .. " [function]"
end
-- Function (execute)
elseif modifier == "execute" then
if vtype ~= "function" then
return "nil"
end
local result = value()
return format(name, result)
end
if MTA_COMPATIBLE then
-- Element
if (isElement(value) and modifier == "") or modifier == "element" then
if not isElement(value) then
return "non-element"
end
local address = SHOW_ADDRESS and (" [0x".. trimAddress(value) .."]") or ""
if value.type == "player" then
return trimPlayerName(value:getName()) .. address
elseif value.type == "vehicle" then
if isElement(value:getOccupant()) then
return value.type .. " (driver: " .. trimPlayerName(value:getOccupant():getName()) .. ")" .. address
else
return value.type .. address
end
else
return value.type .. address
end
-- Resource
elseif (vtype == "userdata" and getResourceName(value) and modifier == "") or modifier == "resource" then
if vtype ~= "userdata" or not getResourceName(value) then
return "non-resource"
end
if SHOW_ADDRESS then
return getResourceName(value) .. " [resource, 0x".. trimAddress(value) .."]"
else
return getResourceName(value) .. " [resource]"
end
end
-- Userdata
if (vtype == "userdata" and modifier == "") or modifier == "userdata" then
if vtype ~= "userdata" then
return "non-userdata"
end
if SHOW_ADDRESS then
return name .. " [userdata, 0x".. trimAddress(value) .."]"
else
return name .. " [userdata]"
end
end
return tostring(value)
end
function debugf(text)
if not DEBUG_MODE or type(DEBUG_PIPE) ~= "function" then
-- Do not parse if debug is disabled
return
end
-- Modify always a copy of the original
local copy = text
-- Match every bracket in the text
for brackets in text:gmatch("{(.-)}") do
-- Check if brackets contain a modifier
if brackets:find("|") ~= nil then
local index, args = 1, {}
-- Get each 'argument' (including modifier)
for value in brackets:gmatch("[^|]+") do
args[index] = value
index = index + 1
end
-- Replace the bracket with the value using a modifier
copy = replace(copy, "{".. brackets .."}", format(args[1], getVariableValue(2, args[1]), args[2]))
else
-- Replace the bracket with the value
copy = replace(copy, "{".. brackets .."}", format(brackets, getVariableValue(2, brackets)))
end
end
-- Forward result to debug pipe function
DEBUG_PIPE(copy)
end
@Necktrox
Copy link
Author

Usage:

local name = "Necktrox"
local favourite = 7
local pi = math.pi
local foo = function() return "GitHub" end

debugf("My name is {name}, my favourite number is {favourite}. PI equals to {pi|number}")
debugf("Lua version: {_VERSION}")
debugf("math.pi = {math.pi|natural}")
debugf("math = {math}")
debugf("This is hosted on {foo|execute}")
debugf("{}")
debugf("{math.pi|nil}")
debugf("print = {print}")
debugf("math as string = {math|string}")

Output:

My name is Necktrox, my favourite number is 7. PI equals to 3.142
Lua version: Lua 5.3
math.pi = 3.1415926535898
math = math [table(31), 0x...]
This is hosted on GitHub
nil
nil
print = print [function, 0x...]
math as string = table: 0...

Notes:
The code includes optional useful code for the Multi Theft Auto modification.

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