INDEX
*  SPIRES Protocols
+  Introduction
1  Overview of Command Facilities
1.1  How Commands Are Used In Protocols
1.2  Introduction to Variables
1.3  Variable Substitution
1.4  Introduction to Functions
1.5  Commands for Input/Output
1.6  Commands for Testing Conditions
1.7  Commands Controlling Execution Flow in a Protocol
2  Invoking and Executing a Protocol
2.1  Executing a Protocol in the Active File: The XEQ Command
2.2  Invoking a Protocol from a Subfile: The XEQ FROM and .. Commands
2.2.1  (Nesting and Chaining Protocols)
2.3  Other XEQ Commands
2.4  Creating Execution Breaks: The BREAK XEQ and CONTINUE XEQ Commands
2.5  (*) Preloading Protocols for Efficiency
3  Controlling Execution Flow in a Protocol
3.1  Block Constructs for Structured Programming
3.1.1  BEGINBLOCK...ENDBLOCK
3.1.2  Looping Blocks: WHILE...ENDWHILE and REPEAT...UNTIL
3.1.3  Other Features of Block Constructs
3.1.4  Some Restrictions
3.2  Nesting and Branching Within Protocols
3.2.1  Executing Internal Procedures: The XEQ PROC Command
3.2.2  Tracing Execution: The SHOW XEQ STACK Command
3.2.3  The JUMP (or GOTO) Command
4  User-Defined Variables and Vgroups
4.1  Defining and Compiling a Global Vgroup
4.1.0  Defining a Local Vgroup
4.1.1  The Elements in a Vgroup Definition
4.1.2  (*) Statistics for Compiled Vgroups
4.2  Using Global Vgroups and Static Variables
4.2.1  Setting (Allocating) a Global Vgroup
4.2.2  Assigning Values to Static Variables
4.2.3  Examining the Values of Static Variables
4.2.4  Storing and Restoring Values
4.2.5  Destroying Vgroups or Stored Static Groups
4.3  Static Variable Arrays
4.4  (*) Details on Dynamic Variables
4.5  Passing Parameters Between System Components
4.5.1  The $SETPARMS and $GETPARMS functions
4.5.2  Passing Parameters by Reference
5  Protocol Commands and Syntax
5.1  Command Statements
5.1.1  Variable Value Assignment: The LET Command
5.1.1.1  (The Concatenation Operator)
5.1.2  Expressions With Mixed Type
5.2  The SET Command
5.3  Prompting the End-User for Input: The ASK Command
5.4  SET ASK
5.5  SET PROMPT
5.6  SET ECHO/NOECHO
5.7  ! Prefix
5.7a  : Prefix (The Colon Prefix)
5.8  Conditional Statements: The IF...THEN Command
5.8.1  The THEN and ELSE Commands
5.8.2  Comma Prefix
5.8.3  Percent Prefix, Unix Emulator
5.9  The SET MESSAGES (SET MES) Command
5.10  SET STOP/NOSTOP
5.10a  The SET XSTOP and SET NOXSTOP Commands
5.11  The WDSR Command
5.12  The WDSW, WDSE, and WDS Commands
5.13  The SET WDSR, SET WDSW, and SET WDST Commands
5.14  The "*" Command and the SHOW EVAL Command
5.15  PAUSE
5.16  The EVAL Command
5.17  Variable Substitution
5.18  Options on the SET TIMER Command: WAIT, PAUSE, and COUNTER
5.19  The WITHIN (WIT) Prefix
5.20  WITHIN LOG Prefix
5.20a  The WITH TIMELIMIT Prefix
5.21  (*) Commands for Monitoring Use of Memory
5.21.1  (*) The SHOW FREE CORE Command
5.21.2  (*) The SHOW SUBFILE MAP Command
5.22  The SET WIDTH Command
5.23  The WRITE FILE LOG Command
5.24  The CLEAR MESSAGE NUMBER Command
5.25  The SET SUBLATCH (SET SUBLAT) Command
6  System-Defined Variables
6.1  The STORE SETTINGS Command
6.2  Variable Types
6.2.1  (Flag Variables)
6.2.2  (Integer Variables)
6.2.3  (Character/String Variables)
6.2.4  (Line Variables)
6.3  Variables Classified by Function
6.3.1  Variables Used Primarily in Formats
6.3.2  Variables Allowed Only in User Defined Processing Rules
6.4  Variable Descriptions in Alphabetical Order
6.4.2  ($ACCOUNT)
6.4.3  ($ACTNUM ($ACTNO))
6.4.4  ($ASK)
6.4.5  ($AREANAME)
6.4.6  ($BIN)
6.4.8  ($CHRX and $CHRI)
6.5.0  ($CLEAR/$NOCLEAR ($CLR/$NOCLR))
6.5.2  ($COMMAND)
6.5.4  ($COMPXEQ)
6.5.6  ($CPUTIME)
6.5.7  ($CRTMODE)
6.5.8  ($CURCMD and $PRECMD)
6.5.9  ($DATE, $DATECC and $UDATE)
6.6.0  ($DEFQLOAD)
6.6.2  ($DELTA)
6.6.4  ($DIAG/$NODIAG)
6.6.8  ($ECHO/$NOECHO)
6.6.9  ($EDITOR (not applicable to Stanford environment))
6.6.9b  ($ELOGSCNT, $ELOGWCNT)
6.7.0  ($ELSE)
6.7.2  ($ENUM, $SNUM, and $PSNUM)
6.7.4  ($ENVIRON ($ENV))
6.7.6  ($ERRCODE ($MSGCODE, $MCODE))
6.7.7  ($ETIME)
6.7.8  ($FILEDATE and $FILETIME)
6.8.0  ($FILENAME)
6.8.2  ($FINDSTAT ($SRCHSTAT))
6.8.4  ($FORMAT)
6.8.6  ($FORTYPE ($FORTYP))
6.8.8  ($FRAME)
6.9.0  ($GETSPATH/$GETXPATH)
6.9.2  ($GLOFORMAT)
6.9.4  ($GOALREC)
6.9.6  ($GXCOUNT, $GPCOUNT, $PXCOUNT, and $PPCOUNT)
6.9.7  ($IDATA (not applicable to Stanford environment))
6.9.8  ($INFOX and $INFOI)
6.9.9  ($INTXEQ)
6.10.0  ($IOCOUNT)
6.10.1  ($JOBNUM)
6.10.2  ($KEY)
6.10.3  ($LASTRESNUM)
6.10.4  ($LASTXEQ ($XEQLAST))
6.10.6  ($LENGTH ($LEN))
6.10.8  ($LIST/$NOLIST)
6.11.0  ($MAIL)
6.11.2  ($MSGINT)
6.11.4  ($MSGLEV)
6.11.6  ($MSGLIT)
6.11.8  ($MSGNUM ($MNUM))
6.12.0  ($MULTILOG variable)
6.12.2  ($NAME)
6.12.4  ($NEXTSLOT and $NEXTSLOTKEY)
6.12.6  ($NEXTWDSR)
6.12.8  ($NEXTWDSW)
6.12.8.1  ($NO)
6.13.0  ($NOLEV ($NOL))
6.13.1  ($ODATA (not applicable to Stanford environment))
6.13.1a  $PARM
6.13.1b  ($PARMCNT)
6.13.2  ($PATHCUR)
6.13.3  ($PATHKEY)
6.13.4  ($PATHNUM)
6.13.6  ($PROGRAM)
6.13.8  ($PROMPT)
6.13.9  ($PROXYID, $PROXYNAME, $PROXYPHONE, $PROXYEMAIL)
6.14.0  ($PRTCNT)
6.14.2  ($PRTLVL)
6.14.6  ($RECSEP)
6.14.8  ($RELPOS)
6.15.0  ($RESCNT)
6.15.1  ($RESHIST)
6.15.2  ($RESNAME)
6.15.3  ($RESNUM)
6.15.4  ($RESULT)
6.15.6  ($RETCODE (not applicable to Stanford environment))
6.15.7  ($SEARCHMOD)
6.15.8  ($SEED)
6.16.0  ($SELCHR, $DISCHR, and $MINCHR)
6.16.2  ($SELECT ($SEL))
6.16.4  ($SELECTED)
6.16.6  ($SEP)
6.16.8  ($SERX and $SERI)
6.17.0  ($SETFORMAT)
6.17.1  ($SITE)
6.17.2  ($SLOT/$NOTSLOT)
6.17.3  ($SORTCODE)
6.17.4  ($STACK)
6.17.6  ($STOP/$NOSTOP)
6.17.8  ($SUBCODE)
6.18.0  ($SUBFSIZE)
6.18.2  ($SUBLATCH)
6.18.4  ($SUPERMAX and $SUPERVAL)
6.18.5  ($SYSTYPE)
6.18.6  ($TCOMMENT)
6.18.8  ($TERMINAL ($TERM, $TER))
6.19.2  ($TERMTYPE ($TTYPE))
6.19.4  ($TIME and $UTIME)
6.19.6  ($TIMELIMIT)
6.19.8  ($TIMER)
6.20.2  ($TRACE/$NOTRACE)
6.20.4  ($TRANSFER ($TRA))
6.20.6  ($TRUE/$FALSE)
6.20.7  ($UNIVID, $UNIVNAME, $UNIVPHONE, $UNIVEMAIL)
6.20.8  ($UPDTYPE)
6.21.2  ($UPPER/$UPLOW)
6.21.4  ($USER ($USE) and $GCODE ($GRO))
6.21.6  ($UCODE ($USERCODE, $UCD))
6.21.8  ($USERNAME variable)
6.22.2  ($VERSION)
6.22.4  ($WARNX and $WARNI)
6.22.6  ($WDSL ($WDSLINE))
6.22.8  ($WDSR)
6.23.2  ($WDST)
6.23.4  ($WDSW)
6.23.6  ($WIDTH ($WID))
6.23.8  ($XEQ)
6.23.9  ($XEQLVL)
6.24.1  ($XTRACE)
6.24.2  ($YES/$NO)
6.24.4  ($ZRESULT/$NZRESULT ($ZRES/$NZRES))
7  Functions
7.1  Overview
7.1.1  Functions for Variable Data Type Conversion (Summary)
7.1.2  String Manipulation Functions (Summary)
7.1.3  Variable Manipulation Functions (Summary)
7.1.4  Functions for Numeric and Packed Decimal Manipulation (Summary)
7.1.5  Functions for Subfile and Data Value Information (Summary)
7.1.6  Functions for Date and Time Manipulation (Summary)
7.1.7  Other Miscellaneous Functions (Summary)
7.2  Function Descriptions in Alphabetical Order
7.2.0b  (The $ABS Function)
7.2.0c  (The $ACCESSTEST Function)
7.2.1  (The $AMATCH Function)
7.2.2  (The $APMATCH Function)
7.2.2a  (The $ARMATCH Function)
7.2.2b  (The $ASCII Function)
7.2.3  (The $ASET Function)
7.2.3.B  (The $BLDLCTR and $UBLDLCTR functions)
7.2.3.D  (The $BNF Semantics)
7.2.3a  (The $ASORT and $ASORTX Functions)
7.2.3b  (The $AUTHPARMS Function)
7.2.3c  (The $BNF Function)
7.2.4  (The $BREAK Function)
7.2.4a  (The $CAPITALIZE Function)
7.2.5  (The $CASE Function)
7.2.6  (The $CHANGE Function)
7.2.6a  (The $CHANGELIST Function)
7.2.7  (The $CHARACTER Function)
7.2.7a  (The $CHECK Function)
7.2.7b  (The $CLRSUBF Function)
7.2.7c  (The $COLUMNTEST Function)
7.2.8  (The $COMPARE Function)
7.2.9  (The $DATEIN Function)
7.2.9.1  (The $DATEOUT Function)
7.2.9.2  (The $DAYS Function)
7.2.9.3  (The $DATETIME Function)
7.2.9.4  (The $DATEOUT Function for datetime)
7.3.1  (The $DECIMAL Function)
7.3.2  (The $DEFQTEST Function)
7.3.2a  (The $DENCODE Function)
7.3.3  (The $DOUBLE Function)
7.3.4  (The $DYNASET Function)
7.3.4a  (The $DYNAZAP Function)
7.3.5  (The $DYNGET, $DYNPUT, and $DYNZAP Functions)
7.3.6  (The $EDIT Function)
7.3.7  (The $ELEMINFO and $ELIDINFO Functions)
7.3.8  (The $ELEMTEST, $ELNOTEST and $ELIDTEST Functions)
7.3.8a  (The $ENCIPHER Function)
7.3.8b  (The $ENCODE and $DECODE Functions)
7.3.9  (The $EVALUATE Function)
7.4.1  (The $EXP Function)
7.4.1b  (The $FACTORIAL Function)
7.4.1c  (The $FACTORS Function)
7.4.2  (The $FLAG and $NOT Functions)
7.4.2a  (The $FRAMETEST Function)
7.4.2b  (The $GETELOG Function)
7.4.2c  (The $GETPARMS Function)
7.4.3  (The $GETxVAL Functions: $GETUVAL, $GETCVAL, $GETIVAL, $GETXVAL)
7.4.3a  (The Structural Occurrence Map with the $GETxVAL Functions)
7.4.3b  (The $GETVMATCH Function)
7.4.3c  (The $GETVOCC Function)
7.4.4  (The $HEX Function)
7.4.5  (The $INDEXINFO Function)
7.4.5.1  (The $INDEXNUM Function)
7.4.5.2  (The $INDEXTERM Function)
7.4.5a  (The $INSERT Function)
7.4.6  (The $INSETL, $INSETR, and $INSETC Functions)
7.4.7  (The $INTEGER Function)
7.4.8  (The $ISSUECMD Function)
7.4.8a  (The $ISSUEMSG Function)
7.4.9  (The $LEFTSTR Function)
7.5.1  (The $LEFTSUB Function)
7.5.2  (The $LINE Function)
7.5.3  (The $LOG Function)
7.5.4  (The $LOOKSUBF and $LOOKSUBG Functions)
7.5.4a  (The $LOOKSYS Function)
7.5.5  (The $MATCH Function)
7.5.6  (The $MOD Function)
7.5.7  (The $NEGATIVE Function)
7.5.8  (The $NOLF Function)
7.5.8b  (The $NORMALIZE Function)
7.5.9  (The $PACKED Function)
7.6.1  (The $PACKTEST Function)
7.6.2  (The $PARSE and $PARSESTRIP Functions)
7.6.2a  (The $PATHFIND and $PATHINFO Functions)
7.6.3  (The $PMATCH Function)
7.6.4  (The $PRECISION and $WINDOW Functions)
7.6.4a  (The $PRISMINFO Function)
7.6.5  (The $PROCSUBG Function)
7.6.6  (The $RANDOM Function)
7.6.7  (The $REAL Function)
7.6.7a  (The $RECINFO Function)
7.6.8  (The $RECTEST Function)
7.6.8a  (The $REF Function)
7.6.9  (The $REMAINDER Function)
7.7.0  (The $RESINFO Function)
7.7.1  (The $RETYPE Function)
7.7.1b  (The $REVERSE Function)
7.7.2  (The $RIGHTSTR Function)
7.7.3  (The $RIGHTSUB Function)
7.7.3a  (The $RMATCH Function)
7.7.3b  (The $ROOT Function)
7.7.3c  (The $RSTRIP and $LSTRIP Functions)
7.7.4  (The $SEARCHINFO Function)
7.7.4a  (The $SEARCHTEST Function)
7.7.4b  (The $SET Function)
7.7.4c  (The $SETPARMS Function)
7.7.5  (The $SIZE Function)
7.7.6  (The $SPAN Function)
7.7.7  (The $SQRT Function)
7.7.8  (The $SQU Function)
7.7.9  (The $SSW Function)
7.8.0  (The $STATGET and $STATPUT Functions)
7.8.1  (The $STRING Function)
7.8.2  (The $STRIP Function)
7.8.3  (The $SUBSTR Function)
7.8.3a  (The $SYSEVAL Function)
7.8.3b  (The $SYSINFO Function)
7.8.4  (The $SYSTEM Function)
7.8.5  (The $TEST Function)
7.8.6  (The $TIMEIN and $TIMEOUT Functions)
7.8.6b  (The $TRANINFO Function)
7.8.7  (The $TRANSLATE Function)
7.8.7.1  (The $TRIM Function)
7.8.8  (The $TRUNC Function)
7.8a  (The $TYPE Function)
7.9.1  (The $TYPETEST Function)
7.9.1.1  (The $UNEDIT Function)
7.9.1.1a  (The $VARGET and $VARPUT Functions)
7.9.1.2  (The $VARTEST Function)
7.9.1.2b  (The $VERIFY Function)
7.9.1.3  (The $VGROUPALTER Function)
7.9.2  (The $VGROUPINIT Function)
7.9.3  (The $WDS Function)
7.9.3.1  (The $WINDOW Function)
7.9.3a  (The $WORKDAYS Function)
7.9.3b  (The $XDATE Function)
7.9.4  (The $XEQSTACK Function)
7.9.5  (The $XSTR Function)
7.9.5a  (The $XSUB Function)
7.9.5b  (The $YYCALC Function)
7.9.5c  (The $YYTEST Function)
7.9.6  (The $ZAP Function)
7a  System Variables and Functions Associated With Triples
7a.1  (The $MAKE Function)
7a.2  (The $NEW Variable)
7a.3  (The $ANY Variable)
7a.4  (The $MADE Function)
7a.5  (The $UNMAKE Function)
7a.6  (The $UNMAKETRIPLE ($UNMAKETRI) Function)
7a.7  (The $LOOKUP Function)
7a.8  (The $ATTRIBUTE ($ATTR), $OBJECT ($OBJ), and $VALUE Functions)
7a.9  (The $GROUP ($GRP) Function)
7a.10  (The $GROUPSIZE ($GRPSIZE, $GRPSZ) Function)
7a.11  (The $GROUPSORT ($GRPSORT) Function)
7a.12  (The $GROUPELEMENT ($GROUPELEM, $GRPELEM) Function)
7a.13  (The SHOW TRIPLES (SHO TRI) and CLEAR TRIPLES (CLE TRI) Commands)
7a.14  (Using Triples -- Some Guidelines and Examples)
8  Protocol Debugging
8.1  Diagnostics
8.2  Display and Modification of Variable Values
8.3  Ascertaining the XEQ Level
8.4  Protocol and Command Execution Tracing
8.4.1  Protocol Tracing: SET XTRACE
8.4.2  Logging for Protocol and Format Tracing: SET TLOG
8.4.3  Error Logging: SET ELOG
8.5  Protocol Testing in Command Mode
9  Building a Protocols File
9.1  The PERFORM BUILD PROTOCOLS Command
9.2  Using PUBLIC PROTOCOLS
9.3  Formatting Protocols Code: The PFORMAT Command
10  Compiling a Protocol
10.1  Compiling a Protocol: Overview
10.2  Compiling Protocols
10.2.1  Zapping Protocols
10.3  Executing Compiled Protocols: The SET COMPXEQ Command
10.4  Efficient Execution: Coding Strategies
10.5  Variable Communication: Local and Global Vgroups
10.6  Executing Compiled and Non-Compiled Protocols Simultaneously
10.7  (*) Statistics for Compiled Protocols
10.8  Compiling a Protocol the Old Way
10.9  Converting from the Old to the New Way of Compiling Protocols
11  Protocol Examples
11.1  (PERFORM)
11.2  (PAGINATE)
11.3  (CLEAR.TEXT)
11.4  (LIST.PROTOCOL)
11.5  (SHOW.FILE.PERMITS)
11.6  (SAVE.SETTINGS)
11.7  (RESTORE.SETTINGS)
11.8  (PAGE.TO.SECTION)
12  Protocol Exercises
12.1  (Using SPIRES system variables in a command)
12.2  (Executing a protocol stored in the active file)
12.3  (Printing the values of variables at the terminal)
12.4  (Substitution versus Evaluation)
12.5  (Using the ASK command to prompt for information from the terminal)
12.6  (Using the WDSW command)
12.7  (Using the ASK command with label statements and JUMP commands)
12.8  (Using the IF command)
12.9  (Using XEQ command options with a protocol in the active file)
12.10  (Choosing a protocol subfile)
12.11  (Using protocols in the PUBLIC PROTOCOLS subfile)
12.12  (Listing protocols in the PUBLIC PROTOCOLS subfile)
12.13  (Establishing your own protocol subfile)
12.14  (Adding protocols to your subfile and executing them)
12.15  (Displaying and modifying protocols in your protocol subfile)
12.16  (Establishing a SPIRES entry protocol)
:  Appendices
:1  Error Messages for VGROUP Compilation
:4.2.1.6.1  * LONG VARIABLE HAS OCC > 1
:4.2.1.6.2  * BAD LEN FOR VARIABLE TYPE
:4.2.1.6.3  * VARIABLE SUBSCRIPTING PROBLEM
:4.2.1.6.4  * UNKNOWN VARIABLE TYPE
:4.2.1.6.5  * VGROUP TOO LARGE
:4.2.1.6.6  * VGROUP VALUE CONVERSION ERROR
:4.2.1.6.7  * VGROUP VALUE ERROR
:4.2.1.6.8  * VALUE INCONSISTENT WITH MULTIPLE OCCS
:4.2.1.6.9  * VALUE MUST BE INTEGER
:4.2.1.6.10  * MULTIPLE OCC VALUE MUST BE INDEXED
:4.2.1.6.11  * INCONSISTENT STRING REDEFINITION
:4.2.1.6.12  * REDEFINITION LOOPING PROBLEM
:4.2.1.6.13  * ILLEGAL USAGE WITH DYNAMIC TYPE
:4.2.1.6.14  * VARIABLE TABLE OVERFLOW
:4.2.1.6.15  * REDEFINED VARIABLE CANNOT HAVE VALUE
:29  SPIRES Documentation

