Output scopes

OmniMark distinguishes the act of creating output from the act of selecting the output destination, meaning that the stream that receives the output data does not have to be lexically in scope for you to output to it. Instead, a stream or a string sink can be placed in an output execution scope. While a stream is in the current output scope, all output statements will output to it regardless of the lexical scope they occur in.

You can use the keywords using output as to create an output scope and to place a stream variable into that output scope. Like any other kind of scope, output scopes can be nested:

  process
      local stream foo
      open foo as file "foo.txt"
      using output as foo
      do
          output "<rhyme>"
          submit "Mary had a little lamb"
          output "</rhyme>"
      done
      
  find ("Mary" | "lamb") => person
      local stream foo
      reopen foo as file "foo2.txt"
      using output as foo
        output "<person>" || person || "</person>"    

Here a new output scope is established in the find rule, causing the material output in the find rule to be sent to a different destination. This output scope is nested inside the output scope created in the process rule. This scope becomes the current output scope again as soon as the find rule exits.

You can also place a stream into the current output scope, without creating a new output scope, using output-to:

  global stream foo 
  global stream bar
      
  process
      open foo as buffer
      open bar as buffer
      using output as foo
          submit "Mary had a little lamb"
      close foo
      close bar
      output "Foo contains: " || foo
      output "%nBar contains: " || bar
          
  find " a "
      output-to bar

This program outputs the following:

  Foo contains: Mary had
  Bar contains: little lamb

The output-to in the find rule resets the destination of the output scope established by the using output as in the process rule. Thus the rest of the text goes to the new destination.

In general, you should use using output as rather then output-to, but output-to is useful in certain situations, especially when the destination of data is determined by examining the data itself.

Consider a piece of XML that might be used to send files across a network. It encapsulates the name of the file and its contents inside "name" and encapsulates "data" elements inside a "file" element:

  <file>
  <name>myfile.txt</name>
  <data>The content of the file.</data>
  </file>

We can process this with the following program:

  global stream file-data
  process 
      do xml-parse document
          scan file "files.xml"
          suppress
      done
      
  element file
      suppress
      close file-data
      
  element name
      open file-data as file "%c"
      output-to file-data
      
  element data
      output "%c"

Here the entire processing of the XML is done in a single output scope, but every time we find a filename in the input we change the destination of the current output scope. It would be difficult to do the same thing with using output as, because the "data" element is not nested inside the "filename" element, so an output scope established in the "name" element would have expired before the "data" element was processed.

(By the way, the example shows poor XML language design. It would have been better to make the filename an attribute of the "file" element. But you can't always control the format of the data you have to process.)

If you use output-to, note that placing a stream into an output scope does not exempt it from the rules of lexical scoping when it comes to the life span of variables. Local variables are created when their lexical scope enters execution scope and destroyed when their lexical scope leaves execution scope. It is an error to allow a local variable to go out of execution scope while it is still in an output scope. You will avoid this problem if you stick to using using output as to create output scopes.

Prerequisite Concepts
Related Topics