swirl Guide to OmniMark 5   OmniMark home
docs home 
IndexConceptsTasksSyntaxLibrariesOMX VariablesErrors
 
  Related Syntax   Related Concepts  
declaration/definition   define function    

Syntax

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

  or

  define external result-type? function
         func-name arg-list? as
         external-name (in library-name)?


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".

Function return types

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 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)

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)

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", "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 or any OMX component type.
  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 three ways to pass a shelf as an argument to a function:

  1. Pass a reference to the shelf and allow the function to alter the shelf. (modifiable)
  2. Pass a reference to the shelf but do not allow the function to alter the shelf. (read-only)
  3. 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)

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

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 stream prefix,
     remainder stream 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 stream 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 stream 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) 

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.

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 predefine B before you define A. To predefine a function, simply replace as with elsewhere and omit the function body. 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 stream 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 stream 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:

Here is some code you can run, with the above function:

  ; heralds-3.xom
  include "ombcd.xin"
  define integer function calculate-volume
     height value integer height
     width value integer width
     depth value integer depth
  as
     return height * width * depth

  process
     local bcd volume
  ; function call follows:
     set volume to calculate-volume height 12 width 7 depth 4
     output "Volume = " || "d" % volume || "%n"
  ; Output: "Volume = 336"

  define stream 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.

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.

Recursion

You can call OmniMark functions recursively. The following program calculates the factorial of a number using a recursive function:

  define integer function factorial (value integer n) as
      do when n <= 0
          return 1
      else
          return n * factorial(n - 1)
      done

  process
      output "d" % factorial (7)   

External functions

External functions are functions written in other languages that you call from your OmniMark program. External functions must be defined in your program, just like regular functions, except that the definition will point to an external function library that contains the function, instead of containing the body of the function. Generally speaking, the person who wrote the external function will provide an OmniMark include file that contains the necessary function definitions. All you have to do is include the appropriate include file and call the function normally.

The form of an external function definition is as follows:

  define external switch function TCPConnectionHasCharactersToRead
              value TCPConnection this-TCPConnection
            timeout value integer timeout-in-milliseconds optional
  as "TCPConnectionHasCharactersToRead"

External output functions

An external output function establishes a sink for an OmniMark program so that data can be written to a destination that OmniMark cannot address itself. In the example below, the external output function establishes a sink for writing to a TCP/IP connection.

  define external output function TCPConnectionGetOutput
                value TCPConnection this-TCPConnection
        timeout value integer timeout-in-milliseconds optional
       protocol value IOProtocol this-IOProtocol optional
  as "TCPConnectionGetOutput"

To write to an external sink, open a stream with the function call after the as in the open statement, and then establish the stream as current output:

  local stream my-response
  open my-response as TCPConnectionGetOutput my-connection
  using output as my-response
   output "Mary had a little lamb.%n"

External source functions

An external source function establishes a source for an OmniMark program so that data can be read from a source that OmniMark cannot address itself. In the example below, the external source function establishes a source for reading from a TCP/IP connection.

  define external source function TCPConnectionGetSource
                value TCPConnection this-TCPConnection
        timeout value integer timeout-in-milliseconds optional
       protocol value IOProtocol this-IOProtocol optional
  as "TCPConnectionGetSource"

To read data from the external source, use using input as, submit, scan, or matches with the function as the argument:

  submit TCPConnectionGetSource my-connection

    Related Syntax
   elsewhere
   external-function
   optional
   set function-library of external-function
 
Related Concepts
   External functions
   Functions
   Pattern matching functions
   Patterns: dynamically defined
   Rule-based program, basic structure
 
----

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

Generated: August 11, 2000 at 3:07:09 pm
If you have any comments about this section of the documentation, send email to docerrors@omnimark.com

Copyright © OmniMark Technologies Corporation, 1988-2000.