con un clic
prog8-coder
// Write Prog8 programs (6502-targeted language with 8-bit retro systems as targets)
// Write Prog8 programs (6502-targeted language with 8-bit retro systems as targets)
| name | prog8-coder |
| description | Write Prog8 programs (6502-targeted language with 8-bit retro systems as targets) |
| license | MIT |
| compatibility | opencode |
You are working with Prog8 source code (.p8 files) or its Intermediate Representation (.p8ir files). Prog8 targets 8-bit systems (C64, CX16, C128, PET32) with the 6502 CPU, plus a virtual target for testing.
Follow ALL the rules below carefully.
main block containing a start subroutine entry point, plus optional other subroutines/blocks%zeropage basicsafe at the top of your program to allow clean return on exit (instead of resetting the machine/emulator)%import modulename — no as aliasing. Use the module's defined prefix (e.g., %import textio → txt.xxx)prog8c -target virtual -emu input.p8 or prog8c -vm input.p8ir for fast testingprog8c -check input.p8 — does NOT produce output files (.prg, .asm, etc.), just errorsprog8c -out outdir input.p8 (default: same dir as source)*.prg (program binary), *.asm (assembly listing), *.list (full listing), *.p8ir (IR for VM), *.vice-mon-list (Vice debug symbols)
.p8ir files contain the Intermediate Representation — a sequence of named chunks, each containing typed instructions and virtual registers. This is a target-independent representation of the program, executable by the built-in Virtual Machine via prog8c -vm file.p8ir. Useful for debugging the compiler's code generation path without involving 6502 assembly.-noopt (disable optimizations), -printast1 (parsed AST), -printast2 (optimized simple AST), -compareir (compare IR outputs)docs/source/todo.rst to see what features are NOT yet implemented%zeropage basicsafe and %option no_sysinit at topsys module: always available, no import needed%encoding iso, call txt.iso() in start(), end with sys.poweroff_system(). For emulator: x16emu -echo iso -run -prg input.prg 2>&1 | grep ...bool, byte, ubyte, word, uword, long, float, strubyte/uword = unsigned; long = signed 4-byte; float = 5-byte MS format; str = 0-terminated ubytes (max 255 chars)%import floats at top of file, else compiler errorsmemory() + pointersmemory(name, size) returns a uword address to a statically reserved block of memory^^StructType ptr = ^^StructType:[val1,val2,...] (the ^^StructType: can be omitted if inferable)str allowed. NO arrays as fields. str in struct = ^^ubyte@nosplit@shared marks variables as "used by external code" (assembly), prevents optimization@zp/@requirezp: use sparingly — only for pointers (limited zeropage space)^^type) support C-style scaled arithmetic; uword pointers always treat element as 1 byte& = untyped address (uword); && = typed pointerP8ZP_SCRATCH_B1, P8ZP_SCRATCH_REG, P8ZP_SCRATCH_W1, P8ZP_SCRATCH_W2, P8ZP_SCRATCH_PTR — and cx16 virtual registers R0-R15 on all targetscx16.r0–cx16.r15): global 16-bit, NOT preserved across calls. R12-R15 may be clobbered by long ops. Save/restore in IRQs with cx16.save_virtual_registers()/cx16.restore_virtual_registers()math.sin8, math.cos8) uses fast LUTs; float trig (floats.sin/cos) is much slowerNo call stack for variable storage — recursion overwrites locals. To handle it:
push()/pushw()/pushl()/pushf() and pop()/popw()/popl()/popf(). Save/restore locals around recursive callsbuffers.stack (uword) / buffers.smallstack (ubyte). Both provide push_b()/push_w() and pop_b()/pop_w()repeat loops with explicit bounds — avoids all stack overheadstr / array: max 256 bytes. long[] limited to 64 entries (64x4=256). str[] for string arrays: str[5] names = ["a","b","c","d","e"]type[rows][cols] name, access name[r][c]. Flat init list only (no nested [[...]]). Total size still ≤ 256 bytes^^ubyte or ^^element)pointer[index].field as assignment target needs ^^: pointer[index]^^.field = valuestr buffer = "." * 50 (empty "" allocates nothing — strings.append() will fail)buffer = "new text" after declaration. Use strings.copy()/strings.ncopy()ptr++ + @(ptr) over @(buffer + offset). Exception: if offset is a ubyte (≤ 255), buffer[offset] works fine@(ptr) = peek/poke byte. For words: peekw/pokew, longs: peekl/pokel, floats: peekf/pokef, bools: peekbool/pokeboollen(array) instead of hardcoded sizesarr[0] is first elementmemory() slaband, or, xor, not
a and b: if a is false, b is NOT evaluateda or b: if a is true, b is NOT evaluatedb has side effects&, |, ^, ~, <<, >>rol()/ror() (through carry), rol2()/ror2() (no carry)if_cs, if_cc, if_z, if_nz (compile to single 6502 branch instructions)when with choice blocks instead of multiple ifif or else body is a single statement, the { } can be omitted. Place the statement on the next line, indented. Example:
if x < 5
txt.print("small")
defer defers statement execution until scope exitgoto, labels, jump lists allowedand/or for bitmasking — use &/| instead!Prog8 supports these loop types. All support break and continue (except unroll).
for loop — iterate over a range or arrayfor statementubyte, byte, uword, word, long, pointer types (NOT float)start to end), descending ranges (start downto end), or arrays/stringsstep <constant> for non-unit step sizesdownto usually produce more efficient 6502 codeubyte i
for i in 20 to 155 {
; body
break ; exit loop
continue ; next iteration
}
; descending
for i in 155 downto 20 {}
for i in 155 to 20 step -1 {}
; iterate over array elements
uword[] fib = [0, 1, 1, 2, 3, 5, 8, 13]
uword num
for num in fib {
...
}
while loop — repeat while condition is truewhile condition {
; body
break
continue
}
do-until loop — always executes body at least oncedo {
; body
break
continue
} until condition
repeat loop — repeat a fixed number of times (most efficient)for when loop variable not neededbreak)repeat 15 {
; body
break
continue
}
; infinite:
repeat {
; body
break if x==5
}
unroll loop — compile-time code duplicationbreak/continue allowedunroll 80 {
cx16.VERA_DATA0 = 255
}
private to restrict to blockinline keyword for subroutines to suggest inlininga, b, c = routine(). Use void to skip: void routine(), a, void, c = routine()For low-level assembly that gets arguments via registers and returns values in registers.
[private] [inline] asmsub subname(params) [clobbers(regs)] [-> returns] { %asm {{ ... }} }asmsub must only contain a single %asm {{ ... }} node. Regular Prog8 statements or nested blocks are NOT allowed.type name @register (e.g., ubyte val @A, uword addr @AX, float f @FAC1).-> type @register (e.g., -> ubyte @A, -> bool @Pz for immediate branch use).@A, @X, @Y@AX, @AY, @XY (register pairs)@FAC1, @FAC2 (Floating Point Accumulators)@R0–@R15 (16-bit), @R0R1–@R14R15 (32-bit combined)@Pc (Carry), @Pz (Zero), @Pv (Overflow), @Pn (Negative)clobbers (A, X, Y) — list all hardware registers modified by the routine.inline asmsub will paste the assembly code directly at the call site, avoiding jsr/rts overhead.p8v_ (vars), p8s_ (subs), p8b_ (blocks), etc.Used to call routines at fixed memory addresses (like ROM KERNAL routines or third-party drivers).
[private] extsub [@bank <value>] address = subname(params) [clobbers(regs)] [-> returns]$C000) or a constant expression.@bank <integer> (constant bank) or @bank <identifier> (variable bank).asmsub, extsub has no { } body; it just maps a signature to an address.; CX16 KERNAL CHROUT
extsub $FFD2 = chrout(ubyte char @A) clobbers(A, X, Y)
; Routine in a specific RAM bank
extsub @bank 10 $C09F = audio_init() clobbers(A, X, Y) -> bool @Pc
p8v_ (variables), p8s_ (subroutines), p8b_ (blocks), p8c_ (constants), p8l_ (labels), p8t_ (structs), p8_ (other)p8b_blockname.p8v_varname, p8b_blockname.p8s_subname.p8v_localvar.proc, short names often work. %option no_symbol_prefixing disables prefixes (used by cbm, cx16, txt)_lsb / _msb to variable name (e.g., p8v_myarray_lsb).proc/.pend, _label for locals, .byte/.word/.dword for data, = for equatesrol, ror, asl, lsr require explicit operand: rol a+ (forward), - (backward), branch with +, ++, +++ or -, --, ---asmsub:
asmsub my_routine(uword ptr @AX) {
%asm {{
ptr_lo = p8zp_scratch_w1
ptr_hi = p8zp_scratch_w1+1
sta ptr_lo
stx ptr_hi
}}
}
type name) and subroutines (name (params) -> returntype)textio module (txt.print, txt.chrout, txt.print_b/_ub/_w/_uw/_l/_bool, txt.print_f for floats, txt.spc(), txt.nl())math module — integer trig (sin8, cos8) via fast LUTs; math.rnd() for random numbersconv module (str_uword, str2word, etc.) — for printing numbers use txt routines insteadstrings module (isdigit, isxdigit, isupper, islower, isletter, isspace, isprint) — use these instead of manual ASCII/PETSCII arithmeticlen = strings.copy(dest, src), len = strings.append(buf, text), len = strings.upper(mystr)$FF (not 0xFF); Binary: %1010 (not 0b1010). Underscores for readability: 25_000_000$0000 = uword. No type suffixes (no 0L). Cast: expr as type+=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=; starts a comment to end of line — NOT a statement separator. One statement per lineelif: use nested else { if ... }expression as type (e.g., bytevar as word). as has very low precedence (lower than arithmetic)byte*byte=byte (overflow possible!). Cast explicitlyfor/if don't introduce scope. Only subroutines do. Variables in blocks are hoisted to subroutine level{ } blocks like C/Javatxt.lowercase() at start for lowercase). Virtual target uses ISO (%encoding iso + txt.iso())str[] types = ["a", "b", "c"]Enum::Value syntax (double colon), declared inside a block. Use enums for related values, const for standaloneglobals.XXXX — move constants closer to where they're used. for both direct and pointer access. The compiler infers the type. For complex assignment targets, ^^ may be needed: ptr^^.field = valuecx16.r0, not relative)if/for/repeat blocks are hoisted to subroutine levelstrings.copy()/strings.ncopy()"" allocates nothing