mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 05:02:58 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e55a675d2e |
No files matched your search
@@ -1,5 +0,0 @@
|
||||
---
|
||||
description: Startup workflow to load project context
|
||||
---
|
||||
|
||||
1. Read the file `AGENTS.md` in the project root as initial system prompt to build context and instructions about the project structure, module descriptions, and development commands.
|
||||
@@ -1,204 +0,0 @@
|
||||
---
|
||||
name: asm6502-coder
|
||||
description: Writing or understanding 6502/65C02 assembly in 64tass syntax, standalone or embedded in Prog8
|
||||
---
|
||||
|
||||
# 6502 Assembly Coder
|
||||
|
||||
Use 64tass syntax, not ca65 or another assembler. Keep answers concise and
|
||||
practical. This applies to standalone `.asm` files and Prog8 `%asm {{ }}` or
|
||||
`asmsub` blocks.
|
||||
|
||||
Reference documentation for the assembler is available locally in
|
||||
`~/Documents/prog8-dev-resources` (the 64tass manual, `64tass.md`). Search this
|
||||
folder first before looking online.
|
||||
|
||||
## Syntax
|
||||
|
||||
- Labels begin in column 1. Instructions are indented at least four spaces.
|
||||
- Use lowercase opcodes and operands, two spaces between opcode and operand,
|
||||
and two spaces before end-of-line comments.
|
||||
- Hex is `$1234`, binary is `%1010`, decimal is `123`.
|
||||
- Use `.byte`, `.word`, `.dword`, `.fill`, `.text`, and equates such as
|
||||
`name = value`.
|
||||
- Sections use `.section name` and `.send name`.
|
||||
- Procedures use `.proc` and `.pend`; labels beginning with `_` are local to a
|
||||
procedure.
|
||||
- Conditional assembly uses `.if`, `.elsif`, `.else`, and `.endif`.
|
||||
|
||||
## Anonymous Labels
|
||||
|
||||
Anonymous label definitions are always a single `+` or `-` in column 1.
|
||||
References use repeated signs:
|
||||
|
||||
- `+` is the next upcoming `+` definition, `++` the second, `+++` the third.
|
||||
- `-` is the most recent `-` definition, `--` the preceding one, and so on.
|
||||
- Passing a `+` definition resets the forward-reference count.
|
||||
|
||||
```asm
|
||||
- dex
|
||||
bne - ; most recent '-' above
|
||||
ldx #5
|
||||
+ dex ; first upcoming '+'
|
||||
bne + ; this '+'
|
||||
sta $400
|
||||
+ lda #0 ; second '+' definition
|
||||
bne ++ ; next '+' after this one
|
||||
rts
|
||||
+ inc $d020 ; third '+' definition
|
||||
```
|
||||
|
||||
## CPU Differences
|
||||
|
||||
- `cx16` is the only Prog8 target that supports 65C02 instructions such as
|
||||
`stz`, `phx`, `plx`, `phy`, `ply`, `bra`, `trb`, `tsb`, `wai`, `stp`, and
|
||||
`bit #imm`. C64, C128, and PET32 use the original 6502.
|
||||
- Rockwell instructions (`rmb`, `smb`, `bbr`, `bbs`) are not available.
|
||||
- Use an explicit `a` for accumulator forms such as `rol a`, `lsr a`, `inc a`,
|
||||
and `dec a`.
|
||||
- Branches are relative. Prog8's assembler invocation enables `--long-branch`
|
||||
for out-of-range branches.
|
||||
- `lda`, `ldx`, and `ldy` do not affect carry. `cmp` sets carry for
|
||||
`register >= operand`.
|
||||
- On NMOS 6502, `jmp ($xxff)` wraps the high-byte fetch within the page.
|
||||
- `BRK` skips the byte after its opcode. The byte is commonly a handler
|
||||
signature.
|
||||
- `bit` memory forms set N and V from the operand and Z from `A AND operand`.
|
||||
65C02 `bit #imm` sets only Z.
|
||||
- Decimal mode can persist into NMOS IRQ handlers. Use `cld` at handler entry
|
||||
and explicitly control D before `adc` or `sbc`.
|
||||
|
||||
## Prog8 Integration
|
||||
|
||||
`asmsub` parameter names are documentation only. Use the actual registers or
|
||||
define assembly aliases, and list every modified register in `clobbers`.
|
||||
|
||||
```prog8
|
||||
asmsub increment(uword value @R0) clobbers (A, X) {
|
||||
%asm {{
|
||||
lda cx16.r0L
|
||||
clc
|
||||
adc #1
|
||||
sta cx16.r0L
|
||||
}}
|
||||
}
|
||||
```
|
||||
|
||||
Annotations include `@A`, `@X`, `@Y`, `@AX`, `@AY`, `@R0`-`@R15`, `@FAC1`,
|
||||
`@FAC2`, `@Pc` (carry), and `@Pz` (zero). Unmapped parameters are accessed as
|
||||
`p8v_name`.
|
||||
|
||||
Prog8 symbols use these prefixes: `p8v_` variables and parameters, `p8s_`
|
||||
subroutines, `p8b_` blocks, `p8c_` constants, `p8l_` labels, and `p8t_` struct
|
||||
types. Fully qualified names may look like
|
||||
`p8b_main.p8s_helper.p8v_local`. `%option no_symbol_prefixing` disables the
|
||||
prefixes. Split word arrays use `_lsb` and `_msb` symbols.
|
||||
|
||||
## Zeropage and Memory
|
||||
|
||||
- Never hardcode zeropage addresses. Use `P8ZP_SCRATCH_B1`,
|
||||
`P8ZP_SCRATCH_REG`, `P8ZP_SCRATCH_W1`, `P8ZP_SCRATCH_W2`, and
|
||||
`P8ZP_SCRATCH_PTR`, or the appropriate `cx16.r0`-`cx16.r15` symbols.
|
||||
- Scratch variables are not necessarily consecutive. CX16 virtual registers
|
||||
are consecutive and have `L` and `H` byte names.
|
||||
- Allocate additional temporary storage in BSS.
|
||||
- The hardware stack occupies `$0100-$01ff` and is limited. Do not overflow
|
||||
it.
|
||||
- Prog8 words are little-endian: load the low byte first, then the high byte.
|
||||
|
||||
```asm
|
||||
clc
|
||||
lda word1
|
||||
adc word2
|
||||
sta result
|
||||
lda word1+1
|
||||
adc word2+1
|
||||
sta result+1
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
```asm
|
||||
ldy #index
|
||||
lda (ptr),y
|
||||
|
||||
jsr p8s_main.p8s_helper
|
||||
```
|
||||
|
||||
Avoid self-modifying code unless it is explicitly required. It cannot run
|
||||
from ROM and complicates debugging and tooling. Prefer lookup tables, RAM
|
||||
vectors, or alternative algorithms.
|
||||
|
||||
## Reference
|
||||
|
||||
### Primary: 6502 Family CPU Reference
|
||||
|
||||
<https://www.pagetable.com/c64ref/6502/?cpu=65c02&tab=2> is the reference to reach for.
|
||||
It covers the whole 6502 family and lets you **select the CPU variant**, which matters
|
||||
because the parts genuinely differ. The variants are `6502`, `6502rorbug`, `65dtv02`,
|
||||
`65c02`, `r65c02` (Rockwell), `65c02s`, `65ce02` and `65c816`. Change the `cpu=` query
|
||||
parameter to switch; for Prog8, use `cpu=65c02` when working on cx16 and `cpu=6502` for
|
||||
c64, c128 and pet32.
|
||||
|
||||
Per instruction it gives the operation, the opcodes, the byte length, the cycle count
|
||||
and which status flags are affected, with tabs for opcodes, instructions, addressing
|
||||
modes and a full opcode table (switchable between 4-4 and 3-3-2 layout). It can also
|
||||
show the NMOS undocumented opcodes, and each part lists what it is based on, so a
|
||||
variant's inherited instructions are visible.
|
||||
|
||||
Cycle counts use a compact notation worth knowing: a trailing `t` means +1 if a branch
|
||||
is taken, `p` means +1 if a page is crossed, and `d` means +1 when the D (decimal) flag
|
||||
is set. So `d0 2+t+p` for `bne` is 2 cycles, 3 if taken, 4 if taken across a page.
|
||||
|
||||
Consult it whenever an exact figure is needed rather than recalled: the byte length or
|
||||
cycle cost of an instruction, which flags it clobbers, whether an addressing mode exists
|
||||
on the target CPU, or whether an instruction belongs to a different part at all. Typical
|
||||
reasons are costing a peephole or code-size tradeoff, checking a hand-written routine,
|
||||
and confirming a mnemonic is valid before using it.
|
||||
|
||||
Do not assume a timing from memory or from another part. Cycle counts are exactly the
|
||||
kind of detail that is misremembered, and the difference matters when it is the basis of
|
||||
an optimization claim. Read the number off the reference for the specific CPU being
|
||||
targeted.
|
||||
|
||||
### Secondary: WDC 65C02 reference
|
||||
|
||||
<https://cx16.dk/65c02/reference.html> is a 65C02-only reference. It is worth having
|
||||
because it states each instruction's effect in a single formula (for example
|
||||
`ADC: A,Z,C,N = A+M+C`), which makes the flag behaviour quick to check, and it lists
|
||||
every status bit per instruction including the ones not affected. Use it for flag
|
||||
semantics; use the primary reference for timings and for anything CPU-specific, since
|
||||
it covers the 65C02 only and will not answer a question about the NMOS 6502.
|
||||
|
||||
### Neither reference covers the assembler
|
||||
|
||||
Both document the CPU, not 64tass. For whether 64tass accepts a mnemonic or a given
|
||||
syntax, assemble it: an instruction outside the selected CPU set fails with
|
||||
`error: general syntax` rather than being silently ignored. Note also that 64tass has
|
||||
no `w65c02s`; its CPU selection is `--m65xx` (default), `--m65c02` and `--m65ce02`, and
|
||||
Prog8 emits `.cpu 'w65c02'`.
|
||||
|
||||
## Tools
|
||||
|
||||
Prog8 invokes 64tass with `--ascii --case-sensitive --long-branch -Wall` plus
|
||||
the target output option such as `--cbm-prg`. For manual assembly:
|
||||
|
||||
```bash
|
||||
64tass --ascii --case-sensitive --long-branch -Wall --cbm-prg \
|
||||
-o myprogram.prg myprogram.asm
|
||||
```
|
||||
|
||||
For debugging, add `--vice-labels --labels=labels.txt` and/or
|
||||
`--list=listing.txt`.
|
||||
|
||||
To check an instruction's encoding or size, assemble it in isolation and read the
|
||||
output bytes. The 2-byte load address comes first, so `stx $7b` / `ldy $7b` below
|
||||
shows as `86 7b a4 7b` - two instructions, four bytes:
|
||||
|
||||
```bash
|
||||
printf '.cpu %s\n* = $0801\nlbl\tstx\t$7b\n\tldy\t$7b\n' "'w65c02'" > sz.asm
|
||||
64tass --ascii --case-sensitive --cbm-prg -o sz.prg sz.asm && xxd sz.prg
|
||||
```
|
||||
|
||||
Note that a 64tass label takes no trailing colon; `lbl:` is a syntax error reported as
|
||||
`error: label required`.
|
||||
@@ -1,197 +0,0 @@
|
||||
---
|
||||
name: m68k-coder
|
||||
description: Writing or understanding Motorola 68000/68020 assembly in vasm mot syntax, standalone or embedded in Prog8
|
||||
---
|
||||
|
||||
# M68K Assembly Coder
|
||||
|
||||
Use Motorola syntax for `vasm`, not Devpac or other assemblers. Keep answers
|
||||
concise and practical.
|
||||
|
||||
Reference documentation for the assembler and linker is available locally in
|
||||
`~/Documents/prog8-dev-resources` (the vasm and vlink manuals). Search this
|
||||
folder first before looking online.
|
||||
|
||||
## Syntax
|
||||
|
||||
- Labels begin in column 1. Instructions are indented at least four spaces.
|
||||
- Use lowercase opcodes and operands, two spaces between opcode and operand,
|
||||
and two spaces before end-of-line comments.
|
||||
- Use `.b`, `.w`, and `.l` for byte, word, and long operations. Do not rely on
|
||||
default sizes.
|
||||
- Hex is `$1234`, binary is `%1010`, decimal is `123`, and characters use
|
||||
`#'A'`.
|
||||
- Local labels start with `.`. Equates use `name = value`.
|
||||
- Data directives are `dc.b`, `dc.w`, `dc.l`; allocation directives are
|
||||
`ds.b`, `ds.w`, `ds.l`.
|
||||
- Sections use forms such as `SECTION .text,code` and `SECTION .bss,bss`.
|
||||
|
||||
```asm
|
||||
moveq #0,d0
|
||||
move.w value,d0
|
||||
add.l d1,d0
|
||||
.loop
|
||||
dbra d0,.loop
|
||||
```
|
||||
|
||||
## FPU Size Suffixes
|
||||
|
||||
FPU instructions that access memory **must** include a size suffix: `.s`
|
||||
(single, 4 bytes), `.d` (double, 8 bytes), or `.x` (extended, 12 bytes).
|
||||
Without a suffix, vasm defaults to `.x`, reading 12 bytes and corrupting
|
||||
results when the operand is smaller.
|
||||
|
||||
**Use `.s` with memory operands:**
|
||||
```asm
|
||||
fmove.s value(pc),fp0 ; load float from memory
|
||||
fcmp.s other(pc),fp0 ; compare float in memory
|
||||
ftst.s value(pc) ; test float in memory
|
||||
```
|
||||
|
||||
**Omit the suffix for register-to-register operations:**
|
||||
```asm
|
||||
fadd fp1,fp0 ; fp0 = fp0 + fp1
|
||||
fcmp fp1,fp0 ; compare fp0 vs fp1
|
||||
ftst fp0 ; test fp0
|
||||
fintrz fp0,fp0 ; truncate fp0
|
||||
```
|
||||
|
||||
The FPU registers are always 80-bit internally, so no size suffix is needed
|
||||
when both operands are registers.
|
||||
|
||||
## Architecture
|
||||
|
||||
- Registers `D0-D7` and `A0-A7` are 32-bit; `A7` is the stack pointer.
|
||||
- Pointers are 32-bit and must normally be accessed with `.l`.
|
||||
- Memory is big-endian: `$1234` is stored as `$12,$34` and a long's low byte
|
||||
is at offset `+3`.
|
||||
- Word and long accesses must be even-aligned on 68000. 68020 permits
|
||||
misalignment but it is slower.
|
||||
- **Byte `A7` auto-update steps by 2, not 1.** If A7 (== SP, the stack pointer) is the address
|
||||
register and the operand length for post-increment or post-decrement is one byte, A7 is
|
||||
decremented/incremented by 2 instead of 1, to maintain a word-aligned stack. This is the
|
||||
specified behaviour of all the 680x0, not a 68000 quirk, so it is identical everywhere and
|
||||
safe to rely on. Word and long operands step by 2 and 4.
|
||||
```asm
|
||||
move.b d0,-(sp) ; A7 steps by 2, so SP stays word-aligned
|
||||
...
|
||||
addq.l #2, sp ; and the matching pop is #2, not #1
|
||||
```
|
||||
Do not "fix" this to an explicit `subq.l #1,sp`: that leaves SP odd and cause crash.
|
||||
- The 68000 (`amiga500`) has only word multiply/divide and no scaled indexing,
|
||||
bit-field, or 32-bit multiply/divide instructions. The 68020 (`qemu68k`)
|
||||
adds those features and `extb.l`.
|
||||
- On 68000, sign-extend a byte with `ext.w` followed by `ext.l`.
|
||||
- `move.b` and `move.w` to a data register do not clear its upper bits. Like
|
||||
`move.w` only writes the lower 16 bits, `move.b` only writes the lower 8 bits;
|
||||
both leave all other bits of the register untouched. So zero-extending a byte
|
||||
to long requires `moveq #0,d0` first, then `move.b src,d0`, then `move.l d0,dst`
|
||||
(or mask with `and.l #$ff` / `and.l #$ffff`). Relying on `move.b` to clear the
|
||||
upper bits is a common bug that corrupts values.
|
||||
- `movea` does not set condition codes; `move` does. Keep the stack word
|
||||
aligned and push words or longs, never bytes.
|
||||
|
||||
## Registers and Calls
|
||||
|
||||
- Return values use `D0` (primary), `D1` and `D2` (additional), or `A0` for a
|
||||
pointer.
|
||||
- `D0-D1` and `A0-A1` are scratch registers. Preserve registers required by
|
||||
the surrounding calling convention, especially `D2-D7` and `A2-A6`.
|
||||
- Never clobber `A5`: on every M68K target it is the Prog8 stack frame pointer
|
||||
(locals at negative offsets, parameters at positive offsets). Overwriting it
|
||||
corrupts the frame of the running subroutine and breaks unwinding.
|
||||
- Never clobber `A6` on the Amiga targets (`amiga500`, `amiga1200`): it holds
|
||||
the AmigaOS library base pointer that library calls depend on. Prog8 itself
|
||||
leaves `A6` free for that reason.
|
||||
- `move.l d0,-(sp)` pushes and `move.l (sp)+,d0` pops.
|
||||
|
||||
### Prog8 M68K stack ABI
|
||||
|
||||
- Normal framed subroutines use `link a5,#-N` and `unlk a5`; locals are at
|
||||
negative `A5` offsets and incoming parameters are at positive offsets.
|
||||
- Arguments use word-packed slots of natural width: byte, bool, word and uword
|
||||
arguments take a 2-byte slot; long, pointer and float arguments take a 4-byte
|
||||
slot. The caller pushes arguments left-to-right (a byte sits at the base byte
|
||||
of its 2-byte slot, the second byte is garbage; word values fill their slot)
|
||||
and removes the complete argument area after the call. The area
|
||||
size is always even, so the stack stays word-aligned; slot bases accumulate from
|
||||
the rightmost argument at `8(a5)` according to each argument's width.
|
||||
- A normal call preserves the caller's frame. A `goto` or conditional branch
|
||||
leaving the current subroutine must execute `unlk a5` on the taken path before
|
||||
transferring control.
|
||||
- The built-in Prog8 `call(address)` is a zero-argument indirect call. It does
|
||||
not marshal stack arguments. `asmsub` and `extsub` keep their explicit
|
||||
register-annotated ABI, separate from normal framed calls.
|
||||
- Push narrow arguments into their own 2-byte slots, each with one instruction: a
|
||||
byte uses `move.b ..., -(sp)` (A7 byte predecrement steps by 2, reserving the
|
||||
slot; the byte lands at the slot's base byte), a word uses a plain
|
||||
`move.w ..., -(sp)`. Never push a narrow value where the callee expects a
|
||||
wide (4-byte) argument: the slot sizes must line up exactly.
|
||||
Wide arguments (`move.l ..., -(sp)` / `fmove.s fp0,-(sp)`) push in one step.
|
||||
Use `lea N(sp),sp` for the cleanup when the call returns a status flag
|
||||
(`addq` clobbers CCR).
|
||||
|
||||
## Prog8 Integration
|
||||
|
||||
Assembly can appear in `%asm {{ }}` blocks or `asmsub` routines. `asmsub`
|
||||
parameter names are documentation only: use the actual annotated registers.
|
||||
Declare every modified register in `clobbers`.
|
||||
|
||||
```prog8
|
||||
asmsub add100(uword value @D0) -> uword @D0 clobbers (D0) {
|
||||
%asm {{
|
||||
add.w #100,d0
|
||||
}}
|
||||
}
|
||||
```
|
||||
|
||||
Do not use `%option no_sysinit` on `amiga500` or `qemu68k` targets when the goal
|
||||
is to create normal runnable programs. It skips critical startup logic including
|
||||
library initialization and CLI argument handling, causing programs to crash on
|
||||
start.
|
||||
|
||||
Supported annotations include `@D0`-`@D7`, `@A0`-`@A6`, virtual `@R0`-`@R15`,
|
||||
floating-point `@FP0`-`@FP7`, and flags `@Pc` and `@Pz`.
|
||||
|
||||
Prog8 assembly symbols are prefixed:
|
||||
|
||||
| Prefix | Symbol |
|
||||
| --- | --- |
|
||||
| `p8v_` | variable or parameter |
|
||||
| `p8s_` | subroutine |
|
||||
| `p8b_` | block |
|
||||
| `p8c_` | constant or enum member |
|
||||
| `p8l_` | label |
|
||||
| `p8t_` | struct type |
|
||||
|
||||
Use fully qualified names when needed, such as
|
||||
`p8b_main.p8v_value`. `%option no_symbol_prefixing` disables prefixes.
|
||||
|
||||
```asm
|
||||
move.l p8v_pointer,a0
|
||||
move.w p8v_word,d0
|
||||
jsr p8s_main.p8s_helper
|
||||
```
|
||||
|
||||
## Useful Patterns
|
||||
|
||||
```asm
|
||||
lea source,a0
|
||||
lea dest,a1
|
||||
.copy
|
||||
move.b (a0)+,(a1)+
|
||||
bne.s .copy
|
||||
```
|
||||
|
||||
Use `moveq` for small constants, `addq`/`subq` for constants 1 through 8, and
|
||||
`lea` for address arithmetic. Check divisors for zero and division overflow.
|
||||
|
||||
## Tools
|
||||
|
||||
For QEMU 68k, Prog8 invokes `vasmm68k_mot` with `-m68020 -m68881 -Felf
|
||||
-opt-speed -warnunaligned -ldots -spaces`, then links with `vlink`. For raw
|
||||
binary output use `-Fbin` and `-join=0x<addr>`. Add `-L listing.txt` when a
|
||||
listing is needed.
|
||||
|
||||
Prog8 runs `amiga500` programs with `vamos --cpu 68000` and `qemu68k` programs
|
||||
with `qemu-system-m68k -M virt -cpu m68020 -m 1M -kernel program.elf -nographic`.
|
||||
@@ -1,168 +0,0 @@
|
||||
---
|
||||
name: prog8-coder
|
||||
description: Writing or understanding Prog8 programs and Prog8 IR
|
||||
---
|
||||
|
||||
# Prog8 Coder
|
||||
|
||||
You are an expert Prog8 assistant. Keep answers concise and practical. This
|
||||
skill covers `.p8` source and `.p8ir` intermediate representation for the
|
||||
6502, M68000, and virtual targets.
|
||||
|
||||
Various reference documentation is available locally in
|
||||
`~/Documents/prog8-dev-resources`:
|
||||
|
||||
Prog8 reference documentation, emulator manuals such as VICE and the X16
|
||||
emulator, the 64tass/vasm/vlink assembler and linker docs, X16 hardware and register specs,
|
||||
the Amiga NDK (docs and includes), and other resources.
|
||||
Search in this folder first before looking online!
|
||||
|
||||
|
||||
## Essentials
|
||||
|
||||
- A program normally has a `main` block with a parameterless `start` entry
|
||||
subroutine. Other blocks and subroutines may be added.
|
||||
- `%import modulename` imports a module without aliases. Use its defined prefix,
|
||||
such as `%import textio` followed by `txt.print(...)`.
|
||||
- `sys` is always available and needs no import.
|
||||
- `.p8ir` is target-independent IR and can be run with
|
||||
`prog8c -vm program.p8ir`.
|
||||
- Use `%zeropage basicsafe` in test programs when a clean return is needed.
|
||||
- Find library routines with `prog8c -libsearch <regex>` or extract them with
|
||||
`prog8c -libdump <dir>`.
|
||||
- Options accept single-dash or double-dash (`-target` == `--target`). Use
|
||||
`-out <dir>` to place output in a directory, or `-o <file>` to name the exact
|
||||
final program artifact (all other output files then land beside it).
|
||||
|
||||
For CX16 emulator programs, use `%encoding iso`, call `txt.iso()` in `start`,
|
||||
and finish with `sys.poweroff_system()`. CBM targets use PETSCII by default;
|
||||
avoid uppercase in test output unless intentional graphics. The virtual target
|
||||
uses ISO when configured with `%encoding iso` and `txt.iso()`.
|
||||
|
||||
On any CBM machine with PETSCII (this includes the Commander X16), files
|
||||
on disk must only consist of 'safe' characters (`a-z`, `0-9`, `.-+!%`).
|
||||
Do NOT mix casing in filenames; in Prog8 source code, filenames should
|
||||
usually be written in *lowercase* letters. Other characters (such as
|
||||
`_` or uppercase mixed with lowercase) can make kernal file operations
|
||||
fail, depending on the drive/host layer.
|
||||
|
||||
Do not use `%option no_sysinit` on `amiga500`, `amiga1200`, or `qemu68k` targets when the goal
|
||||
is to create normal runnable programs. It skips critical startup logic including
|
||||
library initialization (DOS, graphics, intuition, timer) and CLI argument
|
||||
handling, causing programs to crash on start. It is appropriate for library
|
||||
modules, IRQ handlers, boot stubs, or code that deliberately avoids those
|
||||
libraries.
|
||||
|
||||
## Types and Memory
|
||||
|
||||
- Primitive types include `bool`, `byte`, `ubyte`, `word`, `uword`, `long`,
|
||||
`float`, `str`, and `pointer`.
|
||||
- `pointer` is target-sized: 16-bit on 6502 targets, 32-bit on `amiga500`,
|
||||
`qemu68k`, and `virtual`. Use it for portable addresses, not `uword` or
|
||||
`long` chosen by assumption.
|
||||
- M68000 targets are big-endian and use 32-bit pointers. Load the `m68k-coder`
|
||||
skill for assembly details.
|
||||
- `float` requires `%import floats`.
|
||||
- On 6502 and virtual targets, variables are statically allocated and
|
||||
zero-initialized. There is no normal call stack for locals, so recursion
|
||||
overwrites locals unless an explicit hardware or software stack is used.
|
||||
Iterative rewrites are preferred. Arrays are cleared once at startup here,
|
||||
not on every subroutine entry.
|
||||
- On the M68000 targets (`amiga500`, `amiga1200`, `qemu68k`) ordinary subroutine
|
||||
locals and parameters live in an activation record on the CPU stack, so true
|
||||
recursion and re-entrancy work. `link a5,#-N` / `unlk a5`, frame pointer `A5`,
|
||||
locals at negative offsets, parameters at positive offsets. Block-level
|
||||
variables, globals, `@shared`, memory slabs, and objects needing a permanent
|
||||
external address stay static. Clean locals are zeroed on every call
|
||||
including arrays, so the compiler warns for framed arrays over 8 bytes; use
|
||||
`@dirty` when you assign first, otherwise you read stack garbage.
|
||||
- Normal M68K calls use word-packed argument slots: byte/bool/word/uword arguments
|
||||
take a 2-byte slot, long/pointer/float take 4 bytes. The caller pushes arguments
|
||||
left-to-right and removes the complete argument area after the call. Slot bases
|
||||
accumulate right-to-left starting at positive `A5` offset 8, so a parameter's
|
||||
offset depends on the widths of the parameters to its right; locals use negative
|
||||
offsets. See `docs/source/technical.rst` for the full convention and static
|
||||
fallback rules.
|
||||
- `memory(name, size)` reserves static memory and returns a `pointer`.
|
||||
- `str` and arrays have compile-time byte budgets. On 6502 targets the usual
|
||||
limit is 256 bytes; M68000 targets allow up to 32768 bytes. Use `memory()`
|
||||
for larger data.
|
||||
- Word arrays are split into LSB/MSB arrays by default. Use `@nosplit` when
|
||||
contiguous storage is required.
|
||||
- Variable tags follow the type and array dimensions, before the name:
|
||||
|
||||
```prog8
|
||||
ubyte[8] @shared vera_storage
|
||||
uword @requirezp address
|
||||
```
|
||||
|
||||
Use `@shared` for values accessed externally by assembly. Use `@zp` and
|
||||
`@requirezp` sparingly because zeropage is limited. Never assume virtual
|
||||
registers survive calls. Long operations may clobber `R12-R15`.
|
||||
|
||||
## Syntax Pitfalls
|
||||
|
||||
- Hex uses `$FF`, binary uses `%1010`, and casts use `expression as type`.
|
||||
- There is no automatic type widening in expression results: `byte * byte`
|
||||
remains a byte. Cast explicitly when a wider result is needed. Assignments,
|
||||
call arguments, and mixed-type expressions do widen implicitly
|
||||
(`ubyte`/`byte` to `word`/`uword` to `long`); only same-width signedness
|
||||
reinterpretation (`uword` to `word` and back) needs an explicit `as` cast.
|
||||
- `&` is an untyped address and `&&` is a typed pointer. Complex pointer field
|
||||
assignments may require `ptr^^.field`.
|
||||
- `and`, `or`, `xor`, and `not` are logical operators. Use `&`, `|`, `^`, and
|
||||
`~` for bitwise operations.
|
||||
- `and` and `or` short-circuit, so the right operand may not be evaluated.
|
||||
- There is no `elif`, bare block, or semicolon statement separator. Semicolon
|
||||
starts a comment. Prefer one statement per line and four-space indentation.
|
||||
- `defer` executes registered statements in reverse registration order.
|
||||
- Array indexing starts at zero. Use `len(array)` rather than hardcoded sizes.
|
||||
- There is no function overloading. Call type-specific library routines such as
|
||||
`txt.print_ub` or `txt.print_w`.
|
||||
|
||||
## Control Flow
|
||||
|
||||
Prog8 supports `if`/`else`, `when`, `for`, `while`, `do`/`until`, `repeat`,
|
||||
`unroll`, `break`, `continue`, `goto`, and labels. `unroll` duplicates a
|
||||
constant-count body at compile time and does not support `break` or `continue`.
|
||||
`repeat` is usually more efficient than `for` when no loop variable is needed.
|
||||
Replace a `for` loop with a `repeat` loop when the loop variable is not used in the loop body.
|
||||
Use `if_cs`, `if_cc`, `if_z`, and `if_nz` for direct CPU-flag branches.
|
||||
|
||||
## Subroutines and Assembly
|
||||
|
||||
- Subroutines can return zero, one, or multiple values:
|
||||
`a, b = routine()` or `void routine()`.
|
||||
- On M68K, ordinary calls preserve the caller's active frame. A `goto` or branch
|
||||
that leaves the current subroutine must tear that frame down before transferring
|
||||
control. The built-in `call(address)` is a zero-argument indirect call; it does
|
||||
not marshal stack arguments.
|
||||
- Avoid `private` unless requested. Prog8 symbols are public by default.
|
||||
- `asmsub` bodies contain only one `%asm {{ ... }}` node. Parameters are type
|
||||
checked and documented, but assembly must use the mapped registers. Declare
|
||||
every modified hardware register in `clobbers`.
|
||||
- On M68K targets never clobber `A5`, the stack frame pointer. On the Amiga
|
||||
targets never clobber `A6` either, it holds the AmigaOS library base.
|
||||
- `extsub` maps a signature to a fixed external address and has no body. Use
|
||||
it for ROM, kernel, drivers, or binary-library routines.
|
||||
- For inline assembly, load `asm6502-coder` for 64tass syntax, target-specific
|
||||
instructions, zero-page rules, and symbol references. Load `m68k-coder` for
|
||||
M68000 assembly.
|
||||
|
||||
## Interrupts and Hardware
|
||||
|
||||
- Keep IRQ handlers short. Set a flag and do substantial work in the main loop.
|
||||
- Virtual registers `R0-R15` are not preserved across IRQ handlers. Avoid them
|
||||
or save and restore them with the library routines.
|
||||
- CX16 IRQ handlers that touch VERA registers must save and restore VERA
|
||||
context.
|
||||
- Use only symbolic scratch names such as `P8ZP_SCRATCH_W1` and
|
||||
`cx16.r0`; never hardcode zeropage addresses.
|
||||
|
||||
## Verification
|
||||
|
||||
- Use the `virtual` target for behavioral tests when possible:
|
||||
`prog8c -target virtual -emu program.p8`.
|
||||
- Use `-check` for syntax and semantic checks without output generation.
|
||||
- Use `-noopt` to determine whether a failure is optimizer-related.
|
||||
- For IR execution, use `-vmtrace` when control flow needs inspection.
|
||||
@@ -1,20 +0,0 @@
|
||||
# An .aiignore file follows the same syntax as a .gitignore file.
|
||||
# .gitignore documentation: https://git-scm.com/docs/gitignore
|
||||
|
||||
# you can ignore files
|
||||
.DS_Store
|
||||
*.log
|
||||
*.tmp
|
||||
*.bin
|
||||
*.prg
|
||||
|
||||
# or folders
|
||||
dist/
|
||||
build/
|
||||
out/
|
||||
output/
|
||||
gradle/
|
||||
Libs/
|
||||
.gradle/
|
||||
.kotlin/
|
||||
.git/
|
||||
@@ -1,45 +0,0 @@
|
||||
# EditorConfig for Prog8 projects
|
||||
# Ensures consistent formatting across editors and team members
|
||||
# See https://editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
# All files
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
# Prog8 source files (.p8)
|
||||
[*.p8]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
# Assembly files (.asm) - 64tass syntax
|
||||
[*.asm]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
# Kotlin compiler source files
|
||||
[*.kt]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
# Documentation files
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.rst]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
# Gradle build files
|
||||
[*.gradle.kts]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
# Git ignore files
|
||||
[.gitignore]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
@@ -1,14 +0,0 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
#github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
#patreon: # Replace with a single Patreon username
|
||||
#open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: irmen
|
||||
#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
#community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
#liberapay: # Replace with a single Liberapay username
|
||||
#issuehunt: # Replace with a single IssueHunt username
|
||||
#otechie: # Replace with a single Otechie username
|
||||
#lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
#custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
custom: ['https://paypal.me/irmendejong']
|
||||
@@ -10,45 +10,22 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: install 64tass
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install 64tass
|
||||
64tass --version
|
||||
- name: Install 64tass
|
||||
run: sudo apt-get update -y && sudo apt-get install -y 64tass
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v5
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: 17
|
||||
distribution: temurin
|
||||
java-version: 11
|
||||
distribution: adopt
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v6
|
||||
|
||||
- name: Compile
|
||||
run: ./gradlew compileKotlin compileTestKotlin --console=plain
|
||||
|
||||
- name: Test
|
||||
run: ./gradlew test --continue --max-workers=3 -PtestConcurrency=2 -PtestMaxHeap=8g --console=plain
|
||||
|
||||
- name: Package
|
||||
run: |
|
||||
./gradlew shadowJar --console=plain
|
||||
sha256sum -b compiler/build/libs/*-all.jar > compiler/build/libs/hash.txt
|
||||
- name: Build and test with Gradle
|
||||
run: ./gradlew build shadowJar --no-daemon
|
||||
|
||||
- name: Create compiler shadowJar artifact
|
||||
uses: actions/upload-artifact@v6
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: prog8-compiler-jar-zipped
|
||||
path: |
|
||||
compiler/build/libs/*-all.jar
|
||||
compiler/build/libs/hash.txt
|
||||
|
||||
- name: Upload test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: test-results
|
||||
path: compiler/build/test-results/test/
|
||||
path: compiler/build/libs/*-all.jar
|
||||
+3
-20
@@ -1,51 +1,34 @@
|
||||
.idea/workspace.xml
|
||||
.idea/discord.xml
|
||||
.idea/developer-tools.xml
|
||||
.idea/usage.statistics.xml
|
||||
.idea/shelf/
|
||||
.junie/
|
||||
.junie/memory/
|
||||
.serena
|
||||
build/
|
||||
dist/
|
||||
output/
|
||||
out/
|
||||
out-new/
|
||||
out-old/
|
||||
.*cache/
|
||||
*.directory
|
||||
*.prg
|
||||
*.elf
|
||||
*.o
|
||||
*.bin
|
||||
*.p8ir
|
||||
*.bankedcalls
|
||||
*.labels.txt
|
||||
*.vm.txt
|
||||
*.vice-mon-list
|
||||
docs/build
|
||||
out/
|
||||
parser/**/*.interp
|
||||
parser/**/*.tokens
|
||||
parser/**/*.java
|
||||
compiler/src/prog8/buildversion/*
|
||||
languageServer/src/prog8/buildversion/*
|
||||
*.py[cod]
|
||||
*.egg
|
||||
*.egg-info
|
||||
.eggs/
|
||||
/MANIFEST
|
||||
.tox/
|
||||
.kotlin/
|
||||
__pycache__/
|
||||
parser.out
|
||||
parsetab.py
|
||||
.pytest_cache/
|
||||
.attach_pid*
|
||||
compiler/lib/
|
||||
|
||||
.gradle
|
||||
/prog8compiler.jar
|
||||
sd*.img
|
||||
*.d64
|
||||
AsyncIO/
|
||||
AsyncIO.info
|
||||
m68k-amigaos-ahi/
|
||||
Libs/
|
||||
Generated
-10
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JunieProjectTechnologies">
|
||||
<option name="explicitlyEnabledTechs">
|
||||
<set>
|
||||
<option value="junie.python" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+2
-12
@@ -2,16 +2,6 @@
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<option name="BUILD_PROCESS_HEAP_SIZE" value="3000" />
|
||||
<bytecodeTargetLevel target="11">
|
||||
<module name="prog8.codeGenExperimental" target="17" />
|
||||
<module name="prog8.codeGenExperimental.main" target="17" />
|
||||
<module name="prog8.codeGenExperimental.test" target="17" />
|
||||
<module name="prog8.codeGenM68k" target="17" />
|
||||
<module name="prog8.codeGenM68k.main" target="17" />
|
||||
<module name="prog8.codeGenM68k.test" target="17" />
|
||||
<module name="prog8.codeGenNew6502" target="17" />
|
||||
<module name="prog8.codeGenNew6502.main" target="17" />
|
||||
<module name="prog8.codeGenNew6502.test" target="17" />
|
||||
</bytecodeTargetLevel>
|
||||
<bytecodeTargetLevel target="11" />
|
||||
</component>
|
||||
</project>
|
||||
</project>
|
||||
Generated
-30
@@ -1,30 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/codeCore" />
|
||||
<option value="$PROJECT_DIR$/codeGenCpu6502" />
|
||||
<option value="$PROJECT_DIR$/codeGenIntermediate" />
|
||||
<option value="$PROJECT_DIR$/codeGenM68k" />
|
||||
<option value="$PROJECT_DIR$/codeGenNew6502" />
|
||||
<option value="$PROJECT_DIR$/codeOptimizers" />
|
||||
<option value="$PROJECT_DIR$/compiler" />
|
||||
<option value="$PROJECT_DIR$/compilerAst" />
|
||||
<option value="$PROJECT_DIR$/intermediate" />
|
||||
<option value="$PROJECT_DIR$/languageServer" />
|
||||
<option value="$PROJECT_DIR$/parser" />
|
||||
<option value="$PROJECT_DIR$/simpleAst" />
|
||||
<option value="$PROJECT_DIR$/virtualmachine" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="myGradleHome" value="/usr/share/java/gradle" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
+2
-1563
File diff suppressed because it is too large.
Load diff
Generated
-15
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RemoteRepositoriesConfiguration">
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Maven Central repository" />
|
||||
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="80ae155e-b222-4ca0-92cf-6041a298e78a" />
|
||||
<option name="name" value="80ae155e-b222-4ca0-92cf-6041a298e78a" />
|
||||
<option name="url" value="https://jitpack.io" />
|
||||
</remote-repository>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+2
-13
@@ -1,20 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Kotlin2JsCompilerArguments">
|
||||
<option name="moduleKind" value="plain" />
|
||||
</component>
|
||||
<component name="Kotlin2JvmCompilerArguments">
|
||||
<option name="jvmTarget" value="11" />
|
||||
</component>
|
||||
<component name="KotlinCommonCompilerArguments">
|
||||
<option name="apiVersion" value="2.4" />
|
||||
<option name="languageVersion" value="2.4" />
|
||||
</component>
|
||||
<component name="KotlinCompilerSettings">
|
||||
<option name="additionalArguments" value="-jvm-default=no-compatibility" />
|
||||
</component>
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="externalSystemId" value="Gradle" />
|
||||
<option name="version" value="2.4.21" />
|
||||
<option name="version" value="1.8.21-release-380" />
|
||||
</component>
|
||||
</project>
|
||||
</project>
|
||||
Generated
+19
@@ -0,0 +1,19 @@
|
||||
<component name="libraryTable">
|
||||
<library name="KotlinJavaRuntime">
|
||||
<CLASSES>
|
||||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib.jar!/" />
|
||||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-reflect.jar!/" />
|
||||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-test.jar!/" />
|
||||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-jdk7.jar!/" />
|
||||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-jdk8.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-sources.jar!/" />
|
||||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-reflect-sources.jar!/" />
|
||||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-test-sources.jar!/" />
|
||||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-jdk7-sources.jar!/" />
|
||||
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-jdk8-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
Generated
+18
@@ -0,0 +1,18 @@
|
||||
<component name="libraryTable">
|
||||
<library name="antlr.antlr4" type="repository">
|
||||
<properties maven-id="org.antlr:antlr4:4.12.0">
|
||||
<exclude>
|
||||
<dependency maven-id="com.ibm.icu:icu4j" />
|
||||
</exclude>
|
||||
</properties>
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.12.0/antlr4-4.12.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.12.0/antlr4-runtime-4.12.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr-runtime/3.5.3/antlr-runtime-3.5.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/ST4/4.3.4/ST4-4.3.4.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/abego/treelayout/org.abego.treelayout.core/1.0.3/org.abego.treelayout.core-1.0.3.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
<component name="libraryTable">
|
||||
<library name="github.hypfvieh.dbus.java" type="repository">
|
||||
<properties maven-id="com.github.hypfvieh:dbus-java:3.3.2" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/hypfvieh/dbus-java/3.3.2/dbus-java-3.3.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-unixsocket/0.38.17/jnr-unixsocket-0.38.17.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-ffi/2.2.11/jnr-ffi-2.2.11.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jffi/1.3.9/jffi-1.3.9.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jffi/1.3.9/jffi-1.3.9-native.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm/9.2/asm-9.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-commons/9.2/asm-commons-9.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-analysis/9.2/asm-analysis-9.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-tree/9.2/asm-tree-9.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-util/9.2/asm-util-9.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-a64asm/1.0.0/jnr-a64asm-1.0.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-x86asm/1.0.2/jnr-x86asm-1.0.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-constants/0.10.3/jnr-constants-0.10.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-enxio/0.32.13/jnr-enxio-0.32.13.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-posix/3.1.15/jnr-posix-3.1.15.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
Generated
+10
@@ -0,0 +1,10 @@
|
||||
<component name="libraryTable">
|
||||
<library name="glassfish.javax.json" type="repository">
|
||||
<properties include-transitive-deps="false" maven-id="org.glassfish:javax.json:1.1.4" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/javax.json/1.1.4/javax.json-1.1.4.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<component name="libraryTable">
|
||||
<library name="io.kotest.assertions.core.jvm" type="repository">
|
||||
<properties maven-id="io.kotest:kotest-assertions-core-jvm:5.5.5" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.5.5/kotest-assertions-core-jvm-5.5.5.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.21/kotlin-stdlib-jdk8-1.6.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.6.21/kotlin-stdlib-1.6.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.6.21/kotlin-stdlib-jdk7-1.6.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.5.5/kotest-assertions-shared-jvm-5.5.5.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.6.21/kotlin-stdlib-common-1.6.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.6.4/kotlinx-coroutines-jdk8-1.6.4.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.6.21/kotlin-reflect-1.6.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.5.5/kotest-common-jvm-5.5.5.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.5.5/kotest-assertions-api-jvm-5.5.5.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.6.4/kotlinx-coroutines-core-jvm-1.6.4.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
<component name="libraryTable">
|
||||
<library name="io.kotest.property.jvm" type="repository">
|
||||
<properties maven-id="io.kotest:kotest-property-jvm:5.5.5" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-property-jvm/5.5.5/kotest-property-jvm-5.5.5.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.5.5/kotest-common-jvm-5.5.5.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.5.5/kotest-assertions-shared-jvm-5.5.5.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.5.5/kotest-assertions-api-jvm-5.5.5.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.6.4/kotlinx-coroutines-jdk8-1.6.4.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.6.21/kotlin-stdlib-common-1.6.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/curious-odd-man/rgxgen/1.4/rgxgen-1.4.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.21/kotlin-stdlib-jdk8-1.6.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.6.21/kotlin-stdlib-1.6.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.6.21/kotlin-stdlib-jdk7-1.6.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.6.21/kotlin-reflect-1.6.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.6.4/kotlinx-coroutines-core-jvm-1.6.4.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
<component name="libraryTable">
|
||||
<library name="io.kotest.runner.junit5.jvm" type="repository">
|
||||
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:5.5.5" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/5.5.5/kotest-runner-junit5-jvm-5.5.5.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.5.5/kotest-framework-api-jvm-5.5.5.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-test-jvm/1.6.4/kotlinx-coroutines-test-jvm-1.6.4.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.5.5/kotest-assertions-shared-jvm-5.5.5.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.6.21/kotlin-stdlib-common-1.6.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.5.5/kotest-common-jvm-5.5.5.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/5.5.5/kotest-framework-engine-jvm-5.5.5.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.154/classgraph-4.8.154.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/mordant/1.2.1/mordant-1.2.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/colormath/1.2.0/colormath-1.2.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-debug/1.6.4/kotlinx-coroutines-debug-1.6.4.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna/5.9.0/jna-5.9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna-platform/5.9.0/jna-platform-5.9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.10.9/byte-buddy-1.10.9.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy-agent/1.10.9/byte-buddy-agent-1.10.9.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-discovery-jvm/5.5.5/kotest-framework-discovery-jvm-5.5.5.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.5.5/kotest-assertions-core-jvm-5.5.5.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.6.4/kotlinx-coroutines-jdk8-1.6.4.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.5.5/kotest-assertions-api-jvm-5.5.5.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/5.5.5/kotest-extensions-jvm-5.5.5.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-jvm/1.13.1/mockk-jvm-1.13.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-dsl-jvm/1.13.1/mockk-dsl-jvm-1.13.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-agent-jvm/1.13.1/mockk-agent-jvm-1.13.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/objenesis/objenesis/3.2/objenesis-3.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-agent-api-jvm/1.13.1/mockk-agent-api-jvm-1.13.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-core-jvm/1.13.1/mockk-core-jvm-1.13.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/junit/junit/4.13.2/junit-4.13.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter/5.8.2/junit-jupiter-5.8.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-params/5.8.2/junit-jupiter-params-5.8.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-engine/5.8.2/junit-jupiter-engine-5.8.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core/1.6.4/kotlinx-coroutines-core-1.6.4.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/5.5.5/kotest-framework-concurrency-jvm-5.5.5.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.6.4/kotlinx-coroutines-core-jvm-1.6.4.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.7.2/junit-platform-engine-1.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.0/apiguardian-api-1.1.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.7.2/junit-platform-commons-1.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-suite-api/1.7.2/junit-platform-suite-api-1.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-launcher/1.7.2/junit-platform-launcher-1.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.8.2/junit-jupiter-api-5.8.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.21/kotlin-stdlib-jdk8-1.6.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.6.21/kotlin-stdlib-1.6.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.6.21/kotlin-stdlib-jdk7-1.6.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.6.21/kotlin-reflect-1.6.21.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<component name="libraryTable">
|
||||
<library name="jetbrains.kotlinx.cli.jvm" type="repository">
|
||||
<properties include-transitive-deps="false" maven-id="org.jetbrains.kotlinx:kotlinx-cli-jvm:0.3.5" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-cli-jvm/0.3.5/kotlinx-cli-jvm-0.3.5.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
<component name="libraryTable">
|
||||
<library name="michael.bull.kotlin.result.jvm" type="repository">
|
||||
<properties maven-id="com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/1.1.16/kotlin-result-jvm-1.1.16.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.6.20/kotlin-stdlib-common-1.6.20.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.20/kotlin-stdlib-jdk8-1.6.20.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.6.20/kotlin-stdlib-1.6.20.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.6.20/kotlin-stdlib-jdk7-1.6.20.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
Generated
+11
@@ -0,0 +1,11 @@
|
||||
<component name="libraryTable">
|
||||
<library name="slf4j.simple" type="repository">
|
||||
<properties maven-id="org.slf4j:slf4j-simple:2.0.7" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-simple/2.0.7/slf4j-simple-2.0.7.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/2.0.7/slf4j-api-2.0.7.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
Generated
+18
@@ -0,0 +1,18 @@
|
||||
<component name="libraryTable">
|
||||
<library name="takes" type="repository">
|
||||
<properties maven-id="org.takes:takes:1.24.4" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/takes/takes/1.24.4/takes-1.24.4.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/cactoos/cactoos/0.54.0/cactoos-0.54.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/javax/xml/bind/jaxb-api/2.4.0-b180830.0359/jaxb-api-2.4.0-b180830.0359.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/javax/activation/javax.activation-api/1.2.0/javax.activation-api-1.2.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/sun/xml/bind/jaxb-core/4.0.0/jaxb-core-4.0.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/jakarta/xml/bind/jakarta.xml.bind-api/4.0.0/jakarta.xml.bind-api-4.0.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/jakarta/activation/jakarta.activation-api/2.1.0/jakarta.activation-api-2.1.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/angus/angus-activation/1.0.0/angus-activation-1.0.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/sun/xml/bind/jaxb-impl/4.0.0/jaxb-impl-4.0.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
Generated
+3
-11
@@ -4,30 +4,22 @@
|
||||
<option name="perGrammarGenerationSettings">
|
||||
<list>
|
||||
<PerGrammarGenerationSettings>
|
||||
<option name="fileName" value="$PROJECT_DIR$/parser/src/main/antlr/Prog8ANTLR.g4" />
|
||||
<option name="fileName" value="$PROJECT_DIR$/parser/antlr/Prog8ANTLR.g4" />
|
||||
<option name="autoGen" value="true" />
|
||||
<option name="outputDir" value="$PROJECT_DIR$/parser/src/prog8/parser" />
|
||||
<option name="libDir" value="" />
|
||||
<option name="encoding" value="" />
|
||||
<option name="pkg" value="" />
|
||||
<option name="language" value="Java" />
|
||||
<option name="language" value="" />
|
||||
<option name="generateListener" value="false" />
|
||||
<option name="generateVisitor" value="true" />
|
||||
</PerGrammarGenerationSettings>
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.11" />
|
||||
</component>
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="FrameworkDetectionExcludesConfiguration">
|
||||
<type id="Python" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="openjdk-21" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="openjdk-11" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
<component name="PythonCompatibilityInspectionAdvertiser">
|
||||
<option name="version" value="3" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
+14
-2
@@ -2,9 +2,21 @@
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/benchmark-program/benchmark-program.iml" filepath="$PROJECT_DIR$/benchmark-program/benchmark-program.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeCore/codeCore.iml" filepath="$PROJECT_DIR$/codeCore/codeCore.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeGenCpu6502/codeGenCpu6502.iml" filepath="$PROJECT_DIR$/codeGenCpu6502/codeGenCpu6502.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeGenExperimental/codeGenExperimental.iml" filepath="$PROJECT_DIR$/codeGenExperimental/codeGenExperimental.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeGenIntermediate/codeGenIntermediate.iml" filepath="$PROJECT_DIR$/codeGenIntermediate/codeGenIntermediate.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeGenVirtual/codeGenVirtual.iml" filepath="$PROJECT_DIR$/codeGenVirtual/codeGenVirtual.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/prog8.iml" filepath="$PROJECT_DIR$/.idea/modules/prog8.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeOptimizers/codeOptimizers.iml" filepath="$PROJECT_DIR$/codeOptimizers/codeOptimizers.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/compilerAst/compilerAst.iml" filepath="$PROJECT_DIR$/compilerAst/compilerAst.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/dbusCompilerService/dbusCompilerService.iml" filepath="$PROJECT_DIR$/dbusCompilerService/dbusCompilerService.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/httpCompilerService/httpCompilerService.iml" filepath="$PROJECT_DIR$/httpCompilerService/httpCompilerService.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/intermediate/intermediate.iml" filepath="$PROJECT_DIR$/intermediate/intermediate.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/virtualmachine/virtualmachine.iml" filepath="$PROJECT_DIR$/virtualmachine/virtualmachine.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
description: Startup workflow to load project context
|
||||
---
|
||||
|
||||
1. Read the file `AGENTS.md` in the project root as initial system prompt to build context and instructions about the project structure, module descriptions, and development commands.
|
||||
@@ -1,5 +0,0 @@
|
||||
# Agent Instructions
|
||||
|
||||
- The file `AGENTS.md` MUST be loaded as the very first step of every session, if it isn't already loaded by the system.
|
||||
- Follow all project-specific guidelines and information provided in `AGENTS.md`.
|
||||
- Ensure all automated tasks and code generations adhere to the language rules and architectural decisions described in `AGENTS.md`.
|
||||
+9
-11
@@ -9,19 +9,17 @@ version: 2
|
||||
build:
|
||||
os: ubuntu-22.04
|
||||
tools:
|
||||
python: "3.12"
|
||||
python: "3.11"
|
||||
|
||||
# Build documentation in the docs/ directory with Sphinx
|
||||
sphinx:
|
||||
configuration: docs/source/conf.py
|
||||
|
||||
# If using Sphinx, optionally build your docs in additional formats such as PDF
|
||||
formats:
|
||||
- pdf
|
||||
|
||||
# Optionally declare the Python requirements required to build your docs
|
||||
python:
|
||||
install:
|
||||
- requirements: docs/requirements.txt
|
||||
|
||||
# Build documentation in the docs/ directory with Sphinx
|
||||
sphinx:
|
||||
configuration: docs/source/conf.py
|
||||
fail_on_warning: true
|
||||
|
||||
# If using Sphinx, optionally build your docs in additional formats such as PDF
|
||||
formats:
|
||||
- pdf
|
||||
- epub
|
||||
@@ -1,405 +0,0 @@
|
||||
# CRITICAL RULES - MUST FOLLOW
|
||||
|
||||
## RESPONSES
|
||||
|
||||
- Keep responses concise and to the point - unless the user asks otherwise
|
||||
|
||||
## PLANNING MODE
|
||||
|
||||
- Always ask clarifying questions
|
||||
- Never assume design, tech stack or features
|
||||
- Use deep-dive sub-agents to assist with research
|
||||
- Use deep-dive sub-agents to review the different aspects of your plan before presenting to the user
|
||||
|
||||
## CHANGE / EDIT MODE
|
||||
|
||||
- Use a subagent for larger chunks of work
|
||||
- Identify changes from the plan that can be implemented in parallel, and use sub-agents to implement the features efficiently
|
||||
- When using sub-agents to implement features, act as a coordinator only
|
||||
- Use the best model for the task - premium models for complex tasks (like coding) and mid-tier models for simpler tasks, like documentation
|
||||
- After completing features (large or small), run the relevant quality commands (lint, type check, build) to check code quality.
|
||||
Only compiler/standard-library changes require a build: that means Kotlin sources (`.kt`), the grammar (`.g4`), or embedded
|
||||
standard library files (`compiler/res/prog8lib/**/*.p8` and `**/*.asm`). For changes limited to `examples/**`, `docs/**`, or
|
||||
other non-compiler files, verify with the target compiler directly instead (for example `prog8c -target <target> <file>`), and
|
||||
skip the build entirely
|
||||
(exception: if the change is only whitespace or code comments, a build or test run is not necessary)
|
||||
- `gradle build` already runs the full test suite; don't run a separate full `gradle test` before it, to avoid lengthy double test runs (targeted `--tests` filters to find failing tests are fine)
|
||||
|
||||
## Project Overview
|
||||
- This project is a compiler for the Prog8 programming language.
|
||||
- Prog8 is a programming language primarily targeting 8-bit retro systems with the 6502 CPU, such as the Commodore 64, Commodore 128, and Commander X16.
|
||||
- The compiler has a 6502 code generator backend, and an IR code generator.
|
||||
- The IR code is used by machine-specific code generator backends, including 6502 and M68K.
|
||||
- The compiler includes a simple 'virtual machine' that can execute the IR code directly via interpretation.
|
||||
- Prog8 source files have .p8 extension – these are *not* LUA or PICO-8 source files in this case!
|
||||
- Prog8 source files are a "module" that can contain one or more "blocks". They can also import other modules, from internal library files or from source files on the filesystem.
|
||||
- The prog8 compiler is written mostly in Kotlin, those files have the .kt extension.
|
||||
- The standard library is mostly written in Prog8 and assembly code, and can be found in the "compiler" module, in the 'res/prog8lib' directory.
|
||||
- **Library Reference**: For a quick overview of all available modules, routines, and their signatures, **consult the skeleton files (symboldumps)** in `docs/source/_static/symboldumps/`. They are named `skeletons-<target>.txt`.
|
||||
- Kotlin version 2.4 is used for the compiler implementation.
|
||||
- Java 17 is used as Java runtime version.
|
||||
- ANTLR4 version 4.13 is used for the parser implementation.
|
||||
- Dependent library versions can be found in 'build.gradle.kts' and in the IntelliJ IDEA configuration files in .idea/libraries
|
||||
- The compiler main entrypoint is in the "compiler" module, in src/prog8/CompilerMain.kt
|
||||
- **Codebase knowledge graph**: For a structured overview of modules, their dependencies, key entities, the end-to-end compilation pipeline, the standard library layout, and the test structure, consult `docs/CODEBASE-KNOWLEDGE-GRAPH.md`. Read it when you need to understand how the compiler fits together before diving into specific files.
|
||||
|
||||
## Compilation Flow (High-Level)
|
||||
```
|
||||
Source → parseMainModule() → processAst() → optimizeAst() → postprocessAst()
|
||||
→ Simple AST → Code Generator → Assembly → .prg
|
||||
```
|
||||
|
||||
**Key phases in `compiler/src/prog8/compiler/Compiler.kt`:**
|
||||
- `parseMainModule()` - Import modules, parse to Compiler AST
|
||||
- `processAst()` - Semantic analysis, constant folding, type casting, validation
|
||||
- `optimizeAst()` - Dead code elimination, inlining, statement optimization
|
||||
- `postprocessAst()` - Memory layout, final transformations
|
||||
- Code generation – Simple AST → IR or 6502 assembly
|
||||
|
||||
**Understanding this order is important:** For example, `ConstantIdentifierReplacer` runs during `processAst()`, so by the time `AstChecker` runs at the end of that phase, identifier references have already been replaced with their actual values.
|
||||
|
||||
### Target Differences
|
||||
- **CPU instruction set differences**: Only the CommanderX16 target (cx16) can use 65C02 instructions such as STZ. The other targets (C64, C128, PET32) can only use original 6502 instructions.
|
||||
- When writing or understanding 6502/65C02 assembly code, load the `asm6502-coder` skill for 64tass syntax and conventions.
|
||||
- When writing or understanding M68K assembly code (amiga500, amiga1200, qemu68k targets), load the `m68k-coder` skill for vasm mot syntax and conventions.
|
||||
- M68K ordinary subroutine locals and parameters use `A5`-based stack frames. Normal calls pass word-packed argument slots on the stack (2-byte slots for byte/word args, 4-byte for wide args, byte values at the slot base), and the caller cleans up; see `docs/source/technical.rst` for the complete convention and frame eligibility rules.
|
||||
|
||||
|
||||
## DEBUGGING TIP: Use `-noopt` to isolate problems
|
||||
When investigating a possible code generation problem (both IR and 6502), **FIRST try compiling the program with the `-noopt` switch** to disable most of the compiler optimizations.
|
||||
- **Problem gone with `-noopt`**: Issue is in **optimization phases** (`optimizeAst()`, `UnusedCodeRemover`, `Inliner`, etc.)
|
||||
- **Problem persists with `-noopt`**: Issue is in **parsing, semantic analysis, symboltable, or the regular code generation path**.
|
||||
|
||||
This way you can determine if the problem is caused by a faulty optimization step, or just occurs in the regular code generation path.
|
||||
|
||||
## DEBUGGING TIP: Use `-compareir` to see what changed
|
||||
When investigating optimization-related issues or tracking regressions:
|
||||
```bash
|
||||
# Compile without optimizations (baseline)
|
||||
prog8c -target virtual -noopt -out dir program.p8
|
||||
|
||||
# Compile with optimizations and compare
|
||||
prog8c -target virtual -compareir dir/program_noopt.p8ir program.p8
|
||||
```
|
||||
**Output shows:**
|
||||
- Instruction/chunk/register count changes with percentages
|
||||
- First 10 actual instruction differences
|
||||
- Helps identify which optimization transformed the code
|
||||
|
||||
## DEBUGGING TIP: Use `-vmtrace` to trace execution
|
||||
When debugging VM execution or control flow issues:
|
||||
```bash
|
||||
prog8c -target virtual -vm program.p8ir -vmtrace
|
||||
prog8c -target virtual -emu -vmtrace program.p8
|
||||
```
|
||||
**Output format:** `[chunkName:instructionIndex] instruction`
|
||||
- Shows each executed IR instruction with location
|
||||
- Useful for understanding control flow and finding where execution diverges
|
||||
- **Only works on virtual target**
|
||||
|
||||
## Typical debugging workflow:
|
||||
1. **Quick check:** `-check` for syntax errors
|
||||
2. **Isolate:** `-noopt` to determine if problem is optimizer-related
|
||||
3. **Compare:** `-compareir` to see what instructions changed
|
||||
4. **Trace:** `-vmtrace` to watch actual execution flow
|
||||
5. **Deep dive:** `-printast1` / `-printast2` for compiler internals
|
||||
|
||||
## IMPORTANT: SymbolTable cachedFlat cache
|
||||
The `SymbolTable` class has a **cached `flat` property** for fast symbol lookups.
|
||||
|
||||
**CRITICAL:** If you modify the AST **after** SymbolTable construction, you **MUST** call `symbolTable.resetCachedFlat()` or lookups will fail!
|
||||
|
||||
## IMPORTANT: Virtual Target (IR Codegen) Issues
|
||||
For problems that **ONLY occur with the 'virtual' target**, **ONLY modify these modules**:
|
||||
- `codeGenIntermediate` - IR code generator
|
||||
- `intermediate` - IR representation and file I/O
|
||||
- `virtualmachine` - VM that executes IR code
|
||||
|
||||
**DO NOT modify** `compilerAst`, `simpleAst`, `codeGenCpu6502`, etc. The IR codegen has its own separate handling for symbol tables, AST transformations, and unused code removal.
|
||||
|
||||
## IMPORTANT: Compiler Crashes Must Be Fixed in the Compiler
|
||||
A Kotlin/Java crash in the compiler itself (as opposed to a compilation error in the user's program) indicates a bug in the compiler code. **Such crashes should NEVER be worked around by modifying the Prog8 source file that triggered them.**
|
||||
|
||||
Instead, always fix the bug in the compiler's Kotlin code. The user's program is correct - the compiler needs to handle it properly.
|
||||
|
||||
Steps when encountering a compiler crash:
|
||||
1. Create a minimal reproduction case of the Prog8 source that triggers the crash
|
||||
2. Debug the compiler to find the root cause in the Kotlin code
|
||||
3. Fix the compiler bug, not the user program
|
||||
|
||||
## CRITICAL: NO FORMATTING
|
||||
- DO NOT change indentation and formatting of lines that are not being modified. NEVER run formatters (black, ruff, prettier, etc.) after edits,
|
||||
- .editorconfig handles basic formatting (indentation, line endings, whitespace)
|
||||
- Make ONLY the requested changes, touch nothing else
|
||||
- **NO EMOJI in user documentation**. Do not use emoji or decorative unicode symbols in documentation files. Functional unicode symbols are acceptable when they serve a clear purpose (e.g., → for arrows, ± for plus-minus, × for multiplication, ° for degrees). Avoid decorative emoji like ❌ ✅ ⚠️ 🎉 etc.
|
||||
|
||||
### Code Style Guidelines
|
||||
**Code comments**: Comments are the exception, not the norm. A comment earns its place only by recording
|
||||
something the code itself cannot say: the reason for a non-obvious choice, or a constraint that
|
||||
a well-meaning future edit would otherwise violate.
|
||||
|
||||
- Inline comment inside a function body: 1 line, never more.
|
||||
- KDoc on a declaration: 3 lines, unless a genuine non-obvious contract cannot be stated shorter.
|
||||
- Never restate the identifier, the type, or what the following line does.
|
||||
- Prefer a good name or a well-named function over a comment that explains it.
|
||||
|
||||
Before keeping any comment, delete it mentally and ask what the reader loses. If nothing, it goes.
|
||||
|
||||
Keep existing comments as they are; do not expand, reword, or reformat them while editing
|
||||
nearby lines - unless that would cause inconsistency.
|
||||
|
||||
**Kotlin string `$` handling**: The `$` character is special in Kotlin string literals (string interpolation). If you need a literal `$` character, use Kotlin's `$$"..."` interpolation prefix instead of backslash escaping (`\$`) or `${'$'}`. The `$$"..."` syntax is a fairly recent Kotlin feature that avoids nasty escaping. For example:
|
||||
```kotlin
|
||||
val bill = $$"The value is $3.99"
|
||||
```
|
||||
|
||||
|
||||
## Prog8 language information
|
||||
|
||||
When the task involves writing or understanding `.p8` (Prog8 source) or `.p8ir` (Prog8 intermediate representation) files, **load the `prog8-coder` skill** — it contains the full language reference, syntax rules, and standard library guidance.
|
||||
|
||||
## Project Module Descriptions
|
||||
- `compiler` - Main compiler entrypoint (src/prog8/CompilerMain.kt)
|
||||
- `compilerAst` - Complex AST where most optimizations run
|
||||
- `simpleAst` - Simplified AST used by code generator backends
|
||||
- `parser` - ANTLR4 parser implementation
|
||||
- `codeCore` - Core code generation utilities
|
||||
- `codeGenCpu6502` - 6502 assembly code generator backend
|
||||
- `codeGenIntermediate` - Intermediate representation (IR) code generator
|
||||
- `codeGenVirtual` - Virtual machine code generator backend
|
||||
- `codeOptimizers` - Optimization passes
|
||||
- `intermediate` - IR components
|
||||
- `virtualmachine` - VM that executes IR code
|
||||
- `languageServer` - Language Server Protocol implementation
|
||||
- `docs` - Documentation files
|
||||
- `examples` - Example Prog8 programs
|
||||
|
||||
## Key Information
|
||||
- never read the files and directories that are ignored via the .aiignore and .gitignore files (EXCEPT when it is a folder for agent config such as .junie, .opencode, .qwen or .agent)
|
||||
- never perform any git source control write/update/add/commit/branch operations. Read and status operations are allowed.
|
||||
- **git log/history queries can be useful** for understanding when/why a feature was added or tracking down when a bug was introduced, but for locating code use grep_search or glob instead.
|
||||
- Architecture decisions: separation of frontend/parser, IR intermediate representation, multiple backends
|
||||
|
||||
# Dev environment tips
|
||||
|
||||
## Development Workflows
|
||||
|
||||
**1. Testing your own Prog8 programs** (no compiler changes):
|
||||
- No rebuild needed - just run `prog8c` directly
|
||||
- Edit your `.p8` file → compile/run → check stdout output
|
||||
- Example: `prog8c -target virtual -emu myprogram.p8`
|
||||
|
||||
**2. Testing compiler or standard library changes**:
|
||||
- Rebuild required after every change to `.kt`, `.p8`, or `.asm` files in the compiler project
|
||||
- Workflow: edit source → `gradle installdist installshadowdist` → test with `prog8c`
|
||||
- Example: fix bug in `CodeGen6502.kt` → rebuild → `prog8c -target cx16 test.p8`
|
||||
|
||||
## Building and Installing the Compiler
|
||||
- use the system installed gradle command instead of the gradle wrapper.
|
||||
|
||||
### Quick compile check (NO tests)
|
||||
- **After changing compiler Kotlin source (.kt files)**: Use `gradle :compiler:compileKotlin --console=plain` to quickly check for syntax/compile errors
|
||||
- This compiles the compiler but **skips running tests**, this is much faster than `gradle build`
|
||||
- **Note:** This does NOT install the compiler - use `gradle installdist installshadowdist --console=plain` after to actually use your changes
|
||||
|
||||
### When you MUST rebuild AND reinstall the compiler
|
||||
|
||||
**After ANY change to:**
|
||||
1. **Kotlin compiler source code** (`.kt` files in any module, or the `.g4` grammar file)
|
||||
2. **Standard library Prog8 files** (`compiler/res/prog8lib/**/*.p8`)
|
||||
3. **Standard library assembly files** (`compiler/res/prog8lib/**/*.asm`)
|
||||
|
||||
**Run:** `gradle installdist installshadowdist --console=plain`
|
||||
|
||||
**Why:** The standard library files (.p8 and .asm) are embedded into the compiler JAR during the build. Changes to these files are NOT picked up by simply running `prog8c` - you MUST rebuild and reinstall for your changes to take effect.
|
||||
|
||||
**Without this step, your changes will NOT be reflected when running `prog8c`!**
|
||||
- This compiles AND installs, but still skips running tests (faster than `gradle build`)
|
||||
|
||||
### When to run full build with tests
|
||||
- **Before final verification**: Run `gradle build` to ensure all tests pass - but only if the change actually touched the
|
||||
compiler or standard library (`.kt`, `.g4`, or `compiler/res/prog8lib/**`). For `examples/**` or `docs/**`-only changes,
|
||||
verify with `prog8c` directly instead and skip the build
|
||||
- **After major refactoring**: Run `gradle build` to catch regressions
|
||||
- **When debugging test failures**: Use `gradle test --tests "*TestName*"` for specific tests
|
||||
- **When working on a standalone code gen module** (one that reads `.p8ir` files): Run `gradle :<module>:test --console=plain` instead of full `gradle build`. Compiler unit tests are irrelevant for IR-level changes. Do run full `gradle build` before final verification if the change touched compiler sources.
|
||||
|
||||
### Build command summary
|
||||
|
||||
| Command | When to use | Time |
|
||||
|---------|-------------|------|
|
||||
| `gradle :compiler:compileKotlin --console=plain` | Quick syntax check (no tests) | ~10-20s |
|
||||
| `gradle installdist installshadowdist --console=plain` | After compiler changes (to use them) | ~10-20s |
|
||||
| `gradle build --console=plain` | Before commits, after major changes | ~45-60s |
|
||||
| `gradle test --tests "*Name*" --console=plain` | Run specific tests | ~5-30s |
|
||||
|
||||
**Note:** Use system `gradle` command, not wrapper. Run `gradle clean` if you suspect stale artifacts.
|
||||
|
||||
## Using the Compiler (prog8c)
|
||||
- the prog8c compiler executable can be found in the compiler/build/install/prog8c/bin folder (this is already added to the shell's path)
|
||||
- **the `-check` switch performs a quick syntax/semantic check only; it will NOT produce any output files (no .prg, .asm, etc.)**. Use it only for fast error checking during development.
|
||||
- **the `-noopt` switch DISABLES all optimizations** - useful for debugging to determine if a problem is caused by the optimizer. **Optimizations are ENABLED by default** (no flag needed).
|
||||
- **prog8c options accept single-dash or double-dash** (e.g., `-target` and `--target` are equivalent).
|
||||
- **the `-printast1` switch prints out the internal Compiler AST** after parsing and semantic analysis.
|
||||
- **the `-printast2` switch prints out the optimized Simple AST** just before it goes to the code generator. This is useful for debugging optimizer issues.
|
||||
- **the `-out outdir` switch sets an alternative output directory** for compiled files (.prg, .asm, .list, etc.). **By default, output files are written to the current working directory**.
|
||||
- **the `-o <file>` switch names the exact final program artifact** (also spelled `--output`); all other output files go in the same directory with the same stem. Cannot be combined with `-out` or with multiple source files.
|
||||
- **Other useful flags**: `-quiet` (suppress messages), `-warnimplicitcasts` (warn on implicit type widening), `-daemon` (keep a background compiler process alive — must be passed on every invocation)
|
||||
|
||||
### Compilation Output Files
|
||||
Names follow the source file name, or the stem of the `-o` file when that option is used.
|
||||
- `*.prg` - The final compiled program file for the target system (e.g., Commander X16)
|
||||
- `*.asm` - Generated assembly code from the Prog8 source
|
||||
- `*.list` - Generated full assembly listing file from the Prog8 source
|
||||
- `*.p8ir` - Intermediate representation file, can be executed in the Virtual Machine
|
||||
- `*.vice-mon-list` - Vice emulator monitor list file for debugging
|
||||
|
||||
### Execution Examples
|
||||
- `prog8c -target targetname input.p8` - Compile a Prog8 source file "input.p8" for the given target (cx16, c64, pet32, c128, virtual)
|
||||
- `prog8c -target targetname -emu input.p8` - Compile and execute a prog8 file in the emulator for the given target (cx16, c64, pet32, c128, virtual)
|
||||
- `prog8c -vm input.p8ir` - Execute an existing prog8 program, compiled in IR form, in the Virtual Machine
|
||||
|
||||
### Library Tools
|
||||
- **Library search**: `prog8c -libsearch <regex>` — search for a regex pattern in the embedded stdlib files. Extremely useful to quickly find library routines, variables, or signatures (e.g., `prog8c -libsearch "txt\."` lists all textio routines)
|
||||
- **Library dump**: `prog8c -libdump <dir>` — extract all embedded library source files into a directory for direct inspection
|
||||
|
||||
## Testing and Verification
|
||||
|
||||
- Never assume your changes simply work, always test!
|
||||
- If the project does not have any testing tools, scripts, MCP tools, skills, etc. available for testing, ask the user whether testing should be skipped.
|
||||
- in prog8 code being tested, use '@shared' variables to avoid const-folding
|
||||
|
||||
### Automated Tests (gradle)
|
||||
Always use the `--console=plain` flag with these commands to avoid messing up the agent's text interface.
|
||||
- `ionice -c 3 nice gradle build --console=plain` - Full build including all tests (about 50s)
|
||||
- `ionice -c 3 nice gradle :compiler:test --console=plain` - Run only compiler tests (faster)
|
||||
- `ionice -c 3 nice gradle :compiler:test --tests "prog8tests.compiler.TestOptimization" --console=plain` - Run specific test class
|
||||
|
||||
Note: By default, Gradle only shows failed tests. Passed and skipped tests are silent.
|
||||
|
||||
On Unix-like systems, prefix **every** gradle invocation with `nice` to lower its CPU priority,
|
||||
because gradle is CPU-heavy. This applies to all gradle commands (build, compile, test, install), not
|
||||
just `gradle build`. Use the same prefix for the quick compile checks and install commands in
|
||||
"Building and Installing the Compiler" above.
|
||||
|
||||
On **Linux** additionally put `ionice -c 3` in front (idle-class I/O scheduling), so the build doesn't
|
||||
compete for disk with whatever else is running: `ionice -c 3 nice gradle ...`. `ionice` is
|
||||
Linux-only (util-linux); on macOS/BSD just use `nice`.
|
||||
|
||||
**⚠️ CRITICAL: Test Filtering Patterns - Read This First!**
|
||||
|
||||
Gradle's `--tests` filter has **strict rules** that are easy to get wrong:
|
||||
|
||||
| Pattern | Example | Works? |
|
||||
|---------|---------|---------------|
|
||||
| **Full class name** | `--tests "prog8tests.compiler.TestOptimization"` | **USE THIS** |
|
||||
| Wildcard at END | `--tests "prog8tests.compiler.Test*"` | Yes |
|
||||
| Wildcard on package | `--tests "prog8tests.compiler.*"` | Yes |
|
||||
| **Wildcard at START** | `--tests "*TestOptimization"` | FAILS |
|
||||
| Test description | `--tests "*Optimization*inline*"` | FAILS |
|
||||
|
||||
**Why the restrictions?** Gradle's `--tests` filter matches **fully qualified class names only**. Wildcards work as **suffixes** (e.g., `Test*`) but NOT as prefixes (e.g., `*Test`). KoTest test names like `"inline multi-value void"` are _descriptions_, not method names, so you cannot filter by them.
|
||||
|
||||
**To find failing tests after a run:**
|
||||
```bash
|
||||
# Check XML results (reliable)
|
||||
grep "<failure" compiler/build/test-results/test/*.xml
|
||||
|
||||
# Or check HTML report (most reliable)
|
||||
cat compiler/build/reports/tests/test/index.html
|
||||
|
||||
# For test compilation errors
|
||||
gradle :compiler:compileTestKotlin --info 2>&1 | grep "^e:"
|
||||
```
|
||||
|
||||
**Additional notes:**
|
||||
- Unit tests use KoTest (FunSpec style)
|
||||
- Tests are in the "compiler" module's `test` directory (and some other modules)
|
||||
- Test config is in root `build.gradle.kts`; tests run in parallel
|
||||
- **When writing test programs**, add at the top: `%zeropage basicsafe` and `%option no_sysinit` - the latter ONLY when NOT writing for the amiga500 or qemu68k targets
|
||||
- When a test fails, the output shows "There were failing tests. See the report at:" - **read that HTML report**
|
||||
|
||||
### TestCompilerOnExamples expected sizes
|
||||
`TestCompilerOnExamples` asserts exact output sizes per example. A code-generator change shifts them,
|
||||
so expect a handful of `expected:<N> but was:<M>` failures with no stack trace pointing at the cause.
|
||||
Attribute each one before editing: rebuild with the change reverted and compare the two generated
|
||||
`.asm` files, so the size delta is demonstrably yours and not a side effect. Then update the expected
|
||||
size. A delta of a few bytes from removed `ldy`/`lda` instructions is normal; a large or erratic delta
|
||||
means something else changed and needs investigating.
|
||||
|
||||
### Compiler Unit Test Snippets
|
||||
Prog8 code snippets embedded in the compiler's own Kotlin unit tests (e.g., in `TestAstChecks.kt`, `TestOptimization.kt`) follow different rules from standalone programs:
|
||||
- **Keep self-contained** — avoid `%import` directives (test setup may not have library search path configured)
|
||||
- **Use `cx16` target** (not `virtual`, unless specifically testing the VM)
|
||||
- **Set `writeAssembly=false`** if the test only checks generated AST (much faster)
|
||||
- **Set `optimize=false`** by default for these snippets
|
||||
- **Do NOT use** the `%encoding iso` / `txt.iso()` / `sys.poweroff_system()` pattern — that's only for real CX16 emulator runs
|
||||
- **Do NOT** write compiler tests that depend on the `vasm` assembler: it is not always installed
|
||||
(CI only installs `64tass`). For m68k codegen tests (`amiga500`, `qemu68k` targets) pass
|
||||
`writeAssembly = true, assemble = false` to the `compileText`/`compileFile` helpers. This still
|
||||
generates the codegen AST to assert on but skips assembling, so no vasm is needed. If a test's
|
||||
assertions cannot be made from the AST alone, drop the test.
|
||||
|
||||
### Testing Assembly Output
|
||||
When testing generated assembly in Kotlin tests:
|
||||
- **NEVER dump raw assembly in string assertions** (e.g., `asm shouldContain "jsr label"`). On failure, this dumps the ENTIRE assembly listing into test logs.
|
||||
- **Instead, parse into lines and check specific properties:**
|
||||
```kotlin
|
||||
val lines = asm.lines().map { it.trim() }
|
||||
lines.any { it == "lda #42" } shouldBe true
|
||||
lines.count { it.startsWith("jsr") } shouldBe 0
|
||||
```
|
||||
- Prefer `lines.any { ... }` for existence checks and `lines.count { ... } shouldBe 0` for absence checks.
|
||||
|
||||
### Manual Verification & Emulators
|
||||
|
||||
**Testing tip**: When writing and testing Prog8 programs, **use the `virtual` target** (e.g., `prog8c -target virtual -emu input.p8` or `prog8c -vm input.p8ir`). This is the preferred way to test because the virtual target can easily write output to stdout, making it simple to verify program behavior and check results.
|
||||
|
||||
For the amiga targets, `-emu` launches FS-UAE (GUI) while `-emu2` launches Vamos (headless CLI). For automated/non-interactive testing, prefer `-emu2` (vamos) to avoid opening a GUI window.
|
||||
|
||||
**CX16 output verification**: Use `x16emu -echo iso -run -prg input.prg` to echo screen output to stdout **and auto-start the program**. The `-run` flag is **critical**: without it, the program loads but doesn't execute, so you'll see no output. Pipe through `strings` or `grep` to filter: `x16emu -echo iso -run -prg input.prg 2>&1 | grep -E "(PASS|FAIL)"`.
|
||||
**IMPORTANT**: Always add `%encoding iso` at the top of your source file and call `txt.iso()` in `start()`. This prevents PETSCII→ISO charset translation errors that garble uppercase/special characters and make output unreadable:
|
||||
```prog8
|
||||
%encoding iso
|
||||
%import textio
|
||||
main { sub start() { txt.iso(); txt.print("PASS\n") } }
|
||||
```
|
||||
**IMPORTANT: Always use `sys.poweroff_system()` to exit the CX16 emulator cleanly!** Add `sys.poweroff_system()` at the end of your main program block - this exits x16emu automatically in most cases.
|
||||
**Note:** The `sys` module is always available, there is no need to import it ever.
|
||||
|
||||
**Commodore 64 (x64sc)**: `x64sc input.prg` - run an existing compiled program in the Commodore-64 emulator. Ignore any errors and warnings, because the emulator doesn't produce any output on STDOUT.
|
||||
|
||||
### actual 6502 CPU simulation tests
|
||||
For high-fidelity functional verification of generated 6502 code without a full emulator, you can use the `ksim65` simulator in your unit tests. This allows executing the compiled machine code and asserting on memory or register states.
|
||||
- Use the `simulate()` extension function on a `CompilationResult`.
|
||||
- See `prog8tests.codegeneration.TestExecution6502` for examples.
|
||||
- Both 6502 and 65C02 CPUs can be simulated.
|
||||
- The simulator supports capturing serial output and handling hardware reset/poweroff signals.
|
||||
- There are various assertion helper methods to test on the state of CPU registers, memory contents, etc.
|
||||
- Using the simulator is much faster and way more controllable than using a full emulator to run the code.
|
||||
- The simulator DOES NOT represent an actual real world machine and has VERY LIMITED hardware devices so it can generally not be used to run full example prorams on.
|
||||
|
||||
## Git Operations for File Moves/Deletes
|
||||
|
||||
**When renaming or moving git-tracked files, ALWAYS use `git mv`:**
|
||||
```bash
|
||||
# CORRECT - preserves git history
|
||||
git mv old/path/File.kt new/path/File.kt
|
||||
|
||||
# WRONG - git sees this as delete + add (loses history)
|
||||
mv old/path/File.kt new/path/File.kt
|
||||
```
|
||||
|
||||
**When deleting git-tracked files, ALWAYS use `git rm`:**
|
||||
```bash
|
||||
# CORRECT - properly stages the deletion
|
||||
git rm path/to/File.kt
|
||||
|
||||
# WRONG - git sees this as unstaged deletion
|
||||
rm path/to/File.kt
|
||||
```
|
||||
|
||||
**Why this matters:** `git mv` and `git rm` properly stage the changes and preserve file history. Plain `mv`/`rm` requires git to detect renames heuristically, which may not always work correctly.
|
||||
|
||||
## TODO Items
|
||||
The file `docs/source/todo.rst` contains a comprehensive list of things that still have to be fixed, implemented, or optimized. **Use this to understand what features are NOT yet available** in the compiler or Prog8 language: if a user asks for something that's on the TODO list, you'll know it's not implemented yet and can explain the limitation.
|
||||
@@ -1,20 +0,0 @@
|
||||
# super simple Makefile to launch the main gradle targets to build and/or test the prog8 compiler
|
||||
|
||||
.PHONY: all test
|
||||
|
||||
# Use system gradle if available, otherwise fall back to gradlew wrapper
|
||||
# Windows uses 'where' and gradlew.bat, Unix uses 'command -v' and ./gradlew
|
||||
ifeq ($(OS),Windows_NT)
|
||||
GRADLE = $(shell where gradle >nul 2>&1 && echo gradle || echo gradlew.bat)
|
||||
else
|
||||
GRADLE = $(shell command -v gradle >/dev/null 2>&1 && echo gradle || echo ./gradlew)
|
||||
endif
|
||||
|
||||
all:
|
||||
$(GRADLE) installdist installshadowdist
|
||||
@echo "compiler launch script can be found here: compiler/build/install/prog8c-shadow/bin/prog8c"
|
||||
|
||||
test:
|
||||
$(GRADLE) build
|
||||
@echo "compiler launch script can be found here: compiler/build/install/prog8c-shadow/bin/prog8c"
|
||||
|
||||
@@ -1,55 +1,19 @@
|
||||
[](https://ko-fi.com/H2H6S0FFF)
|
||||
|
||||
PayPal: [](https://paypal.me/irmendejong)
|
||||
|
||||
[](https://prog8.readthedocs.io/)
|
||||
|
||||
Prog8 - Structured Programming Language for 6502/65c02 (8-bit) and 68000 (32-bit) microprocessors
|
||||
=================================================================================================
|
||||
Prog8 - Structured Programming Language for 8-bit 6502/65c02 microprocessors
|
||||
============================================================================
|
||||
|
||||
*Written by Irmen de Jong (irmen@razorvine.net)*
|
||||
|
||||
This is a structured programming language for the 8-bit 6502/6510/65c02 microprocessor from the late 1970's and 1980's
|
||||
as used in many home computers from that era. It also supports the 32-bit Motorola 68000 CPU, for which the
|
||||
Commodore Amiga is the intended machine. It is a medium to low level programming language,
|
||||
as used in many home computers from that era. It is a medium to low level programming language,
|
||||
which aims to provide many conveniences over raw assembly code (even when using a macro assembler).
|
||||
|
||||
**Want to buy me a coffee or a pizza perhaps?**
|
||||
|
||||
This project was created over the last couple of years by dedicating a huge amount of my free time to it, to make it the best I possibly can.
|
||||
If you like Prog8, and think it's worth a nice cup of hot coffee or a delicious pizza,
|
||||
you can help me out a little bit over at [ko-fi.com/irmen](https://ko-fi.com/irmen) or [PayPal](https://paypal.me/irmendejong)
|
||||
|
||||
[](https://jb.gg/OpenSource)
|
||||
I am using JetBrains products to develop the Prog8 compiler.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
Full documentation (syntax reference, how to use the language and the compiler, etc.) can be found at:
|
||||
https://prog8.readthedocs.io/
|
||||
|
||||
How to get it/build it
|
||||
----------------------
|
||||
|
||||
- Download the latest [official release](https://github.com/irmen/prog8/releases) from github.
|
||||
- Or, if you want/need a bleeding edge development version, you can:
|
||||
- download a build artifact zipfile from a recent [github action build](https://github.com/irmen/prog8/actions).
|
||||
- you can also compile it yourself from source. [Instructions here](https://prog8.readthedocs.io/en/latest/compiling.html).
|
||||
Note that if you are not using *gradle* to build it, you might have to perform some manual
|
||||
tasks once to make it compile fully. These are explained in the linked instructions.
|
||||
- Alternatively, you can also install the compiler as a package on some linux distros (which will take care of dependencies automatically):
|
||||
- Arch (via AUR): [`prog8`](https://aur.archlinux.org/packages/prog8)
|
||||
- Finally there's a Homebrew recipe for Mac OS (but also for Linux, and WSL2 on Windows, this also takes care of dependencies automatically):
|
||||
``brew install prog8``
|
||||
|
||||
|
||||
Community
|
||||
---------
|
||||
Most of the development on Prog8 and the use of it is currently centered around
|
||||
the [Commander X16](https://www.commanderx16.com/) retro computer. Their [discord server](https://discord.gg/nS2PqEC) contains a small channel
|
||||
dedicated to Prog8. Other than that, use the issue tracker on github.
|
||||
|
||||
|
||||
Software license
|
||||
----------------
|
||||
GNU GPL 3.0 (see file LICENSE), with exception for generated code:
|
||||
@@ -62,65 +26,49 @@ GNU GPL 3.0 (see file LICENSE), with exception for generated code:
|
||||
What does Prog8 provide?
|
||||
------------------------
|
||||
|
||||
- all advantages of a higher level language over having to write assembly code manually
|
||||
- programs run very fast because it's compiled to native machine code
|
||||
- compiled code is very small; much smaller than equivalent C code compiled with CC65, and usually runs faster as well
|
||||
- modularity, symbol scoping, subroutines. No need for forward declarations.
|
||||
- Can make vars and routines private, as a simple way to restrict access and hide implementation details
|
||||
- various data types other than just bytes (16-bit words, long integers, floats, strings)
|
||||
- Structs and typed pointers
|
||||
- 2D arrays (`matrix[row][col]` syntax)
|
||||
- floating point math is supported on certain targets
|
||||
- access to most Kernal ROM routines as external subroutine definitions you can call normally
|
||||
- tight control over Zeropage usage
|
||||
- programs can be run multiple times without reloading because of automatic variable (re)initializations.
|
||||
- programs can be configured to execute in ROM
|
||||
- reduction of source code length over raw assembly
|
||||
- fast execution speed due to compilation to native assembly code. It's possible to write certain raster interrupt 'demoscene' effects purely in Prog8.
|
||||
- modularity, symbol scoping, subroutines
|
||||
- various data types other than just bytes (16-bit words, floats, strings)
|
||||
- floating point math is supported if the target system provides floating point library routines (C64 and Cx16 both do)
|
||||
- strings can contain escaped characters but also many symbols directly if they have a petscii equivalent, such as "♠♥♣♦π▚●○╳". Characters like ^, _, \, {, } and | are also accepted and converted to the closest petscii equivalents.
|
||||
- automatic static variable allocations, automatic string and array variables and string sharing
|
||||
- subroutines with input parameters and result values
|
||||
- high-level program optimizations
|
||||
- conditional branches that map 1:1 to cpu status flags
|
||||
- small program boilerplate/compilersupport overhead
|
||||
- programs can be run multiple times without reloading because of automatic variable (re)initializations.
|
||||
- conditional branches
|
||||
- ``when`` statement to provide a concise jump table alternative to if/elseif chains
|
||||
- ``on .. goto`` statement for fast jump tables
|
||||
- ``in`` expression for concise and efficient multi-value/containment check
|
||||
- ``defer`` statement to help write concise and robust subroutine cleanup logic
|
||||
- several specialized built-in functions such as ``lsb``, ``msb``, ``min``, ``max``, ``rol``, ``ror``
|
||||
- many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``sort`` and ``reverse``
|
||||
- various powerful built-in libraries to do I/O, number conversions, graphics and more
|
||||
- subroutines can return more than one result value
|
||||
- convenience abstractions for low level aspects such as ZeroPage handling, program startup, explicit memory addresses
|
||||
- inline assembly allows you to have full control when every cycle or byte matters
|
||||
- supports the sixteen 'virtual' 16-bit registers R0 - R15 from the Commander X16 (also available on other targets)
|
||||
- encode strings and characters into petscii or screencodes or even other encodings
|
||||
- Automatic ROM/RAM bank switching on certain compiler targets when calling routines in other banks
|
||||
- 50 Kb of available program RAM size on the C64 by default (41 Kb on the C128) because Basic ROM is banked out by default
|
||||
- supports the sixteen 'virtual' 16-bit registers R0 - R15 from the Commander X16, and provides them also on the C64.
|
||||
- encode strings and characters into petscii or screencodes as desired (C64/Cx16)
|
||||
|
||||
*Rapid edit-compile-run-debug cycle:*
|
||||
|
||||
- use a modern PC to do the work on, use nice editors and enjoy quick compilation times
|
||||
- can automatically run the program in the Vice emulator after successful compilation
|
||||
- can automatically run the program in the Vice emulator after succesful compilation
|
||||
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them
|
||||
- source code labels automatically loaded in Vice emulator so it can show them in disassembly
|
||||
|
||||
*Multiple supported compiler targets* (contributions to improve these or to add support for other machines are welcome!):
|
||||
|
||||
- "cx16": [CommanderX16](https://www.commanderx16.com) (65c02 CPU)
|
||||
- "c64": Commodore-64 (6502 like CPU)
|
||||
- "c128": Commodore-128 (6502 like CPU - the Z80 cpu mode is not supported)
|
||||
- "pet32": Commodore PET (limited support)
|
||||
- "amiga500": Commodore Amiga 500 (16/32-bit 68000 CPU; experimental)
|
||||
- "amiga1200": Commodore Amiga 1200 (32-bit 68020 CPU with optional FPU; experimental)
|
||||
- "qemu68k": Qemu M68k simulator (experimental)
|
||||
- via external configurable targets: Atari 800 XL, Neo6502, NES, C64 OS, Foenix F256, ...
|
||||
- "cx16": [CommanderX16](https://www.commanderx16.com) (65c02 CPU)
|
||||
- If you only use standard kernal and prog8 library routines, it is possible to compile the *exact same program* for different machines (just change the compiler target flag)
|
||||
|
||||
The m68k targets (amiga500, amiga1200, qemu68k) and the optional `-newcodegen` 6502 code generator are experimental and not yet suitable for production code.
|
||||
|
||||
|
||||
|
||||
Additional required tools
|
||||
-------------------------
|
||||
|
||||
[64tass](https://sourceforge.net/projects/tass64/) - cross assembler. Install this on your shell path.
|
||||
If you're on Windows, download a recent .exe from the official repository's file section <https://sourceforge.net/projects/tass64/files/binaries/>.
|
||||
For other systems (Mac, Linux) it is very easy to get it via your package manager or to compile it yourself.
|
||||
A recent .exe version of this tool for Windows can be obtained from my [clone](https://github.com/irmen/64tass/releases) of this project.
|
||||
For other platforms it is very easy to compile it yourself (make ; make install).
|
||||
|
||||
A **Java runtime (jre or jdk), version 11 or newer** is required to run a prepackaged version of the compiler.
|
||||
If you want to build it from source, you'll need a Java SDK + Kotlin 1.3.x SDK (or for instance,
|
||||
@@ -143,13 +91,14 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
||||
|
||||
%import textio
|
||||
%zeropage basicsafe
|
||||
|
||||
|
||||
main {
|
||||
bool[256] sieve
|
||||
|
||||
ubyte[256] sieve
|
||||
ubyte candidate_prime = 2 ; is increased in the loop
|
||||
|
||||
sub start() {
|
||||
sys.memset(sieve, 256, 0) ; clear the sieve
|
||||
sys.memset(sieve, 256, false) ; clear the sieve
|
||||
txt.print("prime numbers up to 255:\n\n")
|
||||
ubyte amount=0
|
||||
repeat {
|
||||
@@ -185,7 +134,10 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
||||
}
|
||||
}
|
||||
|
||||
when compiled and ran on a C-64 you'll get:
|
||||
|
||||
|
||||
|
||||
when compiled an ran on a C-64 you'll get:
|
||||
|
||||

|
||||
|
||||
@@ -206,19 +158,3 @@ For instance here's a well known space ship animated in 3D with hidden line remo
|
||||
in the CommanderX16 emulator:
|
||||
|
||||

|
||||
|
||||
|
||||
Performance comparison with various C compilers
|
||||
-----------------------------------------------
|
||||
|
||||
Here is a performance comparison with various C compilers for the 6502/C64 so you can get
|
||||
an idea of how Prog8 stacks up.
|
||||
|
||||
[comparison](benchmark-c/benchmark.md)
|
||||
|
||||
|
||||
Agent context instructions
|
||||
--------------------------
|
||||
|
||||
[Agent context instructions](AGENTS.md)
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
# C-Bench-64 comparison
|
||||
|
||||
Several benchmarks of https://thred.github.io/c-bench-64/
|
||||
have been ported to equivalent Prog8 code and the benchmark results have been penciled in in the graphs below.
|
||||
Because they use the CIA timer for measuring the duration of the runs, the code only works on a C64.
|
||||
|
||||
Maybe one day I'll try to integrate the prog8 data properly but their benchmark site is comparing C compilers, which Prog8 clearly is not.
|
||||
|
||||
However conclusions so far (note: these are micro benchmarks so interpret the results as you will!)
|
||||
|
||||
* Prog8 program size is consistently one of the smallest.
|
||||
* Prog8 execution speed places it more or less in the middle of the stack.
|
||||
|
||||
Measured with Prog8 V12.0 .
|
||||
Benchmarks ran on a PAL C64 emulated through VICE.
|
||||
|
||||
textual results:
|
||||
|
||||
| benchmark | size | time |
|
||||
|-----------|------|----------|
|
||||
| crc8 | 1568 | 2.0 sec |
|
||||
| crc16 | 1608 | 2.5 sec |
|
||||
| crc32 | 1812 | 4.2 sec |
|
||||
| pow | 2152 | 11.7 sec |
|
||||
| sieve | 1772 | 22.1 sec |
|
||||
| sieve_bit | 1918 | 40.1 sec |
|
||||
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
@@ -1,98 +0,0 @@
|
||||
%import textio
|
||||
|
||||
cia {
|
||||
ubyte freq
|
||||
const ubyte CNT = 200
|
||||
|
||||
sub calibrate() {
|
||||
txt.print("calibrating frequency: ")
|
||||
tod_init(0)
|
||||
txt.print_ub(tod_freq())
|
||||
txt.print(" hz\n")
|
||||
tod_reset()
|
||||
}
|
||||
|
||||
sub print_time() {
|
||||
uword t = tod_get10()
|
||||
txt.print("(cia) time: ")
|
||||
txt.print_uw(t / 10)
|
||||
txt.chrout('.')
|
||||
txt.print_uw(t % 10)
|
||||
txt.print(" sec.\n")
|
||||
}
|
||||
|
||||
sub tod_reset() {
|
||||
; set the tod to 0
|
||||
c64.CIA2TODHR = 0
|
||||
c64.CIA2TODMIN = 0
|
||||
c64.CIA2TODSEC = 0
|
||||
c64.CIA2TOD10 = 0
|
||||
}
|
||||
|
||||
sub tod_get10() -> uword {
|
||||
ubyte h, m, s, t
|
||||
uword time
|
||||
h = c64.CIA2TODHR
|
||||
m = c64.CIA2TODMIN
|
||||
s = c64.CIA2TODSEC
|
||||
t = c64.CIA2TOD10
|
||||
time = t
|
||||
time += (s & $0f) * (10 as uword)
|
||||
time += (s >> 4) * (100 as uword)
|
||||
time += (m & $0f) * 600
|
||||
time += (m >> 4) * 6000
|
||||
return time
|
||||
}
|
||||
|
||||
sub tod_init(ubyte f) {
|
||||
if (f == 0)
|
||||
freq = tod_detect_freq()
|
||||
else
|
||||
freq = f
|
||||
|
||||
if (freq == 50)
|
||||
c64.CIA2CRA |= $80
|
||||
else
|
||||
c64.CIA2CRA &= $7f
|
||||
}
|
||||
|
||||
sub tod_freq() -> ubyte {
|
||||
return freq
|
||||
}
|
||||
|
||||
sub tod_detect_freq() -> ubyte {
|
||||
uword cbl
|
||||
|
||||
c64.CIA2CRB = $40 ; stop timer
|
||||
c64.CIA2CRA = $80 ; stop timer
|
||||
|
||||
; set ta to overflow every 10000 count (~= 10ms)
|
||||
c64.CIA2TAL = $10
|
||||
c64.CIA2TAH = $27
|
||||
c64.CIA2TBL = CNT
|
||||
c64.CIA2TBH = 0
|
||||
|
||||
tod_reset()
|
||||
|
||||
c64.CIA2CRB = $41 ; input from tim1 overflow, continuous, start timer
|
||||
c64.CIA2CRA = $81 ; start timer, continuous tod 50HZ
|
||||
|
||||
|
||||
while c64.CIA2TODSEC == 0 {
|
||||
; wait for tod to count 1s
|
||||
}
|
||||
|
||||
; cal=CIA2.ta_lo;
|
||||
; cah=CIA2.ta_hi;
|
||||
cbl = c64.CIA2TBL
|
||||
; cbh=CIA2.tb_hi;
|
||||
|
||||
; printf("count2 = %d %d %d %d\n",cah, cal, cbh, cbl);
|
||||
; printf("elapsed ~= %d0ms\n",CNT-cbl);
|
||||
|
||||
if CNT - cbl > 90
|
||||
return 50
|
||||
else
|
||||
return 60
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
%import textio
|
||||
%import ciatimer
|
||||
|
||||
; note: Prog8 has a complete CRC16 routine in its library: math.crc16
|
||||
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
txt.lowercase()
|
||||
cia.calibrate()
|
||||
test.benchmark_name()
|
||||
test.benchmark()
|
||||
void test.benchmark_check()
|
||||
cia.print_time()
|
||||
repeat {}
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
|
||||
const uword EXPECTED = $ffd0
|
||||
uword crc_result
|
||||
|
||||
sub benchmark_name()
|
||||
{
|
||||
txt.print("crc16.p8\n")
|
||||
txt.print("Calculates the CRC16 of the C64 Kernal\n")
|
||||
}
|
||||
|
||||
sub benchmark()
|
||||
{
|
||||
crc_result = CRC16($e000, $2000)
|
||||
}
|
||||
|
||||
sub benchmark_check() -> bool
|
||||
{
|
||||
txt.print("CRC=")
|
||||
txt.print_uwhex(crc_result, true)
|
||||
|
||||
if crc_result == EXPECTED
|
||||
{
|
||||
txt.print(" [OK]\n")
|
||||
return false
|
||||
}
|
||||
|
||||
txt.print(" [FAIL] - expected ")
|
||||
txt.print_uwhex(EXPECTED, true)
|
||||
txt.nl()
|
||||
return true
|
||||
}
|
||||
|
||||
sub CRC16(^^ubyte data, uword length) -> uword {
|
||||
; CRC-16/XMODEM
|
||||
uword crc
|
||||
|
||||
repeat length
|
||||
{
|
||||
crc ^= mkword(@(data),0) ; currently there's no easy way to affect only the MSB of the variable
|
||||
|
||||
repeat 8
|
||||
{
|
||||
crc <<=1
|
||||
if_cs
|
||||
crc ^= $1021
|
||||
}
|
||||
data++
|
||||
}
|
||||
return crc
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
%import textio
|
||||
%import ciatimer
|
||||
|
||||
; note: Prog8 has a complete CRC32 routine in its library: math.crc32
|
||||
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
txt.lowercase()
|
||||
cia.calibrate()
|
||||
test.benchmark_name()
|
||||
test.benchmark()
|
||||
void test.benchmark_check()
|
||||
cia.print_time()
|
||||
repeat {}
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
|
||||
const long EXPECTED = $e1fa84c6
|
||||
long crc_result
|
||||
|
||||
sub benchmark_name()
|
||||
{
|
||||
txt.print("crc32.p8\n")
|
||||
txt.print("Calculates the CRC32 of the C64 Kernal\n")
|
||||
}
|
||||
|
||||
sub benchmark()
|
||||
{
|
||||
crc_result = CRC32($e000, $2000)
|
||||
}
|
||||
|
||||
sub benchmark_check() -> bool
|
||||
{
|
||||
txt.print("CRC=")
|
||||
txt.print_ulhex(crc_result, true)
|
||||
|
||||
if crc_result == EXPECTED
|
||||
{
|
||||
txt.print(" [OK]\n")
|
||||
return false
|
||||
}
|
||||
|
||||
txt.print(" [FAIL] - expected ")
|
||||
txt.print_ulhex(EXPECTED, true)
|
||||
txt.nl()
|
||||
return true
|
||||
}
|
||||
|
||||
sub CRC32(^^ubyte data, uword length) -> long {
|
||||
; CRC-32/CKSUM
|
||||
long crc
|
||||
|
||||
repeat length
|
||||
{
|
||||
crc ^= (@(data) as long)<<24 ; currently there's no easy way to affect only the MSB of the variable
|
||||
|
||||
repeat 8
|
||||
{
|
||||
crc <<=1
|
||||
if_cs
|
||||
crc ^= $04c11db7
|
||||
}
|
||||
data++
|
||||
}
|
||||
crc ^= $ffffffff
|
||||
return crc
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
%import textio
|
||||
%import ciatimer
|
||||
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
txt.lowercase()
|
||||
cia.calibrate()
|
||||
test.benchmark_name()
|
||||
test.benchmark()
|
||||
void test.benchmark_check()
|
||||
cia.print_time()
|
||||
repeat {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
test {
|
||||
|
||||
sub benchmark_name()
|
||||
{
|
||||
txt.print("crc8.p8\n")
|
||||
txt.print("Calculates the CRC8 of the C64 Kernal\n")
|
||||
}
|
||||
|
||||
sub benchmark()
|
||||
{
|
||||
crc_result = CRC8($e000, $2000)
|
||||
}
|
||||
|
||||
sub benchmark_check() -> bool
|
||||
{
|
||||
txt.print("CRC=")
|
||||
txt.print_ubhex(crc_result, true)
|
||||
|
||||
if crc_result == EXPECTED
|
||||
{
|
||||
txt.print(" [OK]\n")
|
||||
return false
|
||||
}
|
||||
|
||||
txt.print(" [FAIL] - expected ")
|
||||
txt.print_ubhex(EXPECTED, true)
|
||||
txt.nl()
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
const ubyte EXPECTED = $a2
|
||||
ubyte crc_result
|
||||
|
||||
sub CRC8(^^ubyte data, uword length) -> ubyte
|
||||
{
|
||||
; CRC-8/GSM-A
|
||||
ubyte crc
|
||||
|
||||
repeat length
|
||||
{
|
||||
crc ^= @(data)
|
||||
|
||||
repeat 8
|
||||
{
|
||||
if (crc & $80) != 0
|
||||
crc = (crc << 1) ^ $1d
|
||||
else
|
||||
crc <<= 1
|
||||
}
|
||||
data++
|
||||
}
|
||||
return crc
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
%import textio
|
||||
%import floats
|
||||
%import ciatimer
|
||||
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
txt.lowercase()
|
||||
cia.calibrate()
|
||||
test.benchmark_name()
|
||||
test.benchmark()
|
||||
void test.benchmark_check()
|
||||
cia.print_time()
|
||||
repeat {}
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
|
||||
const ubyte N_ITER = 10
|
||||
const ubyte SIZE = 32
|
||||
float[SIZE] array
|
||||
|
||||
const float expected = 3614007361536.000000
|
||||
const float epsilon = 10000000
|
||||
float res
|
||||
|
||||
sub benchmark_name()
|
||||
{
|
||||
txt.print("pow.p8\n")
|
||||
txt.print("Calculates floating point exponential (")
|
||||
txt.print_uw(N_ITER)
|
||||
txt.print(" iterations)\n")
|
||||
}
|
||||
|
||||
sub benchmark()
|
||||
{
|
||||
ubyte i,j
|
||||
res = 0
|
||||
|
||||
for j in 0 to SIZE-1 {
|
||||
array[j]=0
|
||||
}
|
||||
|
||||
for i in 0 to N_ITER-1 {
|
||||
for j in 0 to SIZE-1 {
|
||||
array[j] += testpow(2.5 / ((i + 1) as float), j)
|
||||
}
|
||||
}
|
||||
|
||||
for j in 0 to SIZE-1 {
|
||||
res += array[j]
|
||||
}
|
||||
}
|
||||
|
||||
sub testpow(float x, ubyte y) -> float
|
||||
{
|
||||
float tmp = x
|
||||
|
||||
if y == 0
|
||||
return 1
|
||||
|
||||
repeat y-1
|
||||
tmp *= x
|
||||
|
||||
return tmp
|
||||
}
|
||||
|
||||
|
||||
sub benchmark_check() -> bool
|
||||
{
|
||||
txt.print("res = ")
|
||||
txt.print_f(res)
|
||||
float diff = expected - res;
|
||||
txt.print("\nexpected = ")
|
||||
txt.print_f(expected)
|
||||
txt.print("\nepsilon = ")
|
||||
txt.print_f(epsilon)
|
||||
txt.print("\ndiff = ")
|
||||
txt.print_f(diff)
|
||||
|
||||
if (diff < epsilon and diff > -epsilon)
|
||||
{
|
||||
txt.print(" [OK]\n")
|
||||
return false
|
||||
}
|
||||
|
||||
txt.print("[FAIL]\n")
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 62 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 63 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 62 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 58 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 68 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 65 KiB |
@@ -1,85 +0,0 @@
|
||||
%import textio
|
||||
%import ciatimer
|
||||
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
txt.lowercase()
|
||||
cia.calibrate()
|
||||
test.benchmark_name()
|
||||
test.benchmark()
|
||||
void test.benchmark_check()
|
||||
cia.print_time()
|
||||
repeat {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
test {
|
||||
const ubyte N_ITER = 10
|
||||
const uword SIZE = 8191
|
||||
const uword EXPECTED = 1900
|
||||
uword prime_count
|
||||
^^bool @zp flags = memory("flags", SIZE, 0)
|
||||
|
||||
sub benchmark_name()
|
||||
{
|
||||
txt.print("sieve.c\n")
|
||||
txt.print("Calculates the primes from 1 to ")
|
||||
txt.print_uw(SIZE * 2 + 2)
|
||||
txt.print(" (")
|
||||
txt.print_ub(N_ITER)
|
||||
txt.print(" iterations)\n")
|
||||
}
|
||||
|
||||
sub benchmark()
|
||||
{
|
||||
repeat N_ITER
|
||||
prime_count = sieve(SIZE)
|
||||
}
|
||||
|
||||
sub benchmark_check() -> bool
|
||||
{
|
||||
txt.print("count=")
|
||||
txt.print_uw(prime_count)
|
||||
|
||||
if prime_count == EXPECTED
|
||||
{
|
||||
txt.print(" [OK]\n")
|
||||
return false
|
||||
}
|
||||
|
||||
txt.print(" [FAIL] - expected ")
|
||||
txt.print_uw(EXPECTED)
|
||||
txt.nl()
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
sub sieve(uword size) -> uword
|
||||
{
|
||||
uword i, prime, k
|
||||
uword count = 1
|
||||
|
||||
for i in 0 to size-1
|
||||
flags[i] = true
|
||||
|
||||
for i in 0 to size-1
|
||||
{
|
||||
if flags[i]
|
||||
{
|
||||
prime = i + i + 3
|
||||
k = i + prime
|
||||
while k < size
|
||||
{
|
||||
flags[k] = false
|
||||
k += prime
|
||||
}
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
%import textio
|
||||
%import ciatimer
|
||||
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
txt.lowercase()
|
||||
cia.calibrate()
|
||||
test.benchmark_name()
|
||||
test.benchmark()
|
||||
void test.benchmark_check()
|
||||
cia.print_time()
|
||||
repeat {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
test {
|
||||
const ubyte N_ITER = 4
|
||||
const uword SIZE = 16000
|
||||
const uword EXPECTED = 3432
|
||||
uword prime_count
|
||||
^^ubyte @zp flags = memory("flags", SIZE/8+1, 0)
|
||||
|
||||
sub benchmark_name()
|
||||
{
|
||||
txt.print("sieve_bit.p8\n")
|
||||
txt.print("Calculates the primes from 1 to ")
|
||||
txt.print_uw(SIZE * 2 + 2)
|
||||
txt.print(" (")
|
||||
txt.print_ub(N_ITER)
|
||||
txt.print(" iterations)\n")
|
||||
}
|
||||
|
||||
sub benchmark()
|
||||
{
|
||||
repeat N_ITER
|
||||
prime_count = sieve(SIZE)
|
||||
}
|
||||
|
||||
sub benchmark_check() -> bool
|
||||
{
|
||||
txt.print("count=")
|
||||
txt.print_uw(prime_count)
|
||||
|
||||
if prime_count == EXPECTED
|
||||
{
|
||||
txt.print(" [OK]\n")
|
||||
return false
|
||||
}
|
||||
|
||||
txt.print(" [FAIL] - expected ")
|
||||
txt.print_uw(EXPECTED)
|
||||
txt.nl()
|
||||
return true
|
||||
}
|
||||
|
||||
ubyte[] bitv = [
|
||||
$01,
|
||||
$02,
|
||||
$04,
|
||||
$08,
|
||||
$10,
|
||||
$20,
|
||||
$40,
|
||||
$80
|
||||
]
|
||||
|
||||
sub check_flag(uword idx) -> bool
|
||||
{
|
||||
return flags[idx / 8] & bitv[lsb(idx) & 7] != 0
|
||||
}
|
||||
|
||||
sub clear_flag(uword idx)
|
||||
{
|
||||
flags[idx / 8] &= ~(bitv[lsb(idx) & 7])
|
||||
}
|
||||
|
||||
|
||||
sub sieve(uword size) -> uword
|
||||
{
|
||||
uword i, prime, k
|
||||
uword count=1
|
||||
|
||||
for i in 0 to (size / 8)
|
||||
flags[i] = $ff
|
||||
|
||||
for i in 0 to size-1
|
||||
{
|
||||
if check_flag(i)
|
||||
{
|
||||
prime = i + i + 3
|
||||
k = i + prime;
|
||||
while k < size
|
||||
{
|
||||
clear_flag(k)
|
||||
k += prime
|
||||
}
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
.PHONY: clean run
|
||||
|
||||
run:
|
||||
prog8c -target cx16 benchmark.p8
|
||||
x16emu -run -prg benchmark.prg -warp
|
||||
|
||||
clean:
|
||||
rm -f *.prg *.PRG *.asm *.vice-* *.BIN *.PAL *.zip *.7z
|
||||
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
%import textio
|
||||
%import math
|
||||
|
||||
rotate3d {
|
||||
const ubyte WIDTH = 40
|
||||
const ubyte HEIGHT = 30
|
||||
|
||||
sub benchmark(uword max_time) -> uword {
|
||||
|
||||
uword anglex
|
||||
uword angley
|
||||
uword anglez
|
||||
uword frames
|
||||
|
||||
txt.nl()
|
||||
cbm.SETTIM(0,0,0)
|
||||
|
||||
while cbm.RDTIM16()<max_time {
|
||||
matrix_math.rotate_vertices(msb(anglex), msb(angley), msb(anglez))
|
||||
draw_edges() ; doesn't really draw anything in the benchmark, but does do the screen calculations
|
||||
anglex+=500
|
||||
angley+=215
|
||||
anglez+=453
|
||||
frames++
|
||||
}
|
||||
|
||||
return frames
|
||||
}
|
||||
|
||||
sub draw_edges() {
|
||||
|
||||
; plot the points of the 3d cube
|
||||
; first the points on the back, then the points on the front (painter algorithm)
|
||||
|
||||
ubyte @zp i
|
||||
word @zp rz
|
||||
word @zp persp
|
||||
byte @shared sx
|
||||
byte @shared sy
|
||||
|
||||
for i in 0 to len(matrix_math.xcoor)-1 {
|
||||
rz = matrix_math.rotatedz[i]
|
||||
if rz >= 10 {
|
||||
persp = 600 + (rz>>5)
|
||||
sx = matrix_math.rotatedx[i] / persp as byte + WIDTH/2
|
||||
sy = matrix_math.rotatedy[i] / persp as byte + HEIGHT/2
|
||||
;; txt.setcc(sx as ubyte, sy as ubyte, 46, 7)
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0 to len(matrix_math.xcoor)-1 {
|
||||
rz = matrix_math.rotatedz[i]
|
||||
if rz < 10 {
|
||||
persp = 600 + (rz>>5)
|
||||
sx = matrix_math.rotatedx[i] / persp as byte + WIDTH/2
|
||||
sy = matrix_math.rotatedy[i] / persp as byte + HEIGHT/2
|
||||
;; txt.setcc(sx as ubyte, sy as ubyte, 81, 7)
|
||||
}
|
||||
}
|
||||
|
||||
txt.chrout('.')
|
||||
}
|
||||
}
|
||||
|
||||
matrix_math {
|
||||
; vertices
|
||||
word[] xcoor = [ -40, -40, -40, -40, 40, 40, 40, 40 ]
|
||||
word[] ycoor = [ -40, -40, 40, 40, -40, -40, 40, 40 ]
|
||||
word[] zcoor = [ -40, 40, -40, 40, -40, 40, -40, 40 ]
|
||||
|
||||
; storage for rotated coordinates
|
||||
word[len(xcoor)] rotatedx
|
||||
word[len(ycoor)] rotatedy
|
||||
word[len(zcoor)] rotatedz
|
||||
|
||||
sub rotate_vertices(ubyte ax, ubyte ay, ubyte az) {
|
||||
; rotate around origin (0,0,0)
|
||||
|
||||
; set up the 3d rotation matrix values
|
||||
word wcosa = math.cos8(ax)
|
||||
word wsina = math.sin8(ax)
|
||||
word wcosb = math.cos8(ay)
|
||||
word wsinb = math.sin8(ay)
|
||||
word wcosc = math.cos8(az)
|
||||
word wsinc = math.sin8(az)
|
||||
|
||||
; instead of (slow) /128, we simply shift 7 bits (and take the roundoff error)
|
||||
word wcosa_sinb = wcosa*wsinb >> 7
|
||||
word wsina_sinb = wsina*wsinb >> 7
|
||||
|
||||
word Axx = wcosa*wcosb >> 7
|
||||
word Axy = (wcosa_sinb*wsinc - wsina*wcosc) >> 7
|
||||
word Axz = (wcosa_sinb*wcosc + wsina*wsinc) >> 7
|
||||
word Ayx = wsina*wcosb >> 7
|
||||
word Ayy = (wsina_sinb*wsinc + wcosa*wcosc) >> 7
|
||||
word Ayz = (wsina_sinb*wcosc - wcosa*wsinc) >> 7
|
||||
word Azx = -wsinb
|
||||
word Azy = wcosb*wsinc >> 7
|
||||
word Azz = wcosb*wcosc >> 7
|
||||
|
||||
ubyte @zp i
|
||||
for i in 0 to len(xcoor)-1 {
|
||||
; don't normalize by dividing by 128, instead keep some precision for perspective calc later
|
||||
rotatedx[i] = Axx*xcoor[i] + Axy*ycoor[i] + Axz*zcoor[i]
|
||||
rotatedy[i] = Ayx*xcoor[i] + Ayy*ycoor[i] + Ayz*zcoor[i]
|
||||
rotatedz[i] = Azx*xcoor[i] + Azy*ycoor[i] + Azz*zcoor[i]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
adpcm {
|
||||
|
||||
sub decode_benchmark(uword max_time) -> uword {
|
||||
uword num_blocks
|
||||
txt.nl()
|
||||
cbm.SETTIM(0,0,0)
|
||||
|
||||
while cbm.RDTIM16()<max_time {
|
||||
adpcm.init(0,0)
|
||||
uword @requirezp nibbles_ptr = $0800 ; for benchmark purposes, the exact nibbles don't really matter, so we just take this benchmark program itself as input
|
||||
repeat 252/2 {
|
||||
unroll 2 {
|
||||
ubyte @zp nibble = @(nibbles_ptr)
|
||||
adpcm.decode_nibble(nibble & 15) ; first word (note: upper nibble needs to be zero!)
|
||||
adpcm.decode_nibble(nibble>>4) ; second word (note: upper nibble is zero, after the shifts.)
|
||||
nibbles_ptr++
|
||||
}
|
||||
}
|
||||
num_blocks++
|
||||
txt.chrout('.')
|
||||
}
|
||||
|
||||
return num_blocks
|
||||
}
|
||||
|
||||
; IMA ADPCM decoder. Supports mono and stereo streams.
|
||||
|
||||
byte[] t_index = [ -1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8]
|
||||
uword[] t_step = [
|
||||
7, 8, 9, 10, 11, 12, 13, 14,
|
||||
16, 17, 19, 21, 23, 25, 28, 31,
|
||||
34, 37, 41, 45, 50, 55, 60, 66,
|
||||
73, 80, 88, 97, 107, 118, 130, 143,
|
||||
157, 173, 190, 209, 230, 253, 279, 307,
|
||||
337, 371, 408, 449, 494, 544, 598, 658,
|
||||
724, 796, 876, 963, 1060, 1166, 1282, 1411,
|
||||
1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024,
|
||||
3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484,
|
||||
7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
|
||||
15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794,
|
||||
32767]
|
||||
|
||||
uword @requirezp predict ; decoded 16 bit pcm sample for first channel.
|
||||
ubyte @requirezp index
|
||||
uword @requirezp pstep
|
||||
|
||||
sub init(uword startPredict, ubyte startIndex) {
|
||||
; initialize first decoding channel.
|
||||
predict = startPredict
|
||||
index = startIndex
|
||||
pstep = t_step[index]
|
||||
}
|
||||
|
||||
sub decode_nibble(ubyte @zp nibble) {
|
||||
; Decoder for a single nibble for the first channel. (value of 'nibble' needs to be strictly 0-15 !)
|
||||
; This is the hotspot of the decoder algorithm!
|
||||
; Note that the generated assembly from this is pretty efficient,
|
||||
; rewriting it by hand in asm seems to improve it only ~10%.
|
||||
cx16.r0s = 0 ; difference
|
||||
if nibble & %0100 !=0
|
||||
cx16.r0s += pstep
|
||||
pstep >>= 1
|
||||
if nibble & %0010 !=0
|
||||
cx16.r0s += pstep
|
||||
pstep >>= 1
|
||||
if nibble & %0001 !=0
|
||||
cx16.r0s += pstep
|
||||
pstep >>= 1
|
||||
cx16.r0s += pstep
|
||||
if nibble & %1000 !=0
|
||||
predict -= cx16.r0
|
||||
else
|
||||
predict += cx16.r0
|
||||
|
||||
; NOTE: the original C/Python code uses a 32 bits prediction value and clips it to a 16 bit word
|
||||
; but for speed reasons we only work with 16 bit words here all the time (with possible clipping error)
|
||||
; if predicted > 32767:
|
||||
; predicted = 32767
|
||||
; elif predicted < -32767:
|
||||
; predicted = - 32767
|
||||
|
||||
index += t_index[nibble] as ubyte
|
||||
if_neg
|
||||
index = 0
|
||||
else if index >= len(t_step)-1
|
||||
index = len(t_step)-1
|
||||
pstep = t_step[index]
|
||||
}
|
||||
}
|
||||
@@ -1,234 +0,0 @@
|
||||
; Binary Search Tree.
|
||||
; It's a simple implementation for test/demonstration purposes of the pointer support;
|
||||
; no balancing is done and memory is not freed when elements are removed.
|
||||
|
||||
%import textio
|
||||
|
||||
btree {
|
||||
|
||||
sub benchmark(uword max_time) -> uword {
|
||||
txt.nl()
|
||||
cbm.SETTIM(0,0,0)
|
||||
uword score
|
||||
while cbm.RDTIM16() < max_time {
|
||||
bench_operations()
|
||||
txt.chrout('.')
|
||||
score++
|
||||
}
|
||||
txt.nl()
|
||||
return score
|
||||
}
|
||||
|
||||
sub bench_operations() {
|
||||
arena.freeall()
|
||||
btree.root = 0
|
||||
|
||||
for cx16.r0 in [321, 719, 194, 550, 187, 203, 520, 562, 221, 676, 97, 852, 273, 326, 589, 606, 275, 794, 63, 716]
|
||||
btree.add(cx16.r0)
|
||||
|
||||
cx16.r0L = btree.size()
|
||||
btree.process_tree_inorder()
|
||||
btree.process_tree_preorder()
|
||||
|
||||
void btree.contains(203)
|
||||
void btree.contains(204)
|
||||
void btree.contains(605)
|
||||
void btree.contains(606)
|
||||
|
||||
btree.remove(9999)
|
||||
btree.remove(97)
|
||||
btree.remove(187)
|
||||
btree.remove(203)
|
||||
btree.remove(275)
|
||||
btree.remove(321)
|
||||
btree.remove(520)
|
||||
btree.remove(562)
|
||||
btree.remove(606)
|
||||
btree.remove(719)
|
||||
btree.remove(794)
|
||||
|
||||
cx16.r0L = btree.size()
|
||||
btree.process_tree_inorder()
|
||||
btree.process_tree_preorder()
|
||||
}
|
||||
|
||||
struct Node {
|
||||
^^Node left
|
||||
^^Node right
|
||||
uword value
|
||||
}
|
||||
|
||||
^^Node root = 0
|
||||
|
||||
sub add(uword value) {
|
||||
^^Node node = arena.alloc(sizeof(Node))
|
||||
node.value = value
|
||||
node.left = node.right = 0
|
||||
|
||||
if root==0
|
||||
root=node
|
||||
else {
|
||||
^^Node parent = root
|
||||
repeat {
|
||||
if parent.value >= value {
|
||||
if parent.left!=0
|
||||
parent = parent.left
|
||||
else {
|
||||
parent.left = node
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if parent.right!=0
|
||||
parent = parent.right
|
||||
else {
|
||||
parent.right = node
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub contains(uword value) -> bool {
|
||||
^^Node r = root
|
||||
while r!=0 {
|
||||
if r.value==value
|
||||
return true
|
||||
if r.value>value
|
||||
r = r.left
|
||||
else
|
||||
r = r.right
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
sub size() -> ubyte {
|
||||
ubyte count
|
||||
|
||||
if root!=0
|
||||
count_node(root)
|
||||
|
||||
return count
|
||||
|
||||
sub count_node(^^Node r) {
|
||||
count++
|
||||
if r.left!=0 {
|
||||
pushw(r)
|
||||
count_node(r.left)
|
||||
r = popw()
|
||||
}
|
||||
if r.right!=0 {
|
||||
pushw(r)
|
||||
count_node(r.right)
|
||||
r = popw()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub remove(uword value) {
|
||||
; note: we don't deallocate the memory from the node, for simplicity sake
|
||||
^^Node n = root
|
||||
^^Node parent = 0
|
||||
while n!=0 {
|
||||
if n.value==value {
|
||||
if n.left==0
|
||||
replacechild(parent, n, n.right)
|
||||
else if n.right==0
|
||||
replacechild(parent, n, n.left)
|
||||
else {
|
||||
; Both left & right subtrees are present.
|
||||
; N = node to delete.
|
||||
; Find N's successor S. (N's right subtree's minimum element)
|
||||
; Attach N's left subtree to S.left (S doesn't have a left child)
|
||||
; Attach N's right subtree to Parent in place of N.
|
||||
^^Node successor = find_successor(n)
|
||||
successor.left = n.left
|
||||
replacechild(parent, n, n.right)
|
||||
}
|
||||
return
|
||||
}
|
||||
parent = n
|
||||
if n.value>value
|
||||
n = n.left
|
||||
else
|
||||
n = n.right
|
||||
}
|
||||
|
||||
sub find_successor(^^Node p) -> ^^Node {
|
||||
^^Node succ = p
|
||||
p = p.right
|
||||
while p!=0 {
|
||||
succ = p
|
||||
p = p.left
|
||||
}
|
||||
return succ
|
||||
}
|
||||
|
||||
sub replacechild(^^Node p, ^^Node child, ^^Node newchild) {
|
||||
if p.left==child
|
||||
p.left = newchild
|
||||
else
|
||||
p.right = newchild
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub process_tree_inorder() {
|
||||
if root!=0
|
||||
process_tree(root)
|
||||
|
||||
sub process_tree(^^Node r) {
|
||||
if r.left!=0 {
|
||||
pushw(r)
|
||||
process_tree(r.left)
|
||||
r = popw()
|
||||
}
|
||||
cx16.r0 = r.value
|
||||
if r.right!=0 {
|
||||
pushw(r)
|
||||
process_tree(r.right)
|
||||
r = popw()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub process_tree_preorder() {
|
||||
if root!=0
|
||||
process_tree(root,0)
|
||||
|
||||
sub process_tree(^^Node r, ubyte depth) {
|
||||
cx16.r0 = r.value
|
||||
if r.left!=0 {
|
||||
pushw(r)
|
||||
push(depth)
|
||||
process_tree(r.left, depth+1)
|
||||
depth = pop()
|
||||
r = popw()
|
||||
}
|
||||
if r.right!=0 {
|
||||
pushw(r)
|
||||
push(depth)
|
||||
process_tree(r.right, depth+1)
|
||||
depth = pop()
|
||||
r = popw()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
arena {
|
||||
; extremely trivial arena allocator (that never frees)
|
||||
pointer buffer = memory("arena", 2000, 0)
|
||||
pointer next = buffer
|
||||
|
||||
sub alloc(ubyte size) -> pointer {
|
||||
defer next += size
|
||||
return next
|
||||
}
|
||||
|
||||
sub freeall() {
|
||||
next = buffer
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
%import gfx_lores
|
||||
%import math
|
||||
|
||||
circles {
|
||||
const ubyte MAX_NUM_CIRCLES = 80
|
||||
const ubyte GROWTH_RATE = 4
|
||||
uword[MAX_NUM_CIRCLES] circle_x
|
||||
uword[MAX_NUM_CIRCLES] circle_y
|
||||
ubyte[MAX_NUM_CIRCLES] circle_radius
|
||||
ubyte color
|
||||
uword total_num_circles
|
||||
|
||||
sub draw(bool use_kernal, uword max_time) -> uword {
|
||||
if use_kernal
|
||||
cx16.set_screen_mode(128)
|
||||
else
|
||||
gfx_lores.graphics_mode()
|
||||
|
||||
math.rndseed(12345,6789)
|
||||
cbm.SETTIM(0,0,0)
|
||||
|
||||
total_num_circles = 0
|
||||
color = 16
|
||||
|
||||
while cbm.RDTIM16()<max_time {
|
||||
if use_kernal {
|
||||
cx16.GRAPH_set_colors(0,0,0)
|
||||
cx16.GRAPH_clear()
|
||||
}
|
||||
else
|
||||
gfx_lores.clear_screen(0)
|
||||
total_num_circles += draw_circles(use_kernal, max_time)
|
||||
}
|
||||
|
||||
if use_kernal
|
||||
cx16.set_screen_mode(3)
|
||||
else {
|
||||
gfx_lores.text_mode()
|
||||
}
|
||||
|
||||
return total_num_circles
|
||||
}
|
||||
|
||||
sub draw_circles(bool use_kernal, uword max_time) -> uword {
|
||||
uword @zp x
|
||||
uword @zp y
|
||||
ubyte @zp radius
|
||||
|
||||
ubyte num_circles
|
||||
|
||||
while num_circles<MAX_NUM_CIRCLES and cbm.RDTIM16()<max_time {
|
||||
x = math.rndw() % 320
|
||||
y = math.rndw() % 240
|
||||
radius = GROWTH_RATE
|
||||
if not_colliding() {
|
||||
while not_edge() and not_colliding() {
|
||||
radius += GROWTH_RATE
|
||||
}
|
||||
radius -= GROWTH_RATE
|
||||
if radius>0 {
|
||||
color++
|
||||
if color==0
|
||||
color=16
|
||||
if use_kernal {
|
||||
cx16.GRAPH_set_colors(color, 255-color, 0)
|
||||
cx16.GRAPH_draw_oval(x-radius, y-radius, radius*2, radius*2, true)
|
||||
}
|
||||
else
|
||||
gfx_lores.disc(x, y as ubyte, radius, color)
|
||||
circle_x[num_circles] = x
|
||||
circle_y[num_circles] = y
|
||||
circle_radius[num_circles] = radius
|
||||
num_circles++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return num_circles
|
||||
|
||||
sub not_colliding() -> bool {
|
||||
if num_circles==0
|
||||
return true
|
||||
ubyte @zp c
|
||||
for c in 0 to num_circles-1 {
|
||||
if distance(c) < (radius as uword) + circle_radius[c]
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
sub distance(ubyte cix) -> uword {
|
||||
word dx = x as word - circle_x[cix]
|
||||
word dy = y as word - circle_y[cix]
|
||||
uword sqx = dx*dx as uword
|
||||
uword sqy = dy*dy as uword
|
||||
return sqrt(sqx + sqy)
|
||||
}
|
||||
|
||||
sub not_edge() -> bool {
|
||||
if x as word - radius < 0
|
||||
return false
|
||||
if x + radius >= 320
|
||||
return false
|
||||
if y as word - radius < 0
|
||||
return false
|
||||
if y + radius >= 240
|
||||
return false
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
; conway's game of life.
|
||||
|
||||
%import math
|
||||
%import textio
|
||||
|
||||
life {
|
||||
const ubyte WIDTH = 40
|
||||
const ubyte HEIGHT = 30
|
||||
const uword STRIDE = $0002+WIDTH
|
||||
uword world1 = memory("world1", (WIDTH+2)*(HEIGHT+2), 0)
|
||||
uword world2 = memory("world2", (WIDTH+2)*(HEIGHT+2), 0)
|
||||
uword @requirezp active_world = world1
|
||||
|
||||
sub benchmark(uword max_time) -> uword {
|
||||
txt.clear_screen()
|
||||
sys.memset(world1, (WIDTH+2)*(HEIGHT+2), 0)
|
||||
sys.memset(world2, (WIDTH+2)*(HEIGHT+2), 0)
|
||||
|
||||
set_start_gen()
|
||||
|
||||
uword gen
|
||||
cbm.SETTIM(0,0,0)
|
||||
|
||||
while cbm.RDTIM16()<max_time {
|
||||
next_gen()
|
||||
gen++
|
||||
}
|
||||
|
||||
return gen
|
||||
}
|
||||
|
||||
sub set_start_gen() {
|
||||
|
||||
; some way to set a custom start generation:
|
||||
; str start_gen = " " +
|
||||
; " " +
|
||||
; " " +
|
||||
; " ** " +
|
||||
; " * * " +
|
||||
; " * " +
|
||||
; " * * " +
|
||||
; " ****** " +
|
||||
; " " +
|
||||
; " " +
|
||||
; " " +
|
||||
; " " +
|
||||
; " " +
|
||||
; " " +
|
||||
; " " +
|
||||
; " "
|
||||
;
|
||||
; for y in 0 to 15 {
|
||||
; for x in 0 to 15 {
|
||||
; if start_gen[y*16 + x]=='*'
|
||||
; active_world[offset + x] = 1
|
||||
; }
|
||||
; offset += STRIDE
|
||||
; }
|
||||
|
||||
; randomize whole world
|
||||
math.rndseed(12345,9999)
|
||||
uword offset = STRIDE+1
|
||||
ubyte x
|
||||
repeat HEIGHT {
|
||||
for x in 0 to WIDTH-1 {
|
||||
active_world[offset+x] = math.rnd() & 1
|
||||
}
|
||||
offset += STRIDE
|
||||
}
|
||||
}
|
||||
|
||||
sub next_gen() {
|
||||
const ubyte DXOFFSET = 0
|
||||
const ubyte DYOFFSET = 0
|
||||
ubyte[2] cell_chars = [sc:' ', sc:'●']
|
||||
|
||||
uword @requirezp new_world = world1
|
||||
if active_world == world1
|
||||
new_world = world2
|
||||
|
||||
; To avoid re-calculating word index lookups into the new- and active world arrays,
|
||||
; we calculate the required pointer values upfront.
|
||||
; Inside the loop we can use ptr+x just fine (results in efficient LDA (ptr),Y instruction because x is a byte type),
|
||||
; and for each row we simply add the stride to the pointer.
|
||||
; It's more readable to use active_world[offset] etc, but offset is a word value, and this produces
|
||||
; inefficient assembly code because we can't use a register indexed mode in this case. Costly inside a loop.
|
||||
|
||||
uword @requirezp new_world_ptr = new_world + STRIDE+1-DXOFFSET
|
||||
uword @requirezp active_world_ptr = active_world + STRIDE+1-DXOFFSET
|
||||
|
||||
ubyte x
|
||||
ubyte y
|
||||
for y in DYOFFSET to HEIGHT+DYOFFSET-1 {
|
||||
|
||||
cx16.vaddr_autoincr(1, $b000 + 256*y, 0, 2) ; allows us to use simple Vera data byte assigns later instead of setchr() calls
|
||||
|
||||
for x in DXOFFSET to WIDTH+DXOFFSET-1 {
|
||||
; count the living neighbors
|
||||
ubyte cell = @(active_world_ptr + x)
|
||||
uword @requirezp ptr = active_world_ptr + x - STRIDE - 1
|
||||
ubyte neighbors = @(ptr) + @(ptr+1) + @(ptr+2) +
|
||||
@(ptr+STRIDE) + cell + @(ptr+STRIDE+2) +
|
||||
@(ptr+STRIDE*2) + @(ptr+STRIDE*2+1) + @(ptr+STRIDE*2+2)
|
||||
|
||||
; apply game of life rules
|
||||
if neighbors==3
|
||||
cell=1
|
||||
else if neighbors!=4
|
||||
cell=0
|
||||
@(new_world_ptr + x) = cell
|
||||
|
||||
; draw new cell
|
||||
; txt.setchr(x,y,cell_chars[cell])
|
||||
cx16.VERA_DATA0 = cell_chars[cell]
|
||||
}
|
||||
active_world_ptr += STRIDE
|
||||
new_world_ptr += STRIDE
|
||||
}
|
||||
|
||||
active_world = new_world
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
%import textio
|
||||
%import floats
|
||||
|
||||
mandelbrot {
|
||||
const ubyte width = 39
|
||||
const ubyte height = 29
|
||||
const ubyte max_iter = 15
|
||||
|
||||
sub calc(uword max_time) -> uword {
|
||||
uword num_pixels
|
||||
ubyte pixelx
|
||||
ubyte pixely
|
||||
|
||||
txt.home()
|
||||
cbm.SETTIM(0,0,0)
|
||||
|
||||
while cbm.RDTIM16() < max_time {
|
||||
for pixely in 0 to height-1 {
|
||||
float yy = (pixely as float)/0.40/height - 1.3
|
||||
|
||||
for pixelx in 0 to width-1 {
|
||||
float xx = (pixelx as float)/0.32/width - 2.2
|
||||
|
||||
float xsquared = 0.0
|
||||
float ysquared = 0.0
|
||||
float x = 0.0
|
||||
float y = 0.0
|
||||
ubyte iter = 0
|
||||
|
||||
while iter<max_iter and xsquared+ysquared<4.0 {
|
||||
y = x*y*2.0 + yy
|
||||
x = xsquared - ysquared + xx
|
||||
xsquared = x*x
|
||||
ysquared = y*y
|
||||
iter++
|
||||
}
|
||||
txt.color2(1, max_iter-iter)
|
||||
txt.spc()
|
||||
num_pixels++
|
||||
|
||||
if cbm.RDTIM16()>=max_time
|
||||
goto finished
|
||||
}
|
||||
txt.nl()
|
||||
}
|
||||
|
||||
txt.clear_screen()
|
||||
}
|
||||
|
||||
finished:
|
||||
txt.color2(1, 6)
|
||||
return num_pixels
|
||||
}
|
||||
}
|
||||
@@ -1,343 +0,0 @@
|
||||
%import textio
|
||||
%import math
|
||||
|
||||
; Even though prog8 only has support for extremely limited recursion,
|
||||
; you can write recursive algorithms with a bit of extra work by building your own explicit stack structure.
|
||||
; This program shows a depth-first maze generation algorithm (1 possible path from start to finish),
|
||||
; and a depth-first maze solver algorithm, both using a stack to store the path taken.
|
||||
|
||||
; Note: this program can be compiled for multiple target systems.
|
||||
|
||||
maze {
|
||||
uword score
|
||||
|
||||
sub bench(uword max_time) -> uword {
|
||||
txt.nl()
|
||||
score=0
|
||||
math.rndseed(2345,44332)
|
||||
cbm.SETTIM(0,0,0)
|
||||
while cbm.RDTIM16()<max_time {
|
||||
maze.initialize()
|
||||
maze.drawStartFinish()
|
||||
if maze.generate(max_time) {
|
||||
maze.openpassages()
|
||||
maze.drawStartFinish()
|
||||
if maze.solve(max_time) {
|
||||
maze.drawStartFinish()
|
||||
} else break
|
||||
} else break
|
||||
}
|
||||
|
||||
return score
|
||||
}
|
||||
|
||||
const uword screenwidth = 40
|
||||
const uword screenheight = 30
|
||||
|
||||
const ubyte numCellsHoriz = (screenwidth-1) / 2
|
||||
const ubyte numCellsVert = (screenheight-1) / 2
|
||||
|
||||
; maze start and finish cells
|
||||
const ubyte startCx = 0
|
||||
const ubyte startCy = 0
|
||||
const ubyte finishCx = numCellsHoriz-1
|
||||
const ubyte finishCy = numCellsVert-1
|
||||
|
||||
; cell properties
|
||||
const ubyte STONE = 128
|
||||
const ubyte WALKED = 64
|
||||
const ubyte BACKTRACKED = 32
|
||||
const ubyte UP = 1
|
||||
const ubyte RIGHT = 2
|
||||
const ubyte DOWN = 4
|
||||
const ubyte LEFT = 8
|
||||
const ubyte WALLCOLOR = 12
|
||||
const ubyte EMPTYCOLOR = 0
|
||||
|
||||
; unfortunately on larger screens (cx16), the number of cells exceeds 256 and doesn't fit in a regular array anymore.
|
||||
uword cells = memory("cells", numCellsHoriz*numCellsVert, 0)
|
||||
|
||||
ubyte[256] cx_stack
|
||||
ubyte[256] cy_stack
|
||||
ubyte stackptr
|
||||
|
||||
ubyte[4] directionflags = [LEFT,RIGHT,UP,DOWN]
|
||||
|
||||
sub generate(uword max_time) -> bool {
|
||||
ubyte cx = startCx
|
||||
ubyte cy = startCy
|
||||
|
||||
stackptr = 0
|
||||
@(celladdr(cx,cy)) &= ~STONE
|
||||
drawCell(cx, cy)
|
||||
uword cells_to_carve = numCellsHoriz * numCellsVert - 1
|
||||
|
||||
while cbm.RDTIM16()<max_time {
|
||||
carve_restart_after_repath:
|
||||
ubyte direction = choose_uncarved_direction()
|
||||
if direction==0 {
|
||||
;backtrack
|
||||
stackptr--
|
||||
if stackptr==255 {
|
||||
; stack empty.
|
||||
; repath if we are not done yet. (this is a workaround for the prog8 256 array lenght limit)
|
||||
if cells_to_carve!=0 {
|
||||
if repath()
|
||||
goto carve_restart_after_repath
|
||||
}
|
||||
return true
|
||||
}
|
||||
cx = cx_stack[stackptr]
|
||||
cy = cy_stack[stackptr]
|
||||
} else {
|
||||
cx_stack[stackptr] = cx
|
||||
cy_stack[stackptr] = cy
|
||||
stackptr++
|
||||
if stackptr==0 {
|
||||
; stack overflow, we can't track our path any longer.
|
||||
; repath if we are not done yet. (this is a workaround for the prog8 256 array lenght limit)
|
||||
if cells_to_carve!=0 {
|
||||
if repath()
|
||||
goto carve_restart_after_repath
|
||||
}
|
||||
return true
|
||||
}
|
||||
@(celladdr(cx,cy)) |= direction
|
||||
when direction {
|
||||
UP -> {
|
||||
cy--
|
||||
@(celladdr(cx,cy)) |= DOWN
|
||||
}
|
||||
RIGHT -> {
|
||||
cx++
|
||||
@(celladdr(cx,cy)) |= LEFT
|
||||
|
||||
score++
|
||||
}
|
||||
DOWN -> {
|
||||
cy++
|
||||
@(celladdr(cx,cy)) |= UP
|
||||
}
|
||||
LEFT -> {
|
||||
cx--
|
||||
@(celladdr(cx,cy)) |= RIGHT
|
||||
}
|
||||
}
|
||||
@(celladdr(cx,cy)) &= ~STONE
|
||||
cells_to_carve--
|
||||
drawCell(cx, cy)
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
||||
sub repath() -> bool {
|
||||
; repath: try to find a new start cell with possible directions.
|
||||
; we limit our number of searches so that the algorith doesn't get stuck
|
||||
; for too long on bad rng... just accept a few unused cells in that case.
|
||||
repeat 255 {
|
||||
do {
|
||||
cx = math.rnd() % numCellsHoriz
|
||||
cy = math.rnd() % numCellsVert
|
||||
} until @(celladdr(cx, cy)) & STONE ==0
|
||||
if available_uncarved()!=0
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
sub available_uncarved() -> ubyte {
|
||||
ubyte candidates = 0
|
||||
if cx>0 and @(celladdr(cx-1, cy)) & STONE !=0
|
||||
candidates |= LEFT
|
||||
if cx<numCellsHoriz-1 and @(celladdr(cx+1, cy)) & STONE !=0
|
||||
candidates |= RIGHT
|
||||
if cy>0 and @(celladdr(cx, cy-1)) & STONE !=0
|
||||
candidates |= UP
|
||||
if cy<numCellsVert-1 and @(celladdr(cx, cy+1)) & STONE !=0
|
||||
candidates |= DOWN
|
||||
return candidates
|
||||
}
|
||||
|
||||
sub choose_uncarved_direction() -> ubyte {
|
||||
ubyte candidates = available_uncarved()
|
||||
if candidates==0
|
||||
return 0
|
||||
|
||||
repeat {
|
||||
ubyte choice = candidates & directionflags[math.rnd() & 3]
|
||||
if choice!=0
|
||||
return choice
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub openpassages() {
|
||||
; open just a few extra passages, so that multiple routes are possible in theory.
|
||||
ubyte numpassages
|
||||
ubyte cx
|
||||
ubyte cy
|
||||
do {
|
||||
do {
|
||||
cx = math.rnd() % (numCellsHoriz-2) + 1
|
||||
cy = math.rnd() % (numCellsVert-2) + 1
|
||||
} until @(celladdr(cx, cy)) & STONE ==0
|
||||
ubyte direction = directionflags[math.rnd() & 3]
|
||||
if @(celladdr(cx, cy)) & direction == 0 {
|
||||
when direction {
|
||||
LEFT -> {
|
||||
if @(celladdr(cx-1,cy)) & STONE == 0 {
|
||||
@(celladdr(cx,cy)) |= LEFT
|
||||
drawCell(cx,cy)
|
||||
numpassages++
|
||||
}
|
||||
}
|
||||
RIGHT -> {
|
||||
if @(celladdr(cx+1,cy)) & STONE == 0 {
|
||||
@(celladdr(cx,cy)) |= RIGHT
|
||||
drawCell(cx,cy)
|
||||
numpassages++
|
||||
}
|
||||
}
|
||||
UP -> {
|
||||
if @(celladdr(cx,cy-1)) & STONE == 0 {
|
||||
@(celladdr(cx,cy)) |= UP
|
||||
drawCell(cx,cy)
|
||||
numpassages++
|
||||
}
|
||||
}
|
||||
DOWN -> {
|
||||
if @(celladdr(cx,cy+1)) & STONE == 0 {
|
||||
@(celladdr(cx,cy)) |= DOWN
|
||||
drawCell(cx,cy)
|
||||
numpassages++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} until numpassages==10
|
||||
}
|
||||
|
||||
sub solve(uword max_time) -> bool {
|
||||
ubyte cx = startCx
|
||||
ubyte cy = startCy
|
||||
const uword max_path_length = 1024
|
||||
|
||||
; the path through the maze can be longer than 256 so doesn't fit in a regular array.... :(
|
||||
uword pathstack = memory("pathstack", max_path_length, 0)
|
||||
uword pathstackptr = 0
|
||||
|
||||
@(celladdr(cx,cy)) |= WALKED
|
||||
; txt.setcc(cx*2+1, cy*2+1, 81, 1)
|
||||
|
||||
while cbm.RDTIM16()<max_time {
|
||||
solve_loop:
|
||||
if cx==finishCx and cy==finishCy {
|
||||
;txt.home()
|
||||
txt.print("found! path length: ")
|
||||
txt.print_uw(pathstackptr)
|
||||
txt.nl()
|
||||
return true
|
||||
}
|
||||
|
||||
ubyte cell = @(celladdr(cx,cy))
|
||||
if cell & UP!=0 and @(celladdr(cx,cy-1)) & (WALKED|BACKTRACKED) ==0 {
|
||||
@(pathstack + pathstackptr) = UP
|
||||
;txt.setcc(cx*2+1, cy*2, 81, 3)
|
||||
cy--
|
||||
}
|
||||
else if cell & DOWN !=0 and @(celladdr(cx,cy+1)) & (WALKED|BACKTRACKED) ==0 {
|
||||
@(pathstack + pathstackptr) = DOWN
|
||||
;txt.setcc(cx*2+1, cy*2+2, 81, 3)
|
||||
cy++
|
||||
}
|
||||
else if cell & LEFT !=0 and @(celladdr(cx-1,cy)) & (WALKED|BACKTRACKED) ==0 {
|
||||
@(pathstack + pathstackptr) = LEFT
|
||||
;txt.setcc(cx*2, cy*2+1, 81, 3)
|
||||
cx--
|
||||
}
|
||||
else if cell & RIGHT !=0 and @(celladdr(cx+1,cy)) & (WALKED|BACKTRACKED) ==0 {
|
||||
@(pathstack + pathstackptr) = RIGHT
|
||||
;txt.setcc(cx*2+2, cy*2+1, 81, 3)
|
||||
cx++
|
||||
}
|
||||
else {
|
||||
; dead end, pop stack
|
||||
pathstackptr--
|
||||
if pathstackptr==65535 {
|
||||
txt.print("no solution?!\n")
|
||||
return true
|
||||
}
|
||||
@(celladdr(cx,cy)) |= BACKTRACKED
|
||||
;txt.setcc(cx*2+1, cy*2+1, 81, 2)
|
||||
when @(pathstack + pathstackptr) {
|
||||
UP -> {
|
||||
;txt.setcc(cx*2+1, cy*2+2, 81, 9)
|
||||
cy++
|
||||
}
|
||||
DOWN -> {
|
||||
;txt.setcc(cx*2+1, cy*2, 81, 9)
|
||||
cy--
|
||||
}
|
||||
LEFT -> {
|
||||
;txt.setcc(cx*2+2, cy*2+1, 81, 9)
|
||||
cx++
|
||||
}
|
||||
RIGHT -> {
|
||||
;txt.setcc(cx*2, cy*2+1, 81, 9)
|
||||
cx--
|
||||
|
||||
score++
|
||||
}
|
||||
}
|
||||
goto solve_loop
|
||||
}
|
||||
pathstackptr++
|
||||
if pathstackptr==max_path_length {
|
||||
txt.print("stack overflow, path too long\n")
|
||||
return true
|
||||
}
|
||||
@(celladdr(cx,cy)) |= WALKED
|
||||
;txt.setcc(cx*2+1, cy*2+1, 81, 1)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
sub celladdr(ubyte cx, ubyte cy) -> uword {
|
||||
return cells+(numCellsHoriz as uword)*cy+cx
|
||||
}
|
||||
|
||||
sub drawCell(ubyte cx, ubyte cy) {
|
||||
return
|
||||
; ubyte x = cx * 2 + 1
|
||||
; ubyte y = cy * 2 + 1
|
||||
; ubyte doors = @(celladdr(cx,cy))
|
||||
; if doors & UP !=0
|
||||
; txt.setcc(x, y-1, ' ', EMPTYCOLOR)
|
||||
; if doors & RIGHT !=0
|
||||
; txt.setcc(x+1, y, ' ', EMPTYCOLOR)
|
||||
; if doors & DOWN !=0
|
||||
; txt.setcc(x, y+1, ' ', EMPTYCOLOR)
|
||||
; if doors & LEFT !=0
|
||||
; txt.setcc(x-1, y, ' ', EMPTYCOLOR)
|
||||
; if doors & STONE !=0
|
||||
; txt.setcc(x, y, 160, WALLCOLOR)
|
||||
; else
|
||||
; txt.setcc(x, y, 32, EMPTYCOLOR)
|
||||
;
|
||||
; if doors & WALKED !=0
|
||||
; txt.setcc(x, y, 81, 1)
|
||||
; if doors & BACKTRACKED !=0
|
||||
; txt.setcc(x, y, 81, 2)
|
||||
}
|
||||
|
||||
sub initialize() {
|
||||
sys.memset(cells, numCellsHoriz*numCellsVert, STONE)
|
||||
; txt.fill_screen(160, WALLCOLOR)
|
||||
drawStartFinish()
|
||||
}
|
||||
|
||||
sub drawStartFinish() {
|
||||
; txt.setcc(startCx*2+1,startCy*2+1,sc:'s',5)
|
||||
; txt.setcc(finishCx*2+1, finishCy*2+1, sc:'f', 13)
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
%import textio
|
||||
|
||||
; Recursive N-Queens solver.
|
||||
; The problem is: find all possible ways to place 8 Queen chess pieces on a chess board, so that none of them attacks any other.
|
||||
; (this program prints all solutions without taking mirroring and flipping the chess board into account)
|
||||
; Note: this program can be compiled for multiple target systems.
|
||||
|
||||
queens {
|
||||
const ubyte NUMQUEENS=8
|
||||
ubyte[NUMQUEENS] board
|
||||
|
||||
sub could_place(ubyte row, ubyte col) -> bool {
|
||||
if row==0
|
||||
return true
|
||||
ubyte i
|
||||
for i in 0 to row-1 {
|
||||
if board[i]==col or board[i]-i==col-row or board[i]+i==col+row
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
uword solution_count
|
||||
uword maximum_duration
|
||||
|
||||
sub place_queen(ubyte row) -> bool {
|
||||
if row == NUMQUEENS {
|
||||
solution_count++
|
||||
txt.chrout('.')
|
||||
return cbm.RDTIM16()<maximum_duration
|
||||
}
|
||||
bool continue_running=true
|
||||
ubyte col
|
||||
for col in 0 to NUMQUEENS-1 {
|
||||
if could_place(row, col) {
|
||||
board[row] = col
|
||||
; we need to save the local variables row and col.
|
||||
push(row)
|
||||
push(col)
|
||||
continue_running = place_queen(row + 1)
|
||||
; restore the local variables after the recursive call.
|
||||
col = pop()
|
||||
row = pop()
|
||||
board[row] = 0
|
||||
|
||||
if not continue_running
|
||||
break
|
||||
}
|
||||
}
|
||||
return continue_running
|
||||
}
|
||||
|
||||
sub bench(uword max_time) -> uword {
|
||||
solution_count = 0
|
||||
maximum_duration = max_time
|
||||
txt.nl()
|
||||
cbm.SETTIM(0,0,0)
|
||||
while cbm.RDTIM16() < maximum_duration {
|
||||
void place_queen(0)
|
||||
}
|
||||
return solution_count
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
%import sprites
|
||||
%import coroutines
|
||||
%import math
|
||||
|
||||
|
||||
animsprites {
|
||||
uword num_iterations
|
||||
ubyte[64] sx
|
||||
ubyte[64] sy
|
||||
ubyte[64] sc
|
||||
ubyte[64] dx
|
||||
ubyte[64] dy
|
||||
uword maximum_duration
|
||||
|
||||
sub benchmark(uword max_duration) -> uword {
|
||||
maximum_duration = max_duration
|
||||
math.rndseed(1122,9876)
|
||||
cx16.set_screen_mode(3)
|
||||
cx16.mouse_config2(1)
|
||||
sprites.set_mousepointer_hand()
|
||||
repeat 64
|
||||
void coroutines.add(animsprite, 0)
|
||||
cx16.mouse_config2(0)
|
||||
|
||||
cbm.SETTIM(0,0,0)
|
||||
coroutines.run(supervisor)
|
||||
|
||||
sprites.reset(0, 64)
|
||||
return num_iterations
|
||||
}
|
||||
|
||||
sub supervisor() -> bool {
|
||||
if cbm.RDTIM16() >= maximum_duration {
|
||||
coroutines.killall()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
sub animsprite() {
|
||||
num_iterations++
|
||||
; set up the sprite
|
||||
ubyte sprnum = coroutines.current()
|
||||
cx16.r6L, cx16.r7 = sprites.get_data_ptr(0)
|
||||
sprites.init(sprnum, cx16.r6L, cx16.r7, sprites.SIZE_16, sprites.SIZE_16, sprites.COLORS_256, 0)
|
||||
sx[sprnum] = math.rnd()
|
||||
sy[sprnum] = math.rnd()
|
||||
sc[sprnum] = math.rnd()
|
||||
dx[sprnum] = if math.rnd()&1 == 1 1 else 255
|
||||
dy[sprnum] = if math.rnd()&1 == 1 1 else 255
|
||||
|
||||
; move the sprite around
|
||||
while sc[sprnum]!=0 {
|
||||
animate(sprnum)
|
||||
void coroutines.yield()
|
||||
sprnum = coroutines.current()
|
||||
}
|
||||
|
||||
sub animate(ubyte spr) {
|
||||
defer sc[spr]--
|
||||
sprites.pos(spr, sx[spr], sy[spr])
|
||||
sx[spr] += dx[spr]
|
||||
sy[spr] += dy[spr]
|
||||
}
|
||||
|
||||
; end the task but replace it with a fresh animated sprite task
|
||||
void coroutines.add(animsprite, 0)
|
||||
}
|
||||
}
|
||||
@@ -1,989 +0,0 @@
|
||||
%import textio
|
||||
%import conv
|
||||
%import strings
|
||||
|
||||
|
||||
textelite {
|
||||
|
||||
const ubyte numforLave = 7 ; Lave is 7th generated planet in galaxy one
|
||||
const ubyte numforZaonce = 129
|
||||
const ubyte numforDiso = 147
|
||||
const ubyte numforRiedquat = 46
|
||||
uword num_commands
|
||||
|
||||
sub bench(uword max_time) -> uword {
|
||||
num_commands = 0
|
||||
txt.lowercase()
|
||||
cbm.SETTIM(0,0,0)
|
||||
while cbm.RDTIM16()<max_time {
|
||||
reinit()
|
||||
run_commands(max_time)
|
||||
}
|
||||
return num_commands
|
||||
}
|
||||
|
||||
sub reinit() {
|
||||
;txt.clear_screen()
|
||||
;txt.print("\n --- TextElite v1.3 ---\n")
|
||||
txt.print("\nnew game\n")
|
||||
elite_planet.set_seed(0, 0)
|
||||
elite_galaxy.travel_to(1, numforLave)
|
||||
elite_market.init(0) ; Lave's market is seeded with 0
|
||||
elite_ship.init()
|
||||
elite_planet.display(false, 0)
|
||||
input_index = 0
|
||||
}
|
||||
|
||||
sub run_commands(uword max_time) {
|
||||
while cbm.RDTIM16() < max_time {
|
||||
str input = "????????"
|
||||
;txt.print("\nCash: ")
|
||||
;elite_util.print_10s(elite_ship.cash)
|
||||
;txt.print("\nCommand (?=help): ")
|
||||
ubyte num_chars = next_input(input)
|
||||
;txt.nl()
|
||||
if num_chars!=0 {
|
||||
when input[0] {
|
||||
'q' -> {
|
||||
bool has_error = false
|
||||
if elite_galaxy.number != 2 {
|
||||
txt.print("\nERROR: galaxy is not 2: ")
|
||||
txt.print_ub(elite_galaxy.number)
|
||||
txt.nl()
|
||||
has_error=true
|
||||
}
|
||||
if elite_planet.number != 164 {
|
||||
txt.print("\nERROR: planet is not 164: ")
|
||||
txt.print_ub(elite_planet.number)
|
||||
txt.nl()
|
||||
has_error=true
|
||||
}
|
||||
if elite_planet.x != 116 {
|
||||
txt.print("\nERROR: planet.x is not 116: ")
|
||||
txt.print_ub(elite_planet.x)
|
||||
txt.nl()
|
||||
has_error=true
|
||||
}
|
||||
if elite_planet.y != 201 {
|
||||
txt.print("\nERROR: planet.y is not 201: ")
|
||||
txt.print_ub(elite_planet.y)
|
||||
txt.nl()
|
||||
has_error=true
|
||||
}
|
||||
if "ribeen" != elite_planet.name {
|
||||
txt.print("\nERROR: planet.name is not 'ribeen': ")
|
||||
txt.print(elite_planet.name)
|
||||
txt.nl()
|
||||
has_error=true
|
||||
}
|
||||
if elite_ship.cash != 1212 {
|
||||
txt.print("\nERROR: cash is not 1212: ")
|
||||
txt.print_uw(elite_ship.cash)
|
||||
txt.nl()
|
||||
has_error=true
|
||||
}
|
||||
if elite_ship.fuel != 50 {
|
||||
txt.print("\nERROR: fuel is not 50:")
|
||||
txt.print_ub(elite_ship.fuel)
|
||||
txt.nl()
|
||||
has_error=true
|
||||
}
|
||||
if elite_ship.cargohold[0] != 3 {
|
||||
txt.print("\nERROR: food is not 3:")
|
||||
txt.print_ub(elite_ship.cargohold[0])
|
||||
txt.nl()
|
||||
has_error=true
|
||||
}
|
||||
if elite_ship.cargohold[1] != 0 {
|
||||
txt.print("\nERROR: textiles is not 0:")
|
||||
txt.print_ub(elite_ship.cargohold[1])
|
||||
txt.nl()
|
||||
has_error=true
|
||||
}
|
||||
if has_error
|
||||
sys.exit(1)
|
||||
return
|
||||
}
|
||||
'b' -> elite_trader.do_buy()
|
||||
's' -> elite_trader.do_sell()
|
||||
'f' -> elite_trader.do_fuel()
|
||||
'j' -> elite_trader.do_jump()
|
||||
't' -> elite_trader.do_teleport()
|
||||
'g' -> elite_trader.do_next_galaxy()
|
||||
'i' -> elite_trader.do_info()
|
||||
'm' -> {
|
||||
if input[1]=='a' and input[2]=='p'
|
||||
elite_trader.do_map()
|
||||
else
|
||||
elite_trader.do_show_market()
|
||||
}
|
||||
'l' -> elite_trader.do_local()
|
||||
'c' -> elite_trader.do_cash()
|
||||
'h' -> elite_trader.do_hold()
|
||||
}
|
||||
num_commands++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
str[] inputs = [
|
||||
"i",
|
||||
"diso",
|
||||
"i",
|
||||
"lave",
|
||||
"m",
|
||||
"b",
|
||||
"food",
|
||||
"15",
|
||||
"map",
|
||||
"g",
|
||||
"map",
|
||||
"l",
|
||||
"j",
|
||||
"zao",
|
||||
"s",
|
||||
"food",
|
||||
"12",
|
||||
"tele",
|
||||
"quti",
|
||||
"tele",
|
||||
"aro",
|
||||
"i",
|
||||
"diso",
|
||||
"i",
|
||||
"lave",
|
||||
"i",
|
||||
"zao",
|
||||
"galhyp",
|
||||
"fuel",
|
||||
"20",
|
||||
"j",
|
||||
"rib",
|
||||
"i",
|
||||
"rib",
|
||||
"i",
|
||||
"tiri",
|
||||
"q",
|
||||
0
|
||||
]
|
||||
|
||||
ubyte input_index
|
||||
|
||||
sub next_input(str buffer) -> ubyte {
|
||||
input_index++
|
||||
return strings.copy(inputs[input_index], buffer)
|
||||
}
|
||||
}
|
||||
|
||||
elite_trader {
|
||||
str input = "??????????"
|
||||
ubyte num_chars
|
||||
|
||||
sub do_jump() {
|
||||
;txt.print("\nJump to what system? ")
|
||||
jump_to_system()
|
||||
}
|
||||
|
||||
sub do_teleport() {
|
||||
;txt.print("\nCheat! Teleport to what system? ")
|
||||
ubyte fuel = elite_ship.fuel
|
||||
elite_ship.fuel = 255
|
||||
jump_to_system()
|
||||
elite_ship.fuel = fuel
|
||||
}
|
||||
|
||||
sub jump_to_system() {
|
||||
void textelite.next_input(input)
|
||||
ubyte current_planet = elite_planet.number
|
||||
ubyte x = elite_planet.x
|
||||
ubyte y = elite_planet.y
|
||||
if elite_galaxy.search_closest_planet(input) {
|
||||
ubyte distance = elite_planet.distance(x, y)
|
||||
if distance <= elite_ship.fuel {
|
||||
elite_galaxy.init_market_for_planet()
|
||||
elite_ship.fuel -= distance
|
||||
;txt.print("\n\nHyperspace jump! Arrived at:\n")
|
||||
elite_planet.display(true,0 )
|
||||
return
|
||||
}
|
||||
;txt.print("\nInsufficient fuel\n")
|
||||
} else {
|
||||
;txt.print(" Not found!\n")
|
||||
}
|
||||
elite_galaxy.travel_to(elite_galaxy.number, current_planet)
|
||||
}
|
||||
|
||||
sub do_buy() {
|
||||
;txt.print("\nBuy what commodity? ")
|
||||
str commodity = "???????????????"
|
||||
void textelite.next_input(commodity)
|
||||
ubyte ci = elite_market.match(commodity)
|
||||
if ci & 128 !=0 {
|
||||
txt.print("Unknown\n")
|
||||
} else {
|
||||
;txt.print("\nHow much? ")
|
||||
void textelite.next_input(input)
|
||||
ubyte amount = conv.str2ubyte(input)
|
||||
if elite_market.current_quantity[ci] < amount {
|
||||
txt.print(" Insufficient supply!\n")
|
||||
} else {
|
||||
uword price = elite_market.current_price[ci] * amount
|
||||
;txt.print(" Total price: ")
|
||||
;elite_util.print_10s(price)
|
||||
if price > elite_ship.cash {
|
||||
txt.print(" Not enough cash!\n")
|
||||
} else {
|
||||
elite_ship.cash -= price
|
||||
elite_ship.cargohold[ci] += amount
|
||||
elite_market.current_quantity[ci] -= amount
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub do_sell() {
|
||||
;txt.print("\nSell what commodity? ")
|
||||
str commodity = "???????????????"
|
||||
void textelite.next_input(commodity)
|
||||
ubyte ci = elite_market.match(commodity)
|
||||
if ci & 128 !=0 {
|
||||
txt.print("Unknown\n")
|
||||
} else {
|
||||
;txt.print("\nHow much? ")
|
||||
void textelite.next_input(input)
|
||||
ubyte amount = conv.str2ubyte(input)
|
||||
if elite_ship.cargohold[ci] < amount {
|
||||
txt.print(" Insufficient supply!\n")
|
||||
} else {
|
||||
uword price = elite_market.current_price[ci] * amount
|
||||
;txt.print(" Total price: ")
|
||||
;elite_util.print_10s(price)
|
||||
elite_ship.cash += price
|
||||
elite_ship.cargohold[ci] -= amount
|
||||
elite_market.current_quantity[ci] += amount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub do_fuel() {
|
||||
;txt.print("\nBuy fuel. Amount? ")
|
||||
void textelite.next_input(input)
|
||||
ubyte buy_fuel = 10*conv.str2ubyte(input)
|
||||
ubyte max_fuel = elite_ship.Max_fuel - elite_ship.fuel
|
||||
if buy_fuel > max_fuel
|
||||
buy_fuel = max_fuel
|
||||
uword price = buy_fuel as uword * elite_ship.Fuel_cost
|
||||
if price > elite_ship.cash {
|
||||
txt.print("Not enough cash!\n")
|
||||
} else {
|
||||
elite_ship.cash -= price
|
||||
elite_ship.fuel += buy_fuel
|
||||
}
|
||||
}
|
||||
|
||||
sub do_cash() {
|
||||
;txt.print("\nCheat! Set cash amount: ")
|
||||
void textelite.next_input(input)
|
||||
elite_ship.cash = conv.str2uword(input)
|
||||
}
|
||||
|
||||
sub do_hold() {
|
||||
;txt.print("\nCheat! Set cargohold size: ")
|
||||
void textelite.next_input(input)
|
||||
elite_ship.Max_cargo = conv.str2ubyte(input)
|
||||
}
|
||||
|
||||
sub do_next_galaxy() {
|
||||
txt.print("\n>>>>> Galaxy Hyperjump!\n")
|
||||
elite_galaxy.travel_to(elite_galaxy.number+1, elite_planet.number)
|
||||
elite_planet.display(false, 0)
|
||||
}
|
||||
|
||||
sub do_info() {
|
||||
;txt.print("\nSystem name (empty=current): ")
|
||||
num_chars = textelite.next_input(input)
|
||||
if num_chars!=0 {
|
||||
ubyte current_planet = elite_planet.number
|
||||
ubyte x = elite_planet.x
|
||||
ubyte y = elite_planet.y
|
||||
if elite_galaxy.search_closest_planet(input) {
|
||||
ubyte distance = elite_planet.distance(x, y)
|
||||
elite_planet.display(false, distance)
|
||||
} else {
|
||||
;txt.print(" Not found!")
|
||||
}
|
||||
elite_galaxy.travel_to(elite_galaxy.number, current_planet)
|
||||
} else {
|
||||
elite_planet.display(false, 0)
|
||||
}
|
||||
}
|
||||
|
||||
sub do_local() {
|
||||
elite_galaxy.local_area()
|
||||
}
|
||||
|
||||
sub do_map() {
|
||||
;txt.print("\n(l)ocal or (g)alaxy starmap? ")
|
||||
num_chars = textelite.next_input(input)
|
||||
if num_chars!=0 {
|
||||
elite_galaxy.starmap(input[0]=='l')
|
||||
}
|
||||
}
|
||||
|
||||
sub do_show_market() {
|
||||
elite_market.display()
|
||||
;txt.print("\nFuel: ")
|
||||
;elite_util.print_10s(elite_ship.fuel)
|
||||
;txt.print(" Cargohold space: ")
|
||||
;txt.print_ub(elite_ship.cargo_free())
|
||||
;txt.print("t\n")
|
||||
}
|
||||
}
|
||||
|
||||
elite_ship {
|
||||
const ubyte Max_fuel = 70
|
||||
const ubyte Fuel_cost = 2
|
||||
ubyte Max_cargo = 20
|
||||
|
||||
ubyte fuel
|
||||
uword cash
|
||||
ubyte[17] cargohold
|
||||
|
||||
sub init() {
|
||||
sys.memset(cargohold, len(cargohold), 0)
|
||||
fuel = Max_fuel
|
||||
cash = 1000
|
||||
}
|
||||
}
|
||||
|
||||
elite_market {
|
||||
ubyte[17] baseprices = [$13, $14, $41, $28, $53, $C4, $EB, $9A, $75, $4E, $7C, $B0, $20, $61, $AB, $2D, $35]
|
||||
byte[17] gradients = [-$02, -$01, -$03, -$05, -$05, $08, $1D, $0E, $06, $01, $0d, -$09, -$01, -$01, -$02, -$01, $0F]
|
||||
ubyte[17] basequants = [$06, $0A, $02, $E2, $FB, $36, $08, $38, $28, $11, $1D, $DC, $35, $42, $37, $FA, $C0]
|
||||
ubyte[17] maskbytes = [$01, $03, $07, $1F, $0F, $03, $78, $03, $07, $1F, $07, $3F, $03, $07, $1F, $0F, $07]
|
||||
str[17] names = ["Food", "Textiles", "Radioactives", "Slaves", "Liquor/Wines", "Luxuries", "Narcotics", "Computers",
|
||||
"Machinery", "Alloys", "Firearms", "Furs", "Minerals", "Gold", "Platinum", "Gem-Stones", "Alien Items"]
|
||||
|
||||
ubyte[17] current_quantity
|
||||
uword[17] current_price
|
||||
|
||||
sub init(ubyte fluct) {
|
||||
; Prices and availabilities are influenced by the planet's economy type
|
||||
; (0-7) and a random "fluctuation" byte that was kept within the saved
|
||||
; commander position to keep the market prices constant over gamesaves.
|
||||
; Availabilities must be saved with the game since the player alters them
|
||||
; by buying (and selling(?))
|
||||
;
|
||||
; Almost all commands are one byte only and overflow "errors" are
|
||||
; extremely frequent and exploited.
|
||||
;
|
||||
; Trade Item prices are held internally in a single byte=true value/4.
|
||||
; The decimal point in prices is introduced only when printing them.
|
||||
; Internally, all prices are integers.
|
||||
; The player's cash is held in four bytes.
|
||||
ubyte ci
|
||||
for ci in 0 to len(names)-1 {
|
||||
word product
|
||||
byte changing
|
||||
product = elite_planet.economy as word * gradients[ci]
|
||||
changing = fluct & maskbytes[ci] as byte
|
||||
ubyte q = (basequants[ci] as word + changing - product) as ubyte
|
||||
if q & $80 !=0
|
||||
q = 0 ; clip to positive 8-bit
|
||||
current_quantity[ci] = q & $3f
|
||||
q = (baseprices[ci] + changing + product) as ubyte
|
||||
current_price[ci] = q * $0004
|
||||
}
|
||||
current_quantity[16] = 0 ; force nonavailability of Alien Items
|
||||
}
|
||||
|
||||
sub display() {
|
||||
return
|
||||
; ubyte ci
|
||||
; txt.nl()
|
||||
; elite_planet.print_name_uppercase()
|
||||
; txt.print(" trade market:\n COMMODITY / PRICE / AVAIL / IN HOLD\n")
|
||||
; for ci in 0 to len(names)-1 {
|
||||
; elite_util.print_right(13, names[ci])
|
||||
; txt.print(" ")
|
||||
; elite_util.print_10s(current_price[ci])
|
||||
; txt.column(24)
|
||||
; txt.print_ub(current_quantity[ci])
|
||||
; txt.chrout(' ')
|
||||
; when units[ci] {
|
||||
; 0 -> txt.chrout('t')
|
||||
; 1 -> txt.print("kg")
|
||||
; 2 -> txt.chrout('g')
|
||||
; }
|
||||
; txt.column(32)
|
||||
; txt.print_ub(elite_ship.cargohold[ci])
|
||||
; txt.nl()
|
||||
; }
|
||||
}
|
||||
|
||||
sub match(uword nameptr) -> ubyte {
|
||||
ubyte ci
|
||||
for ci in 0 to len(names)-1 {
|
||||
if elite_util.prefix_matches(nameptr, names[ci])
|
||||
return ci
|
||||
}
|
||||
return 255
|
||||
}
|
||||
}
|
||||
|
||||
elite_galaxy {
|
||||
const uword GALSIZE = 256
|
||||
const uword base0 = $5A4A ; seeds for the first galaxy
|
||||
const uword base1 = $0248
|
||||
const uword base2 = $B753
|
||||
|
||||
str pn_pairs = "..lexegezacebisousesarmaindirea.eratenberalavetiedorquanteisrion"
|
||||
|
||||
ubyte number
|
||||
|
||||
uword[3] seed
|
||||
|
||||
sub init(ubyte galaxynum) {
|
||||
number = 1
|
||||
elite_planet.number = 255
|
||||
seed[0] = base0
|
||||
seed[1] = base1
|
||||
seed[2] = base2
|
||||
repeat galaxynum-1 {
|
||||
nextgalaxy()
|
||||
}
|
||||
}
|
||||
|
||||
sub nextgalaxy() {
|
||||
textelite.num_commands++
|
||||
|
||||
seed[0] = twist(seed[0])
|
||||
seed[1] = twist(seed[1])
|
||||
seed[2] = twist(seed[2])
|
||||
number++
|
||||
if number==9
|
||||
number = 1
|
||||
}
|
||||
|
||||
sub travel_to(ubyte galaxynum, ubyte system) {
|
||||
init(galaxynum)
|
||||
generate_next_planet() ; always at least planet 0 (separate to avoid repeat ubyte overflow)
|
||||
repeat system {
|
||||
generate_next_planet()
|
||||
textelite.num_commands++
|
||||
}
|
||||
void strings.copy(make_current_planet_name(), elite_planet.name)
|
||||
init_market_for_planet()
|
||||
}
|
||||
|
||||
sub init_market_for_planet() {
|
||||
elite_market.init(lsb(seed[0])+msb(seed[2]))
|
||||
}
|
||||
|
||||
sub search_closest_planet(uword nameptr) -> bool {
|
||||
textelite.num_commands++
|
||||
|
||||
ubyte x = elite_planet.x
|
||||
ubyte y = elite_planet.y
|
||||
ubyte current_planet_num = elite_planet.number
|
||||
|
||||
init(number)
|
||||
bool found = false
|
||||
ubyte current_closest_pi
|
||||
ubyte current_distance = 127
|
||||
ubyte pi
|
||||
for pi in 0 to 255 {
|
||||
generate_next_planet()
|
||||
void strings.copy(make_current_planet_name(), elite_planet.name)
|
||||
if elite_util.prefix_matches(nameptr, elite_planet.name) {
|
||||
ubyte distance = elite_planet.distance(x, y)
|
||||
if distance < current_distance {
|
||||
current_distance = distance
|
||||
current_closest_pi = pi
|
||||
found = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if found
|
||||
travel_to(number, current_closest_pi)
|
||||
else
|
||||
travel_to(number, current_planet_num)
|
||||
|
||||
return found
|
||||
}
|
||||
|
||||
sub local_area() {
|
||||
ubyte current_planet = elite_planet.number
|
||||
ubyte px = elite_planet.x
|
||||
ubyte py = elite_planet.y
|
||||
ubyte pn = 0
|
||||
|
||||
init(number)
|
||||
; txt.print("\nGalaxy #")
|
||||
; txt.print_ub(number)
|
||||
; txt.print(" - systems in vicinity:\n")
|
||||
do {
|
||||
generate_next_planet()
|
||||
ubyte distance = elite_planet.distance(px, py)
|
||||
if distance <= elite_ship.Max_fuel {
|
||||
; if distance <= elite_ship.fuel
|
||||
; txt.chrout('*')
|
||||
; else
|
||||
; txt.chrout('-')
|
||||
; txt.spc()
|
||||
void strings.copy(make_current_planet_name(), elite_planet.name)
|
||||
elite_planet.display(true, distance)
|
||||
}
|
||||
pn++
|
||||
} until pn==0
|
||||
|
||||
travel_to(number, current_planet)
|
||||
}
|
||||
|
||||
sub starmap(bool local) {
|
||||
ubyte current_planet = elite_planet.number
|
||||
ubyte px = elite_planet.x
|
||||
ubyte py = elite_planet.y
|
||||
str current_name = " " ; 8 max
|
||||
ubyte pn = 0
|
||||
|
||||
void strings.copy(elite_planet.name, current_name)
|
||||
init(number)
|
||||
; txt.clear_screen()
|
||||
; txt.print("Galaxy #")
|
||||
; txt.print_ub(number)
|
||||
; if local
|
||||
; txt.print(" - local systems")
|
||||
; else
|
||||
; txt.print(" - galaxy")
|
||||
; txt.print(" starmap:\n")
|
||||
ubyte max_distance = 255
|
||||
if local
|
||||
max_distance = elite_ship.Max_fuel
|
||||
ubyte home_sx
|
||||
ubyte home_sy
|
||||
ubyte home_distance
|
||||
|
||||
do {
|
||||
generate_next_planet()
|
||||
ubyte distance = elite_planet.distance(px, py)
|
||||
if distance <= max_distance {
|
||||
void strings.copy(make_current_planet_name(), elite_planet.name)
|
||||
elite_planet.name[0] = strings.upperchar(elite_planet.name[0])
|
||||
uword tx = elite_planet.x
|
||||
uword ty = elite_planet.y
|
||||
if local {
|
||||
tx = tx + 24 - px
|
||||
ty = ty + 24 - py
|
||||
}
|
||||
ubyte sx = display_scale_x(tx)
|
||||
ubyte sy = display_scale_y(ty)
|
||||
ubyte char = '*'
|
||||
if elite_planet.number==current_planet
|
||||
char = '%'
|
||||
if local {
|
||||
print_planet_details(elite_planet.name, sx, sy, distance)
|
||||
} else if elite_planet.number==current_planet {
|
||||
home_distance = distance
|
||||
home_sx = sx
|
||||
home_sy = sy
|
||||
}
|
||||
; txt.setchr(2+sx, 2+sy, char)
|
||||
}
|
||||
pn++
|
||||
} until pn==0
|
||||
|
||||
if not local
|
||||
print_planet_details(current_name, home_sx, home_sy, home_distance)
|
||||
|
||||
; if local
|
||||
; txt.plot(0, display_scale_y(64) + 4)
|
||||
; else
|
||||
; txt.plot(0, display_scale_y(256) + 4 as ubyte)
|
||||
travel_to(number, current_planet)
|
||||
|
||||
sub print_planet_details(str name, ubyte screenx, ubyte screeny, ubyte d) {
|
||||
return
|
||||
; txt.plot(2+screenx-2, 2+screeny+1)
|
||||
; txt.print(name)
|
||||
; if d!=0 {
|
||||
; txt.plot(2+screenx-2, 2+screeny+2)
|
||||
; elite_util.print_10s(d)
|
||||
; txt.print(" LY")
|
||||
; }
|
||||
}
|
||||
|
||||
sub display_scale_x(uword x) -> ubyte {
|
||||
if local
|
||||
return x/2 as ubyte
|
||||
return x/8 as ubyte
|
||||
}
|
||||
|
||||
sub display_scale_y(uword y) -> ubyte {
|
||||
if local
|
||||
return y/4 as ubyte
|
||||
return y/16 as ubyte
|
||||
}
|
||||
}
|
||||
|
||||
ubyte pn_pair1
|
||||
ubyte pn_pair2
|
||||
ubyte pn_pair3
|
||||
ubyte pn_pair4
|
||||
bool longname
|
||||
|
||||
sub generate_next_planet() {
|
||||
determine_planet_properties()
|
||||
longname = lsb(seed[0]) & 64 !=0
|
||||
|
||||
; Always four iterations of random number
|
||||
pn_pair1 = (msb(seed[2]) & 31) * 2
|
||||
tweakseed()
|
||||
pn_pair2 = (msb(seed[2]) & 31) * 2
|
||||
tweakseed()
|
||||
pn_pair3 = (msb(seed[2]) & 31) * 2
|
||||
tweakseed()
|
||||
pn_pair4 = (msb(seed[2]) & 31) * 2
|
||||
tweakseed()
|
||||
}
|
||||
|
||||
sub make_current_planet_name() -> str {
|
||||
ubyte ni = 0
|
||||
str name = " " ; max 8
|
||||
|
||||
if pn_pairs[pn_pair1] != '.' {
|
||||
name[ni] = pn_pairs[pn_pair1]
|
||||
ni++
|
||||
}
|
||||
if pn_pairs[pn_pair1+1] != '.' {
|
||||
name[ni] = pn_pairs[pn_pair1+1]
|
||||
ni++
|
||||
}
|
||||
if pn_pairs[pn_pair2] != '.' {
|
||||
name[ni] = pn_pairs[pn_pair2]
|
||||
ni++
|
||||
}
|
||||
if pn_pairs[pn_pair2+1] != '.' {
|
||||
name[ni] = pn_pairs[pn_pair2+1]
|
||||
ni++
|
||||
}
|
||||
if pn_pairs[pn_pair3] != '.' {
|
||||
name[ni] = pn_pairs[pn_pair3]
|
||||
ni++
|
||||
}
|
||||
if pn_pairs[pn_pair3+1] != '.' {
|
||||
name[ni] = pn_pairs[pn_pair3+1]
|
||||
ni++
|
||||
}
|
||||
|
||||
if longname {
|
||||
if pn_pairs[pn_pair4] != '.' {
|
||||
name[ni] = pn_pairs[pn_pair4]
|
||||
ni++
|
||||
}
|
||||
if pn_pairs[pn_pair4+1] != '.' {
|
||||
name[ni] = pn_pairs[pn_pair4+1]
|
||||
ni++
|
||||
}
|
||||
}
|
||||
|
||||
name[ni] = 0
|
||||
return name
|
||||
}
|
||||
|
||||
sub determine_planet_properties() {
|
||||
; create the planet's characteristics
|
||||
elite_planet.number++
|
||||
elite_planet.x = msb(seed[1])
|
||||
elite_planet.y = msb(seed[0])
|
||||
elite_planet.govtype = lsb(seed[1]) >> 3 & 7 ; bits 3,4 &5 of w1
|
||||
elite_planet.economy = msb(seed[0]) & 7 ; bits 8,9 &A of w0
|
||||
if elite_planet.govtype <= 1
|
||||
elite_planet.economy = (elite_planet.economy | 2)
|
||||
elite_planet.techlevel = (msb(seed[1]) & 3) + (elite_planet.economy ^ 7)
|
||||
elite_planet.techlevel += elite_planet.govtype >> 1
|
||||
if elite_planet.govtype & 1 !=0
|
||||
elite_planet.techlevel++
|
||||
elite_planet.population = 4 * elite_planet.techlevel + elite_planet.economy
|
||||
elite_planet.population += elite_planet.govtype + 1
|
||||
elite_planet.productivity = ((elite_planet.economy ^ 7) + 3) * (elite_planet.govtype + 4)
|
||||
elite_planet.productivity *= elite_planet.population * 8
|
||||
ubyte seed2_msb = msb(seed[2])
|
||||
elite_planet.radius = mkword((seed2_msb & 15) + 11, elite_planet.x)
|
||||
elite_planet.species_is_alien = lsb(seed[2]) & 128 !=0 ; bit 7 of w2_lo
|
||||
if elite_planet.species_is_alien {
|
||||
elite_planet.species_size = (seed2_msb >> 2) & 7 ; bits 2-4 of w2_hi
|
||||
elite_planet.species_color = seed2_msb >> 5 ; bits 5-7 of w2_hi
|
||||
elite_planet.species_look = (seed2_msb ^ msb(seed[1])) & 7 ;bits 0-2 of (w0_hi EOR w1_hi)
|
||||
elite_planet.species_kind = (elite_planet.species_look + (seed2_msb & 3)) & 7 ;Add bits 0-1 of w2_hi to A from previous step, and take bits 0-2 of the result
|
||||
}
|
||||
|
||||
elite_planet.goatsoup_seed[0] = lsb(seed[1])
|
||||
elite_planet.goatsoup_seed[1] = msb(seed[1])
|
||||
elite_planet.goatsoup_seed[2] = lsb(seed[2])
|
||||
elite_planet.goatsoup_seed[3] = seed2_msb
|
||||
}
|
||||
|
||||
sub tweakseed() {
|
||||
uword temp = seed[0] + seed[1] + seed[2]
|
||||
seed[0] = seed[1]
|
||||
seed[1] = seed[2]
|
||||
seed[2] = temp
|
||||
}
|
||||
|
||||
sub twist(uword x) -> uword {
|
||||
ubyte xh = msb(x)
|
||||
ubyte xl = lsb(x)
|
||||
xh <<= 1 ; make sure carry flag is not used on first shift!
|
||||
rol(xl)
|
||||
return mkword(xh, xl)
|
||||
}
|
||||
}
|
||||
|
||||
elite_planet {
|
||||
str[] @nosplit words81 = ["fabled", "notable", "well known", "famous", "noted"]
|
||||
str[] @nosplit words82 = ["very", "mildly", "most", "reasonably", ""]
|
||||
str[] @nosplit words83 = ["ancient", "\x95", "great", "vast", "pink"]
|
||||
str[] @nosplit words84 = ["\x9E \x9D plantations", "mountains", "\x9C", "\x94 forests", "oceans"]
|
||||
str[] @nosplit words85 = ["shyness", "silliness", "mating traditions", "loathing of \x86", "love for \x86"]
|
||||
str[] @nosplit words86 = ["food blenders", "tourists", "poetry", "discos", "\x8E"]
|
||||
str[] @nosplit words87 = ["talking tree", "crab", "bat", "lobst", "\xB2"]
|
||||
str[] @nosplit words88 = ["beset", "plagued", "ravaged", "cursed", "scourged"]
|
||||
str[] @nosplit words89 = ["\x96 civil war", "\x9B \x98 \x99s", "a \x9B disease", "\x96 earthquakes", "\x96 solar activity"]
|
||||
str[] @nosplit words8A = ["its \x83 \x84", "the \xB1 \x98 \x99", "its inhabitants' \x9A \x85", "\xA1", "its \x8D \x8E"]
|
||||
str[] @nosplit words8B = ["juice", "brandy", "water", "brew", "gargle blasters"]
|
||||
str[] @nosplit words8C = ["\xB2", "\xB1 \x99", "\xB1 \xB2", "\xB1 \x9B", "\x9B \xB2"]
|
||||
str[] @nosplit words8D = ["fabulous", "exotic", "hoopy", "unusual", "exciting"]
|
||||
str[] @nosplit words8E = ["cuisine", "night life", "casinos", "sit coms", " \xA1 "]
|
||||
str[] @nosplit words8F = ["\xB0", "The planet \xB0", "The world \xB0", "This planet", "This world"]
|
||||
str[] @nosplit words90 = ["n unremarkable", " boring", " dull", " tedious", " revolting"]
|
||||
str[] @nosplit words91 = ["planet", "world", "place", "little planet", "dump"]
|
||||
str[] @nosplit words92 = ["wasp", "moth", "grub", "ant", "\xB2"]
|
||||
str[] @nosplit words93 = ["poet", "arts graduate", "yak", "snail", "slug"]
|
||||
str[] @nosplit words94 = ["tropical", "dense", "rain", "impenetrable", "exuberant"]
|
||||
str[] @nosplit words95 = ["funny", "wierd", "unusual", "strange", "peculiar"]
|
||||
str[] @nosplit words96 = ["frequent", "occasional", "unpredictable", "dreadful", "deadly"]
|
||||
str[] @nosplit words97 = ["\x82 \x81 for \x8A", "\x82 \x81 for \x8A and \x8A", "\x88 by \x89", "\x82 \x81 for \x8A but \x88 by \x89", "a\x90 \x91"]
|
||||
str[] @nosplit words98 = ["\x9B", "mountain", "edible", "tree", "spotted"]
|
||||
str[] @nosplit words99 = ["\x9F", "\xA0", "\x87oid", "\x93", "\x92"]
|
||||
str[] @nosplit words9A = ["ancient", "exceptional", "eccentric", "ingrained", "\x95"]
|
||||
str[] @nosplit words9B = ["killer", "deadly", "evil", "lethal", "vicious"]
|
||||
str[] @nosplit words9C = ["parking meters", "dust clouds", "ice bergs", "rock formations", "volcanoes"]
|
||||
str[] @nosplit words9D = ["plant", "tulip", "banana", "corn", "\xB2weed"]
|
||||
str[] @nosplit words9E = ["\xB2", "\xB1 \xB2", "\xB1 \x9B", "inhabitant", "\xB1 \xB2"]
|
||||
str[] @nosplit words9F = ["shrew", "beast", "bison", "snake", "wolf"]
|
||||
str[] @nosplit wordsA0 = ["leopard", "cat", "monkey", "goat", "fish"]
|
||||
str[] @nosplit wordsA1 = ["\x8C \x8B", "\xB1 \x9F \xA2", "its \x8D \xA0 \xA2", "\xA3 \xA4", "\x8C \x8B"]
|
||||
str[] @nosplit wordsA2 = ["meat", "cutlet", "steak", "burgers", "soup"]
|
||||
str[] @nosplit wordsA3 = ["ice", "mud", "Zero-G", "vacuum", "\xB1 ultra"]
|
||||
str[] @nosplit wordsA4 = ["hockey", "cricket", "karate", "polo", "tennis"]
|
||||
|
||||
uword[] @shared wordlists = [
|
||||
words81, words82, words83, words84, words85, words86, words87, words88,
|
||||
words89, words8A, words8B, words8C, words8D, words8E, words8F, words90,
|
||||
words91, words92, words93, words94, words95, words96, words97, words98,
|
||||
words99, words9A, words9B, words9C, words9D, words9E, words9F, wordsA0,
|
||||
wordsA1, wordsA2, wordsA3, wordsA4]
|
||||
|
||||
str pairs0 = "abouseitiletstonlonuthnoallexegezacebisousesarmaindirea.eratenbe"
|
||||
|
||||
ubyte[4] goatsoup_rnd = [0, 0, 0, 0]
|
||||
ubyte[4] goatsoup_seed = [0, 0, 0, 0]
|
||||
|
||||
str name = " " ; 8 max
|
||||
ubyte number ; starts at 0 in new galaxy, then increases by 1 for each generated planet
|
||||
ubyte x
|
||||
ubyte y
|
||||
ubyte economy
|
||||
ubyte govtype
|
||||
ubyte techlevel
|
||||
ubyte population
|
||||
uword productivity
|
||||
uword radius
|
||||
bool species_is_alien ; otherwise "Human Colonials"
|
||||
ubyte species_size
|
||||
ubyte species_color
|
||||
ubyte species_look
|
||||
ubyte species_kind
|
||||
|
||||
sub set_seed(uword s1, uword s2) {
|
||||
goatsoup_seed[0] = lsb(s1)
|
||||
goatsoup_seed[1] = msb(s1)
|
||||
goatsoup_seed[2] = lsb(s2)
|
||||
goatsoup_seed[3] = msb(s2)
|
||||
reset_rnd()
|
||||
}
|
||||
|
||||
sub reset_rnd() {
|
||||
goatsoup_rnd[0] = goatsoup_seed[0]
|
||||
goatsoup_rnd[1] = goatsoup_seed[1]
|
||||
goatsoup_rnd[2] = goatsoup_seed[2]
|
||||
goatsoup_rnd[3] = goatsoup_seed[3]
|
||||
}
|
||||
|
||||
sub random_name() -> str {
|
||||
ubyte ii
|
||||
str randname = " " ; 8 chars max
|
||||
ubyte nx = 0
|
||||
for ii in 0 to goatsoup_rnd_number() & 3 {
|
||||
ubyte xx = goatsoup_rnd_number() & $3e
|
||||
if pairs0[xx] != '.' {
|
||||
randname[nx] = pairs0[xx]
|
||||
nx++
|
||||
}
|
||||
xx++
|
||||
if pairs0[xx] != '.' {
|
||||
randname[nx] = pairs0[xx]
|
||||
nx++
|
||||
}
|
||||
}
|
||||
randname[nx] = 0
|
||||
randname[0] = strings.upperchar(randname[0])
|
||||
return randname
|
||||
}
|
||||
|
||||
sub goatsoup_rnd_number() -> ubyte {
|
||||
ubyte xx = goatsoup_rnd[0] * 2
|
||||
uword a = xx as uword + goatsoup_rnd[2]
|
||||
if goatsoup_rnd[0] > 127
|
||||
a ++
|
||||
goatsoup_rnd[0] = lsb(a)
|
||||
goatsoup_rnd[2] = xx
|
||||
xx = goatsoup_rnd[1]
|
||||
ubyte ac = xx + goatsoup_rnd[3] + msb(a)
|
||||
goatsoup_rnd[1] = ac
|
||||
goatsoup_rnd[3] = xx
|
||||
return ac
|
||||
}
|
||||
|
||||
sub distance(ubyte px, ubyte py) -> ubyte {
|
||||
uword ax
|
||||
uword ay
|
||||
if px>x
|
||||
ax=px-x
|
||||
else
|
||||
ax=x-px
|
||||
if py>y
|
||||
ay=py-y
|
||||
else
|
||||
ay=y-py
|
||||
ay /= 2
|
||||
ubyte d = sqrt(ax*ax + ay*ay)
|
||||
if d>63
|
||||
return 255
|
||||
return d*4
|
||||
}
|
||||
|
||||
sub soup() -> str {
|
||||
str planet_result = " " * 160
|
||||
uword[6] source_stack
|
||||
ubyte stack_ptr = 0
|
||||
str start_source = "\x8F is \x97."
|
||||
uword source_ptr = &start_source
|
||||
uword result_ptr = &planet_result
|
||||
|
||||
reset_rnd()
|
||||
recursive_soup()
|
||||
return planet_result
|
||||
|
||||
sub recursive_soup() {
|
||||
repeat {
|
||||
ubyte c = @(source_ptr)
|
||||
source_ptr++
|
||||
if c == $00 {
|
||||
@(result_ptr) = 0
|
||||
return
|
||||
}
|
||||
else if c <= $80 {
|
||||
@(result_ptr) = c
|
||||
result_ptr++
|
||||
}
|
||||
else {
|
||||
if c <= $a4 {
|
||||
ubyte rnr = goatsoup_rnd_number()
|
||||
ubyte wordNr = ((rnr >= $33) as ubyte) + ((rnr >= $66) as ubyte) + ((rnr >= $99) as ubyte) + ((rnr >= $CC) as ubyte)
|
||||
source_stack[stack_ptr] = source_ptr
|
||||
stack_ptr++
|
||||
source_ptr = getword(c, wordNr)
|
||||
recursive_soup() ; RECURSIVE CALL - ignore the warning message from the compiler; we don't use local variables or parameters so we're safe in this case
|
||||
stack_ptr--
|
||||
source_ptr = source_stack[stack_ptr]
|
||||
} else {
|
||||
if c == $b0 {
|
||||
@(result_ptr) = strings.upperchar(name[0])
|
||||
result_ptr++
|
||||
concat_string(&name + 1)
|
||||
}
|
||||
else if c == $b1 {
|
||||
@(result_ptr) = strings.upperchar(name[0])
|
||||
result_ptr++
|
||||
ubyte ni
|
||||
for ni in 1 to len(name) {
|
||||
ubyte cc = name[ni]
|
||||
if cc in ['e', 'o', 0]
|
||||
break
|
||||
else {
|
||||
@(result_ptr) = cc
|
||||
result_ptr++
|
||||
}
|
||||
}
|
||||
@(result_ptr) = 'i'
|
||||
result_ptr++
|
||||
@(result_ptr) = 'a'
|
||||
result_ptr++
|
||||
@(result_ptr) = 'n'
|
||||
result_ptr++
|
||||
}
|
||||
else if c == $b2 {
|
||||
concat_string(random_name())
|
||||
}
|
||||
else {
|
||||
@(result_ptr) = c
|
||||
result_ptr++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub concat_string(uword str_ptr) {
|
||||
repeat {
|
||||
ubyte c = @(str_ptr)
|
||||
if c==0
|
||||
break
|
||||
else {
|
||||
@(result_ptr) = c
|
||||
str_ptr++
|
||||
result_ptr++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub display(bool compressed, ubyte distance) {
|
||||
txt.print(soup())
|
||||
txt.nl()
|
||||
}
|
||||
|
||||
sub getword(ubyte listnum, ubyte wordidx) -> uword {
|
||||
uword list = wordlists[listnum-$81]
|
||||
return peekw(list + wordidx*2)
|
||||
}
|
||||
}
|
||||
|
||||
elite_util {
|
||||
sub prefix_matches(uword prefixptr, uword stringptr) -> bool {
|
||||
repeat {
|
||||
ubyte pc = @(prefixptr)
|
||||
ubyte sc = @(stringptr)
|
||||
if pc == 0
|
||||
return true
|
||||
; to lowercase for case insensitive compare:
|
||||
if strings.lowerchar(pc)!=strings.lowerchar(sc)
|
||||
return false
|
||||
prefixptr++
|
||||
stringptr++
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
|
||||
; This benchmark program is meant to check for regressions in the
|
||||
; Prog8 compiler's code-generator (performance wise).
|
||||
;
|
||||
; As the X16 computer is a more or less fixed system, it's not very useful
|
||||
; to benchmark the computer itself with.
|
||||
;
|
||||
; The individual scores are iteration counts, and they differ by more than an
|
||||
; order of magnitude between benchmarks (maze does ~3000, game of life ~180).
|
||||
; Summing the raw counts therefore let one or two benchmarks dominate the total
|
||||
; and hide regressions in all the others, so the total is a weighted sum - see
|
||||
; benchmark_weight below.
|
||||
|
||||
|
||||
%import textio
|
||||
%import b_adpcm
|
||||
%import b_circles
|
||||
%import b_3d
|
||||
%import b_life
|
||||
%import b_mandelbrot
|
||||
%import b_queens
|
||||
%import b_textelite
|
||||
%import b_maze
|
||||
%import b_sprites
|
||||
%import b_btree
|
||||
|
||||
%zeropage basicsafe
|
||||
%option no_sysinit
|
||||
|
||||
|
||||
main {
|
||||
|
||||
str[20] benchmark_names
|
||||
uword[20] benchmark_score
|
||||
; Weight per benchmark, in the same order as the benchmarks are run below. The total is the
|
||||
; weighted sum of the raw iteration counts.
|
||||
; The raw counts differ by more than an order of magnitude (maze ~3064, game of life ~180), so
|
||||
; in an unweighted sum the two heaviest - maze solver and text-elite - were 57% of the total
|
||||
; while game of life was 2%. A regression in a small benchmark was therefore nearly invisible.
|
||||
; Their weights are scaled down so that every benchmark contributes roughly comparably; the
|
||||
; other eight are left at 256 (i.e. unweighted).
|
||||
uword[10] benchmark_weight = [ 64, 256, 256, 256, 256, 256, 256, 64, 256, 256 ]
|
||||
str version = "14.0"
|
||||
|
||||
|
||||
sub start() {
|
||||
ubyte benchmark_number
|
||||
|
||||
cx16.set_screen_mode(3)
|
||||
txt.color2(1, 6)
|
||||
txt.clear_screen()
|
||||
|
||||
txt.print("\n\n\n prog8 compiler benchmark tests.\n\n benchmark version ")
|
||||
txt.print(version)
|
||||
txt.print("\n\n")
|
||||
sys.wait(120)
|
||||
|
||||
benchmark_number = 0
|
||||
|
||||
announce_benchmark("maze solver")
|
||||
benchmark_score[benchmark_number] = maze.bench(300)
|
||||
benchmark_number++
|
||||
|
||||
announce_benchmark("n-queens")
|
||||
benchmark_score[benchmark_number] = queens.bench(300)
|
||||
benchmark_number++
|
||||
|
||||
announce_benchmark("mandelbrot (floating point)")
|
||||
benchmark_score[benchmark_number] = mandelbrot.calc(400)
|
||||
benchmark_number++
|
||||
|
||||
announce_benchmark("game of life")
|
||||
benchmark_score[benchmark_number] = life.benchmark(300)
|
||||
benchmark_number++
|
||||
|
||||
announce_benchmark("3d model rotation")
|
||||
benchmark_score[benchmark_number] = rotate3d.benchmark(300)
|
||||
benchmark_number++
|
||||
|
||||
announce_benchmark("adpcm audio decoding")
|
||||
benchmark_score[benchmark_number] = adpcm.decode_benchmark(300)
|
||||
benchmark_number++
|
||||
|
||||
announce_benchmark("circles with gfx_lores")
|
||||
benchmark_score[benchmark_number] = circles.draw(false, 400)
|
||||
benchmark_number++
|
||||
|
||||
announce_benchmark("text-elite")
|
||||
benchmark_score[benchmark_number] = textelite.bench(120)
|
||||
benchmark_number++
|
||||
|
||||
announce_benchmark("sprites-coroutines-defer")
|
||||
benchmark_score[benchmark_number] = animsprites.benchmark(300)
|
||||
benchmark_number++
|
||||
|
||||
announce_benchmark("btree-struct-pointers")
|
||||
benchmark_score[benchmark_number] = btree.benchmark(200)
|
||||
benchmark_number++
|
||||
|
||||
benchmark_names[benchmark_number] = 0
|
||||
benchmark_score[benchmark_number] = 0
|
||||
|
||||
cx16.set_screen_mode(3)
|
||||
txt.uppercase()
|
||||
txt.color2(1, 6)
|
||||
uword total_score
|
||||
benchmark_number = 0
|
||||
txt.print("\nscore benchmark (")
|
||||
txt.print(version)
|
||||
txt.print(")\n\n")
|
||||
|
||||
do {
|
||||
; Weighted contribution. Done in long arithmetic on purpose: score*weight overflows a
|
||||
; uword (3064*256 is 784384, which wraps to 63488 and would corrupt the total).
|
||||
uword weighted = ((benchmark_score[benchmark_number] as long) * benchmark_weight[benchmark_number] / 256) as uword
|
||||
txt.spc()
|
||||
txt.print_uw(weighted)
|
||||
txt.column(6)
|
||||
txt.print(benchmark_names[benchmark_number])
|
||||
total_score += weighted
|
||||
txt.nl()
|
||||
benchmark_number++
|
||||
} until benchmark_names[benchmark_number]==0
|
||||
|
||||
txt.print("\n\ntotal score : ")
|
||||
txt.print_uw(total_score)
|
||||
txt.print(" (higher=better)\n")
|
||||
|
||||
sub announce_benchmark(str name) {
|
||||
benchmark_names[benchmark_number] = name
|
||||
cx16.set_screen_mode(3)
|
||||
txt.uppercase()
|
||||
txt.color2(1, 6)
|
||||
txt.clear_screen()
|
||||
txt.plot(4, 6)
|
||||
txt.print(benchmark_names[benchmark_number])
|
||||
txt.nl()
|
||||
sys.wait(60)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.jvm" version "$kotlinVersion" apply false
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmDefaultMode
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "2.4.21"
|
||||
}
|
||||
|
||||
allprojects {
|
||||
apply(plugin="kotlin")
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
maven { url = uri("https://jitpack.io") }
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
freeCompilerArgs = listOf()
|
||||
jvmTarget = JvmTarget.JVM_17
|
||||
jvmDefault = JvmDefaultMode.NO_COMPATIBILITY
|
||||
// languageVersion.set(KotlinVersion.KOTLIN_2_3)
|
||||
}
|
||||
sourceSets.all {
|
||||
languageSettings {
|
||||
// enable language features like so:
|
||||
// enableLanguageFeature(LanguageFeature.WhenGuards.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
java {
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Common Configuration for All Subprojects
|
||||
// ============================================================================
|
||||
// This avoids duplication in each module's build.gradle.kts
|
||||
// ============================================================================
|
||||
|
||||
subprojects {
|
||||
// Common dependency exclusions for all subprojects
|
||||
// Note: antlr4 exclusion is done per-module since parser module needs it
|
||||
configurations.all {
|
||||
exclude(group = "com.ibm.icu", module = "icu4j")
|
||||
}
|
||||
|
||||
// Common test dependencies via Kotest BOM (Bill of Materials)
|
||||
// This manages all Kotest module versions centrally
|
||||
dependencies {
|
||||
testImplementation(platform("io.kotest:kotest-bom:6.2.4"))
|
||||
// implementation("org.codeberg.irmen:ksim65:v2.2")
|
||||
}
|
||||
|
||||
// Common sourceSets configuration
|
||||
sourceSets {
|
||||
main {
|
||||
java.srcDir("${project.projectDir}/src")
|
||||
resources.srcDir("${project.projectDir}/res")
|
||||
}
|
||||
test {
|
||||
java.srcDir("${project.projectDir}/test")
|
||||
}
|
||||
}
|
||||
|
||||
// Common test configuration
|
||||
tasks.withType<Test>().configureEach {
|
||||
// Enable JUnit 5 (required for Kotest)
|
||||
useJUnitPlatform()
|
||||
|
||||
// Forked test JVMs each reserve up to -PtestMaxHeap, so keep the fork count
|
||||
// bounded even on many-core machines to avoid swapping during parallel builds.
|
||||
val testConcurrency = (project.findProperty("testConcurrency")?.toString()?.toIntOrNull()
|
||||
?: (Runtime.getRuntime().availableProcessors() / 2)).coerceIn(1, 4)
|
||||
project.findProperty("testMaxHeap")?.toString()?.let { maxHeapSize = it }
|
||||
|
||||
// Enable concurrent test execution for Kotest
|
||||
// Allow CI to override parallelism for memory-constrained runners
|
||||
jvmArgs("-Dkotest.framework.parallelism=$testConcurrency")
|
||||
|
||||
// Disable Kotest autoscan warning
|
||||
jvmArgs("-Dkotest.framework.classpath.scanning.autoscan.disable=true")
|
||||
|
||||
// Enable Gradle's parallel test execution (runs multiple test classes concurrently)
|
||||
// Use 50% of available processors to avoid over-subscription
|
||||
maxParallelForks = testConcurrency
|
||||
|
||||
// Allow filtering tests via -PtestFilter="*TestName*" from command line
|
||||
// Example: gradle test -PtestFilter="*TestLookup*"
|
||||
val testFilter = project.findProperty("testFilter")?.toString()
|
||||
if(testFilter != null) {
|
||||
filter {
|
||||
includeTestsMatching(testFilter)
|
||||
}
|
||||
}
|
||||
|
||||
// Show test results - only show failures to reduce noise
|
||||
testLogging {
|
||||
events("failed")
|
||||
// Show full exception details for failures
|
||||
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
|
||||
showExceptions = true
|
||||
showCauses = true
|
||||
showStackTraces = true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'application'
|
||||
id "org.jetbrains.kotlin.jvm"
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(javaVersion)
|
||||
}
|
||||
}
|
||||
|
||||
compileKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = javaVersion
|
||||
}
|
||||
}
|
||||
|
||||
compileTestKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = javaVersion
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// should have no dependencies to other modules
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDirs = ["${project.projectDir}/src"]
|
||||
}
|
||||
resources {
|
||||
srcDirs = ["${project.projectDir}/res"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// note: there are no unit tests in this module!
|
||||
@@ -1,11 +0,0 @@
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
`java-test-fixtures`
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// should have no dependencies to other modules
|
||||
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.3.1")
|
||||
|
||||
testImplementation("io.kotest:kotest-runner-junit5")
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -0,0 +1,24 @@
|
||||
package prog8.code
|
||||
|
||||
/**
|
||||
* By convention, the right side of an `Either` is used to hold successful values.
|
||||
*/
|
||||
sealed class Either<out L, out R> {
|
||||
|
||||
data class Left<out L>(val value: L) : Either<L, Nothing>()
|
||||
|
||||
data class Right<out R>(val value: R) : Either<Nothing, R>()
|
||||
|
||||
fun isRight() = this is Right<R>
|
||||
|
||||
fun isLeft() = this is Left<L>
|
||||
|
||||
inline fun <C> fold(ifLeft: (L) -> C, ifRight: (R) -> C): C = when (this) {
|
||||
is Right -> ifRight(value)
|
||||
is Left -> ifLeft(value)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun <L> left(a: L) = Either.Left(a)
|
||||
fun <R> right(b: R) = Either.Right(b)
|
||||
@@ -1,49 +0,0 @@
|
||||
package prog8.code
|
||||
|
||||
import java.io.IOException
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.absolute
|
||||
|
||||
|
||||
const val INTERNED_STRINGS_MODULENAME = "prog8_interned_strings"
|
||||
|
||||
val PROG8_CONTAINER_MODULES = arrayOf(INTERNED_STRINGS_MODULENAME) // option to add more if needed one day
|
||||
|
||||
// all automatically generated labels everywhere need to have the same label name prefix:
|
||||
const val GENERATED_LABEL_PREFIX = "p8_label_gen_"
|
||||
|
||||
|
||||
/**
|
||||
* Returns the absolute path of the given path,
|
||||
* where links are replaced by the actual directories,
|
||||
* and containing no redundant path elements.
|
||||
* If the path doesn't refer to an existing directory or file on the file system,
|
||||
* it is returned unchanged.
|
||||
*/
|
||||
fun Path.sanitize(): Path {
|
||||
return try {
|
||||
this.toRealPath().normalize()
|
||||
} catch (_: java.nio.file.NoSuchFileException) {
|
||||
this.absolute().normalize()
|
||||
//throw NoSuchFileException(this.toFile(), null, nx.reason).also { it.initCause(nx) }
|
||||
} catch (iox: IOException) {
|
||||
throw FileSystemException(this.toFile()).also { it.initCause(iox) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
object SymbolNames {
|
||||
/**
|
||||
* Strips Prog8 symbol prefixes (p8b_, p8t_, p8s_, p8v_, p8l_, p8c_) from a scoped name.
|
||||
* Example: "p8b_plane.p8t_Point" -> "plane.Point"
|
||||
* Used to compare names across different contexts where prefixes may or may not be applied.
|
||||
* See https://github.com/irmen/prog8/issues/198
|
||||
*/
|
||||
fun stripPrefixes(scopedName: String): String {
|
||||
return scopedName.split('.').joinToString(".") { part ->
|
||||
if (part.length > 4 && part.startsWith("p8") && part[2].isLetter() && part[3] == '_')
|
||||
part.drop(4)
|
||||
else part
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
package prog8.code
|
||||
|
||||
import prog8.code.ast.PtNode
|
||||
import prog8.code.ast.PtProgram
|
||||
import prog8.code.core.*
|
||||
|
||||
|
||||
/**
|
||||
* Tree structure containing all symbol definitions in the program
|
||||
* (blocks, subroutines, variables (all types), memoryslabs, and labels).
|
||||
*/
|
||||
class SymbolTable(astProgram: PtProgram) : StNode(astProgram.name, StNodeType.GLOBAL, astProgram) {
|
||||
/**
|
||||
* The table as a flat mapping of scoped names to the StNode.
|
||||
* This gives the fastest lookup possible (no need to traverse tree nodes)
|
||||
*/
|
||||
|
||||
private var cachedFlat: Map<String, StNode>? = null
|
||||
|
||||
val flat: Map<String, StNode> get() {
|
||||
if(cachedFlat!=null)
|
||||
return cachedFlat!!
|
||||
|
||||
val result = mutableMapOf<String, StNode>()
|
||||
fun collect(node: StNode) {
|
||||
for(child in node.children) {
|
||||
result[child.value.scopedName] = child.value
|
||||
collect(child.value)
|
||||
}
|
||||
}
|
||||
collect(this)
|
||||
cachedFlat = result
|
||||
return result
|
||||
}
|
||||
|
||||
fun resetCachedFlat() {
|
||||
cachedFlat = null
|
||||
}
|
||||
|
||||
val allVariables: Collection<StStaticVariable> by lazy {
|
||||
val vars = mutableListOf<StStaticVariable>()
|
||||
fun collect(node: StNode) {
|
||||
for(child in node.children) {
|
||||
if(child.value.type== StNodeType.STATICVAR)
|
||||
vars.add(child.value as StStaticVariable)
|
||||
else
|
||||
collect(child.value)
|
||||
}
|
||||
}
|
||||
collect(this)
|
||||
vars
|
||||
}
|
||||
|
||||
val allMemMappedVariables: Collection<StMemVar> by lazy {
|
||||
val vars = mutableListOf<StMemVar>()
|
||||
fun collect(node: StNode) {
|
||||
for(child in node.children) {
|
||||
if(child.value.type== StNodeType.MEMVAR)
|
||||
vars.add(child.value as StMemVar)
|
||||
else
|
||||
collect(child.value)
|
||||
}
|
||||
}
|
||||
collect(this)
|
||||
vars
|
||||
}
|
||||
|
||||
val allMemorySlabs: Collection<StMemorySlab> by lazy {
|
||||
val vars = mutableListOf<StMemorySlab>()
|
||||
fun collect(node: StNode) {
|
||||
for(child in node.children) {
|
||||
if(child.value.type== StNodeType.MEMORYSLAB)
|
||||
vars.add(child.value as StMemorySlab)
|
||||
else
|
||||
collect(child.value)
|
||||
}
|
||||
}
|
||||
collect(this)
|
||||
vars
|
||||
}
|
||||
|
||||
override fun lookup(scopedName: String) = flat[scopedName]
|
||||
}
|
||||
|
||||
|
||||
enum class StNodeType {
|
||||
GLOBAL,
|
||||
// MODULE, // not used with current scoping rules
|
||||
BLOCK,
|
||||
SUBROUTINE,
|
||||
ROMSUB,
|
||||
LABEL,
|
||||
STATICVAR,
|
||||
MEMVAR,
|
||||
CONSTANT,
|
||||
BUILTINFUNC,
|
||||
MEMORYSLAB
|
||||
}
|
||||
|
||||
|
||||
open class StNode(val name: String,
|
||||
val type: StNodeType,
|
||||
val astNode: PtNode,
|
||||
val children: MutableMap<String, StNode> = mutableMapOf()
|
||||
) {
|
||||
|
||||
lateinit var parent: StNode
|
||||
|
||||
val scopedName: String by lazy { scopedNameList.joinToString(".") }
|
||||
|
||||
open fun lookup(scopedName: String) =
|
||||
lookup(scopedName.split('.'))
|
||||
|
||||
fun lookupUnscopedOrElse(name: String, default: () -> StNode) =
|
||||
lookupUnscoped(name) ?: default()
|
||||
|
||||
fun lookupOrElse(scopedName: String, default: () -> StNode): StNode =
|
||||
lookup(scopedName.split('.')) ?: default()
|
||||
|
||||
fun lookupUnscoped(name: String): StNode? {
|
||||
// first consider the builtin functions
|
||||
var globalscope = this
|
||||
while(globalscope.type!= StNodeType.GLOBAL)
|
||||
globalscope = globalscope.parent
|
||||
val globalNode = globalscope.children[name]
|
||||
if(globalNode!=null && globalNode.type== StNodeType.BUILTINFUNC)
|
||||
return globalNode
|
||||
|
||||
// search for the unqualified name in the current scope or its parent scopes
|
||||
var scope=this
|
||||
while(true) {
|
||||
val node = scope.children[name]
|
||||
if(node!=null)
|
||||
return node
|
||||
if(scope.type== StNodeType.GLOBAL)
|
||||
return null
|
||||
else
|
||||
scope = scope.parent
|
||||
}
|
||||
}
|
||||
|
||||
fun add(child: StNode) {
|
||||
children[child.name] = child
|
||||
child.parent = this
|
||||
}
|
||||
|
||||
private val scopedNameList: List<String> by lazy {
|
||||
if(type==StNodeType.GLOBAL)
|
||||
emptyList()
|
||||
else
|
||||
parent.scopedNameList + name
|
||||
}
|
||||
|
||||
private fun lookup(scopedName: List<String>): StNode? {
|
||||
// a scoped name refers to a name in another namespace, and always stars from the root.
|
||||
var node = this
|
||||
while(node.type!=StNodeType.GLOBAL)
|
||||
node = node.parent
|
||||
|
||||
for(name in scopedName) {
|
||||
if(name in node.children)
|
||||
node = node.children.getValue(name)
|
||||
else
|
||||
return null
|
||||
}
|
||||
return node
|
||||
}
|
||||
}
|
||||
|
||||
class StStaticVariable(name: String,
|
||||
val dt: DataType,
|
||||
val onetimeInitializationNumericValue: Double?, // regular (every-run-time) initialization is done via regular assignments
|
||||
val onetimeInitializationStringValue: StString?,
|
||||
val onetimeInitializationArrayValue: StArray?,
|
||||
val length: Int?, // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte
|
||||
val zpwish: ZeropageWish, // used in the variable allocator
|
||||
astNode: PtNode) : StNode(name, StNodeType.STATICVAR, astNode) {
|
||||
|
||||
val uninitialized = onetimeInitializationArrayValue==null && onetimeInitializationStringValue==null && onetimeInitializationNumericValue==null
|
||||
|
||||
init {
|
||||
if(length!=null) {
|
||||
require(onetimeInitializationNumericValue == null)
|
||||
if(onetimeInitializationArrayValue!=null)
|
||||
require(onetimeInitializationArrayValue.isEmpty() ||onetimeInitializationArrayValue.size==length)
|
||||
}
|
||||
if(onetimeInitializationNumericValue!=null) {
|
||||
require(dt in NumericDatatypes)
|
||||
require(onetimeInitializationNumericValue!=0.0) { "zero as init value should just remain uninitialized"}
|
||||
}
|
||||
if(onetimeInitializationArrayValue!=null) {
|
||||
require(dt in ArrayDatatypes)
|
||||
if(onetimeInitializationArrayValue.all { it.number!=null} ) {
|
||||
require(onetimeInitializationArrayValue.any { it.number != 0.0 }) { "array of all zerors as init value should just remain uninitialized" }
|
||||
}
|
||||
}
|
||||
if(onetimeInitializationStringValue!=null) {
|
||||
require(dt == DataType.STR)
|
||||
require(length == onetimeInitializationStringValue.first.length+1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class StConstant(name: String, val dt: DataType, val value: Double, astNode: PtNode) :
|
||||
StNode(name, StNodeType.CONSTANT, astNode) {
|
||||
}
|
||||
|
||||
|
||||
class StMemVar(name: String,
|
||||
val dt: DataType,
|
||||
val address: UInt,
|
||||
val length: Int?, // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte
|
||||
astNode: PtNode) :
|
||||
StNode(name, StNodeType.MEMVAR, astNode) {
|
||||
|
||||
init{
|
||||
if(dt in ArrayDatatypes || dt == DataType.STR)
|
||||
require(length!=null) { "memory mapped array or string must have known length" }
|
||||
}
|
||||
}
|
||||
|
||||
class StMemorySlab(
|
||||
name: String,
|
||||
val size: UInt,
|
||||
val align: UInt,
|
||||
astNode: PtNode
|
||||
):
|
||||
StNode(name, StNodeType.MEMORYSLAB, astNode) {
|
||||
}
|
||||
|
||||
class StSub(name: String, val parameters: List<StSubroutineParameter>, val returnType: DataType?, astNode: PtNode) :
|
||||
StNode(name, StNodeType.SUBROUTINE, astNode) {
|
||||
}
|
||||
|
||||
|
||||
class StRomSub(name: String,
|
||||
val address: UInt?, // null in case of asmsub, specified in case of romsub
|
||||
val parameters: List<StRomSubParameter>,
|
||||
val returns: List<StRomSubParameter>,
|
||||
astNode: PtNode) :
|
||||
StNode(name, StNodeType.ROMSUB, astNode)
|
||||
|
||||
|
||||
|
||||
class StSubroutineParameter(val name: String, val type: DataType)
|
||||
class StRomSubParameter(val register: RegisterOrStatusflag, val type: DataType)
|
||||
class StArrayElement(val number: Double?, val addressOfSymbol: String?)
|
||||
|
||||
typealias StString = Pair<String, Encoding>
|
||||
typealias StArray = List<StArrayElement>
|
||||
@@ -0,0 +1,202 @@
|
||||
package prog8.code
|
||||
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.VMTarget
|
||||
import java.util.*
|
||||
|
||||
class SymbolTableMaker(private val program: PtProgram, private val options: CompilationOptions) {
|
||||
fun make(): SymbolTable {
|
||||
val st = SymbolTable(program)
|
||||
|
||||
BuiltinFunctions.forEach {
|
||||
st.add(StNode(it.key, StNodeType.BUILTINFUNC, PtIdentifier(it.key, it.value.returnType ?: DataType.UNDEFINED, Position.DUMMY)))
|
||||
}
|
||||
|
||||
val scopestack = Stack<StNode>()
|
||||
scopestack.push(st)
|
||||
program.children.forEach {
|
||||
addToSt(it, scopestack)
|
||||
}
|
||||
require(scopestack.size==1)
|
||||
|
||||
if(options.compTarget.name != VMTarget.NAME) {
|
||||
listOf(
|
||||
PtMemMapped("P8ZP_SCRATCH_B1", DataType.UBYTE, options.compTarget.machine.zeropage.SCRATCH_B1, null, Position.DUMMY),
|
||||
PtMemMapped("P8ZP_SCRATCH_REG", DataType.UBYTE, options.compTarget.machine.zeropage.SCRATCH_REG, null, Position.DUMMY),
|
||||
PtMemMapped("P8ZP_SCRATCH_W1", DataType.UWORD, options.compTarget.machine.zeropage.SCRATCH_W1, null, Position.DUMMY),
|
||||
PtMemMapped("P8ZP_SCRATCH_W2", DataType.UWORD, options.compTarget.machine.zeropage.SCRATCH_W2, null, Position.DUMMY),
|
||||
PtMemMapped("P8ESTACK_LO", DataType.ARRAY_UB, options.compTarget.machine.ESTACK_LO, 128u, Position.DUMMY),
|
||||
PtMemMapped("P8ESTACK_HI", DataType.ARRAY_UB, options.compTarget.machine.ESTACK_HI, 128u, Position.DUMMY)
|
||||
).forEach {
|
||||
it.parent = program
|
||||
st.add(StMemVar(it.name, it.type, it.address, it.arraySize?.toInt(), it))
|
||||
}
|
||||
}
|
||||
|
||||
return st
|
||||
}
|
||||
|
||||
private fun addToSt(node: PtNode, scope: Stack<StNode>) {
|
||||
val stNode = when(node) {
|
||||
is PtAsmSub -> {
|
||||
val parameters = node.parameters.map { StRomSubParameter(it.first, it.second.type) }
|
||||
val returns = node.returns.map { StRomSubParameter(it.first, it.second) }
|
||||
StRomSub(node.name, node.address, parameters, returns, node)
|
||||
}
|
||||
is PtBlock -> {
|
||||
StNode(node.name, StNodeType.BLOCK, node)
|
||||
}
|
||||
is PtConstant -> {
|
||||
StConstant(node.name, node.type, node.value, node)
|
||||
}
|
||||
is PtLabel -> {
|
||||
StNode(node.name, StNodeType.LABEL, node)
|
||||
}
|
||||
is PtMemMapped -> {
|
||||
StMemVar(node.name, node.type, node.address, node.arraySize?.toInt(), node)
|
||||
}
|
||||
is PtSub -> {
|
||||
val params = node.parameters.map {StSubroutineParameter(it.name, it.type) }
|
||||
StSub(node.name, params, node.returntype, node)
|
||||
}
|
||||
is PtVariable -> {
|
||||
val initialNumeric: Double?
|
||||
val initialString: StString?
|
||||
val initialArray: StArray?
|
||||
val numElements: Int?
|
||||
val value = node.value
|
||||
if(value!=null) {
|
||||
val number = (value as? PtNumber)?.number
|
||||
initialNumeric = if(number==0.0) null else number // 0 as init value -> just uninitialized
|
||||
when (value) {
|
||||
is PtString -> {
|
||||
initialString = StString(value.value, value.encoding)
|
||||
initialArray = null
|
||||
numElements = value.value.length + 1 // include the terminating 0-byte
|
||||
}
|
||||
is PtArray -> {
|
||||
val array = makeInitialArray(value)
|
||||
initialArray = if(array.all { it.number==0.0 }) null else array // all 0 as init value -> just uninitialized
|
||||
initialString = null
|
||||
numElements = array.size
|
||||
require(node.arraySize?.toInt()==numElements)
|
||||
}
|
||||
else -> {
|
||||
initialString = null
|
||||
initialArray = null
|
||||
numElements = node.arraySize?.toInt()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
initialNumeric = null
|
||||
initialArray = null
|
||||
initialString = null
|
||||
numElements = node.arraySize?.toInt()
|
||||
}
|
||||
StStaticVariable(node.name, node.type, initialNumeric, initialString, initialArray, numElements, node.zeropage, node)
|
||||
}
|
||||
is PtBuiltinFunctionCall -> {
|
||||
if(node.name=="memory") {
|
||||
// memory slab allocations are a builtin functioncall in the program, but end up named as well in the symboltable
|
||||
require(node.name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name"}
|
||||
val slabname = (node.args[0] as PtString).value
|
||||
val size = (node.args[1] as PtNumber).number.toUInt()
|
||||
val align = (node.args[2] as PtNumber).number.toUInt()
|
||||
// don't add memory slabs in nested scope, just put them in the top level of the ST
|
||||
scope.firstElement().add(StMemorySlab("prog8_memoryslab_$slabname", size, align, node))
|
||||
}
|
||||
null
|
||||
}
|
||||
else -> null // node is not present in the ST
|
||||
}
|
||||
|
||||
if(stNode!=null) {
|
||||
scope.peek().add(stNode)
|
||||
scope.push(stNode)
|
||||
}
|
||||
node.children.forEach {
|
||||
addToSt(it, scope)
|
||||
}
|
||||
if(stNode!=null)
|
||||
scope.pop()
|
||||
}
|
||||
|
||||
private fun makeInitialArray(value: PtArray): List<StArrayElement> {
|
||||
return value.children.map {
|
||||
when(it) {
|
||||
is PtAddressOf -> StArrayElement(null, it.identifier.name)
|
||||
is PtIdentifier -> StArrayElement(null, it.name)
|
||||
is PtNumber -> StArrayElement(it.number, null)
|
||||
else -> throw AssemblyError("invalid array element $it")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// override fun visit(decl: VarDecl) {
|
||||
// val node =
|
||||
// when(decl.type) {
|
||||
// VarDeclType.VAR -> {
|
||||
// var initialNumeric = (decl.value as? NumericLiteral)?.number
|
||||
// if(initialNumeric==0.0)
|
||||
// initialNumeric=null // variable will go into BSS and this will be set to 0
|
||||
// val initialStringLit = decl.value as? StringLiteral
|
||||
// val initialString = if(initialStringLit==null) null else Pair(initialStringLit.value, initialStringLit.encoding)
|
||||
// val initialArrayLit = decl.value as? ArrayLiteral
|
||||
// val initialArray = makeInitialArray(initialArrayLit)
|
||||
// if(decl.isArray && decl.datatype !in ArrayDatatypes)
|
||||
// throw FatalAstException("array vardecl has mismatched dt ${decl.datatype}")
|
||||
// val numElements =
|
||||
// if(decl.isArray)
|
||||
// decl.arraysize!!.constIndex()
|
||||
// else if(initialStringLit!=null)
|
||||
// initialStringLit.value.length+1 // include the terminating 0-byte
|
||||
// else
|
||||
// null
|
||||
// val bss = if(decl.datatype==DataType.STR)
|
||||
// false
|
||||
// else if(decl.isArray)
|
||||
// initialArray.isNullOrEmpty()
|
||||
// else
|
||||
// initialNumeric == null
|
||||
// val astNode = PtVariable(decl.name, decl.datatype, null, null, decl.position)
|
||||
// StStaticVariable(decl.name, decl.datatype, bss, initialNumeric, initialString, initialArray, numElements, decl.zeropage, astNode, decl.position)
|
||||
// }
|
||||
// VarDeclType.CONST -> {
|
||||
// val astNode = PtVariable(decl.name, decl.datatype, null, null, decl.position)
|
||||
// StConstant(decl.name, decl.datatype, (decl.value as NumericLiteral).number, astNode, decl.position)
|
||||
// }
|
||||
// VarDeclType.MEMORY -> {
|
||||
// val numElements =
|
||||
// if(decl.datatype in ArrayDatatypes)
|
||||
// decl.arraysize!!.constIndex()
|
||||
// else null
|
||||
// val astNode = PtVariable(decl.name, decl.datatype, null, null, decl.position)
|
||||
// StMemVar(decl.name, decl.datatype, (decl.value as NumericLiteral).number.toUInt(), numElements, astNode, decl.position)
|
||||
// }
|
||||
// }
|
||||
// scopestack.peek().add(node)
|
||||
// // st.origAstLinks[decl] = node
|
||||
// }
|
||||
//
|
||||
// private fun makeInitialArray(arrayLit: ArrayLiteral?): StArray? {
|
||||
// if(arrayLit==null)
|
||||
// return null
|
||||
// return arrayLit.value.map {
|
||||
// when(it){
|
||||
// is AddressOf -> {
|
||||
// val scopedName = it.identifier.targetNameAndType(program).first
|
||||
// StArrayElement(null, scopedName)
|
||||
// }
|
||||
// is IdentifierReference -> {
|
||||
// val scopedName = it.targetNameAndType(program).first
|
||||
// StArrayElement(null, scopedName)
|
||||
// }
|
||||
// is NumericLiteral -> StArrayElement(it.number, null)
|
||||
// else -> throw FatalAstException("weird element dt in array literal")
|
||||
// }
|
||||
// }.toList()
|
||||
// }
|
||||
//
|
||||
@@ -1,187 +0,0 @@
|
||||
package prog8.code.assembly
|
||||
|
||||
import prog8.code.GENERATED_LABEL_PREFIX
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.C128Target
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.code.target.PETTarget
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
class AssemblyProgram6502(
|
||||
override val name: String,
|
||||
private val options: CompilationOptions,
|
||||
private val compTarget: ICompilationTarget) : IAssemblyProgram {
|
||||
|
||||
override val irInstructionCount: Int = 0
|
||||
override val irChunkCount: Int = 0
|
||||
override val irRegisterCount: Int = 0
|
||||
|
||||
override fun assemble(options: CompilationOptions, errors: IErrorReporter): Boolean {
|
||||
|
||||
val assemblerCommand: List<String>
|
||||
val outs = OutputFiles.of(options, name)
|
||||
|
||||
fun addRemainingOptions(command: MutableList<String>, program: Path, assembly: Path): List<String> {
|
||||
// keep additionalAssemblerOptions BEFORE --output: a target config may contain its own --output
|
||||
if(options.compTarget.additionalAssemblerOptions.isNotEmpty())
|
||||
command.addAll(options.compTarget.additionalAssemblerOptions)
|
||||
|
||||
command.addAll(listOf("--output", program.toString(), assembly.toString()))
|
||||
return command
|
||||
}
|
||||
|
||||
when(options.output) {
|
||||
OutputType.PRG -> {
|
||||
// CBM machines .prg generation.
|
||||
|
||||
val command = mutableListOf("64tass", "--cbm-prg", "--ascii", "--case-sensitive", "--long-branch",
|
||||
"-Wall", "-Wno-implied-reg", "--no-monitor", "--dump-labels", "--vice-labels", "--labels=${outs.viceMonList()}")
|
||||
|
||||
if(options.warnSymbolShadowing)
|
||||
command.add("-Wshadow")
|
||||
else
|
||||
command.add("-Wno-shadow")
|
||||
|
||||
if(options.asmQuiet)
|
||||
command.add("--quiet")
|
||||
|
||||
if(options.asmListfile) {
|
||||
command.add("--list=${outs.list()}")
|
||||
}
|
||||
|
||||
assemblerCommand = addRemainingOptions(command, outs.programFile, outs.asm())
|
||||
if(!options.quiet)
|
||||
println("\nCreating prg for target ${compTarget.name}.")
|
||||
}
|
||||
OutputType.XEX -> {
|
||||
// Atari800XL .xex generation.
|
||||
|
||||
val command = mutableListOf("64tass", "--atari-xex", "--case-sensitive", "--long-branch",
|
||||
"-Wall", "-Wno-implied-reg", "--no-monitor", "--dump-labels", "--vice-labels", "--labels=${outs.viceMonList()}")
|
||||
|
||||
if(options.warnSymbolShadowing)
|
||||
command.add("-Wshadow")
|
||||
else
|
||||
command.add("-Wno-shadow")
|
||||
|
||||
if(options.asmQuiet)
|
||||
command.add("--quiet")
|
||||
|
||||
if(options.asmListfile)
|
||||
command.add("--list=${outs.list()}")
|
||||
|
||||
assemblerCommand = addRemainingOptions(command, outs.programFile, outs.asm())
|
||||
if(!options.quiet)
|
||||
println("\nCreating xex for target ${compTarget.name}.")
|
||||
}
|
||||
OutputType.RAW -> {
|
||||
// Neo6502/headerless raw program generation.
|
||||
val command = mutableListOf("64tass", "--nostart", "--case-sensitive", "--long-branch",
|
||||
"-Wall", "-Wno-implied-reg", "--no-monitor", "--dump-labels", "--vice-labels", "--labels=${outs.viceMonList()}")
|
||||
|
||||
if(options.warnSymbolShadowing)
|
||||
command.add("-Wshadow")
|
||||
else
|
||||
command.add("-Wno-shadow")
|
||||
|
||||
if(options.asmQuiet)
|
||||
command.add("--quiet")
|
||||
|
||||
if(options.asmListfile)
|
||||
command.add("--list=${outs.list()}")
|
||||
|
||||
assemblerCommand = addRemainingOptions(command, outs.programFile, outs.asm())
|
||||
if(!options.quiet)
|
||||
println("\nCreating raw binary for target ${compTarget.name}.")
|
||||
}
|
||||
OutputType.LIBRARY -> {
|
||||
// CBM machines library (.bin) generation (with or without 2 byte load address header depending on the compilation target machine)
|
||||
|
||||
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
|
||||
"-Wall", "-Wno-implied-reg", "--no-monitor", "--dump-labels", "--vice-labels", "--labels=${outs.viceMonList()}")
|
||||
|
||||
if(options.warnSymbolShadowing)
|
||||
command.add("-Wshadow")
|
||||
else
|
||||
command.add("-Wno-shadow")
|
||||
|
||||
if(options.asmQuiet)
|
||||
command.add("--quiet")
|
||||
|
||||
if(options.asmListfile)
|
||||
command.add("--list=${outs.list()}")
|
||||
|
||||
if(compTarget.name in listOf(C64Target.NAME, C128Target.NAME, PETTarget.NAME)) {
|
||||
if(!options.quiet)
|
||||
println("\nCreating binary library file with header for target ${compTarget.name}.")
|
||||
command.add("--cbm-prg")
|
||||
} else {
|
||||
if(!options.quiet)
|
||||
println("\nCreating binary library file without header for target ${compTarget.name}.")
|
||||
command.add("--nostart") // should be headerless bin, because basic has problems doing a normal LOAD"lib",8,1 - need to use BLOAD
|
||||
}
|
||||
|
||||
assemblerCommand = addRemainingOptions(command, outs.programFile, outs.asm())
|
||||
}
|
||||
else -> error("Unsupported output type: ${compTarget.defaultOutputType}")
|
||||
}
|
||||
|
||||
val proc = ProcessBuilder(assemblerCommand)
|
||||
.redirectErrorStream(true)
|
||||
|
||||
if (options.quiet) {
|
||||
proc.redirectOutput(ProcessBuilder.Redirect.DISCARD)
|
||||
}
|
||||
|
||||
val process = try {
|
||||
proc.start()
|
||||
} catch (e: Exception) {
|
||||
System.err.println("daemon: assembler failed to start: ${e.message}")
|
||||
return false
|
||||
}
|
||||
|
||||
if (!options.quiet) {
|
||||
process.inputStream.bufferedReader().use { reader ->
|
||||
reader.forEachLine {
|
||||
println(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val result = process.waitFor()
|
||||
if (result == 0) {
|
||||
removeGeneratedLabelsFromMonlist(outs)
|
||||
generateBreakpointList(outs)
|
||||
}
|
||||
return result==0
|
||||
}
|
||||
|
||||
private fun removeGeneratedLabelsFromMonlist(outs: OutputFiles) {
|
||||
val pattern = Regex("""al (\w+) \S+$GENERATED_LABEL_PREFIX.+?""")
|
||||
val lines = outs.viceMonList().toFile().readLines()
|
||||
outs.viceMonList().toFile().outputStream().bufferedWriter().use {
|
||||
for (line in lines) {
|
||||
if(pattern.matchEntire(line)==null)
|
||||
it.write(line+"\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateBreakpointList(outs: OutputFiles) {
|
||||
// builds list of breakpoints, appends to monitor list file
|
||||
val breakpoints = mutableListOf<String>()
|
||||
val pattern = Regex("""al (\w+) \S+_prog8_breakpoint_\d+.?""") // gather breakpoints by the source label that's generated for them
|
||||
for (line in outs.viceMonList().toFile().readLines()) {
|
||||
val match = pattern.matchEntire(line)
|
||||
if (match != null)
|
||||
breakpoints.add("break $" + match.groupValues[1])
|
||||
}
|
||||
val num = breakpoints.size
|
||||
breakpoints.add(0, "; breakpoint list now follows")
|
||||
breakpoints.add(1, "; $num breakpoints have been defined")
|
||||
breakpoints.add(2, "del")
|
||||
outs.viceMonList().toFile().appendText(breakpoints.joinToString("\n") + "\n")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
package prog8.code.assembly
|
||||
|
||||
import prog8.code.core.CompilationOptions
|
||||
import prog8.code.core.IErrorReporter
|
||||
|
||||
interface IAssemblyProgram {
|
||||
val name: String
|
||||
val irInstructionCount: Int
|
||||
val irChunkCount: Int
|
||||
val irRegisterCount: Int
|
||||
fun assemble(options: CompilationOptions, errors: IErrorReporter): Boolean
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package prog8.code.ast
|
||||
|
||||
import prog8.code.core.IMemSizer
|
||||
import prog8.code.core.IStringEncoding
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.core.SourceCode
|
||||
import java.nio.file.Path
|
||||
|
||||
// New simplified AST for the code generator.
|
||||
|
||||
|
||||
sealed class PtNode(val position: Position) {
|
||||
|
||||
val children = mutableListOf<PtNode>()
|
||||
lateinit var parent: PtNode
|
||||
|
||||
fun add(child: PtNode) {
|
||||
children.add(child)
|
||||
child.parent = this
|
||||
}
|
||||
|
||||
fun add(index: Int, child: PtNode) {
|
||||
children.add(index, child)
|
||||
child.parent = this
|
||||
}
|
||||
|
||||
fun definingBlock() = findParentNode<PtBlock>(this)
|
||||
fun definingSub() = findParentNode<PtSub>(this)
|
||||
fun definingAsmSub() = findParentNode<PtAsmSub>(this)
|
||||
fun definingISub() = findParentNode<IPtSubroutine>(this)
|
||||
}
|
||||
|
||||
|
||||
class PtNodeGroup : PtNode(Position.DUMMY)
|
||||
|
||||
|
||||
sealed class PtNamedNode(var name: String, position: Position): PtNode(position) {
|
||||
// Note that as an exception, the 'name' is not read-only
|
||||
// but a var. This is to allow for cheap node renames.
|
||||
val scopedName: String by lazy {
|
||||
var namedParent: PtNode = this.parent
|
||||
if(namedParent is PtProgram)
|
||||
name
|
||||
else {
|
||||
while (namedParent !is PtNamedNode)
|
||||
namedParent = namedParent.parent
|
||||
namedParent.scopedName + "." + name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class PtProgram(
|
||||
val name: String,
|
||||
val memsizer: IMemSizer,
|
||||
val encoding: IStringEncoding
|
||||
) : PtNode(Position.DUMMY) {
|
||||
|
||||
// fun allModuleDirectives(): Sequence<PtDirective> =
|
||||
// children.asSequence().flatMap { it.children }.filterIsInstance<PtDirective>().distinct()
|
||||
|
||||
fun allBlocks(): Sequence<PtBlock> =
|
||||
children.asSequence().filterIsInstance<PtBlock>()
|
||||
|
||||
fun entrypoint(): PtSub? =
|
||||
allBlocks().firstOrNull { it.name == "main" }?.children?.firstOrNull { it is PtSub && (it.name == "start" || it.name=="main.start") } as PtSub?
|
||||
}
|
||||
|
||||
|
||||
class PtBlock(name: String,
|
||||
val address: UInt?,
|
||||
val library: Boolean,
|
||||
val forceOutput: Boolean,
|
||||
val alignment: BlockAlignment,
|
||||
val source: SourceCode, // taken from the module the block is defined in.
|
||||
position: Position
|
||||
) : PtNamedNode(name, position) {
|
||||
enum class BlockAlignment {
|
||||
NONE,
|
||||
WORD,
|
||||
PAGE
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class PtInlineAssembly(val assembly: String, val isIR: Boolean, position: Position) : PtNode(position) {
|
||||
init {
|
||||
require(!assembly.startsWith('\n') && !assembly.startsWith('\r')) { "inline assembly should be trimmed" }
|
||||
require(!assembly.endsWith('\n') && !assembly.endsWith('\r')) { "inline assembly should be trimmed" }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class PtLabel(name: String, position: Position) : PtNamedNode(name, position)
|
||||
|
||||
|
||||
class PtBreakpoint(position: Position): PtNode(position)
|
||||
|
||||
|
||||
class PtIncludeBinary(val file: Path, val offset: UInt?, val length: UInt?, position: Position) : PtNode(position)
|
||||
|
||||
|
||||
class PtNop(position: Position): PtNode(position)
|
||||
|
||||
|
||||
// find the parent node of a specific type or interface
|
||||
// (useful to figure out in what namespace/block something is defined, etc.)
|
||||
inline fun <reified T> findParentNode(node: PtNode): T? {
|
||||
var candidate = node.parent
|
||||
while(candidate !is T && candidate !is PtProgram)
|
||||
candidate = candidate.parent
|
||||
return if(candidate is PtProgram)
|
||||
null
|
||||
else
|
||||
candidate as T
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
package prog8.code.ast
|
||||
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.Encoding
|
||||
import prog8.code.core.NumericDatatypes
|
||||
import prog8.code.core.Position
|
||||
import java.util.*
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.round
|
||||
|
||||
|
||||
sealed class PtExpression(val type: DataType, position: Position) : PtNode(position) {
|
||||
|
||||
init {
|
||||
if(type==DataType.BOOL)
|
||||
throw IllegalArgumentException("bool should have become ubyte @$position")
|
||||
if(type==DataType.UNDEFINED) {
|
||||
@Suppress("LeakingThis")
|
||||
when(this) {
|
||||
is PtBuiltinFunctionCall -> { /* void function call */ }
|
||||
is PtFunctionCall -> { /* void function call */ }
|
||||
is PtIdentifier -> { /* non-variable identifier */ }
|
||||
else -> throw IllegalArgumentException("type should be known @$position")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
infix fun isSameAs(other: PtExpression): Boolean {
|
||||
return when(this) {
|
||||
is PtAddressOf -> other is PtAddressOf && other.type==type && other.identifier isSameAs identifier
|
||||
is PtArrayIndexer -> other is PtArrayIndexer && other.type==type && other.variable isSameAs variable && other.index isSameAs index
|
||||
is PtBinaryExpression -> other is PtBinaryExpression && other.left isSameAs left && other.right isSameAs right
|
||||
is PtContainmentCheck -> other is PtContainmentCheck && other.type==type && other.element isSameAs element && other.iterable isSameAs iterable
|
||||
is PtIdentifier -> other is PtIdentifier && other.type==type && other.name==name
|
||||
is PtMachineRegister -> other is PtMachineRegister && other.type==type && other.register==register
|
||||
is PtMemoryByte -> other is PtMemoryByte && other.address isSameAs address
|
||||
is PtNumber -> other is PtNumber && other.type==type && other.number==number
|
||||
is PtPrefix -> other is PtPrefix && other.type==type && other.operator==operator && other.value isSameAs value
|
||||
is PtRange -> other is PtRange && other.type==type && other.from==from && other.to==to && other.step==step
|
||||
is PtTypeCast -> other is PtTypeCast && other.type==type && other.value isSameAs value
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
infix fun isSameAs(target: PtAssignTarget): Boolean {
|
||||
return when {
|
||||
target.memory != null && this is PtMemoryByte-> {
|
||||
target.memory!!.address isSameAs this.address
|
||||
}
|
||||
target.identifier != null && this is PtIdentifier -> {
|
||||
this.name == target.identifier!!.name
|
||||
}
|
||||
target.array != null && this is PtArrayIndexer -> {
|
||||
this.variable.name == target.array!!.variable.name && this.index isSameAs target.array!!.index
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
fun asConstInteger(): Int? = (this as? PtNumber)?.number?.toInt()
|
||||
|
||||
fun isSimple(): Boolean {
|
||||
return when(this) {
|
||||
is PtAddressOf -> true
|
||||
is PtArray -> true
|
||||
is PtArrayIndexer -> index is PtNumber || index is PtIdentifier
|
||||
is PtBinaryExpression -> false
|
||||
is PtBuiltinFunctionCall -> name in arrayOf("msb", "lsb", "peek", "peekw", "mkword", "set_carry", "set_irqd", "clear_carry", "clear_irqd")
|
||||
is PtContainmentCheck -> false
|
||||
is PtFunctionCall -> false
|
||||
is PtIdentifier -> true
|
||||
is PtMachineRegister -> true
|
||||
is PtMemoryByte -> address is PtNumber || address is PtIdentifier
|
||||
is PtNumber -> true
|
||||
is PtPrefix -> value.isSimple()
|
||||
is PtRange -> true
|
||||
is PtString -> true
|
||||
is PtTypeCast -> value.isSimple()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
fun clone(): PtExpression {
|
||||
fun withClonedChildrenFrom(orig: PtExpression, clone: PtExpression): PtExpression {
|
||||
orig.children.forEach { clone.add((it as PtExpression).clone()) }
|
||||
return clone
|
||||
}
|
||||
when(this) {
|
||||
is PtAddressOf -> return withClonedChildrenFrom(this, PtAddressOf(position))
|
||||
is PtArray -> return withClonedChildrenFrom(this, PtArray(type, position))
|
||||
is PtArrayIndexer -> return withClonedChildrenFrom(this, PtArrayIndexer(type, position))
|
||||
is PtBinaryExpression -> return withClonedChildrenFrom(this, PtBinaryExpression(operator, type, position))
|
||||
is PtBuiltinFunctionCall -> return withClonedChildrenFrom(this, PtBuiltinFunctionCall(name, void, hasNoSideEffects, type, position))
|
||||
is PtContainmentCheck -> return withClonedChildrenFrom(this, PtContainmentCheck(position))
|
||||
is PtFunctionCall -> return withClonedChildrenFrom(this, PtFunctionCall(name, void, type, position))
|
||||
is PtIdentifier -> return withClonedChildrenFrom(this, PtIdentifier(name, type, position))
|
||||
is PtMachineRegister -> return withClonedChildrenFrom(this, PtMachineRegister(register, type, position))
|
||||
is PtMemoryByte -> return withClonedChildrenFrom(this, PtMemoryByte(position))
|
||||
is PtNumber -> return withClonedChildrenFrom(this, PtNumber(type, number, position))
|
||||
is PtPrefix -> return withClonedChildrenFrom(this, PtPrefix(operator, type, position))
|
||||
is PtRange -> return withClonedChildrenFrom(this, PtRange(type, position))
|
||||
is PtString -> return withClonedChildrenFrom(this, PtString(value, encoding, position))
|
||||
is PtTypeCast -> return withClonedChildrenFrom(this, PtTypeCast(type, position))
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
class PtAddressOf(position: Position) : PtExpression(DataType.UWORD, position) {
|
||||
val identifier: PtIdentifier
|
||||
get() = children.single() as PtIdentifier
|
||||
}
|
||||
|
||||
|
||||
class PtArrayIndexer(elementType: DataType, position: Position): PtExpression(elementType, position) {
|
||||
val variable: PtIdentifier
|
||||
get() = children[0] as PtIdentifier
|
||||
val index: PtExpression
|
||||
get() = children[1] as PtExpression
|
||||
|
||||
init {
|
||||
require(elementType in NumericDatatypes)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class PtArray(type: DataType, position: Position): PtExpression(type, position) {
|
||||
override fun hashCode(): Int = Objects.hash(children, type)
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if(other==null || other !is PtArray)
|
||||
return false
|
||||
return type==other.type && children == other.children
|
||||
}
|
||||
|
||||
val size: Int
|
||||
get() = children.size
|
||||
}
|
||||
|
||||
|
||||
class PtBuiltinFunctionCall(val name: String,
|
||||
val void: Boolean,
|
||||
val hasNoSideEffects: Boolean,
|
||||
type: DataType,
|
||||
position: Position) : PtExpression(type, position) {
|
||||
init {
|
||||
if(!void)
|
||||
require(type!=DataType.UNDEFINED)
|
||||
}
|
||||
|
||||
val args: List<PtExpression>
|
||||
get() = children.map { it as PtExpression }
|
||||
}
|
||||
|
||||
|
||||
class PtBinaryExpression(val operator: String, type: DataType, position: Position): PtExpression(type, position) {
|
||||
// note: "and", "or", "xor" do not occur anymore as operators. They've been replaced int the ast by their bitwise versions &, |, ^.
|
||||
val left: PtExpression
|
||||
get() = children[0] as PtExpression
|
||||
val right: PtExpression
|
||||
get() = children[1] as PtExpression
|
||||
}
|
||||
|
||||
|
||||
class PtContainmentCheck(position: Position): PtExpression(DataType.UBYTE, position) {
|
||||
val element: PtExpression
|
||||
get() = children[0] as PtExpression
|
||||
val iterable: PtIdentifier
|
||||
get() = children[1] as PtIdentifier
|
||||
}
|
||||
|
||||
|
||||
class PtFunctionCall(val name: String,
|
||||
val void: Boolean,
|
||||
type: DataType,
|
||||
position: Position) : PtExpression(type, position) {
|
||||
init {
|
||||
if(!void)
|
||||
require(type!=DataType.UNDEFINED)
|
||||
}
|
||||
|
||||
val args: List<PtExpression>
|
||||
get() = children.map { it as PtExpression }
|
||||
}
|
||||
|
||||
|
||||
class PtIdentifier(val name: String, type: DataType, position: Position) : PtExpression(type, position) {
|
||||
override fun toString(): String {
|
||||
return "[PtIdentifier:$name $type $position]"
|
||||
}
|
||||
|
||||
fun copy() = PtIdentifier(name, type, position)
|
||||
}
|
||||
|
||||
|
||||
class PtMemoryByte(position: Position) : PtExpression(DataType.UBYTE, position) {
|
||||
val address: PtExpression
|
||||
get() = children.single() as PtExpression
|
||||
}
|
||||
|
||||
|
||||
class PtNumber(type: DataType, val number: Double, position: Position) : PtExpression(type, position) {
|
||||
|
||||
companion object {
|
||||
fun fromBoolean(bool: Boolean, position: Position): PtNumber =
|
||||
PtNumber(DataType.UBYTE, if(bool) 1.0 else 0.0, position)
|
||||
}
|
||||
|
||||
init {
|
||||
if(type==DataType.BOOL)
|
||||
throw IllegalArgumentException("bool should have become ubyte @$position")
|
||||
if(type!=DataType.FLOAT) {
|
||||
val rounded = round(number)
|
||||
if (rounded != number)
|
||||
throw IllegalArgumentException("refused rounding of float to avoid loss of precision @$position")
|
||||
}
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = Objects.hash(type, number)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if(other==null || other !is PtNumber)
|
||||
return false
|
||||
return number==other.number
|
||||
}
|
||||
|
||||
operator fun compareTo(other: PtNumber): Int = number.compareTo(other.number)
|
||||
|
||||
override fun toString() = "PtNumber:$type:$number"
|
||||
}
|
||||
|
||||
|
||||
class PtPrefix(val operator: String, type: DataType, position: Position): PtExpression(type, position) {
|
||||
val value: PtExpression
|
||||
get() = children.single() as PtExpression
|
||||
|
||||
init {
|
||||
// note: the "not" operator may no longer occur in the ast; not x should have been replaced with x==0
|
||||
require(operator in setOf("+", "-", "~")) { "invalid prefix operator: $operator" }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class PtRange(type: DataType, position: Position) : PtExpression(type, position) {
|
||||
val from: PtExpression
|
||||
get() = children[0] as PtExpression
|
||||
val to: PtExpression
|
||||
get() = children[1] as PtExpression
|
||||
val step: PtNumber
|
||||
get() = children[2] as PtNumber
|
||||
|
||||
fun toConstantIntegerRange(): IntProgression? {
|
||||
fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression {
|
||||
return when {
|
||||
fromVal <= toVal -> when {
|
||||
stepVal <= 0 -> IntRange.EMPTY
|
||||
stepVal == 1 -> fromVal..toVal
|
||||
else -> fromVal..toVal step stepVal
|
||||
}
|
||||
else -> when {
|
||||
stepVal >= 0 -> IntRange.EMPTY
|
||||
stepVal == -1 -> fromVal downTo toVal
|
||||
else -> fromVal downTo toVal step abs(stepVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val fromLv = from as? PtNumber
|
||||
val toLv = to as? PtNumber
|
||||
val stepLv = step as? PtNumber
|
||||
if(fromLv==null || toLv==null || stepLv==null)
|
||||
return null
|
||||
val fromVal = fromLv.number.toInt()
|
||||
val toVal = toLv.number.toInt()
|
||||
val stepVal = stepLv.number.toInt()
|
||||
return makeRange(fromVal, toVal, stepVal)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class PtString(val value: String, val encoding: Encoding, position: Position) : PtExpression(DataType.STR, position) {
|
||||
override fun hashCode(): Int = Objects.hash(value, encoding)
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if(other==null || other !is PtString)
|
||||
return false
|
||||
return value==other.value && encoding == other.encoding
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class PtTypeCast(type: DataType, position: Position) : PtExpression(type, position) {
|
||||
val value: PtExpression
|
||||
get() = children.single() as PtExpression
|
||||
}
|
||||
|
||||
|
||||
// special node that isn't created from compiling user code, but used internally in the Intermediate Code
|
||||
class PtMachineRegister(val register: Int, type: DataType, position: Position) : PtExpression(type, position)
|
||||
|
||||
|
||||
fun constValue(expr: PtExpression): Double? = if(expr is PtNumber) expr.number else null
|
||||
fun constIntValue(expr: PtExpression): Int? = if(expr is PtNumber) expr.number.toInt() else null
|
||||
@@ -0,0 +1,163 @@
|
||||
package prog8.code.ast
|
||||
|
||||
import prog8.code.core.*
|
||||
|
||||
/**
|
||||
* Produces readable text from a [PtNode] (AST node, usually starting with PtProgram as root),
|
||||
* passing it as a String to the specified receiver function.
|
||||
*/
|
||||
fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Unit) {
|
||||
fun type(dt: DataType) = "!${dt.name.lowercase()}!"
|
||||
fun txt(node: PtNode): String {
|
||||
return when(node) {
|
||||
is PtAssignTarget -> "<target>"
|
||||
is PtAssignment -> "<assign>"
|
||||
is PtAugmentedAssign -> "<inplace-assign> ${node.operator}"
|
||||
is PtBreakpoint -> "%breakpoint"
|
||||
is PtConditionalBranch -> "if_${node.condition.name.lowercase()}"
|
||||
is PtAddressOf -> "&"
|
||||
is PtArray -> "array len=${node.children.size} ${type(node.type)}"
|
||||
is PtArrayIndexer -> "<arrayindexer> ${type(node.type)}"
|
||||
is PtBinaryExpression -> "<expr> ${node.operator} ${type(node.type)}"
|
||||
is PtBuiltinFunctionCall -> {
|
||||
val str = if(node.void) "void " else ""
|
||||
str + node.name + "()"
|
||||
}
|
||||
is PtContainmentCheck -> "in"
|
||||
is PtFunctionCall -> {
|
||||
val str = if(node.void) "void " else ""
|
||||
str + node.name + "()"
|
||||
}
|
||||
is PtIdentifier -> "${node.name} ${type(node.type)}"
|
||||
is PtMachineRegister -> "VMREG#${node.register} ${type(node.type)}"
|
||||
is PtMemoryByte -> "@()"
|
||||
is PtNumber -> {
|
||||
val numstr = if(node.type == DataType.FLOAT) node.number.toString() else node.number.toHex()
|
||||
"$numstr ${type(node.type)}"
|
||||
}
|
||||
is PtPrefix -> node.operator
|
||||
is PtRange -> "<range>"
|
||||
is PtString -> "\"${node.value.escape()}\""
|
||||
is PtTypeCast -> "as ${node.type.name.lowercase()}"
|
||||
is PtForLoop -> "for"
|
||||
is PtIfElse -> "ifelse"
|
||||
is PtIncludeBinary -> "%incbin '${node.file}', ${node.offset}, ${node.length}"
|
||||
is PtInlineAssembly -> {
|
||||
if(node.isIR)
|
||||
"%ir {{ ...${node.assembly.length} characters... }}"
|
||||
else
|
||||
"%asm {{ ...${node.assembly.length} characters... }}"
|
||||
}
|
||||
is PtJump -> {
|
||||
if(node.identifier!=null)
|
||||
"goto ${node.identifier.name}"
|
||||
else if(node.address!=null)
|
||||
"goto ${node.address.toHex()}"
|
||||
else if(node.generatedLabel!=null)
|
||||
"goto ${node.generatedLabel}"
|
||||
else
|
||||
"???"
|
||||
}
|
||||
is PtAsmSub -> {
|
||||
val params = if (node.parameters.isEmpty()) "" else "...TODO ${node.parameters.size} PARAMS..."
|
||||
val clobbers = if (node.clobbers.isEmpty()) "" else "clobbers ${node.clobbers}"
|
||||
val returns = if (node.returns.isEmpty()) "" else (if (node.returns.size == 1) "-> ${node.returns[0].second.name.lowercase()}" else "-> ${node.returns.map { it.second.name.lowercase() }}")
|
||||
val str = if (node.inline) "inline " else ""
|
||||
if(node.address==null) {
|
||||
str + "asmsub ${node.name}($params) $clobbers $returns"
|
||||
} else {
|
||||
str + "romsub ${node.address.toHex()} = ${node.name}($params) $clobbers $returns"
|
||||
}
|
||||
}
|
||||
is PtBlock -> {
|
||||
val addr = if(node.address==null) "" else "@${node.address.toHex()}"
|
||||
val align = if(node.alignment==PtBlock.BlockAlignment.NONE) "" else "align=${node.alignment}"
|
||||
"\nblock '${node.name}' $addr $align"
|
||||
}
|
||||
is PtConstant -> {
|
||||
val value = if(node.type in IntegerDatatypes) node.value.toInt().toString() else node.value.toString()
|
||||
"const ${node.type.name.lowercase()} ${node.name} = $value"
|
||||
}
|
||||
is PtLabel -> "${node.name}:"
|
||||
is PtMemMapped -> {
|
||||
if(node.type in ArrayDatatypes) {
|
||||
val arraysize = if(node.arraySize==null) "" else node.arraySize.toString()
|
||||
val eltType = ArrayToElementTypes.getValue(node.type)
|
||||
"&${eltType.name.lowercase()}[$arraysize] ${node.name} = ${node.address.toHex()}"
|
||||
} else {
|
||||
"&${node.type.name.lowercase()} ${node.name} = ${node.address.toHex()}"
|
||||
}
|
||||
}
|
||||
is PtSub -> {
|
||||
val params = if (node.parameters.isEmpty()) "" else "...TODO ${node.parameters.size} PARAMS..."
|
||||
var str = "sub ${node.name}($params) "
|
||||
if(node.returntype!=null)
|
||||
str += "-> ${node.returntype.name.lowercase()}"
|
||||
str
|
||||
}
|
||||
is PtVariable -> {
|
||||
val str = if(node.arraySize!=null) {
|
||||
val eltType = ArrayToElementTypes.getValue(node.type)
|
||||
"${eltType.name.lowercase()}[${node.arraySize}] ${node.name}"
|
||||
}
|
||||
else if(node.type in ArrayDatatypes) {
|
||||
val eltType = ArrayToElementTypes.getValue(node.type)
|
||||
"${eltType.name.lowercase()}[] ${node.name}"
|
||||
}
|
||||
else
|
||||
"${node.type.name.lowercase()} ${node.name}"
|
||||
if(node.value!=null)
|
||||
str + " = " + txt(node.value)
|
||||
else
|
||||
str
|
||||
}
|
||||
is PtNodeGroup -> "<group>"
|
||||
is PtNop -> "nop"
|
||||
is PtPostIncrDecr -> "<post> ${node.operator}"
|
||||
is PtProgram -> "PROGRAM ${node.name}"
|
||||
is PtRepeatLoop -> "repeat"
|
||||
is PtReturn -> "return"
|
||||
is PtSubroutineParameter -> "${node.type.name.lowercase()} ${node.name}"
|
||||
is PtWhen -> "when"
|
||||
is PtWhenChoice -> {
|
||||
if(node.isElse)
|
||||
"else"
|
||||
else
|
||||
"->"
|
||||
}
|
||||
else -> throw InternalCompilerException("unrecognised ast node $node")
|
||||
}
|
||||
}
|
||||
|
||||
if(root is PtProgram) {
|
||||
output(txt(root))
|
||||
root.children.forEach {
|
||||
walkAst(it) { node, depth ->
|
||||
val txt = txt(node)
|
||||
val library = if(node is PtBlock) node.library else node.definingBlock()?.library==true
|
||||
if(!library || !skipLibraries) {
|
||||
if (txt.isNotEmpty())
|
||||
output(" ".repeat(depth) + txt(node))
|
||||
}
|
||||
}
|
||||
}
|
||||
println()
|
||||
} else {
|
||||
walkAst(root) { node, depth ->
|
||||
val txt = txt(node)
|
||||
val library = if(node is PtBlock) node.library else node.definingBlock()?.library==true
|
||||
if(!library || !skipLibraries) {
|
||||
if (txt.isNotEmpty())
|
||||
output(" ".repeat(depth) + txt(node))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun walkAst(root: PtNode, act: (node: PtNode, depth: Int) -> Unit) {
|
||||
fun recurse(node: PtNode, depth: Int) {
|
||||
act(node, depth)
|
||||
node.children.forEach { recurse(it, depth+1) }
|
||||
}
|
||||
recurse(root, 0)
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
package prog8.code.ast
|
||||
|
||||
import prog8.code.core.*
|
||||
|
||||
|
||||
sealed interface IPtSubroutine {
|
||||
val name: String
|
||||
}
|
||||
|
||||
class PtAsmSub(
|
||||
name: String,
|
||||
val address: UInt?,
|
||||
val clobbers: Set<CpuRegister>,
|
||||
val parameters: List<Pair<RegisterOrStatusflag, PtSubroutineParameter>>,
|
||||
val returns: List<Pair<RegisterOrStatusflag, DataType>>,
|
||||
val inline: Boolean,
|
||||
position: Position
|
||||
) : PtNamedNode(name, position), IPtSubroutine
|
||||
|
||||
|
||||
class PtSub(
|
||||
name: String,
|
||||
val parameters: List<PtSubroutineParameter>,
|
||||
val returntype: DataType?,
|
||||
position: Position
|
||||
) : PtNamedNode(name, position), IPtSubroutine {
|
||||
init {
|
||||
// params and return value should not be str
|
||||
if(parameters.any{ it.type !in NumericDatatypes })
|
||||
throw AssemblyError("non-numeric parameter")
|
||||
if(returntype!=null && returntype !in NumericDatatypes)
|
||||
throw AssemblyError("non-numeric returntype $returntype")
|
||||
parameters.forEach { it.parent=this }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class PtSubroutineParameter(name: String, val type: DataType, position: Position): PtNamedNode(name, position)
|
||||
|
||||
|
||||
sealed interface IPtAssignment {
|
||||
val children: MutableList<PtNode>
|
||||
val target: PtAssignTarget
|
||||
get() = children[0] as PtAssignTarget
|
||||
val value: PtExpression
|
||||
get() = children[1] as PtExpression
|
||||
}
|
||||
|
||||
class PtAssignment(position: Position) : PtNode(position), IPtAssignment
|
||||
|
||||
class PtAugmentedAssign(val operator: String, position: Position) : PtNode(position), IPtAssignment
|
||||
|
||||
|
||||
class PtAssignTarget(position: Position) : PtNode(position) {
|
||||
val identifier: PtIdentifier?
|
||||
get() = children.single() as? PtIdentifier
|
||||
val array: PtArrayIndexer?
|
||||
get() = children.single() as? PtArrayIndexer
|
||||
val memory: PtMemoryByte?
|
||||
get() = children.single() as? PtMemoryByte
|
||||
|
||||
val type: DataType
|
||||
get() {
|
||||
return when(val tgt = children.single()) {
|
||||
is PtIdentifier -> tgt.type
|
||||
is PtArrayIndexer -> tgt.type
|
||||
is PtMemoryByte -> tgt.type
|
||||
else -> throw AssemblyError("weird target $tgt")
|
||||
}
|
||||
}
|
||||
|
||||
infix fun isSameAs(expression: PtExpression): Boolean = expression.isSameAs(this)
|
||||
}
|
||||
|
||||
|
||||
class PtConditionalBranch(val condition: BranchCondition, position: Position) : PtNode(position) {
|
||||
val trueScope: PtNodeGroup
|
||||
get() = children[0] as PtNodeGroup
|
||||
val falseScope: PtNodeGroup
|
||||
get() = children[1] as PtNodeGroup
|
||||
}
|
||||
|
||||
|
||||
class PtForLoop(position: Position) : PtNode(position) {
|
||||
val variable: PtIdentifier
|
||||
get() = children[0] as PtIdentifier
|
||||
val iterable: PtExpression
|
||||
get() = children[1] as PtExpression
|
||||
val statements: PtNodeGroup
|
||||
get() = children[2] as PtNodeGroup
|
||||
}
|
||||
|
||||
|
||||
class PtIfElse(position: Position) : PtNode(position) {
|
||||
val condition: PtExpression
|
||||
get() = children[0] as PtExpression
|
||||
val ifScope: PtNodeGroup
|
||||
get() = children[1] as PtNodeGroup
|
||||
val elseScope: PtNodeGroup
|
||||
get() = children[2] as PtNodeGroup
|
||||
}
|
||||
|
||||
|
||||
class PtJump(val identifier: PtIdentifier?,
|
||||
val address: UInt?,
|
||||
val generatedLabel: String?,
|
||||
position: Position) : PtNode(position) {
|
||||
init {
|
||||
identifier?.let {it.parent = this }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class PtPostIncrDecr(val operator: String, position: Position) : PtNode(position) {
|
||||
val target: PtAssignTarget
|
||||
get() = children.single() as PtAssignTarget
|
||||
}
|
||||
|
||||
|
||||
class PtRepeatLoop(position: Position) : PtNode(position) {
|
||||
val count: PtExpression
|
||||
get() = children[0] as PtExpression
|
||||
val statements: PtNodeGroup
|
||||
get() = children[1] as PtNodeGroup
|
||||
}
|
||||
|
||||
|
||||
class PtReturn(position: Position) : PtNode(position) {
|
||||
val hasValue = children.any()
|
||||
val value: PtExpression?
|
||||
get() {
|
||||
return if(children.any())
|
||||
children.single() as PtExpression
|
||||
else
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sealed interface IPtVariable {
|
||||
val name: String
|
||||
val type: DataType
|
||||
}
|
||||
|
||||
|
||||
class PtVariable(name: String, override val type: DataType, val zeropage: ZeropageWish, val value: PtExpression?, val arraySize: UInt?, position: Position) : PtNamedNode(name, position), IPtVariable {
|
||||
init {
|
||||
value?.let {it.parent=this}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class PtConstant(name: String, override val type: DataType, val value: Double, position: Position) : PtNamedNode(name, position), IPtVariable
|
||||
|
||||
|
||||
class PtMemMapped(name: String, override val type: DataType, val address: UInt, val arraySize: UInt?, position: Position) : PtNamedNode(name, position), IPtVariable
|
||||
|
||||
|
||||
class PtWhen(position: Position) : PtNode(position) {
|
||||
val value: PtExpression
|
||||
get() = children[0] as PtExpression
|
||||
val choices: PtNodeGroup
|
||||
get() = children[1] as PtNodeGroup
|
||||
}
|
||||
|
||||
|
||||
class PtWhenChoice(val isElse: Boolean, position: Position) : PtNode(position) {
|
||||
val values: PtNodeGroup
|
||||
get() = children[0] as PtNodeGroup
|
||||
val statements: PtNodeGroup
|
||||
get() = children[1] as PtNodeGroup
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package prog8.code.core
|
||||
|
||||
class ReturnConvention(val dt: BaseDataType?, val reg: RegisterOrPair?)
|
||||
class ParamConvention(val dt: BaseDataType, val reg: RegisterOrPair?, val variable: Boolean)
|
||||
class ReturnConvention(val dt: DataType?, val reg: RegisterOrPair?, val floatFac1: Boolean)
|
||||
class ParamConvention(val dt: DataType, val reg: RegisterOrPair?, val variable: Boolean)
|
||||
class CallConvention(val params: List<ParamConvention>, val returns: ReturnConvention) {
|
||||
override fun toString(): String {
|
||||
val paramConvs = params.mapIndexed { index, it ->
|
||||
@@ -13,80 +13,52 @@ class CallConvention(val params: List<ParamConvention>, val returns: ReturnConve
|
||||
}
|
||||
val returnConv =
|
||||
when {
|
||||
returns.reg == RegisterOrPair.FAC1 -> "floatFAC1"
|
||||
returns.reg != null -> returns.reg.toString()
|
||||
returns.reg!=null -> returns.reg.toString()
|
||||
returns.floatFac1 -> "floatFAC1"
|
||||
else -> "<no returnvalue>"
|
||||
}
|
||||
return "CallConvention[" + paramConvs.joinToString() + " ; returns: $returnConv]"
|
||||
}
|
||||
}
|
||||
|
||||
class FParam(val name: String, vararg val possibleDatatypes: BaseDataType)
|
||||
|
||||
|
||||
private val IterableDatatypes = arrayOf(BaseDataType.STR, BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW)
|
||||
private val IntegerDatatypes = arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)
|
||||
private val NumericDatatypes = arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
|
||||
class FParam(val name: String, val possibleDatatypes: Array<DataType>)
|
||||
|
||||
class FSignature(val pure: Boolean, // does it have side effects?
|
||||
val returnTypes: Array<BaseDataType>,
|
||||
vararg val parameters: FParam) {
|
||||
val parameters: List<FParam>,
|
||||
val returnType: DataType?) {
|
||||
|
||||
fun callConventionFor6502(actualParamTypes: List<BaseDataType>): CallConvention {
|
||||
val returns: ReturnConvention
|
||||
if(returnTypes.isEmpty())
|
||||
returns = ReturnConvention(null, null)
|
||||
else if(returnTypes.size==1) {
|
||||
val returnType = returnTypes[0]
|
||||
returns = when (returnType) {
|
||||
BaseDataType.UBYTE, BaseDataType.BYTE -> ReturnConvention(returnType, RegisterOrPair.A)
|
||||
BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.POINTER -> ReturnConvention(returnType, RegisterOrPair.AY)
|
||||
BaseDataType.LONG -> ReturnConvention(returnType, RegisterOrPair.R14R15)
|
||||
BaseDataType.FLOAT -> ReturnConvention(returnType, RegisterOrPair.FAC1)
|
||||
in IterableDatatypes -> ReturnConvention(returnType, RegisterOrPair.AY)
|
||||
else -> {
|
||||
// return type depends on arg type
|
||||
when (val paramType = actualParamTypes.first()) {
|
||||
BaseDataType.UBYTE, BaseDataType.BYTE -> ReturnConvention(paramType, RegisterOrPair.A)
|
||||
BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.POINTER -> ReturnConvention(paramType, RegisterOrPair.AY)
|
||||
BaseDataType.LONG -> ReturnConvention(returnType, RegisterOrPair.R14R15)
|
||||
BaseDataType.FLOAT -> ReturnConvention(paramType, RegisterOrPair.FAC1)
|
||||
in IterableDatatypes -> ReturnConvention(paramType, RegisterOrPair.AY)
|
||||
else -> ReturnConvention(paramType, null)
|
||||
}
|
||||
fun callConvention(actualParamTypes: List<DataType>): CallConvention {
|
||||
val returns: ReturnConvention = when (returnType) {
|
||||
DataType.UBYTE, DataType.BYTE -> ReturnConvention(returnType, RegisterOrPair.A, false)
|
||||
DataType.UWORD, DataType.WORD -> ReturnConvention(returnType, RegisterOrPair.AY, false)
|
||||
DataType.FLOAT -> ReturnConvention(returnType, null, true)
|
||||
in PassByReferenceDatatypes -> ReturnConvention(returnType!!, RegisterOrPair.AY, false)
|
||||
null -> ReturnConvention(null, null, false)
|
||||
else -> {
|
||||
// return type depends on arg type
|
||||
when (val paramType = actualParamTypes.first()) {
|
||||
DataType.UBYTE, DataType.BYTE -> ReturnConvention(paramType, RegisterOrPair.A, false)
|
||||
DataType.UWORD, DataType.WORD -> ReturnConvention(paramType, RegisterOrPair.AY, false)
|
||||
DataType.FLOAT -> ReturnConvention(paramType, null, true)
|
||||
in PassByReferenceDatatypes -> ReturnConvention(paramType, RegisterOrPair.AY, false)
|
||||
else -> ReturnConvention(paramType, null, false)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// note: this path is currently never hit because the builtin functions with multiple return values have been handled in specialized optimized codegens already earlier.
|
||||
TODO("multiple return types from builtin function")
|
||||
}
|
||||
|
||||
return when {
|
||||
actualParamTypes.isEmpty() -> CallConvention(emptyList(), returns)
|
||||
actualParamTypes.size==1 -> {
|
||||
// One parameter goes via register/registerpair (except longs)
|
||||
// this avoids repeated code for every caller to store the value in the subroutine's argument variable.
|
||||
// (that store is still done, but only coded once at the start at the subroutine itself rather than at every call site).
|
||||
// one parameter goes via register/registerpair
|
||||
val paramConv = when(val paramType = actualParamTypes[0]) {
|
||||
BaseDataType.UBYTE, BaseDataType.BYTE -> ParamConvention(paramType, RegisterOrPair.A, false)
|
||||
BaseDataType.UWORD, BaseDataType.WORD -> ParamConvention(paramType, RegisterOrPair.AY, false)
|
||||
BaseDataType.LONG -> ParamConvention(paramType, null, true)
|
||||
BaseDataType.FLOAT -> ParamConvention(paramType, RegisterOrPair.AY, false) // NOTE: for builtin functions, floating point arguments are passed by reference (so you get a pointer in AY)
|
||||
in IterableDatatypes -> ParamConvention(paramType, RegisterOrPair.AY, false)
|
||||
DataType.UBYTE, DataType.BYTE -> ParamConvention(paramType, RegisterOrPair.A, false)
|
||||
DataType.UWORD, DataType.WORD -> ParamConvention(paramType, RegisterOrPair.AY, false)
|
||||
DataType.FLOAT -> ParamConvention(paramType, RegisterOrPair.AY, false)
|
||||
in PassByReferenceDatatypes -> ParamConvention(paramType, RegisterOrPair.AY, false)
|
||||
else -> ParamConvention(paramType, null, false)
|
||||
}
|
||||
CallConvention(listOf(paramConv), returns)
|
||||
}
|
||||
actualParamTypes.size==2 && (actualParamTypes[0].isByte && actualParamTypes[1].isWord) -> {
|
||||
TODO("opportunity to pass word+byte arguments in A,Y and X registers but not implemented yet")
|
||||
}
|
||||
actualParamTypes.size==2 && (actualParamTypes[0].isWord && actualParamTypes[1].isByte) -> {
|
||||
TODO("opportunity to pass word+byte arguments in A,Y and X registers but not implemented yet")
|
||||
}
|
||||
actualParamTypes.size==3 && actualParamTypes.all { it.isByte } -> {
|
||||
TODO("opportunity to pass 3 byte arguments in A,Y and X registers but not implemented yet")
|
||||
}
|
||||
else -> {
|
||||
// multiple parameters go via variables
|
||||
val paramConvs = actualParamTypes.map { ParamConvention(it, null, true) }
|
||||
@@ -96,107 +68,46 @@ class FSignature(val pure: Boolean, // does it have side effects?
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
"setlsb" to FSignature(false, emptyArray(), FParam("variable", BaseDataType.WORD, BaseDataType.UWORD, BaseDataType.LONG), FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
|
||||
"setmsb" to FSignature(false, emptyArray(), FParam("variable", BaseDataType.WORD, BaseDataType.UWORD, BaseDataType.LONG), FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
|
||||
"rol" to FSignature(false, emptyArray(), FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
|
||||
"ror" to FSignature(false, emptyArray(), FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
|
||||
"rol2" to FSignature(false, emptyArray(), FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
|
||||
"ror2" to FSignature(false, emptyArray(), FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
|
||||
"cmp" to FSignature(false, emptyArray(), FParam("value1", *IntegerDatatypes), FParam("value2", *NumericDatatypes)), // cmp returns result in the cpu status flags, but not asa proper return value
|
||||
"prog8_lib_stringcompare" to FSignature(true, arrayOf(BaseDataType.BYTE), FParam("str1", BaseDataType.STR), FParam("str2", BaseDataType.STR)),
|
||||
"prog8_lib_square_byte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
|
||||
"prog8_lib_square_word" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.WORD, BaseDataType.UWORD)),
|
||||
"prog8_lib_square_long" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("value", BaseDataType.LONG)),
|
||||
"prog8_lib_structalloc" to FSignature(true, arrayOf(BaseDataType.UWORD)),
|
||||
"prog8_lib_copylong" to FSignature(false, emptyArray(), FParam("pointer1", BaseDataType.POINTER, BaseDataType.UWORD, BaseDataType.LONG), FParam("pointer2", BaseDataType.POINTER, BaseDataType.UWORD, BaseDataType.LONG)),
|
||||
"prog8_lib_copyfloat" to FSignature(false, emptyArray(), FParam("pointer1", BaseDataType.POINTER, BaseDataType.UWORD, BaseDataType.LONG), FParam("pointer2", BaseDataType.POINTER, BaseDataType.UWORD, BaseDataType.LONG)),
|
||||
"abs" to FSignature(true, arrayOf(BaseDataType.UNDEFINED), FParam("value", *NumericDatatypes)),
|
||||
"abs__byte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.BYTE)),
|
||||
"abs__word" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.WORD)),
|
||||
"abs__long" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("value", BaseDataType.LONG)),
|
||||
"abs__float" to FSignature(true, arrayOf(BaseDataType.FLOAT), FParam("value", BaseDataType.FLOAT)),
|
||||
"len" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("values", *IterableDatatypes)),
|
||||
"sizeof" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("object", *(BaseDataType.entries - BaseDataType.STRUCT_INSTANCE).toTypedArray())),
|
||||
"offsetof" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("field", BaseDataType.UBYTE)),
|
||||
"sgn" to FSignature(true, arrayOf(BaseDataType.BYTE), FParam("value", *NumericDatatypes)),
|
||||
"sqrt" to FSignature(true, arrayOf(BaseDataType.UNDEFINED), FParam("value", *NumericDatatypes)),
|
||||
"sqrt__ubyte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.UBYTE)),
|
||||
"sqrt__uword" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.UWORD)),
|
||||
"sqrt__long" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.LONG)),
|
||||
"sqrt__float" to FSignature(true, arrayOf(BaseDataType.FLOAT), FParam("value", BaseDataType.FLOAT)),
|
||||
"divmod" to FSignature(false, arrayOf(BaseDataType.UNDEFINED, BaseDataType.UNDEFINED), FParam("dividend", BaseDataType.UBYTE, BaseDataType.UWORD), FParam("divisor", BaseDataType.UBYTE, BaseDataType.UWORD)),
|
||||
"divmod__ubyte" to FSignature(false, arrayOf(BaseDataType.UBYTE, BaseDataType.UBYTE), FParam("dividend", BaseDataType.UBYTE), FParam("divisor", BaseDataType.UBYTE)),
|
||||
"divmod__uword" to FSignature(false, arrayOf(BaseDataType.UWORD, BaseDataType.UWORD), FParam("dividend", BaseDataType.UWORD), FParam("divisor", BaseDataType.UWORD)),
|
||||
"divmod__byte" to FSignature(false, arrayOf(BaseDataType.BYTE, BaseDataType.BYTE), FParam("dividend", BaseDataType.BYTE), FParam("divisor", BaseDataType.BYTE)),
|
||||
"divmod__word" to FSignature(false, arrayOf(BaseDataType.WORD, BaseDataType.WORD), FParam("dividend", BaseDataType.WORD), FParam("divisor", BaseDataType.WORD)),
|
||||
"lmh" to FSignature(true, arrayOf(BaseDataType.UBYTE, BaseDataType.UBYTE, BaseDataType.UBYTE), FParam("value", BaseDataType.LONG)),
|
||||
"lsb" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
|
||||
"lsb__long" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.LONG)),
|
||||
"msb" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
|
||||
"msb__long" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.LONG)),
|
||||
"lsw" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
|
||||
"msw" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
|
||||
"mkword" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("msb", BaseDataType.UBYTE), FParam("lsb", BaseDataType.UBYTE)),
|
||||
"mklong" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("msb", BaseDataType.UBYTE), FParam("b2", BaseDataType.UBYTE), FParam("b1", BaseDataType.UBYTE), FParam("lsb", BaseDataType.UBYTE)),
|
||||
"mklong2" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("msw", BaseDataType.UWORD), FParam("lsw", BaseDataType.UWORD)),
|
||||
"clamp" to FSignature(true, arrayOf(BaseDataType.UNDEFINED), FParam("value", BaseDataType.BYTE), FParam("minimum", BaseDataType.BYTE), FParam("maximum", BaseDataType.BYTE)),
|
||||
"clamp__byte" to FSignature(true, arrayOf(BaseDataType.BYTE), FParam("value", BaseDataType.BYTE), FParam("minimum", BaseDataType.BYTE), FParam("maximum", BaseDataType.BYTE)),
|
||||
"clamp__ubyte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.UBYTE), FParam("minimum", BaseDataType.UBYTE), FParam("maximum", BaseDataType.UBYTE)),
|
||||
"clamp__word" to FSignature(true, arrayOf(BaseDataType.WORD), FParam("value", BaseDataType.WORD), FParam("minimum", BaseDataType.WORD), FParam("maximum", BaseDataType.WORD)),
|
||||
"clamp__uword" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.UWORD), FParam("minimum", BaseDataType.UWORD), FParam("maximum", BaseDataType.UWORD)),
|
||||
"clamp__long" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("value", BaseDataType.LONG), FParam("minimum", BaseDataType.LONG), FParam("maximum", BaseDataType.LONG)),
|
||||
"min" to FSignature(true, arrayOf(BaseDataType.UNDEFINED), FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
|
||||
"min__byte" to FSignature(true, arrayOf(BaseDataType.BYTE), FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
|
||||
"min__ubyte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("val1", BaseDataType.UBYTE), FParam("val2", BaseDataType.UBYTE)),
|
||||
"min__word" to FSignature(true, arrayOf(BaseDataType.WORD), FParam("val1", BaseDataType.WORD), FParam("val2", BaseDataType.WORD)),
|
||||
"min__uword" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("val1", BaseDataType.UWORD), FParam("val2", BaseDataType.UWORD)),
|
||||
"min__long" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("val1", BaseDataType.LONG), FParam("val2", BaseDataType.LONG)),
|
||||
"max" to FSignature(true, arrayOf(BaseDataType.UNDEFINED), FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
|
||||
"max__byte" to FSignature(true, arrayOf(BaseDataType.BYTE), FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
|
||||
"max__ubyte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("val1", BaseDataType.UBYTE), FParam("val2", BaseDataType.UBYTE)),
|
||||
"max__word" to FSignature(true, arrayOf(BaseDataType.WORD), FParam("val1", BaseDataType.WORD), FParam("val2", BaseDataType.WORD)),
|
||||
"max__uword" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("val1", BaseDataType.UWORD), FParam("val2", BaseDataType.UWORD)),
|
||||
"max__long" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("val1", BaseDataType.LONG), FParam("val2", BaseDataType.LONG)),
|
||||
"peek" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER)),
|
||||
"peekbool" to FSignature(true, arrayOf(BaseDataType.BOOL), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER)),
|
||||
"peekw" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER)),
|
||||
"peekl" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER)),
|
||||
"peekf" to FSignature(true, arrayOf(BaseDataType.FLOAT), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER)),
|
||||
"peekp" to FSignature(true, arrayOf(BaseDataType.POINTER), FParam("address", BaseDataType.POINTER, BaseDataType.UWORD, BaseDataType.LONG)),
|
||||
"poke" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER), FParam("value", BaseDataType.UBYTE, BaseDataType.BYTE)),
|
||||
"pokebool" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER), FParam("value", BaseDataType.BOOL)),
|
||||
"pokebowl" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER), FParam("value", BaseDataType.BOOL)),
|
||||
"pokew" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER), FParam("value", BaseDataType.UWORD, BaseDataType.WORD)),
|
||||
"pokel" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER), FParam("value", BaseDataType.LONG)),
|
||||
"pokef" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER), FParam("value", BaseDataType.FLOAT)),
|
||||
"pokep" to FSignature(false, emptyArray(), FParam("address", BaseDataType.POINTER, BaseDataType.UWORD, BaseDataType.LONG), FParam("value", BaseDataType.POINTER)),
|
||||
"pokemon" to FSignature(false, arrayOf(BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.POINTER), FParam("value", BaseDataType.UBYTE)),
|
||||
"rsave" to FSignature(false, emptyArray()),
|
||||
"rrestore" to FSignature(false, emptyArray()),
|
||||
"memory" to FSignature(true, arrayOf(BaseDataType.POINTER), FParam("name", BaseDataType.STR), FParam("size", BaseDataType.UWORD), FParam("alignment", BaseDataType.UWORD)),
|
||||
"memory__ref" to FSignature(true, arrayOf(BaseDataType.POINTER), FParam("name", BaseDataType.STR)),
|
||||
"callfar" to FSignature(false, arrayOf(BaseDataType.UWORD), FParam("bank", BaseDataType.UBYTE), FParam("address", BaseDataType.POINTER), FParam("arg", BaseDataType.UWORD)),
|
||||
"callfar2" to FSignature(false, arrayOf(BaseDataType.UWORD), FParam("bank", BaseDataType.UBYTE), FParam("address", BaseDataType.POINTER), FParam("argA", BaseDataType.UBYTE), FParam("argX", BaseDataType.UBYTE), FParam("argY", BaseDataType.UBYTE), FParam("argC", BaseDataType.BOOL)),
|
||||
"call" to FSignature(false, arrayOf(BaseDataType.UWORD), FParam("address", BaseDataType.POINTER)),
|
||||
"push" to FSignature(false, emptyArray(), FParam("value", BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.BOOL)),
|
||||
"pushw" to FSignature(false, emptyArray(), FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.POINTER)),
|
||||
"pushl" to FSignature(false, emptyArray(), FParam("value", BaseDataType.LONG)),
|
||||
"pushf" to FSignature(false, emptyArray(), FParam("value", BaseDataType.FLOAT)),
|
||||
"pushp" to FSignature(false, emptyArray(), FParam("value", BaseDataType.POINTER)),
|
||||
"pop" to FSignature(false, arrayOf(BaseDataType.UBYTE)),
|
||||
"popw" to FSignature(false, arrayOf(BaseDataType.UWORD)),
|
||||
"popl" to FSignature(false, arrayOf(BaseDataType.LONG)),
|
||||
"popf" to FSignature(false, arrayOf(BaseDataType.FLOAT)),
|
||||
"popp" to FSignature(false, arrayOf(BaseDataType.POINTER)),
|
||||
// this set of function have no return value and operate in-place:
|
||||
"rol" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
"ror" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
"rol2" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
"ror2" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
"sort" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
|
||||
"reverse" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
|
||||
// cmp returns a status in the carry flag, but not a proper return value
|
||||
"cmp" to FSignature(false, listOf(FParam("value1", IntegerDatatypesNoBool), FParam("value2", NumericDatatypesNoBool)), null),
|
||||
"prog8_lib_stringcompare" to FSignature(true, listOf(FParam("str1", arrayOf(DataType.STR)), FParam("str2", arrayOf(DataType.STR))), DataType.BYTE),
|
||||
"abs" to FSignature(true, listOf(FParam("value", IntegerDatatypesNoBool)), DataType.UWORD),
|
||||
"len" to FSignature(true, listOf(FParam("values", IterableDatatypes)), DataType.UWORD),
|
||||
// normal functions follow:
|
||||
"sizeof" to FSignature(true, listOf(FParam("object", DataType.values())), DataType.UBYTE),
|
||||
"sgn" to FSignature(true, listOf(FParam("value", NumericDatatypesNoBool)), DataType.BYTE),
|
||||
"sqrt16" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD))), DataType.UBYTE),
|
||||
"divmod" to FSignature(false, listOf(FParam("number", arrayOf(DataType.UBYTE)), FParam("divident", arrayOf(DataType.UBYTE)), FParam("division", arrayOf(DataType.UBYTE)), FParam("remainder", arrayOf(DataType.UBYTE))), null),
|
||||
"divmodw" to FSignature(false, listOf(FParam("number", arrayOf(DataType.UWORD)), FParam("divident", arrayOf(DataType.UWORD)), FParam("division", arrayOf(DataType.UWORD)), FParam("remainder", arrayOf(DataType.UWORD))), null),
|
||||
"any" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE),
|
||||
"all" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE),
|
||||
"lsb" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE),
|
||||
"msb" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE),
|
||||
"mkword" to FSignature(true, listOf(FParam("msb", arrayOf(DataType.UBYTE)), FParam("lsb", arrayOf(DataType.UBYTE))), DataType.UWORD),
|
||||
"peek" to FSignature(true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UBYTE),
|
||||
"peekw" to FSignature(true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UWORD),
|
||||
"poke" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), null),
|
||||
"pokemon" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), null),
|
||||
"pokew" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UWORD))), null),
|
||||
"pop" to FSignature(false, listOf(FParam("target", ByteDatatypes)), null),
|
||||
"popw" to FSignature(false, listOf(FParam("target", WordDatatypes)), null),
|
||||
"push" to FSignature(false, listOf(FParam("value", ByteDatatypes)), null),
|
||||
"pushw" to FSignature(false, listOf(FParam("value", WordDatatypes)), null),
|
||||
"rsave" to FSignature(false, emptyList(), null),
|
||||
"rsavex" to FSignature(false, emptyList(), null),
|
||||
"rrestore" to FSignature(false, emptyList(), null),
|
||||
"rrestorex" to FSignature(false, emptyList(), null),
|
||||
"memory" to FSignature(true, listOf(FParam("name", arrayOf(DataType.STR)), FParam("size", arrayOf(DataType.UWORD)), FParam("alignment", arrayOf(DataType.UWORD))), DataType.UWORD),
|
||||
"callfar" to FSignature(false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("arg", arrayOf(DataType.UWORD))), DataType.UWORD),
|
||||
)
|
||||
|
||||
val InplaceModifyingBuiltinFunctions = setOf(
|
||||
"setlsb", "setmsb",
|
||||
"rol", "ror", "rol2", "ror2"
|
||||
)
|
||||
|
||||
val SimpleBuiltinFunctions = setOf(
|
||||
"msb", "lsb", "msw", "lsw",
|
||||
"mkword", "mklong", "mklong2",
|
||||
"set_carry", "set_irqd", "clear_carry", "clear_irqd")
|
||||
val InplaceModifyingBuiltinFunctions = setOf("rol", "ror", "rol2", "ror2", "sort", "reverse")
|
||||
@@ -4,122 +4,28 @@ import java.nio.file.Path
|
||||
import kotlin.io.path.Path
|
||||
|
||||
|
||||
@Suppress("unused")
|
||||
class CompilationOptions(val output: OutputType,
|
||||
val launcher: CbmPrgLauncherType,
|
||||
val zeropage: ZeropageType,
|
||||
val zpReserved: List<UIntRange>,
|
||||
val zpAllowed: List<UIntRange>,
|
||||
val floats: Boolean,
|
||||
val noSysInit: Boolean,
|
||||
val romable: Boolean,
|
||||
val compTarget: ICompilationTarget,
|
||||
val compilerVersion: String,
|
||||
// these are set later, based on command line arguments or options in the source code:
|
||||
var loadAddress: UInt,
|
||||
var memtopAddress: UInt,
|
||||
var varsAddress: UInt? = null,
|
||||
var warnSymbolShadowing: Boolean = false,
|
||||
var warnImplicitTypeCast: Boolean = false,
|
||||
var slowCodegenWarnings: Boolean = false,
|
||||
var optimize: Boolean = false,
|
||||
var optimizeFloatExpressions: Boolean = false,
|
||||
var asmQuiet: Boolean = false,
|
||||
var asmListfile: Boolean = false,
|
||||
var includeSourcelines: Boolean = false,
|
||||
var dumpVariables: Boolean = false,
|
||||
var dumpSymbols: Boolean = false,
|
||||
var newCodegen: Boolean = false,
|
||||
var varsHighBank: Int? = null,
|
||||
var varsGolden: Boolean = false,
|
||||
var breakpointCpuInstruction: String? = null,
|
||||
var ignoreFootguns: Boolean = false,
|
||||
var experimentalCodegen: Boolean = false,
|
||||
var varsHigh: Boolean = false,
|
||||
var useNewExprCode: Boolean = false,
|
||||
var evalStackBaseAddress: UInt? = null,
|
||||
var outputDir: Path = Path(""),
|
||||
var quiet: Boolean = false,
|
||||
var profilingInstrumentation: Boolean = false,
|
||||
var symbolDefs: Map<String, String> = emptyMap(),
|
||||
var privateSymbols: Boolean = false,
|
||||
var outputFile: Path? = null
|
||||
var symbolDefs: Map<String, String> = emptyMap()
|
||||
) {
|
||||
init {
|
||||
compTarget.initializeMemoryAreas(this)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val AllZeropageAllowed: List<UIntRange> = listOf(0u..255u)
|
||||
|
||||
fun builder(compTarget: ICompilationTarget) = Builder(compTarget)
|
||||
|
||||
class Builder(private val compTarget: ICompilationTarget) {
|
||||
private var output: OutputType = compTarget.defaultOutputType
|
||||
private var launcher: CbmPrgLauncherType = CbmPrgLauncherType.NONE
|
||||
private var zeropage: ZeropageType = ZeropageType.DONTUSE
|
||||
private var zpReserved: List<UIntRange> = emptyList()
|
||||
private var zpAllowed: List<UIntRange> = AllZeropageAllowed
|
||||
private var floats: Boolean = false
|
||||
private var noSysInit: Boolean = false
|
||||
private var romable: Boolean = false
|
||||
private var compilerVersion: String = "unknown"
|
||||
private var loadAddress: UInt = compTarget.PROGRAM_LOAD_ADDRESS
|
||||
private var memtopAddress: UInt = compTarget.PROGRAM_MEMTOP_ADDRESS
|
||||
private var varsAddress: UInt? = null
|
||||
private var warnSymbolShadowing: Boolean = false
|
||||
private var warnImplicitTypeCast: Boolean = false
|
||||
private var optimize: Boolean = false
|
||||
private var asmQuiet: Boolean = false
|
||||
private var asmListfile: Boolean = false
|
||||
private var includeSourcelines: Boolean = false
|
||||
private var dumpVariables: Boolean = false
|
||||
private var dumpSymbols: Boolean = false
|
||||
private var varsHighBank: Int? = null
|
||||
private var varsGolden: Boolean = false
|
||||
private var breakpointCpuInstruction: String? = null
|
||||
private var ignoreFootguns: Boolean = false
|
||||
private var outputDir: Path = Path("")
|
||||
private var quiet: Boolean = false
|
||||
private var profilingInstrumentation: Boolean = false
|
||||
private var newCodegen: Boolean = false
|
||||
private var symbolDefs: Map<String, String> = emptyMap()
|
||||
private var outputFile: Path? = null
|
||||
|
||||
fun output(output: OutputType) = apply { this.output = output }
|
||||
fun launcher(launcher: CbmPrgLauncherType) = apply { this.launcher = launcher }
|
||||
fun zeropage(zeropage: ZeropageType) = apply { this.zeropage = zeropage }
|
||||
fun zpReserved(zpReserved: List<UIntRange>) = apply { this.zpReserved = zpReserved }
|
||||
fun zpAllowed(zpAllowed: List<UIntRange>) = apply { this.zpAllowed = zpAllowed }
|
||||
fun floats(floats: Boolean) = apply { this.floats = floats }
|
||||
fun noSysInit(noSysInit: Boolean) = apply { this.noSysInit = noSysInit }
|
||||
fun romable(romable: Boolean) = apply { this.romable = romable }
|
||||
fun compilerVersion(compilerVersion: String) = apply { this.compilerVersion = compilerVersion }
|
||||
fun loadAddress(loadAddress: UInt) = apply { this.loadAddress = loadAddress }
|
||||
fun memtopAddress(memtopAddress: UInt) = apply { this.memtopAddress = memtopAddress }
|
||||
fun varsAddress(varsAddress: UInt?) = apply { this.varsAddress = varsAddress }
|
||||
fun warnSymbolShadowing(warnSymbolShadowing: Boolean) = apply { this.warnSymbolShadowing = warnSymbolShadowing }
|
||||
fun warnImplicitTypeCast(warnImplicitTypeCast: Boolean) = apply { this.warnImplicitTypeCast = warnImplicitTypeCast }
|
||||
fun optimize(optimize: Boolean) = apply { this.optimize = optimize }
|
||||
fun asmQuiet(asmQuiet: Boolean) = apply { this.asmQuiet = asmQuiet }
|
||||
fun asmListfile(asmListfile: Boolean) = apply { this.asmListfile = asmListfile }
|
||||
fun includeSourcelines(includeSourcelines: Boolean) = apply { this.includeSourcelines = includeSourcelines }
|
||||
fun dumpVariables(dumpVariables: Boolean) = apply { this.dumpVariables = dumpVariables }
|
||||
fun dumpSymbols(dumpSymbols: Boolean) = apply { this.dumpSymbols = dumpSymbols }
|
||||
fun varsHighBank(varsHighBank: Int?) = apply { this.varsHighBank = varsHighBank }
|
||||
fun varsGolden(varsGolden: Boolean) = apply { this.varsGolden = varsGolden }
|
||||
fun breakpointCpuInstruction(breakpointCpuInstruction: String?) = apply { this.breakpointCpuInstruction = breakpointCpuInstruction }
|
||||
fun ignoreFootguns(ignoreFootguns: Boolean) = apply { this.ignoreFootguns = ignoreFootguns }
|
||||
fun outputDir(outputDir: Path) = apply { this.outputDir = outputDir }
|
||||
fun quiet(quiet: Boolean) = apply { this.quiet = quiet }
|
||||
fun profilingInstrumentation(profilingInstrumentation: Boolean) = apply { this.profilingInstrumentation = profilingInstrumentation }
|
||||
fun symbolDefs(symbolDefs: Map<String, String>) = apply { this.symbolDefs = symbolDefs }
|
||||
fun newCodegen(newCodegen: Boolean) = apply { this.newCodegen = newCodegen }
|
||||
fun outputFile(outputFile: Path?) = apply { this.outputFile = outputFile }
|
||||
|
||||
fun build(): CompilationOptions {
|
||||
return CompilationOptions(
|
||||
output, launcher, zeropage, zpReserved, zpAllowed, floats, noSysInit, romable, compTarget, compilerVersion,
|
||||
loadAddress, memtopAddress, varsAddress, warnSymbolShadowing, warnImplicitTypeCast, optimize, asmQuiet, asmListfile,
|
||||
includeSourcelines, dumpVariables, dumpSymbols, newCodegen, varsHighBank, varsGolden,
|
||||
breakpointCpuInstruction, ignoreFootguns, outputDir, quiet,
|
||||
profilingInstrumentation, symbolDefs
|
||||
).apply { this.outputFile = this@Builder.outputFile }
|
||||
}
|
||||
}
|
||||
compTarget.machine.initializeMemoryAreas(this)
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,20 @@
|
||||
package prog8.code.core
|
||||
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.pow
|
||||
|
||||
val powersOfTwoFloat = (0..16).map { (2.0).pow(it) }.toTypedArray()
|
||||
val negativePowersOfTwoFloat = powersOfTwoFloat.map { -it }.toTypedArray()
|
||||
val powersOfTwoInt = (0..16).map { 2.0.pow(it).toInt() }.toTypedArray()
|
||||
|
||||
fun Number.toHex(): String {
|
||||
// 0..15 -> "0".."15"
|
||||
// 16..255 -> "$10".."$ff"
|
||||
// 256..65536 -> "$0100".."$ffff"
|
||||
// larger -> "$12345678"
|
||||
// negative values are prefixed with '-'.
|
||||
val integer = this.toLong()
|
||||
if(integer<0) {
|
||||
if(integer==-2147483648L)
|
||||
return "$80000000" // the exception to the rule, because -$80000000 is not a valid hex number
|
||||
val integer = this.toInt()
|
||||
if(integer<0)
|
||||
return '-' + abs(integer).toHex()
|
||||
}
|
||||
return when (integer) {
|
||||
in 0 until 16 -> integer.toString()
|
||||
in 0 until 0x100 -> "$"+integer.toString(16).padStart(2,'0')
|
||||
in 0 until 0x10000 -> "$"+integer.toString(16).padStart(4,'0')
|
||||
else -> "$"+integer.toString(16).padStart(8,'0')
|
||||
else -> throw IllegalArgumentException("number too large for 16 bits $this")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,12 +22,11 @@ fun UInt.toHex(): String {
|
||||
// 0..15 -> "0".."15"
|
||||
// 16..255 -> "$10".."$ff"
|
||||
// 256..65536 -> "$0100".."$ffff"
|
||||
// larger -> "$12345678"
|
||||
return when (this) {
|
||||
in 0u until 16u -> this.toString()
|
||||
in 0u until 0x100u -> "$"+this.toString(16).padStart(2,'0')
|
||||
in 0u until 0x10000u -> "$"+this.toString(16).padStart(4,'0')
|
||||
else -> "$"+this.toString(16).padStart(8,'0')
|
||||
else -> throw IllegalArgumentException("number too large for 16 bits $this")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +58,6 @@ fun String.unescape(): String {
|
||||
'\\' -> '\\'
|
||||
'n' -> '\n'
|
||||
'r' -> '\r'
|
||||
't' -> '\t'
|
||||
'"' -> '"'
|
||||
'\'' -> '\''
|
||||
'u' -> {
|
||||
@@ -98,24 +87,3 @@ fun String.unescape(): String {
|
||||
}
|
||||
return result.joinToString("")
|
||||
}
|
||||
|
||||
// === Symbol name prefixing ===
|
||||
// These are used by code generators to add p8b_/p8s_/p8v_/etc. prefixes to symbol names.
|
||||
// The prefix character depends on the symbol type (block='b', sub='s', variable='v', etc.)
|
||||
// Scoped names (containing dots) have each part prefixed according to its role.
|
||||
|
||||
fun prefixScopedName(name: String, type: Char): String {
|
||||
val generatedPrefix = "p8_label_gen_"
|
||||
if('.' !in name) {
|
||||
if(name.startsWith(generatedPrefix))
|
||||
return name
|
||||
return "p8${type}_$name"
|
||||
}
|
||||
val parts = name.split('.')
|
||||
val firstPrefixed = "p8b_${parts[0]}"
|
||||
val lastPart = parts.last()
|
||||
val lastPrefixed = if(lastPart.startsWith(generatedPrefix)) lastPart else "p8${type}_$lastPart"
|
||||
val inbetweenPrefixed = parts.drop(1).dropLast(1).map{ "p8s_$it" }
|
||||
val prefixed = listOf(firstPrefixed) + inbetweenPrefixed + listOf(lastPrefixed)
|
||||
return prefixed.joinToString(".")
|
||||
}
|
||||
@@ -1,534 +0,0 @@
|
||||
package prog8.code.core
|
||||
|
||||
import java.util.Objects
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Base data types supported by the Prog8 compiler.
|
||||
* These represent the fundamental types that can be used in Prog8 programs.
|
||||
*/
|
||||
enum class BaseDataType {
|
||||
UBYTE, // pass by value 8 bits unsigned
|
||||
BYTE, // pass by value 8 bits signed
|
||||
UWORD, // pass by value 16 bits unsigned
|
||||
WORD, // pass by value 16 bits signed
|
||||
LONG, // pass by value 32 bits signed
|
||||
FLOAT, // pass by value machine dependent
|
||||
BOOL, // pass by value bit 0 of an 8-bit byte
|
||||
STR, // pass by reference
|
||||
ARRAY, // pass by reference, subtype is the element type
|
||||
ARRAY_SPLITW, // pass by reference, split word layout, subtype is the element type (restricted to word types)
|
||||
POINTER, // typed pointer, subtype is whatever type is pointed to
|
||||
STRUCT_INSTANCE, // the actual instance of a struct (not directly supported in the language yet, but we need its type)
|
||||
UNDEFINED;
|
||||
|
||||
|
||||
fun largerSizeThan(other: BaseDataType) =
|
||||
when {
|
||||
this == other -> false
|
||||
this.isByteOrBool -> false
|
||||
this.isWord -> other.isByteOrBool
|
||||
this == LONG -> other.isByteOrBool || other.isWord
|
||||
this == STR && (other == UWORD || other == LONG) || this == UWORD && other == STR -> false
|
||||
this.isArray && other.isArray -> false
|
||||
this.isArray -> other.isByteOrBool
|
||||
this == STR -> other.isByteOrBool
|
||||
this.isPointer -> other.isByteOrBool
|
||||
else -> true
|
||||
}
|
||||
|
||||
fun equalsSize(other: BaseDataType): Boolean = equalsSize(other, null)
|
||||
fun equalsSize(other: BaseDataType, memsizer: IMemSizer?): Boolean =
|
||||
when {
|
||||
this == other -> true
|
||||
this.isArray && other.isArray -> true
|
||||
this.isByteOrBool -> other.isByteOrBool
|
||||
this.isWord -> other.isWord || (other.isPointer && (memsizer == null || memsizer.POINTER_MEM_SIZE == 2u))
|
||||
this.isLong -> other.isLong || (other.isPointer && memsizer?.POINTER_MEM_SIZE != null && memsizer.POINTER_MEM_SIZE != 2u)
|
||||
this.isPointer -> when(memsizer?.POINTER_MEM_SIZE) {
|
||||
null -> other.isWord || other.isPointer
|
||||
2u -> other.isWord || other.isPointer
|
||||
else -> other.isLong || other.isPointer
|
||||
}
|
||||
this == STR && (other == UWORD || other == LONG) || (this == UWORD || this == LONG) && other == STR -> true
|
||||
this == STR && other.isArray -> true
|
||||
this.isArray && other == STR -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// BaseDataType Extension Properties
|
||||
// ============================================================================
|
||||
|
||||
val BaseDataType.isByte get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE)
|
||||
val BaseDataType.isUnsignedByte get() = this == BaseDataType.UBYTE
|
||||
val BaseDataType.isSignedByte get() = this == BaseDataType.BYTE
|
||||
val BaseDataType.isBool get() = this == BaseDataType.BOOL
|
||||
val BaseDataType.isByteOrBool get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.BOOL)
|
||||
val BaseDataType.isWord get() = this in arrayOf(BaseDataType.UWORD, BaseDataType.WORD)
|
||||
val BaseDataType.isUnsignedWord get() = this == BaseDataType.UWORD
|
||||
val BaseDataType.isSignedWord get() = this == BaseDataType.WORD
|
||||
val BaseDataType.isLong get() = this == BaseDataType.LONG
|
||||
val BaseDataType.isFloat get() = this == BaseDataType.FLOAT
|
||||
val BaseDataType.isInteger get() = this in setOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)
|
||||
val BaseDataType.maxUnsignedValue: Double
|
||||
get() = when (this) {
|
||||
BaseDataType.UBYTE -> 255.0
|
||||
BaseDataType.BYTE -> 127.0
|
||||
BaseDataType.UWORD -> 65535.0
|
||||
BaseDataType.WORD -> 32767.0
|
||||
BaseDataType.LONG -> 2147483647.0
|
||||
else -> Double.MAX_VALUE
|
||||
}
|
||||
val BaseDataType.isIntegerOrBool get() = this in setOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.BOOL)
|
||||
val BaseDataType.isWordOrByteOrBool get() = this in setOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.BOOL)
|
||||
val BaseDataType.isNumeric get() = this == BaseDataType.FLOAT || this.isInteger
|
||||
val BaseDataType.isNumericOrBool get() = this == BaseDataType.BOOL || this.isNumeric
|
||||
val BaseDataType.isSigned get() = this in setOf(BaseDataType.BYTE, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
val BaseDataType.isUnsigned get() = !this.isSigned
|
||||
val BaseDataType.isSignedInteger get() = this.isSigned && this.isInteger
|
||||
val BaseDataType.isUnsignedInteger get() = this.isUnsigned && this.isInteger
|
||||
val BaseDataType.isArray get() = this == BaseDataType.ARRAY || this == BaseDataType.ARRAY_SPLITW
|
||||
val BaseDataType.isPointer get() = this == BaseDataType.POINTER
|
||||
val BaseDataType.isStructInstance get() = this == BaseDataType.STRUCT_INSTANCE
|
||||
val BaseDataType.isIterable get() = this in setOf(BaseDataType.STR, BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW)
|
||||
val BaseDataType.isPassByRef get() = this.isIterable && !this.isPointer
|
||||
val BaseDataType.isPassByValue get() = !this.isIterable || this.isPointer
|
||||
|
||||
|
||||
/**
|
||||
* Interface for types that can be used as subtypes in DataType.
|
||||
* Primarily used for struct types.
|
||||
*/
|
||||
interface ISubType {
|
||||
val scopedNameString: String
|
||||
fun memsize(sizer: IMemSizer): Int
|
||||
fun sameas(other: ISubType): Boolean
|
||||
fun getFieldType(name: String): DataType?
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Represents a complete data type in Prog8, including base type and optional subtype.
|
||||
*
|
||||
* DataType is immutable after construction. Use the companion object factory methods
|
||||
* to create instances.
|
||||
*
|
||||
* @property base The base data type (e.g., UBYTE, WORD, ARRAY, POINTER)
|
||||
* @property sub The subtype for arrays and strings (e.g., UBYTE for byte arrays)
|
||||
* @property subType The structured subtype for pointers and struct instances
|
||||
* @property subTypeFromAntlr Deferred subtype resolution from parser
|
||||
*/
|
||||
class DataType private constructor(
|
||||
val base: BaseDataType,
|
||||
val sub: BaseDataType?,
|
||||
val pointeeSub: BaseDataType?,
|
||||
var subType: ISubType?,
|
||||
var subTypeFromAntlr: List<String>? = null
|
||||
) {
|
||||
|
||||
init {
|
||||
when {
|
||||
base.isArray && sub == BaseDataType.POINTER -> {
|
||||
require(pointeeSub != null || subType != null || subTypeFromAntlr != null)
|
||||
}
|
||||
base.isArray -> {
|
||||
require(sub != null)
|
||||
// subType and subTypeFromAntlr allowed for arrays with struct element types
|
||||
// (from parser creating ARRAY(LONG) directly for 32-bit targets, or from VariousCleanups conversion)
|
||||
if(base == BaseDataType.ARRAY_SPLITW)
|
||||
require(sub == BaseDataType.UWORD || sub == BaseDataType.WORD)
|
||||
}
|
||||
base==BaseDataType.STR -> require(sub==BaseDataType.UBYTE) { "string subtype should be ubyte" }
|
||||
base!=BaseDataType.POINTER -> require(sub == null) { "only string, array and pointer base types can have a subtype"}
|
||||
else -> {
|
||||
require(sub == null || (subType == null && subTypeFromAntlr == null)) {
|
||||
"sub and subtype can't both be set"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is DataType) return false
|
||||
if (base != other.base || sub != other.sub || pointeeSub != other.pointeeSub) return false
|
||||
val st1 = subType
|
||||
val st2 = other.subType
|
||||
if (st1 == st2) return true
|
||||
if (st1 == null || st2 == null) return false
|
||||
return st1.sameas(st2)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = Objects.hash(base, sub, pointeeSub, subType)
|
||||
|
||||
fun setActualSubType(actualSubType: ISubType) {
|
||||
subType = actualSubType
|
||||
subTypeFromAntlr = null
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Companion Object - Factory Methods
|
||||
// ============================================================================
|
||||
|
||||
companion object {
|
||||
|
||||
val UBYTE = DataType(BaseDataType.UBYTE, null, null, null)
|
||||
val BYTE = DataType(BaseDataType.BYTE, null, null, null)
|
||||
val UWORD = DataType(BaseDataType.UWORD, null, null, null)
|
||||
val WORD = DataType(BaseDataType.WORD, null, null, null)
|
||||
val LONG = DataType(BaseDataType.LONG, null, null, null)
|
||||
val FLOAT = DataType(BaseDataType.FLOAT, null, null, null)
|
||||
val BOOL = DataType(BaseDataType.BOOL, null, null, null)
|
||||
val STR = DataType(BaseDataType.STR, BaseDataType.UBYTE, null, null)
|
||||
val UNDEFINED = DataType(BaseDataType.UNDEFINED, null, null, null)
|
||||
|
||||
private val simpletypes = mapOf(
|
||||
BaseDataType.UBYTE to DataType(BaseDataType.UBYTE, null, null, null),
|
||||
BaseDataType.BYTE to DataType(BaseDataType.BYTE, null, null, null),
|
||||
BaseDataType.UWORD to DataType(BaseDataType.UWORD, null, null, null),
|
||||
BaseDataType.WORD to DataType(BaseDataType.WORD, null, null, null),
|
||||
BaseDataType.LONG to DataType(BaseDataType.LONG, null, null, null),
|
||||
BaseDataType.FLOAT to DataType(BaseDataType.FLOAT, null, null, null),
|
||||
BaseDataType.BOOL to DataType(BaseDataType.BOOL, null, null, null),
|
||||
BaseDataType.STR to DataType(BaseDataType.STR, BaseDataType.UBYTE, null, null),
|
||||
BaseDataType.POINTER to DataType(BaseDataType.POINTER, null, null, null),
|
||||
BaseDataType.UNDEFINED to DataType(BaseDataType.UNDEFINED, null, null, null)
|
||||
)
|
||||
|
||||
fun forDt(dt: BaseDataType): DataType {
|
||||
if(dt.isStructInstance)
|
||||
return DataType(BaseDataType.STRUCT_INSTANCE, null, null, null)
|
||||
return simpletypes.getValue(dt)
|
||||
}
|
||||
|
||||
fun arrayFor(elementDt: BaseDataType, memsizer: IMemSizer): DataType {
|
||||
require(elementDt != BaseDataType.POINTER) { "use arrayOfPointersTo for arrays of pointers" }
|
||||
val actualElementDt = if(elementDt==BaseDataType.STR) {
|
||||
memsizer.pointerBaseType
|
||||
} else elementDt
|
||||
if(actualElementDt.isNumericOrBool)
|
||||
return DataType(BaseDataType.ARRAY, actualElementDt, null, null)
|
||||
else
|
||||
throw NoSuchElementException("invalid basic element dt $elementDt")
|
||||
}
|
||||
|
||||
fun splitWordArrayFor(elementDt: BaseDataType): DataType {
|
||||
require(elementDt.isWord) { "split word array element type must be word" }
|
||||
return DataType(BaseDataType.ARRAY_SPLITW, elementDt, null, null)
|
||||
}
|
||||
|
||||
fun arrayOfPointersTo(sub: BaseDataType): DataType =
|
||||
DataType(BaseDataType.ARRAY, BaseDataType.POINTER, sub, null)
|
||||
fun arrayOfPointersTo(structType: ISubType?): DataType =
|
||||
DataType(BaseDataType.ARRAY, BaseDataType.POINTER, BaseDataType.STRUCT_INSTANCE, structType)
|
||||
fun arrayOfPointersFromAntlrTo(sub: BaseDataType?, identifier: List<String>?): DataType =
|
||||
DataType(BaseDataType.ARRAY, BaseDataType.POINTER, sub ?: BaseDataType.STRUCT_INSTANCE, null, identifier)
|
||||
|
||||
// array of struct instances: sub is STRUCT_INSTANCE, subType carries the resolved struct
|
||||
fun arrayOfStructs(structType: ISubType): DataType = DataType(BaseDataType.ARRAY, BaseDataType.STRUCT_INSTANCE, null, structType)
|
||||
fun arrayOfStructsFromAntlr(struct: List<String>): DataType =
|
||||
DataType(BaseDataType.ARRAY, BaseDataType.STRUCT_INSTANCE, null, null, subTypeFromAntlr = struct)
|
||||
|
||||
fun pointer(base: BaseDataType): DataType = DataType(BaseDataType.POINTER, base, null, null)
|
||||
fun pointer(dt: DataType): DataType = if(dt.isBasic)
|
||||
DataType(BaseDataType.POINTER, dt.base, null, null)
|
||||
else
|
||||
DataType(BaseDataType.POINTER, null, null, dt.subType, dt.subTypeFromAntlr)
|
||||
fun pointer(structType: ISubType): DataType = DataType(BaseDataType.POINTER, null, null, structType)
|
||||
fun pointerFromAntlr(identifier: List<String>): DataType = DataType(BaseDataType.POINTER, null, null, null, identifier)
|
||||
fun structInstance(type: ISubType?): DataType = DataType(BaseDataType.STRUCT_INSTANCE, sub=null, pointeeSub=null, type)
|
||||
fun structInstanceFromAntlr(struct: List<String>): DataType = DataType(BaseDataType.STRUCT_INSTANCE, null, null, null, subTypeFromAntlr = struct)
|
||||
}
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// DataType Methods
|
||||
// ============================================================================
|
||||
|
||||
fun elementToArray(target: ICompilationTarget, splitwords: Boolean = true): DataType {
|
||||
fun pointerArray(): DataType =
|
||||
if (subType != null)
|
||||
arrayOfPointersTo(subType)
|
||||
else if (subTypeFromAntlr != null)
|
||||
arrayOfPointersFromAntlrTo(sub, subTypeFromAntlr)
|
||||
else
|
||||
arrayOfPointersTo(sub!!)
|
||||
|
||||
if(base == BaseDataType.STRUCT_INSTANCE) {
|
||||
val st = subType
|
||||
return if(st != null)
|
||||
arrayOfStructs(st)
|
||||
else
|
||||
arrayOfStructsFromAntlr(subTypeFromAntlr!!)
|
||||
}
|
||||
|
||||
if(base.isPointer)
|
||||
return pointerArray()
|
||||
|
||||
if(!target.cpu.is6502)
|
||||
return arrayFor(base, target)
|
||||
|
||||
return if (splitwords && (base == BaseDataType.UWORD || base == BaseDataType.WORD || base == BaseDataType.STR))
|
||||
splitWordArrayFor(base)
|
||||
else
|
||||
arrayFor(base, target)
|
||||
}
|
||||
|
||||
fun elementType(): DataType =
|
||||
when {
|
||||
isPointerArray -> {
|
||||
if (subType != null) DataType(BaseDataType.POINTER, null, null, subType)
|
||||
else if (subTypeFromAntlr != null) DataType(BaseDataType.POINTER, null, null, null, subTypeFromAntlr)
|
||||
else DataType(BaseDataType.POINTER, pointeeSub!!, null, null)
|
||||
}
|
||||
base.isArray || base==BaseDataType.STR -> {
|
||||
if (sub == BaseDataType.STRUCT_INSTANCE && subType != null)
|
||||
return DataType(BaseDataType.STRUCT_INSTANCE, null, null, subType)
|
||||
val dt = forDt(sub!!)
|
||||
if (subType != null) DataType(BaseDataType.POINTER, null, null, subType) else dt
|
||||
}
|
||||
else -> throw IllegalArgumentException("not an array")
|
||||
}
|
||||
|
||||
fun typeForUntypedAddressOf(msb: Boolean, mem: IMemSizer): DataType {
|
||||
val untypedPointerType = if(mem.memorySize(BaseDataType.POINTER)>2) LONG else UWORD
|
||||
if (isUndefined)
|
||||
return if(msb) pointer(BaseDataType.UBYTE) else untypedPointerType
|
||||
else {
|
||||
if (isBasic)
|
||||
return pointer(base)
|
||||
if (isString)
|
||||
return pointer(BaseDataType.UBYTE)
|
||||
if (isPointer)
|
||||
return untypedPointerType
|
||||
if (isArray) {
|
||||
val isSplit = base == BaseDataType.ARRAY_SPLITW ||
|
||||
(isPointerArray && mem.memorySize(BaseDataType.POINTER) <= 2)
|
||||
if (msb || isSplit)
|
||||
return pointer(BaseDataType.UBYTE)
|
||||
val elementDt = elementType()
|
||||
if (elementDt.isBasic)
|
||||
return pointer(elementDt)
|
||||
return untypedPointerType
|
||||
}
|
||||
if (subType != null)
|
||||
return pointer(this)
|
||||
return untypedPointerType
|
||||
}
|
||||
}
|
||||
|
||||
fun dereference(): DataType {
|
||||
require(isPointer || isUnsignedWord) { "cannot dereference non-pointer type $this"}
|
||||
return when {
|
||||
isUnsignedWord -> forDt(BaseDataType.UBYTE)
|
||||
sub!=null -> forDt(sub)
|
||||
subType!=null -> DataType(BaseDataType.STRUCT_INSTANCE, null, null, subType)
|
||||
subTypeFromAntlr!=null -> DataType(BaseDataType.STRUCT_INSTANCE, null, null, null, subTypeFromAntlr)
|
||||
else -> throw IllegalArgumentException("cannot dereference this pointer type")
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String = when(base) {
|
||||
BaseDataType.ARRAY -> {
|
||||
when(sub) {
|
||||
BaseDataType.BOOL -> "bool[]"
|
||||
BaseDataType.FLOAT -> "float[]"
|
||||
BaseDataType.BYTE -> "byte[]"
|
||||
BaseDataType.WORD -> "word[]"
|
||||
BaseDataType.UBYTE -> "ubyte[]"
|
||||
BaseDataType.UWORD -> "uword[]"
|
||||
BaseDataType.LONG -> "long[]"
|
||||
BaseDataType.POINTER -> {
|
||||
val pointee = if (subType != null) subType!!.scopedNameString
|
||||
else if (subTypeFromAntlr != null) "$subTypeFromAntlr"
|
||||
else pointeeSub!!.name.lowercase()
|
||||
"^^$pointee[]"
|
||||
}
|
||||
BaseDataType.STRUCT_INSTANCE -> if (subType != null) "${subType!!.scopedNameString}[]" else "$subTypeFromAntlr[]"
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
}
|
||||
}
|
||||
BaseDataType.ARRAY_SPLITW -> {
|
||||
when(sub) {
|
||||
BaseDataType.WORD -> "word[] (split)"
|
||||
BaseDataType.UWORD -> "uword[] (split)"
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
}
|
||||
}
|
||||
BaseDataType.POINTER -> {
|
||||
if(sub!=null) "^^${sub.name.lowercase()}" else if(subType!=null) "^^${subType!!.scopedNameString}" else "^^${subTypeFromAntlr}"
|
||||
}
|
||||
BaseDataType.STRUCT_INSTANCE -> {
|
||||
sub?.name?.lowercase() ?: if (subType!=null) subType!!.scopedNameString else "$subTypeFromAntlr"
|
||||
}
|
||||
else -> base.name.lowercase()
|
||||
}
|
||||
|
||||
fun sourceString(): String = when (base) {
|
||||
BaseDataType.BOOL -> "bool"
|
||||
BaseDataType.UBYTE -> "ubyte"
|
||||
BaseDataType.BYTE -> "byte"
|
||||
BaseDataType.UWORD -> "uword"
|
||||
BaseDataType.WORD -> "word"
|
||||
BaseDataType.LONG -> "long"
|
||||
BaseDataType.FLOAT -> "float"
|
||||
BaseDataType.STR -> "str"
|
||||
BaseDataType.POINTER -> {
|
||||
when {
|
||||
sub!=null -> "^^${sub.name.lowercase()}"
|
||||
subType!=null -> "^^${subType!!.scopedNameString}"
|
||||
subTypeFromAntlr!=null -> "^^${subTypeFromAntlr!!.joinToString(".")}"
|
||||
else -> "?????"
|
||||
}
|
||||
}
|
||||
BaseDataType.STRUCT_INSTANCE -> {
|
||||
when {
|
||||
sub!=null -> sub.name.lowercase()
|
||||
subType!=null -> subType!!.scopedNameString
|
||||
subTypeFromAntlr!=null -> subTypeFromAntlr!!.joinToString(".")
|
||||
else -> "?????"
|
||||
}
|
||||
}
|
||||
BaseDataType.ARRAY -> {
|
||||
when(sub) {
|
||||
BaseDataType.UBYTE -> "ubyte["
|
||||
BaseDataType.UWORD -> "@nosplit uword["
|
||||
BaseDataType.BOOL -> "bool["
|
||||
BaseDataType.BYTE -> "byte["
|
||||
BaseDataType.WORD -> "@nosplit word["
|
||||
BaseDataType.LONG -> "long["
|
||||
BaseDataType.FLOAT -> "float["
|
||||
BaseDataType.POINTER -> {
|
||||
val pointee = if (subType != null) "^^${subType!!.scopedNameString}["
|
||||
else if (subTypeFromAntlr != null) "^^${subTypeFromAntlr!!.joinToString(".")}["
|
||||
else "^^${pointeeSub!!.name.lowercase()}["
|
||||
pointee
|
||||
}
|
||||
BaseDataType.STRUCT_INSTANCE -> if (subType != null) "${subType!!.scopedNameString}[" else "$subTypeFromAntlr["
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
}
|
||||
}
|
||||
BaseDataType.ARRAY_SPLITW -> {
|
||||
when(sub) {
|
||||
BaseDataType.UWORD -> "uword["
|
||||
BaseDataType.WORD -> "word["
|
||||
else -> throw IllegalArgumentException("invalid sub type")
|
||||
}
|
||||
}
|
||||
BaseDataType.UNDEFINED -> throw IllegalArgumentException("wrong dt")
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this type is assignable to the given target type (perhaps via a typecast)
|
||||
* without loss of precision.
|
||||
*/
|
||||
infix fun isAssignableTo(targetType: DataType) =
|
||||
when(base) {
|
||||
BaseDataType.BOOL -> targetType.base == BaseDataType.BOOL
|
||||
BaseDataType.UBYTE -> targetType.base in setOf(BaseDataType.UBYTE, BaseDataType.WORD, BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
BaseDataType.BYTE -> targetType.base in setOf(BaseDataType.BYTE, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
BaseDataType.UWORD -> targetType.base in setOf(BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.FLOAT, BaseDataType.POINTER) || targetType.isPointerArray
|
||||
BaseDataType.WORD -> targetType.base in setOf(BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
BaseDataType.LONG -> targetType.base in setOf(BaseDataType.LONG, BaseDataType.FLOAT, BaseDataType.POINTER) || targetType.isPointerArray
|
||||
BaseDataType.FLOAT -> targetType.base in arrayOf(BaseDataType.FLOAT)
|
||||
BaseDataType.STR -> targetType.base in setOf(BaseDataType.STR, BaseDataType.UWORD, BaseDataType.LONG) || (targetType.isPointer && targetType.sub==BaseDataType.UBYTE)
|
||||
BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW -> {
|
||||
if (isPointerArray) {
|
||||
targetType.isPointerArray && if (subType != null && targetType.subType != null) subType!!.sameas(targetType.subType!!)
|
||||
else if (subType == null && targetType.subType == null) pointeeSub == targetType.pointeeSub
|
||||
else false
|
||||
} else {
|
||||
targetType.base in setOf(BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW) && targetType.sub == sub
|
||||
}
|
||||
}
|
||||
BaseDataType.POINTER -> {
|
||||
when {
|
||||
targetType.base == BaseDataType.UWORD || targetType.base == BaseDataType.LONG -> true
|
||||
targetType.isPointer -> this.isUnsignedWord || this == targetType
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
BaseDataType.STRUCT_INSTANCE -> false // we cannot deal with actual struct instances yet in any shape or form (only getting fields from it)
|
||||
BaseDataType.UNDEFINED -> false
|
||||
}
|
||||
|
||||
fun largerSizeThan(other: DataType): Boolean = base.largerSizeThan(other.base)
|
||||
fun equalsSize(other: DataType): Boolean = base.equalsSize(other.base)
|
||||
fun equalsSize(other: DataType, memsizer: IMemSizer): Boolean = base.equalsSize(other.base, memsizer)
|
||||
|
||||
/**
|
||||
* Returns the memory size in bytes.
|
||||
* Note: for pointer types, size() doesn't return the size of the pointer itself
|
||||
* but the size of the thing it points to.
|
||||
*/
|
||||
fun size(memsizer: IMemSizer): Int = when {
|
||||
base == BaseDataType.ARRAY && sub == BaseDataType.POINTER -> memsizer.memorySize(BaseDataType.POINTER)
|
||||
sub!=null -> memsizer.memorySize(sub)
|
||||
subType!=null -> subType!!.memsize(memsizer)
|
||||
else -> memsizer.memorySize(base)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DataType Properties
|
||||
// ============================================================================
|
||||
|
||||
val isBasic = sub==null && subType==null && subTypeFromAntlr==null
|
||||
val isUndefined = base == BaseDataType.UNDEFINED
|
||||
val isByte = base.isByte
|
||||
val isUnsignedByte = base == BaseDataType.UBYTE
|
||||
val isSignedByte = base == BaseDataType.BYTE
|
||||
val isByteOrBool = base.isByteOrBool
|
||||
val isWord = base.isWord
|
||||
val isUnsignedWord = base == BaseDataType.UWORD
|
||||
val isSignedWord = base == BaseDataType.WORD
|
||||
val isInteger = base.isInteger
|
||||
val isWordOrByteOrBool = base.isWordOrByteOrBool
|
||||
val isIntegerOrBool = base.isIntegerOrBool
|
||||
val isNumeric = base.isNumeric
|
||||
val isNumericOrBool = base.isNumericOrBool
|
||||
val isSigned = base.isSigned
|
||||
val isUnsigned = !base.isSigned
|
||||
val isSignedInteger = isSigned && isInteger
|
||||
val isUnsignedInteger = isUnsigned && isInteger
|
||||
val isArray = base.isArray
|
||||
val isPointer = base.isPointer
|
||||
val isPointerToByte = base.isPointer && sub?.isByteOrBool==true
|
||||
val isPointerToWord = base.isPointer && sub?.isWord==true
|
||||
val isStructInstance = base.isStructInstance
|
||||
val isPointerArray = base == BaseDataType.ARRAY && sub == BaseDataType.POINTER
|
||||
val isBoolArray = base == BaseDataType.ARRAY && sub == BaseDataType.BOOL
|
||||
val isByteArray = base == BaseDataType.ARRAY && (sub == BaseDataType.UBYTE || sub == BaseDataType.BYTE)
|
||||
val isUnsignedByteArray = base == BaseDataType.ARRAY && sub == BaseDataType.UBYTE
|
||||
val isSignedByteArray = base == BaseDataType.ARRAY && sub == BaseDataType.BYTE
|
||||
val isWordArray = (base == BaseDataType.ARRAY || base == BaseDataType.ARRAY_SPLITW) && (sub == BaseDataType.UWORD || sub == BaseDataType.WORD)
|
||||
val isUnsignedWordArray = (base == BaseDataType.ARRAY || base == BaseDataType.ARRAY_SPLITW) && sub == BaseDataType.UWORD
|
||||
val isSignedWordArray = (base == BaseDataType.ARRAY || base == BaseDataType.ARRAY_SPLITW) && sub == BaseDataType.WORD
|
||||
val isLongArray = base == BaseDataType.ARRAY && sub == BaseDataType.LONG
|
||||
val isFloatArray = base == BaseDataType.ARRAY && sub == BaseDataType.FLOAT
|
||||
val isString = base == BaseDataType.STR
|
||||
val isBool = base == BaseDataType.BOOL
|
||||
val isFloat = base == BaseDataType.FLOAT
|
||||
val isLong = base == BaseDataType.LONG
|
||||
fun isStringly(memsizer: IMemSizer): Boolean =
|
||||
base == BaseDataType.STR ||
|
||||
(memsizer.POINTER_MEM_SIZE == 2u && base == BaseDataType.UWORD) ||
|
||||
(memsizer.POINTER_MEM_SIZE > 2u && base == BaseDataType.LONG) ||
|
||||
(base == BaseDataType.ARRAY && (sub == BaseDataType.UBYTE || sub == BaseDataType.BYTE))
|
||||
fun isSplitWordArray(memsizer: IMemSizer): Boolean {
|
||||
return when (base) {
|
||||
BaseDataType.ARRAY_SPLITW -> true
|
||||
BaseDataType.ARRAY if sub == BaseDataType.POINTER -> memsizer.POINTER_MEM_SIZE <= 2u
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
val isSplitUnsignedWordArray = base == BaseDataType.ARRAY_SPLITW && sub == BaseDataType.UWORD
|
||||
val isSplitSignedWordArray = base == BaseDataType.ARRAY_SPLITW && sub == BaseDataType.WORD
|
||||
val isIterable = base.isIterable
|
||||
val isPassByRef = base.isPassByRef
|
||||
val isPassByValue = base.isPassByValue
|
||||
}
|
||||
@@ -1,14 +1,62 @@
|
||||
package prog8.code.core
|
||||
|
||||
enum class DataType {
|
||||
UBYTE, // pass by value
|
||||
BYTE, // pass by value
|
||||
UWORD, // pass by value
|
||||
WORD, // pass by value
|
||||
FLOAT, // pass by value
|
||||
BOOL, // pass by value
|
||||
STR, // pass by reference
|
||||
ARRAY_UB, // pass by reference
|
||||
ARRAY_B, // pass by reference
|
||||
ARRAY_UW, // pass by reference
|
||||
ARRAY_W, // pass by reference
|
||||
ARRAY_F, // pass by reference
|
||||
ARRAY_BOOL, // pass by reference
|
||||
UNDEFINED;
|
||||
|
||||
/**
|
||||
* is the type assignable to the given other type (perhaps via a typecast) without loss of precision?
|
||||
*/
|
||||
infix fun isAssignableTo(targetType: DataType) =
|
||||
when(this) {
|
||||
BOOL -> targetType.oneOf(BOOL, BYTE, UBYTE, WORD, UWORD, FLOAT)
|
||||
UBYTE -> targetType.oneOf(UBYTE, WORD, UWORD, FLOAT, BOOL)
|
||||
BYTE -> targetType.oneOf(BYTE, WORD, FLOAT)
|
||||
UWORD -> targetType.oneOf(UWORD, FLOAT)
|
||||
WORD -> targetType.oneOf(WORD, FLOAT)
|
||||
FLOAT -> targetType.oneOf(FLOAT)
|
||||
STR -> targetType.oneOf(STR, UWORD)
|
||||
in ArrayDatatypes -> targetType == this
|
||||
else -> false
|
||||
}
|
||||
|
||||
fun oneOf(vararg types: DataType) = this in types
|
||||
|
||||
infix fun largerThan(other: DataType) =
|
||||
when {
|
||||
this == other -> false
|
||||
this in ByteDatatypes -> false
|
||||
this in WordDatatypes -> other in ByteDatatypes
|
||||
this== STR && other== UWORD || this== UWORD && other== STR -> false
|
||||
else -> true
|
||||
}
|
||||
|
||||
infix fun equalsSize(other: DataType) =
|
||||
when {
|
||||
this == other -> true
|
||||
this in ByteDatatypes -> other in ByteDatatypes
|
||||
this in WordDatatypes -> other in WordDatatypes
|
||||
this== STR && other== UWORD || this== UWORD && other== STR -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
enum class CpuRegister {
|
||||
A,
|
||||
X,
|
||||
Y,
|
||||
// m68k:
|
||||
D0, D1, D2, D3, D4, D5, D6, D7,
|
||||
A0, A1, A2, A3, A4, A5, A6,
|
||||
FP0, FP1, FP2, FP3, FP4, FP5, FP6, FP7
|
||||
Y
|
||||
}
|
||||
|
||||
enum class RegisterOrPair {
|
||||
@@ -22,63 +70,10 @@ enum class RegisterOrPair {
|
||||
FAC2,
|
||||
// cx16 virtual registers:
|
||||
R0, R1, R2, R3, R4, R5, R6, R7,
|
||||
R8, R9, R10, R11, R12, R13, R14, R15,
|
||||
// combined virtual registers to store 32 bits longs:
|
||||
R0R1, R2R3, R4R5, R6R7, R8R9, R10R11, R12R13, R14R15,
|
||||
// m68k data & address registers:
|
||||
D0, D1, D2, D3, D4, D5, D6, D7,
|
||||
A0, A1, A2, A3, A4, A5, A6,
|
||||
// m68k FPU registers:
|
||||
FP0, FP1, FP2, FP3, FP4, FP5, FP6, FP7;
|
||||
R8, R9, R10, R11, R12, R13, R14, R15;
|
||||
|
||||
companion object {
|
||||
val names: Set<String> = entries.map { it.toString() }.toSet()
|
||||
fun fromCpuRegister(cpu: CpuRegister): RegisterOrPair {
|
||||
return when(cpu) {
|
||||
CpuRegister.A -> A
|
||||
CpuRegister.X -> X
|
||||
CpuRegister.Y -> Y
|
||||
CpuRegister.D0 -> D0
|
||||
CpuRegister.D1 -> D1
|
||||
CpuRegister.D2 -> D2
|
||||
CpuRegister.D3 -> D3
|
||||
CpuRegister.D4 -> D4
|
||||
CpuRegister.D5 -> D5
|
||||
CpuRegister.D6 -> D6
|
||||
CpuRegister.D7 -> D7
|
||||
CpuRegister.A0 -> A0
|
||||
CpuRegister.A1 -> A1
|
||||
CpuRegister.A2 -> A2
|
||||
CpuRegister.A3 -> A3
|
||||
CpuRegister.A4 -> A4
|
||||
CpuRegister.A5 -> A5
|
||||
CpuRegister.A6 -> A6
|
||||
CpuRegister.FP0 -> FP0
|
||||
CpuRegister.FP1 -> FP1
|
||||
CpuRegister.FP2 -> FP2
|
||||
CpuRegister.FP3 -> FP3
|
||||
CpuRegister.FP4 -> FP4
|
||||
CpuRegister.FP5 -> FP5
|
||||
CpuRegister.FP6 -> FP6
|
||||
CpuRegister.FP7 -> FP7
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the starting virtual register name for the current 32-bit combined virtual register
|
||||
* @return The starting register name as a string, WITHOUT THE cx16 block scope prefix!
|
||||
*/
|
||||
fun startregname() = when(this) {
|
||||
R0R1 -> "r0"
|
||||
R2R3 -> "r2"
|
||||
R4R5 -> "r4"
|
||||
R6R7 -> "r6"
|
||||
R8R9 -> "r8"
|
||||
R10R11 -> "r10"
|
||||
R12R13 -> "r12"
|
||||
R14R15 -> "r14"
|
||||
else -> throw IllegalArgumentException("must be a combined virtual register $this")
|
||||
val names by lazy { values().map { it.toString()} }
|
||||
}
|
||||
|
||||
fun asCpuRegister(): CpuRegister = when(this) {
|
||||
@@ -88,22 +83,6 @@ enum class RegisterOrPair {
|
||||
else -> throw IllegalArgumentException("no cpu hardware register for $this")
|
||||
}
|
||||
|
||||
fun asScopedNameVirtualReg(type: DataType?): List<String> {
|
||||
require(this in Cx16VirtualRegisters || this in CombinedLongRegisters)
|
||||
val suffix = when(type?.base) {
|
||||
BaseDataType.UBYTE, BaseDataType.BOOL -> "L"
|
||||
BaseDataType.BYTE -> "sL"
|
||||
BaseDataType.WORD -> "s"
|
||||
BaseDataType.UWORD, BaseDataType.POINTER, null -> ""
|
||||
BaseDataType.LONG -> "sl"
|
||||
else -> throw IllegalArgumentException("invalid register param type for cx16 virtual reg")
|
||||
}
|
||||
return listOf("cx16", name.lowercase()+suffix)
|
||||
}
|
||||
|
||||
fun isWord() = this==AX || this == AY || this==XY || this in Cx16VirtualRegisters
|
||||
fun isLong() = this in CombinedLongRegisters
|
||||
|
||||
} // only used in parameter and return value specs in asm subroutines
|
||||
|
||||
enum class Statusflag {
|
||||
@@ -113,7 +92,7 @@ enum class Statusflag {
|
||||
Pn; // don't use
|
||||
|
||||
companion object {
|
||||
val names: Set<String> = entries.map { it.toString() }.toSet()
|
||||
val names by lazy { values().map { it.toString()} }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,49 +108,57 @@ enum class BranchCondition {
|
||||
PL, // PL == POS
|
||||
POS,
|
||||
VS,
|
||||
VC
|
||||
VC,
|
||||
}
|
||||
|
||||
val Cx16VirtualRegisters = setOf(
|
||||
|
||||
val ByteDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.BOOL)
|
||||
val WordDatatypes = arrayOf(DataType.UWORD, DataType.WORD)
|
||||
val IntegerDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.BOOL)
|
||||
val IntegerDatatypesNoBool = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD)
|
||||
val NumericDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT, DataType.BOOL)
|
||||
val NumericDatatypesNoBool = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT)
|
||||
val SignedDatatypes = arrayOf(DataType.BYTE, DataType.WORD, DataType.FLOAT)
|
||||
val ArrayDatatypes = arrayOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F, DataType.ARRAY_BOOL)
|
||||
val StringlyDatatypes = arrayOf(DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B, DataType.UWORD)
|
||||
val IterableDatatypes = arrayOf(
|
||||
DataType.STR,
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B,
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W,
|
||||
DataType.ARRAY_F, DataType.ARRAY_BOOL
|
||||
)
|
||||
val PassByValueDatatypes = NumericDatatypes
|
||||
val PassByReferenceDatatypes = IterableDatatypes
|
||||
val ArrayToElementTypes = mapOf(
|
||||
DataType.STR to DataType.UBYTE,
|
||||
DataType.ARRAY_B to DataType.BYTE,
|
||||
DataType.ARRAY_UB to DataType.UBYTE,
|
||||
DataType.ARRAY_W to DataType.WORD,
|
||||
DataType.ARRAY_UW to DataType.UWORD,
|
||||
DataType.ARRAY_F to DataType.FLOAT,
|
||||
DataType.ARRAY_BOOL to DataType.BOOL
|
||||
)
|
||||
val ElementToArrayTypes = mapOf(
|
||||
DataType.BYTE to DataType.ARRAY_B,
|
||||
DataType.UBYTE to DataType.ARRAY_UB,
|
||||
DataType.WORD to DataType.ARRAY_W,
|
||||
DataType.UWORD to DataType.ARRAY_UW,
|
||||
DataType.FLOAT to DataType.ARRAY_F,
|
||||
DataType.BOOL to DataType.ARRAY_BOOL
|
||||
)
|
||||
val Cx16VirtualRegisters = arrayOf(
|
||||
RegisterOrPair.R0, RegisterOrPair.R1, RegisterOrPair.R2, RegisterOrPair.R3,
|
||||
RegisterOrPair.R4, RegisterOrPair.R5, RegisterOrPair.R6, RegisterOrPair.R7,
|
||||
RegisterOrPair.R8, RegisterOrPair.R9, RegisterOrPair.R10, RegisterOrPair.R11,
|
||||
RegisterOrPair.R12, RegisterOrPair.R13, RegisterOrPair.R14, RegisterOrPair.R15
|
||||
)
|
||||
|
||||
val CombinedLongRegisters = setOf(
|
||||
RegisterOrPair.R0R1,
|
||||
RegisterOrPair.R2R3,
|
||||
RegisterOrPair.R4R5,
|
||||
RegisterOrPair.R6R7,
|
||||
RegisterOrPair.R8R9,
|
||||
RegisterOrPair.R10R11,
|
||||
RegisterOrPair.R12R13,
|
||||
RegisterOrPair.R14R15
|
||||
)
|
||||
|
||||
val M68kRegisters = setOf(
|
||||
RegisterOrPair.D0, RegisterOrPair.D1, RegisterOrPair.D2, RegisterOrPair.D3,
|
||||
RegisterOrPair.D4, RegisterOrPair.D5, RegisterOrPair.D6, RegisterOrPair.D7,
|
||||
RegisterOrPair.A0, RegisterOrPair.A1, RegisterOrPair.A2, RegisterOrPair.A3,
|
||||
RegisterOrPair.A4, RegisterOrPair.A5, RegisterOrPair.A6,
|
||||
RegisterOrPair.FP0, RegisterOrPair.FP1, RegisterOrPair.FP2, RegisterOrPair.FP3,
|
||||
RegisterOrPair.FP4, RegisterOrPair.FP5, RegisterOrPair.FP6, RegisterOrPair.FP7
|
||||
)
|
||||
|
||||
val CpuRegisters = setOf(
|
||||
RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y,
|
||||
RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY
|
||||
)
|
||||
|
||||
|
||||
enum class OutputType {
|
||||
RAW,
|
||||
PRG,
|
||||
XEX,
|
||||
ELF,
|
||||
AMIGAHUNK,
|
||||
LIBRARY
|
||||
XEX
|
||||
}
|
||||
|
||||
enum class CbmPrgLauncherType {
|
||||
@@ -193,8 +180,3 @@ enum class ZeropageWish {
|
||||
DONTCARE,
|
||||
NOT_IN_ZEROPAGE
|
||||
}
|
||||
|
||||
enum class SplitWish {
|
||||
DONTCARE,
|
||||
NOSPLIT
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package prog8.code.core
|
||||
|
||||
import prog8.code.SymbolTable
|
||||
import prog8.code.ast.PtProgram
|
||||
|
||||
interface ICodeGeneratorBackend {
|
||||
fun generate(program: PtProgram,
|
||||
symbolTable: SymbolTable,
|
||||
options: CompilationOptions,
|
||||
errors: IErrorReporter): IAssemblyProgram?
|
||||
}
|
||||
|
||||
|
||||
interface IAssemblyProgram {
|
||||
val name: String
|
||||
fun assemble(options: CompilationOptions, errors: IErrorReporter): Boolean
|
||||
}
|
||||
@@ -1,112 +1,10 @@
|
||||
package prog8.code.core
|
||||
|
||||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
* Identifies the target CPU type and its key characteristics that affect IR semantics.
|
||||
*
|
||||
* The most important property is [statusBitsOnMultiByteOps], which controls the IR
|
||||
* status-bit contract (see IRInstructions.kt for the full spec):
|
||||
*
|
||||
* - When `true`, the IR generator is allowed to skip emitting explicit CMP/CMPI before
|
||||
* branches that depend on the result of a previous "status-setting" instruction
|
||||
* (LOAD, INC, DEC, AND, OR, XOR, etc.). The contract is: "this instruction sets
|
||||
* Z and N based on the full multi-byte value." Backends for these targets must
|
||||
* honor this contract (e.g., M68000's `move.w #imm,d0` naturally sets Z based on
|
||||
* the 16-bit value, so the IR can rely on it).
|
||||
*
|
||||
* - When `false`, multi-byte operations on this target do NOT set status bits
|
||||
* correctly for the full value (e.g., 6502's `dec lo` only sets Z based on the
|
||||
* low byte of a 16-bit decrement). The IR generator must therefore always emit
|
||||
* an explicit CMP/CMPI before any branch that depends on the result. Backends
|
||||
* for these targets should NOT add `ora` postambles to set Z; instead they
|
||||
* rely on the explicit CMP/CMPI emitted by the IR generator.
|
||||
*
|
||||
* For 8-bit CPUs (6502, 65C02) the natural status flags from a multi-byte
|
||||
* cascade are misleading: Z is set based on the LAST byte operated on, not
|
||||
* the full value. So we treat the contract as "no instruction sets status
|
||||
* bits except CMP/CMPI/SEC/CLC/SGN/BITTST" and have the IR generator emit
|
||||
* explicit compares. This avoids the trap of "this works for BYTE but
|
||||
* silently breaks for WORD/LONG".
|
||||
*/
|
||||
enum class CpuType(val statusBitsOnMultiByteOps: Boolean) {
|
||||
/** 8-bit NMOS 6502. Multi-byte ops don't set Z based on full value. */
|
||||
CPU6502(false),
|
||||
|
||||
/** 8-bit CMOS 65C02. Same status-bit issue as plain 6502. */
|
||||
CPU65C02(false),
|
||||
|
||||
/** 16/32 bit Motorola 68000. has single instructions that set status bits when dealing with multi byte operands */
|
||||
M68000(true),
|
||||
|
||||
/** 32 bit Motorola 68020. has single instructions that set status bits when dealing with multi byte operands */
|
||||
M68020(true),
|
||||
|
||||
/**
|
||||
* Virtual machine target. We use `false` here so the IR generator always
|
||||
* emits explicit CMP/CMPI; the VM's setResultReg then does NOT set Z/N
|
||||
* flags (only CMP/CMPI/SEC/CLC/SGN/BITTST do). This matches the VM's
|
||||
* existing documentation (VirtualMachine.kt top comment) and keeps the
|
||||
* behavior consistent across all current targets.
|
||||
*/
|
||||
VIRTUAL(false)
|
||||
;
|
||||
|
||||
/** True for the 6502-family targets (plain 6502 and 65C02). */
|
||||
val is6502: Boolean
|
||||
get() = this == CPU6502 || this == CPU65C02
|
||||
val is68k: Boolean
|
||||
get() = this == M68000 || this == M68020
|
||||
/** True for targets that use the m68k-style hardware-register calling convention (D0-D7, A0-A6, FP0-FP7). */
|
||||
val usesM68kConvention: Boolean
|
||||
get() = is68k || this == VIRTUAL
|
||||
/** True for big-endian targets. The 6502 family and the VM are little-endian. */
|
||||
val isBigEndian: Boolean
|
||||
get() = this == M68000 || this == M68020
|
||||
}
|
||||
|
||||
interface ICompilationTarget: IStringEncoding, IMemSizer {
|
||||
val name: String
|
||||
|
||||
val FLOAT_MAX_NEGATIVE: Double
|
||||
val FLOAT_MAX_POSITIVE: Double
|
||||
val ARRAY_SIZE_LIMIT: UInt
|
||||
val PROGRAM_LOAD_ADDRESS : UInt
|
||||
val PROGRAM_MEMTOP_ADDRESS: UInt
|
||||
val BSSHIGHRAM_START: UInt
|
||||
val BSSHIGHRAM_END: UInt
|
||||
val BSSGOLDENRAM_START: UInt
|
||||
val BSSGOLDENRAM_END: UInt
|
||||
|
||||
val cpu: CpuType
|
||||
var zeropage: Zeropage
|
||||
val libraryPath: Path?
|
||||
val customLauncher: List<String>
|
||||
val additionalAssemblerOptions: List<String>
|
||||
val defaultOutputType: OutputType
|
||||
val defaultLauncherType: CbmPrgLauncherType
|
||||
val supportsBankedCalls: Boolean
|
||||
|
||||
/**
|
||||
* True when the code generator allocates subroutine locals and per-activation intermediate values
|
||||
* in a stack frame instead of static memory, so a subroutine on a call-graph cycle can have more
|
||||
* than one live activation. False means two live activations would share the same storage.
|
||||
*/
|
||||
val supportsStackFrames: Boolean
|
||||
|
||||
fun initializeMemoryAreas(compilerOptions: CompilationOptions)
|
||||
fun getFloatAsmBytes(num: Number): String
|
||||
|
||||
fun convertFloatToBytes(num: Double): List<UByte>
|
||||
fun convertBytesToFloat(bytes: List<UByte>): Double
|
||||
|
||||
/**
|
||||
* Launches an emulator to run the compiled program artifact.
|
||||
* [programFile] is the exact artifact path including its extension; launchers must use it
|
||||
* verbatim, without stripping or appending one.
|
||||
*/
|
||||
fun launchEmulator(selectedEmulator: Int, programFile: Path, quiet: Boolean)
|
||||
fun isIOAddress(address: UInt): Boolean
|
||||
val machine: IMachineDefinition
|
||||
val supportedEncodings: Set<Encoding>
|
||||
val defaultEncoding: Encoding
|
||||
|
||||
override fun encodeString(str: String, encoding: Encoding): List<UByte>
|
||||
override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String
|
||||
|
||||
@@ -3,16 +3,10 @@ package prog8.code.core
|
||||
interface IErrorReporter {
|
||||
fun err(msg: String, position: Position)
|
||||
fun warn(msg: String, position: Position)
|
||||
fun info(msg: String, position: Position)
|
||||
fun undefined(symbol: List<String>, suggestImport: Boolean=false, position: Position)
|
||||
fun noErrors(): Boolean
|
||||
fun report()
|
||||
fun finalizeNumErrors(numErrors: Int, numWarnings: Int, numInfos: Int) {
|
||||
fun finalizeNumErrors(numErrors: Int, numWarnings: Int) {
|
||||
if(numErrors>0)
|
||||
throw ErrorsReportedException("There are $numErrors errors, $numWarnings warnings, and $numInfos infos.")
|
||||
throw ErrorsReportedException("There are $numErrors errors and $numWarnings warnings.")
|
||||
}
|
||||
|
||||
fun noErrorForLine(position: Position): Boolean
|
||||
|
||||
fun printSingleError(errormessage: String)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package prog8.code.core
|
||||
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
enum class CpuType {
|
||||
CPU6502,
|
||||
CPU65c02,
|
||||
VIRTUAL
|
||||
}
|
||||
|
||||
interface IMachineDefinition {
|
||||
val FLOAT_MAX_NEGATIVE: Double
|
||||
val FLOAT_MAX_POSITIVE: Double
|
||||
val FLOAT_MEM_SIZE: Int
|
||||
var ESTACK_LO: UInt
|
||||
var ESTACK_HI: UInt
|
||||
val PROGRAM_LOAD_ADDRESS : UInt
|
||||
val BSSHIGHRAM_START: UInt
|
||||
val BSSHIGHRAM_END: UInt
|
||||
|
||||
val cpu: CpuType
|
||||
var zeropage: Zeropage
|
||||
var golden: GoldenRam
|
||||
|
||||
fun initializeMemoryAreas(compilerOptions: CompilationOptions)
|
||||
fun getFloatAsmBytes(num: Number): String
|
||||
|
||||
fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String>
|
||||
fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path)
|
||||
fun isIOAddress(address: UInt): Boolean
|
||||
fun overrideEvalStack(evalStackBaseAddress: UInt) {
|
||||
require(evalStackBaseAddress and 255u == 0u)
|
||||
ESTACK_LO = evalStackBaseAddress
|
||||
ESTACK_HI = evalStackBaseAddress + 256u
|
||||
require(ESTACK_LO !in golden.region && ESTACK_HI !in golden.region) { "user-set ESTACK can't be in GOLDEN ram" }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,28 +1,6 @@
|
||||
package prog8.code.core
|
||||
|
||||
interface IMemSizer {
|
||||
val FLOAT_MEM_SIZE: UInt
|
||||
val POINTER_MEM_SIZE: UInt
|
||||
|
||||
val pointerType: DataType
|
||||
get() = if(POINTER_MEM_SIZE > 2u) DataType.LONG else DataType.UWORD
|
||||
|
||||
val pointerBaseType: BaseDataType
|
||||
get() = if(POINTER_MEM_SIZE > 2u) BaseDataType.LONG else BaseDataType.UWORD
|
||||
|
||||
fun memorySize(dt: DataType, numElements: Int?): Int
|
||||
|
||||
fun memorySize(dt: BaseDataType): Int {
|
||||
if(dt.isPassByRef)
|
||||
return POINTER_MEM_SIZE.toInt()
|
||||
try {
|
||||
return memorySize(DataType.forDt(dt), null)
|
||||
} catch (x: NoSuchElementException) {
|
||||
throw IllegalArgumentException(x.message)
|
||||
}
|
||||
}
|
||||
|
||||
fun isSplitWordArray(dt: DataType): Boolean {
|
||||
return dt.isSplitWordArray(this)
|
||||
}
|
||||
fun memorySize(dt: DataType): Int
|
||||
fun memorySize(arrayDt: DataType, numElements: Int): Int
|
||||
}
|
||||
@@ -5,17 +5,10 @@ enum class Encoding(val prefix: String) {
|
||||
PETSCII("petscii"), // c64/c128/cx16
|
||||
SCREENCODES("sc"), // c64/c128/cx16
|
||||
ATASCII("atascii"), // atari
|
||||
ISO("iso"), // cx16 (iso-8859-15)
|
||||
ISO5("iso5"), // cx16 (iso-8859-5, cyrillic)
|
||||
ISO16("iso16"), // cx16 (iso-8859-16, eastern european)
|
||||
CP437("cp437"), // cx16 (ibm pc, codepage 437)
|
||||
KATAKANA("kata"), // cx16 (katakana)
|
||||
C64OS("c64os") // c64 (C64 OS)
|
||||
ISO("iso") // cx16
|
||||
}
|
||||
|
||||
interface IStringEncoding {
|
||||
val defaultEncoding: Encoding
|
||||
|
||||
fun encodeString(str: String, encoding: Encoding): List<UByte>
|
||||
fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String
|
||||
}
|
||||
@@ -22,53 +22,33 @@ abstract class MemoryAllocator(protected val options: CompilationOptions) {
|
||||
abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
|
||||
|
||||
abstract val SCRATCH_B1 : UInt // temp storage for a single byte
|
||||
abstract val SCRATCH_REG : UInt // temp storage for a register byte, must be B1+1
|
||||
abstract val SCRATCH_W1 : UInt // temp storage 1 for a word
|
||||
abstract val SCRATCH_W2 : UInt // temp storage 2 for a word
|
||||
abstract val SCRATCH_PTR : UInt // temp storage for a pointer
|
||||
abstract val SCRATCH_REG : UInt // temp storage for a register, must be B1+1
|
||||
abstract val SCRATCH_W1 : UInt // temp storage 1 for a word $fb+$fc
|
||||
abstract val SCRATCH_W2 : UInt // temp storage 2 for a word $fb+$fc
|
||||
|
||||
|
||||
// Collections for zeropage allocation (single-threaded usage)
|
||||
// the variables allocated into Zeropage.
|
||||
// name (scoped) ==> pair of address to (Datatype + bytesize)
|
||||
val allocatedVariables = mutableMapOf<String, VarAllocation>()
|
||||
val free = mutableSetOf<UInt>() // subclasses must set this to the appropriate free locations.
|
||||
|
||||
val free = mutableListOf<UInt>() // subclasses must set this to the appropriate free locations.
|
||||
|
||||
/**
|
||||
* Report a warning for every scratch byte that is excluded from allocation by a %zpreserved
|
||||
* or %zpallowed directive. The compiler's code generators always use these fixed scratch
|
||||
* locations internally (P8ZP_SCRATCH_*), so a %zpreserved/%zpallowed range that excludes one
|
||||
* of them would let the compiler overwrite memory the user explicitly wants to protect.
|
||||
*/
|
||||
fun checkScratchConflicts(errors: IErrorReporter) {
|
||||
if(options.zeropage== ZeropageType.DONTUSE)
|
||||
return
|
||||
fun removeReservedFromFreePool() {
|
||||
synchronized(this) {
|
||||
for (reserved in options.zpReserved)
|
||||
reserve(reserved)
|
||||
|
||||
val scratchBytes = listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1u, SCRATCH_W2, SCRATCH_W2 + 1u, SCRATCH_PTR, SCRATCH_PTR + 1u)
|
||||
for(byte in scratchBytes) {
|
||||
val preserved = options.zpReserved.any { byte in it }
|
||||
val allowed = options.zpAllowed.any { byte in it }
|
||||
if(preserved || !allowed)
|
||||
errors.warn("zero-page address $${byte.toString(16)} is a fixed scratch location used by the compiler (P8ZP_SCRATCH_*), but it is excluded by a %zpreserved or %zpallowed directive", Position.DUMMY)
|
||||
free.removeAll(setOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1u, SCRATCH_W2, SCRATCH_W2 + 1u))
|
||||
}
|
||||
}
|
||||
|
||||
fun removeReservedFromFreePool() {
|
||||
for (reserved in options.zpReserved)
|
||||
reserve(reserved)
|
||||
|
||||
free.removeAll(setOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1u, SCRATCH_W2, SCRATCH_W2 + 1u, SCRATCH_PTR, SCRATCH_PTR+1u))
|
||||
}
|
||||
|
||||
fun retainAllowed() {
|
||||
for(allowed in options.zpAllowed)
|
||||
free.retainAll { it in allowed }
|
||||
}
|
||||
|
||||
fun availableBytes() = if(options.zeropage== ZeropageType.DONTUSE) 0 else free.size
|
||||
fun hasByteAvailable() = options.zeropage != ZeropageType.DONTUSE && free.isNotEmpty()
|
||||
fun hasByteAvailable() = if(options.zeropage== ZeropageType.DONTUSE) false else free.isNotEmpty()
|
||||
fun hasWordAvailable(): Boolean {
|
||||
return options.zeropage != ZeropageType.DONTUSE && free.windowed(2).any { it[0] == it[1] - 1u }
|
||||
if(options.zeropage== ZeropageType.DONTUSE)
|
||||
return false
|
||||
|
||||
return free.windowed(2).any { it[0] == it[1] - 1u }
|
||||
}
|
||||
|
||||
override fun allocate(name: String,
|
||||
@@ -77,16 +57,15 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
|
||||
position: Position?,
|
||||
errors: IErrorReporter): Result<VarAllocation, MemAllocationError> {
|
||||
|
||||
require(name.isEmpty() || !allocatedVariables.containsKey(name)) {"name can't be allocated twice"}
|
||||
require(name.isEmpty() || name !in allocatedVariables) {"name can't be allocated twice"}
|
||||
|
||||
if(options.zeropage== ZeropageType.DONTUSE)
|
||||
return Err(MemAllocationError("zero page usage has been disabled"))
|
||||
|
||||
val size: Int =
|
||||
when {
|
||||
datatype.isIntegerOrBool -> options.compTarget.memorySize(datatype, null)
|
||||
datatype.isPointer -> options.compTarget.memorySize(datatype, null)
|
||||
datatype.isString || datatype.isArray -> {
|
||||
when (datatype) {
|
||||
in IntegerDatatypes -> options.compTarget.memorySize(datatype)
|
||||
DataType.STR, in ArrayDatatypes -> {
|
||||
val memsize = options.compTarget.memorySize(datatype, numElements!!)
|
||||
if(position!=null)
|
||||
errors.warn("allocating a large value in zeropage; str/array $memsize bytes", position)
|
||||
@@ -94,9 +73,9 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
|
||||
errors.warn("$name: allocating a large value in zeropage; str/array $memsize bytes", Position.DUMMY)
|
||||
memsize
|
||||
}
|
||||
datatype.isFloat -> {
|
||||
DataType.FLOAT -> {
|
||||
if (options.floats) {
|
||||
val memsize = options.compTarget.memorySize(DataType.FLOAT, null)
|
||||
val memsize = options.compTarget.memorySize(DataType.FLOAT)
|
||||
if(position!=null)
|
||||
errors.warn("allocating a large value in zeropage; float $memsize bytes", position)
|
||||
else
|
||||
@@ -107,17 +86,19 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
|
||||
else -> throw MemAllocationError("weird dt")
|
||||
}
|
||||
|
||||
if(free.isNotEmpty()) {
|
||||
if(size==1) {
|
||||
synchronized(this) {
|
||||
if(free.size > 0) {
|
||||
if(size==1) {
|
||||
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) {
|
||||
if(oneSeparateByteFree(candidate))
|
||||
return Ok(VarAllocation(makeAllocation(candidate, 1, datatype, name), datatype,1))
|
||||
}
|
||||
return Ok(VarAllocation(makeAllocation(free[0], 1, datatype, name), datatype,1))
|
||||
}
|
||||
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) {
|
||||
if(oneSeparateByteFree(candidate))
|
||||
return Ok(VarAllocation(makeAllocation(candidate, 1, datatype, name), datatype,1))
|
||||
if (sequentialFree(candidate, size))
|
||||
return Ok(VarAllocation(makeAllocation(candidate, size, datatype, name), datatype, size))
|
||||
}
|
||||
return Ok(VarAllocation(makeAllocation(free.first(), 1, datatype, name), datatype,1))
|
||||
}
|
||||
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) {
|
||||
if (sequentialFree(candidate, size))
|
||||
return Ok(VarAllocation(makeAllocation(candidate, size, datatype, name), datatype, size))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,23 +111,56 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
|
||||
require(size>=0)
|
||||
free.removeAll(address until address+size.toUInt())
|
||||
if(name.isNotEmpty()) {
|
||||
allocatedVariables[name] = when {
|
||||
datatype.isNumericOrBool -> VarAllocation(address, datatype, size) // numerical variables in zeropage never have an initial value here because they are set in separate initializer assignments
|
||||
datatype.isString -> VarAllocation(address, datatype, size)
|
||||
datatype.isArray -> VarAllocation(address, datatype, size)
|
||||
datatype.isPointer -> VarAllocation(address, datatype, size)
|
||||
allocatedVariables[name] = when(datatype) {
|
||||
in NumericDatatypes -> VarAllocation(address, datatype, size) // numerical variables in zeropage never have an initial value here because they are set in separate initializer assignments
|
||||
DataType.STR -> VarAllocation(address, datatype, size)
|
||||
in ArrayDatatypes -> VarAllocation(address, datatype, size)
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
}
|
||||
}
|
||||
return address
|
||||
}
|
||||
|
||||
private fun oneSeparateByteFree(address: UInt) =
|
||||
address in free &&
|
||||
(address == 0u || address-1u !in free) &&
|
||||
address+1u !in free
|
||||
private fun oneSeparateByteFree(address: UInt) = address in free && address-1u !in free && address+1u !in free
|
||||
private fun sequentialFree(address: UInt, size: Int): Boolean {
|
||||
require(size>0)
|
||||
return free.containsAll((address until address+size.toUInt()).toList())
|
||||
}
|
||||
|
||||
abstract fun allocateCx16VirtualRegisters()
|
||||
}
|
||||
|
||||
|
||||
// TODO: this class is not yet used
|
||||
class GoldenRam(options: CompilationOptions, val region: UIntRange): MemoryAllocator(options) {
|
||||
private var nextLocation: UInt = region.first
|
||||
|
||||
override fun allocate(
|
||||
name: String,
|
||||
datatype: DataType,
|
||||
numElements: Int?,
|
||||
position: Position?,
|
||||
errors: IErrorReporter): Result<VarAllocation, MemAllocationError> {
|
||||
|
||||
val size: Int =
|
||||
when (datatype) {
|
||||
in IntegerDatatypes -> options.compTarget.memorySize(datatype)
|
||||
DataType.STR, in ArrayDatatypes -> {
|
||||
options.compTarget.memorySize(datatype, numElements!!)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
if (options.floats) {
|
||||
options.compTarget.memorySize(DataType.FLOAT)
|
||||
} else return Err(MemAllocationError("floating point option not enabled"))
|
||||
}
|
||||
else -> throw MemAllocationError("weird dt")
|
||||
}
|
||||
|
||||
return if(nextLocation<=region.last && (region.last + 1u - nextLocation) >= size.toUInt()) {
|
||||
val result = Ok(VarAllocation(nextLocation, datatype, size))
|
||||
nextLocation += size.toUInt()
|
||||
result
|
||||
} else
|
||||
Err(MemAllocationError("no more free space in Golden RAM to allocate $size sequential bytes"))
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
package prog8.code.core
|
||||
|
||||
val CommutativeOperators = setOf("+", "*", "&", "|", "^", "==", "!=", "xor") // note: and,or are not commutative because of Shortcircuit/McCarthy evaluation
|
||||
val AssociativeOperators = setOf("+", "*", "&", "|", "^", "==", "!=")
|
||||
val ComparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
|
||||
val LogicalOperators = setOf("and", "or", "xor", "not", "in")
|
||||
val LogicalOperators = setOf("and", "or", "xor", "not")
|
||||
val AugmentAssignmentOperators = setOf("+", "-", "/", "*", "&", "|", "^", "<<", ">>", "%", "and", "or", "xor")
|
||||
val BitwiseOperators = setOf("&", "|", "^", "~")
|
||||
val PrefixOperators = setOf("+", "-", "~", "not")
|
||||
// val InvalidOperatorsForBoolean = setOf("+", "-", "*", "/", "%", "<<", ">>") + BitwiseOperators
|
||||
|
||||
fun invertedComparisonOperator(operator: String) =
|
||||
when (operator) {
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
package prog8.code.core
|
||||
|
||||
import prog8.code.target.VMTarget
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.extension
|
||||
import kotlin.io.path.name
|
||||
|
||||
|
||||
/** VICE/box16 monitor (symbol) list file name for an artifact stem. */
|
||||
fun viceMonListName(stem: String) = "$stem.vice-mon-list"
|
||||
|
||||
|
||||
/**
|
||||
* Artifact stem of a user-supplied `-o` output file name.
|
||||
* A dot at index 0 is not an extension separator: ".pgz" keeps its whole name.
|
||||
* Only applied to `-o`; the legacy program stem (source name without extension) is used verbatim.
|
||||
*/
|
||||
fun outputStemOf(name: Path): String {
|
||||
val filename = name.name
|
||||
val dotIndex = filename.lastIndexOf('.')
|
||||
return if (dotIndex <= 0) filename else filename.substring(0, dotIndex)
|
||||
}
|
||||
|
||||
/**
|
||||
* The final program artifact path: [CompilationOptions.outputFile] verbatim when set,
|
||||
* otherwise [defaultName] plus the extension for [CompilationOptions.output], in [CompilationOptions.outputDir].
|
||||
* RAW and LIBRARY both use ".bin"; AMIGAHUNK has no extension.
|
||||
* On the virtual target the artifact is the IR file, so ".p8ir" is always enforced on -o.
|
||||
*/
|
||||
fun CompilationOptions.programFile(defaultName: String): Path {
|
||||
// VMTarget's defaultOutputType = PRG is misleading: its artifact is the .p8ir file, never PRG.
|
||||
// The VM identifies the IR by its extension, so -o is not verbatim here: ".p8ir" is enforced.
|
||||
// Only the exact lowercase extension is recognized (same rule as -vm in CompilerMain).
|
||||
if(compTarget is VMTarget) {
|
||||
val ir = outputFile ?: return outputDir.resolve("$defaultName.p8ir")
|
||||
return if (ir.extension=="p8ir") ir else ir.resolveSibling("${ir.name}.p8ir")
|
||||
}
|
||||
outputFile?.let { return it }
|
||||
val filename = when(output) {
|
||||
OutputType.PRG -> "$defaultName.prg"
|
||||
OutputType.XEX -> "$defaultName.xex"
|
||||
OutputType.RAW -> "$defaultName.bin"
|
||||
OutputType.LIBRARY -> "$defaultName.bin"
|
||||
OutputType.ELF -> "$defaultName.elf"
|
||||
OutputType.AMIGAHUNK -> defaultName
|
||||
}
|
||||
return outputDir.resolve(filename)
|
||||
}
|
||||
|
||||
/**
|
||||
* Output artifact naming.
|
||||
* Invariant: [asm] must resolve into the same directory as `CompilationOptions.outputDir`,
|
||||
* because 64tass resolves `.binary` includes relative to the generated `.asm`.
|
||||
*/
|
||||
class OutputFiles(val programFile: Path, val stem: String, val dir: Path) {
|
||||
fun asm() = dir.resolve("$stem.asm")
|
||||
fun list() = dir.resolve("$stem.list")
|
||||
fun viceMonList() = dir.resolve(viceMonListName(stem))
|
||||
fun binFile() = dir.resolve("$stem.bin")
|
||||
fun ir() = dir.resolve("$stem.p8ir")
|
||||
fun bankedCalls() = dir.resolve("$stem.bankedcalls")
|
||||
fun obj() = dir.resolve("$stem.o")
|
||||
fun linkScript() = dir.resolve("$stem.link.ld")
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Builds the output file set. Without `-o` the stem is [defaultName] verbatim
|
||||
* (outputStemOf is only applied to the `-o` name). With `-o` the stem is derived
|
||||
* from the resolved artifact name, so it also covers the virtual target appending
|
||||
* ".p8ir". [dir] is the program file's parent, or [CompilationOptions.outputDir]
|
||||
* for a bare -o filename.
|
||||
*/
|
||||
fun of(options: CompilationOptions, defaultName: String): OutputFiles {
|
||||
val programFile = options.programFile(defaultName)
|
||||
val stem = options.outputFile?.let { outputStemOf(programFile) } ?: defaultName
|
||||
val dir = programFile.parent ?: options.outputDir
|
||||
return OutputFiles(programFile, stem, dir)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,21 @@
|
||||
package prog8.code.core
|
||||
|
||||
import prog8.code.sanitize
|
||||
import prog8.code.source.SourceCode
|
||||
import prog8.code.core.SourceCode.Companion.libraryFilePrefix
|
||||
import java.nio.file.InvalidPathException
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.absolute
|
||||
|
||||
/**
|
||||
* Source code position.
|
||||
*/
|
||||
data class Position(
|
||||
val file: String,
|
||||
val line: Int, // line number (1-based)
|
||||
val startCol: Int, // start column (1-based, tab-expanded)
|
||||
val endCol: Int // end column (1-based, tab-expanded)
|
||||
) {
|
||||
override fun toString(): String {
|
||||
return "[$file: line $line col ${startCol}-${endCol}]"
|
||||
}
|
||||
|
||||
data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) {
|
||||
override fun toString(): String = "[$file: line $line col ${startCol+1}-${endCol+1}]"
|
||||
fun toClickableStr(): String {
|
||||
if(this===DUMMY)
|
||||
return ""
|
||||
if(SourceCode.isLibraryResource(file))
|
||||
if(file.startsWith(libraryFilePrefix))
|
||||
return "$file:$line:$startCol:"
|
||||
return try {
|
||||
val path = Path(file).sanitize().toUri().toString()
|
||||
"$path:$line:$startCol:"
|
||||
} catch(_: InvalidPathException) {
|
||||
val path = Path(file).absolute().normalize().toString()
|
||||
"file://$path:$line:$startCol:"
|
||||
} catch(x: InvalidPathException) {
|
||||
// this can occur on Windows when the source origin contains "invalid" characters such as ':'
|
||||
"file://$file:$line:$startCol:"
|
||||
}
|
||||
@@ -35,4 +24,4 @@ data class Position(
|
||||
companion object {
|
||||
val DUMMY = Position("~dummy~", 0, 0, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
-41
@@ -1,13 +1,15 @@
|
||||
package prog8.code.source
|
||||
package prog8.code.core
|
||||
|
||||
import prog8.code.sanitize
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.nio.file.Path
|
||||
import java.text.Normalizer
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.absolute
|
||||
import kotlin.io.path.readText
|
||||
|
||||
|
||||
const val internedStringsModuleName = "prog8_interned_strings"
|
||||
|
||||
|
||||
/**
|
||||
* Encapsulates - and ties together - actual source code (=text) and its [origin].
|
||||
*/
|
||||
@@ -23,11 +25,6 @@ sealed class SourceCode {
|
||||
*/
|
||||
abstract val isFromFilesystem: Boolean
|
||||
|
||||
/**
|
||||
* Whether this [SourceCode] instance was created from a library module file
|
||||
*/
|
||||
abstract val isFromLibrary: Boolean
|
||||
|
||||
/**
|
||||
* The logical name of the source code unit. Usually the module's name.
|
||||
*/
|
||||
@@ -57,46 +54,34 @@ sealed class SourceCode {
|
||||
/**
|
||||
* filename prefix to designate library files that will be retreived from internal resources rather than disk
|
||||
*/
|
||||
private const val LIBRARYFILEPREFIX = "library:"
|
||||
private const val STRINGSOURCEPREFIX = "string:"
|
||||
val curdir: Path = Path(".").absolute()
|
||||
fun relative(path: Path): Path = curdir.relativize(path.sanitize())
|
||||
fun isRegularFilesystemPath(pathString: String) = !isLibraryResource(pathString) && !isStringResource(pathString)
|
||||
fun isLibraryResource(path: String) = path.startsWith(LIBRARYFILEPREFIX)
|
||||
fun isStringResource(path: String) = path.startsWith(STRINGSOURCEPREFIX)
|
||||
fun withoutPrefix(path: String): String {
|
||||
return if(isLibraryResource(path))
|
||||
path.removePrefix(LIBRARYFILEPREFIX)
|
||||
else if(isStringResource(path))
|
||||
path.removePrefix(STRINGSOURCEPREFIX)
|
||||
else
|
||||
path
|
||||
}
|
||||
const val libraryFilePrefix = "library:"
|
||||
const val stringSourcePrefix = "string:"
|
||||
val curdir: Path = Path(".").toAbsolutePath()
|
||||
fun relative(path: Path): Path = curdir.relativize(path.toAbsolutePath())
|
||||
fun isRegularFilesystemPath(pathString: String) =
|
||||
!(pathString.startsWith(libraryFilePrefix) || pathString.startsWith(stringSourcePrefix))
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn a plain String into a [SourceCode] object.
|
||||
* [origin] will be something like `string:44c56085`.
|
||||
*/
|
||||
class Text(origText: String): SourceCode() {
|
||||
override val text = origText.replace("\\R".toRegex(), "\n") // normalize line endings
|
||||
class Text(override val text: String): SourceCode() {
|
||||
override val isFromResources = false
|
||||
override val isFromFilesystem = false
|
||||
override val isFromLibrary = false
|
||||
override val origin = "$STRINGSOURCEPREFIX${System.identityHashCode(text).toString(16)}"
|
||||
override val origin = "$stringSourcePrefix${System.identityHashCode(text).toString(16)}"
|
||||
override val name = "<unnamed-text>"
|
||||
}
|
||||
|
||||
/**
|
||||
* Get [SourceCode] from the file represented by the specified Path.
|
||||
* This immediately reads the file fully into memory.
|
||||
* You can only get an instance of this via the ImportFileSystem object.
|
||||
*
|
||||
* [origin] will be the given path in absolute and normalized form.
|
||||
* @throws NoSuchFileException if the file does not exist
|
||||
* @throws FileSystemException if the file cannot be read
|
||||
*/
|
||||
internal class File(path: Path, override val isFromLibrary: Boolean): SourceCode() {
|
||||
class File(path: Path): SourceCode() {
|
||||
override val text: String
|
||||
override val origin: String
|
||||
override val name: String
|
||||
@@ -107,8 +92,7 @@ sealed class SourceCode {
|
||||
val normalized = path.normalize()
|
||||
origin = relative(normalized).toString()
|
||||
try {
|
||||
val contents = Normalizer.normalize(normalized.readText(), Normalizer.Form.NFC)
|
||||
text = contents.replace("\\R".toRegex(), "\n") // normalize line endings
|
||||
text = normalized.readText()
|
||||
name = normalized.toFile().nameWithoutExtension
|
||||
} catch (nfx: java.nio.file.NoSuchFileException) {
|
||||
throw NoSuchFileException(normalized.toFile()).also { it.initCause(nfx) }
|
||||
@@ -120,15 +104,13 @@ sealed class SourceCode {
|
||||
|
||||
/**
|
||||
* [origin]: `library:/x/y/z.p8` for a given `pathString` of "x/y/z.p8"
|
||||
* You can only get an instance of this via the ImportFileSystem object.
|
||||
*/
|
||||
internal class Resource(pathString: String): SourceCode() {
|
||||
class Resource(pathString: String): SourceCode() {
|
||||
private val normalized = "/" + Path(pathString).normalize().toMutableList().joinToString("/")
|
||||
|
||||
override val isFromResources = true
|
||||
override val isFromFilesystem = false
|
||||
override val isFromLibrary = true
|
||||
override val origin = "$LIBRARYFILEPREFIX$normalized"
|
||||
override val origin = "$libraryFilePrefix$normalized"
|
||||
override val text: String
|
||||
override val name: String
|
||||
|
||||
@@ -137,13 +119,12 @@ sealed class SourceCode {
|
||||
if (rscURL == null) {
|
||||
val rscRoot = object {}.javaClass.getResource("/")
|
||||
throw NoSuchFileException(
|
||||
java.io.File(normalized),
|
||||
File(normalized),
|
||||
reason = "looked in resources rooted at $rscRoot"
|
||||
)
|
||||
}
|
||||
val stream = object {}.javaClass.getResourceAsStream(normalized)
|
||||
val contents = stream!!.reader().use { Normalizer.normalize(it.readText(), Normalizer.Form.NFC) }
|
||||
text = contents.replace("\\R".toRegex(), "\n") // normalize line endings
|
||||
text = stream!!.reader().use { it.readText() }
|
||||
name = Path(pathString).toFile().nameWithoutExtension
|
||||
}
|
||||
}
|
||||
@@ -154,8 +135,7 @@ sealed class SourceCode {
|
||||
class Generated(override val name: String) : SourceCode() {
|
||||
override val isFromResources: Boolean = false
|
||||
override val isFromFilesystem: Boolean = false
|
||||
override val isFromLibrary: Boolean = false
|
||||
override val origin: String = name
|
||||
override val text: String = "<generated code node, no text representation>"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
package prog8.code.cpu6502
|
||||
|
||||
// Horner ("shift-add") expansion of a multiply by a small compile-time constant, for the 6502 and
|
||||
// 65C02 backends. The runtime routines prog8_math.multiply_words / multiply_bytes cost the same
|
||||
// whatever the constant is (~442 and ~130 cycles), while this expansion costs O(bit length), so
|
||||
// for a small constant inlining wins. It generalises the hand-written prog8_math.mul_word_N /
|
||||
// mul_byte_N routines, which are themselves unrolled Horner chains (mul_word_3 is "AY = AY*2 + AY",
|
||||
// one Horner step for 11).
|
||||
//
|
||||
// The sequences live here rather than in either backend because the register bookkeeping is easy to
|
||||
// get subtly wrong and the two backends must not drift apart.
|
||||
//
|
||||
// The gate is on generated size, not on the constant's magnitude: cycles alone would argue for
|
||||
// expanding nearly every multiply and bloating every program. Budgets are the max instruction count
|
||||
// the expansion may take; the call sequence each replaces is 6 instructions.
|
||||
|
||||
private const val WORD_BUDGET = 32
|
||||
private const val BYTE_BUDGET = 24
|
||||
|
||||
// --- byte ---
|
||||
// Contract: A holds the multiplicand. Result in A. Only A and one scratch byte are live, so there
|
||||
// is no second register to keep in sync (unlike the word case, where a bare doubling leaves the
|
||||
// high byte stale in Y).
|
||||
|
||||
fun byteShiftAddExpansion(value: Int): List<String>? {
|
||||
if (value !in 3..255)
|
||||
return null
|
||||
val bitlen = 31 - Integer.numberOfLeadingZeros(value)
|
||||
val popcount = Integer.bitCount(value)
|
||||
if (1 + (bitlen-1) + 2*(popcount-1) > BYTE_BUDGET)
|
||||
return null
|
||||
val result = ArrayList<String>(1 + 3*bitlen)
|
||||
result.add("sta P8ZP_SCRATCH_REG") // keep x for the conditional adds
|
||||
for (bit in (bitlen-1) downTo 0) {
|
||||
result.add("asl a") // r = r*2
|
||||
if (value and (1 shl bit) != 0) { // r += x
|
||||
result.add("clc")
|
||||
result.add("adc P8ZP_SCRATCH_REG")
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// --- word ---
|
||||
// Contract on entry: A = r.lo, Y = r.hi, P8ZP_SCRATCH_W1 = r, P8ZP_SCRATCH_W2 = x (all 16-bit,
|
||||
// little-endian: byte 0 is the low byte). On exit: A = result.lo, Y = result.hi.
|
||||
//
|
||||
// Two things this sequence must get right, both learned the hard way:
|
||||
// * the add step must store the new high byte back to W1+1 as well as to Y. Writing it only to Y
|
||||
// leaves W1+1 stale, and the next step's 'rol P8ZP_SCRATCH_W1+1' would then double the wrong
|
||||
// high byte.
|
||||
// * a trailing bare doubling (any even constant) updates A and W1+1 but not Y, so Y needs an
|
||||
// explicit reload at the end. It cannot be 'tay', because at that point A holds the low byte.
|
||||
|
||||
fun wordShiftAddExpansion(value: Int): List<String>? {
|
||||
if (value < 3)
|
||||
return null
|
||||
val bitlen = 31 - Integer.numberOfLeadingZeros(value)
|
||||
val popcount = Integer.bitCount(value)
|
||||
if (1 + 2*(bitlen-1) + 7*(popcount-1) > WORD_BUDGET)
|
||||
return null
|
||||
val result = ArrayList<String>(2 + 9*bitlen)
|
||||
for (bit in (bitlen-1) downTo 0) {
|
||||
result.add("asl a") // r = r*2
|
||||
result.add("rol P8ZP_SCRATCH_W1+1")
|
||||
if (value and (1 shl bit) != 0) { // r += x
|
||||
result.add("clc")
|
||||
result.add("adc P8ZP_SCRATCH_W2")
|
||||
result.add("sta P8ZP_SCRATCH_W1")
|
||||
result.add("lda P8ZP_SCRATCH_W1+1")
|
||||
result.add("adc P8ZP_SCRATCH_W2+1")
|
||||
result.add("sta P8ZP_SCRATCH_W1+1")
|
||||
result.add("tay")
|
||||
result.add("lda P8ZP_SCRATCH_W1")
|
||||
}
|
||||
}
|
||||
if (value and 1 == 0)
|
||||
result.add("ldy P8ZP_SCRATCH_W1+1")
|
||||
return result
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
package prog8.code.source
|
||||
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.sanitize
|
||||
import java.nio.file.Path
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.io.path.Path
|
||||
|
||||
|
||||
// Resource caching "filesystem".
|
||||
// Note that it leaves the decision to load a resource or an actual disk file to the caller.
|
||||
// Thread-safe: uses ConcurrentHashMap for all caches.
|
||||
|
||||
object ImportFileSystem {
|
||||
|
||||
val userHome: Path get() = Path.of(System.getProperty("user.home"))
|
||||
|
||||
fun expandTilde(path: String): String = if (path.startsWith("~")) {
|
||||
userHome.toString() + path.drop(1)
|
||||
} else {
|
||||
path
|
||||
}
|
||||
|
||||
fun expandTilde(path: Path): Path = Path(expandTilde(path.toString()))
|
||||
|
||||
fun getFile(path: Path, isLibrary: Boolean=false): SourceCode {
|
||||
val normalized = path.sanitize()
|
||||
return cache.computeIfAbsent(normalized.toString().lowercase()) {
|
||||
SourceCode.File(normalized, isLibrary)
|
||||
}
|
||||
}
|
||||
|
||||
fun getResource(name: String): SourceCode {
|
||||
return cache.computeIfAbsent(name.lowercase()) {
|
||||
SourceCode.Resource(name)
|
||||
}
|
||||
}
|
||||
|
||||
fun clearCaches() {
|
||||
cache.clear()
|
||||
lineSpanCache.clear()
|
||||
}
|
||||
|
||||
fun retrieveSourceLine(position: Position): String {
|
||||
if(SourceCode.isLibraryResource(position.file)) {
|
||||
val key = SourceCode.withoutPrefix(position.file)
|
||||
val cached = cache[key.lowercase()]
|
||||
?: runCatching { getResource(key) }.getOrNull()
|
||||
if(cached != null)
|
||||
return getLine(cached, position.line)
|
||||
}
|
||||
val cached = cache[position.file.lowercase()]
|
||||
if(cached != null)
|
||||
return getLine(cached, position.line)
|
||||
val path = Path(position.file).sanitize()
|
||||
val cached2 = cache[path.toString().lowercase()]
|
||||
if(cached2 != null)
|
||||
return getLine(cached2, position.line)
|
||||
throw NoSuchElementException("cannot get source line $position, with path $path")
|
||||
}
|
||||
|
||||
private fun getLine(code: SourceCode, lineIndex: Int): String {
|
||||
val spans = lineSpanCache.computeIfAbsent(code) {
|
||||
val lineSpans = Regex("^", RegexOption.MULTILINE).findAll(code.text).map { it.range.first }
|
||||
val ends = lineSpans.drop(1) + code.text.length
|
||||
lineSpans.zip(ends).map { (start, end) -> LineSpan(start, end) }.toList().toTypedArray()
|
||||
}
|
||||
val span = spans[lineIndex - 1]
|
||||
return code.text.substring(span.start, span.end).trim()
|
||||
}
|
||||
|
||||
private class LineSpan(val start: Int, val end: Int)
|
||||
|
||||
private val cache = ConcurrentHashMap<String, SourceCode>()
|
||||
private val lineSpanCache = ConcurrentHashMap<SourceCode, Array<LineSpan>>()
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
package prog8.code.target
|
||||
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.encodings.Encoder
|
||||
import prog8.code.target.zp.M68kZeropage
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
class Amiga1200Target: ICompilationTarget,
|
||||
IStringEncoding by Encoder(false),
|
||||
IMemSizer by NormalMemSizer(4u, 4u) {
|
||||
|
||||
override val name = NAME
|
||||
override val supportsBankedCalls = true
|
||||
override val supportsStackFrames = true
|
||||
override val defaultEncoding = Encoding.ISO
|
||||
override val libraryPath = null
|
||||
override val customLauncher = emptyList<String>()
|
||||
override val additionalAssemblerOptions = emptyList<String>()
|
||||
override val defaultOutputType = OutputType.AMIGAHUNK
|
||||
override val defaultLauncherType = CbmPrgLauncherType.NONE
|
||||
|
||||
companion object {
|
||||
const val NAME = "amiga1200"
|
||||
|
||||
/** needed to map bank numbers to libraries in extsub/CALLFAR library calls */
|
||||
val LibraryNumbers = Amiga500Target.LibraryNumbers
|
||||
}
|
||||
|
||||
override val cpu = CpuType.M68020
|
||||
|
||||
override val FLOAT_MAX_POSITIVE = Float.MAX_VALUE.toDouble()
|
||||
override val FLOAT_MAX_NEGATIVE = -Float.MAX_VALUE.toDouble()
|
||||
override val FLOAT_MEM_SIZE = 4u
|
||||
override val POINTER_MEM_SIZE = 4u
|
||||
override val ARRAY_SIZE_LIMIT = 32768u
|
||||
override val PROGRAM_LOAD_ADDRESS = 0u // amiga executables are relocatable
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0xfffffffeu // not actually used
|
||||
|
||||
override val BSSHIGHRAM_START = 0u // not used
|
||||
override val BSSHIGHRAM_END = 0u // not used
|
||||
override val BSSGOLDENRAM_START = 0u // not used
|
||||
override val BSSGOLDENRAM_END = 0u // not used
|
||||
override lateinit var zeropage: Zeropage
|
||||
|
||||
override fun getFloatAsmBytes(num: Number): String {
|
||||
TODO("float asm bytes")
|
||||
}
|
||||
|
||||
override fun convertFloatToBytes(num: Double): List<UByte> {
|
||||
TODO("convert float to bytes")
|
||||
}
|
||||
|
||||
override fun convertBytesToFloat(bytes: List<UByte>): Double {
|
||||
require(bytes.size==4) { "need 4 bytes" }
|
||||
TODO("convert bytes to float")
|
||||
}
|
||||
|
||||
override fun launchEmulator(selectedEmulator: Int, programFile: Path, quiet: Boolean) {
|
||||
if (selectedEmulator == 1) {
|
||||
CopperlineEmulator.launch(programFile, quiet, "A1200", "68020", listOf("--fast", "2M", "--fpu"))
|
||||
return
|
||||
}
|
||||
if (selectedEmulator != 2) {
|
||||
System.err.println("The amiga1200 target supports FS-UAE (-emu) and Amitools's vamos (-emu2).")
|
||||
return
|
||||
}
|
||||
val cpuStr = when (this.cpu) {
|
||||
CpuType.M68000 -> "68000"
|
||||
CpuType.M68020 -> "68020"
|
||||
else -> error("invalid cpu type")
|
||||
}
|
||||
VamosEmulator.launch(programFile, quiet, cpuStr)
|
||||
return
|
||||
}
|
||||
|
||||
override fun isIOAddress(address: UInt): Boolean =
|
||||
address in 0xbfd000u..0xbfdfffu || // CIA-B
|
||||
address in 0xbfe000u..0xbfefffu || // CIA-A
|
||||
address in 0xdff000u..0xdff1ffu || // Custom chipset (Paula, Alice, Lisa, Denise)
|
||||
address in 0xe80000u..0xefffffu // Zorro II autoconfig space
|
||||
|
||||
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||
zeropage = M68kZeropage(compilerOptions)
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
package prog8.code.target
|
||||
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.encodings.Encoder
|
||||
import prog8.code.target.zp.M68kZeropage
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
class Amiga500Target: ICompilationTarget,
|
||||
IStringEncoding by Encoder(false),
|
||||
IMemSizer by NormalMemSizer(4u, 4u) {
|
||||
|
||||
override val name = NAME
|
||||
override val supportsBankedCalls = true
|
||||
override val supportsStackFrames = true
|
||||
override val defaultEncoding = Encoding.ISO
|
||||
override val libraryPath = null
|
||||
override val customLauncher = emptyList<String>()
|
||||
override val additionalAssemblerOptions = emptyList<String>()
|
||||
override val defaultOutputType = OutputType.AMIGAHUNK
|
||||
override val defaultLauncherType = CbmPrgLauncherType.NONE
|
||||
|
||||
companion object {
|
||||
const val NAME = "amiga500"
|
||||
|
||||
/** needed to map bank numbers to libraries in extsub/CALLFAR library calls */
|
||||
val LibraryNumbers = mapOf(
|
||||
1 to "Sys", // exec
|
||||
2 to "DOS",
|
||||
3 to "Gfx",
|
||||
4 to "Intuition",
|
||||
5 to "GadTools",
|
||||
6 to "Layers",
|
||||
7 to "Asl",
|
||||
8 to "ConsoleDevice",
|
||||
9 to "Utility",
|
||||
10 to "Expansion",
|
||||
11 to "Icon",
|
||||
12 to "Workbench",
|
||||
13 to "Diskfont",
|
||||
14 to "IFFParse",
|
||||
15 to "Input",
|
||||
16 to "Keymap",
|
||||
17 to "Locale",
|
||||
18 to "Timer",
|
||||
19 to "LowLevel",
|
||||
20 to "Math",
|
||||
21 to "MathIeeeSingBas",
|
||||
22 to "MathIeeeSingTrans",
|
||||
23 to "MathIeeeDoubBas",
|
||||
24 to "MathIeeeDoubTrans",
|
||||
25 to "MathTrans",
|
||||
26 to "NV",
|
||||
27 to "RealTime",
|
||||
28 to "Translator",
|
||||
29 to "RexxSys",
|
||||
30 to "Cx",
|
||||
31 to "DataTypes",
|
||||
32 to "Disk",
|
||||
33 to "AmigaGuide",
|
||||
34 to "ARexx",
|
||||
35 to "BattClock",
|
||||
36 to "BattMem",
|
||||
// from here, non-standard libraries are listed
|
||||
100 to "AsyncIO",
|
||||
101 to "AHI", // ahi.device base (amiga1200 target only, 68020+, used by the low-level ahi.* LVOs)
|
||||
)
|
||||
}
|
||||
|
||||
override val cpu = CpuType.M68000
|
||||
|
||||
override val FLOAT_MAX_POSITIVE = Float.MAX_VALUE.toDouble()
|
||||
override val FLOAT_MAX_NEGATIVE = -Float.MAX_VALUE.toDouble()
|
||||
override val FLOAT_MEM_SIZE = 4u
|
||||
override val POINTER_MEM_SIZE = 4u
|
||||
override val ARRAY_SIZE_LIMIT = 32768u
|
||||
override val PROGRAM_LOAD_ADDRESS = 0u // amiga executables are relocatable
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0xfffffffeu // not actually used
|
||||
|
||||
override val BSSHIGHRAM_START = 0u // not used
|
||||
override val BSSHIGHRAM_END = 0u // not used
|
||||
override val BSSGOLDENRAM_START = 0u // not used
|
||||
override val BSSGOLDENRAM_END = 0u // not used
|
||||
override lateinit var zeropage: Zeropage
|
||||
|
||||
override fun getFloatAsmBytes(num: Number): String {
|
||||
TODO("float asm bytes")
|
||||
}
|
||||
|
||||
override fun convertFloatToBytes(num: Double): List<UByte> {
|
||||
TODO("convert float to bytes")
|
||||
}
|
||||
|
||||
override fun convertBytesToFloat(bytes: List<UByte>): Double {
|
||||
require(bytes.size==4) { "need 4 bytes" }
|
||||
TODO("convert bytes to float")
|
||||
}
|
||||
|
||||
override fun launchEmulator(selectedEmulator: Int, programFile: Path, quiet: Boolean) {
|
||||
if (selectedEmulator == 1) {
|
||||
CopperlineEmulator.launch(programFile, quiet, "A500", "68000", listOf("--slow", "512K"))
|
||||
return
|
||||
}
|
||||
if (selectedEmulator != 2) {
|
||||
System.err.println("The amiga500 target supports FS-UAE (-emu) and Amitools's vamos (-emu2).")
|
||||
return
|
||||
}
|
||||
val cpuStr = when (this.cpu) {
|
||||
CpuType.M68000 -> "68000"
|
||||
CpuType.M68020 -> "68020"
|
||||
else -> error("invalid cpu type")
|
||||
}
|
||||
VamosEmulator.launch(programFile, quiet, cpuStr)
|
||||
return
|
||||
}
|
||||
|
||||
override fun isIOAddress(address: UInt): Boolean =
|
||||
address in 0xbfd000u..0xbfdfffu || // CIA-B
|
||||
address in 0xbfe000u..0xbfefffu || // CIA-A
|
||||
address in 0xdff000u..0xdff1ffu || // Custom chipset (Paula, Alice, Lisa, Denise)
|
||||
address in 0xe80000u..0xefffffu // Zorro II autoconfig space
|
||||
|
||||
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||
zeropage = M68kZeropage(compilerOptions)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package prog8.code.target
|
||||
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.atari.AtariMachineDefinition
|
||||
|
||||
|
||||
class AtariTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer {
|
||||
override val name = NAME
|
||||
override val machine = AtariMachineDefinition()
|
||||
override val supportedEncodings = setOf(Encoding.ATASCII)
|
||||
override val defaultEncoding = Encoding.ATASCII
|
||||
|
||||
companion object {
|
||||
const val NAME = "atari"
|
||||
}
|
||||
|
||||
override fun memorySize(dt: DataType): Int {
|
||||
return when(dt) {
|
||||
in ByteDatatypes -> 1
|
||||
in WordDatatypes, in PassByReferenceDatatypes -> 2
|
||||
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
|
||||
else -> Int.MIN_VALUE
|
||||
}
|
||||
}
|
||||
|
||||
override fun memorySize(arrayDt: DataType, numElements: Int) =
|
||||
memorySize(ArrayToElementTypes.getValue(arrayDt)) * numElements
|
||||
}
|
||||
@@ -1,83 +1,20 @@
|
||||
package prog8.code.target
|
||||
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.encodings.Encoder
|
||||
import prog8.code.target.zp.C128Zeropage
|
||||
import java.nio.file.Path
|
||||
import prog8.code.core.Encoding
|
||||
import prog8.code.core.ICompilationTarget
|
||||
import prog8.code.core.IMemSizer
|
||||
import prog8.code.core.IStringEncoding
|
||||
import prog8.code.target.c128.C128MachineDefinition
|
||||
import prog8.code.target.cbm.CbmMemorySizer
|
||||
|
||||
|
||||
class C128Target: ICompilationTarget,
|
||||
IStringEncoding by Encoder(true),
|
||||
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE, 2u) {
|
||||
|
||||
class C128Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
|
||||
override val name = NAME
|
||||
override val supportsBankedCalls = true
|
||||
override val supportsStackFrames = false
|
||||
override val machine = C128MachineDefinition()
|
||||
override val supportedEncodings = setOf(Encoding.PETSCII, Encoding.SCREENCODES)
|
||||
override val defaultEncoding = Encoding.PETSCII
|
||||
override val libraryPath = null
|
||||
override val customLauncher = emptyList<String>()
|
||||
override val additionalAssemblerOptions = emptyList<String>()
|
||||
override val defaultOutputType = OutputType.PRG
|
||||
override val defaultLauncherType = CbmPrgLauncherType.BASIC
|
||||
|
||||
companion object {
|
||||
const val NAME = "c128"
|
||||
}
|
||||
|
||||
|
||||
override val cpu = CpuType.CPU6502
|
||||
|
||||
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
|
||||
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
||||
override val POINTER_MEM_SIZE = 2u
|
||||
override val ARRAY_SIZE_LIMIT = 256u
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x1c01u
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0xc000u
|
||||
|
||||
override val BSSHIGHRAM_START = 0u // no high ram
|
||||
override val BSSHIGHRAM_END = 0u // no high ram
|
||||
override val BSSGOLDENRAM_START = 0x1300u
|
||||
override val BSSGOLDENRAM_END = 0x1bdfu // note: $1be0 - $1bff contains the 16 virtual registers
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
|
||||
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
||||
|
||||
override fun convertFloatToBytes(num: Double): List<UByte> {
|
||||
val m5 = Mflpt5.fromNumber(num)
|
||||
return listOf(m5.b0, m5.b1, m5.b2, m5.b3, m5.b4)
|
||||
}
|
||||
|
||||
override fun convertBytesToFloat(bytes: List<UByte>): Double {
|
||||
require(bytes.size==5) { "need 5 bytes" }
|
||||
val m5 = Mflpt5(bytes[0], bytes[1], bytes[2], bytes[3], bytes[4])
|
||||
return m5.toDouble()
|
||||
}
|
||||
|
||||
override fun launchEmulator(selectedEmulator: Int, programFile: Path, quiet: Boolean) {
|
||||
if(selectedEmulator!=1) {
|
||||
System.err.println("The c128 target only supports the main emulator (Vice).")
|
||||
return
|
||||
}
|
||||
|
||||
if(!quiet)
|
||||
println("\nStarting C-128 emulator x128...")
|
||||
|
||||
val viceMonlist = programFile.resolveSibling(viceMonListName(outputStemOf(programFile)))
|
||||
val cmdline = listOf("x128", "-silent", "-moncommands", viceMonlist.toString(),
|
||||
"-autostartprgmode", "1", "-autostart-warp", "-autostart", programFile.toString())
|
||||
val processb = ProcessBuilder(cmdline)
|
||||
if(!quiet)
|
||||
processb.inheritIO()
|
||||
val process: Process = processb.start()
|
||||
process.waitFor()
|
||||
}
|
||||
|
||||
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu || address in 0xff00u..0xff04u
|
||||
|
||||
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||
zeropage = C128Zeropage(compilerOptions)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,117 +1,22 @@
|
||||
package prog8.code.target
|
||||
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.encodings.Encoder
|
||||
import prog8.code.target.zp.C64Zeropage
|
||||
import java.io.IOException
|
||||
import java.nio.file.Path
|
||||
import prog8.code.core.Encoding
|
||||
import prog8.code.core.ICompilationTarget
|
||||
import prog8.code.core.IMemSizer
|
||||
import prog8.code.core.IStringEncoding
|
||||
import prog8.code.target.c64.C64MachineDefinition
|
||||
import prog8.code.target.cbm.CbmMemorySizer
|
||||
|
||||
|
||||
class C64Target: ICompilationTarget,
|
||||
IStringEncoding by Encoder(true),
|
||||
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE, 2u) {
|
||||
|
||||
class C64Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
|
||||
override val name = NAME
|
||||
override val supportsBankedCalls = true
|
||||
override val supportsStackFrames = false
|
||||
override val machine = C64MachineDefinition()
|
||||
override val supportedEncodings = setOf(Encoding.PETSCII, Encoding.SCREENCODES)
|
||||
override val defaultEncoding = Encoding.PETSCII
|
||||
override val libraryPath = null
|
||||
override val customLauncher = emptyList<String>()
|
||||
override val additionalAssemblerOptions = emptyList<String>()
|
||||
override val defaultOutputType = OutputType.PRG
|
||||
override val defaultLauncherType = CbmPrgLauncherType.BASIC
|
||||
|
||||
companion object {
|
||||
const val NAME = "c64"
|
||||
|
||||
fun viceMonListName(baseFilename: String) = "$baseFilename.vice-mon-list"
|
||||
}
|
||||
|
||||
|
||||
override val cpu = CpuType.CPU6502
|
||||
|
||||
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
|
||||
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
||||
override val POINTER_MEM_SIZE = 2u
|
||||
override val ARRAY_SIZE_LIMIT = 256u
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x0801u
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0xcfe0u // $a000 if floats are used
|
||||
// note that at $cfe0-$cfff are the 16 'virtual registers' R0-R15
|
||||
|
||||
override val BSSHIGHRAM_START = 0xc000u
|
||||
override val BSSHIGHRAM_END = 0xcfdfu
|
||||
override val BSSGOLDENRAM_START = 0u // no golden ram on C64
|
||||
override val BSSGOLDENRAM_END = 0u
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
|
||||
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
|
||||
|
||||
override fun convertFloatToBytes(num: Double): List<UByte> {
|
||||
val m5 = Mflpt5.fromNumber(num)
|
||||
return listOf(m5.b0, m5.b1, m5.b2, m5.b3, m5.b4)
|
||||
}
|
||||
|
||||
override fun convertBytesToFloat(bytes: List<UByte>): Double {
|
||||
require(bytes.size==5) { "need 5 bytes" }
|
||||
val m5 = Mflpt5(bytes[0], bytes[1], bytes[2], bytes[3], bytes[4])
|
||||
return m5.toDouble()
|
||||
}
|
||||
|
||||
override fun launchEmulator(selectedEmulator: Int, programFile: Path, quiet: Boolean) {
|
||||
if(selectedEmulator!=1) {
|
||||
System.err.println("The c64 target only supports the main emulator (Vice).")
|
||||
return
|
||||
}
|
||||
|
||||
for(emulator in listOf("x64sc", "x64")) {
|
||||
if(!quiet)
|
||||
println("\nStarting C-64 emulator $emulator...")
|
||||
|
||||
val viceMonlist = programFile.resolveSibling(viceMonListName(outputStemOf(programFile)))
|
||||
val cmdline = listOf(emulator, "-silent", "-moncommands", viceMonlist.toString(),
|
||||
"-autostartprgmode", "1", "-autostart-warp", "-autostart", programFile.toString())
|
||||
val processb = ProcessBuilder(cmdline)
|
||||
if(!quiet)
|
||||
processb.inheritIO()
|
||||
val process: Process
|
||||
try {
|
||||
process=processb.start()
|
||||
} catch(_: IOException) {
|
||||
continue // try the next emulator executable
|
||||
}
|
||||
process.waitFor()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu
|
||||
|
||||
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||
zeropage = C64Zeropage(compilerOptions)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
val CompilationTargets = listOf(
|
||||
C64Target.NAME,
|
||||
C128Target.NAME,
|
||||
Cx16Target.NAME,
|
||||
PETTarget.NAME,
|
||||
Qemu68kTarget.NAME,
|
||||
Amiga500Target.NAME,
|
||||
Amiga1200Target.NAME,
|
||||
VMTarget.NAME
|
||||
)
|
||||
|
||||
fun getCompilationTargetByName(name: String) = when(name.lowercase()) {
|
||||
C64Target.NAME -> C64Target()
|
||||
C128Target.NAME -> C128Target()
|
||||
Cx16Target.NAME -> Cx16Target()
|
||||
PETTarget.NAME -> PETTarget()
|
||||
Qemu68kTarget.NAME -> Qemu68kTarget()
|
||||
Amiga500Target.NAME -> Amiga500Target()
|
||||
Amiga1200Target.NAME -> Amiga1200Target()
|
||||
VMTarget.NAME -> VMTarget()
|
||||
else -> throw IllegalArgumentException("invalid compilation target")
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
package prog8.code.target
|
||||
|
||||
import prog8.code.core.*
|
||||
import prog8.code.source.ImportFileSystem.expandTilde
|
||||
import prog8.code.target.encodings.Encoder
|
||||
import prog8.code.target.zp.ConfigurableZeropage
|
||||
import prog8.code.target.zp.M68kZeropage
|
||||
import java.io.IOException
|
||||
import java.nio.file.Path
|
||||
import java.util.*
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.inputStream
|
||||
import kotlin.io.path.isDirectory
|
||||
import kotlin.io.path.nameWithoutExtension
|
||||
|
||||
|
||||
class ConfigFileTarget(
|
||||
override val name: String,
|
||||
override val supportsBankedCalls: Boolean,
|
||||
override val defaultEncoding: Encoding,
|
||||
override val cpu: CpuType,
|
||||
override val supportsStackFrames: Boolean,
|
||||
override val PROGRAM_LOAD_ADDRESS: UInt,
|
||||
override val PROGRAM_MEMTOP_ADDRESS: UInt,
|
||||
override val BSSHIGHRAM_START: UInt,
|
||||
override val BSSHIGHRAM_END: UInt,
|
||||
override val BSSGOLDENRAM_START: UInt,
|
||||
override val BSSGOLDENRAM_END: UInt,
|
||||
override val defaultOutputType: OutputType,
|
||||
override val defaultLauncherType: CbmPrgLauncherType,
|
||||
override val libraryPath: Path,
|
||||
override val customLauncher: List<String>,
|
||||
override val additionalAssemblerOptions: List<String>,
|
||||
val varsAddress: UInt? = null,
|
||||
val ioAddresses: List<UIntRange>,
|
||||
val zpScratchB1: UInt,
|
||||
val zpScratchReg: UInt,
|
||||
val zpScratchW1: UInt,
|
||||
val zpScratchW2: UInt,
|
||||
val zpScratchPtr: UInt,
|
||||
val virtualregistersStart: UInt,
|
||||
val zpFullsafe: List<UIntRange>,
|
||||
val zpKernalsafe: List<UIntRange>,
|
||||
val zpBasicsafe: List<UIntRange>,
|
||||
val pointerSize: Int = 2
|
||||
): ICompilationTarget, IStringEncoding by Encoder(true), IMemSizer by NormalMemSizer(8u, pointerSize.toUInt()) {
|
||||
|
||||
init {
|
||||
require(pointerSize == 2 || pointerSize == 4) { "unsupported pointer size: $pointerSize" }
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private fun Properties.getString(property: String): String {
|
||||
val value = this.getProperty(property, null)
|
||||
if(value!=null)
|
||||
return value
|
||||
throw NoSuchElementException("string property '$property' not found in config file")
|
||||
}
|
||||
|
||||
private fun Properties.getInteger(property: String): UInt {
|
||||
val value = this.getProperty(property, null)
|
||||
if(value!=null) return parseInt(value)
|
||||
throw NoSuchElementException("integer property '$property' not found in config file")
|
||||
}
|
||||
|
||||
private fun parseInt(value: String): UInt {
|
||||
if(value.startsWith("0x"))
|
||||
return value.drop(2).toUInt(16)
|
||||
if(value.startsWith("$"))
|
||||
return value.drop(1).toUInt(16)
|
||||
if(value.startsWith("%"))
|
||||
return value.drop(1).toUInt(2)
|
||||
return value.toUInt()
|
||||
}
|
||||
|
||||
private fun parsePointerSize(props: Properties): Int {
|
||||
val pointerSize = parseInt(props.getProperty("pointer_size", "2")).toInt()
|
||||
require(pointerSize == 2 || pointerSize == 4) { "unsupported pointer size: $pointerSize" }
|
||||
return pointerSize
|
||||
}
|
||||
|
||||
private fun parseAddressRanges(key: String, props: Properties): List<UIntRange> {
|
||||
val rangesStr = props.getString(key)
|
||||
if(rangesStr.isBlank())
|
||||
return emptyList()
|
||||
val result = mutableListOf<UIntRange>()
|
||||
val ranges = rangesStr.split(",").map { it.trim() }
|
||||
for(r in ranges) {
|
||||
if ('-' in r) {
|
||||
val (fromStr, toStr) = r.split("-")
|
||||
val from = parseInt(fromStr.trim())
|
||||
val to = parseInt(toStr.trim())
|
||||
result.add(from..to)
|
||||
} else {
|
||||
val address = parseInt(r)
|
||||
result.add(address..address)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun fromConfigFile(configfile: Path): ConfigFileTarget {
|
||||
val props = Properties()
|
||||
props.load(configfile.inputStream())
|
||||
|
||||
val cpuString = props.getString("cpu").uppercase()
|
||||
val cpuType = try {
|
||||
CpuType.valueOf(cpuString)
|
||||
} catch (_: IllegalArgumentException) {
|
||||
CpuType.valueOf("CPU$cpuString")
|
||||
}
|
||||
val ioAddresses = parseAddressRanges("io_regions", props)
|
||||
// The m68k has no zero page, so the zeropage settings below don't apply to an m68k
|
||||
// target and are not required in its config file.
|
||||
fun zpSetting(name: String) = if(cpuType.is68k) 0u else props.getInteger(name)
|
||||
val zpFullsafe = if(cpuType.is68k) emptyList() else parseAddressRanges("zp_fullsafe", props)
|
||||
val zpKernalsafe = if(cpuType.is68k) emptyList() else parseAddressRanges("zp_kernalsafe", props)
|
||||
val zpBasicsafe = if(cpuType.is68k) emptyList() else parseAddressRanges("zp_basicsafe", props)
|
||||
|
||||
val libraryPath = expandTilde(Path(props.getString("library")))
|
||||
if(!libraryPath.isDirectory())
|
||||
throw IOException("invalid library path: $libraryPath")
|
||||
|
||||
val customLauncherStr = props.getProperty("custom_launcher_code", null)
|
||||
val customLauncher =
|
||||
if(customLauncherStr?.isNotBlank()==true)
|
||||
(customLauncherStr+"\n").lines().map { it.trimEnd() }
|
||||
else emptyList()
|
||||
val assemblerOptionsStr = props.getProperty("assembler_options", "").trim()
|
||||
val pointerSize = parsePointerSize(props)
|
||||
val outputTypeString = props.getProperty("output_type", "PRG")
|
||||
val defaultOutputType = OutputType.valueOf(outputTypeString.uppercase())
|
||||
val launcherString = props.getProperty("launcher", "BASIC")
|
||||
val defaultLauncherType = try {
|
||||
CbmPrgLauncherType.valueOf(launcherString.uppercase())
|
||||
} catch (_: IllegalArgumentException) {
|
||||
CbmPrgLauncherType.BASIC
|
||||
}
|
||||
|
||||
val varsAddress = props.getProperty("vars_address")?.let { parseInt(it) }
|
||||
|
||||
return ConfigFileTarget(
|
||||
configfile.nameWithoutExtension,
|
||||
props.getProperty("supports_banked_calls", "false").toBoolean(),
|
||||
Encoding.entries.first { it.prefix==props.getString("encoding") },
|
||||
cpuType,
|
||||
props.getProperty("supports_stack_frames", cpuType.is68k.toString()).toBoolean(),
|
||||
props.getInteger("load_address"),
|
||||
props.getInteger("memtop"),
|
||||
props.getInteger("bss_highram_start"),
|
||||
props.getInteger("bss_highram_end"),
|
||||
props.getInteger("bss_goldenram_start"),
|
||||
props.getInteger("bss_goldenram_end"),
|
||||
defaultOutputType,
|
||||
defaultLauncherType,
|
||||
libraryPath,
|
||||
customLauncher,
|
||||
if(assemblerOptionsStr=="") emptyList() else assemblerOptionsStr.split(" "),
|
||||
varsAddress,
|
||||
ioAddresses,
|
||||
zpSetting("zp_scratch_b1"),
|
||||
zpSetting("zp_scratch_reg"),
|
||||
zpSetting("zp_scratch_w1"),
|
||||
zpSetting("zp_scratch_w2"),
|
||||
zpSetting("zp_scratch_ptr"),
|
||||
zpSetting("virtual_registers"),
|
||||
zpFullsafe,
|
||||
zpKernalsafe,
|
||||
zpBasicsafe,
|
||||
pointerSize,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO floats are not yet supported here, just enter some values
|
||||
override val FLOAT_MAX_POSITIVE = 9.999999999e97
|
||||
override val FLOAT_MAX_NEGATIVE = -9.999999999e97
|
||||
override val FLOAT_MEM_SIZE = 8u
|
||||
override val POINTER_MEM_SIZE = pointerSize.toUInt()
|
||||
override val ARRAY_SIZE_LIMIT = 256u
|
||||
override lateinit var zeropage: Zeropage
|
||||
|
||||
override fun getFloatAsmBytes(num: Number) = TODO("floats")
|
||||
override fun convertFloatToBytes(num: Double): List<UByte> = TODO("floats")
|
||||
override fun convertBytesToFloat(bytes: List<UByte>): Double = TODO("floats")
|
||||
override fun launchEmulator(selectedEmulator: Int, programFile: Path, quiet: Boolean) {
|
||||
throw IllegalArgumentException("Custom compiler target cannot automatically launch an emulator. Do this manually.")
|
||||
}
|
||||
|
||||
override fun isIOAddress(address: UInt): Boolean = ioAddresses.any { address in it }
|
||||
|
||||
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||
// the m68k has no zero page, so the zp scratch addresses in the config file don't apply there
|
||||
zeropage = if(cpu.is68k) M68kZeropage(compilerOptions) else ConfigurableZeropage(
|
||||
zpScratchB1, zpScratchReg, zpScratchW1, zpScratchW2, zpScratchPtr,
|
||||
virtualregistersStart,
|
||||
zpBasicsafe,
|
||||
zpKernalsafe,
|
||||
zpFullsafe,
|
||||
compilerOptions
|
||||
)
|
||||
}
|
||||
}
|
||||
Loaded 100 of 1077 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user