|
|||||
|
|||||
Prerequisite Concepts | Related Syntax | ||||
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".
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"}.
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.
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.
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 stream foo open foo as referent "bar"
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 stream foo local stream 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
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 stream foo local stream 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.
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 stream foo local stream 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.
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 stream foo local stream 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.)
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'".
Prerequisite Concepts Scopes |
Related Syntax referents set referent silent-referent using nested-referents using referents |