Created
November 22, 2011 03:56
-
-
Save daurnimator/1384850 to your computer and use it in GitHub Desktop.
A replacement for lua errors; based on coroutines.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 ; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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