mirror of
https://github.com/KarolS/millfork.git
synced 2024-06-25 19:29:49 +00:00
Preprocessor. Z80 improvements. Library improvements.
This commit is contained in:
parent
35f3638a4f
commit
215d8d92b4
|
@ -10,18 +10,25 @@
|
||||||
|
|
||||||
* Added aliases.
|
* Added aliases.
|
||||||
|
|
||||||
|
* Added preprocessor
|
||||||
|
|
||||||
* Automatic selection of text encoding based on target platform.
|
* Automatic selection of text encoding based on target platform.
|
||||||
|
|
||||||
* **Potentially breaking change!** `scr` now refers to the default screencodes as defined for the platform.
|
* **Potentially breaking change!** `scr` now refers to the default screencodes as defined for the platform.
|
||||||
Code that uses both a custom platform definition and the `scr` encoding needs attention
|
Code that uses both a custom platform definition and the `scr` encoding needs attention
|
||||||
(either change `scr` to `petscr` or add `screen_encoding=petscr` in the platform definition file).
|
(either change `scr` to `petscr` or add `screen_encoding=petscr` in the platform definition file).
|
||||||
|
|
||||||
|
* **Potentially breaking change!** Platform definitions now need appropriate feature definitions.
|
||||||
|
Code that uses a custom platform definitions will cause extra warnings until fixed.
|
||||||
|
|
||||||
* Optimizations for stack variables.
|
* Optimizations for stack variables.
|
||||||
|
|
||||||
* Fixed emitting constant decimal expressions.
|
* Fixed emitting constant decimal expressions.
|
||||||
|
|
||||||
* Parser performance improvement.
|
* Parser performance improvement.
|
||||||
|
|
||||||
|
* Standard libraries improvements.
|
||||||
|
|
||||||
* Other improvements.
|
* Other improvements.
|
||||||
|
|
||||||
## 0.3.0
|
## 0.3.0
|
||||||
|
|
|
@ -34,6 +34,7 @@ no extension for BBC micro program file,
|
||||||
|
|
||||||
* `-r <program>` – Run given program after successful compilation. Useful for automatically launching emulators without any external scripting.
|
* `-r <program>` – Run given program after successful compilation. Useful for automatically launching emulators without any external scripting.
|
||||||
|
|
||||||
|
* `-D <feature>=<value>` – Defines a feature value for the preprocessor.
|
||||||
|
|
||||||
## Verbosity options
|
## Verbosity options
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,10 @@ Default: the same as `encoding`.
|
||||||
|
|
||||||
* `lenient_encoding` - allow for automatic substitution of invalid characters in string literals using the default encodings, default is `false`.
|
* `lenient_encoding` - allow for automatic substitution of invalid characters in string literals using the default encodings, default is `false`.
|
||||||
|
|
||||||
|
#### `[define]` section
|
||||||
|
|
||||||
|
This section defines values of features of the target.
|
||||||
|
See the [preprocessor documentation](../lang/preprocessor.md) for more info.
|
||||||
|
|
||||||
#### `[allocation]` section
|
#### `[allocation]` section
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
|
|
||||||
## Language reference
|
## Language reference
|
||||||
|
|
||||||
|
* [Preprocessor](lang/preprocessor.md)
|
||||||
|
|
||||||
* [Syntax](lang/syntax.md)
|
* [Syntax](lang/syntax.md)
|
||||||
|
|
||||||
* [Types](lang/types.md)
|
* [Types](lang/types.md)
|
||||||
|
@ -25,8 +27,19 @@
|
||||||
|
|
||||||
* [Inline 6502 assembly syntax](lang/assembly.md)
|
* [Inline 6502 assembly syntax](lang/assembly.md)
|
||||||
|
|
||||||
|
* [Inline Z80 assembly syntax](lang/assemblyz80.md)
|
||||||
|
|
||||||
* [Important guidelines regarding reentrancy](lang/reentrancy.md)
|
* [Important guidelines regarding reentrancy](lang/reentrancy.md)
|
||||||
|
|
||||||
|
## Library reference
|
||||||
|
|
||||||
|
* [`stdlib` module](stdlib/stdlib.md)
|
||||||
|
|
||||||
|
* [Other cross-platform modules](stdlib/other.md)
|
||||||
|
|
||||||
|
* [C64-only modules](stdlib/c64.md)
|
||||||
|
|
||||||
|
* [NES-only modules](stdlib/nes.md)
|
||||||
|
|
||||||
## Implementation details
|
## Implementation details
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ Indexing syntax is also the same. Only instructions available on the current CPU
|
||||||
**Work in progress**:
|
**Work in progress**:
|
||||||
Currently, `RMBx`/`SMBx`/`BBRx`/`BBSx` and some extra 65CE02/HuC6280/65816 instructions are not supported yet.
|
Currently, `RMBx`/`SMBx`/`BBRx`/`BBSx` and some extra 65CE02/HuC6280/65816 instructions are not supported yet.
|
||||||
|
|
||||||
Undocumented instructions are supported using various opcodes
|
Undocumented instructions are supported using various opcodes.
|
||||||
|
|
||||||
Labels have to be followed by a colon and they can optionally be on a separate line.
|
Labels have to be followed by a colon and they can optionally be on a separate line.
|
||||||
Indentation is not important:
|
Indentation is not important:
|
||||||
|
@ -29,7 +29,7 @@ Indentation is not important:
|
||||||
|
|
||||||
Label names have to start with a letter and can contain digits, underscores and letters.
|
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.
|
This means than they cannot start with a period like in many other assemblers.
|
||||||
Similarly, anonymous labels designated with `+` or `-` are also not supported
|
Similarly, anonymous labels designated with `+` or `-` are also not supported.
|
||||||
|
|
||||||
Assembly can refer to variables and constants defined in Millfork,
|
Assembly can refer to variables and constants defined in Millfork,
|
||||||
but you need to be careful with using absolute vs immediate addressing:
|
but you need to be careful with using absolute vs immediate addressing:
|
||||||
|
@ -106,7 +106,7 @@ Macro assembly functions can have the following parameter types:
|
||||||
|
|
||||||
For example, if you have:
|
For example, if you have:
|
||||||
|
|
||||||
inline asm void increase(byte ref v, byte const inc) {
|
macro asm void increase(byte ref v, byte const inc) {
|
||||||
LDA v
|
LDA v
|
||||||
CLC
|
CLC
|
||||||
ADC #inc
|
ADC #inc
|
||||||
|
|
|
@ -2,7 +2,132 @@
|
||||||
|
|
||||||
# Using Z80 assembly within Millfork programs
|
# Using Z80 assembly within Millfork programs
|
||||||
|
|
||||||
The compiler does not yet support Z80 assembly. This will be remedied in the future.
|
The compiler supports Z80 assembly only partially. This will be remedied in the future.
|
||||||
|
|
||||||
|
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, `RES/SET/BIT` and some few more instructions are not supported yet.
|
||||||
|
|
||||||
|
Undocumented instructions are not supported, except for `SLL`.
|
||||||
|
|
||||||
|
Labels have to be followed by a colon and they can optionally be on a separate line.
|
||||||
|
Indentation is not important:
|
||||||
|
|
||||||
|
first: INC a
|
||||||
|
second:
|
||||||
|
INC b
|
||||||
|
INC c
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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 {
|
||||||
|
LD A, (fiveVariable)
|
||||||
|
ADD A,fiveConstant
|
||||||
|
LD (result), A
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
You can insert raw bytes into your assembly using the array syntax:
|
||||||
|
|
||||||
|
[ $00, $00 ]
|
||||||
|
"this is a string to print" bbc
|
||||||
|
["this is a string to print but this time it's zero-terminated so it will actually work" bbc, 0]
|
||||||
|
[for x,0,until,8 [x]]
|
||||||
|
|
||||||
|
## 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 `RET`, `RETI` or `RETN`.
|
||||||
|
|
||||||
|
A non-macro assembly function should end with `RET`, `JP`, `RETI` or `RETN` 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 A register.
|
||||||
|
If the size of the return type is two bytes,
|
||||||
|
then the result is passed via the HL register pair.
|
||||||
|
|
||||||
|
### 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:
|
||||||
|
|
||||||
|
macro asm void increase(byte ref v, byte const inc) {
|
||||||
|
LD A,(v)
|
||||||
|
ADD A,inc
|
||||||
|
LDA (v),A
|
||||||
|
}
|
||||||
|
|
||||||
|
and call `increase(score, 10)`, the entire call will compile into:
|
||||||
|
|
||||||
|
LD A,(score)
|
||||||
|
ADD A,10
|
||||||
|
LD (score),A
|
||||||
|
|
||||||
|
Non-macro functions can only have their parameters passed via registers:
|
||||||
|
|
||||||
|
* `byte a`, `byte b`, etc.: a single byte passed via the given CPU register
|
||||||
|
|
||||||
|
* `word hl`, `word bc`, `word de`: a 2-byte word byte passed via given 16-bit register
|
||||||
|
|
||||||
|
**Work in progress**:
|
||||||
|
Currently, only 3 parameter signatures are supported for non-macro assembly functions:
|
||||||
|
`()`, `(byte a)` and `(word hl)`. More parameters or parameters passed via other registers do not work yet.
|
||||||
|
|
||||||
|
Macro assembly functions cannot have any parameter passed via registers.
|
||||||
|
|
||||||
## Safe assembly
|
## Safe assembly
|
||||||
|
|
||||||
|
|
|
@ -17,20 +17,23 @@ the function itself is located in ROM at $FFD2. A call like this:
|
||||||
putchar(13)
|
putchar(13)
|
||||||
```
|
```
|
||||||
|
|
||||||
will be compiled to something like this:
|
will be compiled to something like this on 6502:
|
||||||
|
|
||||||
```
|
```
|
||||||
LDA #13
|
LDA #13
|
||||||
JSR $FFD2
|
JSR $FFD2
|
||||||
```
|
```
|
||||||
|
|
||||||
For more details about how to pass parameters to `asm` functions,
|
For more details about how to pass parameters to `asm` functions, see:
|
||||||
see [Using assembly within Millfork programs#Assembly functions](./assembly.md#assembly-functions).
|
|
||||||
|
* for 6502: [Using 6502 assembly within Millfork programs#Assembly functions](./assembly.md#assembly-functions).
|
||||||
|
|
||||||
|
* for Z80: [Using Z80 assembly within Millfork programs#Assembly functions](./assembly.md#assembly-functions).
|
||||||
|
|
||||||
## Calling external functions at a dynamic address
|
## Calling external functions at a dynamic address
|
||||||
|
|
||||||
To call a function that has its address calculated dynamically,
|
To call a function that has its address calculated dynamically,
|
||||||
you just need to do the same as what you would do in assembly:
|
you just need to do the same as what you would do in assembly; 6502 example:
|
||||||
|
|
||||||
```
|
```
|
||||||
asm void call_function(byte a) {
|
asm void call_function(byte a) {
|
||||||
|
|
|
@ -12,6 +12,7 @@ Certain expressions require the commandline flag `-fzp-register` (`.ini` equival
|
||||||
They will be marked with (zpreg) next to them.
|
They will be marked with (zpreg) next to them.
|
||||||
The flag is enabled by default, but you can disable it if you need to.
|
The flag is enabled by default, but you can disable it if you need to.
|
||||||
|
|
||||||
|
**Work in progress**:
|
||||||
Certain expressions may not work on non-6502 targets yet. This should improve in the future.
|
Certain expressions may not work on non-6502 targets yet. This should improve in the future.
|
||||||
|
|
||||||
## Precedence
|
## Precedence
|
||||||
|
|
96
docs/lang/preprocessor.md
Normal file
96
docs/lang/preprocessor.md
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
[< back to index](../index.md)
|
||||||
|
|
||||||
|
# Preprocessor
|
||||||
|
|
||||||
|
The Millfork preprocessor does 2 things:
|
||||||
|
|
||||||
|
* filters lines in the input file according to current target's features
|
||||||
|
|
||||||
|
* injects the target's feature values as constants into the current file
|
||||||
|
|
||||||
|
Despite its similarity to the C preprocessor, it's much more restricted in its power:
|
||||||
|
|
||||||
|
* no file inclusion
|
||||||
|
|
||||||
|
* no macros
|
||||||
|
|
||||||
|
* separate namespaces for the preprocessor and the language (you need to use `#use` to use a preprocessor constant in the code)
|
||||||
|
|
||||||
|
### Defining feature values
|
||||||
|
|
||||||
|
Feature values are defined in the `[define]` section of the platform definition file.
|
||||||
|
Each value is a signed 64-bit integer number.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
[define]
|
||||||
|
WIDESCREEN=1
|
||||||
|
|
||||||
|
You can also define feature values using the `-D` command line option.
|
||||||
|
|
||||||
|
### Built-in features
|
||||||
|
|
||||||
|
* `ARCH_6502` – 1 if compiling for 6502, 0 otherwise
|
||||||
|
|
||||||
|
* `ARCH_Z80` – 1 if compiling for Z80, 0 otherwise
|
||||||
|
|
||||||
|
### Commonly used features
|
||||||
|
|
||||||
|
* `WIDESCREEN` – 1 if the horizontal screen resolution, ignoring borders, is greater than 256, 0 otherwise
|
||||||
|
|
||||||
|
* `CBM` – 1 if the target is an 8-bit Commodore computer, 0 otherwise
|
||||||
|
|
||||||
|
* `CBM_64` – 1 if the target is an 8-bit Commodore computer compatible with Commodore 64, 0 otherwise
|
||||||
|
|
||||||
|
* `KEYBOARD` – 1 if the target has a keyboard, 0 otherwise
|
||||||
|
|
||||||
|
* `JOYSTICKS` – the maximum number of joysticks using standard hardware configurations, may be 0
|
||||||
|
|
||||||
|
* `HAS_BITMAP_MODE` – 1 if the target has a display mode with every pixel addressable, 0 otherwise
|
||||||
|
|
||||||
|
### Built-in preprocessor functions and operators
|
||||||
|
|
||||||
|
The `defined` function returns 1 if the feature is defined, 0 otherwise.
|
||||||
|
All the other functions and operators treat undefined features as if they were defined as 0.
|
||||||
|
|
||||||
|
TODO
|
||||||
|
`not`, `lo`, `hi`, `+`, `-`, `*`, `|`, `&`, `^`, `||`, `&&`, `<<`, `>>`,`==`, `!=`, `>`, `>=`, `<`, `<=`
|
||||||
|
|
||||||
|
The following Millfork operators and functions are not available in the preprocessor:
|
||||||
|
`+'`, `-'`, `*'`, `<<'`, `>>'`, `:`, `>>>>`, `nonet`, all the assignment operators
|
||||||
|
|
||||||
|
### `#if/#elseif/#else/#endif`
|
||||||
|
|
||||||
|
#if <expr>
|
||||||
|
#elseif <expr>
|
||||||
|
#else
|
||||||
|
#endif
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
### `#fatal/#error/#warn/#info`
|
||||||
|
|
||||||
|
#error fatal error message
|
||||||
|
#error error message
|
||||||
|
#warn warning message
|
||||||
|
#info informational message
|
||||||
|
|
||||||
|
Emits a diagnostic message.
|
||||||
|
|
||||||
|
`#fatal` interrupts the compilation immediately.
|
||||||
|
`#error` causes an error, but the compilation will continue.
|
||||||
|
`#warn` emits a warning. It may be treated as an error depending on compilation options.
|
||||||
|
`#info` emits a benign diagnostic message.
|
||||||
|
|
||||||
|
|
||||||
|
### `#use`
|
||||||
|
|
||||||
|
Exports a feature value under its name to the parser.
|
||||||
|
The parser will substitute every use of that name as a variable or constant
|
||||||
|
with the numeric value of the feature.
|
||||||
|
The substitution will happen only within the current file.
|
||||||
|
To use such value in other files, consider this:
|
||||||
|
|
||||||
|
#use WIDESCREEN
|
||||||
|
const byte is_widescreen = WIDESCREEN
|
||||||
|
|
23
docs/stdlib/c64.md
Normal file
23
docs/stdlib/c64.md
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
[< back to index](../index.md)
|
||||||
|
|
||||||
|
# Commodore 64-oriented modules
|
||||||
|
|
||||||
|
## `c64_kernal` module
|
||||||
|
|
||||||
|
The `c64_kernal` module is imported automatically on the C64 target.
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
## `c64_basic` module
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
## `c1531_mouse` module
|
||||||
|
|
||||||
|
The `c1531_mouse` module implements a Commodore 1531 proportional mouse driver compatible with the `mouse` module.
|
||||||
|
|
||||||
|
#### `void c1531_mouse ()`
|
||||||
|
|
||||||
|
Updates the state of the mouse.
|
||||||
|
|
||||||
|
|
40
docs/stdlib/nes.md
Normal file
40
docs/stdlib/nes.md
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
[< back to index](../index.md)
|
||||||
|
|
||||||
|
# NES/Famicom-oriented modules
|
||||||
|
|
||||||
|
## `nes_hardware` module
|
||||||
|
|
||||||
|
The `nes_hardware` module is imported automatically on NES targets.
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
## `nes_mmc4` module
|
||||||
|
|
||||||
|
The `nes_mmc4` module is imported automatically on the NES MMC4 target.
|
||||||
|
and contains routines related to MMC4 bankswitching.
|
||||||
|
|
||||||
|
#### `void set_prg_bank(byte a)`
|
||||||
|
|
||||||
|
Changes the $8000-$BFFF PRG bank.
|
||||||
|
|
||||||
|
#### `void set_chr_bank0(byte a)`
|
||||||
|
|
||||||
|
Changes the CHR bank 0 ($0000-$0fff in the PPU memory space).
|
||||||
|
|
||||||
|
The high nibble (0 or 1) selects between `chrrom0` and `chrrom1` segments.
|
||||||
|
The low nibble L (0-$F) selects a 4K-aligned address in the segment ($L000).
|
||||||
|
|
||||||
|
#### `void set_chr_bank1(byte a)`
|
||||||
|
|
||||||
|
Changes the CHR bank 1 ($1000-$1fff in the PPU memory space).
|
||||||
|
|
||||||
|
The high nibble (0 or 1) selects between `chrrom0` and `chrrom1` segments.
|
||||||
|
The low nibble L (0-$F) selects a 4K-aligned address in the segment ($L000).
|
||||||
|
|
||||||
|
#### `void set_vertical_mirroring()`
|
||||||
|
|
||||||
|
Switches nametable mirroring to vertical.
|
||||||
|
|
||||||
|
#### `void set_horizontal_mirroring()`
|
||||||
|
|
||||||
|
Switches nametable mirroring to horizontal.
|
53
docs/stdlib/other.md
Normal file
53
docs/stdlib/other.md
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
[< back to index](../index.md)
|
||||||
|
|
||||||
|
## `stdio` module
|
||||||
|
|
||||||
|
The `stdio` module automatically imports the `string` module.
|
||||||
|
It requires an implementation of `void putchar(byte a)` and therefore works only on targets with console output.
|
||||||
|
|
||||||
|
#### `void putstr(pointer str, byte len)`
|
||||||
|
|
||||||
|
Prints a string of length `len` located at address `str`.
|
||||||
|
|
||||||
|
#### `void putstrz(pointer str)`
|
||||||
|
|
||||||
|
Prints a null-terminated string located at address `str`.
|
||||||
|
If the string is longer than 255 bytes, then the behaviour is undefined (might even crash).
|
||||||
|
|
||||||
|
## `string` module
|
||||||
|
|
||||||
|
#### `byte strzlen(pointer str)`
|
||||||
|
|
||||||
|
Calculates the length of a null-terminated string.
|
||||||
|
If the string is longer than 255 bytes, then the behaviour is undefined (might even crash).
|
||||||
|
|
||||||
|
## `mouse` module
|
||||||
|
|
||||||
|
The `mouse` module automatically imports the `x_coord` module.
|
||||||
|
|
||||||
|
The module contains global variables representing the state of the mouse.
|
||||||
|
If the program is not using any mouse driver, the state of these variables is undefined.
|
||||||
|
|
||||||
|
#### `x_coord mouse_x`
|
||||||
|
|
||||||
|
Mouse X position.
|
||||||
|
|
||||||
|
#### `byte mouse_y`
|
||||||
|
|
||||||
|
Mouse Y position.
|
||||||
|
|
||||||
|
#### `byte mouse_lbm`
|
||||||
|
|
||||||
|
1 if the left mouse button is being pressed, 0 otherwise
|
||||||
|
|
||||||
|
#### `byte mouse_rbm`
|
||||||
|
|
||||||
|
1 if the right mouse button is being pressed, 0 otherwise
|
||||||
|
|
||||||
|
## `x_coord` module
|
||||||
|
|
||||||
|
#### `alias x_coord`
|
||||||
|
|
||||||
|
The type for representing horizontal screen coordinates.
|
||||||
|
It's `byte` if the screen is 256 pixels wide or less,
|
||||||
|
or `word` if the screen is more that 256 pixels wide.
|
33
docs/stdlib/stdlib.md
Normal file
33
docs/stdlib/stdlib.md
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
[< back to index](../index.md)
|
||||||
|
|
||||||
|
## `stdlib` module
|
||||||
|
|
||||||
|
The `stdlib` module is automatically imported on most targets.
|
||||||
|
|
||||||
|
#### `macro asm void poke(word const addr, byte a)`
|
||||||
|
|
||||||
|
Stores a byte at given constant address. Will not be optimized away by the optimizer.
|
||||||
|
|
||||||
|
#### `macro asm byte peek(word const addr)`
|
||||||
|
|
||||||
|
Reads a byte from given constant address. Will not be optimized away by the optimizer.
|
||||||
|
|
||||||
|
#### `macro asm void disable_irq()`
|
||||||
|
|
||||||
|
Disables interrupts.
|
||||||
|
|
||||||
|
#### `macro asm void enable_irq()`
|
||||||
|
|
||||||
|
Enables interrupts.
|
||||||
|
|
||||||
|
#### `byte hi_nibble_to_hex(byte a)`
|
||||||
|
|
||||||
|
Returns an ASCII representation of the upper nibble of the given byte.
|
||||||
|
|
||||||
|
#### `byte lo_nibble_to_hex(byte a)`
|
||||||
|
|
||||||
|
Returns an ASCII representation of the lower nibble of the given byte.
|
||||||
|
|
||||||
|
#### `macro asm void panic()`
|
||||||
|
|
||||||
|
Crashes the program.
|
|
@ -1,6 +1,6 @@
|
||||||
[compilation]
|
[compilation]
|
||||||
arch=strict
|
arch=strict
|
||||||
modules=a8_kernel,default_panic
|
modules=a8_kernel,default_panic,stdlib
|
||||||
encoding=atascii
|
encoding=atascii
|
||||||
|
|
||||||
[allocation]
|
[allocation]
|
||||||
|
@ -10,6 +10,13 @@ segment_default_start=$2000
|
||||||
; TODO
|
; TODO
|
||||||
segment_default_end=$3fff
|
segment_default_end=$3fff
|
||||||
|
|
||||||
|
[define]
|
||||||
|
ATARI_8=1
|
||||||
|
WIDESCREEN=1
|
||||||
|
KEYBOARD=1
|
||||||
|
JOYSTICKS=2
|
||||||
|
HAS_BITMAP_MODE=1
|
||||||
|
|
||||||
[output]
|
[output]
|
||||||
;TODO
|
;TODO
|
||||||
style=single
|
style=single
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
|
||||||
|
#if not(ATARI_8)
|
||||||
|
#warn a8_kernel module should be used only on Atari computer-compatible targets
|
||||||
|
#endif
|
||||||
|
|
||||||
asm void putchar(byte a) {
|
asm void putchar(byte a) {
|
||||||
tax
|
tax
|
||||||
lda $347
|
lda $347
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
[compilation]
|
[compilation]
|
||||||
arch=strict
|
arch=strict
|
||||||
encoding=apple2
|
encoding=apple2
|
||||||
modules=apple2_kernel,default_panic
|
modules=apple2_kernel,default_panic,stdlib
|
||||||
|
|
||||||
|
|
||||||
[allocation]
|
[allocation]
|
||||||
|
@ -10,6 +10,14 @@ zp_pointers=6,8,$EB,$ED,$FA,$FC
|
||||||
segment_default_start=$0C00
|
segment_default_start=$0C00
|
||||||
segment_default_end=$95FF
|
segment_default_end=$95FF
|
||||||
|
|
||||||
|
[define]
|
||||||
|
APPLE_2=1
|
||||||
|
WIDESCREEN=0
|
||||||
|
KEYBOARD=1
|
||||||
|
; TODO: ?
|
||||||
|
JOYSTICKS=0
|
||||||
|
HAS_BITMAP_MODE=1
|
||||||
|
|
||||||
[output]
|
[output]
|
||||||
;TODO
|
;TODO
|
||||||
style=single
|
style=single
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
|
||||||
|
#if not(APPLE_2)
|
||||||
|
#warn apple2_kernel module should be used only on Apple II-compatible targets
|
||||||
|
#endif
|
||||||
|
|
||||||
array hires_page_1 [$2000] @$2000
|
array hires_page_1 [$2000] @$2000
|
||||||
array hires_page_2 [$2000] @$4000
|
array hires_page_2 [$2000] @$4000
|
||||||
|
|
||||||
|
|
|
@ -1 +1,6 @@
|
||||||
|
|
||||||
|
#if not(BBC_MICRO)
|
||||||
|
#warn bbc_hardware module should be used only on BBC Micro-compatible targets
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
|
|
||||||
|
#if not(BBC_MICRO)
|
||||||
|
#warn bbc_kernal module should be used only on BBC Micro-compatible targets
|
||||||
|
#endif
|
||||||
|
|
||||||
// OSASCI. Write a character (to screen) from A plus LF if (A)=&0D
|
// OSASCI. Write a character (to screen) from A plus LF if (A)=&0D
|
||||||
// Input: A = Byte to write.
|
// Input: A = Byte to write.
|
||||||
asm void putchar(byte a) @$FFE3 extern
|
asm void putchar(byte a) @$FFE3 extern
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
; "strict" guarantees compatibility with Rockwell CPU's in some later Model B's
|
; "strict" guarantees compatibility with Rockwell CPU's in some later Model B's
|
||||||
arch=strict
|
arch=strict
|
||||||
encoding=bbc
|
encoding=bbc
|
||||||
modules=bbc_kernal,bbc_hardware,default_panic
|
modules=bbc_kernal,bbc_hardware,default_panic,stdlib
|
||||||
|
|
||||||
|
|
||||||
[allocation]
|
[allocation]
|
||||||
|
@ -13,6 +13,14 @@ segment_default_start=$0E00
|
||||||
; The following is for Model B; for Model A, consider changing it to $31FF
|
; The following is for Model B; for Model A, consider changing it to $31FF
|
||||||
segment_default_end=$71FF
|
segment_default_end=$71FF
|
||||||
|
|
||||||
|
[define]
|
||||||
|
BBC_MICRO=1
|
||||||
|
WIDESCREEN=1
|
||||||
|
KEYBOARD=1
|
||||||
|
; TODO: ?
|
||||||
|
JOYSTICKS=1
|
||||||
|
HAS_BITMAP_MODE=1
|
||||||
|
|
||||||
[output]
|
[output]
|
||||||
style=single
|
style=single
|
||||||
format=allocated
|
format=allocated
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
arch=nmos
|
arch=nmos
|
||||||
encoding=petscii
|
encoding=petscii
|
||||||
screen_encoding=petscr
|
screen_encoding=petscr
|
||||||
modules=c128_hardware,loader_1c01,c128_kernal,default_panic
|
modules=c128_hardware,loader_1c01,c128_kernal,default_panic,stdlib
|
||||||
|
|
||||||
|
|
||||||
[allocation]
|
[allocation]
|
||||||
|
@ -11,6 +11,16 @@ zp_pointers=$FB,$FD,$43,$45,$47,$4B,$F7,$F9,$9E,$9B,$3D
|
||||||
segment_default_start=$1C0D
|
segment_default_start=$1C0D
|
||||||
segment_default_end=$FEFF
|
segment_default_end=$FEFF
|
||||||
|
|
||||||
|
[define]
|
||||||
|
CBM=1
|
||||||
|
CBM_128=1
|
||||||
|
CBM_64=1
|
||||||
|
MOS_6510=1
|
||||||
|
WIDESCREEN=1
|
||||||
|
KEYBOARD=1
|
||||||
|
JOYSTICKS=2
|
||||||
|
HAS_BITMAP_MODE=1
|
||||||
|
|
||||||
[output]
|
[output]
|
||||||
style=single
|
style=single
|
||||||
format=startaddr,allocated
|
format=startaddr,allocated
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
|
||||||
|
#if not(CBM_128)
|
||||||
|
#warn c128_hardware module should be only used on C128-compatible targets
|
||||||
|
#endif
|
||||||
|
|
||||||
import c64_vic
|
import c64_vic
|
||||||
import c64_sid
|
import c64_sid
|
||||||
import c64_cia
|
import c64_cia
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
// Routines from Commodore 128 KERNAL ROM
|
// Routines from Commodore 128 KERNAL ROM
|
||||||
|
|
||||||
|
#if not(CBM_128)
|
||||||
|
#warn c128_kernal module should be only used on C128-compatible targets
|
||||||
|
#endif
|
||||||
|
|
||||||
// CHROUT. Write byte to default output. (If not screen, must call OPEN and CHKOUT beforehands.)
|
// CHROUT. Write byte to default output. (If not screen, must call OPEN and CHKOUT beforehands.)
|
||||||
// Input: A = Byte to write.
|
// Input: A = Byte to write.
|
||||||
asm void putchar(byte a) @$FFD2 extern
|
asm void putchar(byte a) @$FFD2 extern
|
|
@ -1,7 +1,12 @@
|
||||||
// mouse driver for Commodore 1531 mouse on Commodore 64
|
// mouse driver for Commodore 1531 mouse on Commodore 64
|
||||||
|
|
||||||
|
#if not(CBM_64) || not(WIDESCREEN)
|
||||||
|
#warn c1531 module should be only used on C64-compatible targets
|
||||||
|
#endif
|
||||||
|
|
||||||
import mouse
|
import mouse
|
||||||
import c64_hardware
|
import c64_hardware
|
||||||
|
import stdlib
|
||||||
|
|
||||||
sbyte _c1531_calculate_delta (byte old, byte new) {
|
sbyte _c1531_calculate_delta (byte old, byte new) {
|
||||||
byte mouse_delta
|
byte mouse_delta
|
||||||
|
@ -53,8 +58,12 @@ byte _c1531_handle_y() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void c1531_mouse () {
|
void c1531_mouse () {
|
||||||
|
|
||||||
cia1_pra = ($3f & cia1_pra) | $40
|
cia1_pra = ($3f & cia1_pra) | $40
|
||||||
_c1531_handle_x()
|
_c1531_handle_x()
|
||||||
_c1531_handle_y()
|
_c1531_handle_y()
|
||||||
|
byte value
|
||||||
|
poke($dc03, 0)
|
||||||
|
value = peek($dc01)
|
||||||
|
mouse_rbm = (value & 1) ^ 1
|
||||||
|
if value & 16 == 0 { mouse_lbm = 1 } else { mouse_lbm = 0}
|
||||||
}
|
}
|
|
@ -2,7 +2,7 @@
|
||||||
arch=nmos
|
arch=nmos
|
||||||
encoding=petscii
|
encoding=petscii
|
||||||
screen_encoding=petscr
|
screen_encoding=petscr
|
||||||
modules=loader_1001,c264_kernal,c264_hardware,default_panic
|
modules=loader_1001,c264_kernal,c264_hardware,default_panic,stdlib
|
||||||
|
|
||||||
|
|
||||||
[allocation]
|
[allocation]
|
||||||
|
@ -11,6 +11,14 @@ zp_pointers=$C1,$C3,$FB,$FD,$39,$3B,$3D,$43,$4B
|
||||||
segment_default_start=$100D
|
segment_default_start=$100D
|
||||||
segment_default_end=$3FFF
|
segment_default_end=$3FFF
|
||||||
|
|
||||||
|
[define]
|
||||||
|
CBM=1
|
||||||
|
CBM_264=1
|
||||||
|
WIDESCREEN=1
|
||||||
|
KEYBOARD=1
|
||||||
|
JOYSTICKS=2
|
||||||
|
HAS_BITMAP_MODE=1
|
||||||
|
|
||||||
[output]
|
[output]
|
||||||
style=single
|
style=single
|
||||||
format=startaddr,allocated
|
format=startaddr,allocated
|
||||||
|
|
|
@ -1 +1,5 @@
|
||||||
|
#if not(CBM_264)
|
||||||
|
#warn c264_hardware module should be only used on C264-compatible targets
|
||||||
|
#endif
|
||||||
|
|
||||||
import c264_ted
|
import c264_ted
|
|
@ -1,4 +1,7 @@
|
||||||
// Routines from C16 and Plus/4 KERNAL ROM
|
// Routines from C16 and Plus/4 KERNAL ROM
|
||||||
|
#if not(CBM_264)
|
||||||
|
#warn c264_kernal module should be only used on C264-compatible targets
|
||||||
|
#endif
|
||||||
|
|
||||||
// CHROUT. Write byte to default output. (If not screen, must call OPEN and CHKOUT beforehands.)
|
// CHROUT. Write byte to default output. (If not screen, must call OPEN and CHKOUT beforehands.)
|
||||||
// Input: A = Byte to write.
|
// Input: A = Byte to write.
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
#if not(CBM_264)
|
||||||
|
#warn c264_ted module should be only used on C264-compatible targets
|
||||||
|
#endif
|
||||||
|
|
||||||
const byte black = 0
|
const byte black = 0
|
||||||
const byte white = $71
|
const byte white = $71
|
||||||
|
|
|
@ -21,6 +21,14 @@ segment_default_codeend=$9fff
|
||||||
segment_default_datastart=after_code
|
segment_default_datastart=after_code
|
||||||
segment_default_end=$cfff
|
segment_default_end=$cfff
|
||||||
|
|
||||||
|
[define]
|
||||||
|
CBM=1
|
||||||
|
CBM_64=1
|
||||||
|
MOS_6510=1
|
||||||
|
WIDESCREEN=1
|
||||||
|
KEYBOARD=1
|
||||||
|
JOYSTICKS=2
|
||||||
|
HAS_BITMAP_MODE=1
|
||||||
|
|
||||||
[output]
|
[output]
|
||||||
; how the banks are laid out in the output files; so far, there is no bank support in the compiler yet
|
; how the banks are laid out in the output files; so far, there is no bank support in the compiler yet
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
// Routines from C64 BASIC ROM
|
// Routines from C64 BASIC ROM
|
||||||
|
|
||||||
|
#if not(CBM_64)
|
||||||
|
#warn c64_basic module should be only used on C64-compatible targets
|
||||||
|
#endif
|
||||||
|
|
||||||
import c64_kernal
|
import c64_kernal
|
||||||
|
|
||||||
// print a 16-bit number on the standard output
|
// print a 16-bit number on the standard output
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
// Hardware addresses for C64
|
// Hardware addresses for C64
|
||||||
|
|
||||||
|
#if not(CBM_64)
|
||||||
|
#warn c64_cia module should be only used on C64-compatible targets
|
||||||
|
#endif
|
||||||
|
|
||||||
// CIA1
|
// CIA1
|
||||||
byte cia1_pra @$DC00
|
byte cia1_pra @$DC00
|
||||||
byte cia1_prb @$DC01
|
byte cia1_prb @$DC01
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
#if not(CBM_64)
|
||||||
|
#warn c64_hardware module should be only used on C64-compatible targets
|
||||||
|
#endif
|
||||||
|
|
||||||
import c64_vic
|
import c64_vic
|
||||||
import c64_sid
|
import c64_sid
|
||||||
import c64_cia
|
import c64_cia
|
||||||
|
|
|
@ -14,6 +14,15 @@ segment_default_start=$80D
|
||||||
segment_default_codeend=$9fff
|
segment_default_codeend=$9fff
|
||||||
segment_default_end=$cfff
|
segment_default_end=$cfff
|
||||||
|
|
||||||
|
[define]
|
||||||
|
CBM=1
|
||||||
|
CBM_64=1
|
||||||
|
MOS_6510=1
|
||||||
|
WIDESCREEN=1
|
||||||
|
KEYBOARD=1
|
||||||
|
JOYSTICKS=2
|
||||||
|
HAS_BITMAP_MODE=1
|
||||||
|
|
||||||
[output]
|
[output]
|
||||||
style=single
|
style=single
|
||||||
format=startaddr,allocated
|
format=startaddr,allocated
|
||||||
|
|
|
@ -16,6 +16,15 @@ segment_default_start=$80D
|
||||||
segment_default_codeend=$9fff
|
segment_default_codeend=$9fff
|
||||||
segment_default_end=$cfff
|
segment_default_end=$cfff
|
||||||
|
|
||||||
|
[define]
|
||||||
|
CBM=1
|
||||||
|
CBM_64=1
|
||||||
|
MOS_6510=1
|
||||||
|
WIDESCREEN=1
|
||||||
|
KEYBOARD=1
|
||||||
|
JOYSTICKS=2
|
||||||
|
HAS_BITMAP_MODE=1
|
||||||
|
|
||||||
[output]
|
[output]
|
||||||
style=single
|
style=single
|
||||||
format=startaddr,allocated
|
format=startaddr,allocated
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
// Hardware addresses for C64
|
// Hardware addresses for C64
|
||||||
|
|
||||||
|
#if not(CBM_64)
|
||||||
|
#warn c64_sid module should be only used on C64-compatible targets
|
||||||
|
#endif
|
||||||
|
|
||||||
// SID
|
// SID
|
||||||
|
|
||||||
word sid_v1_freq @$D400
|
word sid_v1_freq @$D400
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
// Hardware addresses for C64
|
// Hardware addresses for C64
|
||||||
|
|
||||||
|
#if not(CBM_64)
|
||||||
|
#warn c64_vic module should be only used on C64-compatible targets
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
// VIC-II
|
// VIC-II
|
||||||
byte vic_spr0_x @$D000
|
byte vic_spr0_x @$D000
|
||||||
byte vic_spr0_y @$D001
|
byte vic_spr0_y @$D001
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
#if not(MOS_6510) && not(CBM_64)
|
||||||
|
#warn cpu6510 module should be only used on 6510-compatible targets
|
||||||
|
#endif
|
||||||
|
|
||||||
byte cpu6510_ddr @0
|
byte cpu6510_ddr @0
|
||||||
byte cpu6510_port @1
|
byte cpu6510_port @1
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
#if not(CBM)
|
||||||
|
#warn loader_0401 module should be only used on Commodore targets
|
||||||
|
#endif
|
||||||
|
|
||||||
array _basic_loader @$401 = [
|
array _basic_loader @$401 = [
|
||||||
$0b,
|
$0b,
|
||||||
4,
|
4,
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
#if not(CBM)
|
||||||
|
#warn loader_0801 module should be only used on Commodore targets
|
||||||
|
#endif
|
||||||
|
|
||||||
array _basic_loader @$801 = [
|
array _basic_loader @$801 = [
|
||||||
$0b,
|
$0b,
|
||||||
$08,
|
$08,
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
#if not(CBM)
|
||||||
|
#warn loader_0801_16bit module should be only used on Commodore targets
|
||||||
|
#endif
|
||||||
|
|
||||||
array _basic_loader @$801 = [
|
array _basic_loader @$801 = [
|
||||||
$0b,
|
$0b,
|
||||||
$08,
|
$08,
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
#if not(CBM)
|
||||||
|
#warn loader_1001 module should be only used on Commodore targets
|
||||||
|
#endif
|
||||||
|
|
||||||
array _basic_loader @$1001 = [
|
array _basic_loader @$1001 = [
|
||||||
$0b,
|
$0b,
|
||||||
$10,
|
$10,
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
#if not(CBM)
|
||||||
|
#warn loader_1201 module should be only used on Commodore targets
|
||||||
|
#endif
|
||||||
|
|
||||||
array _basic_loader @$1201 = [
|
array _basic_loader @$1201 = [
|
||||||
$0b,
|
$0b,
|
||||||
$12,
|
$12,
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
#if not(CBM)
|
||||||
|
#warn loader_1c01 module should be only used on Commodore targets
|
||||||
|
#endif
|
||||||
|
|
||||||
array _basic_loader @$1C01 = [
|
array _basic_loader @$1C01 = [
|
||||||
$0b,
|
$0b,
|
||||||
$1C,
|
$1C,
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
arch=nmos
|
arch=nmos
|
||||||
encoding=petscii
|
encoding=petscii
|
||||||
screen_encoding=petscr
|
screen_encoding=petscr
|
||||||
modules=lunix
|
modules=lunix,stdlib
|
||||||
lunix=true
|
lunix=true
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,6 +17,14 @@ segment_default_codeend=$8fff
|
||||||
segment_default_datastart=after_code
|
segment_default_datastart=after_code
|
||||||
segment_default_end=$8fff
|
segment_default_end=$8fff
|
||||||
|
|
||||||
|
[define]
|
||||||
|
LUNIX=1
|
||||||
|
CBM_64=1
|
||||||
|
WIDESCREEN=1
|
||||||
|
KEYBOARD=1
|
||||||
|
; TODO: ?
|
||||||
|
JOYSTICKS=2
|
||||||
|
HAS_BITMAP_MODE=1
|
||||||
|
|
||||||
[output]
|
[output]
|
||||||
style=lunix
|
style=lunix
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
#if not(LUNIX)
|
||||||
|
#warn lunix module should be only used on Lunix targets
|
||||||
|
#endif
|
||||||
|
|
||||||
const word lkf_jumptab = $200
|
const word lkf_jumptab = $200
|
||||||
|
|
||||||
byte relocation_offset @$1001
|
byte relocation_offset @$1001
|
||||||
|
|
|
@ -1,8 +1,16 @@
|
||||||
// Generic module for mouse support
|
// Generic module for mouse support
|
||||||
// Resolutions up to 512x256 are supported
|
// Resolutions up to 512x256 are supported
|
||||||
|
|
||||||
|
import x_coord
|
||||||
|
|
||||||
// Mouse X coordinate
|
// Mouse X coordinate
|
||||||
word mouse_x
|
x_coord mouse_x
|
||||||
|
|
||||||
// Mouse Y coordinate
|
// Mouse Y coordinate
|
||||||
byte mouse_y
|
byte mouse_y
|
||||||
|
|
||||||
|
// Left mouse button pressed
|
||||||
|
byte mouse_lbm
|
||||||
|
|
||||||
|
// Right mouse button pressed
|
||||||
|
byte mouse_rbm
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
#if not(NES)
|
||||||
|
#warn nes_hardware module should be only used on NES/Famicom targets
|
||||||
|
#endif
|
||||||
|
|
||||||
byte ppu_ctrl @$2000
|
byte ppu_ctrl @$2000
|
||||||
byte ppu_mask @$2001
|
byte ppu_mask @$2001
|
||||||
byte ppu_status @$2002
|
byte ppu_status @$2002
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
[compilation]
|
[compilation]
|
||||||
arch=ricoh
|
arch=ricoh
|
||||||
modules=nes_hardware,nes_routines,default_panic,nes_mmc4
|
modules=nes_hardware,nes_routines,default_panic,nes_mmc4,stdlib
|
||||||
ro_arrays=true
|
ro_arrays=true
|
||||||
|
|
||||||
[allocation]
|
[allocation]
|
||||||
|
@ -55,6 +55,13 @@ segment_chrrom0_end=$ffff
|
||||||
segment_chrrom1_start=$0000
|
segment_chrrom1_start=$0000
|
||||||
segment_chrrom1_end=$ffff
|
segment_chrrom1_end=$ffff
|
||||||
|
|
||||||
|
[define]
|
||||||
|
NES=1
|
||||||
|
WIDESCREEN=0
|
||||||
|
KEYBOARD=0
|
||||||
|
JOYSTICKS=2
|
||||||
|
HAS_BITMAP_MODE=0
|
||||||
|
|
||||||
[output]
|
[output]
|
||||||
style=single
|
style=single
|
||||||
format=$4E,$45,$53,$1A, 8,16,$A0,8, 0,0,$07,0, 2,0,0,0, prgrom0:$8000:$bfff,prgrom1:$8000:$bfff,prgrom2:$8000:$bfff,prgrom3:$8000:$bfff,prgrom4:$8000:$bfff,prgrom5:$8000:$bfff,prgrom6:$8000:$bfff,prgrom7:$c000:$ffff,chrrom0:$0000:$ffff,chrrom1:$0000:$ffff
|
format=$4E,$45,$53,$1A, 8,16,$A0,8, 0,0,$07,0, 2,0,0,0, prgrom0:$8000:$bfff,prgrom1:$8000:$bfff,prgrom2:$8000:$bfff,prgrom3:$8000:$bfff,prgrom4:$8000:$bfff,prgrom5:$8000:$bfff,prgrom6:$8000:$bfff,prgrom7:$c000:$ffff,chrrom0:$0000:$ffff,chrrom1:$0000:$ffff
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
#if not(NES)
|
||||||
|
#warn nes_mmc4 module should be only used on NES/Famicom targets
|
||||||
|
#endif
|
||||||
|
|
||||||
asm inline void set_prg_bank(byte a) {
|
asm inline void set_prg_bank(byte a) {
|
||||||
STA $A000
|
STA $A000
|
||||||
? RTS
|
? RTS
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
#if not(NES)
|
||||||
|
#warn nes_routines module should be only used on NES/Famicom targets
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
asm void on_reset() {
|
asm void on_reset() {
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
[compilation]
|
[compilation]
|
||||||
arch=ricoh
|
arch=ricoh
|
||||||
modules=nes_hardware,nes_routines,default_panic
|
modules=nes_hardware,nes_routines,default_panic,stdlib
|
||||||
ro_arrays=true
|
ro_arrays=true
|
||||||
|
|
||||||
[allocation]
|
[allocation]
|
||||||
|
@ -24,6 +24,13 @@ segment_prgrom_end=$ffff
|
||||||
segment_chrrom_start=$0000
|
segment_chrrom_start=$0000
|
||||||
segment_chrrom_end=$1fff
|
segment_chrrom_end=$1fff
|
||||||
|
|
||||||
|
[define]
|
||||||
|
NES=1
|
||||||
|
WIDESCREEN=0
|
||||||
|
KEYBOARD=0
|
||||||
|
JOYSTICKS=2
|
||||||
|
HAS_BITMAP_MODE=0
|
||||||
|
|
||||||
[output]
|
[output]
|
||||||
style=single
|
style=single
|
||||||
format=$4E,$45,$53,$1A, 2,1,0,0, 0,0,0,0, 0,0,0,0, prgrom:$8000:$ffff, chrrom:$0000:$1fff
|
format=$4E,$45,$53,$1A, 2,1,0,0, 0,0,0,0, 0,0,0,0, prgrom:$8000:$ffff, chrrom:$0000:$1fff
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
[compilation]
|
[compilation]
|
||||||
arch=z80
|
arch=z80
|
||||||
encoding=jisx
|
encoding=jisx
|
||||||
modules=default_panic
|
modules=default_panic,stdlib
|
||||||
|
|
||||||
[allocation]
|
[allocation]
|
||||||
; TODO: find a more optimal start address
|
; TODO: find a more optimal start address
|
||||||
|
@ -12,6 +12,14 @@ segment_default_codeend=$bfff
|
||||||
segment_default_datastart=after_code
|
segment_default_datastart=after_code
|
||||||
segment_default_end=$efff
|
segment_default_end=$efff
|
||||||
|
|
||||||
|
[define]
|
||||||
|
NEC_PC_88=1
|
||||||
|
WIDESCREEN=1
|
||||||
|
KEYBOARD=1
|
||||||
|
; TODO:
|
||||||
|
JOYSTICKS=1
|
||||||
|
HAS_BITMAP_MODE=1
|
||||||
|
|
||||||
[output]
|
[output]
|
||||||
style=single
|
style=single
|
||||||
format=d88
|
format=d88
|
||||||
|
|
|
@ -9,6 +9,14 @@ zp_pointers=$C1,$C3,$FB,$FD,$39,$3B,$3D,$43,$4B
|
||||||
segment_default_start=$40D
|
segment_default_start=$40D
|
||||||
segment_default_end=$FFF
|
segment_default_end=$FFF
|
||||||
|
|
||||||
|
[define]
|
||||||
|
CBM=1
|
||||||
|
CBM_PET=1
|
||||||
|
WIDESCREEN=0
|
||||||
|
KEYBOARD=1
|
||||||
|
JOYSTICKS=0
|
||||||
|
HAS_BITMAP_MODE=0
|
||||||
|
|
||||||
[output]
|
[output]
|
||||||
style=single
|
style=single
|
||||||
format=startaddr,allocated
|
format=startaddr,allocated
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
// Routines from Commodore PET KERNAL ROM
|
// Routines from Commodore PET KERNAL ROM
|
||||||
|
|
||||||
|
#if not(CBM_PET)
|
||||||
|
#warn pet_kernal module should be only used on PET targets
|
||||||
|
#endif
|
||||||
|
|
||||||
// CHROUT. Write byte to default output. (If not screen, must call OPEN and CHKOUT beforehands.)
|
// CHROUT. Write byte to default output. (If not screen, must call OPEN and CHKOUT beforehands.)
|
||||||
// Input: A = Byte to write.
|
// Input: A = Byte to write.
|
||||||
asm void putchar(byte a) @$FFD2 extern
|
asm void putchar(byte a) @$FFD2 extern
|
|
@ -2,7 +2,7 @@
|
||||||
arch=nmos
|
arch=nmos
|
||||||
encoding=petscii
|
encoding=petscii
|
||||||
screen_encoding=petscr
|
screen_encoding=petscr
|
||||||
modules=loader_1001,c264_kernal,c264_hardware,default_panic
|
modules=loader_1001,c264_kernal,c264_hardware,default_panic,stdlib
|
||||||
|
|
||||||
|
|
||||||
[allocation]
|
[allocation]
|
||||||
|
@ -11,6 +11,14 @@ zp_pointers=$C1,$C3,$FB,$FD,$39,$3B,$3D,$43,$4B
|
||||||
segment_default_start=$100D
|
segment_default_start=$100D
|
||||||
segment_default_end=$3FFF
|
segment_default_end=$3FFF
|
||||||
|
|
||||||
|
[define]
|
||||||
|
CBM=1
|
||||||
|
CBM_264=1
|
||||||
|
WIDESCREEN=1
|
||||||
|
KEYBOARD=1
|
||||||
|
JOYSTICKS=2
|
||||||
|
HAS_BITMAP_MODE=1
|
||||||
|
|
||||||
[output]
|
[output]
|
||||||
style=per_bank
|
style=per_bank
|
||||||
format=startaddr,allocated
|
format=startaddr,allocated
|
||||||
|
|
|
@ -1,5 +1,13 @@
|
||||||
// target-independent standard I/O routines
|
// target-independent standard I/O routines
|
||||||
|
|
||||||
|
import string
|
||||||
|
#if ZX_SPECTRUM
|
||||||
|
import stdio_zxspectrum
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#if not(ZX_SPECTRUM)
|
||||||
|
|
||||||
void putstr(pointer str, byte len) {
|
void putstr(pointer str, byte len) {
|
||||||
byte index
|
byte index
|
||||||
index = 0
|
index = 0
|
||||||
|
@ -8,7 +16,6 @@ void putstr(pointer str, byte len) {
|
||||||
index += 1
|
index += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void putstrz(pointer str) {
|
void putstrz(pointer str) {
|
||||||
byte index
|
byte index
|
||||||
index = 0
|
index = 0
|
||||||
|
@ -18,11 +25,5 @@ void putstrz(pointer str) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
byte strzlen(pointer str) {
|
#endif
|
||||||
byte index
|
|
||||||
index = 0
|
|
||||||
while str[index] != 0 {
|
|
||||||
index += 1
|
|
||||||
}
|
|
||||||
return index
|
|
||||||
}
|
|
||||||
|
|
31
include/stdio_zxspectrum.mfk
Normal file
31
include/stdio_zxspectrum.mfk
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
|
||||||
|
#if not(ZX_SPECTRUM)
|
||||||
|
#warn stdio_zxspectrum module should be only used on ZX Spectrum-compatible targets
|
||||||
|
#endif
|
||||||
|
|
||||||
|
import stdio
|
||||||
|
|
||||||
|
void putstr(pointer str, byte len) {
|
||||||
|
asm {
|
||||||
|
? LD HL,(str)
|
||||||
|
? LD D, H
|
||||||
|
? LD E, L
|
||||||
|
? LD A,(len)
|
||||||
|
? LD B, 0
|
||||||
|
? LD C, A
|
||||||
|
CALL 8252
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void putstrz(pointer str) {
|
||||||
|
word length = strzlen(str)
|
||||||
|
asm {
|
||||||
|
? LD HL,(str)
|
||||||
|
? LD D, H
|
||||||
|
? LD E, L
|
||||||
|
? LD HL,(length)
|
||||||
|
? LD B, H
|
||||||
|
? LD C, L
|
||||||
|
CALL 8252
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,46 +1,9 @@
|
||||||
// target-independent things
|
// target-independent things
|
||||||
|
|
||||||
word nmi_routine_addr @$FFFA
|
#if ARCH_6502
|
||||||
word reset_routine_addr @$FFFC
|
import stdlib_6502
|
||||||
word irq_routine_addr @$FFFE
|
#elseif ARCH_Z80
|
||||||
|
import stdlib_z80
|
||||||
macro asm void poke(word const addr, byte a) {
|
#else
|
||||||
STA addr
|
#warn Unsupported architecture
|
||||||
}
|
#endif
|
||||||
|
|
||||||
macro asm byte peek(word const addr) {
|
|
||||||
?LDA addr
|
|
||||||
}
|
|
||||||
|
|
||||||
macro asm void disable_irq() {
|
|
||||||
SEI
|
|
||||||
}
|
|
||||||
|
|
||||||
macro asm void enable_irq() {
|
|
||||||
CLI
|
|
||||||
}
|
|
||||||
|
|
||||||
asm byte hi_nibble_to_hex(byte a) {
|
|
||||||
LSR
|
|
||||||
LSR
|
|
||||||
LSR
|
|
||||||
LSR
|
|
||||||
JMP lo_nibble_to_hex
|
|
||||||
}
|
|
||||||
|
|
||||||
asm byte lo_nibble_to_hex(byte a) {
|
|
||||||
AND #$F
|
|
||||||
CLC
|
|
||||||
ADC #$30
|
|
||||||
CMP #$3A
|
|
||||||
BCC _lo_nibble_to_hex_lbl
|
|
||||||
ADC #$6 // carry is set
|
|
||||||
_lo_nibble_to_hex_lbl:
|
|
||||||
RTS
|
|
||||||
}
|
|
||||||
|
|
||||||
macro asm void panic() {
|
|
||||||
JSR _panic
|
|
||||||
}
|
|
||||||
|
|
||||||
array __constant8 = [8]
|
|
||||||
|
|
50
include/stdlib_6502.mfk
Normal file
50
include/stdlib_6502.mfk
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
// target-independent things
|
||||||
|
|
||||||
|
#if not(ARCH_6502)
|
||||||
|
#warn stdlib_6502 module should be only used on 6502-compatible targets
|
||||||
|
#endif
|
||||||
|
|
||||||
|
word nmi_routine_addr @$FFFA
|
||||||
|
word reset_routine_addr @$FFFC
|
||||||
|
word irq_routine_addr @$FFFE
|
||||||
|
|
||||||
|
macro asm void poke(word const addr, byte a) {
|
||||||
|
STA addr
|
||||||
|
}
|
||||||
|
|
||||||
|
macro asm byte peek(word const addr) {
|
||||||
|
LDA addr
|
||||||
|
}
|
||||||
|
|
||||||
|
macro asm void disable_irq() {
|
||||||
|
SEI
|
||||||
|
}
|
||||||
|
|
||||||
|
macro asm void enable_irq() {
|
||||||
|
CLI
|
||||||
|
}
|
||||||
|
|
||||||
|
asm byte hi_nibble_to_hex(byte a) {
|
||||||
|
LSR
|
||||||
|
LSR
|
||||||
|
LSR
|
||||||
|
LSR
|
||||||
|
JMP lo_nibble_to_hex
|
||||||
|
}
|
||||||
|
|
||||||
|
asm byte lo_nibble_to_hex(byte a) {
|
||||||
|
AND #$F
|
||||||
|
CLC
|
||||||
|
ADC #$30
|
||||||
|
CMP #$3A
|
||||||
|
BCC _lo_nibble_to_hex_lbl
|
||||||
|
ADC #$6 // carry is set
|
||||||
|
_lo_nibble_to_hex_lbl:
|
||||||
|
RTS
|
||||||
|
}
|
||||||
|
|
||||||
|
macro asm void panic() {
|
||||||
|
JSR _panic
|
||||||
|
}
|
||||||
|
|
||||||
|
array __constant8 = [8]
|
45
include/stdlib_z80.mfk
Normal file
45
include/stdlib_z80.mfk
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
// target-independent things
|
||||||
|
|
||||||
|
#if not(ARCH_Z80)
|
||||||
|
#warn stdlib_z80 module should be only used on 6502-compatible targets
|
||||||
|
#endif
|
||||||
|
|
||||||
|
macro asm void poke(word const addr, byte a) {
|
||||||
|
LD (addr), A
|
||||||
|
}
|
||||||
|
|
||||||
|
macro asm byte peek(word const addr) {
|
||||||
|
LD A, (addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
macro asm void disable_irq() {
|
||||||
|
DI
|
||||||
|
}
|
||||||
|
|
||||||
|
macro asm void enable_irq() {
|
||||||
|
EI
|
||||||
|
}
|
||||||
|
|
||||||
|
asm byte hi_nibble_to_hex(byte a) {
|
||||||
|
SRL A
|
||||||
|
SRL A
|
||||||
|
SRL A
|
||||||
|
SRL A
|
||||||
|
JP lo_nibble_to_hex
|
||||||
|
}
|
||||||
|
|
||||||
|
asm byte lo_nibble_to_hex(byte a) {
|
||||||
|
AND $F
|
||||||
|
ADD A,$30
|
||||||
|
CP $3A
|
||||||
|
JR C,_lo_nibble_to_hex_lbl
|
||||||
|
ADD A,$6 // carry is set
|
||||||
|
_lo_nibble_to_hex_lbl:
|
||||||
|
RET
|
||||||
|
}
|
||||||
|
|
||||||
|
macro asm void panic() {
|
||||||
|
CALL _panic
|
||||||
|
}
|
||||||
|
|
||||||
|
array __constant8 = [8]
|
10
include/string.mfk
Normal file
10
include/string.mfk
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
|
||||||
|
|
||||||
|
byte strzlen(pointer str) {
|
||||||
|
byte index
|
||||||
|
index = 0
|
||||||
|
while str[index] != 0 {
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
return index
|
||||||
|
}
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
[compilation]
|
[compilation]
|
||||||
arch=nmos
|
arch=nmos
|
||||||
modules=vcs_hardware,default_panic
|
modules=vcs_hardware,default_panic,stdlib
|
||||||
ro_arrays=true
|
ro_arrays=true
|
||||||
; use -fzp-register to override this:
|
; use -fzp-register to override this:
|
||||||
zeropage_register=false
|
zeropage_register=false
|
||||||
|
@ -21,6 +21,13 @@ segment_default_end=$ef
|
||||||
segment_prgrom_start=$f000
|
segment_prgrom_start=$f000
|
||||||
segment_prgrom_end=$ffff
|
segment_prgrom_end=$ffff
|
||||||
|
|
||||||
|
[define]
|
||||||
|
ATARI_2600=1
|
||||||
|
WIDESCREEN=0
|
||||||
|
KEYBOARD=0
|
||||||
|
JOYSTICKS=2
|
||||||
|
HAS_BITMAP_MODE=0
|
||||||
|
|
||||||
[output]
|
[output]
|
||||||
style=single
|
style=single
|
||||||
format=prgrom:$f000:$ffff
|
format=prgrom:$f000:$ffff
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
|
||||||
|
#if not(ATARI_2600)
|
||||||
|
#warn vcs_hardware module should be only used on Atari 2600-compatible targets
|
||||||
|
#endif
|
||||||
|
|
||||||
byte VSYNC @$00
|
byte VSYNC @$00
|
||||||
byte VBLANK @$01
|
byte VBLANK @$01
|
||||||
byte WSYNC @$02
|
byte WSYNC @$02
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
arch=nmos
|
arch=nmos
|
||||||
encoding=petscii
|
encoding=petscii
|
||||||
screen_encoding=petscr
|
screen_encoding=petscr
|
||||||
modules=loader_1001,vic20_kernal,default_panic
|
modules=loader_1001,vic20_kernal,default_panic,stdlib
|
||||||
|
|
||||||
|
|
||||||
[allocation]
|
[allocation]
|
||||||
|
@ -11,6 +11,14 @@ zp_pointers=$C1,$C3,$FB,$FD,$39,$3B,$3D,$43,$4B
|
||||||
segment_default_start=$100D
|
segment_default_start=$100D
|
||||||
segment_default_end=$1CFF
|
segment_default_end=$1CFF
|
||||||
|
|
||||||
|
[define]
|
||||||
|
CBM=1
|
||||||
|
CBM_VIC=1
|
||||||
|
WIDESCREEN=0
|
||||||
|
KEYBOARD=1
|
||||||
|
JOYSTICKS=1
|
||||||
|
HAS_BITMAP_MODE=1
|
||||||
|
|
||||||
[output]
|
[output]
|
||||||
style=single
|
style=single
|
||||||
format=startaddr,allocated
|
format=startaddr,allocated
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
arch=nmos
|
arch=nmos
|
||||||
encoding=petscii
|
encoding=petscii
|
||||||
screen_encoding=petscr
|
screen_encoding=petscr
|
||||||
modules=loader_0401,vic20_kernal,default_panic
|
modules=loader_0401,vic20_kernal,default_panic,stdlib
|
||||||
|
|
||||||
|
|
||||||
[allocation]
|
[allocation]
|
||||||
|
@ -11,6 +11,14 @@ zp_pointers=$C1,$C3,$FB,$FD,$39,$3B,$3D,$43,$4B
|
||||||
segment_default_start=$40D
|
segment_default_start=$40D
|
||||||
segment_default_end=$1CFF
|
segment_default_end=$1CFF
|
||||||
|
|
||||||
|
[define]
|
||||||
|
CBM=1
|
||||||
|
CBM_VIC=1
|
||||||
|
WIDESCREEN=0
|
||||||
|
KEYBOARD=1
|
||||||
|
JOYSTICKS=1
|
||||||
|
HAS_BITMAP_MODE=1
|
||||||
|
|
||||||
[output]
|
[output]
|
||||||
style=single
|
style=single
|
||||||
format=startaddr,allocated
|
format=startaddr,allocated
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
arch=nmos
|
arch=nmos
|
||||||
encoding=petscii
|
encoding=petscii
|
||||||
screen_encoding=petscr
|
screen_encoding=petscr
|
||||||
modules=loader_1201,vic20_kernal,default_panic
|
modules=loader_1201,vic20_kernal,default_panic,stdlib
|
||||||
|
|
||||||
|
|
||||||
[allocation]
|
[allocation]
|
||||||
|
@ -11,6 +11,14 @@ zp_pointers=$C1,$C3,$FB,$FD,$39,$3B,$3D,$43,$4B
|
||||||
segment_default_start=$120D
|
segment_default_start=$120D
|
||||||
segment_default_end=$1FFF
|
segment_default_end=$1FFF
|
||||||
|
|
||||||
|
[define]
|
||||||
|
CBM=1
|
||||||
|
CBM_VIC=1
|
||||||
|
WIDESCREEN=0
|
||||||
|
KEYBOARD=1
|
||||||
|
JOYSTICKS=1
|
||||||
|
HAS_BITMAP_MODE=1
|
||||||
|
|
||||||
[output]
|
[output]
|
||||||
style=single
|
style=single
|
||||||
format=startaddr,allocated
|
format=startaddr,allocated
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
// Routines from C16 and Plus/4 KERNAL ROM
|
// Routines from C16 and Plus/4 KERNAL ROM
|
||||||
|
|
||||||
|
#if not(CBM_VIC)
|
||||||
|
#warn vic20_kernal module should be only used on VIC-20 targets
|
||||||
|
#endif
|
||||||
|
|
||||||
// CHROUT. Write byte to default output. (If not screen, must call OPEN and CHKOUT beforehands.)
|
// CHROUT. Write byte to default output. (If not screen, must call OPEN and CHKOUT beforehands.)
|
||||||
// Input: A = Byte to write.
|
// Input: A = Byte to write.
|
||||||
asm void putchar(byte a) @$FFD2 extern
|
asm void putchar(byte a) @$FFD2 extern
|
6
include/x_coord.mfk
Normal file
6
include/x_coord.mfk
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
#if WIDESCREEN
|
||||||
|
alias x_coord = word
|
||||||
|
#else
|
||||||
|
alias x_coord = byte
|
||||||
|
#endif
|
|
@ -1,4 +1,8 @@
|
||||||
|
|
||||||
|
#if not(ARCH_6502)
|
||||||
|
#warn zp_reg module should be used only on 6502-compatible targets
|
||||||
|
#endif
|
||||||
|
|
||||||
inline asm byte __mul_u8u8u8() {
|
inline asm byte __mul_u8u8u8() {
|
||||||
? LDA #0
|
? LDA #0
|
||||||
? JMP __mul_u8u8u8_start
|
? JMP __mul_u8u8u8_start
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
[compilation]
|
[compilation]
|
||||||
arch=z80
|
arch=z80
|
||||||
encoding=bbc
|
encoding=bbc
|
||||||
modules=default_panic,zxspectrum
|
modules=default_panic,zxspectrum,stdlib
|
||||||
|
|
||||||
[allocation]
|
[allocation]
|
||||||
segments=default,slowram
|
segments=default,slowram
|
||||||
|
@ -13,6 +13,14 @@ segment_default_end=$ffff
|
||||||
segment_slowram_start=$5ccb
|
segment_slowram_start=$5ccb
|
||||||
segment_slowram_end=$7fff
|
segment_slowram_end=$7fff
|
||||||
|
|
||||||
|
[define]
|
||||||
|
ZX_SPECTRUM=1
|
||||||
|
WIDESCREEN=0
|
||||||
|
KEYBOARD=1
|
||||||
|
; TODO: ?
|
||||||
|
JOYSTICKS=1
|
||||||
|
HAS_BITMAP_MODE=1
|
||||||
|
|
||||||
[output]
|
[output]
|
||||||
style=single
|
style=single
|
||||||
format=tap
|
format=tap
|
||||||
|
|
|
@ -1,4 +1,23 @@
|
||||||
|
|
||||||
|
#if not(ZX_SPECTRUM)
|
||||||
|
#warn zxspectrum module should be only used on ZX Spectrum-compatible targets
|
||||||
|
#endif
|
||||||
|
|
||||||
inline asm void putchar(byte a) {
|
inline asm void putchar(byte a) {
|
||||||
rst $10
|
rst $10
|
||||||
? ret
|
? ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline asm void set_border(byte a) {
|
||||||
|
out (254),a
|
||||||
|
? ret
|
||||||
|
}
|
||||||
|
|
||||||
|
const byte black = 0
|
||||||
|
const byte blue = 1
|
||||||
|
const byte red = 2
|
||||||
|
const byte purple = 3
|
||||||
|
const byte green = 4
|
||||||
|
const byte cyan = 5
|
||||||
|
const byte yellow = 6
|
||||||
|
const byte white = 7
|
||||||
|
|
|
@ -116,7 +116,7 @@ case class CompilationOptions(platform: Platform, commandLineFlags: Map[Compilat
|
||||||
}
|
}
|
||||||
|
|
||||||
object CpuFamily extends Enumeration {
|
object CpuFamily extends Enumeration {
|
||||||
val M6502, I80, M6809, I8086, M65K, ARM = Value
|
val M6502, I80, M6809, I86, M68K, ARM = Value
|
||||||
|
|
||||||
def forType(cpu: Cpu.Value): CpuFamily.Value = {
|
def forType(cpu: Cpu.Value): CpuFamily.Value = {
|
||||||
import Cpu._
|
import Cpu._
|
||||||
|
|
|
@ -30,6 +30,7 @@ case class Context(inputFileNames: List[String],
|
||||||
outputLabels: Boolean = false,
|
outputLabels: Boolean = false,
|
||||||
includePath: List[String] = Nil,
|
includePath: List[String] = Nil,
|
||||||
flags: Map[CompilationFlag.Value, Boolean] = Map(),
|
flags: Map[CompilationFlag.Value, Boolean] = Map(),
|
||||||
|
features: Map[String, Long] = Map(),
|
||||||
verbosity: Option[Int] = None) {
|
verbosity: Option[Int] = None) {
|
||||||
def changeFlag(f: CompilationFlag.Value, b: Boolean): Context = {
|
def changeFlag(f: CompilationFlag.Value, b: Boolean): Context = {
|
||||||
if (flags.contains(f)) {
|
if (flags.contains(f)) {
|
||||||
|
@ -73,7 +74,7 @@ object Main {
|
||||||
val platform = Platform.lookupPlatformFile(c.includePath, c.platform.getOrElse {
|
val platform = Platform.lookupPlatformFile(c.includePath, c.platform.getOrElse {
|
||||||
ErrorReporting.info("No platform selected, defaulting to `c64`")
|
ErrorReporting.info("No platform selected, defaulting to `c64`")
|
||||||
"c64"
|
"c64"
|
||||||
})
|
}, c.features)
|
||||||
val options = CompilationOptions(platform, c.flags, c.outputFileName, c.zpRegisterSize.getOrElse(platform.zpRegisterSize))
|
val options = CompilationOptions(platform, c.flags, c.outputFileName, c.zpRegisterSize.getOrElse(platform.zpRegisterSize))
|
||||||
ErrorReporting.debug("Effective flags: ")
|
ErrorReporting.debug("Effective flags: ")
|
||||||
options.flags.toSeq.sortBy(_._1).foreach{
|
options.flags.toSeq.sortBy(_._1).foreach{
|
||||||
|
@ -258,6 +259,21 @@ object Main {
|
||||||
c.copy(runFileName = Some(p))
|
c.copy(runFileName = Some(p))
|
||||||
}.description("Program to run after successful compilation.")
|
}.description("Program to run after successful compilation.")
|
||||||
|
|
||||||
|
parameter("-D", "--define").placeholder("<feature>=<value>").action { (p, c) =>
|
||||||
|
val tokens = p.split('=')
|
||||||
|
if (tokens.length == 2) {
|
||||||
|
assertNone(c.features.get(tokens(0)), "Feature already defined")
|
||||||
|
try {
|
||||||
|
c.copy(features = c.features + (tokens(0) -> tokens(1).toLong))
|
||||||
|
} catch {
|
||||||
|
case _:java.lang.NumberFormatException =>
|
||||||
|
ErrorReporting.fatal("Invalid syntax for -D option")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ErrorReporting.fatal("Invalid syntax for -D option")
|
||||||
|
}
|
||||||
|
}.description("Define a feature value for the preprocessor.")
|
||||||
|
|
||||||
endOfFlags("--").description("Marks the end of options.")
|
endOfFlags("--").description("Marks the end of options.")
|
||||||
|
|
||||||
fluff("", "Verbosity options:", "")
|
fluff("", "Verbosity options:", "")
|
||||||
|
|
|
@ -23,6 +23,7 @@ class Platform(
|
||||||
val startingModules: List[String],
|
val startingModules: List[String],
|
||||||
val defaultCodec: TextCodec,
|
val defaultCodec: TextCodec,
|
||||||
val screenCodec: TextCodec,
|
val screenCodec: TextCodec,
|
||||||
|
val features: Map[String, Long],
|
||||||
val outputPackager: OutputPackager,
|
val outputPackager: OutputPackager,
|
||||||
val codeAllocators: Map[String, UpwardByteAllocator],
|
val codeAllocators: Map[String, UpwardByteAllocator],
|
||||||
val variableAllocators: Map[String, VariableAllocator],
|
val variableAllocators: Map[String, VariableAllocator],
|
||||||
|
@ -41,18 +42,18 @@ class Platform(
|
||||||
|
|
||||||
object Platform {
|
object Platform {
|
||||||
|
|
||||||
def lookupPlatformFile(includePath: List[String], platformName: String): Platform = {
|
def lookupPlatformFile(includePath: List[String], platformName: String, featureOverrides: Map[String, Long]): Platform = {
|
||||||
includePath.foreach { dir =>
|
includePath.foreach { dir =>
|
||||||
val file = Paths.get(dir, platformName + ".ini").toFile
|
val file = Paths.get(dir, platformName + ".ini").toFile
|
||||||
ErrorReporting.debug("Checking " + file)
|
ErrorReporting.debug("Checking " + file)
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
return load(file)
|
return load(file, featureOverrides)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ErrorReporting.fatal(s"Platfom definition `$platformName` not found", None)
|
ErrorReporting.fatal(s"Platfom definition `$platformName` not found", None)
|
||||||
}
|
}
|
||||||
|
|
||||||
def load(file: File): Platform = {
|
def load(file: File, featureOverrides: Map[String, Long]): Platform = {
|
||||||
val conf = new INIConfiguration()
|
val conf = new INIConfiguration()
|
||||||
val bytes = Files.readAllBytes(file.toPath)
|
val bytes = Files.readAllBytes(file.toPath)
|
||||||
conf.read(new StringReader(new String(bytes, StandardCharsets.UTF_8)))
|
conf.read(new StringReader(new String(bytes, StandardCharsets.UTF_8)))
|
||||||
|
@ -193,12 +194,26 @@ object Platform {
|
||||||
case x => ErrorReporting.fatal(s"Invalid output style: `$x`")
|
case x => ErrorReporting.fatal(s"Invalid output style: `$x`")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val builtInFeatures = builtInCpuFeatures(cpu)
|
||||||
|
|
||||||
|
import scala.collection.JavaConverters._
|
||||||
|
val ds = conf.getSection("define")
|
||||||
|
val definedFeatures = ds.getKeys().asScala.toList.map { k =>
|
||||||
|
val value = ds.get(classOf[String], k).trim() match {
|
||||||
|
case "true" | "on" | "yes" => 1L
|
||||||
|
case "false" | "off" | "no" | "" => 0L
|
||||||
|
case x => x.toLong
|
||||||
|
}
|
||||||
|
k -> value
|
||||||
|
}.toMap
|
||||||
|
|
||||||
new Platform(
|
new Platform(
|
||||||
cpu,
|
cpu,
|
||||||
flagOverrides,
|
flagOverrides,
|
||||||
startingModules,
|
startingModules,
|
||||||
codec,
|
codec,
|
||||||
srcCodec,
|
srcCodec,
|
||||||
|
builtInFeatures ++ definedFeatures ++ featureOverrides,
|
||||||
outputPackager,
|
outputPackager,
|
||||||
codeAllocators.toMap,
|
codeAllocators.toMap,
|
||||||
variableAllocators.toMap,
|
variableAllocators.toMap,
|
||||||
|
@ -211,6 +226,25 @@ object Platform {
|
||||||
outputStyle)
|
outputStyle)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@inline
|
||||||
|
private def toLong(b: Boolean): Long = if (b) 1L else 0L
|
||||||
|
|
||||||
|
def builtInCpuFeatures(cpu: Cpu.Value): Map[String, Long] = {
|
||||||
|
Map[String, Long](
|
||||||
|
"ARCH_6502" -> toLong(CpuFamily.forType(cpu) == CpuFamily.M6502),
|
||||||
|
"ARCH_Z80" -> toLong(CpuFamily.forType(cpu) == CpuFamily.I80),
|
||||||
|
"ARCH_X86" -> toLong(CpuFamily.forType(cpu) == CpuFamily.I86),
|
||||||
|
"ARCH_6509" -> toLong(CpuFamily.forType(cpu) == CpuFamily.M6809),
|
||||||
|
"ARCH_ARM" -> toLong(CpuFamily.forType(cpu) == CpuFamily.ARM),
|
||||||
|
"ARCH_68K" -> toLong(CpuFamily.forType(cpu) == CpuFamily.M68K),
|
||||||
|
"HAS_HARDWARE_MULTIPLY" -> (CpuFamily.forType(cpu) match {
|
||||||
|
case CpuFamily.M6502 | CpuFamily.I80 | CpuFamily.M6809 => 0L
|
||||||
|
case CpuFamily.I86 | CpuFamily.ARM | CpuFamily.M68K => 1L
|
||||||
|
})
|
||||||
|
// TODO
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
def parseNumberOrRange(s:String): Seq[Int] = {
|
def parseNumberOrRange(s:String): Seq[Int] = {
|
||||||
if (s.contains("-")) {
|
if (s.contains("-")) {
|
||||||
val segments = s.split("-")
|
val segments = s.split("-")
|
||||||
|
|
|
@ -378,7 +378,7 @@ object Z80BulkMemoryOperations {
|
||||||
}
|
}
|
||||||
Z80StatementCompiler.compile(ctx, IfStatement(
|
Z80StatementCompiler.compile(ctx, IfStatement(
|
||||||
FunctionCallExpression(operator, List(f.start, f.end)),
|
FunctionCallExpression(operator, List(f.start, f.end)),
|
||||||
List(Z80AssemblyStatement(ZOpcode.NOP, NoRegisters, LiteralExpression(0, 1), elidable = false)),
|
List(Z80AssemblyStatement(ZOpcode.NOP, NoRegisters, None, LiteralExpression(0, 1), elidable = false)),
|
||||||
Nil))
|
Nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -141,14 +141,35 @@ object Z80StatementCompiler extends AbstractStatementCompiler[ZLine] {
|
||||||
}
|
}
|
||||||
case ExpressionStatement(e) =>
|
case ExpressionStatement(e) =>
|
||||||
Z80ExpressionCompiler.compile(ctx, e, ZExpressionTarget.NOTHING)
|
Z80ExpressionCompiler.compile(ctx, e, ZExpressionTarget.NOTHING)
|
||||||
case Z80AssemblyStatement(op, reg, expression, elidable) =>
|
case Z80AssemblyStatement(op, reg, offset, expression, elidable) =>
|
||||||
val param = ctx.env.evalForAsm(expression) match {
|
val param = ctx.env.evalForAsm(expression) match {
|
||||||
case Some(v) => v
|
case Some(v) => v
|
||||||
case None =>
|
case None =>
|
||||||
ErrorReporting.error("Inlining failed due to non-constant things", expression.position)
|
ErrorReporting.error("Inlining failed due to non-constant things", expression.position)
|
||||||
Constant.Zero
|
Constant.Zero
|
||||||
}
|
}
|
||||||
List(ZLine(op, reg, param, elidable))
|
val registers = (reg, offset) match {
|
||||||
|
case (OneRegister(r), Some(o)) => ctx.env.evalForAsm(expression) match {
|
||||||
|
case Some(NumericConstant(v, _)) => OneRegisterOffset(r, v.toInt)
|
||||||
|
case Some(_) =>
|
||||||
|
ErrorReporting.error("Non-numeric constant", o.position)
|
||||||
|
reg
|
||||||
|
case None =>
|
||||||
|
ErrorReporting.error("Inlining failed due to non-constant things", o.position)
|
||||||
|
reg
|
||||||
|
}
|
||||||
|
case (TwoRegisters(t, s), Some(o)) => ctx.env.evalForAsm(expression) match {
|
||||||
|
case Some(NumericConstant(v, _)) => TwoRegistersOffset(t, s, v.toInt)
|
||||||
|
case Some(_) =>
|
||||||
|
ErrorReporting.error("Non-numeric constant", o.position)
|
||||||
|
reg
|
||||||
|
case None =>
|
||||||
|
ErrorReporting.error("Inlining failed due to non-constant things", o.position)
|
||||||
|
reg
|
||||||
|
}
|
||||||
|
case _ => reg
|
||||||
|
}
|
||||||
|
List(ZLine(op, registers, param, elidable))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -298,7 +298,7 @@ case class MosAssemblyStatement(opcode: Opcode.Value, addrMode: AddrMode.Value,
|
||||||
override def getAllExpressions: List[Expression] = List(expression)
|
override def getAllExpressions: List[Expression] = List(expression)
|
||||||
}
|
}
|
||||||
|
|
||||||
case class Z80AssemblyStatement(opcode: ZOpcode.Value, registers: ZRegisters, expression: Expression, elidable: Boolean) extends ExecutableStatement {
|
case class Z80AssemblyStatement(opcode: ZOpcode.Value, registers: ZRegisters, offsetExpression: Option[Expression], expression: Expression, elidable: Boolean) extends ExecutableStatement {
|
||||||
override def getAllExpressions: List[Expression] = List(expression)
|
override def getAllExpressions: List[Expression] = List(expression)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -183,6 +183,23 @@ class Z80Assembler(program: Program,
|
||||||
writeByte(bank, index, 0x40 + internalRegisterIndex(source) + internalRegisterIndex(target) * 8)
|
writeByte(bank, index, 0x40 + internalRegisterIndex(source) + internalRegisterIndex(target) * 8)
|
||||||
index + 1
|
index + 1
|
||||||
}
|
}
|
||||||
|
case ZLine(IN_IMM, OneRegister(ZRegister.A), param, _) =>
|
||||||
|
writeByte(bank, index, 0xdb)
|
||||||
|
writeByte(bank, index + 1, param)
|
||||||
|
index + 2
|
||||||
|
case ZLine(IN_C, OneRegister(reg), param, _) =>
|
||||||
|
writeByte(bank, index, 0xed)
|
||||||
|
writeByte(bank, index + 1, 0x40 + 8 * internalRegisterIndex(reg))
|
||||||
|
index + 2
|
||||||
|
case ZLine(OUT_IMM, OneRegister(ZRegister.A), param, _) =>
|
||||||
|
writeByte(bank, index, 0xd3)
|
||||||
|
writeByte(bank, index + 1, param)
|
||||||
|
index + 2
|
||||||
|
case ZLine(OUT_C, OneRegister(reg), param, _) =>
|
||||||
|
writeByte(bank, index, 0xed)
|
||||||
|
writeByte(bank, index + 1, 0x41 + 8 * internalRegisterIndex(reg))
|
||||||
|
index + 2
|
||||||
|
|
||||||
case ZLine(CALL, NoRegisters, param, _) =>
|
case ZLine(CALL, NoRegisters, param, _) =>
|
||||||
writeByte(bank, index, 0xcd)
|
writeByte(bank, index, 0xcd)
|
||||||
writeWord(bank, index + 1, param)
|
writeWord(bank, index + 1, param)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package millfork.parser
|
package millfork.parser
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
import java.nio.file.{Files, Paths}
|
import java.nio.file.{Files, Paths}
|
||||||
|
|
||||||
import fastparse.core.Parsed.{Failure, Success}
|
import fastparse.core.Parsed.{Failure, Success}
|
||||||
|
@ -8,6 +9,7 @@ import millfork.error.ErrorReporting
|
||||||
import millfork.node.{ImportStatement, Position, Program}
|
import millfork.node.{ImportStatement, Position, Program}
|
||||||
|
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
|
import scala.collection.convert.ImplicitConversionsToScala._
|
||||||
|
|
||||||
abstract class AbstractSourceLoadingQueue[T](val initialFilenames: List[String],
|
abstract class AbstractSourceLoadingQueue[T](val initialFilenames: List[String],
|
||||||
val includePath: List[String],
|
val includePath: List[String],
|
||||||
|
@ -49,15 +51,15 @@ abstract class AbstractSourceLoadingQueue[T](val initialFilenames: List[String],
|
||||||
ErrorReporting.fatal(s"Module `$moduleName` not found", position)
|
ErrorReporting.fatal(s"Module `$moduleName` not found", position)
|
||||||
}
|
}
|
||||||
|
|
||||||
def createParser(filename: String, src: String, parentDir: String) : MfParser[T]
|
def createParser(filename: String, src: String, parentDir: String, featureConstants: Map[String, Long]) : MfParser[T]
|
||||||
|
|
||||||
def parseModule(moduleName: String, includePath: List[String], why: Either[Option[Position], String]): Unit = {
|
def parseModule(moduleName: String, includePath: List[String], why: Either[Option[Position], String]): Unit = {
|
||||||
val filename: String = why.fold(p => lookupModuleFile(includePath, moduleName, p), s => s)
|
val filename: String = why.fold(p => lookupModuleFile(includePath, moduleName, p), s => s)
|
||||||
ErrorReporting.debug(s"Parsing $filename")
|
ErrorReporting.debug(s"Parsing $filename")
|
||||||
val path = Paths.get(filename)
|
val path = Paths.get(filename)
|
||||||
val parentDir = path.toFile.getAbsoluteFile.getParent
|
val parentDir = path.toFile.getAbsoluteFile.getParent
|
||||||
val src = new String(Files.readAllBytes(path))
|
val (src, featureConstants) = Preprocessor(options, Files.readAllLines(path, StandardCharsets.UTF_8).toIndexedSeq)
|
||||||
val parser = createParser(filename, src, parentDir)
|
val parser = createParser(filename, src, parentDir, featureConstants)
|
||||||
parser.toAst match {
|
parser.toAst match {
|
||||||
case Success(prog, _) =>
|
case Success(prog, _) =>
|
||||||
parsedModules.synchronized {
|
parsedModules.synchronized {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package millfork.parser
|
package millfork.parser
|
||||||
|
|
||||||
|
import java.lang.Long.parseLong
|
||||||
import java.nio.file.{Files, Paths}
|
import java.nio.file.{Files, Paths}
|
||||||
import java.util
|
import java.util
|
||||||
|
|
||||||
|
@ -12,7 +13,7 @@ import millfork.{CompilationFlag, CompilationOptions, SeparatedList}
|
||||||
/**
|
/**
|
||||||
* @author Karol Stasiak
|
* @author Karol Stasiak
|
||||||
*/
|
*/
|
||||||
abstract class MfParser[T](filename: String, input: String, currentDirectory: String, options: CompilationOptions) {
|
abstract class MfParser[T](filename: String, input: String, currentDirectory: String, options: CompilationOptions, featureConstants: Map[String, Long]) {
|
||||||
|
|
||||||
import MfParser._
|
import MfParser._
|
||||||
|
|
||||||
|
@ -62,6 +63,14 @@ abstract class MfParser[T](filename: String, input: String, currentDirectory: St
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//noinspection NameBooleanParameters
|
||||||
|
val variableAtom: P[Expression] = identifier.map{ i =>
|
||||||
|
featureConstants.get(i) match {
|
||||||
|
case Some(value) => LiteralExpression(value, size(value, false ,false, false))
|
||||||
|
case None => VariableExpression(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val literalAtom: P[LiteralExpression] = charAtom | binaryAtom | hexAtom | octalAtom | quaternaryAtom | decimalAtom
|
val literalAtom: P[LiteralExpression] = charAtom | binaryAtom | hexAtom | octalAtom | quaternaryAtom | decimalAtom
|
||||||
|
|
||||||
val atom: P[Expression] = P(position() ~ (literalAtom | variableAtom)).map{case (p,a) => a.pos(p)}
|
val atom: P[Expression] = P(position() ~ (literalAtom | variableAtom)).map{case (p,a) => a.pos(p)}
|
||||||
|
@ -108,6 +117,12 @@ abstract class MfParser[T](filename: String, input: String, currentDirectory: St
|
||||||
asmExpression.map(_ -> false)
|
asmExpression.map(_ -> false)
|
||||||
)).map { case (p, e) => e._1.pos(p) -> e._2 }
|
)).map { case (p, e) => e._1.pos(p) -> e._2 }
|
||||||
|
|
||||||
|
def asmExpressionWithParensOrApostrophe: P[(Expression, Boolean)] = (position() ~ NoCut(
|
||||||
|
("(" ~ HWS ~ asmExpression ~ HWS ~ ")").map(_ -> true) |
|
||||||
|
(asmExpression ~ "'").map(_ -> true) |
|
||||||
|
asmExpression.map(_ -> false)
|
||||||
|
)).map { case (p, e) => e._1.pos(p) -> e._2 }
|
||||||
|
|
||||||
def asmParamDefinition: P[ParameterDeclaration]
|
def asmParamDefinition: P[ParameterDeclaration]
|
||||||
|
|
||||||
def arrayListElement: P[ArrayContents] = arrayStringContents | arrayLoopContents | arrayFileContents | mfExpression(nonStatementLevel).map(e => LiteralContents(List(e)))
|
def arrayListElement: P[ArrayContents] = arrayStringContents | arrayLoopContents | arrayFileContents | mfExpression(nonStatementLevel).map(e => LiteralContents(List(e)))
|
||||||
|
@ -400,14 +415,14 @@ object MfParser {
|
||||||
|
|
||||||
val doubleQuotedString: P[List[Char]] = P("\"" ~/ CharsWhile(c => c != '\"' && c != '\n' && c != '\r').! ~ "\"").map(_.toList)
|
val doubleQuotedString: P[List[Char]] = P("\"" ~/ CharsWhile(c => c != '\"' && c != '\n' && c != '\r').! ~ "\"").map(_.toList)
|
||||||
|
|
||||||
def size(value: Int, wordLiteral: Boolean, farwordLiteral: Boolean, longLiteral: Boolean): Int = {
|
def size(value: Long, wordLiteral: Boolean, farwordLiteral: Boolean, longLiteral: Boolean): Int = {
|
||||||
val w = value > 255 || value < -0x80 || wordLiteral
|
val w = value > 255 || value < -0x80 || wordLiteral
|
||||||
val f = value > 0xffff || value < -0x8000 || farwordLiteral
|
val f = value > 0xffff || value < -0x8000 || farwordLiteral
|
||||||
val l = value > 0xffffff || value < -0x800000 || longLiteral
|
val l = value > 0xffffff || value < -0x800000 || longLiteral
|
||||||
if (l) 4 else if (f) 3 else if (w) 2 else 1
|
if (l) 4 else if (f) 3 else if (w) 2 else 1
|
||||||
}
|
}
|
||||||
|
|
||||||
def sign(abs: Int, minus: Boolean): Int = if (minus) -abs else abs
|
def sign(abs: Long, minus: Boolean): Long = if (minus) -abs else abs
|
||||||
|
|
||||||
val invalidCharLiteralTypes: Set[Int] = Set[Int](
|
val invalidCharLiteralTypes: Set[Int] = Set[Int](
|
||||||
Character.LINE_SEPARATOR,
|
Character.LINE_SEPARATOR,
|
||||||
|
@ -422,7 +437,7 @@ object MfParser {
|
||||||
minus <- "-".!.?
|
minus <- "-".!.?
|
||||||
s <- CharsWhileIn("1234567890", min = 1).!.opaque("<decimal digits>") ~ !("x" | "b")
|
s <- CharsWhileIn("1234567890", min = 1).!.opaque("<decimal digits>") ~ !("x" | "b")
|
||||||
} yield {
|
} yield {
|
||||||
val abs = Integer.parseInt(s, 10)
|
val abs = parseLong(s, 10)
|
||||||
val value = sign(abs, minus.isDefined)
|
val value = sign(abs, minus.isDefined)
|
||||||
LiteralExpression(value, size(value, s.length > 3, s.length > 5, s.length > 7))
|
LiteralExpression(value, size(value, s.length > 3, s.length > 5, s.length > 7))
|
||||||
}
|
}
|
||||||
|
@ -433,7 +448,7 @@ object MfParser {
|
||||||
_ <- P("0b" | "%") ~/ Pass
|
_ <- P("0b" | "%") ~/ Pass
|
||||||
s <- CharsWhileIn("01", min = 1).!.opaque("<binary digits>")
|
s <- CharsWhileIn("01", min = 1).!.opaque("<binary digits>")
|
||||||
} yield {
|
} yield {
|
||||||
val abs = Integer.parseInt(s, 2)
|
val abs = parseLong(s, 2)
|
||||||
val value = sign(abs, minus.isDefined)
|
val value = sign(abs, minus.isDefined)
|
||||||
LiteralExpression(value, size(value, s.length > 8, s.length > 16, s.length > 24))
|
LiteralExpression(value, size(value, s.length > 8, s.length > 16, s.length > 24))
|
||||||
}
|
}
|
||||||
|
@ -444,7 +459,7 @@ object MfParser {
|
||||||
_ <- P("0x" | "0X" | "$") ~/ Pass
|
_ <- P("0x" | "0X" | "$") ~/ Pass
|
||||||
s <- CharsWhileIn("1234567890abcdefABCDEF", min = 1).!.opaque("<hex digits>")
|
s <- CharsWhileIn("1234567890abcdefABCDEF", min = 1).!.opaque("<hex digits>")
|
||||||
} yield {
|
} yield {
|
||||||
val abs = Integer.parseInt(s, 16)
|
val abs = parseLong(s, 16)
|
||||||
val value = sign(abs, minus.isDefined)
|
val value = sign(abs, minus.isDefined)
|
||||||
LiteralExpression(value, size(value, s.length > 2, s.length > 4, s.length > 6))
|
LiteralExpression(value, size(value, s.length > 2, s.length > 4, s.length > 6))
|
||||||
}
|
}
|
||||||
|
@ -455,7 +470,7 @@ object MfParser {
|
||||||
_ <- P("0o" | "0O") ~/ Pass
|
_ <- P("0o" | "0O") ~/ Pass
|
||||||
s <- CharsWhileIn("01234567", min = 1).!.opaque("<octal digits>")
|
s <- CharsWhileIn("01234567", min = 1).!.opaque("<octal digits>")
|
||||||
} yield {
|
} yield {
|
||||||
val abs = Integer.parseInt(s, 8)
|
val abs = parseLong(s, 8)
|
||||||
val value = sign(abs, minus.isDefined)
|
val value = sign(abs, minus.isDefined)
|
||||||
LiteralExpression(value, size(value, s.length > 3, s.length > 6, s.length > 9))
|
LiteralExpression(value, size(value, s.length > 3, s.length > 6, s.length > 9))
|
||||||
}
|
}
|
||||||
|
@ -466,13 +481,11 @@ object MfParser {
|
||||||
_ <- P("0q" | "0Q") ~/ Pass
|
_ <- P("0q" | "0Q") ~/ Pass
|
||||||
s <- CharsWhileIn("0123", min = 1).!.opaque("<quaternary digits>")
|
s <- CharsWhileIn("0123", min = 1).!.opaque("<quaternary digits>")
|
||||||
} yield {
|
} yield {
|
||||||
val abs = Integer.parseInt(s, 4)
|
val abs = parseLong(s, 4)
|
||||||
val value = sign(abs, minus.isDefined)
|
val value = sign(abs, minus.isDefined)
|
||||||
LiteralExpression(value, size(value, s.length > 4, s.length > 8, s.length > 12))
|
LiteralExpression(value, size(value, s.length > 4, s.length > 8, s.length > 12))
|
||||||
}
|
}
|
||||||
|
|
||||||
val variableAtom: P[VariableExpression] = identifier.map(VariableExpression)
|
|
||||||
|
|
||||||
val mfOperators = List(
|
val mfOperators = List(
|
||||||
List("+=", "-=", "+'=", "-'=", "^=", "&=", "|=", "*=", "*'=", "<<=", ">>=", "<<'=", ">>'="),
|
List("+=", "-=", "+'=", "-'=", "^=", "&=", "|=", "*=", "*'=", "<<=", ">>=", "<<'=", ">>'="),
|
||||||
List("||", "^^"),
|
List("||", "^^"),
|
||||||
|
|
|
@ -10,7 +10,7 @@ import millfork.CompilationOptions
|
||||||
/**
|
/**
|
||||||
* @author Karol Stasiak
|
* @author Karol Stasiak
|
||||||
*/
|
*/
|
||||||
case class MosParser(filename: String, input: String, currentDirectory: String, options: CompilationOptions) extends MfParser[AssemblyLine](filename, input, currentDirectory, options) {
|
case class MosParser(filename: String, input: String, currentDirectory: String, options: CompilationOptions, featureConstants: Map[String, Long]) extends MfParser[AssemblyLine](filename, input, currentDirectory, options, featureConstants) {
|
||||||
|
|
||||||
import MfParser._
|
import MfParser._
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ case class MosParser(filename: String, input: String, currentDirectory: String,
|
||||||
val asmStatement: P[ExecutableStatement] = (position("assembly statement") ~ P(asmLabel | asmMacro | arrayContentsForAsm | asmInstruction)).map { case (p, s) => s.pos(p) } // TODO: macros
|
val asmStatement: P[ExecutableStatement] = (position("assembly statement") ~ P(asmLabel | asmMacro | arrayContentsForAsm | asmInstruction)).map { case (p, s) => s.pos(p) } // TODO: macros
|
||||||
|
|
||||||
|
|
||||||
val appcSimple: P[ParamPassingConvention] = P("xy" | "yx" | "ax" | "ay" | "xa" | "ya" | "stack" | "a" | "x" | "y").!.map {
|
val appcSimple: P[ParamPassingConvention] = P(("xy" | "yx" | "ax" | "ay" | "xa" | "ya" | "stack" | "a" | "x" | "y") ~ !letterOrDigit).!.map {
|
||||||
case "xy" => ByMosRegister(MosRegister.XY)
|
case "xy" => ByMosRegister(MosRegister.XY)
|
||||||
case "yx" => ByMosRegister(MosRegister.YX)
|
case "yx" => ByMosRegister(MosRegister.YX)
|
||||||
case "ax" => ByMosRegister(MosRegister.AX)
|
case "ax" => ByMosRegister(MosRegister.AX)
|
||||||
|
|
|
@ -10,7 +10,8 @@ class MosSourceLoadingQueue(initialFilenames: List[String],
|
||||||
includePath: List[String],
|
includePath: List[String],
|
||||||
options: CompilationOptions) extends AbstractSourceLoadingQueue[AssemblyLine](initialFilenames, includePath, options) {
|
options: CompilationOptions) extends AbstractSourceLoadingQueue[AssemblyLine](initialFilenames, includePath, options) {
|
||||||
|
|
||||||
override def createParser(filename: String, src: String, parentDir: String): MfParser[AssemblyLine] = MosParser(filename, src, parentDir, options)
|
override def createParser(filename: String, src: String, parentDir: String, featureConstants: Map[String, Long]): MfParser[AssemblyLine] =
|
||||||
|
MosParser(filename, src, parentDir, options, featureConstants)
|
||||||
|
|
||||||
def enqueueStandardModules(): Unit = {
|
def enqueueStandardModules(): Unit = {
|
||||||
if (options.zpRegisterSize > 0) {
|
if (options.zpRegisterSize > 0) {
|
||||||
|
|
243
src/main/scala/millfork/parser/Preprocessor.scala
Normal file
243
src/main/scala/millfork/parser/Preprocessor.scala
Normal file
|
@ -0,0 +1,243 @@
|
||||||
|
package millfork.parser
|
||||||
|
|
||||||
|
import fastparse.core.Parsed.{Failure, Success}
|
||||||
|
import millfork.{CompilationOptions, Platform, SeparatedList}
|
||||||
|
import millfork.error.ErrorReporting
|
||||||
|
import millfork.node.Position
|
||||||
|
|
||||||
|
import scala.collection.mutable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
object Preprocessor {
|
||||||
|
|
||||||
|
private val Regex = raw"\A\s*#\s*([a-z]+)\s*(.*?)\s*\z".r
|
||||||
|
|
||||||
|
def preprocessForTest(options: CompilationOptions, code: String): (String, Map[String, Long]) = {
|
||||||
|
apply(options, code.lines.toSeq)
|
||||||
|
}
|
||||||
|
|
||||||
|
case class IfContext(hadEnabled: Boolean, hadElse: Boolean, enabledBefore: Boolean)
|
||||||
|
|
||||||
|
def apply(options: CompilationOptions, lines: Seq[String]): (String, Map[String, Long]) = {
|
||||||
|
val platform = options.platform
|
||||||
|
// if (ErrorReporting.traceEnabled) {
|
||||||
|
// platform.features.foreach{
|
||||||
|
// case (k, v) => ErrorReporting.trace(f"#define $k%15s $v%d")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
val result = mutable.ListBuffer[String]()
|
||||||
|
val featureConstants = mutable.Map[String, Long]()
|
||||||
|
var enabled = true
|
||||||
|
val ifStack = mutable.Stack[IfContext]()
|
||||||
|
var lineNo = 0
|
||||||
|
|
||||||
|
def evalParam(param: String, pos: Some[Position]): Long = {
|
||||||
|
PreprocessorParser.expression.parse(param) match {
|
||||||
|
case Success(q, _) =>
|
||||||
|
val value = q.apply(platform.features).getOrElse(0L)
|
||||||
|
// ErrorReporting.trace(param + " ===> " + value)
|
||||||
|
value
|
||||||
|
case Failure(_, _, _) =>
|
||||||
|
ErrorReporting.error("Failed to parse expression", pos)
|
||||||
|
0L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (line <- lines) {
|
||||||
|
lineNo += 1
|
||||||
|
var resulting = ""
|
||||||
|
line match {
|
||||||
|
case Regex(keyword, param) =>
|
||||||
|
val pos = Some(Position("", lineNo, 0, 0))
|
||||||
|
keyword match {
|
||||||
|
case "use" => if (enabled) {
|
||||||
|
if (param == "") ErrorReporting.error("#use should have a parameter", pos)
|
||||||
|
featureConstants += param -> platform.features.getOrElse(param, {
|
||||||
|
ErrorReporting.warn(s"Undefined parameter $param, assuming 0", options, pos)
|
||||||
|
0L
|
||||||
|
})
|
||||||
|
}
|
||||||
|
case "fatal" => if (enabled) ErrorReporting.fatal(param, pos)
|
||||||
|
case "error" => if (enabled) ErrorReporting.error(param, pos)
|
||||||
|
case "warn" => if (enabled) ErrorReporting.warn(param, options, pos)
|
||||||
|
case "info" => if (enabled) ErrorReporting.info(param, pos)
|
||||||
|
case "if" =>
|
||||||
|
if (enabled) {
|
||||||
|
val value = evalParam(param, pos)
|
||||||
|
enabled = value != 0
|
||||||
|
ifStack.push(IfContext(enabled, false, true))
|
||||||
|
} else {
|
||||||
|
ifStack.push(IfContext(false, false, false))
|
||||||
|
}
|
||||||
|
case "endif" =>
|
||||||
|
if (param != "") ErrorReporting.error("#endif shouldn't have a parameter", pos)
|
||||||
|
if (ifStack.isEmpty) ErrorReporting.error("Unmatched #endif", pos)
|
||||||
|
else {
|
||||||
|
enabled = ifStack.pop().enabledBefore
|
||||||
|
}
|
||||||
|
case "elseif" =>
|
||||||
|
val skippingDepth = ifStack.top
|
||||||
|
if (ifStack.isEmpty) ErrorReporting.error("Unmatched #elseif", pos)
|
||||||
|
else {
|
||||||
|
val i = ifStack.top
|
||||||
|
if (i.hadElse) ErrorReporting.error("#elseif after #else", pos)
|
||||||
|
if (i.hadEnabled) {
|
||||||
|
enabled = false
|
||||||
|
} else {
|
||||||
|
val value = evalParam(param, pos)
|
||||||
|
enabled = value != 0
|
||||||
|
ifStack.push(ifStack.pop().copy(hadEnabled = enabled))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "else" =>
|
||||||
|
if (param != "") ErrorReporting.error("#else shouldn't have a parameter", pos)
|
||||||
|
if (ifStack.isEmpty) ErrorReporting.error("Unmatched #else", pos)
|
||||||
|
else {
|
||||||
|
val i = ifStack.top
|
||||||
|
if (i.hadElse) ErrorReporting.error("Duplicate #else", pos)
|
||||||
|
if (i.hadEnabled) {
|
||||||
|
enabled = false
|
||||||
|
} else {
|
||||||
|
enabled = !enabled
|
||||||
|
}
|
||||||
|
ifStack.push(ifStack.pop().copy(hadEnabled = true, hadElse = true))
|
||||||
|
}
|
||||||
|
case _ =>
|
||||||
|
ErrorReporting.error("Invalid preprocessor directive: #" + keyword, pos)
|
||||||
|
|
||||||
|
}
|
||||||
|
case _ => if (enabled) resulting = line
|
||||||
|
}
|
||||||
|
result += resulting
|
||||||
|
}
|
||||||
|
if (ifStack.nonEmpty) {
|
||||||
|
ErrorReporting.error("Unclosed #if")
|
||||||
|
}
|
||||||
|
// if (ErrorReporting.traceEnabled) {
|
||||||
|
// result.zipWithIndex.foreach {
|
||||||
|
// case (line, i) => ErrorReporting.trace(f"${i + 1}%-4d $line%s")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
(result.mkString("\n"), featureConstants.toMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
object PreprocessorParser {
|
||||||
|
|
||||||
|
import fastparse.all._
|
||||||
|
import MfParser.{HWS, identifier, nonStatementLevel, mfOperators}
|
||||||
|
|
||||||
|
type M = Map[String, Long]
|
||||||
|
type Q = M => Option[Long]
|
||||||
|
val alwaysNone: M => Option[Long] = (_: M) => None
|
||||||
|
|
||||||
|
val literalAtom: P[Q] = (MfParser.binaryAtom | MfParser.hexAtom | MfParser.octalAtom | MfParser.quaternaryAtom | MfParser.decimalAtom).map(l => _ => Some(l.value))
|
||||||
|
|
||||||
|
val variableAtom: P[Q] = identifier.map(k => _.get(k))
|
||||||
|
|
||||||
|
val atom: P[Q] = P(literalAtom | variableAtom)
|
||||||
|
|
||||||
|
def mfParenExpr: P[Q] = P("(" ~/ HWS ~/ mfExpression(nonStatementLevel) ~ HWS ~/ ")")
|
||||||
|
|
||||||
|
def functionCall: P[Q] = for {
|
||||||
|
name <- identifier
|
||||||
|
params <- HWS ~ "(" ~/ HWS ~/ mfExpression(nonStatementLevel).rep(min = 0, sep = HWS ~ "," ~/ HWS) ~ HWS ~/ ")" ~/ ""
|
||||||
|
} yield (name, params.toList) match {
|
||||||
|
case ("defined", List(p)) => {m:M => Some(if (p(m).isDefined) 1 else 0)}
|
||||||
|
case ("not", List(p)) => {m:M => Some(if (p(m).getOrElse(0L) == 0) 1 else 0)}
|
||||||
|
case ("lo", List(p)) => {m:M => Some(p(m).getOrElse(0L) & 0xff)}
|
||||||
|
case ("hi", List(p)) => {m:M => Some(p(m).getOrElse(0L).>>(8).&(0xff))}
|
||||||
|
case ("defined" | "lo" | "hi" | "not", ps) =>
|
||||||
|
ErrorReporting.error(s"Invalid number of parameters to $name: ${ps.length}")
|
||||||
|
alwaysNone
|
||||||
|
case _ =>
|
||||||
|
ErrorReporting.error("Invalid preprocessor function " + name)
|
||||||
|
alwaysNone
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def tightMfExpression: P[Q] = P(mfParenExpr | functionCall | atom) // TODO
|
||||||
|
|
||||||
|
def tightMfExpressionButNotCall: P[Q] = P(mfParenExpr | atom) // TODO
|
||||||
|
|
||||||
|
def expression: P[Q] = Start ~ HWS ~/ mfExpression(nonStatementLevel) ~ HWS ~ End
|
||||||
|
|
||||||
|
def mfExpression(level: Int): P[Q] = {
|
||||||
|
val allowedOperators = MfParser.mfOperatorsDropFlatten(level)
|
||||||
|
|
||||||
|
def inner: P[SeparatedList[Q, String]] = {
|
||||||
|
for {
|
||||||
|
head <- tightMfExpression ~/ HWS
|
||||||
|
maybeOperator <- StringIn(allowedOperators: _*).!.?
|
||||||
|
maybeTail <- maybeOperator.fold[P[Option[List[(String, Q)]]]](Pass.map(_ => None))(o => (HWS ~/ inner ~/ HWS).map(x2 => Some((o -> x2.head) :: x2.tail)))
|
||||||
|
} yield {
|
||||||
|
maybeTail.fold[SeparatedList[Q, String]](SeparatedList.of(head))(t => SeparatedList(head, t))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def arithOp(xs:SeparatedList[SeparatedList[Q, String], String], level: Int, f: (Long, Long) => Long): Q = {
|
||||||
|
m:M => Some(xs.items.map{value => p(value, level + 1)(m).getOrElse(0L)}.reduce(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
def boolOp(xs:SeparatedList[SeparatedList[Q, String], String], level: Int, f: (Boolean, Boolean) => Boolean): Q = {
|
||||||
|
m:M => {
|
||||||
|
val b = xs.items.map { value => p(value, level + 1)(m).getOrElse(0L) }.map(_ != 0).reduce(f)
|
||||||
|
Some(if (b) 1L else 0L)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def compOp(xs:SeparatedList[SeparatedList[Q, String], String], level: Int, f: (Long, Long) => Boolean): Q = {
|
||||||
|
m:M => {
|
||||||
|
val values = xs.items.map { value => p(value, level + 1)(m).getOrElse(0L) }
|
||||||
|
Some(if (values.init.zip(values.tail).forall(f.tupled)) 1L else 0L)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def p(list: SeparatedList[Q, String], level: Int): Q =
|
||||||
|
if (level == mfOperators.length) list.head
|
||||||
|
else {
|
||||||
|
val xs = list.split(mfOperators(level).toSet(_))
|
||||||
|
xs.separators.distinct match {
|
||||||
|
case Nil =>
|
||||||
|
if (xs.tail.nonEmpty)
|
||||||
|
ErrorReporting.error("Too many different operators")
|
||||||
|
p(xs.head, level + 1)
|
||||||
|
case List("+") | List("-") | List("+", "-") | List("-", "+") =>
|
||||||
|
val tuples = xs.toPairList("+")
|
||||||
|
m:M => Some(tuples.map {
|
||||||
|
case ("+", value) => p(value, level + 1)(m).getOrElse(0L)
|
||||||
|
case ("-", value) => -p(value, level + 1)(m).getOrElse(0L)
|
||||||
|
case _ => 0L
|
||||||
|
}.sum)
|
||||||
|
case List("+'") | List("-'") | List("+'", "-'") | List("-'", "+'") => ???
|
||||||
|
case List(":") => ???
|
||||||
|
case List("&&") => boolOp(xs, level, _ && _)
|
||||||
|
case List("||") => boolOp(xs, level, _ || _)
|
||||||
|
case List("&") => arithOp(xs, level, _ & _)
|
||||||
|
case List("|") => arithOp(xs, level, _ | _)
|
||||||
|
case List("^") => arithOp(xs, level, _ ^ _)
|
||||||
|
case List("*") => arithOp(xs, level, _ * _)
|
||||||
|
case List("<<") => arithOp(xs, level, _ << _)
|
||||||
|
case List(">>") => arithOp(xs, level, _ >> _)
|
||||||
|
case List("==") => compOp(xs, level, _ == _)
|
||||||
|
case List("!=") => compOp(xs, level, _ != _)
|
||||||
|
case List("<") => compOp(xs, level, _ < _)
|
||||||
|
case List(">") => compOp(xs, level, _ > _)
|
||||||
|
case List("<=") => compOp(xs, level, _ <= _)
|
||||||
|
case List(">=") => compOp(xs, level, _ >= _)
|
||||||
|
case List(x) =>
|
||||||
|
ErrorReporting.error("Unsupported operator " + x)
|
||||||
|
alwaysNone
|
||||||
|
case _ =>
|
||||||
|
ErrorReporting.error("Too many different operators")
|
||||||
|
alwaysNone
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inner.map(x => p(x, 0))
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,7 @@ package millfork.parser
|
||||||
|
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
import fastparse.all._
|
import fastparse.all.{parserApi, _}
|
||||||
import fastparse.core
|
import fastparse.core
|
||||||
import millfork.CompilationOptions
|
import millfork.CompilationOptions
|
||||||
import millfork.assembly.z80.{ZOpcode, _}
|
import millfork.assembly.z80.{ZOpcode, _}
|
||||||
|
@ -13,18 +13,20 @@ import millfork.node._
|
||||||
/**
|
/**
|
||||||
* @author Karol Stasiak
|
* @author Karol Stasiak
|
||||||
*/
|
*/
|
||||||
case class Z80Parser(filename: String, input: String, currentDirectory: String, options: CompilationOptions) extends MfParser[ZLine](filename, input, currentDirectory, options) {
|
case class Z80Parser(filename: String, input: String, currentDirectory: String, options: CompilationOptions, featureConstants: Map[String, Long]) extends MfParser[ZLine](filename, input, currentDirectory, options, featureConstants) {
|
||||||
|
|
||||||
import MfParser._
|
import MfParser._
|
||||||
|
|
||||||
private val zero = LiteralExpression(0, 1)
|
private val zero = LiteralExpression(0, 1)
|
||||||
|
|
||||||
val appcSimple: P[ParamPassingConvention] = P("a" | "b" | "c" | "d" | "e" | "hl" | "bc" | "de").!.map {
|
val appcSimple: P[ParamPassingConvention] = (P("hl" | "bc" | "de" | "a" | "b" | "c" | "d" | "e" | "h" | "l").! ~ !letterOrDigit).map {
|
||||||
case "a" => ByZRegister(ZRegister.A)
|
case "a" => ByZRegister(ZRegister.A)
|
||||||
case "b" => ByZRegister(ZRegister.B)
|
case "b" => ByZRegister(ZRegister.B)
|
||||||
case "c" => ByZRegister(ZRegister.C)
|
case "c" => ByZRegister(ZRegister.C)
|
||||||
case "d" => ByZRegister(ZRegister.D)
|
case "d" => ByZRegister(ZRegister.D)
|
||||||
case "e" => ByZRegister(ZRegister.E)
|
case "e" => ByZRegister(ZRegister.E)
|
||||||
|
case "h" => ByZRegister(ZRegister.H)
|
||||||
|
case "l" => ByZRegister(ZRegister.L)
|
||||||
case "hl" => ByZRegister(ZRegister.HL)
|
case "hl" => ByZRegister(ZRegister.HL)
|
||||||
case "bc" => ByZRegister(ZRegister.BC)
|
case "bc" => ByZRegister(ZRegister.BC)
|
||||||
case "de" => ByZRegister(ZRegister.DE)
|
case "de" => ByZRegister(ZRegister.DE)
|
||||||
|
@ -38,33 +40,60 @@ case class Z80Parser(filename: String, input: String, currentDirectory: String,
|
||||||
} yield ParameterDeclaration(typ, appc).pos(p)
|
} yield ParameterDeclaration(typ, appc).pos(p)
|
||||||
|
|
||||||
// TODO: label and instruction in one line
|
// TODO: label and instruction in one line
|
||||||
val asmLabel: P[ExecutableStatement] = (identifier ~ HWS ~ ":" ~/ HWS).map(l => Z80AssemblyStatement(ZOpcode.LABEL, NoRegisters, VariableExpression(l), elidable = true))
|
val asmLabel: P[ExecutableStatement] = (identifier ~ HWS ~ ":" ~/ HWS).map(l => Z80AssemblyStatement(ZOpcode.LABEL, NoRegisters, None, VariableExpression(l), elidable = true))
|
||||||
|
|
||||||
val asmMacro: P[ExecutableStatement] = ("+" ~/ HWS ~/ functionCall).map(ExpressionStatement)
|
val asmMacro: P[ExecutableStatement] = ("+" ~/ HWS ~/ functionCall).map(ExpressionStatement)
|
||||||
|
|
||||||
private def normalOp8(op: ZOpcode.Value): P[(ZOpcode.Value, ZRegisters, Expression)] = asmExpressionWithParens.map {
|
private val toRegister: Map[String, ZRegister.Value] = Map(
|
||||||
case (VariableExpression("A" | "a"), false) => (op, OneRegister(ZRegister.A), zero)
|
"A" -> ZRegister.A, "a" -> ZRegister.A,
|
||||||
case (VariableExpression("B" | "b"), false) => (op, OneRegister(ZRegister.A), zero)
|
"B" -> ZRegister.B, "b" -> ZRegister.B,
|
||||||
case (VariableExpression("C" | "c"), false) => (op, OneRegister(ZRegister.A), zero)
|
"C" -> ZRegister.C, "c" -> ZRegister.C,
|
||||||
case (VariableExpression("D" | "d"), false) => (op, OneRegister(ZRegister.A), zero)
|
"D" -> ZRegister.D, "d" -> ZRegister.D,
|
||||||
case (VariableExpression("E" | "e"), false) => (op, OneRegister(ZRegister.A), zero)
|
"E" -> ZRegister.E, "e" -> ZRegister.E,
|
||||||
case (VariableExpression("HL" | "hl"), true) => (op, OneRegister(ZRegister.MEM_HL), zero)
|
"H" -> ZRegister.H, "h" -> ZRegister.H,
|
||||||
// TODO: IX/IY
|
"L" -> ZRegister.L, "l" -> ZRegister.L,
|
||||||
case (e, false) => (op, OneRegister(ZRegister.IMM_8), e)
|
"HL" -> ZRegister.HL, "hl" -> ZRegister.HL,
|
||||||
|
"AF" -> ZRegister.AF, "af" -> ZRegister.AF,
|
||||||
|
"BC" -> ZRegister.BC, "bc" -> ZRegister.BC,
|
||||||
|
"DE" -> ZRegister.DE, "de" -> ZRegister.DE,
|
||||||
|
"IX" -> ZRegister.IX, "ix" -> ZRegister.IX,
|
||||||
|
"IY" -> ZRegister.IY, "iy" -> ZRegister.IY,
|
||||||
|
"SP" -> ZRegister.SP, "sp" -> ZRegister.SP,
|
||||||
|
)
|
||||||
|
|
||||||
|
private def param(allowAbsolute: Boolean): P[(ZRegister.Value, Option[Expression])] = asmExpressionWithParens.map {
|
||||||
|
case (VariableExpression(r), false) if toRegister.contains(r)=> (toRegister(r), None)
|
||||||
|
case (VariableExpression("HL" | "hl"), true) => (ZRegister.MEM_HL, None)
|
||||||
|
case (VariableExpression("BC" | "bc"), true) => (ZRegister.MEM_BC, None)
|
||||||
|
case (VariableExpression("DE" | "de"), true) => (ZRegister.MEM_DE, None)
|
||||||
|
case (FunctionCallExpression("IX" | "ix", List(o)), true) => (ZRegister.MEM_IX_D, Some(o))
|
||||||
|
case (FunctionCallExpression("IY" | "iy", List(o)), true) => (ZRegister.MEM_IY_D, Some(o))
|
||||||
|
case (e, true) if allowAbsolute => (ZRegister.MEM_ABS_8, Some(e))
|
||||||
|
case (e, _) => (ZRegister.IMM_8, Some(e))
|
||||||
}
|
}
|
||||||
|
|
||||||
private def modifyOp8(op: ZOpcode.Value): P[(ZOpcode.Value, ZRegisters, Expression)] = asmExpressionWithParens.map {
|
def mapOne8Register(op: ZOpcode.Value)(param: (ZRegister.Value, Option[Expression])): (ZOpcode.Value, OneRegister, Option[Expression], Expression) = param match {
|
||||||
case (VariableExpression("A" | "a"), false) => (op, OneRegister(ZRegister.A), zero)
|
case (reg@(ZRegister.MEM_IX_D | ZRegister.MEM_IY_D), Some(e)) => (op, OneRegister(reg), Some(e), zero)
|
||||||
case (VariableExpression("B" | "b"), false) => (op, OneRegister(ZRegister.A), zero)
|
case (reg, addr) => (op, OneRegister(reg), None, addr.getOrElse(zero))
|
||||||
case (VariableExpression("C" | "c"), false) => (op, OneRegister(ZRegister.A), zero)
|
|
||||||
case (VariableExpression("D" | "d"), false) => (op, OneRegister(ZRegister.A), zero)
|
|
||||||
case (VariableExpression("E" | "e"), false) => (op, OneRegister(ZRegister.A), zero)
|
|
||||||
case (VariableExpression("HL" | "hl"), true) => (op, OneRegister(ZRegister.MEM_HL), zero)
|
|
||||||
// TODO: IX/IY
|
|
||||||
case (e, _) => ErrorReporting.fatal("Invalid parameter for " + op, e.position)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val jumpCondition: P[ZRegisters] = (HWS ~ identifier ~ HWS).map {
|
def one8Register(op: ZOpcode.Value): P[(ZOpcode.Value, OneRegister, Option[Expression], Expression)] = param(allowAbsolute = false).map{
|
||||||
|
case (reg@(ZRegister.MEM_IX_D | ZRegister.MEM_IY_D), Some(e)) => (op, OneRegister(reg), Some(e), zero)
|
||||||
|
case (reg, addr) => (op, OneRegister(reg), None, addr.getOrElse(zero))
|
||||||
|
}
|
||||||
|
|
||||||
|
def one16Register(op: ZOpcode.Value): P[(ZOpcode.Value, OneRegister, Option[Expression], Expression)] = param(allowAbsolute = false).map{
|
||||||
|
case (reg@(ZRegister.MEM_IX_D | ZRegister.MEM_IY_D), Some(e)) => (op, OneRegister(reg), Some(e), zero)
|
||||||
|
case (ZRegister.MEM_ABS_8, addr) => (op, OneRegister(ZRegister.MEM_ABS_16), None, addr.getOrElse(zero))
|
||||||
|
case (ZRegister.IMM_8, addr) => (op, OneRegister(ZRegister.IMM_16), None, addr.getOrElse(zero))
|
||||||
|
case (reg, addr) => (op, OneRegister(reg), None, addr.getOrElse(zero))
|
||||||
|
}
|
||||||
|
|
||||||
|
private val jumpCondition: P[ZRegisters] = (HWS ~ (
|
||||||
|
"NZ" | "nz" | "nc" | "NC" |
|
||||||
|
"PO" | "po" | "PE" | "pe" |
|
||||||
|
"m" | "M" | "p" | "P" |
|
||||||
|
"c" | "C" | "Z" | "z").! ~ HWS).map {
|
||||||
case "Z" | "z" => IfFlagSet(ZFlag.Z)
|
case "Z" | "z" => IfFlagSet(ZFlag.Z)
|
||||||
case "PE" | "pe" => IfFlagSet(ZFlag.P)
|
case "PE" | "pe" => IfFlagSet(ZFlag.P)
|
||||||
case "C" | "c" => IfFlagSet(ZFlag.C)
|
case "C" | "c" => IfFlagSet(ZFlag.C)
|
||||||
|
@ -73,43 +102,202 @@ case class Z80Parser(filename: String, input: String, currentDirectory: String,
|
||||||
case "PO" | "po" => IfFlagClear(ZFlag.P)
|
case "PO" | "po" => IfFlagClear(ZFlag.P)
|
||||||
case "NC" | "nc" => IfFlagClear(ZFlag.C)
|
case "NC" | "nc" => IfFlagClear(ZFlag.C)
|
||||||
case "P" | "p" => IfFlagClear(ZFlag.S)
|
case "P" | "p" => IfFlagClear(ZFlag.S)
|
||||||
case _ => ErrorReporting.fatal("Invalid condition flag")
|
case _ => NoRegisters // shouldn't happen
|
||||||
|
}
|
||||||
|
|
||||||
|
private def is16Bit(r: ZRegister.Value): Boolean = r match {
|
||||||
|
case ZRegister.HL => true
|
||||||
|
case ZRegister.BC => true
|
||||||
|
case ZRegister.DE => true
|
||||||
|
case ZRegister.IX => true
|
||||||
|
case ZRegister.IY => true
|
||||||
|
case ZRegister.SP => true
|
||||||
|
case ZRegister.AF => true
|
||||||
|
case ZRegister.MEM_ABS_16 => true
|
||||||
|
case ZRegister.IMM_16 => true
|
||||||
|
case _ => false
|
||||||
|
}
|
||||||
|
|
||||||
|
private def merge(op8: ZOpcode.Value, op16: ZOpcode.Value, skipTargetA: Boolean)
|
||||||
|
(params: (ZRegister.Value, Option[Expression], ZRegister.Value, Option[Expression]))
|
||||||
|
: (ZOpcode.Value, ZRegisters, Option[Expression], Expression) = params match {
|
||||||
|
case (ZRegister.A, _, s, e) if skipTargetA => mapOne8Register(op8)(s -> e)
|
||||||
|
|
||||||
|
case (tr@(ZRegister.MEM_IX_D | ZRegister.MEM_IY_D), Some(o), sr@(ZRegister.MEM_ABS_8 | ZRegister.IMM_8), Some(v)) =>
|
||||||
|
(op8, TwoRegisters(tr, sr), Some(o), v)
|
||||||
|
case (tr@(ZRegister.MEM_ABS_8 | ZRegister.IMM_8), Some(v), sr@(ZRegister.MEM_IX_D | ZRegister.MEM_IY_D), Some(o)) =>
|
||||||
|
(op8, TwoRegisters(tr, sr), Some(o), v)
|
||||||
|
|
||||||
|
case (tr@(ZRegister.MEM_IX_D | ZRegister.MEM_IY_D), Some(o), sr, _) =>
|
||||||
|
(op8, TwoRegisters(tr, sr), Some(o), zero)
|
||||||
|
case (tr, _, sr@(ZRegister.MEM_IX_D | ZRegister.MEM_IY_D), Some(o)) =>
|
||||||
|
(op8, TwoRegisters(tr, sr), Some(o), zero)
|
||||||
|
|
||||||
|
case (tr, _, ZRegister.MEM_ABS_8, Some(v)) if is16Bit(tr) =>
|
||||||
|
(op16, TwoRegisters(tr, ZRegister.MEM_ABS_16), None, v)
|
||||||
|
case (tr, _, ZRegister.IMM_8, Some(v)) if is16Bit(tr) =>
|
||||||
|
(op16, TwoRegisters(tr, ZRegister.IMM_16), None, v)
|
||||||
|
case (ZRegister.MEM_ABS_8, Some(v), sr, _) if is16Bit(sr) =>
|
||||||
|
(op16, TwoRegisters(ZRegister.MEM_ABS_16, sr), None, v)
|
||||||
|
case (ZRegister.IMM_8, Some(v), sr, _) if is16Bit(sr) =>
|
||||||
|
(op16, TwoRegisters(ZRegister.IMM_16, sr), None, v)
|
||||||
|
|
||||||
|
case (t, Some(v), s, None) =>
|
||||||
|
(op8, TwoRegisters(t, s), None, v)
|
||||||
|
case (t, None, s, Some(v)) =>
|
||||||
|
(op8, TwoRegisters(t, s), None, v)
|
||||||
|
case (t, None, s, None) =>
|
||||||
|
(op8, TwoRegisters(t, s), None, zero)
|
||||||
|
case _ => ???
|
||||||
}
|
}
|
||||||
|
|
||||||
private val jumpConditionWithComma: P[ZRegisters] = (jumpCondition ~ "," ~/ HWS).?.map (_.getOrElse(NoRegisters))
|
private val jumpConditionWithComma: P[ZRegisters] = (jumpCondition ~ "," ~/ HWS).?.map (_.getOrElse(NoRegisters))
|
||||||
|
private val jumpConditionWithoutComma: P[ZRegisters] = (jumpCondition ~/ HWS).?.map (_.getOrElse(NoRegisters))
|
||||||
|
|
||||||
val asmInstruction: P[ExecutableStatement] = {
|
val asmInstruction: P[ExecutableStatement] = {
|
||||||
import ZOpcode._
|
import ZOpcode._
|
||||||
for {
|
for {
|
||||||
el <- elidable
|
el <- elidable
|
||||||
|
pos <- position()
|
||||||
opcode: String <- identifier ~/ HWS
|
opcode: String <- identifier ~/ HWS
|
||||||
(actualOpcode, registers, param) <- opcode.toUpperCase(Locale.ROOT) match {
|
tuple4/*: (ZOpcode.Value, ZRegisters, Option[Expression], Expression)*/ <- opcode.toUpperCase(Locale.ROOT) match {
|
||||||
case "RST" => asmExpression.map((RST, NoRegisters, _))
|
case "RST" => asmExpression.map((RST, NoRegisters, None, _))
|
||||||
case "RET" => P("").map(imm(RET)) // TODO: conditionals
|
case "EI" => imm(EI)
|
||||||
case "CALL" => (jumpCondition~asmExpression).map{case (reg, param) => (CALL, reg, param)}
|
case "DI" => imm(DI)
|
||||||
case "JP" => (jumpCondition~asmExpression).map{case (reg, param) => (JP, reg, param)}
|
case "HALT" => imm(HALT)
|
||||||
case "JR" => (jumpCondition~asmExpression).map{case (reg, param) => (JR, reg, param)}
|
|
||||||
case "DJNZ" => asmExpression.map((DJNZ, NoRegisters, _))
|
case "RETN" => imm(RETN)
|
||||||
case "CP" => normalOp8(CP)
|
case "RETI" => imm(RETI)
|
||||||
case "AND" => normalOp8(AND)
|
case "RET" => P(jumpConditionWithoutComma).map((RET, _, None, zero))
|
||||||
case "OR" => normalOp8(OR)
|
case "CALL" => (jumpConditionWithComma~asmExpression).map{case (reg, param) => (CALL, reg, None, param)}
|
||||||
case "XOR" => normalOp8(XOR)
|
case "JP" => (jumpConditionWithComma~asmExpression).map{case (reg, param) => (JP, reg, None, param)}
|
||||||
case "RL" => modifyOp8(RL)
|
case "JR" => (jumpConditionWithComma~asmExpression).map{case (reg, param) => (JR, reg, None, param)}
|
||||||
case "RR" => modifyOp8(RR)
|
case "DJNZ" => asmExpression.map((DJNZ, NoRegisters, None, _))
|
||||||
case "RLC" => modifyOp8(RLC)
|
|
||||||
case "RRC" => modifyOp8(RRC)
|
case "CP" => one8Register(CP)
|
||||||
case "SLA" => modifyOp8(SLA)
|
case "AND" => one8Register(AND)
|
||||||
case "SLL" => modifyOp8(SLL)
|
case "OR" => one8Register(OR)
|
||||||
case "SRA" => modifyOp8(SRA)
|
case "XOR" => one8Register(XOR)
|
||||||
case "SRL" => modifyOp8(SRL)
|
case "SUB" => one8Register(SUB)
|
||||||
case _ => ErrorReporting.fatal("Unsupported opcode " + opcode)
|
case "DEC" => one8Register(DEC)
|
||||||
|
case "INC" => one8Register(INC)
|
||||||
|
|
||||||
|
case "RLA" => regA(RL)
|
||||||
|
case "RRA" => regA(RR)
|
||||||
|
case "RLCA" => regA(RLC)
|
||||||
|
case "RRCA" => regA(RRC)
|
||||||
|
case "RL" => one8Register(RL)
|
||||||
|
case "RR" => one8Register(RR)
|
||||||
|
case "RLC" => one8Register(RLC)
|
||||||
|
case "RRC" => one8Register(RRC)
|
||||||
|
case "SLA" => one8Register(SLA)
|
||||||
|
case "SLL" => one8Register(SLL)
|
||||||
|
case "SRA" => one8Register(SRA)
|
||||||
|
case "SRL" => one8Register(SRL)
|
||||||
|
|
||||||
|
case "SCF" => imm(SCF)
|
||||||
|
case "CCF" => imm(CCF)
|
||||||
|
case "CPL" => imm(CPL)
|
||||||
|
case "DAA" => imm(DAA)
|
||||||
|
case "EXX" => imm(EXX)
|
||||||
|
case "NOP" => imm(NOP)
|
||||||
|
|
||||||
|
case "LDI" => imm(LDI)
|
||||||
|
case "LDD" => imm(LDD)
|
||||||
|
case "LDIR" => imm(LDIR)
|
||||||
|
case "LDDR" => imm(LDDR)
|
||||||
|
case "CPI" => imm(CPI)
|
||||||
|
case "CPD" => imm(CPD)
|
||||||
|
case "CPIR" => imm(CPIR)
|
||||||
|
case "CPDR" => imm(CPDR)
|
||||||
|
case "INI" => imm(INI)
|
||||||
|
case "IND" => imm(IND)
|
||||||
|
case "INIR" => imm(INIR)
|
||||||
|
case "INDR" => imm(INDR)
|
||||||
|
case "OUTI" => imm(OUTI)
|
||||||
|
case "OUTD" => imm(OUTD)
|
||||||
|
case "OUTIR" => imm(OUTIR)
|
||||||
|
case "OUTDR" => imm(OUTDR)
|
||||||
|
case "OTIR" => imm(OUTIR)
|
||||||
|
case "OTDR" => imm(OUTDR)
|
||||||
|
|
||||||
|
case "PUSH" => one16Register(PUSH)
|
||||||
|
case "POP" => one16Register(POP)
|
||||||
|
|
||||||
|
case "IN" => (asmExpressionWithParens ~ HWS ~ position("comma").map(_ => ()) ~ "," ~/ HWS ~ asmExpressionWithParens).map { p: (Expression, Boolean, (Expression, Boolean)) =>
|
||||||
|
p match {
|
||||||
|
case (VariableExpression(r), false, (VariableExpression("C" | "c"), true))
|
||||||
|
if toRegister.contains(r) =>
|
||||||
|
(IN_C, OneRegister(toRegister(r)), None, zero)
|
||||||
|
case (VariableExpression(r), false, (port, true))
|
||||||
|
if toRegister.contains(r) =>
|
||||||
|
(IN_IMM, OneRegister(toRegister(r)), None, port)
|
||||||
|
case _ =>
|
||||||
|
ErrorReporting.error("Invalid parameters for IN", Some(pos))
|
||||||
|
(NOP, NoRegisters, None, zero)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "OUT" => (asmExpressionWithParens ~ HWS ~ position("comma").map(_ => ()) ~ "," ~/ HWS ~ asmExpressionWithParens).map { p: (Expression, Boolean, (Expression, Boolean)) =>
|
||||||
|
p match {
|
||||||
|
case (VariableExpression("C" | "c"), true, (VariableExpression(r), false))
|
||||||
|
if toRegister.contains(r) =>
|
||||||
|
(OUT_C, OneRegister(toRegister(r)), None, zero)
|
||||||
|
case (port, true, (VariableExpression(r), false))
|
||||||
|
if toRegister.contains(r) =>
|
||||||
|
(OUT_IMM, OneRegister(toRegister(r)), None, port)
|
||||||
|
case _ =>
|
||||||
|
ErrorReporting.error("Invalid parameters for OUT", Some(pos))
|
||||||
|
(NOP, NoRegisters, None, zero)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "EX" => (asmExpressionWithParens ~ HWS ~ position("comma").map(_ => ()) ~ "," ~/ HWS ~ asmExpressionWithParens).map { p: (Expression, Boolean, (Expression, Boolean)) =>
|
||||||
|
p match {
|
||||||
|
case (VariableExpression("AF" | "af"), false, (VariableExpression("AF" | "af"), true)) =>
|
||||||
|
(EX_AF_AF, NoRegisters, None, zero)
|
||||||
|
case (VariableExpression("DE" | "de"), false, (VariableExpression("HL" | "hl"), false)) =>
|
||||||
|
(EX_DE_HL, NoRegisters, None, zero)
|
||||||
|
case (VariableExpression("HL" | "hl"), false, (VariableExpression("DE" | "de"), false)) =>
|
||||||
|
(EX_DE_HL, NoRegisters, None, zero)
|
||||||
|
case (VariableExpression("SP" | "sp"), true, (VariableExpression(r), false))
|
||||||
|
if toRegister.contains(r) =>
|
||||||
|
(EX_SP, OneRegister(toRegister(r)), None, zero)
|
||||||
|
case (VariableExpression(r), false, (VariableExpression("SP" | "sp"), true))
|
||||||
|
if toRegister.contains(r) =>
|
||||||
|
(EX_SP, OneRegister(toRegister(r)), None, zero)
|
||||||
|
case _ =>
|
||||||
|
ErrorReporting.error("Invalid parameters for EX", Some(pos))
|
||||||
|
(NOP, NoRegisters, None, zero)
|
||||||
}
|
}
|
||||||
} yield Z80AssemblyStatement(actualOpcode, registers, param, el)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private def imm(opcode: ZOpcode.Value): Any => (ZOpcode.Value, ZRegisters, Expression) = (_: Any) => {
|
case "LD" => (param(true) ~ HWS ~ position("comma").map(_ => ()) ~ "," ~/ HWS ~ param(true)).map {
|
||||||
(opcode, NoRegisters, zero)
|
case (r1, e1, (r2, e2)) => merge(LD, LD_16, skipTargetA = false)((r1, e1, r2, e2))
|
||||||
}
|
}
|
||||||
|
case "ADD" => (param(false) ~ HWS ~ position("comma").map(_ => ()) ~ "," ~/ HWS ~ param(false)).map {
|
||||||
|
case (r1, e1, (r2, e2)) => merge(ADD, ADD_16, skipTargetA = true)((r1, e1, r2, e2))
|
||||||
|
}
|
||||||
|
case "ADC" => (param(false) ~ HWS ~ position("comma").map(_ => ()) ~ "," ~/ HWS ~ param(false)).map {
|
||||||
|
case (r1, e1, (r2, e2)) => merge(ADC, ADC_16, skipTargetA = true)((r1, e1, r2, e2))
|
||||||
|
}
|
||||||
|
case "SBC" => (param(false) ~ HWS ~ position("comma").map(_ => ()) ~ "," ~/ HWS ~ param(false)).map {
|
||||||
|
case (r1, e1, (r2, e2)) => merge(SBC, SBC_16, skipTargetA = true)((r1, e1, r2, e2))
|
||||||
|
}
|
||||||
|
|
||||||
|
case _ =>
|
||||||
|
ErrorReporting.error("Unsupported opcode " + opcode, Some(pos))
|
||||||
|
imm(NOP)
|
||||||
|
}
|
||||||
|
} yield {
|
||||||
|
val (actualOpcode, registers, offset, param) = tuple4
|
||||||
|
Z80AssemblyStatement(actualOpcode, registers, offset, param, el)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def imm(opcode: ZOpcode.Value): P[(ZOpcode.Value, ZRegisters, Option[Expression], Expression)] =
|
||||||
|
P("").map(_=>(opcode, NoRegisters, None, zero))
|
||||||
|
|
||||||
|
private def regA(opcode: ZOpcode.Value): P[(ZOpcode.Value, ZRegisters, Option[Expression], Expression)] =
|
||||||
|
P("").map(_=>(opcode, OneRegister(ZRegister.A), None, zero))
|
||||||
|
|
||||||
override val asmStatement: P[ExecutableStatement] = (position("assembly statement") ~ P(asmLabel | asmMacro | arrayContentsForAsm | asmInstruction)).map { case (p, s) => s.pos(p) } // TODO: macros
|
override val asmStatement: P[ExecutableStatement] = (position("assembly statement") ~ P(asmLabel | asmMacro | arrayContentsForAsm | asmInstruction)).map { case (p, s) => s.pos(p) } // TODO: macros
|
||||||
|
|
||||||
|
@ -119,23 +307,23 @@ case class Z80Parser(filename: String, input: String, currentDirectory: String,
|
||||||
case Some(xs) =>
|
case Some(xs) =>
|
||||||
if (flags("interrupt")) {
|
if (flags("interrupt")) {
|
||||||
if (xs.exists {
|
if (xs.exists {
|
||||||
case Z80AssemblyStatement(ZOpcode.RET, _, _, _) => true
|
case Z80AssemblyStatement(ZOpcode.RET, _, _, _, _) => true
|
||||||
case _ => false
|
case _ => false
|
||||||
}) ErrorReporting.warn("Assembly interrupt function `$name` contains RET, did you mean RETI/RETN?", options, Some(p))
|
}) ErrorReporting.warn("Assembly interrupt function `$name` contains RET, did you mean RETI/RETN?", options, Some(p))
|
||||||
} else {
|
} else {
|
||||||
if (xs.exists {
|
if (xs.exists {
|
||||||
case Z80AssemblyStatement(ZOpcode.RETI, _, _, _) => true
|
case Z80AssemblyStatement(ZOpcode.RETI, _, _, _, _) => true
|
||||||
case Z80AssemblyStatement(ZOpcode.RETN, _, _, _) => true
|
case Z80AssemblyStatement(ZOpcode.RETN, _, _, _, _) => true
|
||||||
case _ => false
|
case _ => false
|
||||||
}) ErrorReporting.warn("Assembly non-interrupt function `$name` contains RETI or RETN, did you mean RET?", options, Some(p))
|
}) ErrorReporting.warn("Assembly non-interrupt function `$name` contains RETI or RETN, did you mean RET?", options, Some(p))
|
||||||
}
|
}
|
||||||
if (!name.startsWith("__") && !flags("macro")) {
|
if (!name.startsWith("__") && !flags("macro")) {
|
||||||
xs.last match {
|
xs.last match {
|
||||||
case Z80AssemblyStatement(ZOpcode.RET, NoRegisters, _, _) => () // OK
|
case Z80AssemblyStatement(ZOpcode.RET, NoRegisters, _, _, _) => () // OK
|
||||||
case Z80AssemblyStatement(ZOpcode.RETN, NoRegisters, _, _) => () // OK
|
case Z80AssemblyStatement(ZOpcode.RETN, NoRegisters, _, _, _) => () // OK
|
||||||
case Z80AssemblyStatement(ZOpcode.RETI, NoRegisters, _, _) => () // OK
|
case Z80AssemblyStatement(ZOpcode.RETI, NoRegisters, _, _, _) => () // OK
|
||||||
case Z80AssemblyStatement(ZOpcode.JP, NoRegisters, _, _) => () // OK
|
case Z80AssemblyStatement(ZOpcode.JP, NoRegisters, _, _, _) => () // OK
|
||||||
case Z80AssemblyStatement(ZOpcode.JR, NoRegisters, _, _) => () // OK
|
case Z80AssemblyStatement(ZOpcode.JR, NoRegisters, _, _, _) => () // OK
|
||||||
case _ =>
|
case _ =>
|
||||||
val validReturn = if (flags("interrupt")) "RETI/RETN" else "RET"
|
val validReturn = if (flags("interrupt")) "RETI/RETN" else "RET"
|
||||||
ErrorReporting.warn(s"Non-macro assembly function `$name` should end in " + validReturn, options, Some(p))
|
ErrorReporting.warn(s"Non-macro assembly function `$name` should end in " + validReturn, options, Some(p))
|
||||||
|
|
|
@ -11,7 +11,8 @@ class ZSourceLoadingQueue(initialFilenames: List[String],
|
||||||
includePath: List[String],
|
includePath: List[String],
|
||||||
options: CompilationOptions) extends AbstractSourceLoadingQueue[ZLine](initialFilenames, includePath, options) {
|
options: CompilationOptions) extends AbstractSourceLoadingQueue[ZLine](initialFilenames, includePath, options) {
|
||||||
|
|
||||||
override def createParser(filename: String, src: String, parentDir: String): MfParser[ZLine] = Z80Parser(filename, src, parentDir, options)
|
override def createParser(filename: String, src: String, parentDir: String, featureConstants: Map[String, Long]): MfParser[ZLine] =
|
||||||
|
Z80Parser(filename, src, parentDir, options, featureConstants)
|
||||||
|
|
||||||
def enqueueStandardModules(): Unit = {
|
def enqueueStandardModules(): Unit = {
|
||||||
// TODO
|
// TODO
|
||||||
|
|
|
@ -168,4 +168,45 @@ class BasicSymonTest extends FunSuite with Matchers {
|
||||||
| }
|
| }
|
||||||
""".stripMargin){ m => () }
|
""".stripMargin){ m => () }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test("Preprocessor test") {
|
||||||
|
EmuUnoptimizedCrossPlatformRun(Cpu.Mos, Cpu.Z80)(
|
||||||
|
"""
|
||||||
|
| byte output @$c000
|
||||||
|
|
|
||||||
|
| #use ARCH_6502
|
||||||
|
| #use ARCH_Z80
|
||||||
|
|
|
||||||
|
| #if 1
|
||||||
|
| asm void main () {
|
||||||
|
| #if ARCH_6502
|
||||||
|
| lda #ARCH_6502
|
||||||
|
| sta output
|
||||||
|
| rts
|
||||||
|
| #elseif ARCH_Z80
|
||||||
|
| ld a,ARCH_Z80
|
||||||
|
| ld (output),a
|
||||||
|
| ret
|
||||||
|
| #else
|
||||||
|
| #error unsupported architecture
|
||||||
|
| #endif
|
||||||
|
| }
|
||||||
|
| #endif
|
||||||
|
|
|
||||||
|
| #if 1 + 1 == 2
|
||||||
|
| #info 1
|
||||||
|
| #if 1 == 3
|
||||||
|
| #error 1 == 3
|
||||||
|
| #elseif 1 == 5
|
||||||
|
| #error 1 == 5
|
||||||
|
| #else
|
||||||
|
| #info 2
|
||||||
|
| #endif
|
||||||
|
| #else
|
||||||
|
| #error not( 1 + 1 == 2 )
|
||||||
|
| #endif
|
||||||
|
""".stripMargin){ m =>
|
||||||
|
m.readByte(0xc000) should equal(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ object EmuPlatform {
|
||||||
Nil,
|
Nil,
|
||||||
TextCodec.Ascii,
|
TextCodec.Ascii,
|
||||||
TextCodec.Ascii,
|
TextCodec.Ascii,
|
||||||
|
Platform.builtInCpuFeatures(cpu),
|
||||||
CurrentBankFragmentOutput(0, 0xffff),
|
CurrentBankFragmentOutput(0, 0xffff),
|
||||||
Map("default" -> new UpwardByteAllocator(0x200, 0xb000)),
|
Map("default" -> new UpwardByteAllocator(0x200, 0xb000)),
|
||||||
Map("default" -> new VariableAllocator(
|
Map("default" -> new VariableAllocator(
|
||||||
|
|
|
@ -16,7 +16,7 @@ import millfork.error.ErrorReporting
|
||||||
import millfork.node.StandardCallGraph
|
import millfork.node.StandardCallGraph
|
||||||
import millfork.node.opt.NodeOptimization
|
import millfork.node.opt.NodeOptimization
|
||||||
import millfork.output.{MemoryBank, MosAssembler}
|
import millfork.output.{MemoryBank, MosAssembler}
|
||||||
import millfork.parser.MosParser
|
import millfork.parser.{MosParser, Preprocessor}
|
||||||
import millfork.{CompilationFlag, CompilationOptions, CpuFamily}
|
import millfork.{CompilationFlag, CompilationOptions, CpuFamily}
|
||||||
import org.scalatest.Matchers
|
import org.scalatest.Matchers
|
||||||
|
|
||||||
|
@ -119,7 +119,8 @@ class EmuRun(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization],
|
||||||
if (!source.contains("_panic")) effectiveSource += "\n void _panic(){while(true){}}"
|
if (!source.contains("_panic")) effectiveSource += "\n void _panic(){while(true){}}"
|
||||||
if (source.contains("import zp_reg"))
|
if (source.contains("import zp_reg"))
|
||||||
effectiveSource += Files.readAllLines(Paths.get("include/zp_reg.mfk"), StandardCharsets.US_ASCII).asScala.mkString("\n", "\n", "")
|
effectiveSource += Files.readAllLines(Paths.get("include/zp_reg.mfk"), StandardCharsets.US_ASCII).asScala.mkString("\n", "\n", "")
|
||||||
val parserF = MosParser("", effectiveSource, "", options)
|
val (preprocessedSource, features) = Preprocessor.preprocessForTest(options, effectiveSource)
|
||||||
|
val parserF = MosParser("", preprocessedSource, "", options, features)
|
||||||
parserF.toAst match {
|
parserF.toAst match {
|
||||||
case Success(unoptimized, _) =>
|
case Success(unoptimized, _) =>
|
||||||
ErrorReporting.assertNoErrors("Parse failed")
|
ErrorReporting.assertNoErrors("Parse failed")
|
||||||
|
|
|
@ -10,7 +10,7 @@ import millfork.error.ErrorReporting
|
||||||
import millfork.node.StandardCallGraph
|
import millfork.node.StandardCallGraph
|
||||||
import millfork.node.opt.NodeOptimization
|
import millfork.node.opt.NodeOptimization
|
||||||
import millfork.output.{MemoryBank, Z80Assembler}
|
import millfork.output.{MemoryBank, Z80Assembler}
|
||||||
import millfork.parser.Z80Parser
|
import millfork.parser.{Preprocessor, Z80Parser}
|
||||||
import millfork.{CompilationFlag, CompilationOptions, CpuFamily}
|
import millfork.{CompilationFlag, CompilationOptions, CpuFamily}
|
||||||
import millfork.compiler.z80.Z80Compiler
|
import millfork.compiler.z80.Z80Compiler
|
||||||
import org.scalatest.Matchers
|
import org.scalatest.Matchers
|
||||||
|
@ -33,7 +33,8 @@ class EmuZ80Run(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimizatio
|
||||||
ErrorReporting.verbosity = 999
|
ErrorReporting.verbosity = 999
|
||||||
var effectiveSource = source
|
var effectiveSource = source
|
||||||
if (!source.contains("_panic")) effectiveSource += "\n void _panic(){while(true){}}"
|
if (!source.contains("_panic")) effectiveSource += "\n void _panic(){while(true){}}"
|
||||||
val parserF = Z80Parser("", effectiveSource, "", options)
|
val (preprocessedSource, features) = Preprocessor.preprocessForTest(options, effectiveSource)
|
||||||
|
val parserF = Z80Parser("", preprocessedSource, "", options, features)
|
||||||
parserF.toAst match {
|
parserF.toAst match {
|
||||||
case Success(unoptimized, _) =>
|
case Success(unoptimized, _) =>
|
||||||
ErrorReporting.assertNoErrors("Parse failed")
|
ErrorReporting.assertNoErrors("Parse failed")
|
||||||
|
|
Loading…
Reference in New Issue
Block a user