Exceptions provide a way of subverting the normal flow of control. Their main use is error reporting and cleanup tasks, but sometimes exceptions are just a funny way to jump from one code location to another one. Parrot uses a robust exception mechanism and makes it available to PIR.
Exceptions are objects that hold essential information about an exceptional situation: the error message, the severity and type of the error, the location of the error, and backtrace information about the chain of calls that led to the error. Exception handlers are ordinary subroutines, but user code never calls them directly from within user code. Instead, Parrot invokes an appropriate exception handler to catch a thrown exception.
throw opcode throws an exception object.
This example creates a new
Exception object in
$P0 and throws it:
Setting the string value of an exception object sets its error message:
Other parts of Parrot throw their own exceptions.
die opcode throws a fatal (that is,
Many opcodes throw exceptions to indicate error conditions.
/ operator (the
throws an exception on attempted division by zero.
When no appropriate handlers are available to catch an exception, Parrot treats it as a fatal error and exits, printing the exception message followed by a backtrace showing the location of the thrown exception:
I really had my heart set on halibut. current instr.: 'main' pc 6 (pet_store.pir:4)
Exception handlers catch exceptions, making it possible to recover from errors in a controlled way, instead of terminating the process entirely.
push_eh opcode creates an exception handler object and stores it in the list of currently active exception handlers. The body of the exception handler is a labeled section of code inside the same subroutine as the call to
push_eh. The opcode takes one argument, the name of the label:
This example creates an exception handler with a destination address of the
my_handler label, then creates a new exception and throws it. At this point, Parrot checks to see if there are any appropriate exception handlers in the currently active list. It finds
my_handler and runs it, printing "caught an exception". The "never printed" line never runs, because the exceptional control flow skips right over it.
Because Parrot scans the list of active handlers from newest to oldest, you don't want to leave exception handlers lying around when you're done with them. The
pop_eh opcode removes an exception handler from the list of currently active handlers:
This example creates an exception handler
my_handler and then runs a division operation that will throw a "division by zero" exception if
$I2 is 0. When
$I2 is 0,
div throws an exception. The exception handler catches it, prints "caught an exception", and then clears itself with
$I2 is a non-zero value, there is no exception. The code clears the exception handler with
pop_eh, then prints "maybe printed". The
goto skips over the code of the exception handler, as it's just a labeled unit of code within the subroutine.
The exception object provides access to various attributes of the exception for additional information about what kind of error it was, and what might have caused it. The directive
.get_results retrieves the
Exception object from inside the handler:
Not all handlers are able to handle all kinds of exceptions. If a handler determines that it's caught an exception it can't handle, it can
rethrow the exception to the next handler in the list of active handlers:
If none of the active handlers can handle the exception, the exception becomes a fatal error. Parrot will exit, just as if it could find no handlers.
An exception handler creates a return continuation with a snapshot of the current interpreter context. If the handler is successful, it can resume running at the instruction immediately after the one that threw the exception. This resume continuation is available from the
resume attribute of the exception object. To resume after the exception handler is complete, call the resume handler like an ordinary subroutine:
Exception objects contain several useful pieces of information about the exception. To set and retrieve the exception message, use the
message key on the exception object:
... or set and retrieve the string value of the exception object directly:
The severity and type of the exception are both integer values:
The payload holds any user-defined data attached to the exception object:
The attributes of the exception are useful in the handler for making decisions about how and whether to handle an exception and report its results:
Exception handlers are subroutine-like PMC objects, derived from Parrot's
Continuation type. When you use
push_eh with a label to create an exception handler, Parrot creates the handler PMC for you. You can also create it directly by creating a new
ExceptionHandler object, and setting its destination address to the label of the handler using the
ExceptionHandler PMCs have several methods for setting or checking handler attributes. The
can_handle method reports whether the handler is willing or able to handle a particular exception. It takes one argument, the exception object to test:
max_severity methods set and retrieve the severity attributes of the handler, allowing it to refuse to handle any exceptions whose severity is too high or too low. Both take a single optional integer argument to set the severity; both return the current value of the attribute as a result:
handle_types_except methods tell the exception handler what types of exceptions it should or shouldn't handle. Both take a list of integer types, which correspond to the
type attribute set on an exception object:
The following example creates an exception handler that only handles exception types 1 and 2. Instead of having
push_eh create the exception handler object, it creates a new
ExceptionHandler object manually. It then calls
handle_types to identify the exception types it will handle:
This handler can only handle exception objects with a type of 1 or 2. Parrot will skip over this handler for all other exception types.
Annotations are pieces of metadata code stored in a bytecode file. This is especially important when dealing with high-level languages, where annotations contain information about the HLL's source code such as the current line number and file name.
Create an annotation with the
.annotate directive. Annotations consist of a key/value pair, where the key is a string and the value is an integer, or a string. Bytecode stores annotations as constants in the compiled bytecode. Consequently, you may not store PMCs.
Annotations exist, or are "in force" throughout the entire subroutine or until their redefinition. Creating a new annotation with the same name as an old one overwrites it with the new value. The
annotations opcode retrieves the current hash of annotations:
To retrieve a single annotation by name, use the name with
Exception objects contain information about the annotations that were in force when the exception was thrown. Retrieve them with the
annotations method on the exception PMC object:
Exceptions can also include a backtrace to display the program flow to the point of the throw:
The backtrace PMC is an array of hashes. Each element in the array corresponds to a function in the current call chain. Each hash has two elements:
annotation (the hash of annotations in effect at that point) and
sub (the Sub PMC of that function).