Warning: this is an htmlized version!
The original is here, and
the conversion rules are here.
-- This file:
--   https://github.com/edrx/emacs-lua/
--       http://angg.twu.net/emacs-lua/EdrxPcall.lua.html
--       http://angg.twu.net/emacs-lua/EdrxPcall.lua
--        (find-angg        "emacs-lua/EdrxPcall.lua")
-- Author: Eduardo Ochs <eduardoochs@gmail.com>
--
-- In the HTML version the sexp hyperlinks work.
-- See: (find-eev-quick-intro "3. Elisp hyperlinks")
--      (find-eepitch-intro "3. Test blocks")
--
-- Author:  Eduardo Ochs <eduardoochs@gmail.com>
-- Version: 20210918
-- License: GPL3 at this moment.
-- If you need another license, get in touch!
--
-- Some eev-isms:
-- (defun o () (interactive) (find-angg "emacs-lua/README.org"))
-- (defun r () (interactive) (find-angg "emacs-lua/edrxrepl.lua"))
-- (defun x () (interactive) (find-angg "emacs-lua/EdrxPcall.lua"))



-- Introduction
-- ============
-- When we do things like this in Lua,
--
--   status, error = pcall(f)
--   status, error = xpcall(f, errorhandler)
--
-- the function f is called in "protected mode", and any errors are
-- handled by pcall and xpcall. The argument "errorhandler" to xpcall
-- is a function that is run after the error occurs and before the
-- stack is unwinded, and it can be used to produce a traceback or
-- save it into a variable. See:
--
--   (find-lua51manual "#pdf-pcall"  "pcall (f, arg1, ...)")
--   (find-lua51manual "#pdf-xpcall" "xpcall (f, err)")
--   (find-lua52manual "#pdf-xpcall" "xpcall (f, msgh [, arg1, ...])")
--   (find-lua51manual "#pdf-debug.traceback")
--   (find-lua51manual "#pdf-error")
--   (find-pil3page (+ 19 77) "8.4 Errors")
--   (find-pil3page (+ 19 79) "8.5 Error Handling and Exceptions")
--   (find-pil3page (+ 19 79) "8.6 Error Messages and Tracebacks")
--
-- I always found xpcall too hard to use, so I wrote the class
-- EdrxPcall below, that feels more hacker-friendly (to me)... it uses
-- eoo.lua, it saves all the intermediate values, and all its methods
-- can be overridden.
--
-- The six high-level ways of calling function with EdrxPcall are
-- these ones:
--
--   EdrxPcall.new():  call(F2, 3, 4):  out()
--   EdrxPcall.new():  call(F2, 3, 4):  out("=")
--   EdrxPcall.new():  call(F2, 3, 4):  out("=", "\n")
--   EdrxPcall.new():prcall(F2, 3, 4):prout()
--   EdrxPcall.new():prcall(F2, 3, 4):prout("=")
--   EdrxPcall.new():prcall(F2, 3, 4):prout("=", "\n")
--
-- The option "=" behaves like an initial "=" in the REPLs of Lua5.1
-- and Lua5.2, in the sense that it makes the results of F2(3, 4) be
-- printed in case of success. The alternatives with "pr" redirect the
-- outputs of the "print"s called by F2(3, 4) to a string before
-- printing them, and used by emacs-lua. To understand how all this
-- works try the tests at the end of this file.
--
-- The alternatives with "\n" are harder to explain. Remember that in
-- the REPLs of Lua5.1 and Lua5.1 typing something like "= <exprs>" at
-- the REPL only prints something when "<exprs>" return something, and
-- remember that "return", "return nil", are "return nil, nil" are all
-- different, as they return lists of values of length 0, 1, and 2...
-- The option "\n" at the end adds a "\n" at the end of the output
-- only when the "=" receives a non-empty list of values (but this
-- still has some bugs).



-- «.download»		(to "download")
--
-- «.from-init»		(to "from-init")
-- «.Class»		(to "Class")
-- «.EdrxPcall»		(to "EdrxPcall")
-- «.EdrxPcall-tests»	(to "EdrxPcall-tests")



