1
0
mirror of https://github.com/KarolS/millfork.git synced 2024-12-25 21:29:25 +00:00
millfork/docs/lang/operators.md
2020-10-05 22:20:08 +02:00

325 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[< back to index](../doc_index.md)
# Operators
Unlike in high-level languages, operators in Millfork have limited applicability.
Not every well-formed expression is actually compilable.
Most expressions involving single bytes compile,
but for larger types usually you need to use in-place modification operators.
Further improvements to the compiler may increase the number of acceptable combinations.
On 6502-like targets, certain expressions require the commandline flag `-fzp-register` (`.ini` equivalent: `zeropage_register`) to be enabled.
They will be marked with (zpreg) next to them.
The flag is enabled by default, but you can disable it if you need to.
## Precedence
Millfork has different operator precedence compared to most other languages. From highest to lowest it goes:
* `->` and `[]`
* `*`, `*'`, `/`, `%%`
* `+`, `+'`, `-`, `-'`, `|`, `&`, `^`, `>>`, `>>'`, `<<`, `<<'`, `>>>>`
* `:`
* `==`, `!=`, `<`, `>`, `<=`, `>=`
* `&&`
* `||`
* assignment and in-place modification operators
You cannot use two different operators at the same precedence levels without using parentheses to disambiguate.
It is to prevent confusion about whether `a + b & c << d` means `(a + b) & (c << d)` `((a + b) & c) << d` or something else.
The only exceptions are `+` and `-`, and `+'` and `-'`.
They are interpreted as expected: `5 - 3 + 2 == 4` and `5 -' 3 +' 2 == 4`.
Note that you cannot mix `+'` and `-'` with `+` and `-`.
Certain operators (`/`, `%%`, `<<`, `>>`, `<<'`, `>>'`, `>>>>`, `:`, `!=`) cannot have more than 2 parameters,
i.e. `x / y / z` will not compile.
The decimal operators have two different forms:
* apostrophe form (e.g. `+'`) the original one, to be deprecated in the future, may be removed in Millfork 0.4
* dollar form (e.g. `$+`) available since Millfork 0.3.22
## Argument types
In the descriptions below, arguments to the operators are explained as follows:
* `enum` means any enumeration type
* `byte` means any numeric one-byte type
* `unsigned byte` means any numeric one-byte type that is not signed
* `word` means any numeric two-byte type, or a byte expanded to a word; `pointer` is considered to be numeric
* `long` means any numeric type longer than two bytes, or a shorter type expanded to such length to match the other argument
* `constant` means a compile-time constant
* `trivial` means either a constant or a non-stack variable
* `simple` means either: a constant, a non-stack variable,
a pointer indexed with a constant, a pointer indexed with a non-stack variable,
an array indexed with a constant, an array indexed with a non-stack variable,
an array indexed with a sum of a constant and a non-stack variable,
or a split-word expression made of two simple expressions.
Examples: `1`, `a`, `p[2]`, `p[i]`, `arr[2]`, `arr[i]`, `arr[i+2]`, `h:l`, `h[i]:l[i]`
Such expressions have the property that the only register they may clobber is Y.
* `mutable` means an expression that can be assigned to
## Split-word operator
Expressions of the shape `h:l` where `h` and `l` are of type byte, are considered expressions of type word.
If and only if both `h` and `l` are assignable expressions, then `h:l` is also an assignable expression.
## Indirect field access operator
`->`
TODO
## Binary arithmetic operators
* `+`, `-`:
`byte + byte`
`constant word + constant word`
`constant long + constant long`
`constant word + byte`
`word + word` (zpreg)
* `*`: multiplication; the size of the result is the same as the size of the arguments
`byte * constant byte`
`constant byte * byte`
`constant word * constant word`
`constant long * constant long`
`byte * byte` (zpreg)
`word * byte` (zpreg)
`byte * word` (zpreg)
`word * word` (zpreg)
* `/`, `%%`: unsigned division and unsigned modulo
`unsigned byte / unsigned byte` (zpreg)
`word / unsigned byte` (zpreg)
`word / word` (zpreg)
`constant word / constant word`
`constant long / constant long`
## Bitwise operators
* `|`, `^`, `&`: OR, EXOR and AND
`byte | byte`
`constant word | constant word`
`constant long | constant long`
`word | word` (zpreg)
* `<<`, `>>`: bit shifting; shifting pads the result with zeroes
`byte << byte`
`word << byte` (zpreg)
`constant word << constant byte`
`constant long << constant byte`
* `>>>>`: shifting a 9-bit value and returning a byte; `a >>>> b` is equivalent to `(a & $1FF) >> b`
`word >>>> constant byte`
## Decimal arithmetic operators
These operators work using the decimal arithmetic (packed BCD).
On Ricoh-based targets (e.g. Famicom) they require the zeropage register to have size at least 4
* `+'`, `-'`: decimal addition/subtraction
`$+`, `$-` (since Millfork 0.3.22)
`byte +' byte`
`constant word +' constant word`
`constant long +' constant long`
`word +' word` (zpreg)
* `*'`: decimal multiplication
`$*` (since Millfork 0.3.22)
`constant *' constant`
* `<<'`, `>>'`: decimal multiplication/division by power of two
`$<<`, `$>>` (since Millfork 0.3.22)
`byte <<' constant byte`
## Comparison operators
These operators (except for `!=`) can accept more than 2 arguments.
In such case, the result is true if each comparison in the group is true.
Note you cannot mix those operators, so `a <= b < c` is not valid.
**WARNING:** Currently in cases like `a < f() < b`, `f()` may be evaluated an undefined number of times
(the current implementation calls it twice, but do not rely on this behaviour).
The `==` and `!=` operators also work for non-arithmetic types.
* `==`: equality
`enum == enum`
`byte == byte`
`simple word == simple word`
`word == constant`
`simple word == word` (zpreg)
`word == simple word` (zpreg)
`simple long == simple long`
* `!=`: inequality
`enum != enum`
`byte != byte`
`simple word != simple word`
`word != constant`
`simple word != word` (zpreg)
`word != simple word` (zpreg)
`simple long != simple long`
* `>`, `<`, `<=`, `>=`: inequality
`byte > byte`
`simple word > simple word`
`simple word > word` (zpreg)
`word > simple word` (zpreg)
`simple long > simple long`
Currently, `>`, `<`, `<=`, `>=` operators perform signed comparison
if any of the types of their arguments is signed,
and unsigned comparison otherwise.
## Assignment and in-place modification operators
**WARNING:** Unlike other languages, Millfork does not provide any guarantees about how many times the left hand side will be evaluated.
An expression of form `a[f()] += b` may call `f` an undefined number of times.
* `=`: normal assignment
`mutable enum = enum`
`mutable byte = byte`
`mutable word = word`
`mutable long = long`
* `+=`, `+'=`, `|=`, `^=`, `&=`: modification in place
`$+=` (since Millfork 0.3.22)
`mutable byte += byte`
`mutable word += word`
`mutable trivial long += long`
* `<<=`, `>>=`: shift in place
`mutable byte <<= byte`
`mutable word <<= byte`
`mutable trivial long <<= byte`
* `<<'=`, `>>'=`: decimal shift in place
`$<<=`, `$>>=` (since Millfork 0.3.22)
`mutable byte <<'= constant byte`
`mutable word <<'= constant byte`
`mutable trivial long <<'= constant byte`
* `-=`, `-'=`: subtraction in place
`$-=` (since Millfork 0.3.22)
`mutable byte -= byte`
`mutable word -= simple word`
`mutable trivial long -= simple long`
* `*=`: multiplication in place
`mutable byte *= constant byte`
`mutable byte *= byte` (zpreg)
`mutable word *= unsigned byte` (zpreg)
`mutable word *= word` (zpreg)
* `*'=`: decimal multiplication in place
`$*=` (since Millfork 0.3.22)
`mutable byte *'= constant byte`
* `/=`, `%%=`: unsigned division and modulo in place
`mutable unsigned byte /= unsigned byte` (zpreg)
`mutable word /= unsigned byte` (zpreg)
`mutable word /= word` (zpreg)
There are no `||=`, `&&=` or `>>>>=` operators.
## Indexing
While Millfork does not consider indexing an operator, this is a place as good as any to discuss it.
An expression of form `a[i]`, where `i` is an expression of type `byte`, is:
* when `a` is an array that has numeric index type and `T` value type:
an access to the `i`-th element of the array `a`
* when `a` is a raw pointer variable:
an access to the byte in memory at address `a + i`
* when `a` is a typed pointer variable to a 1-byte type `T`:
an access to the value pointed to by `a`
* when `a` is a typed pointer variable to a 2-byte type `T` and `i` is zero:
an access to the value pointed to by `a`
* otherwise: a compile error
On 8080-like targets, and on 6502 if the zeropage register is enabled, `i` can also be of type `word`.
An expression of form `a[i]`, where `i` is an expression of a enumeration type, is:
* when `a` is an array that has index type equal to the type of `i`:
an access to the element of the array `a` at the location assigned to the key `i`
* otherwise: a compile error
Note that you cannot access a whole array element if it's bigger than 2 bytes (except in a simple assignment),
but you can access its fields or take its pointer:
array(int32) a[6]
a[2] // not ok
a[2] = 4 // ok, assignments are an exception
x = a[2] // ok, assignments are an exception
a[2].b0 // ok
a[2].loword // ok
a[2].pointer // ok
a[2].addr // ok
a[2].b0.addr // ok, equal to the above on little-endian targets
## Built-in functions
* `not`: negation of a boolean expression
`not(bool)`
* `nonet`: expansion of an 8-bit operation to a 9-bit operation
`nonet(byte + byte)`
`nonet(byte +' byte)`
`nonet(byte << constant byte)`
`nonet(byte <<' constant byte)`
Other kinds of expressions than the above (even `nonet(byte + byte + byte)`) will not work as expected.
* `hi`, `lo`: most/least significant byte of a word
`hi(word)`
Furthermore, any type that can be assigned to a variable can be used to convert
either from one type either to another type of the same size,
or from a 1-byte integer type to a compatible 2-byte integer type.
`byte``word`
`word``pointer`
some enum → `byte`
`byte` → some enum
but not
`word``byte`
some enum → `word`
* `sizeof`: size of the argument in bytes; the argument can be an expression or a type,
and the result is a constant of either `byte` or `word` type, depending on the actual value.
In case of aligned types, this returns the aligned size.
* `call`: calls a function via a pointer;
the first argument is the pointer to the function;
the second argument, if present, is the argument to the called function.
The function can have max one parameter, of size max 2 bytes, and may return a value of size max 2 bytes.
You can't create typed pointers to other kinds of functions anyway.
If the pointed-to function returns a value, then the result of `call(...)` is the result of the function.
Using `call` on 6502 targets requires at least 4 bytes of zeropage pseudoregister.