swirl
Guide to OmniMark 9   OmniMark home
docs home 
IndexConceptsTasksSyntaxLibrariesLegacy LibrariesErrors
 
    Related Topics  

Records

You can use records to create user defined data types. OmniMark provides a number of different simple data types, such as integer, switch, and string. A simple variable, however, can only hold a single value. In many cases you may want a variable that can hold multiple related values. Examples include variables that describe the metadata fields attached to a document or variables that describe a point on a two-dimensional grid.

Here is how you would declare a record type to hold a simple metadata record for a document:

  declare record metadata-label
     field string author
     field string title
     field string publisher
     field string year
     field switch in-print initial {true}

Each field in the record is itself of a particular type and is declared just as a local variable of that type would be declared, substituting the word field for the word local.

However, the record declaration by itself only defines a record type. It does not create an actual record variable. Even though you can declare initial values for the fields, this only specifies what the initial values of those fields will be when an actual record is created.

To create an actual record of type metadata-label, you use a standard local or global declaration:

  local metadata-label doc-info

Once you have a specific variable of type metadata-label, you can read and write the individual fields of the record. To address a particular field of a record, you use the variable name, followed by a colon, followed by the field name:

  set doc-info:author to "Mark Baker"
  set doc-info:title to "Internet Programming with OmniMark"
  set doc-info:publisher to "Kluwer Academic Publishers"
  set doc-info:year to "2000"
  
  set author-name to doc-info:author
  output doc-info:title

You can pass a record to a function:

  define string source function format-metadata
   value metadata-label x
   as
     output "<H1>%n"
         || x:title
         || "</H1><P><B>by "
         || x:author
         || "</B></P>%n<P>"
         || x:publisher
         || "(" || x:year || ") "
     output "<I>out of print</I>" unless x:in-print
     output "</P>"

You can also return a record from a function:

  define metadata-label function get-doc-info
   as
     local metadata-label x
     set x:author to "Mark Baker"
     set x:title to "Internet Programming with OmniMark"
     set x:publisher to "Kluwer Academic Publishers"
     set x:year to "2000"
     return x

Initializing a record

You can initialize a record by writing a function that takes the record values as arguments and returns a record:

  define point function make-point
   ( value integer x,
     value integer y
   ) as
     local point a
     set a:x to x
     set a:y to y
     return a

You can then call this function to initialize a record, either when the record variable is declared, or in the body of a rule or function:

  process
     local point foo
     local point bar initial {make-point (12,32)}
  
     set foo to make-point (14,81)

However, there is another way to initialize a record that may be neater and easier to use in many circumstances. This is to write a conversion function that converts a stream into a record of the appropriate type. Here is a conversion function that converts a specific string format to a point record. Note that the it uses assert to verify the string format before creating the point.

  define point conversion-function
   value string coordinates
   as
     local point a
     assert coordinates matches (digit+ "," digit+ =|)
      message "String "
           || coordinates
           || " is not a valid set of coordinates."
     set a:x to coordinates take digit+
     set a:y to coordinates drop (digit+ ",")
     return a

Once this function is part of your program you can initialize a point like this:

  process
     local point foo initial {"23,86"}
     local point bar
  
     set bar to "123,678"

Using this technique you can initialize objects directly from input data by placing the parsing code in a conversion-function.

Outputting records

You will frequently want to convert the value of a record to text for output. If you usually want the output to take the same format, you can write a conversion function to convert from a record type to a stream. The following conversion function converts a point record to a comma-separated pair of values:

  define string conversion-function
   value point p
   as
      return "d" % p:x
          || ","
          || "d" % p:y

Once this function is part of your program you can output a string representation of a point like this:

  process
     local point foo initial {"23,86"}
  
     output foo

Operations on records

A record may be used to represent mathematical concepts, such as a point, and therefore it may be desirable to perform mathematical operations on records. For example, you may want to add points together. You could write a function specifically to add points:

  define point function add-points
   ( value point a,
     value point b
   ) as
     local point c
     set c:x to a:x + b:x
     set c:y to a:y + b:y
     return c

However, it would be more elegant if you could use the OmniMark + operator to add points. You can do this by writing your point adding function as an overloading of the + operator.

  define overloaded point infix-function
   value point a
   +
   value point b
   as
     local point c
     set c:x to a:x + b:x
     set c:y to a:y + b:y
     return c

