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.