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 asis applied to a
markup sink functionor a
string sink functioncall, the function is the consumer and the output scope within
using output asacts as the producer.
using input asis applied to a
markup source functionor a
string source functioncall, the roles are opposite: the function becomes the producer and the scope within
using input asbecomes the consumer.
do markup-parseis applied to a
markup source functioncall, the function is the producer. The scope body and the markup rules it fires act as the consumer coroutine.
do xml-parseis applied to a
string source functioncall, the function is the producer and the scope body and the markup rules it fires are the consumer.
submitis applied to a
string source functioncall,
findrules take the consumer role and the function acts as the producer.
repeat scanare applied to a
string source functioncall, the consumer role is taken by their
setaction can have a
string sinkfunction call on its left-hand side as consumer and a compatible
string sourcefunction call on the right-hand side as producer.
string source functionis passed as a
value string sourceargument to another function, the former is the producer and the latter the consumer. The same can be said for
markup sourcefunctions passed as arguments.
string sink functionis passed as a
value string sinkargument to another function, the former is the consumer and the latter the producer, and an equivalent relationship holds for
markup sinkfunctions 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.
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
... to file "input.txt" with a
string sink function call on the left-hand side of the
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
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 globalshelves, 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
#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.