Chapter 9
Programming in ICL

 9.1 Control statements
  9.1.1 IF
  9.1.2 LOOP
 9.2 Procedures and command files
  9.2.1 Defining procedures — PROC
  9.2.2 Running procedures
  9.2.3 Listing procedures — LIST
  9.2.4 Editing procedures — EDIT, SET EDITOR
  9.2.5 Direct execution of statements during procedure entry
  9.2.6 Saving, loading and deleting procedures — SAVE, LOAD, DELETE
  9.2.7 Variables
  9.2.8 Finding out what is available — VARS, PROCS
  9.2.9 Tracing execution — SET (NO)TRACE
  9.2.10 Command files
  9.2.11 Running ICL as a batch job
 9.3 Exceptions
  9.3.1 Exception handlers
  9.3.2 Keyboard aborts
  9.3.3 SIGNAL
 9.4 ICL login files
  9.4.1 Hidden procedures
 9.5 Extending on-line help
 9.6 Example procedures

9.1 Control statements

These statements provide a means of defining ICL procedures and of controlling the flow of execution within them. There are four of them:

PROC
— Defines a procedure.
IF
— Provides a decision-making structure.
LOOP
— Provides a looping structure.
EXCEPTION
— Provides an error-handling structure.

Unlike direct statements, they can only be used in a procedure and are not accepted in direct mode.

9.1.1 IF

The IF statement is essentially the same as the block IF of Fortran. It has the following general form:

    IF expression  
        statements  
    ELSE IF expression  
        statements  
    ELSE IF expression  
        statements  
    ...  
    ELSE  
        statements  
    END IF

The expressions (which must give logical values) are evaluated in turn until one is found to be true; the following statements are then executed. If none of the expressions are true, the statements following ELSE are executed.

Every IF statement must begin with IF and end with END IF (or ENDIF). The ELSE IF (or ELSEIF) and ELSE clauses are optional, so the simplest IF statement would have the form:

    IF expression  
        statements  
    ENDIF

