Compare commits

..
926 Commits
Author SHA1 Message Date
Irmen de Jong 02a1deb1e0 update github build actions 2026-05-23 00:13:14 +02:00
Irmen de Jong 3ba8e5de10 Add serial download example for CX16 and fix serial module import 2026-05-23 00:05:40 +02:00
Irmen de Jong 819d1b4af7 Add serial library for CX16 with UART and ZiModem support
version 12.2-BETA2
2026-05-22 23:52:58 +02:00
Irmen de Jong cdd3d37376 update asmsub inlining behavior and tests: improve support for parameterized calls 2026-05-22 04:17:43 +02:00
Irmen de Jong d0bc01d618 improve inliner: support slightly more complex return expressions 2026-05-22 03:15:57 +02:00
Irmen de Jong ffab73334a enforce errors for empty enum and struct declarations, add corresponding tests 2026-05-22 02:39:09 +02:00
Irmen de Jong 83aeb8846c add support for private keyword for structs, enums, aliases, variables, and subroutines 2026-05-22 02:27:38 +02:00
Irmen de Jong 3a2087310b background daemon compiler mode via -daemon option 2026-05-21 19:08:29 +02:00
Irmen de Jong 61b616701e fix -watch mode to not use stale cached files on subsequent runs 2026-05-20 22:54:52 +02:00
Irmen de Jong bddf872906 better romable awareness in IR 2026-05-20 19:54:12 +02:00
Irmen de Jong 48214a6fdb more IR peephole optimizations, added some implementation plans to the todo 2026-05-19 00:26:35 +02:00
Irmen de Jong b7340856df fix long expression evaluation bug clobbering R14R15 intermediary results 2026-05-18 20:07:23 +02:00
Irmen de Jong 2685854113 memory("name") with only 1 arg now allowed to refer to the actual memory reservation call elsewhere (with all three name, size, align args). 2026-05-16 23:24:41 +02:00
Irmen de Jong ad92f39469 fix error message for conflicting memory() calls 2026-05-16 23:01:49 +02:00
Irmen de Jong d3fe041e01 update kotlin and ksim65 2026-05-16 15:33:03 +02:00
Irmen de Jong f32bac5004 implement 6502 long division routine 2026-05-16 15:04:09 +02:00
Irmen de Jong 9a8e4ac105 lineclip.inside() at top level to check a single point 2026-05-15 21:53:13 +02:00
Irmen de Jong 78857e5c22 pet32: diskio.list_filenames() now checks buffer size 2026-05-15 19:38:45 +02:00
Irmen de Jong 1bb828875d improved vm waitvsync 2026-05-15 18:05:01 +02:00
Irmen de Jong 82aa67ffda lineclip module added 2026-05-15 17:45:13 +02:00
Irmen de Jong 7c44835772 const folding is always done even when -noopt is used 2026-05-15 12:22:52 +02:00
Irmen de Jong 8b9c63fe90 lineclip 2026-05-15 00:31:27 +02:00
Irmen de Jong e77fcc8c16 fix an unsigned long problem 2026-05-10 21:29:42 +02:00
Irmen de Jong 08c12131d4 tweak the rules for turning pointers into consts 2026-05-10 19:51:06 +02:00
Irmen de Jong 5a3ddf705a threshold 2 on ptr usages before no longer const
version 12.2-BETA
2026-05-10 03:08:54 +02:00
Irmen de Jong 20b29fb9b1 add getStaticAddressLowHigh to simplify address calculations and improve arithmetic code generation 2026-05-10 00:09:10 +02:00
Irmen de Jong fd3d59e159 fix sizeof() on a memory slab reference to return const 2 again
diskio.list_filenames() now checks filenames_buf_size minimum (>20)
2026-05-09 23:43:05 +02:00
Irmen de Jong 6d1dfd2a09 due to internal complexity, only pointers to bytes can be made const 2026-05-09 22:45:44 +02:00
Irmen de Jong 7095b14bcb checks 2026-05-09 22:05:57 +02:00
Irmen de Jong 1fe5ff18b1 memory() turned into 2 new ast nodes (slab reservation and slab ref) - still somewhat broken 2026-05-09 11:16:09 +02:00
Irmen de Jong 793b2a703b first step refactoring memory() calls 2026-05-08 19:32:33 +02:00
Irmen de Jong 53948be68f better skill name 2026-05-08 19:30:00 +02:00
Irmen de Jong 3d0dbb71ce created coding agent skills for prog8 and 6502 asm 2026-05-08 19:17:39 +02:00
Irmen de Jong 3dfa804e52 more optimized image conversion script/library 2026-05-07 21:59:53 +02:00
Irmen de Jong fd93d103f6 fix const value problems related to memory(), and fix a bug in constvalue calculation for ArrayIndexedExpression 2026-05-07 03:07:01 +02:00
Irmen de Jong 28977996e2 non-const pointers now can get optimized to const too. 2026-05-06 19:50:21 +02:00
Irmen de Jong e867cb88a7 fix astprinter crash,
IR: fixed using memory() as initialization value for a const uword or const pointer crashed the compiler
2026-05-06 01:31:40 +02:00
Irmen de Jong 1ba8dfcc8b actually doing some const-folding for most const pointer usages. 2026-05-06 00:31:47 +02:00
Irmen de Jong cd2d30c2c7 const pointer support added (but lacks proper const-folding still) 2026-05-05 23:21:32 +02:00
Irmen de Jong a47d2fa40d hint about efficient buffer iteration and skeleton files 2026-05-05 18:39:40 +02:00
Irmen de Jong 6be70af79f fix Inliner crash when using void in multi-value assignment.
update to gradle 9.4.1, tweak agent instructions.
2026-05-03 21:01:57 +02:00
Irmen de Jong 15a28814d5 optimized peekl() mklong() mklong2() to write directly into target variable if possible 2026-05-03 13:42:13 +02:00
Irmen de Jong 049acf0e98 implement long min() max() clamp() and abs() 2026-05-03 08:31:46 +02:00
Irmen de Jong df0d9e567f fix codegen for min() and max() for signed bytes 2026-05-03 07:11:42 +02:00
Irmen de Jong 78920aa115 script to create disk images 2026-05-03 05:15:19 +02:00
Irmen de Jong 1bb9ad5f46 fix peekw target register assignment crash 2026-05-02 23:38:56 +02:00
Andrew Gillham 1f30a06429 Don't require the BASIC launcher when the target has a custom launcher defined. (#224) 2026-05-02 23:38:47 +02:00
Irmen de Jong 5560c342db Merge branch 'optimizing_ifs' 2026-05-02 18:47:04 +02:00
Irmen de Jong e073c007ad better error when trying to run non-6502 code in the 6502 simulator 2026-05-02 18:37:07 +02:00
Irmen de Jong 9d1fa856d4 added unit tests that use the ksim65 simulator to actually run the compiled code 2026-05-02 06:15:09 +02:00
Irmen de Jong 7a26bb972f fix problem where no efficient code was generated anymore for pointer+index 2026-05-02 03:05:19 +02:00
Irmen de Jong e72a4723f9 Merge branch 'master' into optimizing_ifs 2026-05-02 02:17:23 +02:00
Irmen de Jong 96f0d112d2 fix problem where no efficient code was generated anymore for pointer+index 2026-05-02 02:14:18 +02:00
Irmen de Jong eac1b64f81 fix invalid optimization from commit b175dd0265 2026-05-02 00:39:05 +02:00
Irmen de Jong 0011dd8f4c Merge branch 'master' into optimizing_ifs
# Conflicts:
#	codeGenCpu6502/src/prog8/codegen/cpu6502/IfElseAsmGen.kt
#	compiler/test/TestCompilerOnExamples.kt
2026-05-01 19:54:16 +02:00
Irmen de Jong 5ce463d133 added jitpack.io library repository and ksim65 library for possible future use 2026-05-01 18:59:58 +02:00
Irmen de Jong 5926473281 working on improving operand order swap 2026-04-30 05:44:13 +02:00
Irmen de Jong 0a1d06b7f2 corrected associative -> commutative operators names, simplified maySwapOperandOrder logic 2026-04-30 01:13:50 +02:00
Irmen de Jong f98e7d6ad9 optimizing a=b+c; use 64tass local anonymous labels 2026-04-29 22:20:14 +02:00
Irmen de Jong 37896aeed4 optimizing a=b+c 2026-04-29 19:36:38 +02:00
Irmen de Jong b175dd0265 optimizing if-expressions too: more tweaks 2026-04-28 19:54:29 +02:00
Irmen de Jong 310d17d8bb optimizing if-expressions too: typecast const operand unwrapping 2026-04-28 19:54:29 +02:00
Irmen de Jong 371bd4b120 optimizing if-expressions too: final step 2026-04-28 19:54:29 +02:00
Irmen de Jong 3965d77813 optimizing if-expressions too: optimized word and long comparisons more 2026-04-28 19:54:29 +02:00
Irmen de Jong e0ed761661 optimizing if-expressions too: optimized word and long comparisons 2026-04-28 19:54:29 +02:00
Irmen de Jong 51d5261776 optimizing if-expressions too: pointer deref support 2026-04-28 19:54:29 +02:00
Irmen de Jong b85ac38b3b optimizing if-expressions too 2026-04-28 19:54:29 +02:00
Irmen de Jong 6ff9153d9b attempt to optimize word and long comparisons 2026-04-28 19:54:29 +02:00
Irmen de Jong e0d2e407e4 sort operands on complexity 2026-04-28 19:54:29 +02:00
Irmen de Jong 0117ab410b fix an uninitialized parent issue 2026-04-28 19:52:28 +02:00
Irmen de Jong b78a0cf076 make now auto selects system gradle or gradlew wrapper script 2026-04-28 13:29:51 +02:00
Irmen de Jong 722d52c939 revert status channel read on the C64 because it messes with the open file I/O
culprit was commit 2a9ac45cad
2026-04-27 23:37:17 +02:00
Irmen de Jong 162e5ffe3c agent instruction 2026-04-27 11:32:02 +02:00
Irmen de Jong 7383ffae46 fix UnusedCodeRemover wrongly removing var initializers 2026-04-27 00:19:16 +02:00
Irmen de Jong 1b4635d27e missing file 2026-04-26 00:22:41 +02:00
Irmen de Jong 1c376aa9d1 fix some 6502 code gen regressions 2026-04-26 00:18:28 +02:00
Irmen de Jong 91ff17f666 fix some 6502 code gen regressions 2026-04-25 23:32:30 +02:00
Irmen de Jong 5a81021ddc -out now creates the directory if it does not exist yet 2026-04-25 22:48:24 +02:00
Irmen de Jong 9fdd9254f1 fix missing CMP in IR after conditional expression 2026-04-25 22:23:10 +02:00
Irmen de Jong ae4c4ca160 IR: optimized boolean expression codegen (use status flags directly more often instead of transforming to 1 / 0 values) 2026-04-24 22:26:16 +02:00
Irmen de Jong 7ab4880c4a IR: optimized poke() and pokemon() 2026-04-24 19:38:18 +02:00
Irmen de Jong 5c16a40f33 IR: optimized float and bol comparison code gen 2026-04-24 09:41:45 +02:00
Irmen de Jong 6f9f31cd8a better const folding for multivalue assignments / vardecl inits in StatementReorderer 2026-04-24 07:18:01 +02:00
Irmen de Jong 7cd70acc01 implemented const folding of lmh() and divmod() 2026-04-24 06:02:16 +02:00
Irmen de Jong 2d3c20d347 c128: define golden ram area at $1300-$1BDF 2026-04-24 05:26:25 +02:00
Irmen de Jong b05bc9ac0e add IR peephole optimization for x * negative power of 2 2026-04-24 04:57:28 +02:00
Irmen de Jong 2589f953c3 fix IR peephole optimizer fault that incorrectly optimized 20-x expression 2026-04-24 04:49:09 +02:00
Irmen de Jong a40f9de9e6 fix IR codegen issue with DIVMOD and some other instructions 2026-04-24 04:22:29 +02:00
Irmen de Jong e7491a56ef todo 2026-04-23 02:50:56 +02:00
Irmen de Jong 2bf5d1406d make cx16/starszoom example fill the whole screen, and rotate 2026-04-22 19:52:11 +02:00
Irmen de Jong c32e7ee57b improve docs about operator precedence 2026-04-22 02:46:17 +02:00
Irmen de Jong a633b5a4b5 split Agent Prog8 language instructions 2026-04-21 22:13:03 +02:00
Irmen de Jong a5c078be14 fix inline warning 2026-04-21 06:27:40 +02:00
Irmen de Jong 547d21a42b added more redundant variable initialization removals 2026-04-21 06:13:39 +02:00
Irmen de Jong e2e0992d02 verafx: fix copy() and clear() unrolling 2026-04-21 03:55:28 +02:00
Irmen de Jong 10fd321537 use STZ in certain repeat loops on 65c02
remove unused memcopy16_up
2026-04-21 00:07:29 +02:00
Irmen de Jong fb26a7af3b redundant 2026-04-19 11:06:46 +02:00
Irmen de Jong 8592a3a200 use VarDecl builder instead of constructor 2026-04-19 02:26:50 +02:00
Irmen de Jong 930697dad5 using builder to improve massive lists of constructor params 2026-04-19 01:39:50 +02:00
Irmen de Jong a60ba27bad improve LSP 2026-04-18 23:30:12 +02:00
Irmen de Jong 87480b150a fix monogfx doublebuffer 2026-04-18 20:20:33 +02:00
Irmen de Jong 0cb9d408ff made some internal routines and variables in the library routines private 2026-04-18 19:55:10 +02:00
Irmen de Jong c784fd03fd remove internal_string_copy because strings can no longer be assigned by value that always needs strings.copy() 2026-04-18 19:29:05 +02:00
Irmen de Jong 5dcc11d67e try to avoid crash when importing modules twice (via symlink for instance). 2026-04-18 18:06:41 +02:00
Irmen de Jong e36a589e23 try to avoid crash when importing modules twice (via symlink for instance). 2026-04-18 12:56:30 +02:00
Irmen de Jong fe6a8f63a0 cx16: make gfx_lores actually use the kernal's screen mode 128 (keeps text layer)
also for gfx_hires and monogfx. fixes #220
2026-04-18 12:25:46 +02:00
Irmen de Jong 60e2a38060 fix IR read error on structs with typed pointer fields 2026-04-17 21:01:51 +02:00
Irmen de Jong d808818506 refactor grammar for signatures 2026-04-15 01:51:58 +02:00
Irmen de Jong 0f72bafeae improve code for in-place assignments to indexed pointers 2026-04-14 19:11:13 +02:00
Irmen de Jong c794d2a50b fix invalid IR opcode used for inplace assignments to memory targets 2026-04-14 15:32:12 +02:00
Irmen de Jong db84c375f1 implement a missing long pointer assignment case 2026-04-14 01:55:02 +02:00
Irmen de Jong 03fa6422b3 fix codegen for split array access using some peek/poke patterns 2026-04-13 21:43:39 +02:00
Irmen de Jong ad34633ca9 fix codegen for split array access using pointer + or - offset 2026-04-13 21:18:32 +02:00
Irmen de Jong d5eef79892 implement @nosplit pointer arrays 2026-04-13 20:41:57 +02:00
Irmen de Jong d5a53c5d21 implement missing division operations in VM 2026-04-13 18:48:35 +02:00
Irmen de Jong bd51296a33 implement V flag on comparisons in the virtualmachine 2026-04-12 20:47:56 +02:00
Irmen de Jong c0103f149e more general implementation of struct definition equality check see github #198 2026-04-12 20:43:19 +02:00
Irmen de Jong 86174e49c9 fix invalid ast node lookup in replacements 2026-04-12 16:41:00 +02:00
Irmen de Jong 0be336c8a8 lineclip test code 2026-04-12 16:36:10 +02:00
Irmen de Jong d836f48f38 updated prog8 instructions 2026-04-12 13:00:19 +02:00
Irmen de Jong 952d7b3162 fix faulty float optimization and ptrderef equality check 2026-04-11 23:55:59 +02:00
Irmen de Jong 5638eb1c53 asm optimizations: fix inconsistent line property, float copy optimization bug, Y register modification bug 2026-04-11 19:24:12 +02:00
Irmen de Jong d3f19bb438 fix lacking checks for IO registers in asm optimizer, caused incorrect instruction removals 2026-04-11 18:01:57 +02:00
Irmen de Jong be60021edc fix lacking checks for IO registers in asm optimizer, caused incorrect instruction removals 2026-04-11 04:18:22 +02:00
Irmen de Jong b2ed3b5934 add C128 MMU control registers to 'IO' registers ranges.
remove unused GoldenRam configuration stuff.
2026-04-10 16:52:28 +02:00
Irmen de Jong 59d0f7d49c 2-dimensional array support 2026-04-10 04:22:38 +02:00
Irmen de Jong 47700918cf preparing for improvements in pointer dereference grammar 2026-04-09 19:29:32 +02:00
Irmen de Jong db7edde2aa report multiple parse errors at once.
grammar rules improvements.
2026-04-09 07:06:03 +02:00
Irmen de Jong e2702d3e22 IRFileWriter and IRFileReader improvements 2026-04-09 05:06:39 +02:00
Irmen de Jong 74475d023b improving IR symbolic references 2026-04-09 03:54:51 +02:00
Irmen de Jong 9ae0623ad0 making MemoryAddress an Unsigned Int 2026-04-09 02:40:24 +02:00
Irmen de Jong 33b34b990e making RegisterNum type safe 2026-04-08 02:57:45 +02:00
Irmen de Jong 6dc96bb8f0 making IRInstruction parameters type safe 2026-04-07 20:15:55 +02:00
Irmen de Jong aeca6350da make IR instruction number order more pleasant to read 2026-04-07 14:39:28 +02:00
Irmen de Jong 6a340aa59d refactor void return value handling for multivalue results 2026-04-07 13:41:07 +02:00
Irmen de Jong 207650fbc2 fix multivalue pha/pla regression 2026-04-07 05:52:50 +02:00
Irmen de Jong 4fb3df977e fix correct evaluation of floats in multi-value returns 2026-04-07 05:41:27 +02:00
Irmen de Jong 9f742e1806 6502 codegen: handle floats as part of multi-value result 2026-04-07 04:50:23 +02:00
Irmen de Jong aca6c22098 IR codegen: handle floats as part of multi-value result 2026-04-07 04:31:17 +02:00
Irmen de Jong 4b45dae68a IR codegen: optimized array +/-/* in place 2026-04-07 04:12:44 +02:00
Irmen de Jong 0b68481fda optimized 6502 codegen for split array |=, &=, ^= 2026-04-06 23:27:25 +02:00
Irmen de Jong bba08f55de use compact loop to copy longs instead of 4x lda/sta pairs 2026-04-06 20:47:20 +02:00
Irmen de Jong 9e5df3d2d4 Optimize the IfExpression code generation to be more like regular if-else code (tighter branch code) 2026-04-06 20:25:41 +02:00
Irmen de Jong 5c1aa2307d for loops where the counter variable is unused in the loop body and count is constant -> simple repeat loops 2026-04-05 23:35:58 +02:00
Irmen de Jong 3129f5e071 fix IR return type assignment error 2026-04-05 12:44:06 +02:00
Irmen de Jong 04a8181c05 fix error when assigning str to ^^ubyte in multi-value assign 2026-04-05 12:26:20 +02:00
Irmen de Jong 27bf9c9a4c fix compiler crash when comparing str with number 2026-04-04 02:47:02 +02:00
Irmen de Jong 7658a0ab12 added strings.isxdigit()
fixed size bugs in several virtual string to number conv routines
2026-04-03 23:33:00 +02:00
Irmen de Jong 42d3340fe4 fix an msb() codegen crash when assigning back to word 2026-04-03 22:15:08 +02:00
Irmen de Jong 1100ab0d22 performance tweaks 2026-04-03 19:54:37 +02:00
Irmen de Jong 96eb3f0fb2 ImportFileSystem thread safe, IR performance tweaks 2026-04-03 06:53:08 +02:00
Irmen de Jong 1ed618c474 performance improvements in parser 2026-04-03 04:31:29 +02:00
Irmen de Jong e6c26587d6 improve Position accuracy 2026-04-03 03:42:17 +02:00
Irmen de Jong 4739bd087a IR: avoid redundant CMPI in if-expressions, remove dead stores 2026-04-02 23:41:03 +02:00
Irmen de Jong 92751d9285 document links in the LSP, remove repl/beanshell 2026-04-02 21:55:56 +02:00
Irmen de Jong 0792571b28 parse errors in the LSP 2026-04-02 02:29:38 +02:00
Irmen de Jong 511564dd1e symbol extraction in the LSP 2026-04-02 01:14:36 +02:00
Irmen de Jong 3da3d63eb4 more inlining support
fix invalid character in LSP
2026-04-01 19:04:03 +02:00
Irmen de Jong 3c859512a5 TDD inilining with parameters tests 2026-04-01 00:05:22 +02:00
Irmen de Jong 45c05522d5 fix inliner bug 2026-03-31 21:25:34 +02:00
Irmen de Jong db417ab9ce refactoring AstModification 2026-03-31 19:59:56 +02:00
Irmen de Jong c17a3aff5c refactoring AstModification 2026-03-31 15:37:55 +02:00
Irmen de Jong 6184a5004c wrapping child nodes mutable list away to avoid direct access that might corrupt it 2026-03-31 10:33:54 +02:00
Irmen de Jong 07772bddfd better zeropage tests and symboltable tweaks 2026-03-31 07:21:56 +02:00
Irmen de Jong 15bf4c7692 unit test helpers refactoring 2026-03-31 05:50:30 +02:00
Irmen de Jong f38c701658 some support for inlining multi-value returns (where there are no function parameters to deal with) 2026-03-31 04:42:34 +02:00
Irmen de Jong 10ce992b35 IR example metrics now count instructions not p8ir lines 2026-03-29 22:46:51 +02:00
Irmen de Jong 331e2b8f2b improve ast source position tracking 2026-03-29 19:49:42 +02:00
Irmen de Jong 05093e35c8 refactor inliner 2026-03-29 11:00:26 +02:00
Irmen de Jong 5fd11ca036 more consistent allowance of inline keyword 2026-03-29 10:15:51 +02:00
Irmen de Jong 5330069b53 disallow weird sizeof argument types 2026-03-29 09:50:13 +02:00
Irmen de Jong 5408a3ca68 fix compiler crash on certain pointer type struct fields
added new compiler options for debugging purposes
2026-03-29 08:25:53 +02:00
Irmen de Jong cecf49b8e3 IR: attempt to optimize negative comparisons by using direct status flag branches instead 2026-03-29 01:23:24 +01:00
Irmen de Jong 65c17b920a add txt.iso() to virtual target for source code compatibility and tests 2026-03-29 00:39:04 +01:00
Irmen de Jong 7666b72fea implement missing codegen for returning multivalue result of a subroutine call 2026-03-28 23:38:52 +01:00
Irmen de Jong bcd12a0fc7 improve handling of boolean literals in logical expressions with -noopt 2026-03-28 19:19:14 +01:00
Irmen de Jong dd0828a1fc fix a circular dependency in gfx_lores.p8 between line and plot 2026-03-28 15:35:56 +01:00
Irmen de Jong 770532a039 tweak line drawing routine for better accurracy
better agent instructions about asmsubs
2026-03-28 14:22:21 +01:00
Irmen de Jong 400149d5a3 standardize gradle project configurations across submodules 2026-03-28 10:08:47 +01:00
Irmen de Jong d6ed40af36 SymbolTable improvements 2026-03-28 04:14:26 +01:00
Irmen de Jong 570ea50a3b cleanups 2 2026-03-28 01:22:33 +01:00
Irmen de Jong 04b9f8e749 cleanups 2026-03-28 00:48:29 +01:00
Irmen de Jong 2bd7f3e210 improved SimpleAST optimizer 2026-03-27 01:13:38 +01:00
Irmen de Jong 00bae6e746 comment 2026-03-27 00:20:35 +01:00
Irmen de Jong f6d5e88f31 Fix test cases 2026-03-26 23:32:27 +01:00
Irmen de Jong 8d44a1b226 implemented bitmap text drawing for the virtual target (so monogfx.text() now works there) 2026-03-26 22:47:44 +01:00
Irmen de Jong a47be61812 fix bobs example, add fps counter to cobra example 2026-03-25 19:35:36 +01:00
Irmen de Jong 68226a8fcd Update Kotlin and dependencies to latest versions
- Bump Kotlin to 2.3.20 across all configurations.
- Upgrade `kotlin-result-jvm` to 2.3.1 in all modules.
- Update Gradle wrapper to 9.4.0.
- Add `poweroff_system()` function in virtual target symboldump
2026-03-25 17:51:01 +01:00
Irmen de Jong 2016883f6c compare p8ir line counts instead of file size 2026-03-21 02:44:21 +01:00
Irmen de Jong d4dd2f02e8 new example floatparse routine 2026-03-21 02:06:46 +01:00
Irmen de Jong bc959435ec fix missing const-evaluation for abs() and clamp()
add `poweroff_system()` for virtual target function
clarify CX16 usage and debugging tips
2026-03-20 02:31:17 +01:00
Irmen de Jong e5c6e4b854 add output size verification for compiled examples; include PET target in tests 2026-03-19 20:16:47 +01:00
Irmen de Jong 4294658d71 fix IR symbol lookup for deeply scoped variables 2026-03-18 23:31:39 +01:00
Irmen de Jong 0a7352de99 add disableCache option to SymbolTable to assist debugging and optimize behavior when -noopt is used 2026-03-18 23:12:48 +01:00
Irmen de Jong bb907aa2af fix chained aliasing errors 2026-03-18 21:52:42 +01:00
Irmen de Jong e5d74d55b5 vm memory optimizations 2026-03-18 18:02:13 +01:00
Irmen de Jong c291d7d4a8 const memory() support in struct initializers 2026-03-18 04:16:05 +01:00
Irmen de Jong d2633187a6 more work on making memory() const (6502) 2026-03-18 02:57:59 +01:00
Irmen de Jong ca56cf26c5 more work on making memory() const (virtual target only for now) 2026-03-18 02:20:50 +01:00
Irmen de Jong f72bec3c4a working on making memory() accepted as constant initializer value 2026-03-17 23:14:03 +01:00
Irmen de Jong 06b4ec600a more permanent solution for the struct name prefix bug when merging blocks, fixes #198 2026-03-17 23:09:52 +01:00
Irmen de Jong 410e330889 add --nostdlib compiler option to disable loading built-in standard libraries 2026-03-17 21:46:33 +01:00
Irmen de Jong 11f187bda8 implement signed % remainder operator for virtual target 2026-03-17 03:36:38 +01:00
Irmen de Jong a53e7c7050 fix divmod result when dividing by 0 2026-03-16 03:25:34 +01:00
Irmen de Jong e56ccdbc3a add signed divmod() support for virtual target 2026-03-16 01:19:02 +01:00
Irmen de Jong 51264267ad fix multi variable initialization from builtin function call that returns multiple values (such as divmod) 2026-03-16 00:45:37 +01:00
Irmen de Jong 8be0cd3c19 fix certain value copying through pointer dereferences 2026-03-16 00:10:47 +01:00
Irmen de Jong decfbfc8cc allow float constants even without float option enable 2026-03-15 22:46:35 +01:00
Irmen de Jong 652da4c692 fix some (not all) problems with directly returning multivalue returncalls 2026-03-15 22:33:17 +01:00
Irmen de Jong ce58f0e759 fix virtual target diskio.f_write(), added f_tell() and f_seek()
cx16 diskio.f_seek() and f_seek_w() now return success status
2026-03-15 21:04:06 +01:00
Irmen de Jong 8ba8efbc3e added sys.die() , a bare bones emergency exit function 2026-03-14 22:13:13 +01:00
Irmen de Jong 276121bfb3 updated Kate syntax file to fix string match problem 2026-03-14 19:06:17 +01:00
Irmen de Jong 5c9c6bd023 updated syntax files 2026-03-14 15:37:27 +01:00
Irmen de Jong 86f331aa39 thread locking tweaks 2026-03-14 00:13:56 +01:00
Irmen de Jong e61328b6d6 update Java dependency to 17, update results library version 2026-03-14 00:04:11 +01:00
Irmen de Jong 9502a6f590 fix invalid augmented assignment optimization, and a call to pop() that crashed a symbol prefix test because the call wasn't defined as a builtin 2026-03-13 22:08:36 +01:00
Irmen de Jong 01748517ae most often used long-copies now done with shorter loop instead of unrolled lda/sta 2026-03-13 21:21:38 +01:00
Irmen de Jong 3890191981 implement missing mul_long routine for long-long multiplication support 2026-03-13 20:32:42 +01:00
Irmen de Jong c20e5b788b implement missing square_long routine
improve agents system prompt file
2026-03-13 18:21:47 +01:00
Irmen de Jong fb3a001a99 attempting to optimize redundant variable initializations (STZs) 2026-03-12 21:38:28 +01:00
Irmen de Jong 04910ffbc0 add enum syntax (sugar for list of const decls) 2026-03-10 20:33:43 +01:00
Irmen de Jong caa991e478 IR: remove redundant add when pointer dereferencing 2026-03-08 21:31:31 +01:00
Irmen de Jong f9767a5b1b cx16: added sys.waitirq() that just does a WAI like waitvsync() but it is explicit about its behavior 2026-03-08 18:05:43 +01:00
Irmen de Jong 3b68741208 replace indexed pointer deref expressions with peek(ptr+index) 2026-03-08 12:59:34 +01:00
Irmen de Jong b8ef64139a avoid always including profile support routine in resulting asm 2026-03-08 03:16:17 +01:00
Irmen de Jong 1ad9e0fc46 implement missing struct field array indexing and faulty pointer dereferencing 2026-03-08 02:43:52 +01:00
Irmen de Jong ce244906ef added wavfile and adpcm library modules (adpcm only on X16) 2026-03-07 20:47:23 +01:00
Irmen de Jong 63ab79157f optimize palette module routines slightly to no longer use vaddr()
fix unused symbol warnings in bcd library module
2026-03-05 03:55:08 +01:00
Irmen de Jong 9ac0cfd14c fix palette.fade_step_colors() for color indexes >=128 2026-03-05 03:05:19 +01:00
Irmen de Jong 02c544bb62 fix cx16.getlfs() and add diskio.reuse_last_drive()
update cx16images.py to use improved quantization
2026-03-05 01:11:02 +01:00
Irmen de Jong 998d1ad446 IR: storezi now also takes an immediate offset argument, optimized some code gen to utilize that offset better insteads of doing separate ADDs 2026-03-04 01:12:01 +01:00
Irmen de Jong 89e38c6d42 IR: loadfield/storefield are now just loadi/storei 2026-03-03 21:17:27 +01:00
Irmen de Jong a326c6f079 adding multivalue vardecls and assignments 2026-03-03 00:06:48 +01:00
Irmen de Jong acf70d68ca todo 2026-03-02 02:54:16 +01:00
Irmen de Jong 17850d3bc1 fix certain long initialization type error 2026-03-02 02:14:31 +01:00
Irmen de Jong d03e49a92f implemented 6502 long loops with step >1 2026-03-01 03:13:54 +01:00
Irmen de Jong 62c5b531c5 implemented 6502 long loops with step 1 or -1 2026-03-01 02:06:18 +01:00
Irmen de Jong 8911f58bd2 fix IR long comparisons (used in for loops for instance) 2026-03-01 01:45:07 +01:00
Irmen de Jong da6e47d422 fix IR long comparisons (used in for loops for instance) 2026-03-01 01:17:22 +01:00
Irmen de Jong 10d80d0274 tinyc64 custom config: move SCRATCH_PTR to $04+$05, from $9b+$9c because $9b is used by JiffyDOS. see #217 2026-02-28 23:19:57 +01:00
Irmen de Jong 23b9a2792d C64: move SCRATCH_PTR to $04+$05, from $9b+$9c because $9b is used by JiffyDOS. Fixes #217 2026-02-28 22:55:44 +01:00
Irmen de Jong 0a52cd2cee changed math.crc16() to now also take an initvalue and xorout value, to make it more flexible in calculating various variants of CRC16 checksums 2026-02-28 18:13:00 +01:00
Irmen de Jong fedf342cee fix interned string problem with -profiling 2026-02-28 16:46:27 +01:00
Irmen de Jong 2bafc161d6 attempt to fix doc 2026-02-27 23:03:14 +01:00
Irmen de Jong d0c5e35f0a attempt to fix doc 2026-02-27 22:56:58 +01:00
Irmen de Jong 859a02fb0a adding profiling instrumentation support 2026-02-27 22:41:42 +01:00
Irmen de Jong 2a53dbc1f3 tweaks 2026-02-27 01:56:55 +01:00
Irmen de Jong 63cba4825a doc tweaks 2026-02-26 01:44:36 +01:00
Irmen de Jong 08e3652924 sgn() can look directly at msb if arg is a variable 2026-02-24 22:11:50 +01:00
Irmen de Jong 10796c09c7 cleanup weiord multi-return type handling more 2026-02-24 21:42:44 +01:00
Irmen de Jong a3cbf4c3db fix divmod doc 2026-02-23 22:01:59 +01:00
Irmen de Jong bdbdf6e5cd add lmh(longvalue) builtin function that returns the low, mid, hi (=bank) bytes of a long
symboldumps now also contain the builtin functions
2026-02-23 21:30:39 +01:00
Irmen de Jong 1b5dd980df optimize result register cases for mklong() and mkword() 2026-02-23 02:07:43 +01:00
Irmen de Jong f6b59c6832 optimize some result register cases for peekw() 2026-02-23 00:54:12 +01:00
Irmen de Jong fe8d904792 fixing result register assignment from builtin functions 2026-02-22 19:54:21 +01:00
Irmen de Jong 9736ac19d1 implementing multiple return values for builtin functions 2026-02-22 19:54:21 +01:00
Irmen de Jong 14abc1f0b7 clarify compiler errors output 2026-02-22 03:22:20 +01:00
Irmen de Jong effc7f1b56 optimize and fix diskio.status_code() also for C64 and PET 2026-02-21 11:07:57 +01:00
Irmen de Jong 093be9f2dd sys.push(), sys.pop() etc etc are now builtin functions again push() pop() to avoid storing value in temporary variables
this means that all of the syslib.p8 library files no longer contain all those stack related asmsubs
2026-02-21 02:11:34 +01:00
Irmen de Jong 9aff85f08a optimize diskio.status_code() size 2026-02-20 20:52:49 +01:00
Irmen de Jong f2077cb56f sqrt_long() no longer allocates a bunch of zeropage variables 2026-02-20 01:23:33 +01:00
Irmen de Jong 2a9ac45cad diskio: make f_open, f_open_w, and exists to read the command channel for success status rather than depending on flaky ST 2026-02-18 02:05:47 +01:00
Irmen de Jong e15452b110 doc 2026-02-17 21:49:35 +01:00
Irmen de Jong a64b586a43 Merge branch 'swap-statement' 2026-02-17 21:00:43 +01:00
Irmen de Jong 2613d45cd5 swap() is now a statement instead of a builtin function call 2026-02-17 20:56:21 +01:00
Irmen de Jong c25fbbccfb update kotlin to 2.3.10 2026-02-17 16:33:21 +01:00
Irmen de Jong 77f0cf8e89 defer stack doc and better error message for invalid range syntax 2026-02-16 23:00:50 +01:00
Irmen de Jong 74ca190ce0 long args call convention is to pass via parameter variable not R14R15 2026-02-15 17:36:09 +01:00
Irmen de Jong 6a0b22bb64 implement sqrt(long) 2026-02-15 17:11:31 +01:00
Irmen de Jong 07be7f0154 fix long argument @R0R1 register usage in regular subroutines 2026-02-15 14:54:47 +01:00
Irmen de Jong d1383813d2 version 12.1.1 2026-02-13 22:11:29 +01:00
Irmen de Jong 3bd2064c3a mention empty file issue 2026-02-12 22:10:53 +01:00
Irmen de Jong f95f568319 fix mflpt5 overflow error message 2026-02-12 02:29:10 +01:00
Irmen de Jong 5eb873b722 conv.str2long() added. alias problems detected. 2026-02-12 00:38:42 +01:00
Irmen de Jong 3f181b0ede allow alias to blocks 2026-02-10 22:51:05 +01:00
Irmen de Jong d6c8fc0c61 alias refactorings 2026-02-10 21:08:35 +01:00
Irmen de Jong 709ced1bca alias test program, replace alias chains 2026-02-10 21:08:35 +01:00
Irmen de Jong 3b16d08246 arrays can contain pointers too 2026-02-10 21:03:07 +01:00
Irmen de Jong b9a6deea25 implement ror / rol on ptr expressions 2026-02-08 21:20:13 +01:00
Andy De George 099514a2c0 Fix error output file path: Encode to URI (#214) 2026-02-06 22:09:58 +01:00
Irmen de Jong ed02b84f7d micro optimization for containment checks in compiler 2026-02-06 02:43:12 +01:00
Irmen de Jong 7d6e67e251 fix numeric type error when comparing undefined symbols 2026-02-04 23:55:08 +01:00
Irmen de Jong 4aa364925d todo 2026-02-02 23:02:21 +01:00
Andrew Gillham e5ae9ad4e0 Add PET 80 column support. (#211) 2026-02-02 23:02:01 +01:00
Irmen de Jong 6329388a4e skeleton 2026-01-31 19:39:43 +01:00
Irmen de Jong ac851a35bc implement FREADUY on pet, so that ubyte to float now works 2026-01-31 19:31:26 +01:00
Irmen de Jong bc0d6453ae revert 2026-01-30 23:40:45 +01:00
Irmen de Jong 8c65e0a537 attempt to create index in latex pdf 2026-01-30 23:38:26 +01:00
Irmen de Jong d415cf356f attempt to create index in latex pdf 2026-01-30 23:33:54 +01:00
Andrew Gillham 2bd74b8c44 Add an index and set some non-ASCII fonts to improve the resulting pdf. (#210)
* Add an index and set some non-ASCII fonts to improve the resulting pdf.

* Fix coroutines index interfering with code block

* Add genindex which is needed to get a link to the html index.
2026-01-30 22:03:40 +01:00
Irmen de Jong 88b48f58c9 fix string init error about string multiplication. fixes #209 2026-01-30 17:36:48 +01:00
Irmen de Jong 75accf4059 release 12.1 2026-01-28 23:45:42 +01:00
Irmen de Jong c3f9959891 fixed invalid replacement in optimization of if-statement.
better replacement errors.
fixes #208
2026-01-28 22:58:53 +01:00
Irmen de Jong ee492a1e9a some more optimized swap() code paths 2026-01-28 00:07:10 +01:00
Irmen de Jong 415f0fb071 pet: added bunch more diskio routines like directory(), delete(), rename() and list_filenames() 2026-01-27 01:07:08 +01:00
Irmen de Jong 8e7bf4227b pet: added diskio.save() and save_raw() 2026-01-26 22:32:46 +01:00
Irmen de Jong d2b743c296 pet: added diskio.load() and load_raw() 2026-01-26 02:38:40 +01:00
Irmen de Jong cb44e76626 swap 2026-01-25 19:14:05 +01:00
Irmen de Jong b2983e912d swap longs now uses R14+R15 as temporary storage instead of R0+R1 2026-01-25 17:02:47 +01:00
Irmen de Jong 10ae42ed59 suggest import for undefined block symbols, update github links 2026-01-25 05:45:33 +01:00
Irmen de Jong 5cbd4aafcc strings: added next_token() which mimicks C's strtok() routine 2026-01-25 00:09:52 +01:00
Irmen de Jong 4aa326d5ca diskio: added lf_start_list_having_prefix()
strings: added split()
2026-01-24 22:42:54 +01:00
Irmen de Jong 39b1bbcf58 pet32: added more missing routines in textio like column(), row() 2026-01-24 19:34:33 +01:00
Irmen de Jong 4661d3bada implement simple array element swap() 2026-01-24 18:41:25 +01:00
Irmen de Jong ab98f4a5e2 fix charset encoding error with merge. Fixes #207 2026-01-24 16:13:11 +01:00
Irmen de Jong b99b054a09 cx16: add missing extapi16 call numbers
tweak swap(long,long) and swap(ptr^^, ptr^^)
2026-01-24 11:51:42 +01:00
Irmen de Jong 5d5ad8a70e fix void/nonvoid assign target issue 2026-01-24 01:11:30 +01:00
Irmen de Jong 9a5edeebbc upgrade to kotlin 2.3.0 2026-01-24 00:29:45 +01:00
Irmen de Jong 3fb45a611a make math.gcd() use swap() builtin 2026-01-24 00:14:31 +01:00
Irmen de Jong 2e0db6f211 add swap() builtin for optimized value swaps without the need for a temporary variable 2026-01-23 23:17:10 +01:00
Irmen de Jong 51ac096a31 add a -warnimplicitcasts compiler option 2026-01-23 03:06:53 +01:00
Irmen de Jong dfad83650d add math.gcd() 2026-01-23 00:03:13 +01:00
Irmen de Jong 492cd686a0 avoid invalid pointer arithmetic in joined expresssions. Fixes #206 2026-01-22 22:25:48 +01:00
Irmen de Jong 561e05a764 decent error message for unsupported struct instance expressions. Fixes #205 2026-01-22 02:47:44 +01:00
Irmen de Jong f56037142b added petgfx module for pet32, c64, c128 for petscii block characters drawing (double resolution) 2026-01-22 02:17:04 +01:00
Irmen de Jong a41cb60328 pet: working boingball example, implement floats.GIVUAYFAY
cx16: improved floats.GIVUAYFAY to no longer depend on magic zp locations
2026-01-20 22:58:47 +01:00
Irmen de Jong ca3e4b161b add Amiga boing ball example, plus more pet syslib definitions, and pet diskio beginnings 2026-01-19 22:47:58 +01:00
Irmen de Jong 4971ebd41f pet32: added floats support! 2026-01-18 23:32:19 +01:00
Irmen de Jong e2276f000c fix alias issue when it targeted multi variable declaration 2026-01-18 19:48:37 +01:00
Irmen de Jong 520a12194e cx16: add new X16Edit rom entrypoint x16edit_loadfile_options2() that also takes starting line number 2026-01-18 19:10:06 +01:00
Irmen de Jong 6c61e1e5e8 fix alias target scoping problem 2026-01-17 21:39:39 +01:00
Irmen de Jong a385412e70 smaller floats.internal_long_to_float() 2026-01-17 20:38:30 +01:00
Irmen de Jong 928443469a strings: implement compare_nocase and compare_nocase_iso for 6502 2026-01-17 17:54:29 +01:00
Irmen de Jong 823f1cd6e2 strings: removed ncompare 2026-01-17 17:29:21 +01:00
Irmen de Jong 3009ddaaab strings: adding compare_nocase and compare_nocase_iso 2026-01-17 17:01:34 +01:00
Irmen de Jong bc0aecb1af cx16 diskio: way simpler solution for case insensitive file name listing 2026-01-17 01:02:38 +01:00
Irmen de Jong 573702111e attempt at improving compilation speed by caching scopedName 2026-01-16 22:46:04 +01:00
Irmen de Jong eaab2cfe5f version 12.1 2026-01-16 21:19:05 +01:00
Irmen de Jong 666285a002 add petsnd to symboldumps 2026-01-16 00:38:33 +01:00
Irmen de Jong d477e9b2bd pet32: added VIA registers (in 'pet' namespace) and petsnd module to play sound. 2026-01-16 00:15:04 +01:00
Irmen de Jong d774bad2b4 docs for new case routines 2026-01-15 22:06:05 +01:00
Irmen de Jong e3bb09d2a1 diskio: case insensitive file/dir listings only on CX16 target, and based on ISO encoding 2026-01-14 23:12:12 +01:00
Irmen de Jong 112bce0ac0 cx16: added ISO lowercase and uppercase routines to strings module 2026-01-14 22:07:24 +01:00
Irmen de Jong aac7d68455 fix watch mode to clear file cache every cycle 2026-01-14 20:16:33 +01:00
Irmen de Jong 620793011e way faster long to float conversion routine, fixes #202 2026-01-13 22:55:37 +01:00
Irmen de Jong cb4ffaa028 added case-insensitive pattern matcher strings.pattern_match_nocase()
added various case-insensitive versions of the file and directory listing routines in diskio (the ones that take an optional pattern)
2026-01-13 01:33:46 +01:00
Irmen de Jong 8c4944d703 optimized long and float copying through pointers 2026-01-12 00:19:21 +01:00
Irmen de Jong f5fbb5f930 implement some missing float copy 2026-01-11 22:06:21 +01:00
Irmen de Jong 40ce40db32 irq doc 2026-01-11 19:00:37 +01:00
Irmen de Jong ee556434bc doc 2026-01-11 14:12:16 +01:00
Irmen de Jong 5cd175d54f optimize successive additions/subtractions and multiplications (with const) into just a single add/sub, mul
optimize var = simpleexpression |  var += expression    -->   var = simpleexpression + expression  (for any augmented operator)
2026-01-11 01:30:04 +01:00
Irmen de Jong b51e116b63 optimize expression: uwordvar + 256 and variants to only manipulate msb 2026-01-10 22:50:33 +01:00
Irmen de Jong 5c7ac0efb7 IR: remove broken loadi/storei optimization 2026-01-10 22:23:01 +01:00
Irmen de Jong 34b228cfca fix peek/poke error with const offset 2026-01-10 20:58:16 +01:00
Irmen de Jong 2b26952044 better error 2026-01-10 19:06:59 +01:00
Irmen de Jong db38c27224 optimized peeks with a ubyte variable offset 2026-01-10 00:22:39 +01:00
Irmen de Jong 560013e58b optimized pokes with a ubyte variable offset 2026-01-09 22:36:04 +01:00
Irmen de Jong cdb41f4352 better errors when multiplying string or array with bogus value 2026-01-09 18:43:41 +01:00
Irmen de Jong 13cd68ba96 IR: fix LOADI.f peephole optimization 2026-01-09 03:49:55 +01:00
Irmen de Jong f69b293b7f optimized some more cases of poke and peek 2026-01-09 02:57:12 +01:00
Irmen de Jong d4bf0bf5b7 optimized simple cases of pokel() 2026-01-09 01:21:11 +01:00
Irmen de Jong 8c6a95f5e2 optimized simple cases of v = peekl() 2026-01-08 23:37:06 +01:00
Irmen de Jong 6301a8b040 optimize msb(long<<8/16/24) into single byte accesses (skip the whole shift) 2026-01-08 22:38:07 +01:00
Irmen de Jong daa9b40883 optimize lsb(long>>8/16/24) into single byte accesses (skip the whole shift) 2026-01-08 03:21:57 +01:00
Irmen de Jong aa045bb383 optimized certain word and long shifts to get the lsb/msb 2026-01-08 02:45:09 +01:00
Irmen de Jong 9d7049a659 optimized long>>8, long>>16, long>>24 2026-01-08 00:31:39 +01:00
Irmen de Jong bbfb2eb367 optimized longvar = lptr^^ 2026-01-07 23:30:35 +01:00
Irmen de Jong 447271f3f2 implemented missing byte ptr assignment 2026-01-07 22:42:53 +01:00
Irmen de Jong 78d6d2d59a breaking change: renamed R0R1_32 etc combined register parameters to just R0R1, R2R3, etc etc (_32 suffix is removed to make it more consistent with the other existing register names) 2026-01-07 21:56:16 +01:00
Irmen de Jong c6cd39b4ab slighty improved fix len() error message for pointer types 2026-01-07 21:37:07 +01:00
Irmen de Jong cd34c4eed0 fix len() error message for pointer types 2026-01-07 03:25:52 +01:00
Irmen de Jong 652b585ea4 code style 2026-01-07 00:48:45 +01:00
Irmen de Jong 2913fa47b2 fix pointer assignment TODO 2026-01-07 00:31:06 +01:00
Irmen de Jong d6e4175e21 fix msb(a+b), lsb(a+b) 2026-01-07 00:20:49 +01:00
Irmen de Jong 3a0add60fd optimize msb(lsw(longvar)) into @(&longvar+1) 2026-01-06 22:58:45 +01:00
Irmen de Jong bc4470aeda remove bsb() again - too confusing? 2026-01-06 22:39:04 +01:00
Irmen de Jong 97dade3655 fix setmsb() on long types 2026-01-06 22:11:58 +01:00
Irmen de Jong 54f23387d0 added bsb(long) builtin function that returns the bank byte of the long value (bits 16-23) 2026-01-06 20:56:03 +01:00
Irmen de Jong 77161e0f39 avoid separate definitions for list of simple builtin functions 2026-01-06 18:19:15 +01:00
Irmen de Jong 70d99bebb0 on c64/c128, txt.getclr() now always returns 0-15 (upper 4 bits are cleared). fixes #204 2026-01-05 21:50:38 +01:00
Irmen de Jong 75156f7dbd fix optimized long comparison against 0 2026-01-05 19:08:56 +01:00
Irmen de Jong 9827be0047 optimize sgn(integer) comparisons 2026-01-05 18:33:26 +01:00
Irmen de Jong 495dd526e1 comment correction 2026-01-04 23:04:33 +01:00
Irmen de Jong 3d20bc13b5 Virtual target: fix strings.right() and strings.slice() 2026-01-04 20:55:02 +01:00
Irmen de Jong 6028cc49ab simplify when into if-else, if possible 2026-01-04 19:39:38 +01:00
Irmen de Jong 23d3fb96b9 improved parsing rule of constdecl 2026-01-02 23:36:55 +01:00
Irmen de Jong 51503e054a IR: improve use of loadfield for msb/lsb of struct fields 2026-01-02 22:10:10 +01:00
Irmen de Jong 3f6177cbb8 improved 6502 codegen for lsb/msb of struct field 2026-01-02 21:36:11 +01:00
Irmen de Jong c1c88e0327 improved 6502 codegen for memory byte access into structs 2026-01-02 21:03:13 +01:00
Irmen de Jong 18ba25d6a4 IR: optimize double ADD/SUB 2026-01-02 20:24:35 +01:00
Irmen de Jong e74162671a clarify doc, fix possible poke() ast rewrite error 2026-01-02 19:11:57 +01:00
Irmen de Jong 2290308e06 fix invalid 6502 instructions in math.diff. Fixes #203 2026-01-02 01:08:50 +01:00
Irmen de Jong 82693cc3ee optimized lsb(msb(longvar)) which grabs the bank byte from a long 2026-01-01 21:39:34 +01:00
Irmen de Jong 50b8cae519 sys.save_prog8_internals() now also correctly saves the SCRATCH_PTR temp var. Moved a bunch of common sys routines into separate shared_sys_functions.p8 module file 2025-12-31 12:10:04 +01:00
Irmen de Jong 3a74931a61 c128: added c128.fast() and c128.slow() to enable/disable the CPU's 2 MHz mode.
Also added c128.is80(), set80(), set40() to check and set 40/80 column screen mode
2025-12-30 23:53:58 +01:00
Irmen de Jong c87e920c61 IR: stick all variable elements in <VARS> to improve the xml file structure a bit 2025-12-30 22:17:29 +01:00
Irmen de Jong 2c2de8bfb3 fix IR peephole optimizer index off by 1 error and make SSA blocks configurable (still defaults to yes in this version) 2025-12-30 18:58:42 +01:00
Irmen de Jong d386343fe7 fix romable error check for arrays 2025-12-30 02:26:01 +01:00
Irmen de Jong 5b32cfd172 fix some obscure errors, improve some error messages 2025-12-28 23:02:03 +01:00
Irmen de Jong 46dffcfb96 cx16: sysinit now explicitly makes vera border color also black (0) 2025-12-28 17:18:43 +01:00
Irmen de Jong b156c876f0 fix long expression typecast error 2025-12-28 01:24:54 +01:00
Irmen de Jong 71b66f506d fix asmsub register order crash when using longs + something else 2025-12-28 01:08:10 +01:00
Irmen de Jong 28d087b8b4 psg2 tweaks 2025-12-27 17:47:40 +01:00
Irmen de Jong a57815153d Merge branch 'psg2-module'
# Conflicts:
#	examples/test.p8
2025-12-27 17:12:05 +01:00
Irmen de Jong f910f73f28 added txt.iso2petscii() and txt.iso2petscii_str() 2025-12-26 17:55:07 +01:00
Irmen de Jong 6635515bf0 fix subtracting from str pointer, this fixes strings.endswith() for instance 2025-12-26 15:15:15 +01:00
Irmen de Jong cc0425a2c4 detect circular aliases, also fix error message for aliased function call with wrong number of args 2025-12-26 15:04:55 +01:00
Irmen de Jong c7052a183e fixing wrong error message about wrong arg count with alias 2025-12-25 23:49:03 +01:00
Irmen de Jong d9a0f3cb4a add line numbers to certain error messages 2025-12-25 22:30:05 +01:00
Irmen de Jong 80d6eb232d bool variables were not converted to constants if that was possible 2025-12-25 17:39:54 +01:00
Irmen de Jong d9ffb10eb5 added psg2 module (cx16 only) 2025-12-25 01:22:38 +01:00
Irmen de Jong 463d53345d more helpful min max error message for floats 2025-12-24 17:19:47 +01:00
Irmen de Jong 4b1063530e define cx16.VERA_PSG_BASE, VERA_PALETTE_BASE, VERA_SPRITES_BASE as long consts into Vera VRAM
returning a bool is no longer allowed if the sub return type is byte
2025-12-24 03:20:53 +01:00
Irmen de Jong 9266943e74 fix pointer assign type error with cast to uword 2025-12-24 01:18:23 +01:00
Irmen de Jong 1e3ae2cb1d IR: just use loadhay to get the result from CALLI 2025-12-23 19:07:51 +01:00
Irmen de Jong 55bc242e8b implemented in-place long negation and some missing long to float casts 2025-12-23 15:30:39 +01:00
Irmen de Jong aa3bbbb867 tweaks to wav streaming examples 2025-12-23 05:32:28 +01:00
Irmen de Jong 45ad0911df emudbg.cpu_cycles() now returns a long type instead of 2 separate words 2025-12-23 02:02:34 +01:00
Irmen de Jong 946def4613 IR: fix call() / CALLI return value handling 2025-12-23 00:39:14 +01:00
Irmen de Jong 3000c399bd cx16: add support for VIA timer IRQ in the irq routines 2025-12-22 21:37:09 +01:00
Irmen de Jong 1682400988 fix missing cx16 virtual register symbols in asm file (bool and long variants)
fix actually relocating all of them in the cx16 module
pet32 and c128 targets now also relocate them to ZP if there is space
2025-12-22 17:56:25 +01:00
Irmen de Jong 6ad435df1f mention main block size restriction 2025-12-22 05:28:55 +01:00
Irmen de Jong cd433b2111 c128: disable f-keys macros on startup 2025-12-22 00:23:21 +01:00
Irmen de Jong a50eebb369 fixed the balls example to also work on the monochrome pet32 target 2025-12-21 22:27:54 +01:00
Irmen de Jong 13d4af59c7 c64+c128: improved speed of txt.setchr, txt.getchr, txt.setclr, txt.getclr, txt.setcc 2025-12-21 22:23:17 +01:00
Irmen de Jong b6f4f2adb7 c64+c128: improved speed of txt.setchr and txt.getchr 2025-12-21 22:23:17 +01:00
Irmen de Jong e57d9f59a2 fix long 0 argument to asmsub with 32-bits combined virtual register to pass it in 2025-12-21 22:23:17 +01:00
Irmen de Jong 071d5dc0c5 pet32: improved speed of txt.setchr and txt.getchr 2025-12-21 22:23:16 +01:00
Irmen de Jong 4030a1b9e5 cleanup, animals example removed because it's superseded by the pointers/animalgame example now 2025-12-16 16:48:34 +01:00
Irmen de Jong 0217d05c68 IR: loadfield/storefield now accept offsets up to 65535 2025-12-16 14:56:11 +01:00
Irmen de Jong 8553f3c9f1 IR: loadfield is a better name 2025-12-16 12:57:45 +01:00
Irmen de Jong d157d03ea0 IR: optimize load+add+load into single loadm with offset (also store)
update gradle wrapper to 9.2.0
2025-12-16 12:17:52 +01:00
Irmen de Jong 1799b5f00c updated bundled zsmkit version to 2.8 2025-12-15 18:41:28 +01:00
Irmen de Jong 2ddb5e11b7 updated symboldumps 2025-12-13 02:12:00 +01:00
Irmen de Jong 3aa8b9fd12 turn prefix - numbers into negative numbers asap to avoid type problems later 2025-12-13 01:13:52 +01:00
Irmen de Jong 51300196e8 fix more long comparisons with arrays, fixes #196 2025-12-12 21:01:51 +01:00
Irmen de Jong f7b2f19cba remove restriction about long comparison with expression 2025-12-11 21:43:53 +01:00
Irmen de Jong 014a82a1ee always put all struct types as .struct in asm code to make them all accessible for size and offsets 2025-12-10 21:39:44 +01:00
Irmen de Jong 745cf3d958 IR: removed loadix/storeix instructions 2025-12-09 22:55:53 +01:00
Irmen de Jong c4aa681d58 IR: better name for loadfield/storefield instructions 2025-12-09 00:40:26 +01:00
Irmen de Jong feb0774bbd in benchmark-c routines, use same CIA timer as the original C code did so the program size is better comparable 2025-12-08 01:08:33 +01:00
Irmen de Jong 2ccfb2ac90 fix double evaluation (sign extension) of word to long assignment. Fixes #201 2025-12-07 22:23:59 +01:00
Irmen de Jong 2191c4b7d8 remove redundant expression assignment before multiplying certain words 2025-12-07 14:06:59 +01:00
Irmen de Jong 39c1cf0c3c fix diskio lf_start_list_dirs() and lf_start_list_files() not processing the pattern argument 2025-12-07 05:11:08 +01:00
Irmen de Jong c693be0dd6 fix byte subtract from long
fix IR register check error for %ir segments
2025-12-06 20:40:42 +01:00
Irmen de Jong 0f433055a7 fix pointer dereference division and modulus , fixes #199 2025-12-05 22:19:54 +01:00
Irmen de Jong 76c95ba6fa some more obscure inplace modifications implemented 2025-12-05 21:00:14 +01:00
Irmen de Jong bd272a8567 optimize some specialized cases of long << 2025-12-05 01:08:19 +01:00
Irmen de Jong b38fff76e9 hack to work around struct name prefix inconsistency
see github issue 198
2025-12-03 21:07:08 +01:00
Irmen de Jong 07b7639ff2 better error msg 2025-12-02 22:56:40 +01:00
Irmen de Jong 6871561c09 fix parent node error in sizeof() 2025-12-01 23:21:14 +01:00
Irmen de Jong 77a71138ad fix typo r8r0sl -> r8r9sl 2025-11-30 22:05:57 +01:00
Irmen de Jong 165f94bc93 fix long expression <0 / >0 comparison 2025-11-30 16:57:05 +01:00
Irmen de Jong b75090f928 fix some register clobbering with long operations 2025-11-30 16:44:36 +01:00
Irmen de Jong 62b0c82f93 implement long <=0 and long >=0 comparisons
make sure the adpcm benchmark doesn't use random memory contents
2025-11-30 16:25:58 +01:00
Irmen de Jong b1ef863c7f make $8000000 a valid long integer (-2147483648) 2025-11-26 23:10:59 +01:00
Irmen de Jong e78345410f fix long-- high word wrap bug 2025-11-26 20:13:17 +01:00
Irmen de Jong 8b86f97aaa Implement struct field long and signed word bitshifts. Fixes #194 2025-11-25 22:28:28 +01:00
Irmen de Jong 93135774e6 fix IR signed bitshift right 2025-11-25 22:16:28 +01:00
Irmen de Jong a64f27c6b0 add cx16.r0r1sl, cx16.r2r3sl, ... that memory-map signed longs on the virtual registers 2025-11-25 21:13:03 +01:00
Irmen de Jong 69ef63c96d tweaks 2025-11-24 22:23:40 +01:00
Irmen de Jong df1a2a1611 also optimize BRA+RTS into just BRA
release 12.0
2025-11-23 15:18:05 +01:00
Irmen de Jong d19a3af9ed change some single use float global constants to their asm proc 2025-11-21 21:35:51 +01:00
Irmen de Jong 352c11ad9f optimize float<>0 into sgn(float)<>0 2025-11-21 00:57:43 +01:00
Irmen de Jong 9504711fc7 IR/VM: SGN sets status bits 2025-11-19 21:37:04 +01:00
Irmen de Jong 590feda903 IR: fix sgn(float) register type error 2025-11-19 21:11:31 +01:00
Irmen de Jong 6e7e2922bf moved the float <> long cast routines to float.asm where the other cast routines are too 2025-11-19 18:45:32 +01:00
Irmen de Jong 01df1f0083 way more efficient implementation of internal_cast_as_long now using QINT 2025-11-19 18:32:40 +01:00
Irmen de Jong 397299bd1d implement 6502 codegen for casting float to long (super slow...) 2025-11-19 00:05:18 +01:00
Irmen de Jong c275aacd38 implement 6502 codegen for casting long to float 2025-11-18 23:26:12 +01:00
Irmen de Jong 6a6e18773e C64: key repeat is now enabled at program startup, to fall in line with the default key repeat behavior on the C128 and X16. 2025-11-18 21:09:13 +01:00
Irmen de Jong 99e037489b IR: added float<>long casts, cx16: blink_cursor extapi tested in test.p8 2025-11-17 23:13:56 +01:00
Irmen de Jong fb5290e17b cx16: added diskio.f_fatlba() 2025-11-15 20:17:26 +01:00
Irmen de Jong 818774ab84 added conv.hex2long(), added more new X16 extapi calls (rom 49+) 2025-11-15 17:52:34 +01:00
Irmen de Jong d667312acc Merge branch 'refs/heads/12.1-SNAPSHOT'
# Conflicts:
#	docs/source/todo.rst
#	examples/test.p8
2025-11-15 15:15:06 +01:00
Irmen de Jong 64d0cd87a8 diskio.f_seek() and f_tell() now use longs instead of separate words
also fix bug when returning multiple long values
2025-11-15 15:13:26 +01:00
Irmen de Jong 2fffbdde89 todo 2025-11-14 20:05:46 +01:00
Irmen de Jong dc8ea31c49 fix codegen error for a word array equality test 2025-11-13 21:18:04 +01:00
Irmen de Jong f4ead66e91 fix param name breakage in PET txt.setcc for ignored charcolor parameter 2025-11-12 22:00:30 +01:00
Irmen de Jong 314e7f5691 doc updates 2025-11-10 23:42:29 +01:00
Irmen de Jong da31465b7f added the missing smallstack to buffers module 2025-11-10 00:21:13 +01:00
Irmen de Jong 397a907088 fix handling and errors of using long addresses in memread/memwrite 2025-11-09 18:17:57 +01:00
Irmen de Jong 50c6962e1f fix symbol lookup crash 2025-11-09 15:10:10 +01:00
Irmen de Jong b7d1fb1342 translate address-of into + expression for non-const address ptrs too,
also IR optimization to use immediate arithmetic opcodes if possible
2025-11-09 01:36:34 +01:00
Irmen de Jong afb458a7da fix crash in long address-of 2025-11-08 22:24:37 +01:00
Irmen de Jong da3c7f267f improved identifier const value handling if types differ 2025-11-08 20:34:51 +01:00
Irmen de Jong b7ba7c50b1 handle &ptr[offset] for non-uword ptr 2025-11-08 20:31:28 +01:00
Irmen de Jong bdbfe7048b updated library symboldumps 2025-11-08 00:21:53 +01:00
Irmen de Jong 79039b66d4 beta6 2025-11-07 23:40:03 +01:00
Irmen de Jong 833e463525 String indexing bound check now includes the terminating 0 character. Also fix negative indexes on strings.
fixes #190
2025-11-07 23:20:44 +01:00
Irmen de Jong 6611e4e092 improve error message 2025-11-07 01:22:41 +01:00
Irmen de Jong 1f31cb18e4 IR: fix missing source lines in p8ir file 2025-11-06 00:39:31 +01:00
Irmen de Jong bc8ba252a5 made Position line, col, endcol all 1-based
added a precise (but slow) back face culling routine to the cobra-mk3 example
2025-11-05 00:25:43 +01:00
Irmen de Jong c353dd40bf put on..call jumplist in correct scope, fixes #197 2025-11-03 23:46:49 +01:00
Irmen de Jong 928ef6bbaa fix on..call parse problem, also struct name cannot be a keyword 2025-11-03 21:21:07 +01:00
Irmen de Jong 45dde856c6 upgrade to kotlin 2.2.21 2025-11-02 20:08:59 +01:00
Irmen de Jong e1ccef4e89 sort the library routines better, updated result of crc32 benchmark 2025-11-02 16:58:49 +01:00
Irmen de Jong 4d3f0ec223 sizeof(string) is now defined 2025-11-01 21:19:30 +01:00
Irmen de Jong b73c958c4a ir immediate syntax fixes 2025-11-01 16:55:00 +01:00
Irmen de Jong 2eac457e1c clarify string assignment (or rather, the lack thereof) 2025-11-01 00:40:52 +01:00
Irmen de Jong 815ef7e654 add check for not yet implement long expression comparisons 2025-11-01 00:26:36 +01:00
Irmen de Jong 0cd8c4f87e add strings.ncopy and nappend (length limited) 2025-10-31 21:22:15 +01:00
Irmen de Jong b02a8ed954 strings can no longer be assigned by-value. Use strings.copy() instead. Fixes #189 2025-10-31 19:22:40 +01:00
Irmen de Jong b1e07f3fdb better error when trying to use a const pointer (which is not supported yet) 2025-10-30 20:58:21 +01:00
Irmen de Jong fc8727f81e allow pointer to pointer assignment in cast codegen, fixes #191 2025-10-30 19:35:44 +01:00
Irmen de Jong ec728bad52 fix certain float array assignment, fixes #193 2025-10-30 01:45:25 +01:00
Irmen de Jong a5e827e40f implement some more long array operations 2025-10-30 01:20:59 +01:00
Irmen de Jong 34061c5a63 implement inplace bitwise operations on long struct fields 2025-10-28 23:44:34 +01:00
Irmen de Jong 9c2bcab4a5 fix more ptr/long issues 2025-10-28 23:08:12 +01:00
Irmen de Jong 2f07f41664 detect self-referencing aliases 2025-10-23 21:24:14 +02:00
Irmen de Jong 7027304597 put some upper bounds on more internal compiler loops 2025-10-23 20:37:44 +02:00
Irmen de Jong ee75333891 implement more long assignments through pointers 2025-10-21 22:55:46 +02:00
Irmen de Jong 6a70fb0480 deal with invalid sqrt results (negative argument), allow sqrt of longs (like floats) 2025-10-21 21:01:03 +02:00
Irmen de Jong ebc738b132 'hack' to allow unsigned long constants such as $ffffffff to be assigned to longs without casts 2025-10-20 00:33:40 +02:00
Irmen de Jong e5939be0bd some more long operations implemented 2025-10-19 23:03:17 +02:00
Irmen de Jong 0c0affd1bf cx16 diskio.load_size() now returns full long size. Fixes #184 2025-10-19 21:18:46 +02:00
Irmen de Jong c7158fd968 implement more long operations on struct fields 2025-10-17 01:55:11 +02:00
Irmen de Jong de2f3f0d0d tweak TODO messages for longs 2025-10-16 23:20:34 +02:00
Irmen de Jong c0286e3349 added txt.size() to return text screen width and height in 1 call. Also fixed txt.width/height on the C128 (where SCREEN reports 1 less for them...) 2025-10-16 00:33:14 +02:00
Irmen de Jong 513ee25432 beta5 2025-10-15 18:29:36 +02:00
Irmen de Jong be74290ddc many more TODO messages now log proper source positions 2025-10-15 17:58:40 +02:00
Irmen de Jong a36501a9ed fix long bitwise expressions temp register usage 2025-10-14 02:57:48 +02:00
Irmen de Jong ee5d33a230 fix long if-expressions temp register usage 2025-10-14 02:16:42 +02:00
Irmen de Jong a8fd66cfdb fix long==0 long!=0 register usage 2025-10-14 01:49:42 +02:00
Irmen de Jong f5ce744748 undo the math crc param optimization that now causes the footgun warning when you import math. + doc fix about arithmetic expressions 2025-10-14 00:31:29 +02:00
Irmen de Jong 68066acdec changed (and fixed) msb(long) and lsb(long) 2025-10-13 21:34:03 +02:00
Irmen de Jong 6286035d89 support long +/- number in expressions 2025-10-13 02:50:48 +02:00
Irmen de Jong 9a37eb9146 save A and Y when storing longs 2025-10-13 02:12:07 +02:00
Irmen de Jong 307796f115 fixed some bugs in optimizing long values 2025-10-12 23:15:42 +02:00
Irmen de Jong e962431139 added bcd.addtol()/subfroml() for in-place long calculations
fix long overflow messages, IR register type error
2025-10-12 22:08:10 +02:00
Irmen de Jong b7620abc9a longs: fix msw() and lsw() 2025-10-12 19:49:36 +02:00
Irmen de Jong d2719d23f5 C128: adjust MEMTOP value to $C000 during runtime (41 Kb of available space because Basic ROM is banked out) 2025-10-12 15:02:36 +02:00
Irmen de Jong d69eead6d8 fix abs(long), mklong(), mklong2(), peekl(), pokel() 2025-10-12 14:17:27 +02:00
Irmen de Jong 6db3611d93 fix typecast error when assigning pointer to long 2025-10-12 12:44:28 +02:00
Irmen de Jong a84320c7e2 add sgn(long) support, fix sgn() result type in IR 2025-10-12 12:22:32 +02:00
Irmen de Jong dfc720557c fix some more long hex value comparisons 2025-10-12 04:40:50 +02:00
Irmen de Jong 32149d073a bitwise long exprs now use R12-R13 instead of R2-R3 2025-10-12 03:02:09 +02:00
Irmen de Jong fc38be6376 fix a const long value hex asm mistake, fix conv.str_l(0) empty output 2025-10-12 01:52:35 +02:00
Irmen de Jong 13f6efc1d1 long +/- long now use R12-R15 instead of R0-R3 2025-10-12 01:35:02 +02:00
Irmen de Jong 8296002887 fix combined reg asm name 2025-10-12 01:34:00 +02:00
Irmen de Jong 424b89f357 longs now returned in R14+R15 instead of R0+R1 2025-10-12 00:27:17 +02:00
Irmen de Jong 4f5590fbff doc 2025-10-11 21:37:59 +02:00
Irmen de Jong 598e70c49a allow negative values on poke and pokew without an explicit cast 2025-10-11 16:39:36 +02:00
Irmen de Jong a6d051b496 update syntax files 2025-10-11 16:15:34 +02:00
Irmen de Jong 796d3242e1 optimize word pointer indexing and fix long pointer indexing assignment (pokel) 2025-10-11 15:59:27 +02:00
Irmen de Jong 1e8ff6f82a warning in doc about problems when using long + R0/R1 together 2025-10-11 15:36:57 +02:00
Irmen de Jong 07bb5c36bd avoid fallback JSR peek for common case pointer+offset 2025-10-11 04:45:03 +02:00
Irmen de Jong 7e26ecb0b6 allow 'then' in if-expressions to separate condition and result value 2025-10-10 23:26:53 +02:00
Irmen de Jong 0c59ad70d4 fix aliased pointer deref problem 2025-10-10 22:13:24 +02:00
Irmen de Jong 53ce688cc5 fix pointer type check for R0-R15 subroutine arg 2025-10-10 00:54:52 +02:00
Irmen de Jong bb8b44d9d0 fix missing byte to long assignment, add c-bench-64 comparison 2025-10-10 00:01:54 +02:00
Irmen de Jong 51ae32d23e fix up todo 2025-10-09 21:14:06 +02:00
markjreed c873fac0dc feat: point doc source links to matching version (#179)
* feat: point doc source links to matching version

* fix: formatting, remove debug
2025-10-09 20:03:46 +02:00
Irmen de Jong f0a67fff8a added missing byte to long type assignment 2025-10-09 02:52:33 +02:00
Irmen de Jong f85ccd837d tweak conv.str_l() so that it properly reuses the conv.string_out buffer 2025-10-08 22:33:03 +02:00
Irmen de Jong 396fcbc927 implement missing long typecasts 2025-10-07 17:46:10 +02:00
Irmen de Jong 0f564b301d implement peekl() and pokel() 2025-10-07 00:53:00 +02:00
Irmen de Jong f4f34fc2ed added symboltable into CompilationResult because it might be useful to inspect in tests 2025-10-07 00:26:43 +02:00
Irmen de Jong f7639cb78f implement long comparisons > and <= 2025-10-06 23:42:40 +02:00
Irmen de Jong f5f5aef722 implement long comparisons < and >= 2025-10-06 22:30:17 +02:00
Irmen de Jong d6e30d8468 fix signed word and long cmp() 2025-10-06 22:05:34 +02:00
Irmen de Jong 37afdf5a18 tweak antlr grammar to improve parsing performance 2025-10-06 01:28:33 +02:00
Irmen de Jong 5eb7074172 fix gfx_lores and gfx_hires disc() inaccurracy 2025-10-05 23:21:19 +02:00
Irmen de Jong 9aff280d10 fix monogfx.disc() inaccurracy 2025-10-05 23:03:53 +02:00
Irmen de Jong 44741a8e32 fix c64 graphics.disc() inaccurracy 2025-10-05 22:57:39 +02:00
Irmen de Jong 37535f2913 optimize charfade example, added textspotlight example 2025-10-05 21:18:47 +02:00
Irmen de Jong ec9475c308 use pointer arithmetic instead of raw peeks and pokes 2025-10-05 18:38:01 +02:00
Irmen de Jong b961ce97d6 add -libdump and -libsearch compiler options 2025-10-05 17:07:55 +02:00
Irmen de Jong 4ed92d71a7 remove "@split" tag
The default is to split word arrays. If you need your word array to not be split, use @nosplit on the array.
2025-10-05 17:06:21 +02:00
Irmen de Jong 3e1386a987 remove the -dontsplitarrays compiler option
it was still there for backward compatibility reasons with really old prog8 code. If you need a word array to be not split, just use @nosplit on the array.
2025-10-05 14:44:29 +02:00
Irmen de Jong 4195e3968a remove needless library module imports in unit tests 2025-10-05 13:10:02 +02:00
Irmen de Jong 961095bfec fix output path for shadow jar 2025-10-04 23:31:52 +02:00
Irmen de Jong 3ea8ca59b7 beta 4 now with support for long values 2025-10-04 22:55:18 +02:00
Irmen de Jong 71ffbe2ba7 document the long type 2025-10-04 22:33:43 +02:00
Irmen de Jong e63921009c added math.mul32(), verafx.muls now returns long 2025-10-04 21:54:53 +02:00
Irmen de Jong db1aa3f257 math.crc32() now returns the crc value as a long 2025-10-04 17:40:49 +02:00
Irmen de Jong 8abdb837b2 fix long equality comparisons 2025-10-04 17:21:39 +02:00
Irmen de Jong cdeabd4b66 only * and / 2025-10-04 05:13:18 +02:00
Irmen de Jong efff74c0f1 added sys.pushl() and sys.popl() 2025-10-04 03:05:50 +02:00
Irmen de Jong 0e177cb531 added cbm.RDTIML and cbm.SETTIML wrappers 2025-10-04 02:00:24 +02:00
Irmen de Jong 35b921b062 added bcd module for decimal addition and subtraction using BCD mode 2025-10-03 23:36:17 +02:00
Irmen de Jong a7d98e43b8 cmp(long, long) 2025-10-03 22:41:59 +02:00
Irmen de Jong 845ee2dd83 smaller code for several long operations 2025-10-03 21:25:13 +02:00
Irmen de Jong f9b0bfe31b implement rol() and ror() on longs (also rol2 and ror2 and abs) 2025-10-03 01:22:24 +02:00
Irmen de Jong f303d15628 fix long equality checks 2025-10-03 01:22:24 +02:00
Irmen de Jong 67f7ffa52d multi value returns with longs 2025-10-03 01:22:24 +02:00
Irmen de Jong 88c5d9783a long params and return values 2025-10-03 01:22:24 +02:00
Irmen de Jong e7fc0360ad long bitwise operator expressions 2025-10-03 01:22:24 +02:00
Irmen de Jong cd1862dd9f fix signed long shift right 2025-10-03 01:22:24 +02:00
Irmen de Jong 045c4909a8 long equality comparisons 2025-10-03 01:22:24 +02:00
Irmen de Jong f1bfe619b2 avoiding endless loop in optimizer 2025-10-03 01:22:24 +02:00
Irmen de Jong e0107bacbd implement long << >> expressions 2025-10-03 01:22:24 +02:00
Irmen de Jong b3bd2a6a09 fixed a bunch of long type handling 2025-10-03 01:22:24 +02:00
Irmen de Jong ff1f58e022 implement simple + / - long expressions 2025-10-03 01:22:24 +02:00
Irmen de Jong 557b12668d implement print_ulhex(long, prefix) 2025-10-03 01:22:24 +02:00
Irmen de Jong b058f1c7c2 implement mklong(a,b,c,d) and mklong2(w1,w2) 2025-10-03 01:22:24 +02:00
Irmen de Jong 3e07b6ca70 adding long arrays 2025-10-03 01:22:24 +02:00
Irmen de Jong d66dc664de work on longs 2025-10-03 01:22:24 +02:00
Irmen de Jong a2b9d78cf3 start with introducing LONG datatype (32 bits signed integer) 2025-10-03 01:22:24 +02:00
Irmen de Jong 44f70da113 upgrade to JDK 17 for the build process (required by Gradle 9) 2025-10-03 00:18:55 +02:00
Irmen de Jong c87eaf576d buildfix and update to Gradle 9.1.0 2025-10-03 00:11:52 +02:00
Irmen de Jong c09b1395f6 IR: add a unit test for the SSA basic block change 2025-10-01 22:49:51 +02:00
Irmen de Jong d6a8201291 IR: fix SQRT reg1 type 2025-10-01 20:28:38 +02:00
Irmen de Jong 4187f97f7a IR: rename <CODE> to <CHUNK> , put the code lines in a new sub node <CODE> for improved xml structure 2025-10-01 19:32:05 +02:00
Irmen de Jong 9d3b2f12fd IR code blocks now better SSA basic blocks (ending with single branch instruction) 2025-10-01 19:08:44 +02:00
Irmen de Jong 87c1bbbf40 readme about LSP 2025-10-01 01:15:00 +02:00
Irmen de Jong a7ad6abdb9 Merge branch 'languageServer'
# Conflicts:
#	examples/test.p8
2025-09-30 22:14:03 +02:00
Irmen de Jong a611406020 IR: add compilerversion attribute to p8ir file 2025-09-30 22:07:04 +02:00
Irmen de Jong 75da38224d IR: make LSIG,MSIG,CONCAT instruction set flags to skip cmp #0 afterwards (if msb(x)>0) 2025-09-30 21:48:57 +02:00
Irmen de Jong 8d6f3301c8 conv.any2uword() return values have been changed to be more useful and convenient
now returns both the actual value and the number of characters, and the return values for the virtual target now matches the others.
2025-09-30 20:44:02 +02:00
Irmen de Jong 86b52a1c5e fix endless loop in rewriting type of const long values 2025-09-29 22:28:13 +02:00
Irmen de Jong 2c8b1c2022 moved cx16.cpu_is_65816() to sys.cpu_is_65816(). It know also does proper detection on the C64 and C128 like on the X16, because those two computers can also have this CPU via a SuperCPU expansion. 2025-09-29 21:37:26 +02:00
Irmen de Jong ab1f065752 first setup of LSP languageserver 2025-09-27 15:27:37 +02:00
Irmen de Jong 2c7256a443 support assignment to indexed pointer targets 2025-09-27 14:52:04 +02:00
Irmen de Jong 97420b28e5 preparing to support assignment to indexed pointer targets 2025-09-27 11:03:55 +02:00
Irmen de Jong 1467c7039d fix pointer deref ast printer 2025-09-27 10:42:55 +02:00
Irmen de Jong d319badc6c default 0 for long type 2025-09-25 21:32:23 +02:00
Irmen de Jong 65d6c1c438 fix IR instruction R/W usage counter 2025-09-25 01:50:27 +02:00
Irmen de Jong abeefb5655 improved pointer[0] 2025-09-24 22:26:09 +02:00
Irmen de Jong 50fecbcebe c64 sprite multiplexer WIP 2025-09-24 21:11:08 +02:00
Irmen de Jong 4fe8b72d42 fix broken uword comparison and asm peephole optimization 2025-09-24 02:42:26 +02:00
Irmen de Jong f3b060df51 no longer save Y register when loading value through ZP pointer LDA (zp),Y 2025-09-23 22:59:01 +02:00
Irmen de Jong 09d1cb6925 fix crash when indexing on a label or subroutine name
working on multiplexer
2025-09-23 20:07:52 +02:00
Irmen de Jong 54fa72fa98 added sys.waitrasterline() routine like sys.waitvsync() but wait for a given raster line
optimize uword <= $xx00 into msb(uword)<$xx
2025-09-23 01:09:54 +02:00
Irmen de Jong fd62fe7511 fix crash on invalid type cast added to ptr deref expression ('.' operator) 2025-09-22 23:54:01 +02:00
Irmen de Jong bfb34dff62 fix recursive var decl error in case of some pointer derefs 2025-09-22 23:45:59 +02:00
Irmen de Jong 817b623596 optimize uword >= $xx00 into msb(uword)>=$xx 2025-09-22 22:59:56 +02:00
Irmen de Jong f6dbeb1f63 working on a more complex c64 sprite multiplexer 2025-09-22 21:58:12 +02:00
Irmen de Jong c4b9bdd33f proper pointer array initializer size checking, nicer sprite movement in simplemultiplexer 2025-09-22 20:07:38 +02:00
Irmen de Jong 68e0d5f1b5 add sys.set_rasterline on c64 and c128 targets as well (cx16 already had it) 2025-09-21 01:36:43 +02:00
Irmen de Jong 4939e3df55 add sys.update_rasterirq, more robust sei/cli handling, added simple c64 sprite multiplexer example 2025-09-21 00:28:33 +02:00
Irmen de Jong 19f19f3880 doc and opening borders 2025-09-20 21:44:48 +02:00
Irmen de Jong ad0c767ea8 %breakpoint! introduced to place after an assignment to make it parse correctly 2025-09-20 00:46:15 +02:00
Irmen de Jong ed5f4d5855 fix missing subroutine argument list check 2025-09-19 05:35:03 +02:00
Irmen de Jong c2f5d37486 new cx16/charfade.p8 example 2025-09-19 04:44:08 +02:00
Irmen de Jong 231b50dacb optimize certain word multiplication with bit shifting if it's a power of 2 2025-09-18 20:57:26 +02:00
Irmen de Jong a71895cbe8 optimize pointer.field += 1 into pointer.field INC/DEC 2025-09-18 19:27:36 +02:00
Irmen de Jong a8bede17b2 fix defer() with the arena allocator ("return values are evaluated before the defer is executed") 2025-09-17 23:59:32 +02:00
Irmen de Jong f6c8e693a5 add offsetof() 2025-09-17 23:30:15 +02:00
Irmen de Jong 9461e4088c fixed the hashtable example and workarounds for misbehaving defer in allocators 2025-09-17 22:53:01 +02:00
Irmen de Jong efd73fd10d implement some more in place pointer operators 2025-09-17 18:19:03 +02:00
Irmen de Jong ddf6e84a1a fix type checks for wrong pointer types in pointer array initalizer and assignment 2025-09-16 21:18:29 +02:00
Irmen de Jong 633d6c34e2 add support for struct pointers and short-form initializers in arrays and assignments 2025-09-16 20:37:50 +02:00
Irmen de Jong 6e7fbc6683 fix crash when invalid struct name is used in pointer decl 2025-09-15 20:39:21 +02:00
Irmen de Jong 124ea1230b fix vm to understand struct instances in arrays 2025-09-14 19:04:01 +02:00
Irmen de Jong 8b48a295b6 allow struct initializers to occur in array literals 2025-09-14 18:16:46 +02:00
Irmen de Jong d285d37fdb fix clobbers syntax in symboldumper 2025-09-13 21:47:52 +02:00
Irmen de Jong 8bb927b483 fix compiler crash with on..call statement in nested scope 2025-09-13 06:44:49 +02:00
Irmen de Jong 1af4cd0d63 fix struct initializer error checking 2025-09-12 19:55:12 +02:00
Irmen de Jong db2f28c4cd add struct and pointer benchmark to benchmark program (btree, subscore=654, total 7420)
fix nullpointer in array initalizer
2025-09-12 17:09:44 +02:00
Irmen de Jong 5d9fbd2ccc fix doc build 2025-09-11 22:53:57 +02:00
Irmen de Jong 7efc709538 release 12.0 BETA 1 2025-09-11 22:50:34 +02:00
Irmen de Jong 79419a98d0 add if-expression versions for the conditionals if_cc, if_cs, if_vc etc 2025-09-11 01:57:30 +02:00
Irmen de Jong 1c77d5d5e7 avoid creating inferredtype instances all the time 2025-09-10 20:43:15 +02:00
Irmen de Jong c6854e22a3 fix wrong operand order for in place pointer subtraction 2025-09-10 17:19:03 +02:00
Irmen de Jong 83acc2f285 fix pointer variable overwriting when used in expression 2025-09-10 16:40:45 +02:00
Irmen de Jong 6de95f7a3b new static struct initializer syntax ^^Node : [1,2,3]
avoids confusion with function calls
2025-09-10 13:47:12 +02:00
Irmen de Jong 82839a2d82 fix alias error and byte to word assignment error 2025-09-09 15:05:02 +02:00
Irmen de Jong e5fc9b3132 fix 6502 pointer arithmetic optimization that led to increased code size 2025-09-09 14:06:41 +02:00
Irmen de Jong ced4c5944a fix broken optimization for wordvar - value expressions 2025-09-09 08:05:51 +02:00
Irmen de Jong d4c460072b don't place library and subroutine parameter pointers in ZP automatically, to save ZP space 2025-09-09 06:25:40 +02:00
Irmen de Jong 0b9384b556 fix typecasted byte-to-word pointer assignment, some more asm optimizations 2025-09-09 05:56:55 +02:00
Irmen de Jong 6c3277e3e3 fix more alias bugs 2025-09-09 02:28:05 +02:00
Irmen de Jong d9ff1eb38a fix internal error in on..call 2025-09-08 19:30:32 +02:00
Irmen de Jong e178097735 support more struct instance assignments via memcopy() 2025-09-08 13:18:50 +02:00
Irmen de Jong a1ab8ed208 fixing alias bugs 2025-09-07 22:52:13 +02:00
Irmen de Jong 6ababbf8f4 support more struct instance assignments via memcopy() 2025-09-07 00:29:18 +02:00
Irmen de Jong 79629befc1 fix pointer arithmetic error in struct instance assignments (that get turned into a memcopy call) 2025-09-06 00:28:59 +02:00
Irmen de Jong 8022c0772a oops 2025-09-05 20:37:19 +02:00
Irmen de Jong 8ad2b4638b fix and optimize nodes[i].field (it could clobber the actual temporary pointer value in a scratch register) 2025-09-05 20:02:37 +02:00
Irmen de Jong 3a0392df8a there's a problem with the struct initializer syntax 2025-09-05 00:18:42 +02:00
Irmen de Jong beb28b061d fix non-struct pointer deref code bug
fix gradle 9 compatibility issue (fixes #175)
2025-09-04 21:30:20 +02:00
Irmen de Jong c39acc5031 fix mistakes 2025-09-04 00:30:00 +02:00
Irmen de Jong f54a29c415 small 6502 pointer arithmetic expression rewrite that produces smaller code 2025-09-03 23:54:10 +02:00
Irmen de Jong 783b111059 pointer to uword casts should not be removed
optimized addUnsignedByteOrWordToAY a tiny bit (but needs more)
2025-09-03 23:09:11 +02:00
Irmen de Jong fb286f8b54 optimize operatorDereference() to not always use a zp scratch pointer 2025-09-03 20:30:10 +02:00
Irmen de Jong 5999110e3f avoid using temporary pointer to access struct fields if a simple Y indexed cpu instruction can be used 2025-09-03 18:37:56 +02:00
Irmen de Jong 52a757ea78 todo 2025-09-02 00:42:46 +02:00
Irmen de Jong 28df08eea8 introduce P8ZP_SCRATCH_PTR temporary zp pointer to avoid clobbering W1,W2 2025-09-01 23:56:31 +02:00
Irmen de Jong 79505308ba optimize indexed pointer code a bit more 2025-09-01 22:18:17 +02:00
Irmen de Jong a7e9d8e14b fix struct field offset calculations 2025-09-01 18:57:02 +02:00
Irmen de Jong 08f3abe5bf prefer to put pointer variables into zeropage (add implicit @zp) 2025-08-31 17:06:14 +02:00
Irmen de Jong 3ef09d7d9a use LOADFIELD instruction more instead of an extra explicit ADD
add implicit @zp to pointer variables if they don't have a preference
2025-08-31 15:53:44 +02:00
Irmen de Jong a9142b9ce5 slightly optimize certain pointer indexing calculation, fix invalid deref optimization 2025-08-31 13:40:28 +02:00
Irmen de Jong 13e6f64d3b implement missing struct pointer indexing codegen 2025-08-31 12:20:38 +02:00
Irmen de Jong 4a1256c772 optimizing some pointer codegen for indexed derefs 2025-08-30 21:20:14 +02:00
Irmen de Jong ff9bec90ec fix struct pointer array indexing field dereferencing
the pointers/sorting example now actually works on the 6502 too
2025-08-30 12:24:30 +02:00
Irmen de Jong a6fee1e510 fixing deref() 2025-08-30 07:04:01 +02:00
Irmen de Jong 80538f101e Merge branch 'master' into structs6502 2025-08-29 21:42:03 +02:00
Irmen de Jong aee53b14c7 avoid costly multiplications to get indexed pointer address 2025-08-29 20:18:26 +02:00
Irmen de Jong 5eb2fc8d86 fix a bad pointer arithmetic optimization 2025-08-29 19:36:57 +02:00
Irmen de Jong 98f91bbf88 another attempt to get correct docs chapters 2025-08-29 02:50:09 +02:00
Irmen de Jong b48b36ef18 another attempt to get correct docs chapters 2025-08-29 00:52:29 +02:00
Irmen de Jong 221a093e5f optimize some pointer arithmetic, fix pointer arithmetic for ptr-value 2025-08-28 23:08:06 +02:00
Irmen de Jong 2ca1820d4e Merge branch 'master' into structs6502 2025-08-28 21:55:46 +02:00
Irmen de Jong d58737d5be use RST chapter notation to attempt to fix PDF chapters 2025-08-28 21:46:09 +02:00
Irmen de Jong b52cee3154 use RST chapter notation to attempt to fix PDF chapters 2025-08-28 21:19:16 +02:00
Irmen de Jong 9a76941e10 fix invalid optimization for ptr-value 2025-08-28 20:13:02 +02:00
Irmen de Jong 0285a4cce1 avoid needless pointer arithmetic multiplication by struct size 1 2025-08-27 21:50:09 +02:00
Irmen de Jong 5a3aa1bd25 optimized IR to return a constant value: use RETURNI 2025-08-26 22:54:11 +02:00
Irmen de Jong 0f79351de9 support word size indexing on typed pointers 2025-08-25 22:58:48 +02:00
Irmen de Jong 3cdb25ce8e fix invalid string comparisons being generated for regular pointer value comparison 2025-08-24 16:19:42 +02:00
Irmen de Jong b7193bd0c6 update examples to use typed pointers where appropriate
(found 2 regressions that still need to be handled)
2025-08-24 15:17:47 +02:00
Irmen de Jong 10ff6a0095 Merge branch 'master' into structs6502 2025-08-24 14:34:15 +02:00
Irmen de Jong d30f58004e converting untyped to typed pointers in custom target libraries 2025-08-24 14:27:25 +02:00
Irmen de Jong 17bdb22e4f converting untyped to typed pointers in c64,c128,pet32 libraries 2025-08-24 14:21:51 +02:00
Irmen de Jong a68209be37 converting untyped to typed pointers in cx16 libraries 2025-08-24 14:13:02 +02:00
Irmen de Jong 7b40eade44 added txt.rvs_on() and txt.rvs_off(), added txt.color() support for virtual target 2025-08-24 13:29:29 +02:00
Irmen de Jong 8a717c74b9 sorting lib symboldumps 2025-08-24 13:07:10 +02:00
Irmen de Jong aa72ded21e add sorting module to symboldump, moved shared cbm textio routines to shared module 2025-08-24 13:05:38 +02:00
Irmen de Jong 8e53c83844 optimize certain ptr+value expression on 6502 2025-08-24 05:46:49 +02:00
Irmen de Jong e2a2db1256 converting untyped to typed pointers in libraries 2025-08-22 22:47:19 +02:00
markjreed 2afd2d4dae Make PET PLOT routine more efficient (#174)
* feat: take advantage of KERNAL vars to implement PLOT

* fix: tabs

* fix: update TBLX too, so cursor change sticks after output

* fix: formatting
2025-08-22 21:28:21 +02:00
Irmen de Jong fad17cc094 support a few other simple cases of struct instance assignment 2025-08-21 00:04:30 +02:00
Irmen de Jong f93d957999 proper warning for struct instance assignment (not yet supported) 2025-08-20 20:35:29 +02:00
Irmen de Jong 8db0344cee Merge branch 'master' into structs6502
# Conflicts:
#	codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt
2025-08-19 23:10:15 +02:00
Irmen de Jong 32e531f951 test 2025-08-19 23:09:33 +02:00
Irmen de Jong a4769702f9 todo 2025-08-19 23:06:04 +02:00
Irmen de Jong cf19fb8df1 fix 6502 struct type and field name prefixing and datatype references to stale structs from stale Symboltable 2025-08-19 20:47:37 +02:00
Irmen de Jong 79b8bb5c9f Merge branch 'master' into structs6502 2025-08-19 01:12:50 +02:00
Irmen de Jong fc5889ec0b kotlin 2.2.10, kotest 2025-08-19 01:04:24 +02:00
Irmen de Jong 369303c46d fix register clobbering in pointer deref 2025-08-18 23:36:35 +02:00
Irmen de Jong d65670cc7b fix 6502 address-of for array-indexed pointers (pointer arithmetic) 2025-08-18 20:24:39 +02:00
Irmen de Jong f74eeaee0f fix IR address-of for array-indexed pointers (pointer arithmetic) 2025-08-18 03:24:32 +02:00
Irmen de Jong 826fb3e9c2 implemented assign indexed ptr byte 2025-08-17 13:21:31 +02:00
Irmen de Jong a3d7b8a899 fix detection of string comparisons (make it aware of new pointer types equivalent to STR) 2025-08-16 17:04:59 +02:00
Irmen de Jong 0cc36ed6e4 pointer TODO's all as stub methods in the PointerAssignmentsGen class 2025-08-16 16:21:06 +02:00
Irmen de Jong 976bd52972 docs dark mode 2025-08-16 11:32:24 +02:00
Irmen de Jong 4a8d5def84 code cleanups, pointer TODOs, docs dark mode 2025-08-16 11:25:18 +02:00
Irmen de Jong 2f60716082 6502 struct allocation to asm file, struct name and field prefixing (maybe unneeded...) 2025-08-16 01:49:22 +02:00
Irmen de Jong 729efb04e1 fix code size regressions 2025-08-14 23:59:59 +02:00
Irmen de Jong 4ea8b4d445 fix unknown field test and redundant errors 2025-08-14 21:01:25 +02:00
Irmen de Jong e800c165f9 fix 6502 inplace pointer variable assignment 2025-08-14 20:38:18 +02:00
Irmen de Jong fd9bd23449 6502 statementreorderer: str -> ^^ubyte 2025-08-13 18:45:29 +02:00
Irmen de Jong 8880ed1393 fix address-of struct fields 2025-08-08 23:08:47 +02:00
Irmen de Jong f7fde070ca fix boolean pointer condition in if expression 2025-08-08 22:15:58 +02:00
Irmen de Jong 5ada80779d Merge branch 'refs/heads/master' into structs6502
# Conflicts:
#	examples/test.p8
2025-08-07 21:25:07 +02:00
Irmen de Jong 8972235a0e fix missing peekbool() and pokebool() code gen 2025-08-07 21:22:48 +02:00
Irmen de Jong e56f533e38 more basic pointer inplace operations 2025-08-07 00:36:47 +02:00
Irmen de Jong 324fb7dbf7 more basic pointer inplace operations (float)
basic pointers unit test now passes
2025-08-06 22:26:23 +02:00
Irmen de Jong 44285b9b5d more basic pointer inplace operations 2025-08-06 00:32:15 +02:00
Irmen de Jong a68f477d61 Merge branch 'master' into structs6502
# Conflicts:
#	docs/source/todo.rst
#	examples/test.p8
2025-08-05 23:29:58 +02:00
Irmen de Jong ae9f99448e fix pointer subtract arithmetic 2025-08-05 23:23:29 +02:00
Irmen de Jong 7c0fb10197 fix bug: VM MULR float error, another pointer dependency checker error 2025-08-05 22:00:05 +02:00
Irmen de Jong 9e85571a7b fix pointer variable usage detection in other block 2025-08-05 17:39:22 +02:00
Irmen de Jong 9e10c15e2e working on 6502 pointer inplace assignments 2025-08-04 23:32:17 +02:00
Irmen de Jong 6bd7752bac working on 6502 pointer dereferencing 2025-08-04 20:22:13 +02:00
Irmen de Jong 83ec437e8a testpointers unit test now also for 6502 targets
implementing first simple pointer operations
pointer vars also allocated in ZP for dontcare
2025-08-03 22:12:03 +02:00
Irmen de Jong 4a1d05dd46 first 6502 codegen results 2025-08-03 16:10:00 +02:00
Irmen de Jong aa324e355a remove 6502 pointer check, TODOs for pointer assignments 2025-08-03 13:28:39 +02:00
Irmen de Jong 5cb8bcead7 todo 2025-08-03 00:54:43 +02:00
Irmen de Jong bbd06c0c99 implement peekbool/pokebool on 6502, fix float assignment register error 2025-08-02 21:38:29 +02:00
Irmen de Jong 651830ea82 update syntax files 2025-08-02 21:00:16 +02:00
Irmen de Jong c70146f1dc Merge branch 'master' into structs
# Conflicts:
#	docs/source/todo.rst
2025-08-02 19:26:54 +02:00
Irmen de Jong d4e83b28bb error messages and trying to improve support for struct allocs in arrays
added sorting example
2025-08-02 19:22:58 +02:00
Irmen de Jong bc58a25765 allow sizeof(^^type) to return the size of a pointer 2025-08-02 11:33:15 +02:00
Irmen de Jong 38645022c9 actually disallow ^^str 2025-08-02 05:49:12 +02:00
Irmen de Jong 647cd0fbe1 fix pointer[i].field compiler crash 2025-08-02 05:29:16 +02:00
Irmen de Jong ea8935a346 doc 2025-08-02 00:02:29 +02:00
Irmen de Jong 7ea80babfc todo 2025-08-01 23:38:49 +02:00
Irmen de Jong dee761a99e fix compiler crash on certain pointer assignments 2025-08-01 22:22:43 +02:00
Irmen de Jong 88ee7a8187 fix expected outcome of function call arg type pointer test 2025-08-01 00:23:10 +02:00
Irmen de Jong eb8b408b82 fix countries[2]^^ = 0 compiler crash 2025-07-31 02:03:18 +02:00
Irmen de Jong 3d10882f57 fix ast printing 2025-07-30 23:42:45 +02:00
Irmen de Jong 1988496512 correct commit hash 2025-07-30 01:08:22 +02:00
Irmen de Jong 88b074c208 pointer types should just be uwords in IR 2025-07-30 01:07:37 +02:00
Irmen de Jong c4c5636a81 fixing array indexing on pointers 2025-07-29 23:41:38 +02:00
Irmen de Jong c39d570b72 make more use of ISubType interface itself rather than casting it to StructDecl all the time 2025-07-29 22:59:31 +02:00
Irmen de Jong 4ccd7f9f3a improve docs about recursion 2025-07-29 22:49:24 +02:00
Irmen de Jong 1c9c5aeef7 todos 2025-07-28 23:29:18 +02:00
Irmen de Jong 23ad540aa5 fix IR codegen type error on array pointer dereference 2025-07-28 20:25:56 +02:00
Irmen de Jong 08810c2749 proper error message for unsupported &&subroutine 2025-07-27 23:44:24 +02:00
Irmen de Jong a52966f327 rollback implicit casts to boolean in struct initializers and function call arguments 2025-07-27 03:27:26 +02:00
Irmen de Jong 624220e9a3 fix boolean struct field as if conditional 2025-07-27 01:58:18 +02:00
Irmen de Jong 842b11ed9e struct docs 2025-07-27 00:00:40 +02:00
Irmen de Jong 82267b3f56 Merge branch 'master' into structs
# Conflicts:
#	compiler/test/codegeneration/TestVariables.kt
#	docs/source/_static/symboldumps/skeletons-c128.txt
#	docs/source/_static/symboldumps/skeletons-c64.txt
#	docs/source/_static/symboldumps/skeletons-cx16.txt
#	docs/source/_static/symboldumps/skeletons-pet32.txt
#	docs/source/_static/symboldumps/skeletons-virtual.txt
#	docs/source/todo.rst
#	examples/test.p8
2025-07-26 12:43:13 +02:00
Irmen de Jong 67fb45a55b don't produce invalid boolean initalization error. Fixes #173 2025-07-26 12:35:23 +02:00
Irmen de Jong 11186f1dbe make sure that the virtual target -emu (vm) only runs the actual .p8ir file (fixes #172)
added emudbg.console_nl()
2025-07-26 11:19:01 +02:00
Irmen de Jong 0116fac201 release 11.4.1 2025-07-24 23:00:10 +02:00
Irmen de Jong 817f4f8e7c Merge branch 'master' into structs
# Conflicts:
#	compiler/src/prog8/compiler/Compiler.kt
#	compiler/src/prog8/compiler/astprocessing/AstExtensions.kt
#	compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt
#	docs/source/todo.rst
#	examples/test.p8
2025-07-24 22:27:29 +02:00
Irmen de Jong 866313209b fixed zp vars 0 initialization 2025-07-24 00:17:31 +02:00
Irmen de Jong 28e351daab new library routine skeletons 2025-07-22 18:18:15 +02:00
Irmen de Jong 893e16d814 replace str or ubyte[] param and returnvalue type into ^^ubyte rather than uword 2025-07-21 22:59:14 +02:00
Irmen de Jong 33470c47fc last changes to virtual diskio to make untyped pointers typed 2025-07-21 22:13:36 +02:00
Irmen de Jong 63f7b87572 Merge branch 'master' into structs 2025-07-21 22:11:34 +02:00
Irmen de Jong e2901cca1b fix virtual diskio save_raw() 2025-07-21 22:10:50 +02:00
Irmen de Jong ce8006992a changing virtual diskio to use typed pointers 2025-07-21 22:10:04 +02:00
Irmen de Jong 0b5413ad83 Merge branch 'master' into structs
# Conflicts:
#	docs/source/todo.rst
#	examples/test.p8
2025-07-21 20:53:06 +02:00
Irmen de Jong dd7adde387 fix virtual diskio.f_write 2025-07-21 20:52:11 +02:00
Irmen de Jong 23058b51a1 started changing libs to typed pointers 2025-07-21 20:50:33 +02:00
Irmen de Jong 2f90c53ad0 started changing libs to typed pointers 2025-07-20 23:59:59 +02:00
Irmen de Jong c3be7ab4b3 fix if expression type problems with pointers 2025-07-18 23:44:55 +02:00
Irmen de Jong a9b8fbc6c6 Merge branch 'master' into structs
# Conflicts:
#	examples/test.p8
2025-07-18 22:37:36 +02:00
Irmen de Jong 720988ae72 proper warnings for using pure builtin functions as a statement (discarding the result)
swallow a defer warning for a very common use case
2025-07-18 22:37:07 +02:00
Irmen de Jong b0981a5fae pointers no longer implicitly converted to boolean in expressions, to be consistent with how integers are handled in conditionals
adding particles fountain examples
2025-07-18 01:43:35 +02:00
Irmen de Jong ea5deeefbd new links to Codebase64 website 2025-07-17 23:06:39 +02:00
Irmen de Jong 054c98da7c add link to extra prog8 compilation targets 2025-07-15 00:17:04 +02:00
Irmen de Jong dc434d034a fix address-of identifier alias replacement stripping array index away 2025-07-14 23:54:26 +02:00
Irmen de Jong a4a1b563aa sdks 2025-07-14 21:54:56 +02:00
Irmen de Jong 3f34b83e0d sdks 2025-07-14 21:54:40 +02:00
Irmen de Jong 9c63ef39c7 fix pointer test 2025-07-07 23:09:45 +02:00
Irmen de Jong 9f6106452e revert & to untyped pointer, added && for typed pointer address-of 2025-07-07 16:17:07 +02:00
Irmen de Jong f9fbfe30e3 fix &x +/- offset pointer arithmetic expression 2025-07-07 13:11:57 +02:00
Irmen de Jong 9a9bf170c6 Merge branch 'master' into structs 2025-07-06 23:17:00 +02:00
Irmen de Jong 7dd64b4f13 fix 'cpa' instruction generated in certain boolean assignment, must be 'cmp' 2025-07-06 22:43:19 +02:00
Irmen de Jong b6c0bac96f identified problems with pointer array as parameter 2025-07-06 20:33:20 +02:00
Irmen de Jong 8ede098154 fix pointer array initialization 2025-07-06 19:42:54 +02:00
Irmen de Jong 2a4a3b786e cleanup error message for currently unsupported deref'd pointer assignments 2025-07-06 14:59:42 +02:00
Irmen de Jong b4e0a2019e fixed assignment to a[i]^^ 2025-07-06 13:38:22 +02:00
Irmen de Jong e14c3f8b59 code cleanups 2025-07-06 00:52:37 +02:00
Irmen de Jong c81f76226d Merge branch 'master' into structs 2025-07-06 00:37:58 +02:00
Irmen de Jong edc353cc24 more kotlin 2.2 settings 2025-07-06 00:37:15 +02:00
Irmen de Jong dcce519c69 Merge branch 'master' into structs
# Conflicts:
#	.idea/libraries/KotlinJavaRuntime.xml
#	build.gradle.kts
#	gradle.properties
2025-07-06 00:07:16 +02:00
Irmen de Jong 0a16dcafc0 update kotlin and gradle wrapper 2025-07-06 00:01:24 +02:00
Irmen de Jong 54d41b7f6f fixed a[i]^^ 2025-07-05 23:54:08 +02:00
Irmen de Jong 0541b84d09 Merge branch 'master' into structs 2025-06-29 16:34:32 +02:00
Irmen de Jong 2119817e4a Merge branch 'master' into structs 2025-06-24 21:14:53 +02:00
Irmen de Jong a68cf3c812 fix animalgame node reuse 2025-06-18 23:34:47 +02:00
Irmen de Jong c2bf9024f8 start writing docs about structs and pointers, update syntax files with ^^ 2025-06-18 19:00:18 +02:00
Irmen de Jong bd72eaad4c Merge branch 'refs/heads/master' into structs
# Conflicts:
#	examples/test.p8
2025-06-18 17:53:20 +02:00
Irmen de Jong 1d306e5cdc moved new animalgame 2025-06-17 23:21:54 +02:00
Irmen de Jong b137164fe6 allow str assigned to ^^ubyte without an explicit cast 2025-06-17 18:29:48 +02:00
Irmen de Jong 67d4ad50e1 add new animals example (that uses a pointer tree) 2025-06-17 01:08:36 +02:00
Irmen de Jong c71066af4c fixing name lookup issue 2025-06-16 22:15:51 +02:00
Irmen de Jong 6f0a0981bd fixing name lookup issue 2025-06-16 00:21:54 +02:00
Irmen de Jong 49a4d9ba37 allow str as struct field type (^^ubyte) and strings in struct initializers 2025-06-15 00:29:59 +02:00
Irmen de Jong fcdfa741b9 Merge branch 'master' into structs
# Conflicts:
#	compiler/src/prog8/compiler/astprocessing/AstPreprocessor.kt
2025-06-14 21:07:23 +02:00
Irmen de Jong 3bab177d50 working on pointers/binarytree example 2025-06-13 23:20:15 +02:00
Irmen de Jong 12abafb917 Merge branch 'master' into structs
# Conflicts:
#	docs/source/todo.rst
#	examples/test.p8
#	gradle.properties
2025-06-12 00:11:58 +02:00
Irmen de Jong 0be90dedf2 check for split word array as argument 2025-06-11 21:35:36 +02:00
Irmen de Jong e6bab3ceeb IR: adding LOADFIELD and STOREFIELD instructions 2025-06-09 01:41:45 +02:00
Irmen de Jong 59387b2ae8 Merge branch 'master' into structs
# Conflicts:
#	docs/source/todo.rst
#	examples/test.p8
2025-06-07 20:28:23 +02:00
Irmen de Jong f8f20440d3 allow pointer to be treated as uword value in augmented assignments 2025-06-03 21:33:35 +02:00
Irmen de Jong f8722faa4e Merge branch 'master' into structs
# Conflicts:
#	examples/test.p8
2025-06-03 21:15:19 +02:00
Irmen de Jong 26fbbf48a4 adapt new antlr visitor to the pointer/struct additions 2025-06-03 01:28:58 +02:00
Irmen de Jong d5cc414221 Merge branch 'master' into structs
# Conflicts:
#	codeOptimizers/src/prog8/optimizer/StatementOptimizer.kt
#	compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt
#	compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt
#	examples/test.p8
2025-06-02 20:52:50 +02:00
Irmen de Jong d28f154f1c Merge branch 'master' into structs
# Conflicts:
#	examples/test.p8
#	parser/src/main/antlr/Prog8ANTLR.g4
2025-06-01 17:53:41 +02:00
Irmen de Jong 1c02179c5c refactor loadIndexReg() 2025-05-30 21:39:17 +02:00
Irmen de Jong 77584493fd support a.b.ptr[i]^^.value as expression (RHS) 2025-05-30 20:28:10 +02:00
Irmen de Jong a36709e638 Merge branch 'master' into structs
# Conflicts:
#	compiler/src/prog8/compiler/Compiler.kt
2025-05-30 12:39:23 +02:00
Irmen de Jong 8d63cce749 working on deref after array indexing 2025-05-30 11:30:49 +02:00
Irmen de Jong 8e7bbcdbe0 clear syntax error for yet unsupported deref after array indexing 2025-05-29 21:14:38 +02:00
Irmen de Jong 37ecdc47b3 allow ptr1^^ = ptr^^ (replaces it with memcopy) 2025-05-29 16:34:47 +02:00
Irmen de Jong 112ca3cc53 allow sizeof(&thing), add sys.SIZEOF_POINTER 2025-05-29 15:58:29 +02:00
Irmen de Jong 33b3a1664c replace sizeof(list^^) with sizeof(List) to allow it to compile. Same with simple pointers. 2025-05-29 14:13:42 +02:00
Irmen de Jong 8a0c02e264 Merge branch 'refs/heads/master' into structs
# Conflicts:
#	codeCore/src/prog8/code/target/NormalMemSizer.kt
#	codeCore/src/prog8/code/target/VMTarget.kt
#	compiler/src/prog8/compiler/BuiltinFunctions.kt
#	compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt
#	examples/test.p8
2025-05-29 13:42:16 +02:00
Irmen de Jong e40ace9dea todo 2025-05-29 01:35:53 +02:00
Irmen de Jong 4c0e6e2640 fix split/nosplit pointer arrays, replace ubyteptr^^ with @(ubyteptr), fix double dereference 2025-05-29 00:36:55 +02:00
Irmen de Jong 08b314c37d IR: fix various register type mismatches 2025-05-28 22:15:07 +02:00
Irmen de Jong 86da9d3c7e assigning to plain pointer with array indexing 2025-05-28 18:08:53 +02:00
Irmen de Jong 4e61e25c02 Merge branch 'master' into structs
# Conflicts:
#	compiler/test/TestTypecasts.kt
2025-05-27 23:52:59 +02:00
Irmen de Jong 5097d52d99 IR codegen for pointer indexing expressions, -assignment 2025-05-27 23:41:08 +02:00
Irmen de Jong 09d2185bb1 PtArrayIndexer variable is now nullable (because it could be a ptr deref instead) 2025-05-25 23:04:32 +02:00
Irmen de Jong 5c02e2bd71 fix a ptr indexing case, fix address-of fields 2025-05-25 21:32:31 +02:00
Irmen de Jong fb01389b3d cleaning up pointer deref 2025-05-25 18:33:37 +02:00
Irmen de Jong aaa81210ce cleaning up pointer indexing 2025-05-25 02:56:32 +02:00
Irmen de Jong 51269257ea fix a.b.c.d desugaring into pointer deref chain 2025-05-24 14:48:02 +02:00
Irmen de Jong 23a853db1e Merge branch 'master' into structs
# Conflicts:
#	compiler/src/prog8/compiler/astprocessing/AstChecker.kt
#	examples/test.p8
2025-05-23 19:00:59 +02:00
Irmen de Jong db6c887795 Merge branch 'master' into structs
# Conflicts:
#	compiler/test/ast/TestVariousCompilerAst.kt
2025-05-21 00:27:45 +02:00
Irmen de Jong 61fe55168a Merge branch 'master' into structs
# Conflicts:
#	codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt
#	codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt
#	examples/test.p8
2025-05-20 22:35:04 +02:00
Irmen de Jong 1c4999ec87 adding ptr unit tests 2025-05-20 20:57:05 +02:00
Irmen de Jong c726d3f937 fix ptr errors 2025-05-19 22:49:07 +02:00
Irmen de Jong f70341df1b fix ptr errors 2025-05-19 20:06:31 +02:00
Irmen de Jong f0b791452e fix many ptr deref errors 2025-05-19 01:33:57 +02:00
Irmen de Jong adf5600a9b simplify 2025-05-18 18:37:19 +02:00
Irmen de Jong 6d4ccc5feb fix pointer variable not getting marked as used in some cases 2025-05-18 13:23:56 +02:00
Irmen de Jong 5f3829d5cc partly fix weird errors for ptr indexed expressions 2025-05-17 22:56:41 +02:00
Irmen de Jong 770ebdcd4a party fix weird errors for ptr indexed assignment 2025-05-17 20:54:02 +02:00
Irmen de Jong 96f690e749 fix a ptr indexing error 2025-05-17 20:27:38 +02:00
Irmen de Jong eabdd3a8f3 fix the ptr.uword[index] assignment target error 2025-05-17 14:57:24 +02:00
Irmen de Jong 50650b966b repeat countervars again in zeropage if possible, fix pointer arithmetic error 2025-05-17 14:18:56 +02:00
Irmen de Jong 65e34d4989 stricter types for & operator (address-of), and fix invalid recursive subroutine flagging related to struct definition 2025-05-17 11:32:54 +02:00
Irmen de Jong 05dad5ab5f cleanup 6502 pointer usage checks 2025-05-16 21:44:28 +02:00
Irmen de Jong 1a69a2f1bc fix some ptr vs uword type checks 2025-05-16 20:31:15 +02:00
Irmen de Jong 435faafaad fix split-word storage (lsb/msb) of arrays of pointers 2025-05-16 17:53:15 +02:00
Irmen de Jong 686b32dc29 replace ^^str by ^^ubyte and allow returning ubyte/uword when pointer is expected 2025-05-16 17:53:15 +02:00
Irmen de Jong 0e64a22910 tweak address-of types 2025-05-16 17:53:15 +02:00
Irmen de Jong 4f0839f27e rewrite pointer[0] into @(pointer) if its ^^ubyte 2025-05-16 17:53:15 +02:00
Irmen de Jong bb1953267d Merge branch 'master' into structs
# Conflicts:
#	examples/test.p8
2025-05-15 23:09:30 +02:00
Irmen de Jong cd8aae4681 allow @(..) to take a ^^ubyte address pointer, not only uwords. 2025-05-15 22:12:42 +02:00
Irmen de Jong 11456496bd Merge branch 'master' into structs
# Conflicts:
#	compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt
#	parser/src/main/antlr/Prog8ANTLR.g4
2025-05-15 21:35:31 +02:00
Irmen de Jong 86eef7039f @(..) now also accepts pointer to ubyte address 2025-05-15 20:07:02 +02:00
Irmen de Jong f4b2264fcf fix struct type checks for subroutine call arguments 2025-05-14 23:33:55 +02:00
Irmen de Jong 9b36ae2277 implement inplace boolean short-circuit operators on pointer dereferenced booleans 2025-05-14 21:29:50 +02:00
Irmen de Jong 913ab03963 get rid of invalid ARRAY_STRUCT data type (arrays of struct instance are not yet supported) 2025-05-14 20:43:00 +02:00
Irmen de Jong 38448e471c Merge branch 'master' into structs
# Conflicts:
#	codeGenCpu6502/src/prog8/codegen/cpu6502/ForLoopsAsmGen.kt
#	codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt
#	examples/test.p8
2025-05-13 23:42:13 +02:00
Irmen de Jong d138a7a567 add struct and pointers to IDEA syntax 2025-05-11 23:35:53 +02:00
Irmen de Jong ea27d732ab Merge branch 'refs/heads/master' into structs 2025-05-11 23:35:13 +02:00
Irmen de Jong 924e28e9b3 Merge branch 'master' into structs
# Conflicts:
#	codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt
#	compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt
#	compiler/src/prog8/compiler/astprocessing/SimplifiedAstMaker.kt
#	compilerAst/src/prog8/ast/AstToSourceTextConverter.kt
#	compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt
#	compilerAst/src/prog8/ast/walk/AstWalker.kt
#	compilerAst/src/prog8/ast/walk/IAstVisitor.kt
#	docs/source/todo.rst
#	examples/test.p8
#	parser/src/main/antlr/Prog8ANTLR.g4
2025-05-11 23:23:06 +02:00
Irmen de Jong 60a73248cd todo 2025-05-11 03:16:48 +02:00
Irmen de Jong abbb7d7ba3 fix struct pointers in subroutine parameters and return values 2025-05-11 02:08:59 +02:00
Irmen de Jong 59c378089e fix some struct type and symbol lookup errors 2025-05-11 00:52:35 +02:00
Irmen de Jong 0b789b5f0b added most inplace operators for pointer deref 2025-05-10 20:58:01 +02:00
Irmen de Jong 4382b96a9a tweaking pointer deref in IR 2025-05-10 19:52:06 +02:00
Irmen de Jong 246e4f35a6 Merge branch 'master' into structs
# Conflicts:
#	codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt
#	codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt
#	compiler/test/ast/TestConst.kt
#	docs/source/todo.rst
#	examples/test.p8
2025-05-10 16:36:16 +02:00
Irmen de Jong 506062c6b6 start implementing ptr deref augmented assigns 2025-05-09 23:05:27 +02:00
Irmen de Jong 8353c689ca start making '&' (address-of) return a typed pointer, fixes some errors 2025-05-08 23:40:41 +02:00
Irmen de Jong e98e6f70ac Merge branch 'master' into structs
# Conflicts:
#	compiler/src/prog8/compiler/astprocessing/AstChecker.kt
#	compiler/test/TestTypecasts.kt
#	docs/source/todo.rst
#	examples/test.p8
#	gradle.properties
2025-05-07 23:26:21 +02:00
Irmen de Jong 134352ed7c Merge branch 'master' into structs
# Conflicts:
#	compiler/test/TestTypecasts.kt
#	docs/source/todo.rst
#	examples/test.p8
#	virtualmachine/src/prog8/vm/VmProgramLoader.kt
2025-05-07 22:32:41 +02:00
Irmen de Jong 5de626aab8 support comparison operators on pointers 2025-05-06 22:26:27 +02:00
Irmen de Jong 7aad5d486e Merge pull request #168 from adiee5/master
Update `nano` syntax highlighting
2025-05-06 18:17:58 +02:00
Irmen de Jong 701f155951 Merge branch 'master' into structs
# Conflicts:
#	codeGenCpu6502/src/prog8/codegen/cpu6502/ProgramAndVarsGen.kt
#	compiler/test/TestSymbolTable.kt
#	docs/source/todo.rst
#	examples/test.p8
#	intermediate/src/prog8/intermediate/IRFileReader.kt
#	intermediate/src/prog8/intermediate/IRFileWriter.kt
#	intermediate/src/prog8/intermediate/IRSymbolTable.kt
#	simpleAst/src/prog8/code/SymbolTable.kt
#	simpleAst/src/prog8/code/SymbolTableMaker.kt
#	virtualmachine/src/prog8/vm/VmProgramLoader.kt
2025-05-06 17:59:36 +02:00
Irmen de Jong 8c324d7514 tweak error 2025-05-06 17:50:53 +02:00
adiee5 97390db5f5 Update prog8.nanorc 2025-05-05 21:06:56 +02:00
Irmen de Jong af920d1427 pointer arithmetic for '-', fixed '+' 2025-05-05 21:06:49 +02:00
Irmen de Jong 779ebc0537 pointer arithmetic for '+' 2025-05-05 18:09:19 +02:00
Irmen de Jong 38949b82c3 type check tuning 2025-05-05 15:41:32 +02:00
Irmen de Jong d11386ef26 type check tuning 2025-05-04 23:23:21 +02:00
Irmen de Jong 0e0377d1f0 IR/VM implemented struct allocations and initialization 2025-05-04 14:04:44 +02:00
Irmen de Jong 55e0dbab27 preparing for statically allocating struct instances 2025-05-03 23:44:29 +02:00
Irmen de Jong 4dc82f2c83 preparing for statically allocating struct instances 2025-05-03 19:00:27 +02:00
Irmen de Jong 1ba5587404 allow syntax for declaring variables with struct instance type 2025-05-03 16:14:22 +02:00
Irmen de Jong 835c4b6da3 allow multi-field declarations in structs, get rid of . -> ^^ rewrite 2025-05-03 12:32:29 +02:00
Irmen de Jong dbd955b61e tweak typecheck and better code for constant indexes in pointer derefs 2025-05-02 23:44:26 +02:00
Irmen de Jong d20e2fd88c structs will be a new major version of the compiler 2025-05-02 22:25:26 +02:00
Irmen de Jong e0dea89477 added support for ptr[x].field 2025-05-02 22:16:20 +02:00
Irmen de Jong 6fc2902895 fixing ptr traversal typecheck issues 2025-05-02 00:41:42 +02:00
Irmen de Jong c96e4b40d4 building syntax support for ptr[x].field
attempting to do this by making '.' an expression operator
2025-05-02 00:41:42 +02:00
Irmen de Jong 37da3e2170 parser 2025-05-02 00:41:42 +02:00
Irmen de Jong 2661d3c489 allow array syntax on pointers 2025-05-02 00:41:42 +02:00
Irmen de Jong b89bbb9281 allow pointers in subroutines params and return values 2025-05-02 00:41:42 +02:00
Irmen de Jong 696bf636ed better parsing of directive names results in better error messages when an invalid one is found 2025-05-02 00:41:42 +02:00
Irmen de Jong 40952a788a PtSub: params and returns now as children (in PtSignature node) for easier Ast walking
PtPointerDeref: same but for its start identifier
2025-05-02 00:41:42 +02:00
Irmen de Jong 0162e7a0c1 fix the scoping problems on subtypes 2025-05-02 00:41:42 +02:00
Irmen de Jong 6ce099f176 IR: fix ptr type checks and struct field assignment errors 2025-05-02 00:41:42 +02:00
Irmen de Jong 476a4bac8e IR: LOADI allows r1 and r2 to be the same for pointer chain dereference optimalization 2025-05-02 00:41:42 +02:00
Irmen de Jong 63a410a6df implicit cast to bool for numeric or pointers as condition arguments (to if, while, until) 2025-05-02 00:41:42 +02:00
Irmen de Jong cca27faa3b fix pointer value assignment (not dereferencing the actual pointer) 2025-05-02 00:41:42 +02:00
Irmen de Jong 803e6bd81a fix uword vs pointer type errors and casts 2025-05-02 00:41:42 +02:00
Irmen de Jong 88269628a2 had to turn ^type syntax into ^^type to avoid confusion with the eor operator once again 2025-05-02 00:41:42 +02:00
Irmen de Jong b920d553a0 make address-of dereference work 2025-05-02 00:41:42 +02:00
Irmen de Jong 5e2d0d0dfc fix param order of AssignTarget 2025-05-02 00:41:42 +02:00
Irmen de Jong 2ae3bd68eb more pointer dereferencing for chains 2025-05-02 00:41:42 +02:00
Irmen de Jong 9c183f27eb pointer dereferencing for chains 2025-05-02 00:41:42 +02:00
Irmen de Jong 8046023e82 pointer dereferencing for simple types (read and write) 2025-05-02 00:41:42 +02:00
Irmen de Jong e328520588 initial struct and typed pointer support 2025-05-02 00:41:40 +02:00
493 changed files with 70945 additions and 22255 deletions

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
---
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.
+167
View File
@@ -0,0 +1,167 @@
---
name: asm6502-coder
description: Write 6502/65C02 assembly code using 64tass syntax, for use within Prog8 programs
license: MIT
compatibility: opencode
---
# 65(C)02 Assembly Coder Skill
You are writing **6502/65C02 assembly** using **64tass syntax**, in separate `*.asm` files or embedded in a Prog8 program (inside `%asm {{ }}` blocks or `asmsub` routines). Follow all rules below.
## Debugging Generated Assembly
- The prog8 compiler outputs `*.asm` (assembly source) and (with -asmlist option) also `*.list` (full listing with address/symbols) when compiling. Inspect these to debug generated code, verify optimizations, and trace instruction sequences.
## Assembler: 64tass Syntax
- **NOT ca65/cc65** or other assemblers. Key differences:
- `.proc` / `.pend` for procedures (scoping)
- `_label` for local labels (prefixed with underscore, scoped to `.proc`)
- **Anonymous labels**: defined as `+` (forward) or `-` (backward) at the start of a line. Reference them in branches using `+`, `++`, `+++` etc. (first/second/third *upcoming* anonymous forward label) and `-`, `--`, `---` etc. (first/second/third *preceding* anonymous backward label).
- Crucial: `+` refers to the NEXT upcoming `+` label, `++` refers to the ONE AFTER that, etc.
- `-` refers to the MOST RECENT `-` label, `--` to the one before that, etc.
- When a `+` label is passed, the forward-reference count resets (so `++` then refers to the next one after that new label)
- Example:
```asm
- dex ; backward label '-'
bne - ; branch to most recent '-' (the dex above)
ldx #5
+ dex ; forward label '+'
bne + ; branch to this same '+' (forward)
sta $400
+ lda #0 ; second '+'
bne ++ ; branch to the '++' below
rts
++ inc $d020 ; third forward label
```
- Data directives: `.byte`, `.word`, `.dword`
- Equates: `label = value` (not `label .equ value` or `#define`)
- Zero-page variables defined with `=`
- `.text` for inline string data
## Instructions
- **Instructions like `rol`, `ror`, `asl`, `lsr`, `php`, `pla` require an explicit operand for accumulator**: write `rol a`, `ror a`, not just `rol`/`ror`
- Standard 6502 addressing modes: implied, immediate (`#`), zero-page (`zp`), zero-page,X (`zp,x`), absolute (`abs`), absolute,X (`abs,x`), absolute,Y (`abs,y`), indirect (`(abs)`), indirect,X (`(zp,x)`), indirect,Y (`(zp),y`), relative (branches), accumulator
- Branches: `bne`, `beq`, `bmi`, `bpl`, `bcs`, `bcc`, `bvs`, `bvc` (relative, max +127/-128 bytes)
- Jumps: `jmp` (absolute or indirect), `jsr`/`rts` (subroutine call/return)
- No `push`/`pop` mnemonics — use `pha`/`pla` (byte) and `txa`/`phx`/`plx`/`tay`/`phy`/`ply` for registers
## 6502 vs 65C02
- **CX16 target only**: can use 65C02 instructions — `stz`, `phx`, `plx`, `phy`, `ply`, `bra`, `trb`, `tsb`, `stp`, `wai`, `clr`, `ina`, `dea`, `cmp (zp)`, `dec`/`inc abs,x` etc.
- **C64, C128, PET32 targets**: original 6502 only — no `stz`, no `phx`/`plx`/`phy`/`ply`, no `bra`
- Check the target before using 65C02-specific instructions
- **6502 / 65C02 instruction reference**: https://www.pagetable.com/c64ref/6502/?cpu=65c02&tab=2
## Calling Convention / Register Conventions
- **Accumulator (A)**: 8-bit, used for most arithmetic, data movement, return values
- **X register**: 8-bit, often used for indexing, loop counters
- **Y register**: 8-bit, often used for indirect addressing index
- **Processor Status (P)**: flags — carry (C), zero (Z), negative/N (sign bit 7), overflow (V), decimal (D), interrupt (I), break (B)
- **No caller-saved vs callee-saved convention** — list all modified registers in `clobbers (A, X, Y)` when writing `asmsub`
- The CPU stack (SP, $0100-$01FF) is limited (usually ~128 bytes free). Do not overflow it
## Assembly within Prog8 Programs
### `%asm {{ }}` blocks
- Embed arbitrary 64tass assembly directly in your Prog8 source
- Access Prog8 symbols using their prefixed names (see below)
- Can be placed inside subroutines or at block level
### `asmsub` (assembly subroutine)
- For kernel (ROM) routines or low-level assembly
- Parameters passed via registers: `@A`, `@X`, `@Y`, `@AX` (A low, X high), `@AY` (A low, Y high), `@R0`-`@R15`, `@FAC1`/`@FAC2` (float), `@Pc` (carry), `@Pz` (zero)
- Return value: `-> type @register` — also via `@Pz`/`@Pc` for flags
- Clobbers: `clobbers (A, X, Y)` — MUST list all modified registers
- **Parameter names are documentation only** — use the actual registers in assembly, NOT parameter names
- Create symbolic aliases at assembly top for clarity: `x1 = cx16.r0`, `y1 = cx16.r0L`
Example:
```prog8
asmsub line(uword x1 @R0, ubyte y1 @A, uword x2 @R1, ubyte y2 @Y) clobbers (A, X, Y) {
%asm {{
x1 = cx16.r0
x2 = cx16.r1
lda x1 ; use alias, not "_x1"
}}
}
```
### `asmsub` parameter annotation reference
| Annotation | Register | Size |
|------------|----------|------|
| `@A` | Accumulator | 8-bit |
| `@X` | X register | 8-bit |
| `@Y` | Y register | 8-bit |
| `@AX` | A (low) + X (high) | 16-bit |
| `@AY` | A (low) + Y (high) | 16-bit |
| `@R0`-`@R15` | cx16 virtual registers | 16-bit each |
| `@FAC1`/`@FAC2` | Floating-point accumulators | 5-byte float |
| `@Pc` | Carry flag | bool |
| `@Pz` | Zero flag | bool |
## Accessing Prog8 Symbols from Assembly
All Prog8 symbols are prefixed when accessed from assembly:
| Prefix | Refers to | Example |
|--------|-----------|---------|
| `p8v_` | Variables, parameters | `p8v_myvar` |
| `p8s_` | Subroutines | `p8s_mysub` |
| `p8b_` | Blocks | `p8b_myblock` |
| `p8c_` | Constants, enum members | `p8c_myconst`, `p8c_MyEnum_Member` |
| `p8l_` | Labels | `p8l_mylabel` |
| `p8t_` | Struct types | `p8t_MyStruct` |
| `p8_` | Other symbols | |
- **Fully qualified**: e.g., `p8b_myblock.p8v_myvar`, `p8b_myblock.p8s_mysub.p8v_localvar`
- Within a `.proc` (subroutine) in your assembly, short names often work (assembler scoping)
- **`%option no_symbol_prefixing`**: disables all prefixes. Stdlib modules (`cbm`, `cx16`, `txt`) use this — you can write `cbm.CHROUT` directly
- **Split word arrays**: two separate byte arrays — append `_lsb` and `_msb` to the name: `p8v_myarray_lsb`, `p8v_myarray_msb`
## Zeropage Usage
- Do NOT use arbitrary zeropage locations. Only use these predefined scratch variables:
- `P8ZP_SCRATCH_B1` (byte)
- `P8ZP_SCRATCH_REG` (byte)
- `P8ZP_SCRATCH_W1` (word)
- `P8ZP_SCRATCH_W2` (word)
- `P8ZP_SCRATCH_PTR` (word)
- On CX16: virtual registers `cx16.r0`-`cx16.r15` are in zeropage (and their low/high bytes: `cx16.r0L`, `cx16.r0H`, etc.)
- Virtual registers `cx16.r0`-`cx16.r15` are available on ALL targets, but only on CX16 in zeropage
- Assume the scratch variables are not consecutive in zeropage. The CX16 virtual registers ARE consecutive in memory though.
- For additional temporary storage, allocate regular variables in BSS
## Common 6502 Patterns
### Looping (downto with BNE)
```asm
ldx #count
loop ; do work here
dex
bne loop ; loop while X != 0
```
### Indirect indexed read (table of data)
```asm
ldy #index
lda (ptr),y ; read byte at address stored in zp ptr + Y
```
### 16-bit arithmetic (word add)
```asm
clc
lda word1_lo
adc word2_lo
sta result_lo
lda word1_hi
adc word2_hi
sta result_hi
```
### Calling a Prog8 subroutine from assembly
```asm
jsr p8s_myblock.p8s_mysub
```
### Reading a word variable
```asm
lda p8v_myword ; loads LSB
ldy p8v_myword+1 ; loads MSB (word variables are stored LSB-first)
```
+253
View File
@@ -0,0 +1,253 @@
---
name: prog8-coder
description: Write Prog8 programs (6502-targeted language with 8-bit retro systems as targets)
license: MIT
compatibility: opencode
---
# Prog8 Coder Skill
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.
## General & Setup
- A program = a `main` block containing a `start` subroutine entry point, plus optional other subroutines/blocks
- Add `%zeropage basicsafe` at the top of your program to allow clean return on exit (instead of resetting the machine/emulator)
- Module imports: `%import modulename` — no `as` aliasing. Use the module's defined prefix (e.g., `%import textio` → `txt.xxx`)
- **Virtual target for testing**: Use `prog8c -target virtual -emu input.p8` or `prog8c -vm input.p8ir` for fast testing
- **Fast syntax check**: `prog8c -check input.p8` — does NOT produce output files (.prg, .asm, etc.), just errors
- **Output directory**: `prog8c -out outdir input.p8` (default: same dir as source)
- **Compilation outputs**: `*.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.
- **Debugging switches**: `-noopt` (disable optimizations), `-printast1` (parsed AST), `-printast2` (optimized simple AST), `-compareir` (compare IR outputs)
- **TODO list**: Check `docs/source/todo.rst` to see what features are NOT yet implemented
- **Test programs**: add `%zeropage basicsafe` and `%option no_sysinit` at top
- **`sys` module**: always available, no import needed
- **CX16 debugging**: Add `%encoding iso`, call `txt.iso()` in `start()`, end with `sys.poweroff_system()`. For emulator: `x16emu -echo iso -run -prg input.prg 2>&1 | grep ...`
## Datatypes & Variables
- Primitives: `bool`, `byte`, `ubyte`, `word`, `uword`, `long`, `float`, `str`
- `ubyte`/`uword` = unsigned; `long` = signed 4-byte; `float` = 5-byte MS format; `str` = 0-terminated ubytes (max 255 chars)
- **float requires `%import floats`** at top of file, else compiler errors
- Arrays: max 256 bytes (512 for split word arrays). For larger data, use `memory()` + pointers
- `memory(name, size)` returns a `uword` address to a statically reserved block of memory
- Struct initialization: `^^StructType ptr = ^^StructType:[val1,val2,...]` (the `^^StructType:` can be omitted if inferable)
- Struct fields: only simple types + `str` allowed. NO arrays as fields. `str` in struct = `^^ubyte`
- Word/pointer arrays split into LSB/MSB by default. Override with `@nosplit`
- **No call stack**: all variables statically allocated. No recursion without manual stack management
- Variables zero-initialized (globals at start, locals on subroutine entry)
- `@shared` marks variables as "used by external code" (assembly), prevents optimization
- `@zp`/`@requirezp`: use sparingly — only for pointers (limited zeropage space)
- Pointer-like typed pointers (`^^type`) support C-style scaled arithmetic; `uword` pointers always treat element as 1 byte
- `&` = untyped address (uword); `&&` = typed pointer
- Available zeropage scratch: `P8ZP_SCRATCH_B1`, `P8ZP_SCRATCH_REG`, `P8ZP_SCRATCH_W1`, `P8ZP_SCRATCH_W2`, `P8ZP_SCRATCH_PTR` — and cx16 virtual registers R0-R15 on all targets
- Virtual registers (`cx16.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 performance: integer trig (`math.sin8`, `math.cos8`) uses fast LUTs; float trig (`floats.sin`/`cos`) is much slower
## Recursion & Stack Management
No call stack for variable storage — recursion overwrites locals. To handle it:
1. **CPU hardware stack**: `push()`/`pushw()`/`pushl()`/`pushf()` and `pop()`/`popw()`/`popl()`/`popf()`. Save/restore locals around recursive calls
2. **Software stacks**: `buffers.stack` (uword) / `buffers.smallstack` (ubyte). Both provide `push_b()`/`push_w()` and `pop_b()`/`pop_w()`
3. **Iterative rewrite (preferred)**: Many recursive algorithms work as `repeat` loops with explicit bounds — avoids all stack overhead
## Strings, Arrays & Pointers
- `str` / array: max 256 bytes. `long[]` limited to 64 entries (64x4=256). `str[]` for string arrays: `str[5] names = ["a","b","c","d","e"]`
- 2D arrays: `type[rows][cols] name`, access `name[r][c]`. Flat init list only (no nested `[[...]]`). Total size still ≤ 256 bytes
- str/array passed as pointer to subroutine (receiving subroutine gets `^^ubyte` or `^^element`)
- **No const pointers** or pointer-to-pointer currently supported
- **Parsing limitation**: `pointer[index].field` as assignment target needs `^^`: `pointer[index]^^.field = value`
- **Pre-allocate buffers**: `str buffer = "." * 50` (empty `""` allocates nothing — `strings.append()` will fail)
- **No reassignment**: can't `buffer = "new text"` after declaration. Use `strings.copy()`/`strings.ncopy()`
- String concat is expensive on 6502. Prefer separate prints over concatenation
- **Efficient buffer iteration**: prefer `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`/`pokebool`
- Use `len(array)` instead of hardcoded sizes
- Array indexing is 0-based: `arr[0]` is first element
- Static memory only — real dynamic allocation impossible, but can emulate with a simple arena allocator over a `memory()` slab
## Logic & Control Flow
- Logical operators (short-circuit, bool only): `and`, `or`, `xor`, `not`
- In `a and b`: if `a` is false, `b` is NOT evaluated
- In `a or b`: if `a` is true, `b` is NOT evaluated
- Important when `b` has side effects
- Bitwise operators: `&`, `|`, `^`, `~`, `<<`, `>>`
- Bit rotation: `rol()`/`ror()` (through carry), `rol2()`/`ror2()` (no carry)
- CPU status flag branches: `if_cs`, `if_cc`, `if_z`, `if_nz` (compile to single 6502 branch instructions)
- Use `when` with choice blocks instead of multiple `if`
- If-expressions for simple value assignments based on a choice
- **Optional braces in if/else**: when the `if` 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 exit
- `goto`, labels, jump lists allowed
- **Common mistake**: `and`/`or` for bitmasking — use `&`/`|` instead!
## Loop Constructs
Prog8 supports these loop types. All support `break` and `continue` (except `unroll`).
### `for` loop — iterate over a range or array
- Loop variable **must be declared separately** before the `for` statement
- Works with `ubyte`, `byte`, `uword`, `word`, `long`, pointer types (NOT `float`)
- Iterates over ranges (`start to end`), descending ranges (`start downto end`), or arrays/strings
- Optional `step <constant>` for non-unit step sizes
- Loop variable value **after the loop is undefined** — don't rely on it
- Descending loops with `downto` usually produce more efficient 6502 code
```
ubyte 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 true
```
while condition {
; body
break
continue
}
```
### `do`-`until` loop — always executes body at least once
```
do {
; body
break
continue
} until condition
```
### `repeat` loop — repeat a fixed number of times (most efficient)
- Most efficient code generation — prefer over `for` when loop variable not needed
- Omit count for infinite loop (still supports `break`)
```
repeat 15 {
; body
break
continue
}
; infinite:
repeat {
; body
break if x==5
}
```
### `unroll` loop — compile-time code duplication
- Not a real loop — duplicates body N times at compile time
- No `break`/`continue` allowed
- Only simple statements (assignments, calls) in body
- Constant iteration count required
```
unroll 80 {
cx16.VERA_DATA0 = 255
}
```
## Subroutines & Return Values
- Symbols public by default; use `private` to restrict to block
- `inline` keyword for subroutines to suggest inlining
- No function overloading (except builtins). Cannot use builtin names (msw, lsw, msb, lsb, mkword, mklong, peek, peekw, peekl, etc.) as variable/sub names
- Can return 0, 1, or multiple values: `a, b, c = routine()`. Use `void` to skip: `void routine()`, `a, void, c = routine()`
- Nested subroutines access parent scope variables directly
## Assembly Subroutines (asmsub)
For low-level assembly that gets arguments via registers and returns values in registers.
- **Syntax**: `[private] [inline] asmsub subname(params) [clobbers(regs)] [-> returns] { %asm {{ ... }} }`
- **Body Restriction**: The body of an `asmsub` **must only contain** a single `%asm {{ ... }}` node. Regular Prog8 statements or nested blocks are NOT allowed.
- **Parameters**: `type name @register` (e.g., `ubyte val @A`, `uword addr @AX`, `float f @FAC1`).
- **Return Values**: `-> type @register` (e.g., `-> ubyte @A`, `-> bool @Pz` for immediate branch use).
- **Registers**:
- **8-bit**: `@A`, `@X`, `@Y`
- **16-bit**: `@AX`, `@AY`, `@XY` (register pairs)
- **Float**: `@FAC1`, `@FAC2` (Floating Point Accumulators)
- **Virtual**: `@R0`–`@R15` (16-bit), `@R0R1`–`@R14R15` (32-bit combined)
- **Status Flags (for returns)**: `@Pc` (Carry), `@Pz` (Zero), `@Pv` (Overflow), `@Pn` (Negative)
- **Clobbers**: `clobbers (A, X, Y)` — list all hardware registers modified by the routine.
- **Parameter names** are for documentation and type checking only. Use the registers in your assembly code.
- **Inlining**: `inline asmsub` will paste the assembly code directly at the call site, avoiding `jsr`/`rts` overhead.
- **Symbol prefixes** in assembly: `p8v_` (vars), `p8s_` (subs), `p8b_` (blocks), etc.
## External Subroutines (extsub)
Used to call routines at fixed memory addresses (like ROM KERNAL routines or third-party drivers).
- **Syntax**: `[private] extsub [@bank <value>] address = subname(params) [clobbers(regs)] [-> returns]`
- **Address**: Can be a hex literal (`$C000`) or a constant expression.
- **Bank (optional)**: `@bank <integer>` (constant bank) or `@bank <identifier>` (variable bank).
- **No Body**: Unlike `asmsub`, `extsub` has no `{ }` body; it just maps a signature to an address.
- **Example**:
```prog8
; 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
```
## Assembly Programming Details
- **Symbol prefixes**: `p8v_` (variables), `p8s_` (subroutines), `p8b_` (blocks), `p8c_` (constants), `p8l_` (labels), `p8t_` (structs), `p8_` (other)
- **Fully qualified names**: `p8b_blockname.p8v_varname`, `p8b_blockname.p8s_subname.p8v_localvar`
- **Within a `.proc`**, short names often work. `%option no_symbol_prefixing` disables prefixes (used by `cbm`, `cx16`, `txt`)
- **Split word arrays**: append `_lsb` / `_msb` to variable name (e.g., `p8v_myarray_lsb`)
- **CX16 target**: use 65C02 instructions (STZ, PHX, etc). Others: 6502 only
- **Assembly syntax**: 64tass. `.proc`/`.pend`, `_label` for locals, `.byte`/`.word`/`.dword` for data, `=` for equates
- **Instructions** like `rol`, `ror`, `asl`, `lsr` require explicit operand: `rol a`
- **Anonymous labels**: `+` (forward), `-` (backward), branch with `+`, `++`, `+++` or `-`, `--`, `---`
- **Register Aliases**: It's common to define aliases at the start of an `asmsub`:
```prog8
asmsub my_routine(uword ptr @AX) {
%asm {{
ptr_lo = p8zp_scratch_w1
ptr_hi = p8zp_scratch_w1+1
sta ptr_lo
stx ptr_hi
}}
}
```
## Standard Library
- Find routines, functions, variables, modules and signatures in the symbol dump file for the given compilation target.
- Online location: https://prog8.readthedocs.io/en/latest/libraries.html#low-fi-variable-and-subroutine-definitions-in-all-available-library-modules they are linked there 1 for each compilation target
- Structure: builtin functions, then module sections with variables/constants (`type name`) and subroutines (`name (params) -> returntype`)
- Text output: `textio` module (`txt.print`, `txt.chrout`, `txt.print_b`/`_ub`/`_w`/`_uw`/`_l`/`_bool`, `txt.print_f` for floats, `txt.spc()`, `txt.nl()`)
- Math: `math` module — integer trig (`sin8`, `cos8`) via fast LUTs; `math.rnd()` for random numbers
- String conversion: `conv` module (`str_uword`, `str2word`, etc.) — for printing numbers use txt routines instead
- Char operations: `strings` module (`isdigit`, `isxdigit`, `isupper`, `islower`, `isletter`, `isspace`, `isprint`) — use these instead of manual ASCII/PETSCII arithmetic
- String functions return useful lengths — capture them: `len = strings.copy(dest, src)`, `len = strings.append(buf, text)`, `len = strings.upper(mystr)`
## Syntax & Formatting
- Hex: `$FF` (not `0xFF`); Binary: `%1010` (not `0b1010`). Underscores for readability: `25_000_000`
- 4-digit hex `$0000` = uword. No type suffixes (no `0L`). Cast: `expr as type`
- Augmented assignment: `+=`, `-=`, `*=`, `/=`, `%=`, `&=`, `|=`, `^=`, `<<=`, `>>=`
- **`;` starts a comment to end of line** — NOT a statement separator. One statement per line
- **No `elif`**: use nested `else { if ... }`
- Type casting: `expression as type` (e.g., `bytevar as word`). `as` has very low precedence (lower than arithmetic)
- **No automatic type widening**: `byte*byte=byte` (overflow possible!). Cast explicitly
- **No block scope**: `for`/`if` don't introduce scope. Only subroutines do. Variables in blocks are hoisted to subroutine level
- **No bare `{ }` blocks** like C/Java
- Indentation: 4 spaces for .p8 and .asm files (no tabs)
- Character encoding: 6502 targets use PETSCII by default (call `txt.lowercase()` at start for lowercase). Virtual target uses ISO (`%encoding iso` + `txt.iso()`)
- Array size inferred from initializer: `str[] types = ["a", "b", "c"]`
- Enums: `Enum::Value` syntax (double colon), declared inside a block. Use enums for related values, `const` for standalone
- Avoid `globals.XXXX` — move constants closer to where they're used
- Member access through pointers: use `.` for both direct and pointer access. The compiler infers the type. For complex assignment targets, `^^` may be needed: `ptr^^.field = value`
- Qualified names: must use full path from top level (e.g., `cx16.r0`, not relative)
- **No block scope** — variables in `if`/`for`/`repeat` blocks are hoisted to subroutine level
- **No reassigning strings** after declaration — use `strings.copy()`/`strings.ncopy()`
- **Pre-allocate string buffers** — `""` allocates nothing
+17
View File
@@ -0,0 +1,17 @@
# 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/
.kotlin/
+45
View File
@@ -0,0 +1,45 @@
# 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
+5 -5
View File
@@ -10,7 +10,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: build and install recent 64tass
run: |
@@ -20,10 +20,10 @@ jobs:
make -j4
sudo make install
- name: Set up JDK 11
uses: actions/setup-java@v4
- name: Set up JDK 17
uses: actions/setup-java@v5
with:
java-version: 11
java-version: 17
distribution: temurin
- name: Build and test with Gradle
@@ -32,7 +32,7 @@ jobs:
sha256sum -b compiler/build/libs/*-all.jar > compiler/build/libs/hash.txt
- name: Create compiler shadowJar artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
name: prog8-compiler-jar-zipped
path: |
+6
View File
@@ -3,14 +3,19 @@
.idea/developer-tools.xml
.idea/usage.statistics.xml
.idea/shelf/
.junie/
.junie/memory/
build/
dist/
output/
out/
out-new/
out-old/
.*cache/
*.directory
*.prg
*.bin
*.p8ir
*.labels.txt
*.vm.txt
*.vice-mon-list
@@ -19,6 +24,7 @@ parser/**/*.interp
parser/**/*.tokens
parser/**/*.java
compiler/src/prog8/buildversion/*
languageServer/src/prog8/buildversion/*
*.py[cod]
*.egg
*.egg-info
+10
View File
@@ -0,0 +1,10 @@
<?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>
File diff suppressed because it is too large. Load diff
+15
View File
@@ -0,0 +1,15 @@
<?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>
+5 -5
View File
@@ -7,13 +7,13 @@
<option name="jvmTarget" value="11" />
</component>
<component name="KotlinCommonCompilerArguments">
<option name="apiVersion" value="2.1" />
<option name="languageVersion" value="2.1" />
<option name="apiVersion" value="2.3" />
<option name="languageVersion" value="2.3" />
</component>
<component name="KotlinCompilerSettings">
<option name="additionalArguments" value="-Xwhen-guards" />
<option name="additionalArguments" value="-jvm-default=no-compatibility" />
</component>
<component name="KotlinJpsPluginSettings">
<option name="version" value="2.1.10" />
<option name="version" value="2.3.21" />
</component>
</project>
</project>
+10 -10
View File
@@ -1,23 +1,23 @@
<component name="libraryTable">
<library name="KotlinJavaRuntime" type="repository">
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.1.20" />
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.3.21" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.1.20/kotlin-stdlib-jdk8-2.1.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.1.20/kotlin-stdlib-2.1.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.21/kotlin-stdlib-jdk8-2.3.21.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.21/kotlin-stdlib-2.3.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/2.1.20/kotlin-stdlib-jdk7-2.1.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.21/kotlin-stdlib-jdk7-2.3.21.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.1.20/kotlin-stdlib-jdk8-2.1.20-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.1.20/kotlin-stdlib-2.1.20-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.21/kotlin-stdlib-jdk8-2.3.21-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.21/kotlin-stdlib-2.3.21-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.1.20/kotlin-stdlib-jdk7-2.1.20-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.21/kotlin-stdlib-jdk7-2.3.21-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.1.20/kotlin-stdlib-jdk8-2.1.20-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.1.20/kotlin-stdlib-2.1.20-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.21/kotlin-stdlib-jdk8-2.3.21-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.21/kotlin-stdlib-2.3.21-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.1.20/kotlin-stdlib-jdk7-2.1.20-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.21/kotlin-stdlib-jdk7-2.3.21-sources.jar!/" />
</SOURCES>
</library>
</component>
+5 -5
View File
@@ -1,11 +1,11 @@
<component name="libraryTable">
<library name="eclipse.lsp4j" type="repository">
<properties maven-id="org.eclipse.lsp4j:org.eclipse.lsp4j:0.24.0" />
<properties maven-id="org.eclipse.lsp4j:org.eclipse.lsp4j:1.0.0" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j/0.24.0/org.eclipse.lsp4j-0.24.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j.jsonrpc/0.24.0/org.eclipse.lsp4j.jsonrpc-0.24.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/code/gson/gson/2.13.1/gson-2.13.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.38.0/error_prone_annotations-2.38.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j/1.0.0/org.eclipse.lsp4j-1.0.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j.jsonrpc/1.0.0/org.eclipse.lsp4j.jsonrpc-1.0.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/code/gson/gson/2.14.0/gson-2.14.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.48.0/error_prone_annotations-2.48.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
+14
View File
@@ -0,0 +1,14 @@
<component name="libraryTable">
<library name="github.irmen.ksim65" type="repository">
<properties maven-id="com.github.irmen:ksim65:v2.1" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/github/irmen/ksim65/v2.1/ksim65-v2.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.20/kotlin-stdlib-jdk8-2.3.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.20/kotlin-stdlib-2.3.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/2.3.20/kotlin-stdlib-jdk7-2.3.20.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
-20
View File
@@ -1,20 +0,0 @@
<component name="libraryTable">
<library name="io.kotest.assertions.core.jvm" type="repository">
<properties maven-id="io.kotest:kotest-assertions-core-jvm:5.9.1" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.9.1/kotest-assertions-core-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.9.1/kotest-assertions-shared-jvm-5.9.1.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.3.0/opentest4j-1.3.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.23/kotlin-stdlib-1.9.23.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.8.0/kotlinx-coroutines-jdk8-1.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.9.23/kotlin-reflect-1.9.23.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.9.1/kotest-common-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.9.1/kotest-assertions-api-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.8.0/kotlinx-coroutines-core-jvm-1.8.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
+7 -7
View File
@@ -1,19 +1,19 @@
<component name="libraryTable">
<library name="michael.bull.kotlin.result.jvm" type="repository">
<properties maven-id="com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.1" />
<properties maven-id="com.michael-bull.kotlin-result:kotlin-result-jvm:2.3.1" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.0.1/kotlin-result-jvm-2.0.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.22/kotlin-stdlib-1.9.22.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.3.1/kotlin-result-jvm-2.3.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.10/kotlin-stdlib-2.3.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.0.1/kotlin-result-jvm-2.0.1-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.22/kotlin-stdlib-1.9.22-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.3.1/kotlin-result-jvm-2.3.1-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.10/kotlin-stdlib-2.3.10-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.0.1/kotlin-result-jvm-2.0.1-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.22/kotlin-stdlib-1.9.22-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.3.1/kotlin-result-jvm-2.3.1-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.10/kotlin-stdlib-2.3.10-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
</SOURCES>
</library>
+3
View File
@@ -26,4 +26,7 @@
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="openjdk-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
<component name="PythonCompatibilityInspectionAdvertiser">
<option name="version" value="3" />
</component>
</project>
+1 -1
View File
@@ -2,7 +2,6 @@
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/beanshell/beanshell.iml" filepath="$PROJECT_DIR$/beanshell/beanshell.iml" />
<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" />
@@ -15,6 +14,7 @@
<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$/intermediate/intermediate.iml" filepath="$PROJECT_DIR$/intermediate/intermediate.iml" />
<module fileurl="file://$PROJECT_DIR$/languageServer/languageServer.iml" filepath="$PROJECT_DIR$/languageServer/languageServer.iml" />
<module fileurl="file://$PROJECT_DIR$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/prog8.iml" filepath="$PROJECT_DIR$/.idea/modules/prog8.iml" />
<module fileurl="file://$PROJECT_DIR$/simpleAst/simpleAst.iml" filepath="$PROJECT_DIR$/simpleAst/simpleAst.iml" />
+5
View File
@@ -0,0 +1,5 @@
---
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
@@ -0,0 +1,5 @@
# 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`.
+31
View File
@@ -0,0 +1,31 @@
{
"context": {
"fileName": "AGENTS.md"
},
"$version": 3,
"permissions": {
"allow": [
"Bash(prog8c *)",
"Bash(gradle *)",
"Bash(mkdir *)",
"Bash(ls *)",
"Bash(cat *)",
"Bash(find *)",
"Bash(sed *)",
"Bash(grep *)",
"Bash(xargs *)",
"Bash(wc *)",
"Bash(diff *)",
"Bash(paste *)",
"Bash(echo *)",
"Bash(cp *)",
"Bash(time)",
"Bash(kotlinc *)",
"Bash(python3 *)",
"Bash(jar *)",
"Bash(od *)",
"Bash(xxd *)",
"Bash(gcc *)"
]
}
}
+336
View File
@@ -0,0 +1,336 @@
# 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
- Never implement features yourself when possible - use sub-agents!
- 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), always run commands like lint, type check and next build to check code quality
## 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 meant to be used in a new machine specific code generator backend (primarily 6502 but maybe 68000 as well later)
- 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.3 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
## 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 assembly code, load the `asm6502-coder` skill for 64tass syntax and conventions.
## 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
**Minimal comments when making changes**: When modifying existing code, add only essential comments that explain *why* a change was made or document non-obvious behavior. **Do not add verbose comments** that restate what the code does; let the code speak for itself. Existing extensive comments should be preserved, but new changes should have minimal commentary.
## 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
## Junie Agent Commitments
These instructions apply specifically to the Junie agent:
* **Code Discovery and Navigation**: Use the `get_file_structure` tool to understand the API and structure of files before reading or editing them.
* **Symbol-Aware Searching**: Use the `search_project` tool as the primary method for finding symbol definitions (classes, methods, variables) across the project.
* **Project-Wide Refactoring**: Exclusively use the `rename_element` tool for any symbol renames to ensure all references, imports, and documentation are updated correctly.
* **Structured Test Execution**: Use the `run_test` tool for rapid, isolated verification of Kotlin logic changes.
* **Contextual Exploration**: Use the `open` tool with specific `line_number` parameters to navigate directly to relevant code sections.
* **Gradle Console Output**: Always use the `--console=plain` flag with `gradle` commands. Fancy console output (like progress bars) will mess up the coding agent's text interface.
**Note on Terminal usage**: Junie uses `gradle` and terminal commands primarily for project lifecycle tasks (e.g., `gradle installdist` to re-embed standard library resources), manual compiler verification with `prog8c`, and full regression testing before submission. When using `gradle`, always append `--console=plain`.
# 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 committing changes**: Always run `gradle build` to ensure all tests pass
- **After major refactoring**: Run `gradle build` to catch regressions
- **When debugging test failures**: Use `gradle test --tests "*TestName*"` for specific tests
### 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 uses single-dash command line options** (e.g., `-target`, `-noopt`, `-check`), NOT double-dash (`--target` is invalid).
- **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 same directory as the source file**.
### Compilation Output Files
- `*.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
## 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.
### Automated Tests (gradle)
Always use the `--console=plain` flag with these commands to avoid messing up the agent's text interface.
- `gradle build --console=plain` - Full build including all tests (about 50s)
- `gradle :compiler:test --console=plain` - Run only compiler tests (faster)
- `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.
**⚠️ 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`
- When a test fails, the output shows "There were failing tests. See the report at:" - **read that HTML report**
### 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.
**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.
+11 -3
View File
@@ -1,12 +1,20 @@
# super simple Makefile to lauch the main gradle targets to build and/or test the prog8 compiler
# 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
$(GRADLE) installdist installshadowdist
@echo "compiler launch script can be found here: compiler/build/install/compiler-shadow/bin/prog8c"
test:
gradle build
$(GRADLE) build
@echo "compiler launch script can be found here: compiler/build/install/compiler-shadow/bin/prog8c"
+26 -5
View File
@@ -34,8 +34,11 @@ How to get it/build it
- 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:
- 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
---------
@@ -58,9 +61,11 @@ 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
- code often is smaller and faster than equivalent C code compiled with CC65 or even LLVM-MOS
- 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.
- various data types other than just bytes (16-bit words, floats, strings)
- 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
@@ -81,7 +86,7 @@ What does Prog8 provide?
- 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; because Basic ROM is banked out altogether
- 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
*Rapid edit-compile-run-debug cycle:*
@@ -171,7 +176,7 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
}
}
when compiled an ran on a C-64 you'll get:
when compiled and ran on a C-64 you'll get:
![c64 screen](docs/source/_static/primes_example.png)
@@ -192,3 +197,19 @@ 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)
-65
View File
@@ -1,65 +0,0 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
id("application")
kotlin("jvm")
}
val serverMainClassName = "prog8lsp.MainKt"
val applicationName = "prog8-beanshell"
application {
mainClass.set(serverMainClassName)
description = "Code completions, diagnostics and more for Prog8"
// applicationDefaultJvmArgs = listOf("-DkotlinLanguageServer.version=$version")
applicationDistribution.into("bin") {
filePermissions {
user {
read=true
execute=true
write=true
}
other.execute = true
group.execute = true
}
}
}
repositories {
mavenCentral()
}
dependencies {
implementation(files("lib/bsh-3.0.0-SNAPSHOT.jar"))
}
configurations.forEach { config ->
config.resolutionStrategy {
preferProjectModules()
}
}
sourceSets.main {
java.srcDir("src")
resources.srcDir("resources")
}
tasks.startScripts {
applicationName = "prog8-beanshell"
}
tasks.register<Exec>("fixFilePermissions") {
// When running on macOS or Linux the start script
// needs executable permissions to run.
onlyIf { !System.getProperty("os.name").lowercase().contains("windows") }
commandLine("chmod", "+x", "${tasks.installDist.get().destinationDir}/bin/prog8-beanshell")
}
tasks.installDist {
finalizedBy("fixFilePermissions")
}
tasks.build {
finalizedBy("installDist")
}
Binary file not shown.
@@ -1,48 +0,0 @@
package prog8beanshell
import java.io.FilterReader
import java.io.Reader
class CommandLineReader(val input: Reader): FilterReader(input) {
private val normal = 0
private val lastCharNL = 1
private val sentSemi = 2
private var state = lastCharNL
override fun read(): Int {
if (state == sentSemi) {
this.state = lastCharNL
return 10
} else {
var b = input.read()
while(b==13) b = input.read()
if (b == 10) {
if (this.state == lastCharNL) {
b = 59
this.state = sentSemi
} else {
this.state = lastCharNL
}
} else {
this.state = normal
}
return b
}
}
override fun read(buff: CharArray, off: Int, len: Int): Int {
val b = read()
if (b == -1) {
return -1
} else {
buff[off] = b.toChar()
return 1
}
}
}
-23
View File
@@ -1,23 +0,0 @@
package prog8beanshell
import bsh.FileReader
import bsh.Interpreter
class BeanshellInterpreter {
fun run(symbols: Map<String, Any>) {
val interpreter = Interpreter(CommandLineReader(FileReader(System.`in`)), System.out, System.err, true)
interpreter.setExitOnEOF(false)
symbols.forEach { (name, value) -> interpreter.set(name, value) }
interpreter.run()
}
}
fun main(args: Array<String>) {
val i = BeanshellInterpreter()
i.run(mapOf(
"env" to System.getenv(),
"args" to args
))
}
+39
View File
@@ -0,0 +1,39 @@
# 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
@@ -0,0 +1,98 @@
%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
@@ -0,0 +1,71 @@
%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
@@ -0,0 +1,72 @@
%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
@@ -0,0 +1,72 @@
%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
@@ -0,0 +1,93 @@
%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.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

+85
View File
@@ -0,0 +1,85 @@
%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
@@ -0,0 +1,107 @@
%import textio
%import ciatimer
main {
sub start() {
txt.lowercase()
cia.calibrate()
test.benchmark_name()
test.benchmark()
void test.benchmark_check()
cia.print_time()
repeat {}
}
}
test {
const ubyte N_ITER = 4
const uword SIZE = 16000
const uword EXPECTED = 3432
uword prime_count
^^ubyte @zp flags = memory("flags", SIZE/8+1, 0)
sub benchmark_name()
{
txt.print("sieve_bit.p8\n")
txt.print("Calculates the primes from 1 to ")
txt.print_uw(SIZE * 2 + 2)
txt.print(" (")
txt.print_ub(N_ITER)
txt.print(" iterations)\n")
}
sub benchmark()
{
repeat N_ITER
prime_count = sieve(SIZE)
}
sub benchmark_check() -> bool
{
txt.print("count=")
txt.print_uw(prime_count)
if prime_count == EXPECTED
{
txt.print(" [OK]\n")
return false
}
txt.print(" [FAIL] - expected ")
txt.print_uw(EXPECTED)
txt.nl()
return true
}
ubyte[] bitv = [
$01,
$02,
$04,
$08,
$10,
$20,
$40,
$80
]
sub check_flag(uword idx) -> bool
{
return flags[idx / 8] & bitv[lsb(idx) & 7] != 0
}
sub clear_flag(uword idx)
{
flags[idx / 8] &= ~(bitv[lsb(idx) & 7])
}
sub sieve(uword size) -> uword
{
uword i, prime, k
uword count=1
for i in 0 to (size / 8)
flags[i] = $ff
for i in 0 to size-1
{
if check_flag(i)
{
prime = i + i + 3
k = i + prime;
while k < size
{
clear_flag(k)
k += prime
}
count++
}
}
return count
}
}
+1 -1
View File
@@ -7,7 +7,7 @@ adpcm {
while cbm.RDTIM16()<max_time {
adpcm.init(0,0)
uword @requirezp nibbles_ptr = $a000 ; for benchmark purposes, the exact nibbles don't really matter, so we just take the basic ROM as input
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)
+234
View File
@@ -0,0 +1,234 @@
; 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)
uword buffer = memory("arena", 2000, 0)
uword next = buffer
sub alloc(ubyte size) -> uword {
defer next += size
return next
}
sub freeall() {
next = buffer
}
}
+1 -2
View File
@@ -61,8 +61,7 @@ life {
math.rndseed(12345,9999)
uword offset = STRIDE+1
ubyte x
ubyte y
for y in 0 to HEIGHT-1 {
repeat HEIGHT {
for x in 0 to WIDTH-1 {
active_world[offset+x] = math.rnd() & 1
}
+4 -4
View File
@@ -35,12 +35,12 @@ queens {
if could_place(row, col) {
board[row] = col
; we need to save the local variables row and col.
sys.push(row)
sys.push(col)
push(row)
push(col)
continue_running = place_queen(row + 1)
; restore the local variables after the recursive call.
col = sys.pop()
row = sys.pop()
col = pop()
row = pop()
board[row] = 0
if not continue_running
+5 -5
View File
@@ -472,7 +472,7 @@ elite_galaxy {
generate_next_planet()
textelite.num_commands++
}
elite_planet.name = make_current_planet_name()
void strings.copy(make_current_planet_name(), elite_planet.name)
init_market_for_planet()
}
@@ -494,7 +494,7 @@ elite_galaxy {
ubyte pi
for pi in 0 to 255 {
generate_next_planet()
elite_planet.name = make_current_planet_name()
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 {
@@ -532,7 +532,7 @@ elite_galaxy {
; else
; txt.chrout('-')
; txt.spc()
elite_planet.name = make_current_planet_name()
void strings.copy(make_current_planet_name(), elite_planet.name)
elite_planet.display(true, distance)
}
pn++
@@ -548,7 +548,7 @@ elite_galaxy {
str current_name = " " ; 8 max
ubyte pn = 0
current_name = elite_planet.name
void strings.copy(elite_planet.name, current_name)
init(number)
; txt.clear_screen()
; txt.print("Galaxy #")
@@ -569,7 +569,7 @@ elite_galaxy {
generate_next_planet()
ubyte distance = elite_planet.distance(px, py)
if distance <= max_distance {
elite_planet.name = make_current_planet_name()
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
+20 -13
View File
@@ -16,6 +16,7 @@
%import b_textelite
%import b_maze
%import b_sprites
%import b_btree
%zeropage basicsafe
%option no_sysinit
@@ -25,6 +26,7 @@ main {
str[20] benchmark_names
uword[20] benchmark_score
str version = "12.1"
sub start() {
@@ -34,8 +36,10 @@ main {
txt.color2(1, 6)
txt.clear_screen()
txt.print("\n\n\n prog8 compiler benchmark tests.\n")
sys.wait(60)
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
@@ -64,13 +68,9 @@ main {
benchmark_number++
announce_benchmark("circles with gfx_lores")
benchmark_score[benchmark_number] = circles.draw(false, 300)
benchmark_score[benchmark_number] = circles.draw(false, 400)
benchmark_number++
; announce_benchmark("circles with kernal")
; benchmark_score[benchmark_number] = circles.draw(true, 300)
; benchmark_number++
announce_benchmark("text-elite")
benchmark_score[benchmark_number] = textelite.bench(120)
benchmark_number++
@@ -79,28 +79,35 @@ main {
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 final_score
uword total_score
benchmark_number = 0
txt.print("\nscore benchmark\n\n")
txt.print("\nscore benchmark (")
txt.print(version)
txt.print(")\n\n")
do {
txt.spc()
txt.print_uw(benchmark_score[benchmark_number])
txt.column(6)
txt.print(benchmark_names[benchmark_number])
final_score += benchmark_score[benchmark_number]
total_score += benchmark_score[benchmark_number]
txt.nl()
benchmark_number++
} until benchmark_names[benchmark_number]==0
txt.print("\n\nfinal score : ")
txt.print_uw(final_score)
txt.nl()
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
+77 -6
View File
@@ -1,9 +1,9 @@
import org.jetbrains.kotlin.gradle.dsl.JvmDefaultMode
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.internal.config.LanguageFeature
plugins {
kotlin("jvm") version "2.1.20"
kotlin("jvm") version "2.3.21"
}
allprojects {
@@ -12,12 +12,15 @@ allprojects {
repositories {
mavenLocal()
mavenCentral()
maven { url = uri("https://jitpack.io") }
}
kotlin {
compilerOptions {
freeCompilerArgs = listOf("-Xwhen-guards")
jvmTarget = JvmTarget.JVM_11
freeCompilerArgs = listOf()
jvmTarget = JvmTarget.JVM_17
jvmDefault = JvmDefaultMode.NO_COMPATIBILITY
// languageVersion.set(KotlinVersion.KOTLIN_2_3)
}
sourceSets.all {
languageSettings {
@@ -28,7 +31,75 @@ allprojects {
}
java {
targetCompatibility = JavaVersion.VERSION_11
sourceCompatibility = JavaVersion.VERSION_11
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:5.9.1"))
// implementation("com.github.irmen:ksim65:v2.1")
}
// 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()
// Enable concurrent test execution for Kotest
// Set parallelism to number of CPU cores
jvmArgs("-Dkotest.framework.parallelism=${Runtime.getRuntime().availableProcessors()}")
// 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 = (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1)
// 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
}
}
}
+4 -16
View File
@@ -1,24 +1,12 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
kotlin("jvm")
`java-test-fixtures`
}
dependencies {
// should have no dependencies to other modules
// implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.1")
}
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.3.1")
sourceSets {
main {
java {
srcDir("${project.projectDir}/src")
}
resources {
srcDir("${project.projectDir}/res")
}
}
testImplementation("io.kotest:kotest-runner-junit5")
testImplementation("io.kotest:kotest-framework-datatest")
}
// note: there are no unit tests in this module!
+4 -1
View File
@@ -4,11 +4,14 @@
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<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" />
<orderEntry type="library" scope="TEST" name="io.kotest.runner.junit5.jvm" level="project" />
<orderEntry type="library" scope="TEST" name="io.kotest.framework.datatest" level="project" />
</component>
</module>
</module>
+22 -2
View File
@@ -5,9 +5,10 @@ import java.nio.file.Path
import kotlin.io.path.absolute
// the automatically generated module where all string literals are interned to:
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_"
@@ -28,4 +29,23 @@ fun Path.sanitize(): Path {
} 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('.')
.map { part ->
if(part.length > 4 && part.startsWith("p8") && part[2].isLetter() && part[3] == '_')
part.drop(4)
else part
}
.joinToString(".")
}
}
+121 -75
View File
@@ -30,37 +30,48 @@ private val NumericDatatypes = arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, Ba
class FSignature(val pure: Boolean, // does it have side effects?
val returnType: BaseDataType?,
val returnTypes: Array<BaseDataType>,
vararg val parameters: FParam) {
fun callConvention(actualParamTypes: List<BaseDataType>): CallConvention {
val returns: ReturnConvention = when (returnType) {
BaseDataType.UBYTE, BaseDataType.BYTE -> ReturnConvention(returnType, RegisterOrPair.A)
BaseDataType.UWORD, BaseDataType.WORD -> ReturnConvention(returnType, RegisterOrPair.AY)
BaseDataType.FLOAT -> ReturnConvention(returnType, RegisterOrPair.FAC1)
in IterableDatatypes -> ReturnConvention(returnType!!, RegisterOrPair.AY)
null -> ReturnConvention(null, null)
else -> {
// return type depends on arg type
when (val paramType = actualParamTypes.first()) {
BaseDataType.UBYTE, BaseDataType.BYTE -> ReturnConvention(paramType, RegisterOrPair.A)
BaseDataType.UWORD, BaseDataType.WORD -> ReturnConvention(paramType, RegisterOrPair.AY)
BaseDataType.FLOAT -> ReturnConvention(paramType, RegisterOrPair.FAC1)
in IterableDatatypes -> ReturnConvention(paramType, RegisterOrPair.AY)
else -> ReturnConvention(paramType, null)
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 -> 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 -> 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)
}
}
}
} 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.
// 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).
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)
else -> ParamConvention(paramType, null, false)
@@ -86,67 +97,102 @@ class FSignature(val pure: Boolean, // does it have side effects?
}
val BuiltinFunctions: Map<String, FSignature> = mapOf(
"setlsb" to FSignature(false, null, FParam("variable", BaseDataType.WORD, BaseDataType.UWORD), FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
"setmsb" to FSignature(false, null, FParam("variable", BaseDataType.WORD, BaseDataType.UWORD), FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
"rol" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD)),
"ror" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD)),
"rol2" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD)),
"ror2" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD)),
"cmp" to FSignature(false, null, FParam("value1", *IntegerDatatypes), FParam("value2", *NumericDatatypes)), // cmp returns a status in the carry flag, but not a proper return value
"prog8_lib_stringcompare" to FSignature(true, BaseDataType.BYTE, FParam("str1", BaseDataType.STR), FParam("str2", BaseDataType.STR)),
"prog8_lib_square_byte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)),
"prog8_lib_square_word" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.WORD, BaseDataType.UWORD)),
"abs" to FSignature(true, null, FParam("value", *NumericDatatypes)),
"abs__byte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.BYTE)),
"abs__word" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.WORD)),
"abs__float" to FSignature(true, BaseDataType.FLOAT, FParam("value", BaseDataType.FLOAT)),
"len" to FSignature(true, BaseDataType.UWORD, FParam("values", *IterableDatatypes)),
"sizeof" to FSignature(true, BaseDataType.UBYTE, FParam("object", *BaseDataType.entries.toTypedArray())),
"sgn" to FSignature(true, BaseDataType.BYTE, FParam("value", *NumericDatatypes)),
"sqrt" to FSignature(true, null, FParam("value", *NumericDatatypes)),
"sqrt__ubyte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UBYTE)),
"sqrt__uword" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UWORD)),
"sqrt__float" to FSignature(true, BaseDataType.FLOAT, FParam("value", BaseDataType.FLOAT)),
"divmod" to FSignature(false, null, FParam("dividend", BaseDataType.UBYTE, BaseDataType.UWORD), FParam("divisor", BaseDataType.UBYTE, BaseDataType.UWORD), FParam("quotient", BaseDataType.UBYTE, BaseDataType.UWORD), FParam("remainder", BaseDataType.UBYTE, BaseDataType.UWORD)),
"divmod__ubyte" to FSignature(false, null, FParam("dividend", BaseDataType.UBYTE), FParam("divisor", BaseDataType.UBYTE), FParam("quotient", BaseDataType.UBYTE), FParam("remainder", BaseDataType.UBYTE)),
"divmod__uword" to FSignature(false, null, FParam("dividend", BaseDataType.UWORD), FParam("divisor", BaseDataType.UWORD), FParam("quotient", BaseDataType.UWORD), FParam("remainder", BaseDataType.UWORD)),
"lsb" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
"lsw" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
"msb" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
"msw" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
"mkword" to FSignature(true, BaseDataType.UWORD, FParam("msb", BaseDataType.UBYTE), FParam("lsb", BaseDataType.UBYTE)),
"clamp" to FSignature(true, null, FParam("value", BaseDataType.BYTE), FParam("minimum", BaseDataType.BYTE), FParam("maximum", BaseDataType.BYTE)),
"clamp__byte" to FSignature(true, BaseDataType.BYTE, FParam("value", BaseDataType.BYTE), FParam("minimum", BaseDataType.BYTE), FParam("maximum", BaseDataType.BYTE)),
"clamp__ubyte" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UBYTE), FParam("minimum", BaseDataType.UBYTE), FParam("maximum", BaseDataType.UBYTE)),
"clamp__word" to FSignature(true, BaseDataType.WORD, FParam("value", BaseDataType.WORD), FParam("minimum", BaseDataType.WORD), FParam("maximum", BaseDataType.WORD)),
"clamp__uword" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.UWORD), FParam("minimum", BaseDataType.UWORD), FParam("maximum", BaseDataType.UWORD)),
"min" to FSignature(true, null, FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
"min__byte" to FSignature(true, BaseDataType.BYTE, FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
"min__ubyte" to FSignature(true, BaseDataType.UBYTE, FParam("val1", BaseDataType.UBYTE), FParam("val2", BaseDataType.UBYTE)),
"min__word" to FSignature(true, BaseDataType.WORD, FParam("val1", BaseDataType.WORD), FParam("val2", BaseDataType.WORD)),
"min__uword" to FSignature(true, BaseDataType.UWORD, FParam("val1", BaseDataType.UWORD), FParam("val2", BaseDataType.UWORD)),
"max" to FSignature(true, null, FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
"max__byte" to FSignature(true, BaseDataType.BYTE, FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
"max__ubyte" to FSignature(true, BaseDataType.UBYTE, FParam("val1", BaseDataType.UBYTE), FParam("val2", BaseDataType.UBYTE)),
"max__word" to FSignature(true, BaseDataType.WORD, FParam("val1", BaseDataType.WORD), FParam("val2", BaseDataType.WORD)),
"max__uword" to FSignature(true, BaseDataType.UWORD, FParam("val1", BaseDataType.UWORD), FParam("val2", BaseDataType.UWORD)),
"peek" to FSignature(true, BaseDataType.UBYTE, FParam("address", BaseDataType.UWORD)),
"peekw" to FSignature(true, BaseDataType.UWORD, FParam("address", BaseDataType.UWORD)),
"peekf" to FSignature(true, BaseDataType.FLOAT, FParam("address", BaseDataType.UWORD)),
"poke" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UBYTE)),
"pokew" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UWORD)),
"pokef" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.FLOAT)),
"pokemon" to FSignature(false, BaseDataType.UBYTE, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UBYTE)),
"rsave" to FSignature(false, null),
"rrestore" to FSignature(false, null),
"memory" to FSignature(true, BaseDataType.UWORD, FParam("name", BaseDataType.STR), FParam("size", BaseDataType.UWORD), FParam("alignment", BaseDataType.UWORD)),
"callfar" to FSignature(false, BaseDataType.UWORD, FParam("bank", BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD), FParam("arg", BaseDataType.UWORD)),
"callfar2" to FSignature(false, BaseDataType.UWORD, FParam("bank", BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD), FParam("argA", BaseDataType.UBYTE), FParam("argX", BaseDataType.UBYTE), FParam("argY", BaseDataType.UBYTE), FParam("argC", BaseDataType.BOOL)),
"call" to FSignature(false, BaseDataType.UWORD, FParam("address", BaseDataType.UWORD)),
"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.UWORD), FParam("pointer2", BaseDataType.UWORD)),
"prog8_lib_copyfloat" to FSignature(false, emptyArray(), FParam("pointer1", BaseDataType.UWORD), FParam("pointer2", BaseDataType.UWORD)),
"abs" to FSignature(true, emptyArray(), 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, emptyArray(), 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, emptyArray(), 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, emptyArray(), 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, emptyArray(), 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)),
"peekbool" to FSignature(true, arrayOf(BaseDataType.BOOL), FParam("address", BaseDataType.UWORD)),
"peekw" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("address", BaseDataType.UWORD)),
"peekl" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("address", BaseDataType.UWORD)),
"peekf" to FSignature(true, arrayOf(BaseDataType.FLOAT), FParam("address", BaseDataType.UWORD)),
"poke" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UBYTE, BaseDataType.BYTE)),
"pokebool" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.BOOL)),
"pokebowl" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.BOOL)),
"pokew" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UWORD, BaseDataType.WORD)),
"pokel" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.LONG)),
"pokef" to FSignature(false, emptyArray(), FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.FLOAT)),
"pokemon" to FSignature(false, arrayOf(BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UBYTE)),
"rsave" to FSignature(false, emptyArray()),
"rrestore" to FSignature(false, emptyArray()),
"memory" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("name", BaseDataType.STR), FParam("size", BaseDataType.UWORD), FParam("alignment", BaseDataType.UWORD)),
"memory__ref" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("name", BaseDataType.STR)),
"callfar" to FSignature(false, arrayOf(BaseDataType.UWORD), FParam("bank", BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD), FParam("arg", BaseDataType.UWORD)),
"callfar2" to FSignature(false, arrayOf(BaseDataType.UWORD), FParam("bank", BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD), 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.UWORD)),
"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)),
"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)),
)
val InplaceModifyingBuiltinFunctions = setOf(
"setlsb", "setmsb",
"rol", "ror", "rol2", "ror2",
"divmod", "divmod__ubyte", "divmod__uword"
"rol", "ror", "rol2", "ror2"
)
val SimpleBuiltinFunctions = setOf(
"msb", "lsb", "msw", "lsw",
"mkword", "mklong", "mklong2",
"set_carry", "set_irqd", "clear_carry", "clear_irqd")
@@ -13,10 +13,12 @@ class CompilationOptions(val output: OutputType,
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 warnSymbolShadowing: Boolean = false,
var warnImplicitTypeCast: Boolean = false,
var optimize: Boolean = false,
var asmQuiet: Boolean = false,
var asmListfile: Boolean = false,
@@ -28,11 +30,11 @@ class CompilationOptions(val output: OutputType,
var varsGolden: Boolean = false,
var slabsHighBank: Int? = null,
var slabsGolden: Boolean = false,
var dontSplitWordArrays: Boolean = false,
var breakpointCpuInstruction: String? = null,
var ignoreFootguns: Boolean = false,
var outputDir: Path = Path(""),
var quiet: Boolean = false,
var profilingInstrumentation: Boolean = false,
var symbolDefs: Map<String, String> = emptyMap()
) {
init {
@@ -41,5 +43,81 @@ class CompilationOptions(val output: OutputType,
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 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 experimentalCodegen: Boolean = false
private var varsHighBank: Int? = null
private var varsGolden: Boolean = false
private var slabsHighBank: Int? = null
private var slabsGolden: 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 symbolDefs: Map<String, String> = emptyMap()
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 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 experimentalCodegen(experimentalCodegen: Boolean) = apply { this.experimentalCodegen = experimentalCodegen }
fun varsHighBank(varsHighBank: Int?) = apply { this.varsHighBank = varsHighBank }
fun varsGolden(varsGolden: Boolean) = apply { this.varsGolden = varsGolden }
fun slabsHighBank(slabsHighBank: Int?) = apply { this.slabsHighBank = slabsHighBank }
fun slabsGolden(slabsGolden: Boolean) = apply { this.slabsGolden = slabsGolden }
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 build(): CompilationOptions {
return CompilationOptions(
output, launcher, zeropage, zpReserved, zpAllowed, floats, noSysInit, romable, compTarget, compilerVersion,
loadAddress, memtopAddress, warnSymbolShadowing, warnImplicitTypeCast, optimize, asmQuiet, asmListfile,
includeSourcelines, dumpVariables, dumpSymbols, experimentalCodegen, varsHighBank, varsGolden,
slabsHighBank, slabsGolden, breakpointCpuInstruction, ignoreFootguns, outputDir, quiet,
profilingInstrumentation, symbolDefs
)
}
}
}
}
+5 -2
View File
@@ -13,9 +13,12 @@ fun Number.toHex(): String {
// 256..65536 -> "$0100".."$ffff"
// larger -> "$12345678"
// negative values are prefixed with '-'.
val integer = this.toInt()
if(integer<0)
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
return '-' + abs(integer).toHex()
}
return when (integer) {
in 0 until 16 -> integer.toString()
in 0 until 0x100 -> "$"+integer.toString(16).padStart(2,'0')
+441
View File
@@ -0,0 +1,441 @@
package prog8.code.core
import java.util.*
/**
* 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)
ARRAY_POINTER, // array of pointers (uwords), subtype is whatever type each element points to
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 || this == UWORD && other == STR -> false
this.isArray && other.isArray -> false
this.isArray -> other != FLOAT
this == STR -> other != FLOAT
this.isPointer -> other.isByteOrBool
else -> true
}
fun equalsSize(other: BaseDataType) =
when {
this == other -> true
this.isArray && other.isArray -> true
this.isByteOrBool -> other.isByteOrBool
this.isWord -> other.isWord || other.isPointer
this.isPointer -> other.isWord
this == STR && other== UWORD || this== UWORD && 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.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.isArray get() = this == BaseDataType.ARRAY || this == BaseDataType.ARRAY_SPLITW || this == BaseDataType.ARRAY_POINTER
val BaseDataType.isPointer get() = this == BaseDataType.POINTER
val BaseDataType.isStructInstance get() = this == BaseDataType.STRUCT_INSTANCE
val BaseDataType.isPointerArray get() = this == BaseDataType.ARRAY_POINTER
val BaseDataType.isSplitWordArray get() = this == BaseDataType.ARRAY_SPLITW || this == BaseDataType.ARRAY_POINTER // pointer arrays are also always stored as split uwords
val BaseDataType.isIterable get() = this in setOf(BaseDataType.STR, BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW, BaseDataType.ARRAY_POINTER)
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?,
var subType: ISubType?,
var subTypeFromAntlr: List<String>? = null
) {
init {
when {
base.isPointerArray -> {
require(sub!=null || subType!=null || subTypeFromAntlr!=null)
}
base.isArray -> {
require(sub != null && subType==null && subTypeFromAntlr==null)
if(base.isSplitWordArray)
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
return base == other.base && sub == other.sub && (subType==other.subType || subType!!.sameas(other.subType!!))
}
override fun hashCode(): Int = Objects.hash(base, sub, subType)
fun setActualSubType(actualSubType: ISubType) {
subType = actualSubType
subTypeFromAntlr = null
}
// ============================================================================
// Companion Object - Factory Methods
// ============================================================================
companion object {
val UBYTE = DataType(BaseDataType.UBYTE, null, null)
val BYTE = DataType(BaseDataType.BYTE, null, null)
val UWORD = DataType(BaseDataType.UWORD, null, null)
val WORD = DataType(BaseDataType.WORD, null, null)
val LONG = DataType(BaseDataType.LONG, null, null)
val FLOAT = DataType(BaseDataType.FLOAT, null, null)
val BOOL = DataType(BaseDataType.BOOL, null, null)
val STR = DataType(BaseDataType.STR, BaseDataType.UBYTE, null)
val UNDEFINED = DataType(BaseDataType.UNDEFINED, null, null)
private val simpletypes = mapOf(
BaseDataType.UBYTE to DataType(BaseDataType.UBYTE, null, null),
BaseDataType.BYTE to DataType(BaseDataType.BYTE, null, null),
BaseDataType.UWORD to DataType(BaseDataType.UWORD, null, null),
BaseDataType.WORD to DataType(BaseDataType.WORD, null, null),
BaseDataType.LONG to DataType(BaseDataType.LONG, null, null),
BaseDataType.FLOAT to DataType(BaseDataType.FLOAT, null, null),
BaseDataType.BOOL to DataType(BaseDataType.BOOL, null, null),
BaseDataType.STR to DataType(BaseDataType.STR, BaseDataType.UBYTE, null),
BaseDataType.UNDEFINED to DataType(BaseDataType.UNDEFINED, null, null)
)
fun forDt(dt: BaseDataType): DataType {
if(dt.isStructInstance)
TODO("cannot use struct instance as a data type (yet) - use a pointer instead")
return simpletypes.getValue(dt)
}
fun arrayFor(elementDt: BaseDataType, splitwordarray: Boolean=true): DataType {
require(!elementDt.isPointer) { "use other array constructor for arrays of pointers" }
val actualElementDt = if(elementDt==BaseDataType.STR) BaseDataType.UWORD else elementDt // array of strings is actually just an array of UWORD pointers
return if(splitwordarray && actualElementDt.isWord)
DataType(BaseDataType.ARRAY_SPLITW, actualElementDt, null)
else {
if(actualElementDt.isNumericOrBool)
DataType(BaseDataType.ARRAY, actualElementDt, null)
else
throw NoSuchElementException("invalid basic element dt $elementDt")
}
}
fun arrayOfPointersTo(sub: BaseDataType): DataType = DataType(BaseDataType.ARRAY_POINTER, sub, null)
fun arrayOfPointersTo(structType: ISubType?): DataType = DataType(BaseDataType.ARRAY_POINTER, null, structType)
fun arrayOfPointersFromAntlrTo(sub: BaseDataType?, identifier: List<String>?): DataType =
DataType(BaseDataType.ARRAY_POINTER, sub, null, identifier)
fun pointer(base: BaseDataType): DataType = DataType(BaseDataType.POINTER, base, null)
fun pointer(dt: DataType): DataType = if(dt.isBasic)
DataType(BaseDataType.POINTER, dt.base, null)
else
DataType(BaseDataType.POINTER, null, dt.subType, dt.subTypeFromAntlr)
fun pointer(structType: ISubType): DataType = DataType(BaseDataType.POINTER, null, structType)
fun pointerFromAntlr(identifier: List<String>): DataType = DataType(BaseDataType.POINTER, null, null, identifier)
fun structInstance(type: ISubType?): DataType = DataType(BaseDataType.STRUCT_INSTANCE, sub=null, type)
fun structInstanceFromAntlr(struct: List<String>): DataType = DataType(BaseDataType.STRUCT_INSTANCE, null, null, subTypeFromAntlr = struct)
}
// ============================================================================
// DataType Methods
// ============================================================================
fun elementToArray(splitwords: Boolean = true): DataType {
return if (base == BaseDataType.UWORD || base == BaseDataType.WORD || base == BaseDataType.STR) arrayFor(base, splitwords)
else arrayFor(base, false)
}
fun elementType(): DataType =
when {
isPointerArray -> DataType(BaseDataType.POINTER, sub, subType)
base.isArray || base==BaseDataType.STR -> forDt(sub!!)
else -> throw IllegalArgumentException("not an array")
}
fun typeForAddressOf(msb: Boolean): DataType {
if (isUndefined)
return if(msb) pointer(BaseDataType.UBYTE) else UWORD
else {
if (isBasic)
return pointer(base)
if (isString)
return pointer(BaseDataType.UBYTE)
if (isPointer)
return UWORD
if (isArray) {
if (msb || isSplitWordArray)
return pointer(BaseDataType.UBYTE)
val elementDt = elementType()
require(elementDt.isBasic)
return pointer(elementDt)
}
if (subType != null)
return pointer(this)
return UWORD
}
}
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, subType)
subTypeFromAntlr!=null -> DataType(BaseDataType.STRUCT_INSTANCE, 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[]"
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.ARRAY_POINTER -> {
if(sub!=null) "^^${sub.name.lowercase()}[] (split)" else if (subType!=null) "^^${subType!!.scopedNameString}[] (split)" else "^^${subTypeFromAntlr}[] (split)"
}
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_POINTER -> {
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["
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, BaseDataType.ARRAY_POINTER)
BaseDataType.WORD -> targetType.base in setOf(BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
BaseDataType.LONG -> targetType.base in setOf(BaseDataType.LONG, BaseDataType.FLOAT)
BaseDataType.FLOAT -> targetType.base in arrayOf(BaseDataType.FLOAT)
BaseDataType.STR -> targetType.base in setOf(BaseDataType.STR, BaseDataType.UWORD) || (targetType.isPointer && targetType.sub==BaseDataType.UBYTE)
BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW -> 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.ARRAY_POINTER -> false
BaseDataType.UNDEFINED -> false
}
fun largerSizeThan(other: DataType): Boolean = base.largerSizeThan(other.base)
fun equalsSize(other: DataType): Boolean = base.equalsSize(other.base)
/**
* 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 = if(sub!=null) {
memsizer.memorySize(sub)
} else if(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.isPointerArray
val isBoolArray = base.isArray && !base.isPointerArray && sub == BaseDataType.BOOL
val isByteArray = base.isArray && !base.isPointerArray && (sub == BaseDataType.UBYTE || sub == BaseDataType.BYTE)
val isUnsignedByteArray = base.isArray && !base.isPointerArray && sub == BaseDataType.UBYTE
val isSignedByteArray = base.isArray && !base.isPointerArray && sub == BaseDataType.BYTE
val isWordArray = base.isArray && !base.isPointerArray && (sub == BaseDataType.UWORD || sub == BaseDataType.WORD)
val isUnsignedWordArray = base.isArray && !base.isPointerArray && sub == BaseDataType.UWORD
val isSignedWordArray = base.isArray && !base.isPointerArray && sub == BaseDataType.WORD
val isLongArray = base.isArray && sub == BaseDataType.LONG
val isFloatArray = base.isArray && !base.isPointerArray && sub == BaseDataType.FLOAT
val isString = base == BaseDataType.STR
val isBool = base == BaseDataType.BOOL
val isFloat = base == BaseDataType.FLOAT
val isLong = base == BaseDataType.LONG
val isStringly = base == BaseDataType.STR || base == BaseDataType.UWORD || (base == BaseDataType.ARRAY && (sub == BaseDataType.UBYTE || sub == BaseDataType.BYTE))
val isSplitWordArray = base.isSplitWordArray
val isSplitUnsignedWordArray = base.isSplitWordArray && !base.isPointerArray && sub == BaseDataType.UWORD
val isSplitSignedWordArray = base.isSplitWordArray && !base.isPointerArray && sub == BaseDataType.WORD
val isIterable = base.isIterable
val isPassByRef = base.isPassByRef
val isPassByValue = base.isPassByValue
}
+43 -245
View File
@@ -1,240 +1,5 @@
package prog8.code.core
import java.util.*
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)
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 || this == UWORD && other == STR -> false
this.isArray && other.isArray -> false
this.isArray -> other != FLOAT
this == STR -> other != FLOAT
else -> true
}
fun equalsSize(other: BaseDataType) =
when {
this == other -> true
this.isArray && other.isArray -> true
this.isByteOrBool -> other.isByteOrBool
this.isWord -> other.isWord
this == STR && other== UWORD || this== UWORD && other== STR -> true
this == STR && other.isArray -> true
this.isArray && other == STR -> true
else -> false
}
}
val BaseDataType.isByte get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE)
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.isInteger get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)
val BaseDataType.isIntegerOrBool get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG, 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 arrayOf(BaseDataType.BYTE, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
val BaseDataType.isArray get() = this == BaseDataType.ARRAY || this == BaseDataType.ARRAY_SPLITW
val BaseDataType.isSplitWordArray get() = this == BaseDataType.ARRAY_SPLITW
val BaseDataType.isIterable get() = this in arrayOf(BaseDataType.STR, BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW)
val BaseDataType.isPassByRef get() = this.isIterable
val BaseDataType.isPassByValue get() = !this.isIterable
class DataType private constructor(val base: BaseDataType, val sub: BaseDataType?) {
init {
if(base.isArray) {
require(sub != null)
if(base.isSplitWordArray)
require(sub == BaseDataType.UWORD || sub == BaseDataType.WORD)
}
else if(base==BaseDataType.STR)
require(sub==BaseDataType.UBYTE) { "string subtype should be ubyte" }
else
require(sub == null) { "only string and array base types can have a subtype"}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is DataType) return false
return base == other.base && sub == other.sub
}
override fun hashCode(): Int = Objects.hash(base, sub)
companion object {
val UBYTE = DataType(BaseDataType.UBYTE, null)
val BYTE = DataType(BaseDataType.BYTE, null)
val UWORD = DataType(BaseDataType.UWORD, null)
val WORD = DataType(BaseDataType.WORD, null)
val LONG = DataType(BaseDataType.LONG, null)
val FLOAT = DataType(BaseDataType.FLOAT, null)
val BOOL = DataType(BaseDataType.BOOL, null)
val STR = DataType(BaseDataType.STR, BaseDataType.UBYTE)
val UNDEFINED = DataType(BaseDataType.UNDEFINED, null)
private val simpletypes = mapOf(
BaseDataType.UBYTE to DataType(BaseDataType.UBYTE, null),
BaseDataType.BYTE to DataType(BaseDataType.BYTE, null),
BaseDataType.UWORD to DataType(BaseDataType.UWORD, null),
BaseDataType.WORD to DataType(BaseDataType.WORD, null),
BaseDataType.LONG to DataType(BaseDataType.LONG, null),
BaseDataType.FLOAT to DataType(BaseDataType.FLOAT, null),
BaseDataType.BOOL to DataType(BaseDataType.BOOL, null),
BaseDataType.STR to DataType(BaseDataType.STR, BaseDataType.UBYTE),
BaseDataType.UNDEFINED to DataType(BaseDataType.UNDEFINED, null)
)
fun forDt(dt: BaseDataType) = simpletypes.getValue(dt)
fun arrayFor(elementDt: BaseDataType, splitwordarray: Boolean=true): DataType {
val actualElementDt = if(elementDt==BaseDataType.STR) BaseDataType.UWORD else elementDt // array of strings is actually just an array of UWORD pointers
return if(splitwordarray && actualElementDt.isWord)
DataType(BaseDataType.ARRAY_SPLITW, actualElementDt)
else {
if(actualElementDt.isNumericOrBool && actualElementDt != BaseDataType.LONG)
DataType(BaseDataType.ARRAY, actualElementDt)
else
throw NoSuchElementException("invalid element dt $elementDt")
}
}
}
fun elementToArray(splitwords: Boolean = true): DataType {
return if (base == BaseDataType.UWORD || base == BaseDataType.WORD || base == BaseDataType.STR) arrayFor(base, splitwords)
else arrayFor(base, false)
}
fun elementType(): DataType =
if(base.isArray || base==BaseDataType.STR)
forDt(sub!!)
else
throw IllegalArgumentException("not an array")
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[]"
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")
}
}
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.ARRAY -> {
when(sub) {
BaseDataType.UBYTE -> "ubyte["
BaseDataType.UWORD -> "@nosplit uword["
BaseDataType.BOOL -> "bool["
BaseDataType.BYTE -> "byte["
BaseDataType.WORD -> "@nosplit word["
BaseDataType.FLOAT -> "float["
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")
}
// is the type assignable to the given other 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 arrayOf(BaseDataType.UBYTE, BaseDataType.WORD, BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.FLOAT)
BaseDataType.BYTE -> targetType.base in arrayOf(BaseDataType.BYTE, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
BaseDataType.UWORD -> targetType.base in arrayOf(BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.FLOAT)
BaseDataType.WORD -> targetType.base in arrayOf(BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
BaseDataType.LONG -> targetType.base in arrayOf(BaseDataType.LONG, BaseDataType.FLOAT)
BaseDataType.FLOAT -> targetType.base in arrayOf(BaseDataType.FLOAT)
BaseDataType.STR -> targetType.base in arrayOf(BaseDataType.STR, BaseDataType.UWORD)
BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW -> targetType.base in arrayOf(BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW) && targetType.sub == sub
BaseDataType.UNDEFINED -> false
}
fun largerSizeThan(other: DataType): Boolean = base.largerSizeThan(other.base)
fun equalsSize(other: DataType): Boolean = base.equalsSize(other.base)
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 isIntegerOrBool = base.isIntegerOrBool
val isNumeric = base.isNumeric
val isNumericOrBool = base.isNumericOrBool
val isSigned = base.isSigned
val isUnsigned = !base.isSigned
val isArray = base.isArray
val isBoolArray = base.isArray && sub == BaseDataType.BOOL
val isByteArray = base.isArray && (sub == BaseDataType.UBYTE || sub == BaseDataType.BYTE)
val isUnsignedByteArray = base.isArray && sub == BaseDataType.UBYTE
val isSignedByteArray = base.isArray && sub == BaseDataType.BYTE
val isWordArray = base.isArray && (sub == BaseDataType.UWORD || sub == BaseDataType.WORD)
val isUnsignedWordArray = base.isArray && sub == BaseDataType.UWORD
val isSignedWordArray = base.isArray && sub == BaseDataType.WORD
val isFloatArray = base.isArray && sub == BaseDataType.FLOAT
val isString = base == BaseDataType.STR
val isBool = base == BaseDataType.BOOL
val isFloat = base == BaseDataType.FLOAT
val isLong = base == BaseDataType.LONG
val isStringly = base == BaseDataType.STR || base == BaseDataType.UWORD || (base == BaseDataType.ARRAY && (sub == BaseDataType.UBYTE || sub == BaseDataType.BYTE))
val isSplitWordArray = base.isSplitWordArray
val isSplitUnsignedWordArray = base.isSplitWordArray && sub == BaseDataType.UWORD
val isSplitSignedWordArray = base.isSplitWordArray && sub == BaseDataType.WORD
val isIterable = base.isIterable
val isPassByRef = base.isPassByRef
val isPassByValue = base.isPassByValue
}
enum class CpuRegister {
A,
@@ -253,10 +18,12 @@ enum class RegisterOrPair {
FAC2,
// cx16 virtual registers:
R0, R1, R2, R3, R4, R5, R6, R7,
R8, R9, R10, R11, R12, R13, R14, R15;
R8, R9, R10, R11, R12, R13, R14, R15,
// combined virtual registers to store 32 bits longs:
R0R1, R2R3, R4R5, R6R7, R8R9, R10R11, R12R13, R14R15;
companion object {
val names by lazy { entries.map { it.toString()} }
val names: Set<String> = entries.map { it.toString() }.toSet()
fun fromCpuRegister(cpu: CpuRegister): RegisterOrPair {
return when(cpu) {
CpuRegister.A -> A
@@ -266,6 +33,22 @@ enum class RegisterOrPair {
}
}
/**
* 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")
}
fun asCpuRegister(): CpuRegister = when(this) {
A -> CpuRegister.A
X -> CpuRegister.X
@@ -274,16 +57,21 @@ enum class RegisterOrPair {
}
fun asScopedNameVirtualReg(type: DataType?): List<String> {
require(this in Cx16VirtualRegisters)
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, null -> ""
else -> throw kotlin.IllegalArgumentException("invalid register param type")
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 {
@@ -293,7 +81,7 @@ enum class Statusflag {
Pn; // don't use
companion object {
val names by lazy { entries.map { it.toString()} }
val names: Set<String> = entries.map { it.toString() }.toSet()
}
}
@@ -312,14 +100,25 @@ enum class BranchCondition {
VC
}
val Cx16VirtualRegisters = arrayOf(
val Cx16VirtualRegisters = setOf(
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 CpuRegisters = arrayOf(
val CombinedLongRegisters = setOf(
RegisterOrPair.R0R1,
RegisterOrPair.R2R3,
RegisterOrPair.R4R5,
RegisterOrPair.R6R7,
RegisterOrPair.R8R9,
RegisterOrPair.R10R11,
RegisterOrPair.R12R13,
RegisterOrPair.R14R15
)
val CpuRegisters = setOf(
RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y,
RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY
)
@@ -354,6 +153,5 @@ enum class ZeropageWish {
enum class SplitWish {
DONTCARE,
SPLIT,
NOSPLIT
}
}
@@ -13,7 +13,7 @@ interface ICompilationTarget: IStringEncoding, IMemSizer {
val FLOAT_MAX_NEGATIVE: Double
val FLOAT_MAX_POSITIVE: Double
val FLOAT_MEM_SIZE: Int
val FLOAT_MEM_SIZE: UInt
val STARTUP_CODE_RESERVED_SIZE: UInt // this is here, so that certain compiler targets are able to tune this
val PROGRAM_LOAD_ADDRESS : UInt
val PROGRAM_MEMTOP_ADDRESS: UInt
@@ -24,7 +24,6 @@ interface ICompilationTarget: IStringEncoding, IMemSizer {
val cpu: CpuType
var zeropage: Zeropage
var golden: GoldenRam
val libraryPath: Path?
val customLauncher: List<String>
val additionalAssemblerOptions: List<String>
@@ -4,7 +4,7 @@ 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>, 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) {
+26 -61
View File
@@ -23,30 +23,26 @@ 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 $fb+$fc
abstract val SCRATCH_W2 : UInt // temp storage 2 for a word $fb+$fc
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
// the variables allocated into Zeropage.
// Collections for zeropage allocation (single-threaded usage)
// name (scoped) ==> pair of address to (Datatype + bytesize)
val allocatedVariables = mutableMapOf<String, VarAllocation>()
val free = mutableListOf<UInt>() // subclasses must set this to the appropriate free locations.
val free = mutableSetOf<UInt>() // subclasses must set this to the appropriate free locations.
fun removeReservedFromFreePool() {
synchronized(this) {
for (reserved in options.zpReserved)
reserve(reserved)
for (reserved in options.zpReserved)
reserve(reserved)
free.removeAll(arrayOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1u, SCRATCH_W2, SCRATCH_W2 + 1u))
}
free.removeAll(setOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1u, SCRATCH_W2, SCRATCH_W2 + 1u, SCRATCH_PTR, SCRATCH_PTR+1u))
}
fun retainAllowed() {
synchronized(this) {
for(allowed in options.zpAllowed)
free.retainAll { it in allowed }
}
for(allowed in options.zpAllowed)
free.retainAll { it in allowed }
}
fun availableBytes() = if(options.zeropage== ZeropageType.DONTUSE) 0 else free.size
@@ -64,7 +60,7 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
position: Position?,
errors: IErrorReporter): Result<VarAllocation, MemAllocationError> {
require(name.isEmpty() || name !in allocatedVariables) {"name can't be allocated twice"}
require(name.isEmpty() || !allocatedVariables.containsKey(name)) {"name can't be allocated twice"}
if(options.zeropage== ZeropageType.DONTUSE)
return Err(MemAllocationError("zero page usage has been disabled"))
@@ -72,6 +68,7 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
val size: Int =
when {
datatype.isIntegerOrBool -> options.compTarget.memorySize(datatype, null)
datatype.isPointer -> options.compTarget.memorySize(datatype, null)
datatype.isString || datatype.isArray -> {
val memsize = options.compTarget.memorySize(datatype, numElements!!)
if(position!=null)
@@ -93,19 +90,17 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
else -> throw MemAllocationError("weird dt")
}
synchronized(this) {
if(free.isNotEmpty()) {
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))
}
if(free.isNotEmpty()) {
if(size==1) {
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) {
if (sequentialFree(candidate, size))
return Ok(VarAllocation(makeAllocation(candidate, size, datatype, name), datatype, size))
if(oneSeparateByteFree(candidate))
return Ok(VarAllocation(makeAllocation(candidate, 1, datatype, name), datatype,1))
}
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))
}
}
@@ -122,49 +117,19 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
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)
else -> throw AssemblyError("invalid dt")
}
}
return address
}
private fun oneSeparateByteFree(address: UInt) = address in free && address-1u !in free && address+1u !in free
private fun oneSeparateByteFree(address: UInt) =
address in free &&
(address == 0u || 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())
}
}
// 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.isIntegerOrBool -> options.compTarget.memorySize(datatype, null)
datatype.isString -> numElements!!
datatype.isArray -> options.compTarget.memorySize(datatype, numElements!!)
datatype.isFloat -> {
if (options.floats) {
options.compTarget.memorySize(DataType.FLOAT, null)
} 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"))
}
}
+5 -5
View File
@@ -1,10 +1,10 @@
package prog8.code.core
val AssociativeOperators = arrayOf("+", "*", "&", "|", "^", "==", "!=", "xor") // note: and,or are not associative because of Shortcircuit/McCarthy evaluation
val ComparisonOperators = arrayOf("==", "!=", "<", ">", "<=", ">=")
val LogicalOperators = arrayOf("and", "or", "xor", "not", "in")
val BitwiseOperators = arrayOf("&", "|", "^", "~")
val PrefixOperators = arrayOf("+", "-", "~", "not")
val CommutativeOperators = setOf("+", "*", "&", "|", "^", "==", "!=", "xor") // note: and,or are not commutative because of Shortcircuit/McCarthy evaluation
val ComparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
val LogicalOperators = setOf("and", "or", "xor", "not", "in")
val BitwiseOperators = setOf("&", "|", "^", "~")
val PrefixOperators = setOf("+", "-", "~", "not")
fun invertedComparisonOperator(operator: String) =
when (operator) {
+16 -5
View File
@@ -5,16 +5,27 @@ import prog8.code.source.SourceCode
import java.nio.file.InvalidPathException
import kotlin.io.path.Path
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}]"
/**
* 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}]"
}
fun toClickableStr(): String {
if(this===DUMMY)
return ""
if(SourceCode.isLibraryResource(file))
return "$file:$line:$startCol:"
return try {
val path = Path(file).sanitize().toString()
"file://$path:$line:$startCol:"
val path = Path(file).sanitize().toUri().toString()
"$path:$line:$startCol:"
} catch(_: InvalidPathException) {
// this can occur on Windows when the source origin contains "invalid" characters such as ':'
"file://$file:$line:$startCol:"
@@ -24,4 +35,4 @@ data class Position(val file: String, val line: Int, val startCol: Int, val endC
companion object {
val DUMMY = Position("~dummy~", 0, 0, 0)
}
}
}
@@ -3,12 +3,13 @@ package prog8.code.source
import prog8.code.core.Position
import prog8.code.sanitize
import java.nio.file.Path
import java.util.*
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 {
@@ -23,45 +24,45 @@ object ImportFileSystem {
fun getFile(path: Path, isLibrary: Boolean=false): SourceCode {
val normalized = path.sanitize()
val cached = cache[normalized.toString()]
if (cached != null)
return cached
val file = SourceCode.File(normalized, isLibrary)
cache[normalized.toString()] = file
return file
return cache.computeIfAbsent(normalized.toString().lowercase()) {
SourceCode.File(normalized, isLibrary)
}
}
fun getResource(name: String): SourceCode {
val cached = cache[name]
if (cached != null) return cached
val resource = SourceCode.Resource(name)
cache[name] = resource
return resource
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 cached = cache[SourceCode.withoutPrefix(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]
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()]
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 {
var spans = lineSpanCache[code]
if(spans==null) {
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
spans = lineSpans.zip(ends).map { (start, end) -> LineSpan(start, end) }.toList().toTypedArray()
lineSpanCache[code] = spans
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()
@@ -69,6 +70,6 @@ object ImportFileSystem {
private class LineSpan(val start: Int, val end: Int)
private val cache = TreeMap<String, SourceCode>(String.CASE_INSENSITIVE_ORDER)
private val lineSpanCache = mutableMapOf<SourceCode, Array<LineSpan>>()
}
private val cache = ConcurrentHashMap<String, SourceCode>()
private val lineSpanCache = ConcurrentHashMap<SourceCode, Array<LineSpan>>()
}
+6 -8
View File
@@ -26,18 +26,17 @@ class C128Target: ICompilationTarget,
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 FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE.toUInt()
override val STARTUP_CODE_RESERVED_SIZE = 20u
override val PROGRAM_LOAD_ADDRESS = 0x1c01u
override val PROGRAM_MEMTOP_ADDRESS = 0xc000u
override val BSSHIGHRAM_START = 0u // TODO
override val BSSHIGHRAM_END = 0u // TODO
override val BSSGOLDENRAM_START = 0u // TODO
override val BSSGOLDENRAM_END = 0u // TODO
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 lateinit var golden: GoldenRam
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
@@ -71,11 +70,10 @@ class C128Target: ICompilationTarget,
process.waitFor()
}
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu
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)
golden = GoldenRam(compilerOptions, UIntRange.EMPTY) // TODO does the c128 have some of this somewhere?
}
}
+2 -4
View File
@@ -9,7 +9,7 @@ import java.nio.file.Path
class C64Target: ICompilationTarget,
IStringEncoding by Encoder(true),
IMemSizer by NormalMemSizer(Mflpt5.Companion.FLOAT_MEM_SIZE) {
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
override val name = NAME
override val defaultEncoding = Encoding.PETSCII
@@ -29,7 +29,7 @@ class C64Target: ICompilationTarget,
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 FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE.toUInt()
override val STARTUP_CODE_RESERVED_SIZE = 20u
override val PROGRAM_LOAD_ADDRESS = 0x0801u
override val PROGRAM_MEMTOP_ADDRESS = 0xcfe0u // $a000 if floats are used
@@ -41,7 +41,6 @@ class C64Target: ICompilationTarget,
override val BSSGOLDENRAM_END = 0u
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
@@ -87,7 +86,6 @@ class C64Target: ICompilationTarget,
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = C64Zeropage(compilerOptions)
golden = GoldenRam(compilerOptions, UIntRange.EMPTY)
}
}
@@ -33,6 +33,7 @@ class ConfigFileTarget(
val zpScratchReg: UInt,
val zpScratchW1: UInt,
val zpScratchW2: UInt,
val zpScratchPtr: UInt,
val virtualregistersStart: UInt,
val zpFullsafe: List<UIntRange>,
val zpKernalsafe: List<UIntRange>,
@@ -132,6 +133,7 @@ class ConfigFileTarget(
props.getInteger("zp_scratch_reg"),
props.getInteger("zp_scratch_w1"),
props.getInteger("zp_scratch_w2"),
props.getInteger("zp_scratch_ptr"),
props.getInteger("virtual_registers"),
zpFullsafe,
zpKernalsafe,
@@ -143,10 +145,9 @@ class ConfigFileTarget(
// 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 = 8
override val FLOAT_MEM_SIZE = 8u
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam // TODO this is not yet used
override fun getFloatAsmBytes(num: Number) = TODO("floats")
override fun convertFloatToBytes(num: Double): List<UByte> = TODO("floats")
@@ -159,13 +160,12 @@ class ConfigFileTarget(
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = ConfigurableZeropage(
zpScratchB1, zpScratchReg, zpScratchW1, zpScratchW2,
zpScratchB1, zpScratchReg, zpScratchW1, zpScratchW2, zpScratchPtr,
virtualregistersStart,
zpBasicsafe,
zpKernalsafe,
zpFullsafe,
compilerOptions
)
// note: there's no golden ram yet
}
}
+2 -4
View File
@@ -8,7 +8,7 @@ import java.nio.file.Path
class Cx16Target: ICompilationTarget,
IStringEncoding by Encoder(true),
IMemSizer by NormalMemSizer(Mflpt5.Companion.FLOAT_MEM_SIZE) {
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
override val name = NAME
override val defaultEncoding = Encoding.PETSCII
@@ -26,7 +26,7 @@ class Cx16Target: ICompilationTarget,
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 FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE.toUInt()
override val STARTUP_CODE_RESERVED_SIZE = 20u
override val PROGRAM_LOAD_ADDRESS = 0x0801u
override val PROGRAM_MEMTOP_ADDRESS = 0x9f00u
@@ -37,7 +37,6 @@ class Cx16Target: ICompilationTarget,
override val BSSGOLDENRAM_END = 0x07ffu
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
@@ -87,7 +86,6 @@ class Cx16Target: ICompilationTarget,
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = CX16Zeropage(compilerOptions)
golden = GoldenRam(compilerOptions, BSSGOLDENRAM_START..BSSGOLDENRAM_END)
}
+3 -3
View File
@@ -18,8 +18,8 @@ data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, va
// and https://en.wikipedia.org/wiki/IEEE_754-1985
val flt = num.toDouble()
if (flt < FLOAT_MAX_NEGATIVE || flt > FLOAT_MAX_POSITIVE)
throw InternalCompilerException("floating point number out of 5-byte mflpt range: $this")
if (flt !in FLOAT_MAX_NEGATIVE..FLOAT_MAX_POSITIVE)
throw InternalCompilerException("floating point number out of 5-byte mflpt range: $flt")
if (flt == 0.0)
return zero
@@ -40,7 +40,7 @@ data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, va
return when {
exponent < 0 -> zero // underflow, use zero instead
exponent > 255 -> throw InternalCompilerException("floating point overflow: $this")
exponent > 255 -> throw InternalCompilerException("floating point overflow: $flt")
exponent == 0 -> zero
else -> {
val mantLong = mantissa.toLong()
@@ -7,11 +7,14 @@ import prog8.code.core.IMemSizer
internal class NormalMemSizer(val floatsize: Int): IMemSizer {
override fun memorySize(dt: DataType, numElements: Int?): Int {
if(dt.isArray) {
if(dt.isPointerArray)
return 2 * numElements!! // array of pointers is just array of uwords
else if(dt.isArray) {
if(numElements==null) return 2 // treat it as a pointer size
return when(dt.sub) {
BaseDataType.BOOL, BaseDataType.UBYTE, BaseDataType.BYTE -> numElements
BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.STR -> numElements * 2
BaseDataType.LONG -> numElements * 4
BaseDataType.FLOAT-> numElements * floatsize
BaseDataType.UNDEFINED -> throw IllegalArgumentException("undefined has no memory size")
else -> throw IllegalArgumentException("invalid sub type")
@@ -26,6 +29,8 @@ internal class NormalMemSizer(val floatsize: Int): IMemSizer {
dt.isByteOrBool -> 1 * (numElements ?: 1)
dt.isFloat -> floatsize * (numElements ?: 1)
dt.isLong -> 4 * (numElements ?: 1)
dt.isPointer -> 2 // pointer is just a uword
dt.isStructInstance -> dt.subType!!.memsize(this)
dt.isUndefined -> throw IllegalArgumentException("undefined has no memory size")
else -> 2 * (numElements ?: 1)
}
+2 -4
View File
@@ -8,7 +8,7 @@ import java.nio.file.Path
class PETTarget: ICompilationTarget,
IStringEncoding by Encoder(true),
IMemSizer by NormalMemSizer(Mflpt5.Companion.FLOAT_MEM_SIZE) {
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
override val name = NAME
override val defaultEncoding = Encoding.PETSCII
@@ -25,7 +25,7 @@ class PETTarget: ICompilationTarget,
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 FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE.toUInt()
override val STARTUP_CODE_RESERVED_SIZE = 20u
override val PROGRAM_LOAD_ADDRESS = 0x0401u
override val PROGRAM_MEMTOP_ADDRESS = 0x8000u
@@ -36,7 +36,6 @@ class PETTarget: ICompilationTarget,
override val BSSGOLDENRAM_END = 0u
override lateinit var zeropage: Zeropage
override lateinit var golden: GoldenRam
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
@@ -74,7 +73,6 @@ class PETTarget: ICompilationTarget,
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = PETZeropage(compilerOptions)
golden = GoldenRam(compilerOptions, UIntRange.EMPTY)
}
}
+14 -14
View File
@@ -3,6 +3,7 @@ package prog8.code.target
import prog8.code.core.*
import prog8.code.target.encodings.Encoder
import java.nio.file.Path
import kotlin.io.path.extension
import kotlin.io.path.isReadable
import kotlin.io.path.name
import kotlin.io.path.readText
@@ -27,7 +28,7 @@ class VMTarget: ICompilationTarget,
override val FLOAT_MAX_POSITIVE = Double.MAX_VALUE
override val FLOAT_MAX_NEGATIVE = -Double.MAX_VALUE
override val FLOAT_MEM_SIZE = VMTarget.FLOAT_MEM_SIZE
override val FLOAT_MEM_SIZE = VMTarget.FLOAT_MEM_SIZE.toUInt()
override val STARTUP_CODE_RESERVED_SIZE = 0u // not actually used
override val PROGRAM_LOAD_ADDRESS = 0u // not actually used
override val PROGRAM_MEMTOP_ADDRESS = 0xffffu // not actually used
@@ -37,7 +38,6 @@ class VMTarget: ICompilationTarget,
override val BSSGOLDENRAM_START = 0u // not actually used
override val BSSGOLDENRAM_END = 0u // not actually used
override lateinit var zeropage: Zeropage // not actually used
override lateinit var golden: GoldenRam // not actually used
override fun getFloatAsmBytes(num: Number): String {
// little endian binary representation
@@ -68,35 +68,33 @@ class VMTarget: ICompilationTarget,
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path, quiet: Boolean) {
launchEmulatorWithTrace(programNameWithPath, quiet, traceEnabled = false)
}
fun launchEmulatorWithTrace(programNameWithPath: Path, quiet: Boolean, traceEnabled: Boolean) {
if(!quiet)
println("\nStarting Virtual Machine...")
// to not have external module dependencies in our own module, we launch the virtual machine via reflection
val vm = Class.forName("prog8.vm.VmRunner").getDeclaredConstructor().newInstance() as IVirtualMachineRunner
val filename = programNameWithPath.name
if(programNameWithPath.isReadable()) {
vm.runProgram(programNameWithPath.readText(), quiet)
} else {
val withExt = programNameWithPath.resolveSibling("$filename.p8ir")
if(withExt.isReadable())
vm.runProgram(withExt.readText(), quiet)
else
throw java.nio.file.NoSuchFileException(withExt.name, null, "not a .p8ir file")
}
val withExt = if(programNameWithPath.extension=="p8ir") programNameWithPath else programNameWithPath.resolveSibling("${programNameWithPath.name}.p8ir")
if(withExt.isReadable())
vm.runProgram(withExt.readText(), quiet, traceEnabled)
else
throw java.nio.file.NoSuchFileException(withExt.name, null, "not a .p8ir file")
}
override fun isIOAddress(address: UInt): Boolean = false
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = VirtualZeropage(compilerOptions)
golden = GoldenRam(compilerOptions, UIntRange.EMPTY)
}
}
interface IVirtualMachineRunner {
fun runProgram(irSource: String, quiet: Boolean)
fun runProgram(irSource: String, quiet: Boolean, traceEnabled: Boolean = false)
}
private class VirtualZeropage(options: CompilationOptions): Zeropage(options) {
@@ -108,4 +106,6 @@ private class VirtualZeropage(options: CompilationOptions): Zeropage(options) {
get() = throw IllegalStateException("virtual shouldn't use this zeropage variable")
override val SCRATCH_W2: UInt
get() = throw IllegalStateException("virtual shouldn't use this zeropage variable")
override val SCRATCH_PTR: UInt
get() = throw IllegalStateException("virtual shouldn't use this zeropage variable")
}
@@ -1,9 +1,6 @@
package prog8.code.target.zp
import prog8.code.core.CompilationOptions
import prog8.code.core.InternalCompilerException
import prog8.code.core.Zeropage
import prog8.code.core.ZeropageType
import prog8.code.core.*
// reference: "Mapping the C128" zeropage chapter.
@@ -14,6 +11,7 @@ class C128Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_REG = 0x75u // temp storage for a register byte, must be B1+1
override val SCRATCH_W1 = 0xfbu // temp storage 1 for a word $fb+$fc
override val SCRATCH_W2 = 0xfdu // temp storage 2 for a word $fd+$fe
override val SCRATCH_PTR = 0x0bu // temp storage for a pointer $0b+$0c
init {
if (options.floats && options.zeropage !in arrayOf(
@@ -45,13 +43,13 @@ class C128Zeropage(options: CompilationOptions) : Zeropage(options) {
0xb0u, 0xb1u, 0xb4u, 0xb5u, 0xb6u
))
// if(options.zeropage==ZeropageType.BASICSAFE) {
// can also clobber the FP locations (unconditionally, because the C128 target doesn't support floating point calculations in prog8 at this time0
free.addAll(arrayOf(0x14u, 0x28u, 0x29u, 0x2au, 0x2bu, 0x2cu,
0x50u, 0x51u, 0x52u, 0x53u, 0x54u, 0x59u, 0x5au, 0x5bu, 0x5cu, 0x5du, 0x5eu, 0x5fu, 0x60u, 0x61u, 0x62u,
0x63u, 0x64u, 0x65u, 0x66u, 0x67u, 0x68u,
0x6au, 0x6bu, 0x6cu, 0x6du, 0x6eu, 0x6fu, 0x71u))
// }
// C128 doesn't support floating point calculations in Prog8, so we can use the FP zeropage locations
// for general variables in both BASICSAFE and FLOATSAFE modes.
// These addresses are normally used by the C128 BASIC floating point routines.
free.addAll(arrayOf(0x14u, 0x28u, 0x29u, 0x2au, 0x2bu, 0x2cu,
0x50u, 0x51u, 0x52u, 0x53u, 0x54u, 0x59u, 0x5au, 0x5bu, 0x5cu, 0x5du, 0x5eu, 0x5fu, 0x60u, 0x61u, 0x62u,
0x63u, 0x64u, 0x65u, 0x66u, 0x67u, 0x68u,
0x6au, 0x6bu, 0x6cu, 0x6du, 0x6eu, 0x6fu, 0x71u))
}
ZeropageType.DONTUSE -> {
free.clear() // don't use zeropage at all
@@ -63,6 +61,43 @@ class C128Zeropage(options: CompilationOptions) : Zeropage(options) {
free.addAll(distinctFree)
removeReservedFromFreePool()
if(options.zeropage==ZeropageType.FULL || options.zeropage==ZeropageType.KERNALSAFE) {
// C128 DESIGN: Virtual registers are ONLY allocated when sufficient zeropage space exists.
// C128 has limited zeropage space in BASICSAFE/FLOATSAFE modes.
// Virtual registers are only allocated in FULL and KERNALSAFE modes where space permits.
allocateCx16VirtualRegisters()
}
retainAllowed()
}
private fun allocateCx16VirtualRegisters() {
// Note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped* elsewhere to fixed addresses.
// However, to be able for the compiler to "see" them as zeropage variables, we have to register them here as well.
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
val base = 0x0a // Unfortunately it cannot be the same as on the Commander X16 ($02).
for(reg in 0..15) {
allocatedVariables["cx16.r${reg}"] = VarAllocation((base+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
allocatedVariables["cx16.r${reg}s"] = VarAllocation((base+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
allocatedVariables["cx16.r${reg}L"] = VarAllocation((base+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
allocatedVariables["cx16.r${reg}H"] = VarAllocation((base+1+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
allocatedVariables["cx16.r${reg}sL"] = VarAllocation((base+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
allocatedVariables["cx16.r${reg}sH"] = VarAllocation((base+1+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
allocatedVariables["cx16.r${reg}bL"] = VarAllocation((base+reg*2).toUInt(), DataType.BOOL, 1) // cx16.r0bL .. cx16.r15bL
allocatedVariables["cx16.r${reg}bH"] = VarAllocation((base+1+reg*2).toUInt(), DataType.BOOL, 1) // cx16.r0bH .. cx16.r15bH
free.remove((base+reg*2).toUInt())
free.remove((base+1+reg*2).toUInt())
}
// 32 bits combined register pairs cx16.r0r1 .. cx16.r14r15
allocatedVariables["cx16.r0r1sl"] = VarAllocation((base+0*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r2r3sl"] = VarAllocation((base+1*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r4r5sl"] = VarAllocation((base+2*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r6r7sl"] = VarAllocation((base+3*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r8r9sl"] = VarAllocation((base+4*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r10r11sl"] = VarAllocation((base+5*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r12r13sl"] = VarAllocation((base+6*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r14r15sl"] = VarAllocation((base+7*4).toUInt(), DataType.LONG, 4)
}
}
@@ -9,6 +9,7 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_REG = 0x03u // temp storage for a register byte, must be B1+1
override val SCRATCH_W1 = 0xfbu // temp storage 1 for a word $fb+$fc
override val SCRATCH_W2 = 0xfdu // temp storage 2 for a word $fd+$fe
override val SCRATCH_PTR = 0x04u // temp storage for a pointer $04+$05
init {
@@ -21,7 +22,6 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
if (options.zeropage == ZeropageType.FULL) {
free.addAll(0x02u..0xffu)
free.removeAll(arrayOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1+1u, SCRATCH_W2, SCRATCH_W2+1u))
free.removeAll(arrayOf(0xa0u, 0xa1u, 0xa2u, 0x91u, 0xc0u, 0xc5u, 0xcbu, 0xf5u, 0xf6u)) // these are updated by IRQ
} else {
if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
@@ -57,7 +57,7 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
// add the free Zp addresses
// these are valid for the C-64 but allow BASIC to keep running fully *as long as you don't use tape I/O*
free.addAll(arrayOf(0x02, 0x03, 0x04, 0x05, 0x06, 0x0a, 0x0e,
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa6,
0x92, 0x96, 0x9c, 0x9e, 0x9f, 0xa6,
0xb0, 0xb1, 0xbe, 0xbf, 0xf9).map{it.toUInt()})
} else {
// don't use the zeropage at all
@@ -72,7 +72,10 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
removeReservedFromFreePool()
if(options.zeropage==ZeropageType.FULL || options.zeropage==ZeropageType.KERNALSAFE) {
// in these cases there is enough space on the zero page to stick the cx16 virtual registers in there as well.
// C64 DESIGN: Virtual registers are ONLY allocated when sufficient zeropage space exists.
// C64 has limited zeropage space compared to CX16. In BASICSAFE and FLOATSAFE modes,
// there isn't enough contiguous space for all 32 bytes of virtual register variants.
// Virtual registers are only allocated in FULL and KERNALSAFE modes where space permits.
allocateCx16VirtualRegisters()
}
@@ -83,16 +86,28 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
// Note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped* elsewhere to fixed addresses.
// However, to be able for the compiler to "see" them as zeropage variables, we have to register them here as well.
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
// The base addres is $04. Unfortunately it cannot be the same as on the Commander X16 ($02).
val base = 0x04 // Unfortunately it cannot be the same as on the Commander X16 ($02).
for(reg in 0..15) {
allocatedVariables["cx16.r${reg}"] = VarAllocation((4+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
allocatedVariables["cx16.r${reg}s"] = VarAllocation((4+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
allocatedVariables["cx16.r${reg}L"] = VarAllocation((4+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
allocatedVariables["cx16.r${reg}H"] = VarAllocation((5+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
allocatedVariables["cx16.r${reg}sL"] = VarAllocation((4+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
allocatedVariables["cx16.r${reg}sH"] = VarAllocation((5+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
free.remove((4+reg*2).toUInt())
free.remove((5+reg*2).toUInt())
allocatedVariables["cx16.r${reg}"] = VarAllocation((base+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
allocatedVariables["cx16.r${reg}s"] = VarAllocation((base+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
allocatedVariables["cx16.r${reg}L"] = VarAllocation((base+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
allocatedVariables["cx16.r${reg}H"] = VarAllocation((base+1+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
allocatedVariables["cx16.r${reg}sL"] = VarAllocation((base+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
allocatedVariables["cx16.r${reg}sH"] = VarAllocation((base+1+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
allocatedVariables["cx16.r${reg}bL"] = VarAllocation((base+reg*2).toUInt(), DataType.BOOL, 1) // cx16.r0bL .. cx16.r15bL
allocatedVariables["cx16.r${reg}bH"] = VarAllocation((base+1+reg*2).toUInt(), DataType.BOOL, 1) // cx16.r0bH .. cx16.r15bH
free.remove((base+reg*2).toUInt())
free.remove((base+1+reg*2).toUInt())
}
// 32 bits combined register pairs cx16.r0r1 .. cx16.r14r15
allocatedVariables["cx16.r0r1sl"] = VarAllocation((base+0*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r2r3sl"] = VarAllocation((base+1*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r4r5sl"] = VarAllocation((base+2*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r6r7sl"] = VarAllocation((base+3*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r8r9sl"] = VarAllocation((base+4*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r10r11sl"] = VarAllocation((base+5*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r12r13sl"] = VarAllocation((base+6*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r14r15sl"] = VarAllocation((base+7*4).toUInt(), DataType.LONG, 4)
}
}
@@ -9,6 +9,7 @@ class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_REG = 0x7bu // temp storage for a register byte, must be B1+1
override val SCRATCH_W1 = 0x7cu // temp storage 1 for a word $7c+$7d
override val SCRATCH_W2 = 0x7eu // temp storage 2 for a word $7e+$7f
override val SCRATCH_PTR = 0x22u // temp storage for a pointer $22+$23
init {
@@ -21,41 +22,47 @@ class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
// the addresses 0x02 to 0x21 (inclusive) are taken for sixteen virtual 16-bit api registers.
synchronized(this) {
when (options.zeropage) {
ZeropageType.FULL -> {
free.addAll(0x22u..0xffu)
}
ZeropageType.KERNALSAFE -> {
free.addAll(0x22u..0x7fu)
free.addAll(0xa9u..0xffu)
}
ZeropageType.FLOATSAFE -> {
free.addAll(0x22u..0x7fu)
free.addAll(0xd4u..0xffu)
}
ZeropageType.BASICSAFE -> {
free.addAll(0x22u..0x7fu)
}
ZeropageType.DONTUSE -> {
free.clear() // don't use zeropage at all
}
// Initialize free list - no synchronization needed during construction
// (object reference doesn't escape until fully constructed)
when (options.zeropage) {
ZeropageType.FULL -> {
free.addAll(0x22u..0xffu)
}
ZeropageType.KERNALSAFE -> {
free.addAll(0x22u..0x7fu)
free.addAll(0xa9u..0xffu)
}
ZeropageType.FLOATSAFE -> {
free.addAll(0x22u..0x7fu)
free.addAll(0xd4u..0xffu)
}
ZeropageType.BASICSAFE -> {
free.addAll(0x22u..0x7fu)
}
ZeropageType.DONTUSE -> {
free.clear() // don't use zeropage at all
}
val distinctFree = free.distinct()
free.clear()
free.addAll(distinctFree)
removeReservedFromFreePool()
allocateCx16VirtualRegisters()
retainAllowed()
}
val distinctFree = free.distinct()
free.clear()
free.addAll(distinctFree)
removeReservedFromFreePool()
allocateCx16VirtualRegisters()
retainAllowed()
}
private fun allocateCx16VirtualRegisters() {
// Note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped* elsewhere to fixed addresses.
// However, to be able for the compiler to "see" them as zeropage variables, we have to register them here as well.
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
// CX16 DESIGN: Virtual registers are ALWAYS allocated in zeropage for all ZeropageType modes.
// The Commander X16 has ample zeropage space (0x22-0x7F in BASICSAFE, up to 0x22-0xFF in FULL),
// so virtual registers can always fit. This ensures consistent behavior across all modes.
// Virtual registers occupy addresses 0x02-0x21 (32 bytes for r0-r15 variants).
//
// Note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped*
// elsewhere to fixed addresses. However, to be able for the compiler to "see" them as zeropage
// variables, we have to register them here as well. This is important because the compiler
// sometimes treats ZP variables more efficiently (for example if it's a pointer).
for(reg in 0..15) {
allocatedVariables["cx16.r${reg}"] = VarAllocation((2+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
allocatedVariables["cx16.r${reg}s"] = VarAllocation((2+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
@@ -63,6 +70,19 @@ class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
allocatedVariables["cx16.r${reg}H"] = VarAllocation((3+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
allocatedVariables["cx16.r${reg}sL"] = VarAllocation((2+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
allocatedVariables["cx16.r${reg}sH"] = VarAllocation((3+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
allocatedVariables["cx16.r${reg}bL"] = VarAllocation((2+reg*2).toUInt(), DataType.BOOL, 1) // cx16.r0bL .. cx16.r15bL
allocatedVariables["cx16.r${reg}bH"] = VarAllocation((3+reg*2).toUInt(), DataType.BOOL, 1) // cx16.r0bH .. cx16.r15bH
}
// 32 bits combined register pairs cx16.r0r1 .. cx16.r14r15
allocatedVariables["cx16.r0r1sl"] = VarAllocation((2+0*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r2r3sl"] = VarAllocation((2+1*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r4r5sl"] = VarAllocation((2+2*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r6r7sl"] = VarAllocation((2+3*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r8r9sl"] = VarAllocation((2+4*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r10r11sl"] = VarAllocation((2+5*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r12r13sl"] = VarAllocation((2+6*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r14r15sl"] = VarAllocation((2+7*4).toUInt(), DataType.LONG, 4)
}
}
@@ -10,6 +10,8 @@ class ConfigurableZeropage(
override val SCRATCH_REG: UInt, // temp storage for a register byte, must be B1+1
override val SCRATCH_W1: UInt, // temp storage 1 for a word
override val SCRATCH_W2: UInt, // temp storage 2 for a word
override val SCRATCH_PTR: UInt, // temp storage for a pointer
val virtualRegistersStart: UInt, // location of 32 bytes for the r0-r15 virtual registers
basicsafe: List<UIntRange>,
kernalsafe: List<UIntRange>,
@@ -55,7 +57,24 @@ class ConfigurableZeropage(
allocatedVariables["cx16.r${reg}H"] = VarAllocation(address+1u, DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
allocatedVariables["cx16.r${reg}sL"] = VarAllocation(address, DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
allocatedVariables["cx16.r${reg}sH"] = VarAllocation(address+1u, DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
allocatedVariables["cx16.r${reg}bL"] = VarAllocation(address, DataType.BOOL, 1) // cx16.r0bL .. cx16.r15bL
allocatedVariables["cx16.r${reg}bH"] = VarAllocation(address+1u, DataType.BOOL, 1) // cx16.r0bH .. cx16.r15bH
free.remove(address)
free.remove(address+1u)
}
}
if(virtualRegistersStart<0xffu) {
// 32 bits combined register pairs cx16.r0r1 .. cx16.r14r15
val start = virtualRegistersStart.toInt()
allocatedVariables["cx16.r0r1sl"] = VarAllocation((start + 0 * 4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r2r3sl"] = VarAllocation((start + 1 * 4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r4r5sl"] = VarAllocation((start + 2 * 4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r6r7sl"] = VarAllocation((start + 3 * 4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r8r9sl"] = VarAllocation((start + 4 * 4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r10r11sl"] = VarAllocation((start + 5 * 4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r12r13sl"] = VarAllocation((start + 6 * 4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r14r15sl"] = VarAllocation((start + 7 * 4).toUInt(), DataType.LONG, 4)
}
}
}
@@ -1,9 +1,6 @@
package prog8.code.target.zp
import prog8.code.core.CompilationOptions
import prog8.code.core.InternalCompilerException
import prog8.code.core.Zeropage
import prog8.code.core.ZeropageType
import prog8.code.core.*
// reference: http://www.zimmers.net/cbmpics/cbm/PETx/petmem.txt
@@ -14,6 +11,7 @@ class PETZeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_REG = 0xb4u // temp storage for a register byte, must be B1+1
override val SCRATCH_W1 = 0xb6u // temp storage 1 for a word
override val SCRATCH_W2 = 0xb8u // temp storage 2 for a word
override val SCRATCH_PTR = 0xb1u // temp storage for a pointer $b1+$b2
init {
if (options.floats && options.zeropage !in arrayOf(
@@ -34,7 +32,7 @@ class PETZeropage(options: CompilationOptions) : Zeropage(options) {
}
ZeropageType.FLOATSAFE,
ZeropageType.BASICSAFE -> {
free.addAll(0xb3u..0xbau) // TODO more?
free.addAll(0xb1u..0xbau) // TODO more?
}
ZeropageType.DONTUSE -> {
free.clear() // don't use zeropage at all
@@ -46,6 +44,43 @@ class PETZeropage(options: CompilationOptions) : Zeropage(options) {
free.addAll(distinctFree)
removeReservedFromFreePool()
if(options.zeropage==ZeropageType.FULL || options.zeropage==ZeropageType.KERNALSAFE) {
// PET DESIGN: Virtual registers are ONLY allocated when sufficient zeropage space exists.
// PET has very limited zeropage space in BASICSAFE/FLOATSAFE modes (only 0xB1-0xBA).
// Virtual registers are only allocated in FULL and KERNALSAFE modes where space permits.
allocateCx16VirtualRegisters()
}
retainAllowed()
}
private fun allocateCx16VirtualRegisters() {
// Note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped* elsewhere to fixed addresses.
// However, to be able for the compiler to "see" them as zeropage variables, we have to register them here as well.
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
val base = 0x04 // Unfortunately it cannot be the same as on the Commander X16 ($02). TODO: is this valid on PET?
for(reg in 0..15) {
allocatedVariables["cx16.r${reg}"] = VarAllocation((base+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
allocatedVariables["cx16.r${reg}s"] = VarAllocation((base+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
allocatedVariables["cx16.r${reg}L"] = VarAllocation((base+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
allocatedVariables["cx16.r${reg}H"] = VarAllocation((base+1+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
allocatedVariables["cx16.r${reg}sL"] = VarAllocation((base+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
allocatedVariables["cx16.r${reg}sH"] = VarAllocation((base+1+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
allocatedVariables["cx16.r${reg}bL"] = VarAllocation((base+reg*2).toUInt(), DataType.BOOL, 1) // cx16.r0bL .. cx16.r15bL
allocatedVariables["cx16.r${reg}bH"] = VarAllocation((base+1+reg*2).toUInt(), DataType.BOOL, 1) // cx16.r0bH .. cx16.r15bH
free.remove((base+reg*2).toUInt())
free.remove((base+1+reg*2).toUInt())
}
// 32 bits combined register pairs cx16.r0r1 .. cx16.r14r15
allocatedVariables["cx16.r0r1sl"] = VarAllocation((base+0*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r2r3sl"] = VarAllocation((base+1*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r4r5sl"] = VarAllocation((base+2*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r6r7sl"] = VarAllocation((base+3*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r8r9sl"] = VarAllocation((base+4*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r10r11sl"] = VarAllocation((base+5*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r12r13sl"] = VarAllocation((base+6*4).toUInt(), DataType.LONG, 4)
allocatedVariables["cx16.r14r15sl"] = VarAllocation((base+7*4).toUInt(), DataType.LONG, 4)
}
}
@@ -0,0 +1,67 @@
package prog8tests.helpers
import prog8.code.core.*
/**
* Dummy IMemSizer implementation for testing.
* Returns standard memory sizes for all data types.
*/
object DummyMemsizer : IMemSizer {
override fun memorySize(dt: DataType, numElements: Int?): Int {
if (dt.isPointerArray)
return 2 * numElements!!
else if (dt.isArray) {
require(numElements != null)
return when (dt.sub) {
BaseDataType.BOOL, BaseDataType.BYTE, BaseDataType.UBYTE -> numElements
BaseDataType.UWORD, BaseDataType.WORD -> numElements * 2
BaseDataType.LONG -> numElements * 4
BaseDataType.FLOAT -> numElements * 5
else -> throw IllegalArgumentException("invalid sub type")
}
}
return when {
dt.isByteOrBool -> 1 * (numElements ?: 1)
dt.isLong -> 4 * (numElements ?: 1)
dt.isFloat -> 5 * (numElements ?: 1)
else -> 2 * (numElements ?: 1)
}
}
override fun memorySize(dt: BaseDataType): Int {
return memorySize(DataType.forDt(dt), null)
}
}
/**
* Dummy IStringEncoding implementation for testing.
* Returns empty list/string for all operations.
*/
object DummyStringEncoder : IStringEncoding {
override val defaultEncoding: Encoding = Encoding.ISO
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
return emptyList()
}
override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String {
return ""
}
}
/**
* ASCII IStringEncoding implementation for testing.
* Encodes/decodes using simple ASCII mapping.
*/
object AsciiStringEncoder : IStringEncoding {
override val defaultEncoding: Encoding = Encoding.ISO
override fun encodeString(str: String, encoding: Encoding): List<UByte> =
str.map { it.code.toUByte() }
override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String =
bytes.joinToString("") { it.toInt().toChar().toString() }
}
@@ -3,7 +3,17 @@ package prog8tests.helpers
import prog8.code.core.IErrorReporter
import prog8.code.core.Position
internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors: Boolean=true, private val keepMessagesAfterReporting: Boolean=false): IErrorReporter {
/**
* Test implementation of IErrorReporter that collects errors/warnings/infos in memory.
*
* @param throwExceptionAtReportIfErrors If true, report() will throw an exception when errors exist
* @param keepMessagesAfterReporting If false, clear() is called after report()
*/
class ErrorReporterForTests(
private val throwExceptionAtReportIfErrors: Boolean = true,
private val keepMessagesAfterReporting: Boolean = false
) : IErrorReporter {
val errors = mutableListOf<String>()
val warnings = mutableListOf<String>()
@@ -11,37 +21,42 @@ internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors:
override fun err(msg: String, position: Position) {
val text = "${position.toClickableStr()} $msg"
if(text !in errors)
if (text !in errors)
errors.add(text)
}
override fun warn(msg: String, position: Position) {
val text = "${position.toClickableStr()} $msg"
if(text !in warnings)
if (text !in warnings)
warnings.add(text)
}
override fun info(msg: String, position: Position) {
val text = "${position.toClickableStr()} $msg"
if(text !in infos)
if (text !in infos)
infos.add(text)
}
override fun undefined(symbol: List<String>, position: Position) {
override fun undefined(symbol: List<String>, suggestImport: Boolean, position: Position) {
err("undefined symbol: ${symbol.joinToString(".")}", position)
}
override fun noErrors(): Boolean = errors.isEmpty()
override fun noErrorForLine(position: Position) = !errors.any { ":${position.line}:" in it }
override fun printSingleError(errormessage: String) { /* prints nothing in tests */ }
override fun noErrors(): Boolean = errors.isEmpty()
override fun noErrorForLine(position: Position): Boolean =
!errors.any { ":${position.line}:" in it }
override fun printSingleError(errormessage: String) {
// prints nothing in tests
}
override fun report() {
infos.forEach { println("UNITTEST COMPILATION REPORT: INFO: $it") }
warnings.forEach { println("UNITTEST COMPILATION REPORT: WARNING: $it") }
errors.forEach { println("UNITTEST COMPILATION REPORT: ERROR: $it") }
if(throwExceptionAtReportIfErrors)
if (throwExceptionAtReportIfErrors)
finalizeNumErrors(errors.size, warnings.size, infos.size)
if(!keepMessagesAfterReporting) {
if (!keepMessagesAfterReporting) {
clear()
}
}
@@ -0,0 +1,109 @@
package prog8tests.codecore
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import prog8.code.core.escape
import prog8.code.core.toHex
import prog8.code.core.unescape
/**
* Unit tests for conversion functions in codeCore.
* Focus on toHex boundaries and escape/unescape roundtrips.
*/
class TestConversions: FunSpec({
// ============================================================================
// toHex() Tests
// ============================================================================
test("toHex for 0-15 (decimal output)") {
0.toHex() shouldBe "0"
10.toHex() shouldBe "10"
15.toHex() shouldBe "15"
}
test("toHex for 16-255 (hex with $)") {
16.toHex() shouldBe "\$10"
255.toHex() shouldBe "\$ff"
}
test("toHex for 256-65535 (4 digit hex)") {
256.toHex() shouldBe "\$0100"
65535.toHex() shouldBe "\$ffff"
}
test("toHex for larger numbers (8 digit hex)") {
65536.toHex() shouldBe "\$00010000"
0x12345678.toHex() shouldBe "\$12345678"
}
test("toHex for negative numbers") {
(-1).toHex() shouldBe "-1"
(-255).toHex() shouldBe "-\$ff"
Int.MIN_VALUE.toHex() shouldBe "\$80000000"
}
test("UInt.toHex") {
0u.toHex() shouldBe "0"
15u.toHex() shouldBe "15"
255u.toHex() shouldBe "\$ff"
65535u.toHex() shouldBe "\$ffff"
}
// ============================================================================
// escape() Tests
// ============================================================================
test("escape for plain string") {
"hello".escape() shouldBe "hello"
}
test("escape for tab and newline") {
"hello\tworld".escape() shouldBe "hello\\tworld"
"hello\nworld".escape() shouldBe "hello\\nworld"
}
test("escape for double quote") {
"hello\"world".escape() shouldBe "hello\\\"world"
}
test("escape for unicode outside passthrough range") {
"€".escape() shouldBe "\\u20ac"
}
test("escape for passthrough characters (\\u8000-\\u80ff)") {
"\u8000".escape() shouldBe "\\x00"
"\u80ff".escape() shouldBe "\\xff"
}
// ============================================================================
// unescape() Tests
// ============================================================================
test("unescape for plain string") {
"hello".unescape() shouldBe "hello"
}
test("unescape for tab and newline") {
"hello\\tworld".unescape() shouldBe "hello\tworld"
"hello\\nworld".unescape() shouldBe "hello\nworld"
}
test("unescape for double quote") {
"hello\\\"world".unescape() shouldBe "hello\"world"
}
test("unescape for unicode") {
"\\u0041".unescape() shouldBe "A"
"\\u20ac".unescape() shouldBe "€"
}
// ============================================================================
// Roundtrip Tests
// ============================================================================
test("escape then unescape roundtrip") {
val original = "hello\t\nworld\"test"
original.escape().unescape() shouldBe original
}
})
@@ -0,0 +1,296 @@
package prog8tests.codecore
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.booleans.shouldBeFalse
import io.kotest.matchers.booleans.shouldBeTrue
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import prog8.code.core.*
/**
* Unit tests for DataType and BaseDataType classes in codeCore.
* Focus on type predicates, relationships, and key operations.
*/
class TestDataType: FunSpec({
val dummyMemSizer = object : IMemSizer {
override fun memorySize(dt: DataType, numElements: Int?): Int {
if (dt.isPointerArray) return 2 * numElements!!
if (dt.isArray || dt.isSplitWordArray) {
require(numElements != null)
return when (dt.sub) {
BaseDataType.BOOL, BaseDataType.BYTE, BaseDataType.UBYTE -> numElements
BaseDataType.UWORD, BaseDataType.WORD -> numElements * 2
BaseDataType.LONG -> numElements * 4
BaseDataType.FLOAT -> numElements * 5
else -> throw IllegalArgumentException("invalid sub type")
}
}
return when {
dt.isByteOrBool -> 1 * (numElements ?: 1)
dt.isLong -> 4 * (numElements ?: 1)
dt.isFloat -> 5 * (numElements ?: 1)
else -> 2 * (numElements ?: 1)
}
}
override fun memorySize(dt: BaseDataType): Int {
return memorySize(DataType.forDt(dt), null)
}
}
// ============================================================================
// BaseDataType Extension Property Tests
// ============================================================================
test("isByte extension") {
BaseDataType.UBYTE.isByte.shouldBeTrue()
BaseDataType.BYTE.isByte.shouldBeTrue()
BaseDataType.UWORD.isByte.shouldBeFalse()
BaseDataType.WORD.isByte.shouldBeFalse()
}
test("isByteOrBool extension") {
BaseDataType.UBYTE.isByteOrBool.shouldBeTrue()
BaseDataType.BYTE.isByteOrBool.shouldBeTrue()
BaseDataType.BOOL.isByteOrBool.shouldBeTrue()
BaseDataType.UWORD.isByteOrBool.shouldBeFalse()
}
test("isWord extension") {
BaseDataType.UWORD.isWord.shouldBeTrue()
BaseDataType.WORD.isWord.shouldBeTrue()
BaseDataType.UBYTE.isWord.shouldBeFalse()
}
test("isInteger extension") {
BaseDataType.UBYTE.isInteger.shouldBeTrue()
BaseDataType.WORD.isInteger.shouldBeTrue()
BaseDataType.LONG.isInteger.shouldBeTrue()
BaseDataType.FLOAT.isInteger.shouldBeFalse()
}
test("isNumeric extension") {
BaseDataType.UBYTE.isNumeric.shouldBeTrue()
BaseDataType.FLOAT.isNumeric.shouldBeTrue()
BaseDataType.BOOL.isNumeric.shouldBeFalse()
}
test("isSigned extension") {
BaseDataType.BYTE.isSigned.shouldBeTrue()
BaseDataType.WORD.isSigned.shouldBeTrue()
BaseDataType.LONG.isSigned.shouldBeTrue()
BaseDataType.UBYTE.isSigned.shouldBeFalse()
}
test("isArray extension") {
BaseDataType.ARRAY.isArray.shouldBeTrue()
BaseDataType.ARRAY_SPLITW.isArray.shouldBeTrue()
BaseDataType.ARRAY_POINTER.isArray.shouldBeTrue()
BaseDataType.UBYTE.isArray.shouldBeFalse()
}
test("isPointer extension") {
BaseDataType.POINTER.isPointer.shouldBeTrue()
BaseDataType.UBYTE.isPointer.shouldBeFalse()
}
test("isSplitWordArray extension") {
BaseDataType.ARRAY_SPLITW.isSplitWordArray.shouldBeTrue()
BaseDataType.ARRAY_POINTER.isSplitWordArray.shouldBeTrue()
BaseDataType.ARRAY.isSplitWordArray.shouldBeFalse()
}
// ============================================================================
// DataType Companion Object Tests
// ============================================================================
test("DataType.forDt creates simple types") {
DataType.forDt(BaseDataType.UBYTE) shouldBe DataType.UBYTE
DataType.forDt(BaseDataType.WORD) shouldBe DataType.WORD
DataType.forDt(BaseDataType.LONG) shouldBe DataType.LONG
DataType.forDt(BaseDataType.FLOAT) shouldBe DataType.FLOAT
}
test("DataType.forDt throws for struct instance") {
shouldThrow<NotImplementedError> {
DataType.forDt(BaseDataType.STRUCT_INSTANCE)
}
}
test("DataType.arrayFor creates array types") {
val ubyteArray = DataType.arrayFor(BaseDataType.UBYTE)
ubyteArray.base shouldBe BaseDataType.ARRAY
ubyteArray.sub shouldBe BaseDataType.UBYTE
val wordArraySplit = DataType.arrayFor(BaseDataType.WORD)
wordArraySplit.base shouldBe BaseDataType.ARRAY_SPLITW
wordArraySplit.sub shouldBe BaseDataType.WORD
}
test("DataType.pointer creates pointer types") {
val ptrByte = DataType.pointer(BaseDataType.UBYTE)
ptrByte.base shouldBe BaseDataType.POINTER
ptrByte.sub shouldBe BaseDataType.UBYTE
}
// ============================================================================
// DataType Property Tests
// ============================================================================
test("DataType.isBasic property") {
DataType.UBYTE.isBasic.shouldBeTrue()
DataType.WORD.isBasic.shouldBeTrue()
DataType.STR.isBasic.shouldBeFalse()
DataType.arrayFor(BaseDataType.UBYTE).isBasic.shouldBeFalse()
}
test("DataType.isByte property") {
DataType.UBYTE.isByte.shouldBeTrue()
DataType.BYTE.isByte.shouldBeTrue()
DataType.WORD.isByte.shouldBeFalse()
}
test("DataType.isNumeric property") {
DataType.UBYTE.isNumeric.shouldBeTrue()
DataType.FLOAT.isNumeric.shouldBeTrue()
DataType.BOOL.isNumeric.shouldBeFalse()
DataType.STR.isNumeric.shouldBeFalse()
}
test("DataType.isArray property") {
DataType.arrayFor(BaseDataType.UBYTE).isArray.shouldBeTrue()
DataType.UBYTE.isArray.shouldBeFalse()
}
test("DataType.isPointer property") {
DataType.pointer(BaseDataType.UBYTE).isPointer.shouldBeTrue()
DataType.UBYTE.isPointer.shouldBeFalse()
}
test("DataType.isString property") {
DataType.STR.isString.shouldBeTrue()
DataType.UBYTE.isString.shouldBeFalse()
}
test("DataType.isPassByRef property") {
DataType.STR.isPassByRef.shouldBeTrue()
DataType.arrayFor(BaseDataType.UBYTE).isPassByRef.shouldBeTrue()
DataType.UBYTE.isPassByRef.shouldBeFalse()
DataType.pointer(BaseDataType.UBYTE).isPassByRef.shouldBeFalse()
}
// ============================================================================
// DataType Method Tests
// ============================================================================
test("DataType.elementType returns element type for arrays") {
DataType.arrayFor(BaseDataType.UBYTE).elementType() shouldBe DataType.UBYTE
DataType.arrayFor(BaseDataType.WORD).elementType() shouldBe DataType.WORD
DataType.STR.elementType() shouldBe DataType.UBYTE
}
test("DataType.elementType throws for non-arrays") {
shouldThrow<IllegalArgumentException> {
DataType.UBYTE.elementType()
}
}
test("DataType.dereference for pointers") {
DataType.pointer(BaseDataType.UBYTE).dereference() shouldBe DataType.UBYTE
DataType.pointer(BaseDataType.WORD).dereference() shouldBe DataType.WORD
DataType.UWORD.dereference() shouldBe DataType.UBYTE
}
test("DataType.size with memsizer") {
DataType.UBYTE.size(dummyMemSizer) shouldBe 1
DataType.WORD.size(dummyMemSizer) shouldBe 2
DataType.LONG.size(dummyMemSizer) shouldBe 4
DataType.FLOAT.size(dummyMemSizer) shouldBe 5
}
// ============================================================================
// DataType Comparison Tests
// ============================================================================
test("DataType equals for same types") {
DataType.UBYTE shouldBe DataType.UBYTE
DataType.WORD shouldBe DataType.WORD
DataType.STR shouldBe DataType.STR
}
test("DataType equals for different types") {
DataType.UBYTE shouldNotBe DataType.BYTE
DataType.UBYTE shouldNotBe DataType.WORD
}
test("BaseDataType.largerSizeThan") {
BaseDataType.WORD.largerSizeThan(BaseDataType.UBYTE).shouldBeTrue()
BaseDataType.LONG.largerSizeThan(BaseDataType.WORD).shouldBeTrue()
BaseDataType.UBYTE.largerSizeThan(BaseDataType.WORD).shouldBeFalse()
}
test("BaseDataType.equalsSize") {
BaseDataType.UBYTE.equalsSize(BaseDataType.UBYTE).shouldBeTrue()
BaseDataType.UBYTE.equalsSize(BaseDataType.BYTE).shouldBeTrue()
BaseDataType.WORD.equalsSize(BaseDataType.UWORD).shouldBeTrue()
BaseDataType.UBYTE.equalsSize(BaseDataType.WORD).shouldBeFalse()
}
// ============================================================================
// DataType.isAssignableTo Tests
// ============================================================================
test("bool assignability") {
DataType.BOOL.isAssignableTo(DataType.BOOL).shouldBeTrue()
DataType.BOOL.isAssignableTo(DataType.UBYTE).shouldBeFalse()
}
test("ubyte assignability") {
DataType.UBYTE.isAssignableTo(DataType.UBYTE).shouldBeTrue()
DataType.UBYTE.isAssignableTo(DataType.UWORD).shouldBeTrue()
DataType.UBYTE.isAssignableTo(DataType.LONG).shouldBeTrue()
DataType.UBYTE.isAssignableTo(DataType.FLOAT).shouldBeTrue()
}
test("byte assignability") {
DataType.BYTE.isAssignableTo(DataType.BYTE).shouldBeTrue()
DataType.BYTE.isAssignableTo(DataType.WORD).shouldBeTrue()
DataType.BYTE.isAssignableTo(DataType.LONG).shouldBeTrue()
}
test("uword assignability") {
DataType.UWORD.isAssignableTo(DataType.UWORD).shouldBeTrue()
DataType.UWORD.isAssignableTo(DataType.LONG).shouldBeTrue()
DataType.UWORD.isAssignableTo(DataType.FLOAT).shouldBeTrue()
}
test("string assignability") {
DataType.STR.isAssignableTo(DataType.STR).shouldBeTrue()
DataType.STR.isAssignableTo(DataType.UWORD).shouldBeTrue()
}
test("pointer assignability") {
val ptr = DataType.pointer(BaseDataType.UBYTE)
ptr.isAssignableTo(DataType.UWORD).shouldBeTrue()
ptr.isAssignableTo(DataType.LONG).shouldBeTrue()
ptr.isAssignableTo(DataType.pointer(BaseDataType.UBYTE)).shouldBeTrue()
}
// ============================================================================
// DataType.toString Tests
// ============================================================================
test("DataType.toString for basic types") {
DataType.UBYTE.toString() shouldBe "ubyte"
DataType.WORD.toString() shouldBe "word"
DataType.LONG.toString() shouldBe "long"
DataType.FLOAT.toString() shouldBe "float"
}
test("DataType.toString for arrays") {
DataType.arrayFor(BaseDataType.UBYTE).toString() shouldBe "ubyte[]"
DataType.arrayFor(BaseDataType.WORD).toString() shouldBe "word[] (split)"
}
})
@@ -0,0 +1,721 @@
package prog8tests.codecore
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.booleans.shouldBeTrue
import io.kotest.matchers.collections.shouldBeEmpty
import io.kotest.matchers.ints.shouldBeGreaterThan
import io.kotest.matchers.maps.shouldContainKey
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import prog8.code.core.*
import prog8.code.target.C128Target
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8.code.target.PETTarget
import prog8.code.target.zp.C128Zeropage
import prog8.code.target.zp.C64Zeropage
import prog8.code.target.zp.CX16Zeropage
import prog8.code.target.zp.PETZeropage
/**
* Comprehensive tests for Zeropage free list configurations across all targets.
*
* Tests verify that for each target and ZeropageType combination:
* - The exact list of free locations is correct
* - Scratch locations are properly reserved
* - Virtual registers are allocated when appropriate
*/
class TestZeropageFreeList: FunSpec({
// ============================================================================
// Helper Functions
// ============================================================================
fun createOptions(target: ICompilationTarget, zpType: ZeropageType, floats: Boolean = false): CompilationOptions {
return CompilationOptions.builder(target)
.output(OutputType.RAW)
.zeropage(zpType)
.floats(floats)
.compilerVersion("test")
.build()
}
fun verifyScratchLocationsReserved(zp: Zeropage) {
// Verify scratch locations are not in free list
zp.free.contains(zp.SCRATCH_B1) shouldBe false
zp.free.contains(zp.SCRATCH_REG) shouldBe false
zp.free.contains(zp.SCRATCH_W1) shouldBe false
zp.free.contains(zp.SCRATCH_W1 + 1u) shouldBe false
zp.free.contains(zp.SCRATCH_W2) shouldBe false
zp.free.contains(zp.SCRATCH_W2 + 1u) shouldBe false
zp.free.contains(zp.SCRATCH_PTR) shouldBe false
zp.free.contains(zp.SCRATCH_PTR + 1u) shouldBe false
}
fun verifyVirtualRegistersAllocated(zp: Zeropage, expected: Boolean) {
if (expected) {
// Check that virtual registers are allocated
for (reg in 0..15) {
zp.allocatedVariables shouldContainKey "cx16.r${reg}"
}
}
}
// ============================================================================
// C64 Zeropage Tests
// ============================================================================
context("C64Zeropage") {
test("DONTUSE should have no free locations") {
val options = createOptions(C64Target(), ZeropageType.DONTUSE)
val zp = C64Zeropage(options)
zp.free.shouldBeEmpty()
zp.availableBytes() shouldBe 0
}
test("BASICSAFE should have specific free locations") {
val options = createOptions(C64Target(), ZeropageType.BASICSAFE)
val zp = C64Zeropage(options)
// BASICSAFE includes specific BASIC-safe locations
zp.free.isNotEmpty().shouldBeTrue()
zp.free.size shouldBeGreaterThan 0
// Verify scratch locations are reserved
verifyScratchLocationsReserved(zp)
// Virtual registers should be allocated for BASICSAFE
verifyVirtualRegistersAllocated(zp, expected = false) // C64 doesn't allocate virtual regs for BASICSAFE
}
test("KERNALSAFE should have larger free list than BASICSAFE") {
val options = createOptions(C64Target(), ZeropageType.KERNALSAFE)
val zp = C64Zeropage(options)
zp.free.isNotEmpty().shouldBeTrue()
// KERNALSAFE has more locations than BASICSAFE
val basicsafeOptions = createOptions(C64Target(), ZeropageType.BASICSAFE)
val basicsafeZp = C64Zeropage(basicsafeOptions)
zp.free.size shouldBeGreaterThan basicsafeZp.free.size
verifyScratchLocationsReserved(zp)
verifyVirtualRegistersAllocated(zp, expected = true) // KERNALSAFE gets virtual registers
}
test("FLOATSAFE should exclude float-used locations") {
val options = createOptions(C64Target(), ZeropageType.FLOATSAFE, floats = true)
val zp = C64Zeropage(options)
zp.free.isNotEmpty().shouldBeTrue()
// FLOATSAFE should have fewer locations than KERNALSAFE due to float exclusions
val kernalsafeOptions = createOptions(C64Target(), ZeropageType.KERNALSAFE)
val kernalsafeZp = C64Zeropage(kernalsafeOptions)
zp.free.size shouldBeLessThan kernalsafeZp.free.size
// Some float-related locations should not be free (exact set depends on implementation)
// Just verify the size difference is significant
(kernalsafeZp.free.size - zp.free.size) shouldBeGreaterThan 10
verifyScratchLocationsReserved(zp)
}
test("FULL should have maximum free locations minus IRQ") {
val options = createOptions(C64Target(), ZeropageType.FULL)
val zp = C64Zeropage(options)
// FULL should have the most free locations
val kernalsafeOptions = createOptions(C64Target(), ZeropageType.KERNALSAFE)
val kernalsafeZp = C64Zeropage(kernalsafeOptions)
zp.free.size shouldBeGreaterThan kernalsafeZp.free.size
// IRQ locations should not be free
zp.free.contains(0xa0u) shouldBe false
zp.free.contains(0xa1u) shouldBe false
zp.free.contains(0xa2u) shouldBe false
zp.free.contains(0x91u) shouldBe false
zp.free.contains(0xc0u) shouldBe false
zp.free.contains(0xc5u) shouldBe false
zp.free.contains(0xcbu) shouldBe false
zp.free.contains(0xf5u) shouldBe false
zp.free.contains(0xf6u) shouldBe false
verifyScratchLocationsReserved(zp)
verifyVirtualRegistersAllocated(zp, expected = true)
}
}
// ============================================================================
// CX16 Zeropage Tests
// ============================================================================
context("CX16Zeropage") {
test("DONTUSE should have no free locations") {
val options = createOptions(Cx16Target(), ZeropageType.DONTUSE)
val zp = CX16Zeropage(options)
zp.free.shouldBeEmpty()
zp.availableBytes() shouldBe 0
}
test("BASICSAFE should have 0x22-0x7F") {
val options = createOptions(Cx16Target(), ZeropageType.BASICSAFE)
val zp = CX16Zeropage(options)
// Virtual registers 0x02-0x21 should be allocated, not free
for (addr in 0x02u..0x21u) {
zp.free shouldNotBe addr
}
// Free should be 0x22-0x7F minus scratch locations
for (addr in 0x22u..0x7Fu) {
if (addr !in setOf(zp.SCRATCH_B1, zp.SCRATCH_REG, zp.SCRATCH_W1, zp.SCRATCH_W1 + 1u,
zp.SCRATCH_W2, zp.SCRATCH_W2 + 1u, zp.SCRATCH_PTR, zp.SCRATCH_PTR + 1u)) {
zp.free.contains(addr) shouldBe true
}
}
verifyScratchLocationsReserved(zp)
}
test("KERNALSAFE should have 0x22-0x7F and 0xA9-0xFF") {
val options = createOptions(Cx16Target(), ZeropageType.KERNALSAFE)
val zp = CX16Zeropage(options)
val basicsafeOptions = createOptions(Cx16Target(), ZeropageType.BASICSAFE)
val basicsafeZp = CX16Zeropage(basicsafeOptions)
zp.free.size shouldBeGreaterThan basicsafeZp.free.size
// Should include high memory range
zp.free.contains(0xa9u) shouldBe true
zp.free.contains(0xffu) shouldBe true
verifyScratchLocationsReserved(zp)
}
test("FLOATSAFE should have 0x22-0x7F and 0xD4-0xFF") {
val options = createOptions(Cx16Target(), ZeropageType.FLOATSAFE, floats = true)
val zp = CX16Zeropage(options)
// Should include 0xD4-0xFF range
zp.free.contains(0xd4u) shouldBe true
zp.free.contains(0xffu) shouldBe true
// FLOATSAFE has different high range than KERNALSAFE (0xD4-0xFF vs 0xA9-0xFF)
val kernalsafeOptions = createOptions(Cx16Target(), ZeropageType.KERNALSAFE)
val kernalsafeZp = CX16Zeropage(kernalsafeOptions)
// FLOATSAFE should have fewer locations than KERNALSAFE (smaller high range)
zp.free.size shouldBeLessThan kernalsafeZp.free.size
verifyScratchLocationsReserved(zp)
}
test("FULL should have 0x22-0xFF") {
val options = createOptions(Cx16Target(), ZeropageType.FULL)
val zp = CX16Zeropage(options)
// FULL should have the most free locations
val kernalsafeOptions = createOptions(Cx16Target(), ZeropageType.KERNALSAFE)
val kernalsafeZp = CX16Zeropage(kernalsafeOptions)
zp.free.size shouldBeGreaterThan kernalsafeZp.free.size
// Should include all locations from 0x22 to 0xFF (minus scratch)
for (addr in 0x80u..0xA8u) {
if (addr !in setOf(zp.SCRATCH_B1, zp.SCRATCH_REG, zp.SCRATCH_W1, zp.SCRATCH_W1 + 1u,
zp.SCRATCH_W2, zp.SCRATCH_W2 + 1u, zp.SCRATCH_PTR, zp.SCRATCH_PTR + 1u)) {
zp.free.contains(addr) shouldBe true
}
}
verifyScratchLocationsReserved(zp)
}
}
// ============================================================================
// PET Zeropage Tests
// ============================================================================
context("PETZeropage") {
test("DONTUSE should have no free locations") {
val options = createOptions(PETTarget(), ZeropageType.DONTUSE)
val zp = PETZeropage(options)
zp.free.shouldBeEmpty()
zp.availableBytes() shouldBe 0
}
test("BASICSAFE should have minimal locations") {
val options = createOptions(PETTarget(), ZeropageType.BASICSAFE)
val zp = PETZeropage(options)
// BASICSAFE has minimal locations (0xB1-0xBA per code, minus scratch locations)
zp.free.isNotEmpty().shouldBeTrue()
// After scratch removal, some locations in 0xb1-0xba range should still be free
// Just verify the free list is small but non-empty
zp.free.size shouldBeGreaterThan 0
zp.free.size shouldBeLessThan 20
verifyScratchLocationsReserved(zp)
}
test("FLOATSAFE should be same as BASICSAFE") {
val floatOptions = createOptions(PETTarget(), ZeropageType.FLOATSAFE, floats = true)
val floatZp = PETZeropage(floatOptions)
val basicOptions = createOptions(PETTarget(), ZeropageType.BASICSAFE)
val basicZp = PETZeropage(basicOptions)
// FLOATSAFE and BASICSAFE should have same free locations for PET
floatZp.free.size shouldBe basicZp.free.size
}
test("KERNALSAFE should exclude IRQ locations") {
val options = createOptions(PETTarget(), ZeropageType.KERNALSAFE)
val zp = PETZeropage(options)
// IRQ locations should not be free
zp.free.contains(0x8du) shouldBe false
zp.free.contains(0x8eu) shouldBe false
zp.free.contains(0x8fu) shouldBe false
zp.free.contains(0x97u) shouldBe false
zp.free.contains(0x98u) shouldBe false
zp.free.contains(0x99u) shouldBe false
zp.free.contains(0x9au) shouldBe false
zp.free.contains(0x9bu) shouldBe false
zp.free.contains(0x9eu) shouldBe false
zp.free.contains(0xa7u) shouldBe false
zp.free.contains(0xa8u) shouldBe false
zp.free.contains(0xa9u) shouldBe false
zp.free.contains(0xaau) shouldBe false
zp.free.contains(0xaaau) shouldBe false
verifyScratchLocationsReserved(zp)
}
test("FULL should have all locations minus IRQ") {
val options = createOptions(PETTarget(), ZeropageType.FULL)
val zp = PETZeropage(options)
// FULL should have most locations
zp.free.size shouldBeGreaterThan 100
// IRQ locations should not be free
zp.free.contains(0x8du) shouldBe false
zp.free.contains(0x97u) shouldBe false
zp.free.contains(0xa7u) shouldBe false
verifyScratchLocationsReserved(zp)
verifyVirtualRegistersAllocated(zp, expected = true)
}
}
// ============================================================================
// C128 Zeropage Tests
// ============================================================================
context("C128Zeropage") {
test("DONTUSE should have no free locations") {
val options = createOptions(C128Target(), ZeropageType.DONTUSE)
val zp = C128Zeropage(options)
zp.free.shouldBeEmpty()
zp.availableBytes() shouldBe 0
}
test("BASICSAFE should have specific safe locations") {
val options = createOptions(C128Target(), ZeropageType.BASICSAFE)
val zp = C128Zeropage(options)
zp.free.isNotEmpty().shouldBeTrue()
// Should include some specific BASIC-safe locations (excluding scratch)
// 0x0b is SCRATCH_PTR, 0x74 is SCRATCH_B1 for C128, so they're not free
// Check for other locations that should be free
zp.free.contains(0x0du) shouldBe true
zp.free.contains(0x1bu) shouldBe true // In BASICSAFE range
verifyScratchLocationsReserved(zp)
}
test("FLOATSAFE should be same as BASICSAFE for C128") {
val floatOptions = createOptions(C128Target(), ZeropageType.FLOATSAFE, floats = true)
val floatZp = C128Zeropage(floatOptions)
val basicOptions = createOptions(C128Target(), ZeropageType.BASICSAFE)
val basicZp = C128Zeropage(basicOptions)
// FLOATSAFE and BASICSAFE should have same free locations for C128
floatZp.free.size shouldBe basicZp.free.size
}
test("KERNALSAFE should have BASIC variables area") {
val options = createOptions(C128Target(), ZeropageType.KERNALSAFE)
val zp = C128Zeropage(options)
// Should include 0x0A-0x8F (BASIC variables area) minus scratch/virtual reg locations
// 0x0a is where virtual registers start for C128, so not free
// 0x8fu should be free
zp.free.contains(0x8fu) shouldBe true
// Should include additional specific locations
zp.free.contains(0x92u) shouldBe true
// 0xb6u is SCRATCH_W1 for C128 - won't be free
zp.free.contains(0x96u) shouldBe true // Alternative location
verifyScratchLocationsReserved(zp)
}
test("FULL should have maximum locations minus IRQ") {
val options = createOptions(C128Target(), ZeropageType.FULL)
val zp = C128Zeropage(options)
// FULL should have many locations (0x0A-0xFF minus IRQ and scratch)
// 0x0a is where virtual registers start for C128, so not free
zp.free.size shouldBeGreaterThan 150
// IRQ locations should not be free
zp.free.contains(0x90u) shouldBe false
zp.free.contains(0x91u) shouldBe false
zp.free.contains(0xa0u) shouldBe false
zp.free.contains(0xa1u) shouldBe false
zp.free.contains(0xa2u) shouldBe false
zp.free.contains(0xc0u) shouldBe false
zp.free.contains(0xccu) shouldBe false
zp.free.contains(0xcdu) shouldBe false
zp.free.contains(0xd0u) shouldBe false
zp.free.contains(0xd1u) shouldBe false
zp.free.contains(0xd2u) shouldBe false
zp.free.contains(0xd3u) shouldBe false
zp.free.contains(0xd4u) shouldBe false
zp.free.contains(0xd5u) shouldBe false
zp.free.contains(0xf7u) shouldBe false
verifyScratchLocationsReserved(zp)
verifyVirtualRegistersAllocated(zp, expected = true)
}
}
// ============================================================================
// Cross-Target Comparison Tests
// ============================================================================
context("Cross-Target Comparisons") {
test("all targets should have empty free list for DONTUSE") {
// C64
val c64Options = createOptions(C64Target(), ZeropageType.DONTUSE)
C64Zeropage(c64Options).free.shouldBeEmpty()
// CX16
val cx16Options = createOptions(Cx16Target(), ZeropageType.DONTUSE)
CX16Zeropage(cx16Options).free.shouldBeEmpty()
// PET
val petOptions = createOptions(PETTarget(), ZeropageType.DONTUSE)
PETZeropage(petOptions).free.shouldBeEmpty()
// C128
val c128Options = createOptions(C128Target(), ZeropageType.DONTUSE)
C128Zeropage(c128Options).free.shouldBeEmpty()
}
test("scratch locations differ between targets") {
val c64Options = createOptions(C64Target(), ZeropageType.BASICSAFE)
val c64Zp = C64Zeropage(c64Options)
val cx16Options = createOptions(Cx16Target(), ZeropageType.BASICSAFE)
val cx16Zp = CX16Zeropage(cx16Options)
val petOptions = createOptions(PETTarget(), ZeropageType.BASICSAFE)
val petZp = PETZeropage(petOptions)
val c128Options = createOptions(C128Target(), ZeropageType.BASICSAFE)
val c128Zp = C128Zeropage(c128Options)
// Each target has different scratch locations
c64Zp.SCRATCH_B1 shouldBe 0x02u
cx16Zp.SCRATCH_B1 shouldBe 0x7au
petZp.SCRATCH_B1 shouldBe 0xb3u
c128Zp.SCRATCH_B1 shouldBe 0x74u
}
}
// ============================================================================
// Exhaustive Zeropage Address Tests - All 256 locations
// ============================================================================
context("Exhaustive C64 Zeropage Tests") {
test("DONTUSE - no addresses should be free") {
val options = createOptions(C64Target(), ZeropageType.DONTUSE)
val zp = C64Zeropage(options)
for (addr in 0x00u..0xFFu) {
zp.free.contains(addr) shouldBe false
}
}
test("BASICSAFE - exact free address set") {
val options = createOptions(C64Target(), ZeropageType.BASICSAFE)
val zp = C64Zeropage(options)
// BASICSAFE: specific BASIC-safe locations plus virtual registers at 0x04-0x23
// Scratch locations are removed from free list
// Just verify key properties
zp.free.size shouldBeGreaterThan 10
// Scratch locations should NOT be free
setOf(0x02u, 0x03u, 0x04u, 0x05u, 0xFBu, 0xFCu, 0xFDu, 0xFEu).forEach { addr ->
zp.free.contains(addr) shouldBe false
}
// Some known free locations
zp.free.contains(0x06u) shouldBe true
zp.free.contains(0x92u) shouldBe true
}
test("KERNALSAFE - exact free address set") {
val options = createOptions(C64Target(), ZeropageType.KERNALSAFE)
val zp = C64Zeropage(options)
// KERNALSAFE: larger set including more locations, plus virtual registers at 0x04-0x23
// Scratch locations (0x02,0x03,0x04,0x05,0xFB,0xFC,0xFD,0xFE) are removed
// Virtual registers 0x04-0x23 are allocated (not free)
// Just verify key properties since exact set is complex
zp.free.size shouldBeGreaterThan 50
// IRQ locations should NOT be free
setOf(0xA0u, 0xA1u, 0xA2u, 0x91u, 0xC0u, 0xC5u, 0xCBu, 0xF5u, 0xF6u).forEach { addr ->
zp.free.contains(addr) shouldBe false
}
// Scratch locations should NOT be free
setOf(0x02u, 0x03u, 0x04u, 0x05u, 0xFBu, 0xFCu, 0xFDu, 0xFEu).forEach { addr ->
zp.free.contains(addr) shouldBe false
}
}
test("FLOATSAFE - excludes float-used locations") {
val options = createOptions(C64Target(), ZeropageType.FLOATSAFE, floats = true)
val zp = C64Zeropage(options)
// FLOATSAFE is KERNALSAFE minus float-used locations
zp.free.size shouldBeGreaterThan 20
// FLOATSAFE should have fewer locations than KERNALSAFE
val kernalsafeOptions = createOptions(C64Target(), ZeropageType.KERNALSAFE)
val kernalsafeZp = C64Zeropage(kernalsafeOptions)
zp.free.size shouldBeLessThan kernalsafeZp.free.size
}
test("FULL - maximum free locations minus IRQ") {
val options = createOptions(C64Target(), ZeropageType.FULL)
val zp = C64Zeropage(options)
// FULL should have the most locations
zp.free.size shouldBeGreaterThan 100
// IRQ locations should NOT be free
setOf(0xA0u, 0xA1u, 0xA2u, 0x91u, 0xC0u, 0xC5u, 0xCBu, 0xF5u, 0xF6u).forEach { addr ->
zp.free.contains(addr) shouldBe false
}
// Scratch locations should NOT be free
setOf(0x02u, 0x03u, 0x04u, 0x05u, 0xFBu, 0xFCu, 0xFDu, 0xFEu).forEach { addr ->
zp.free.contains(addr) shouldBe false
}
}
}
context("Exhaustive CX16 Zeropage Tests") {
test("DONTUSE - no addresses should be free") {
val options = createOptions(Cx16Target(), ZeropageType.DONTUSE)
val zp = CX16Zeropage(options)
for (addr in 0x00u..0xFFu) {
zp.free.contains(addr) shouldBe false
}
}
test("BASICSAFE - 0x22-0x7F minus scratch and virtual registers") {
val options = createOptions(Cx16Target(), ZeropageType.BASICSAFE)
val zp = CX16Zeropage(options)
// Virtual registers 0x02-0x21 are allocated (not free)
for (addr in 0x02u..0x21u) {
zp.free.contains(addr) shouldBe false
}
// BASICSAFE: 0x22-0x7F minus scratch
for (addr in 0x22u..0x7Fu) {
val isScratch = addr in setOf(0x7Au, 0x7Bu, 0x7Cu, 0x7Du, 0x7Eu, 0x7Fu, 0x22u, 0x23u)
zp.free.contains(addr) shouldBe !isScratch
}
// Above 0x7F should not be free for BASICSAFE
for (addr in 0x80u..0xFFu) {
zp.free.contains(addr) shouldBe false
}
}
test("KERNALSAFE - 0x22-0x7F and 0xA9-0xFF") {
val options = createOptions(Cx16Target(), ZeropageType.KERNALSAFE)
val zp = CX16Zeropage(options)
// Virtual registers 0x02-0x21 are allocated (not free)
for (addr in 0x02u..0x21u) {
zp.free.contains(addr) shouldBe false
}
// Should include 0xA9-0xFF range
zp.free.contains(0xA9u) shouldBe true
zp.free.contains(0xFFu) shouldBe true
}
test("FLOATSAFE - 0x22-0x7F and 0xD4-0xFF") {
val options = createOptions(Cx16Target(), ZeropageType.FLOATSAFE, floats = true)
val zp = CX16Zeropage(options)
// Should include 0xD4-0xFF range
zp.free.contains(0xD4u) shouldBe true
zp.free.contains(0xFFu) shouldBe true
// Should NOT include 0xA9-0xD3 range that KERNALSAFE has
val kernalsafeOptions = createOptions(Cx16Target(), ZeropageType.KERNALSAFE)
val kernalsafeZp = CX16Zeropage(kernalsafeOptions)
zp.free.size shouldBeLessThan kernalsafeZp.free.size
}
test("FULL - 0x22-0xFF minus scratch") {
val options = createOptions(Cx16Target(), ZeropageType.FULL)
val zp = CX16Zeropage(options)
// Virtual registers 0x02-0x21 are allocated (not free)
for (addr in 0x02u..0x21u) {
zp.free.contains(addr) shouldBe false
}
// FULL should have most locations from 0x22-0xFF
zp.free.size shouldBeGreaterThan 150
}
}
context("Exhaustive PET Zeropage Tests") {
test("DONTUSE - no addresses should be free") {
val options = createOptions(PETTarget(), ZeropageType.DONTUSE)
val zp = PETZeropage(options)
for (addr in 0x00u..0xFFu) {
zp.free.contains(addr) shouldBe false
}
}
test("BASICSAFE - minimal locations around 0xB1-0xBA") {
val options = createOptions(PETTarget(), ZeropageType.BASICSAFE)
val zp = PETZeropage(options)
// BASICSAFE has minimal locations (0xB1-0xBA minus scratch)
zp.free.size shouldBeGreaterThan 0
zp.free.size shouldBeLessThan 15
}
test("KERNALSAFE - excludes IRQ locations") {
val options = createOptions(PETTarget(), ZeropageType.KERNALSAFE)
val zp = PETZeropage(options)
// IRQ locations should NOT be free
setOf(0x8Du, 0x8Eu, 0x8Fu, 0x97u, 0x98u, 0x99u, 0x9Au, 0x9Bu, 0x9Eu,
0xA7u, 0xA8u, 0xA9u, 0xAAu).forEach { addr ->
zp.free.contains(addr) shouldBe false
}
}
test("FULL - all minus IRQ locations") {
val options = createOptions(PETTarget(), ZeropageType.FULL)
val zp = PETZeropage(options)
// FULL should have most locations
zp.free.size shouldBeGreaterThan 150
// IRQ locations should NOT be free
setOf(0x8Du, 0x97u, 0xA7u).forEach { addr ->
zp.free.contains(addr) shouldBe false
}
}
}
context("Exhaustive C128 Zeropage Tests") {
test("DONTUSE - no addresses should be free") {
val options = createOptions(C128Target(), ZeropageType.DONTUSE)
val zp = C128Zeropage(options)
for (addr in 0x00u..0xFFu) {
zp.free.contains(addr) shouldBe false
}
}
test("BASICSAFE - specific safe locations") {
val options = createOptions(C128Target(), ZeropageType.BASICSAFE)
val zp = C128Zeropage(options)
// BASICSAFE has specific locations
zp.free.size shouldBeGreaterThan 50
// Note: Virtual registers are NOT allocated for BASICSAFE on C128
// (only for FULL and KERNALSAFE modes)
}
test("KERNALSAFE - BASIC variables area 0x0A-0x8F") {
val options = createOptions(C128Target(), ZeropageType.KERNALSAFE)
val zp = C128Zeropage(options)
// Virtual registers at 0x0A-0x29 are allocated (not free)
for (addr in 0x0Au..0x29u) {
zp.free.contains(addr) shouldBe false
}
// Should include some locations in BASIC variables area
zp.free.contains(0x8Fu) shouldBe true
}
test("FULL - 0x0A-0xFF minus IRQ") {
val options = createOptions(C128Target(), ZeropageType.FULL)
val zp = C128Zeropage(options)
// FULL should have many locations
zp.free.size shouldBeGreaterThan 150
// IRQ locations should NOT be free
setOf(0x90u, 0x91u, 0xA0u, 0xA1u, 0xA2u, 0xC0u, 0xCCu, 0xCDu,
0xD0u, 0xD1u, 0xD2u, 0xD3u, 0xD4u, 0xD5u, 0xF7u).forEach { addr ->
zp.free.contains(addr) shouldBe false
}
}
}
})
// Helper infix function for readability
infix fun Int.shouldBeLessThan(value: Int) {
if (this >= value) {
throw AssertionError("Expected $this to be less than $value")
}
}
+4 -38
View File
@@ -1,5 +1,3 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
kotlin("jvm")
}
@@ -7,41 +5,9 @@ plugins {
dependencies {
implementation(project(":codeCore"))
implementation(project(":simpleAst"))
// implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.1")
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.3.1")
testImplementation("io.kotest:kotest-runner-junit5-jvm:5.9.1")
testImplementation("io.kotest:kotest-framework-datatest:5.9.1")
testImplementation("org.junit.jupiter:junit-jupiter:5.9.1")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
sourceSets {
main {
java {
setSrcDirs(listOf("$projectDir/src"))
}
resources {
setSrcDirs(listOf("$projectDir/res"))
}
}
test {
java {
setSrcDirs(listOf("$projectDir/test"))
}
}
}
tasks.test {
// Enable JUnit 5 (Gradle 4.6+).
useJUnitPlatform()
// Always run tests, even when nothing changed.
dependsOn("cleanTest")
// Show test results.
testLogging {
events("skipped", "failed")
}
testImplementation(testFixtures(project(":codeCore")))
testImplementation("io.kotest:kotest-runner-junit5")
testImplementation("io.kotest:kotest-framework-datatest")
}
+3 -4
View File
@@ -13,8 +13,7 @@
<orderEntry type="module" module-name="codeCore" />
<orderEntry type="module" module-name="simpleAst" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
<orderEntry type="library" name="io.kotest.framework.datatest" level="project" />
<orderEntry type="library" scope="TEST" name="io.kotest.runner.junit5.jvm" level="project" />
<orderEntry type="library" scope="TEST" name="io.kotest.framework.datatest" level="project" />
</component>
</module>
</module>
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -1,6 +1,7 @@
package prog8.codegen.cpu6502
import prog8.code.ast.PtAsmSub
import prog8.code.core.CombinedLongRegisters
import prog8.code.core.Cx16VirtualRegisters
import prog8.code.core.RegisterOrPair
@@ -8,18 +9,21 @@ import prog8.code.core.RegisterOrPair
fun asmsub6502ArgsEvalOrder(sub: PtAsmSub): List<Int> {
val order = mutableListOf<Int>()
// order is:
// 1) cx16 virtual word registers,
// 2) paired CPU registers,
// 3) single CPU registers (order Y,X,A),
// 4) floating point registers (FAC1, FAC2),
// 5) CPU Carry status flag
// 1) combined cx16 virtual registers (longs),
// 2) cx16 virtual word registers,
// 3) paired CPU registers,
// 4) single CPU registers (order Y,X,A),
// 5) floating point registers (FAC1, FAC2),
// 6) CPU Carry status flag
val args = sub.parameters.withIndex()
val (cx16regs, args2) = args.partition { it.value.first.registerOrPair in Cx16VirtualRegisters }
val (combinedLongRegs, args1) = args.partition { it.value.first.registerOrPair in CombinedLongRegisters }
val (cx16regs, args2) = args1.partition { it.value.first.registerOrPair in Cx16VirtualRegisters }
val pairedRegisters = arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)
val (pairedRegs , args3) = args2.partition { it.value.first.registerOrPair in pairedRegisters }
val (singleRegsMixed, rest) = args3.partition { it.value.first.registerOrPair != null }
val (singleCpuRegs, floatRegs) = singleRegsMixed.partition {it.value.first.registerOrPair != RegisterOrPair.FAC1 && it.value.first.registerOrPair != RegisterOrPair.FAC2 }
combinedLongRegs.forEach { order += it.index }
cx16regs.forEach { order += it.index }
pairedRegs.forEach { order += it.index }
singleCpuRegs.sortedBy { it.value.first.registerOrPair!!.asCpuRegister() }.asReversed().forEach { order += it.index }
@@ -17,6 +17,10 @@ internal class AssemblyProgram(
outputDir: Path,
private val compTarget: ICompilationTarget) : IAssemblyProgram {
override val irInstructionCount: Int = 0
override val irChunkCount: Int = 0
override val irRegisterCount: Int = 0
private val assemblyFile = outputDir.resolve("$name.asm")
private val prgFile = outputDir.resolve("$name.prg") // CBM prg executable program
private val xexFile = outputDir.resolve("$name.xex") // Atari xex executable program
File diff suppressed because it is too large. Load diff
@@ -500,149 +500,183 @@ $endLabel""")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.add(endLabel)
val iterableName = asmgen.asmVariableName(ident)
val numElements = when(val symbol = asmgen.symbolTable.lookup(ident.name)) {
val numElements: UInt = when(val symbol = asmgen.symbolTable.lookup(ident.name)) {
is StStaticVariable -> symbol.length!!
is StMemVar -> symbol.length!!
else -> 0
else -> 0u
}
when {
iterableDt.isString -> {
if(asmgen.options.romable) {
val indexVar = asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt)
asmgen.out("""
fun iterateStrings() {
if(asmgen.options.romable) {
val indexVar = asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt)
asmgen.out("""
ldy #0
sty $indexVar
$loopLabel lda $iterableName,y
beq $endLabel
sta ${asmgen.asmVariableName(stmt.variable)}""")
asmgen.translate(stmt.statements)
asmgen.out("""
asmgen.translate(stmt.statements)
asmgen.out("""
inc $indexVar
ldy $indexVar
bne $loopLabel
$endLabel""")
} else {
val indexVar = asmgen.makeLabel("for_index")
asmgen.out("""
} else {
val indexVar = asmgen.makeLabel("for_index")
asmgen.out("""
ldy #0
sty $indexVar
$loopLabel lda $iterableName,y
beq $endLabel
sta ${asmgen.asmVariableName(stmt.variable)}""")
asmgen.translate(stmt.statements)
asmgen.out("""
asmgen.translate(stmt.statements)
asmgen.out("""
inc $indexVar
ldy $indexVar
bne $loopLabel
$indexVar .byte 0
$endLabel""")
}
}
iterableDt.isByteArray || iterableDt.isBoolArray -> {
val indexVar = if(asmgen.options.romable)
asmgen.createTempVarReused(iterableDt.elementType().base, false, stmt)
else
asmgen.makeLabel("for_index")
asmgen.out("""
}
fun iterateBytes() {
val indexVar = if(asmgen.options.romable)
asmgen.createTempVarReused(iterableDt.elementType().base, false, stmt)
else
asmgen.makeLabel("for_index")
asmgen.out("""
ldy #0
$loopLabel sty $indexVar
lda $iterableName,y
sta ${asmgen.asmVariableName(stmt.variable)}""")
asmgen.translate(stmt.statements)
if(numElements<=255) {
asmgen.out("""
asmgen.translate(stmt.statements)
if(numElements<=255u) {
asmgen.out("""
ldy $indexVar
iny
cpy #$numElements
beq $endLabel
bne $loopLabel""")
} else {
// length is 256
asmgen.out("""
} else {
// length is 256
asmgen.out("""
ldy $indexVar
iny
bne $loopLabel
beq $endLabel""")
}
if(!asmgen.options.romable) {
if(numElements>=16) {
// allocate index var on ZP if possible, otherwise inline
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
result.fold(
success = { (address, _, _)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
failure = { asmgen.out("$indexVar .byte 0") }
)
} else {
asmgen.out("$indexVar .byte 0")
}
}
asmgen.out(endLabel)
}
iterableDt.isSplitWordArray -> {
val indexVar = if(asmgen.options.romable)
asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt)
else
asmgen.makeLabel("for_index")
val loopvarName = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
if(!asmgen.options.romable) {
if(numElements>=16u) {
// allocate index var on ZP if possible, otherwise inline
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
result.fold(
success = { (address, _, _)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
failure = { asmgen.out("$indexVar .byte 0") }
)
} else {
asmgen.out("$indexVar .byte 0")
}
}
asmgen.out(endLabel)
}
fun iterateSplitWords() {
val indexVar = if(asmgen.options.romable)
asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt)
else
asmgen.makeLabel("for_index")
val loopvarName = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
ldy #0
$loopLabel sty $indexVar
lda ${iterableName}_lsb,y
sta $loopvarName
lda ${iterableName}_msb,y
sta $loopvarName+1""")
asmgen.translate(stmt.statements)
if(numElements<=255) {
asmgen.out("""
asmgen.translate(stmt.statements)
if(numElements<=255u) {
asmgen.out("""
ldy $indexVar
iny
cpy #$numElements
beq $endLabel
bne $loopLabel""")
} else {
// length is 256
asmgen.out("""
} else {
// length is 256
asmgen.out("""
ldy $indexVar
iny
bne $loopLabel
beq $endLabel""")
}
if(!asmgen.options.romable) {
if(numElements>=16) {
// allocate index var on ZP if possible, otherwise inline
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
result.fold(
success = { (address, _, _)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
failure = { asmgen.out("$indexVar .byte 0") }
)
} else {
asmgen.out("$indexVar .byte 0")
}
}
asmgen.out(endLabel)
}
iterableDt.isWordArray -> {
val indexVar = if(asmgen.options.romable)
asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt)
else
asmgen.makeLabel("for_index")
val loopvarName = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
if(!asmgen.options.romable) {
if(numElements>=16u) {
// allocate index var on ZP if possible, otherwise inline
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
result.fold(
success = { (address, _, _)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
failure = { asmgen.out("$indexVar .byte 0") }
)
} else {
asmgen.out("$indexVar .byte 0")
}
}
asmgen.out(endLabel)
}
fun iterateWords(actuallyLongs: Boolean = false) {
val indexVar = if(asmgen.options.romable)
asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt)
else
asmgen.makeLabel("for_index")
val loopvarName = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
ldy #0
$loopLabel sty $indexVar
lda $iterableName,y
sta $loopvarName
lda $iterableName+1,y
sta $loopvarName+1""")
asmgen.translate(stmt.statements)
if(numElements<=127) {
if(actuallyLongs) {
asmgen.out("""
lda $iterableName+2,y
sta $loopvarName+2
lda $iterableName+3,y
sta $loopvarName+3""")
}
asmgen.translate(stmt.statements)
if(numElements<=127u) {
if(actuallyLongs) {
asmgen.out("""
ldy $indexVar
iny
iny
cpy #${numElements*2}
iny
iny
cpy #${numElements*4u}
beq $endLabel
bne $loopLabel""")
} else {
asmgen.out("""
ldy $indexVar
iny
iny
cpy #${numElements*2u}
beq $endLabel
bne $loopLabel""")
}
} else {
if(actuallyLongs) {
// array size is 64 longs, 256 bytes
asmgen.out("""
ldy $indexVar
iny
iny
iny
iny
bne $loopLabel
beq $endLabel""")
} else {
// array size is 128 words, 256 bytes
asmgen.out("""
@@ -652,23 +686,29 @@ $loopLabel sty $indexVar
bne $loopLabel
beq $endLabel""")
}
if(!asmgen.options.romable) {
if(numElements>=16) {
// allocate index var on ZP if possible, otherwise inline
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
result.fold(
success = { (address, _, _)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
failure = { asmgen.out("$indexVar .byte 0") }
)
} else {
asmgen.out("$indexVar .byte 0")
}
}
if(!asmgen.options.romable) {
if(numElements>=16u) {
// allocate index var on ZP if possible, otherwise inline
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
result.fold(
success = { (address, _, _)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
failure = { asmgen.out("$indexVar .byte 0") }
)
} else {
asmgen.out("$indexVar .byte 0")
}
asmgen.out(endLabel)
}
iterableDt.isFloatArray -> {
throw AssemblyError("for loop with floating point variables is not supported")
}
asmgen.out(endLabel)
}
when {
iterableDt.isString -> iterateStrings()
iterableDt.isByteArray || iterableDt.isBoolArray -> iterateBytes()
iterableDt.isSplitWordArray -> iterateSplitWords()
iterableDt.isWordArray -> iterateWords()
iterableDt.isLongArray -> iterateWords(true)
iterableDt.isFloatArray -> throw AssemblyError("for loop with floating point variables is not supported")
else -> throw AssemblyError("can't iterate over $iterableDt")
}
asmgen.loopEndLabels.removeLast()
@@ -687,23 +727,28 @@ $loopLabel sty $indexVar
if(range.step==1 && range.last>range.first) return translateForSimpleWordRangeAsc(stmt, range)
if(range.step==-1 && range.last<range.first) return translateForSimpleWordRangeDesc(stmt, range)
}
else if(iterableDt.isLongArray) {
if(range.last==range.first) return translateForSimpleLongRangeAsc(stmt, range)
if(range.step==1 && range.last>range.first) return translateForSimpleLongRangeAsc(stmt, range)
if(range.step==-1 && range.last<range.first) return translateForSimpleLongRangeDesc(stmt, range)
}
// not one of the easy cases, generate more complex code...
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.add(endLabel)
val varname = asmgen.asmVariableName(stmt.variable)
when {
iterableDt.isByteArray -> {
// loop over byte range via loopvar, step >= 2 or <= -2
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.out("""
lda #${range.first}
sta $varname
$loopLabel""")
asmgen.translate(stmt.statements)
when (range.step) {
0, 1, -1 -> {
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $range ${stmt.position}")
1, -1 -> {
throw AssemblyError("step 1 and -1 should have been handled specifically $range ${stmt.position}")
}
2 -> {
if(range.last==255 || range.last==254) {
@@ -760,10 +805,9 @@ $loopLabel""")
}
iterableDt.isWordArray && !iterableDt.isSplitWordArray -> {
// loop over word range via loopvar, step >= 2 or <= -2
val varname = asmgen.asmVariableName(stmt.variable)
when (range.step) {
0, 1, -1 -> {
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt")
1, -1 -> {
throw AssemblyError("step 1 and -1 should have been handled specifically $stmt")
}
else -> {
// word, step >= 2 or <= -2
@@ -781,7 +825,6 @@ $loopLabel""")
bne +
lda $varname+1
cmp #>${range.last}
bne +
beq $endLabel
+ lda $varname
clc
@@ -795,7 +838,61 @@ $loopLabel""")
}
}
}
else -> throw AssemblyError("range expression can only be byte or word")
iterableDt.isLongArray -> {
// loop over long range via loopvar, step >= 2 or <= -2
when(range.step) {
1, -1 -> {
throw AssemblyError("step 1 and -1 should have been handled specifically $stmt")
}
else -> {
// long, step >= 2 or <= -2
// note: range.last has already been adjusted by kotlin itself to actually be the last value of the sequence
val from = range.first.toLongHex()
val to = range.last.toLongHex()
val step = range.step.toLongHex()
asmgen.out("""
lda #$${from.substring(6, 8)}
sta $varname
lda #$${from.substring(4, 6)}
sta $varname+1
lda #$${from.substring(2, 4)}
sta $varname+2
lda #$${from.take(2)}
sta $varname+3
$loopLabel""")
asmgen.translate(stmt.statements)
asmgen.out("""
lda $varname
cmp #$${to.substring(6, 8)}
bne +
lda $varname+1
cmp #$${to.substring(4, 6)}
bne +
lda $varname+2
cmp #$${to.substring(2, 4)}
bne +
lda $varname+3
cmp #$${to.take(2)}
beq $endLabel
+ lda $varname
clc
adc #$${step.substring(6, 8)}
sta $varname
lda $varname+1
adc #$${step.substring(4, 6)}
sta $varname+1
lda $varname+2
adc #$${step.substring(2, 4)}
sta $varname+2
lda $varname+3
adc #$${step.take(2)}
sta $varname+3""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
}
}
}
else -> throw AssemblyError("invalid range expression datatype")
}
asmgen.loopEndLabels.removeLast()
}
@@ -864,6 +961,7 @@ $loopLabel""")
}
private fun translateForSimpleWordRangeAsc(stmt: PtForLoop, range: IntProgression) {
require(range.step==1)
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.add(endLabel)
@@ -891,6 +989,7 @@ $loopLabel""")
}
private fun translateForSimpleWordRangeDesc(stmt: PtForLoop, range: IntProgression) {
require(range.step==-1)
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.add(endLabel)
@@ -927,6 +1026,110 @@ $loopLabel""")
asmgen.loopEndLabels.removeLast()
}
private fun translateForSimpleLongRangeAsc(stmt: PtForLoop, range: IntProgression) {
require(range.step==1)
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.add(endLabel)
val varname = asmgen.asmVariableName(stmt.variable)
val from = range.first.toLongHex()
val to = range.last.toLongHex()
asmgen.out("""
lda #$${from.substring(6, 8)}
sta $varname
lda #$${from.substring(4, 6)}
sta $varname+1
lda #$${from.substring(2, 4)}
sta $varname+2
lda #$${from.take(2)}
sta $varname+3
$loopLabel""")
asmgen.translate(stmt.statements)
asmgen.out("""
lda $varname
cmp #$${to.substring(6, 8)}
bne +
lda $varname+1
cmp #$${to.substring(4, 6)}
bne +
lda $varname+2
cmp #$${to.substring(2, 4)}
bne +
lda $varname+3
cmp #$${to.take(2)}
beq $endLabel
+ inc $varname
bne $loopLabel
inc $varname+1
bne $loopLabel
inc $varname+2
bne $loopLabel
inc $varname+3""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
asmgen.loopEndLabels.removeLast()
}
private fun translateForSimpleLongRangeDesc(stmt: PtForLoop, range: IntProgression) {
require(range.step==-1)
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.add(endLabel)
val varname = asmgen.asmVariableName(stmt.variable)
val from = range.first.toLongHex()
val to = range.last.toLongHex()
asmgen.out("""
lda #$${from.substring(6, 8)}
sta $varname
lda #$${from.substring(4, 6)}
sta $varname+1
lda #$${from.substring(2, 4)}
sta $varname+2
lda #$${from.take(2)}
sta $varname+3
$loopLabel""")
asmgen.translate(stmt.statements)
if(range.last==0) {
asmgen.out("""
lda $varname
bne ++++
lda $varname+1
bne +++
lda $varname+2
bne ++
lda $varname+3
beq $endLabel""")
} else {
asmgen.out("""
lda $varname
cmp #$${to.substring(6, 8)}
bne +
lda $varname+1
cmp #$${to.substring(4, 6)}
bne +
lda $varname+2
cmp #$${to.substring(2, 4)}
bne +
lda $varname+3
cmp #$${to.take(2)}
beq $endLabel""")
}
asmgen.out("""
+ lda $varname
bne +++
lda $varname+1
bne ++
lda $varname+2
bne +
dec $varname+3
+ dec $varname+2
+ dec $varname+1
+ dec $varname""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
asmgen.loopEndLabels.removeLast()
}
private fun assignLoopvarWord(stmt: PtForLoop, range: PtRange) =
asmgen.assignExpressionToVariable(
range.from,
@@ -21,7 +21,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
// we consider them NOT to be optimized into (possibly different) CPU registers.
// Just load them in whatever the register spec says.
return when (params.size) {
1 -> params[0].type.isIntegerOrBool && params[0].register == null
1 -> params[0].register == null && (params[0].type.isWordOrByteOrBool || params[0].type.isPointer)
2 -> params[0].type.isByteOrBool && params[1].type.isByteOrBool && params[0].register == null && params[1].register == null
else -> false
}
@@ -36,7 +36,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
val symbol = asmgen.symbolTable.lookup(call.name)!!
if(symbol.type == StNodeType.LABEL) {
require(call.void)
asmgen.out(" jsr ${asmgen.asmSymbolName(symbol.scopedName)}")
asmgen.out(" jsr ${asmgen.asmSymbolName(symbol.scopedNameString)}")
return
}
@@ -139,15 +139,16 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
}
}
else if(sub is PtSub) {
if(optimizeIntArgsViaCpuRegisters(sub.parameters)) {
val parameters = sub.signature.children.filterIsInstance<PtSubroutineParameter>()
if(optimizeIntArgsViaCpuRegisters(parameters)) {
// Note that if the args fit into cpu registers, we don't concern ourselves here
// if they should be put into regular subroutine parameter variables, or the R0-R15 register variables.
// That is now up to the subroutine itself.
useCpuRegistersForArgs(call.args, sub)
} else {
// arguments via variables
val paramValues = sub.parameters.zip(call.args)
val (normalParams, registerParams) = paramValues.partition { it.first.register == null }
val paramValues = parameters.zip(call.args)
val (normalParams, registerParams) = paramValues.partition { (it.first.register == null) }
if (normalParams.isNotEmpty()) {
for (p in normalParams)
argumentViaVariable(sub, p.first, p.second)
@@ -166,7 +167,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
}
private fun useCpuRegistersForArgs(args: List<PtExpression>, sub: PtSub) {
val params = sub.parameters
val params = sub.signature.children.filterIsInstance<PtSubroutineParameter>()
when(params.size) {
1 -> {
val register = if (params[0].type.isByteOrBool) RegisterOrPair.A else RegisterOrPair.AY
@@ -202,12 +203,16 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
private fun usesOtherRegistersWhileEvaluating(arg: PtExpression): Boolean {
return when(arg) {
is PtBuiltinFunctionCall -> {
if (arg.name in arrayOf("lsb", "msb", "lsw", "msw"))
return usesOtherRegistersWhileEvaluating(arg.args[0])
if (arg.name == "mkword")
return usesOtherRegistersWhileEvaluating(arg.args[0]) || usesOtherRegistersWhileEvaluating(arg.args[1])
return !arg.isSimple()
is PtFunctionCall if arg.builtin -> {
if (arg.name in setOf("lsb", "msb", "lsw", "msw"))
usesOtherRegistersWhileEvaluating(arg.args[0])
else if (arg.name == "mkword" || arg.name == "mklong2")
usesOtherRegistersWhileEvaluating(arg.args[0]) || usesOtherRegistersWhileEvaluating(arg.args[1])
else if (arg.name == "mklong")
usesOtherRegistersWhileEvaluating(arg.args[0]) || usesOtherRegistersWhileEvaluating(arg.args[1]) ||
usesOtherRegistersWhileEvaluating(arg.args[2]) || usesOtherRegistersWhileEvaluating(arg.args[3])
else
!arg.isSimple()
}
is PtAddressOf -> false
is PtIdentifier -> false
@@ -263,8 +268,8 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
val reg = parameter.register
if(reg!=null) {
require(reg in Cx16VirtualRegisters) { "can only use R0-R15 'registers' here" }
val varName = "cx16.${reg.name.lowercase()}"
require(reg in Cx16VirtualRegisters || reg in CombinedLongRegisters) { "can only use R0-R15 'registers' here" }
val varName = reg.asScopedNameVirtualReg(value.type).joinToString(".")
asmgen.assignExpressionToVariable(value, varName, parameter.type)
} else {
val varName = asmgen.asmVariableName(sub.scopedName + "." + parameter.name)
@@ -275,7 +280,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
private fun argumentViaRegister(sub: IPtSubroutine, parameter: IndexedValue<PtSubroutineParameter>, value: PtExpression, registerOverride: RegisterOrPair? = null): RegisterOrStatusflag {
// pass argument via a register parameter
if(!isArgumentTypeCompatible(value.type, parameter.value.type))
throw AssemblyError("argument type incompatible")
throw AssemblyError("argument type incompatible ${value.type} vs param ${parameter.value.type} at ${parameter.value.position}")
val paramRegister: RegisterOrStatusflag = when(sub) {
is PtAsmSub -> if(registerOverride==null) sub.parameters[parameter.index].first else RegisterOrStatusflag(registerOverride, null)
@@ -286,11 +291,11 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
val requiredDt = parameter.value.type
if(requiredDt!=value.type) {
if(value.type.largerSizeThan(requiredDt))
throw AssemblyError("can only convert byte values to word param types")
throw AssemblyError("can only convert byte values to word param types ${value.position}")
}
if (statusflag!=null) {
if(requiredDt!=value.type)
throw AssemblyError("for statusflag, byte or bool value is required")
throw AssemblyError("for statusflag, byte or bool value is required ${value.position}")
if (statusflag == Statusflag.Pc) {
// this boolean param needs to be set last, right before the jsr
// for now, this is already enforced on the subroutine definition by the Ast Checker
@@ -318,7 +323,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
asmgen.out(" ror a")
}
}
} else throw AssemblyError("can only use Carry as status flag parameter")
} else throw AssemblyError("can only use Carry as status flag parameter ${value.position}")
return RegisterOrStatusflag(null, statusflag)
}
else {
@@ -328,18 +333,18 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
// we need to sign extend the source, do this via temporary word variable
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_W1", DataType.UBYTE)
asmgen.signExtendVariableLsb("P8ZP_SCRATCH_W1", value.type.base)
asmgen.assignVariableToRegister("P8ZP_SCRATCH_W1", register, null, Position.DUMMY)
asmgen.assignVariableToRegister("P8ZP_SCRATCH_W1", register, null, value.position)
} else {
val scope = value.definingISub()
val target: AsmAssignTarget =
if(parameter.value.type.isByte && (register==RegisterOrPair.AX || register == RegisterOrPair.AY || register==RegisterOrPair.XY || register in Cx16VirtualRegisters))
if(parameter.value.type.isByte && register.isWord())
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, parameter.value.type, scope, value.position, register = register)
else {
AsmAssignTarget.fromRegisters(register, parameter.value.type.isSigned, value.position, scope, asmgen)
}
val src = if(value.type.isPassByRef) {
if(value is PtIdentifier) {
val addr = PtAddressOf(Position.DUMMY)
val addr = PtAddressOf(value.type.typeForAddressOf(false), false, value.position)
addr.add(value)
addr.parent = scope as PtNode
AsmAssignSource.fromAstSource(addr, program, asmgen).adjustSignedUnsigned(target)
@@ -358,12 +363,17 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
private fun isArgumentTypeCompatible(argType: DataType, paramType: DataType): Boolean {
if(argType isAssignableTo paramType)
return true
if(argType.isBool && paramType.isBool)
return true
// signed/unsigned args vs parameters are considered compatible
if(argType.isByte && paramType.isByte)
return true
if(argType.isWord && paramType.isWord)
return true
// only signed longs and floats are available so these were already covered
// if(argType.isLong && paramType.isLong)
// return true
// if(argType.isFloat && paramType.isFloat)
// return true
// we have a special rule for some types.
// strings are assignable to UWORD, for example, and vice versa
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -44,6 +44,7 @@ internal class ProgramAndVarsGen(
asmgen.out("${flt.value}\t.byte $floatFill ; float $floatvalue")
}
structInstances2asm()
memorySlabs()
footer()
}
@@ -70,6 +71,7 @@ internal class ProgramAndVarsGen(
asmgen.out("P8ZP_SCRATCH_REG = ${zp.SCRATCH_REG}")
asmgen.out("P8ZP_SCRATCH_W1 = ${zp.SCRATCH_W1} ; word")
asmgen.out("P8ZP_SCRATCH_W2 = ${zp.SCRATCH_W2} ; word")
asmgen.out("P8ZP_SCRATCH_PTR = ${zp.SCRATCH_PTR} ; word")
if(compTarget.name=="c64") {
if(options.floats)
asmgen.out("PROG8_C64_BANK_CONFIG=31 ; basic+IO+kernal")
@@ -207,18 +209,20 @@ internal class ProgramAndVarsGen(
private fun memorySlabs() {
if(symboltable.allMemorySlabs.isNotEmpty()) {
asmgen.out("; memory slabs\n .section slabs_BSS")
asmgen.out("prog8_slabs\t.block")
asmgen.out("; memory slabs\n .section BSS_SLABS")
asmgen.out("$StMemorySlabBlockName\t.block")
for (slab in symboltable.allMemorySlabs) {
if (slab.align > 1u)
asmgen.out("\t.align ${slab.align.toHex()}")
asmgen.out("${slab.name}\t.fill ${slab.size}")
}
asmgen.out("\t.bend\n .send slabs_BSS")
asmgen.out("\t.bend\n .send BSS_SLABS")
}
}
private fun footer() {
asmgen.out(" .dsection STRUCTINSTANCES\n")
var relocateBssVars = false
var relocateBssSlabs = false
var relocatedBssStart = 0u
@@ -274,14 +278,14 @@ internal class ProgramAndVarsGen(
asmgen.out("PROG8_VARSHIGH_RAMBANK = ${options.varsHighBank ?: 1}")
if(relocateBssVars) {
if(!relocateBssSlabs)
asmgen.out(" .dsection slabs_BSS")
asmgen.out(" .dsection BSS_SLABS")
asmgen.out("prog8_program_end\t; end of program label for progend()")
asmgen.out(" * = ${relocatedBssStart.toHex()}")
asmgen.out(" .dsection BSS_NOCLEAR")
asmgen.out("prog8_bss_section_start")
asmgen.out(" .dsection BSS")
if(relocateBssSlabs)
asmgen.out(" .dsection slabs_BSS")
asmgen.out(" .dsection BSS_SLABS")
asmgen.out(" .cerror * > ${relocatedBssEnd.toHex()}, \"too many variables/data for BSS section\"")
asmgen.out("prog8_bss_section_size = * - prog8_bss_section_start")
} else {
@@ -290,12 +294,12 @@ internal class ProgramAndVarsGen(
asmgen.out(" .dsection BSS")
asmgen.out("prog8_bss_section_size = * - prog8_bss_section_start")
if(!relocateBssSlabs)
asmgen.out(" .dsection slabs_BSS")
asmgen.out(" .dsection BSS_SLABS")
asmgen.out("prog8_program_end\t; end of program label for progend()")
if(relocateBssSlabs) {
asmgen.out(" * = ${relocatedBssStart.toHex()}")
asmgen.out(" .dsection slabs_BSS")
asmgen.out(" .cerror * > ${relocatedBssEnd.toHex()}, \"too many data for slabs_BSS section\"")
asmgen.out(" .dsection BSS_SLABS")
asmgen.out(" .cerror * > ${relocatedBssEnd.toHex()}, \"too many data for BSS_SLABS section\"")
}
}
@@ -328,10 +332,9 @@ internal class ProgramAndVarsGen(
if (initializers.isNotEmpty()) {
asmgen.out("prog8_init_vars\t.block")
initializers.forEach { assign ->
if((assign.value as? PtNumber)?.number != 0.0 || allocator.isZpVar(assign.target.identifier!!.name))
val constvalue = assign.value as? PtNumber
if(constvalue==null || constvalue.number!=0.0 || allocator.isZpVar(assign.target.identifier!!.name))
asmgen.translate(assign)
else
throw AssemblyError("non-zp variable should not be initialized to zero; it will be zeroed as part of BSS clear")
// the other variables that should be set to zero are done so as part of the BSS section clear.
}
asmgen.out(" rts\n .bend")
@@ -349,7 +352,7 @@ internal class ProgramAndVarsGen(
val varsInBlock = getVars(scope)
// Zeropage Variables
val varnames = varsInBlock.filter { it.value.type==StNodeType.STATICVAR }.map { it.value.scopedName }.toSet()
val varnames = varsInBlock.filter { it.value.type==StNodeType.STATICVAR }.mapTo(mutableSetOf()) { it.value.scopedNameString }
zeropagevars2asm(varnames)
// MemDefs and Consts
@@ -363,11 +366,96 @@ internal class ProgramAndVarsGen(
// normal statically allocated variables
val variables = varsInBlock
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName) }
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedNameString) }
.map { it.value as StStaticVariable }
nonZpVariables2asm(variables)
}
private fun asmTypeString(dt: DataType): String {
return when {
dt.isBool || dt.isUnsignedByte -> ".byte"
dt.isSignedByte -> ".char"
dt.isUnsignedWord || dt.isPointer -> ".word"
dt.isSignedWord -> ".sint"
dt.isLong -> ".dint"
dt.isFloat -> ".byte"
else -> {
throw AssemblyError("weird dt")
}
}
}
private fun structInstances2asm() {
fun initValues(instance: StStructInstance): List<String> {
val structtype: StStruct = symboltable.lookup(instance.structName) as StStruct
return structtype.fields.zip(instance.initialValues).map { (field, value) ->
if(field.first.isFloat) {
val numValue = (value as? StArrayElement.Number)?.value
?: throw AssemblyError("expected number for float field")
"["+compTarget.getFloatAsmBytes(numValue)+"]"
} else {
when (value) {
is StArrayElement.Number -> {
if(field.first.isPointer)
"$"+value.value.toInt().toString(16)
else if(field.first.isInteger)
value.value.toInt().toString()
else
value.value.toString()
}
is StArrayElement.AddressOf -> value.symbol
is StArrayElement.MemorySlab -> "$StMemorySlabBlockName.${value.name}"
is StArrayElement.BoolValue -> if(value.value) "1" else "0"
is StArrayElement.StructInstance -> throw AssemblyError("struct instance in struct initializer")
}
}
}
}
asmgen.out("; struct types")
symboltable.allStructTypes().distinctBy { it.name }.forEach { structtype ->
val structargs = structtype.fields.withIndex().joinToString(",") { field -> "f${field.index}" }
asmgen.out("${structtype.scopedNameString} .struct $structargs\n")
structtype.fields.withIndex().forEach { (index, field) ->
val dt = field.first
val varname = "f${index}"
val type = asmTypeString(dt)
asmgen.out("p8v_${field.second} $type \\$varname") // note: struct field symbol prefixing done here because that is a lot simpler than fixing up all expressions in the AST
}
asmgen.out(" .endstruct\n")
}
val (instancesNoInit, instances) = symboltable.allStructInstances().partition { it.initialValues.isEmpty() }
asmgen.out("; struct instances without initialization values, as BSS zeroed at startup\n")
asmgen.out(" .section BSS\n")
asmgen.out("${StStructInstanceBlockName}_bss .block\n")
instancesNoInit.forEach {
val structtype: StStruct = symboltable.lookup(it.structName) as StStruct
val zerovalues = structtype.fields.map { field ->
if(field.first.isFloat) {
val floatbytes = List(compTarget.memorySize(BaseDataType.FLOAT)) { "?" }
"[${floatbytes.joinToString(",")}]"
}
else "?"
}
asmgen.out("${it.name} .dstruct ${it.structName}, ${zerovalues.joinToString(",")}\n")
}
asmgen.out(" .endblock\n")
asmgen.out(" .send BSS\n")
asmgen.out("; struct instances with initialization values\n")
asmgen.out(" .section STRUCTINSTANCES\n")
asmgen.out("$StStructInstanceBlockName .block\n")
instances.forEach {
val instancename = it.name.substringAfter('.')
asmgen.out("$instancename .dstruct ${it.structName}, ${initValues(it).joinToString(",")}\n")
}
asmgen.out(" .endblock\n")
asmgen.out(" .send STRUCTINSTANCES\n")
}
internal fun translateAsmSubroutine(sub: PtAsmSub) {
if(sub.inline) {
return // subroutine gets inlined at call site.
@@ -417,7 +505,7 @@ internal class ProgramAndVarsGen(
val varsInSubroutine = getVars(scope)
// Zeropage Variables
val varnames = varsInSubroutine.filter { it.value.type==StNodeType.STATICVAR }.map { it.value.scopedName }.toSet()
val varnames = varsInSubroutine.filter { it.value.type==StNodeType.STATICVAR }.mapTo(mutableSetOf()) { it.value.scopedNameString }
zeropagevars2asm(varnames)
// MemDefs and Consts
@@ -435,7 +523,7 @@ internal class ProgramAndVarsGen(
if((sub.name=="start" || sub.name=="p8s_start") && (sub.definingBlock()!!.name=="main" || sub.definingBlock()!!.name=="p8b_main"))
entrypointInitialization()
val params = sub.parameters
val params = sub.signature.children.filterIsInstance<PtSubroutineParameter>()
if(functioncallAsmGen.optimizeIntArgsViaCpuRegisters(params)) {
asmgen.out("; simple int arg(s) passed via cpu register(s)")
@@ -479,6 +567,7 @@ internal class ProgramAndVarsGen(
else when(dt) {
BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.BOOL -> asmgen.out("$name .byte ?")
BaseDataType.UWORD, BaseDataType.WORD -> asmgen.out("$name .word ?")
BaseDataType.LONG -> asmgen.out("$name .sint ?")
BaseDataType.FLOAT -> asmgen.out("$name .fill ${options.compTarget.FLOAT_MEM_SIZE}")
else -> throw AssemblyError("weird dt for extravar $dt")
}
@@ -491,7 +580,7 @@ internal class ProgramAndVarsGen(
// normal statically allocated variables
val variables = varsInSubroutine
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName) }
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedNameString) }
.map { it.value as StStaticVariable }
nonZpVariables2asm(variables)
@@ -546,12 +635,12 @@ internal class ProgramAndVarsGen(
stringVarsWithInitInZp.forEach {
val varname = asmgen.asmVariableName(it.name)+"_init_value"
outputStringvar(varname, 0, it.value.second, it.value.first)
outputStringvar(varname, 0u, it.value.second, it.value.first)
}
arrayVarsWithInitInZp.forEach {
val varname = asmgen.asmVariableName(it.name)+"_init_value"
arrayVariable2asm(varname, it.alloc.dt, 0, it.value, null)
arrayVariable2asm(varname, it.alloc.dt, 0u, it.value, null)
}
asmgen.out("+")
@@ -619,7 +708,7 @@ internal class ProgramAndVarsGen(
fun generate(section: String, variables: List<StStaticVariable>) {
asmgen.out(" .section $section")
val (notAligned, aligned) = variables.partition { it.align == 0 }
val (notAligned, aligned) = variables.partition { it.align == 0u }
notAligned.sortedWith(compareBy<StStaticVariable> { it.name }.thenBy { it.dt.base }).forEach {
uninitializedVariable2asm(it)
}
@@ -644,8 +733,8 @@ internal class ProgramAndVarsGen(
if(varsWithInit.isNotEmpty()) {
asmgen.out("; non-zeropage variables with init value")
val (stringvars, othervars) = varsWithInit.sortedBy { it.name }.partition { it.dt.isString }
val (notAlignedStrings, alignedStrings) = stringvars.partition { it.align==0 }
val (notAlignedOther, alignedOther) = othervars.partition { it.align==0 }
val (notAlignedStrings, alignedStrings) = stringvars.partition { it.align==0u }
val (notAlignedOther, alignedOther) = othervars.partition { it.align==0u }
notAlignedStrings.forEach {
outputStringvar(
it.name,
@@ -653,7 +742,7 @@ internal class ProgramAndVarsGen(
it.initializationStringValue!!.second,
it.initializationStringValue!!.first
)
asmgen.romableError("string (${it.dt} ${it.name}) can only be used as read-only in ROMable code", Position.DUMMY, false) // TODO print warning with position of the var
asmgen.romableError("string (${it.dt} ${it.name}) can only be used as read-only in ROMable code", it.astNode?.position ?: Position.DUMMY, false)
}
alignedStrings.sortedBy { it.align }.forEach {
outputStringvar(
@@ -662,22 +751,22 @@ internal class ProgramAndVarsGen(
it.initializationStringValue!!.second,
it.initializationStringValue!!.first
)
asmgen.romableError("string (${it.dt} ${it.name}) can only be used as read-only in ROMable code", Position.DUMMY, false) // TODO print warning with position of the var
asmgen.romableError("string (${it.dt} ${it.name}) can only be used as read-only in ROMable code", it.astNode?.position ?: Position.DUMMY, false)
}
notAlignedOther.sortedBy { it.type }.forEach {
staticVariable2asm(it)
if(it.dt.isArray)
asmgen.romableError("array (${it.dt} ${it.name}) can only be used as read-only in ROMable code", Position.DUMMY, false) // TODO print warning with position of the var
asmgen.romableError("array (${it.dt} ${it.name}) can only be used as read-only in ROMable code", it.astNode?.position ?: Position.DUMMY, false)
else
asmgen.romableError("inlined variable (${it.dt} ${it.name})", Position.DUMMY) // TODO print warning with position of the var
asmgen.romableError("inlined variable (${it.dt} ${it.name})", it.astNode?.position ?: Position.DUMMY)
}
alignedOther.sortedBy { it.align }.sortedBy { it.type }.forEach {
staticVariable2asm(it)
if(it.dt.isArray)
asmgen.romableError("array (${it.dt} ${it.name}) can only be used as read-only in ROMable code", Position.DUMMY, false) // TODO print warning with position of the var
asmgen.romableError("array (${it.dt} ${it.name}) can only be used as read-only in ROMable code", it.astNode?.position ?: Position.DUMMY, false)
else
asmgen.romableError("inlined variable (${it.dt} ${it.name})", Position.DUMMY) // TODO print warning with position of the var
asmgen.romableError("inlined variable (${it.dt} ${it.name})", it.astNode?.position ?: Position.DUMMY)
}
}
}
@@ -689,26 +778,31 @@ internal class ProgramAndVarsGen(
dt.isSignedByte -> asmgen.out("${variable.name}\t.char ?")
dt.isUnsignedWord -> asmgen.out("${variable.name}\t.word ?")
dt.isSignedWord -> asmgen.out("${variable.name}\t.sint ?")
dt.isLong -> asmgen.out("${variable.name}\t.dint ?")
dt.isFloat -> asmgen.out("${variable.name}\t.fill ${compTarget.FLOAT_MEM_SIZE}")
dt.isSplitWordArray -> {
alignVar(variable.align)
val numbytesPerHalf = compTarget.memorySize(variable.dt, variable.length!!) / 2
val numbytesPerHalf = compTarget.memorySize(variable.dt, variable.length!!.toInt()) / 2
asmgen.out("${variable.name}_lsb\t.fill $numbytesPerHalf")
asmgen.out("${variable.name}_msb\t.fill $numbytesPerHalf")
}
dt.isArray -> {
alignVar(variable.align)
val numbytes = compTarget.memorySize(variable.dt, variable.length!!)
val numbytes = compTarget.memorySize(variable.dt, variable.length!!.toInt())
asmgen.out("${variable.name}\t.fill $numbytes")
}
dt.isPointer -> asmgen.out("${variable.name}\t.word ?") // a pointer is just an uword address
dt.isPointerArray -> {
TODO("pointers are not supported yet for uninitialized array ${variable.astNode?.position}")
}
else -> {
throw AssemblyError("weird dt")
}
}
}
private fun alignVar(align: Int) {
if(align > 1)
private fun alignVar(align: UInt) {
if(align > 1u)
asmgen.out(" .align ${align.toHex()}")
}
@@ -733,6 +827,7 @@ internal class ProgramAndVarsGen(
// dt.isSignedByte -> asmgen.out("${variable.name}\t.char $initialValue")
// dt.isUnsignedWord -> asmgen.out("${variable.name}\t.word ${initialValue.toHex()}")
// dt.isSignedWord -> asmgen.out("${variable.name}\t.sint $initialValue")
// dt.isLong -> asmgen.out("${variable.name}\t.dint $initialValue")
// dt.isFloat -> {
// if(initialValue==0) {
// asmgen.out("${variable.name}\t.byte 0,0,0,0,0 ; float")
@@ -745,7 +840,7 @@ internal class ProgramAndVarsGen(
throw AssemblyError("all string vars should have been interned into prog")
}
dt.isArray -> {
arrayVariable2asm(variable.name, variable.dt, variable.align, variable.initializationArrayValue, variable.length)
arrayVariable2asm(variable.name, variable.dt, variable.align, variable.initializationArrayValue, variable.length?.toInt())
}
else -> {
throw AssemblyError("weird dt")
@@ -753,7 +848,7 @@ internal class ProgramAndVarsGen(
}
}
private fun arrayVariable2asm(varname: String, dt: DataType, align: Int, value: StArray?, orNumberOfZeros: Int?) {
private fun arrayVariable2asm(varname: String, dt: DataType, align: UInt, value: StArray?, orNumberOfZeros: Int?) {
alignVar(align)
when {
dt.isUnsignedByteArray || dt.isBoolArray -> {
@@ -777,7 +872,7 @@ internal class ProgramAndVarsGen(
}
}
dt.isSplitWordArray -> {
if(dt.elementType().isUnsignedWord) {
if(dt.elementType().isUnsignedWord || dt.elementType().isPointer) {
val data = makeArrayFillDataUnsigned(dt, value, orNumberOfZeros)
asmgen.out("_array_$varname := ${data.joinToString()}")
asmgen.out("${varname}_lsb\t.byte <_array_$varname")
@@ -809,10 +904,22 @@ internal class ProgramAndVarsGen(
asmgen.out(" .sint " + chunk.joinToString())
}
}
dt.isLongArray -> {
val data = makeArrayFillDataSigned(dt, value, orNumberOfZeros)
if (data.size <= 16)
asmgen.out("$varname\t.dint ${data.joinToString()}")
else {
asmgen.out(varname)
for (chunk in data.chunked(16))
asmgen.out(" .dint " + chunk.joinToString())
}
}
dt.isFloatArray -> {
val array = value ?: zeroFilledArray(orNumberOfZeros!!)
val floatFills = array.map {
compTarget.getFloatAsmBytes(it.number!!)
val numValue = (it as? StArrayElement.Number)?.value
?: throw AssemblyError("expected number for float array")
compTarget.getFloatAsmBytes(numValue)
}
asmgen.out(varname)
for (f in array.zip(floatFills))
@@ -823,11 +930,7 @@ internal class ProgramAndVarsGen(
}
private fun zeroFilledArray(numElts: Int): StArray {
val values = mutableListOf<StArrayElement>()
repeat(numElts) {
values.add(StArrayElement(0.0, null, null))
}
return values
return List(numElts) { StArrayElement.Number(0.0) }
}
private fun memdefsAndConsts2asm(memvars: Collection<StMemVar>, consts: Collection<StConstant>) {
@@ -835,10 +938,17 @@ internal class ProgramAndVarsGen(
asmgen.out(" ${it.name} = ${it.address.toHex()}")
}
consts.sortedBy { it.name }.forEach {
if(it.dt==BaseDataType.FLOAT)
asmgen.out(" ${it.name} = ${it.value}")
else
asmgen.out(" ${it.name} = ${it.value.toHex()}")
val nameWithoutColons = it.name.replace("::", "_")
if(it.dt.isFloat)
asmgen.out(" ${nameWithoutColons} = ${it.value}")
else {
if(it.value!=null)
asmgen.out(" ${nameWithoutColons} = ${it.value!!.toHex()}")
else if(it.memorySlab!=null)
asmgen.out(" ${nameWithoutColons} = ${StMemorySlabBlockName}.${it.memorySlab!!.name}")
else
TODO("asm gen for ${nameWithoutColons} = memory alloc?")
}
}
}
@@ -855,7 +965,7 @@ internal class ProgramAndVarsGen(
}
}
private fun outputStringvar(varname: String, align: Int, encoding: Encoding, value: String) {
private fun outputStringvar(varname: String, align: UInt, encoding: Encoding, value: String) {
alignVar(align)
asmgen.out("$varname\t; $encoding:\"${value.escape().replace("\u0000", "<NULL>")}\"", false)
val bytes = compTarget.encodeString(value, encoding).plus(0.toUByte())
@@ -868,35 +978,36 @@ internal class ProgramAndVarsGen(
val array = value ?: zeroFilledArray(orNumberOfZeros!!)
return when {
dt.isBoolArray ->
// byte array can never contain pointer-to types, so treat values as all integers
array.map {
if(it.boolean!=null)
if(it.boolean==true) "1" else "0"
else {
val number = it.number!!
if(number==0.0) "0" else "1"
when(it) {
is StArrayElement.BoolValue -> if(it.value) "1" else "0"
is StArrayElement.Number -> if(it.value==0.0) "0" else "1"
else -> throw AssemblyError("unexpected array element type for bool array")
}
}
dt.isUnsignedByteArray ->
// byte array can never contain pointer-to types, so treat values as all integers
array.map {
val number = it.number!!.toInt()
"$"+number.toString(16).padStart(2, '0')
val numValue = (it as? StArrayElement.Number)?.value
?: throw AssemblyError("expected number for ubyte array")
"$"+numValue.toInt().toString(16).padStart(2, '0')
}
dt.isArray && dt.elementType().isUnsignedWord -> array.map {
if(it.number!=null) {
"$" + it.number!!.toInt().toString(16).padStart(4, '0')
dt.isArray && (dt.elementType().isUnsignedWord || dt.elementType().isPointer) -> array.map {
when(it) {
is StArrayElement.Number -> "$" + it.value.toInt().toString(16).padStart(4, '0')
is StArrayElement.AddressOf -> {
val symbol = symboltable.lookup(it.symbol)!!
if(symbol is StStaticVariable && symbol.dt.isSplitWordArray)
asmgen.asmSymbolName(it.symbol+"_lsb")
else
asmgen.asmSymbolName(it.symbol)
}
is StArrayElement.MemorySlab -> asmgen.asmSymbolName("$StMemorySlabBlockName.${it.name}")
is StArrayElement.StructInstance -> {
val prefix = if(it.uninitialized) "${StStructInstanceBlockName}_bss" else StStructInstanceBlockName
asmgen.asmSymbolName("$prefix.${it.name}")
}
is StArrayElement.BoolValue -> throw AssemblyError("bool in word array")
}
else if(it.addressOfSymbol!=null) {
val addrOfSymbol = it.addressOfSymbol!!
val symbol = symboltable.lookup(addrOfSymbol)!!
if(symbol is StStaticVariable && symbol.dt.isSplitWordArray)
asmgen.asmSymbolName(addrOfSymbol+"_lsb") // the _lsb split array comes first in memory
else
asmgen.asmSymbolName(addrOfSymbol)
}
else
throw AssemblyError("weird array elt")
}
else -> throw AssemblyError("invalid dt")
}
@@ -905,33 +1016,66 @@ internal class ProgramAndVarsGen(
private fun makeArrayFillDataSigned(dt: DataType, value: StArray?, orNumberOfZeros: Int?): List<String> {
val array = value ?: zeroFilledArray(orNumberOfZeros!!)
return when {
// byte array can never contain pointer-to types, so treat values as all integers
dt.isUnsignedByteArray ->
array.map {
val number = it.number!!.toInt()
val numValue = (it as? StArrayElement.Number)?.value
?: throw AssemblyError("expected number for ubyte array")
val number = numValue.toInt()
"$"+number.toString(16).padStart(2, '0')
}
dt.isSignedByteArray ->
array.map {
val number = it.number!!.toInt()
val numValue = (it as? StArrayElement.Number)?.value
?: throw AssemblyError("expected number for byte array")
val number = numValue.toInt()
val hexnum = number.absoluteValue.toString(16).padStart(2, '0')
if(number>=0)
"$$hexnum"
else
"-$$hexnum"
if(number>=0) "$$hexnum" else "-$$hexnum"
}
dt.isArray && (dt.elementType().isUnsignedWord || dt.elementType().isPointer) ->
array.map {
when(it) {
is StArrayElement.Number -> "$" + it.value.toInt().toString(16).padStart(4, '0')
is StArrayElement.AddressOf -> {
val symbol = symboltable.lookup(it.symbol)!!
if(symbol is StStaticVariable && symbol.dt.isSplitWordArray)
asmgen.asmSymbolName(it.symbol+"_lsb")
else
asmgen.asmSymbolName(it.symbol)
}
is StArrayElement.MemorySlab -> asmgen.asmSymbolName("$StMemorySlabBlockName.${it.name}")
is StArrayElement.StructInstance -> {
val prefix = if(it.uninitialized) "${StStructInstanceBlockName}_bss" else StStructInstanceBlockName
asmgen.asmSymbolName("$prefix.${it.name}")
}
is StArrayElement.BoolValue -> throw AssemblyError("bool in word array")
}
}
dt.isSignedWordArray ->
array.map {
when(it) {
is StArrayElement.Number -> {
val number = it.value.toInt()
val hexnum = number.absoluteValue.toString(16).padStart(4, '0')
if (number >= 0) "$$hexnum" else "-$$hexnum"
}
is StArrayElement.AddressOf -> asmgen.asmSymbolName(it.symbol)
is StArrayElement.MemorySlab -> asmgen.asmSymbolName("$StMemorySlabBlockName.${it.name}")
else -> throw AssemblyError("unexpected array element type for signed word array")
}
}
dt.isLongArray ->
array.map {
when(it) {
is StArrayElement.Number -> {
val number = it.value.toInt()
val hexnum = number.absoluteValue.toLongHex()
if (number >= 0) "$$hexnum" else "-$$hexnum"
}
is StArrayElement.AddressOf -> asmgen.asmSymbolName(it.symbol)
is StArrayElement.MemorySlab -> asmgen.asmSymbolName("$StMemorySlabBlockName.${it.name}")
else -> throw AssemblyError("unexpected array element type for long array")
}
}
dt.isArray && dt.elementType().isUnsignedWord -> array.map {
val number = it.number!!.toInt()
"$" + number.toString(16).padStart(4, '0')
}
dt.isArray && dt.elementType().isSignedWord -> array.map {
val number = it.number!!.toInt()
val hexnum = number.absoluteValue.toString(16).padStart(4, '0')
if(number>=0)
"$$hexnum"
else
"-$$hexnum"
}
else -> throw AssemblyError("invalid dt")
}
}
@@ -1,7 +1,7 @@
package prog8.codegen.cpu6502
import com.github.michaelbull.result.fold
import com.github.michaelbull.result.onSuccess
import com.github.michaelbull.result.onOk
import prog8.code.*
import prog8.code.core.*
@@ -52,7 +52,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
val varsRequiringZp = allVariables.filter { it.zpwish == ZeropageWish.REQUIRE_ZEROPAGE }
val varsPreferringZp = allVariables.filter { it.zpwish == ZeropageWish.PREFER_ZEROPAGE }
val varsNotZp = allVariables.filter { it.zpwish == ZeropageWish.NOT_IN_ZEROPAGE }
val (varsDontCareWithoutAlignment, varsDontCareWithAlignment) = allVariables.filter { it.zpwish == ZeropageWish.DONTCARE }.partition { it.align == 0 }
val (varsDontCareWithoutAlignment, varsDontCareWithAlignment) = allVariables.filter { it.zpwish == ZeropageWish.DONTCARE }.partition { it.align == 0u }
require(varsDontCareWithAlignment.size + varsDontCareWithoutAlignment.size + varsRequiringZp.size + varsPreferringZp.size + varsNotZp.size == numberOfAllocatableVariables)
var numVariablesAllocatedInZP = 0
@@ -60,9 +60,9 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
varsRequiringZp.forEach { variable ->
val result = zeropage.allocate(
variable.scopedName,
variable.scopedNameString,
variable.dt,
variable.length,
variable.length?.toInt(),
variable.astNode?.position ?: Position.DUMMY,
errors
)
@@ -79,33 +79,33 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
if(errors.noErrors()) {
varsPreferringZp.forEach { variable ->
val result = zeropage.allocate(
variable.scopedName,
variable.scopedNameString,
variable.dt,
variable.length,
variable.length?.toInt(),
variable.astNode?.position ?: Position.DUMMY,
errors
)
result.onSuccess { numVariablesAllocatedInZP++ }
result.onOk { numVariablesAllocatedInZP++ }
// no need to check for allocation error, if there is one, just allocate in normal system ram.
}
// try to allocate the "don't care" interger variables into the zeropage until it is full.
// TODO some form of intelligent priorization? most often used variables first? loopcounter vars first? ...?
// TODO some form of intelligent priorization? most often used variables first? loopcounter vars first? ...? Simply sorting by size (bytes prioritized) has BAD results
if(errors.noErrors()) {
val sortedList = varsDontCareWithoutAlignment.sortedByDescending { it.scopedName }
val sortedList = varsDontCareWithoutAlignment.sortedByDescending { it.scopedNameString }
for (variable in sortedList) {
if(variable.dt.isIntegerOrBool) {
if(variable.dt.isIntegerOrBool || variable.dt.isPointer) {
if(zeropage.free.isEmpty()) {
break
} else {
val result = zeropage.allocate(
variable.scopedName,
variable.scopedNameString,
variable.dt,
variable.length,
variable.length?.toInt(),
variable.astNode?.position ?: Position.DUMMY,
errors
)
result.onSuccess { numVariablesAllocatedInZP++ }
result.onOk { numVariablesAllocatedInZP++ }
}
} else
numberOfNonIntegerVariables++
@@ -8,6 +8,7 @@ import prog8.code.core.DataType
import prog8.code.core.RegisterOrPair
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8.code.target.PETTarget
import prog8.codegen.cpu6502.AsmGen6502Internal
//
@@ -20,6 +21,8 @@ internal class AnyExprAsmGen(
private val asmgen: AsmGen6502Internal
) {
fun assignAnyExpressionUsingStack(expr: PtBinaryExpression, assign: AsmAssignment): Boolean {
if(expr.operator==".")
throw AssemblyError("pointer deref expression should have been handled elsewhere ${expr.position}")
when {
expr.type.isByteOrBool -> {
if(expr.left.type.isByteOrBool && expr.right.type.isByteOrBool)
@@ -40,12 +43,24 @@ internal class AnyExprAsmGen(
}
throw AssemblyError("expression should have been handled otherwise: word ${expr.operator} at ${expr.position}")
}
expr.type.isLong -> {
require(expr.left.type.isLong && expr.right.type.isLong) {
"both operands must be longs"
}
throw AssemblyError("expression should have been handled otherwise: long ${expr.operator} at ${expr.position}")
}
expr.type.isFloat -> {
require(expr.left.type.isFloat && expr.right.type.isFloat) {
"both operands must be floats"
}
return assignFloatBinExpr(expr, assign)
}
expr.type.isPointer -> {
require((expr.left.type.isPointer || expr.left.type.isUnsignedWord) && (expr.right.type.isPointer || expr.right.type.isUnsignedWord)) {
"both operands must be pointers or uwords"
}
throw AssemblyError("expression should have been handled otherwise: pointer ${expr.operator} at ${expr.position}")
}
else -> throw AssemblyError("weird expression type in assignment")
}
}
@@ -171,13 +186,13 @@ internal class AnyExprAsmGen(
asmgen.assignRegister(RegisterOrPair.A, assign.target)
return true
}
else -> TODO("float expression operator ${expr.operator}")
else -> TODO("float expression operator ${expr.operator} ${expr.position}")
}
}
private fun assignFloatOperandsToFACandARG(left: PtExpression, right: PtExpression) {
when(asmgen.options.compTarget.name) {
C64Target.NAME -> {
C64Target.NAME, PETTarget.NAME -> {
// C64 math library has a quirk: you have always make sure FAC2/ARG is loaded last (done using CONUPK)
// otherwise the result of certain floating point operations such as FDIVT will be wrong.
// see https://www.c64-wiki.com/wiki/CONUPK
@@ -1,16 +1,19 @@
package prog8.codegen.cpu6502.assignment
import prog8.code.StConstant
import prog8.code.ast.*
import prog8.code.core.*
import prog8.code.core.BaseDataType
import prog8.codegen.cpu6502.AsmGen6502Internal
internal enum class TargetStorageKind {
VARIABLE,
VARIABLE, // non-pointer variable
ARRAY,
MEMORY,
REGISTER,
VOID // assign nothing - used in multi-value assigns for void placeholders
POINTER,
VOID // assign nothing - used in multi-value assigns for void placeholders
}
internal enum class SourceStorageKind {
@@ -32,6 +35,7 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
val array: PtArrayIndexer? = null,
val memory: PtMemoryByte? = null,
val register: RegisterOrPair? = null,
val pointer: PtPointerDeref? = null,
val origAstTarget: PtAssignTarget? = null
)
{
@@ -39,13 +43,28 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
val asmVarname: String by lazy {
if (array == null)
variableAsmName!!
else
asmgen.asmVariableName(array.variable)
else {
if(array.variable==null)
TODO("asmVarname for array with pointer $position")
asmgen.asmVariableName(array.variable!!)
}
}
init {
if(register!=null && !datatype.isNumericOrBool)
throw AssemblyError("must be numeric type")
if(register!=null) {
if(datatype.isPointer)
throw AssemblyError("register target type must be uword, not pointer $datatype $position")
else if(!datatype.isNumericOrBool)
throw AssemblyError("target type must be numeric not $datatype $position")
}
if(kind==TargetStorageKind.REGISTER)
require(register!=null)
else
require(register==null)
if(kind==TargetStorageKind.POINTER)
require(pointer!=null)
if(pointer!=null)
require(kind==TargetStorageKind.POINTER)
}
companion object {
@@ -77,6 +96,7 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
}
array != null -> return AsmAssignTarget(TargetStorageKind.ARRAY, asmgen, type, definingSub, target.position, array = array, origAstTarget = this)
memory != null -> return AsmAssignTarget(TargetStorageKind.MEMORY, asmgen, type, definingSub, target.position, memory = memory, origAstTarget = this)
pointerDeref != null -> return AsmAssignTarget(TargetStorageKind.POINTER, asmgen, type, definingSub, target.position, pointer = pointerDeref, origAstTarget = this)
else -> throw AssemblyError("weird target")
}
}
@@ -100,25 +120,17 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
RegisterOrPair.FAC2 -> {
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.FLOAT, scope, pos, register = registers)
}
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 -> {
in Cx16VirtualRegisters -> {
val dt = if(signed) DataType.WORD else DataType.UWORD
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, dt, scope, pos, register = registers)
}
in CombinedLongRegisters -> {
val dt = if(signed) DataType.LONG
else
TODO("unsigned long $pos")
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, dt, scope, pos, register = registers)
}
else -> throw AssemblyError("weird register $registers")
}
}
@@ -134,11 +146,14 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
left is PtIdentifier && left.name==scopedName
}
TargetStorageKind.ARRAY -> {
left is PtArrayIndexer && left isSameAs array!! && left.splitWords==array.splitWords
left is PtArrayIndexer && left isSameAs array!! && left.splitWords==array.splitWords && (left.pointerderef==null && array.pointerderef==null || left.pointerderef!! isSameAs array.pointerderef!!)
}
TargetStorageKind.MEMORY -> {
left isSameAs memory!!
}
TargetStorageKind.POINTER -> {
TODO("is pointer deref target same as expression? ${this.position}")
}
TargetStorageKind.REGISTER -> false
TargetStorageKind.VOID -> false
}
@@ -160,8 +175,11 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
val asmVarname: String
get() = if(array==null)
variableAsmName!!
else
asmgen.asmVariableName(array.variable)
else {
if(array.variable==null)
TODO("asmVarname for array with pointer")
asmgen.asmVariableName(array.variable!!)
}
companion object {
fun fromAstSource(value: PtExpression, program: PtProgram, asmgen: AsmGen6502Internal): AsmAssignSource {
@@ -180,6 +198,19 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
val parameter = asmgen.findSubroutineParameter(value.name, asmgen)
if(parameter?.definingAsmSub() != null)
throw AssemblyError("can't assign from a asmsub register parameter $value ${value.position}")
// Check if this identifier is a constant with a value - if so, use the value directly instead of reading from memory
val symbol = asmgen.symbolTable.lookup(value.name)
if(symbol is StConstant && symbol.value != null) {
val constValue = symbol.value!!
val baseType = when(symbol.dt.base) {
BaseDataType.POINTER -> BaseDataType.UWORD
else -> symbol.dt.base
}
return AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, symbol.dt,
number = PtNumber(baseType, constValue, value.position))
}
val varName=asmgen.asmVariableName(value)
// special case: "cx16.r[0-15]" are 16-bits virtual registers of the commander X16 system
if(value.type.isUnsignedWord && varName.lowercase().startsWith("cx16.r")) {
@@ -196,19 +227,20 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
is PtArrayIndexer -> {
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, value.type, array = value)
}
is PtBuiltinFunctionCall -> {
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, value.type, expression = value)
}
is PtFunctionCall -> {
val symbol = asmgen.symbolTable.lookup(value.name) ?: throw AssemblyError("lookup error ${value.name}")
val sub = symbol.astNode as IPtSubroutine
val returnType =
if(sub is PtSub && sub.returns.size>1)
DataType.UNDEFINED // TODO list of types instead?
else
sub.returnsWhatWhere().firstOrNull { rr -> rr.first.registerOrPair != null || rr.first.statusflag!=null }?.second
?: throw AssemblyError("can't translate zero return values in assignment")
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value)
if(value.builtin) {
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, value.type, expression = value)
} else {
val symbol = asmgen.symbolTable.lookup(value.name) ?: throw AssemblyError("lookup error ${value.name} ${value.position}")
val sub = symbol.astNode as IPtSubroutine
val returnType =
if(sub is PtSub && sub.signature.returns.size>1)
DataType.UNDEFINED // TODO list of types instead?
else
sub.returnsWhatWhere().firstOrNull { rr -> rr.first.registerOrPair != null || rr.first.statusflag!=null }?.second
?: throw AssemblyError("can't translate zero return values in assignment")
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value)
}
}
else -> {
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, value.type, expression = value)
@@ -250,7 +282,7 @@ internal sealed class AsmAssignmentBase(val source: AsmAssignSource,
}
val target: AsmAssignTarget
get() = targets.single()
get() = targets.singleOrNull() ?: throw AssemblyError("code assumes single target but assignment is multi-value $position")
}
internal class AsmAssignment(source: AsmAssignSource,
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
Loaded 100 of 493 files, more files were not shown because too many files have changed in this diff. Show more