Functions are described in the Expressions section of the documentation. You can make/write/define your own functions. The implementation of your function (the Function Implementation) consists of invocations of commands, and these command invocations may include function invocations within the parameters of the commands. Thus a Function Implementation consists of invocations of commands and other functions.
Remember that the entire source code consists of a list of invocations of commands (command invocations), with each command invocation having 0, 1, or more parameters. So everything is done using commands. You even use a command to define a new command, and a command to define a new function.
Thus to define your new Function Implementation, use the command named "Function" (shortcut "func"), followed by the commands you want to be invoked when your function is invoked, and end with the command named "EndFunction" (shortcut "efunc"). In the following example, a new Function Implementation is defined with the name "Test", with a return type of "TUInt", and consisting of an invocation of the command named "Return".
Function Test, TUInt;
Return 3;
EndFunction;
Using shortcut names, it looks like this:
func Test, uint;
return 3;
efunc;
The "EndFunction" ("efunc") command can optionally have 1 parameter, the name of the new Function Implementation ending there (example follows). This must be exactly the same name as was provided in the corresponding "Function" command (the compiler will report an error if the name at the beginning does not match the name at the end). This optional feature can help catch mistakes, and make the source code easier to read. It reminds the reader WHICH Function Implementation is being ended there, and that can be especially helpful when the "Function" command is a far distance away from the "EndFunction". It may also assist in nesting situations.
Function Test, TUInt;
Return 3;
EndFunction Test;
Function Implementations (and Command Implementations) must actually be placed in a module. Modules help you organize your program. A Function Implementation cannot be defined outside of a module. In the following example, 2 Function Implementations named "Test" and "AnotherFunc" are defined inside the module "MyProgram".
Module MyProgram;
Function Test, TUInt;
Return 3;
EndFunction Test;
Function AnotherFunc, TUInt;
Return 7;
EndFunction AnotherFunc;
EndModule MyProgram;
Like "EndFunction", the "EndModule" command can optionally include the name of the module being ended there, for the same reasons. The "Module" and "EndModule" commands also have shortcut names "module" and "emodule".
Every function returns a value. This is the result or "answer" of the function/calculation. For example a function named "GetThree" might return the number 3. The return value is produced by the function and passed in an outgoing direction to the invoker of the function, and then the invoker can use the value to help it perform its task.
The type of the return value must be specified in the second parameter of the command named "Function". Return types are the same as variable and attribute types. For information about types, see the Variable/Attribute Types section of the documentation.
A function always has exactly 1 result / return value (no more, no less). If you want multiple return values, then you are required to make a command, not a function. Although a function always has only 1 return value and type, it is possible for that single return value to consist of multiple components, if the type of the return value is a type that has multiple components.
The command named "Return" ("return") causes the function to terminate its execution, and return to the invoker (the invoker is the command or function that invoked this function). The "Return" command has 1 parameter that must be supplied when used in a Function Implementation. This parameter is a value to use as the result / return value of the function. The value is copied in an outgoing direction to the invoker.
The "Return" command may be invoked multiple times in different places within the Function Implementation, with same or different values. The function may follow different paths of execution depending on the situation and terminate with different "Return" commands in different places. After the "Return" command is executed, no more commands in the Function Implementation are executed.
The "Return" command must be used at least once. If the Function Implementation does not use the "Return" command, then the compiler produces an error message.
To make a Function Implementation really useful, you need to be able to pass information to it (in an incoming direction). This is achieved using parameters. Parameters are defined using the command named "Parameter" (shortcut "prm"). For example:
Function MyAdd, TUInt;
Parameter inA, TUInt;
Parameter inB, TUInt;
Return inA + inB;
EndFunction;
Using shortcut names, it looks like this:
func MyAdd, TUInt;
prm inA, TUInt;
prm inB, TUInt;
return inA + inB;
efunc;
The above example defines a function named "MyAdd" with 2 parameters. The first parameter is named "inA" and the second parameter is named "inB". Both parameters are of type "TUInt". The function returns as its result the sum/addition of the numbers supplied for the "inA" and "inB" parameters when the function is invoked.
"Parameter" commands must be the first commands in the Function Implementation. At the end of each "Parameter" line, you may want to place a comment containing a short description of the parameter. For example:
func MyAdd, TUInt;
prm inA, TUInt; {Value to be added with inB.}
prm inB, TUInt; {Value to be added with inA.}
return inA + inB;
efunc;
The "Parameter" command is defined the same as in Command Implementations. For more information, see the Command Implementations section of the documentation.
Note that unlike Command Implementations, Function Implementations do not allow output or input/output parameters (other than the return value). All parameters in a Function Implementation are input parameters.
A Function Implementation is invoked in the same manner as intrinsic (built-in) functions. A function invocation in an expression begins with the name of the function, immediately followed by square brackets containing supplied values for the parameters separated by commas. See the Expressions section of the documentation for more information on invoking intrinsic functions.
The following example defines a module named "MyProgram" containing a function named "MyAdd" and a command named "Test". The command "Test" invokes the intrinsic command "Set" used to set the value of a variable. The first parameter of the "Set" command is the variable to set. The second parameter of the "Set" command is the value to set the variable to. In this example, an expression is supplied for the second parameter of "Set". The expression includes an invocation of the "MyAdd" function in "MyProgram".
Module MyProgram;
Function MyAdd, TUInt;
Parameter inA, TUInt;
Parameter inB, TUInt;
Return inA + inB;
EndFunction;
Command Test;
Variable a, TUInt, 46;
Variable b, TUInt, 10;
Variable x, TUInt;
Set x, 2 * MyProgram.MyAdd[a, b] + 1;
{x now contains 113.}
EndCommand;
EndModule MyProgram;
The full name of the Function Implementation must be specified when invoking it. The full name includes the module name with a dot (".") to separate the module name and function name (module name first). Requiring the full name avoids conflicts with intrinsic functions. If you see a function invocation with a dot (".") in the name, then you know it is an invocation of a Function Implementation. Or if there is no dot in the name, then you know it is an intrinsic function.
Imagine you have a command named "IsValidRange" defined like the following, with 2 input parameters and 1 output boolean parameter.
Command IsValidRange;
Parameter inStart, TUInt;
Parameter inEnd, TUInt;
Parameter outValid, Out TBoolean;
{...}
Set outValid, xxxxx;
EndCommand;
Command Test;
{...}
Variable isValid, TBoolean;
MyProgram.IsValidRange startOfRange, endOfRange, isValid;
If isValid;
{...}
EndIf;
EndCommand;
You could change it to a function with 2 parameters like this:
Function IsValidRange, TBoolean;
Parameter inStart, TUInt;
Parameter inEnd, TUInt;
{...}
Return xxxxx;
EndFunction;
Command Test;
{...}
If MyProgram.IsValidRange[startOfRange, endOfRange];
{...}
EndIf;
EndCommand;
A pure function is a function that depends only on its parameters, and its only output is its return value. It causes no side-effects. A pure function is "referentially transparent", meaning the invocation of the function can be replaced with the return value of the function without changing the state of the program.
In a pure function, any variables that persist outside of the scope of the function cannot be modified. These variables cannot be read either, unless the variables are immutable/invariant.
Function Implementations can be marked as "Pure" or "Impure", as in the following example:
Function Test1, TUInt, Pure;
Return 50;
EndFunction;
Function Test2, TUInt, Impure;
Return 50;
EndFunction;
If neither "Pure" or "Impure" is specified, then "Pure" is assumed by default. Thus explicitly specifying "Pure" is unnecessary but it may be specified anyway, to aid in self-documentation and readability, and to be unambiguous.
Pure functions cannot invoke impure functions or commands. If a pure function has reference or pointer type parameters, the targets of the references/pointers must be marked as read-only. The compiler verifies pure Function Implementations to ensure that they do not violate the rules of pure functions.
A pure function can invoke the "Fail" command (see the Error Management section of the documentation).
Function Implementations are encouraged to be pure. Command Implementations are not. Command Implementations often need to change the state of the program. Attempting to write all Command Implementations in a purely functional manner is not recommended because it would make many tasks more difficult than necessary, and would likely reduce performance.