Author: Todd Miller

# PyRAF ECL Support

# Overview

PyRAF can now support the error handling constructs of IRAF's ECL scripting language. In addition to PyRAF's classic Python exception handling, PyRAF's ECL support enables certain errors to be trapped and to cause exception handling statements to execute.

PyRAF's ECL captures the following errors:

-   System exceptions (FPE, segfault, etc) thrown by compiled tasks (i.e. SPP or C)
-   error() calls from compiled tasks
-   error() calls from CL scripts
-   division by zero in CL scripts

# Activating ECL Support

During it's introduction, PyRAF's ECL support is optional and is only activated by one of the following means:

Activation Method | Description
    pyraf -e
| Use command line switch -e when invoking pyraf.
    pyaf –ecl
| Use the verbose switch, --ecl, when invoking pyraf
    epyraf
| Link pyraf to epyraf and run as epyraf.
    setenv PYRAF_USE_ECL 1
| Set the environment variable PYRAF_USE_ECL to 1

In the absence of the above methods, PyRAF runs without ECL support.

# New ECL Keywords

PyRAF's ECL support uses the following words as keywords, i.e. words which are part of ECL and can no longer be used as program identifiers (i.e. variable, task, or procedure names):

-     iferr

-     ifnoerr

-     then

# ECL Grammar Extensions

PyRAF's support for ECL includes two new symmetric statements, iferr and ifnoerr. iferr is used to describe what should be done when an error occurs in a group of guarded statements, and ifnoerr is used to emphasize what should be done when an error does not occur in a group of guarded statements. A “guarded statement” is essentially a block of ordinary CL statements for which errors should be trapped. An “except action” are the statement(s) which should be executed when an error occurs (iferr) or does not occur (ifnoerr). An “else action” are the statement(s) which should be executed when an error occurs. Below is the section of the PyRAF grammar which describes ECL iferr statements; IFERR, IFNOERR, THEN, and ELSE denote keyword literals:

    iferr_stmt    ::= if_kind guarded_stmt except_action
    iferr_stmt    ::= if_kind guarded_stmt opt_newline THEN except_action
    iferr_stmt    ::= if_kind guarded_stmt opt_newline THEN except_action opt_newline ELSE else_action
    if_kind       ::= IFERR
    if_kind       ::= IFNOERR
    guarded_stmt  ::=  { opt_newline statement_list }
    except_action ::= compound_stmt
    else_action   ::= compound_stmt

A compound statement can be a single statement or block of statements.

# ECL Syntax Examples

The simplest form of ECL error statement is a block of guarded statements followed by a single handler statement which should execute when one or more of the guarded statements fail. The then keyword is optional in this form. A curious property of ECL error handling is that all guarded statements execute, even those following the first failed statement. This contrasts sharply with Python's exception handling model which performs it's traceback immediately following the first error.

    iferr {
    <guarded statements>
    } <error-handler statement>

    iferr {
    <guarded statements>
    } then
    <error-handler statement>

When a block of error handler statements is desired, the then keyword should be used to be compatible with ECL. An optional else clause may be used to specify what to do when the guarded statements all succeed; either the then clause or the else clause is executed, but never both.

    iferr {
    <guarded statements>
    } then {
    <error-handler statements>
    } else {
    <non-error-handler statements>
    }

There is a symmetric form of iferr which uses the keyword ifnoerr. It is perhaps most useful when one doesn't want to specify anything to handle an error, but only specify what to do when the guarded statements succeed. ifnoerr is effectively iferr with the error-handling and success-handling statements reversed.

    ifnoerr {
    <guarded statements>
    } then {
    <non-error statements>
    }

    ifnoerr {
    <guarded statements>
    } then {
    <non-error statements>
    } else {
    <error handling statements>
    }

# ECL Pseudo Variables

PyRAF in ECL mode defines the following pseudo variables which are associated with each task object:

Variable | Description
    $errno
| The numerical value associated with the last error.
    $errtask
| The task which caused the error.
    $errmsg
| The text message associated with the last error.
    $err_dzvalue
| The result value of a division by zero.

Since pseudo variables are associated with a task object, they have several properties:

1. They are persistent, i.e. not cleared until a task is re-run, and only then based on erract's clear field. They are however overwritten with each new error.

2. They can be accessed in CL code as written in the table above.

3. They can be accessed from the command line using DOLLAR notation after a traceback has occurred:

   --> iraf.failed_task.DOLLARerrno

   57

   --> iraf.failed_task.DOLLARerrmsg

   'becuz something went wrong...'

   --> iraf.failed_task_caller.DOLLARerrtask

   failed_task

4. They are not re-entrant – i.e., recursive procedures using them are only referring to a single storage location and will interfere with one another, only recording the last error.

# ECL Functions

PyRAF in ECL mode defines the following error handling functions which are analogous to the pseudo variables and easier to use.

Function | Description
    error()
| Forces a CL error state, generally raising a traceback.
    errno()
| Returns the numerical value associated with the last error.
    errmsg()
| Returns the message associated with the last error.
    errtask()
| Returns the task associated with the last error.

# Division By Zero

PyRAF's ECL mode now traps division by zero and either raises and ECL exception or returns the default result value contained in the variable $err_dzvalue.

So in ECL, the following guarded code:

    iferr {
    $err_dzvalue = 33
    print 1/0
    }

Outputs:

    Warning on line 6 of 'nested5':  divide by zero - using $err_dzvalue = 33
    33

While un-guarded code such as:

    $err_dzvalue = 33
    print 1/0

Generates a traceback and outputs:

    ERROR (1): divide by zero
       'print 1/0'
          line 4: /home/jmiller/nested5.cl
    Traceback (innermost last):
      File "<CL script CL1>", line 8, in <module>
    IrafError: ERROR: divide by zero

# Controlling ECL Behavior using erract

PyRAF's ECL mode behavior is controlled by a multi-field environment variable named erract. erract is set in PyRAF as a string containing one or more field modifiers.

    --> show erract
    abort trace flpr clear full ecl

Multiple fields may be changed with a single “set” command, and not all fields need be specified. Fields not mentioned in a set statement retain their old values.

    --> set erract="noflpr noclear"

    --> show erract
    abort trace noflpr noclear full ecl

Erract Field | Description
--- | ---
    abort / noabort
| Outside an iferr block, a failed task results in an immediate error. Inside an iferr block, a failed task causes an error as soon as the iferr guarded block is exited. Set to noabort and errors won't stop execution regardless of iferr usage.
    trace / notrace
| Print traceback information to stderr, or don't print.
    flpr / noflpr
| Flush the process cache on error, or don't flush.
    clear / noclear
| Clear the error pseudo variables for a task before running it, or retain the old error values which may or may not be overwritten.
    full / nofull
| Print traceback information on the entire procedure call chain, or only on the innermost CL procedure.
    ecl  / noecl
| Use the new ECL error handling, or use classic PyRAF/Python exception handling inside iferr and ifnoerr blocks. Setting noecl causes an error to raise an immediate exception and give a Python traceback. This is a runtime control. It does not affect ECL compilation which can only be activated at system startup.