Every variable and function in OSL is associated with a scope. Whether or not a variable or function is accessible by a given piece of code is determined by its scope. There are two variable scopes in OSL scripts: global and local.
By default, variables and functions have local scope. Local variables declared inside a function are only accessible inside that function. Local variables declared outside of a function in the main body of a script are only accessible from the main body of the script.
Note that local variables declared in the main body of a script are not accessible by functions declared in the same script. Local variables must be passed as parameters to a function for a function to access their contents.
The global scope is accessible anywhere in the current execution environment. The execution environment includes every script that has been loaded since OBO-Edit or obo2obo was started.
Functions may be declared global by including the global keyword before the function name. Local variables and functions can be made global through the use of the export keyword.
Note that all functions declared inthe same script always have access to each other, even if they have different scopes. For example:
global function multiplyByTwoAndAddFive(x) {
return addFive(multiplyByTwo(x));
}
function multiplyByTwo(x) {
return x*2;
}
function addFive(x) {
return x+5;
}
Because multiplyByTwoAndAddFive is a global function, any other script in the same execution environment can call it. Even if multiplyByTwoAndAddFive is called from a context that cannot access the local functions multiplyByTwo or addFive, multiplyByTwoAndAddFive still has access to those functions, so external calls to multiplyByTwoAndAddFive will work properly.
The same variable name may be declared in two different scopes. For example, there could be a global variable named x and a function parameter named x. In these cases, the local variable always masks the global variable.
The following example illustrates several of these points.
1:global function printWithEmphasis(str) {
2: println(str+"!");
3: println("globalX = "+globalX);
4:}
5:
6:globalX="Hello";
7:str="Spicy";
8:export str;
9:printWithEmphasis("Saucy");
10:export globalX;
11:printWithEmphasis(str);
This program will produce the following output:
Saucy! globalX = null Spicy! globalX = Hello
These results may be a little suprising at first glance.
This first line of output is "Saucy!" rather than "Spicy!", because the local variable str (which was passed the value "Saucy!" on line 9) masks the value of the global variable str.
The second line is "globalX = null", because the variable globalX is not available to printWithEmphasis the first time printWithEmphasis is called.
The third line is "Spicy!" because the value of the global variable str has been passed to printWithEmphasis.
The last line is "globalX = Hello" because globalX became a global variable on line 10. Thus, globalX is defined when printWithEmphasis runs the second time.
Function calls always take the form:
<function name> ( <argument1>, <argument2> ... <argumentN>);
Parameters are always passed by value. But the way that the arguments are packaged when they are passed to the function depends on how the function was declared.
One way of declaring a function is to provide a name for every parameter passed to the function, like so:
1: function createRepeatingString(repeatme, times) {
2: out = "";
3: for(i=0; i < times; i++)
4: out = out + repeatme;
5: return out;
6: }
7:
8: print(createRepeatingString("All work and no play makes Jack a dull boy. ", 100));
The script above prints "All work and no play makes Jack a dull boy." one hundred times. When createRepeatingString is called, the variables repeatme and times are populated with the values "All work and no play makes Jack a dull boy" and 100 respectively. The values are passed positionally - the first value is copied to the first parameter, the second value to the second parameter, etc.
It is not an error to provide more arguments or fewer arguments than the function declaration expects when calling a function. If too many arguments are provided, the extra arguments are ignored. If too few are provided, null is passed as the value of each missing arguments.
Another way of declaring a function is with a list parameter. The following program behaves identically to the program above, but uses list parameters instead of named parameters.
1: function createRepeatingString(arguments*) {
2: out = "";
3: for(i=0; i < arguments[1]; i++)
4: out = out + arguments[0];
5: return out;
6: }
7:
8: print(createRepeatingString("All work and no play makes Jack a dull boy. ", 100));
When list parameters are used, only one function parameter is declared. When the function is called, all the function parameters are passed in as a single list.
List parameters are useful when a function needs to handle an arbitrary number of arguments:
1: function sum(values*) {
2: x = 0;
3: foreach (val in values)
4: x = x + val;
5: return x;
6: }
7:
8: print(sum(1,2,3,4,5,6,7,8,9,10,11));
The sum function above can be used to add any number of values together.
Just as parameters are passed to functions, parameters are sometimes passed to an entire script. Script parameters are named variables that are automatically available when a script runs. These parameters are defined in advance by OBO-Edit or obo2obo. See OBO-Edit-Specific Variables for more information.
Both functions and scripts always have a return value. The return value is passed to whatever code called the function or script initially.
When a script is called by OBO-Edit, the return value may or may not be used, based on the context in which the script was called. For example, the return value of a backtick expressions is used to set a layout parameter or define a search, but the return value of a script passed to obo2obo is ignored.
Normally, the return value of a function or script is the value of the last statement in the function or script. For example, the following script:
1: x = 5;
2: x + 2;
produces a return value of 7.
It is possible to immediately end a script or function and return a value with the return statement. The following script:
1: x = 5;
2: return 8;
3: x + 2;
produces a return value of 8. Line 3 is never even executed, because execution halts immediately when the return statement is encountered.
The return statement is particularly useful when performing a long calculation that should end abruptly when a certain condition is met:
1: function getIndex(list, target) {
2: i = 0;3: foreach (val in list) {
4: if (val == target)5: return i;6: i++;
7: }
8: -1;
9: }
The example above uses both methods of returning a value. If the desired item in the list is found, the index of the item is immediately returned (see line 5). If the item is not found, -1, the value of the last statement (line 8), is returned.
Functions may be assigned to variables by referring to the function name with no parameters or parentheses:
1: function addFive(x) {
2: return x+5;
3: }
4:
5: y = addFive;
6: y(3);
The script above returns the value 8, because y(3) has become equivalent to addFive(3).
Functions may also be passed as parameters to other functions:
1: function map(list, mappingFunction) {
2: // this function uses the Util library provided by OBO-Edit and obo2obo
3: out = Util.createList();
4: foreach (x in list) {5: out.add(mappingFunction(x));6: }7: return out;8: }
The map function is passed a list and a mapping function. The mapping function is called on each item in the list, and the result is added to an output list. The result is a copy of the original list with each item replaced by the results of the mapping function.
OSL scripts are often given access to Java objects. Java objects are passed in as script parameters, or returned from method calls on a Java object. All OSL literals are actually implemented as Java objects, so Java methods can be called on strings, numbers, and other primitives.s
Method calls in OSL have exactly the same syntax as in Java:
1: x = "Welcome to Paris!";
2: x = x.replaceAll("Paris", "Epcot Center");
See The Java API Reference and the OBO-Edit API Reference for the methods available for the various Java objects in OSL.
OSL is an untyped language. Any variable can hold a value of any type.
However, all OSL objects are implemented as Java objects, and all Java objects have specific types. This has some side effects that can be surprising.