*  SPIRES Protocols

******************************************************************
*                                                                *
*                     Stanford Data Center                       *
*                     Stanford University                        *
*                     Stanford, Ca.   94305                      *
*                                                                *
*       (c)Copyright 1994 by the Board of Trustees of the        *
*               Leland Stanford Junior University                *
*                      All rights reserved                       *
*            Printed in the United States of America             *
*                                                                *
******************************************************************

        SPIRES (TM) is a trademark of Stanford University.

+  Introduction

What is a Protocol?

A protocol is a series of SPIRES and/or system commands, gathered together into a single data set or record, and generally devoted to accomplishing a single task or a series of closely related tasks.

Usually you store a protocol as a record in a SPIRES protocol subfile. [See 9.] Once you have set the protocol subfile for execution, with either the SET XEQ or the SET COMPXEQ command [See 2.2, 10.3.] you can cause a protocol to execute by issuing a single command:

  ..protocolname            <--or XEQ FROM protocolname

For example, to execute a protocol called "Print.Report" stored in the protocol subfile "ProtoFile":

  -> set xeq protofile
  -> ..print.report

"PrintReport" might consist of a few commands or a hundred commands -- in either case, the entire protocol is set in motion by the above commands. [You can also execute a protocol by placing it in your active file and issuing the XEQ command.]


