swirl
Guide to OmniMark 8   OmniMark home
docs home 
IndexConceptsTasksSyntaxLibrariesLegacy LibrariesErrors
 
  Related Syntax   Related Concepts  
declaration/definition  

function, define function

 
 

Syntax

define result-type? function function-name
       argument-list? ((as function-body) | elsewhere)



Purpose

You can define a function using define function.

This is a simple example of a function definition:

  define integer function add 
     (value integer x, 
      value integer y)
     as
     return x + y

A function must be defined before it can be called. That is, the definition of the function must occur in the source code before any calls to the function.

Function name

The function name follows the function keyword and the data type, if specified. The function name must be a legal OmniMark name. The name of the function defined above is "add".

Value returning functions

A function may return a value. If so, the data type of the return value is specified between the define and function keywords. The type may be any of the OmniMark data types or any user-defined record type, or any OMX component type.

You can call a function anywhere you can use an expression of the same type. For example, here the integer function "add" (defined above) is used as part of a numeric (integer) expression:

  process
     local integer foo
     set foo to 27 - add (3,4)

string source functions

A function may return a stream of input to the calling environment. An string source function is like a stream returning function in that it returns a stream to the calling environment. However, an string source function differs from a stream returning function in that its return value is composed of all the output generated while the function is executing (unless that output is specifically directed to another destination). The following string source function scans a stream, escapes the markup characters, and returns the escaped stream:

  define string source function escape-text
   value string original
   as 
     repeat scan original
        match [\"&<>"]+ => ordinary
           output ordinary
        match "&"
           output "&amp;"
        match "<"
           output "&lt;"
        match ">"
           output "&gt;"
     again

string source functions run as co-routines.

Action functions

You can also define a function that does not return a value. Such a function is commonly called an "action function" because it is used just like an OmniMark action. To define an action function, simply omit the return type:

  define function output-sum
     (value integer x, 
      value integer y)
     as
     output "d" % x + y

You call an action function just as you would use a regular OmniMark action:

  process
     output-sum(12,87)

While an action function does not return a value, it may modify the value of a shelf that is passed to it. The following function sets all the values of a switch shelf to false.

  define function flip
   (modifiable switch flags)
   as 
     repeat over flags
        set flags to not flags
     again
     
  process
     local switch status initial{true,true,false,true}
     flip (status) 
  
     repeat over status
        do when status = true
           output "TRUE%n"
        else
            output "FALSE%n" 
        done
     again

Function arguments

You can use arguments to pass information to a function.

The declaration of a function argument has four parts:

  1. The method of passing the argument to the function. This can be "modifiable", "read-only", "write-only", "value", or "remainder". The meaning of these keywords is discussed below.
  2. The data type of the argument. This can be any of the OmniMark data types, any record type, or any OMX component type. It can also be of source or output type, though the output type may only be used with external functions.
  3. The name of the argument. This becomes a local variable name within the function. It must be a legal OmniMark name.
  4. The optional "optional" keyword. If an argument is declared optional, it need not be included in the call to the function.

The maximum number of arguments for a function call is 16383.

How arguments are passed to a function

All OmniMark variables are shelves. OmniMark gives you four ways to pass a shelf as an argument to a function:

  1. Pass a reference to the shelf and allow the function to read and to alter the shelf. (modifiable)
  2. Pass a reference to the shelf and allow the function to alter the shelf (but not read the shelf). (write-only)
  3. Pass a reference to the shelf and allow the function to read the shelf (but not to alter the shelf). (read-only)
  4. Make a copy of a single item on the shelf, pass it to the function, but do not let the function alter the copy. (value, remainder)

Modifiable arguments

If an argument is declared modifiable, the following conditions apply:

The following example uses a modifiable argument:

  
  define function decrement-all 
     (modifiable integer the-numbers
     ) as
     repeat over the-numbers
        decrement the-numbers
     again
     
  process
     local integer my-numbers size 5 initial {3,5,7,9,11} 
     decrement-all(my-numbers)
     repeat over my-numbers
        output "%d(my-numbers)%n"
     again
     

Write-only

write-only arguments are chiefly useful when dealing with extended records.

If an argument is declared write-only, the following conditions apply:

