mirror of
https://github.com/KarolS/millfork.git
synced 2026-04-25 19:17:54 +00:00
Rename documentation files
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
# Using assembly within Millfork programs
|
||||
|
||||
There are two ways to include raw assembly code in your Millfork programs:
|
||||
|
||||
* inline assembly code blocks
|
||||
|
||||
* whole assembly functions
|
||||
|
||||
## Assembly syntax
|
||||
|
||||
Millfork inline assembly uses the same three-letter opcodes as most other 6502 assemblers.
|
||||
Indexing syntax is also the same. Only instructions available on the current CPU architecture are available.
|
||||
|
||||
**Work in progress**:
|
||||
Currently, `RMBx`/`SMBx`/`BBRx`/`BBSx` and some extra 65CE02/HuC6280/65816 instructions are not supported yet.
|
||||
|
||||
Undocumented instructions are supported using various opcodes
|
||||
|
||||
Labels have to be followed by a colon and they can optionally be on a separate line.
|
||||
Indentation is not important:
|
||||
|
||||
first: INC x
|
||||
second:
|
||||
INC y
|
||||
INC z
|
||||
|
||||
|
||||
Label names have to start with a letter and can contain digits, underscores and letters.
|
||||
This means than they cannot start with a period like in many other assemblers.
|
||||
Similarly, anonymous labels designated with `+` or `-` are also not supported
|
||||
|
||||
Labels are global,
|
||||
which means that they live in the same namespace as functions, types and global variables.
|
||||
|
||||
Assembly can refer to variables and constants defined in Millfork,
|
||||
but you need to be careful with using absolute vs immediate addressing:
|
||||
|
||||
const byte fiveConstant = 5
|
||||
byte fiveVariable = 5
|
||||
|
||||
byte ten() {
|
||||
byte result
|
||||
asm {
|
||||
LDA #fiveConstant
|
||||
CLC
|
||||
ADC fiveVariable
|
||||
STA result
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
Any assembly opcode can be prefixed with `?`, which allows the optimizer change it or elide it if needed.
|
||||
Opcodes without that prefix will be always compiled as written.
|
||||
|
||||
You can insert macros into assembly, by prefixing them with `+` and using the same syntax as in Millfork:
|
||||
|
||||
macro void run(byte x) {
|
||||
output = x
|
||||
}
|
||||
|
||||
byte output @$c000
|
||||
|
||||
void main () {
|
||||
byte a
|
||||
a = 7
|
||||
asm {
|
||||
+ run(a)
|
||||
}
|
||||
}
|
||||
|
||||
Currently there is no way to insert raw bytes into inline assembly
|
||||
(required for certain optimizations and calling conventions).
|
||||
|
||||
## Assembly functions
|
||||
|
||||
Assembly functions can be declared as `macro` or not.
|
||||
|
||||
A macro assembly function is inserted into the calling function like an inline assembly block,
|
||||
and therefore usually it shouldn't end with `RTS` or `RTI`.
|
||||
|
||||
A non-macro assembly function should end with `RTS`, `JMP` or `RTI` as appropriate,
|
||||
or it should be an external function.
|
||||
|
||||
For both macro and non-macro assembly functions,
|
||||
the return type can be any valid return type, like for Millfork functions.
|
||||
If the size of the return type is one byte,
|
||||
then the result is passed via the accumulator.
|
||||
If the size of the return type is two bytes,
|
||||
then the low byte of the result is passed via the accumulator
|
||||
and the high byte of the result is passed via the X register.
|
||||
|
||||
|
||||
### Assembly function parameters
|
||||
|
||||
An assembly function can have parameters.
|
||||
They differ from what is used by Millfork functions.
|
||||
|
||||
Macro assembly functions can have the following parameter types:
|
||||
|
||||
* reference parameters: `byte ref paramname`: every occurrence of the parameter will be replaced with the variable given as an argument
|
||||
|
||||
* constant parameters: `byte const paramname`: every occurrence of the parameter will be replaced with the constant value given as an argument
|
||||
|
||||
For example, if you have:
|
||||
|
||||
inline asm void increase(byte ref v, byte const inc) {
|
||||
LDA v
|
||||
CLC
|
||||
ADC #inc
|
||||
STA v
|
||||
}
|
||||
|
||||
and call `increase(score, 10)`, the entire call will compile into:
|
||||
|
||||
LDA score
|
||||
CLC
|
||||
ADC #10
|
||||
STA score
|
||||
|
||||
Non-macro functions can only have their parameters passed via registers:
|
||||
|
||||
* `byte a`, `byte x`, `byte y`: a single byte passed via the given CPU register
|
||||
|
||||
* `word xa`, `word ax`, `word ay`, `word ya`, `word xy`, `word yx`: a 2-byte word byte passed via given two CPU registers, with the high byte passed through the first register and the low byte passed through the second register
|
||||
|
||||
Macro assembly functions can have maximum one parameter passed via a register.
|
||||
|
||||
### External functions
|
||||
|
||||
An external function should be declared with a defined memory address
|
||||
and the `extern` keyword instead of the body:
|
||||
|
||||
asm void putchar(byte a) @$FFD2 extern
|
||||
|
||||
## Safe assembly
|
||||
|
||||
Since assembly gives the programmer unlimited access to all machine features,
|
||||
certain assumptions about the code may be broken.
|
||||
In order to make assembly cooperate with the rest of the Millfork code,
|
||||
it should abide to the following rules:
|
||||
|
||||
* don't leave the D flag set
|
||||
|
||||
* don't jump between functions if either of functions has stack variables
|
||||
|
||||
* don't do `RTS` or `RTI` if the function has stack variables
|
||||
|
||||
* don't jump or call things that are not functions or labels
|
||||
|
||||
* don't store data in locations other than variables or arrays
|
||||
|
||||
* don't change the stack pointer
|
||||
|
||||
* end non-inline assembly functions with `RTS`, `JMP` or `RTI` as appropriate
|
||||
|
||||
* on NMOS 6502:
|
||||
|
||||
* don't use `XAA`, `LXA`, `AHX`, `SHX`, `SHY`, `LAS` and `TAS` instructions
|
||||
|
||||
* on 65816:
|
||||
|
||||
* keep the direct page register set to $0000
|
||||
|
||||
* keep the M and X flags set to 1 (8-bit registers by default, native mode)
|
||||
|
||||
* if running in the native mode, be careful with the stack pointer (you should keep it between $000100 and $0001FF)
|
||||
|
||||
* do not change the data page register (keep an eye at the `PLD`, `MVN`, `MVP` instructions)
|
||||
|
||||
* explicitly use 16-bit immediate operands when appropriate; the assembler doesn't track flags and assumes 8-bit immediates by default
|
||||
|
||||
* use far jumps unless you're sure that the called function returns with an `RTS`
|
||||
|
||||
* on 65CE02:
|
||||
|
||||
* keep the `B` register set to $00
|
||||
|
||||
* don't change the `E` flag
|
||||
|
||||
* on HuC6280
|
||||
|
||||
* don't use the `SET` instruction
|
||||
|
||||
The above list is not exhaustive.
|
||||
@@ -0,0 +1,45 @@
|
||||
# Function definitions
|
||||
|
||||
Syntax:
|
||||
|
||||
`[segment (<segment>)] [<modifiers>] <return_type> <name> ( <params> ) [@ <address>] { <body> }`
|
||||
|
||||
`[segment (<segment>)] asm <return_type> <name> ( <params> ) @ <address> extern`
|
||||
|
||||
* `<segment>`: segment name; if absent, then defaults to `default_code_segment` as defined for the platform
|
||||
|
||||
* `<modifiers>`: zero or more of the following:
|
||||
|
||||
* `asm` – the function is written in assembly, not in Millfork (obligatory for `extern` functions),
|
||||
see [Using assembly within Millfork programs#Assembly functions](./assembly.md#assembly-functions)
|
||||
|
||||
* `macro` – the function is a macro,
|
||||
see [Macros_and inlining#Macros](../abi/inlining.md#macros)
|
||||
|
||||
* `inline` – the function should preferably be inlined
|
||||
see [Macros_and inlining#Inlining](../abi/inlining.md#automatic_inlining.md)
|
||||
|
||||
* `noinline` – the function should never be inlined
|
||||
|
||||
* `interrupt` – the function is a hardware interrupt handler.
|
||||
You are not allowed to call such functions directly.
|
||||
The function cannot have parameters and the retrn type should be `void`.
|
||||
|
||||
* `kernal_interrupt` – the function is an interrupt handler called from a generic vendor-provider hardware interrupt handler.
|
||||
The hardware instruction handler is assumed to have preserved the CPU registers,
|
||||
so this function only has to preserve the zeropage pseudoregisters.
|
||||
An example is the Commodore 64 interrupt handler that calls the function at an address read from $314/$315.
|
||||
Unline hardware handlers with `interrupt`, you can treat functions with `kernal_interrupt` like normal functions.
|
||||
|
||||
* `<return_type>` is a valid return type, see [Types](./types.md)
|
||||
|
||||
* `<params>` is a comma-separated list of parameters, in form `type name`. Allowed types are the same as for local variables.
|
||||
|
||||
* `<address>` is a constant expression that defines where in the memory the function is or will be located.
|
||||
|
||||
* `extern` is a keyword than marks functions that are not defined in the current program,
|
||||
but are likely to be available at certain address in memory.
|
||||
Such functions should be marked as written in assembly and should have their parameters passed through registers.
|
||||
|
||||
* `<body>` is a newline-separated list of either Millfork or assembly statements
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
# Interfacing with external code
|
||||
|
||||
## Calling external functions at a static address
|
||||
|
||||
To call an external function, you need to declare it as `asm extern`. For example:
|
||||
|
||||
```
|
||||
asm void putchar(byte a) @$FFD2 extern
|
||||
```
|
||||
|
||||
The function parameter will be passed via the accumulator,
|
||||
the function itself is located in ROM at $FFD2. A call like this:
|
||||
|
||||
```
|
||||
putchar(13)
|
||||
```
|
||||
|
||||
will be compiled to something like this:
|
||||
|
||||
```
|
||||
LDA #13
|
||||
JSR $FFD2
|
||||
```
|
||||
|
||||
For more details about how to pass parameters to `asm` functions,
|
||||
see [Using assembly within Millfork programs#Assembly functions](./assembly.md#assembly-functions).
|
||||
|
||||
## Calling external functions at a dynamic address
|
||||
|
||||
To call a function that has its address calculated dynamically,
|
||||
you just need to do the same as what you would do in assembly:
|
||||
|
||||
```
|
||||
asm void call_function(byte a) {
|
||||
JMP (function_address)
|
||||
}
|
||||
```
|
||||
|
||||
where `function_address` is a variable that contains the address of the function to call.
|
||||
@@ -0,0 +1,46 @@
|
||||
# Literals and initializers
|
||||
|
||||
## Numeric literals
|
||||
|
||||
Decimal: `1`, `10`
|
||||
|
||||
Binary: `%0101`, `0b101001`
|
||||
|
||||
Quaternary: `0q2131`
|
||||
|
||||
Octal: `0o172`
|
||||
|
||||
Hexadecimal: `$D323`, `0x2a2`
|
||||
|
||||
## String literals
|
||||
|
||||
String literals are surrounded with double quotes and followed by the name of the encoding:
|
||||
|
||||
"this is a string" ascii
|
||||
|
||||
Characters between the quotes are interpreted literally,
|
||||
there are no ways to escape special characters or quotes.
|
||||
|
||||
Currently available encodings:
|
||||
|
||||
* `ascii` – standard ASCII
|
||||
|
||||
* `pet` or `petscii` – PETSCII (ASCII-like character set used by Commodore machines)
|
||||
|
||||
* `scr` – Commodore screencodes
|
||||
|
||||
When programming for Commodore,
|
||||
use `pet` for strings you're printing using standard I/O routines
|
||||
and `scr` for strings you're copying to screen memory directly.
|
||||
|
||||
|
||||
## Array initialisers
|
||||
|
||||
An array is initialized with either a string literal,
|
||||
or a list of byte literals and strings, surrounded by brackets:
|
||||
|
||||
array a = [1, 2]
|
||||
array b = "----" scr
|
||||
array c = ["hello world!" ascii, 13]
|
||||
|
||||
Trailing commas (`[1, 2,]`) are not allowed.
|
||||
@@ -0,0 +1,201 @@
|
||||
# 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.
|
||||
|
||||
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 it.
|
||||
|
||||
## Precedence
|
||||
|
||||
Millfork has different operator precedence compared to most other languages. From highest to lowest it goes:
|
||||
|
||||
* `*`, `*'`
|
||||
|
||||
* `+`, `+'`, `-`, `-'`, `|`, `&`, `^`, `>>`, `>>'`, `<<`, `<<'`, `>>>>`
|
||||
|
||||
* `:`
|
||||
|
||||
* `==`, `!=`, `<`, `>`, `<=`, `>=`
|
||||
|
||||
* `&&`
|
||||
|
||||
* `||`
|
||||
|
||||
* 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 `-`.
|
||||
|
||||
## Argument types
|
||||
|
||||
In the descriptions below, arguments to the operators are explained as follows:
|
||||
|
||||
* `byte` means any one-byte type
|
||||
|
||||
* `word` means any two-byte type, or a byte expanded to a word
|
||||
|
||||
* `long` means any type longer than two bytes, or a shorter type expanded to such length to match the other argument
|
||||
|
||||
* `constant` means a compile-time constant
|
||||
|
||||
* `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.
|
||||
|
||||
## 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)
|
||||
|
||||
There are no division, remainder or modulo operators.
|
||||
|
||||
## 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 and will not work on Ricoh CPU's.
|
||||
The compiler issues a warning if these operators appear in the code.
|
||||
|
||||
* `+'`, `-'`: decimal addition/subtraction
|
||||
`byte +' byte`
|
||||
`constant word +' constant word`
|
||||
`constant long +' constant long`
|
||||
`word +' word` (zpreg)
|
||||
|
||||
* `*'`: decimal multiplication
|
||||
`constant *' constant`
|
||||
|
||||
* `<<'`, `>>'`: decimal multiplication/division by power of two
|
||||
`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.
|
||||
|
||||
Note that currently in cases like `a < f() < b`, `f()` will be evaluated twice!
|
||||
|
||||
* `==`: equality
|
||||
`byte == byte`
|
||||
`simple word == simple word`
|
||||
`simple long == simple long`
|
||||
|
||||
* `!=`: inequality
|
||||
`byte != byte`
|
||||
`simple word != simple word`
|
||||
`simple long != simple long`
|
||||
|
||||
* `>`, `<`, `<=`, `>=`: inequality
|
||||
`byte > byte`
|
||||
`simple word > simple word`
|
||||
`simple long > simple long`
|
||||
|
||||
Currently, `>`, `<`, `<=`, `>=` operators perform unsigned comparison
|
||||
if none of the types of their arguments is signed,
|
||||
and fail to compile otherwise. This will be changed in the future.
|
||||
|
||||
## Assignment and in-place modification operators
|
||||
|
||||
* `=`: normal assignment
|
||||
`mutable byte = byte`
|
||||
`mutable word = word`
|
||||
`mutable long = long`
|
||||
|
||||
* `+=`, `+'=`, `|=`, `^=`, `&=`: modification in place
|
||||
`mutable byte += byte`
|
||||
`mutable word += word`
|
||||
`mutable long += long`
|
||||
|
||||
* `<<=`, `>>=`: shift in place
|
||||
`mutable byte <<= byte`
|
||||
`mutable word <<= byte`
|
||||
`mutable long <<= byte`
|
||||
|
||||
* `<<'=`, `>>'=`: decimal shift in place
|
||||
`mutable byte <<= constant byte`
|
||||
`mutable word <<= constant byte`
|
||||
`mutable long <<= constant byte`
|
||||
|
||||
* `-=`, `-'=`: subtraction in place
|
||||
`mutable byte -= byte`
|
||||
`mutable word -= simple word`
|
||||
`mutable long -= simple long`
|
||||
|
||||
* `*=`: multiplication in place
|
||||
`mutable byte *= constant byte`
|
||||
`mutable byte *= byte` (zpreg)
|
||||
|
||||
* `*'=`: decimal multiplication in place
|
||||
`mutable byte *'= constant byte`
|
||||
|
||||
## 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: an access to the `i`-th element of the array `a`
|
||||
|
||||
* when `a` is a pointer variable: an access to the byte in memory at address `a + i`
|
||||
|
||||
Those expressions are of type `byte`. If `a` is any other kind of expression, `a[i]` is invalid.
|
||||
|
||||
## 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.
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
# Reentrancy
|
||||
|
||||
A function is called reentrant,
|
||||
when its execution can be interrupted and the function can be then safely called again.
|
||||
|
||||
When programming in Millfork, you need to distinguish conceptually three kinds of reentrant functions:
|
||||
|
||||
* nesting-safe
|
||||
|
||||
* recursion-safe
|
||||
|
||||
* interrupt-safe
|
||||
|
||||
As Millfork is a middle-level language, it leaves taking care of those issues to the programmer.
|
||||
|
||||
## Nesting safety
|
||||
|
||||
Nesting occurs when a function is called when calculating parameters for another call of the same function:
|
||||
|
||||
f(f(4))
|
||||
f(0, f(1,1))
|
||||
f(g(f(5))
|
||||
f(g()) // where g calls f, directly or indirectly
|
||||
|
||||
Since parameters are passed via global variables,
|
||||
calling a function while preparing parameters for another call to the same function may cause undefined behaviour.
|
||||
|
||||
For that reason, a function is considered nesting-safe if it has maximum one parameter.
|
||||
|
||||
It is possible to make a safe nested call to a non-nesting safe function, provided two conditions are met:
|
||||
|
||||
* the function cannot modify its parameters
|
||||
|
||||
* the non-nested parameters have to have the same values in all co-occurring calls: `f(5, f(5, 6, 7), 7)`
|
||||
|
||||
In all other cases, the nested call may cause undefined behaviour.
|
||||
|
||||
## Recursion safety
|
||||
|
||||
A function is recursive if it calls itself, either directly or indirectly.
|
||||
|
||||
Since most automatic variables will be overwritten by the inner call, the function is recursive-safe if:
|
||||
|
||||
* parameters are no longer read after the recursive call is made
|
||||
|
||||
* an automatic variable is not read from without reinitialization after each recursive call
|
||||
|
||||
* all the other variables are stack variables
|
||||
|
||||
In all other cases, the recursive call may cause undefined behaviour.
|
||||
|
||||
The easiest, but suboptimal way to make a function recursion-safe is to make all local variables stack-allocated
|
||||
and assigning all parameters to variables as soon as possible. This is slow though, so don't do it unless really necessary.
|
||||
|
||||
## Interrupt safety
|
||||
|
||||
A function is interrupt-safe if it can be safely called, either directly or indirectly,
|
||||
simultaneously by the main code and by an interrupt routine.
|
||||
|
||||
The only way to make a function interrupt-safe is to have no parameters and make all local variables stack-allocated.
|
||||
|
||||
# Reentrancy safety violations
|
||||
|
||||
Each of the following things is a violation of reentrancy safety rules and will cause undefined behaviour with high probability:
|
||||
|
||||
* calling a non-nesting-safe function without extra precautions as above while preparing another call to that function
|
||||
|
||||
* calling a non-recursion-safe function from within itself recursively
|
||||
|
||||
* calling a non-interrupt-safe function from both the main code and an interrupt
|
||||
@@ -0,0 +1,222 @@
|
||||
# Syntax
|
||||
|
||||
For information about types, see [Types](./types.md).
|
||||
For information about literals, see [Literals](./literals.md).
|
||||
For information about assembly, see [Using assembly within Millfork programs](./assembly.md).
|
||||
|
||||
## Comments
|
||||
|
||||
Comments start with `//` and last until the end of line.
|
||||
|
||||
## Declarations
|
||||
|
||||
|
||||
### Variable declarations
|
||||
|
||||
A variable declaration can happen at either top level of a file (*global* variables),
|
||||
or a top level of a function (*local* variables).
|
||||
|
||||
Syntax:
|
||||
|
||||
`[segment(<segment>)] [<storage>] <type> <name> [@<address>] [= <initial_value>]`
|
||||
|
||||
* `<segment>`: segment name; if absent, then defaults to `default`.
|
||||
|
||||
* `<storage>` can be only specified for local variables. It can be either `stack`, `static`, `register` or nothing.
|
||||
`register` is only a hint for the optimizer.
|
||||
See [the description of variable storage](../abi/variable-storage.md).
|
||||
|
||||
* `<address>` is a constant expression that defines where in the memory the variable will be located.
|
||||
If not specified, it will be located according to the usual allocation rules.
|
||||
`stack` variables cannot have a defined address.
|
||||
|
||||
* `<initial_value>` is a constant expression that contains the initial value of the variable.
|
||||
Only global variables can be initialized that way.
|
||||
The behaviour is undefined when targeting a ROM-based platform.
|
||||
|
||||
### Constant declarations
|
||||
|
||||
`const <type> <name> = <value>`
|
||||
|
||||
TODO
|
||||
|
||||
### Array declarations
|
||||
|
||||
An array is a continuous sequence of bytes in memory.
|
||||
|
||||
Syntax:
|
||||
|
||||
`[segment(<segment>)] array <name> [[<size>]] [@<address>] [= <initial_values>]`
|
||||
|
||||
* `<segment>`: segment name; if absent,
|
||||
then defaults to `default_code_segment` as defined for the platform if the array has initial values,
|
||||
or to `default` if it doesn't.
|
||||
|
||||
TODO
|
||||
|
||||
### Function declarations
|
||||
|
||||
A function can be declared at the top level. For more details, see [Functions](./functions.md)
|
||||
|
||||
## `import` statements
|
||||
|
||||
TODO
|
||||
|
||||
## Statements
|
||||
|
||||
### Expression statement
|
||||
|
||||
TODO
|
||||
|
||||
### `if` statement
|
||||
|
||||
Syntax:
|
||||
|
||||
```
|
||||
if <expression> {
|
||||
<body>
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
if <expression> {
|
||||
<body>
|
||||
} else {
|
||||
<body>
|
||||
}
|
||||
```
|
||||
|
||||
### `return` statement
|
||||
|
||||
Syntax:
|
||||
|
||||
```
|
||||
return
|
||||
```
|
||||
```
|
||||
return <expression>
|
||||
```
|
||||
|
||||
### `return[]` statement (return dispatch)
|
||||
|
||||
Syntax examples:
|
||||
|
||||
```
|
||||
return [a + b] {
|
||||
0 @ underflow
|
||||
255 @ overflow
|
||||
default @ nothing
|
||||
}
|
||||
```
|
||||
```
|
||||
return [getF()] {
|
||||
1 @ function1
|
||||
2 @ function2
|
||||
default(5) @ functionDefault
|
||||
}
|
||||
```
|
||||
```
|
||||
return [i] (param1, param2) {
|
||||
1,5,8 @ function1(4, 6)
|
||||
2 @ function2(9)
|
||||
default(0,20) @ functionDefault
|
||||
}
|
||||
```
|
||||
|
||||
Return dispatch calculates the value of an index, picks the correct branch,
|
||||
assigns some global variables and jumps to another function.
|
||||
|
||||
The index has to evaluate to a byte. The functions cannot be `macro` and shouldn't have parameters.
|
||||
Jumping to a function with parameters gives those parameters undefined values.
|
||||
|
||||
The functions are not called, so they don't return to the function the return dispatch statement is in, but to its caller.
|
||||
The return values are passed along. If the dispatching function has a non-`void` return type different that the type
|
||||
of the function dispatched to, the return value is undefined.
|
||||
|
||||
If the `default` branch exists, then it is used for every missing index value between other supported values.
|
||||
Optional parameters to `default` specify the maximum, or both the minimum and maximum supported index value.
|
||||
In the above examples: the first example supports values 0–255, second 1–5, and third 0–20.
|
||||
|
||||
If the index has an unsupported value, the behaviour is formally undefined, but in practice the program will simply crash.
|
||||
|
||||
Before jumping to the function, the chosen global variables will be assigned parameter values.
|
||||
Variables have to be global byte-sized. Some simple array indexing expressions are also allowed.
|
||||
Parameter values have to be constants.
|
||||
For example, in the third example one of the following will happen:
|
||||
|
||||
* if `i` is 1, 5 or 8, then `param1` is assigned 4, `param2` is assigned 6 and then `function1` is called;
|
||||
|
||||
* if `i` is 2, then `param1` is assigned 9, `param2` is assigned an undefined value and then `function2` is called;
|
||||
|
||||
* if `i` is any other value from 0 to 20, then `param1` and `param2` are assigned undefined values and then `functionDefault` is called;
|
||||
|
||||
* if `i` has any other value, then undefined behaviour.
|
||||
|
||||
### `while` and `do-while` statements
|
||||
|
||||
Syntax:
|
||||
|
||||
```
|
||||
while <expression> {
|
||||
<body>
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
do {
|
||||
<body>
|
||||
} while <expression>
|
||||
```
|
||||
|
||||
### `for` statements
|
||||
|
||||
**Warning: `for` loops are a bit buggy.**
|
||||
|
||||
Syntax:
|
||||
|
||||
```
|
||||
for <variable>,<start>,<direction>,<end> {
|
||||
}
|
||||
```
|
||||
|
||||
* `<variable>` – an already defined numeric variable
|
||||
|
||||
* `<direction>` – the range to traverse:
|
||||
|
||||
* `to` – from `<start>` inclusive to `<end>` inclusive, in ascending order
|
||||
(e.g. `0,to,9` to traverse 0, 1,... 9)
|
||||
|
||||
* `downto` – from `<start>` inclusive to `<end>` inclusive, in descending order
|
||||
(e.g. `9,downto,0` to traverse 9, 8,... 0)
|
||||
|
||||
* `until` – from `<start>` inclusive to `<end>` exclusive, in ascending order
|
||||
(e.g. `0,until,10` to traverse 0, 1,... 9)
|
||||
|
||||
* `parallelto` – the same as `to`, but the iterations may be executed in any order
|
||||
|
||||
* `paralleluntil` – the same as `until`, but the iterations may be executed in any order
|
||||
|
||||
There is no `paralleldownto`, because it would do the same as `parallelto`.
|
||||
|
||||
### `break` and `continue` statements
|
||||
|
||||
Syntax:
|
||||
|
||||
```
|
||||
break
|
||||
break for
|
||||
break while
|
||||
break do
|
||||
break <variable>
|
||||
continue
|
||||
continue for
|
||||
continue while
|
||||
continue do
|
||||
continue <variable>
|
||||
```
|
||||
|
||||
### `asm` statements
|
||||
|
||||
See [Using assembly within Millfork programs](./assembly.md).
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
# Types
|
||||
|
||||
Millfork puts extra limitations on which types can be used in which contexts.
|
||||
|
||||
## Numeric types
|
||||
|
||||
* `byte` – 1-byte value of undefined signedness, defaulting to unsigned
|
||||
|
||||
* `word` – 2-byte value of undefined signedness, defaulting to unsigned
|
||||
|
||||
* `long` – 4-byte value of undefined signedness, defaulting to unsigned
|
||||
|
||||
* `sbyte` – signed 1-byte value
|
||||
|
||||
* `ubyte` – unsigned 1-byte value
|
||||
|
||||
* `pointer` – the same as `word`, but variables of this type default to be zero-page-allocated
|
||||
and you can index `pointer` variables (not arbitrary `pointer`-typed expressions though, `f()[0]` won't compile)
|
||||
|
||||
Functions cannot return types longer than 2 bytes.
|
||||
|
||||
Numeric types can be converted automatically:
|
||||
|
||||
* from a smaller type to a bigger type (`byte`→`word`)
|
||||
|
||||
* from a type of undefined signedness to a type of defined signedness (`byte`→`sbyte`)
|
||||
|
||||
* from a type of defined signedness to a type of undefined signedness (`sbyte`→`byte`)
|
||||
|
||||
## Boolean types
|
||||
|
||||
TODO
|
||||
|
||||
## Special types
|
||||
|
||||
* `void` – a unit type containing no information, can be only used as a return type for a function.
|
||||
Reference in New Issue
Block a user