Compare commits

..
1 Commits
Author SHA1 Message Date
Irmen de Jong e55a675d2e release 8.14 2023-05-29 23:00:43 +02:00
1077 changed files with 48260 additions and 245006 deletions

No files matched your search

-5
View File
@@ -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.
-204
View File
@@ -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`.
-197
View File
@@ -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`.
-168
View File
@@ -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.
-20
View File
@@ -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/
-45
View File
@@ -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
-14
View File
@@ -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']
+11 -34
View File
@@ -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
View File
@@ -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/
-10
View File
@@ -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>
+2 -12
View File
@@ -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>
-30
View File
@@ -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>
File diff suppressed because it is too large. Load diff
-15
View File
@@ -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>
+2 -13
View File
@@ -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>
+19
View File
@@ -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>
+18
View File
@@ -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
View File
@@ -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>
+10
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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>
+11
View File
@@ -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>
+18
View File
@@ -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>
+3 -11
View File
@@ -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>
+14 -2
View File
@@ -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
View File
@@ -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>
-5
View File
@@ -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.
-5
View File
@@ -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
View File
@@ -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
-405
View File
@@ -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.
-20
View File
@@ -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"
+28 -92
View File
@@ -1,55 +1,19 @@
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/H2H6S0FFF)
PayPal: [![PayPal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://paypal.me/irmendejong)
[![Documentation](https://readthedocs.org/projects/prog8/badge/?version=latest)](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)
[![JetBrains logo.](https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.svg)](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:
![c64 screen](docs/source/_static/primes_example.png)
@@ -206,19 +158,3 @@ For instance here's a well known space ship animated in 3D with hidden line remo
in the CommanderX16 emulator:
![cobra3d](docs/source/_static/cobra3d.png)
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)
-39
View File
@@ -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 |
![crc8](result-crc8.png)
![crc16](result-crc16.png)
![crc32](result-crc32.png)
![pow](result-pow.png)
![sieve](result-sieve.png)
![sieve-bit](result-sieve-bit.png)
-98
View File
@@ -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
}
}
-71
View File
@@ -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
}
}
-72
View File
@@ -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
}
}
-72
View File
@@ -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
}
}
-93
View File
@@ -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

-85
View File
@@ -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
}
}
-107
View File
@@ -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
}
}
-10
View File
@@ -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
-110
View File
@@ -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]
}
}
}
-89
View File
@@ -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]
}
}
-234
View File
@@ -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
}
}
-111
View File
@@ -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
}
}
}
-122
View File
@@ -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
}
}
-54
View File
@@ -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
}
}
-343
View File
@@ -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)
}
}
-63
View File
@@ -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
}
}
-69
View File
@@ -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)
}
}
-989
View File
@@ -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++
}
}
}
-141
View File
@@ -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)
}
}
}
+10
View File
@@ -0,0 +1,10 @@
plugins {
id "org.jetbrains.kotlin.jvm" version "$kotlinVersion" apply false
}
allprojects {
repositories {
mavenLocal()
mavenCentral()
}
}
-111
View File
@@ -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
}
}
}
+43
View File
@@ -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!
-11
View File
@@ -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")
}
+14
View File
@@ -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>
+24
View File
@@ -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)
-49
View File
@@ -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
}
}
}
+251
View File
@@ -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>
+202
View File
@@ -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
}
+116
View File
@@ -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
+163
View File
@@ -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
}
+67 -156
View File
@@ -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)
}
}
+4 -36
View File
@@ -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(".")
}
-534
View File
@@ -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
}
+94 -112
View File
@@ -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" }
}
}
+2 -24
View File
@@ -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
}
+74 -60
View File
@@ -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"))
}
}
+4 -2
View File
@@ -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)
}
}
}
+9 -20
View File
@@ -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)
}
}
}
@@ -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
}
+9 -72
View File
@@ -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)
}
}
+11 -106
View File
@@ -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