declaration/definition
define result-class? result-type? function function-name argument-list? ((as function-body) | elsewhere)
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.
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
.
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 opaque data 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 i set i to 27 - add (3,4)
A function may return a reference to a shelf.
If so, the data type of the shelf reference must be specified, but unlike the simple expression-class functions discussed previously, a shelf class must also be specified. The allowed classes are read-only
, modifiable
, and write-only
. The allowed data types are the basic data types, the stream
data type, the markup data types, an opaque type, or a record type.
You can call a shelf-class function anywhere you can use a shelf reference of the same type. For example, here a read-only
integer
shelf-class function some-primes
is used in a repeat over
loop:
define read-only integer function some-primes () as return { 2, 3, 5, 7, 11, 13, 17 } process repeat over some-primes () as p output "d" % p || "%n" again
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 "&" match "<" output "<" match ">" output ">" again
string source
functions run as co-routines.
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 !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
You can use arguments to pass information to a function.
The declaration of a function argument has four parts:
modifiable
, read-only
, write-only
, value
, or remainder
. The meaning of these keywords is discussed below.
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.
All OmniMark variables are shelves. OmniMark gives you four ways to pass a shelf as an argument to a function:
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
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 string publication-name field string publisher declare record book extends publication field string author variable field string year-of-publication field string binding field string ISBN declare record novel extends book field string genre field string locale variable define function add-novel (write-only novel this-novel) as local novel n set n:publication-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)
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)
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))
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)
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.
is specified
.
The following code illustrates the use of initial
to specify a default on a value
argument:
define string function number-to-string (value integer the-number, value integer the-base optional initial { 10 }) as local string 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 string 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 i initial { 23456 } output "Decimal: " || number-to-string (i) || "%nHexadecimal: " || number-to-string (i, 16) || "%nOctal: " || number-to-string (i, 8) || "%nBinary: " || number-to-string (i, 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, it must be the last argument, and cannot be the only argument. The heralded form, discussed below, gives you greater flexibility.
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"
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.
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.
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 s as output s ||* 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; however, optional
cannot be used as
a herald, as this would lead to an ambiguity.
define function output-repeatedly value string s repeats value integer repetitions as output s ||* 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 string 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 i initial { 23456 } output "Decimal: " || number-to-string i || "%nHexadecimal: " || number-to-string i using-base 16 width 8 || "%nOctal: " || number-to-string i using-base 8 || "%nWide: " || number-to-string i 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."
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.