# Copyright (C) 2001-2006, The Perl Foundation.
# $Id: pdd23_exceptions.pod 14139 2006-08-16 16:48:09Z creiss $

{{ NOTE: "rethrow", and "pushaction" are removed, and "die" is different }}
{{ TODO: enable backtrace }}

=head1 NAME

docs/pdds/pdd23_exceptions.pod - Parrot Exceptions

=head1 ABSTRACT

This document defines the requirements and implementation strategy for
Parrot's exception system.

=head1 VERSION

$Revision: 14139 $

=head1 DESCRIPTION

I<Exceptions> are indications by running code that something unusual -- an
"exception" to the normal processing -- has occurred.  When code detects an
exceptional condition, it I<throws> an exception object.  Before this occurs,
code can register exception I<handlers>, which are functions (or closures)
which may (but are not obligated to) I<handle> the exception.  Some exceptions
permit continued execution immediately after the I<throw>; some don't.

Exceptions transfer control to a piece of code outside the normal flow of
control.  They are mainly used for error reporting or cleanup tasks.

(A digression on terminology: In a system analysis sense, the word "exception"
usually refers to the exceptional event that requires out-of-band handling.
However, in Parrot, "exception" also refers to the object that holds all the
information describing the exceptional condition: the nature of the exception,
the error message describing it, and other ancillary information.  The
specific type (class) of an exception object indicates its category.)

=head2 Exception Opcodes

These are the opcodes relevant to exceptions and exception handlers:

=over

=item B<push_eh I<SUB_LABEL>>

=item B<push_eh I<INVOCABLE_PMC>>

Push an invocable PMC -- usually a closure or, in rarer cases, a subroutine --
onto the exception handler stack.

If a I<SUB_LABEL> is provided, Parrot automatically performs the equivalent of
a C<newclosure> operation on the given subroutine, and pushes the resulting
closure.

When an exception is thrown, Parrot walks up the stack of active exception
handlers, invoking each one in turn, but still in the dynamic context of the
exception (i.e. the call stack is I<not> unwound first).  See below for more
detail.

=item B<pop_eh>

Pop the most recently pushed exception handler off the exception handler stack.

{{ TODO: Provide exception handler stack introspection. }}

=item B<throw I<EXCEPTION>>

Throw an exception consisting of the given I<EXCEPTION> PMC.  Active exception
handlers (if any) will be invoked with I<EXCEPTION> as the only parameter.

Throwing an exception with C<throw> is a one-way trip (unless you have made
other arrangements) because Parrot does not take a continuation after this
opcode.  (But see B<throwcc> below.)

Any type of PMC can be thrown as an exception.  However, if there's any chance
of cross-language calls -- and in a Parrot environment, cross-language
operations are kind of the point -- then you should be prepared to catch
exception classes you would never have thrown yourself.

That said, it is I<VERY STRONGLY RECOMMENDED> that any thrown PMC that can
possibly escape your private sandbox should meet the minimal interface
requirements of the C<parrot;exception> class, described below.

=item B<throwcc I<EXCEPTION> [ , I<CONTINUATION> ]>

Throw an exception consisting of the given I<EXCEPTION> PMC after taking
a continuation at the next opcode.  When a I<CONTINUATION> is passed in,
it will use that instead. Active exception handlers (if any) will be
invoked with I<EXCEPTION> and the given continuation as parameters.

Except for the continuation which is passed to exception handlers,
C<throwcc> is just like C<throw>.  This opcode is useful for exceptions
that are more like warnings or notices than errors.

Exception handlers can resume execution immediately after the C<throwcc>
opcode by executing the C<handled> opcode, and then invoking the given
continuation which they receive as a parameter.  That continuation must be
invoked with no parameters; in other words, C<throwcc> never returns a
value.

=item B<die [ I<MESSAGE> ]>

The C<die> opcode throws an exception of type C<exception;death> with a
payload of I<MESSAGE>.  If I<MESSAGE> is a string register, the exception
payload is a C<String> PMC containing I<MESSAGE>; if I<MESSAGE> is a PMC, it
is used directly as the exception payload.

The default when no I<MESSAGE> is given is "Fatal exception at LINE in
FILE." followed by a backtrace.

If this exception is not handled, it results in Parrot returning an error
indication and the stringification of I<MESSAGE> to its embedding environment.
When running standalone, this means writing the stringification of I<MESSAGE>
to standard error and executing the standard C function C<exit(1)>.

=item B<exit [ I<EXITCODE> ]>

Throw an exception of type C<exception;exit> with a payload of I<EXITCODE>,
which defaults to zero, as an Integer PMC.

If not handled, this exception results in Parrot returning I<EXITCODE>
as a status to its embedded environment, or when running standalone,
to execute the C function C<exit(I<EXITCODE>)>.

