User-defined functions
In addition to the predefined functions, users can define their own functions in AddyScript and use them. The following sections describe how to do this.
Creating a function
To create a function in AddyScript, use the following syntax:
function functionName (comma_separated_list_of_parameters)
{
// function's logic goes here
// returning a value is optional and can be done like this:
return 10;
}
When the body of a function is reduced to a return statement or a simple expression, the entire function can be formulated with this shorter syntax:
| Example | |
|---|---|
Invoking functions
As in most languages, a function invocation in AddyScript consists of its name followed by a comma-separated list of arguments in parentheses. In AddyScript, however, arguments can be either positional or named.
A positional argument is any expression that appears at a particular position in an argument list. It is automatically mapped at runtime to the parameter that appears at the same position in the function header.
A named argument is one that consists of a name (i.e., an identifier) followed by a colon (:) and then an expression.
It is mapped to the parameter that has the same name in the function header.
Named arguments are particularly useful when you are calling a function that has optional parameters
and you do not want to provide values for some of the ones that come first in the function header.
Positional arguments must always come first in an argument list. Once the parser encounters a named argument, it expects all following arguments to be named as well.
Spreading arguments
An important feature of the AddyScript syntax is that you can invoke a function with arguments of type list
or set preceded by the spread operator (..).
This tells AddyScript that the list or set should be substituted for its contents.
This works similarly to the right operand of group assignment.
The only requirement here is that none of the parameters provided by the spread collection should be passed to the function by reference.
Manage how parameters are passed to a function
A parameter can be passed to a function by value, by reference, or as a variable-length list of values.
By default, parameters are passed by value.
To indicate that a parameter is passed by reference, simply prefix it with the ampersand sign (&).
Similarly, prefixing a parameter with the double-dot sign (..) indicates that it represents a variable-length list of values.
Note that a variable-length list of values must always be the last in a parameter list.
Thus, a function cannot have multiple variable-length lists of values in its header.
When a parameter is passed by value to a function, it can be assigned a default value. This makes the parameter optional (i.e., it does not need to be provided with a value when calling the function). Once you add an optional parameter to a parameter list, the only types of parameters that can follow are other optional parameters and a variable-length list of values.
Another feature that AddyScript offers in handling function parameters is emptiness checking.
When a parameter name is followed by an exclamation mark (!) in a function header,
this tells AddyScript that the parameter in question should not receive empty values.
Empty here means a null reference, a zero-length string, or an empty collection.
Whenever the parameter receives such a value, an exception is thrown.
Closures
A closure is a function used as a variable. Closures are typically used to pass functions as parameters to other functions (customizing their behavior) or to return a function as the result of another function. They appear in two forms in AddyScript: function references and inline function declarations. A function reference is like a reference to a variable (a simple identifier in the code) while an inline function declaration is an anonymous function definition that appears where an expression was expected. Both techniques are illustrated in the example below:
Remarks:
- When the body of an anonymous function is reduced to a return statement or a simple expression, the entire function can be formulated like this:
|parameters| => expression. In this form, we call it a lambda expression. For example, we could invoke the "repeat" function from the previous example like this:repeat(myList, |n| => println('{0} x 2 = {1}', n, 2*n));. A lambda expression can also have a real function body delimited by curly braces and optionally ending with a return statement. - If the parameter list of a lambda expression is empty, put a space between the vertical bars. This prevents parsers from confusing them with an or-else operator (
||).
The closure's "bind" method
The closure type has a single member: the "bind" method. Its prototype is: closure closure::bind(string parameterName, any defaultValue). Its main purpose is to create a clone of a closure with a modified prototype. The "bind" method operates as follows:
-
If parameterName matches the name of an existing parameter in the original function's header, "bind" proceeds to currying: The parameter parameterName is removed from the resulting function's header and is replaced by a fixed value, which in this case is defaultValue. The resulting function will then have one parameter less than the original one, with the exact same body that will always evaluate parameterName to defaultValue. Here is an example of how to curry a function:
-
If parameterName doesn't match the name of an existing parameter in the original function's header, "bind" simply adds an optional parameter with the given name and default value to the resulting function. This is very helpful when you are creating a function that expects a closure as an argument and that you want the closure to match variable prototypes. As an example, the "each" method of the list type is defined as follows:
External functions
AddyScript allows a script to invoke a function declared in a native library (such as a DLL or a shared object). To do this, the target function must first be declared as an external function in the script using this syntax:
[LibImport("nativeLibraryName", procName = "importedFunctionName", returnType = "someDotNetType")]
extern function functionName(list_of_parameters_with_type_attribute);
Where :
- nativeLibraryName is the name of the native library that contains the definition of the function we want to import,
- importedFunctionName is the name of the function we want to import, and
- someDotNetType is the name of the return type of the function.
- functionName is the name we want to give to the function in our code.
Notes:
- If functionName is equal to importedFunctionName, then the procName field of the LibImport attribute can be omitted.
- If the function does not return anything, then returnType can also be omitted.
- Each parameter in the formal parameter list must be decorated with a Type attribute indicating what type the parameter is.
- If the Type attribute is omitted, the System.Object type will be used by default.
- In fact, when it comes to P/Invoke, AddyScript is less dynamically typed than usual.
- Unqualified type names will be prefixed with "System.", and will therefore be searched in the System.Private.CorLib assembly.
The following example shows how to invoke the Win32 MessageBox function from a script:
Re-using code: the import directive
AddyScript obviously allows the user to define a function once and reuse it multiple times later. To do this, simply save the target functions in a script and import that script from another. You typically import a script using the import directive. Its syntax is as follows:
import script_name_without_extension;
AddyScript assumes that imported scripts have the extension .add. The imported script will first be searched in the same directory as the script from which it is imported. If it is not found in that directory, AddyScript will continue searching in each of the directories that are listed in the ImportPaths property of the ScriptContext instance with which the current ScriptEngine object was initialized. An error occurs if the search is unsuccessful. To indicate that the script to be imported would be in a subdirectory, use the double-colon operator (::) as a path separator in the script name.
Example:
Suppose that you have the following file structure:

To import funcs.add from main.add, simply add the following line of code to main.add:
import funcs;
To import moreFuncs.add from main.add, simply add the following line of code to main.add:
import lib::moreFuncs;
Supposing that the full path to scripts figures in the ImportPaths property of the current ScriptContext, we can import math.add from main.add by simply adding the following line of code to main.add:
import math;
And finally, scripts being in the ImportPaths, we can import core.add from main.add by simply adding the following line of code to main.add:
import graph::core;
Remarks:
The import directive can be used to import any symbol defined in a script. These include constants, variables, functions and classes.