SpectNetIDE allows you to use structure definitions and placements in your Z80 programs. If you know the struct
construct from C, C++, or C#, the concept in Z80 is only partly similar.
Understanding Structures
In SpectNetIDE assembler, a structure definition is a placeholder that defines a byte pattern like this:
Object2D: .struct
.defw 0
.defw 0
.defb 1
.defb 1
.ends
This definition says that Object2D
is a structure of six bytes (two 16-bit words and two 8-bit bytes) with the following bytes emitted: #00, #00, #00, #00, #01, #01. Whenever you place a structure declaration in a program, just like in this sample, the assembler will emit the bytes you specified in the .struct
definition:
Object2D() ; emits #00, #00, #00, #00, #01, #01
As you can see, a set of six subsequent bytes do not tell enough about the semantics of Object2D
. When we created this structure, the original concept was to use two 16-bit numbers to specify the X and Y coordinates of the object, plus two 8-bit numbers to describe its horizontal and vertical velocity. With field definitions (see X
, Y
, DX
, and DY
), the meaning of Object2D
is more straightforward than it was before:
Object2D: .struct
X: .defw 0
Y: .defw 0
DX: .defb 1
DY: .defb 1
.ends
Of course, we would like to initialize objects with different states. With field initializers, we can define structures with initial states that are different from the .struct
definition:
Apple: Object2D()
X -> .defw 100
Y -> .defw 100
Pear: Object2D()
DX -> .defb -1
DY -> .defb -1
; Some other code
; ...
ld hl,Apple
ld de,Pear
In this sample, the Apple
label (that the code later loads into HL) points to an Object2D
declaration that holds 100 in its X and Y values. Another label, Pear
points to a different instance of Object2D
(later the code loads that address into DE). Pear
has a converse velocity compared to Apple
.
The SpecNetIDE assembler allows you initialize structures with any pragma that emit bytes to the assembly output. For example, the following code snippet sets a new Object2D
structure in a particular way:
Banana: Object2D()
-> .defb 10, 1
-> .defb 12, 2
DX -> .defb 2, 2
The first .defb
pragma (right after the first ->
) emits two bytes, 10, and 1, respectively – it sets the X field to 266 (10 + 1256). The second .defb
sets Y to 524 (12 * 2256). The initialization of DX (DX -> .defb 2, 2
) emits two bytes and sets both DX
and DY
to 2.
Structure Definition
You can place a structure definition between the .struct
and .ends
statements. Each structure must have a unique name that you can declare with a label. The compiler accepts all of these definitions:
; Version #1
MyStruct .struct
; ...
.ends
; Version #2
MyStruct: .struct
; ...
.ends
; Version #3
MyStruct
.struct
; ...
.ends
; Version#4
MyStruct:
.struct
; ...
.ends
Nonetheless, it raises an error if you do not name the structure:
.struct ; ERROR: .struct must have a name
; ...
.ends
Note: The assembler accepts the following alternative keywords for
.struct
:.STRUCT
,struct
, orSTRUCT
. Similarly,.ends
has these aliases, too:.ENDS
,ends
,ENDS
. Though you can define an empty structure, there is no practical reason to do so.
In the body of the structure, you can use only one of these byte-emitter pragmas: .defb
, .defw
, .defm
, .defn
, .defc
, .defs
, .fillb
, .fillw
, .defg
, or .defgx
. If you try to use any other construct, the compiler raises an error message.
As you saw earlier, you can specify field labels within the structure body. The assembler is flexible: you can omit field labels, or even use multiple labels for a single field:
Object2D_A: .struct
Coords:
X: .defw 0
Y: .defw 0
Velocity:
.defb 1
.defb 1
.ends
Here, Object2D_A
has two field name for the first .defw
field, Coords
, and X
, respectively. The last .defb
does not have its own field name, unlike the one before, Velocity
.
Labels and Field Names
The label assigned to the .struct
definition has a dual role. When used in a structure initialization (for example, as Object2D
is utilized in the Object2D()
initializer, it identifies the structure. Nonetheless, you can use the label name in Z80 instructions, too. In this case, the label’s value is the size of the structure. For example, these instructions are equivalent, as the size of the Object2D
structure is six bytes:
ld a,Object2D ; Size of Object2D
; ...
ld a,6 ;
You can allocate a memory block for 100 uninitialized instances of Object2D
with this pragma:
My100Objects:
.defs 100 * Object2D
Note: You cannot assign a label to the
.ends
statement. If you do, the compiler throws an error.
Labels assigned to the body of the .struct
definition are used as field names. You can use them only with the structure name:
Object2D: .struct
X: .defw 0
Y: .defw 0
DX: .defb 1
DY: .defb 1
.ends
Apple: Object2D()
; ...
ld hl,Apple + Object2D.DX
ld a,(hl)
Field label values contain the offset of the particular field from the beginning of the structure. Thus, the last two instructions in the code above load the content of the Apple
structure’s DX
field into A.
According to this definitions, here are the field label values of Object2D
:
Object2D.X: 0
Object2D.Y: 2
Object2D.DX: 4
Object2D.DY: 5
Structure initialization
The .struct
definition does not emit any code, it just tells the compiler the layout and initial contents of a structure. To allocate space for a particular structure, you need to initialize it with the name of the structure plus a pair of opening and closing parentheses, just like these samples show:
; Initalize an Object2D
MyObject: Object2D()
; Initialize another one
OtherObject: Object2D()
When you add a label to the structure initialization, that label’s value points to the beginning of the structure in the memory.
start: .org #8000
DistanceEntry:
.struct
Address: .defw $
SeqNo: .defb Index
.ends
Data: .org #9000
Index = 1;
Entry1: DistanceEntry()
Index = 2;
Entry2: DistanceEntry()
Index = 3;
Entry3: DistanceEntry()
The compiler emits the initialization code for Entry1
, Entry2
, and Entry3
as if you wrote this:
Data .org #9000
Entry1:
.defw #9000
.defb 1
Entry2:
.defw #9003
.defb 2
Entry3:
.defw #9006
.defb 03
Field Initializers
Field initializers can be used to change the default structure initializer. A field initializer has this syntax:
[identifier] ->
byte-emitter-pragma
For a moment let’s forget about the fact that identifier is optional. When you use it, it must be one of the structure’s fields names. The byte-emitter-pragma is one of the SpectNetIDE pragmas you can use to define a structure body; namely these: .defb
, .defw
, .defm
, .defn
, .defc
, .defs
, .fillb
, .fillw
, .defg
, or .defgx
.
Note: You can use the label syntax for field names, so you can add an optional colon after the identifier.
You can apply field initializer statements right after the structure initialization. Any other Z80 instruction, pragma, statement, directive signs the end of the structure initialization. This code snippet shows two examples of setting up Object2D
structures. The first is correct, however, the second one raises an error:
Object2D: .struct
X: .defw 0
Y: .defw 0
DX: .defb 1
DY: .defb 1
.ends
Obj1: Object2D()
DX -> .defb 2
DY -> .defb 2
ld a,b
; ... Some other code
Obj2: Object2D()
X -> .defw 100
Y -> .defw 100
ld hl,#4000 ; Field initialization stops here
DX -> .defb 4 ; ERROR
The compiler does not care how you initialize fields. For example, even if you have created the X
field of Object2D
with a .defw
pragma, you can set its value with .defb
, like in this example:
Obj3: Object2D()
X -> .defb 100, 0
You can even initialize two fields with a simple initializer statement. For example, DX
and DY
are one byte each. You can initialize both of these fields in a single step, as these code snippet shows:
Obj4: Object2D()
DX -> .defb 2,2
Obj5: Object2D()
DX -> .defw #0202
The order of fields is unimportant, you can initialize them in any order:
Obj6: Object2D()
DX -> .defb 1
X -> .defw 100
DY -> .defb 1
Y -> .defw 200
Fluent Structure Initialization
The assembler allows using flexible initialization, where you do not use field names. The compiler amits bytes as the byte emitter pragmas would do if you were not within a structure initialization. Let’s assume, you initialize an Object2D
this way:
Obj7: Object2D()
-> .defb 1, 0
DX -> defw #0303
Without the field initializers the structure would contain these six bytes:
#00, #00, #00, #00, #01, #01
However, the field initializers overwrote the default bytes with the ones displayed in boldface:
#01, #00, #00, #00, #03, #03
You can choose your preferred way to initialize a structure using field or unnamed initializers. The compiler does not care how you assemble the set of bytes within the structure. However, it does not allow you to overflow the structure boundaries.
This sample shows you two initialization of Object2D
. The first is correct, as it emits exactly six bytes. However, the second raises an error, since the initialization tries to put eight bytes into the structure:
Obj8: Object2D()
-> .defm "012345"
Obj9: Object2D() ; ERROR: The code tries to intialize the structure with 8 bytes
X -> .defw 100
-> .defm "012345"