1. Re: Standardized Euphoria -> Debug and Error Handling
- Posted by Chris Bensler <bensler at nt.net> Jan 13, 2007
- 554 views
Getting back to something that is a little more productive.. I'll offer a few proposals for some of the things that I think should be staple parts of the standardized libraries. std/core/debug.e -- this lib is concept only. I've not prototyped it yet. provides a standardized way for library developers to incorperate debug code into their work without the need for an explicit set of debug routines. In this way an end programmer could toggle debugging overall, for their whole project and rely on uniform behaviour. I'll have to dwell on this one more to give better details. Right now I just wanted to instigate consideration and discussion. std/core/error.e -- this lib has been prototyped IMO it's been very successful and has saved me loads of time as well as making code much cleaner/clearer. There is two levels of functionality to error.e: Direct error handling and exception error handling. Perhaps the file should be offered as two. One for the lower level, direct error handling and another for the exception handling. Basically, the direct error handling is comprised of the 3 crash_* functions plus fatal_error(), abort_error() and error() fatal_error() is particularly for runtime debugging and forces an ex.err dump by calling ? 1/0. The only problem here is that the applications halt within the fatal_error routine and the ex.err traceback has to be followed out of the error branch. I've not experimented with crash_routine(). It might be possible to rectify the issue using it. Either way I find the abstracted traceback to be a minor inconvenience and the benefits of generic error handling hugely outweigh it. abort_error() is a generic routine for graceful errors. error() uses a custom definable error routine, allowing function chains to be created. The default error() behaviour is to call fatal_error() The exception handling would be better if it were supported internally, but I find this library method is a good alternative. It works on a throw and catch mechanism. Developers define exception handlers using callback routines. When an error is encountered, data can be associated with an exception handler and thrown. Thrown exceptions can be configured to cause an immediate error or can be queued for catching later or a hybrid of both where an existing error will be triggered only if an error is already pending. This can be very useful for localizing the error handling to the libraries instead of making it the end programmers resposibility. The end programmer need only check if an error occurred instead of testing all kinds of error conditions. Eg..
global constant ERROR_FOPEN = define_error(NULL) -- just use the fefault error() routine global function fopen(sequence fname, sequence flags) integer fn fn = open(fname, flags) if fn = -1 then throw_error(ERROR_FOPEN,"failed to open "&fname) end if return fn end function
It can then be called like..
allow_error_trapping(TRUE) allow_pending_errors(TRUE) fn1 = fopen("foo.txt","w") fn2 = fopen("bar.txt","w") fn3 = fopen("fubar.txt","w") trap_error(ERROR_FOPEN) -- NULL could be used instead of ERROR_FOPEN if you only care if ANY exception has occurred.
Here is the library code: (if you don't have or feel like getting the ELF libs I posted earlier, you can easily port this lib so it doesn't rely on declares.e)
include std/core/declares.e --------------------------------------------------------------------------------- -- BASIC ERROR ROUTINES -- -- force a fatal error and display the given msg -- use this to force a debug dump to ex.err global procedure fatal_error(sequence msg) crash_message("Fatal Error: "&msg&LF) ? 1/0 end procedure -- display a msg on STDERR, wait for the user to press a key, then abort with the specified code -- (graceful error termination) global procedure abort_error(integer code,sequence msg) if length(msg) then puts(STDERR,msg&LF) end if puts(STDERR,"\r\npress any key\r\n") machine_proc(M_WAIT_KEY, NULL) abort(code) end procedure -- default callback for the error routine, below integer error_routine error_routine = routine_id("fatal_error") -- use this routine to define a custom error routine global procedure set_error_routine(integer id) if id < 0 then fatal_error("an invalid routine_id was specified for set_error_routine()") end if error_routine = id end procedure -- fetch the routine id of the current error routine global function get_error_routine() return error_routine end function -- This error routine will call fatal_error() by default. -- You can define your own custom error handler with set_error_routine() global procedure error(object args) call_proc(error_routine,{args}) end procedure --------------------------------------------------------------------------------- -- ADVANCED, ERROR TRAPPING ROUTINES -- -- By defining error id's and associating handlers, errors can be handled almost automatically -- while maintaining, and even improving flexibility. -- These routines make it possible to throw an error from one routine back through the call stack -- where they can be trapped and handled automatically using the defined error handler -- or they can be caught and handled manually at the program level. sequence ErrorIndex ErrorIndex = {} sequence PendingIDs PendingIDs = {} sequence PendingData PendingData = {} integer Error_Trapping_Allowed Error_Trapping_Allowed = TRUE integer Pending_Errors_Allowed Pending_Errors_Allowed = FALSE -- defines a new error id and associated handler -- if handler_id is 0 the default error routine will be used global function define_error(integer handler_id) if handler_id = NULL then handler_id = error_routine -- default error handler end if ErrorIndex &= handler_id return length(ErrorIndex) end function -- redefines an error handler -- like above, but instead of creating a new ID and handler, -- this redefines a new handler for the existing ID global procedure redefine_error(integer error_id, integer handler_id) if error_id > 0 and error_id <= length(ErrorIndex) then ErrorIndex[error_id] = handler_id end if end procedure -- error_id is a valid error_id, returned from re/define_error(), or NULL -- if error_id is NULL, catch_error() returns TRUE (1) if there are any errors in the stack -- returns FALSE if the error is not in the pending errors stack -- returns the stack value, if the specified error is pending -- errors are caught in FIFO order, so the first error thrown will be 1, and -- the last error thrown will be length(pending_ids) -- if an error is caught, the value returned is the index to the first occurance of the given error -- in the pending errors stack global function catch_error(integer error_id) if not error_id then return (length(PendingIDs) != 0) else return find(error_id,PendingIDs) end if end function procedure drop_error2(integer stack_id) integer l l = length(PendingIDs) PendingIDs = PendingIDs[1..stack_id-1] & PendingIDs[stack_id+1..l] PendingData = PendingData[1..stack_id-1] & PendingData[stack_id+1..l] end procedure -- drop_error() allows you to test for the existence of a pending error, and eliminate it. global function drop_error(integer error_id) integer stack_id stack_id = catch_error(error_id) if stack_id then drop_error2(stack_id) end if return (stack_id != FALSE) end function function fetch_error2(integer stack_id) return PendingData[stack_id] end function -- fetch_error() allows you to retrieve the error data for a specified error id. global function fetch_error(integer error_id) integer stack_id stack_id = catch_error(error_id) if stack_id then return fetch_error2(stack_id) end if return FALSE end function global procedure intercept_error2(integer stack_id,integer error_id,sequence args) integer alen,dlen alen = length(args) dlen = length(PendingData[stack_id]) if alen < dlen then args &= PendingData[stack_id][alen+1..dlen] end if PendingIDs[stack_id] = error_id PendingData[stack_id] = args end procedure -- intercept_error() is a funnny one. This will allow you to test for the existence of an error, -- if one is found, it is replaced with the new_error_id and the error data is replaced with args. global procedure intercept_error(integer old_error_id,integer new_error_id,sequence args) integer stack_id stack_id = catch_error(old_error_id) if stack_id then intercept_error2(stack_id,new_error_id,args) end if end procedure -- Clears any pending errors for the specific error_id -- If error_id is 0, all pending errors will be cleared. global procedure flush_error(integer error_id) integer stack_id stack_id = catch_error(error_id) while stack_id do drop_error2(stack_id) stack_id = catch_error(error_id) end while end procedure --------------------------------------------------------------------------------- -- MAIN ERROR ROUTINES -- -- enable or disable error trapping. -- Error trapping makes it possible to stack errors, to be dealt with later. -- if error trapping is disabled, allow_pending_errors() will have no affect. global procedure allow_error_trapping(integer enabled) Error_Trapping_Allowed = enabled end procedure -- enable or disable pending errors. -- With pending errors enabled, the programmer can stack multiple errors, for handling later. global procedure allow_pending_errors(integer enabled) Pending_Errors_Allowed = enabled end procedure -- tells you if error trapping is allowed global function is_error_trapping_allowed() return Error_Trapping_Allowed end function -- tells you if pending errors are allowed global function is_pending_errors_allowed() return Pending_Errors_Allowed end function -- checks for the existence of the given error_id on the pending error stack. -- if the specified error is found, the error handler associated with the given error_id is called for the first occurance of the error. -- it is possible to have multiple instances of the same error on the pending stack. -- if the error handler is not fatal, you can continue to process the same error_id, until all errors are handled. global procedure trap_error(integer error_id) integer stack_id,l if Error_Trapping_Allowed then if not Pending_Errors_Allowed then error_id = NULL end if stack_id = catch_error(error_id) if stack_id then l = length(PendingIDs) call_proc(ErrorIndex[PendingIDs[stack_id]],{PendingData[stack_id]}) PendingIDs = PendingIDs[1..stack_id-1] & PendingIDs[stack_id+1..length(PendingIDs)] PendingData = PendingData[1..stack_id-1] & PendingData[stack_id+1..length(PendingData)] end if end if end procedure -- puts a new error on the pending error stack global procedure throw_error(integer error_id, object data) if Error_Trapping_Allowed then if not Pending_Errors_Allowed then trap_error(NULL) end if PendingIDs &= error_id PendingData &={ data } else call_proc(ErrorIndex[error_id],data) end if end procedure
Chris Bensler ~ The difference between ordinary and extraordinary is that little extra ~ http://empire.iwireweb.com - Empire for Euphoria