Error Handling

If an error occurs during the execution of an Enki language function, it normally terminates the execution and the error is presented to the user – either in the form of a message box, or as a small red square in the corner of the dispel where the error occurred (then the user can inspect the details of the error in the context menu). However, it may be desirable to intercept the error and recover from it.

The Error Type

In order to facilitate exact error diagnostic and to enable error handling, the data type Error is defined. This type records information about an error. The Error type bears following information:
error category
The category generally identifies the source of error. Following categories are defined:
system error
An error originating in the operating system (e.g. an error in manipulation with a file, etc.).
syntax error
An error in the syntax of an Enki function or Enkidu template.
computation error
Run-time error during a function execution.
data error
An error in data manipulation (non-existent data item etc.).
user defined error
An error defined by user in an Enki function.
error code
A numeric code which enables to identify the individual case of error.
error description
A human-readable description presented to the user.
data reference
A reference to the data item to which the error is related. For instance, in the case of a syntax or computation error, it contains the reference to the source code of the function. For a data error, the reference identifies the data item causing the error. For a user defined error, the user is responsible for appropriate setting the data reference.
cause of error
In a real life situation, a lower level error often renders the higher level operation unable to be successfully finished, so even the higher level terminates with an error (and so on). In order to enable problem solving at an adequate level, a higher-level error includes the lower-level error that caused it. A description of errors at all the levels is finally presented to the user, together with appropriate data references. The user can thus better resolve the error situation.

Non-Strict Values

All the Enki language constructs are in principle operators, which take values returned by subordinated operators as their parameter and, in turn, they themselves return a value to the superodinate operator. A function expressed in the Enki language has therefore always a form of a single expression, which is hierarchically composed of individual operators. If an error occurs at a level, it is interpreted as a value of the Error type, instead of a regular value, being returned. If an operator is of that nature that an error can occur during its evaluation, the type of its result is called a non-strict value. A non-strict value is, therefore, such a type that allows instances of the type Error in addition to its regular values. The opposite term is a strict-value – that is such a value that cannot be an error.
If an error occurs during operator execution, while the superordinate operator requires a strict value as its parameter (i.e. it is unable to handle an Error value as its input value – which is the common case), the same error is passed up to its superordinate operator, as if the error were its result. This way, the error is propagated up along the operator hierarchy either until an operator allowing a non-strict value as its parameter is reached or the error is returned as the result of the entire function.
For error handling, a set of functions that accept non-strict values is defined. Moreover, the user can defined his own function accepting non-strict values – see the function definitions in the document section Definitions or in data provider.

Standard Error Handling Methods

The first method to handle errors is the try operator:
try block1
try block1 else block2
In the both versions of the operator, the block1 is first evaluated. If its result is not an error, it is returned as the result of the try operator. If an error occurred, the version without the else branch returns the initial value of the type of the block1 expression, while the version with the else branch evaluates the block2 expression and returns its result. The variable last_error is always defined in the else branch, which contains the Error value returned by the block1. It is either possible to analyze the error and correct it or it can be encapsulated to a higher-level error as its cause and this higher-level error return as the result of the try operator.
Example 1:
x = try a/b else 0
This expression assigns the quotient a/b to the variable x. However, if an error occurs (division by zero), 0 is assigned.
Example 2:
var File f;
f <- open("info.txt");
var String line = read_line(f);
error("unable to read file 'info.txt'", last_error);
If an error occurs while reading from the file, an error value describing explaining the situation at the higher level is created, and the original error value (stored in last_error) is attached to it. (The description presented in the example would not be very useful, as the original error would contain the reference to the file name. In a real-life situation, the purpose of reading the file should come here.)
The second method to handle errors is to assign the result to a non-strict variable and then to test, whether an error occurred.
var Real ?x = a/b;
x = 0;
The result of dividing a/b is assigned to the variable x. The result is assigned even in the case of error, as the variable is defined as non-strict (a non-strict variable is defined by using a question mark in from of its name). Subsequently, it is tested whether an error occurred and if so, 0 is assigned to the variable.
All functions for error values manipulation are described in the chapter Error Handling Functions.