HOME | COMPANY | SOFTWARE | DOCUMENTATION | EDUCATION & TRAINING | SALES & SERVICE | |
"The Official Guide to Programming with OmniMark" |
|
International Edition |
Previous chapter is Chapter 7, "Shelves".
Next chapter is Chapter 9, "Expressions And Operators".
A scope is a region of the program in which a particular set of variable names is visible. In general, programmers may declare new variables at the beginning of a scope, and these variables cease to be visible at the end of the scope.
There are two kinds of scopes:
Variables declared in the global scope are called global variables. Global variables can be declared anywhere in the global scope except within rules and function definitions.
Variables declared in the local scope are called local variables. They must be declared at the beginning of the scope.
The following are all examples of local scopes:
This chapter discusses:
There are three kinds of control structures in OmniMark:
This control structure groups a sequence of actions into a block which is treated as if it were a single action. The entire block forms a local scope and all of the actions in the block are executed in order.
The compound action block is delimited by DO and DONE.
This control structure contains one or more parts (or paths) with some selection criteria. Only the first part whose criteria is satisfied is executed. If no criteria can be satisfied, none of the parts are executed. Each part forms its own local scope.
The selection structures in OmniMark are:
"DO SKIP" can also be considered to be a selection structure, although it can have one path at most.
This control structure is used to execute a group of actions repeatedly. The entire loop forms a local scope.
When a variable is declared inside of a loop, it is re-initialized each time the body of the loop executes. If it is important to use the current value in the next iteration, the variable must be declared outside the loop.
The loops in OmniMark are:
"REPEAT SCAN" is actually a composite of a loop and a selection structure, but is classed with loops because that is how it is differentiated from "DO SCAN".
DO local-declaration* action* DONE
The DO action simply groups several actions together so that one condition or one USING action can be applied to the group. It can also be used to allow local variables to be declared for the use of the actions within the block.
An example of extending the scope of USING prefixes across several actions is:
GLOBAL COUNTER page-width ... USING ATTRIBUTE margin OF PARENT DO DECREMENT page-width BY ATTRIBUTE margin OUTPUT "\newmarg{%v(margin)}%n" DONE
This section describes some of the selection structures in OmniMark. "DO SCAN" and "DO SKIP" are described in Section 3.2.2, "Scanning Actions" and Section 3.2.3, "Skipping Input".
DO condition? local-declaration* action* (ELSE condition local-declaration* action*)* (ELSE local-declaration* action*)? DONE
"DO WHEN" (or "DO UNLESS") is a more general form of DO. It allows different groups of actions to be selected depending upon the results of one or more conditions.
An example of this form without any ELSE parts is:
GLOBAL COUNTER page-width ... DO WHEN ATTRIBUTE margin OF PARENT IS SPECIFIED DECREMENT page-width BY ATTRIBUTE margin USING ATTRIBUTE margin OF PARENT OUTPUT "\newmarg{%v(margin)}%n" DONE
One or more ELSE parts are specified to look for one condition among several possible choices. As soon as one condition is TRUE, the actions immediately following it are carried out, and the actions in the other ELSE parts are skipped. If none of the conditions in the "DO WHEN" or "ELSE WHEN" parts are TRUE, and if there is an ELSE part without a condition, then the actions in that part are done.
The following example shows a typical use of this fourth form:
LOCAL COUNTER itemcount ... DO WHEN ATTRIBUTE type = "NUMBER" OUTPUT "\listitem{%d(itemcount)}{%c}%n" ELSE WHEN ATTRIBUTE type = "BULLET" OUTPUT "\listitem{\bullet}{%c}%n" ELSE OUTPUT "\listitem{\dash}{%c}%n" DONE
The final ELSE phrase (the one without a condition) may be omitted if it does not contain actions.
A condition can be placed after the DONE keyword. In this case it applies to the DO action as a whole. For example:
GLOBAL COUNTER page-width ... DO DECREMENT page-width BY ATTRIBUTE margin USING ATTRIBUTE margin OF PARENT OUTPUT "\newmarg{%v(margin)}%n" DONE WHEN ATTRIBUTE margin OF PARENT IS SPECIFIED
DO SELECT numeric-expression (CASE selector (| selector)* condition? local-declaration* action*)+ (ELSE condition? local-declaration* action*)? DONE
The keyword OR can be used in place of the "|" operator. The selector has the form:
Syntax
constant-numeric-expression (TO constant-numeric-expression)?
"DO SELECT" evaluates a numeric expression, and then compares it against the numeric values or ranges in the CASE alternatives, and selects the alternative that applies. It is an error for more than one CASE alternative to apply to the same value.
If none of the CASE alternatives apply, and there is an ELSE part, then the ELSE part is selected. Otherwise none of the parts are selected.
If a part was selected, then the actions in that part are performed. Otherwise, none of the actions are performed.
The numeric ranges are inclusive. If the value falls between the upper and lower bounds, or if it matches either bound, then the test succeeds and that CASE alternative is selected.
An example is:
LOCAL COUNTER val ... DO SELECT val CASE 1 | 3 | 5 ; do something CASE 2 | 4 | 6 TO 10 ; do something else ELSE ; do something different DONE
It is an error for a selecting value to match more than one CASE part. Each range set in a "DO SELECT" must describe a set of values disjoint from those in all the other range sets. This provision provides a bit of error detection, avoids "what happens when?" questions, and allows for serious optimization.
This section describes the common qualities of looping constructs and the basic REPEAT loop. "REPEAT SCAN" is described in Section 3.2.2, "Scanning Actions", and "REPEAT OVER" is described in Section 7.6, "Repeating Actions on Each Item of a Shelf".
REPEAT local-declaration* action* AGAIN
The REPEAT action is used to repeat a group of actions until the programmer explicitly exits the loop.
action is one or more actions. It is generally a good idea for one of those actions to be an EXIT (as described in Section 8.1.4, "Leaving a Loop"), a HALT, or a RETURN action to terminate the loop.
For example, suppose item counts for a structure of nested lists are stored on a shelf called list-item. The following actions might be used to generate a dot numbering scheme in which, for instance, the fifth item in a sublist under the fourth item in a sublist under the eighth item of the main list is identified as 8.4.5:
LOCAL COUNTER curlevel LOCAL COUNTER listlevel LOCAL COUNTER list-item VARIABLE ... SET curlevel TO 1 REPEAT USING list-item @ curlevel OUTPUT "%d(list-item)" EXIT WHEN curlevel = listlevel OUTPUT "." INCREMENT curlevel AGAIN
The AGAIN keyword may be followed by a condition that applies to the whole REPEAT action: that is, it determines whether the REPEAT action is to be performed or not. It is only tested once prior to beginning the REPEAT action. The REPEAT action cannot be terminated by making the condition fail from within the REPEAT action.
For long REPEAT actions with an overall condition, it is probably clearer to place the whole thing in a DO block. Rather than this form:
Syntax
REPEAT local-declaration* action+ AGAIN condition
This form is more self-explanatory:
Syntax
DO WHEN condition REPEAT local-declaration* action+ AGAIN DONE
EXIT terminates the innermost REPEAT action. In other words, an EXIT action causes control to pass to the point after the AGAIN. If REPEAT actions nest, the EXIT action causes control to pass to the point after the AGAIN in the REPEAT that contains the EXIT action.
EXIT
EXIT can only be used inside a REPEAT loop. It can be used with any form of REPEAT action (REPEAT, "REPEAT OVER" or "REPEAT SCAN").
All of the DO and REPEAT actions encapsulate one or more new nested local scopes. Each part of the DO or REPEAT action which can contain actions is a local scope.
The outermost local scope is always the one defined by the body of the rule or function. Also, in a "DO WHEN", each "DO WHEN", "ELSE WHEN", and ELSE part define separate local scopes. Each MATCH alternative in a "REPEAT SCAN" or a "DO SCAN" also defines a separate local scope. Each CASE alternative in a "DO SELECT" defines a local scope.
OmniMark allows local declarations to appear at the beginning of a local scope. The local declarations must appear prior to the actions, just as they must in rules. The local declarations apply only to the (zero or more) actions in the same local scope.
For example, in the following, the variable char-value in the ELSE part is not the one declared in the "DO UNLESS" part.
LOCAL STREAM char LOCAL COUNTER char-value ... DO UNLESS char MATCHES DIGIT LOCAL COUNTER char-value SET char-value TO BINARY char PUT #ERROR "The character with value %d(char-value)" _" is not a digit.%n" ELSE SET char-value TO char DONE
Redeclaring variable names is generally considered to be bad practice because it leads to confusing code.
Another example of declaring a variable in a local scope is the following. The value of temp that is output is 1 (one) each time round, because the local scope is re-entered each time round the loop, and each iteration gets, in effect, its own temp.
LOCAL COUNTER k ... SET k TO 4 REPEAT LOCAL COUNTER temp OUTPUT "temp's value is %d(temp).%n" SET temp TO k DECREMENT k EXIT WHEN k = 0 AGAIN
When a local scope exits, any open streams declared in that local scope are automatically closed, just as any open global streams are automatically closed when the program ends.
However, it is an error to close a stream which is in any #CURRENT-OUTPUT set (active or saved). In those cases OUTPUT-TO should be used before the end of the local scope to change the current output set to a stream (or streams) which is not declared in that local scope.
Pattern variables are "declared" in a manner slightly different from other names: they are declared in a pattern assignment embedded in a pattern, instead of a variable declaration.
Because PATTERN variables are declared and can be referenced before the local scope begins, they are treated differently than the variables declared within the local scope. In effect, they are treated as if two local scopes are defined, one within the other. The outer one defines the pattern variables, and the inner one can contain local declarations to declare other kinds of shelves.
This means:
The other context in which patterns, and therefore pattern assignments, can occur, is the second argument of the MATCHES operator. This argument defines an entire local scope itself. This means that:
LOCAL STREAM x LOCAL STREAM y DO WHEN y ITEM 1 MATCHES (DIGIT+ => ITEM "," ITEM) SET x TO y ITEM 1 DONE
In the above example, item is only defined as a pattern variable within the second argument of the MATCHES operator. Outside of the pattern, item retains its meaning as a keyword, and the PATTERN variable item is no longer available.
A SAVE or a SAVE-CLEAR declaration establishes a new instance of a global shelf, which hides the previous instance for any new references to it. The currently-selected item for the new instance always begins as the lastmost item, even if the shelf had been the subject of a USING prefix or a "REPEAT OVER" when it was saved. At the end of the local scope, when the old instance of the shelf is unhidden, the previous currently-selected item will become the currently-selected item again.
A global variable can only be saved in one domain at a time. Different global variables can be saved in different domains. For instance, if a global variable is already saved in the input processor, it cannot be saved in the output processor.
As an example of the effect of SAVE and SAVE-CLEAR on current item selection, the following outputs "20" and then "30":
LOCAL COUNTER my-shelf SIZE 3 SET my-shelf @ 1 TO 10 SET my-shelf @ 2 TO 20 SET my-shelf @ 3 TO 30 USING my-shelf @ 2 DO OUTPUT "%d(my-shelf)%n" DO SAVE my-shelf OUTPUT "%d(my-shelf)%n" DONE DONE
SAVE and SAVE-CLEAR declarations are allowed in any local scope and are in force for the duration of the actions that follow the declarations. So, for example, in the following, the same result will be displayed each time round the loop, because the saved "totals" shelf is restored at the end of each iteration.
LOCAL COUNTER k LOCAL COUNTER totals ... SET k TO 4 REPEAT SAVE totals LOCAL COUNTER n SET n TO NUMBER OF totals REMOVE totals DECREMENT k EXIT WHEN k = 0 AGAIN
HALT (WITH numeric-expression)?
Normally, a translation program ends when all the input text has been processed. A process program normally ends when all of the PROCESS-START, PROCESS, and PROCESS-END rules have completed. Some errors cause OmniMark to halt earlier.
The HALT action terminates OmniMark explicitly under program control. When it is encountered, OmniMark acts as if the end of all input has been reached. No further OmniMark rules or actions are performed. Processing of all output files is completed, including processing of referents.
The numeric-expression specifies the value to return to the operating system after halting. If no numeric-expression is given, HALT returns a value of 1 (one) by default.
For example, in order to halt an OmniMark program execution when the number of SGML errors detected in the document instance exceeds a pre-determined threshold, the following could be used:
SGML-ERROR ... DO WHEN #SGML-ERROR-COUNT = 50 OUTPUT "Present SGML error count is 50.%n" OUTPUT "Program terminating.%n" HALT WITH #SGML-ERROR-COUNT DONE
This example also has the effect of returning the number of errors to the operating system. How the operating system makes the return value available to other programs is highly machine- and system-dependent.
If a HALT action is performed that has a numeric expression specified, then the value of that numeric expression is used as the "return code" of the OmniMark program. If no numeric expression is specified, then the return code is set to one (1).
If a referenced referent is found that has not been defined, the error will be reported, and the program's exit code reset to 1 (one) if the HALT action set it to 0 (zero). If the halt action exit code is non-zero, that value is used instead.
HALT-EVERYTHING (WITH numeric-expression)?
Sometimes, it is necessary to halt the OmniMark program without trying to finish writing to files or resolving referents. HALT-EVERYTHING is used in those cases.
HALT-EVERYTHING terminates the OmniMark program in the same manner as HALT, except that referents are not resolved, and no error messages indicating unresolved referents are issued.
Next chapter is Chapter 9, "Expressions And Operators".
Copyright © OmniMark Technologies Corporation, 1988-1997. All rights reserved.
EUM27, release 2, 1997/04/11.