Records may contain other records or arrays as fields. Consider the following definition:
type Pixel: record Pt: point; color: dword; endrecord;
The definition above defines a single point with a 32-bit color component. When initializing an object of type Pixel
, the first initializer corresponds to the Pt
field, not the x-coordinate
field. The following definition is incorrect:
static ThisPt: Pixel := Pixel:[ 5, 10 ]; // Syntactically incorrect!
The value of the first field (5) is not an object of type point
. Therefore, the assembler generates an error when encountering this statement. HLA will allow you to initialize the fields of Pixel
using declarations like the following:
static ThisPt: Pixel := Pixel:[ point:[ 1, 2, 3 ], 10 ]; ThatPt: Pixel := Pixel:[ point:[ 0, 0, 0 ], 5 ];
Accessing Pixel
fields is very easy. As in a high-level language, you use a single period to reference the Pt
field and a second period to access the x
, y
, and z
fields of point
:
stdout.put( "ThisPt.Pt.x = ", ThisPt.Pt.x, nl ); stdout.put( "ThisPt.Pt.y = ", ThisPt.Pt.y, nl ); stdout.put( "ThisPt.Pt.z = ", ThisPt.Pt.z, nl ); . . . mov( eax, ThisPt.Color );
You can also declare arrays as record fields. The following record creates a data type capable of representing an object with eight points (for example, a cube):
type Object8: record Pts: point[8]; Color: dword; endrecord;
This record allocates storage for eight different points. Accessing an element of the Pts
array requires that you know the size of an object of type point
(remember, you must multiply the index into the array by the size of one element, 12 in this particular case). Suppose, for example, that you have a variable Cube
of type Object8
. You could access elements of the Pts
array as follows:
// Cube.Pts[i].x := 0; mov( i, ebx ); intmul( 12, ebx ); mov( 0, Cube.Pts.x[ebx] );
The one unfortunate aspect of all this is that you must know the size of each element of the Pts
array. Fortunately, you can rewrite the code above using @size
as follows:
// Cube.Pts[i].x := 0; mov( i, ebx ); intmul( @size( point ), ebx ); mov( 0, Cube.Pts.x[ebx] );
Note in this example that the index specification ([ebx]
) follows the whole object name even though the array is Pts
, not x
. Remember, the [ebx]
specification is an indexed addressing mode, not an array index. Indexes always follow the entire name, so you do not attach them to the array component as you would in a high-level language like C/C++ or Pascal. This produces the correct result because addition is commutative, and the dot operator (as well as the index operator) corresponds to addition. In particular, the expression Cube.Pts.x[ebx]
tells HLA to compute the sum of Cube
(the base address of the object) plus the offset to the Pts
field, plus the offset to the x
field, plus the value of EBX. Technically, we're really computing offset(Cube
) + offset(Pts
) + EBX + offset(x
), but we can rearrange this because addition is commutative.
You can also define two-dimensional arrays within a record. Accessing elements of such arrays is no different than accessing any other two-dimensional array other than the fact that you must specify the array's field name as the base address for the array. For example:
type RecW2DArray: record intField: int32; aField: int32[4,5]; . . . endrecord; static recVar: RecW2DArray; . . . // Access element [i,j] of the aField field using row-major ordering: mov( i, ebx ); intmul( 5, ebx ); add( j, ebx ); mov( recVar.aField[ ebx*4 ], eax ); . . .
The code above uses the standard row-major calculation to index into a 4x5 array of double words. The only difference between this example and a standalone array access is the fact that the base address is recVar.aField
.
There are two common ways to nest record definitions. As this section notes, you can create a record type in a type
section and then use that type name as the data type of some field within a record (e.g., the Pt:point
field in the Pixel
data type above). It is also possible to declare a record directly within another record without creating a separate data type for that record; the following example demonstrates this:
type NestedRecs: record iField: int32; sField: string; rField: record i:int32; u:uns32; endrecord; cField:char; endrecord;
Generally, it's a better idea to create a separate type rather than embed records directly in other records, but nesting them is perfectly legal.
If you have an array of records and one of the fields of that record type is an array, you must compute the indexes into the arrays independently of one another and then use the sum of these indexes as the ultimate index. The following example demonstrates how to do this:
type recType: record arrayField: dword[4,5]; << Other fields >> endrecord; static aryOfRecs: recType[3,3]; . . . // Access aryOfRecs[i,j].arrayField[k,l]: intmul( 5, i, ebx ); // Computes index into aryOfRecs add( j, ebx ); // as (i*5 +j)*@size( recType ). intmul( @size( recType ), ebx ); intmul( 3, k, eax ); // Computes index into aryOfRecs add( l, eax ); // as (k*3 + j) (*4 handled later). mov( aryOfRecs.arrayField[ ebx + eax*4 ], eax );
Note the use of the base plus scaled indexed addressing mode to simplify this operation.