-- «download»  (to ".download")
--[[
-- (setq ee-git-dir "/tmp/")
-- (find-git-links "https://github.com/edrx/emacs-lua" "emacslua")

 (eepitch-shell)
 (eepitch-kill)
 (eepitch-shell)
rm -Rfv /tmp/emacs-lua/
cd      /tmp/
git clone https://github.com/edrx/emacs-lua
cd      /tmp/emacs-lua/

 (code-c-d "emacslua" "/tmp/emacs-lua/" :anchor)
 (find-emacslua "EdrxPcall.lua" "EdrxPcall-tests")

--]]


-- «from-init»  (to ".from-init")
-- Some functions from my initfile. See:
-- (find-angg "LUA/lua50init.lua" "pack-and-unpack")
-- (find-angg "LUA/lua50init.lua" "splitlines-5.3")
-- (find-angg "LUA/lua50init.lua" "split")
-- (find-es "lua5" "loadstring")
loadstring = loadstring or load
pack   = table.pack or function (...) return {n=select("#", ...), ...} end
unpack = unpack or table.unpack
myunpack = function (arg) return unpack(arg, 1, arg.n) end
split = function (str, pat)
    local arr = {}
    string.gsub(str, pat or "([^%s]+)", function (word)
        table.insert(arr, word)
      end)
    return arr
  end
splitlines = function (bigstr)
    local arr = split(bigstr, "([^\n]*)\n?")
    if _VERSION:sub(5) < "5.3" then
      table.remove(arr)
    end
    return arr
  end

