Error handling

With EGL, you can specify how your program functions if an error occurs.

Handling errors means anticipating the kinds of problems that might occur in your program and providing a code path for each of them. If you decide not to handle errors, all but the most trivial (see "I/O errors" later in this topic) will cause your program to end.

Error handling in EGL is based on the concept of the exception, which is a stereotype that applies to a record. EGL has a number of predefined exceptions (see "EGL Exception records" in the Reference section), or you can define your own. Each exception record contains at least these fields:
messageID
A STRING that contains the EGL message for the exception. For example, if you try to use an array that is not initialized, EGL sets the messageID in the NullValueException record to EGL0106E.
message
A STRING that contains a brief explanation of the problem. For example, the message that goes with messageID EGL0106E is "A null reference was used."

The exception record can contain additional fields. For example, the IndexOutOfBoundsException has an additional field for indexValue, which contains the value of the array index that EGL could not process.

The try block

The EGL try keyword introduces a block of code in which you can catch and handle exceptions. If an exception occurs inside a try block, EGL looks for an onException statement within the try block that matches the exception type. Each onException statement includes the declaration of an exception variable. This is a record variable similar to variables that you declare elsewhere in EGL; the declaration looks something like this example:
onException(myEx NullValueException)
If EGL throws a NullValueException inside this try block, you can access fields in that exception record. For example, you can find the EGL message ID in myEx.msgID:
try
  intArray[10] = 0;  // this may not be initialized
onException(myEx NullValueException)
  writeStdErr(myEx);
  myErrorHandler(myEx);
end

The myErrorHandler() function could, for example, initialize the array and perform the assignment again.

AnyException is a special type of exception that is available to catch any exception and that is comparable to the ANY primitive type. You declare a record variable for AnyException; if EGL throws an exception, your variable takes on the type of the actual exception record:
try
  get next mySerialRecord
onException(myEx AnyException)
  myErrorHandler(myEx);
end
If you have a specific code path to follow if EGL throws a FileIOException, you can add a special check for that path:
try
  get next mySerialRecord
onException(myEx FileIOException)
   myErrorHandler1(myEx);
onException(myEx AnyException)
  myErrorHandler2(myEx);
end

In this example, myErrorHandler2() deals with any exception other than a FileIOException.

If EGL throws an exception but no onException block catches the exception, the function ends immediately and control returns to the function that called the function that threw the error. In this way, EGL passes the exception upward until a function catches the exception with an onException block or the exception reaches the main function. If the main function fails to catch the exception, the program ends immediately and writes the message field of the exception to the log. When an exception occurs in a remotely called program, the calling program receives an InvocationException rather than the original exception. Similarly, an exception in a service function delivers a ServiceInvocationException to the calling program.

In other words, a function can catch exceptions that are thrown in functions that it calls:
function FuncOne(myRec serialRecordType)
  try
    FuncTwo(myRec);
  onException(myEx AnyException)
    myErrorHandler2(myEx);
  end
end

function FuncTwo(myRec serialRecordType)
  get next myRec;
end
In V6 exception compatibility mode, exceptions do not pass from function to function in this way; for more information, see "V6 exception compatibility" in this topic.

Hard and soft I/O errors

When you are reading or writing to a file, EGL makes a distinction between hard and soft I/O errors. The following errors are considered soft, that is, not likely to cause a loss of data:

Table 1. Soft I/O errors
Error Meaning
duplicate For an indexed or relative record, this error indicates that a second record has the same key.
endOfFile For a serial, indexed, or relative record, this error indicates that an attempt was made to read past the end of the file.
noRecordFound For any record type, the error indicates an that an attempt was made to read a record that could not be found.

Other errors, such as an invalid file format or a full file, are considered hard errors. A hard I/O error on an indexed, relative, or serial file throws a FileIOException. A hard I/O error on an SQL database throws an SQLException.

