Assembler Statements
Statements are Klive Assembler-specific control flow constructs that instruct the compiler about loop-like and conditional compilation. Thanks to Simon Brattel for the inspiration.
Note: While directives help you to organize your code and include code files optionally according to the compilation context, statements provide you with more valuable tools to shorten the way you can declare Z80 assembly code.
Each statement can be written with a leading dot or without it, and the compiler accepts both lowercase and uppercase versions. For example, all of these versions are valid: .if, if, .IF, and IF.
The LOOP Block
With the LOOP block, you can organize a cycle to emit code. Here is a sample that tells the gist:
.loop 6
add hl,hl
.endlThis construct is a shorter way to multiply HL with 64. It is equivalent to the following code:
add hl,hl
add hl,hl
add hl,hl
add hl,hl
add hl,hl
add hl,hlThe .loop statement accepts an expression. The compiler repeats the instructions within the
loop’s body according to the value of the expression. The .endl statement marks the end of the loop.
Note: You can use many flavors for the
.endlblock closing statement..endl,endl,.lend,lendare all accepted — with uppercase letters, too.
Look at this code:
counter .equ 2
; do something (code omitted)
.loop counter + 1
.db #80, #00
.endlThis code is as if you wrote this:
.db #80, #00
.db #80, #00
.db #80, #00The LOOP Scope
The .loop statement declares a scope for all labels, symbols, and variables in the loop’s body. Every iteration has its separate local scope. When the assembler resolves symbols, it starts from the loop’s scope and tries to resolve the symbol’s value. If the lookup fails, it steps out to
the outer scope, and goes on with the resolution.
Check this code:
value .equ 2
; do something (code omitted)
.loop 2
value .equ 5
ld a,value
.endlThe compiler takes it into account as if you wrote this:
ld a,5
ld a,5The value symbol declared within the loop overrides value in the outer scope, and thus, 5 is used instead of 2.
Nonetheless, when you utilize a different construct, it seems a bit strange at first:
value .equ 2
; do something (code omitted)
.loop 2
ld a,value
value .equ 5
ld b,value
.endlThe strange thing is that the compiler creates this:
ld a,2
ld b,5
ld a,2
ld b,5When the assembler resolves value in the ld a,value instruction, if finds value in the outer scope only, as it is not declared yet within the loop’s scope. In the ld b,value instruction value gets resolved from the inner scope and takes 5.
Variables and Scopes
Unlike symbols that work as constant values, variables (declared with the .var pragma or its syntactical equivalents, the = or := tokens) can change their values.
Take a look at this code:
counter = 4
.loop 3
innercounter = 4
ld a,counter + innercounter
counter = counter + 1
.endlHere, the counter variable is defined in the global scope (out of the loop’s scope), while innercounter in the local scope of the loop. When evaluating the counter = counter + 1 statement, the compiler finds counter in the outer scope, so it uses that variable to increment its value. This code emits machine code for this source:
ld a,#08
ld a,#09
ld a,#0ANow, add a single line to the loop’s code:
counter = 4
.loop 3
innercounter = 4
ld a,counter + innercounter
counter = counter + 1
.endl
ld b,innercounterThe compiler will not compile this code, as it cannot find the value for innercounter in the ld b,innercount instruction. Because innercounter is defined in the local scope of the loop, this scope is immediately disposed as the loop is completed. When the compiler processes the ld b,innercounter instruction, the local scope is not
available.
Labels and Scopes
Labels behave like symbols, and they work similarly. When you create a label within a loop, that label is created in the local scope of the loop. The following code helps you understand which labels are part of the global scope and which are created in the loop’s scope:
.org #8000
MyLoop: .loop 2
ld bc,MyLoop
Inner:
ld de,MyEnd
ld hl,Inner
ld ix,Outer
MyEnd: .endl
Outer: nopThe label of the .loop statement is part of the outer (global) scope, just like the label that follows the
.endl statement. However, all labels declared within the loop’s body, including the label of the .endl statement, belong to the local scope of the loop.
Thus, the compiler translates the code above into this one:
(#8000): ld bc,#8000 (MyLoop)
Inner_1 (#8003): ld de,#800D (MyEnd_1)
(#8006): ld hl,#8003 (Inner_1)
(#8009): ld ix,#801A (Outer)
MyEnd_1 (#800D): ld bc,#8000 (MyLoop)
Inner_2 (#8010): ld de,#801A (MyEnd_2)
(#8013): ld hl,#8010 (Inner_2)
(#8016): ld ix,#801A (Outer)
MyEnd_2
Outer (#801A): nopHere, Inner_1, Inner_2, MyEnd_1, and MyEnd_2 represent the labels created in the local scope of the loop. The _1 and _2 suffixes indicate that each loop iteration has a separate local scope. As you can see, the last iteration of MyLabel points to the first outer address (Outer label).
Nesting LOOPs
Of course, you can nest loops, such as in this code:
.loop 3
nop
.loop 2
ld a,b
.endl
inc b
.endlThis code snippet translates to this:
nop
ld a,b
ld a,b
inc b
nop
ld a,b
ld a,b
inc b
nop
ld a,b
ld a,b
inc bWhen you nest loops, each loop has its separate scope.
The $CNT value
It is handy to use the $cnt value that represents the current loop counter. It starts from 1 and increments to the maximum number of loops. This sample demonstrates how you can use it:
.loop 2
outerCount = $cnt
.loop 3
.db #10 * outerCount + $cnt
.endl
.endlThis code translates to this:
.db #11
.db #12
.db #13
.db #21
.db #22
.db #23You can observe that each loop has its separate $cnt value.
Note: The
$cntvalue has several syntax versions that the compiler accepts:$CNT,.cnt, and.CNT.
The PROC..ENDP Block
In the previous section, you could understand how labels and scopes work for the .loop statement. You can utilize this scoping mechanism with the help of the .proc...endp statement. This sample code demonstrates the concepts (just as you learned earlier):
.org #8000
MyLabel:
ld de,Outer
ld hl,Mylabel
call MyProc
halt
MyProc:
.proc
ld bc,MyProc
MyLabel:
ld de,MyEnd
ld hl,MyLabel
ld ix,Outer
ret
MyEnd:
.endp
Outer: nopThe first MyLabel label belongs to the global scope, while the second (within MyProc) to the local scope of the procedure wrapped between .proc and endp. MyProc belongs to the global scope, too, however, MyEnd is part of the MyProc scope, so it is visible only from
within the procedure.
The assembler emits this code:
MyLabel (#8000): ld de,#8018 (Outer)
(#8003): ld hl,#8000 (MyLabel)
(#8006): call #800A (MyProc)
(#8009): halt
MyProc (#800A): ld bc,#800A (MyProc)
MyLabel_ (#800D): ld de,#8018 (MyEnd)
(#8010): ld hl,#800D (MyLabel_)
(#8013): ld ix,#8018 (Outer)
(#8017): ret
MyEnd
Outer (#8018): nopYou can nest PROC blocks just as LOOP blocks. Each PROC block has its private scope. When the compiler sees a PROC block, it works as if you wrote .loop 1.
Note:
PROCis different than a loop. You cannot use the$cntvalue. Similarly, thebreakandcontinueinstructions are unavailable within aPROCblock.
Note: The assembler accepts these aliases for
PROCandENDP:.proc,proc,.PROC,PROC,.endp,.ENDP,endp,ENDP,.pend,.PEND,pend, andPEND.
The REPEAT..UNTIL Block
While the .loop statement works with an expression that specifies the loop counter, the .repeat...until block uses an exit condition to create more flexible loops. Here is a sample:
counter = 0
.repeat
.db counter
counter = counter + 3
.until counter % 7 == 0The counter % 7 == 0 condition specifies when to exit the loop. Because the exit condition is examined only at the end of the loop, the .repeat blocks execute at least once.
The sample above translates to this:
.db 0
.db 3
.db 6
.db 9
.db 12
.db 15
.db 18The .repeat block uses the same approach to handle its local scope, symbols, labels, and variables as the .loop block. The block also provides the $cnt loop counter that starts from 1 and increments in every loop cycle.
This sample demonstrates the .repeat block in action:
.org #8000
counter = 0
.repeat
.db low(EndLabel), high(Endlabel), $cnt
counter = counter + 3
EndLabel: .until counter % 7 == 0The compiler translates the code to this:
.db #03, #80, #01
.db #06, #80, #02
.db #09, #80, #03
.db #0C, #80, #04
.db #0F, #80, #05
.db #12, #80, #06
.db #15, #80, #07The WHILE..ENDW Block
With .while loop, you can create another kind of block, which uses an entry condition. For example, the following code snippet generates instructions to create the sum of numbers from 1 to 9:
counter = 1
ld a,0
.while counter < 10
add a,counter
counter = counter + 1
.endwThe .while...endw block uses an entry condition declared in the .while statement. Provided this condition is true, the compiler enters into the body of the loop and compiles all instructions and statements
until it reaches the .endw statement. The body of the loop may never be reached.
The compiler translates the code snippet above to the following:
ld a,0
add a,1
add a,2
add a,3
add a,4
add a,5
add a,6
add a,7
add a,8
add a,9Like the .loop and the .repeat blocks, .while uses the same approach to handle its local scope, symbols, labels, and variables. This block also provides the $cnt loop counter that starts from 1 and increments in every loop cycle.
This code demonstrates the .while block with labels and using the $cnt value:
counter = 0
.while counter < 21
.db low(EndLabel), high(Endlabel), $cnt
counter = counter + 3
EndLabel: .endwThe compiler translates the code to this:
.db #03, #80, #01
.db #06, #80, #02
.db #09, #80, #03
.db #0C, #80, #04
.db #0F, #80, #05
.db #12, #80, #06
.db #15, #80, #07Note: You can use many flavors for the
.endwblock closing statement..endw,endw,.wend, andwendare all accepted — with uppercase letters, too.
The FOR..NEXT Loop
You can use the traditional .for...next loop to create a loop:
.for myVar = 2 .to 5
.db 1 << int(myVar)
.nextThis loop uses the myVar variable as its iteration variable, which iterates from 1 to 4. As you expect, the compiler translates the for-loop into this:
.db #04
.db #08
.db #10
.db #20You can specify a .step close to change the loop increment value:
.for myVar = 1 .to 7 .step 2
.db 1 << int(myVar)
.nextNow, the code translates to this:
.db #02
.db #08
.db #20
.db #80You can create a loop with decrementing iteration variable value:
.for myVar = 7 .to 1 .step -2
.db 1 << int(myVar)
.nextAs you expect, now you get this translation:
.db #80
.db #20
.db #08
.db #02Note: As with the other statements, you can use the
.for,.to, and.stepkeywords without the.prefix, sofor,to, andstepare also valid.
The for-loop can do the same stunts as the other loops; it handles labels, symbols, and variables similarly. There’s only one exception: the loop iteration variable. If this variable is found in an outer scope, instead of using that value, the compiler raises an error. You can use the for-loop only with a freshly created variable.
So, both cases in this code raise an error:
myVar = 0
.for myVar = 1 .to 4 ; ERROR: Variable myVar is already declared
; ...
.next
.for _i = 1 .to 3
.for _i = 3 .to 8 ; ; ERROR: Variable _i is already declared
; ...
.next
.nextNote: As
iis a reserved token (it represents theIregister), you cannot useias a variable name. Nonetheless,_iis a valid variable name.
The for-loop works with both integer and float variables. If the initial value, the last value (the one after .to), or the increment value (the one after .step) is a float value, the for-loop uses float operations; otherwise, it uses integer operations.
This code snippet demonstrates the difference:
.for myVar = 1 .to 4 .step 1
.db 1 << myVar
.next
.for myVar = 1 .to 4 .step 1.4
.db 1 << myVar ; ERROR: Right operand of the shift left operator must be integral
.nextNonetheless, you can solve this issue by applying the int() function:
.for myVar = 1 .to 4 .step 1.4
.db 1 << int(myVar) ; Now, it's OK.
.nextNote: You can still use the
$cntvalue in for loops. Just like with other loops, it indicates the count of cycles starting from one and incremented by one in each iteration.
Maximum Loop Count
It’s pretty easy to create an infinite (or at least a very long) loop. For example, these loops are infinite ones:
.repeat
.until false
.while true
.wend The assembler checks the loop counter during compilation. Whenever it exceeds #FFFF (65535), it raises an error.
The IF..ELIF..ELSE..ENDIF Statement
You can use the .if statement to create branches with conditions. For example, this code emits inc b or inc c statement depending on whether the value of branch is even or odd:
.if branch % 2 == 0
inc b
.else
inc c
.endifYou do not have to specify an .else branch, so this statement is entirely valid:
.if branch % 2 == 0
inc b
.endifYou can nest if statements like this to manage four different code branches according to the value of branch:
.if branch == 1
inc b
.else
.if branch == 2
inc c
.else
.if branch == 3
inc d
.else
inc e
.endif
.endif
.endifNonetheless, you can use the .elif statement to create the code snippet above more clearly:
.if branch == 1
inc b
.elif branch == 2
inc c
.elif branch == 3
inc d
.else
inc e
.endifIF and Scopes
Unlike the loop statements, .if does not provide its local scope. Whenever you create a symbol, a label, or a variable, those get into the current scope. This code defines a label with the same name in each branch. Because the compiler evaluates the .if branches from top to down, it either compiles one of the .elif branches —
the first with a matching condition — or the else branch. Thus, this code does not define MyLabel twice:
branch = 4 ; Try to set up a different value
; Do something (omitted from code)
ld hl,MyLabel
.if branch == 1
inc b
MyLabel ld a,20
.elif branch > 2
MyLabel ld a,30
inc c
.elif branch < 6
inc d
MyLabel ld a,40
.else
MyLabel ld a,50
inc e
.endifGenerally, you can decorate any statement with labels. The .elif and .else statements are exceptions. If you do so, the compiler raises an error:
.if branch == 1
inc b
MyLabel ld a,20
.elif branch > 2
MyLabel ld a,30
inc c
Other .elif branch < 6 ; ERROR: ELIF section cannot have a label
inc d
MyLabel ld a,40
Another .else ; ERROR: ELSE section cannot have a label
MyLabel ld a,50
inc e
.endifIF Nesting
When you nest .if statements, ensure each has a corresponding .endif. Whenever the compiler finds an .endif, it associates it with the closest .if statement before .endif. Use indentation to make the structure more straightforward, as the following code snippet shows:
row = 2
col = 2
; Change row and col (omitted from code)
.if row == 0
.if col == 0
.db #00
.elif col == 1
.db #01
.else
.db #02
.endif
.elif row == 1
.if col == 0
.db #03
.elif col == 1
.db #04
.else
.db #05
.endif
.elif row == 2
.if col == 0
.db #06
.elif col == 1
.db #07
.else
.db #08
.endif
.else
.if col == 0
.db #09
.elif col == 1
.db #0A
.else
.db #0B
.endif
.endifThe IFUSED/IFNUSED Statements
Klive offers a similar construct to IF..ELIF..ELSE..ENDIF, using the IFUSED or IFNUSED statement instead of IF. These new statements are specialized forms of IF. You can use these statements to emit code depending on whether a symbol (label, .EQU, .VAR, structure, or structure field) exists and has already been used by the code preceding the IFUSED/IFNUSED statement.
Here are a few examples:
MyProc:
ld hl,#5800
ld (hl),a
ret
; some other code
.ifused MyProc
MyMsg: .defn "MyProc is used"
.else
MyMsg: .defn "MyProc is not used"
.endif
Main:
ld hl,MyMsgHere, the .ifused statement will set the string the MyMsg label point to according to whether the MyProc label is used. As in this case, MyProc is defined but not invoked before the .ifused statement, HL will point to the “MyProc is not used” message.
Should you call MyProc before .ifused, HL would point to the other message, “MyProc is used”:
MyProc:
ld hl,#5800
ld (hl),a
ret
; some other code
call MyProc
; some other code
.ifused MyProc
MyMsg: .defn "MyProc is used"
.else
MyMsg: .defn "MyProc is not used"
.endif
Main:
ld hl,MyMsgThe .ifnused statement is the complement of .ifused. It is evaluated to a true condition value only if the symbol following .ifnused is not defined or, if defined, is not used.
IFUSED/IFNUSED Syntax
You must specify a symbol after the .ifused or .ifnused keywords. These symbols must follow the syntax of identifiers. They can be compound names used for modules and structures. So, all of these symbol names are correct:
MyLabel
MyStruct
MyStruct.FieldX
MyModule.Main
::NestedModule.Start.MyProcNote: You can use these aliases for
.ifused:.IFUSED,ifused,IFUSED. Similarly,.ifnusedaccept alternative tokens:.IFNUSED,ifnused, andIFNUSED.
IFUSED/IFNUSED Semantics
The Klive Assembler accepts any .ifused and .ifnused statements until they are syntactically correct. When the assembler tests their condition, it works this way:
- If the specified symbol does not exist,
.ifusedevaluates to false, while.ifnusedevaluates to true. - If the particular symbol exists and is used in the code section preceding the
.ifusedor.ifnusedstatement,.ifusedevaluates to true, and.ifnusedto false. - If the particular symbol exists and it is not used in the code section preceding the
.ifusedor.ifnusedstatement,.ifusedevaluates to false,.ifnusedto true.
These statements do not support the look-ahead in the code. This behavior could lead to paradoxical situations, like in this example:
MyFlag = true
MyValue: .equ #1234
; some other code that does not use MyValue
.ifused MyValue
MyFlag = false;
.endif
; some other code that does not change MyFlag
.if MyFlag
ld a,MyValue
.endifShould .ifused work with look-ahead, this code would make the compiler scratch its virtual head. Because MyFlag is set to true, the .if statement at the bottom of the code would emit an ld a,MyValue instruction. The compiler would say that .ifused MyValue should be considered true. However, in this case, the body .ifused would set MyFlag to true, and that would prevent the bottom .if from emitting ld a,MyValue, and then MyValue would not be used at all.
Block Statements without a Closing Statement
The compiler automatically recognizes if a block does not have a closing statement and provides an error message accordingly.
Orphan Closing Statements
When the compiler finds a closing statement (such as .endw, .endl, .until, .endif, etc.) it will issue an error.
The BREAK statement
You can exit the loop — independently of the loop’s exit condition — with the .break statement:
; LOOP sample
.loop 5
.if $cnt == 4
.break
.endif
.db $cnt
.endl
; REPEAT sample
.repeat
.if $cnt == 4
.break
.endif
.db $cnt
.until $cnt == 5
; WHILE sample
.while $cnt < 5
.if $cnt == 4
.break
.endif
.db $cnt
.endw
; FOR-loop sample
.for value = 1 to 5
.if value == 4
.break
.endif
.db value
.nextBecause all these loops are exited at the beginning of the 4th iteration, they produce this output:
.db #01
.db #02
.db #03Note: You cannot use the
.breakstatement outside a loop construct. If you do so, the compiler raises an error.
The CONTINUE Statement
You can interrupt the current iteration of the loop and carry on the next iteration with the .continue statement:
; LOOP sample
.loop 5
.if $cnt == 4
.continue
.endif
.db $cnt
.endl
; REPEAT sample
.repeat
.if $cnt == 4
.continue
.endif
.db $cnt
.until $cnt == 5
; WHILE sample
.while $cnt <= 5
.if $cnt == 4
.continue
.endif
.db $cnt
.endw
; FOR-loop sample
.for value = 1 to 5
.if value == 4
.continue
.endif
.db value
.nextBecause all these loops skip the 4th iteration, they produce this output:
.db #01
.db #02
.db #03
; #04 is skipped
.db #05Note: You cannot use the
.continuestatement outside of a loop construct. If you do so, the Assembler raises an error.