Contents and Structure of a Protocol

A protocol is mostly made up of the same sorts of commands that you might issue in an online session, in "command" mode. Thus the commands issued interactively on the left below can also be issued within a protocol, as shown on the right:

  (Commands Online)                    (Sample Protocol)
  -------------------------------      ----------------------------
  -> select drinks                     * SAMPLE.PROTO
  -> set format display                ++DISPLAY
  -> for tree                          Select Drinks
  +> in active continue display 5      Set Format Display
                                       For Tree
                                       In Active Continue Display 5
                                       Return

In addition to the familiar set of interactive commands, protocols provide ways to nest to subroutines [See 1.7, 3.2.1.] or to execute commands only if certain conditions are met [See 1.6, 3.1.2, 5.8.] or to invoke one protocol from another in "nested" execution [See 2.2.] and so on. Many of these special facilities are not available interactively, but only as protocol statements (or as Uprocs in other SPIRES components such as formats).


Uses and Benefits of Protocols

What are protocols for? Though protocols can be used to accomplish any task involving series of commands, usually they're designed to work in close cooperation with other Prism or SPIRES features in order to customize a complete application. For instance, a protocol can interact with a SPIRES format by issuing the SET FORMAT command to set the format, later issuing commands that execute frames within the format or that use its frames to display and update records.

There are many advantages to accomplishing such tasks through protocol control, rather than doing them interactively: one big advantage is that end-users don't have to know the syntax of commands that a protocol executes on their behalf. This can provide greater comfort for an application's users while simultaneously strengthening the security of that application's data.


Labels and Procs

The basic skeleton of a protocol might be pictured like this:

  * Key            <--record-key of protocol -- the protocol's "name"
     :
  ++Label          <--label statement
  Command
  Command          <--virtually any command
     :
  Return           <--return to previous level, e.g., from protocol
                      to command level

In the diagram above, everything from "++Label" down to "Return" could be seen as a single Proc (i.e., a subroutine or self-contained procedure). A protocol is often structured in a number of separate labelled Procs along this model -- in fact, protocols for Prism are always structured in self-contained and labelled Procs. In protocols outside Prism, labels and Procs are optional, but they can help clarify the program's structure as well as providing a destination for a nesting or branching command like XEQ PROC or JUMP:

  * TEST.PROTO
      :
  Xeq Proc SELECT.FILE  <--Causes protocol to nest to Proc named SELECT.FILE
  Show Eval 'Testfile is now selected.'
  Xeq Proc SEARCH
      :
  Return
      :
  ++SELECT.FILE
  Select Testfile
  Set Format Test
  Return              <--Causes protocol to "return" to previous level,
      :                   and execute SHOW EVAL command above

Thus Procs help structure a protocol into subprograms, though (given the complex ways protocols interact with other tools such as Prism or formats), the structure of a protocol is rarely as tidy as in the example above. In any case, though the natural bent of a protocol is to execute commands from top down, you'll find numerous tools in upcoming sections to help control execution flow more precisely.


What's to Come

The rest of this document covers the following topics:

Much of the material covered in this document also applies, in most details, to procedural code in other SPIRES components: i.e., Uprocs in SPIRES formats, and Uprocs within file definition Userprocs. For information on procedural code in these components, see the manuals "SPIRES Formats" and "SPIRES File Definition". For information on protocols in Prism, see the document "Prism Applications".

1  Overview of Command Facilities

This chapter provides an overview of the command facilities most useful within a protocol. Many of these commands and procedures can be used, sometimes in slightly different form, as Uprocs within formats and file-definition Userprocs as well.

After discussing general uses for protocol commands, the chapter goes on to provide:

1.1  How Commands Are Used In Protocols

A protocol in SPIRES can contain virtually any SPIRES command that can be issued interactively (i.e., in "command mode"), and nearly any WYLBUR (or ORVYL or MILTEN) command that can be issued in an interactive, line-by-line session.

The forms and options of these commands are the same under protocol control as in "command" mode. Note that access to WYLBUR and ORVYL commands means you can use and save data sets, collect text, and so on, all under protocol control.

Protocols To Control the End-User's Environment

Since virtually any interactive command can be issued within a protocol, you can use a protocol to execute a long and repetitive or technical series of commands. The protocol can also establish a specially-controlled environment (perhaps with security features) to modify and/or simplify the end-user's view of his or her environment.

For example, the simple Prism protocol below, executed when the file is selected, uses the SET SEARCH MODIFIER command so that only "Public" records are retrieved in a search, and uses SET FILTER so that only date values of 1986 are displayed:

  * FILE
  ++SELECTION
  Set Search Modifier and Public = Yes
  Set Filter for Date Where Date = 1986
  Return

End-users never need to learn the complicated syntax of, say, SET FILTER, since the protocol executes the command on their behalf. An additional advantage is that end-users may not be able to circumvent the security provided by the commands. [Since protocols can issue most SPIRES commands, some commands mentioned in passing in this document are covered in detail elsewhere: for instance, filters are discussed in "SPIRES Technical Notes", SET SEARCH MODIFIER in "SPIRES Searching and Updating".]

Constructing Commands for the End-User

In addition to issuing sequences of literal commands, protocols can "construct" complex commands based on an end-user's responses to a simpler set of prompts or questions. In protocols written for SPIRES, you usually use the ASK command to prompt for end-user input, [See 1.6, 5.3.] then construct your command using this response.

In Prism, you never use ASK (since Prism handles end-user prompting), but you still might construct a command based on user input. For instance, a file of restaurants might install a search type called FRENCH, supported by a "protocol step" that executes when an end-user requests a search such as FIND FRENCH. The protocol step could perform the full search behind the scenes:

  * FILE
    :
  ++FRENCH.FOOD
  Perform Prism Search Cuisine = French or Country = France
  Return

In this case, the protocol helps a user benefit from SPIRES indexes without having to name the index or even know it exists. Prism and the protocol construct the search -- the user just answers a prompt or two. [See the "Prism Applications" document for details on PERFORM PRISM SEARCH.]

You can use protocols for your own convenience as well as your end-user's. For instance, if you always issue the same set of commands when you enter SPIRES, you can assemble the commands in a protocol and add it, with your account as key, to the ENTRY COMMANDS subfile. The protocol will execute automatically whenever you call SPIRES. [See the "SPIRES Searching and Updating" manual for details.]

For instance, below the user GQ.PRO below uses ENTRY COMMANDS to establish a personal protocol subfile for execution, and accomplish a few other tasks: [See 2.2 for SET XEQ, 10.3 for SET COMPXEQ for compiled protocols.]

  * GQ.PRO
       :
  Set Xeq My Protofile      <--or Set Compxeq for compiled protocols
  Set Messages 4            <--for automatic explanatory text
  Return

The next few sections discuss variables, system functions, and other useful protocol facilities.

1.2  Introduction to Variables

Variables in the broadest sense are reserved places where you or the system can store and access information. As you'll see, one of the most powerful features of a protocol is the way it can access and store this "variable" information.

Variables in SPIRES can be divided into two broad categories, system-defined variables and user-defined variables, which we'll discuss in turn. In addition, this section briefly introduces two important commands discussed in more detail later in the manual:

  Command to Assign      Purpose
  Value to Variable      of command
  -------------------    -------------------------------
  SET                    Assign value to certain system
                         variables (or set a condition)

  LET                    Assign value to user variable

System Variables

System variables are variables defined and maintained by SPIRES, such as $DATE (which holds the current date) or $RESULT (which represents the number of records in a current search result).

With some system variables, you can both access the value and set it, using the SET command: for instance, SET LENGTH [See 5.2.] resets the value of the system variable $LENGTH:

  -> set length 68
  -> show eval $length
  68

(If you have SET WYLBUR, prefix the SET LENGTH command with a !, so Wylbur will pass the command to SPIRES.)

In other cases, you can access the value of a system variable but not change it -- for instance, you could not reset $DATE.

The values of some system variables change as a result of command execution: for instance, a failed command sets $NO, and selecting a subfile sets $SELECTED. In cases like these, accessing the variable's current value can give you important information about the status of your end-user's current session. [See 6 for more information on system-defined variables.]

Example

