scScript Variables
Variables can be declared in the main body (globals), in a function (locals), or in any {} sub-block (also local). A function definition will usually include named in and out parameters that will act as pre-initialized local variables when the function is called. The scope of a declaration extends to the end of its block, or the end of the program in the case of globals. Variable names must start with a letter, potentially followed by more letters and digits. scScript is case sensitive (like C) but only for user-defined names.
A variable declaration has the form:
Var MyVar;
or perhaps
Var MyVar1, MyVar2, MyVar3;
These are all automatically initialized to Int 0.
Just like C, these may also be initialized/assigned within the declaration:
Var MyVar1 = 12, MyVar2 = “This”, MyVar3 = MyGlobVar – ThatFunc(MyArr);
All variables are declared the same way, and are free to assume any
value type, including integer, floating point, string, closure, array,
dict, etc. Empty arrays and dicts are created with their respective
constructors:
MyArr = Array(); or MyArr = Array(12);
Also
MyDict = Dict(); or MyDict = Dict(12);
This assignment can be done when the variable is created, or later. In
scScript, a missing arg in a call is generally interpreted as Int
0, so Array() and Array(0) are the same
and will create an array with the (default) minimum size. (Some pkg
functions may assume other default values for missing args.)
Dicts are simple assoc lists, storing key-value pairs that are later retrieved with the key. Like other variables, both the key and its value can have any form, although the key is most often a short string. Once a key-value pair is stored in the dict, it cannot be removed. But the value side of the pair can be easily changed.
The Dict (or Arr) itself can be removed by assigning
another value (often Int 0) to the variable that holds it.
The Dict (or Arr) will be garbage collected when there are no
more variables pointing to it.
Arrays and dicts can also be constructed with specific initial content:
MyArr = [“this”, “is”, “interesting”];
ArrX = [Var1+2, OtherArr[3], MyFunc(Var3+4)];
MyDict = [“W1”:”this”, “W2”:”is”, “W3”:”interesting”];
The contents of the [] constructors are evaluated,
so named variables are accessed and any indicated operations (such as
indexing into OtherArr or calling MyFunc) are
performed. Similarly, a string value must be quoted, lest it be
interpreted as a named variable.
Both arrays and dicts can be indexed using [] brackets:
MyArr[12] = MyArr[X + 2] + (MyDict[“Count”] * 2);
In the case of arrays, the index must be an integer, but it can
be any key type for dicts. Importantly, the contents of the index is
evaluated, so the string “Count”
in MyDict["Count"] must be quoted. In contrast,
MyDict[Count] will use the value stored in
the Count variable as index.
Dicts can also be indexed using the dot format:
MyArr[12] = MyArr[X + 2] + (MyDict.Count * 2);
The dot format assumes a string key name
so MyDict.Count is exactly the same
as MyDict[“Count”]. The dot provides its own implicit
quotes around the key that follows (Count) and will not
evaluate it.
Variables are named for the benefit of human programmers. The compiler just translates them to indices into a storage list. Once compiled, bytecodes deal only with the index and know nothing of variable names. In fact it takes substantial extra work to include global variable names in the running script for debugging displays!
In the diagram below, MyGlobal (holding Pi) is the
3rd entry on the list of globals and ArrX is the
4th local variable in the 14th run
of MyFunc. These lists simply point to the storage space
for the variable. In the case of numbers (integer or float), the values
are stored directly in the variables. But for anything more complicated
(such as an array or dict), the variable will in turn point to the
storage for that data structure, which may itself have multiple
parts.
scScript uses Garbage Collection to recover memory from data structures
that can no longer be accessed. For example in:
MyVar = Array(7); // Allocate array with 7 elts
...
MyVar = 3.14159; // Replace the array with a Flt!
The old value of MyVar (the array of 7 elts) is replaced,
rendering the array itself inaccessible! But scScript uses
a RefCount to track the number of references to the array.
When this RefCount reaches zero, the storage taken by the
array is immediately recovered. (scScript also has a Mark/Sweep garbage
collector for handling circular references that cannot be recovered with
RefCounts.)
scScript allocates local variables within the top (body) block of a
function, even if defined in nested sub-blocks. The current (ByteCode)
instruction set refers to these locals using a 1-byte index. But each
function currently reserves 7 special local variables as registers.
In addition, index 0x00 and 0xFF designate the
runtime stack (differentiates data vs reference address on stack). As a
result, a function is limited to only 247 locals. While these will be
allocated (hoisted) in the top block of a function, they are scoped
and accessible only within the sub-block that defines them. scScript is
currently not smart enough to share/re-use variable allocations in
disjoint nested sub-blocks, so all count towards the limit.
The system also allows for up to 65K global variables, defined in the main body of the script. While these globals are indexed with a 2-byte designator, they cannot be directly addressed in the instruction set. Instead, they are first aliased with the aforementioned local registers. These are repurposed and reused as necessary, so all globals can be manipulated within a given function. Importantly, the main scScript body is itself regarded as an implicit function. Therefore, in addition to registers, sub-blocks within the main body define local variables and there can only be a total of 247 such variables.
scScript treats functions as simple variables (whether local or
global), but there is one difference. The scope of a defined sub-function
is the top level of the function that defines it. So even if
MySubFunc is defined 5 levels deep
within BigFunc, it will be accessible
throughout BigFunc. (This only applies if the
function is explicitly declared with a Function keyword; not
if it is initially declared as a Var and then assigned
a nameless lambda function!)