Referents

You will sometimes find it necessary to output a value before you have enough information to set that value. For instance, if you are building an HTML table, you may need to output the "<TD>" and "</TD>" tags for a particular cell before you have the value that will be placed in that cell. In this situation you can output a referent that will act as a placeholder for the value. You can supply the value of the referent when you have the data available. OmniMark will handle the necessary buffering transparently and efficiently.

Here is a very simple example of the use of a referent. This program numbers the lines of a text file using the format "<line> of <total lines> ". Since the total number of lines is not known until the whole file has been scanned, the program outputs a referent as a placeholder for the total number of lines.

  global integer line-count
  
  process
     submit file #args[1]
     set referent "total-lines" to "d" % line-count 
     
  find (any-text* "%n") => line
     increment line-count
     output "d" % line-count
         || " of " 
         || referent "total-lines"
         || " "
         || line

The integer line-count is used to count the lines and is incremented each time a line is found. After the file has been processed by the submit, the value of line-count is used to supply a value for the referent "total-lines".

Note that the name of a referent cannot be an empty string.

When you output a referent, OmniMark does two things. First, it outputs a placeholder with the specified referent name into the output stream. Second, it creates an item on a shelf of potential values called the "referents shelf" with the specified referent name as a key. If you output the same referent multiple times, a single entry on the referents shelf is used for all referents of the same name.

At the end of the scope in which a referent exists, OmniMark replaces the placeholders with the corresponding items from the referents shelf.

You can assign many different potential values to a referent before it is resolved. Only the value existing at the time the referents are resolved will be used to replace the placeholder. Thus you could write the above program so that the potential value is updated for every line, instead of once at the end of the program:

  global integer line-count
  
  process
     submit file #args[1]
     
  find (any-text* "%n") => line
     increment line-count
     set referent "total-lines" to "d" % line-count 
     output "d" % line-count
         || " of " 
         || referent "total-lines"
         || " "
         || line

You cannot read the value of a referent (the placeholder) or the stream that contains it until it has been resolved because its value is indeterminate until the placeholder referents are resolved. But you can read and write values on the referents shelf.

You can read a value on the referents shelf just as you would read any other shelf value, by specifying its item number or key. Thus, to read the referents shelf item with the key "total-lines" you could write output referents{"total-lines"}.

You cannot write values to the referents shelf the way you would a normal stream shelf. You have to use the special syntax set referent "total-lines" to <string value>.

This means you could rewrite the line numbering program to eliminate the global integer line-count and use the value on the referent shelf instead:

  process
     set referent "total-lines" to 0
     submit file #args[1]
     
  find (any-text* "%n") => line
     set referent "total-lines" 
      to "d" % (referents{"total-lines"} + 1)
     output referents{"total-lines"}
         || " of " 
         || referent "total-lines"
         || " "
         || line

Note that the referents shelf value must be initialized to "0" before the file is processed, so that the expression (referents{"total-lines"} + 1) does not produce an error trying to read an uninitialized value.

Note also that it is possible to create an item on the referents shelf before outputting a referent (placeholder) of the same name.

In the output statement, note that referents{"total-lines"} is used to output the current value of referents {"total-lines"} while, referent "total-lines" is used to output the referent (placeholder) named "total-lines" that will eventually be resolved to the final value of referents{"total-lines"}.

Controlling the scope of referent resolution

Referents exist in scopes. A referents shelf and all the associated referent placeholders are the property of a referent scope. Referents in any given referent scope are resolved when that scope ends. The default referent scope is the program as a whole, so referents in the default scope are resolved when the program ends.

You can establish a referents scope with the expression using nested-referents. Like all using statements, using nested-referents is a prefix that establishes a referents scope for the statement or block that it prefixes. Thus, to establish a referents scope (sometimes called a "nested-referents" scope) for a submit action you would use the code:

  using nested-referents
   submit file args[1]

You can nest referent scopes as deeply as you need to. The referent names in each referent scope are independent of each other, so you can use the same name in different referent scopes. This can be particularly useful in dealing with nested structures. Suppose you are processing an XML table that may contain other tables. You can write your table element rule like this:

  element "table"
     using nested-referents
      output "<TABLE BORDER="1">%c</TABLE>"

This rule will fire for the outer table, establishing a referents scope for the processing of that table. If a table occurs within that table, the rule will fire again, creating another referents scope for the processing of the inner table. Any referents used in processing the content of the outer table, will be independent of those used to process the content of inner tables.

Allowing and disallowing the use of referents

