Menu

scScript Functions

Functions in scScript are passed around as nameless lambda closures stored in variables. They are a first class data type that can be freely stored, returned, and assigned, just as integers, strings, arrays, and dicts. The function itself does not have a name, that belongs to the variable that holds its closure.

Functions can be declared and defined globally as well as within other functions. So a function can return a sub-function that is later stored in a dict and called by a subsequent one that retrieves it.

A simple (somewhat pointless) function definition with no formal parameters can be:
Var MyFunc = Function()() Show.Globs();
or more commonly written as:
Function MyFunc()() Show.Globs();

The preferred latter form, which implicitly declares MyFunc as a variable, is slightly more concise and perhaps more stylistically elegant (scEmacs also knows to hilite the Function keyword making it very visible in script-mode). But a new function can be defined and assigned to a var long after the variable has been declared. Var ArrX, ArrY, ArrFunc; ... ArrFunc = Function()() {blah blah blah… }

However, it is not possible to use
Function ArrFunc()() {blah blah blah…}
for this purpose, as it would attempt to re-declare the previously declared ArrFunc variable!

One can pre-declare a function (must be without parameters) to satisfy coding dependencies, such as for mutual recursion: Function MyFunc; // Pre-decl with no arg lists! ... Function OtherFunc(A)() {MyFunc(A); … } // Uses the pre-decl of MyFunc! ... Function MyFunc(X)() {OtherFunc(X); … } // Finally define MyFunc here...

Every function returns a value. This is normally specified with:
Return N;
where N can be a literal value (Int, Flt, Str), a var, a function call, an arr/dict, or even a function closure. A simple Return; or just reaching the end of the function without an explicit Return, will also return Int 0. (Note that Function F()(); will create an empty function that returns 0 immediately!)

A function definition requires both in (pass-by-value) and out (pass-by-reference) parameter lists, even if both are empty. A function call, however, requires only the in list, out args are optional.

From within the function, all in and out parameters look like local named variables. When the function is called, in parameters are initialized with the values provided by the caller. For example, if:
Function TheFunc(InA, InB, InC)(OutA, OutB, OutC) { ...body... }
is eventually called with: MyVar = 14; TheFunc(MyVar, “This”, 3.14)(OtherVar, MyArr[2], MyDict.Size);

the body of the function will see a local variable InA with the value 14, InB with the string “This” as value, and InC storing 3.14. NewFunc can re-assign, increment, or otherwise manipulate these values within its body. But the outside world, including the caller of TheFunc will not see these changes nor know of InA, InB, or InC. The in list simply passes values into the function (hence pass-by-value) which can use and manipulate them at will. But just like C, the outside world will be able to see the new contents if entire arrays or dicts are passed into a function that changes what is stored inside them!

The above call also has an out list where OutA becomes OtherVar, OutB becomes MyArr[2], and OutC becomes MyDict.Size. TheFunc can also re-assign, increment, or manipulate these variables, treating OutA, OutB, and OutC as any other local variables. But the result of such manipulations will be directly manifested outside TheFunc where these storage locations exist and are quite visible. Any changes to OutA are exactly reflected in OtherVar, which can be a global or perhaps a local variable in the caller of TheFunc. Similarly, setting OutB to 9 will have the exact same effect as setting MyArr[2] to 9, because OutB is just a reference to MyArr[2], therefore points to the same storage location. So pass-by-reference will set the formal out parameter to a pre-existing storage location.

scScript functions can be called with fewer args than what is specified in their definition. So TheFunc (defined above) can be called with:
TheFunc(3)(OtherVar);

This will pass 3 to InA, but also a default Int 0 to InB and InC. On the out side, OutA becomes OtherVar, but OutB and OutC will be given their own temporary new storage, each initialized with Int 0 as value. So TheFunc will run as if called by:
TheFunc(3, 0, 0)(OtherVar, NewVar1, NewVar2);
where NewVar1 and NewVar2 are implicitly declared as
Var NewVar1 = 0, NewVar2 = 0;
But of course, these two are not named and cannot be seen outside of TheFunc.

scScript functions can also be called with more args than what is specified in their definition. These extra args obviously cannot be mapped to named formal parameters. But they can be extracted in array form with a simple Sys.GetArgs call.

Suppose TheFunc is called with: TheFunc(MyVar, “This”, 3.14, “That”, 2.718) (OtherVar, MyArr[2], MyD.Height, MyD.Weight);

All In/Out args can be extracted with: (from within TheFunc itself) Var InArr, InCount, OutArr, OutCount; // Declare locals first Sys.GetArgs()(InArr, InCount, OutArr, OutCount); // Get proper values for them

Note: Sys.GetArgs uses its out parameter list, not in like most functions!

Sys.GetArgs will pack in list vars (from TheFunc) into InArr, so InArr[2] (arrays are 0-based, like C) will get InC (value 3.14), and InCount will get the arg count of 5. OutArr will pack the vars passed to the out list into an array, just like the InArr, but the elts will be the vars! Rather than create/allocate new elts for the arrays, they will use the same storage as their respective args. So OutArr[3] will be MyD.Weight, and setting OutArr[3] to 150 will set MyD.Weight to 150! As expected, OutCount will be 4.

Sys.GetArgs is itself a function call. So its output variables are placed in its out List. One can also call GetArgs itself with fewer args, perhaps as
Sys.GetArgs()(InArr, Null, OutArr)
if not interested in getting the arg counts (no need for a trailing Null). Either way, Sys.GetArgs will discard any previous values in its out args as it loads in parameters from its function. (Note: Null or Void are valid out list args and naturally used as placeholders!)

But Sys.GetArgs works in a slightly unexpected way if TheFunc is called with fewer args than formal parameters. In that case, the InArr or OutArr will also contain fewer entries than formal parameters! So the default entries in the in list and the temporary storage for the out list parameters will not show up in the GetArgs results. While logically correct, this may come as a surprise.

As mentioned above, every function returns a single value (which defaults to Int 0). But the out list allows functions to conceptually export more than one value. This is very much in keeping with the spirit of C and an original way of providing that functionality without the unwelcome addition of pointers (or types) in scripting. The only downside is the minor weirdness of an extra () in every function definition!

Note: Functions can be defined with empty in or out lists. Functions may also have Null (or Void) in their argument lists. These are generally used as placeholders for expected, but ignored, args. While legal, it makes little sense to have extra Null args after the last named one.
F(Null, Null, Null)(Null, Null) --> F()() F(Null, Null, A, Null)(Null, X, Null) --> F(Null, Null, A)(Null, X)

The 2nd example is useful if the script expects to call F with F(V1, V2, V3)(Out1, Out2) but the function itself really only cares about V3 and Out2.

Limits: ByteCodes, low level ops that run scScript, can only address local variables with a single byte. So the total number is limited to 256. Unfortunately, 0x00 and 0xFF are reserved, as are 7 local registers used to alias global variables. So the total number left is only 247. Importantly, in and out parameters are included in this count (as are locals closed over from superior functions). So a function with 10 in and 7 out parameters, can have only 230 local variables. Yes, it is truly difficult to write a function with only that many locals, but to your best.