Re: Standardized Euphoria -> Debug and Error Handling

new topic     » topic index » view thread      » older message » newer message

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

new topic     » topic index » view thread      » older message » newer message

Search



Quick Links

User menu

Not signed in.

Misc Menu