For example, the line from a Prism protocol below tests $NO to see if the preceding command failed. If the command fails, the protocol sets the value of the Prism variable $STATUS to 'STOP', which tells Prism to discontinue processing the command sequence (e.g, selecting a file). The protocol also sets the variable $MSGLINE with a message to display to the end-user:

  ++CHECK.AUTH
     :
  - (Preceding lines check user's authorization)
  If $No Then Set MsgLine = 'Please check authorized code and try again.'
  Then Set Status = 'STOP'
  Return

User Variables

Besides system variables, SPIRES offers the facility of user-defined variables, which you define yourself. One common way to assign a value to a user-defined variable is to use the LET command [See 5.1.1.] as in the example below:

  Let CurrTime = $Time     <--assigns value of $TIME system variable
                              to the user variable CurrTime

As a system variable is generally prefixed by a dollar sign, a user variable is generally prefixed by a pound sign (#). Actually, to be more precise, when you're referring to a user variable's value you prefix its name with a '#'; when you're referring to its name you leave the '#' off:

  Let LineNum = #LastLine  <--"Assign the VALUE of the variable named
                               LastLine to the variable named LineNum"

If you ever need to use a pound sign in a statement that would otherwise be evaluated (with variable substitution) you can either put the pound sign in quotation marks to show it is a string, or, if that's not possible, double it.

Dynamic and Static User Variables

User-defined variables themselves come in two different flavors: dynamic variables and static (that is, pre-compiled) variables.

Dynamic variables are user variables that you define just by naming them and using them in an assignment statement such as a LET statement. [Dynamic variables can also be explicitly defined in a variable group or "vgroup" or in a file definition's Userproc, where they can be extremely useful. [See 4.4]] Dynamic variables are handy for quick or small-scale tasks, but for the most efficient use of system memory, you should rely primarily on static variables within a production application.

Static variables are user variables that you predefine and store together in a vgroup (variable-group), usually (for a protocol) in the system subfile VGROUPS. When your application needs to call on these static variables, your code can first allocate them as a group (using the SET VGROUP command), utilizing computer memory more efficiently than dynamic variables do. [See 4 and the following sections for complete details on defining and compiling vgroups.]

The Data Type of Variables

Variables can be of several data types: string, integer, flag, line, real, and several others. In manipulating user variables, it can be important to declare or confirm a variable's type in order to avoid conversion errors. [See 5.1.2.] For static variables, data type is generally part of the variable's definition within the vgroup. For dynamic variables, you can often confirm data type by using a SPIRES system function. [See 1.4, 7.]

The SHOW EVAL Command and Variables

To display a variable's value online, you can use the SHOW EVAL command, as in the example below:

   -> show eval $date             <---(displays a system variable's value)
   09/16/86
   -> let currentdate = $date
   -> show eval #currentdate      <---(displays a user variable's value)
   09/16/86

The SHOW EVAL command has other uses as well. [See 5.14 for details on the command.]

1.3  Variable Substitution

When a SPIRES or WYLBUR command contains a user-defined variable or a system variable, you'll sometimes want to force variable substitution before you pass the command on to be parsed and executed. (In "variable substitution" the variable's name, as it appears in the command prefixed with # or $, is replaced with the variable's current value, converted to a string.)

The way to force variable substitution in a command is to prefix the command with a slash ('/'), as in the example below:

  -> select paperbacks
  -> set ask updike
  -> /find author $ask
  -Result: 10 TITLES

The slash before the FIND command causes the value "updike" to be substituted for the system variable $ASK before the FIND command is actually executed. [The slash prefix is not allowed in formats or file-definition Userprocs.]

An important exception: there are four SPIRES commands -- IF, LET, EVAL, and SHOW EVAL -- that almost never use the slash prefix, because variable substitution occurs automatically with these commands. [See 5.1.1, 5.8, 5.17.]

The example below suggests when a slash prefix is needed and when not:

  -> show line abc              <---No variable is involved, so no
    126 GQ.ABC   .....              slash is necessary

  -> / show line $user          <---The slash is necessary to force
    126 GQ.ABC   .....              evaluation of the $USER variable

  -> show eval $user            <---Despite the variable, no slash is
  ABC                               necessary with SHOW EVAL

For more information about variable substitution, EXPLAIN VARIABLE SUBSTITUTION online or see the more detailed section later in this document. [See 5.17.]

1.4  Introduction to Functions

SPIRES offers a number of useful system functions for performing predefined operations on a value that you supply. Most system functions take the "input value" that you supply and return a second value that is the result of the function's operation. For instance, $CAPITALIZE('abc def') takes the input value 'abc def', and as a result of the function's processing returns the value 'ABC DEF'. The $SIZE function processing the same value returns the length of the value:

  -> show eval $capitalize('abc def')
  ABC DEF
  -> show eval $size('abc def')
  7

By the way, delimiter characters (such as the apostrophes above), can have a significant effect on how a function processes an input value. [See 7.1.] Without delimiters, the blank space in the input value below is ignored:

  -> show eval $capitalize(abc def)
  ABCDEF

There are several basic kinds of functions, including string manipulation functions and functions to convert the data type of variable values. [See 7 for a more detailed overview and alphabetical listing of functions.] The small guided tour below demonstrates only a few of the many functions available, namely $LEFTSTR (or $LSTR), $TYPE, and $INTEGER (or $INT):

  -> show eval $time
  15:47:40

  Use the $LEFTSTR function to return a "substring"
  based on the first two characters of $Time

  -> show eval $leftstr($time,2)
  15

  Store the value in a user variable:

  -> let hour = $leftstr($time,2)

  Use the $TYPE function to determine the data type
  of #hour:

  -> show eval $type(#hour)
  STR

  Convert Hour to have a data type of "integer"
  using the $INTEGER function:

  -> let hour = $integer(#hour)
  -> show eval $type(#hour)
  INT

Using A Function to Check or Convert Data Type

The functions for variable data type conversion [See 7.1.1.] are often important for converting a value's "data type" to a different type -- or checking to make sure the input value is legally convertible to a needed data type. For instance, to ensure that the value of the dynamic variable Number is legally convertible to an integer (and isn't, say, a decimal fraction):

  If $TypeTest(#Number,INT) ~= 'INT' then * (Number is not an integer)

1.5  Commands for Input/Output

This section provides a very brief overview of commands available to protocols for input and output. Two things to note: 1) many input/output tasks involving data base access are handled better by formats than by protocols; 2) some command facilities described below are not needed for Prism development (e.g., you would rarely or never need to use ASK or SHOW EVAL in a Prism application).

  Input/Output Command           Purpose
  ----------------------------   ---------------------------------
  ASK                            Prompt user for input at terminal
  WDSR                           Read line from active file
  WDSW, WDSE, IN ACTIVE prefix   Write to active file
  SHOW EVAL, * command           Display text at the terminal

To prompt your user for input at the terminal, you can use the ASK command. The user's response is stored in the variable $ASK, where you can test it; you can also have the command take a special response if the user only presses the RETURN or the ATTN/BREAK key:

  ASK PROMPT='Please enter your name' NULL='Jump Retry' ATTN='Jump Exit'

Here the user will be prompted with the string 'Please enter your name'; the protocol will JUMP to a label group labelled "Retry" if the user presses RETURN, and will jump to the label group "Exit" if the user presses BREAK. [See 5.3 for details on the ASK command, 3.2 for label groups, 3.2.3 for the JUMP command.]

As mentioned above, the ASK command is not used in Prism, which handles prompting of the end-user itself.

To read a line from the logged-on user's active file (rather than from the terminal), you can use the WDSR command. [See 5.11.] Again the value will be stored in $ASK.

To display a message to the terminal, you can use the SHOW EVAL command or the * command. [See 5.14.] For instance, the ASK command above might be followed by this command:

  SHOW EVAL 'Your name is ' $ASK

Note that SHOW EVAL evaluates variables such as $ASK automatically, but literal strings ('Your name is ') should be surrounded by apostrophes.

To write to the active file, you can use the WDSW or WDSE commands [See 5.12.] or the more versatile IN ACTIVE prefix, as in the example below:

  ++DISPLAY
  /Wdse Records added on $Date
  For Adds
  In Active Continue Display All, End = 'Jump Print'
    :

[See "SPIRES Searching and Updating" for more on the IN ACTIVE prefix.]

In addition to the input/output commands mentioned above, SPIRES offers methods for transmitting data to or from several other areas or devices besides the active file or the terminal. For a detailed discussion, see the manual "SPIRES Device Services".

1.6  Commands for Testing Conditions

Condition testing is a basic feature not only in protocols but also in formats and Userprocs. One common way to test a condition in SPIRES is to use the three basic statements IF, THEN, and ELSE:

  Condition Testing Command   Purpose
  -------------------------   ----------------------------------
  IF                          Test condition(s) following IF
  THEN                        Execute if condition is true
  ELSE                        Execute if condition is false

If the condition you name after IF is true, then subsequent commands prefixed by THEN are executed -- if the IF condition is false, then subsequent commands prefixed by ELSE are executed.

   If $Result Then Xeq Proc Result
   Else Xeq Proc NoResult

Actually, you can almost think of IF...THEN as a single command-complex, in the sense that an IF clause must always be followed by a THEN clause and THEN must always be preceded by IF. [You can use a colon in place of THEN: If $Result : Xeq Proc Result.] Likewise, ELSE is never used except after a preceding IF...THEN. For example, the statement below is incorrect because no THEN statement has been coded:

   If $Result Xeq Proc Result       <--invalid command

In addition to these condition commands, the block constructs REPEAT...UNTIL and WHILE...ENDWHILE also test conditions. These constructs are discussed in the following section. [See 1.7.]

What is a "condition"? A condition can be a single term, such as a flag variable whose value is either "1" (for TRUE, or ON) or "0" (for FALSE, or OFF):

  If $Selected Then...       <--(The $SELECTED system variable tells
                                whether or not a subfile is selected.)

It might also be a comparison of two terms (e.g., a comparison of two numbers):

  If #Total = 0 Then...

The condition might even be compound:

  If #Number > 0 Or #Number < 20 Then...

For a fuller description of the different kinds of conditions, see the later section on IF...THEN [See 5.8, 5.8.1 for a discussion of ELSE.]

Example of IF...THEN

In the following example, the user variable Hour is assigned a value based on the current hour. Then the IF statement tests whether the hour is earlier than eight in the morning or later than six at night: if so, the protocol executes the proc NightRates. If the condition is false (if the hour is between eight and six), the protocol instead executes the ELSE command, nesting to the proc DayRates:

     ++CHECK.TIME
     Let Hour = $Leftstr($Time,2)
     Let Hour = $Integer(#Hour)
     If #Hour < 8 or >= 18 Then Xeq Proc NIGHT.RATES
     Else Xeq Proc DAY.RATES

Note that the statement converting Hour to type integer will not be needed if Hour is a static variable, defined in a vgroup as type integer -- production applications should typically rely on static, not dynamic, variables. [See 4.]

1.7  Commands Controlling Execution Flow in a Protocol

Like any computer program, a SPIRES protocol benefits from having as clear a structure as possible, so that the flow of execution within it can be easily followed. This section discusses three tools called block constructs that help you create structured programming, and also discusses the important nesting and branching commands, XEQ PROC and JUMP.

Block Constructs for Structured Protocols

The following paired commands, called "block constructs", help control flow of execution in a protocol:

  Block Construct to          Purpose of
  Control Execution Flow      Construct
  -------------------------   --------------------------------------
  BEGINBLOCK                  Declare a block of code to accomplish
  ENDBLOCK                    a single, self-contained task

  REPEAT                      Declare block of code that REPEATs
  UNTIL conditions            execution UNTIL the conditions are met

  WHILE conditions            Declare block of code that continues
  ENDWHILE                    executing WHILE the conditions are true

Each block construct consists of a pair of commands, one command to open the construct and one to close it: within the construct is a "block" of code devoted to accomplishing a single task.

BEGINBLOCK...ENDBLOCK

BEGINBLOCK...ENDBLOCK [See 3.1.1.] is often used to define separate blocks of commands, only one of which is executed, depending on the outcome of a condition-test:

                                           _
  If #Age < 18 Then BeginBlock              |   (This first block gets
    Find Status = Minor                     |--- executed if #Age < 18
    Let MinorCount = #MinorCount + 1        |
    EndBlock                               _|
                                           _
   Else BeginBlock                          |   (This second block gets
    Find Status = Veteran                   |--- executed only if
    Let VetCount = #VetCount + 1            |    #Age >= 18)
    Endblock                               _|

  Type

After executing one of the two blocks above, the protocol automatically goes on to execute the next command in the command stream, which in this case is TYPE.

REPEAT...UNTIL and WHILE...ENDWHILE

REPEAT...UNTIL and WHILE...ENDWHILE [See 3.1.2.] designate blocks that loop (execute over and over again) either WHILE the stated conditions are true or UNTIL the stated conditions become true. Thus, for instance, the entire block below will keep repeating execution until the end-user has finally selected a file, so that the $SELECTED variable is true:

  Set Prompt = 'What subfile would you like to select?'
  Repeat                                                ;SELECTBLOCK
    Ask, Attn = 'Return'
    /Select $Ask
    If $No Then Show Eval 'Please check spelling and try again.'
    Until $Selected = $True                             ;SELECTBLOCK
  Show Eval $Select ' is now selected.'

Incidentally, a useful way to make block constructs easier to follow is to name each block, using the semicolon delimiter as shown above. [See 5.1.]


WHILE...ENDWHILE is similar to REPEAT...UNTIL, but the condition is checked at the beginning of the block. Below, the block is used to add up values stored [See 4.] in the variable array Number. [Variable arrays have not been discussed in this overview chapter, but are a useful way to store related variable values. See Chapter 4 for information.] At each iteration, #Count is incremented by 1 until its value is 6, at which point the block stops executing and the protocol executes the SHOW EVAL command below it:

  Let Count = 1
  Let Total = 0
  While #Count < 6            <--Remember #-sign to avoid infinite loop!
    Let Total = #Total + #Number::#Count
    Let Count = #Count + 1
    EndWhile
  Show Eval 'The total is ' #Total

These examples just give a taste of block structures; later in the document we'll discuss them in more detail, [See 3.1.] and also discuss two commands that let you LEAVE a looping block in its middle, or ITERATE it (start it over from the top) automatically.

XEQ PROC and JUMP

The following commands are also very useful for controlling flow of execution in a protocol:

  Branching Command    Purpose
  -----------------    ---------------------------------------
  XEQ PROC             Cause program to execute the Proc (sub-
                       routine) named with the command

  RETURN               Within a Proc, cause the program to
                       return control to the calling point

  JUMP                 Cause program to branch to label group
                       named with the command

The XEQ PROC command [See 3.2.1.] causes the program to nest to the label statement named with the command, and execute the statements following it as a subroutine; when the protocol encounters a RETURN statement within the subroutine, it "returns" to the calling point in the protocol.

Thus, in the example below, if #Value is greater than 10, the program executes the Proc HIGH.VALUE -- when it reaches the RETURN statement in the HIGH.VALUE Proc, it returns to its calling point, in this case executing the PRINT command:

  ++CHECK.VALUE
  If #Value > 10 Then Xeq Proc HIGH.VALUE
  /Print Copies = #Value
      :
  Return
  ++HIGH.VALUE
  Let Value = 10
  Let HighCount = #HighCount + 1
      :
  Return

Like XEQ PROC, the JUMP command (alias GOTO) [See 3.2.3.] causes a protocol to jump to the label statement named with the command. However, unlike XEQ PROC, JUMP does not "remember" the point in the program from which it was called. Thus, in the example above, you could say "Jump HIGH.VALUE" instead of "Xeq Proc HIGH.VALUE" but you could not count on the RETURN command to "return" you to the calling point in the protocol.

2  Invoking and Executing a Protocol

This chapter discusses the following commands for invoking and executing protocols:

  Command                  Purpose
  ----------------------   ------------------------------------------
  XEQ                      Execute a protocol within your active file.

  SET XEQ                  To set a protocol subfile for execution.
                           (See also SET COMPXEQ.)

  XEQ FROM protocol.name   Execute a protocol from a subfile that
  ..protocol.name          has been set for execution.

  RETURN                   Return to the preceding level in a
  (or CLEAR XEQ)           protocol, e.g., to command level

  CLEAR XEQS               Return immediately to command level

  BREAK XEQ                Cause a temporary break in the execution of
                           a protocol.  (CONTINUE XEQ resumes
                           execution.)

Some other important XEQ commands are also discussed briefly here but in much more detail elsewhere: XEQ PROC, which executes a subroutine nested within a protocol; and XEQ FRAME, which executes an XEQ-type frame within the set format. The chapter also discusses the LOAD PROTOCOL facility for preloading protocols into computer memory [See 2.5.]

2.1  Executing a Protocol in the Active File: The XEQ Command

During your SPIRES session, you can execute a protocol that is in your active file by issuing the following command:

where the AT and USING clauses are optional. Execution in a given line range begins with the label statement indicated in the AT clause.

A line.range is an explicit active file range, contiguous or disjoint, or an associative range. The line.range defines the WYLBUR line numbers of the statements to be executed. Labelname is a label statement [See 3.2.] somewhere in the USING line range; it is the point in the command stream where execution is to begin. If no AT option is given, execution begins with the first line of the USING line range. If, in addition, no USING option is given, the entire content of the active file is executed.

SPIRES uses the command XEQ in place of EXEC or EXECUTE in order to avoid conflicts with WYLBUR's command language.

2.2  Invoking a Protocol from a Subfile: The XEQ FROM and .. Commands

Generally, you store your protocols in (and execute your protocols from) a SPIRES protocol subfile, usually one that you've created yourself, using the PERFORM BUILD PROTOCOLS command. [See 9.]

Schematically, a protocol record stored in a protocol file looks like the example below. (Note the asterisk required in column one of line one, preceding the protocol's name.)

  * Protoname     <--The protocol's name = the key of the record as
                      stored in the protocol subfile.
  (command)
  (command)       <--each line of the record is a single command
     :
     :
     :
  Return

The SET XEQ Command

You may set a protocol subfile for execution by selecting it and issuing the SET XEQ command or by naming the subfile as part of the command, in which case you need not select the file first:

   SET XEQ                     <---if the file is selected
   SET XEQ protocol.subfile

In the second case, replace "protocol.subfile" with the name of the file you wish to set. For example:

  -> select test protocols
  -> set xeq
        OR
  -> set xeq test protocols

Once you have SET XEQ for a particular subfile, it remains set until a SET XEQ is issued for a different subfile selection, SPIRES is exited, or you issue the SET NOXEQ command. Thus, you do not need to have a protocols file currently selected in order to execute a protocol in it.

Compiled Protocols and SET COMPXEQ

Protocols for production applications should virtually always be compiled for efficiency, a process discussed in chapter 10 of this manual. To set for execution a file of compiled protocols, you would use the command SET COMPXEQ in place of (or in addition to) the SET XEQ command. [See 10.3 for a discussion of SET COMPXEQ with full syntax.]

Executing a Protocol

To begin protocol execution, issue either of the following commands:

  -> XEQ FROM protocol.name
  -> ..protocol.name  [parameters list]

The second form is allowed only if protocol.name contains no embedded blanks or special characters other than periods or underscores. (Strictly speaking, you can often get around that restriction by placing the name in single or double quotes, as in ..'two or more words'.) The second form also allows an optional parameters list to be passed to the protocol. The parameter list is passed as a string in the system variable $ASK. [See 6.4.]

For example:

  -> xeq from transcript
  -> ..transcript clear

When a protocol is invoked using either of the above commands, the system first searches the protocol file for which XEQ was last set. If it does not find the named record there, it searches each of the COMPXEQ files in the order in which they were set. [See 10.3.] If the protocol whose name was given in the command cannot be found in any of these files, an error condition is raised.

The user may optionally state the label at which execution is to begin in an AT clause, with a command of the form:

  -> XEQ [ AT label.name ]  FROM protocol.name

Note that there is a similar form of the XEQ PROC command:

which is used when the named protocol is already in the XEQ stack. [See 3.2.1.]

Variables set before the protocol is invoked [See 4.2.1.] are not initialized by the XEQ command; until the protocol explicitly assigns values to them, they retain whatever values were in effect at the time of protocol invocation. However, if you set dynamic variables within your protocol, you may clear them with the $ZAP function or the CLEAR DYNAMIC VARIABLES command. [See 7.2, 8.2.] There are also a number of ways to reinitialize or restore the values of static variables. [See 4.1.1, 4.2.4.]

2.2.1  (Nesting and Chaining Protocols)

The above-mentioned protocol execution commands -- XEQ FROM, .., and XEQ -- can all be issued from within one protocol in order to invoke another, different protocol. That is, the following commands may be embedded in a protocol:

If the execution command is the last statement in the first protocol, then the two protocols are said to be "chained" together, as in the diagram below:

   * Proto1
   (commands)
   ..Proto2 ------------> * Proto2            (Proto1 is "chained"
                          (commands)          to Proto2)
                          Return

The last statement in Proto1 invokes execution of Proto2; when Proto2 encounters the RETURN command it returns to command level.

If the execution command is not the last command in the calling protocol, then the called protocol is said to be "nested" within the calling protocol:

   * Proto1
   (commands)
   ..Proto2 ------------> * Proto2             (Proto2 is "nested"
   (commands) <---\       (commands)           within Proto1)
   (commands)      \_____ Return
   Return

In this case, a .. command within Proto1 invokes execution of Proto2; when Proto2 encounters its RETURN command, it returns not to command level but back to Proto1, which continues executing the next command in its command stream.

The RETURN Command

As the examples above indicate, the RETURN command (alias CLEAR XEQ) always returns execution to the next highest level of nesting (which is simply command level if no nesting has occurred). You can use RETURN in order to return immediate control from a nested protocol to the protocol that called it, as in the example above. RETURN is also useful for returning from nested Procs or subroutines that have been invoked with the XEQ PROC command. [See 3.2.1.]

The CLEAR XEQS Command

The nesting level limit is set to a depth of 100. If execution is occurring in a nested protocol, and you wish to return immediately to command level, you can issue the CLEAR XEQS command. No matter how deeply you are nested, CLEAR XEQS will return you to command level. Note the difference between CLEAR XEQS and CLEAR XEQ (which, like RETURN, only backs up one nested level).

A RETURN command can have virtually any other command (including a RETURN command) appended to it, as in this example:

  Return  Return  Show Eval 'Backing up two levels !!'

This command moves execution back two levels and displays a message at the terminal.

To track nested execution, you can use the SHOW XEQ STACK command [See 3.2.2.]

2.3  Other XEQ Commands

SPIRES offers some other XEQ commands besides the commands discussed so far in this chapter. The commands are treated in more detail elsewhere, but are worth mentioning briefly, for the sake of completeness, in this "XEQ" chapter:

XEQ PROC

The XEQ PROC "label-name" will be covered in detail in a later chapter. XEQ PROC issued within a protocol invokes a subroutine (a proc whose first line is labeled with the '++' prefix) and executes the subroutine, returning after execution to the point from which it was called. For instance the command XEQ PROC GETINFO branches to a subroutine whose first line is ++GETINFO, executes it, and returns to the calling point when it reaches the RETURN statement in the GETINFO Proc.

XEQ PROC is also available, in somewhat different form, as a Uproc within a file definition Userproc or a format. [See 3.2.1 later in this document for more information about XEQ PROC.]

XEQ FRAME

The XEQ FRAME "frame-name" command issued within a protocol invokes a special type of format frame called an XEQ frame, which handles many of the same procedural duties as a protocol, but has easier access to the processing powers of system procs. For instance, the command XEQ FRAME SHOWHELP executes the frame named SHOWHELP in the currently set format, if any. [See the manual "SPIRES Formats" for complete details on XEQ frames.]

XEQ USERPROC

In file definition Userprocs, yet another XEQ command is available as a Uproc: XEQ USERPROC "userproc-name", which invokes another Userproc within the same USERDEFS section of that file definition. [See the manual "SPIRES File Definition" for complete details on Userprocs.]

SHOW XEQ STACK

The SHOW XEQ STACK command lets you trace the hierarchy of nested execution in a protocol. It lists the XEQ commands that have issued (for instance, XEQ FROM and XEQ PROC commands), listing the most recently issued XEQ command first. This command is treated in more detail later in the document. [See 3.2.2.]

2.4  Creating Execution Breaks: The BREAK XEQ and CONTINUE XEQ Commands

In some situations it may be necessary to interrupt execution of a protocol and return end-users temporarily to "command mode". For instance, an execution break might be necessary in a protocol that displayed electronic mail, so end-users could collect a reply in their active file. (Note that since an execution break interrupts your protocol's special features, you'd want to use it with caution.)

More commonly, you may want to break execution of a test or pre-production protocol in order to debug it and test its commands.

The following command issued within a protocol causes an execution break:

To resume execution of the protocol from command mode, the protocol's end-user would type:

(The CONTINUE XEQ command can be abbreviated to its first four letters.)

When a BREAK XEQ command is encountered in protocol execution, the message "-Type CONTINUE XEQ to resume" is sent to the terminal. All system prompts while the break is in effect become "X->" instead of "->" as a reminder that an XEQ is still in effect. (This assumes that you have not SET WYLBUR.) Any protocol, SPIRES or WYLBUR command may be issued in response to the 'X->' prompt.

To exit protocol mode while the break is in effect, and return to command mode, the user could type the CLEAR XEQS command.

Execution Breaks and Debugging

Execution breaks can also be caused by some types of programming errors, and cause the following terminal prompt:

If you respond to this prompt with "BREAK" or "BRE" a normal execution break occurs, as if the "BREAK XEQ" command had been encountered in the protocol command stream. (Other possible responses to the prompt are NO, YES, and OK.)

Within the execution break, you can use debugging features such as SHOW EVAL $LASTXEQ, to determine what command caused the break.

This makes it easier to correct errors that cause execution breaks before you release the protocol for production use. [See 8 and following sections for more on debugging tools.]

Note: To suppress execution breaks within your protocol even after errors, issue the SET NOSTOP command. [See 5.10.] To turn the execution break facility on, issue the SET STOP command. [See 5.10.]

2.5  (*) Preloading Protocols for Efficiency

The following section discusses three optional commands to help a SPIRES application make efficient use of computer memory. (The commands are not needed for Prism protocols, for reasons described further below.)

  Command                 Purpose
  -------------------     ---------------------------------------
  LOAD PROTOCOL ...       Preload a protocol into computer memory
  UNLOAD PROTOCOL ...     Unload a protocol from computer memory
  SHOW LOADED PROTOCOLS   Display the names of preloaded protocols

The LOAD PROTOCOL command preloads the protocol named with the command into computer memory and maintains the protocol there after execution finishes. Preloading is an efficient procedure if the protocols are likely to be called more than once during the end-user's session.

The UNLOAD PROTOCOL command unloads the protocol named with the command.

The syntax for these commands is as follows:

  LOAD PROTOCOL [protocolname]
  UNLOAD PROTOCOL [protocolname]

"Protocolname" represents the name of the protocol you wish to load or unload. For LOAD PROTOCOL, if you leave off "protocolname", the system will ask you which record you wish to load. For UNLOAD PROTOCOL, if you leave off "protocolname", the most recently loaded protocol will be unloaded.

Prism automatically loads and unloads the appropriate protocol records for your application when it calls your file (or calls a particular report or entry form). Thus, Prism protocols automatically benefit from the efficiency of these commands -- you do not need to issue the commands yourself.

How Long the Protocol Remains Preloaded

For a protocol to be preloaded, the subfile containing it must first be set for execution with the SET XEQ or SET COMPXEQ command. But once the protocol is loaded it remains in computer memory until the SPIRES session finishes, or until your code unloads it with the UNLOAD PROTOCOL command. In other words, commands like CLEAR SELECT (to deselect your file) or SET NOXEQ or SET NOCOMPXEQ (to clear the protocol execution file) will not unload the protocol from memory.

For efficient clean-up, you'll probably want to UNLOAD all loaded protocols at the time when people leave your application, in much the same way as you deallocate any global variable-groups ("vgroups") that you previously allocated. (Once again, for Prism protocols, Prism takes care of unloading automatically.)

Example

In a complex SPIRES application, a small protocol might be invoked near the beginning of an application to preload and later to unload the application's main protocols:

  * LOAD.PROTO
      :
  ++LOAD
  Set CompXeq BIG.PROTOS
  Load Protocol MAIN.PROTO1
  Load Protocol MAIN.PROTO2
  Set NoCompXeq
      :
  ++CLEAN.UP
  Unload Protocol MAIN.PROTO1
  Unload Protocol MAIN.PROTO2
  Return

SHOW LOADED PROTOCOLS

To see which protocols are currently loaded, use the SHOW LOADED PROTOCOLS command, which displays the names of loaded protocols beginning with the one most recently loaded:

  -> load protocol proto1
  -> load protocol proto2
  -> show loaded protocols
  - PROTO2                      <--"Last in first out"
  - PROTO1
  -> unload protocol
  -> show loaded protocols
  - PROTO1

When you or a user invokes a protocol by name, using the .. or XEQ FROM command, SPIRES checks the following places (always in this order) for a copy of the protocol to be executed:

Note: it's possible that two or more protocols made available through LOAD PROTOCOL, SET XEQ and/or SET COMPXEQ, might have the same name, in which case SPIRES will use the procedure above in order to determine which protocol should be executed. (If two protocols with the same name are both preloaded with LOAD PROTOCOL, the most recently loaded protocol of the two will be executed.)

3  Controlling Execution Flow in a Protocol

This chapter describes facilities to help control flow of execution in a protocol. [See 1.7 for an introductory overview of these tools.] These facilities help structure code into easy-to-follow subroutines and subprograms. The following topics in particular are covered:


Block Constructs


Nesting and Branching Commands

By the way, most of these facilities are also available as Uprocs within a format or within a file-definition Userproc. (For details see the manuals "SPIRES Formats" and "SPIRES File Definition".) Note also that these facilities to control flow of execution are not available as interactive SPIRES commands -- they can only be issued within a SPIRES component such as a protocol or format.

How These Tools Clarify a Protocol's Structure

The tools discussed in this chapter foster a more "structured" programming, making it easier to construct a protocol from the top down: for instance, you can construct the main body of your protocol as a driver, whose statements provide a sort of high level outline of the program's purpose. The driver can then call on subroutines with XEQ PROC (or XEQ FRAME to call format code), in order to take care of important lower-level tasks.

The driver of a protocol might look almost as simple as this:

  ++HIGH.LEVEL
  Xeq Proc SELECT.FILE
  Xeq Proc SET.FORMAT
  Xeq Proc SEARCH.FILE
     (etc.)
  Return

The subroutines SELECT.FILE, etc., elsewhere in the protocol would take care of the actual business of selecting the file -- the top level of the protocol shows what is being done without having to show how it's done.

Likewise, the block constructs discussed in this chapter are useful devices for organizing related programming statements into complete tasks, with each small-sized task more or less contained within its block. Such devices make it easier to follow a protocol's general design and logic, even weeks or months later, when it needs to be maintained.

3.1  Block Constructs for Structured Programming

A block construct is a special set of paired statements that delineate a self-contained block of commands. Though commands within the block execute in usual fashion, one by one from the top down, you can also think of a block as a complete self-contained entity (almost a little subprogram) which executes either as a whole or not at all:

  If #Num > 2 Then BeginBlock   _
    Let A = 1                    |  If #Num > 2, then the entire
    Let B = 2                    |  block is executed -- otherwise
    Let C = 3                   _|  the entire block is skipped.
    EndBlock

Because block constructs organize commands into larger related units, they are a powerful tool for clarifying the structure of code, and for executing and controlling loops.

There are three varieties of block constructs. BEGINBLOCK and ENDBLOCK [See 3.1.1.] open and close a simple block of commands. WHILE and ENDWHILE [See 3.1.2.] open and close a block that loops WHILE condition(s) stated at the beginning of the block are true. REPEAT and UNTIL [See 3.1.2.] open and close a block of code that loops UNTIL condition(s) stated at the end of the block are true. [See 5.8 for more on conditions.]

A block construct can be diagrammed in three parts:

  (1) BEGINBLOCK        (1) WHILE condition(s)     (1) REPEAT
  (2) commands          (2) commands               (2) commands
  (3) ENDBLOCK          (3) ENDWHILE               (3) UNTIL condition(s)

Statement (1) opens the block of commands (2), and statement (3) closes the block. Note that statement (1) must always be paired with its corresponding statement (3): for instance, every BEGINBLOCK statement must be paired with an ENDBLOCK statement, and vice-versa. Note also that statement (1) is often prefixed by an IF...THEN condition test, as for example, "IF $NO THEN REPEAT". [See 5.8.]

We'll begin by discussing BEGINBLOCK...ENDBLOCK.

3.1.1  BEGINBLOCK...ENDBLOCK

A BEGINBLOCK...ENDBLOCK construct is simply a block of commands, with BEGIN BLOCK beginning the block and ENDBLOCK ending it. (BEGINBLOCK can be abbreviated to BEGIN, or written BEGIN BLOCK, and ENDBLOCK can be abbreviated to ENDB.) A BEGINBLOCK...ENDBLOCK construct almost always follows THEN in an IF...THEN command, as in this example:

  If ~$Selected Then BeginBlock   _
    Select Drinks                  | <--(This can be any block of
    Set Format Display             |    procedural commands)
    Show Eval 'DRINKS selected.'   |
    EndBlock                      _|

BEGINBLOCK...ENDBLOCK constructs are especially useful coded as a pair after a condition test, [See 5.8.] where the outcome of the test results in the execution of only one of the two blocks. For instance, a protocol can test whether a user wants debugging turned on or not, and execute one of two different blocks depending on the answer:

  If #Debugging = $True Then BeginBlock   _
      Set Ftrace                           | (This whole block is
      Set Messages 2                       | executed only if the
      Set Stop                             | condition is true)
      Let TestFlag = 1                     |
      EndBlock                            _|
    Else BeginBlock                       _
      Clear Ftrace                         | (This whole block is
      Set Messages 0                       | executed only if the
      Set NoStop                           | condition is false)
      Let TestFlag = 0                     |
      EndBlock                            _|

Without the block construct, you would either have to use JUMP or XEQ PROC, transferring control to a part of the protocol distant from the IF-test itself, or, if you wanted to keep the code in one place, you would have to execute a cumbersome series of THEN and ELSE commands:

  If #Debugging = $True Then Set Ftrace
    Then Set Messages 2
    Then Set Stop
       :
    Else Clear Ftrace
    Else Set Messages 0
       :

Clearly, the block is easier to read than a series of ELSE commands, and makes the basic structure of the task easier to follow.

Some other interesting features of BEGINBLOCK...ENDBLOCK constructs -- for instance, how they can be chained or nested, or how they save and restore the value of the $ELSE variable -- are covered in an upcoming section. [See 3.1.3.]

3.1.2  Looping Blocks: WHILE...ENDWHILE and REPEAT...UNTIL

The WHILE...ENDWHILE and REPEAT...UNTIL looping block constructs create blocks that execute over and over, either WHILE the stated conditions remain true, or UNTIL the stated conditions become true. (A block construct also terminates execution when it encounters a LEAVE command, a RETURN command, or a JUMP or GOTO command that jumps to a label outside the block.)

WHILE...ENDWHILE

In a WHILE...ENDWHILE construct (where ENDWHILE can be abbreviated to ENDW) the block of commands executes WHILE the conditions stated at the beginning of the block are true. (A LEAVE statement also terminates execution of the block.) Once the conditions cease to be true, the statement following ENDWHILE is executed. If the WHILE conditions are false from the very beginning, the block of commands within the loop never execute at all.

For instance:

  ++ASK.NUMBER
  Set Prompt = 'Please specify a number'
  Ask, Attn = 'Return'
  While $TypeTest($Ask,INT) ~= 'INT'
    Show Eval 'The value must be an integer.'
    Ask, Attn = 'Return'
    EndWhile
  Return

The block above continues executing until the user finally specifies a value that can be converted to integer, at which point the code moves on to whatever command follows the ENDWHILE in the protocol.

One use of WHILE...ENDWHILE is to sum values. The example below initializes a counter variable to zero, then sums up the values contained in five occurrences of the variable array Units. (The block executes five times, and the final value of the Total variable will equal the summed values of Units::0 plus Units::1 plus Units::2 plus Units::3 plus Units::4.)

  Let Total = 0
  Let Counter = 0
  While #Counter < 5
    Let Total = #Total + #Units::#Counter
    Let Counter = #Counter + 1
    EndWhile

You can also use WHILE...ENDWHILE for placing values into arrays:

  Let Counter = 0
  While #Counter < 5
    Let Name::#Counter = $GetUval(Name,#Counter,none,'')
    Let Counter = #Counter + 1
    EndWhile

REPEAT...UNTIL

The REPEAT...UNTIL construct is similar to WHILE...ENDWHILE, except that the condition(s) controlling execution are named at the end of the construct instead of at the beginning. The construct REPEATs execution over and over UNTIL these conditions are true, or until the block encounters a LEAVE statement. (JUMP, GOTO or RETURN would also cause the block to terminate.) For instance, the block below loops until a subfile is selected:

  Repeat
    Show Eval 'First select a subfile.'
    Ask Upper, Prompt = 'Subfile name', Attn = 'Jump Exit'
    /Select $Ask
    If $No Then Show Eval 'Please check spelling and try again.'
    Until $Selected
  Show Eval $Select ' is now selected.'

When to Use REPEAT...UNTIL

Many tasks can be accomplished with either one of the two looping constructs (though WHILE...ENDWHILE is probably more often used than REPEAT...UNTIL). REPEAT...UNTIL comes in most handy when the condition(s) you want to test can only be meaningfully tested after the looping block has executed at least once. This is because a REPEAT...UNTIL construct always executes once, even when its UNTIL conditions happen to be true from the beginning. (That is, the conditions in WHILE...ENDWHILE are checked at the beginning of the block, before it executes; the conditions in REPEAT...UNTIL are checked at the end of the block, after it has executed at least once.)

For instance, in the example below, it only makes sense to test $ASK after the block has executed -- $ASK has a value before the block executes, but it's the value established within the block that is to be tested:

  Let FirstValue = $Int($Ask)
  Set Prompt = 'Specify a second, smaller value'
  Repeat
    Ask, Attn = 'Jump Exit'
    If $Int($Ask) >= #FirstValue Then Show Eval ...
          ... 'Value should be SMALLER than ' #FirstValue
    Until $Int($Ask) < #FirstValue
  Return

(*) The LEAVE and ITERATE Commands

The LEAVE and ITERATE commands help control execution of a looping block more precisely. The LEAVE command always causes the protocol to exit the current looping block and go on to execute whatever command follows the block in the protocol.

For instance, below if an end-user presses ATTN/BREAK (or an equivalent key) in response to the ASK prompt, the block immediately passes control to whatever command follows the block:

  Repeat
    Add
    Ask Upper Prompt = 'Add another record?', Attn = 'Leave'
    Until $Ask = 'NO'

The ITERATE command immediately causes the looping block to execute the closing statement of the block. For WHILE...ENDWHILE, the protocol executes the ENDWHILE statement and immediately bounces backward to recheck the WHILE statement at the beginning of the block. For REPEAT...UNTIL, the protocol immediately executes the UNTIL statement and checks whether the conditions it states are still true. As long as the conditions stated after WHILE (or after UNTIL) are still true, the block is executed again ("iterated"), from the top down.

Neither LEAVE nor ITERATE may be used anywhere except within a looping block.


Looping Block Constructs: Cautions and Considerations

The considerations below may seem obvious, but are probably worth mentioning:

Some restrictions on block constructs are discussed in an upcoming section. [See 3.1.4.]

Note that a looping block construct, like a BEGINBLOCK...ENDBLOCK construct, is able to save and restore the value of the condition test that invoked it. Also, a looping block can be chained to (or nested within) another block. [See 3.1.3 for details on these features.]

3.1.3  Other Features of Block Constructs

This section briefly discusses some additional features of block constructs:

Nested Blocks

Any block construct can be nested within another block. In the diagram below, a WHILE...ENDWHILE construct is nested within a BEGINBLOCK...ENDBLOCK construct.

  If condition1 Then BeginBlock      ; OuterBlock
    commands
    While condition2                 ; InnerBlock
      commands
      EndWhile                       ; InnerBlock
    commands
    EndBlock                         ; OuterBlock

As a courtesy to other programmers who may later need to interpret your code, it's recommended that you "name" nested blocks, using the comment statement or a semicolon delimiter, as in the diagram above. In fact, you may decide to name all your block constructs, not just nested ones.

Although block constructs can be nested, they cannot be interleaved. [See 3.1.4.]

Chained Blocks

Block constructs can also be chained together, as in the example below, which chains a series of condition tests together:

   (1) If ~$Selected Then BeginBlock                     ; Block1
           Let PrevFile = 'NO FILE'
           Select NewBooks
           Let PathOpen = No
           EndBlock                                      ; Block1
   (2)   Else If $Select ~= 'NEWBOOKS' Then BeginBlock   ; Block2
           Let PrevFile = $Select
           Thru NewPath Select NewBooks
           /Set Default Path $PathNum
           Let PathOpen = Yes
           EndBlock                                      ; Block2
   (3)   Else BeginBlock                                 ; Block3
           Let PrevFile = 'NEWBOOKS'
           Let PathOpen = No
           EndBlock                                      ; Block3

If condition (1) is true (and no file is selected), then the first block is executed and blocks (2) and (3), prefixed by ELSE, are completely bypassed. If condition (1) is false, the entire first block is bypassed and condition (2) is tested. If condition (2) turns out to be true, the second block is executed and block (3), because it's prefixed by ELSE, is bypassed. Thus, only if condition (1) and condition (2) both turn out to be false will block (3) be executed.

Block Constructs and $ELSE

An important feature to note is the way a block construct "remembers" and restores the result of the condition test that called it. (That is, a block saves the value of the $ELSE variable [See 6.4.] when it begins execution, and restores this value when the block terminates.) Even if you use IF...THEN within a block to test a local condition, the result of the "larger" condition test (the one that caused the block to be executed in the first place) will be restored when the block is finished.

For instance, the code below tests the value of "Num" and executes one of two different blocks depending on the result of the test. Though there's a "local" condition test of "B" within the first block, that "local" test won't affect whether the second block executes or not:

  If #Num >= 0 Then BeginBlock                  ; Block1
    -  (Block1 executes if #Num >= 0)
    Let A = #Num
    If #B >= #Num Then Let B = #Num
    -  (This IF...THEN won't affect execution of Block2)
    Let C = 3
    EndBlock                                    ; Block1

    Else BeginBlock                             ; Block2
      -  (Block2 only executes if #Num < 0)
      Let Num = 0
      Let A = 0
      EndBlock                                  ; Block2

Note that if you exit a block using JUMP, GOTO or RETURN, $ELSE is not restored to the value it had upon beginning execution of the block.

3.1.4  Some Restrictions

The following restrictions apply to the block construct commands BEGINBLOCK, ENDBLOCK, WHILE, ENDWHILE, REPEAT and UNTIL. Some of them apply to the LEAVE and ITERATE commands as well.

   -> begin block                   <--not valid as interactive command
   -Allowed only in XEQ
    Wdsr, End = 'BeginBlock'        <--BEGINBLOCK is invalid in a clause

    Ask Upper, Attn = 'Leave'       <--LEAVE is allowed in a clause
    Let T = '#X < #Y'
    /While #T                       <--Invalid block construct

    Let T = '#X < #Y'
    While $Test(#T)                 <--This is valid
    If #Status = '' Then EndWhile   <--Invalid command
    If #Status = '' Then Leave      <--Use this instead
   BeginBlock     _                    BeginBlock  _
      :        _   |                      :         |
     Repeat     |  |                     Repeat     | _
      :         |  |  <--valid            :         |  |  <--invalid
     Until ... _|  |                     EndBlock  _|  |
      :            |                      :            |
     EndBlock     _|                     Until        _|

Some other basic restrictions are mentioned in earlier sections -- for instance, every REPEAT statement must be paired by an UNTIL statement, and vice-versa. [See 3.1.]


3.2  Nesting and Branching Within Protocols

The next few sections discuss methods for nesting and branching within a protocol -- i.e., transferring control from one part of a protocol to another:

In addition to nesting within a protocol, you can nest protocols themselves, as described in an earlier section. [See 2.2, 2.2.1.]

Before discussing these commands, we'll begin by describing the label statement in more detail.

Label Statements: The ++ Prefix

You use a label statement in a protocol to name the destination of a branching or nesting command. (You can also use a label statement simply to set off a series of related statements or commands.)

Label statements are of the following form:

The label.name can be up to 16 alphanumeric characters (not counting the "++"). Periods are the only allowable special character in the name -- no embedded blanks are allowed. Label statements cannot be issued as interactive commands.

Some examples of label statements follow:

  ++CHECK.INT  <--A command XEQ PROC CHECK.INT elsewhere in the protocol
                  will cause Proc beginning with this line to execute

  ++900.EXIT   <--A command JUMP 900.EXIT elsewhere in the protocol
                  will jump to this statement

Though labels are optional in SPIRES protocols, they are required in protocols for Prism, and can aid in clarifying the structure of any protocol. For instance, they can often help restructure a large program into a series of smaller and more manageable "subprograms".

3.2.1  Executing Internal Procedures: The XEQ PROC Command

An executing protocol can treat a contiguous series of statements within itself as a closed subroutine or "Proc", whenever the first statement in the subroutine is a ++label.name statement, and the last statement is RETURN.

You invoke such a subroutine internally (i.e., from within the protocol) by using the following command:

  XEQ PROC label.name [IN protocol.name]     <--(or XEQ PROCEDURE...)

where "label.name" is the name of the label that will immediately begin the subroutine. [See 3.2.] For instance, the command XEQ PROC VALIDATE transfers control to a Proc or subroutine labelled ++VALIDATE. When the VALIDATE Proc encounters a RETURN command, control returns to the point in the protocol from which the Proc was called. [See 2.2, 2.2.1 for details on the RETURN command.]

The "IN protocol.name" suffix is described further below.

As mentioned above, the last command in a Proc or subroutine should be RETURN. When the protocol reaches this RETURN statement, it generally returns to (and executes) the NEXT command in the protocol, the command that follows the XEQ PROC command -- the one exception to this is when XEQ PROC occurs as a parameter on the ASK command (e.g., in a NULL clause), in which case the protocol generally returns to the ASK command and executes it again. [See 5.3 for details on the ASK command and its interaction with XEQ PROC.]

Though the analogy should not be pushed too far, in a way a Proc or subroutine can be pictured as a second self-contained protocol sitting nested within its containing protocol.

Example of an XEQ PROC Subroutine

For example, the subroutine labeled CONFIRM below could be invoked repeatedly (by the request XEQ PROC CONFIRM) whenever the program asked a yes-or-no question:

  * SAMPLE.PROTOCOL
      :
  Xeq Proc CONFIRM
      :
  Return
  ++CONFIRM
  Repeat
   /Ask Exact Upper Prompt = '#CurrQuestion' Attn = ''
   Let Response = $PMatch($Ask,Y?ES,N?O)
   If #Response = 0 Then Show Eval 'Please respond Yes or No.'
  Until #Response ~= 0
  Return
      :
      :

(Though not shown, this subroutine would probably also contain some code to handle the situation where the end-user typed HELP or ?.)

XEQ PROC, Branching and Nesting

Most of what XEQ PROC accomplishes can also be accomplished by JUMP or GOTO branching, but the XEQ PROC method of nesting is easier to track, because of the way it returns to a particular place with RETURN. A JUMP statement might jump to any label within the protocol; a RETURN statement within a subroutine can be trusted to return to a particular pre-specified place.

The SHOW XEQ STACK command [See 3.2.2.] or the $XEQSTACK function [See 7.2.] can be very useful for keeping track of where you are in a set of nested subroutines.

Because a Proc is a self-contained unit, it should not include JUMP or GOTO statements that branch outside it, because those statements would defeat the purpose of nesting.

(*) The "IN protocol.name" Suffix to the XEQ PROC Command

In large and complex applications, sometimes the protocol driving the application becomes too large to compile, and must be divided into two protocols, one of which invokes and executes subroutines in the other. If you encounter this situation, for efficiency you should use the "IN protocol.name" suffix on XEQ PROC commands, whenever one of the protocols invokes and executes statements stored in the other protocol. (The "IN protocol.name" suffix is also available on the JUMP or GOTO command.) Both protocols must first have been invoked and brought into computer memory for this technique to work. [To execute a procedure in a protocol that is not currently in the XEQ stack, use the "XEQ AT label FROM protocol" form of the XEQ FROM command. [See 2.2.]]

The best way to explain the "IN protocol.name" suffix is to show a condensed and simplified example:

     * PROTO1                      * PROTO2
        :                             :
(1)  ..PROTO2                         :
        :                             :
     Return                   (2)  Xeq Proc CHECK.NUMBER in PROTO1
(3)  ++CHECK.NUMBER                   :
        :                             :
(4)  Return                        Return

Here the driving protocol code has been split into two protocols, called PROTO1 and PROTO2. When PROTO1 is invoked, one of its first actions (1) is to invoke PROTO2 and bring it into computer memory. Since both protocols are now in computer memory, PROTO2 can include the command (2) XEQ PROC CHECK.NUMBER IN PROTO1, which executes the subroutine CHECK.NUMBER in the other protocol (3). At the RETURN statement (4), control returns to PROTO2.

XEQ PROC is also available in slightly different form as a Uproc in formats and in file definition USERPROCs. See the manuals "SPIRES Formats" and "SPIRES File Definition" for more information.

3.2.2  Tracing Execution: The SHOW XEQ STACK Command

The SHOW XEQ STACK command shows you the hierarchy of nested execution in a protocol, by listing the XEQ commands that have been issued, beginning with the command most recently executed.

For example, consider the following simple protocol, which calls two Procs and then issues a BREAK XEQ command:

Issuing the SHOW XEQ STACK command at the execution break calls up the following display:

If the protocol had been executed from the active file, the calling XEQ command would have been displayed as "XEQ USING" rather than "XEQ FROM TEST".

Once you return to command level, by way of either a RETURN command or a CLEAR XEQ or CLEAR XEQS command, the protocol is no longer nested, so the SHOW XEQ STACK will not return anything. That is, there is no xeq stack at command level.

The SHOW XEQ STACK command may be preceded by the IN ACTIVE prefix to place the display in the active file.

Note that the $XEQSTACK function [See 7.2.] also returns previously issued XEQ commands. For example, $XEQSTACK(0) returns the most recently issued XEQ command, $XEQSTACK(1) returns the XEQ command issued at the previous level (one level higher), and so on. See also the $XEQLVL system variable [See 6.4.] which contains the level to which protocol execution is currently nested.

The maximum number of nesting levels is 128.

3.2.3  The JUMP (or GOTO) Command

To branch within a protocol, you can use the JUMP (or GOTO) command, followed by the label.name of the label to which you wish to branch:

For instance, the statement JUMP MENU causes a protocol to transfer unconditional control to code beginning with the line labelled ++MENU. [See 3.2.]

Code using JUMP can be hard to read and maintain, so SPIRES offers some alternatives that may help create a more clearly structured program. To execute a Proc or subroutine (a self-contained procedure), use the XEQ PROC command rather than JUMP or GOTO. [See 3.2.1.] Unlike code called by JUMP, a subroutine invoked by XEQ PROC "remembers" its calling point and returns there as soon as it encounters a RETURN command. Thus XEQ PROC subroutines are easier to track.

For looping, the block constructs REPEAT...UNTIL and WHILE...ENDWHILE [See 3.1.2.] create structures that are clearer to follow than loops controlled by the JUMP command.

4  User-Defined Variables and Vgroups

This chapter covers "user-defined variables", variables that you as an application developer define and use for temporary storage of values. [Besides user-defined variables the other main category of variables in SPIRES is system-defined variables. [See 6.]] Topics covered in this chapter include the following:

The chapter will also demonstrate how to create and use variable arrays [See 4.3.] and includes a section on dynamic variables. [See 4.4.] Note that, because static variables are more efficient than dynamic variables, static variables and vgroups are the main focus of this chapter.

Advantages of Static Variables

You can create a variable interactively in SPIRES, just by issuing a LET command such as "LET NUMBER = 2". [See 1.2.] The variable ("NUMBER" in the example above) need not exist before you create it in the LET command. (A variable created on the fly like this is called a "dynamic" variable.) Why then go to the trouble of predefining variables in vgroups before using them with a command such as LET?

The main advantage of static variables (variables predefined in a vgroup) is superior efficiency during execution. Static variables are allocated to a reserved space in memory, making them easy for SPIRES to locate during execution. By contrast, since a dynamic variable is defined on the fly, it is much more difficult to locate in memory during execution, and can also cause serious fragmentation of memory.

Static variables also offer greater power and flexibility for defining variable arrays, storing and initializing values, ensuring the data-type of values, and many other important tasks. In general, dynamic variables are best suited for quick interactive use or for testing applications in early stages. (By the way, vgroups themselves may contain variables whose type is "dynamic".) Remember that you can convert dynamic variables into static variables at any time -- in fact, the next section shows how to do this.

Compiled protocols too run more efficiently with static variables. Also, when you compile a protocol, SPIRES will give you warning errors if you use dynamic variables, since statements that use dynamic variables cannot be compiled and thus execute uncompiled. [See 10.2.] In fact, if you use the DECLARE VGROUP or DECLARE GLOBAL VGROUPS commands to define and allocate any variables, then the protocol will not compile at all if SPIRES finds any dynamic variables that are not defined in the local or global vgroups.

4.1  Defining and Compiling a Global Vgroup

When converting the variables your protocol uses into static variables, you choose between two types of vgroups, each with its own construction procedure:

This section describes how to define and compile a global vgroup. The methods for creating a local vgroup are described in the next section. [See 4.1.0.]

To create a global vgroup, you create a record describing the variables to SPIRES, generally following the steps shown below:


Here is the procedure shown in more detail:

Step 1: create the record in your active file.

The record might look something like this:

  VGROUP = gq.jpr.testvars;
  AUTHOR = jeff rensch, 723-2530;
  VARIABLE = integer;                     (See 4.1.1 for a full
    OCC = 1; TYPE = int;                  explanation of each of
  VARIABLE = hexint;                      these elements.)
    OCC = 1; TYPE = hex;
    REDEFINES = integer;
  VARIABLE = question;
    OCC = 1; TYPE = string;
  VARIABLE = answer;
    LEN = 1; OCC = 3; TYPE = char;
    VALUES = A, B, C;

The statements in this record name your vgroup and, for each variable, give it a name (VARIABLE), as well as usually specifying the number of occurrences (OCC), type (TYPE), and initial value(s) (VALUE). [See 4.1.1 for details on all these statements.]


Step 2: Select the VGROUPS subfile in SPIRES and add the record.

  -> spires
  -> select vgroups
  -> add

To update an existing vgroup definition you'd use the familiar TRANSFER and UPDATE commands.


Step 3: Issue the COMPILE command.

  -> compile gq.jpr.testvars
  -Vgroup Definition Compiled
  ->

Or use RECOMPILE instead of COMPILE after modifying an existing vgroup.

You may receive error messages when you compile the vgroup. [See this manual's appendix for a list of error messages.] If errors are found, update your vgroup definition and issue the COMPILE command again.

Using a Global Vgroup

After successfully completing the three steps listed above, you're ready to use your global vgroup in SPIRES. To allocate a global vgroup within a protocol, it is best to use the DECLARE GLOBAL VGROUPS command block:

Another way to allocate a global vgroup, which is not generally recommended within protocols, is to issue the SET VGROUP (alias ALLOCATE) command; to deallocate it when you're through using it, issue the CLEAR VGROUP (alias DEALLOCATE) command. You can do these commands outside of a protocol, or within. However, these commands have no effect on whether SPIRES can find a variable during compilation. If you are compiling the protocol, you will need to use either the DECLARE GLOBAL VGROUP command technique above, or the older technique of allocating the vgroup within a SYS PROTO record. References to variables in a vgroup that has been allocated with SET VGROUP rather than with DECLARE GLOBAL VGROUP or within a SYS PROTO record will not be compiled (though the protocol itself will compile).

But the SET and CLEAR VGROUP technique is very handy in command mode when you are working with a global vgroup, since DECLARE command blocks are not available outside of protocols:

SET VGROUP does have an important use within a protocol: use it there along with the DECLARE GLOBAL VGROUP command if you need the vgroup to stick around in memory after the protocol finishes execution.

Later we'll go into more detail on these commands [See 4.2.1.] but first we'll describe the elements of a vgroup definition in more detail. [See 4.1.1.]

4.1.0  Defining a Local Vgroup

You can define a local vgroup to be used within a protocol two different ways:

The first method, which makes the protocol work the same way whether or not it is compiled, is the preferred one. The second, an older method, is described at the end of Chapter 10. [See 10.8.]

To create a local vgroup with the DECLARE VGROUP command, you add most of the pieces of a vgroup definition record [See 4.1.1.] near the start of your protocol, corralled by a DECLARE VGROUP command and an ENDDECLARE command.

For example, the INTEREST protocol below begins by creating a local vgroup called LOCAL.INTEREST that contains the variables AMOUNT, MONTHS, etc.

A local vgroup declaration begins with the DECLARE VGROUP command:

The "vgroup-name" is a name from 1 to 16 characters. Unlike the name of a global vgroup, which must begin with your account number (since it is the key of a record in the VGROUPS system subfile), this name should not include your account number. (If you include it, it will count as part of your 16 allowed characters.) No blanks are allowed. Using characters other than letters, numerals or periods is not recommended.

The vgroup definition follows, consisting of variable definitions as described in the next section. [See 4.1.1.]

The vgroup declaration ends with the ENDDECLARE command, which has no o