=item B<handled I<EXCEPTION>>

While handling an exception, tell Parrot that the exception has been handled
and should be removed from the stack of active exceptions.  This opcode is an
exception handler's way of telling Parrot that it has handled the exception.

=back

=head2 Order of Operations in Exception Handling

When B<throw> or B<throwcc> is called, for all active exception
handlers, in LIFO order:

=over

=item 1
Find the topmost exception handler.

=item 2
Push an exception record somewhere, presumably on the exception handler
stack.  The exception record contains a pointer to an exception handler
block, an exception PMC, and (optionally) a continuation.

=item 3
Invoke the handler (note: this is still in the thrower's dynamic
context).

=back

If the handler returns without calling C<handled>:

=over

=item 1
Find the "exception handling in progress" record.

=item 2
Find the next exception handler.

=item 3
If the handler is found, invoke it.


=item 4
If no handler is found, and the exception is non-fatal (such as a
warning), and there is a continuation in the exception record (because
the throwing opcode was C<throwcc>), invoke the continuation (resume
execution). Whether to resume or die when an exception isn't handled is
determined by the severity of the exception.

=item 5
Otherwise terminate program a la C<die>. 

=back

When running an embedded Parrot interpreter, the interpreter does not
immediately terminate on an unhandled exception, it merely returns
control to the embedding program and stores the unhandled exception so
that it may be queried by the embedding program. The embedding program
may choose to handle the exception and continue execution by invoking
the exception's continuation.


When the C<handled> opcode is called:

=over

=item 1
Pop and destroy the exception record.

=item 2
If there was a continuation in the exception record, invoke the
continuation.

=back


=head1 STANDARD EXCEPTIONS

=head2 Universal Exception Object Interface [Advisory]

All of Parrot's standard exceptions provide at least the following interface.
It is I<STRONGLY RECOMMENDED> that all classes intended for throwing also
provide at least this interface as well.

=over 4

=item B<PMC *get_message()>

Get an exception's human-readable self-description.  Note that the type of the
returned PMC may not be C<String>, but you should still be able to stringify
and print it.

=item B<PMC *get_payload()>

Get the datum that more specifically identifies the detailed cause/nature of
the exception.  Each exception class will have its own specific payload
type(s).  See the table of standard exception classes for examples.

=item B<PMC *get_inner_exception()>

If an exception is a consequence of a previous exception, the
C<get_inner_exception()> method returns that previous exception, else
it returns null.

=back

=head2 Interface of Standard Parrot Exceptions

Parrot's standard exceptions provide some additional methods beyond the three
universal exception methods shown above.  The additional methods are:

=over 4

=item B<init_pmc(PMC *payload)>

Initialize the exception PMC with the given payload.  Note that the payload
will be interpreted differently depending on the specific type of the
exception.  For example, the payload of C<exception;errno> is an integer.
In addition, some exceptions don't require payloads, thus:

=item B<init()>

Initialize the exception PMC without a payload.  Some exceptions are
adequately self-explanatory without payloads.

=item B<void set_inner_exception(PMC *inner)>

If an exception is a consequence of a previous exception, use the
C<set_inner_exception()> method to store that previous exception
as part of the exception object.

=back

=head2 Standard Parrot Exceptions

Parrot comes with a small hierarchy of classes designed for use as exceptions.
Parrot throws them when internal Parrot errors occur, but any user code can
throw them too.

=over

=item B<exception>

Base class of all standard exceptions.  Provides no special functionality.
Exists for the purpose of C<isa> testing.

=item B<exception;errno>

A system error as reported in the C variable C<errno>.  Payload is an integer.
Message is the return value of the standard C function C<strerror()>.

=item B<exception;math>

Generic base class for math errors.

=item B<exception;math;division_by_zero>

Division by zero (integer or float).  No payload.

=item B<exception;domain>

Generic base class for miscellaneous domain (input value) errors.  Payload is
an array, the first element of which is the operation that failed (e.g. the
opcode name); subsequent elements depend on the value of the first element.

(Note: There is not a separate exception class for every operation that might
throw a domain exception.  Class proliferation is expensive, both to Parrot
and to the humans working with it who have to memorize a class hierarchy.  But
I understand the temptation.)

=item B<exception;lexical>

An C<find_lex> or C<store_lex> operation failed because a given lexical
variable was not found.  Payload is an array: [0] the name of the lexical
variable that was not found, [1] the LexPad in which it was not found.

=back

=head2 Opcodes that Throw Exceptions

