NAME
Worlogog::Incident - Lisp-style resumable exceptions (conditions)
SYNOPSIS
use Worlogog::Incident -all => { -prefix => 'incident_' };
use Worlogog::Restart -all => { -prefix => 'restart_' };
sub log_analyzer {
incident_handler_bind {
for my $log (find_all_logs()) {
analyze_log($log);
}
} [
'MalformedLogEntryError' => sub {
restart_invoke 'skip_log_entry'; # ignore invalid log entries
# we could also do this:
#restart_invoke 'use_value' => MalformedLogEntry->new(text => $_[0]); # use invalid log entries as-is
},
];
}
sub analyze_log {
my ($log) = @_;
for my $entry (parse_log_file($log)) {
analyze_entry($entry);
}
}
sub parse_log_file {
my ($file) = @_;
open my $fh, '<', $file or die "$0: $file: $!\n";
my @results;
while (my $text = readline $fh) {
chomp $text;
my $entry = restart_case {
parse_log_entry($text)
} {
skip_log_entry => sub { undef },
};
push @results, $entry if $entry;
}
@results
}
sub parse_log_entry {
my ($text) = @_;
if (is_well_formed_log_entry($text)) {
return LogEntry->new(... $text ...); # parsing details omitted
}
restart_case {
incident_error MalformedLogEntryError->new(text => $text);
} {
use_value => sub { $_[0] },
reparse_entry => sub { parse_log_entry($_[0]) },
}
}
DESCRIPTION
This module provides resumable exceptions ("conditions") similar to those found in Common Lisp. A condition is a bit like an exception where the exception handler can decide to do other things than unwind the call stack and transfer control to itself.
A note on naming: This module doesn't follow Common Lisp terminology (there are conditions and you can signal them) because I think it would be confusing: The controlling expressions in if
or while
are also called conditions, and "signal" is more closely associated with %SIG
and kill
.
Instead I will refer to incidents, which you can report.
Functions
The following functions are available:
- handler_bind BLOCK CODEREF
- handler_bind BLOCK ARRAYREF
-
Executes BLOCK while registering the incident handler(s) speficied by CODEREF or ARRAYREF (see below). If (during the execution of BLOCK) an incident is reported, the innermost registered handler is called with one argument (the incident object).
An incident handler can either return normally, which causes the incident machinery to call the next active handler on the stack, or abnormally, which terminates the incident report. Abnormal returns are possible directly with Return::MultiLevel or indirectly via Worlogog::Restart. That is, an incident handler can abort an incident in progress by invoking a restart that transfers control to an outer point in the running program.
If an ARRAYREF is used, it is passed on to
dispatch
inDispatch::Class
(which see for details) to assemble the incident handler. The format of the ARRAYREF is that of a dispatch table, i.e. a list ofCLASS, CODEREF
pairs where CLASS specifies what class of exception objects should be handled (use the pseudo-class:str
to match plain strings), and CODEREF is the actual body of the handler. - handler_case BLOCK CODEREF
- handler_case BLOCK ARRAYREF
-
Similar to
handler_bind
above. The main difference is that an incident handler established byhandler_case
has to decide up front whether to handle the incident, and if it does, the stack is unwound first.The way this is done depends on the second argument: If it's an ARRAYREF, the class of the incident object decides whether a handler (and which one) is entered.
If it's a CODEREF, it is called with the incident object as its first (and only) argument. If the CODEREF returns another coderef, that is taken to be the handler (i.e. the stack is unwound, the returned (inner) coderef is called with the incident object, and the return value of the inner coderef is returned from
handler_case
). If the CODEREF returns undef, the search for an incident handler that feels responsible continues outwards. - report INCIDENT
-
Reports an incident; i.e. it searches the call stack for all appropriate incident handlers registered with
handler_bind
orhandler_case
. It calls them in order from innermost (closest to thereport
call) to outermost (closest to the main program), then returns.An incident handler can decline to handle a particular incident by returning normally. Conversely, an incident handler can stop a report in progress by not returning normally. This can be done by throwing an exception (
die
), using a non-local return (Return::MultiLevel
), or invoking a restart that does one of these things (Worlogog::Restart
).Incident handlers registered with
handler_case
implicitly perform a non-local return. - error INCIDENT
-
Reports INCIDENT. If no handler takes over and handles the incident, throws INCIDENT as a normal Perl exception. Equivalent to:
sub error { my ($incident) = @_; report $incident; die $incident; }
- cerror INCIDENT
-
Works like
error
with a restart called'continue'
wrapped around it:sub cerror { my ($incident) = @_; restart_case { error $incident; } { continue => sub {}, }; }
This way an incident handler can
invoke
'continue'
to prevent the exception that would normally occur from being thrown. - warn INCIDENT
-
Reports INCIDENT. If no handler takes over and handles the incident, outputs INCIDENT as a warning via
Carp::carp
unless the restart'muffle_warning'
is invoked. Equivalent to:sub warn { my ($incident) = @_; restart_case { report $incident; carp $incident; } { muffle_warning => sub {}, }; }
This module uses Exporter::Tiny
, so you can rename the imported functions at use
time.
SEE ALSO
Exporter::Tiny, Worlogog::Restart, Return::MultiLevel, Dispatch::Class
AUTHOR
Lukas Mai, <l.mai at web.de>
COPYRIGHT & LICENSE
Copyright 2013, 2014 Lukas Mai.
This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License.
See http://dev.perl.org/licenses/ for more information.