Good day fellow monastery dwellers! ;-)

This is my first attempt at implementing an exception handling mechanism similar to how it is done in Java/C++ yet compatible with existing eval/die Perl mechanism.

Read my code comments for more info. I'd be happy to receive any kind of constructive feedback. Note, that this is not my attempt to replace any existing Exception handling packages/classes. I'm simply extending the one that I had used in my for some time. Frankly, based on a number of searches that I'd done on CPAN there were only a handful of somewhat similar packages, however, they didn't meet my needs... The purpose for thise effort, therefore, may be to find another 'alternative' approach at error handling. Anyhow, just read the comments to get a better idea at what's going on here ;-)

Thank you in advance for your support.

Exception.pm
package Exception; # DESCRIPTION: # # An attempt at writing an exception handling package # implementing exception handling mechanism similar # to those present in Java/C++. This package # (and the Exception class it implements) may also # be used to encapsulate any piece of code still relaying # on try/die as a means to track program failures. # For example, catch() method is coded to recognize # and deal with die strings ($@ scalar). There are # other approaches at implementing similar exception # mechanism where $@ would hold an exception object. # This package, however, avoids storing # object refs in $@ (e.g. via die $object) in order # to be reverse compatible with code relaying on # $@ being a scalar (conventional try/die mechanism). # Such may be a case if the code implementing/utilizing # this Exception package is used inside a different # package/program that has no knowledge of the Exception # package. # # Here're a few examples of how Exception package could # be used inside your code: # # sub foo { # for ((my $i=10;$i>-10;$i--) { # print "Result: ". (10/$i) ."\n"; # } # } # # (by the way, i _do_ hate having to see # that 'sub' word right after try... # since there's no other way to # pass a block of code as an argument other than # via the anonymous sub/closure) # # try sub { # foo(); # }; # # # catch _all_ exceptions here # if (my $exception = catch()) { # # will log error and quit. # $exception->fatal(); # } # # Note: relating to the use of 'try sub { ... };' construct you # may replace it in favor of 'eval { }' unless you require special # exception handling (which may only be done witih the try() sub, # at least in the future implementations ;-) # # Therefore an alternative code might look something like this: # eval { # foo(); # }; # # # catch all exceptions # if (my $exception = catch()) { # # will log error and quit. # $exception->fatal(); # } # # # EXAMPLE 1: # # All these examples dealt with exceptions caused # by an 'uncontrolled' (meaning none of the Exeption # methods was invoked to cause them) die. However, # lets also look at how throw() works. # # use Exception; # # sub foo { # for ((my $i=10;$i>-10;$i--) { # if ($i == 0) { # # create and throw a new exception # throw new Exception, "Can't devide by 0!"; # } # print "Result: ". (10/$i) ."\n"; # } # } # # eval { # foo(); # }; # # # catch any exception (even that # # caused by an uncontrolled die) # if (catch()) { # $_->fatal(); # handle exception # } # # print "did ok\n"; # # EXAMPLE 2: # # Here's how throwable() might be used. # (note, sub foo() is unchanged) # # use Myexception; # # eval { # # expecting exception 'MyException' # # (so, in case of an uncontrolled die() # # a 'MyException' object will be created) # # Note that throwable('Exception') is # # 'implied' by default even if throwable() # # is not invoked here explicitly # # since any die should cause an exception. # # # throwable('MyException'); # foo(); # }; # # # catch Myexception exception object # # note: this object is infact created # # inside the catch() method # # based on value set by throwable(). # # This will only happen if there was an # # uncontrolled die! That is, if # # throwable() was followed by a # # throw() somewhere inside the same # # eval{} block, an exception object # # created by throw() will be generated # # and returned by this catch(). # if (catch()) { # $_->fatal(); # handle exception # } # # More examples are coming soon... # (I still keep most of them in my head) # use Devel::StackTrace; require Exporter; @ISA = qw(Exporter); @EXPORT = qw(try throw catch throwable); $::trace_depth = 10; $::error_text = ""; # $@ # Recent exception (works similar to $@ for die()) # Cleared only after it's caught. $::recent = undef; # Name of an exception to expect in case # of an uncontrolled (without explicit throw()) die. $::throwable = __PACKAGE__; @::stack_items = qw(package filename line subroutine hasargs wantarray evaltext is_require hints bitmask); sub new { # new() may be invoked via an existing object # like: $exception_object->new() # or via package name # like: new Exception() # or: Exception::new() # Therefore, ref() will return object's # class name if first case is true; otherwise, # name of this package will be used (from $_[0]) my $class = ref($_[0]) || $_[0]; $DB::single = 1; my $self = { # text of exception (optional) info => $_[1], # i figure this data is useful to have? time => CORE::time(), pid => $$, uid => $<, euid => $>, gid => $(, egid => $), stack => new Devel::StackTrace(ignore_package => __PACKA +GE__), }; bless($self, $class); } # standard accessor to property 'info' sub info { return $_[0]->{info} if wantarray; $_[0]->{info} = $_[1]; } ################################# ## SUBS THAT MAY BE REDEFINED ################################# sub trace_as_string { $DB::single = 1; my $self = shift; my $trace_str; $trace_str = $self->{stack}->as_string(); return $trace_str; } sub as_string { my $self = shift; return "\nEXCEPTION!!" . "\nINFO:\n " . $self->{info} . "\nTRACE:\n " . $self->trace_as_string() . "\n"; } # fatal(error code) # # Cause program to die! Print exception information # to STDERR and exit with given error code # or 1 by default. sub fatal { my ($self, $errcode) = ($_[0], $_[1] || 1); print STDERR $self->as_string(); exit($errcode); } ################################# ## CLASS METHODS ################################# # catch(list of exception names) # sub catch { # $@ should always be set (either via # die() or throw()) return undef unless $@; $::error_text = $@; $@ = ""; unless ($::recent) { # if no 'throw()' was invoked # $::recent will not not be set # and therefore, we have to check the # $@ variable set by an uncontrolled # die(). In such case, # we'll create a default throwable # exception (set with throwable()) # $DB::single = 1; $::throwable ||= __PACKAGE__; $::recent = eval('new '.caller().'::'. $::throwable .'()'); if ($@) { die "Failed to initialize '$::throwable' exception!\n" ."Check that throwable('YourClassName') refers to existing cl +ass.\n"; } $::recent->info($::error_text); } # look at list of exceptions that # have to be caught and return # recent exception if a match is found. # Default throwable exception name is used if no other # list of exceptions to look for was specified (in the # argument list) # my $recent_type = ref($::recent); for (@_ || $::throwable) { return get_recent() if $recent_type =~ m/$_/; } # rethrow uncaught exception throw($::recent); } sub clear_recent { $::recent = undef; } sub get_recent { my $bug = $::recent; $::recent = undef; $::throwable = ""; return $bug; } # try a block of code (anonymous sub) # do nothing more (for now) than a simple # eval. sub try { # return if not dealing with code! return unless (ref $_[0] eq 'CODE'); # make sure that the sub is executed # in its own (native) package! my $package = ref $_[0]; eval "package $package; " . '$_[0]->();'; } # should receive a reference to an Exception # object sub throw { $_[0]->info($_[1]) if @_ == 2; # save exception, which will be examined # later inside catch() $::recent ||= $_[0]; # cause die to interrupt program # (normally this will simply cause # eval {} inside try() to return # having $@ set to die string!) die $_[0]->info(); } sub throwable { # implements: # eval { # throwable 'Exception'; # ... # } # this tells which exception object # should be created on an uncontrolled die. # Any throw() statements inside # the block are still valid and may be # used to throw other than the default # exception. # $::throwable = $_[0]; } 1;


"There is no system but GNU, and Linux is one of its kernels." -- Confession of Faith

In reply to Exception handling (alternatives) by vladb

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post, it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.