******************************************************************
* *
* 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.
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.]
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).
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.
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.
The rest of this document covers the following topics:
- How to store and execute a protocol. (Including how to build your own protocol subfile.)
- Command facilities used in protocols for branching, testing conditions, debugging, storing a user's settings, etc.
- How to define and use variables and "variable-groups" (vgroups).
- How to use system variables, system functions and "triples".
- How to compile a protocol for greater efficiency.
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".
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:
- a brief introduction to variables [See 1.2.] and variable substitution [See 1.3.]
- a brief introduction to functions [See 1.4.]
- a discussion of commands for input and output [See 1.5.]
- a discussion of commands for testing conditions [See 1.6.]
- a discussion of commands to control the execution flow in a protocol. [See 1.7.]
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.
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".]
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.
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 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.]
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
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.
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.]
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.]
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.]
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.]
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
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)
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".
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.]
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.]
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.
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 [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 [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.
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.
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.]
During your SPIRES session, you can execute a protocol that is in your active file by issuing the following command:
-> XEQ [AT label.name] [USING line.range]
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.
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
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.
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.]
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:
XEQ PROC label.name IN protocol.name
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.]
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:
XEQ FROM protocol.name ..protocol.name [parameters list] XEQ [ AT label.name ] [ USING line.range ]
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.
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 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.]
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:
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.]
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.]
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.]
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.]
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:
BREAK XEQ
To resume execution of the protocol from command mode, the protocol's end-user would type:
CONTINUE XEQ
(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 can also be caused by some types of programming errors, and cause the following terminal prompt:
-Continue XEQ?
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.
-Continue XEQ? break X-> show eval $lastxeq Select Fliedef <---(command failed because of misspelling)
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.]
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.
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.)
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
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:
- First it checks all preloaded protocols to see if one of them matches the name specified.
- Next it checks the protocol subfile (if any) that was SET XEQ, looking for a record with the name specified.
- Finally it checks the protocol subfile(s), if any, that are SET COMPXEQ, checking them in the order in which they were set, looking for the protocol record specified. (If there's no record with matching name in any of these places, SPIRES raises an error condition.)
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.)
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:
- The basic construct BEGINBLOCK...ENDBLOCK specifies a simple block of related commands. [See 3.1.1.]
- The looping block constructs, REPEAT...UNTIL and WHILE...ENDWHILE, can execute multiple times, based on conditions that you specify as part of the block. [See 3.1.2.]
- The LEAVE and ITERATE commands offer further control over execution of a looping block construct. [See 3.1.2.]
- The XEQ PROC command nests (transfers control temporarily) to a labelled subroutine. [See 3.2.1.]
- The SHOW XEQ STACK command helps you trace nested execution. [See 3.2.2.]
- The JUMP (or GOTO) command branches (transfers unconditional control) to a label within the protocol. [See 3.2.3.]
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.
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.
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.
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.]
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.)
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
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.'
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 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.
The considerations below may seem obvious, but are probably worth mentioning:
- Remember that something must happen within a looping block to make its conditions true (for REPEAT...UNTIL) or false (for WHILE...ENDWHILE), or else the block will execute indefinitely. For instance, if a block begins with the statement WHILE #TOTAL < 10, some statement within the block will need to increment #TOTAL till it reaches 10. (Or alternatively, you could use the LEAVE statement to exit the looping block from the middle.)
- Make sure your specified condition(s) do not lead to unexpected infinite loops. (This is easier to do than it sounds!) For instance, the statement WHILE COUNTER < 5 would cause an infinite loop because, without the #-prefix, "COUNTER" is considered a string value that is "less than" the number 5.
- To exit a looping block from the middle, use the LEAVE command in preference to the JUMP command. In fact, JUMP should probably only be used in block constructs for special cases, such as branching to an exit point, as in the example shown earlier.
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.]
This section briefly discusses some additional features of block constructs:
- how block constructs can be nested, using comments for clarity;
- how block constructs can be chained;
- how a block construct "remembers" and restores the result of the condition test that invoked it.
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.]
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.
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.
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.
- None of the commands named above (including LEAVE and ITERATE) may be issued interactively, but only within a protocol or other SPIRES programming tool:
-> begin block <--not valid as interactive command -Allowed only in XEQ
- Of the commands listed above, only LEAVE and ITERATE may be used as part of an ATTN clause, END clause, or NULL clause. (For instance, BEGINBLOCK may not be used within an ATTN clause.) In addition, only LEAVE and ITERATE may be used after a RETURN statement or after a command prefix, such as WITHIN LOG:
Wdsr, End = 'BeginBlock' <--BEGINBLOCK is invalid in a clause
Ask Upper, Attn = 'Leave' <--LEAVE is allowed in a clause
- A statement that contains block construct commands may not also use the slash prefix. (If you need to perform variable substitution, use the $TEST or $EVAL function instead of the slash.)
Let T = '#X < #Y'
/While #T <--Invalid block construct
Let T = '#X < #Y'
While $Test(#T) <--This is valid
- Statements beginning with UNTIL, ENDWHILE, or ENDBLOCK may not be the object of IF, THEN, or ELSE statements. (If your goal is to exit a looping block construct from the middle, use LEAVE instead.)
If #Status = '' Then EndWhile <--Invalid command
If #Status = '' Then Leave <--Use this instead
- Block constructs can be nested within each other, but not interleaved:
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.]
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:
- The XEQ PROC command [See 3.2.1.] nests to a subroutine or procedure (Proc) named with the command, temporarily transferring control to that Proc. (When the Proc encounters a RETURN command, control is returned to the calling point.)
- The JUMP (or GOTO) command [See 3.2.3.] branches to the label statement named with the command, transferring control to the part of the protocol beginning with that label statement.
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.
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:
++label.name
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".
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.
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 ?.)
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.
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.
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:
* TEST Xeq Proc FIRST.PROC Show Eval 'Now Return to Command Level' Return ++FIRST.PROC Xeq Proc SECOND.PROC Return ++SECOND.PROC Break Xeq Return
Issuing the SHOW XEQ STACK command at the execution break calls up the following display:
-> ..test -Type CONTINUE XEQ to resume -> show xeq stack - BREAK XEQ - XEQ PROC SECOND.PROC - XEQ PROC FIRST.PROC - XEQ FROM TEST
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.
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:
JUMP label.name
or
GOTO label.name
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.
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:
- Differences between static (pre-defined) variables and dynamic variables, and why static variables are preferable for production applications;
- How to define a global "vgroup" (variable group) for your protocol's static variables, and how to compile the vgroup; [See 4.1, 4.1.1.]
- How to set and clear a vgroup during execution of your application (the DECLARE GLOBAL VGROUPS and ENDDECLARE commands, the SET VGROUP and CLEAR VGROUP commands, or ALLOCATE and DEALLOCATE); [See 4.2.1.]
- How to see what vgroups are currently set and what the current values of variables are (SHOW ALLOCATED and SHOW STATIC VARIABLES); [See 4.2.3.]
- How to store and restore values in a vgroup (STORE STATIC and RESTORE STATIC); [See 4.2.4.]
- How to destroy a vgroup when you're finished with it (ZAP VGROUP), or destroy a particular set of stored values (ZAP STATIC). [See 4.2.5.]
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.
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.
When converting the variables your protocol uses into static variables, you choose between two types of vgroups, each with its own construction procedure:
- global vgroups, whose variable values can be shared among protocols and formats when and while they are allocated; and
- local vgroups, whose scope is only the protocol or format in which they are used.
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:
- Step 1: Define the vgroup in your active file by creating a record describing the characteristics (e.g., type and length) of each of your variables.
- Step 2: Select the VGROUPS subfile in SPIRES and add your vgroup definition to the file.
- Step 3: Compile the record.
Here is the procedure shown in more detail:
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.]
-> spires -> select vgroups -> add
To update an existing vgroup definition you'd use the familiar TRANSFER and UPDATE commands.
-> 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.
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:
DECLARE GLOBAL VGROUPS ALLOCATE = ORV.GQ.JPR.TESTVARS; ALLOCATE = ORV.GQ.JPR.TESTVARS2, HIDDEN; ENDDECLARE
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 orv.gq.jpr.testvars -> let select = 'TEST' || $SELECT : : -> clear vgroup orv.gq.jpr.testvars
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.]
You can define a local vgroup to be used within a protocol two different ways:
- within the protocol itself, using the DECLARE VGROUP command; or
- within a SYS PROTO record, if you are compiling the protocol.
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.
* INTEREST
- Computes amount of each of M monthly payments on loan of
- amount A at interest rate I.
DECLARE VGROUP LOCAL.INTEREST
VARIABLE = AMOUNT;
TYPE = PACK;
VARIABLE = MONTHS;
TYPE = INT; VALUE = 360;
VARIABLE = INTEREST;
TYPE = PACK;
ENDDECLARE
++MORTGAGE
...
A local vgroup declaration begins with the DECLARE VGROUP command:
DECLARE VGROUP vgroup-name [, HIDDEN]
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