Once this function is defined, you can add two points together like this:

  process
     local point foo initial {"23,86"}
     local point bar initial {"74,98"}
     local point baz
  
     set baz to foo + bar

Once you have defined overloaded operators for your records, and have defined conversion functions to translate from strings to your record type, you may also want to extend your overloaded operators to accept the string representation of your record type:

  define overloaded point infix-function
   value (point | stream into point) a
   +
   value point b
   as
     local point c
     set c:x to a:x + b:x
     set c:y to a:y + b:y
     return c
  
  define overloaded point infix-function
   value point a
   +
   value (stream into point) b
   as
     local point c
     set c:x to a:x + b:x
     set c:y to a:y + b:y
     return c

With these versions of the overloaded + operator in place, as well as the conversion functions between points and streams, you can write code like this:

  process
     local point foo initial {"23,86"}
     local point bar
  
     set bar to "123,678"
  
     output foo + bar
         || "%n"
         || foo + "176,34"
         || "%n"
         || "7,9" + bar
         || "%n"
         || point "7,9" + "176,34"

Notice that in the last case, where we add two string representations of points, it is necessary to cast one of the arguments to a point, otherwise the operation is interpreted as the adding of two strings, which is not a supported operation.

References

While you can treat record variables like regular variables for most purposes, there are some important differences. Record variables are not in fact records themselves, the way an integer variable really is an integer. A record variable is actually a reference to a record.

As long as you address the individual fields of a record, this makes no difference. The difference comes when you use a record variable alone. Consider what happens in the following code:

  process
     local integer i initial {2}
     local integer j initial {4}
  
     set j to i
     set i to 5
  
     output "%d(j)"

This program, as you would expect, outputs the number "2", the last value assigned to the variable j.

Now consider this program:

  declare record point
   field integer x
   field integer y
  
  process
     local point alpha
     local point omega
  
     set alpha:x to 2
     set alpha:y to 4
  
     set omega:x to 3
     set omega:y to 9
  
     set omega to alpha
  
     set alpha:x to 12
     set alpha:y to 15
  
     output "d" % omega:x || "|" || "d" % omega:y

You might expect the output of this program to be "2|4" since that is the set of values in the record alpha when we set omega to alpha. However, if you run the program you will see that the actual output is "12|15", the last values assigned to alpha.

Why does this happen? It happens because alpha and omega are both references to records, not the names of the records themselves. When we set set omega to alpha therefore, we made the variable omega reference the same record as the variable alpha. That left us with two references to one record and no references to the other record. When we changed the values of the record referenced by alpha, we were also changing the values of the record referenced by omega, since both alpha and omega reference the same record.

What happened to the orphaned record that used to be referenced by omega? Once there were no references to it, that record was no longer available to the program. Sooner or later, it will be cleaned up and disposed of automatically by OmniMark's garbage collector.

The fact that records are references has important consequences for how you compare two records.

Copying records

But if set copies references to records, how do you copy the values of one record to another? One way is to do it field by field:

     set alpha:x to 2
     set alpha:y to 4
  
     set omega:x to 3
     set omega:y to 9
  
     set omega:x to alpha:x
     set omega:y to alpha:y

However, this is cumbersome. Another approach is to write a function to copy points:

  define function copy-point
   value point a
   to value point b
   as
     set b:x to a:x
     set b:y to a:y
  
  process
     ...
     copy-point alpha to omega

You may notice something odd about this function. Both a and b are passed to the function as value arguments and value arguments cannot be modified. Yet we are changing the values of record b. Why does this work? It works because a and b are both references to records, not records themselves. It is the references that are passed by value and the references are not being changed, only the records that they reference.

    Related Topics
 
 

Top [ INDEX ] [ CONCEPTS ] [ TASKS ] [ SYNTAX ] [ LIBRARIES ] [ LEGACY LIBRARIES ] [ ERRORS ]

OmniMark 9.1.0 Documentation Generated: September 2, 2010 at 1:35:14 pm
If you have any comments about this section of the documentation, please use this form.

Copyright © Stilo International plc, 1988-2010.