Advertisement
osmarks

stack_trace

Sep 23rd, 2018
2,514
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 3.10 KB | None | 0 0
  1. -- see pastebin.com/NdUKJ07j for license info
  2. -- mostly written by SquidDev, bodged by gollark/osmarks
  3.  
  4. local type = type
  5. local debug_traceback = type(debug) == "table" and type(debug.traceback) == "function" and debug.traceback
  6.  
  7. local _pcall, _xpcall = pcall, xpcall
  8.  
  9. local function traceback(x)
  10.   -- Attempt to detect error() and error("xyz", 0).
  11.   -- This probably means they're erroring the program intentionally and so we
  12.   -- shouldn't display anything.
  13.   if x == nil or (type(x) == "string" and not x:find(":%d+:")) or type(x) ~= "string" then
  14.     return x
  15.   end
  16.  
  17.   if debug_traceback then
  18.     -- The parens are important, as they prevent a tail call occuring, meaning
  19.     -- the stack level is preserved. This ensures the code behaves identically
  20.     -- on LuaJ and PUC Lua.
  21.     return (debug_traceback(tostring(x), 2))
  22.   else
  23.     local level = 3
  24.     local out = { tostring(x), "stack traceback:" }
  25.     while true do
  26.       local _, msg = _pcall(error, "", level)
  27.       if msg == "" then break end
  28.  
  29.       out[#out + 1] = "  " .. msg
  30.       level = level + 1
  31.     end
  32.  
  33.     return table.concat(out, "\n")
  34.   end
  35. end
  36.  
  37. local function trim_traceback(target, marker)
  38.   local target = tostring(target)
  39.   local ttarget, tmarker = {}, {}
  40.   for line in target:gmatch("([^\n]*)\n?") do ttarget[#ttarget + 1] = line end
  41.   for line in marker:gmatch("([^\n]*)\n?") do tmarker[#tmarker + 1] = line end
  42.  
  43.   -- Trim identical suffixes
  44.   local t_len, m_len = #ttarget, #tmarker
  45.   while t_len >= 3 and ttarget[t_len] == tmarker[m_len] do
  46.     table.remove(ttarget, t_len)
  47.     t_len, m_len = t_len - 1, m_len - 1
  48.   end
  49.  
  50.   -- Trim elements from this file and xpcall invocations
  51.   while t_len >= 1 and ttarget[t_len]:find("^\tstack_trace%.lua:%d+:") or
  52.         ttarget[t_len] == "\t[C]: in function 'xpcall'" or ttarget[t_len] == "  xpcall: " do
  53.     table.remove(ttarget, t_len)
  54.     t_len = t_len - 1
  55.   end
  56.  
  57.   return ttarget
  58. end
  59.  
  60. --- Run a function with
  61. local function xpcall_with(fn, ...)
  62.   local args = {...}
  63.   -- So this is rather grim: we need to get the full traceback and current one and remove
  64.   -- the common prefix
  65.   local trace
  66.   local res = table.pack(_xpcall(function() return fn(unpack(args)) end, traceback)) if not res[1] then trace = traceback("stack_trace.lua:1:") end
  67.   local ok, err = res[1], res[2]
  68.  
  69.   if not ok and err ~= nil then
  70.     trace = trim_traceback(err, trace)
  71.  
  72.     -- Find the position where the stack traceback actually starts
  73.     local trace_starts
  74.     for i = #trace, 1, -1 do
  75.       if trace[i] == "stack traceback:" then trace_starts = i; break end
  76.     end
  77.  
  78.     -- If this traceback is more than 15 elements long, keep the first 9, last 5
  79.     -- and put an ellipsis between the rest
  80.     local max = 15
  81.     if trace_starts and #trace - trace_starts > max then
  82.       local keep_starts = trace_starts + 10
  83.       for i = #trace - trace_starts - max, 0, -1 do table.remove(trace, keep_starts + i) end
  84.       table.insert(trace, keep_starts, "  ...")
  85.     end
  86.  
  87.     return false, table.concat(trace, "\n")
  88.   end
  89.  
  90.   return table.unpack(res, 1, res.n)
  91. end
  92.  
  93. _G.pcall = xpcall_with
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement