![]() |
|
||||
![]() ![]() ![]() ![]() ![]() ![]() ![]() |
|||||
|
|
|||||
| Related Syntax | Related Concepts | ||||
| declaration/definition |
function, define function |
||||
Syntax
define 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 typeX.
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)
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:
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.
value, you can specify a default value for the argument.
is specified.
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 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 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.
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 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; however, optional cannot be used as
a herald, as this would lead to an ambiguity.
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 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 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."
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 |
Related Concepts |
Copyright © Stilo International plc, 1988-2010.