Exceptions have been incorporated into built-in opcodes in a limited way.  For
the most part, they're used when the return value is either impractical to
check (perhaps because we don't want to add that many error checks in line),
or where the output type is unable to represent an error state (e.g. the
output I register of the C<ord> opcode).

The C<div>, C<fdiv>, and C<cmod> opcodes throw
C<exception;math;division_by_zero>.

The C<ord> opcode throws C<exception;domain> when it's passed an empty
argument or a string index that's outside the length of the string.  Payload
is an array, first element being the string 'ord'.

The C<classoffset> opcode throws C<exception;domain> when it's asked to
retrieve the attribute offset for a class that isn't in the object's
inheritance hierarchy.  Payload is an array: [0] string 'classoffset',
[1] object in question, [2] ID of class not found.

The C<find_charset> opcode throws C<exception;domain> if the charset name it's
looking up doesn't exist.  Payload is an array: [0] string 'find_charset', [1]
charset name that was not found.

The C<trans_charset> opcode throws C<exception;domain> on "information loss"
(presumably, this means when one charset doesn't have a one-to-one
correspondence in the other charset).  Payload is an array: [0] string
'trans_charset', [1] source charset name, [2] destination charset name, [3]
untranslatable code point.

The C<find_encoding> opcode throws C<exception;domain> if the encoding name
it's looking up doesn't exist.  Payload is an array: [0] string
'find_encoding', [1] encoding name that was not found.

The C<trans_encoding> opcode throws C<exception;domain> on "information loss"
(presumably, this means when one encoding doesn't have a one-to-one
correspondence in the other encoding).  Payload is an array: [0] string
'trans_encoding', [1] source encoding name, [2] destination encoding name, [3]
untranslatable code point.

Parrot's default version of the C<LexPad> PMC throws C<exception;lexical> for
some error conditions, though other implementations can choose to return error
values instead.

By default, the C<find_lex> and C<store_lex> opcodes throw an exception
(C<exception;lexical>) when the given name can't be found in any visible
lexical pads.  However, this behavior is only a default, as provided by the
default Parrot lexical pad PMC C<LexPad>.  If a given HLL has its own lexical
pad PMC, its behavior may be very different.  (For example, in Tcl,
C<store_lex> is likely to succeed every time, as creating new lexicals at
runtime is OK in Tcl.)

{{ TODO: List any other opcodes that currently throw exceptions and
general categories of opcodes that should throw exceptions. }}

Other opcodes respond to an C<errorson> setting to decide whether to
throw an exception or return an error value. C<find_global> throws an
exception (or returns a Null PMC) if the global name requested doesn't
exist. C<find_name> throws an exception (or returns a Null PMC) if the
name requested doesn't exist in a lexical, current, global, or built-in
namespace.

{{ TODO: "errorson" as specified is dynamically rather than lexically
scoped; is this good? Probably not good. Let's revisit it when we get
the basic exceptions functionality implemented. }}

It's a little odd that so few opcodes throw exceptions (these are the
ones that are documented, but a few others throw exceptions internally
even though they aren't documented as doing so). It's worth considering
either expanding the use of exceptions consistently throughout the
opcode set, or eliminating exceptions from the opcode set entirely. The
strategy for error handling should be consistent, whatever it is. [I
like the way C<LexPad>s and the C<errorson> settings provide the option
for exception-based or non-exception-based implementations, rather than
forcing one or the other.]

{{ NOTE: There are a couple of different factors here.  One is the
ability to globally define the severity of certain exceptions or
categories of exceptions without needing to define a handler for each
one. (e.g. Perl 6 may have pragmas to set how severe type-checking
errors are. A simple "incompatible type" error may be fatal under one
pragma, a resumable warning under another pragma, and completely silent
under a third pragma.) Another is the ability to "defang" opcodes so
they return error codes instead of throwing exceptions. We might provide
a very simple interface to catch an exception and capture its payload
without the full complexity of manually defining exception handlers
(though it would still be implemented as an exception handler
internally). Something like:

  .local pmc error_code
  .capture_start error_code
  $P1 = find_lex 'foo'
  .capture_end

  # error_code contains what would have been the "error" return value

This could eliminate the need for "defanging" because it would be almost
as easy to use as error codes. It could be implemented once for all
exceptional opcodes, instead of needing to be defined for each one. And,
it still keeps the error information out-of-band, instead of mixing the
error in with normal return values. }}

=head2 Resuming after Exceptions

Exceptions thrown by standard Parrot opcodes (like the one thrown by
C<find_global> above or by the C<throw> opcode) are always resumable,
so when the exception handler function returns normally it continues
execution at the opcode immediately after the one that threw the
exception. Other exceptions at the run-loop level are also generally
resumable.

  $P0 = new String
  $P0 = "something bad happened"
  $P1 = new ['parrot';'exception'], $P0  # create new exception object
  throw $P1                              # throw it

=head1 ATTACHMENTS

None.

=head1 FOOTNOTES

None.

=head1 REFERENCES

  src/ops/core.ops
  src/exceptions.c

=cut

__END__
Local Variables:
  fill-column:78
End:
