
Arrays
An array is created with the array
keyword. After it has been declared,
the elements can be accessed with numerical subscripts, which are contained in
pairs of parentheses. The language allows for arrays to have an
arbitrary number of dimensions. The size and dimension of an array are static
and set when the array is declared. For example, to create a one-dimensional
array:
array vector(5)
In this example, the array named vector has 5 elements and can be accessed with subscripts from 0 to 4.
After an array has been created, it can be written with assign
and read
wherever any other variable is allowed.
array hues(10)
assign hues(5) 120
hue hues(5)
Accessing an array with a subscript that is negative or outside the bounds of its size will cause an error to be logged, but in most cases, the virtual machine will make an attempt to provide a reasonable default. If the subscript is a floating-point value, the fraction will be truncated to yield an integer.
To declare a multi-dimensional array, use a series of square brackets, each with a size. For example:
array colors(8)(4)(9)
assign colors(1)(3)(8) 100
Within the declaration, the size of the array can be calculated at run-tme. However, after an arrary has been declared, its size may not be changed.
array hue_segment(hue / 10)
assign array_size 24 * 60
array day(array_size)
Assigning an array to a variable creates an alias and does not make a copy:
array mat(3)
assign mat(0) 100
assign mat2 mat
assign mat2(0) 200
# This will print 200
print mat(0)
Partially Dereferencing an Array
When an array is partially subscripted, the result is itself an array with a reduced dimension, which can be kept in a variable.
array mat_3(5)(5)(5)
assign mat_2 mat_3(4)
assign mat_2(0)(0) 100
assign vector mat_3(4)(2)
assign vector(0) 200
Iterating Over an Array
To iterate over a specific range, use the repeat
with
construct:
array vector(5)
repeat with i from 1 to 3
assign vector(i) i * 2
To iterate over all of the elements in an array, use a repaeat
in
as
clause:
array hues(3)
repeat in hues as i begin
assign hues(i) i * 120
end
Similar constructs can be used for multi-dimensional arrays:
array colors(5)(4)
repeat in colors as color_num
repeat in colors(color_num) as setting_num
assign colors(color_num)(setting_num) 100
repeat in colors(0) as setting_num
assign colors(0)(setting_num) 200
array matrix(6)(5)(3)
repeat in matrix as i
repeat in matrix(i) as j
repeat in matrix(i)(j) as k
assign matrix(i)(j)(k) 500
Arrays as Parameters
When an array is passed into a routine, it needs to be declared as such in the routine’s definition. This is done with a pair of square brackets for each dimension. For example:
define sum_array with arr()() begin
assign sum 0
repeat with i from 0 to 4 begin
repeat with j from 0 to 9 begin
assign sum sum + arr(i)(j)
end
end
return sum
end
array matrix(5)(10)
# ...some code here fills the "matrix" array with data.
println (sum_array matrix)
(Maybe not in the first release) The code using the array can also be written without explicitly giving the lengths of each dimension:
define sum_array with arr()() begin
assign sum 0
repeat in arr as i begin
repeat in arr(i) as j begin
assign sum sum + arr(i)(j)
end
end
end
Arrays as Return Values
A function is definition has no concept of a return type. In fact, nothing in the language prevents a function from potentially returning different types of values. Therefore, it will be up to the script writer to correctly use the return value from a function call.
The parser will know if an incoming parameter, or a local variable, is an array, and handle it accordingly. However, it will not be able to do any compile-time checks on the return value of a function, because that is always determined at run-time.
When an array has been fully dereferenced within a function before being returned, the return value is an immutable scalar. If the array is not fully dereferenced before being returned, the return value can be further indexed, potentially involving an assignment to one of its elements.
Starting with a very simple example:
array arr(10)
assign arr(5) 2
define access_arr with vec() begin
return vec(5)
end
As illustrated by this example:
The declaration specifies that
vec
is a one-dimensional array, but its size is not given. The script writer is responsible for avoiding an out-of-bounds index.Because
vec
is fully dereferenced, the function returns 2.
An example returning a locally-declared array:
define return_arr begin
array local_array(5)
repeat in local_array as i begin
assign local_array(i) i * 10
end
return local_array
end
assign external_ref [return_arr]
repeat in external_ref as j begin
printf "{} " external_ref(j)
end
This script will print out:
0 10 20 30 40
This example illustrates returning a partially-dereferenced array:
define return_partial with arr()() n begin
return arr(n)
end
array mat(3)(3)
repeat in mat as i begin
repeat in mat(i) as j begin
assign mat(i)(j) i * 100 + j
end
end
assign vec [return_partial 2]
repeat with i in vec begin
printf "{} " vec(i)
end
This script will output:
200 201 202
This example has a function returning a partially dereferenced array that is then indexed:
define return_partial with arr()() n begin
return arr(n)
end
array vec(3)(3)
assign [return_partial vec 1](2) 1000
println vec(1)(2)
This outputs:
Design Overview
Support for arrays is accomplished with some new instructions and new handling of certain types of parameters passed into to stack-related instructions.
An Array class contains a Python List object that holds the data. The class also keeps the declared size of the array.
The ArrayCursor class is used to navigate arrays. It contains a reference to the underlying Array object, as well as an index value that designates a specific element within the Array.
A specialized version of ArrayCursor is used to create arrays, as opposed to navigating them.
New VM Instructions
OpCode.OP, Operator.SET
This is a new binary operation for the vm_math module. It does the following:
Pop the rvalue from the top of the stack.
Pop the lvalue from the top of the stack.
Assign the rvalue to the lvalue.
(Consider pushing the lvalue back onto the stack.)
This code sequence does not leave anything on top of the stack. It replaces
the previous implementation of the assign
command. The implementation of
the SET operation will need to handle array cursors differently than simple
variables.
Array-Centric Op-Codes
OpCode.ARRAY: Pop the name off the stack and create an array with that name. Push that array object back onto the stack.
OpCode.DEREF: Pop the name of the array off the top of the stack. Create an array cursor using that name and push it onto the stack.
OpCode.INDEX: Pop the index value off of the stack, and then pop the array object off of the stack. If the popped array object is a cursor, apply the index to the cursor and push the resulting array cursor back onto the stack. If the popped object is an array and not a cursor, add a dimension to the array with a length equal to the index value, and push the object back onto the stack.
When the parser reaches the end of the square-bracket pairs, it adds an instruction to pop the stack to clear it of the array under construction.
For example, this script:
array a(10)
can be accomplished with:
OpCode.PUSHQ, "a"
OpCode.ARRAY
OpCode.PUSHQ, 10
OpCode.INDEX
OpCode.POP
In this case, OpCode.INDEX pops the array off the stack, pops 5 off the stack, and adds the dimension to the array, with 5 slots. The final pop instruction leaves the stack in its previous state.
Then, to reference the array, such as a(5):
OpCode.PUSHQ, "a"
OpCode.DEREF
OpCode.PUSHQ, 5
OpCode.INDEX
After this code has been executed, the array cursor will occupy the top of the stack, with the index value of 5.
If it is used as an lvalue, that cursor stores the incoming value in the underlying array, at position 5. If it is used as an rvalue, it provides the value stored in the underlying array, also at position 5.
Therefore, this:
hue a(5)
would produce something like:
OpCode.PUSHQ, "a"
OpCode.DEREF
OpCode.PUSHQ, 5
OpCode.INDEX
OpCode.POP, Register.HUE
Note that the stack returns to the same condition it was in prior to this code being executed.
Conversely, assignment to the array, such as:
assign a(5) 10
can be done with:
OpCode.PUSHQ, "a"
OpCode.DEREF
OpCode.PUSHQ, 5
OpCode.INDEX
OpCode.PUSHQ, 10
OpCode.OP, Operator.SET
In response to the SET command, the VM pops the array cursor, pops the name of the destination variable (“a”), and assigns the value from the cursor to the variable. The stack will not have anything added to it.
Compare this to the code associated with a scalar:
assign b 20
which might produce:
OpCode.PUSHQ, "b"
OpCode.PUSHQ, 20
OpCode.OP, Operator.SET
In both cases (scalar or array), the SET operation leaves the stack in its previous state.
Multi-Dimensional Arrays
Two kinds of array cursors handle OpCode.INDEX. A cursor used for creation responds to indexing by adding another dimension to the array. For example, to have v(10)(5):
array v(10)(5)
this can be done with:
OpCode.PUSHQ, "v"
OpCode.ARRAY
OpCode.PUSHQ, 10
OpCode.INDEX
OpCode.PUSHQ, 5
OpCode.INDEX
OpCode.POP
Note that the first OpCode.INDEX creates the first dimension, creating the equivalent of v(10). The second OpCode.INDEX creates the second dimension. In the underlying array object, each of the first 10 elements is populated with a 5-element sub-array, which amounts to v(10)(5), for a total of 50 slots.
In this context, every INDEX instruction leaves the array object on the stack. No cursors are created here. When construction of the array is complete, the POP at the end clears it off the stack.
Assignment uses an array cursor that acts as a gateway to the underlying array. For example:
assign x v(2)(3)
can be done with:
OpCode.PUSHQ, "x"
OpCode.PUSHQ, "v"
OpCode.DEREF
OpCode.PUSHQ, 2
OpCode.INDEX
OpCode.PUSHQ, 3
OpCode.INDEX
OpCode.SET
Conversely, to assign in the opposite direction:
assign x v(4)(1)
can be done with:
OpCode.PUSHQ, "v"
OpCode.PUSHQ, 4
OpCode.INDEX
OpCode.PUSHQ, 1
OpCode.INDEX
OpCode.PUSHQ, "x"
OpCode.OP, Operator.SET
Partially-Dereferenced Arrays
Only one cursor is needed when iterating over a one-dimensional array. Arrays of more than one dimension require one cursor for each dimension. This is true during creation and access.
Returning to example:
array v (5)(10)
assign x v(3)
assign y x(7)
the cursors work as follows:
# array v (5)(10)
#
OpCode.PUSHQ, "v"
OpCode.ARRAY
OpCode.PUSHQ, 2
OpCode.INDEX
OpCode.PUSHQ, 3
OpCode.INDEX
OpCode.POP
# assign x v(3)
#
OpCode.PUSHQ, "x"
OpCode.PUSHQ, "v"
OpCode.DEREF
OpCode.PUSHQ, 3
OpCode.INDEX
OpCode.OP, Operator.SET
# assign y x(7)
#
OpCode.PUSHQ, "y"
OpCode.PUSHQ, "x"
OpCode.DEREF
OpCode.PUSHQ, 7
OpCode.INDEX
OpCode.OP, Operator.SET
Array as Parameter
The key concept here is that the incoming parameter is a cursor, and there is no DEREF instruction inside the routine.
Script:
define fn with v() begin
assign v(7) 9
return v(5)
end
array arr(10)
assign arr(5) 250
hue (fn arr)
VM code:
# define fn with v() begin
#
OpCode.ROUTINE, "fn"
# assign v(7) 9
#
OpCode.PUSHQ, "v"
OpCode.PUSHQ, 7
OpCode.INDEX
OpCode.PUSHQ, 9
OpCode.OP, Operator.SET
# return v(5)
# end
#
OpCode.PUSHQ, "v"
OpCode.PUSHQ, 5
OpCode.INDEX
OpCode.RETURN # Functions leave the return value on the stack.
# array arr(10)
#
OpCode.PUSHQ, "arr"
OpCode.ARRAY
OpCode.PUSHQ, 10
OpCode.INDEX
OpCode.POP
# assign arr(5) 250
#
OpCode.PUSHQ, "arr"
OpCode.DEREF
OpCode.PUSHQ, 5
OpCode.INDEX
OpCode.PUSHQ, 250
OpCode.OP, Operator.SET
# hue (fn arr)
#
OpCode.CTX
OpCode.PUSHQ, "arr"
OpCode.DEREF
OpCode.POP, Register.RESULT # Register.RESULT now holds a cursor.
OpCode.PARAM, "v", Register.RESULT # Pass that cursor as parameter v.
OpCode.JSR, "fn"
OpCode.END_CTX
OpCode.POP, Register.RESULT
OpCode.MOVE, Register.RESULT, Register.HUE
Partially-Dereferenced Array as a Parameter
As with the previous use case, the incoming parameter is a cursor, and there is no DEREF instruction inside the routine.
Script:
define fn with v() begin
assign v(6) 7
return v(5)
end
array arr(10)(20)
assign arr(3)(5) 100
hue (fn arr(3))
VM code:
# define fn with v() begin
#
OpCode.ROUTINE, "fn"
# assign v(6) 7
#
OpCode.PUSHQ, "v"
OpCode.PUSHQ, 6
OpCode.INDEX
OpCode.PUSHQ, 7
OpCode.OP, Operator.SET
# return v(5)
# end
#
OpCode.PUSHQ, "v"
OpCode.PUSHQ, 5
OpCode.INDEX
OpCode.RETURN
# array arr(10)(20)
#
OpCode.PUSHQ, "arr"
OpCode.ARRAY
OpCode.PUSHQ, 10
OpCode.INDEX
OpCode.PUSHQ, 20
OpCode.INDEX
OpCode.POP
# assign arr(3)(5) 100
#
OpCode.PUSHQ, "arr"
OpCode.DEREF
OpCode.PUSHQ, 3
OpCode.INDEX
OpCode.PUSHQ, 5
OpCode.INDEX
OpCode.PUSHQ, 100
OpCode.OP, Operator.SET
# hue (fn arr(3))
#
OpCode.CTX
OpCode.PUSHQ, "arr"
OpCode.DEREF
OpCode.PUSHQ, 3
OpCode.INDEX
OpCode.POP, Register.RESULT # Cursor associated with arr(3).
OpCode.PARAM, "v", Register.RESULT
OpCode.JSR, "fn"
OpCode.END_CTX
OpCode.POP, Register.RESULT
OpCode.MOVE, Register.RESULT, Register.HUE
Loop over 1-D Array
This kind of loop resembles a standard counted loop.
array v(10)
repeat with i from 0 to 9
hue v(i)
Note that the above code is functionally equivalent to:
array v(10)
repeat in v as i
hue v(i)
Therefore the parser, generates code similar to that of a counting loop. Because the size of the array is calculated at run-time, the limit for the loop must also be determined dynamically. The new Operand.SIZE designates this.
For example, to retrieve the size of array v and leave it on top of the stack:
OpCode.PUSHQ, "v"
OpCode.OP, Operator.SIZE
To integrate this into loop code:
# Enter the loop.
OpCode.LOOP
# Initialize the counter
#
OpCode.PUSHQ, "v"
OpCode.Op, Operator.SIZE
OpCode.POP, LoopVar.COUNTER
# This is where the JUMP code points. Check that the number of remaining
# iterations > 0.
#
OpCode.PUSH, LoopVar.COUNTER
OpCode.PUSHQ, 0
OpCode.OP, Operator.GT
OpCode.JUMP, JumpCondition.IF_FALSE, 11
# (Code inside the loop goes here.)
# Decrement the counter.
#
OpCode.PUSH, LoopVar.COUNTER
OpCode.PUSHQ, 1
OpCode.OP, Operator.SUB
OpCode.POP, LoopVar.COUNTER
# Jump to the next potential iteration, where the counter is compared to 0.
#
OpCode.JUMP, JumpCondition.ALWAYS, -5
# Exit the loop.
#
OpCode.END_LOOP
Interpolation works the same as with counted loops as well:
array v(10)
repeat in v as i with x from 10 to 100
hue v(i) + x
Loop over 2-D Array
Script:
array m(5)(10)
repeat in m as i
repeat in m(i) as j
assign m(i)(j) 0
repeat in m as i
repeat in m(i) as j
hue m(i)(j)