Referents: debugging

Because of the delayed resolution inherent in the use of referents, debugging a program that uses referents presents some unique challenges. Here's how you can overcome some common problems that can occur when using referents.

The "intermediate stream" problem

If you attempt to write a referent to a stream that does not allow referents, you will get the runtime error "Attempting to write a referent to a stream which does not allow referents." Most of the time the error message will name the stream that does not allow referents. Sometimes, however, it says "For an intermediate stream."

This occurs when you you use the set operator with a source argument that is a stream containing referents. set uses an intermediate stream to stream the value of a set operation from its source to its destination. It is that intermediate stream that must be opened with referents allowed in order for a source containing referents to be assigned to a destination stream. The destination stream must also be opened with referents allowed. You can cause both the intermediate and destination streams to be opened with referents allowed by specifying the with referents-allowed modifier for the set:

  set foo
   with referents allowed
   to bar

The "not fully resolved" problem.

If you attempt to use the value of a stream that has referents-allowed, except by writing it to another referents-allowed stream, you will get the runtime error: "Trying to use value of stream which is not fully resolved."

Cases where this can occur include:

  • attempting to scan a referents-allowed stream
  • attempting to format a referents-allowed stream
  • attempting to pass a referents-allowed stream as a value argument to a function

Notice that the important factor here is whether or not the stream was opened with referents allowed and not whether or not it actually contains referents.

There is no work-around for this. You can't use the value of a stream that contains referents until those referents are resolved. If you are in a place in your code where it is sensible to want to use this value, then you may be able to solve your problem by reducing the size of the referents scope so that it ends before you need to access the value.

In general, you should keep your referent scopes as small as possible, closing them as soon as all referent values are determined. This reduces resource consumption and avoids programming problems.

Notice that while the "referents-allowed" status of a stream is set when you open the stream, the duration of that status is tied to the referents scope in which the stream is opened, and not to the "open" state of the stream. That is:

  • If you close the stream inside the referent scope in which you opened it, it will remain referents-allowed until the referent scope ends.
  • If the stream remains open past the end of the referent scope in which it was opened, it ceases to be referents-allowed when the referent scope ends.

Referents are not scoped to files

Many programmers expect that referents written to a file are resolved when they close the file. This is not the case. In the first place, in OmniMark you can only open and close streams, not files. OmniMark will open and close files and other destinations as needed to read from them or write to them, but this occurs below the surface of your program. So when you say close foo, you are not closing the file attached to foo, you are simply closing the stream named foo.

Secondly, referents are not scoped to the files they are written to. They are scoped to referent scopes. Referents are resolved when their referent scopes end, neither sooner nor later. (After all, it is not necessarily the case that you know all the values of all the referents written to a stream just because you are ready to close that stream.)

When you open a stream with referents allowed and attach it to a file, OmniMark does not open that file. Output sent to the stream goes to a referent buffer. If you close the stream in the same referent scope, OmniMark still does not write data to the file. When the referent scope ends, OmniMark opens the file, writes the content of the stream, with all the resolved referent values, to the file, and closes the file.

While OmniMark handles all of this transparently when the output is to a file, when a stream is attached to an external string sink, you explicitly control the opening of the connection to the data destination. However, you should let OmniMark handle closing the connection. OmniMark will close the connection automatically either when the external opaque variable that controls it goes out of scope, or when all referent scopes with output pending for that connection have been resolved and sent, whichever is later.

If you explicitly close a connection (for instance, using the TCPConnnectionClose function of the TCP library) before referents are resolved for data output to that connection, the output will never be sent and no runtime error will occur.

The main pitfall here is attempting to write a file, using referents, and then reading or writing the contents of that file before referents are resolved. In this case the read attempt will give you the original untouched version of the file, and the write attempt will succeed but will be overwritten when referents are resolved.

Identifying placeholder locations

If you need to see where referent placeholders have been inserted in your output, you can replace the referents-allowed modifier with the referents-displayed. This causes a text representation of the referent placeholder to be inserted into the output when referents are resolved:

  process
     local string foo
     local string bar
      
     set bar with referents-displayed 
      to "Ba, ba, " || referent "animal"
     
     set referent "animal" 
      to " black sheep"
  
     output bar || "%n"

This program will output:

  Ba, ba, <?REF "animal">

If there is a value on the referents shelf at the time the referent is output, it will be shown in the output:

  process
     local string foo
     local string bar
      
     set referent "animal" 
      to " black sheep"
  
     set bar with referents-displayed 
      to "Ba, ba, " || referent "animal"
     
     output bar || "%n"

This program will output:

  Ba, ba, <?REF "animal"=" black sheep">

The #error string has referents-displayed by default.

The "improper nesting" problem.

If you use referents in a program that uses a string source function you may run into the error message "Improper nesting of referent scopes."