Soft I/O errors associate the error value with record, but do not cause an exception to be thrown. To test for this situation, use the is or not operator. You do not have to place the I/O statement in question inside a try block to use these operators. However, unless you put the I/O statement inside a try block, you cannot check at the same time for hard I/O errors:
while(TRUE)  // endless loop
  try
    get next mySerialRecord;
    if(mySerialRecord is endOfFile)
      exit while;
    end
  onException(myEx AnyException)
    myErrorHandler(myEx);
  end
end

throwNrfEofExceptions property

By default, the throwNrfEofExceptions program property is set to NO, which means that the program continues after the soft I/O errors noRecordFound and endOfFile occur, even if the error occurs outside of a try block:
get myCustomer;
if(myCustomer is noRecordFound)
   add myCustomer;
end
You might prefer to have EGL throw an exception when one of these errors occurs. To do so, set the throwNrfEofExceptions program property to YES. In that case, EGL throws one of the following exceptions:
  • If you are performing file I/O, EGL throws a RuntimeException.
  • If you are performing SQL I/O, EGL throws an SQLException.
If you do not catch this error in a try block, the program will terminate. Using the previous example, if no record is found for the get statement, the program terminates. However, use either of the following techniques to handle the exception:
  • Continue after the soft error:
    try
      get myCustomer; 
    end
    
    if (myCustomer is noRecordFound) 
      add myCustomer; 
    end
  • Catch the exception, which is the preferred technique:
    try
      get myCustomer; 
    onException (myEx FileIOException)
      if (myCustomer is noRecordFound) 
        add myCustomer; 
      else
        myErrorHandler(myEx);
      end
    end

Throwing your own exceptions

By using an EGL throw statement, you can throw any of the predefined system exceptions. As in exception handling, you must declare a variable that is based on the record:
nullEx NullValueException{};
 ... 
throw nullEx;
In addition to the exceptions that the system defines, you can create your own exception records. As with other records, you must define a Record part first, and then declare a variable that is based on it.
Record CustomerException type Exception
  customerNumber INT;
end
 ... 
throw new customerException {
  customerNumber = custNum,
  message = "Illegal customer number" };
Custom exceptions records such as this one automatically include the messageID and message fields, just as the system exception records do.

V6 exception compatibility

For compatibility with earlier versions, you can still use the error handling methods from version 6 of EGL.

You specify V6 exception mode on a program-by-program basis when you set the v60ExceptionCompatibility property of the program to YES. For best results, use the same setting for all of your programs.

V6 exceptions are typically handled through a try block. The difference is that V6 exceptions allow only a single onException statement, and do not specify an exception type. The behavior is the same as if the onException statement had an implied AnyException modifier:
try
  posNum = abs(myVar);
onException
  if(sysVar.errorCode = "00000008") // invalid input
    myErrorHandler1();
  if(sysVar.errorCode = "00000012") // cannot assign value
    myErrorHandler2();
  else
    myErrorHandler3();
end
Instead of being based on exception records, V6 exception compatibility relies on the sysVar.errorCode system variable. If you are running in V6 exception mode, sysVar.errorCode is set in the following cases:
  • At the completion of a call statement
  • After a call to a service
  • After a file I/O statement such as get or replace
  • After invoking many of the EGL system functions
In addition, for SQL I/O errors, V6 exception compatibility relies on the system variables in sysVar.sqlData.
You do not need a try block to access sysVar.errorCode in V6 exception mode if you have the vgVar.handleSysLibraryErrors system variable (for functions in system libraries) or vgVar.handleHardIOErrors (for file and SQL I/O) set to 1:
vgVar.handleSysLibraryErrors = 1;
posNum = abs(myVar);
if(sysVar.errorCode == "00000008") // invalid input
  myErrorHandler1();
else
  if(sysVar.errorCode == "00000012") // cannot assign
    myErrorHandler2();
  else
    exit program (-1);
  end
end

If vgVar.handleSysLibraryErrors is set to 0 (the default) and you do not use a try block to catch exceptions, any exception will cause the program to terminate.

For more information, see "Exception handling."


Feedback