The following example procedure illustrates the use of the IF statement, and shows how they may be nested inside each other:

    PROC QUADRATIC A,B,C  
    { A Procedure to find the roots of the quadratic equation  
    { A*X**2 + B*X + C = 0  
       IF A=0 AND B=0  
          PRINT The equation is degenerate  
       ELSE IF A=0  
          PRINT Single Root is (-C/B)  
       ELSE IF C=0  
          PRINT The roots are (-B/A) and 0  
       ELSE  
          RE = -B/(2*A)  
          DISCRIMINANT = B*B - 4*A*C  
          IM = SQRT(ABS(DISCRIMINANT)) / (2*A)  
          IF DISCRIMINANT >= 0  
             PRINT The Roots are (RE + IM) and (RE - IM)  
          ELSE  
             PRINT The Roots are complex  
             PRINT (RE) +I* (IM) and  
             PRINT (RE) -I* (IM)  
          ENDIF  
       ENDIF  
    END PROC

9.1.2 LOOP

The LOOP statement is used to execute repeatedly a group of statements. It has three different forms:

LOOP:  This is the simplest form of looping structure:
    LOOP  
        statements  
    END LOOP

This form sets up an infinite loop. Fortunately, an additional statement (BREAK) can be used to terminate the loop; BREAK would normally appear inside an IF statement within the loop. For example:

    PROC COUNT  
    { A procedure to print the numbers from 1 to 10  
       I = 1  
       LOOP  
          PRINT (I)  
          I = I+1  
          IF I>10  
             BREAK  
          ENDIF  
       ENDLOOP  
    ENDPROC

LOOP FOR:  As it is frequently required to loop over a sequential range of numbers, a special form of the LOOP statement is provided for this purpose. It has the following form:
    LOOP FOR variable = expression1 TO expression2 [STEP expression3]  
        statements  
    END LOOP

This form is essentially equivalent to the DO loop in Fortran. The expressions specifying the range of values for the control variable are rounded to the nearest integer so that the variable always has an integer value. Using this form of the LOOP statement you can simplify the previous example as follows:

    PROC COUNT  
    { A procedure to print the numbers from 1 to 10  
       LOOP FOR I = 1 TO 10  
          PRINT (I)  
       ENDLOOP  
    ENDPROC

Note that there is an optional STEP clause in the LOOP FOR statement. If this is not specified, a STEP of 1 is assumed. The STEP clause can be used to specify a different value. A step of 1 must be specified to get a loop which counts down from a high value to a lower value. For example:

    LOOP FOR I = 10 TO 1 STEP -1

will count down from 10 to 1. LOOP WHILE:  The third form of LOOP statement specifies loops which terminate on a condition. It has the form:

    LOOP WHILE expression  
        statements  
    END LOOP

The expression is evaluated each time round the loop and if it has the logical value TRUE, the statements which form the body of the loop are executed. If it has the value FALSE, execution continues with the statement following END LOOP. Using this form you can write yet another version of the COUNT procedure:

    PROC COUNT  
    { A procedure to print the numbers from 1 to 10  
       I = 1  
       LOOP WHILE I<=10  
          PRINT (I)  
          I = I+1  
       ENDLOOP  
    ENDPROC

In the above case, the LOOP WHILE form is more complicated than the LOOP FOR form. However, LOOP WHILE can be used to express more general forms of loop where the termination condition is something derived inside the loop. An example is a program which prompts you for an answer to a question and has to keep repeating the prompt until a valid answer is received:

    FINISHED = FALSE  
    LOOP WHILE NOT FINISHED  
       INPUT Enter YES or NO:  (ANSWER)  
       FINISHED = ANSWER = ’YES’ OR ANSWER = ’NO’  
    END LOOP

9.2 Procedures and command files



LIST List a procedure


EDIT Edit a procedure
SET EDITOR Change editor used by EDIT


SAVE Save a procedure
LOAD Accept commands from a saved procedure
DELETE Delete a procedure


PROCS List procedure names
VARS List procedure variables


SET (NO)TRACE Switch tracing of procedures on/off


An ICL procedure is like a subroutine in Fortran. It allows you to write a sequence of ICL statements which can later be run with a single command. The procedure may have parameters which are used to pass values to the procedure and return values from it.

9.2.1 Defining procedures — PROC

To define a procedure, type a PROC command which specifies the name of the procedure and the names of its parameters. ICL then returns a new prompt using the name of the procedure (rather than ICL>) to show that you are in the procedure entry phase of procedure mode. The statements that make up the procedure are then entered, followed by an END PROC or ENDPROC to mark the end of the procedure. For example:

    ICL> PROC SQUARE_ROOT X  
    SQUARE_ROOT> { An ICL procedure to print the square root of a number  
    SQUARE_ROOT> PRINT The Square Root of (X) is (SQRT(X))  
    SQUARE_ROOT> END PROC  
    ICL>

9.2.2 Running procedures

To run the procedure you have entered, use its name as an ICL command and add any parameter values required:

    ICL> SQUARE_ROOT (2)  
    The Square Root of         2 is 1.414214

9.2.3 Listing procedures — LIST

The LIST command lists a procedure on the terminal. Just type ‘LIST’, followed by the name of the procedure you want to list:

    ICL> LIST SQUARE_ROOT  
 
        PROC SQUARE_ROOT X  
        { An ICL procedure to print the square root of a number  
        PRINT The Square Root of (X) is (SQRT(X))  
        END PROC  
 
    ICL>

9.2.4 Editing procedures — EDIT, SET EDITOR

Typing in procedures directly is fine for very simple procedures, but for anything complex it is likely that some mistakes will be made. When this happens, it will be necessary to edit the procedure. Editing can be done from within ICL using standard editors. For example the command:

    ICL> EDIT SQUARE_ROOT

can be used to edit the SQUARE_ROOT procedure. By default the TPU editor is used. It is also possible to select the EDT or LSE editors using the SET EDITOR command. For example, to select EDT, type:

    ICL> SET EDITOR EDT

When editing a procedure there are two possible options:

It is possible to create procedures from scratch using the editors. However, it is recommended that procedures be typed in directly to start with. The advantage is that during direct entry, any syntactic errors will be detected immediately. Thus, if you mistype the PRINT line in the above example you get an error message as follows:

    SQUARE_ROOT> PRINT The Square Root of (X is (SQRT(X))  
    PRINT The Square Root of (X is (SQRT(X))  
                                ^  
    Right parenthesis expected

The error message consists of the line in which the error was detected, a pointer which indicates where in the line ICL had got to when it found something was wrong, and a message indicating what was wrong. In this case it encountered the ’is’ string when a right parenthesis was expected. Following such an error message, you can use the command line editing facility to correct the line and reenter it. If the same error occurred during procedure entry using an editor, the error message would only be generated at the time of exit from the editing session, and it would be necessary to edit the procedure again to correct it.

9.2.5 Direct execution of statements during procedure entry

It is sometimes useful to have a statement executed directly while entering a procedure. When using the direct entry method, this can be done by prefixing the command with a ‘%’ character. For example:

    ICL> PROC SQUARE_ROOT X  
    SQUARE_ROOT> %HELP

would give you on-line help information you might need to complete the procedure. If the ‘%’ was omitted, the HELP command would be included as part of the procedure.

9.2.6 Saving, loading and deleting procedures — SAVE, LOAD, DELETE

Procedures created by direct entry will only exist for the duration of an ICL session. If you need to keep them longer, they need to be saved in a disk file. This is achieved by means of the SAVE command. To save your SQUARE_ROOT procedure on disk you would use:

    ICL> SAVE SQUARE_ROOT

To load it again, probably in a subsequent ICL session, you would use the LOAD command.

    ICL> LOAD SQUARE_ROOT

The SAVE command causes the procedure to be saved in a file with name SQUARE_ROOT.ICL in the current default directory. LOAD will load the procedure from the same file, also in the default directory. However, with LOAD it is possible to specify an alternative directory if required:

    ICL> LOAD DISK$USER:[ABC]SQUARE_ROOT

If you have many procedures, you may not want to save and load them all individually. It is possible to save all the current procedures using the command:

    ICL> SAVE ALL

This saves them in a single file with the name SAVE.ICL. They may then be reloaded with the command:

    ICL> LOAD SAVE

The SAVE ALL command is rarely used, however, because it is executed automatically when you exit from ICL. This ensures that ICL procedures do not get accidentally lost because you forget to save them.

Procedures can be deleted with the DELETE command. Just follow the command name with the name of the procedure to be deleted:

    DELETE  name

9.2.7 Variables

Any variable used within a procedure is completely distinct from a variable of the same name used outside the procedure or within a different procedure, as can be seen in the following example:

    ICL> X=1  
    ICL> PROC FRED  
    FRED> X=1.2345  
    FRED> =X  
    FRED> END PROC  
    ICL> FRED  
    1.234500  
    ICL> =X  
            1  
    ICL>

When you run the procedure FRED you get the value of the variable X within the procedure. Then, typing ‘=X’ gives the value of X outside the procedure, which has remained unchanged during execution of the procedure. This feature has the consequence that you can use procedures freely without having to worry about any possible side effects on variables outside them.

The situation is exactly the same as that in Fortran where variables in a subroutine are local to the subroutine in which they are used. In Fortran, the COMMON statement is provided for use in cases where it is required to extend the scope of a variable over more than one routine. ICL does not have a COMMON facility, but does provide an alternative mechanism for accessing variables outside their scope using the command VARS and the function VARIABLE.

9.2.8 Finding out what is available — VARS, PROCS

The VARS command lists all the variables of a procedure. It has one parameter, which is the name of the procedure. If the parameter is omitted, the outer level variables, i.e. those that are not part of any procedure, are listed. Thus, after the previous example you would get:

    ICL> VARS FRED  
                     X  REAL     1.2345000000000E+00  
    ICL> VARS  
                     X  INTEGER          1  
    ICL>

VARIABLE():  The function VARIABLE gives the value of a specified variable in a specified procedure, for example:
    ICL> =VARIABLE(FRED,X)  
    1.234500  
    ICL>

and thus allows a variable belonging to a procedure to be accessed outside that procedure.

Note that the variables belonging to a procedure continue to exist after it finishes execution, and if the procedure is executed a second time they will retain their values from the first time through the procedure on entry to the procedure for the second time. PROCS:  To find the names of all the current procedures, use the ‘PROCS’ command:

    ICL> PROCS  
 
    SQUARE_ROOT  
 
    ICL>

9.2.9 Tracing execution — SET (NO)TRACE

The commands SET TRACE and SET NOTRACE switch ICL in and out of trace mode. When in trace mode, each statement executed will be listed on the terminal. Trace mode is very useful for debugging procedures. The commands can be issued either from direct mode to turn on tracing for the entire execution of a procedure, or inserted in the procedure itself, making it possible to trace just part of its execution.

9.2.10 Command files

You have seen that the LOAD command ‘loads’ a procedure that had previously been stored in a file by a SAVE command. What

    ICL> LOAD SQUARE_ROOT

actually does is to read ICL commands from the file SQUARE_ROOT.ICL and obey them. In the case of SQUARE_ROOT, these commands define a procedure of the same name. However, there is nothing to stop us storing any ICL commands you like in a file of type .ICL, and then you can load and execute them using the LOAD command. The PROC statement is designed only for creating procedures, so for creating general .ICL files you must use a normal editor. These general .ICL files are called Command Files.

A common use of command files is to store a set of command definitions for a monolith. You have already seen an example of this in file KAPPA_DIR:KAPPA.PRC. When you startup the KAPPA package, this command:

    ICL> LOAD KAPPADIR:KAPPA.PRC

is automatically executed, and this causes the command definitions it contains to become effective. An example of setting up your own command file is given in Section 11.8.

A subtlety to be aware of is the difference between storing commands within a procedure in a command file, and just storing the commands. For example, if you wanted to store the command definitions for your TESTx programs (see Chapter 11) in a procedure (given that these programs had all been put into a monolith called TEST), you could do this from within ICL as follows:

    ICL> PROC TEST  
    TEST> { Define commands to run the TESTx programs  
    TEST> DEFINE TESTC TEST  
    TEST> DEFINE TESTI TEST  
    TEST> DEFINE TESTL TEST  
    TEST> DEFINE TESTR TEST  
    TEST> END PROC  
    ICL> SAVE TEST

In a later ICL session you could load this procedure from the file TEST.ICL in which it had been stored:

    ICL> LOAD TEST

This command would read the commands from TEST.ICL and obey them, and this would have the effect of defining the procedure TEST. However, the DEFINE commands within the procedure would not be executed until the procedure was executed. Thus, the commands TESTC etc would not be recognised until you had run the procedure as follows:

    ICL> TEST

However, if you used your favourite editor to store the following ICL commands in the command file TEST.ICL (independently of any ICL session):

    { Define commands to run the TESTx programs  
    DEFINE TESTC TEST  
    DEFINE TESTI TEST  
    DEFINE TESTL TEST  
    DEFINE TESTR TEST

in your next ICL session you could execute these commands by typing:

    ICL> LOAD TEST

The difference from the previous method is that the commands TESTC etc are now defined and can be used immediately without having to execute a procedure.

9.2.11 Running ICL as a batch job

It is sometimes useful to run one or more ICL procedures as a batch job in VMS. It is quite easy to set this up by using a parameter with the ICL command to specify a file from which commands will be taken:

    $ ICL filename

This form of the command is equivalent to typing ICL and then typing:

    ICL> LOAD filename

Note that a LOAD file may include direct commands as well as procedures. In order to create a Batch job, you must set up a file which contains all the procedures wanted, a command (or commands) to run them, and an EXIT command to terminate the job. Here is the file for a simple Batch job to print a table of square roots using your earlier example procedure:

    PROC SQUARE_ROOT X  
    { An ICL procedure to print the square root of a number  
       PRINT The Square Root of (X) is (SQRT(X))  
    END PROC  
 
    PROC TABLE  
    { A procedure to print a table of square roots of numbers from 1 to 100  
       LOOP FOR I=1 TO 100  
          SQUARE_ROOT (I)  
       END LOOP  
    END PROC  
 
    { Next, the command to run this procedure  
 
    TABLE  
 
    { And then an EXIT command to terminate the job  
 
    EXIT

This file can be generated using the EDIT command from DCL. If the procedures have already been tested from ICL, it is convenient to use a SAVE ALL command (or exit from ICL) to save them, and then edit the SAVE.ICL file to add the additional direct commands. Suppose this file is called TABLE.ICL, then to create a batch job, a command file is needed which could be called TABLE.COM and would contain the following:

    $ ICL TABLE  
    $ EXIT

It might also need to contain a SET DEF command to set the appropriate directory, or a directory specification on the TABLE file name if it is not in the top level directory.

To submit the job to the batch queue, the following command is used:

    $ SUBMIT/KEEP TABLE

The /KEEP qualifier specifies that the output file for the batch job is to be kept. This file will appear as TABLE.LOG in your top level directory and will contain the output from the batch job. An /OUTPUT qualifier can be used to specify a different file name or directory for it.

9.3 Exceptions



SIGNAL Signal an ICL exception


Error conditions and other unexpected events are referred to as Exceptions. When such a condition is detected in direct mode, a message is output. For example, if you enter a statement which results in an error:

    ICL> =SQRT(-1)  
    SQUROONEG   Square Root of Negative Number  
    ICL>

you get a message consisting of the name of the exception (SQUROONEG) and a description of the nature of the exception. A full list of ICL exceptions is given in Chapter 19.

If the error occurs within a procedure, the message contains a little more information. For example, if you use your square root procedure with an invalid value, you get the following messages:

    ICL> SQUARE_ROOT (-1)  
    SQUROONEG   Square Root of Negative Number  
    In Procedure: SQUARE_ROOT  
    At Statement: PRINT  The Square Root of (X) is (SQRT(X))  
    ICL>

If one procedure is called by another, the second procedure will also be listed in the error message. For example, if you run the following procedure:

    PROC TABLE  
    { Print a table of Square roots from 2 down to -2  
       LOOP FOR I = 2 TO -2 STEP -1  
         SQUARE_ROOT (I)  
       END LOOP  
    END PROC

you get:

    ICL> TABLE  
    The Square Root of 2 is 1.414214  
    The Square Root of 1 is 1  
    The Square Root of 0 is 0  
    SQUROONEG   Square Root of Negative Number  
    In Procedure: SQUARE_ROOT  
    At Statement: PRINT  The Square Root of (X) is (SQRT(X))  
    Called by: TABLE  
    ICL>

9.3.1 Exception handlers

It is often useful to be able to modify the default behaviour on an error condition. You may not want to output an error message and return to the ICL > prompt, but rather to handle the condition in some other way. This can be done by writing an Exception Handler. Here is an example of an exception handler in the SQUARE_ROOT procedure:

    PROC SQUARE_ROOT X  
    { An ICL procedure to print the square root of a number  
       PRINT The Square Root of (X) is (SQRT(X))  
       EXCEPTION SQUROONEG  
    { Handle the imaginary case  
          SQ = SQRT(ABS(X))  
          PRINT The Square Root of (X) is (SQ&’i’)  
       END EXCEPTION  
    END PROC

Now running the TABLE procedure gives:

    ICL> TABLE  
    The Square Root of 2 is 1.414214  
    The Square Root of 1 is 1  
    The Square Root of 0 is 0  
    The Square Root of -1 is 1i  
    The Square Root of -2 is 1.414214i  
    ICL>

The exception handler has two effects. First, the code contained in the exception handler is executed when the exception occurs. Second, the procedure exits normally to its caller (in this case TABLE) rather than aborting execution completely and returning to the ICL > prompt.

Exception handlers should be placed after the normal code, but before the END PROC statement. There may be any number of exception handlers in a procedure, each for a different exception. The exception handler begins with an EXCEPTION statement specifying the exception name, and finishes with an END EXCEPTION statement. Between these may be any ICL statements, including calls to other procedures.

An exception handler does not have to be in the procedure causing the exception, but could be in a procedure further up the chain of calls. In your example you could put an exception handler for SQUROONEG in TABLE rather than in SQUARE_ROOT:

    PROC TABLE  
    { Print a table of Square roots from 2 down to -2  
       LOOP FOR I = 2 TO -2 STEP -1  
          SQUARE_ROOT (I)  
       END LOOP  
       EXCEPTION SQUROONEG  
          PRINT ’Can’’t handle negative numbers - TABLE Aborting’  
       END EXCEPTION  
    END PROC

giving:

    ICL> TABLE  
    The Square Root of 2 is 1.414214  
    The Square Root of 1 is 1  
    The Square Root of 0 is 0  
    Can’t handle negative numbers - TABLE aborting  
    ICL>

Below is an example of a pair of procedures which use an exception handler for floating point overflow in order to locate the largest floating point number allowed on the system. Starting with a value of 1, this is multiplied by 10 repeatedly until floating point overflow occurs. The highest value found in this way is then multiplied by 1.1 repeatedly until overflow occurs, then by 1.01 etc:

    PROC LARGE  START, FAC, L  
    { Return in L the largest floating point number before  
    { overflow occurs when START is repeatedly multiplied by FAC.  
       L = START  
       LOOP  
          L = L * FAC  
       END LOOP  
       EXCEPTION FLTOVF  
    { This exception handler doesn’t have any code - it just  
    { causes the procedure to exit normally on overflow.  
       END EXCEPTION  
    END PROC  
 
    PROC LARGEST  
    { A Procedure to find the largest allowed floating point number on the system.  
       FAC = 10.0  
       LARGE  1.0, (FAC), (L)  
       LOOP WHILE FAC > 0.00000001  
          LARGE (L), (1.0+FAC), (L)  
          FAC = FAC/10.0  
       END LOOP  
       PRINT  The largest floating point number allowed is (L)  
    END PROC

9.3.2 Keyboard aborts

One exception which is commonly encountered is that which results when a ctrl/C is entered on the terminal. This results in the exception CTRLC and may therefore be used to abort execution of a procedure and return ICL to direct mode. However, an exception handler for CTRLC may be added to a procedure to modify the behaviour when a ctrl/C is typed.

9.3.3 SIGNAL

The exceptions described up to now have all been generated internally by the ICL system, or, in the case of CTRLC, initiated by the user. It is also possible for ICL procedures to generate exceptions which may be used to indicate error conditions. This is done by the SIGNAL command which has the form:

    SIGNAL  name  text

where name is the name of the exception, and text is the message text associated with the exception. The exception name may be any valid ICL identifier. Exceptions generated by SIGNAL work in exactly the same way as the standard exceptions listed in SG/5. An exception handler will be executed if one exists, otherwise an error message will be output and ICL will return to direct mode.

One use of the SIGNAL command is as a means of escaping from deeply nested loops. The BREAK statement can be used to exit from a single loop, but is not applicable if two or more loops are nested. In these cases, the following structure could be used:

    LOOP  
       LOOP  
          LOOP  
             ...  
             IF FINISHED  
                SIGNAL ESCAPE  
             END IF  
             ...  
          END LOOP  
       END LOOP  
    END LOOP  
 
    EXCEPTION ESCAPE  
    END EXCEPTION

where the exception handler again contains no statements, but simply exists to cause normal procedure exit, rather than an error message, when the exception is signalled.

9.4 ICL login files

If you frequently need to define commands to run particular programs, it is convenient to define them in an ICL login file which will be loaded automatically each time ICL is started up. An ICL login file works in exactly the same way as a DCL login file. ICL uses the logical name ICL_LOGIN to locate this file, so store the required definitions in a file called LOGIN.ICL in your top level directory, and put the following definition in your DCL LOGIN.COM file:

   $ DEFINE ICL_LOGIN DISK$USER:[ABC]LOGIN.ICL

where DISK$USER:[ABC] needs to be replaced by the actual directory used. This command file will then be loaded automatically whenever you start up ICL and can include procedures, definitions of commands, or indeed any valid ICL command. Below is an example of an ICL login file which illustrates some of the facilities which may be used:

    { ICL Login File  
 
    { Define TYPE command  
    DEFSTRING  T(YPE)  DCL TYPE  
 
    { Define EDIT command  
    HIDDEN PROC EDIT name  
       IF INDEX(name,’.’) = 0  
          #EDIT (name)  
       ELSE  
          $ EDIT (name)  
       ENDIF  
    END PROC  
 
    { Login Message  
    PRINT  
    PRINT   Starting ICL at (TIME()) on (DATE())  
    PRINT

9.4.1 Hidden procedures

The definition of the EDIT command shown in the login file above is done using a Hidden procedure. Since EDIT is an ICL command to edit procedures, if you just used DEFSTRING to define EDIT as DCL EDIT, you would lose the ability to edit ICL procedures — the EDIT command would always edit VMS files. The procedure used to redefine EDIT gets around this by testing for the existence of a dot in the name of the file to be edited using the INDEX function. If a dot is present, it assumes that a VMS file is being edited and issues the command ‘$ EDIT (name)’. If no dot is present, it assumes that an ICL procedure is being edited and the command ‘#EDIT (name)’ is issued. The # character forces the internal definition of EDIT to be used, rather than the definition currently being defined.

The procedure is written as a hidden procedure, indicated by the word HIDDEN preceding PROC. A hidden procedure works in exactly the same way as a normal procedure, but it does not appear in the listing of procedures produced by a PROCS statement, nor can it be edited, deleted, or saved from within ICL. It is convenient to make all procedures in your login file hidden procedures so that they do not clutter your directory of procedures and cannot be deleted accidentally.

9.5 Extending on-line help



HELP Display on-line documentation
DEFHELP Define the source of Help information


ICL includes a HELP command which provides on-line documentation on ICL itself. Using the DEFHELP command it is possible to extend this facility to access information on the commands you have added. In order to do this, you need to create a help library in the normal format used by the VMS help system. This is described in the VAX/VMS documentation for the Librarian utility. You can then specify topics from this library which will be available using the ICL HELP command by using a command of the form:

    DEFHELP EDIT LIBRARY.HLB

This will cause a:

    HELP EDIT

command to return the information on EDIT in help library LIBRARY.HLB, rather than in the standard ICL library.

9.6 Example procedures

This final section lists some examples of ICL procedures taken from the KAPPA manual (SUN/95). They should help you understand how to use the programming facilities of ICL correctly. Many of the commands used in the procedures are KAPPA applications. Unsharpmask:  Suppose you have a series of commands to run on a number of files. You could create a procedure to perform all the stages of the processing, deleting the intermediate files that it creates.

     PROC UNSHARPMASK NDFIN CLIP NDFOUT  
 
     { Clip the image to remove the cores of stars and galaxies above  
     { a nominated threshold.  
        THRESH (NDFIN) TMP1 THRHI=(CLIP) NEWHI=(CLIP) \  
 
     { Apply a couple of block smoothings with boxsizes of 5 and 13  
     { pixels.  Delete the temporary files as we go along.  
        BLOCK TMP1 TMP2 5  
        $ DELETE TMP1.SDF;0  
        BLOCK TMP2 TMP3 13  
        $ DELETE TMP2.SDF;0  
 
     { Multiply the smoothed image by a scalar.  
        CMULT TMP3 0.8 TMP4  
        $ DELETE TMP3.SDF;0  
 
     { Subtract the smoothed and renormalised image from the input image.  
     { The effect is to highlight the fine detail, but still retain some of the  
     { low-frequency features.  
        SUB (NDFIN) TMP4 (NDFOUT)  
        $ DELETE TMP4.SDF;0  
     END PROC
Multistat:  A common use of procedures is likely to be duplicate processing for several files. Here is an example procedure that does that. It uses some intrinsic functions which look just like Fortran.
     PROC MULTISTAT  
 
     { Prompt for the number of NDFs to analyse.  Ensure that it is positive.  
        INPUTI Number of frames:  (NUM)  
        NUM = MAX(1, NUM)  
 
     { Find the number of characters required to format the number as  
     { a string using a couple of ICL functions.  
        NC = INT(LOG10(NUM)) + 1  
 
     { Loop NUM times.  
        LOOP FOR I=1 TO (NUM)  
 
     { Generate the name of the NDF to be analysed via the ICL function SNAME.  
          FILE = ’@’ & SNAME(’REDX’,I,NC)  
 
     { Form the statistics of the image.  
          STATS NDF=(FILE)  
        END LOOP  
     END PROC

If NUM is set to 10, the above procedure obtains the statistics of the images named REDX1, REDX2, …REDX10. The ICL variable FILE is in parentheses because its value is to be substituted into parameter NDF. There is a piece of syntax to note which often catches people out. Filenames passed via ICL variables, such as FILE in the above example, must be preceded by an @. Flatfield:  Here is another example, which could be used to flat field a series of CCD frames. Instead of executing a specific number of files, you can enter an arbitrary sequence of NDFs. When processing is completed a !! is entered rather than an NDF name, and that exits the loop. Note the ˜ continuation character. (It’s not required but it’s included for pedagogical reasons.)

     PROC FLATFIELD  
 
     { Obtain the name of the flat-field NDF.  If it does not have a  
     { leading @ insert one.  
        INPUT Which flat field frame?: (FF)  
        IF SUBSTR(FF,1,1) <> ’@’  
           FF = ’@’ & (FF)  
        END IF  
 
     { Loop until there are no further NDFs to flat field.  
        MOREDATA = TRUE  
        LOOP WHILE MOREDATA  
 
     { Obtain the frame to flat field.  Assume that it will not have  
     { an @ prefix. Generate a title for the flattened frame.  
          INPUT Enter frame to flat field (!! to exit): (IMAGE)  
           MOREDATA = IMAGE = ’!!’  
           IF MOREDATA  
              TITLE = ’Flat field of ’ & (IMAGE)  
              IMAGE = ’@’ & (IMAGE)  
 
      { Generate the name of the flattened NDF.  
              IMAGEOUT = (IMAGE) & ’F’  
              PRINT Writing to (IMAGEOUT)  
 
      { Divide the image by the flat field.  
 
              DIV IN1=(IMAGE) IN2=(FF) OUT=(IMAGEOUT) ~  
                  OTITLE= (TITLE)  
           ENDIF  
        END LOOP  
     END PROC
Colstar:  Some KAPPA applications, particularly the statistical ones, produce output parameters which can be passed between applications via ICL variables. Here is an example to draw a perspective histogram centred about a star in a nominated data array from only the star’s approximate position. The region about the star is stored in an output NDF file. Note, in a procedure meant to be used in earnest, there would be checks that input and output names begin with an @.
     PROC COLSTAR FILE,X,Y,SIZE,OUTFILE  
 
     {+  
     {  Arguments:  
     {     FILE = FILENAME (Given)  
     {        Input NDF containing one or more star images.  
     {     X = REAL (Given)  
     {        The approximate x position of the star.  
     {     Y = REAL (Given)  
     {        The approximate y position of the star.  
     {     SIZE = REAL (Given)  
     {        The half-width of the region about the star’s centroid to be  
     {        plotted and saved in the output file.  
     {     OUTFILE = FILENAME (Given)  
     {        Output primitive NDF of 2*%SIZE+1 pixels square (unless  
     {        constrained by the size of the data array or because the location  
     {        of the star is near an edge of the data array.  
     {-  
 
     { Search for the star in a 21x21 pixel box.  The centroid of the  
     { star is stored in the ICL variables XC and YC.  
        CENTROID INPIC=(FILE) XINIT=(X) YINIT=(Y) XCEN=(XC) YCEN=(YC) ~  
          MODE=INTERFACE SEARCH=21 MAXSHIFT=14  
 
     { Convert the co-ordinates to pixel indices.  
        IX = NINT(XC + 0.5)  
        IY = NINT(YC + 0.5)  
 
     { Find the upper and lower bounds of the data array to plot. Note  
     { this assumes no origin information is stored in the data file.  
        XL = MAX(1, IX - SIZE)  
        YL = MAX(1, IY - SIZE)  
        XU = MAX(1, IX + SIZE)  
        YU = MAX(1, IY + SIZE)  
 
     { Create a new IMAGE file centred on the star.  
        PICK2D INPIC=(FILE) OUTPIC=(OUTFILE) XSTART=(XL) YSTART=(YL) ~  
           XFINISH=(XU) YFINISH=(YU)  
 
     { Draw a perspective histogram around the star on the current  
     { graphics device.  
        COLUMNAR IN=(OUTFILE)  
 
     { Exit if an error occurred, such as not being to find a star  
     { near the supplied position, or being unable to make the plot.  
        EXCEPTION ADAMERR  
           PRINT Unable to find or plot the star.  
        END EXCEPTION  
     END PROC
Fancylook:  This creates a fancy display of an image with axes and a key showing data values. Note the need to give an expression combining the x-y bounds of the key to the LBOUND and UBOUND parameter arrays.
     PROC FANCYLOOK NDF  
 
     { Find the extent of the current picture.  
        GDSTATE NCX1=(FX1) NCX2=(FX2) NCY1=(FY1) NCY2=(FY2) NOREPORT  
 
     { Display the image with axes using the most-ornate font.  
        DISPLAY (NDF) MODE=PE AXES FONT=NCAR COSYS=D SCALOW=(LOW) SCAHIGH=(HIGH) \  
 
     { Find the extent of the image picture.  
        PICIN NCX1=(DX1) NCX2=(DX2) NCY1=(DY1) NCY2=(DY2) NOREPORT  
 
     { Determine the widths of the borders.  
        XL = DX1 - FX1  
        XR = FX2 - DX2  
        YB = DY1 - FY1  
        YT = FY2 - DY2  
 
     { Only plot a key if there is room.  
        IF MAX(XL, XR, YB, YT) > 0.0  
 
     { Determine which side has most room for the key, and derive the  
     { the location of the key. First, see if the key is vertical.  
           IF MAX(XL,XR) >= MAX(YB,YT)  
              WIDTH = MIN(0.4*MAX(XL,XR), 0.25*(DX2-DX1))  
              HEIGHT = MIN(6.0*WIDTH, 0.7*(DY2-DY1))  
              IF XL > XR  
                 XK1 = DX1 - 1.5 * WIDTH  
                 XK2 = DX1 - 0.5 * WIDTH  
              ELSE  
                 XK1 = DX2 + 0.5 * WIDTH  
                 XK2 = DX2 + 1.5 * WIDTH  
              ENDIF  
              YK1 = 0.5 * (DY2 + DY1 - HEIGHT)  
              YK2 = 0.5 * (DY2 + DY1 + HEIGHT)  
           ELSE  
 
     { Deal with horizontal key.  
              WIDTH = MIN(0.4 * MAX(YB,YT), 0.25 * (DY2-DY1))  
              HEIGHT = MIN(6.0 * WIDTH, 0.7 * (DX2-DX1))  
              IF YB > YT  
                 YK1 = DY1 - 1.5 * WIDTH  
                 YK2 = DY1 - 0.5 * WIDTH  
              ELSE  
                 YK1 = DY2 + 0.5 * WIDTH  
                 YK2 = DY2 + 1.5 * WIDTH  
              ENDIF  
              XK1 = 0.5 * (DX2 + DX1 - HEIGHT)  
              XK2 = 0.5 * (DX2 + DX1 + HEIGHT)  
           ENDIF  
 
     { Draw the key to fit within the current picture annotating with  
     { the scaling used in DISPLAY.  
           LUTVIEW LOW=(LOW) HIGH=(HIGH) LBOUND=[(XK1&’,’&YK1)] ~  
             UBOUND=[(XK2&’,’&YK2)] MODE=XY  
        ENDIF  
     END PROC