[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Why you need coroutines: pcall and error without pcall and error
- From: "Soni L." <fakedme@...>
- Date: Fri, 24 Feb 2017 12:16:19 -0300
Original post:
https://gist.github.com/SoniEx2/ab5a2caa5f41347db7b83a9b455d0192/28a20b6ead0a72a655b86646d7947a597e696d07#why-you-need-coroutines-pcall-and-error-without-pcall-and-error
(short link: https://git.io/vyJ2X)
This version is tweaked to be more complete (includes assert and xpcall).
# Why you need coroutines: pcall and error without pcall and error.
So, let's take Lua, remove pcall and error, and tweak coroutines a bit:
```lua
-- NOTE: THIS IMPLEMENTATION IS UNTESTED AND MAY CONTAIN BUGS.
-- get rid of native pcall and error (and stuff)
pcall = nil
error = nil
xpcall = nil
assert = nil
-- we're gonna wrap coroutines, so this is important
local cocreate, coresume, costatus, corunning = coroutine.create,
coroutine.resume, coroutine.status, coroutine.running
local isdead = {}
setmetatable(isdead, {__mode="k"})
-- reimplement coroutine.resume
coroutine.resume = function(...)
return (function(ok, ...) if ok then return ... else return ok, ...
end end)(coresume(...))
end
-- reimplement coroutine.yield
coroutine.yield = function(...)
return coyield(true, ...)
end
-- reimplement coroutine.status
coroutine.status = function(co)
if isdead[co] == true then return "dead" else return costatus(co) end
end
-- reimplement coroutine.wrap
coroutine.wrap = function(f)
local co = cocreate(f)
return function(...)
return (function(...)
if ... then
return select(2, ...)
else
-- note: using global. will be redefined below.
error((select(2, ...)))
end
end)(coroutine.resume(co)) -- note that we replaced
coroutine.resume above
end
end
-- reimplement error on top of coroutines
error = function(msg, level)
local f = isdead[corunning()] -- ew, but eh it works \o/
isdead[corunning()] = true
if f then
if not pcall(f, msg) then -- uses our own pcall
level = 0
msg = "error in message handler"
end
end
coyield(false, msg, level or 1) -- uses raw/internal coyield, not the
wrapper we created above
while true do coyield(false, "attempt to resume a dead coroutine") end
end
-- reimplement pcall on top of (our) xpcall
pcall = function(f, ...)
return xpcall(f, nil, ...)
end
-- reimplement xpcall on top of coroutines
xpcall = function(f, msgh, ...)
local co = cocreate(f)
assert(msgh ~= true, "bad argument #2 (function expected, got
boolean)") -- lol
isdead[co] = msgh
local function recurse2(recurse, ...)
return recurse(coroutine.resume(co, ...))
end
local function recurse(ok, ...)
if coroutine.status(co) == "dead" then
return ok, ...
else
return recurse2(recurse, coroutine.yield(...))
end
end
return recurse2(recurse, ...)
end
-- assert is a simple wrapper around error
assert = function(...)
local ok, err = ...
if not ok then error(err or "assertion failed") end
return ...
end
```
And now we have pcall and error implemented on top of coroutines (a
"high-level" pcall and error).
--
Disclaimer: these emails may be made public at any given time, with or without reason. If you don't agree with this, DO NOT REPLY.