lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


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.