Warning: this is an htmlized version!
The original is here, and the conversion rules are here. |
-- This file: edrxrepl.lua - a REPL for Lua. -- http://angg.twu.net/edrxrepl/edrxrepl.lua.html -- http://angg.twu.net/edrxrepl/edrxrepl.lua -- (find-angg "edrxrepl/edrxrepl.lua") -- https://github.com/edrx/edrxrepl -- https://github.com/edrx/edrxrepl#Introduction -- -- 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: 2021jan11 -- License: GPL3 at this moment. -- If you need another license, get in touch! -- «.Class» (to "Class") -- «.MyXpcall» (to "MyXpcall") -- «.MyXpcall-class» (to "MyXpcall-class") -- «.MyXpcall-tests» (to "MyXpcall-tests") -- «.Repl» (to "Repl") -- «.Repl-tests» (to "Repl-tests") -- 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 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 -- «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) -- __ __ __ __ _ _ -- | \/ |_ _\ \/ /_ __ ___ __ _| | | -- | |\/| | | | |\ /| '_ \ / __/ _` | | | -- | | | | |_| |/ \| |_) | (_| (_| | | | -- |_| |_|\__, /_/\_\ .__/ \___\__,_|_|_| -- |___/ |_| -- -- «MyXpcall» (to ".MyXpcall") -- See: (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-es "lua5" "xpcall-2020") -- -- If a Lua REPL calls F2, which calls F1, which calls F0, which -- yields an error, we have this call diagram: -- -- REPL -- : \-> F2 -- : \-> F1 -- : \-> F0 -- : \-> error -- : |-> print(debug.traceback()) -- v <---------------/ -- -- Lua's "xpcall" lets us do something similar to that outside the Lua -- REPL, but in a way that I found very difficult to decypher and -- quite difficult to use. My MyXpcall class is a wrapper around -- xpcall that lets me use xpcall in a way that 1) has good defaults, -- 2) is super-easy to hack, 3) lets me change the defaults easily, 4) -- saves all the intermediate results. If I do -- -- myx = MyXpcall:new() -- -- then the call diagram of myx:call(F2) on errors is: -- -- myx:call(F2) -- : \-> F2 -- : \-> F1 -- : \-> F0 -- : \-> myx.errhandler -- : |-> debug.traceback -- : |-> print(myx:shortertraceback()) -- : <---------------/ -- v -- -- and these fields get set: -- -- myx.xp_results: the results of xpcall(wrapper_around_F2) -- myx.eh_args: the arguments given to myx.errhandler -- myx.tb_string: the string returned by debug.traceback() -- -- when F2 succeeds these fields get set: -- -- myx.f_results: the results of F2(...) -- myx.xp_results: the results of xpcall(wrapper_around_F2) -- -- For more details see -- the code and the tests. -- «MyXpcall-class» (to ".MyXpcall-class") -- Also in: (find-angg "LUA/lua50init.lua" "MyXpcall") -- Note that we always use these abbreviations: -- tb for traceback, -- eh for errhandler, -- xp for xpcall. -- -- Fields with "_"s are variables, and -- fields without "_"s are methods. -- MyXpcall = Class { type = "MyXpcall", new = function (T) return MyXpcall(T or {tb_lvl = 3}) end, __index = { call = function (myx, f, ...) return myx:call0(f, ...):ret() end, call0 = function (myx, f, ...) local f_args = pack(...) local g = function () myx.f_results = pack(f(unpack(f_args))) end myx.xp_results = pack(xpcall(g, myx:eh())) return myx end, -- tb = function (myx) myx.tb_string = debug.traceback(myx:tbargs()) local lines = splitlines(myx.tb_string) myx.tb_shorter = table.concat(lines, "\n", 1, #lines - 6) return myx.tb_shorter end, eh = function (myx) return function (...) myx.eh_args = pack(...) print(myx:tb()) return "(eh returns this)" end end, -- success = function (myx) return myx.xp_results[1] end, errmsg = function (myx) return myx.eh_args[1] end, tbargs = function (myx) return myx:errmsg(), myx.tb_lvl end, results = function (myx) return unpack(myx.f_results) end, ret = function (myx) if myx:success() then return myx:results() end end, }, } -- «MyXpcall-tests» (to ".MyXpcall-tests") --[[ * (eepitch-lua51) * (eepitch-kill) * (eepitch-lua51) dofile "edrxrepl.lua" fcode = function (n) return format("F%d = function (...) return 0,F%d(...) end", n+1, n) end -- Test calling a function -- that yields an error -- F0 = function (...) PP(...); error("Errrr!") end for i=0,20 do print(fcode(i)) end for i=0,20 do eval(fcode(i)) end F20(2, 3, 4) F20 (2, 3, 4) myx = MyXpcall.new():call0(F20, 2, 3, 4) PP(myx:ret()) --> PP(myx:success()) --> <false> PP(myx:errmsg()) --> "stdin:1: Errrr!" PP(myx.xp_results) --> {1=<false>, 2="(eh returns this)", "n"=2} PP(myx.eh_args) --> {1="stdin:1: Errrr!", "n"=1} PPV(sorted(keys(myx))) -- Test calling a function -- that succeeds -- F0 = function (...) PP(...); return 22,33 end F20 (2, 3, 4) myx = MyXpcall.new():call0(F20, 2, 3, 4) PP(myx:success()) --> <true> PP(myx:ret()) --> 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 22 33 PP(myx.xp_results) --> {1=<true>, "n"=1} PPV(sorted(keys(myx))) --]] -- ____ _ -- | _ \ ___ _ __ | | -- | |_) / _ \ '_ \| | -- | _ < __/ |_) | | -- |_| \_\___| .__/|_| -- |_| -- -- «Repl» (to ".Repl") -- Also in: (find-angg "LUA/lua50init.lua" "Repl") -- A simple REPL for Lua that handles errors well enough. -- It uses MyXpcall to run code. -- Usage: -- -- REPL = Repl:new(); REPL:repl() -- -- To exit the REPL set REPL.stop to true. -- -- NOTE ON '='s: The Repl class implements a trick that is found in -- the REPLs of Lua5.1 and Lua5.2 but was dropped from Lua5.3 onwards. -- It is explained in the manpages for lua5.1 and lua5.2 as: -- -- If a line starts with '=', then lua displays the values of all -- the expressions in the remainder of the line. The expressions -- must be separated by commas. -- -- In Lua5.3 they changed that to this (also copied from the manpage): -- -- If the line contains an expression or list of expressions, then -- the line is evaluated and the results are printed. -- -- See for example this thread: -- http://lua-users.org/lists/lua-l/2020-10/msg00209.html Repl = Class { type = "Repl", new = function () return Repl({}) end, __index = { bigstr = function (r) return table.concat(r.lines, "\n") end, errincomplete = function (r, err) return err:find(" near '?<eof>'?$") end, incompletep0 = function (r, bigstr) local f, err = loadstring(bigstr) return (f == nil) and r:errincomplete(err) end, code = function (r) return r:bigstr():gsub("^=", "return ") end, incompletep = function (r) return r:incompletep0(r:code()) end, read00 = function (r, prompt) io.write(prompt); return io.read() end, read0 = function (r, prompt) table.insert(r.lines, r:read00(prompt)) end, read1 = function (r) return r:read0 ">>> " end, read2 = function (r) return r:read0 "... " end, read = function (r) r.lines = {} r:read1() while r:incompletep() do r:read2() end return r end, evalprint = function (r) r.f, r.err = loadstring(r:code()) if not r.f then print(r.err); return r end r.myx = MyXpcall.new():call0(r.f) if r.myx:success() and r:bigstr():match("^=") then r:print() end return r end, print = function (r) print(unpack(r.myx.f_results)) end, repl = function (r) while not r.stop do r:read():evalprint() end end, }, } -- «Repl-tests» (to ".Repl-tests") -- (find-elisp-intro "5. Variables" "If you execute lines 1, 3, and 4") -- (find-angg "LUA/lua50init.lua") --[[ * (eepitch-shell) * (eepitch-kill) * (eepitch-shell) export LUA_INIT="" export LUA_INIT=@$HOME/LUA/lua50init.lua lua5.1 lua5.2 lua5.3 lua5.4 PP() * (eepitch-lua51) * (eepitch-kill) * (eepitch-lua51) dofile "edrxrepl.lua" REPL = Repl.new() REPL:repl() print( 1+2 ) = 1+2 = 1, 2, 3 = nil, 22 = fcode = function (n) return format("F%d = function (...) return 0,F%d(...) end", n+1, n) end F0 = function (...) PP(...); error("Errrr!") end for i=0,20 do print(fcode(i)) end for i=0,20 do eval(fcode(i)) end F20(2, 3, 4) print(REPL.myx.tb_string) REPL.stop = 1 print(eval) --]]