OmniMark can execute multiple computations concurrently by interleaving the execution of their actions. The interleaved computations are called coroutines.
Coroutines are an important ingredient of the streaming programming paradigm. Every coroutine in OmniMark either produces a stream or consumes it. There are four types of coroutines:
string sourcecoroutines that produce a stream of textual data,
string sinkcoroutines which consume a stream of textual data,
markup sourcecoroutines, producing a stream of parsed markup, and
markup sinkcoroutines that consume a stream of parsed markup.
Another feature of coroutines in OmniMark is that they always exist in pairs. A
string source producer
coroutine is always paired with a
string sink consumer coroutine, and vice versa. The same relationship
exists between the coroutines of
markup source and
markup sink type; they are always paired
together as well. The data stream produced by one coroutine, which it outputs into its
fed to and consumed by the other coroutine through its
To define a coroutine, use
define function syntax with one of the four types listed above: for
define string sink function. OmniMark will create a new coroutine pair whenever the function
is called. The following example will serve to demonstrate some of the concepts behind coroutines in OmniMark:
define string source function producer () as using output as #main-output & #current-output repeat for integer count output "4fkd" % count || "%n" again process local integer total output "The numbers that add up to more than 100 are:%n" repeat scan producer () match white-space* digit+ => n set total to total + n exit when total > 100 again output "----%n" output "4fkd" % total || "%n"
In this example, the function
producer defines a coroutine producing a
string source. When
the function is called, OmniMark creates a coroutine pair out of
producer and the
loop that acts as the consumer. After that, the following things happen in order:
producerhad any arguments, for example, they would be evaluated and passed to it, and any optional initializers would run as well.
repeat scanbegins to run.
matchclause attempts to match a pattern against
#current-input. Since there is nothing there yet, the consumer suspends and hands control over to
producer. If the consumer terminated without trying to consume its input, the producer would not have run at all.
repeat forloop, outputs the first number (both to the consumer and to
#main-outputwhere we can see it) and suspends.
matchclause consumes the first number, adds it to
total, and loops.
repeat scan, alternate in producing and consuming the numbers …
repeat scanloop is exited and the consumer terminates.
producer: with no consumer to pair up with, its output is not needed any longer. If
producerwas left to run on its own, it would never terminate.
processrule continues to execute and outputs the final
The output of the program is:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 ---- 105
The two coroutines in a pair form a coroutine scope: OmniMark guarantees that they have the same lifetime by synchronizing their initialization and termination.
The use of coroutines has three principal advantages:
For the most part, you do not need to concern yourself with the mechanics of coroutines. OmniMark handles all the details for you. However, there are some important restrictions you need to be aware of:
save-clearoperations are specific to the coroutine in which they occur, if performed on a
globaldeclared with the
using nested-referentscan only be used in one coroutine at a time.
If you write code that depends on the interaction between two coroutines, you may need to be aware of the rules OmniMark uses when switching from one coroutine to another.
string sourcefunction that feeds
do xml-parse, you can test to determine what the current element is in the parsing routine using the
outputaction, except as follows.
#markup-parser, there will be no context switch for the duration of a
repeat overloop or a
usingblock applied to the
data-attributesshelf, or a list-valued attribute shelf.