.. figure:: logo.png :align: center http://www.bardolph.org .. _array: .. index:: array ****** 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 square brackets. 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: .. code-block:: lightbulb 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. .. code-block:: lightbulb 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 dimensions, separated by spaces. For example: .. code-block:: lightbulb 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. .. code-block:: lightbulb 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: .. code-block:: lightbulb array mat[3] assign mat[0] 100 assign mat2 mat assign mat2[0] 200 # This will print 200 print mat[0] .. index:: array; partial dereference 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. .. code-block:: lightbulb 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 .. index:: array; iterating over Iterating Over an Array ======================= To iterate over a specific range, use the ``repeat`` ``with`` construct: .. code-block:: lightbulb 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: .. code-block:: lightbulb array hues[3] repeat in hues as i begin assign hues[i] (i * 120) end Similar constructs can be used for multi-dimensional arrays: .. code-block:: lightbulb 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 .. index:: array; assignment from one to another Assigning One Array to Another ------------------------------ After a varable has been declared as an array, it can be assigned the value of another array. This assignment effectively creates an alias. For example: .. code-block:: lightbulb array a[10] assign a[5] 200 array b[] assign b a println b[5] assign a[4] 400 println b[4] This will produce the output: .. code-block:: 200 400 When an array is overwritten, its previous contents are discarded: .. code-block:: lightbulb array a[5] assign a[3] 20 array b[] assign b[3] 40 assign a b println a[3] In this example, the output will be: .. code-block:: 40 An array can be assigned the result of partially dereferencing another array: .. code-block:: lightbulb array a[3 3] repeat for i in a repeat for j in a[i] assign a[i j] (i * 10 + j) assign b a[2] repeat for k in b println b[k] This will produce the output: .. code-block:: 20 21 22 An array may not be assigned to an element of another array: .. code-block:: lightbulb array a[3] array b[2 3] assign b[1 1] a[0] # ok assign b[2] a # error assign b a # ok In other words, an entire array can have its value replaced, but its elements may be set only to scalar values. An array cannot be converted to a scalar: .. code-block:: lightbulb array a[3] array b[10] assign a[0] 5 # ok assign a b # ok assign a 5 # error .. index:: array; as a parameter Arrays as Parameters ==================== .. code-block:: lightbulb 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] Although the dimensionality of arr is unspecified, it must always be treated as an array. This eliminates the ambiguity between function calls and array dereferences. It also places the burden on the code to handle the dimensionality correctly. The code using the array can also be written without explicitly giving the lengths of each dimension: .. code-block:: lightbulb define sum_array with arr[] begin assign sum 0 repeat in arr as i begin repeat in arr[i] as j assign sum (sum + arr[i j]) end end This increases the burden on the code to handle potentially complex issues of dimensionality. .. code-block:: lightbulb define f with a return a assign g 3 define set_element with arr[] begin assign arr[f 2] 10 # 1 - always an error assign arr[[f 2] 2] 10 # 2 - ok if arr is two-dimensional assign arr[g 2] 12 # 3 - ok if arr is two-dimensional end array x_one[5] array x_two[5 5] set_element x_one # 4 - causes errors at lines 2 and 3 set_element x_two # 5 - ok Line 1 is an error, because f is a function that returns a value, and must be invoked within its own pair of square brackets, as shown in line 2. The correctness of line 2 depends on what is passed in as arr. Because line 2 assumes that arr is a two-dimensional array, the invocation at line 4 will be an error, but the one at line 5 will work. Invocation of set_element in line 4 is an error because x_one is a one-dimensional array. However, this error will *not* be detected at compile time. The VM will make an effort to handle this error gracefully and keep executing the script. (Maybe not in the first release) Stronger error detection can be achieved if the size of the array is included in the procedure definition: .. code-block:: lightbulb define f with arr[2 3] .. index:: array; spaces in code Spaces ------ These two lines are semantically equivalent. .. code-block:: lightbulb assign arr[[f 2]] 5 assign arr [[f 2]] 5 .. index:: array; as return value Arrays as Return Values ======================= A function can be defined to return either a scalar or an array. If it returns an array, its name must be followed by an pair of square brackets in its declaration. For example: .. code-block:: lightbulb define return_arr[] begin array a[2] assign a[0] 10 assign a[1] 20 return a end println [return_arr][1] Running this code will output .. code-block:: 20 This example illustrates returning a partially-dereferenced array: .. code-block:: lightbulb array mat[5 5] for i in mat[] for j in mat[i] assign mat[i, j] (i * 10 + j) 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: .. code-block:: 200 201 202 This example has a function returning a partially dereferenced array that is then indexed: .. code-block:: lightbulb define return_partial with arr n begin return arr[n] end array vec[3 3] assgn vec[1 2] 1000 array a[] # 1 assign a [return_partial vec 1] # 2 println a[2] # 3 This outputs: .. code:block:: 1000 Note that *a* must first be declared as an array (as done in line 1), or it can not be assigned the return value of *return_partial*. Related to this requirement: in lines 2 and 3, the parser knows that *return_partial* returns an array. Given that knowledge, the parser checks whether the routine call is immediately followed by square brackets. In this example, an ``assign`` statement in line 3 immediately follows the routine call, without the brackets. Therefore, the context of that call must be appropriate for an array. That requirement is met at line 2 because the return value is assigned to array variable *a*. 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: .. code-block:: lightbulb array a(10) can be accomplished with: .. code-block:: python 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)`: .. code-block:: python 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: .. code-block:: lightbulb hue a(5) would produce something like: .. code-block:: python 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: .. code-block:: python assign a(5) 10 can be done with: .. code-block:: python 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: .. code-block:: python assign b 20 which might produce: .. code-block:: python 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): .. code-block:: lightbulb array v(10)(5) this can be done with: .. code-block:: python 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: .. code-block:: lightbulb assign x v(2)(3) can be done with: .. code-block:: python 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: .. code-block:: lightbulb assign x v(4)(1) can be done with: .. code-block:: python 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: .. code-block:: lightbulb array v (5)(10) assign x v(3) assign y x(7) the cursors work as follows: .. code-block:: python # 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: .. code-block:: lightbulb 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: .. code-block:: python # 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: .. code-block:: lightbulb 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: .. code-block:: python # 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. .. code-block:: lightbulb array v(10) repeat with i from 0 to 9 hue v(i) Note that the above code is functionally equivalent to: .. code-block:: lightbulb 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: .. code-block:: python OpCode.PUSHQ, "v" OpCode.OP, Operator.SIZE To integrate this into loop code: .. code-block:: python # 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: .. code-block:: lightbulb array v(10) repeat in v as i with x from 10 to 100 hue v(i) + x Loop over 2-D Array ------------------- Script: .. code-block:: lightbulb 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)