save

declaration/definition

Syntax
save shelf-name


Purpose

You can use save to create a shelf that is local in lifetime but global in visibility. This means you can act on that shelf outside the lexical scope in which it occurs, as long as that scope is still executing. To accomplish this, you must first declare a global shelf and then, in the appropriate scope, save it. This creates a local copy of the shelf with the global shelf name attached to it:

  global integer row-count
  
  process
     do xml-parse document scan file "myfile.xml"
        output "%c"
     done
     
  element "table"
     save row-count
  
     set row-count to 0
     output "<table border = %"1%">%c</table>"
     output "<p>The table above has %d(row-count) rows."
     
  element "row"
     output "<tr>%c</tr>"
     increment row-count 
     
  element "cell"
     output "<td>%c</td>"

In the code above, row-count is saved in the table element rule. It is then a local of the table element rule, but it has global visibility. We are therefore able to increment it in the row element rule.

The principal advantage of using a saved shelf rather than just using the global directly comes when we encounter recursive structures. Suppose that in our data we have tables within tables. The code above would work for nested tables without modification. When the table rule was called for the nested table, the shelf would be saved again. Since save creates a new local shelf which simply borrows the global name, no data is lost no matter how deep the recursion. As each level of recursion exits, the local shelf at the next level, whose value has not changed, regains the use of the global name, and processing continues.

The local shelf created by a save is initialized to the current value of the global with the same name. If the global is a multi-item shelf, the local is initialized as a shelf of the same size with the same values.

When save is applied to a stream shelf, every item on that shelf must be either unattached or closed and attached to a buffer.

save is a declaration, not an action: It must be placed with declarations at the beginning of a local scope. It cannot have a condition applied to it.

You cannot save a function argument inside a function.

Save and coroutines

In a program that executes sequentially, the lexical scopes that save a shelf always execute in a properly nested way. The state of a saved shelf outside the scope and inside the scope are always distinct and cannot be accessed at the same time. If, however, the program executes multiple coroutines that share access to the same global shelf, what happens when only one of the coroutines saves the shelf?

If the shelf is declared as global, the action save has global effect equally visible in all coroutines. All saves must be properly scoped, just as in the case of a sequentially-executing program. If two coroutines try saving the same global shelf in overlapping scopes, OmniMark throws a program error at the end of the first scope, before the shelf value gets corrupted.

If the shelf is declared as domain-bound global, the effect of save is visible only in the coroutine that saves the shelf. For any other coroutine, the shelf retains the same items it had outside the saving scope. A domain-bound global can be saved in overlapping scopes without any constraints.

The following program demonstrates the difference between a global and a domain-bound global shelf:

  global              string s1 initial { "Hello, World!" }
  domain-bound global string s2 initial { "Hello, World!" }
  
  define string source function
     f ()
  as
     save s1
     save s2
  
     set s1 to "Salut, Monde!"
     set s2 to "Salut, Monde!"
  
     output "Ready.%n"
     output "Within the save scope: " || s1 || " " || s2 || "%n"
  
  process
     using input as f ()
     do
        output #current-input take "Ready.%n"
        output "Outside the save scope: " || s1 || " " || s2 || "%n"
  
        do
           save s1
           save s2
  
           set s1 to "Hola, Mundo!"
           set s2 to "Hola, Mundo!"
           output #current-input take (any ** "%n")
        done
     done

The output of this example is

  Outside the save scope: Salut, Monde! Hello, World!
  Within the save scope: Hola, Mundo! Salut, Monde!

Also note that, if we were to shorten the last output line in the program to output #current-input, the function would end before the using input as scope. The program would then fail with errors.

Save with records and opaque data type shelves

Shelves of record types and opaque data types are references, so when you save a shelf of this type, you are saving the references, not the actual record or opaque instance. This means you have a new local reference to the record or opaque shelf, but still only one global copy of the record or opaque itself. Therefore any changes you make to the record or opaque in the scope of the save is made to the global record or opaque, just as if you had not performed a save. On the other hand, any changes you make to the reference itself are local to the scope of the save and the original reference will be restored when the save scope ends.

The following program illustrates the difference:

  declare record greeting
     field stream word
     
  global greeting meeting
  global greeting parting
  
  process
     set meeting:word to "Hello"
     set parting:word to "Goodbye"
     
     output meeting:word || "%n"
     output parting:word || "%n"
     
     do
        local greeting meeting-french
        save meeting
        save parting
  
        set meeting-french:word to "Bonjour"
        set meeting to meeting-french
        set parting:word to "Au revoir"
        
        output meeting:word || "%n"
        output parting:word || "%n"
     done
     
     output meeting:word || "%n"
     output parting:word || "%n"

The output of this program is as follows:

  Hello
  Goodbye
  Bonjour
  Au revoir
  Hello
  Au revoir

Notice that within the save scope the values of the record referenced by the shelf parting are updated directly within the save scope and are therefore permanent and continue after the end of the scope. In the case of the shelf meeting however, meeting is changed to reference a locally-created record referenced by meeting-french. When the local copy of meeting is destroyed at the end of the save scope, the original shelf is restored and it still references the original record, and thus displays its value in the final output statement.

Related Syntax
Related Concepts