Klive Z80 Assembler
Expressions

Expressions

The Klive Assembler has a rich syntax for evaluating expressions. You can use the same syntax with the #if directives, the Z80 instructions, and the compiler statements.

You can use operands and operators just like in most programming languages. Nevertheless, the Klive implementation has its particular way of evaluating expressions:

  • Expressions can be one of these types:

    • Booleans (true or false)
    • integers (64-bit)
    • floating point numbers (64-bit precision)
    • strings (with 8-bit characters)
  • The assembler applies implicit conversion whenever it's possible.

    • Floating point numbers are truncated to integer values.
    • The true Boolean literal is represented with the integer value 1; false with 0.
    • When the assembler needs a Boolean value, 0 is considered false, and any other values as true.
    • There is no implicit conversion between strings and any numeric values.
  • When the compiler needs a 16-bit value (for example, ld hl,NNNN), it uses the rightmost 16 bits of an expression's value.

  • When a Z80 operation (for example, ld a,NN) needs an 8-bit value, it utilizes the rightmost 8 bits.

  • Besides the parentheses — ( and ) — you can use square brackets — [ and ] — to group operations and change operator precedence.

; This is valid
ld hl,(Offset+#20)*2+BaseAddr

; Just like this
ld hl,[Offset+#20]*2+BaseAddr

Instant and Late Expression Evaluation

Depending on the context in which an expression is used, the compiler evaluates it instantly or decides to postpone the evaluation. For example, when you use the .org pragma, the compiler applies immediate evaluation. Let's assume this is your code:

Start: .org #8000 + Later
; code body (omitted)
Later: .db #ff

The value of Later depends on the address in .org, and the .org address depends on Later, so this declaration could not be adequately resolved; it's like a deadlock. The .org pragma would raise an error to avoid such situations, as at the moment of its evaluation, the Later symbol's value is unknown.

For most Z80 instructions, the compiler uses late evaluation:

Start: .org #6000
    ld hl,(MyVar)
    ; code body omitted
    ret
MyVar: .defs 2

When the compiler reaches the ld hl,(MyVar) instruction, it does not know the value of MyVar. Nonetheless, it does not stop with an error but generates the machine code for ld hl,(0), namely #21, #00, and #00; takes a note (it is called a fixup) when MyVal gets a value, the two #00 bytes generated at address #6001 should be updated accordingly.

Operands

You can use the following operands in expressions:

  • Boolean, Decimal and hexadecimal literals
  • Character literals
  • Identifiers
  • The current assembly address

Note: String literals cannot be used as operands.

Operators

You can use about a dozen operators, including unary, binary, and ternary. In this section, you will learn about them. They will be introduced in descending order of their precedence.

Conditional Operator

The assembler supports using only one ternary operator, the conditional operator:

conditional-expression ? true-value : false-value

This operation results in -1:

2 > 3 ? 2 : -1

When the conditional-expression evaluates to true, the operation results in true-value; otherwise in false-value.

Note: Conditional expressions are evaluated from right to left, unlike binary operators, which use left-to-right evaluation.

Binary Bitwise Operators

Operator tokenPrecedenceDescription
``1
^2Bitwise XOR
&3Bitwise AND — string concatenation with new line

Note: The & operator can be applied to two strings. If you do so, the compiler concatenates the two strings and puts a \r\n (new line) character pair between them.

Relational Operators

Operator tokenPrecedenceDescription
==4Equality
!=4Non-equality
<5Less than
<=5Less than or equal
>5Greater than
>=5Greater than or equal

Shift Operators

The bits of the left operand are shifted by the number of bits given by the right operand.

Operator tokenPrecedenceDescription
<<6Shift left
>>6Shift right

Basic Arithmetic Operators

Operator tokenPrecedenceDescription
+7Addition — string concatenation
-7Subtraction
*8Multiplication
/8Division
%8Modulo calculation

Min-Max operators

Operator tokenPrecedenceDescription
<?9Minimum of the left and right operand
>?9Maximum of the left and right operand

Unary operators

Operator tokenPrecedenceDescription
+10Unary plus
-10Unary minus
~10Unary bitwise NOT
!10Unary logical NOT

Do not forget, you can change the default precedence with ( and ), or with [ and ].

Functions

The Z80 assembler provides a number of functions that can have zero, one, or more arguments. Several functions (for example as rnd()) have overloads with different signatures. Each function has a name and a parameter list wrapped into parentheses, the parameters are separated by a comma. Of course, parameters can be expressions, and they may invoke other functions, too. Here are a few samples:

length("Hello" + " world")
max(value1, value2)
sin(pi()/2)
sqrt(pear + 3.0)

The Klive support these function signatures:

SignatureValueDescription
abs(integer)integerThe absolute value of an integer number.
abs(float)floatThe absolute value of a float number.
acos(float)floatThe angle whose cosine is the specified number.
asin(float)floatThe angle whose sine is the specified number.
atan(float)floatThe angle whose tangent is the specified number.
atan2(float, float)floatThe angle whose tangent is the quotient of two specified numbers.
attr(integer, integer, boolean, boolean)integerRetrieves the color attribute byte value defined by ink (first argument, 0 to 7), paper (second argument, 0 to 7), bright (third argument, 0 - non-zero), and flash (fourth argument, 0 - non-zero). The bright and flash values are optional.
attraddr(integer, integer)integerReturns the memory address of the byte specified screen attribute in the given line (first argument, from top to bottom, 0-192) and column (second argument, from left to right, 0-255).
bright(boolean)integerRetrieves the bright flag defined by the attribute (0 - non-zero). It can be ORed to create a color attribute value.
ceiling(float)floatThe smallest integral value greater than or equal to the specified number.
cos(float)floatThe cosine of the specified angle.
cosh(float)floatThe hyperbolic cosine of the specified angle.
exp(float)floate raised to the specified power.
fill(string, integer)stringCreates a new string by concatenating the specified one with the given times.
flash(boolean)integerRetrieves the flash flag defined by the argument (0 - non-zero). It can be ORed to create a color attribute value.
floor(float)floatThe largest integer less than or equal to the specified number.
frac(float)floatThe fractional part of the specified number.
high(integer)integerThe leftmost 8 bits (MSB) of a 16-bit integer number.
ink(integer)integerRetrieves the three ink bits defined by the color argument (0 to 7). It can be ORed to create a color attribute value.
int(float)integerThe integer part of the specified number.
lcase(string)stringThe lowercase version of the input string.
left(string, integer)stringTakes the leftmost characters of the string with the length specified.
len(string)integerThe length of the specified string.
length(string)integerThe length of the specified string.
log(float)floatThe natural (base e) logarithm of a specified number.
log(float, float)floatThe logarithm of a specified number in a specified base.
log10(float)floatThe base 10 logarithm of a specified number.
low(integer)integerThe rightmost 8 bits (LSB) of an integer number.
lowercase(string)stringThe lowercase version of the input string.
max(integer, integer)integerThe larger of two integer numbers.
max(float, float)floatThe larger of two float numbers.
min(integer, integer)integerThe smaller of two integer numbers.
min(float, float)floatThe smaller of two float numbers.
nat()floatRepresents the natural logarithmic base, specified by the constant, e.
paper(integer)integerretrieves the three paper bits defined by the argument (0 to 7). It can be ORed to create a color attribute value.
pi()floatRepresents the ratio of the circumference of a circle to its diameter, specified by the constant, π.
pow(float, float)floatThe specified number raised to the specified power.
right(string, integer)stringTakes the rightmost characters of the string with the length specified.
round(float)floatRounds a float value to the nearest integral value.
round(float, int)floatRounds a float value to a specified number of fractional digits.
rnd()integerReturns a random 32-bit number.
rnd(integer, integer)integerReturns a random 32-bit integer between the first and second number.
scraddr(integer, integer)integerRetrieves the memory address of the screen pixel byte in the specified line (first argument, from top to bottom, 0-192) and in the specified column (second argument, from left to right, 0-255).
sign(integer)integerReturns an integer that indicates the sign of an integer number.
sign(float)integerReturns an integer that indicates the sign of a float number.
sin(float)floatThe sine of the specified angle.
sinh(float)floatThe hyperbolic sine of the specified angle.
sqrt(float)floatThe square root of a specified number.
str(bool)stringConvert the input value to a string.
str(integer)stringConvert the input value to a string.
str(float)stringConvert the input value to a string.
substr(string, integer, integer)stringTakes a substring of the specified string from the given position (zero-based) and length.
tan(float)floatThe tangent of the specified angle.
tanh(float)floatThe hyperbolic tangent of the specified angle.
truncate(float)integerCalculates the integral part of a specified number.
ucase(string)stringThe uppercase version of the input string.
uppercase(string)stringThe uppercase version of the input string.
word(integer)integerThe rightmost 16 bits of an integer number.

Functions have the same precedence as the unary operators (such as the unary + and -).

Parse Time Functions

The compiler provides a construct, parse time functions. These functions can receive a Z80 assembly language token and transform them into other language constructs. As the name suggests, these functions run in the parsing phase before the compiler emits code.

The lreg() and hreg() Parse Time Functions

These functions accept a 16-bit register pair token (BC, DE, HL, IX, or IY) and retrieve the lower or higher 8-bit register half of their input. Here is a sample code snippet:

ld a,lreg(bc)
ld c,hreg(hl)
ld a,lreg(ix)
ld l,hreg(de)

The compiler sees as if you wrote this:

ld a,c
ld c,h
ld a,ixl
ld l,d

The textof() Parse Time Function

You can use textof(), which accepts these kinds of tokens: mnemonic, register, register indirection, C port, or condition. This function translates these tokens into uppercase string constants that represent them. Here is a sample:

.dm textof(ldir)
.dm textof(bc)
.dm textof((de))
.dm textof((c))
.dm textof(nz)

The compiler sees as if you wrote this code:

.dm "LDIR"
.dm "BC"
.dm "(DE)"
.dm "(C)"
.dm "NZ"