Subroutines

The commands can include subroutines and definitions, both of which must be defined at the start of the commands or be loaded with Load commands. The commands that get executed and will call subroutines or use definitions must start with the first command after the last subroutine and definition has been defined. The subroutine and defintion topics are:

Defining and Calling Subroutines

A subroutine, which can be called with a GoSub command, is defined as follows:

Sub subname #arg1,#arg2,...,#argn
  Local #x,#y,...
  (subroutine commands)
    .
    .
    .
  return (expr)
. . . EndSub

where

Defining Custom Functions

You can define custom functions with the single line command:

Def fxnname,(types),(expression),(default1),(default2),...

where

Defined functions can be used in expressions by same methods used for built-in functions. They can be either numerical or string functions. They are only available since 3.5/1.0.

For an example, the function:

Def add,"nn","#1+#2+#3+#4",0,0

creates a function to add numbers. It can take 2 to 4 arguments. The first 2 are required and must be numbers. If omitted, the 3rd and 4th arguments are zero. If not omitted, they are used, but that are not validated as being strings or numbers. Some example output is

add(3,4) = 7
add(3,4,5) = 12
add(3,4,5,6) = 18
"3 plus 7 ="&add(3,4) = "3 plus 4 = 7"
add(3,4,5,6,7) causes error because too many arguments
add(3) causes error because of two few arguments
add(3,x) causes error because required numerica variable is not a number

Note that all errors simply report as "Invalid text, custom function 'add', or function arguments in expression".

When writing internal scripts, a defined function can fetch object properties. For example:

Def name,"s","@#1.name"

allows name(indi) to be used in expressions with indi as an object variable for an individual record and evaluates to the individuals name.

Scoping of Variables

The default variable scoping for subroutines is for all variables to be global variables. Thus subroutines can read and change variables from the main commands and the main commands can access variables defined in the subroutines including subroutine arguments. A drawback of this default scoping is that if a subroutine uses a variable that unintentionally matches the name of a variable in the main commands, that subroutine will overwrite that variable in the main commands. This overwriting can also happen if any of the subroutine arguments unintentionally matches a variable name in the main commands. Three options can alter this default scoping.

Local Commands

