Skip to content

Instantly share code, notes, and snippets.

@daurnimator
Created November 22, 2011 03:56
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save daurnimator/1384850 to your computer and use it in GitHub Desktop.
Save daurnimator/1384850 to your computer and use it in GitHub Desktop.
A replacement for lua errors; based on coroutines.
local select = select
local error = error
local cocreate , coresume , corunning , costatus , coyield = coroutine.create , coroutine.resume , coroutine.running , coroutine.status , coroutine.yield
local getinfo = debug.getinfo
local err_signifier = { }
local ec = { }
local function xpcall_resume ( co , hand , ok , ... )
local status = costatus ( co )
if status == "dead" then -- Either function finished, or a real error occured...
if ok then
return true , ...
else
return false , hand ( co , select ( 2 , ... ) )
end
elseif status == "suspended" then
if (...) == err_signifier then -- Has a recoverable error
return false , hand ( co , select ( 2 , ... ) )
else -- Normal yield
return xpcall_resume ( co , hand , coresume ( co , coyield ( ... ) ) )
end
else
error ( "I haven't thought what should be here yet" )
end
end
-- Change to normal xpcall behaviour: handler's first argument is the thread where the error occured
local function ec_xpcall ( func , hand , ... )
local co = cocreate ( func )
return xpcall_resume ( co , hand , coresume ( co , ... ) )
end
-- Just do pcall by using xpcall
local function ec_pcall ( func , ... )
local co = cocreate ( func )
return xpcall_resume ( co , function ( ... ) return ... end , coresume ( co , ... ) )
end
local rpcall_resume
local function recov_handler_handler ( co , errhand , recovhand , ok , ... )
if ok then -- Should we constrict to boolean values only?
return rpcall_resume ( co , errhand , recovhand , coresume ( co , ... ) )
else
-- Hmm, should errhand be called on an error in recovhand? No.
return false , ...
end
end
rpcall_resume = function ( co , errhand , recovhand , ok , ... )
local status = costatus ( co )
if status == "dead" then -- Either function finished, or a real error occured...
if ok then
return true , ...
else
return false , errhand ( co , ... )
end
elseif status == "suspended" then
if (...) == err_signifier then -- Has a recoverable error
return recov_handler_handler ( co , errhand , recovhand , recovhand ( co , select ( 2 , ... ) ) )
else -- Normal yield
return rpcall_resume ( co , errhand , recovhand , coresume ( co , coyield ( ... ) ) )
end
else
error ( "I haven't thought what should be here yet" )
end
end
-- Like xpcall but with additional handler: called when the error is recoverable
-- first return value signifies if the error was recovered;
-- followed by values to return to error-er.
-- default unrecoverable error handler is to raise an ec error if in a coroutine; or a lua error if in the main thread
local function ec_rpcall ( func , recovhand , errhand , ... )
errhand = errhand or function ( co , ... )
return ec.error ( ... )
end
local co = cocreate ( func )
return rpcall_resume ( co , errhand , recovhand , coresume ( co , ... ) )
end
-- Error can take extra arguments now => only useful for recoverable errors anyway
local function ec_error ( ob , lvl , ... )
lvl = lvl or 1
if corunning ( ) == nil then -- In main thread
if lvl == 0 then
else
lvl = lvl + 2
end
return error ( ob , lvl )
else
return coyield ( err_signifier , ob , lvl , ... )
end
end
local function check_assert ( ret , ... )
if ret then
return ret , ...
else -- Tried to resume with a non-true
return error ( "tried to resume an assert with a non-true value" , 2 )
end
end
local function ec_assert ( cond , ob , ... )
if cond then
return cond , ob , ...
else
if ob == nil then ob = "assertion failed!" end
if corunning ( ) == nil then -- In main thread
return error ( ob , 2 )
else
return check_assert ( coyield ( err_signifier , ob , lvl , ... ) )
end
end
end
return {
xpcall = ec_xpcall ;
pcall = ec_pcall ;
rpcall = ec_rpcall ;
error = ec_error ;
assert = ec_assert ;
}
local ec = require"ec"
for k , func in ipairs ({
function ( ... )
print ( "ARGS:" , ... )
print ( "THROW ERROR; returns:" , ec.error ( "An error" ) )
print ( "B" )
return "DONE"
end ;
function ( a , b , c )
a = ec.assert ( type ( a ) == "number" , "Not a number" )
b = ec.assert ( type ( b ) == "string" , "Not a string" )
return "DONE"
end ;
function ( )
error ( "Look, a normal lua error!" )
return "Won't get here"
end ;
}) do
print ( "DEMO" , k )
print ( "RPCALL returned",
ec.rpcall (
func ,
-- Recoverable error handler
function ( thread , ob )
print("RPCALL",thread,ob)
-- Returns if the error was recovered; and return arguments to the error raiser.
return true , 42
end ,
-- Unrecoverable error handler (can pass nil/false if you want the default)
function ( thread , ob )
error ( "OMG ERROR: " .. tostring ( ob ) )
end ,
"Arg1" , "Arg2" , "Arg3"
)
)
print ( )
end
PROMPT> luajit ec_test.lua
DEMO 1
ARGS: Arg1 Arg2 Arg3
RPCALL thread: 0x000795c8 An error
THROW ERROR; returns: 42
B
RPCALL returned true DONE
DEMO 2
RPCALL thread: 0x00079658 Not a number
RPCALL returned true DONE
DEMO 3
luajit: ec_test.lua:34: OMG ERROR: ec_test.lua:18: Look, a normal lua error!
stack traceback:
[C]: in function 'error'
ec_test.lua:34: in function 'errhand'
.\ec.lua:56: in function 'rpcall'
ec_test.lua:24: in main chunk
[C]: ?
PROMPT> lua ec_test.lua
DEMO 1
ARGS: Arg1 Arg2 Arg3
RPCALL thread: 0053CA68 An error
THROW ERROR; returns: 42
B
RPCALL returned true DONE
DEMO 2
RPCALL thread: 0053E740 Not a number
RPCALL returned true DONE
DEMO 3
lua: ec_test.lua:34: OMG ERROR: ec_test.lua:18: Look, a normal lua error!
stack traceback:
[C]: in function 'error'
ec_test.lua:34: in function 'errhand'
.\ec.lua:56: in function <.\ec.lua:50>
(tail call): ?
ec_test.lua:24: in main chunk
[C]: ?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment