HOME | COMPANY | SOFTWARE | DOCUMENTATION | EDUCATION & TRAINING | SALES & SERVICE | |
"The Official Guide to Programming with OmniMark" |
|
International Edition |
Previous chapter is Chapter 6, "OmniMark Data Types".
Next chapter is Chapter 8, "Variable Scopes".
Up until now, OmniMark counters, switches, and streams have been described and illustrated as "scalar" (or single-valued) objects. In fact, all OmniMark objects are "vector" (or multi-valued) objects called shelves.
OmniMark shelves have the following features:
When a shelf has only one item, that item is the default item on the shelf, and does not need to be explicitly selected with an item number, or a key.
For instance, suppose a counter named list-item counts items within a list. When lists cannot be listed, list-item only needs a single value, and it can always be referenced without an item number or a key.
DOWN-TRANSLATE GLOBAL COUNTER list-item ELEMENT list SET list-item TO 0 OUTPUT "%c" ELEMENT item INCREMENT list-item OUTPUT "%d(list-item). %c%n"
More importantly, nested data structures can take advantage of the default item being the last item on the shelf.
If one list can occur inside another, only a slight change to the code is required. When a sublist is encountered, a new item must be added to the shelf. The new item becomes the default item. That means that the code which accessed list-item without an item number or a key will always refer to the number of items in the inner-most list.
When the end of the list is encountered, the last item can be removed. The default item then reverts to the enclosing list.
DOWN-TRANSLATE GLOBAL COUNTER list-item VARIABLE INITIAL-SIZE 0 ELEMENT list NEW list-item SET list-item TO 0 OUTPUT "%c" REMOVE list-item ELEMENT item INCREMENT list-item OUTPUT ("%_" ||* (NUMBER OF list-item * 6)) || "%d(list-item). %c%n"
Note that the only differences in the new example involve the creation and the removal of new list-item values, and ensuring that the indentation of each list item is correct.
The ability to model a nested data with virtually the same code that models scalar data results in code that is much easier to understand.
Finally, OmniMark makes it easy to iterate over each item of a shelf. For instance, suppose the list item numbers are to reflect the item numbers of each list currently open. If the list is two levels deep, and the inner list occurs in the fourth item of the outer list, then the items of the inner list would be numbered as "4.1", "4.2", "4.3", and so on.
This is easily done by using a looping construct to emit the list-item values.
DOWN-TRANSLATE GLOBAL COUNTER list-item VARIABLE INITIAL-SIZE 0 ELEMENT list NEW list-item SET list-item TO 0 OUTPUT "%c" REMOVE list-item ELEMENT item INCREMENT list-item REPEAT OVER list-item OUTPUT "%d(list-item)." AGAIN OUTPUT " %c%n"
Again, very little change is required. The additional nesting of inner list items has been removed because the item numbers will clearly show which items belong to which list level. Other than that, the only change is to add the looping construct.
Shelves can be global or local. Global shelves can be referenced anywhere within a program. Local shelves can only referenced in the local scope in which they are declared. Rule bodies and function bodies are examples of local scopes. Other examples are described in Chapter 8, "Variable Scopes".
GLOBAL shelf-type shelf-name shelf-size? initializer?
GLOBAL declarations define global shelves which exist for the lifetime of the entire program. All global shelves must be declared before they are used. (The "DECLARE HERALDED-NAMES" declaration can change this behaviour. See Section 19.1.4.8, "Version 2 Compatibility".)
The shelf-type must be either COUNTER, SWITCH, or STREAM. The shelf-size is discussed below in Section 7.1.3, "Shelf Sizes".
If the shelf-size is not given, the shelf is a fixed size shelf with one item.
The initializer begins with the keyword INITIAL and is used to automatically initialize the global variable. It is described in Section 7.1.4, "Initializing Shelves".
A global shelf may be declared several times, but all the declarations must describe the same shelf. As an example, the following three declarations all describe a scalar shelf of the same type and dimension:
GLOBAL COUNTER list-level GLOBAL COUNTER list-level SIZE 1 GLOBAL COUNTER list-level SIZE (2 * 2) - 3
This is primarily intended to accommodate declarations in include files when more than one include file uses the same global variables. The global variables can be defined in all of the include files, allowing the include files to be included in any order. It is not recommended that programmers define global variables many times in the same file.
Streams, counters, and switches defined on the command line (using the -os, -define, -activate, or -counter command-line options) must either be declared as global shelves with a SIZE of one (1) (either explicitly, or by omitting the shelf-size entirely), or as a VARIABLE sized shelf.
If there are many GLOBAL declarations in a row, the keyword AND or operator "&" can be used instead of the keyword GLOBAL in the second and subsequent declarations. This form is deprecated, however, since it adds nothing to readability.
LOCAL shelf-type shelf-name shelf-size? initializer?
A LOCAL declaration declares shelves which only exist for the duration of a local scope. The body of a rule is one example of a local scope. Local scopes are further defined in Chapter 8, "Variable Scopes".
The shelf-size is described below in Section 7.1.3, "Shelf Sizes".
Local shelves are created when the local scope is entered. Local shelves are destroyed when the local scope completes. For the local scope that contains the rule body, this is when the rule completes execution.
An example of using a local shelf declaration is the following:
ELEMENT cell-contents WHEN PARENT IS chapter LOCAL STREAM cell-text SET cell-text TO "%sc" OUTPUT "[CELL CONTENTS]%g(cell-text)%n[CELL-ITEM]%g(cell-text)%n"
In this example, even if the cell-contents element contains other cell-contents elements, each one is writing to its own stream.
A local shelf may only be declared once in each scope. This differs from global shelves, which may be declared many times as long as all of the declarations are the same.
The shelf-size has the syntax:
Syntax
(VARIABLE (TO numeric-expression)? (INITIAL-SIZE numeric-expression)?) | (SIZE numeric-expression)
The keyword VARIABLE indicates that items can be added or removed from the shelf. For VARIABLE shelves:
Otherwise, the maximum size of the shelf is at most 2,147,483,647. The maximum size may be smaller depending on the amount of memory available.
This is especially useful for initializing shelves to size zero, rather than having to CLEAR them immediately after they are declared.
LOCAL STREAM names VARIABLE INITIAL-SIZE 0
When no INITIAL-SIZE is given, the shelf initially has one item.
The keyword SIZE indicates that the shelf's size is fixed. The numeric-expression that follows indicates the size of the shelf. The numeric-expression must be a constant.
The SIZE, INITIAL-SIZE and TO specifications and the number of items in the INITIAL part must conform to the following rules:
The initializer in GLOBAL and LOCAL declarations can be used to provide initial values for the items on a shelf.
The syntax of the initializer is:
Syntax
INITIAL { initial-value (WITH KEY string-expression)? (, initial-value (WITH KEY string-expression)?)* }
If "WITH KEY" is specified for an item, then the string-expression specified following "WITH KEY" is used as the item's key. Otherwise the item is initially unkeyed.
The initial-value depends on the shelf type:
When INITIAL is specified, the shelf is initialized using each initial-value as the value, and optionally key, of an item. There are as many items on the shelf initially as there are instances of initial-value. For each item, the initial-value in each initial-value is used as the item's value.
For STREAM shelves, if the keyword UNATTACHED is used as the initial value for an item, then the item is an unattached stream. If a constant string expression is used, the initial value for the item is a closed buffer, with the text of the expression as its contents.
Since a shelf can contain more than one item, a method of selecting particular items from a shelf is needed. The method for selecting an object is with an indexer phrase:
selects an item according to its position.
selects an item according to its associated key.
selects the last item on the shelf.
Additionally, it is possible to accept the currently selected item by not specifying an index phrase.
Think of a shelf of items as a bookshelf full of books. "@ 1" refers to the first item on the shelf. If a shelf has four values (10, 20, 30, 40), then "@ 2" refers to the value 20, and "@ 4" refers to the value 40. Likewise, referring to the shelf without using an index phrase refers to the value 40.
Accessing shelf items by key is like choosing a book based on its title.
Whenever a shelf item is referenced without explicitly selecting an item (with a "^" (KEY), "@" (ITEM) or LASTMOST indexer), the currently selected item is used.
Initially, the currently selected item on a shelf is always the last item of the shelf. (If the shelf has no items, then there is no currently selected item.)
The currently selected item can be changed by a "REPEAT OVER" loop, a USING prefix, or a function call.
There are three cases where a shelf has no current item:
The "@" operator selects an item based on its position in the shelf.
(COUNTER | STREAM | SWITCH)? shelf-name @ numeric-expression
This construct refers to the object in position numeric-expression from the "left" or the "bottom" of the shelf.
The keyword ITEM can be used as a synonym for the "@" operator.
For example,
LOCAL COUNTER list-item VARIABLE ... list-item @ 3
refers to the third item on the counter shelf for list-item,
LOCAL COUNTER list-item VARIABLE LOCAL COUNTER list-level ... list-item @ list-level
refers to the item indicated by the current value of counter list-level, and
LOCAL COUNTER list-item ... list-item NUMBER OF list-item
refers to the lastmost, or default, item on shelf list-item.
The selector must not be greater than the number of items on the shelf. The number of items in a shelf can be determined by using the "NUMBER OF" numeric value described in Section 7.4.1, "Determining the Size of a Shelf".
An "@" operator can follow a shelf name in most contexts. Possibilities are illustrated below:
Example A
GLOBAL COUNTER figurenum VARIABLE GLOBAL COUNTER chapno ... ELEMENT graphic WHEN figurenum @ chapno > 1
Example B
GLOBAL STREAM index VARIABLE GLOBAL COUNTER idxnum ... PUT index @ idxnum "%c"
Example C
GLOBAL SWITCH flag VARIABLE ... SET flag @ 2 TO TRUE
The "@" operator cannot be used in format items in an OmniMark string. Section 7.5, "Specifying a New Default Selected Item" describes how to write values from a shelf to a stream.
An "@" operation is similar to indexing into an array. Indirect indexing can be obtained by using an item on another shelf as the item index. For example:
GLOBAL COUNTER a VARIABLE GLOBAL COUNTER b VARIABLE GLOBAL COUNTER c VARIABLE ... a @ b @ c @ d
An expression like this is evaluated from right to left. First COUNTER d is evaluated to get the index to COUNTER c. This value gives the index into COUNTER b. This value gives the index into COUNTER a. In other words, it is equivalent to:
GLOBAL COUNTER a VARIABLE GLOBAL COUNTER b VARIABLE GLOBAL COUNTER c VARIABLE ... a @ (b @ (c @ d))
There is no limit on the number of "@" operations used. Note that this method cannot be used to support multi-dimensional arrays. Multi-dimensional arrays are best modelled with keys, shown in Section 7.2.3, "Key-Based Item Selection".
The "^" (cirumflex) operator is used to select the item that has the specified key.
(COUNTER | STREAM | SWITCH)? shelf-name ^ string-expression
where the string-expression is usually referred to as a key.
This construct can be more powerful than numeric indexing with the "@" operator, because it allows associating an arbitrary string value with each item on the shelf. The keys can contain dynamic values, and there is no effective limit on their length. Examples of their use are given in Section 7.2.3, "Key-Based Item Selection".
An item on a shelf can be assigned a key by the actions NEW, "SET NEW", and "SET KEY".
For example,
GLOBAL STREAM toc VARIABLE ... toc ^ "title-3"
refers to the item on the counter shelf called toc that has a key equal to "title-3".
The keyword KEY can be used as a synonym for the "^" operator.
Keys can consist of any string expression. This includes valid format items, including "%c", "%g", "%v", and "%x".
Key-based selection is a good method of setting up an association list. In a stream shelf, each item can be bound to a buffer set to some value. When each item is created it can be given a key that is related to the value according to some function (see Section 7.3.1, "Adding New Items To Shelves"). Association lists can also be used with counters and streams.
The following example shows a straightforward use of keys. This example finds the number of occurrences of each word (ignoring case) in the input. The count is kept in the shelf word-counts. The key of an item is the word itself; the value of the item is the number of times its key was found in the input.
CROSS-TRANSLATE GLOBAL COUNTER word-counts VARIABLE INITIAL-SIZE 0 FIND (WORD-START LETTER+ WORD-END) => word DO WHEN word-counts HAS KEY "%ux(word)" INCREMENT word-counts ^ "%ux(word)" ELSE ; Default value is 1, which is what we want. NEW word-counts ^ "%ux(word)" DONE FIND ANY ; Now print out the results FIND-END REPEAT OVER word-counts OUTPUT KEY OF word-counts OUTPUT "%t%d(word-counts)%n" AGAIN
The NEW action places a new item on the word-counts shelf and sets it to one (1) every time a new word is found in the input document.
Just as each item on a shelf has a unique positional index, every key on a shelf must be unique. The "HAS KEY" test operator can be used to guarantee this. (See Section 7.4.3, "Testing a Shelf For a Given Key Value").
Key-based indexing can also be used to simulate multi-dimensional arrays.
One example of a two-dimensional array is a table. Each item in the table has a row and a column. The key could contain the row number and the key number separated by some separator character like a period ".". Alternatively, if each row and column has a "name", then those names could be used in the keys instead of the row and column numbers.
DOWN-TRANSLATE GLOBAL COUNTER row GLOBAL COUNTER col GLOBAL STREAM table VARIABLE ELEMENT table CLEAR table SET row TO 0 SUPPRESS ELEMENT row INCREMENT row SET col TO 0 SUPPRESS ELEMENT col INCREMENT col SET table ^ "%d(row).%d(col)" TO "%c"
Another example of a three dimensional array with which most readers will be familiar can be found with Rubik's cube. Suppose we are using OmniMark to solve the cube. The cube is made up of 26 sub-cubes (the center, where you would expect to find the 27th, is empty and thus need not take up an item on our shelf). Each sub-cube is given a three part key. The first part indicates where it is relative to the table: 1 if it's in the top layer, 2 if it's in the middle layer, and 3 if it's in the bottom layer. The second part of the key indicates if the sub-cube is in the left slice of the cube (1), the middle slice (2), or the right slice (3). Finally, the third part is used to tell if the sub-cube is facing us (1), in the middle (2), or facing away from us (3).
So the top-left-front sub-cube would be indicated with a key of "1.1.1" and the bottom-right-rear sub-cube by "3.3.3". Note that there would be no item with a key of "2.2.2" because that is the empty position. An arbitrary position given by three counters, x, y, and z, could be specified by the key "%d(x).%d(y).%d(z)".
How this shelf would actually be used to solve Rubik's Cube is, as the old saying goes, left as an exercise to the interested reader.
LASTMOST can be used wherever an "@" (ITEM) or "^" (KEY) indexer can be used, to identify a shelf item to be accessed. The LASTMOST indexer always accesses the last item on the shelf.
The LASTMOST item can be selected with the syntax:
Syntax
(COUNTER | STREAM | SWITCH)? shelf-name LASTMOST
The two examples below are equivalent:
Example A
LOCAL STREAM foo VARIABLE ... foo LASTMOST
Example B
LOCAL STREAM foo VARIABLE ... foo @ NUMBER OF foo
Although the two forms:
Syntax
shelf-name LASTMOST
and
Syntax
shelf-name @ NUMBER OF shelf-name
are identical when used to access an item on a shelf, they behave differently when they are used to set the currently-selected item:
In those cases, when the default item of the shelf or function argument is accessed, the LASTMOST item is recalculated, while the "NUMBER OF" expression is not.
This means that when the default item is set to the LASTMOST item, it will always be the last item on the shelf regardless of how the shelf changes.
In the following example:
PROCESS LOCAL COUNTER c VARIABLE CLEAR c SET NEW c TO 1 USING c LASTMOST DO OUTPUT "The value is %d(c)%n" SET NEW c TO 2 OUTPUT "The value is %d(c)%n" SET NEW c TO 3 OUTPUT "The value is %d(c)%n" DONE
The output is
The value is 1 The value is 2 The value is 3
However, when the default is set to the last item with "@ NUMBER OF":
PROCESS LOCAL COUNTER c VARIABLE CLEAR c SET NEW c TO 1 USING c @ NUMBER OF c DO OUTPUT "The value is %d(c)%n" SET NEW c TO 2 OUTPUT "The value is %d(c)%n" SET NEW c TO 3 OUTPUT "The value is %d(c)%n" DONE
The output is
The value is 1 The value is 1 The value is 1
That's because the number of items on the shelf c is just 1. That becomes the numeric index into the shelf for the duration of the USING.
NEW shelf-type? shelf-name (^ string-expression)? insertion-point?
The NEW action inserts a new value in a shelf. The NEW action can only be invoked on shelves which were declared as VARIABLE. (See Section 7.1, "Shelf Declarations").
shelf-type is an optional herald which can either be COUNTER, SWITCH, or STREAM.
insertion-point is a phrase describing where in the shelf that the new value should be inserted. If no insertion-point is specified, the new item is added at the "right" end of the shelf. (In other words, it becomes the new lastmost item.)
The insertion-point is described in Section 7.3.1.3, "Inserting Into Shelves".
The new value is initialized according to its type: counters are set to 1, switches are FALSE, and stream buffers are UNATTACHED.
If a key is specified, the named item has that key. Two items on the same shelf may not have the same key.
If the program has a sequence of NEW actions in a row, the keyword NEW can be replaced by AND or operator "&" in the second and subsequent actions. This form is deprecated.
SET NEW shelf-type? shelf-name (^ string-expression)? insertion-point? TO (numeric-expression | string-expression | test-expression)
The "SET NEW" action is essentially a composition of the SET action and the NEW action. (It simply replaces the shelf-name part of a SET action with an entire NEW action.)
The "SET NEW" action first performs the embedded NEW action, and then sets the value of the inserted item to the value of the expression after the keyword TO.
The type of expression appearing after the keyword TO depends on the shelf type:
The insertion-point in the NEW and "SET NEW" actions has the form:
Syntax
(BEFORE | AFTER) ((@ numeric-expression) | (^ string-expression) | LASTMOST)
If an insertion point is specified then the new item is placed on the shelf:
If the "^" (KEY) form of the insertion-point is used, then the shelf must already have an item with a key that matches the given one. If the "@" (ITEM) form of the insertion-point is used then:
If a shelf is the subject of a "REPEAT OVER" action, then no action within that "REPEAT OVER" or in any function called within the "REPEAT OVER" can insert into the shelf prior to the lastmost item that was on the shelf when the "REPEAT OVER" action commenced. "REPEAT OVER" essentially "locks" the shelf items that were present when it began. Appending to the end is allowed, as is insertion ahead of an appended item.
Note that the default insertion point is always after the lastmost item on the shelf -- the one with the highest item number. Because the default selected item can be changed with a USING or inside of a function call, the last inserted item is therefore not necessarily the default selected item.
Items can either be removed from a shelf one at a time, or all at once.
CLEAR shelf-type? shelf-name
The CLEAR action removes all items from a shelf.
The shelf-type is an optional herald.
The CLEAR action is not allowed on shelves that have been declared with a fixed size (see Section 7.1, "Shelf Declarations").
A shelf cannot be cleared if it is the subject of a "REPEAT OVER".
If a number of CLEAR actions occur consecutively, the keyword CLEAR can be replaced in the second and subsequent actions by the keyword AND or operator "&". This form is deprecated as it does not increase readability.
REMOVE shelf-type? shelf-name indexer?
The REMOVE action removes the selected item from a shelf.
The shelf-type is an optional herald. It can be one of the keywords COUNTER, SWITCH, or STREAM. The indexer has the form:
Syntax
(@ numeric-expression) | (^ string-expression) | LASTMOST
If no indexer is given, the currently selected item is removed.
When an item is removed, the item numbers of all the items that follow it in the shelf drop by one. Items that have keys keep the same keys though. REMOVE is allowed to produce a "cleared" shelf, with no items.
If a keyed item is removed from a shelf, that item's key can be assigned to a new item on that shelf.
Unless modified by a "REPEAT OVER" or USING prefix, referring to a shelf item without using an index refers to the last (lastmost) item on the shelf. So if a shelf is thought of as a stack, NEW can be thought of as "push", and REMOVE as "pop". On the other hand if a shelf is thought of as a queue, NEW can be thought of as "enqueue", and REMOVE, with "@ 1" as "dequeue" (add to the end; remove from the start).
For example, the following removes the first item from the counter shelf numbers:
LOCAL COUNTER numbers VARIABLE ... REMOVE numbers @ 1
The REMOVE action is not allowed on shelves that have been declared with a fixed size (see Section 7.1, "Shelf Declarations"). Also, for shelves indexed by a "REPEAT OVER" action, the REMOVE action is not allowed on the original items in the shelf. However, items explicitly created inside the "REPEAT OVER" block may be deleted in that same block.
For example, the following sequence is not allowed:
LOCAL STREAM cd-titles VARIABLE ... REPEAT OVER cd-titles REMOVE cd-titles AGAIN
But the following is, assuming the stream cd-titles contains 10 items originally:
LOCAL STREAM cd-titles VARIABLE ... REPEAT OVER cd-titles NEW cd-titles REMOVE cd-titles @ 11 AGAIN
COPY shelf-type? shelf-name TO shelf-name
The shelf-type is an optional herald indicating the shelf type. The first shelf-name refers to the shelf that is being copied, and the second to the destination shelf.
The COPY action can be used to copy the entire contents of a shelf to another shelf.
Both of the shelves must have the same type (COUNTER or STREAM or SWITCH).
Because the destination shelf will become the same size as the source shelf, the destination shelf must either be declared VARIABLE or it must have the same size as the source shelf currently has. If the destination shelf was declared as VARIABLE with a maximum size, the maximum size must be greater than the current size of the source shelf.
When STREAM shelves are copied, all streams on the source shelf (the one being copied from) must either be closed and attached to buffers, or they must be UNATTACHED.
Also, when STREAM shelves are copied, all open streams on the destination shelf (the one copied to) will be automatically closed before the COPY operation takes place.
A COPY causes whatever contents the second shelf (the one copied to) had prior to the copy to be entirely replaced by the new items.
The following example copies all the items of the counter shelf reference-counts to saved-reference-counts, so that after the copy, saved-reference-counts has the same number of items as reference-counts, and each item in reference-counts has the same value and key as its corresponding item in saved-reference-counts.
GLOBAL COUNTER reference-counts ... LOCAL COUNTER saved-reference-counts COPY reference-counts TO saved-reference-counts
The result of copying a shelf to itself is undefined.
COPY-CLEAR shelf-type? shelf-name TO shelf-name
The shelf-type is an optional herald indicating the shelf type. The first shelf-name refers to the shelf that is being copied, and the second to the destination shelf.
The COPY-CLEAR action can be used to copy the entire contents of a shelf to another shelf, and then clear the source shelf. This is essentially moving the contents of the source shelf to the destination shelf.
Both of the shelves must have the same type (COUNTER or STREAM or SWITCH).
Because the destination shelf will become the same size as the source shelf, the destination shelf must either be declared VARIABLE or it must have the same size as the source shelf currently has. If the destination shelf was declared as VARIABLE with a maximum size, the maximum size must be greater than the current size of the source shelf.
Unlike COPY, COPY-CLEAR requires the source shelf to be declared VARIABLE.
A copy causes whatever contents the second shelf (the one copied to) had prior to the copy to be entirely replaced by the new items. It also causes the source shelf to be cleared.
Unlike the CLEAR action, when STREAM shelves are the subject of a COPY-CLEAR action, there is no restriction on the streams on the source shelf. Specifically, open streams are moved from the source shelf to the destination without being closed.
The destination shelf in a COPY-CLEAR is treated the same as the destination shelf in the COPY action: when STREAM shelves are copied, all open streams on the destination shelf will be automatically closed before the COPY operation takes place.
Keys are usually attached to shelf items when the shelf item is inserted (with NEW or "SET NEW"). Keys can also be attached to shelf items in the initialization part of the shelf declaration.
Where these techniques are not sufficient, OmniMark allows keys to be attached, changed, and removed directly.
SET KEY OF shelf-type? shelf-name indexer? TO string-expression
shelf-type is an optional type herald. indexer is:
Syntax
(@ numeric-expression) | (^ string-expression) | LASTMOST
"SET KEY" sets the key of the selected item of the indicated shelf to the value of the string-expression. If the item was already keyed, then the previous key is deleted first.
It is an error to set the key of a nonexistent item.
REMOVE KEY OF shelf-type? shelf-name indexer?
shelf-type is an optional type herald. indexer is:
Syntax
(@ numeric-expression) | (^ string-expression) | LASTMOST
"REMOVE KEY" removes the key, if any, of the selected item of the indicated shelf to the value of the string-expression.
Removing the key of an unkeyed item is not an error. It is an error if the specified item does not exist.
NUMBER OF shelf-type? shelf-name
The "NUMBER OF" operator returns the number of items on a shelf.
The shelf-type is an optional herald indicating the shelf type.
There is no practical restriction on the maximum size of a shelf other than the memory constraints of a particular computer system. It is, however, an error to attempt to access, from a shelf, an item which does not exist. For instance, if the list-valued attribute cw has only 3 values, it is an error to refer to "cw @ 4".
NUMBER-OF is provided as a synonym of "NUMBER OF" for compatibility with early versions of OmniMark.
ITEM OF shelf-type? shelf-name indexer?
The shelf-type is an optional herald, and the indexer has the syntax:
Syntax
(@ numeric-expression) | (^ string-expression) | LASTMOST
Once it has been determined that a shelf has a specific key, it is possible to find which item has that key with the "ITEM OF" operator.
This operation gives the position of the specified item on the shelf. It is an error if the specified item does not exist.
One way to avoid this error when selecting an item by its KEY is to guarantee that the shelf has the key before looking for its position:
GLOBAL SWITCH assoc-list GLOBAL COUNTER item-no ... FIND "{" [ANY EXCEPT "}"]+= curr-key "}" DO WHEN assoc-list HAS KEY "%x(curr-key)" SET item-no TO ITEM OF assoc-list ^ "%x(curr-key)" ELSE ; An index of 0 means that the key wasn't found. SET item-no TO 0 DONE
The KEY-less form of the "ITEM OF" operator is primarily useful within actions modified by a USING prefix (Section 7.5, "Specifying a New Default Selected Item") or in functions (Section 12.2, "Functions -- Shelves and Arguments").
shelf-type? shelf-name (HAS | HASNT) KEY string-expression
The shelf-type is an optional herald indicating the shelf type.
The "HAS KEY" test yields:
"HASNT KEY" yields the opposite result from "HAS KEY".
shelf-type? shelf-name indexer? (IS | ISNT) KEYED
"IS KEYED" tests whether the specified shelf item has a key associated with it.
The shelf-type is an optional herald, and the indexer has the syntax:
Syntax
(@ numeric-expression) | (^ string-expression) | LASTMOST
The test yields TRUE if the specified item has a key, and yields FALSE if it doesn't. The ISNT form of the test reverses these two meanings.
If no item is specified, the currently selected item is examined. This form is primarily useful within actions modified by a USING prefix (Section 7.5, "Specifying a New Default Selected Item") or functions (Section 12.2, "Functions -- Shelves and Arguments").
It is an error if the specified item does not exist.
KEY OF shelf-type? shelf-name indexer?
The shelf-type is an optional herald, and the indexer has the syntax:
Syntax
(@ numeric-expression) | (^ string-expression) | LASTMOST
This operation returns a string expression containing the item's key. If an indexer is not specified, the currently selected item on the shelf is examined.
It is an error if the specified item does not exist.
A shelf name without an "@" (ITEM) or "^" (KEY) indexer normally refers to the lastmost value on the shelf. The USING prefix can be placed before an action to override this convention for a particular shelf. The USING prefix has the form:
Syntax
USING shelf-type? shelf-name indexer
where shelf-type is one of the keywords COUNTER, SWITCH, or STREAM. The indexer selects the item. Note that it is required. The indexer has the form:
Syntax
(@ numeric-expression) | (^ string-expression) | LASTMOST
Multiple USING prefixes can be applied to one action, as shown below:
GLOBAL COUNTER chapno GLOBAL COUNTER secno GLOBAL COUNTER section VARIABLE ... LOCAL COUNTER listitem VARIABLE LOCAL COUNTER listlevel ... USING listitem @ listlevel USING section ^ "%d(chapno)-%d(secno)" OUTPUT "%d(section).%d(listitem)"
The scope of the USING prefix is the action it precedes. It does not carry down into subelements through a "%c" format. The USING prefix is frequently employed with the compound actions described in Section 8.1, "Control Structures".
Each time an item on the specified shelf is referenced in the action without an indexer, the item specified by the USING prefix is used.
The item specified by the USING prefix is looked up again every time the shelf is referenced without an indexer. That means that if the USING prefix specifies the item using:
The selected item changes when new items are added or removed from the end of the shelf.
The selected item changes when new items are inserted before the specified one.
The selected item changes if the key is removed from the selected item, and applied to a different item.
For instance, the following two examples are not equivalent:
Example A
LOCAL STREAM foo VARIABLE ... USING foo LASTMOST
Example B
LOCAL STREAM foo VARIABLE ... USING foo @ NUMBER OF foo
because NUMBER OF STREAM foo is calculated at the start of the USING and the current item does not change if the number of items in foo changes.
Specifying LASTMOST in a USING or in a passed READ-ONLY or MODIFIABLE function argument effectively re-establishes the default behaviour for the current item of a shelf. That is, the last item on the shelf when it is referenced is the default item. It can be used to cancel the effect of any outer establishment of a default item.
When a condition and a USING prefix both apply to an action, the USING prefix applies to both the action and the condition together. In other words, the USING prefix is evaluated first, and then, if a condition is present, it is evaluated.
Care must be taken when a USING prefix must be guarded by a condition to prevent an error. For example, the following usually does not work:
GLOBAL COUNTER page-width ... USING ATTRIBUTE margin OF PARENT DO DECREMENT page-width BY "%v(margin)" OUTPUT "\newmarg{%v(margin)}%n" DONE WHEN ATTRIBUTE margin IS SPECIFIED
The problem is that the USING prefix is evaluated before the specification test. This example could cause an error if a non-existent attribute appeared in the USING prefix. The correct way of expressing this is:
GLOBAL COUNTER page-width ... DO WHEN ATTRIBUTE margin IS SPECIFIED USING ATTRIBUTE margin OF PARENT DO DECREMENT page-width BY "%v(margin)" OUTPUT "\newmarg{%v(margin)}%n" DONE DONE
REPEAT OVER shelf-type? shelf-name (& shelf-type? shelf-name)* local-declaration* action* AGAIN
"REPEAT OVER" is a special form of the REPEAT action for processing successive values on a shelf. The shelf-type is an optional type herald.
The actions are repeated once for each value on the shelf. References to shelf-name are interpreted as though followed by ITEM 1 during the first iteration, by ITEM 2 during the second iteration, and so forth. Thus, the following iterates over the shelf listitem, printing a period-separated list of the item numbers:
LOCAL COUNTER curlevel LOCAL COUNTER listlevel LOCAL COUNTER listitem VARIABLE ... SET curlevel REPEAT OVER listitem OUTPUT "%d(listitem)" OUTPUT "." UNLESS curlevel = listlevel INCREMENT curlevel AGAIN
"REPEAT OVER" can process the items on a number of different shelves in parallel. The first iteration of the "REPEAT OVER" references the first item of all of the shelves. The second iteration references the second item of all of the shelves, and so on.
For example, suppose items in the nested lists of the previous example can be identified with letters in an alphabetic sequence as well as by decimal numbers. A shelf of switches called listtype-is-numeric indicates which type of label is given to each level of list. The following actions might be used:
LOCAL COUNTER curlevel LOCAL COUNTER listlevel LOCAL COUNTER listitem VARIABLE ... SET curlevel REPEAT OVER listitem & listtype-is-numeric DO WHEN listtype-is-numeric OUTPUT "%d(listitem)" ELSE OUTPUT "%a(listitem)" DONE OUTPUT "." UNLESS curlevel = listlevel INCREMENT curlevel AGAIN
The following syntactic variation is permitted:
It is always possible to add items to the end of a shelf while it is being processed by a "REPEAT OVER" action. However, the number of iterations is determined before the first iteration. If, for example, counter shelf sizes has five items in the following example, the loop will run five times, not ten:
LOCAL COUNTER sizes VARIABLE ... ; Initially, NUMBER OF sizes = 5 REPEAT OVER sizes NEW sizes AGAIN ; The loop runs five times, so now, NUMBER OF sizes = 10
Removing items from a shelf that is being repeated over is more problematic. It is allowed only if the index of the item being removed is greater than the initial size. In the above example, the only items that can be removed from counter sizes are the ones created by the NEW action.
In a "REPEAT OVER", it is often useful to know whether the loop is at the first or last iteration. Two predefined switches provide this information. The switch named #FIRST is only active on the first iteration of a "REPEAT OVER", and the switch #LAST is only active on the last. They are both active on the only iteration over a set with one member. These two switches are read-only; they can be tested in a switch test, but they cannot be explicitly activated or deactivated.
The following example shows how to differentiate the first item of a list, the last, and the inner ones from each other. It also handles the single-item list as a special case. If the COUNTER shelf sums is empty, the "REPEAT OVER" loop is never entered.
LOCAL COUNTER sums VARIABLE ... REPEAT OVER sums DO WHEN #FIRST DO WHEN #LAST ; only one item, handle specially OUTPUT "Only sum is %d(sums).%n" ELSE OUTPUT "Sums are %d(sums)" ; print start DONE ELSE DO WHEN #LAST OUTPUT " and %d(sums).%n" ; print end ELSE OUTPUT ", %d(sums)" ; print inner item DONE DONE AGAIN
When "REPEAT OVER" loops nest, each instance has its own #FIRST and #LAST switches.
An OmniMark program can also ask for the index of the current iteration by accessing the predefined counter, #ITEM. It can be used in numeric expressions or in format items that format counters. For example:
LOCAL SWITCH flags VARIABLE ... REPEAT OVER flags DO SELECT #ITEM CASE 1 OUTPUT "1st flag is " CASE 2 OUTPUT "2nd flag is " CASE 3 OUTPUT "3rd flag is " CASE 4 TO 20 OUTPUT "%d(#ITEM)th flag is " ELSE OUTPUT "Flag number %d(#ITEM) is " DONE DO WHEN flags OUTPUT "on.%n" ELSE OUTPUT "off.%n" DONE AGAIN
When "REPEAT OVER" loops nest, each instance has its own indices. The following example shows how the counter #ITEM "belongs" to the outer loop at line 2, the inner loop at line 4.
REPEAT OVER outer ; 1 OUTPUT "outer index = %d(#item)%n" ; 2 REPEAT OVER inner ; 3 OUTPUT "inner index = %d(#item)%n" ; 4 AGAIN ; 5 AGAIN ; 6
"REPEAT OVER" can also be used for processing:
A "REPEAT OVER" can also iterate over a shelf and any of these other objects simultaneously as well, provided that all of the objects being iterated over have the same number of items or values.
Many SGML element structures are recursive; lists in lists and tables in tables for example. In most cases, it is often desirable to set up a rule which will handle the recursion in a sensible way. In particular, the values of counters, switches, and streams must be the same after the recursion as they were before the recursion. In the case of lists, for example, this allows a list to continue numbering where it left off when the sub-list occurred.
It is often sufficient to store a value before processing a nested structure and restore the former value at the end of the substructure. In OmniMark, the values and keys of a shelf can be saved at the start of a rule and automatically restored after the rule is processed.
SAVE shelf-type? shelf-name
The shelf-type is an optional herald indicating the shelf type.
SAVE declarations can be intermixed with LOCAL variable declarations at the beginning of a local scope. The SAVE declaration can only be applied to GLOBAL shelves. It causes a copy to be made of the specified shelf. For the duration of the local scope, every time the GLOBAL shelf is referenced by name, it is the copy that is used. (This includes references from inside nested rules, function calls, and from rules in the other domain.) When the local scope completes, the copy is destroyed, and the saved shelf becomes the accessible one.
Essentially, SAVE guarantees that a global shelf will have the same contents at the end of the local scope that it had at the beginning.
The nested lists described above might be processed by the following ELEMENT rules:
GLOBAL COUNTER list-item ... ELEMENT list SAVE list-item SET list-item TO 1 OUTPUT "%c" ELEMENT @ OUTPUT "%d(listitem). %c%n" INCREMENT listitem
It is an error to save the same shelf from rules in both domains (the output processor and the input processor) at the same time. This prevents SAVEs that are not properly nested with respect to each other. This rule ensures that shelves will always be "unsaved" in the opposite order from which they were saved.
If a shelf of streams is saved then all streams on the shelf must be buffers, none of which are referents, and all streams on the shelf must be closed.
Because SAVE is a declaration, it may not have conditions.
When SAVE is applied to a STREAM shelf, every item on that shelf must either be UNATTACHED or CLOSED and attached to a BUFFER.
If a number of SAVE declarations occur consecutively, the keyword SAVE can be replaced in the second and subsequent declarations by the keyword AND or operator "&". This form is deprecated as it does not add to readability.
SAVE-CLEAR shelf-type? shelf-name
The SAVE-CLEAR action is equivalent to following a SAVE action immediately by a CLEAR action.
The shelf-type is an optional herald indicating the shelf type. Each shelf-type is one of the keywords COUNTER, SWITCH, or STREAM and each shelf-name is the name of an object of the corresponding type.
If the current contents of the saved shelf are not required in the local scope that performed the SAVE, SAVE-CLEAR can be used instead. The only difference is that after SAVE-CLEAR saves the shelf it clears the copy.
Because it changes the size of a shelf, the SAVE-CLEAR action is only allowed on VARIABLE-sized shelves (see Section 7.1, "Shelf Declarations").
As with SAVE declarations, SAVE-CLEAR declarations may not have attached conditions.
Unlike SAVE, when SAVE-CLEAR is applied to a STREAM shelf there is no restriction on what the items may be attached to.
If a number of SAVE-CLEAR declarations occur consecutively, the keyword SAVE-CLEAR can be replaced in the second and subsequent declarations by the keyword AND or operator "&". This form is deprecated as it does not add to readability.
OmniMark requires that any SAVE declaration, SAVE-CLEAR declaration, and LOCAL declarations appear before the rest of a rule. However, a local shelf declaration differs from a SAVE declaration or SAVE-CLEAR declaration in several ways, as discussed in the rest of this section.
The initial value of a shelf declared by a local shelf declaration is one (1), deactivated or empty stream, depending on its type. It has no relation to any global shelf of the same name.
A local shelf may be declared after a global shelf of the same name has been SAVEd or SAVE-CLEARed. Only global shelves can be saved.
SAVE and LOCAL declarations can be used to control various aspects of a shelf related to protecting and changing its values.
For example, in the following sequence of declarations and actions, the SAVE declaration saves the global counter item-number, and the RESET action sets the value of the local counter item-number to 2. If other ELEMENT rules called by the "%c" format item refer to a counter called item-number without declaring their own local copy, they are referring to the global shelf item-number after it was saved by ELEMENT a. And if they change item-number, those changes will be lost when processing of ELEMENT a finishes, and the original shelf is restored.
GLOBAL COUNTER item-number ... ELEMENT a SAVE item-number LOCAL COUNTER item-number SET item-number TO 2 OUTPUT "%c"
USING and SAVE/SAVE-CLEAR can interact in complex ways, especially if they occur in different domains. The following subsections examine a number of cases, and describes the interaction in terms of these cases.
The meanings of USING and SAVE are made a bit more complex by the following example:
CONTEXT-TRANSLATE GLOBAL COUNTER c VARIABLE INITIAL-SIZE 1 ELEMENT #IMPLIED SAVE-CLEAR c SET NEW c TO 25 OUTPUT "%c" FIND "A" USING c @ 1 DO SET c TO 6 PUT #SGML "<x>xxx" INCREMENT c DONE PUT #CONSOLE "c = %d(c)%n"
The question is, when an "A" is found in the input, what is the value of c written to #CONSOLE? Is it 7, 25, or 26? The answer is 25.
This answer is based on the following rule: a shelf or attribute bound by a USING prefix or a MODIFIABLE or READ-ONLY function argument refers to the instantiation of the shelf or attribute that existed at the point that the USING prefix was performed or the function was called, independent of what may have happened within the action following the USING prefix or the local scope body of the called function.
In the example, within the USING, c refers to the shelf as it was at the point of the USING, even though the PUT caused the shelf to be saved and a new instance of the shelf created. The saved shelf is what is incremented (to 7). However, the PUT, outside of the USING, refers to the current instantiation of the shelf c, which is the one created by the SAVE-CLEAR in the ELEMENT rule (the one triggered by the PUT to the #SGML stream).
The following variant of the previous example differs from that in the previous subsection in that the FIND rule causes an ELEMENT rule to be exited rather than entered. It is in error, because the INCREMENT refers to an alias of an instantiation of the shelf c that no longer exists:
CONTEXT-TRANSLATE GLOBAL COUNTER c VARIABLE INITIAL-SIZE 1 ELEMENT #IMPLIED SAVE-CLEAR c SET NEW c TO 25 OUTPUT "%c" FIND "A" USING c @ 1 DO SET c TO 6 PUT #SGML "xxx</x>" INCREMENT c DONE PUT #CONSOLE "c = %d(c)%n"
The error occurs when leaving the ELEMENT rule: the instantiation of the shelf c for which the USING has created an alias ceases to exist (is "popped"). The INCREMENT has nothing to increment.
To simplify catching this error and making a useful report to the user, it is an error to exit a scope that SAVEs or SAVE-CLEARs a shelf for which there is an existing alias. So OmniMark reports the error at the end of the ELEMENT rule rather than at the INCREMENT action. One consequence of this is that even if there were no actions in the body of the USING following the PUT, there would still be an error -- even though there would be no adverse consequences of that error.
Previous versions of OmniMark did not catch this error, which may have resulted in unexpected behaviour.
Next chapter is Chapter 8, "Variable Scopes".
Copyright © OmniMark Technologies Corporation, 1988-1997. All rights reserved.
EUM27, release 2, 1997/04/11.