The following example uses a write-only argument. The function add-novel has a write-only argument of type "novel". However, the shelf passed to this function by the process rule is of type "publication". This is allowed for a write-only argument because "novel" is an extension of "book" which is an extension of "publication". This means that a record of type "novel" can be written to a shelf of type "publication". The write-only argument type allows such a shelf to be passed to a function. Note also that the function cannot address the fields of objects on the shelf this-novel because it does not know what types they are. (They might be books, or publications instead of novels.) To add an item to the shelf, therefore, the function declares a local record n of type "novel", sets the values of its fields, and then assigns n to the this-novel shelf.

  declare record publication
   field stream name
   field stream publisher
  
  declare record book
   extends publication
   field stream author variable
   field stream year-of-publication
   field stream binding
   field stream ISBN
   
  declare record novel
   extends book
   field stream genre
   field stream locale variable
   
  define function add-novel
   (write-only novel this-novel)
   as
      local novel n
      set n:name to "The Wailing Wind"
      set n:publisher to "HarperCollins"
      set new n:author to "Tony Hillerman"
      set n:year-of-publication to "2002"
      set n:binding to "paperback"
      set n:ISBN to "0061098795"
      set n:genre to "mystery"
      set new n:locale to "Four Corners"
      set new this-novel to n
    
  process
     local publication reading-material variable
     add-novel (reading-material)

Read-only arguments

If an argument is declared read-only, the following conditions apply:

The following sample uses a read-only argument:

  global stream animals size 3 initial {"dog", "cat", "cow"} 
  
  define function output-all 
     (read-only stream things-to-output
     ) as
     repeat over things-to-output
        output things-to-output when things-to-output is closed
     again
     
  process
     output-all (animals)

Value arguments

If an argument is declared value, the following conditions apply:

The following sample uses value arguments:

  global integer a initial {7}
  
  define integer function add 
     (value integer x, 
      value integer y)
     as
     return x + y
     
  process
     output "d" % 
      add(2 + 6, add (a * 12, 9))
      

Remainder arguments

You can also pass a number of value arguments of the same type to a function using a "remainder" argument.

If an argument is declared remainder, the following conditions apply:

The following sample uses a remainder argument:

  define function prefixed-output 
     (value string prefix,
     remainder string items)
     as
     repeat over items
        output prefix || items
     again
     
  process
     prefixed-output ("<item>" , "red%n", "yellow%n", "blue%n")

When you use only a single remainder argument, you must include an ellipsis (three periods) in your function definition as shown below:

  define integer function sum 
     (remainder integer x, ...)
     as
     local integer a initial {0}
     repeat over x
        set a to a + x
     again
     return a
     
  process
     output "d" % sum (2,3,4,6)

Optional arguments

You can declare an argument optional, which simply means that you do not need to specify a value for that argument when you call the function. To declare an argument optional, place the optional keyword after the argument name.

If an argument is declared optional, the function must deal with the possibility that the argument value has not been specified. You can do this one of two ways.

The following code illustrates the use of initial to specify a default value:

  define string function number-to-string
      (value integer the-number,
       value integer the-base optional initial {10}
      ) as
      
      local stream format-string
      set format-string to ("d" % the-base) || "rd"
      return format-string % the-number
      
  process
     local integer my-number initial {23456}
     
     output "Decimal: " || number-to-string (my-number ) 
         || "%nHexadecimal: " || number-to-string (my-number , 16) 
         || "%nOctal: " || number-to-string (my-number , 8) 
         || "%nBinary: " || number-to-string (my-number , 2) 

The following code illustrates the use of is specified to test whether an optional parameter has been passed:

  define string function number-to-string
      (value integer the-number,
       value integer the-base optional 
      ) as
      
      local stream format-string
      do when the-base is specified
          set format-string to ("d" % the-base) || "rd"
      else
          set format-string to "d"
      done
      return format-string % the-number
      
  process
     local integer foo initial {23456}
     
     output "Decimal: " || number-to-string (foo) 
         || "%nHexadecimal: " || number-to-string (foo, 16) 
         || "%nOctal: " || number-to-string (foo, 8) 
         || "%nBinary: " || number-to-string (foo, 2) 

The initial value of optional function arguments can be dynamic values. For example, the following function outputs a "modified" tag and dynamically calculates the default value of the "when" argument:

  define string source function modified-tag
   (value string by,
    value string why,
    value string when optional initial {date "=xY-=xM-=xD =xH:=xm:=xs"})
    as
      output "<modified by='" || by
          || "' why='" || why
          || "' when='" || when
          || "'>%c</modified>"

With the conventional parentheses-and-comma style of function arguments, you are only permitted one optional argument and it must be the last argument. The heralded form, discussed below, gives you greater flexibility.

Source parameters

A source parameter is used to pass an OmniMark source to a function. OmniMark has several built-in sources such as #main-input. The OmniMark keyword file returns a source, as do many external functions. In the following example, an string source function is written to convert any specified source to lowercase. In the example, the source argument is provided by a call to the file function:

  define string source function lower-case
   value string source s
   as
     repeat over s
        match any => c
           output "lg" % c
     again
  
  
  process
   output lower-case file "out.txt"

