The interface appears to work, now let's see if we can implement it.
The first thing to note is that unresolved referents are by definition something out of band. This means that our referent buffer has to keep track of them separately from the fixed data, and also that we cannot use the same mechanism for emitting an unresolved referent that we use for emitting regular data.
The standard solution for emitting out-of-band information are signals. We'll declare a catch
with a referent argument. Then we can use signal throw
to emit a referent. The reason for having the parent type markup-future
for markup-referent-buffer
will be explained later.
declare record fragment declare record data-fragment extends fragment field markup-buffer data export record markup-future extends fragment export record markup-referent-buffer extends markup-future field string name field fragment contents variable export global markup-referent-buffer referents variable export markup-referent-buffer function referent value string name as using new? referents{name} as ref do set ref:name to name return ref done export catch markup-referent value markup-future ref export markup source conversion-function value markup-referent-buffer b as signal throw markup-referent b
The next piece of functionality to tackle is the ability to set the referent value. The following function accomplishes the task. Note that it automatically accepts and stores referents and not only plain data. There is no need for any equivalent of the with referents-allowed
modifier when setting a referent value.
export markup sink conversion-function value markup-referent-buffer b as repeat local data-fragment frag set new b:contents to frag using output as frag:data output #current-input exit catch markup-referent ref set new b:contents to ref again
The resolve-referents
function starts by reproducing its input until it encounters the first markup-referent
signal. After that, it has to start buffering the signalled referents alongside the regular data until the end of the input. Since the markup-referent-buffer
type already knows how to accomplish this task we'll delegate the bufferring to an anonymous instance of the type — there's no reason to duplicate the code.
export markup sink function resolve-referents into value markup sink destination as using output as destination repeat output #current-input take any* exit catch #markup-point event signal rethrow catch #markup-start event signal rethrow catch #markup-end event signal rethrow catch markup-referent event do local markup-referent-buffer ref using output as ref do signal rethrow output #current-input done output resolved ref done again
Once the end of input is reached, the dynamic function resolved
performs the actual work of emitting the current value of the referent. Since referents can contain other referents, this function must be recursive, but its implementation is trivial.
export dynamic markup source function resolved value fragment f as not-reached message "Unresolvable referent fragment." define overriding markup source function resolved value data-fragment f as output f:data export overriding markup source function resolved value markup-referent-buffer b as assert number of b:contents > 0 message "Referent '" || b:name || "' has no defined value." repeat over b:contents as fragment output resolved fragment again