By starting a subroutine with one or more Local commands, you can define any number of variables to be local variables that only exist in the body of the subroutine or in any subroutines called from the subroutine (unless that called subroutine also defines some as their own local variables). If you want subroutine argument variables (in #arg1,#arg2,...,#argn) to be local, they have to be in Local commands as well. The following example illustrates effects of Local commands:

Sub AddOne,#x
  Local #x
  #x+=1
  write "AddOne start x="&#x&return
  GoSub AddTwo,#x
  write "AddOne end x="&#x&return
EndSub

Sub AddTwo,#x
  Local #x
  #x+=2
  write "AddTwo x="&#x&return
EndSub

! Main commands
#x=0
write "Main start x="&#x&return
GoSub AddOne,#x
write "Main end x="&#x&return

The output of these commands for various combinations of Local commands are:

! Both Locals     ! No AddTwo Local  ! No AddOne Local  ! No Locals
Main start x=0    Main start x=0     Main start x=0     Main start x=0
AddOne start x=1  AddOne start x=1   AddOne start x=1   AddOne start x=1
AddTwo x=3        AddTwo x=3         AddTwo x=3         AddTwo x=3
AddOne end x=1    AddOne end x=3     AddOne end x=1     AddOne end x=3
Main end x=0      Main end x=0       Main end x=1       Main end x=3

The first column is from the above commands. The variable #x in passed to the two subroutines that add 1 and then 2. When control returns to AddOne, #x reverts to 1 because local changes made in AddTwo are discarded when that subroutine ends. Likewise when control returns to the main commands, #x reverts to 0. In other words, subroutines that define #x as local do not change the value it had in calling code.

If the Local command is removed from AddTwo only (second column), that subroutine changes #x from AddOne such that it has the new value when control returns to AddOne. When control returns to the main commands, #x reverts to its initial value.

If the Local command is removed from AddOne only (third column), that subroutine changes #x from main commands. The call to AddTwo has only local changes that do not return back to AddOne. When control returns to the main commands, #x includes the changes made in AddOne but not in AddTwo.

Finally, the last column is the default mode when there are no Local commands. Now #x retains its global status such that all changes made in subroutines are intact when control returns to the main commands.

Push and Pop Commands

An alternative to selecting which variables are local and which are global is to surround calls to a subroutine with PushVariables and PopVariables commands. By this approach all variables within the called subroutine are local variables and their status cannot be changed (i.e., there is no Global command to change local status of any variables back to global).

Because all variables are local, the only way to get information back when bracketing subroutine calls with PushVariables and PopVariables commands is exit the subroutine with a return (expr) command. The calling code can access the result of (expr) in the #returnValue$ read-only variable.

Personal Namespace

A third option is to adopt personal conventions for naming variables. For example, one could choose to end all variables used in subroutines with an underscore (e.g., #var_) while never using an underscore in the main code. When subroutines call other subroutines, this approach might need to define separate naming conventions for each subroutine to avoid conflicts (e.g. subroutines A, B, and C end variables in _A, _B, and _C). This approach breaks done for subroutines that are called recursively; such subroutines would need to use one of first two options instead.

Passing and Scoping of Objects

When writing internal scripts, object names are always global. Subroutines should be careful not to overwrite objects in the main commands (i.e., be systematic in your choice of object names such as to always use underscode ("_") at the end of object names in your subroutines).

One way to pass an object to a subroutine is to define it in the main commands. Because objects are global, the subroutine can use this object. Sometimes, however, you might want a subroutine that can apply to various objects and therefore you need a method to pass that object as subroutine argument. Because subroutine arguments (e.g., #arg1,#arg2,...,#argn) can only be strings that are assigned to variables, passing an object to a subroutine must use one of the following two methods:

Sub UseObject,#obj
  Local #obj
  GetObject obj_,#obj
  obj_.0.get "#listItem"
  
  ! ... or directly dereference the object name
  ^#obj.1.get passedObj
EndSub

CreateList mylist,"one string item",anObject
GoSub UseObject,"mylist"

The object name mylist is passed as a string that gets set to the local subroutine variable #obj (the object name only needs to be quoted if you have also used mylist as a variable name). The GetObject command evaluates that variable as an object name and then sets obj_ to that object. Alternatively, the object can be directly dereferenced with ^#obj.

When writing a subroutine that defines a new object, the main commands can access the object using the name defined in the subroutine. Better practice is to return the object name and the main commands can convert to a desired object name. Here are commands to create an object in a subroutines and then use it in calling code:

Sub CreateMyList
  CreateList list_
  list_.addString "Item 1","Item 2","Item 3"
  return list_
EndSub

GoSub CreateList
GetObject mylist,#returnValue$

Using Subroutines as Header Files

You can use subroutines to implement something analogous to header files in other programming languages. The most common use would be to create global variables that can then be used by any command (or any called subroutine) in the template or internal script that uses it. The process is:

  1. Create a subroutine with any name such as "Globals.txt" (or "Globals.gcscpt" for internal scripts).
  2. Load it into any template or script that needs it with a Load command.
  3. Call it at the start of a template or an internal script. Any variables or objects defined in that subroutine will then be available for use in any subsequent commands (including commands in other called subroutines).

Notes

  1. See the GoSub command for information on calling subroutines.
  2. Subroutines that might get called by many browser style templates can alternatively be defined in subroutine templates. To be able to use such subroutines in the current template commands, the defined subroutine templates must be loaded with a Load command at the beginning of the other template.
  3. Similarly, when writing internal scripts, a collection of subroutines and definitions from files in the extension or in the "Modules" folder can be loaded with a Load command at the beginning of the main commands.
  4. Subroutines can be nested which means subroutines can call any other subroutines with GoSub commands.
  5. Definitions can use other defined functions, but cannot use itself (e.g. a definition for add could be "sub(#1,#2),#3" where sub is some other defined function, but cannot be "add(#1,#2),#3").
  6. Browser style templates are compiled for speed. The current compiling method creates and checks subroutine definitions on its first pass through the code. As a consequence any GoSub command can only reference a subroutine that is defined higher up in the code. This behavior only influences subroutines that call other subroutines. The one that is called must be defined before the one that calls it. For example, if the above example was modified to run in a browser style template, it would need to define AddTwo before AddOne because AddOne calls AddTwo. When writing internal scripts, subroutines can be defined in any order.