HOME | COMPANY | SOFTWARE | DOCUMENTATION | EDUCATION & TRAINING | SALES & SERVICE

    "The Official Guide to Programming with OmniMark"

Site Map | Search:   
OmniMark Magazine Developer's Forum   

  International Edition   

OmniMark® Programmer's Guide Version 3

7. Shelves

Detailed Table of Contents

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.

7.1 Shelf Declarations

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".

7.1.1 Declaring Global Shelves

Syntax

   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.

7.1.2 Declaring Local Shelves

Syntax

   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.

7.1.3 Shelf Sizes

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:

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:

7.1.4 Initializing Shelves

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.


7.2 Indexing Items on a Shelf

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:

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.

7.2.1 The Currently Selected Shelf Item

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:

7.2.2 Positional Item Selection

The "@" operator selects an item based on its position in the shelf.

Syntax

   (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".

7.2.3 Key-Based Item Selection

The "^" (cirumflex) operator is used to select the item that has the specified key.

Syntax

   (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").

7.2.3.1 Multi-Dimensional Arrays

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.

7.2.4 Lastmost Item Selection

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.


7.3 Actions On Shelves

7.3.1 Adding New Items To Shelves

7.3.1.1 Inserting a New Item With Default Initialization

Syntax

   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.

7.3.1.2 Inserting a New Item With a Specified Initialization

Syntax

   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:

7.3.1.3 Inserting Into Shelves

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.

7.3.2 Removing Items From a Shelf

Items can either be removed from a shelf one at a time, or all at once.

7.3.2.1 Removing All Items From a Shelf

Syntax

   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.

7.3.2.2 Removing a Single Item

Syntax

   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

7.3.3 Copying Shelf Contents

Syntax

   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.

7.3.3.1 Moving Shelf Contents To Another Shelf

Syntax

   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.

7.3.4 Actions on Shelf Item Keys

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.

7.3.4.1 Changing or Setting the Key of a Shelf Item

Syntax

   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.

7.3.4.2 Removing the Key of a Shelf Item

Syntax

   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.


7.4 Operators on Shelves

7.4.1 Determining the Size of a Shelf

Syntax

   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.

7.4.2 Returning the Position of an Item on a Shelf

Syntax

   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").

7.4.3 Testing a Shelf For a Given Key Value

Syntax

   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".

7.4.4 Testing a Given Item For Any Key

Syntax

   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.

7.4.5 Finding the Key of a Specific Item

Syntax

   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.


7.5 Specifying a New Default Selected Item

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:

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.

7.5.1 Order of Evaluation of USING and Conditions

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

7.6 Repeating Actions on Each Item of a Shelf

Syntax

   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:

7.6.1 Adding Items Inside Repeat Over Loops

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.

7.6.2 Loop Indices

7.6.2.1 Testing for the First or Last Iteration of a Loop

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.

7.6.2.2 Retrieving The Current Iteration Count

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

7.6.3 Other Forms of "REPEAT OVER"

"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.


7.7 Saving Shelf Contents

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.

7.7.1 The Save Action

Syntax

   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.

7.7.2 The Save-Clear Declaration

Syntax

   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.

7.7.3 Mixing Save Actions and Local Declarations

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"

7.7.4 The Interaction of USING Prefixes and SAVE Declarations

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.

7.7.4.1 Case 1 -- Binding Shelf Instances with USING

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).

7.7.4.2 Case 2 -- Aliasing Errors

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.

Home Copyright Information Website Feedback Site Map Search