Skip to content

Instantly share code, notes, and snippets.

@alexshpilkin
Last active April 29, 2022 00:08
Show Gist options
  • Save alexshpilkin/23d3e0a8a6e6e6510874e99ffe5fc079 to your computer and use it in GitHub Desktop.
Save alexshpilkin/23d3e0a8a6e6e6510874e99ffe5fc079 to your computer and use it in GitHub Desktop.
Condition handling in Lua
local M = {}
local error, unpack = error, unpack or table.unpack
local running = coroutine.running
local stderr = io.stderr
local exit = os.exit
local insert, remove = table.insert, table.remove
-- conditions
local handlers = setmetatable({}, {
__mode = 'k', -- do not retain dead coroutines
__index = function (self, key) -- no handlers by default
self[key] = {}; return self[key]
end,
})
local function removing(xs, x, ok, ...)
assert(remove(xs) == x)
if ok then return ... else error(...) end
end
-- establish a handler during call
function M.hcall(h, f, ...)
local hs = handlers[running()]
insert(hs, h)
return removing(hs, h, pcall(f, ...))
end
-- signal the given condition to currently active handlers
function M.signal(...)
local hs = handlers[running()]
for i = #hs, 1, -1 do hs[i](...) end
end
local signal = M.signal
function M.error(...)
signal(...)
stderr:write("error: " .. tostring(...) .. "\n")
exit(1)
end
function M.warn(...)
signal(...)
stderr:write("warning: " .. tostring(...) .. "\n")
end
-- restarts
-- invoke the given restart
function M.restart(r, ...)
local n = select('#', ...); r.n = n
for i = 1, n do r[i] = select(i, ...) end
error(r)
end
local function continue(r, ok, ...)
if ok then return ok, ... end
if ... == r then return false, unpack(r, 1, r.n) end
error(...)
end
-- establish a restart during call
function M.rcall(f, ...)
local r = {}
return continue(r, pcall(f, r, ...))
end
return M
local cond = require 'cond'
-- common condition types (XXX should use proper dynamic variables instead)
local retry, use = nil, nil
-- I/O library
local function _gets()
if math.random() < 0.5 then cond.error 'lossage' end
return 'user input'
end
local function gets()
local ok, value = cond.rcall(function (_use)
use = _use
local ok, value
repeat ok, value = cond.rcall(function (_retry)
retry = _retry
return _gets()
end) until ok
return value
end)
-- ok or not, we got a value either way
return value
end
-- application (knows nothing about errors)
local function app()
for i = 1, 5 do print(string.format("got: %q", gets())) end
return "success"
end
-- shell
local ok, value = cond.rcall(function (abort)
return cond.hcall(function (err)
io.stderr:write("I/O error: " .. err .. "\n")
while true do
io.stderr:write("[a]bort, [r]etry, [u]se value? ")
local answer = io.read('*l')
if answer == 'a' then cond.restart(abort, "aborted") end
if answer == 'r' then cond.restart(retry) end
if answer == 'u' then
io.stderr:write("value? ")
cond.restart(use, io.read('*l'))
end
end
end, app)
end)
print(ok, value)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment