Enhanced CL (ECL) - Beta Release Available

Enhanced CL (ECL) - Beta Release Available

Updated: 27Jun2005


A Beta release of the new Error-Handling CL (ECL) is now available for testing on machines which get their IRAF from the downtown Tucson servers (ursa/gemini), or as an add-on distribution for laptop systems. So that no special setup is required and because no current functionality is affected, we are taking the unusual step of adding this test release to the standard IRAF V2.12.2a system rather than to just the development version. ECL is now available for Solaris, RedHat, Linux, and Mac OS X systems and will be part of the next IRAF patch release.

New Features of the ECL Include:

Detailed release notes are available from
        ftp://iraf.noao.edu/pub/ecl.readme
or within the ECL using the command
        ecl> page pkg$ecl/Notes.ecl
To start the new version of the CL simply issue the command
        % cl -ecl
The default prompt for the ECL is now "ecl>" as a visual clue to which version is being used, the above command can be aliased by users who wish to make the ECL their default.

Personal Machine Installation

Distribution for laptop or standalone IRAF systems is in the form of a self-installing shell script containing all needed files. Installers may be downloaded for various platforms from
        ftp://iraf.noao.edu/pub/ecl_install_<arch>.csh
where <arch> is the standard iraf architecture name for each platform (i.e. 'redhat', 'linux', 'macosx' or 'ssun').

The installer will currently overlay the IRAF system on the machine and create a symlink in a system bin directory and so requires root permission to execute, but it may be run from any directory. To install the files simply download the installer in binary mode, reset the execute permissions if needed, and run the script. A "personal installation" version for users who do not have permissions to overwrite their IRAF installation will be available with the first public release.

Note this is still a test release and problems can be expected. The familiar 'cl' command will still instantiate the traditional CL and users must purposefully start the ECL to take advantage of new features. No other changes to the system or external packages have been made or are required, users of the old CL will see no change as a result of this test release.

Future updates to the Tucson servers will be done during off-hours to minimize interference with currently running sessions. Please report any problems, comments or suggestions to

        iraf@noao.edu
as soon as they are discovered.

		ECL:  Enhanced CL Release Notes and User's Guide
		================================================

		           Michael Fitzpatrick
			     NOAO/IRAF Group
			        12/12/04

			   Revised: 5/28/05
								

********************************************************************************
Release History:
     02/10/05		** Alpha Release for testing
     05/06/05		** 2nd Alpha Release for testing
     06/07/05		** 1st Beta Release for testing


********************************************************************************

Table of Contents
-----------------

    Introduction

    Installation and Use
  	To Install the CL
  	    Personal Installation Instructions
  	Determine CL Version Type

    Error Handling
  	Introduction and Cautions
  	    Example Descriptions
  	Reporting Errors
	    Traceback
	Trapping Errors
	    The 'iferr' Syntax
	    The 'erract' Environment Variable
	    Error Handling: Then and Now
	New CL parameters
	What Errors Are NOT Trapped

    Command-line History and BackSpace Revisions
	Input Command Summary

    New Builtin Functions and Variables
	Error Functions
	String Functions
	Trig Functions
	Utility Functions
	Bitwise Operations

    Defined Constants

    Post-Release Notes


********************************************************************************

============
Introduction
============

    The primary goals of the ECL project were to 

	o  add an error-handling capability to the existing IRAF CL, 
	o  include other functionality which could improve the
	   scripting environment (e.g. pre-defined language constants
	   such as 'PI') and add any other features we found lacking
	   (e.g. missing trig functions and string utilities), and
	o  add commonly requested features.

Where possible, small enhancements such as a new utility builtin function
will be implemented in the "old" CL as well, however as scripts begin to
use the more advanced features scripts will naturally become less backward
compatible.  Future work will build on the version presented here with
the hope that users will migrate to the new system over a short time.

	This is a work in progress.  Users are encouraged to experiment with
features, request future enhancements, and to please report any errors or
problems to 
		iraf@noao.edu

New releases will be announced on the IRAF website (http://iraf.noao.edu)
following the addition of any new features or when critical bugs have been
fixed.



====================
Installation and Use
====================

	The ECL is being distributed in a self-extracting script file
rather than the traditional IRAF external package since it is meant to
overlay an existing IRAF system until the time when it becomes part of
the core distribution.  Since the script creates a new command link in
the unix system "local bin directory" and adds files to the IRAF source 
tree, it MUST be run as the root user (the script will terminate or ask
if you wish to proceed with a no-op installation otherwise).

The installation script does the following to your system:

    1)  Replaces the existing hlib$cl.csh script with a modified 
	version after creating a hlib$cl.csh.ORIG backup file

    2)  Creates an "ecl" command link in the same directory as the
	current "cl" IRAF command link.  Both links point to the same
	hlib$cl.csh script which checks for how it was called an 
	invokes the proper binary.

    3)  Moves the "ecl.e" binary to the proper iraf$bin.<arch> directory,
	changing the ownership to the 'iraf' user and setting the execute
	permissions on the file.

    4)  Creates a iraf$pkg/ecl directory and moves all ECL sources there.

The install script may be run from any directory on the system, it is
unpacked in /tmp and cleans up temp files when complete.   A "personal
installation" option is not implemented at this time but could be considered
later for users who don't have write permission on their IRAF tree.  Please
contact iraf@noao.edu for instructions on how to manually setup such a
system for personal use.


Note:   The installer scripts require the 'uudecode' command which may be
	in an optional package under some operating systems.  Linux users
	should be sure the 'uudecode' command exists (e.g. in /bin, /usr/bin
	or using the "which uudecode" command) and/or that the 'sharutils'
	package is installed on their system.


To Install the ECL
------------------

Step 1)	Download the distribution file appropriate for your system.  For
	example, 

        	% ftp iraf.noao.edu (140.252.1.1)
        	login: anonymous
        	password: [your email address]
        	ftp> cd pub
        	ftp> binary
        	ftp> get ecl_install_redhat.csh
        	ftp> quit

Step 2) Execute the script AS ROOT:

		% su				# become the root user
		# ./ecl_install_redhat.csh

	The script will prompt you for the local bin directory or any 
	iraf paths needed, simply accept the default values determined for
	your system or override them with others.

	Once executed, the ECL source and binaries will be installed in
	the system as described above.  The file you are reading right
	now is available as iraf$pkg/ecl/Notes.ecl and will be updated
	with post-release notes at the end of the file with each new
	release.

Step 3) Start the ECL from your normal IRAF login directory as either

		% ecl
	or
		% cl -ecl

	The second form of the command is needed on systems which mount
	IRAF from another machine since the CL command links are created
	at IRAF install time.  One reason for replacing the hlib$cl.csh
	script is to allow for the "-ecl" argument to override the binary
	to be used on systems where only the 'cl' command is available and
	so that the installation isn't required on all machines mounting
	a common IRAF.

	The default ECL prompt is now "ecl>" in the new version as a visual
	clue that the new system is being used.  Additionally, package prompts
	default to using the complete package name rather than the familiar
	2-character prefix as another clue.  This behavior can be changed
	by adding the string "nolongprompt" to the CL 'ehinit' parameter,
	e.g.

	       cl> cl.ehinit = cl.ehinit // " nolongprompt"


Except as described below, use of the ECL should be identical to the
traditional CL for most users.


Personal Installation Instructions
----------------------------------

	At this time the auto-installer doesn't implement a personal
install option, however this can be done pretty easily by hand.  If
you do not have root permission on your machine, you can install manually
using the following procedure:


1) Download the source tarball from

        ftp://iraf.noao.edu/pub/ecl.tar.gz

2) Build and install:

   If you have 'iraf' permissions and want to install in the IRAF tree:

        a)  Create a iraf$pkg/ecl directory

                % mkdir /iraf/iraf/pkg/ecl

        b)  Unpack the tar file in the new directory

                % cd /iraf/iraf/pkg/ecl                 # go there
                % cat /<path>/ecl.tar.gz | gunzip | tar -xvf -

        c)  Build the executable (assumes $iraf/$IRAFARCH defined)

                % mkpkg relink

            This creates an 'ecl.e' executable in the directory.

        d)  Install the files

                % cp ecl.e $iraf/bin.<arch>/            # install binary
                % cp $hlib/cl.csh $hlib/cl.csh.ORIG     # backup cl.csh
                % cp cl.csh $hlib/cl.csh                # replace w/ new one

        e)  Run the ECL using the command

                % cl -ecl

            This install ECL for use by anyone on the system, however it
            does not create an 'ecl' command link so the above is required.


   If you want to do a personal installtion (i.e. no root or iraf
permissions)

        a)  Create a directory for the ECL sources:

                % mkdir ~/iraf/ecl

        b)  Unpack the tar file in the new directory

                % cd ~/iraf/ecl                         # go there
                % cat /<path>/ecl.tar.gz | gunzip | tar -xvf -

        c)  Build the executable (assumes $iraf/$IRAFARCH defined)

                % mkpkg relink

            This creates an 'ecl.e' executable in the directory.

        d)  Since the 'cl.csh' script assumes the binary is in the IRAF
            tree it cannot be used for a personal install, however one can
            define a unix environment to use the binary directly, e.g.

            % setenv iraf /iraf/iraf/              # trailing '/' required
            % setenv IRAFARCH redhat               # or ssun, or macosx, or
            % ...
            % setenv arch .$IRAFARCH               # Note '.' !!!
            % source $iraf/unix/hlib/irafuser.csh  # additional definitions

            These lines should be put in the user's .cshrc file.

        e)  Run the ECL by calling the binary directly, i.e.

                % /home/fred/iraf/ecl/ecl.e

            Or simply create a command alias to do it, i.e.

                alias ecl       /home/fred/iraf/ecl/ecl.e

Note that $iraf path and IRAFARCH settings need to be correct for the
machine.  The ECL requires a login.cl/uparm just as the old CL does.



Determining CL Version
----------------------

	As users begin to make regular use of features found only in the
ECL, the first error to be checked is that the script is running using the
proper version of the CL.  This needs to be done using features found in
both the ECL and traditional CL languages.  The simplest test, for either
package loading scripts or within tasks, is something like

	if (defpar ("$errno")) {
	    print ("You are using the ECL")
	} else {
	    print ("You are using the old CL")
	}




==============
Error Handling
==============

Introduction and Cautions
=========================
    
    The error-handling enhancements are composed of two elements: 

	o  the reporting of errors within scripts, and
	o  the ability to trap and recover those errors.  

The first case addresses the long-standing problem in which an error message
returned by a script gives a line number that has no basis in reality, and
which gives no useful information about the underlying task that created it.
In the second case, one often wants scripts to be able to trap errors from
compiled tasks so that some sort of cleanup can be done in order to allow
the script to continue, or so that an error status code can be examined
and some specific action taken (which may simply be to ignore the error).

    In the ECL, messages are now printed with the correct line number and
with a detailed traceback to the user's command-line showing more precisely
what was called at the time of the error.  New language constructs are
available which allow scripts to conditionally check for errors from
tasks they call and branch to code to deal with those errors.  Finally,
new ECL environment variables and builtin functions allow for limited
error-handling control over scripts already in the system which have not
been retrofitted to specifically trap errors.  Details of each of these
capabilities and examples of how they may be used by developers and users
are given below.  It is also worth discussing the types of errors which
can occur in a script task before getting into details about how they
might be handled by the user or script programmer.

Error conditions in the CL break down into roughly the following types:

          Error Type	       		Examples
          ----------			--------

    Compiled Task Errors    1) A call to a compiled task in the system
			       dies unexpectedly with an exception (e.g.
			       FPE, segmentation violation, etc)
			    2) A task aborts due to an error condition the
			       task has trapped and cannot recover (e.g.
			       invalid parameters, out of memory, etc).

    CL Internal Errors	    1) Script code performs an illegal operation
			       causing an exception (e.g. "i = j / k"
			       where 'k' is zero.
			    2) Script code triggers a runtime error within
			       the CL itself (e.g. "log (string_value)")

    CL Error Assertions	    1) Script programmer forces the task to exit
			       with a call to the CL error() builtin.
			    2) Script programmer simply prints and error
			       message indicating a problem and returns
			       without further processing.

All of these errors can be detected at some level, however not all of
them can be handled in a way which allows a calling script to recover
and continue executing, nor would it always make sense to do so.
Errors such as a floating-point-exception (FPE) may be data-dependent,
a segmentation violation may indicate a coding error in a compiled task
or a platform-specific bug, or an error in another script task may be
beyond the control of the scripter to fix.  Error assertions by a script
programmer are not meant to be recoverable, and in the second example
an arbitrary problem message cannot be trapped by the system.

    An error-handling capability in the ECL (or any language) is not a
panacea for all error conditions one might encounter, the best a script
programmer can hope to do is to trap an error and take some reasonable
action at the time.  The ECL offers a way for a script to print a more
meaningful error message, or at least abort gracefully after cleaning
itself up.  However, depending on the type of error,  *your* script may
still never run to completion until somebody else fixes *their* code.

    Lastly, it is also important to note that trapping an error means the
script finds itself in an unnatural state.  Proper recovery requires
that the script programmer understand the error condition as well as
the state of the script at that point of execution.  The error-handling
code must restore the script to a state where it can continue running
(if possible) and avoid potential side-effects caused by e.g. forgetting
to clean up intermediate files or reset counter variables.  New language
features mean new types of bugs can be introduced into a script, even if
the irony is that these new features are meant to trap bugs!


Example Descriptions
--------------------

	In the examples to follow we will make use of an ERRTEST package
distributed with the ECL source and containing the following tasks used
in the examples to follow:

     nested -- Test various error conditions from layered scripts
      nest0 -- Dummy layer for nested testing
    errtype -- Low-level script to test compiled and CL error conditions
    
        fpe -- Compiled task producing an arithmetic exception
     segvio -- Compiled task producing a segmentation violation
     spperr -- Compiled task invoking the SPP error() function
    


Reporting of Errors
===================
    
Traceback
---------

    The most obvious change to users will be in the traceback of errors
reported by the ECL.  As an example, suppose we have a test script
called NESTED that calls several layers of other scripts until it gets
to a compiled task called FPE which simply triggers a divide-by-zero
arithmetic exception.  The calling sequence we use is

	NESTED (type)			# toplevel test task
	    NEST0 (type)		# hidden script task
		ERRTYPE (type)		# script task
		    FPE ()		# compiled task giving the error

(The 'type' argument here is a code used to test various types of system
errors but its value isn't important to the current discussion.)  In the 
traditional CL, executing this script results in the following and familiar
message:
		cl> nested 1
		ERROR on line 72: floating point divide by zero
		    errtype (type=1)
		    nested (type=1)

There are a number of issues with the error report here we wish to correct:

    1)  The error is reported to be on line 72, but none of the scripts
        called invoke any task on that line, or even have that many lines,
        and so it is clearly wrong.
    2)  Was it the ERRTYPE script that caused an error or something else?
    3)  There is no mention of the FPE task we know to be the culprit.

These problems are resolved in the ECL where the error report now looks like:

	cl> nested 1
	ERROR: floating point divide by zero
	  "fpe ()"
	     line 15: errtest$errtype.cl
	     called as: `errtype (type=1)'
	  "errtype (type)"
	     line 13: errtest$nest0.cl (hidden task)
	     called as: `nest0 (type=1)'
	  "nest0 (type)"
	     line 11: errtest$nested.cl
	     called as: `nested (type=1)'

The traceback is more complete and begins with the task which actually
throws the error.  Checking the line numbers of the ERRTEST package
scripts we find that indeed FPE is called on line 15 of 'errtype.cl',
ERRTYPE is called from line 13 of 'nest0.cl', and so on.

    For each task in the calling sequence the format of the traceback is

	<script code fragment executing at the time of error>
	   LINE <number>: <script file containing line>
	   CALLED AS: <how this script was called>

The length of the traceback may be controlled with the new 'erract'
environment variable discussed in more detail below.  In short, 'erract'
allows the traceback to be suppressed entirely, to print information only
at the level where the error occurred, or to print a full calling stack
trace (default).


Trapping Errors
===================

The 'iferr' Syntax
------------------

    The ECL provides new language constructs to enable error actions, error
handling and recovery.  This syntax will already be familiar to SPP programmers
and will quickly become obvious to even novice script programmers.

    Error recovery is implemented using the IFERR and IFNOERR statements
to "post" an error handler that is called at the end of a block of code and
which checks for error conditions that may have occurred in that block.
The syntax for these statements is of the form:


    iferr { <statement> } 		ifnoerr { <statement> }
        <error action statement> 	    <success action statement>


    iferr { 				ifnoerr {
        <block of statements> 	    	    <block of statements>
    } then 				} then
        <error action statement> 	    <success action statement>


    iferr { 				ifnoerr {
        <block of statements> 		    <block of statements>
    } then { 				} then {
        <block of error stmts> 		    <block of success action stmts>
    } 					}


The IFERR is grammatically equivalent to the IF statement and means "if an
error occurs during the processing of the enclosed code, execute the error
action statement to follow".  IFNOERR is the same except that the sense
of the test is reversed and the action statements are executed only if the
enclosed code completes without error.  Additionally, these statements take
an ELSE clause allowing both forms of the test to be combined.  For example,


    iferr { 				ifnoerr {
        <block of statements> 		    <block of statements>
    } then { 				} then {
        <error stmts> 		    	    <success stmts>
    } else { 				} else {
        <success stmts>	    	    	    <error stmts>
    } 					}


In all cases 

    o  Curly braces around the code to be checked are required,
    o  Curly braces are required when any action is a compound block 
    o  The THEN statement is optional if a single statement is executed
       as part of the action block
    o  The THEN statement is required for a compound action or when using
       an ELSE clause
    o  It is a syntax error for a condition block to itself directly contain
       an IFERR or IFNOERR statement and action statements, i.e.  IFERR
       statements may not be nested


    To make effective use of these statements a few points need to be
kept in mind:

    o   The check for errors happens only after ALL statements in the
	condition block are executed;
    o   Statements which generate errors did not execute to completion,
	subsequent code relying on that result cannot be trusted
    o   Code in the condition block which executes following an initial
	error may itself trigger errors due to the failure of a previous
	statement or the resulting side-effects;

This implies that IFERR statements should be used to target only critical
pieces of code where a particular error condition might be expected, and/or
where an action block could reasonably react to that error.  As an example
of how ignoring these points could be problematic consider the code snippet:

	iferr {
	    task_a ()
	    task_b () | scan (x)
	    task_c (x)
	} then {
	    error (99, "An error occurred\n")
	}

All three tasks in the condition block will be executed, however the
behavior of the code being check depends on which task in the block fails;
If 'task_a' fails there may be no consequences for the remaining calls,
however if 'task_b' fails the value of 'x' may never be set and 'task_c'
may also fail (or at least produce spurious results).  Cascading errors like
this will also be trapped and the action statement will still execute, but
the system error message strings will be incomplete (more about that below).

    While it is possible to have a failure from each statement in a condition
block branch immediately to the action block by checking each statement
individually, doing so would permit poor programming practices such as
iteratively testing for the name of the failed task and taking different
recovery methods in the action block.  If this is actually required for the
script to recover cleanly, the recommended method is to put an IFERR block
around smaller pieces of code where the recovery statements relate more
directly to the code being checked.

Errors trapped by IFERR statements include:

    o  System exceptions (FPE, segfault, etc) thrown by compiled tasks
    o  SPP error() returns from compiled tasks
    o  CL script error() assertions

Below we discuss errors which cannot be trapped using the IFERR syntax as
well as strategies for how to handle those errors which can be detected.
We'll also see how to determine which task in a condition block failed
and why.


The 'erract' Environment Variable
----------------------------------

	The ECL has a new 'erract' environment variable used to control the
different aspects of the error handling.  This is a whitespace-delimited
string comprised of the following options:


    abort	Script task should abort at an error and begin error
		recovery back to the command-line

    noabort	Task should not abort, but continue execution if possible

    trace	Print a traceback of the calling sequence including all
		line numbers and calling statements

    notrace	Print only the error message, no linenumbers or calls

    clear	Clear the error params (i.e. $errmsg, $errnum, $errtask)
		at each new task call.  This reseets the params with each
		task invocation allowing them to be examined after each
		call regardless of whether the code is in an IFERR block.

    noclear	Do not clear the CL error params at each new task call,
		the params are only reset when an error is encountered.

    flpr	Automatically issue a 'flpr' when an error is seen.  This
		is used to flush any failed task from the process cache to
		avoid potential future problems caused by a corrupted task.

    noflpr	Do not issue a 'flpr' when an error is seen, tasks remain
		in the process cache, possibly in an error state.

    full	Print a complete traceback of the calling sequence.

    nofull	Print only the error report for the task causing the error
		and none of its parents.


The default value is set as:

    set erract = "abort trace flpr clear full"

Note that erract is implemented as an environment variable rather than
as a new CL parameter (similar to the ehinit/epinit params) in order to
minimize changes in the CL parameter file itself during the transition
to the ECL.  The difference is that the 'set' (or 'reset') command must
be used to define the values, whereas with ehinit/epinit they may be
assigned directly.  For this variable it is also possible to (re)define
a single parameter without affecting other options, e.g.

    cl> show erract			# print options
    abort trace flpr clear full	
    cl> set erract = "noabort"		# reset one of them
    cl> show erract			# print options again
    noabort trace flpr clear full



Error Handling: Then and Now
----------------------------

    To better understand the new error detection and recovery behavior
(and to document this for future reference), let's look at the old error
mechanisms of the CL language:  Any command called from the CL executes in a
context defined by the task hierarchy initiating the command, i.e. from the
command-line CL prompt one has a "first" task context, scripts calling child
(compiled or script) tasks push a new CL context inheriting the current
CL environment and who's 'parent' is the context that invoked the task.

    In the traditional CL with an error occuring in a compiled task,
recovery first takes place in the SPP code who may choose to either handle
the error itself or may abort completely by doing a long-jump back to the
IRAF main() procedure (i.e. an EA_FATAL error type).  In this latter case,
the process binary (running as a detached process from the CL) sends an
error() command back to the CL telling it the task has terminated abnormally
(a normal task shutdown leaves the executable simply waiting for more input
from the CL, e.g. another task to execute).  This returned error() statement
is the same CL error() command one would use to abort a script task, and its
effect is to tell the CL to abort the current context and long-jump back
to the command-line after cleaning up running processes and freeing the
dictionary space (what the CL uses to catalog tasks/packages, parameters,
etc).  [NOTE: Whether it is a system exception or a programmer-posted error,
the error sent back to the CL has always included both the error code and
message, it is just that the CL has never made use of these until now.]
Similarly, errors which occur while running script tasks (e.g. 'task not
found' errors, invalid use of string values, divide-by-zero from local
script variables, etc) also end up in the same CL error() procedure via
internal procedure calls made while executing the script.

    Syntax errors are caught when the script is 'compiled' into the opcode
execution stack and are reported before the script begins to execute.
A script calling a child script containing a syntax error cannot trap
that error even though it will not be reported until the child script is
'compiled' just prior to execution.  We assume that all script tasks are
well-formed and free of ntax errors.

    ECL error recovery is somewhat simplified by the fact that errors,
either from external tasks or the execution of scripts, all converge in
a single procedure in the CL source code.  The trick is to modify the
runtime behavior of the CL so that once we know we have an error we can
branch to conditional code instead of simply jumping all the way back to the
command line.  Since we also wish to improve the error reporting we'd also
like make better use of information about how the failed code was called.

    The first step is to realize that when executing a script the CL
language is "compiled" into a series of 'opcode' instructions comprising an
intermediate runtime language (similar to assembly language).  Scripts are
run by executing the opcode instruction at the current 'program counter'
location, pushing task arguments or getting labels for jumps from the
dictionary, restoring a previous CL context, etc.  The compilation stage
already has information about the script line being parsed so by adding
this line-number to the opcode instruction it is now possible to trace a
fault in any opcode back to the originating line of the script, and from
there back up to the command line through the calling tree.  This extra
information makes the runtime size of the script slightly larger so
extremely large scripts may experience "dictionary full" problems not
previously seen (various CL buffer sizes were increased to help offset
this problem).  This relatively minor change is all that is required to
address the problems mentioned above in error reporting.

    Error trapping and recovery is done in a manner similar to the
implementation in SPP:  The IFERR statement isn't actually an instruction
in the runtime script, rather it is used to tell the parser to insert
code around the block to be checked using traditional IF statements.
As an example, consider

	iferr { 
	    task1 (arg1, arg2) 
	    task2 (arg1)
	} then {
	    recovery ()
	}

When compiled this is the equivalent of writing


	_errpsh () 
	task1 (arg1, arg2) 
	task2 (arg1)
	if (_errpop () != 0) {
	    recovery ()
	}

The _errpsh() is a hidden builtin function which "pushes" an error
structure onto the runtime stack, the _errpop() test at the end then
queries that structure to see whether any statement since the previous
push set the error flag and filled in the structure with the task name,
line number and other information.  The push also temporarily deactivates
the behavior of the error() function so it no longer aborts entirely,
allowing the script to continue after cleaning up the current error.

	In order to keep the model simple, nested iferr statements within
the same script are not currently implemented but are a possible future
enhancement.  Complications arise from examples such as

	iferr { 
	    task1 (arg1, arg2) 
	    iferr { task2 (arg1) } then 
	        recovery2 ()
	} then {
	    recovery1 ()
	}

Consider the case where task1() succeeds and task2() fails and is
recovered properly with the recovery2() procedure.  As far as the outer
IFERR block is concerned, did an error occur or not?  If the remainder of
the script depends on task2() succeeding then the answer is possibly 'no'
(depending on what the recovery does) and we should additionally call
the recovery1() procedure (who is responsible for dealing with an error
condition in that block), if there is no dependency then we may want
*any* failure to be considered, or perhaps even have a way to "clear"
error conditions within the block.  Now assume instead it is the first
task which fails and that triggers the second to fail because we depend
on the first succeeding, how should we post the error number/message for
the script?  We simply disallow nested IFERR statements for the moment
to avoid dealing with these complex interactions


New CL parameters
===================

	On order for script programmers to make use of errors that have
been trapped by the ECL, one generally needs access to the details of
that error, e.g. the message, task name, error number, etc.  To this end
the ECL implements several new pseudo-parameters and builtin functions
containing this information.  These include

    Param	    Function        Meaning
    -----	    --------        -------
    $errno	    errno()	    The system error number
    $errmsg	    errmsg()	    The system error message string
    $errtask	    errtask()	    Task which created the error

By default these parameters are re-defined as each task is called, in theory
allowing a script to trap errors without the IFERR by doing something like

	mytask1 () 
	if ($errno != 0) <statement>
	mytask2 () 
	if ($errno != 0) <statement> 
	    :

This behavior can be modified by the 'erract' environment variable 'clear'
or 'noclear' settings so that they only change when an error condition is
found (i.e. erract set to 'noclear', tasks which complete successfully
do not modify variables).

	Additionally, a new $err_dzvalue pseudo-parameter is defined to
be used by the CL interpreter when a divide-by-zero condition is encountered
in the CL itself.  (This value has no builtin function equivalent.)
This is an integer and will be cast to floating-point automatically if
needed, the default value of 1 (one) was chosen to allow the script to
continue executing but it should be noted that this value is only used
when an error is found within an IFERR block.  For example,

	ecl> = 1 / 0
	ERROR: integer divide by zero
	ecl> = 1. / 0.
	ERROR: floating divide by zero

However,

	ecl> iferr {
	>>>     = 1 / 0
	>>> } then ;;
	Warning on line 31 of : integer divide by zero - using $err_dzvalue = 1
	1

Note the warning message indicating the use of the parameter followed by the
result.



What Errors Are NOT Trapped
===========================

	As mentioned above, not all CL errors can or should be trapped
by the new system.  The (incomplete) list of error conditions which 
CANNOT be trapped during task execution using the IFERR or other new
features includes:

    o CL-language syntax errors
    o CL internal errors, for example
	- invalid procedure arguments (e.g. "parameter not found")
	- improper usage of intrinsic procedures (e.g. log(-10) )
	- operand type mis-matches (e.g. "s1 + x")
	- parser errors (e.g. newline in string)
    o CL runtime errors
	- too many background jobs (e.g. "no available job slots")
	- insufficient resource messages (e.g. out of memory)
	- can't read/write/create files (e.g. permissions problem on uparm$)
	- ambiguous task name
	- scan/print string exceeds max length
    o User-defined error messages and returns (i.e. the script writer
	outputs an error message and returns from the procedure but 
	does not use something like thea CL error() function to abort.
	For instance, a script prints "I can't run this on Tuesdays" and
	returns to the command-line but does not otherwise post an error
	condition for the calling context.

    

============================================
Command-line History and BackSpace Revisions
============================================

	The ECL now implements the common GNU Readline interface for input
handing meaning that many familiar tcsh-like features such as Up/Down-Arrow
history, Left/Right cursor-position movement, and <Tab> command-line
completion of tasks, filenames and parameters are now understood in the
IRAF environment.  It follows that many of the problems encountered with
the DEL/BS key to erase characters when entering input on the commandline
have also been eliminated on most systems since the readline interface
internally handles the delete-key mappings imposed on most systems.

	It is important to note that this implementation was done so as
to not interfere with the native IRAF ehist/epar cursor and history
mechanism.  From the ECL prompt, all commands recognized by readline()
interface (including user mappings defined in an ".inputrc" file) will
be honored.  If that command is ehist/epar or one of the recognized
IRAF history editing metacharacters then these will be processed in the
traditional IRAF manner.

	Should a problem with readline input be found, it can be disabled
from the user's session by adding the string "noreadline" to the CL
'ehinit' parameter, e.g.

	ecl> cl.ehinit = cl.ehinit // " noreadline"



Input Command Summary
---------------------
	
	The following Control/Meta key sequences are understood by the 
readline() interface for command input:

  Basic Commands

    Ctrl-b 	    Move cursor back one character. 
    Ctrl-f 	    Move cursor forward one character. 
    DEL 	    Delete the character to the left of the cursor. 
    Backspace 	    Delete the character to the left of the cursor. 
    Ctrl-d 	    Delete the character underneath the cursor. 
    Ctrl-_ 	    Undo the last editing command
    Ctrl-x Ctrl-u   Undo the last editing command

    Up-Arrow 	    Move up through the command-history list
    Down-Arrow 	    Move down through the command-history list
    Left-Arrow 	    Move cursor left one character on command line
    Right-Arrow     Move cursor right one character on command line

  Cursor Movement Commands

    Ctrl-a 	Move to the start of the line. 
    Ctrl-e 	Move to the end of the line. 
    Meta-f 	Move forward a word, where a word is composed of letters/digits.
    Meta-b 	Move backward a word. 
    Ctrl-l 	Clear the screen, reprinting the current line at the top.

  Text Deletion Commands

    Ctrl-k 	Kill the text from the current cursor position to the end of
		the line.
    Meta-d 	Kill from the cursor to the end of the current word, or, if
		between words, to the end of the next word. Word boundaries
		are the same as those used by Meta-f.
    Meta-DEL 	Kill from the cursor the start of the current word, or, if
		between words, to the start of the previous word. Word
		boundaries are the same as those used by Meta-b.
    Ctrl-w 	Kill from the cursor to the previous whitespace. This is
		different than Meta-DEL because the word boundaries differ.

    To yank (copy the most-recently-killed text from the kill buffer) the text
    back into the line:

    Ctrl-y 	Yank the most recently killed text back into the buffer at
		the cursor.
    Meta-y 	Rotate the kill-ring, and yank the new top. You can only do
		this if the prior command is Ctrl-y or Meta-y. 

  History Searching Commands

    Ctrl-r	Search backward through the history for a particular string
    Ctrl-s	Search forward through the history for a particular string
    ESC		Terminate the search
    Ctrl-g	Terminate the search and restore original line

	As each character of the search string is typed, Readline displays
    the next entry from the history matching the string typed so far. An
    incremental search requires only as many characters as needed to
    find the desired history entry.  To find other matching entries in
    the history list, type Ctrl-r or Ctrl-s as appropriate from the current
    search position.

    NOTE:  In many terminal settings the Ctrl-s key is mapped to the tty
   	'stop' character and the window will appear to no longer accept 
	input.  In these cases a Ctrl-q will normally return the terminal
	to proper function and so the forward search mechanism isn't
	generally recommended.



=====================
New Builtin Functions
=====================

Error-Handling Functions
------------------------

	The following builtin functions were added as alternatives to the
matching CL parameters.  The difference is almost entirely stylistic and
the rules about the longevity of the values described above apply in either
case.

      errmsg ()		Return last error message string (i.e. cl.$errmsg)
      errcode ()	Return last integer error code  (i.e. cl.$errno)
      errtask ()	Return taskname posting fatal error (i.e. cl.$errtask)

Examples:

	iferr {
	    sometask (par1, ....)
	} then {
	    printf ("Error in '%s': %s\n", errtask(), errmsg())
	    # or equivalently
	    printf ("Error in '%s': %s\n", $errtask, $errmsg)
	}



String Functions
----------------

	Beginning with V2.12.2 several new functions were added to the
CL to improve string handling and the provide complementary functions
to those which already exist.  Items marked with a '*' first appeared in
V2.12.2, all others are new to this release.  

New functions include:

    isindef (expr)							(*)
       	Can be used to check for INDEF values in expressions.  INDEF
       	values may be tested for equality, however when otherwise used
       	in a boolean expression the result of the boolean is also
       	INDEF.  This function can be used to trap this particular
       	case, or for INDEF strings/variable directly.  Result is a
       	boolean yes/no.

       	Example:
	    cl> junk = fscan (threshold, tval)
	    cl> if (isindef (tval) == yes) 
	    	error (0, "INDEF 'threshold' parameter value")

    strlwr (str)							(*)
    strupr (str)							(*)
       	Convert the string to lower/upper case, returns a string.     

       	Example:
	    cl> s1 = "test" ; s2 = "TEST"
	    cl> = strupr (s1) ; = strlwr (s2)
	    TEST
	    test

    strstr (str1, str2)							(*)
       	Search for first occurance of 'str1' in 'str2', returns index
       	of the start of 'str1' or zero if not found.

       	Example:
	    cl> = strstr ("imh", "imhead.imh")
	    1
	    cl> = strstr ("head", "imhead.imh")
	    3

    strldx (chars, str)							(*)
       	Complement to the stridx() which returns the last occurance of
       	any of 'chars' in 'str'.  Returns index of last char or zero
       	if not found.

       	Example:
	    cl> = strldx (".", "junk.fits")
	    5
		

    strlstr (str1, str2)						(*)
       	Search for last occurance of 'str1' in 'str2', returns index
       	of the start of 'str1' or zero if not found.

       	Example:
	    cl> = strlstr ("imh", "imhead.imh")
	    8

       [NOTE: String indices are 1-indexed in the CL]

    trim (str [, trimchars])
    triml (str [, trimchars])
    trimr (str [, trimchars])
	Trim any of the chars in 'trimchars' from the ends of 'str'.
	The trim() function removes chars from both the front and back
	of the string, triml() removes only from the left side of the
	string, and trimr() removes only from the right side.  If the
	'trimchars' argument is not specified the whitespace chars
	(tab and space) are assumed.  

			 
       	Example:
	    cl> printf ("'%s'\n", trim ("   test   "))
	    'test'
	    cl> = trimr ("/iraf/iraf///////", "/")
	    /iraf/iraf

	    To check for strings containing only whitespace:

	        if (trim (foo) == "")
	            error (0, "no legal value specified for 'foo'")


        The new string functions are particularly useful for dealing with
pathnames where one needs to find and extension, separate a file from a
path prefix, trim trailing slashes. and so on.

	Additionally, the existing substr() function has been modified to
allow a 'last' index greater than a 'first' index, in which case the return
string is reversed.


Trig Functions
--------------

	The following trigonometric functions have been added as new builtins
to the CL.  These complement existing functions as well as provide utility
versions to simplify degree/radian conversion.

      asin (arg)	    Inverse SIN, result in radians
      acos (arg)	    Inverse COS, result in radians

      rad (rad_arg)	    Convert arg in radians to degrees
      deg (deg_arg)	    Convert arg in degrees to radians

      dsin (deg_arg)	    Sine function, arg in degrees
      dcos (deg_arg)	    Cosine function, arg in degrees
      dtan (deg_arg)	    Tangent function, arg in degrees
      dasin (arg)	    Inverse sine function, result in degrees
      dacos (arg)	    Inverse cosine function, result in degrees
      datan2 (y, x)	    Inverse tangent function, result in degrees


Utility Functions
-----------------

  The following utility functions have been added.

      fp_equal (arg1, arg2) Floating point compare (w/in machine precision)
      hypot (x, y)	    Euclidean distance (i.e. sqrt (x*x + y*y))
      sign (arg)	    Sign of argument (-1 or 1)


  Examples:
	cl> = fp_equal (1.2345, 1.234)
	0
	cl> = hypot (3, 4)	# may also take real arguments
	5
	cl> = sign (-23)	# may also take real arguments
	-1


Bitwise Operations
------------------

	The following bitwise operands have been added in the V2.12.2b.  
Note that these are bitwise operands and not logical operands.  While there 
is presently no direct need for these they are seen as potentially useful
in e.g. evaluating bit-flags stored in image header keywords and support the
goal of providing a richer scripting language.

      not (arg1)	    Bitwise boolean NOT of an integer  
      and (arg1, arg2)	    Bitwise boolean AND of two integers
      or (arg1, arg2)	    Bitwise boolean OR of two integers
      xor (arg1, arg2)	    Bitwise exclusive OR of two integers

  Examples:
      cl> = radix (12, 2)	# print bit pattern of number 12
      1100
      cl> = radix (13, 2)	# print bit pattern of number 13
      1101

      cl> = and (12, 13)	# 1100 & 1101 == 1100
      12	
      cl> = or (12, 13)	# 1100 | 1101 == 1101
      13	
      cl> = xor (12, 13)	# (1100 & ~1101) | (~1100 & 1101) == 1
      1	

      cl> = not (12)
      -13
      cl> = radix (not(12), 2)
      11111111111111111111111111110011



=================
Defined Constants
=================

	The ECL also introduces the ability to use common numerical and
physical constants in scripts as part of the language keyword set.  Constants
are, by convention, always upper case identifiers and are listed in the table
below:

  Numerical constants				
  +---------------------------------------------------------------------+
  |  Name		| Value			 | Units		|
  +---------------------------------------------------------------------+
  |  BASE_E          	| 2.7182818284590452353	 |			|
  |  FOURPI          	| 12.566370614359172953	 |			|
  |  GAMMA           	| .57721566490153286061	 |			|
  |  HALFPI          	| 1.5707963267948966192	 |			|
  |  LN_10           	| 2.3025850929940456840	 |			|
  |  LN_2            	| .69314718055994530942	 |			|
  |  LN_PI           	| 1.1447298858494001741	 |			|
  |  LOG_E           	| .43429448190325182765	 |			|
  |  PI              	| 3.1415926535897932385	 |			|
  |  RADIAN          	| 57.295779513082320877	 |			|
  |  SQRTOF2         	| 1.4142135623730950488	 |			|
  |  SQRTOFPI        	| 1.7724538509055160273	 |			|
  |  TWOPI           	| 6.2831853071795864769	 |			|
  +---------------------------------------------------------------------+

  Physical constants				
  +---------------------------------------------------------------------+
  |  Name		| Value			 | Units		|
  +---------------------------------------------------------------------+
  |  AU              	| 1.49597870691e11       |  m            	|
  |  GRAV_ACCEL      	| 9.80665e0              |  m / sec^2    	|
  |  GRAV_CONST      	| 6.673e-11              |  m^3 / kg s^2 	|
  |  LIGHT_YEAR      	| 9.46053620707e15       |  m            	|
  |  PARSEC          	| 3.08567758135e16       |  m            	|
  |  SPEED_OF_LIGHT  	| 299792458.0            |  m / sec      	|
  |  SOLAR_MASS      	| 1.98892e30             |  kg           	|
  +---------------------------------------------------------------------+

  For example, these may be used in scripts as:

	area = (PI * radius ** 2) 	# Compute area of circle.
	rad = degrees / RADIAN		# Convert degrees to radians



===============================================================================
# Post-Release Notes
===============================================================================



[IRAF logo] IRAF [NOAO logo] NOAO

Last updated: 06Mar2000