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 --]]