FUNCTION FN'name(var1,...varN)
<optional local MAP statements, with optional STATIC prefix>
<optional XGETARGs>
<nearly any kind of statements>
FN'name = <expression> ! set return value
ENDFUNCTION
Function Name
The function name must begin with FN' (case insensitive). A side effect of this rule is that you may not define an array with a name starting with FN'. If the function name ends in $, then it returns a string value; otherwise it returns a floating point value.
Invoking a Function
To invoke the function, just reference it using array syntax, i.e.:
A = FN'name(x,y,z)
You may also invoke it as a statement rather than an expression, using:
CALL FN'name(x,y,z)
Parameter passing
Any variables in the formal parameter list (var1,...,varn) will be assigned from the calling parameters, converting as necessary. Their types will be determined by the normal rules for unmapped variables. If the call does not specify all the params, any leftover are set to 0 or null. If the call specifies more params than are listed in the function definition, or the parameter types are not suitable for the default string/floating point types, you may also retrieve the arguments via XGETARG (just as you would in an SBX). The number of parameters passed will be available in the system 'dot' variable .ARGCNT, but the types and sizes will not be available. (If you need that kind of flexibility, use an SBX!) The main reason for using XGETARG or XPUTARG would be to pass a structure parameter.
Note that you may want to use STRSIZ # just prior to the function definition to define the default string size, if you plan to pass strings longer than the usual default.
The parameter list may be empty, in which case you use an empty pair of parentheses ().
Local variables
Any MAP statements defined between FUNCTION and ENDFUNCTION are considered local stack variables, which are allocated and initialized for each instance of the function.
You may also prefix MAP statements within the function definition with STATIC (e.g. STATIC MAP1 S'COUNT,F) to make the local variable persistent across instances of the function. Such a variable will only be initialized once, the first time the function is called.
Global variables
By default, you may not access variables defined outside the function. However, you can enable this capability with ++PRAGMA AUTO_EXTERN "<boolean>" (e.g. ++PRAGMA AUTO_EXTERN "TRUE"), in which case any variable referenced within the function that is not locally mapped will be assumed to be a global variable. (This pragma is turned off by default at the start of each function.)
Return expression
The default return value is 0 for numeric functions and "" for string functions. To set another return value, assign it to the variable matching the function name (e.g. FN'name = -1). Note that this is similar in concept to the RETURN(expr) statement in an SBX, except that it doesn't end the function. (You can have many assignments to the return variable; only the last one will matter at the time the function returns.)
Error trapping
By default, Basic errors are internally filtered in such a way that the error appears to the caller to have occurred on the line the called the function. (This allows the stack to be properly unwound before doing an unstructured ON ERROR GOTO out of the function.) You may, however, create a local error trap inside the function, using the regular ON ERROR GOTO statement. The local error trap may resume back to locations within the function. A new variation of the RESUME statement also allows the local error trap to forward errors to the caller:
RESUME ENDFUNCTION {WITH_ERROR {N}}
Without the optional WITH_ERROR clause, this acts like a normal RESUME LABEL, except that a special reserved label ENDFUNCTION is used to indicate the end of the function. A common way to use this feature would be:
FN'name = err(0) ! assign basic error # to function value
RESUME ENDFUNCTION ! return to caller (with error cleared)
This would clear the Basic error condition (so it doesn't trigger the caller's ON ERROR GOTO), but it would still return to the caller the error number so that the caller could take action in a more structured manner (as opposed to via an error trap).
If the WITH_ERROR {N} clause is specified, then the function returns, but the current error (or error N, if specified) is set, causing the caller to get an error on the line that called the function. RESUME WITH_ERROR is equivalent to what happens if you don't establish error trapping within the function at all.
Exiting the function
Besides exiting by hitting the ENDFUNCTION statement naturally, you may also exit from anywhere inside the function by executing the EXITFUNCTION statement.
Example
This example of a function to allocate a file illustrates many of the possible statements:
strsiz 100 ! make sure spec$ gets a reasonable size
Function Fn'Alloc(spec$,bytes)
map1 blocks,f,6
static s'tot'allocations,f
on error goto Trap ! local error trap
blocks = int((bytes + 511) / 512)
allocate spec$,blocks
s'tot'allocations = s'tot'allocations + blocks
Fn'Alloc = 0 ! unnecessary but explicit
exitfunction ! exit function
Trap: ! note this label doesn't conflict with outer TRAP
Fn'Alloc = err(0) ! set return value to error #
resume endfunction ! clear Basic error and return
EndFunction
In the above example, the local static variable s'tot'allocations will get incremented by the number of blocks each time the function is called. (It doesn't serve any purpose in this example, as there is no way to retrieve it, but it might, for example, be used to prevent the program from allocating too many blocks in one session, or for computing statistics.)