Because any stream that contains referents must be buffered by OmniMark until the referents scope ends, you must explicitly state your intention to use referents when you open the stream. The only exception is for the default output stream (#main-output) which is allowed to use referents by default if the program contains any mention of referents.

Streaming data to items on the referents shelf

An item on the referents shelf is a data destination just like any other. As such you can stream data to it. As with any other data destination, you stream data to an item on the referents shelf by opening a stream with the item as the attachment. Not surprisingly, the attachment type for the referents shelf is referent. The syntax for opening a stream named "foo" to an item on the referents shelf with the key "bar" is:

  local string foo
  open foo as referent "bar"

Handling referents without values

When referents are resolved, there must be a value on the referents shelf for every referent (placeholder) in the referents scope. Items on the referents shelf that have not been given values are "unattached", and you can loop over the referents shelf looking for unattached referents and supplying an appropriate value:

  repeat over referents
     set this referent 
      to "<!-- missing referent -->"
      when this referent isnt attached
  again

Note that you must refer to the current item as this referent, not referents.

You can also specify a default value that will be used for any referent to which a value has not been assigned. The default value is specified for the stream that the referents are written to, not to the referents shelf. Specifying a default value for referents does not cause items on the referents shelf to have that value by default. Instead, the default values are used on a stream-by-stream basis to replace referents whose values are unattached. This means that you can write the same referent to two different streams with different referent defaults and get different results:

  process
     local string foo
     local string bar
     
     using nested-referents
      do
        open foo
         with referents-allowed defaulting {"X"}
         as buffer 
        open bar
         with referents-allowed defaulting {"O"}
         as buffer 
        using output as foo & bar
         output "{" || referent "baz" || "}"
        close foo & bar
      done
      output foo || bar || "%n"
This program will output:
  {X}{O}

The referent "baz" is written to both streams but never has a value assigned. On referent resolution, the referent is replaced by each stream's referent default value.

You can specify the referent default value for #main-output with the following declaration:

  declare #main-input has referents-allowed defaulting {"X"}

Finally, if you supply two strings in the defaulting statement, OmniMark will replace referents without values with the first string, followed by the referent name, followed by the second string:

  process
     local stream foo
     local stream bar
     
     using nested-referents
      do
        set foo
         with referents-allowed 
         defaulting {'<!-- missing referent "', '" -->'}
         to "Mary had a "
         || referent "size" 
         || " lamb%n" 
      done
      output foo  

This program will output:

  Mary had a <!-- missing referent "size" --> lamb

Handling streams that have referents

Any stream that has had referents written to it (or one that has simply been opened with referents allowed) has an indeterminate value. Therefore, you cannot read its value. However, you can write its contents, referents and all, to another stream that has referents allowed:

  process
     local stream foo
     local stream bar
     
     open foo with referents-allowed as buffer 
     open bar with referents-allowed as buffer
  
     put foo referent "color" || " sheep." 
     close foo
     
     put bar "Ba, ba, " || foo
     close bar
     
     set referent "color" to "black"
     output  bar || "%n"

In this program the referent "color" is written to the stream foo. The stream foo is then written to the stream bar, including the referent "color". The effect on bar is the same as if the referent had been written to it directly.

You can also use a set action to assign the contents of a stream containing referents to another stream. Because the set statement opens the destination stream automatically, without giving you the chance to open it with referents allowed, you must specify that the set operation itself has referents allowed:

  process
     local string foo
     local string bar
     
     set foo with referents-allowed 
      to referent "color" || " sheep."
      
     set bar with referents-allowed 
      to "Ba, ba, " || foo
     
     set referent "color" to "black"
     output bar || "%n"

Many of the problems programmers have with referents is forgetting to allow referents in set operations when the source stream contains referents.

Referents that have referents

Items on the referents shelf can themselves contain referents. That is, you can write a referent (placeholder) to an item on the referents shelf:

  process
     local string foo
     local string bar
     
     set bar with referents-allowed 
      to "Ba, ba, " || referent "animal"
     
     set referent "animal" 
      with referents-allowed
      to referent "color"
      || " sheep"
     set referent "color" to "black"
     output bar || "%n"  

Note that both the first and second set statements must be done with referents-allowed as their sources contain referents.

Silent referents

Each time you output a referent (using a new referent name) an item is created on the referents shelf. If you have a large number of referents most of which will be set to default values, rather than receiving particular values, you can reduce your program's resource consumption by using silent referents. Outputting a silent referent does not cause an item to be created on the referents shelf. Items are created on the referents shelf only when a value is assigned to the referent. To output a silent referent, simply use silent-referent instead of referent:

  process
     local string foo
     local string bar
     
     set bar with referents-allowed 
      defaulting {""}
      to "Ba, ba, " || silent-referent "animal"
     
     set referent "animal" 
      with referents-allowed
      defaulting {""}
      to silent-referent "color"
      || " sheep"
     set referent "color" to "black"
     output bar || "%n"

Notice that only the statements that output referent placeholders use the "silent-" form. Operations on the referents shelf still use the normal form.

Silent referents must still be given values when referents are resolved. However, you cannot find missing silent referents by repeating over the referents shelf, since they are not on it. Therefore, you should always supply default referent values for any stream to which to write silent-referents. (This is not necessary in the example above because all the referents do receive values. But there is no point in using silent referents if you know all the referents will receive values. In practice, therefore, you should always supply defaults when using silent-referents.)

Talking about referents

In most discussions of referents the term "referent" is used interchangeably to refer to the placeholder written to a stream and to the corresponding item on the referent's shelf. You need to recognize that the expression "to output referent 'foo'" means, "to output a placeholder with the name 'foo'" while "to set the value of referent 'foo'" means "set the value of the item on the referents shelf with the key 'foo'".