map = function (f, arr, n)
    local brr = {}
    for i=1,(n or #arr) do table.insert(brr, (f(arr[i]))) end
    return brr
  end
mapconcat = function (f, arr, sep, n)
    return table.concat(map(f, arr, n), sep)
  end
mapconcatpacked = function (f, arr, sep)
    return mapconcat(f or tostring, arr, sep or "  ", arr.n)
  end
print_to_string = function (...)
    return mapconcatpacked(tostring, pack(...), "\t")
  end

-- «Class»  (to ".Class")
-- Commented version:
-- (find-angg "dednat6/dednat6/eoo.lua" "Class")
Class = {
    type   = "Class",
    __call = function (class, o) return setmetatable(o, class) end,
  }
setmetatable(Class, Class)



-- «EdrxPcall»  (to ".EdrxPcall")
-- My class for calling xpcall in configurable ways.
-- See the introduction at the top of this file.
--
EdrxPcall = Class {
  type = "EdrxPcall",
  new  = function (T) return EdrxPcall(T or {}) end,
  __tostring = tos_VTable,
  __index = {
    --
    -- The method :call(f, ...) is the standard way to call
    -- a function with arguments in protected mode.
    call = function (xpc, f, ...)
        return xpc:callprep(f, ...):callrun()
      end,
    callprep = function (xpc, f, ...)
        xpc.f = f
        xpc.f_args = pack(...)
        xpc.g = function ()
            xpc.f_results = pack(xpc.f(myunpack(xpc.f_args)))
          end
        xpc.eh = xpc:eh0()
	return xpc
      end,
    callrun = function (xpc)
        xpc.xp_results = pack(xpcall(xpc.g, xpc.eh))
        return xpc
      end,
    --
    -- Error handler (eh) and traceback (tb).
    -- The method xpc:eh0() returns the error handler xpc.eh.
    eh0 = function (xpc)
        return function (errmsg)
	    xpc.err_msg = errmsg
            xpc:tb()
          end
      end,
    tb = function (xpc)
        xpc.tb_string  = debug.traceback("", xpc.tb_lvl)
      end,
    tb_lvl = 3,
    tb_e   = 6,
    --
    -- The method :out() returns the "output" of the call.
    -- For example:
    --   EdrxPcall.new():call(expr, "2,    3"):out("=")        --> "2  3"
    --   EdrxPcall.new():call(expr, "2,    3"):out("=", "\n")  --> "2  3\n"
    --   EdrxPcall.new():call(expr, "2,    3"):out()           --> ""
    --   EdrxPcall.new():call(expr, "2 + nil"):out()           --> errmsg/traceback
    success      = function (xpc) return xpc.xp_results[1] end,
    resultsempty = function (xpc) return (xpc.xp_results.n == 0) and "" end,
    results0000 = function (xpc) return myunpack(xpc.f_results) end,
    results000  = function (xpc) return print_to_string(xpc:results0000()) end,
    results00   = function (xpc, nl) return xpc:results000()..(nl or "") end,
    results0    = function (xpc, nl) return xpc:resultsempty() or xpc:results00(nl) end,
    results     = function (xpc, printresults, nl)
        if printresults then return xpc:results0(nl) end
        return ""
      end,
    tbshorter = function (xpc, tbe)
        local lines = splitlines(xpc.tb_string)
	return table.concat(lines, "\n", 1, #lines - (tbe or xpc.tb_e))
      end,
    outerror = function (xpc, nl)
        return xpc.err_msg .. xpc:tbshorter() .. (nl or "")
      end,
    out = function (xpc, printresults, nl)
        if xpc:success()
        then return xpc:results(printresults, nl)
        else return xpc:outerror(nl)
        end
      end,
    --
    -- The method :prcall(f, ...) is a variant of :call that
    --   "captures the outputs of the prints in :call(f, ...)". 
    -- The method :prout() returns the "output" of the call,
    --   _including the outputs of all "print"s in the call_.
    -- The method :pr0() creates the setup for capturing "print"s.
    -- The method :pr1() puts the outputs in xpc.pr_out.
    pr0 = function (xpc)
        xpc.pr_list = {}
        xpc.pr_oldprint = print
        print = function (...)
            table.insert(xpc.pr_list, print_to_string(...).."\n")
          end
        return xpc
      end,
    pr1 = function (xpc)
        print = xpc.pr_oldprint
        xpc.pr_out = table.concat(xpc.pr_list, "")
        return xpc
      end,
    prcall = function (xpc, ...)
        return xpc:pr0():call(...):pr1()
      end,
    prout = function (xpc, printresults, nl)
        return xpc.pr_out .. xpc:out(printresults, nl)
      end,
  },
}


-- «EdrxPcall-tests»  (to ".EdrxPcall-tests")
--[[

 (eepitch-lua51)
 (eepitch-kill)
 (eepitch-lua51)
dofile "EdrxPcall.lua"

= EdrxPcall.new():call(expr, "2,    3"):out("=", "\n")  --> "2  3\n"
= EdrxPcall.new():call(expr, "2,    3"):out("=")        --> "2  3"
= EdrxPcall.new():call(expr, "2,    3"):out()     --> ""
= EdrxPcall.new():call(expr, "2 + nil"):out()     --> errmsg/traceback

F2  = function (a, b) print("F2",  a, b); return F1(2, 3), 4 end
F1  = function (a, b) print("F1",  a, b); return F0(1, 2), 3 end
F01 = function (a, b) print("F01", a, b); return 0, 1 end
F00 = function (a, b) print("F00", a, b); error("F00 ERROR!!!") end

F0 = F01
  F2(3, 4)   --> prints F2/F1/F01
= F2(3, 4)   --> prints F2/F1/F01, 04

F0 = F00
  F2(3, 4)   --> prints F2/F1/F00, error, traceback
= F2(3, 4)   --> prints F2/F1/F00, error, traceback

F0 = F01
xpc = EdrxPcall.new():  call(F2, 3, 4)  --> prints F2/F1/F01
= xpc:out()                             --> prints ""
= xpc:out("=")                          --> prints "0  4"

xpc = EdrxPcall.new():prcall(F2, 3, 4)  --> prints nothing
= xpc:prout()                           --> prints F2/F1/F01
= xpc:prout("=")                        --> prints F2/F1/F01, 04

= xpc

F0 = F00
xpc = EdrxPcall.new():  call(F2, 3, 4)  --> prints F2/F1/F01
= xpc:out()                             --> prints error, traceback
= xpc:out("=")                          --> prints error, traceback

xpc = EdrxPcall.new():prcall(F2, 3, 4)  --> prints nothing
= xpc:prout()                           --> prints F2/F1/F01, error, traceback
= xpc:prout("=")                        --> prints F2/F1/F01, error, traceback

= xpc

--]]