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

20. Macros

Detailed Table of Contents

Previous chapter is Chapter 19, "Customizing OmniMark Behaviour".

Next chapter is Chapter 21, "Differences from OmniMark V2".

Macros define text substitutions that can be parameterized. They can be used for the following purposes:

This chapter discusses the definition and use of macros.

20.1 Macro Syntax

Macros are defined with the syntax:

Syntax

   MACRO macro-name
       ( (ARG argument-name delimiter) |
          (TOKEN argument-name delimiter?))*
   IS
   macro-body
   MACRO-END

The syntax further breaks down as follows:

A macro keyword is either:

A name token consists of a letter or a high-order ASCII character (ASCII 128 to 255 inclusive) followed by any combination of letters, high-order ASCII characters, digits, underscores ( "_"), hyphens ( "-"), and periods ( ".").

A punctuation character is any character from the following list:

   ( ) { } [ ] ! @ $ % ^ & * - + = | \ ~ : < , > . ? /

Specifically, the backquote character (`) and the octothorpe (#) are not permitted in macro names, macro argument names, or macro argument delimiters.

Punctuation characters do not need to be separated from other macro keywords by white space in the same way that names need to be. Spaces can be used to help readability.

Versions of OmniMark prior to V3 did allow the backquote character. Programs which use the backquote character must either:

20.1.1 Invoking Macros Without Arguments

Macros without arguments are invoked simply by referencing their name.

For example, suppose a program frequently searches for a word pattern:

   WORD-START LETTER+ WORD-END

The programmer might define macro a-word as a shorthand:

   MACRO a-word IS
      (WORD-START LETTER+ WORD-END)
   MACRO-END

After this definition, the name a-word can appear anywhere the original condition could be used. Thus,

   FIND a-word

is equivalent to

   FIND (WORD-START LETTER+ WORD-END)

As with other OmniMark names, case distinctions are ignored in macro names. The macro just defined, for instance, can also be invoked as A-Word or A-WORD, etc.

20.1.1.1 Restrictions On Macro Use

The following restrictions apply to macro use:

20.1.2 Invoking Macros With Token Arguments

In a macro definition, a token argument is indicated by the word TOKEN followed by the argument-name. There may or may not be a delimiter after the argument-name.

When the macro is invoked, the programmer replaces the keyword TOKEN and the argument name with a single OmniMark token: a name, keyword, string, or delimiter. If there is a delimiter after the token argument, then the same delimiter must follow the argument value in the invocation as well.

For instance, the following macro looks for the first enclosing element that has an attribute of a specified name set, and copies the attribute of that name into a stream item of the same name.

   DOWN-TRANSLATE
   ...

   MACRO get-attribute TOKEN attribute-name IS
     REPEAT OVER REVERSED CURRENT ELEMENTS AS elem
        DO WHEN ATTRIBUTE attribute-name OF CURRENT ELEMENT elem
              IS SPECIFIED
           ;
           ; The next statement requires a STREAM variable with the name
           ; that was passed in through the attribute-name argument
           ;
           SET attribute-name TO ATTRIBUTE attribute-name OF elem
        DONE
     AGAIN
   MACRO-END
   ...
   ELEMENT p
      LOCAL STREAM security

      get-attribute security
      DO WHEN security ISNT ATTACHED | security = UL "unclassified"
         OUTPUT "%c"
      ELSE
         SUPPRESS
      DONE

Once the macro is defined, any occurrence of the word get-attribute is considered to invoke the macro. The next token that follows is the macro argument. It is used as the name of the attribute that is being retrieved, and also as the name of the stream item that it is being assigned to.

A better interface for this macro is to allow the user of the macro to explicitly specify the name of the stream into which the value of the attribute will be written:

   DOWN-TRANSLATE
   ...

   MACRO get-attribute TOKEN attribute-name into TOKEN stream-name IS
     REPEAT OVER REVERSED CURRENT ELEMENTS AS elem
        DO WHEN ATTRIBUTE attribute-name OF CURRENT ELEMENT elem
              IS SPECIFIED
           SET stream-name TO ATTRIBUTE attribute-name OF elem
        DONE
     AGAIN
   MACRO-END
   ...

   ELEMENT p
      LOCAL STREAM sec

      get-attribute security into sec
      DO WHEN sec ISNT ATTACHED | sec = UL "unclassified"
         OUTPUT "%c"
      ELSE
         SUPPRESS
      DONE

In this case, the macro keyword "into" serves as a delimiter for the first token argument. Delimiters have no intrinsic meaning after token arguments. They are used strictly for improving the readability of the macro invocation.

20.1.3 Delimited Macro Arguments

A macro definition can also use delimited arguments. A delimited argument is indicated in the macro definition with the keyword ARG. A delimited argument must be followed by a delimiter which is used to indicate the end of the argument.

A delimited argument is an argument value that can contain zero or more OmniMark tokens. Everything up to and not including the delimiter is used as the macro argument.

The following example shows a macro which will match text up to, but not including a specified pattern:

   CROSS-TRANSLATE

   MACRO upto (ARG pat) IS
      ((LOOKAHEAD ! (pat)) ANY)+
   MACRO-END

      FIND UL "begin" upto (UL "end") => body UL "end"
         ...      

The FIND rule will match all of the text between the words "begin" and "end", regardless of whether they occur in upper-case or lower-case.

20.1.3.1 Brackets In Delimited Arguments

OmniMark expects parentheses, brackets, and braces to match within delimited arguments. In other words, tokens inside parentheses are not compared with the argument's closing delimiter. When a "(" is found, all the following tokens through the next ")" are included as part of the argument, even if the closing delimiter occurs before the ")". The character pairs "[" and "]", and "{" and "}", are treated analogously.

This convention allows parenthesized expressions to appear within a macro argument. For example, the upto macro defined above can be used as is with patterns that contain nested parentheses:

   CROSS-TRANSLATE

   MACRO upto (ARG pat) IS
      ((LOOKAHEAD ! (pat)) ANY)+
   MACRO-END

      FIND UL "begin"
              upto (UL ("end" | "finish")) => body
              UL ("end" | "finish")
         ...

The inner parentheses are matched automatically so that the second, and not the first closing parenthesis is deemed to terminate the pat argument.

Notice that the parentheses that are part of the upto macro call are not automatically matched, but are recognized as part of the macro call.

20.1.4 Combining Delimited and Token Arguments

A macro can have both token and delimited arguments. The following example shows a very powerful macro for matching a specified SGML-like tag. The delimited argument gives the pattern that the element name must satisfy, and the token argument specifies the name of a pattern variable where any attributes will be saved.

   CONTEXT-TRANSLATE

   MACRO string-quoted-by TOKEN quote-char IS
      (quote-char [ANY EXCEPT quote-char]* quote-char)
   MACRO-END

   MACRO start-tag ARG tag-pat attributes save-into TOKEN pat-var-name IS
      ("<" (tag-pat)                      ; match specified tag name
       (LOOKAHEAD (WHITE-SPACE | ">"))   ; ensure whole name is matched
       ([ANY EXCEPT "%'%">"]+
          | string-quoted-by "%'"
          | string-quoted-by "%"")* => pat-var-name
       ">")
      
   MACRO-END

   ...

   FIND start-tag "chapter" attributes save-into attribute-values
       ...
       REPEAT SCAN attribute-values
          ...
       AGAIN

   ...
   FIND start-tag ("section" | ("sec" digit+)) => tag
           attributes save-into attribute-values
       ...

Notice the following things about this example:

20.1.5 Inserting Macro Arguments Into Strings

OmniMark also allows tokens passed as macro arguments to be inserted into strings. This is done by using the "%@" format item inside of a macro.

20.1.5.1 The Macro Argument Format Item

Syntax

   % format-modifier? @( argument-name )

The "%@" format item is only recognized within MACRO declarations. As an example of this, here is a macro that dumps the current setting of a switch item:

   CROSS-TRANSLATE
   ...
   MACRO dump-switch TOKEN switch-name IS
      DO
         OUTPUT "%@(switch-name) = "
         DO WHEN switch-name
            OUTPUT "TRUE"
         ELSE
            OUTPUT "FALSE"
         DONE
      DONE
   MACRO-END

   ...
   FIND ...
      LOCAL SWITCH verbose-mode
      ...
      dump-switch verbose-mode

If the argument used in a "%@" format is a string, the quotation marks delimiting the string are removed in the result. If the argument consists of several tokens, they are separated by single spaces.

Thus, in the following example, all of the macro calls are equivalent:

   CROSS-TRANSLATE

   MACRO copyright(ARG company) IS
     OUTPUT "\copyright 1990 by %@(company)"
   MACRO-END

   FIND ...
      copyright(The ABC Company)
      copyright("The ABC Company")
      copyright(The
                ABC
                Company)

The "u" and "l" format modifiers are permitted with the "%@" format item. The effect is to upper-case or lower-case the argument at the point it is copied into a string in the macro body.

20.1.5.2 String Arguments in Nested Macros

Care must be taken when calling macros from other macros. While non-string arguments behave as described above, "%@" format items are evaluated at the point the macro is declared. So for example, the sequence:

   MACRO do-single-comparison (ARG lhs) (ARG comparison) (ARG rhs) IS
     OUTPUT "Test failed: %@(lhs) %@(comparison) %@(rhs)%n"
       UNLESS lhs comparison rhs
   MACRO-END

   MACRO do-comparisons (ARG lhs) (ARG rhs)
                         TOKEN lt TOKEN le TOKEN eq TOKEN ge TOKEN gt
         IS
     do-single-comparison (lhs) (lt LESS-THAN) (rhs)
     do-single-comparison (lhs) (le LESS-EQUAL) (rhs)
     do-single-comparison (lhs) (eq EQUAL) (rhs)
     do-single-comparison (lhs) (ge GREATER-EQUAL) (rhs)
     do-single-comparison (lhs) (gt GREATER-THAN) (rhs)
   MACRO-END

   DOCUMENT-START
     do-comparisons (5) (15 / 3) ISNT IS IS IS ISNT

expands to the following sequence of OmniMark Code:

   DOCUMENT-START
   OUTPUT "Test failed: lhs lt LESS-THAN rhs%n"
                   UNLESS 5 ISNT LESS-THAN 15 / 3
   OUTPUT "Test failed: lhs le LESS-EQUAL rhs%n"
                   UNLESS 5 IS LESS-EQUAL 15 / 3
   OUTPUT "Test failed: lhs eq EQUAL rhs%n"
                   UNLESS 5 IS EQUAL 15 / 3
   OUTPUT "Test failed: lhs ge GREATER-EQUAL rhs%n"
                   UNLESS 5 IS GREATER-EQUAL 15 / 3
   OUTPUT "Test failed: lhs gt GREATER-THAN rhs%n"
                   UNLESS 5 ISNT GREATER-THAN 15 / 3

The expansion is not:

   DOCUMENT-START
   OUTPUT "Test failed: 5 ISNT LESS-THAN 15 / 3%n"
                   UNLESS 5 ISNT LESS-THAN 15 / 3
        ...

The best way to pass string values from outer macros to inner ones (such as from do-comparisons to do-single-comparison) is with the following technique of explicitly converting macro arguments into strings and passing the converted strings as in the following example:

   MACRO do-single-comparison (ARG lhs) TOKEN lhs-str
                   (ARG comparison) TOKEN comparison-str
                   (ARG rhs) TOKEN rhs-str IS
     OUTPUT "Test failed: %@(lhs-str) %@(comparison-str) %@(rhs-str)%n"
       UNLESS lhs comparison rhs
   MACRO-END

   MACRO do-comparisons (ARG lhs) (ARG rhs)
                         TOKEN lt TOKEN le TOKEN eq TOKEN ge TOKEN gt
           IS
     do-single-comparison (lhs) "%@(lhs)"
             (lt LESS-THAN) "%@(lt) LESS-THAN" (rhs) "%@(rhs)"
     do-single-comparison (lhs) "%@(lhs)"
             (le LESS-EQUAL) "%@(le) LESS-EQUAL" (rhs) "%@(rhs)"
     do-single-comparison (lhs) "%@(lhs)"
             (eq EQUAL) "%@(eq) EQUAL" (rhs) "%@(rhs)"
     do-single-comparison (lhs) "%@(lhs)"
             (ge GREATER-EQUAL) "%@(ge) GREATER-EQUAL" (rhs) "%@(rhs)"
     do-single-comparison (lhs) "%@(lhs)"
             (gt GREATER-THAN) "%@(gt) GREATER-THAN" (rhs) "%@(rhs)"
   MACRO-END

   DOCUMENT-START
     do-comparisons (5) (15 / 3) ISNT IS IS IS ISNT

20.2 How Macro Replacement Works

This section discusses macro replacement in more detail.

Programmers should note that running the OmniMark with the -expand command-line option will show the final expansion of all of the macros. This can be a useful debugging technique when problems are encountered inside of macros.

20.2.1 Compound Macro Names

Macro names and argument delimiters can consist of a sequence of macro keywords instead of just one.

In the following example, the macro name consists of the two words "get" and "attribute". Because these are both name tokens they must be separated by some form of white space, although it doesn't matter what kind or how much.

   DOWN-TRANSLATE
   ...

   MACRO get attribute TOKEN attribute-name IS
     REPEAT OVER REVERSED CURRENT ELEMENTS AS elem
        DO WHEN ATTRIBUTE attribute-name OF CURRENT ELEMENT elem
              IS SPECIFIED
           ;
           ; The next statement requires a STREAM variable with the name
           ; that was passed in through the attribute-name argument
           ;
           SET attribute-name TO ATTRIBUTE attribute-name OF elem
        DONE
     AGAIN
   MACRO-END
   ...
   ELEMENT p
      LOCAL STREAM security

      get attribute security
      DO WHEN security ISNT ATTACHED | security = "unclassified"
         OUTPUT "%c"
      ELSE
         SUPPRESS
      DONE

Another more subtle example occurs in the upto macro:

   MACRO upto (ARG pattern) IS
     ((LOOKAHEAD ! (pattern)) ANY)*
   MACRO-END

Here, the left parenthesis is actually part of the macro name.

It is possible for one multi-token macro name to be a substring of another. For example, it may be useful to write an unparenthesized form of the upto macro for cases where the argument is a single token:

   MACRO upto TOKEN pattern IS
     ((LOOKAHEAD ! pattern) ANY)*
   MACRO-END

When such macros are used, OmniMark always selects the macro name that matches the greatest number of tokens. So if the parenthesis is present, the "upto (" macro is used (the one with the delimited argument). If not, the "upto" macro is used (the one with the token argument).

This "longest-match" principle only applies to the macro name and not to any delimiters following any of the arguments. For instance, in the following example:

   CROSS-TRANSLATE

   MACRO write ARG string-expression
         to TOKEN file-name
   IS
     DO
        LOCAL STREAM s
        REOPEN s AS FILE "%@(file-name)"
        PUT s string-expression
        CLOSE s
     DONE
   MACRO-END

   MACRO write (ARG string-expression) IS
     DO
        PUT #MAIN-OUTPUT s string-expression
     DONE
   MACRO-END

   FIND-START
   ...
      write (KEY OF x || "%n") to myfile.dat

The macro invocation matches both of the names "write" and "write (". The second match is taken because it is longer. When OmniMark comes to the closing parenthesis, it has completed the invocation of the second macro. Thus, the program expands to the following:

   FIND-START
   ...
     DO
        PUT #MAIN-OUTPUT s KEY OF x || "%n"
     DONE
     to myfile.dat

The keyword "to" causes a syntax error. Note that if the second macro definition had not existed, the invocation would have invoked the first macro correctly, and no error would have occurred.

The OmniMark requirement that macro names not be redefined applies to the full macro name. For example, if a macro write ( is already defined, defining write does not constitute a redefinition.

20.2.2 Compound Argument Delimiters

To provide a familiar and readable syntax for using macros with multiple arguments, a macro declaration can require a delimiter containing one or more macro keywords after any argument. (A delimited argument must have a delimiter, but for a token argument, it is optional.)

   MACRO set-buffer
     TOKEN stream-name
     (ARG text)
   IS
     OPEN stream-name AS BUFFER
     PUT stream-name text
     CLOSE stream-name
   MACRO-END

so it can be used as

   set-buffer chapno ("%g(chp-num)")

ARG macro arguments must have a trailing delimiter, such as the ")" in this example to indicate where the end of the argument is. These trailing delimiters can consist of more than one token, as in:

   MACRO push TOKEN counter ( ARG value ) ! IS
     NEW  counter
     SET counter TO ( value )
   MACRO-END

When using this macro, the expression that provides the value of the newly pushed counter on the indicated shelf must not only be surrounded by parentheses, but the closing parenthesis must be followed by an exclamation point. A closing parenthesis without a following exclamation point is not considered an error: it simply does not terminate the argument.

20.2.3 Delimiters and Macro Names

Argument delimiters are recognized in preference to macro names. In other words, if there is a macro whose name is the same as the delimiter following an ARG argument, that macro name will never be recognized within that argument. Further, if a delimiter is the same as the first part of a macro name, the delimiter will still be recognized. In this case, the usual "longest-match" rule does not apply.

For example, given:

   DOWN-TRANSLATE
   ...
   MACRO size ARG thing of TOKEN other-thing IS
     NUMBER OF ATTRIBUTE thing OF other-thing
   MACRO-END

   MACRO of grandparent IS
     OF PARENT OF PARENT
   MACRO-END

   ...
   ELEMENT #IMPLIED
       ....
      DO WHEN size widths of grandparent of parent > 10
         ...
      DONE

the macro invocation expands to:

   ...
   ELEMENT #IMPLIED
       ....
      DO WHEN NUMBER OF ATTRIBUTE widths OF grandparent of parent > 10
         ...
      DONE

OmniMark recognizes the first of as the trailing delimiter of the thing argument in preference to a call to the of grandparent macro. Then the next token, "grandparent", is taken as the second argument to the "size" macro. That satisfies the macro invocation, and the remaining two tokens are simply left over.

Note that the resulting code produces a syntax error.

20.2.4 Punctuation As Argument Names

The same punctuation sequences that can be used as macro names can be used as argument names within a MACRO declaration.

For example, the following macro definition is legal:

   MACRO INC500
     TOKEN *
   IS
     INCREMENT * BY 500
   MACRO-END

Where * is the name of the argument. The macro call

   INC500 figure-depth

is replaced by

   INCREMENT figure-depth BY 500

20.2.5 Protecting Macro Keywords

The keyword LITERAL can be used anywhere in a macro definition to allow the keywords ARG, TOKEN, LITERAL and IS to be used as part of a macro name or argument delimiter, and to allow the keyword MACRO-END to appear in a macro definition. Place LITERAL ahead of the keyword that needs protection. For example, given the definition:

   MACRO this LITERAL arg LITERAL is IS
     LITERAL macro-end =
   MACRO-END

The macro call:

   this arg is

is replaced by:

   macro-end =

In the above macro definition, the keywords ARG and IS need protection when used as part of a macro name, and the keyword MACRO-END needs protection when used as part of the macro replacement.

To aid in detecting syntax errors, OmniMark will issue warnings if the keywords MACRO or MACRO-END are used anywhere within a macro declaration without preceding them with the keyword LITERAL. Although there are situations where doing so is not ambiguous, the majority of these cases happen when the programmer forgets an IS keyword to terminate the macro header, or forgets the MACRO-END keyword to terminate the macro. So programmers should always precede the keywords MACRO or MACRO-END with the keyword LITERAL.

If the keyword LITERAL is to be used in a macro as itself, it must itself be protected by entering it as LITERAL literal.

20.2.6 Macros Invoked In Other Macro Declarations

Macros can be used inside the definition of another macro. For example, the macro inc defined as follows:

   MACRO inc(TOKEN c, ARG v) IS
     SET c TO (c + v)
   MACRO-END

can be used in the definition of inc10:

   MACRO inc10(TOKEN c) IS
     inc(c, 10)
   MACRO-END

Macros used in such definitions are evaluated when the MACRO declaration is processed and therefore must themselves have been defined at that time. In other words, a macro must be defined before it can be used, whether that use is the definition of another macro or another OmniMark construct.

In the above example, the declaration defining inc must appear in the program before the declaration defining inc10.

The part of a macro definition between the keywords MACRO and IS is not examined for macro calls. This allows the definition of macro names that include parts of a previously defined macro's names.

20.2.7 Macros Declared In Other Macro Declarations

Macro expansions can themselves contain macro declarations. The programmer must be careful to escape the keywords MACRO and MACRO-END in the inner declaration.

This can be very convenient when many similar macros must be defined. In the following example:

   DOWN-TRANSLATE

   MACRO constant TOKEN constant-name
         LITERAL is ( ARG value )
   IS
      LITERAL MACRO constant-name IS (value) LITERAL MACRO-END
   MACRO-END

   constant left-margin is (10)
   constant right-margin is (10)
   constant indent-amount is (5)

   GLOBAL COUNTER list-level
   ...
   ELEMENT list
      INCREMENT list-level
      OUTPUT "%c"

   ELEMENT item
      LOCAL COUNTER total-indent

      SET total-indent TO left-margin + list-level * indent-amount
      ...
     

The constant macro can be used as a short-form for macro declarations:

   MACRO left-margin IS (10) MACRO-END
   MACRO right-margin IS (10) MACRO-END
   MACRO indent-amount IS (5) MACRO-END

This causes the rest of the program to expand as follows:

   DOWN-TRANSLATE

   GLOBAL COUNTER list-level
   ...
   ELEMENT list
      INCREMENT list-level
      OUTPUT "%c"

   ELEMENT item
      LOCAL COUNTER total-indent

      SET total-indent TO (10) + list-level * (5)
      ...

Macros defined within other macro definitions only become defined when the outer macro is expanded.


20.3 Using Macros

20.3.1 Good Practises

There are a few techniques that should be employed when declaring macros to ensure that macros will be expanded in the way expected.

The same principles can be applied to macro arguments although the programmer can usually tell from looking at the macro itself whether parentheses are needed around an argument:

20.3.2 Application-Specific Macros

Application-specific macros are very useful in defining patterns and parts of patterns used in up-translations and context-translations. The following examples aid in converting from Rich Text Format (RTF) into SGML.

An RTF command has the syntax recognized by the pattern defined by the following macro:

   MACRO rtfcmd IS
         ("\" LETTER+ ("-"? DIGIT+)? "%_"? "%n"*)
   MACRO-END

Trailing newlines are not actually part of the RTF command. They are not significant in RTF. It is useful to pick them up in this macro so that patterns that use this macro do not have to do so themselves.

As this macro illustrates, macros can also provide a powerful documentation tool by explicitly isolating and naming component patterns. The following examples not only make patterns easier to enter, they make it much easier to understand what complex patterns are looking for.

An RTF "item" is an RTF command, two hexadecimal digits preceded by a backslash ("\'"), anything else preceded by backslash ("\") or otherwise a single character:

   MACRO hex-digit IS [DIGIT | "abcdef" | "ABCDEF"] MACRO-END

   MACRO rtfitem IS
         (rtfcmd | "\'" hex-digit hex-digit | "\" ANY | ANY)
   MACRO-END

Note the use of a "helper" macro hex-digit to make repeated parts of the patterns easier to enter.

A particular RTF command can be recognized by using this macro:

   MACRO \ TOKEN command-name IS
         ("\%@(command-name)"
          ("-"? DIGIT+ | LOOKAHEAD ! LETTER)
          "%_"? "%n"*)
   MACRO-END

This macro shows the convenience both of delimiter macro names and token argument types (no trailing delimiter is needed on the parameter). It allows the \par RTF command to be found by:

   FIND \par
      OUTPUT "</paragraf>"

which expands to

   FIND ( "\par" ( "-" ? DIGIT + | LOOKAHEAD ! LETTER )
          "%_" ? "%n" * )
      OUTPUT "</paragraf>"

Individual commands may also be useful as macros. For example, the following macro recognizes an RTF "style" command for a particular style:

   MACRO style TOKEN style-number IS
         ("\s%@(style-number)" (LOOKAHEAD ! DIGIT)
          "%_"? "%n"*)
   MACRO-END

The LOOKAHEAD eliminates spurious matches on longer style commands.

The above macros can be combined in more complex patterns that would be difficult to enter without their help. The following FIND recognizes any RTF \par command, followed by any number of RTF commands that includes a call-out of style number 3:

   FIND \par ((LOOKAHEAD ! style 3) rtfcmd)* style 3 rtfcmd*
      OUTPUT "<chapter>"

Macros that define macros can also be helpful in patterns:

   MACRO define style TOKEN style-name IS
         MACRO style-name IS
               ("\%@(style-name)" (LOOKAHEAD ! DIGIT) "%_"? "%n"*)
               LITERAL MACRO-END
   MACRO-END

The DEFINE STYLE macro can be used as follows:

   DEFINE STYLE s3

   DEFINE STYLE s4

   DEFINE STYLE s5

The first DEFINE STYLE macro call expands to:

   MACRO s3 IS
         ("\s3" (LOOKAHEAD ! DIGIT) "%_"? "%n"*)
   MACRO-END

Once these calls have been made, the macros s3, s4 and s5 are defined. The previous FIND example could be then recoded:

   FIND \par ((LOOKAHEAD ! s3) rtfcmd)* s3 rtfcmd*
      OUTPUT "<chapter>"

20.3.3 A For-Loop Construct

If loops iterating over a counter value are used regularly, the following macro is useful:

   MACRO repeat for TOKEN counter-name
         from ARG min-value to ARG max-value
         doing ARG actions again
      IS
         DO
            LOCAL COUNTER counter-name

            SET counter-name TO min-value
            REPEAT
               EXIT WHEN counter-name > (max-value)
               actions
               INCREMENT counter-name
               AGAIN
            AGAIN
         DONE
   MACRO-END

For example

   REPEAT FOR column-number
              FROM current-column + 1
              TO total-columns
      DOING
         OUTPUT "\emptycolumn{%d(column-number)}"
   AGAIN

would generate an emptycolumn command for each column in a table between current-column and total-columns.

Several things should be noted about the definition of REPEAT FOR:

  1. The keyword DOING is needed to separate the max-value argument from the actions in the loop. An alternative way to do this would have been to require parentheses around the max-value argument, as in:
       MACRO repeat for ARG counter-name
             from ( ARG min-value ) to ( ARG max-value )
             ARG actions again
          IS
    

    In this case the min-value argument is also parenthesized for consistency (an important consideration when defining macros). In this case the above macro call would look like:

       REPEAT FOR column-number
                FROM (current-column + 1)
                TO (total-columns)
             OUTPUT "\emptycolumn{%d(column-number)}"
       AGAIN
    
  2. Because the REPEAT FOR uses AGAIN to terminate its actions, consistently with other REPEAT actions in OmniMark, the keyword AGAIN will be recognized as terminating its actions even if the AGAIN appears in a REPEAT that is one of the actions in the body of the REPEAT FOR.

    For example, in:

       REPEAT FOR index FROM 1 TO 10 DOING
          REPEAT OVER ATTRIBUTE types
             do-something
          AGAIN
       AGAIN
    

    the first AGAIN terminates the REPEAT FOR. When the macro call is expanded the OmniMark compiler will discover a syntax error.

    The solution to this problem is to define another macro as follows:

       MACRO repeat ARG body again IS
             REPEAT body AGAIN
       MACRO-END
    

    This macro simply finds any REPEAT construct that is not a REPEAT FOR, matches the REPEAT with itsAGAIN and expands to the same thing it was entered as. Because macro calls are searched for inside ARG parameters of macros, any REPEAT inside the actions of a REPEAT FOR will be found and protected.

20.3.4 Defining Operations On A Group Of Shelves

It sometimes happens that an OmniMark programmer may need to track the same type of information for different objects. When that information is most conveniently broken down amongst several OmniMark shelves, macros can be used to construct the shelf names from the macro parameters. This can be a powerful technique.

As an example, consider the chore of tracking the number of columns and the column widths for three different parts of a table: the table header, the table body, and the table footer. The following example shows how to use quoted names to make macros which are easier to use:

   ...
   GLOBAL COUNTER table.header.number-of-columns
   GLOBAL COUNTER table.header.column-widths VARIABLE INITIAL-SIZE 0
   GLOBAL COUNTER table.body.number-of-columns
   GLOBAL COUNTER table.body.column-widths VARIABLE INITIAL-SIZE 0
   GLOBAL COUNTER table.footer.number-of-columns
   GLOBAL COUNTER table.footer.column-widths VARIABLE INITIAL-SIZE 0

   MACRO add-column to TOKEN table-part width (ARG column-width) AS
      DO
         INCREMENT 'table.%@(table-part).number-of-columns'
         SET NEW COUNTER 'table.%@(table-part).column-width'
            TO column-width
      DONE
   MACRO-END

   MACRO clear-columns in TOKEN table-part AS
      DO
         SET COUNTER 'table.%@(table-part).number-of-columns' TO 0
         CLEAR COUNTER 'table.%@(table-part).column-width'
      DONE
   MACRO-END

   ...

   ELEMENT tblbody
      clear-columns in body

   ELEMENT colspec WHEN PARENT IS tblbody
      add-column to body width (ATTRIBUTE width)

   ELEMENT tblhead
      clear-columns in head

   ELEMENT colspec WHEN PARENT IS tblhead
      add-column to head width (ATTRIBUTE width)

   ...

Notice that the type heralds are required in front of the quoted names in those actions where they can be specified. (Heralds are only required on actions like NEW which can operate on any kind of shelf, and when a shelf reference occurs in an expression context.)

Next chapter is Chapter 21, "Differences from OmniMark V2".

Copyright © OmniMark Technologies Corporation, 1988-1997. All rights reserved.
EUM27, release 2, 1997/04/11.

Home Copyright Information Website Feedback Site Map Search