A coroutine scope consists of two coroutines, a producer and a consumer, running
synchronously. Coroutines are always created in producer-consumer pairs. The producer coroutine executes an
output scope. It outputs data into its #current-output
, which then feeds it to
the consumer through its #current-input
. The consumer executes an input scope.
OmniMark alternates execution between the producer and consumer depending on the data flow between them.
The following actions can be used to create a new coroutine scope:
using output as
is applied to a markup sink function
or a string sink
function
call, the function is the consumer and the output scope within using output as
acts as the
producer.
using input as
is applied to a markup source function
or a string source
function
call, the roles are opposite: the function becomes the producer and the scope within using
input as
becomes the consumer.
do markup-parse
is applied to a markup source function
call, the function is the
producer. The scope body and the markup rules it fires act as the consumer coroutine.
do sgml-parse
or do xml-parse
is applied to a string source function
call, the
function is the producer and the scope body and the markup rules it fires are the consumer.
submit
is applied to a string source function
call, find
rules take the consumer
role and the function acts as the producer.
do scan
or repeat scan
are applied to a string source function
call, the consumer
role is taken by their match
clauses.
set
action can have a markup sink
or string sink
function call on its left-hand
side as consumer and a compatible markup source
or string source
function call on the
right-hand side as producer.
string source function
is passed as a value string source
argument to another
function, the former is the producer and the latter the consumer. The same can be said for markup
source
functions passed as arguments.
string sink function
is passed as a value string sink
argument to another function,
the former is the consumer and the latter the producer, and an equivalent relationship holds for
markup sink
functions and arguments.
In all the cases listed above, consumer and producer are both defined by the user. There are simpler cases,
however, when OmniMark pairs a user-defined producer with a primitive consumer built into OmniMark, or vice versa.
For example, set file "output.txt" to
can be applied to a string source
function call. The
file
consumer in this example is built into the language. The opposite example would be applying set
... to file "input.txt"
with a string sink
function call on the left-hand side of the set
action.
OmniMark guarantees that the two coroutines in a producer-consumer pair always start and terminate together. If
the consumer coroutine terminates first, the producer will be forcibly terminated and only its always
clauses will run. If the producer terminates before the consumer, the consumer receives value-end
in its
#current-input
and runs until it terminates.
In OmniMark, every coroutine function call is scoped in this way. The coroutines always complete their execution before the caller proceeds with its own. This greatly simplifies reasoning about OmniMark programs:
domain-bound global
shelves, and
parsing state from their caller.
As any other scope in OmniMark, coroutine scopes can nest. A producer coroutine, for
example, can invoke another producer-consumer coroutine pair and delegate its work to them. Furthermore, it can
pass its #current-output
, a reference to its consumer sibling, to either or both of its child coroutines.
A chain of coroutines can be built this way.