Function body

You introduce the body of your function with the keyword as. The body of the function consists of a sequence of OmniMark statements, just as in the body of a rule.

Functions defined elsewhere

A function must be defined before it can be called. This is a problem if function A calls function B and function B calls function A. If A is defined before B, then A cannot legally call B. To get around this, you can pre-define B before you define A. To predefine a function, simply replace as with elsewhere and omit the function body. (Any initial values for optional arguments should also be omitted.) Of course, you must still define B fully in its place, and the full definition must match all the elements of the predefinition.

Heralded function arguments

So far, all the examples we have looked at use the conventional parentheses and commas for delimiting function arguments. The parentheses and commas are used in both the function declaration and the function call to separate one argument from another.

If you use the parentheses and commas form, it may not always be clear, when you read the resulting code, what role each of the function arguments plays.

You can make your code clearer if you use heralds instead of parentheses and commas to separate the arguments of your function. A herald is simply a word that you specify as a separator. Notice that the name of a function itself often acts as a herald, so the simplest form of a heralded function is one with a single argument and no parentheses:

  define function output-twice
     value string foo 
     as
     output foo ||* 2
  
  process
      output-twice "Tom"

If you have more than one argument, you can specify heralds as argument separators. Choose heralds that identify the role that the argument is to play. A well-chosen herald will make it easier to remember how to use your function, and easier to see what the function does in your code. A herald can be any token that follows the rules for OmniMark names:

  define function output-repeatedly
             value string foo 
     repeats value integer repetitions
     as
     output foo ||* repetitions
  
  process
      output-repeatedly "Hip hip hooray!%n" repeats 3

While the function name itself is sometimes an adequate herald for the first argument, you will often want to specify a herald for the first argument (it is common and acceptable for the heralds and the argument names to be the same):

  define integer function calculate-volume
     height value integer height
     width value integer width
     depth value integer depth
  as
     return height * width * depth

This is what the function call might look like:

  set volume to calculate-volume  height 12 width 7 depth 4 

An important property of heralded function arguments is that they allow you to have more than one optional argument:

  define string function number-to-string
                 value integer the-number
      using-base value integer the-base optional initial {10}
           width value integer the-width optional 
      as
      
      local stream format-string
      do when the-width is specified
          set format-string to ("d" % the-base) 
                            || "r"
                            || ("d" % the-width)
                            || "fzd"
      else
          set format-string to ("d" % the-base) || "rd"
      done
      return format-string % the-number
      
  process
     local integer foo initial {23456}
     
     output "Decimal: " || number-to-string foo 
         || "%nHexadecimal: " || number-to-string foo using-base 16 width 8 
         || "%nOctal: " || number-to-string foo using-base 8 
         || "%nWide: " || number-to-string foo width 12 

Note that while it is legal to use the same herald for more than one argument of your function, you cannot have two consecutive arguments with the same herald if the first argument is optional, because it would be ambiguous as to which argument was intended by using the herald.

You can also use remainder arguments with heralded function names. In the case of a remainder argument with a herald, you must repeat the herald before each item in the function call.

  define string source function concatenate
   value string a
   and remainder string b
   as
      output a
      repeat over b
         output b
      again
      
  process
     output concatenate "This " and "dog " and "has " and "fleas."

Calling heralded functions

When you call a function that uses conventional parentheses and commas to separate function arguments, you can pass a complex expression as a function argument. In the code below, the expression "6 * 5" is used as a function argument:

  define integer function add 
      (value integer x,
      value integer y)
      as 
      return x + y
      
  process
      output "d" % add (4, 6 * 5)

If you use heralded function arguments, however, you must place complex expressions in parentheses:

  define integer function add 
      value integer x
      to value integer y
      as 
      return x + y
      
  process
      output "d" % add 4 to (6 * 5)

If you omit the parentheses, OmniMark will recognize the first simple expression ("6" in this case) as the whole argument, and will raise a compile-time error when it sees the rest of the expression (unless, by chance, the rest of the expression constitutes valid OmniMark code in that place). This rule avoids the possibility of ambiguity in recognizing argument separators.

    Related Syntax
   elsewhere
   external-function
   optional
   set function-library of external-function
   require
 
Related Concepts
   External functions
   Functions
   Functions: pre-defining
   Module parameterization
   Modules, defining
   Pattern matching functions
   Patterns: dynamically defined
   Records, extended
   Rule-based program, basic structure
   Tree data structures
 
 

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

OmniMark 8.2.0 Documentation Generated: March 13, 2008 at 3:33:48 pm
If you have any comments about this section of the documentation, please use this form.

Copyright © Stilo International plc, 1988-2008.