Skip to content

Instantly share code, notes, and snippets.

@SoniEx2
Last active February 24, 2017 19:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SoniEx2/ab5a2caa5f41347db7b83a9b455d0192 to your computer and use it in GitHub Desktop.
Save SoniEx2/ab5a2caa5f41347db7b83a9b455d0192 to your computer and use it in GitHub Desktop.

You don't need special syntax - Why you need coroutines: Try-catch in Lua.

local t = try(file(io.open("/dev/null", 'w')), function(devnull)
  devnull:write("stuff")
  return {}
end).catch("IOException", function(e)
  -- ignore it
end).finally()

The call to finally is required. When called, finally first calls the function from try in a coroutine, then if that coroutine yields an IOException in a specific way, it calls the function from catch, or if another type of exception is yielded, it yields to the parent try-catch, and so on. Either way, it calls finally after exiting the try, and automatically closes any associated resources (which need to be wrapped with file() or closeable() or some other wrapper, since you can't shove varargs/varrets in the middle of the args list and stuff).

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:

-- NOTE: THIS IMPLEMENTATION IS UNTESTED AND MAY CONTAIN BUGS.

-- get rid of native pcall and error
pcall = nil
error = 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] 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)
  isdead[corunning()] = true
  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 coroutines
pcall = function(f, ...)
  local co = cocreate(f)
  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

-- xpcall is similar, except the error handler needs to be called by error().

And now we have pcall and error implemented on top of coroutines (a "high-level" pcall and error).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment