Compare commits

...
670 Commits
Author SHA1 Message Date
Irmen de Jong 90ff904b6e add doc about newcodegen 2026-08-05 03:45:14 +02:00
Irmen de Jong ba54601eab release 12.3 2026-08-05 03:25:32 +02:00
Irmen de Jong 1c71f702f4 fix invalid const-folding of certain strings char classification routines such as isupper() 2026-08-05 02:55:36 +02:00
Irmen de Jong 3d3c567ea6 fix memory-byte read sign extension error when the byte was signed 2026-08-05 02:54:59 +02:00
Irmen de Jong 539c75bbf6 fix expression operand swap bug when there are side effects 2026-08-05 02:51:37 +02:00
Irmen de Jong 2683b6388a update history 2026-08-05 02:40:30 +02:00
Irmen de Jong a919ea63e4 remove wrong ast node copy() overrides 2026-08-04 04:54:53 +02:00
Irmen de Jong 07910fc543 IR: fix register type error for power of 2 multiplies as shifts 2026-08-04 00:06:09 +02:00
Irmen de Jong 13e032e1a3 add new IR instructions ADDIM and SUBIM 2026-08-03 19:31:57 +02:00
Irmen de Jong c3b5ee3dfd optimize imports 2026-08-03 14:31:42 +02:00
Irmen de Jong b0e0058310 split huge test file 2026-08-03 14:23:21 +02:00
Irmen de Jong 9cbbf85a85 fix chained expression optimization bug 2026-08-03 14:22:46 +02:00
Irmen de Jong 9936a855ab fix ast remainder optimization check 2026-08-03 13:05:26 +02:00
Irmen de Jong bb1d1e2007 gradle --non-interactive didn't work 2026-08-02 17:27:39 +02:00
Irmen de Jong 0aa6c130e8 update to kotlin 2.4.10 2026-08-02 17:23:22 +02:00
Irmen de Jong 51257e47da structs in symboldumps 2026-07-14 08:05:03 +02:00
Irmen de Jong 67ddd97f3d fix skill to allow fixed-size arrays as struct fields 2026-07-14 07:31:10 +02:00
Irmen de Jong abea7bf329 fix const evaluation type problem in bitwise operations 2026-07-14 07:28:31 +02:00
Irmen de Jong bc3f49dc13 unit test for bitwise long const errors 2026-07-14 07:27:44 +02:00
Irmen de Jong cf2a5ae4f9 fix test sizes 2026-07-11 23:06:26 +02:00
Irmen de Jong e5865a1446 fix delete root node bug in binarytree.p8 2026-07-11 22:16:51 +02:00
Irmen de Jong 5b5f553d37 improved error message when attempting to alias types,
fix size test
2026-07-11 17:04:52 +02:00
Irmen de Jong b9fe1f2057 fix fibonacci sequence to start from 0 2026-07-11 14:48:52 +02:00
Irmen de Jong cfd1f951f0 should call through 2026-07-08 23:36:48 +02:00
Irmen de Jong b0e55eb25b fix another float to long cast error 2026-07-08 23:27:10 +02:00
Irmen de Jong b405ffb8bd fix missing float to long cast, fix missing inlined floats routines prefix 2026-07-08 20:42:54 +02:00
Irmen de Jong 0fa08310e7 add missing floats.pow() to the docs, optimize the signature of many float functions 2026-07-08 19:28:34 +02:00
Irmen de Jong 968d9819db removed -nostdlib option 2026-07-07 23:31:20 +02:00
Irmen de Jong f85c59b12c add sys.MAX_LONG and sys.MIN_LONG 2026-07-07 04:37:59 +02:00
Irmen de Jong 34eedd1fe7 fix missing return values in symboldumps 2026-07-07 00:41:14 +02:00
Andy De George 2c10e2b102 Fix the swap documentation now that it's a statement and no longer a function (#229)
* [docs] Remove swap from built in functions

* Move swap info to programming doc

* Reintroduce swap statement documentation

* Apply suggestion from @Thraka
2026-07-07 00:08:29 +02:00
Irmen de Jong 05bea61a69 builtin functions now have a signature in the symbol dump files 2026-07-05 01:11:40 +02:00
Irmen de Jong 2a8b24074b remove error about memory mapped pointers 2026-07-05 00:53:13 +02:00
Irmen de Jong 1f184a14ae diskio: document path length limit 2026-07-04 18:24:19 +02:00
Irmen de Jong 8be1cd1090 pet32: added some alternative hopefully unused ZP locations (as long as you're not using tape I/O which is a common assumption for every other cbm target as well) 2026-07-04 18:12:31 +02:00
Irmen de Jong c3ec400ee7 pet32: don't use problematic zeropage locations 2026-07-04 00:03:22 +02:00
Irmen de Jong 5130a74754 removed expericodegen 2026-07-02 01:07:21 +02:00
Irmen de Jong e61db5ed3f cleanup 2026-07-01 22:16:00 +02:00
Irmen de Jong cc5ef21fd6 don't publish unfinished codegens yet 2026-07-01 21:40:00 +02:00
Irmen de Jong f1f925ecee clarify sqrt() behavior on values <0 2026-07-01 20:51:06 +02:00
Irmen de Jong ab422c0ebc fix wrong codegen and 'empty' library subroutines 2026-07-01 20:25:00 +02:00
Irmen de Jong 05a13463f4 tweak agent skills and helper routine visibilities 2026-07-01 17:55:13 +02:00
Irmen de Jong 19238cc366 IR: introduce strict and broad status flags contract for cpu targets
fix cmpi for words and longs
2026-07-01 01:38:35 +02:00
Irmen de Jong e3d4401d99 fix callfar 2026-07-01 00:02:26 +02:00
Irmen de Jong 5da4efc2a3 shorten long operations by using a Y-loop 2026-06-30 22:47:50 +02:00
Irmen de Jong c0c44156f1 new codegen: implement roxl/roxr longs, float casting, callfar 2026-06-30 22:18:41 +02:00
Irmen de Jong 8645833319 new codegen: float support and CALLI 2026-06-30 20:40:34 +02:00
Irmen de Jong 6a867d98eb new codegen: syscall support started 2026-06-30 05:57:19 +02:00
Irmen de Jong 58363ff4e4 Add required support for inlined asmsub routines in IR and new codegen 2026-06-30 03:17:19 +02:00
Irmen de Jong 2555aa72ca improve codegen argument slot processing (func call arg order) 2026-06-30 02:45:19 +02:00
Irmen de Jong 3b94fd4c4b IR: more consistent BSS handling 2026-06-30 01:14:31 +02:00
Irmen de Jong 95f500c32f fixing codegen 2026-06-29 23:10:55 +02:00
Irmen de Jong 870c2e8827 add name to StExtSubParameter to pass this to the codegen (required for @R0 etc) 2026-06-29 04:43:24 +02:00
Irmen de Jong af2360648f implement codegen mul and div routines, and fix symboloffsets 2026-06-29 04:43:24 +02:00
Irmen de Jong bb95d40d6d asm syntax 2026-06-29 04:43:24 +02:00
Irmen de Jong 55ea599fca Remove hardcoded external KERNAL entry points in codegen 2026-06-29 04:43:24 +02:00
Irmen de Jong fd5fdf3d13 IR codegen: Emit extsub as inline assembly equate for codegen references 2026-06-29 04:43:24 +02:00
Irmen de Jong ca129edce4 adding new 6502 codegen 2026-06-29 04:43:23 +02:00
Irmen de Jong c1694a4b08 add m68k codegen skeleton 2026-06-29 04:43:23 +02:00
Irmen de Jong a348bbbebe adding new 6502 codegen based on IR files 2026-06-29 04:43:23 +02:00
Irmen de Jong e6b0a89d64 asm coder skill 2026-06-29 04:43:09 +02:00
Irmen de Jong 9a4821d1e3 extra asm syntax rule in skill 2026-06-28 22:50:43 +02:00
Irmen de Jong 71583a5b93 Move symbol prefixing logic from 6502 codegen to IR generation. 2026-06-28 21:33:10 +02:00
Irmen de Jong 407ba34c72 IR fix certain setmsb/setlsb and rol/ror crashes on indexed pointers 2026-06-27 14:50:45 +02:00
Irmen de Jong fc4782a35b Support &blockname in IR generation 2026-06-27 12:54:36 +02:00
Irmen de Jong 7a83b7db1b Fix dark mode styling for ..data:: headers in documentation 2026-06-27 10:03:15 +02:00
Irmen de Jong a827f4c4ce Support block-level %align in IR 2026-06-27 09:51:08 +02:00
Irmen de Jong 01c5794799 IR: fixing zeropage array initialization 2026-06-27 02:42:55 +02:00
Irmen de Jong 1c093cca2d fix zeropage array initialization for split word arrays 2026-06-27 01:39:38 +02:00
Irmen de Jong a85b267872 streamline asm generation 2026-06-26 20:33:40 +02:00
Irmen de Jong 5e0c1c9794 Optimize parser by introducing SLL prediction mode with LL fallback for improved performance and error handling. 2026-06-25 03:53:18 +02:00
Irmen de Jong 887df44359 fix IR loadhr/storehr parsing from p8ir 2026-06-25 03:30:10 +02:00
Irmen de Jong a5fc162c71 Optimize block sorting and subroutine reordering logic in AST processing steps. 2026-06-25 02:22:44 +02:00
Irmen de Jong 9d3fc99d71 ignores 2026-06-25 01:58:14 +02:00
Irmen de Jong a871713907 some more explanations 2026-06-23 20:57:06 +02:00
Irmen de Jong 2a19e5682a add cx16 dynamicbanking example 2026-06-23 20:45:34 +02:00
Irmen de Jong 22d4fa73d6 rename bankmanager to bankselector 2026-06-23 19:07:31 +02:00
Irmen de Jong 2ed95c52ef tune JSRFAR bank byte setup 2026-06-23 01:01:35 +02:00
Irmen de Jong 73fb04aaf5 Add support for unique call-site IDs in banked subroutines 2026-06-23 00:28:22 +02:00
Irmen de Jong f97a7f426c improved cmp() documentation 2026-06-22 19:53:45 +02:00
Irmen de Jong c1189617f3 fix 64tass repo link 2026-06-22 00:12:55 +02:00
Irmen de Jong 78d43c3cb6 Merge branch 'refs/heads/bugfix12.2.1'
# Conflicts:
#	examples/test.p8
2026-06-20 19:50:42 +02:00
Irmen de Jong 86f9b6aa69 fixed cmp() sign/flags errors 2026-06-20 19:49:45 +02:00
Irmen de Jong 8df2bb8d2e Merge branch 'bugfix12.2.1' 2026-06-20 15:47:41 +02:00
Irmen de Jong 33f30d925e Update documentation with proper example links 2026-06-20 15:45:01 +02:00
Irmen de Jong c8c51245af Merge branch 'bugfix12.2.1' 2026-06-20 15:18:14 +02:00
Irmen de Jong db227679a8 fix empty/full boundary checks on various datastructions in the buffers module 2026-06-20 15:05:37 +02:00
Irmen de Jong 02110de844 optimized floats minf, maxf, clampf routines 2026-06-20 13:56:17 +02:00
Irmen de Jong 7d77a0eca6 Merge branch 'bugfix12.2.1'
# Conflicts:
#	compiler/test/TestCompilerOnExamples.kt
2026-06-19 23:37:49 +02:00
Irmen de Jong 0f3475128a fix divmod_uw_asm to not clobber R15 anymore 2026-06-19 23:23:53 +02:00
Irmen de Jong e6e4714b02 adjust pet music tempo a little
todo about memory mapping pointers
2026-06-19 19:59:54 +02:00
Irmen de Jong d83ece397d add configurable default launcher types for custom compilation targets 2026-06-19 18:58:05 +02:00
Irmen de Jong a3c768bc6b add floats.mod() 2026-06-19 18:35:09 +02:00
Irmen de Jong a3d14003cb Enhanced inlining: support subroutines with one simple parameter. NOTE: risky optimization, maybe still broken in obscure cases
also improve parent pointer relinking, fix namespace scoping, and refine assembly checks.
2026-06-19 04:57:54 +02:00
Irmen de Jong d451953e40 improve prog8 skill 2026-06-19 02:18:41 +02:00
Irmen de Jong d505c42d15 improve undefined symbol error handling
(cherry picked from commit 8c92663824)
2026-06-19 02:16:11 +02:00
Irmen de Jong 8c92663824 improve undefined symbol error handling 2026-06-19 02:13:03 +02:00
Irmen de Jong 10d86eb4d8 Merge remote-tracking branch 'origin/master' 2026-06-19 00:45:32 +02:00
markjreed ee5e734f23 feat: add symbols for enharmonic flats (#226) 2026-06-19 00:44:50 +02:00
Irmen de Jong 6b233b7da3 pet: add sys.set_irq() and sys.restore_irq() support routines 2026-06-19 00:34:53 +02:00
Irmen de Jong a140519374 fix pet music example to use higher octave to be able to play correct B section end note 2026-06-18 23:57:38 +02:00
Irmen de Jong 2b118f2e42 pet32: add petsnd REST constant for silence, and set_gap() for intra note silence gap 2026-06-18 23:13:13 +02:00
Irmen de Jong be9d9fe377 pet32: added new playback functions to petsnd module to play songs (blocking or non-blocking). New 'music' example added for pet. 2026-06-18 21:02:33 +02:00
Irmen de Jong d1345aafec don't limit callfar/banked call support on fixed set of built in targets
but you have to set supports_banked_calls = true in the custom target config file.
2026-06-18 19:09:10 +02:00
Irmen de Jong d5932a7a57 improved bankin support error message 2026-06-17 23:45:30 +02:00
Irmen de Jong 5751beb72f fix some more LSP tests 2026-06-17 20:52:59 +02:00
Irmen de Jong 02194a735a Add supports_banked_calls property to target configuration files 2026-06-17 20:30:21 +02:00
Irmen de Jong b5fc25b2f4 Merge branch 'v12.2.1'
# Conflicts:
#	compiler/test/TestCompilerOnExamples.kt
#	docs/source/_static/symboldumps/skeletons-cx16.txt
2026-06-17 19:41:34 +02:00
Irmen de Jong 5a593b9ef3 Preserve cx16.r15 register in % for words. Update related code and documentation accordingly. 2026-06-17 19:23:19 +02:00
Irmen de Jong b411914624 Document virtual register clobbering details and update related comments. 2026-06-17 03:00:33 +02:00
Irmen de Jong 0f80b695e7 Fix invalid optimizer edge cases 2026-06-17 01:18:39 +02:00
Irmen de Jong 63c425f247 Merge branch 'refs/heads/v12.2.1' 2026-06-16 23:25:18 +02:00
Irmen de Jong 276627abcb remove broken expression optimization that combined a=x, a*=y into a=x*y 2026-06-16 23:19:42 +02:00
Irmen de Jong 34225d10aa add missing division and modulus code generation support for LONG variables 2026-06-16 23:18:07 +02:00
Irmen de Jong 69261c51b3 IR: Add single-bit manipulation opcodes (BITTST, BITSET, BITCLR, BITTOG) 2026-06-16 21:52:31 +02:00
Irmen de Jong 63d7ef7105 IR: Replace 6502-specific hardware register LOAD/STORE opcodes with generic, parameterized LOADHR/STOREHR opcodes 2026-06-16 20:59:51 +02:00
Irmen de Jong 88a3e5b23f Merge branch 'v12.2.1'
# Conflicts:
#	compiler/test/TestCompilerOnExamples.kt
#	compiler/test/codegeneration/TestArrayThings.kt
#	examples/test.p8
2026-06-16 00:48:35 +02:00
Irmen de Jong 282c3aa00b fix a return split word array codegen issue where it didn't use lsb suffix 2026-06-16 00:39:51 +02:00
Irmen de Jong 19f3b03ac3 remove problematic inlining for subroutines having 1 or more parameters, to avoid an endless loop 2026-06-16 00:02:05 +02:00
Irmen de Jong 1b5efe1e5d fix datatype size comparison for strings vs pointers 2026-06-15 09:03:13 +02:00
Irmen de Jong 2b5a15a178 limit iteration cycles in AST processing steps to prevent endless loops 2026-06-15 03:35:14 +02:00
Irmen de Jong a9f22d9375 handle library path files earlier during import to mark them as library files 2026-06-14 19:29:44 +02:00
Irmen de Jong f9b3514f2a fixes and tests for embedded arrays in structs 2026-06-14 16:18:23 +02:00
Irmen de Jong be12fb507e arrays in structs 2026-06-14 15:35:30 +02:00
Irmen de Jong b2dcd02ee4 prioritize frequently accessed variables for zeropage allocation based on usage scores 2026-06-14 13:41:31 +02:00
Irmen de Jong 5c23471254 Merge branch 'v12.2.1' 2026-06-13 12:16:14 +02:00
Irmen de Jong 6cb6f37871 exclude private symbols from completion suggestions in the LSP 2026-06-13 12:15:11 +02:00
Irmen de Jong 67e284b680 complexity based argument evaluation order in sub calls to optimize register spill handling 2026-06-13 03:22:15 +02:00
Irmen de Jong 1e5ba48fe0 tweak banked call support checks on compilation targets 2026-06-13 02:01:34 +02:00
Irmen de Jong 8e6a27d7c1 @bank now also allows the name of a subroutine for dynamic / overlay banking 2026-06-12 23:47:16 +02:00
Irmen de Jong d0edc0b484 prevent TCO with defer blocks 2026-06-12 03:05:26 +02:00
Irmen de Jong ccd3d94ec0 prevent TCO with defer blocks 2026-06-12 03:02:58 +02:00
Irmen de Jong 98355d3850 avoid shouldContain asserts on large strings in tests to avoid massive log dumps 2026-06-12 02:48:41 +02:00
Irmen de Jong 0e53113b85 Merge branch 'v12.2.1'
# Conflicts:
#	gradle.properties
2026-06-11 23:12:28 +02:00
Irmen de Jong e9a3ed457e fix optimizer problem that removed var inits for pointer vars in certain cases. Fixes #225 2026-06-11 22:37:51 +02:00
Irmen de Jong 6001da30d8 add comments to float library routines about the Z flag wrapper routines
(cherry picked from commit c55f5e4b50)
2026-06-11 21:50:25 +02:00
Irmen de Jong c55f5e4b50 add comments to float library routines about the Z flag wrapper routines 2026-06-11 21:49:51 +02:00
Irmen de Jong e983c0c2b9 fixed the c64 float heisenbug
(cherry picked from commit 532bf13057)
2026-06-11 21:40:42 +02:00
Irmen de Jong 532bf13057 fixed the c64 float heisenbug 2026-06-11 21:29:06 +02:00
Irmen de Jong de706e79de add friendly error for invalid 2D array syntax 2026-06-10 04:14:32 +02:00
Irmen de Jong a19f337ebe update prog8-coder skill 2026-06-10 02:49:48 +02:00
Irmen de Jong 041a904e44 fix serialdownload size test 2026-06-09 19:24:02 +02:00
Irmen de Jong 131134d24b fix daemon output path 2026-06-09 02:06:07 +02:00
Irmen de Jong 97fb5298a8 fix daemon output path 2026-06-09 01:59:06 +02:00
Irmen de Jong b580a177d4 serial module now has configurable baud rate, and some uart tweaks 2026-06-09 01:46:46 +02:00
Irmen de Jong a0b1ca5d9f Merge branch 'next-version-12.3' 2026-06-08 21:35:53 +02:00
Irmen de Jong 3a291c56b4 ci: split build step into compile, test, and package for better granularity 2026-06-08 21:07:25 +02:00
Irmen de Jong ed41d5dbc5 update libs and Kotlin
remove docs and examples imls
2026-06-08 20:58:27 +02:00
Irmen de Jong 628bd41740 update kotest 2026-06-08 20:43:54 +02:00
Irmen de Jong de988f72c6 switch from IDEA iml to Gradle build config, this finally fixes Kotest integration as well 2026-06-08 20:43:54 +02:00
Irmen de Jong 869c5ef544 move -srcdirs directories in front of everything else.
add -traceimports option to trace module imports.
2026-06-08 20:35:39 +02:00
Irmen de Jong 17ee233e8b tweak import path search order and document it 2026-06-08 20:34:17 +02:00
Irmen de Jong f2d1dffbe0 preparing for 12.2 release 2026-06-08 20:15:19 +02:00
Irmen de Jong 4008280a62 fix 64tass output order in daemon 2026-06-07 02:45:52 +02:00
Irmen de Jong 2e7235ec02 fix daemon mode error reporting paths 2026-06-07 02:31:06 +02:00
Irmen de Jong a4330f5048 fix -emu program name problem with -daemon 2026-06-07 00:34:35 +02:00
Irmen de Jong f65aeff231 -emu works again together with -daemon 2026-06-07 00:08:07 +02:00
Irmen de Jong 505243aff7 fix a optimizer crash 2026-06-06 23:29:16 +02:00
Irmen de Jong d0f2fc13ba add dummy rambank() and rombank() support for virtual target 2026-06-06 23:26:47 +02:00
Irmen de Jong 53d51dcd38 improve -libsearch by adding fuzzy search fallback 2026-06-06 19:28:53 +02:00
Irmen de Jong c1aa0e49fe fix a possible vera registers clobbering issue when using psg2 from irq handler 2026-06-06 17:30:08 +02:00
Irmen de Jong e1e72d4317 fix crash when using const ubyte pointer as string argument 2026-06-06 17:12:15 +02:00
Irmen de Jong feec343cbc made -daemon more robust 2026-06-06 12:41:15 +02:00
Irmen de Jong 5cdc4233f3 ideas & update prog8-coder skill 2026-06-04 01:16:41 +02:00
Irmen de Jong 1ae89d18cc prevent string deduplication for struct initializers 2026-06-03 01:43:50 +02:00
Irmen de Jong eb19d00a1e prevent faulty inlining of subs with register-aliased parameters 2026-06-02 23:14:55 +02:00
Irmen de Jong e01ee10ef0 fix output binary path displayed to the user 2026-06-02 21:05:03 +02:00
Irmen de Jong f4b3cb12e1 history 2026-06-01 23:19:31 +02:00
Irmen de Jong 77ced60ad6 fix psg2 release timing to make it linear just as attack.
clarify envelope timing details in docs.
2026-06-01 22:58:42 +02:00
Irmen de Jong 6fad90af6e long for loop warning 2026-05-31 23:42:58 +02:00
Irmen de Jong 3076318d7e implement missing for loops on long rages for 6502 2026-05-31 23:38:35 +02:00
Irmen de Jong 9c99110738 doc 2026-05-31 23:07:16 +02:00
Irmen de Jong 1457d36997 beta3 2026-05-31 17:47:37 +02:00
Irmen de Jong 43fb9296dd clarify serial module docs 2026-05-31 17:46:33 +02:00
Irmen de Jong 8bf744cc4a fix some bitwise expression optimization bugs 2026-05-31 07:35:32 +02:00
Irmen de Jong f1f5f070cf fix invalid asm for some in-place long operations on zp vars 2026-05-31 05:14:33 +02:00
Irmen de Jong 191a240d0d adjust extsub jump table offsets to more closely match reality 2026-05-30 23:42:52 +02:00
Irmen de Jong cafb2e355b implement long loop counter support 2026-05-30 23:36:08 +02:00
Irmen de Jong c99c5751f8 tweak serial 2026-05-30 21:11:19 +02:00
Irmen de Jong 435b7e56fa IR VM refactor to reduce class size 2026-05-29 23:14:44 +02:00
Irmen de Jong 14f13fac53 IR tweaks 2026-05-29 22:17:56 +02:00
Irmen de Jong f8b5037924 spelling corrections 2026-05-29 20:58:06 +02:00
Irmen de Jong d2870cb02e implement hex mode file transfer in serial module
as an attempt to have a working file transfer because the binary mode is still failing right now
2026-05-29 20:04:40 +02:00
Irmen de Jong 8e43fc426c IR: handle extsub with banked addresses properly 2026-05-28 03:25:04 +02:00
Irmen de Jong ba1b6e127a IR: properly consume registers for void targets 2026-05-28 00:57:00 +02:00
Irmen de Jong 4f36a6958c fix a few if-expression typecast issues 2026-05-28 00:55:15 +02:00
Irmen de Jong df8f08b4ab more serial tweaks to attempt to fix binary downloads 2026-05-27 23:42:55 +02:00
Irmen de Jong fd5746d3fc tweak the serial module a bit for more robustness 2026-05-27 02:22:35 +02:00
Irmen de Jong 6ab74fd51d don't erroneously allow taking the address of const values 2026-05-27 00:58:01 +02:00
Irmen de Jong 245082ca1d plain console output in github action builds 2026-05-24 12:40:43 +02:00
Irmen de Jong 845e58ea10 64tass install step 2026-05-24 12:33:47 +02:00
Irmen de Jong 2a5dbc1ef7 update project urls that have been moved from github to codeberg 2026-05-24 12:29:09 +02:00
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
507 changed files with 58728 additions and 23268 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.
+291
View File
@@ -0,0 +1,291 @@
---
name: asm6502-coder
description: Writing or understanding 6502/65C02 assembly code using 64tass syntax, for use as stand alone .asm or inline within Prog8 programs
---
# 65(C)02 Assembly Coder Skill
You are an expert MOS 6502 assembly development assistant. Keep responses concise and practical — prefer short, correct code examples over lengthy prose.
You are working with **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.
## Assembler: 64tass Syntax
### Formatting Rules
- Labels start in the first column of the line.
- Instructions are indented at least 4 spaces.
- Two spaces between the instruction opcode and its operand (e.g., `lda #1`, `sta $d020`).
- End-of-line comments are preceded by two spaces before the `;` (e.g., `sta $d020 ; border color`).
- If a comment is the only thing on a line, it starts in the first column (no indentation).
- Opcodes and operands are written in lowercase (e.g., `lda`, not `LDA`; `$d020`, not `$D020`).
- **NOT ca65/cc65** or other assemblers. Key differences:
- `.proc` / `.pend` for procedures (scoping)
- `_label` for local labels (prefixed with underscore, scoped to `.proc`)
- **Symbol aliases**: inside a `.proc`, use `_name = P8ZP_SCRATCH_B1` to give a scratch variable a descriptive name.
- **Anonymous labels**: defined as `+` (forward) or `-` (backward) at the start of a line.
- **CRITICAL DISTINCTION**: Label **definitions** are always just a single `+` or `-`. Label **references** use `+`, `++`, `+++` to refer to the 1st, 2nd, 3rd upcoming forward label, and `-`, `--`, `---` for the 1st, 2nd, 3rd preceding backward label.
- `+` 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 '+' label definition (just '+')
bne ++ ; branch to the '++' below (third forward '+')
rts
+ inc $d020 ; third forward label definition (just '+', referred to as '++')
```
- Data directives: `.byte`, `.word`, `.dword`, `.fill` (allocate space)
- Equates: `label = value` (not `label .equ value` or `#define`)
- **Number Literals**: Hex `$1234`, Binary `%10101010`, Decimal `123`.
- Zero-page variables defined with `=`
- `.text` for inline string data
- **Conditional assembly**: `.if`, `.elsif`, `.else`, `.endif`
- **Memory Sections**: `.section <name>`, `.send <name>` (common sections: `CODE`, `DATA`, `BSS`, `BSS_NOCLEAR`)
## Instructions
- **Instructions like `rol`, `ror`, `asl`, `lsr` (and `inc`, `dec` on 65C02) require an explicit operand for accumulator**: write `rol a`, `inc a`, etc., not just `rol` or `inc`.
- 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
### Instruction Side Effects (Flags)
- **`Z` (Zero)**: Set if the result of an operation is 0.
- **`N` (Negative)**: Set if bit 7 of the result is 1.
- **`C` (Carry)**: Used for unsigned overflow and shifts. `cmp` sets `C` if `Register >= Operand`.
- **`V` (Overflow)**: Set if a signed arithmetic operation overflowed.
- **Commonly affected by**: `lda`, `ldx`, `ldy`, `inx`, `dex`, `tax`, `tay`, `txa`, `tya`, `and`, `ora`, `eor`, `asl`, `lsr`, `rol`, `ror`, `adc`, `sbc`, `cmp`, `cpx`, `cpy`, `bit`.
- **Note**: `lda`, `ldx`, `ldy` do NOT affect the Carry flag. Only `adc`, `sbc`, `cmp`, and shift/rotate instructions affect Carry.
## 6502 vs 65C02
- **CX16 target only**: can use WDC 65C02 instructions — `stz`, `phx`, `plx`, `phy`, `ply`, `bra`, `trb`, `tsb`, `stp`, `wai`, `inc a`, `dec a`, `bit #imm`, `bit zp,x`, `bit abs,x`, `jmp (abs,x)`, and `(zp)` indirect addressing mode (e.g., `lda (zp)`).
- **C64, C128, PET32 targets**: original 6502 only — no `stz`, no `phx`/`plx`/`phy`/`ply`, no `bra` etc.
- **Note**: The Rockwell/bit-manipulation instructions (`rmb`, `smb`, `bbr`, `bbs`) are **NOT** available.
- Check the target before using 65C02-specific instructions
- **6502 / 65C02 instruction reference table**: https://www.pagetable.com/c64ref/6502/?cpu=65c02&tab=4 (provides exact instruction details for all opcodes: operation, addressing modes, byte length, and cycle count).
## Commander X16 Memory Map
```
Bank Offset Content
$0000-$9EFF Fixed/System RAM
$9F00-$9FFF I/O Area (VIA, VERA, YM2151)
$00-$FF:$A000-$BFFF Banked RAM (max 2 MiB, often 512 KiB) (256x8K banks)
$00-$1F:$C000-$FFFF System ROM (512 KiB) (32x16K banks)
```
- Bank registers (in zeropage): `$00` selects the current RAM bank (0-255), `$01` selects the current ROM bank (0-31). For JSRFAR/banked calls from Prog8, use `extsub @bank ...` instead of manipulating these directly.
- Full hardware specs at https://ayce.dev/emptyx16.html#emptyx16---x16-hardware-specs
## 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
- **CRITICAL**: Parameter names in `asmsub` are **documentation only**. You MUST use the actual registers in your assembly code, NOT the parameter names (unless you create aliases yourself).
- Create symbolic aliases at assembly top for clarity: `x1 = cx16.r0`, `y1 = cx16.r0L`
- Accessing Prog8 parameters if they were NOT mapped to registers: use `p8v_paramname`. (Mapping to registers is preferred for speed).
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
- **CRITICAL: NEVER use hardcoded zeropage addresses** (like `$1e`, `$22`, `$7c`, etc.) to reference P8ZP_SCRATCH registers or cx16 virtual registers. Their addresses differ per compilation target. **ALWAYS** use the symbolic names (`P8ZP_SCRATCH_W1`, `cx16.r14`, `cx16.r14+1`, etc.) so the assembler resolves the correct address for the target.
## Common 6502 Patterns
### Common Branch Logic (Comparisons)
| Logic | Unsigned | Signed |
|-------|----------|--------|
| `A == imm` | `cmp #imm`, `beq label` | (Same) |
| `A != imm` | `cmp #imm`, `bne label` | (Same) |
| `A < imm` | `cmp #imm`, `bcc label` | `sec`, `sbc #imm`, `bvc *+4`, `eor #$80`, `bmi label` |
| `A >= imm` | `cmp #imm`, `bcs label` | `sec`, `sbc #imm`, `bvc *+4`, `eor #$80`, `bpl label` |
| `A <= imm` | `beq label`, `bcc label` | (Use complex signed logic or reorder) |
| `A > imm` | `beq +`, `bcs label`, `+` | (Use complex signed logic or reorder) |
### Looping (downto with BNE)
```asm
ldx #count
loop ; do work here
dex
bne loop ; loop while X != 0 (runs 'count' times)
```
### Self-Modifying Code (SMC) — Avoid When Possible
**Do NOT use self-modifying code unless absolutely necessary** — self-modifying code writes to the code segment at runtime, which means it **cannot run from ROM** and is generally fragile, hard to debug, and violates the expected behavior of modern tooling (emulators, debuggers, etc.). Prefer lookup tables, indirect jumps via a vector in RAM, or alternative algorithms that keep code in place.
### Self-Modifying Code (SMC) Detection
Look for `sta`, `stx`, or `sty` pointing into code labels:
```asm
lda #$42
sta _target+1 ; Modifies the immediate operand of the LDA at _target
...
_target lda #$00 ; This #$00 will be replaced by #$42 at runtime
```
### 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)
```
## CPU Quirks and Pitfalls
### JMP ($xxFF) Page Wrap Bug (NMOS 6502)
- **Problem**: On original 6502 CPUs, `jmp ($caff)` will fetch the LSB from `$caff` but the MSB from `$ca00` (instead of `$cb00`).
- **Target**: Affects C64, C128, PET32.
- **Solution**: Avoid placing indirect jump vectors on a page boundary, or use the CX16 (65C02) which fixed this bug.
### BRK Instruction and the "Signature Byte"
- **Behavior**: After a `BRK` instruction, the return address on the stack is incremented by **2**. This means the CPU skips the byte immediately following the `BRK` opcode.
- **Usage**: This skipped byte is often used as a "signature" or parameter byte for the BRK handler.
### BIT Instruction Flags
- **Absolute/Zero-page**: `bit $1234` copies bit 7 of the memory value to the **N** flag and bit 6 to the **V** flag. The **Z** flag is set based on `A AND memory`.
- **Immediate (65C02 only)**: `bit #$01` only affects the **Z** flag; it does **NOT** modify N or V.
- **NMOS 6502**: Does **not** support `bit #imm`.
### The "B" (Break) Flag
- **Quirk**: The B flag (bit 4 of the status register) doesn't actually exist in the hardware status register. It only exists on the stack after a `PHP` or `BRK` instruction (set to 1) or a hardware IRQ/NMI (set to 0).
- **Detection**: To tell if an interrupt was caused by `BRK` or a hardware IRQ, your handler must `pla`, `and #$10`, and check the result.
### Decimal Mode Flag (D) Persistence
- **Pitfall**: On NMOS 6502, the `D` flag is **not** cleared on interrupt. Always use `cld` in IRQ handlers. On 65C02, it is cleared automatically, but `cld` is still good practice.
- **ADC/SBC**: Be extremely careful with arithmetic if you haven't explicitly set or cleared the `D` flag, as its state might be unknown.
## Optimization Tips
- **`stz` (65C02 only)**: Saves cycles and bytes compared to `lda #0`, `sta ...`.
- **`bra` (65C02 only)**: Shorter and usually faster than `jmp`.
- **Avoid `clc` before `bcc`**: `cmp` already sets the carry flag correctly for `bcc`/`bcs`.
- **`inx` / `dex` vs `clc`+`adc #1`**: Incrementing/decrementing is faster and doesn't affect the carry flag.
- **Zero-page usage**: Accessing variables in zeropage is 1 cycle faster and 1 byte shorter than absolute addressing.
- **Cycle Counting**: Most instructions take 2-4 cycles. `jsr` takes 6, `rts` takes 6. Branches take 2 (no branch), 3 (branch taken), or 4 (branch taken across page boundary).
## 64tass Macros
Define macros for common tasks:
```asm
pushax .macro
pha
txa
pha
.endm
popax .macro
pla
tax
pla
.endm
```
Call them with `#pushax` and `#popax`.
## IRQ Handler Best Practices
- **Clear the Decimal Flag (`cld`)**: On the original 6502, the decimal flag (`D`) is **not** automatically cleared when an interrupt occurs. If the interrupted code was in decimal mode, your handler will also run in decimal mode, causing arithmetic errors. Always call `cld` at the beginning of your handler. The 65C02 clears it automatically, but `cld` is still recommended for portability.
## Invoking the Assembler (64tass)
If you need to manually invoke `64tass` to assemble a generated `.asm` file, you should be aware of the default arguments that `prog8c` supplies to ensure compatibility with the generated code:
- `--ascii`: **CRITICAL.** Prog8 generates character and string data in ASCII. Without this flag, `64tass` defaults to PETSCII, which will garble your strings.
- `--case-sensitive`: Prog8 is case-sensitive and expects the assembler to be as well.
- `--long-branch`: Enables automatic conversion of relative branches (`beq`, `bne`, etc.) to absolute jumps if the target is out of range. Prog8 relies on this.
- `-Wno-implied-reg`: Suppresses warnings when the accumulator `a` is omitted from instructions like `rol`, `lsr`, etc. (though the skill recommends always using `rol a` for clarity).
- `-Wall`: Enables all warnings.
- `--cbm-prg` (or `--atari-xex` / `--nostart`): Sets the output format and adds the appropriate load address header.
### Optional but Recommended for Debugging:
- `--vice-labels --labels=labels.txt`: Generates a label file that can be loaded into the VICE monitor (`load_labels "labels.txt"`) to see your Prog8 symbol names while debugging.
- `--list=listing.txt`: Generates a full assembly listing file with addresses and opcodes.
### Example manual invocation:
```bash
64tass --ascii --case-sensitive --long-branch -Wall --cbm-prg -o myprogram.prg myprogram.asm
```
+247
View File
@@ -0,0 +1,247 @@
---
name: prog8-coder
description: Writing or understanding Prog8 programs
---
# Prog8 Coder Skill
You are an expert retro system development assistant, versed in the Prog8 programming language. Keep responses concise and practical — prefer short, correct code examples over lengthy prose.
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`)
- `.p8ir` files are the **Intermediate Representation** — target-independent, executable via `prog8c -vm file.p8ir`
- **Library search**: `prog8c -libsearch <regex>` to find routines in the embedded stdlib (e.g., `prog8c -libsearch "txt\."`)
- **Library dump**: `prog8c -libdump <dir>` to extract all embedded library source files
- **`sys` module**: always available, no import needed
- **CX16 programs**: add `%encoding iso`, call `txt.iso()` in `start()`, end with `sys.poweroff_system()`
## 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
- To point a typed pointer at a `memory()` block, assign the uword directly: `^^MyStruct ptr = memory("name", size)`. The `^^Type:expression` syntax (below) only works with array literals, not general uword expressions.
- Struct initialization: `^^StructType ptr = ^^StructType:[val1,val2,...]` (the `^^StructType:` can be omitted if inferable). This `^^Type:[...]` syntax does NOT work with variables or `memory()` — only literal arrays.
- Struct definitions must be inside a block, not at file level.
- Struct fields: simple types, `str`, and fixed-size arrays. Array fields: `ubyte[108] name`. `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`, `@zp`, `@requirezp`, `@dirty`, `@nosplit` are **tags** that go on variable declarations. **Place them after the datatype (and array specifiers), before the variable name(s):**
```
private ubyte[8] @shared vera_storage ; single var with tag
ubyte @zp @shared varname ; multiple tags allowed
ubyte @requirezp var1 ; require zeropage
ubyte[] @shared names ; array with tag
```
The grammar is: `[private] datatype [arraydims] [tags...] identifierlist`
- `@shared` marks variables as "used by external code" (assembly), prevents the optimizer from removing them
- `@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.
- **WARNING: Virtual registers in ISR/IRQ handlers**: The virtual registers R0-R15 are *not preserved* across the IRQ handler call. If your handler uses them, it will corrupt the interrupted program's state. Either avoid using them in the handler, or save/restore with `cx16.save_virtual_registers()` / `cx16.restore_virtual_registers()`. This applies to all targets, not just CX16.
- **WARNING: Long operations clobber R12-R15**: Some operations on `long` values use R12-R15 as temporary storage and will silently overwrite them. Do not rely on R12-R15 values when working with longs, and avoid using R12-R15 explicitly if your code uses long arithmetic.
- **WARNING: VERA registers in ISR handlers (CX16)**: If your IRQ handler reads or writes VERA control registers (e.g., `cx16.VERA_DATA0`, `cx16.VERA_ADDR_L`, etc.), you must save and restore the VERA context around the handler's work using `cx16.save_vera_context()` / `cx16.restore_vera_context()`. Without this, the handler will corrupt any VERA operations (tilemap updates, sprite positioning, etc.) happening in the interrupted main program.
- **IRQ handler best practices**: Keep handlers extremely short and fast — they run with interrupts disabled and steal cycles from the main program. Do NOT do lengthy processing, I/O, or complex subroutine calls inside the handler. Instead, set a boolean flag or semaphore that the main loop checks periodically, and do the actual work there.
- 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`
- Assigning a uword address to a `str` field of a struct is done by direct assignment. Compute the address in a uword variable first, then assign: `cx16.r0 = &namebufs + offset; entry.name = cx16.r0`. The `^^ubyte:(expr)` cast syntax does not parse — use a temp uword instead.
- 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)
- 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. Multiple defers fire in **reverse registration order** (LIFO / stack order — last deferred runs first). A defer is only registered if execution reaches that statement — conditional paths that skip the `defer` line will not register it.
- `goto`, labels, jump lists allowed
- **Common mistake**: `and`/`or` for bitmasking — use `&`/`|` instead!
### The when Statement
The `when` statement is a control flow construct that enables you to execute a specific action based on the value of an expression. It is generally more readable and often more efficient than a sequence of `if-else if` statements, as the compiler can optimize it into more efficient branching structures, such as a jump table.
- **Expression**: Evaluates an expression and compares it against case values.
- **Cases**: Defined by a value followed by the `->` operator.
- **Blocks**: Use `{ }` to enclose multiple statements for a case.
- **Else Clause**: Serves as a default handler; mandatory unless the expression type is fully covered.
- **Efficiency**: Recommended for handling sets of fixed choices as it typically results in better assembly code.
## 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
- **Don't use `private` on subroutines and variables** (including nested ones) unless the user asks for it. Everything is public by default in Prog8 — follow that convention.
- `inline` keyword for subroutines to suggest inlining
- No **function overloading** (except builtins) and no **polymorphism** in general. This means you must call specific routines for different types (e.g., `txt.print_ub(val)` vs `txt.print_w(val)` instead of a generic `print(val)`). 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.
## 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
```
## Inline Assembly
When writing inline assembly (`%asm {{ }}` blocks or `asmsub` routines), load the **asm6502-coder** skill for formatting rules, symbol references, anonymous labels, and 64tass syntax details.
## 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()`). **Note: Prog8 has no function overloading**, so you cannot use `txt.print(number)` — you must call the specific routine for the type (e.g., `txt.print_ub(val)` for an unsigned byte).
- 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 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. They are syntactic sugar for a list of `const` declarations, not a type. 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)
+2
View File
@@ -13,5 +13,7 @@ dist/
build/
out/
output/
gradle/
.gradle/
.kotlin/
.git/
+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
+26 -12
View File
@@ -10,32 +10,46 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: build and install recent 64tass
- name: install 64tass
run: |
sudo apt-get install -y make build-essential
git clone --depth=1 https://github.com/irmen/64tass
cd 64tass
make -j4
sudo make install
sudo apt update
sudo apt install 64tass
64tass --version
- name: Set up JDK 17
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
java-version: 17
distribution: temurin
- name: Build and test with Gradle
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v6
- name: Compile
run: ./gradlew compileKotlin compileTestKotlin --console=plain
- name: Test
run: ./gradlew test --continue --console=plain
- name: Package
run: |
./gradlew build shadowJar --no-daemon
./gradlew shadowJar --console=plain
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: |
compiler/build/libs/*-all.jar
compiler/build/libs/hash.txt
- name: Upload test results
if: always()
uses: actions/upload-artifact@v6
with:
name: test-results
path: compiler/build/test-results/test/
+5 -1
View File
@@ -3,6 +3,9 @@
.idea/developer-tools.xml
.idea/usage.statistics.xml
.idea/shelf/
.junie/
.junie/memory/
.serena
build/
dist/
output/
@@ -14,6 +17,7 @@ out-old/
*.prg
*.bin
*.p8ir
*.bankedcalls
*.labels.txt
*.vm.txt
*.vice-mon-list
@@ -22,6 +26,7 @@ parser/**/*.interp
parser/**/*.tokens
parser/**/*.java
compiler/src/prog8/buildversion/*
languageServer/src/prog8/buildversion/*
*.py[cod]
*.egg
*.egg-info
@@ -35,7 +40,6 @@ parsetab.py
.pytest_cache/
.attach_pid*
compiler/lib/
.gradle
sd*.img
*.d64
+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>
+11 -1
View File
@@ -2,6 +2,16 @@
<project version="4">
<component name="CompilerConfiguration">
<option name="BUILD_PROCESS_HEAP_SIZE" value="3000" />
<bytecodeTargetLevel target="11" />
<bytecodeTargetLevel target="11">
<module name="prog8.codeGenExperimental" target="17" />
<module name="prog8.codeGenExperimental.main" target="17" />
<module name="prog8.codeGenExperimental.test" target="17" />
<module name="prog8.codeGenM68k" target="17" />
<module name="prog8.codeGenM68k.main" target="17" />
<module name="prog8.codeGenM68k.test" target="17" />
<module name="prog8.codeGenNew6502" target="17" />
<module name="prog8.codeGenNew6502.main" target="17" />
<module name="prog8.codeGenNew6502.test" target="17" />
</bytecodeTargetLevel>
</component>
</project>
+29
View File
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="distributionType" value="LOCAL" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/codeCore" />
<option value="$PROJECT_DIR$/codeGenCpu6502" />
<option value="$PROJECT_DIR$/codeGenIntermediate" />
<option value="$PROJECT_DIR$/codeOptimizers" />
<option value="$PROJECT_DIR$/compiler" />
<option value="$PROJECT_DIR$/compilerAst" />
<option value="$PROJECT_DIR$/intermediate" />
<option value="$PROJECT_DIR$/languageServer" />
<option value="$PROJECT_DIR$/parser" />
<option value="$PROJECT_DIR$/simpleAst" />
<option value="$PROJECT_DIR$/virtualmachine" />
</set>
</option>
<option name="myGradleHome" value="/usr/share/java/gradle" />
</GradleProjectSettings>
</option>
</component>
</project>
-16
View File
@@ -534,7 +534,6 @@
<inspection_tool class="GroovyUnusedDeclaration" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovyUnusedIncOrDec" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GroovyVariableNotAssigned" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="GrpcSchemes" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="Guava" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="HardcodedFileSeparators" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_recognizeExampleMediaType" value="false" />
@@ -585,20 +584,6 @@
</inspection_tool>
<inspection_tool class="HtmlUnknownTarget" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="HtmlWrongAttributeValue" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="HttpClientDuplicateImportInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="HttpClientInappropriateProtocolUsageInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="HttpClientRunRequestNameInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="HttpClientUnresolvedAuthId" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="HttpClientUnresolvedVariable" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="HttpRequestContentLengthIsIgnored" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="HttpRequestCustomHttpMethodInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="HttpRequestEnvironmentAuthConfigurationValidationInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="HttpRequestJsonBodyInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="HttpRequestPlaceholder" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="HttpRequestRequestSeparatorJsonBodyInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="HttpRequestRequestSeparatorXmlBodyInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="HttpRequestRequestSeparatorYamlBodyInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="HttpRequestWhitespaceInsideRequestTargetPath" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="IOResource" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredTypesString" value="java.io.ByteArrayOutputStream,java.io.ByteArrayInputStream,java.io.StringBufferInputStream,java.io.CharArrayWriter,java.io.CharArrayReader,java.io.StringWriter,java.io.StringReader" />
<option name="insideTryAllowed" value="false" />
@@ -629,7 +614,6 @@
<option name="reportPerFile" value="true" />
<option name="kotlinOnly" value="true" />
</inspection_tool>
<inspection_tool class="IncorrectHttpHeaderInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="IncrementDecrementUsedAsExpression" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="IndexZeroUsage" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="InfiniteLoopJS" enabled="false" level="WARNING" enabled_by_default="false" />
+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 -4
View File
@@ -7,13 +7,14 @@
<option name="jvmTarget" value="11" />
</component>
<component name="KotlinCommonCompilerArguments">
<option name="apiVersion" value="2.2" />
<option name="languageVersion" value="2.2" />
<option name="apiVersion" value="2.4" />
<option name="languageVersion" value="2.4" />
</component>
<component name="KotlinCompilerSettings">
<option name="additionalArguments" value="-Xwhen-guards -jvm-default=no-compatibility" />
<option name="additionalArguments" value="-jvm-default=no-compatibility" />
</component>
<component name="KotlinJpsPluginSettings">
<option name="version" value="2.2.0" />
<option name="externalSystemId" value="Gradle" />
<option name="version" value="2.4.10" />
</component>
</project>
-23
View File
@@ -1,23 +0,0 @@
<component name="libraryTable">
<library name="KotlinJavaRuntime" type="repository">
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.2.21" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.2.21/kotlin-stdlib-jdk8-2.2.21.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.21/kotlin-stdlib-2.2.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.2.21/kotlin-stdlib-jdk7-2.2.21.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.2.21/kotlin-stdlib-jdk8-2.2.21-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.21/kotlin-stdlib-2.2.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.2.21/kotlin-stdlib-jdk7-2.2.21-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.2.21/kotlin-stdlib-jdk8-2.2.21-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.21/kotlin-stdlib-2.2.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.2.21/kotlin-stdlib-jdk7-2.2.21-sources.jar!/" />
</SOURCES>
</library>
</component>
-18
View File
@@ -1,18 +0,0 @@
<component name="libraryTable">
<library name="antlr.antlr4" type="repository">
<properties maven-id="org.antlr:antlr4:4.13.2">
<exclude>
<dependency maven-id="com.ibm.icu:icu4j" />
</exclude>
</properties>
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.13.2/antlr4-4.13.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.13.2/antlr4-runtime-4.13.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr-runtime/3.5.3/antlr-runtime-3.5.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/ST4/4.3.4/ST4-4.3.4.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/abego/treelayout/org.abego.treelayout.core/1.0.3/org.abego.treelayout.core-1.0.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
-13
View File
@@ -1,13 +0,0 @@
<component name="libraryTable">
<library name="eclipse.lsp4j" type="repository">
<properties maven-id="org.eclipse.lsp4j:org.eclipse.lsp4j:0.24.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!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
-23
View File
@@ -1,23 +0,0 @@
<component name="libraryTable">
<library name="io.kotest.framework.datatest" type="repository">
<properties maven-id="io.kotest:kotest-framework-datatest:5.9.1" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-datatest/5.9.1/kotest-framework-datatest-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-datatest-jvm/5.9.1/kotest-framework-datatest-jvm-5.9.1.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-core-jvm/1.8.0/kotlinx-coroutines-core-jvm-1.8.0.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$/org/jetbrains/kotlin/kotlin-reflect/1.9.23/kotlin-reflect-1.9.23.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.9.1/kotest-framework-api-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/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-jdk8/1.8.0/kotlinx-coroutines-jdk8-1.8.0.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/kotlinx/kotlinx-coroutines-test-jvm/1.8.0/kotlinx-coroutines-test-jvm-1.8.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
-41
View File
@@ -1,41 +0,0 @@
<component name="libraryTable">
<library name="io.kotest.runner.junit5.jvm" type="repository">
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:5.9.1" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/5.9.1/kotest-runner-junit5-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.9.1/kotest-framework-api-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/jetbrains/kotlinx/kotlinx-coroutines-test-jvm/1.8.0/kotlinx-coroutines-test-jvm-1.8.0.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-framework-engine-jvm/5.9.1/kotest-framework-engine-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.172/classgraph-4.8.172.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/mordant/1.2.1/mordant-1.2.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/colormath/1.2.0/colormath-1.2.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-debug/1.8.0/kotlinx-coroutines-debug-1.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna/5.9.0/jna-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna-platform/5.9.0/jna-platform-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.10.9/byte-buddy-1.10.9.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy-agent/1.10.9/byte-buddy-agent-1.10.9.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-discovery-jvm/5.9.1/kotest-framework-discovery-jvm-5.9.1.jar!/" />
<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$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.8.0/kotlinx-coroutines-jdk8-1.8.0.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$/io/kotest/kotest-extensions-jvm/5.9.1/kotest-extensions-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/5.9.1/kotest-framework-concurrency-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!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/23.0.0/annotations-23.0.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.8.2/junit-platform-engine-1.8.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.8.2/junit-platform-commons-1.8.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-suite-api/1.8.2/junit-platform-suite-api-1.8.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-launcher/1.8.2/junit-platform-launcher-1.8.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.8.2/junit-jupiter-api-5.8.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.23/kotlin-stdlib-1.9.23.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.9.23/kotlin-reflect-1.9.23.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
-10
View File
@@ -1,10 +0,0 @@
<component name="libraryTable">
<library name="jetbrains.kotlinx.cli.jvm" type="repository">
<properties include-transitive-deps="false" maven-id="org.jetbrains.kotlinx:kotlinx-cli-jvm:0.3.6" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-cli-jvm/0.3.6/kotlinx-cli-jvm-0.3.6.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
-20
View File
@@ -1,20 +0,0 @@
<component name="libraryTable">
<library name="michael.bull.kotlin.result.jvm" type="repository">
<properties maven-id="com.michael-bull.kotlin-result:kotlin-result-jvm:2.1.0" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.1.0/kotlin-result-jvm-2.1.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.0/kotlin-stdlib-2.2.0.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.1.0/kotlin-result-jvm-2.1.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.0/kotlin-stdlib-2.2.0-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.1.0/kotlin-result-jvm-2.1.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.0/kotlin-stdlib-2.2.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
</SOURCES>
</library>
</component>
+1
View File
@@ -20,6 +20,7 @@
<component name="Black">
<option name="sdkName" value="Python 3.11" />
</component>
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="FrameworkDetectionExcludesConfiguration">
<type id="Python" />
</component>
-15
View File
@@ -2,24 +2,9 @@
<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" />
<module fileurl="file://$PROJECT_DIR$/codeGenExperimental/codeGenExperimental.iml" filepath="$PROJECT_DIR$/codeGenExperimental/codeGenExperimental.iml" />
<module fileurl="file://$PROJECT_DIR$/codeGenIntermediate/codeGenIntermediate.iml" filepath="$PROJECT_DIR$/codeGenIntermediate/codeGenIntermediate.iml" />
<module fileurl="file://$PROJECT_DIR$/codeGenVirtual/codeGenVirtual.iml" filepath="$PROJECT_DIR$/codeGenVirtual/codeGenVirtual.iml" />
<module fileurl="file://$PROJECT_DIR$/codeOptimizers/codeOptimizers.iml" filepath="$PROJECT_DIR$/codeOptimizers/codeOptimizers.iml" />
<module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" />
<module fileurl="file://$PROJECT_DIR$/compilerAst/compilerAst.iml" filepath="$PROJECT_DIR$/compilerAst/compilerAst.iml" />
<module fileurl="file://$PROJECT_DIR$/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" />
<module fileurl="file://$PROJECT_DIR$/virtualmachine/virtualmachine.iml" filepath="$PROJECT_DIR$/virtualmachine/virtualmachine.iml" />
</modules>
</component>
</project>
Generated
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="" vcs="Git" />
</component>
</project>
+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 *)"
]
}
}
+355
View File
@@ -0,0 +1,355 @@
# 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.4 is used for the compiler implementation.
- Java 17 is used as Java runtime version.
- ANTLR4 version 4.13 is used for the parser implementation.
- Dependent library versions can be found in 'build.gradle.kts' and in the IntelliJ IDEA configuration files in .idea/libraries
- The compiler main entrypoint is in the "compiler" module, in src/prog8/CompilerMain.kt
## 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.
**Kotlin string `$` handling**: The `$` character is special in Kotlin string literals (string interpolation). If you need a literal `$` character, use Kotlin's `$$"..."` interpolation prefix instead of backslash escaping (`\$`) or `${'$'}`. The `$$"..."` syntax is a fairly recent Kotlin feature that avoids nasty escaping. For example:
```kotlin
val bill = $$"The value is $3.99"
```
## Prog8 language information
When the task involves writing or understanding `.p8` (Prog8 source) or `.p8ir` (Prog8 intermediate representation) files, **load the `prog8-coder` skill** — it contains the full language reference, syntax rules, and standard library guidance.
## Project Module Descriptions
- `compiler` - Main compiler entrypoint (src/prog8/CompilerMain.kt)
- `compilerAst` - Complex AST where most optimizations run
- `simpleAst` - Simplified AST used by code generator backends
- `parser` - ANTLR4 parser implementation
- `codeCore` - Core code generation utilities
- `codeGenCpu6502` - 6502 assembly code generator backend
- `codeGenIntermediate` - Intermediate representation (IR) code generator
- `codeGenVirtual` - Virtual machine code generator backend
- `codeOptimizers` - Optimization passes
- `intermediate` - IR components
- `virtualmachine` - VM that executes IR code
- `languageServer` - Language Server Protocol implementation
- `docs` - Documentation files
- `examples` - Example Prog8 programs
## Key Information
- never read the files and directories that are ignored via the .aiignore and .gitignore files (EXCEPT when it is a folder for agent config such as .junie, .opencode, .qwen or .agent)
- never perform any git source control write/update/add/commit/branch operations. Read and status operations are allowed.
- **git log/history queries can be useful** for understanding when/why a feature was added or tracking down when a bug was introduced, but for locating code use grep_search or glob instead.
- Architecture decisions: separation of frontend/parser, IR intermediate representation, multiple backends
# Dev environment tips
## Development Workflows
**1. Testing your own Prog8 programs** (no compiler changes):
- No rebuild needed - just run `prog8c` directly
- Edit your `.p8` file → compile/run → check stdout output
- Example: `prog8c -target virtual -emu myprogram.p8`
**2. Testing compiler or standard library changes**:
- Rebuild required after every change to `.kt`, `.p8`, or `.asm` files in the compiler project
- Workflow: edit source → `gradle installdist installshadowdist` → test with `prog8c`
- Example: fix bug in `CodeGen6502.kt` → rebuild → `prog8c -target cx16 test.p8`
## Building and Installing the Compiler
- use the system installed gradle command instead of the gradle wrapper.
### Quick compile check (NO tests)
- **After changing compiler Kotlin source (.kt files)**: Use `gradle :compiler:compileKotlin --console=plain` to quickly check for syntax/compile errors
- This compiles the compiler but **skips running tests**, this is much faster than `gradle build`
- **Note:** This does NOT install the compiler - use `gradle installdist installshadowdist --console=plain` after to actually use your changes
### When you MUST rebuild AND reinstall the compiler
**After ANY change to:**
1. **Kotlin compiler source code** (`.kt` files in any module, or the `.g4` grammar file)
2. **Standard library Prog8 files** (`compiler/res/prog8lib/**/*.p8`)
3. **Standard library assembly files** (`compiler/res/prog8lib/**/*.asm`)
**Run:** `gradle installdist installshadowdist --console=plain`
**Why:** The standard library files (.p8 and .asm) are embedded into the compiler JAR during the build. Changes to these files are NOT picked up by simply running `prog8c` - you MUST rebuild and reinstall for your changes to take effect.
**Without this step, your changes will NOT be reflected when running `prog8c`!**
- This compiles AND installs, but still skips running tests (faster than `gradle build`)
### When to run full build with tests
- **Before final verification**: 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
- **When working on a standalone codegen module** (one that reads `.p8ir` files): Run `gradle :<module>:test --console=plain` instead of full `gradle build`. Compiler unit tests are irrelevant for IR-level changes. Do run full `gradle build` before final verification.
### 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**.
- **Other useful flags**: `-quiet` (suppress messages), `-warnimplicitcasts` (warn on implicit type widening), `-daemon` (keep a background compiler process alive — must be passed on every invocation)
### Compilation Output Files
- `*.prg` - The final compiled program file for the target system (e.g., Commander X16)
- `*.asm` - Generated assembly code from the Prog8 source
- `*.list` - Generated full assembly listing file from the Prog8 source
- `*.p8ir` - Intermediate representation file, can be executed in the Virtual Machine
- `*.vice-mon-list` - Vice emulator monitor list file for debugging
### Execution Examples
- `prog8c -target targetname input.p8` - Compile a Prog8 source file "input.p8" for the given target (cx16, c64, pet32, c128, virtual)
- `prog8c -target targetname -emu input.p8` - Compile and execute a prog8 file in the emulator for the given target (cx16, c64, pet32, c128, virtual)
- `prog8c -vm input.p8ir` - Execute an existing prog8 program, compiled in IR form, in the Virtual Machine
### Library Tools
- **Library search**: `prog8c -libsearch <regex>` — search for a regex pattern in the embedded stdlib files. Extremely useful to quickly find library routines, variables, or signatures (e.g., `prog8c -libsearch "txt\."` lists all textio routines)
- **Library dump**: `prog8c -libdump <dir>` — extract all embedded library source files into a directory for direct inspection
## Testing and Verification
- Never assume your changes simply work, always test!
- If the project does not have any testing tools, scripts, MCP tools, skills, etc. available for testing, ask the user whether testing should be skipped.
### 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**
### Compiler Unit Test Snippets
Prog8 code snippets embedded in the compiler's own Kotlin unit tests (e.g., in `TestAstChecks.kt`, `TestOptimization.kt`) follow different rules from standalone programs:
- **Keep self-contained** — avoid `%import` directives (test setup may not have library search path configured)
- **Use `cx16` target** (not `virtual`, unless specifically testing the VM)
- **Set `writeAssembly=false`** if the test only checks generated AST (much faster)
- **Set `optimize=false`** by default for these snippets
- **Do NOT use** the `%encoding iso` / `txt.iso()` / `sys.poweroff_system()` pattern — that's only for real CX16 emulator runs
### Testing Assembly Output
When testing generated assembly in Kotlin tests:
- **NEVER dump raw assembly in string assertions** (e.g., `asm shouldContain "jsr label"`). On failure, this dumps the ENTIRE assembly listing into test logs.
- **Instead, parse into lines and check specific properties:**
```kotlin
val lines = asm.lines().map { it.trim() }
lines.any { it == "lda #42" } shouldBe true
lines.count { it.startsWith("jsr") } shouldBe 0
```
- Prefer `lines.any { ... }` for existence checks and `lines.count { ... } shouldBe 0` for absence checks.
### Manual Verification & Emulators
**Testing tip**: When writing and testing Prog8 programs, **use the `virtual` target** (e.g., `prog8c -target virtual -emu input.p8` or `prog8c -vm input.p8ir`). This is the preferred way to test because the virtual target can easily write output to stdout, making it simple to verify program behavior and check results.
**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.
+13 -5
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
@echo "compiler launch script can be found here: compiler/build/install/compiler-shadow/bin/prog8c"
$(GRADLE) installdist installshadowdist
@echo "compiler launch script can be found here: compiler/build/install/prog8c-shadow/bin/prog8c"
test:
gradle build
@echo "compiler launch script can be found here: compiler/build/install/compiler-shadow/bin/prog8c"
$(GRADLE) build
@echo "compiler launch script can be found here: compiler/build/install/prog8c-shadow/bin/prog8c"
+15 -4
View File
@@ -15,10 +15,12 @@ which aims to provide many conveniences over raw assembly code (even when using
**Want to buy me a coffee or a pizza perhaps?**
This project was created over the last couple of years by dedicating thousands of hours of my free time to it, to make it the best I possibly can.
This project was created over the last couple of years by dedicating a huge amoung of my free time to it, to make it the best I possibly can.
If you like Prog8, and think it's worth a nice cup of hot coffee or a delicious pizza,
you can help me out a little bit over at [ko-fi.com/irmen](https://ko-fi.com/irmen) or [PayPal](https://paypal.me/irmendejong)
[![JetBrains logo.](https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.svg)](https://jb.gg/OpenSource)
I am using JetBrains products to develop the Prog8 compiler.
Documentation
-------------
@@ -63,8 +65,10 @@ What does Prog8 provide?
- programs run very fast because it's compiled to native machine code
- compiled code is very small; much smaller than equivalent C code compiled with CC65, and usually runs faster as well
- modularity, symbol scoping, subroutines. No need for forward declarations.
- Can make vars and routines private, as a simple way to restrict access and hide implementation details
- various data types other than just bytes (16-bit words, long integers, floats, strings)
- Structs and typed pointers
- 2D arrays (`matrix[row][col]` syntax)
- floating point math is supported on certain targets
- access to most Kernal ROM routines as external subroutine definitions you can call normally
- tight control over Zeropage usage
@@ -109,8 +113,8 @@ Additional required tools
-------------------------
[64tass](https://sourceforge.net/projects/tass64/) - cross assembler. Install this on your shell path.
A recent .exe version of this tool for Windows can be obtained from my [clone](https://github.com/irmen/64tass/releases) of this project.
For other platforms it is very easy to compile it yourself (make ; make install).
If you're on Windows, download a recent .exe from the official repository's file section <https://sourceforge.net/projects/tass64/files/binaries/>.
For other systems (Mac, Linux) it is very easy to get it via your package manager or to compile it yourself.
A **Java runtime (jre or jdk), version 11 or newer** is required to run a prepackaged version of the compiler.
If you want to build it from source, you'll need a Java SDK + Kotlin 1.3.x SDK (or for instance,
@@ -175,7 +179,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)
@@ -205,3 +209,10 @@ Here is a performance comparison with various C compilers for the 6502/C64 so yo
an idea of how Prog8 stacks up.
[comparison](benchmark-c/benchmark.md)
Agent context instructions
--------------------------
[Agent context instructions](AGENTS.md)
-24
View File
@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="library" name="jetbrains.kotlinx.cli.jvm" level="project" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/lib/bsh-3.0.0-SNAPSHOT.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
</component>
</module>
-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
))
}
+14 -1
View File
@@ -2,15 +2,28 @@
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 the smallest by a fair bit.
* 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)
+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
}
}
+5 -5
View File
@@ -1,5 +1,5 @@
%import textio
%import floats
%import ciatimer
; note: Prog8 has a complete CRC16 routine in its library: math.crc16
@@ -7,12 +7,11 @@
main {
sub start() {
txt.lowercase()
cia.calibrate()
test.benchmark_name()
cbm.SETTIM(0,0,0)
test.benchmark()
txt.print_f(floats.time() / 60)
txt.print(" seconds\n")
void test.benchmark_check()
cia.print_time()
repeat {}
}
}
@@ -40,12 +39,13 @@ test {
if crc_result == EXPECTED
{
txt.print(" [OK]")
txt.print(" [OK]\n")
return false
}
txt.print(" [FAIL] - expected ")
txt.print_uwhex(EXPECTED, true)
txt.nl()
return true
}
+5 -5
View File
@@ -1,5 +1,5 @@
%import textio
%import floats
%import ciatimer
; note: Prog8 has a complete CRC32 routine in its library: math.crc32
@@ -7,12 +7,11 @@
main {
sub start() {
txt.lowercase()
cia.calibrate()
test.benchmark_name()
cbm.SETTIM(0,0,0)
test.benchmark()
txt.print_f(floats.time() / 60)
txt.print(" seconds\n")
void test.benchmark_check()
cia.print_time()
repeat {}
}
}
@@ -40,12 +39,13 @@ test {
if crc_result == EXPECTED
{
txt.print(" [OK]")
txt.print(" [OK]\n")
return false
}
txt.print(" [FAIL] - expected ")
txt.print_ulhex(EXPECTED, true)
txt.nl()
return true
}
+5 -5
View File
@@ -1,16 +1,15 @@
%import textio
%import floats
%import ciatimer
main {
sub start() {
txt.lowercase()
cia.calibrate()
test.benchmark_name()
cbm.SETTIM(0,0,0)
test.benchmark()
txt.print_f(floats.time() / 60)
txt.print(" seconds\n")
void test.benchmark_check()
cia.print_time()
repeat {}
}
}
@@ -36,12 +35,13 @@ test {
if crc_result == EXPECTED
{
txt.print(" [OK]")
txt.print(" [OK]\n")
return false
}
txt.print(" [FAIL] - expected ")
txt.print_ubhex(EXPECTED, true)
txt.nl()
return true
}
+3 -3
View File
@@ -1,16 +1,16 @@
%import textio
%import floats
%import ciatimer
main {
sub start() {
txt.lowercase()
cia.calibrate()
test.benchmark_name()
cbm.SETTIM(0,0,0)
test.benchmark()
txt.print_f(floats.time() / 60)
txt.print(" seconds\n")
void test.benchmark_check()
cia.print_time()
repeat {}
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 65 KiB

+5 -5
View File
@@ -1,16 +1,15 @@
%import textio
%import floats
%import ciatimer
main {
sub start() {
txt.lowercase()
cia.calibrate()
test.benchmark_name()
cbm.SETTIM(0,0,0)
test.benchmark()
txt.print_f(floats.time() / 60)
txt.print(" seconds\n")
void test.benchmark_check()
cia.print_time()
repeat {}
}
}
@@ -46,12 +45,13 @@ test {
if prime_count == EXPECTED
{
txt.print(" [OK]")
txt.print(" [OK]\n")
return false
}
txt.print(" [FAIL] - expected ")
txt.print_uw(EXPECTED)
txt.nl()
return true
}
+5 -5
View File
@@ -1,16 +1,15 @@
%import textio
%import floats
%import ciatimer
main {
sub start() {
txt.lowercase()
cia.calibrate()
test.benchmark_name()
cbm.SETTIM(0,0,0)
test.benchmark()
txt.print_f(floats.time() / 60)
txt.print(" seconds\n")
void test.benchmark_check()
cia.print_time()
repeat {}
}
}
@@ -46,12 +45,13 @@ test {
if prime_count == EXPECTED
{
txt.print(" [OK]")
txt.print(" [OK]\n")
return false
}
txt.print(" [FAIL] - expected ")
txt.print_uw(EXPECTED)
txt.nl()
return true
}
+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)
+16 -16
View File
@@ -113,14 +113,14 @@ btree {
sub count_node(^^Node r) {
count++
if r.left!=0 {
sys.pushw(r)
pushw(r)
count_node(r.left)
r = sys.popw()
r = popw()
}
if r.right!=0 {
sys.pushw(r)
pushw(r)
count_node(r.right)
r = sys.popw()
r = popw()
}
}
}
@@ -179,15 +179,15 @@ btree {
sub process_tree(^^Node r) {
if r.left!=0 {
sys.pushw(r)
pushw(r)
process_tree(r.left)
r = sys.popw()
r = popw()
}
cx16.r0 = r.value
if r.right!=0 {
sys.pushw(r)
pushw(r)
process_tree(r.right)
r = sys.popw()
r = popw()
}
}
}
@@ -200,18 +200,18 @@ btree {
sub process_tree(^^Node r, ubyte depth) {
cx16.r0 = r.value
if r.left!=0 {
sys.pushw(r)
sys.push(depth)
pushw(r)
push(depth)
process_tree(r.left, depth+1)
depth = sys.pop()
r = sys.popw()
depth = pop()
r = popw()
}
if r.right!=0 {
sys.pushw(r)
sys.push(depth)
pushw(r)
push(depth)
process_tree(r.right, depth+1)
depth = sys.pop()
r = sys.popw()
depth = pop()
r = popw()
}
}
}
+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
+9 -3
View File
@@ -26,6 +26,7 @@ main {
str[20] benchmark_names
uword[20] benchmark_score
str version = "12.1"
sub start() {
@@ -35,8 +36,10 @@ main {
txt.color2(1, 6)
txt.clear_screen()
txt.print("\n\n\n prog8 compiler benchmark tests.\n\n benchmark version v12\n\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
@@ -88,7 +91,10 @@ main {
txt.color2(1, 6)
uword total_score
benchmark_number = 0
txt.print("\nscore benchmark (v12)\n\n")
txt.print("\nscore benchmark (")
txt.print(version)
txt.print(")\n\n")
do {
txt.spc()
txt.print_uw(benchmark_score[benchmark_number])
+75 -8
View File
@@ -1,11 +1,9 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.JvmDefaultMode
import org.jetbrains.kotlin.gradle.internal.config.LanguageFeature
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
kotlin("jvm") version "2.2.21"
kotlin("jvm") version "2.4.10"
}
allprojects {
@@ -14,12 +12,13 @@ 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)
}
@@ -32,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:6.2.3"))
// 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
}
}
}
+3 -14
View File
@@ -1,22 +1,11 @@
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.1.0")
}
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")
}
// note: there are no unit tests in this module!
-14
View File
@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
</component>
</module>
+20 -1
View File
@@ -29,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 -94
View File
@@ -30,40 +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.LONG -> ReturnConvention(returnType, RegisterOrPair.R14R15_32)
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.LONG -> ReturnConvention(returnType, RegisterOrPair.R14R15_32)
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, RegisterOrPair.R14R15_32, 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)
@@ -89,83 +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, BaseDataType.LONG)),
"ror" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
"rol2" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
"ror2" to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD, BaseDataType.LONG)),
"cmp" to FSignature(false, null, 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, 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)),
"prog8_lib_structalloc" to FSignature(true, 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__long" to FSignature(true, BaseDataType.LONG, FParam("value", BaseDataType.LONG)),
"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 - BaseDataType.STRUCT_INSTANCE).toTypedArray())),
"offsetof" to FSignature(true, BaseDataType.UBYTE, FParam("field", BaseDataType.UBYTE)),
"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__long" to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.LONG)),
"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)),
"lsb__long" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.LONG)),
"msb" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
"msb__long" to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.LONG)),
"lsw" to FSignature(true, BaseDataType.UWORD, 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)),
"mklong" to FSignature(true, BaseDataType.LONG, FParam("msb", BaseDataType.UBYTE), FParam("b2", BaseDataType.UBYTE), FParam("b1", BaseDataType.UBYTE), FParam("lsb", BaseDataType.UBYTE)),
"mklong2" to FSignature(true, BaseDataType.LONG, FParam("msw", BaseDataType.UWORD), FParam("lsw", BaseDataType.UWORD)),
"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)),
"clamp__long" to FSignature(true, BaseDataType.LONG, FParam("value", BaseDataType.LONG), FParam("minimum", BaseDataType.LONG), FParam("maximum", BaseDataType.LONG)),
"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)),
"min__long" to FSignature(true, BaseDataType.LONG, FParam("val1", BaseDataType.LONG), FParam("val2", BaseDataType.LONG)),
"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)),
"max__long" to FSignature(true, BaseDataType.LONG, FParam("val1", BaseDataType.LONG), FParam("val2", BaseDataType.LONG)),
"peek" to FSignature(true, BaseDataType.UBYTE, FParam("address", BaseDataType.UWORD)),
"peekbool" to FSignature(true, BaseDataType.BOOL, FParam("address", BaseDataType.UWORD)),
"peekw" to FSignature(true, BaseDataType.UWORD, FParam("address", BaseDataType.UWORD)),
"peekl" to FSignature(true, BaseDataType.LONG, 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, BaseDataType.BYTE)),
"pokebool" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.BOOL)),
"pokebowl" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.BOOL)),
"pokew" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UWORD, BaseDataType.WORD)),
"pokel" to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.LONG)),
"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, arrayOf(BaseDataType.UNDEFINED), FParam("value", *NumericDatatypes)),
"abs__byte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.BYTE)),
"abs__word" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.WORD)),
"abs__long" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("value", BaseDataType.LONG)),
"abs__float" to FSignature(true, arrayOf(BaseDataType.FLOAT), FParam("value", BaseDataType.FLOAT)),
"len" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("values", *IterableDatatypes)),
"sizeof" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("object", *(BaseDataType.entries - BaseDataType.STRUCT_INSTANCE).toTypedArray())),
"offsetof" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("field", BaseDataType.UBYTE)),
"sgn" to FSignature(true, arrayOf(BaseDataType.BYTE), FParam("value", *NumericDatatypes)),
"sqrt" to FSignature(true, arrayOf(BaseDataType.UNDEFINED), FParam("value", *NumericDatatypes)),
"sqrt__ubyte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.UBYTE)),
"sqrt__uword" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.UWORD)),
"sqrt__long" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.LONG)),
"sqrt__float" to FSignature(true, arrayOf(BaseDataType.FLOAT), FParam("value", BaseDataType.FLOAT)),
"divmod" to FSignature(false, arrayOf(BaseDataType.UNDEFINED, BaseDataType.UNDEFINED), FParam("dividend", BaseDataType.UBYTE, BaseDataType.UWORD), FParam("divisor", BaseDataType.UBYTE, BaseDataType.UWORD)),
"divmod__ubyte" to FSignature(false, arrayOf(BaseDataType.UBYTE, BaseDataType.UBYTE), FParam("dividend", BaseDataType.UBYTE), FParam("divisor", BaseDataType.UBYTE)),
"divmod__uword" to FSignature(false, arrayOf(BaseDataType.UWORD, BaseDataType.UWORD), FParam("dividend", BaseDataType.UWORD), FParam("divisor", BaseDataType.UWORD)),
"divmod__byte" to FSignature(false, arrayOf(BaseDataType.BYTE, BaseDataType.BYTE), FParam("dividend", BaseDataType.BYTE), FParam("divisor", BaseDataType.BYTE)),
"divmod__word" to FSignature(false, arrayOf(BaseDataType.WORD, BaseDataType.WORD), FParam("dividend", BaseDataType.WORD), FParam("divisor", BaseDataType.WORD)),
"lmh" to FSignature(true, arrayOf(BaseDataType.UBYTE, BaseDataType.UBYTE, BaseDataType.UBYTE), FParam("value", BaseDataType.LONG)),
"lsb" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
"lsb__long" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.LONG)),
"msb" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
"msb__long" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.LONG)),
"lsw" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
"msw" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)),
"mkword" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("msb", BaseDataType.UBYTE), FParam("lsb", BaseDataType.UBYTE)),
"mklong" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("msb", BaseDataType.UBYTE), FParam("b2", BaseDataType.UBYTE), FParam("b1", BaseDataType.UBYTE), FParam("lsb", BaseDataType.UBYTE)),
"mklong2" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("msw", BaseDataType.UWORD), FParam("lsw", BaseDataType.UWORD)),
"clamp" to FSignature(true, arrayOf(BaseDataType.UNDEFINED), FParam("value", BaseDataType.BYTE), FParam("minimum", BaseDataType.BYTE), FParam("maximum", BaseDataType.BYTE)),
"clamp__byte" to FSignature(true, arrayOf(BaseDataType.BYTE), FParam("value", BaseDataType.BYTE), FParam("minimum", BaseDataType.BYTE), FParam("maximum", BaseDataType.BYTE)),
"clamp__ubyte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.UBYTE), FParam("minimum", BaseDataType.UBYTE), FParam("maximum", BaseDataType.UBYTE)),
"clamp__word" to FSignature(true, arrayOf(BaseDataType.WORD), FParam("value", BaseDataType.WORD), FParam("minimum", BaseDataType.WORD), FParam("maximum", BaseDataType.WORD)),
"clamp__uword" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.UWORD), FParam("minimum", BaseDataType.UWORD), FParam("maximum", BaseDataType.UWORD)),
"clamp__long" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("value", BaseDataType.LONG), FParam("minimum", BaseDataType.LONG), FParam("maximum", BaseDataType.LONG)),
"min" to FSignature(true, arrayOf(BaseDataType.UNDEFINED), FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
"min__byte" to FSignature(true, arrayOf(BaseDataType.BYTE), FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
"min__ubyte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("val1", BaseDataType.UBYTE), FParam("val2", BaseDataType.UBYTE)),
"min__word" to FSignature(true, arrayOf(BaseDataType.WORD), FParam("val1", BaseDataType.WORD), FParam("val2", BaseDataType.WORD)),
"min__uword" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("val1", BaseDataType.UWORD), FParam("val2", BaseDataType.UWORD)),
"min__long" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("val1", BaseDataType.LONG), FParam("val2", BaseDataType.LONG)),
"max" to FSignature(true, arrayOf(BaseDataType.UNDEFINED), FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
"max__byte" to FSignature(true, arrayOf(BaseDataType.BYTE), FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
"max__ubyte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("val1", BaseDataType.UBYTE), FParam("val2", BaseDataType.UBYTE)),
"max__word" to FSignature(true, arrayOf(BaseDataType.WORD), FParam("val1", BaseDataType.WORD), FParam("val2", BaseDataType.WORD)),
"max__uword" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("val1", BaseDataType.UWORD), FParam("val2", BaseDataType.UWORD)),
"max__long" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("val1", BaseDataType.LONG), FParam("val2", BaseDataType.LONG)),
"peek" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD)),
"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")
@@ -18,13 +18,13 @@ class CompilationOptions(val output: OutputType,
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,
var includeSourcelines: Boolean = false,
var dumpVariables: Boolean = false,
var dumpSymbols: Boolean = false,
var experimentalCodegen: Boolean = false,
var varsHighBank: Int? = null,
var varsGolden: Boolean = false,
var slabsHighBank: Int? = null,
@@ -33,6 +33,7 @@ class CompilationOptions(val output: OutputType,
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 +42,79 @@ 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 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 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, varsHighBank, varsGolden,
slabsHighBank, slabsGolden, breakpointCpuInstruction, ignoreFootguns, outputDir, quiet,
profilingInstrumentation, symbolDefs
)
}
}
}
}
+25 -1
View File
@@ -14,8 +14,11 @@ fun Number.toHex(): String {
// larger -> "$12345678"
// negative values are prefixed with '-'.
val integer = this.toLong()
if(integer<0)
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')
@@ -95,3 +98,24 @@ fun String.unescape(): String {
}
return result.joinToString("")
}
// === Symbol name prefixing ===
// These are used by code generators to add p8b_/p8s_/p8v_/etc. prefixes to symbol names.
// The prefix character depends on the symbol type (block='b', sub='s', variable='v', etc.)
// Scoped names (containing dots) have each part prefixed according to its role.
fun prefixScopedName(name: String, type: Char): String {
val generatedPrefix = "p8_label_gen_"
if('.' !in name) {
if(name.startsWith(generatedPrefix))
return name
return "p8${type}_$name"
}
val parts = name.split('.')
val firstPrefixed = "p8b_${parts[0]}"
val lastPart = parts.last()
val lastPrefixed = if(lastPart.startsWith(generatedPrefix)) lastPart else "p8${type}_$lastPart"
val inbetweenPrefixed = parts.drop(1).dropLast(1).map{ "p8s_$it" }
val prefixed = listOf(firstPrefixed) + inbetweenPrefixed + listOf(lastPrefixed)
return prefixed.joinToString(".")
}
+444
View File
@@ -0,0 +1,444 @@
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.isByteOrBool
this == STR -> other.isByteOrBool
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.isUnsigned get() = !this.isSigned
val BaseDataType.isSignedInteger get() = this.isSigned && this.isInteger
val BaseDataType.isUnsignedInteger get() = this.isUnsigned && this.isInteger
val BaseDataType.isArray get() = this == BaseDataType.ARRAY || this == BaseDataType.ARRAY_SPLITW || 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
}
+30 -407
View File
@@ -1,387 +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)
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
}
}
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.isLong get() = this == BaseDataType.LONG
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.isWordOrByteOrBool get() = this in arrayOf(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 arrayOf(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 arrayOf(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 ISubType {
val scopedNameString: String
fun memsize(sizer: IMemSizer): Int
fun sameas(other: ISubType): Boolean
fun getFieldType(name: String): DataType?
}
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 {
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)
}
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)
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")
}
// 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.POINTER, BaseDataType.ARRAY_POINTER)
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) || (targetType.isPointer && targetType.sub==BaseDataType.UBYTE)
BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW -> targetType.base in arrayOf(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)
// 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)
}
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 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
}
enum class CpuRegister {
A,
@@ -402,10 +20,10 @@ enum class RegisterOrPair {
R0, R1, R2, R3, R4, R5, R6, R7,
R8, R9, R10, R11, R12, R13, R14, R15,
// combined virtual registers to store 32 bits longs:
R0R1_32, R2R3_32, R4R5_32, R6R7_32, R8R9_32, R10R11_32, R12R13_32, R14R15_32;
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
@@ -415,15 +33,19 @@ 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_32 -> "r0"
R2R3_32 -> "r2"
R4R5_32 -> "r4"
R6R7_32 -> "r6"
R8R9_32 -> "r8"
R10R11_32 -> "r10"
R12R13_32 -> "r12"
R14R15_32 -> "r14"
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")
}
@@ -435,19 +57,20 @@ 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, 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
fun isLong() = this in CombinedLongRegisters
} // only used in parameter and return value specs in asm subroutines
@@ -458,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()
}
}
@@ -477,25 +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 combinedLongRegisters = arrayOf(
RegisterOrPair.R0R1_32,
RegisterOrPair.R2R3_32,
RegisterOrPair.R4R5_32,
RegisterOrPair.R6R7_32,
RegisterOrPair.R8R9_32,
RegisterOrPair.R10R11_32,
RegisterOrPair.R12R13_32,
RegisterOrPair.R14R15_32
val CombinedLongRegisters = setOf(
RegisterOrPair.R0R1,
RegisterOrPair.R2R3,
RegisterOrPair.R4R5,
RegisterOrPair.R6R7,
RegisterOrPair.R8R9,
RegisterOrPair.R10R11,
RegisterOrPair.R12R13,
RegisterOrPair.R14R15
)
val CpuRegisters = arrayOf(
val CpuRegisters = setOf(
RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y,
RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY
)
@@ -531,4 +154,4 @@ enum class ZeropageWish {
enum class SplitWish {
DONTCARE,
NOSPLIT
}
}
@@ -2,10 +2,48 @@ package prog8.code.core
import java.nio.file.Path
enum class CpuType {
CPU6502,
CPU65C02,
VIRTUAL
/**
* Identifies the target CPU type and its key characteristics that affect IR semantics.
*
* The most important property is [statusBitsOnMultiByteOps], which controls the IR
* status-bit contract (see IRInstructions.kt for the full spec):
*
* - When `true`, the IR generator is allowed to skip emitting explicit CMP/CMPI before
* branches that depend on the result of a previous "status-setting" instruction
* (LOAD, INC, DEC, AND, OR, XOR, etc.). The contract is: "this instruction sets
* Z and N based on the full multi-byte value." Backends for these targets must
* honor this contract (e.g., M68000's `move.w #imm,d0` naturally sets Z based on
* the 16-bit value, so the IR can rely on it).
*
* - When `false`, multi-byte operations on this target do NOT set status bits
* correctly for the full value (e.g., 6502's `dec lo` only sets Z based on the
* low byte of a 16-bit decrement). The IR generator must therefore always emit
* an explicit CMP/CMPI before any branch that depends on the result. Backends
* for these targets should NOT add `ora` postambles to set Z; instead they
* rely on the explicit CMP/CMPI emitted by the IR generator.
*
* For 8-bit CPUs (6502, 65C02) the natural status flags from a multi-byte
* cascade are misleading: Z is set based on the LAST byte operated on, not
* the full value. So we treat the contract as "no instruction sets status
* bits except CMP/CMPI/SEC/CLC/SGN/BITTST" and have the IR generator emit
* explicit compares. This avoids the trap of "this works for BYTE but
* silently breaks for WORD/LONG".
*/
enum class CpuType(val statusBitsOnMultiByteOps: Boolean) {
/** 8-bit NMOS 6502. Multi-byte ops don't set Z based on full value. */
CPU6502(false),
/** 8-bit CMOS 65C02. Same status-bit issue as plain 6502. */
CPU65C02(false),
/**
* Virtual machine target. We use `false` here so the IR generator always
* emits explicit CMP/CMPI; the VM's setResultReg then does NOT set Z/N
* flags (only CMP/CMPI/SEC/CLC/SGN/BITTST do). This matches the VM's
* existing documentation (VirtualMachine.kt top comment) and keeps the
* behavior consistent across all current targets.
*/
VIRTUAL(false)
}
interface ICompilationTarget: IStringEncoding, IMemSizer {
@@ -13,8 +51,7 @@ interface ICompilationTarget: IStringEncoding, IMemSizer {
val FLOAT_MAX_NEGATIVE: Double
val FLOAT_MAX_POSITIVE: Double
val FLOAT_MEM_SIZE: Int
val STARTUP_CODE_RESERVED_SIZE: UInt // this is here, so that certain compiler targets are able to tune this
val FLOAT_MEM_SIZE: UInt
val PROGRAM_LOAD_ADDRESS : UInt
val PROGRAM_MEMTOP_ADDRESS: UInt
val BSSHIGHRAM_START: UInt
@@ -24,11 +61,12 @@ interface ICompilationTarget: IStringEncoding, IMemSizer {
val cpu: CpuType
var zeropage: Zeropage
var golden: GoldenRam
val libraryPath: Path?
val customLauncher: List<String>
val additionalAssemblerOptions: List<String>
val defaultOutputType: OutputType
val defaultLauncherType: CbmPrgLauncherType
val supportsBankedCalls: Boolean
fun initializeMemoryAreas(compilerOptions: CompilationOptions)
fun getFloatAsmBytes(num: Number): 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) {
+21 -59
View File
@@ -28,26 +28,21 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
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, SCRATCH_PTR, SCRATCH_PTR+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
@@ -65,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"))
@@ -95,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))
}
}
@@ -131,43 +124,12 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
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) {
+14 -11
View File
@@ -5,14 +5,17 @@ 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}-${endCol}]"
init {
if(!file.startsWith('~') ||!file.endsWith('~'))
require(line>0 && startCol>=0 && endCol>=startCol) {
"Invalid position: $this"
}
/**
* 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 {
@@ -21,8 +24,8 @@ data class Position(val file: String, val line: Int, val startCol: Int, val endC
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:"
@@ -32,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,18 +3,20 @@ 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 {
val userHome: Path get() = Path.of(System.getProperty("user.home"))
fun expandTilde(path: String): String = if (path.startsWith("~")) {
val userHome = System.getProperty("user.home")
userHome + path.drop(1)
userHome.toString() + path.drop(1)
} else {
path
}
@@ -23,45 +25,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 +71,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>>()
}
+8 -9
View File
@@ -11,11 +11,13 @@ class C128Target: ICompilationTarget,
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
override val name = NAME
override val supportsBankedCalls = true
override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null
override val customLauncher = emptyList<String>()
override val additionalAssemblerOptions = emptyList<String>()
override val defaultOutputType = OutputType.PRG
override val defaultLauncherType = CbmPrgLauncherType.BASIC
companion object {
const val NAME = "c128"
@@ -26,18 +28,16 @@ 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 STARTUP_CODE_RESERVED_SIZE = 20u
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE.toUInt()
override val PROGRAM_LOAD_ADDRESS = 0x1c01u
override val PROGRAM_MEMTOP_ADDRESS = 0xc000u
override val BSSHIGHRAM_START = 0u // TODO address?
override val BSSHIGHRAM_END = 0u // TODO address?
override val BSSGOLDENRAM_START = 0u // TODO address?
override val BSSGOLDENRAM_END = 0u // TODO address?
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 +71,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?
}
}
+3 -4
View File
@@ -12,11 +12,13 @@ class C64Target: ICompilationTarget,
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
override val name = NAME
override val supportsBankedCalls = true
override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null
override val customLauncher = emptyList<String>()
override val additionalAssemblerOptions = emptyList<String>()
override val defaultOutputType = OutputType.PRG
override val defaultLauncherType = CbmPrgLauncherType.BASIC
companion object {
const val NAME = "c64"
@@ -29,8 +31,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 STARTUP_CODE_RESERVED_SIZE = 20u
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE.toUInt()
override val PROGRAM_LOAD_ADDRESS = 0x0801u
override val PROGRAM_MEMTOP_ADDRESS = 0xcfe0u // $a000 if floats are used
// note that at $cfe0-$cfff are the 16 'virtual registers' R0-R15
@@ -41,7 +42,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 +87,6 @@ class C64Target: ICompilationTarget,
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = C64Zeropage(compilerOptions)
golden = GoldenRam(compilerOptions, UIntRange.EMPTY)
}
}
@@ -15,16 +15,17 @@ import kotlin.io.path.nameWithoutExtension
class ConfigFileTarget(
override val name: String,
override val supportsBankedCalls: Boolean,
override val defaultEncoding: Encoding,
override val cpu: CpuType,
override val PROGRAM_LOAD_ADDRESS: UInt,
override val PROGRAM_MEMTOP_ADDRESS: UInt,
override val STARTUP_CODE_RESERVED_SIZE: UInt,
override val BSSHIGHRAM_START: UInt,
override val BSSHIGHRAM_END: UInt,
override val BSSGOLDENRAM_START: UInt,
override val BSSGOLDENRAM_END: UInt,
override val defaultOutputType: OutputType,
override val defaultLauncherType: CbmPrgLauncherType,
override val libraryPath: Path,
override val customLauncher: List<String>,
override val additionalAssemblerOptions: List<String>,
@@ -112,19 +113,26 @@ class ConfigFileTarget(
val assemblerOptionsStr = props.getProperty("assembler_options", "").trim()
val outputTypeString = props.getProperty("output_type", "PRG")
val defaultOutputType = OutputType.valueOf(outputTypeString.uppercase())
val launcherString = props.getProperty("launcher", "BASIC")
val defaultLauncherType = try {
CbmPrgLauncherType.valueOf(launcherString.uppercase())
} catch (_: IllegalArgumentException) {
CbmPrgLauncherType.BASIC
}
return ConfigFileTarget(
configfile.nameWithoutExtension,
props.getProperty("supports_banked_calls", "false").toBoolean(),
Encoding.entries.first { it.prefix==props.getString("encoding") },
cpuType,
props.getInteger("load_address"),
props.getInteger("memtop"),
0u, // used only in a very specific error condition check in a certain scenario...
props.getInteger("bss_highram_start"),
props.getInteger("bss_highram_end"),
props.getInteger("bss_goldenram_start"),
props.getInteger("bss_goldenram_end"),
defaultOutputType,
defaultLauncherType,
libraryPath,
customLauncher,
if(assemblerOptionsStr=="") emptyList() else assemblerOptionsStr.split(" "),
@@ -145,10 +153,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")
@@ -168,6 +175,5 @@ class ConfigFileTarget(
zpFullsafe,
compilerOptions
)
// note: there's no golden ram yet
}
}
+3 -4
View File
@@ -11,11 +11,13 @@ class Cx16Target: ICompilationTarget,
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
override val name = NAME
override val supportsBankedCalls = true
override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null
override val customLauncher = emptyList<String>()
override val additionalAssemblerOptions = emptyList<String>()
override val defaultOutputType = OutputType.PRG
override val defaultLauncherType = CbmPrgLauncherType.BASIC
companion object {
const val NAME = "cx16"
@@ -26,8 +28,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 STARTUP_CODE_RESERVED_SIZE = 20u
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE.toUInt()
override val PROGRAM_LOAD_ADDRESS = 0x0801u
override val PROGRAM_MEMTOP_ADDRESS = 0x9f00u
@@ -37,7 +38,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 +87,6 @@ class Cx16Target: ICompilationTarget,
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = CX16Zeropage(compilerOptions)
golden = GoldenRam(compilerOptions, BSSGOLDENRAM_START..BSSGOLDENRAM_END)
}
+2 -2
View File
@@ -19,7 +19,7 @@ data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, va
val flt = num.toDouble()
if (flt !in FLOAT_MAX_NEGATIVE..FLOAT_MAX_POSITIVE)
throw InternalCompilerException("floating point number out of 5-byte mflpt range: $this")
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()
@@ -30,10 +30,10 @@ internal class NormalMemSizer(val floatsize: Int): IMemSizer {
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.isStructInstance -> dt.subType?.memsize(this) ?: 0
dt.isUndefined -> throw IllegalArgumentException("undefined has no memory size")
else -> 2 * (numElements ?: 1)
}
}
}
}
+3 -4
View File
@@ -11,11 +11,13 @@ class PETTarget: ICompilationTarget,
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
override val name = NAME
override val supportsBankedCalls = false
override val defaultEncoding = Encoding.PETSCII
override val libraryPath = null
override val customLauncher = emptyList<String>()
override val additionalAssemblerOptions = emptyList<String>()
override val defaultOutputType = OutputType.PRG
override val defaultLauncherType = CbmPrgLauncherType.BASIC
companion object {
const val NAME = "pet32"
@@ -25,8 +27,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 STARTUP_CODE_RESERVED_SIZE = 20u
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE.toUInt()
override val PROGRAM_LOAD_ADDRESS = 0x0401u
override val PROGRAM_MEMTOP_ADDRESS = 0x8000u
@@ -36,7 +37,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 +74,6 @@ class PETTarget: ICompilationTarget,
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
zeropage = PETZeropage(compilerOptions)
golden = GoldenRam(compilerOptions, UIntRange.EMPTY)
}
}
+9 -6
View File
@@ -13,11 +13,13 @@ class VMTarget: ICompilationTarget,
IMemSizer by NormalMemSizer(FLOAT_MEM_SIZE) {
override val name = NAME
override val supportsBankedCalls = false
override val defaultEncoding = Encoding.ISO
override val libraryPath = null
override val customLauncher = emptyList<String>()
override val additionalAssemblerOptions = emptyList<String>()
override val defaultOutputType = OutputType.PRG
override val defaultLauncherType = CbmPrgLauncherType.BASIC
companion object {
const val NAME = "virtual"
@@ -28,8 +30,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 STARTUP_CODE_RESERVED_SIZE = 0u // not actually used
override val FLOAT_MEM_SIZE = VMTarget.FLOAT_MEM_SIZE.toUInt()
override val PROGRAM_LOAD_ADDRESS = 0u // not actually used
override val PROGRAM_MEMTOP_ADDRESS = 0xffffu // not actually used
@@ -38,7 +39,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
@@ -69,6 +69,10 @@ 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...")
@@ -76,7 +80,7 @@ class VMTarget: ICompilationTarget,
val vm = Class.forName("prog8.vm.VmRunner").getDeclaredConstructor().newInstance() as IVirtualMachineRunner
val withExt = if(programNameWithPath.extension=="p8ir") programNameWithPath else programNameWithPath.resolveSibling("${programNameWithPath.name}.p8ir")
if(withExt.isReadable())
vm.runProgram(withExt.readText(), quiet)
vm.runProgram(withExt.readText(), quiet, traceEnabled)
else
throw java.nio.file.NoSuchFileException(withExt.name, null, "not a .p8ir file")
}
@@ -85,14 +89,13 @@ class VMTarget: ICompilationTarget,
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) {
@@ -8,7 +8,7 @@ import java.io.CharConversionException
object PetsciiEncoding {
// decoding: from Petscii/Screencodes (0-255) to Unicode
// character tables used from https://github.com/irmen/cbmcodecs2
// character tables used from https://codeberg.org/irmen/cbmcodecs2.git
private val decodingPetsciiLowercase = charArrayOf(
'\u0000', // 0x00 -> \u0000
@@ -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.
@@ -46,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
@@ -64,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,7 +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 = 0x9bu // temp storage for a pointer $9b+$9c
override val SCRATCH_PTR = 0x04u // temp storage for a pointer $04+$05
init {
@@ -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)
}
}
@@ -22,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
@@ -64,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)
}
}
@@ -57,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
@@ -35,7 +32,10 @@ class PETZeropage(options: CompilationOptions) : Zeropage(options) {
}
ZeropageType.FLOATSAFE,
ZeropageType.BASICSAFE -> {
free.addAll(0xb1u..0xbau) // TODO more?
// Only scratch register area is safe for transient use.
// $b5 and $ba are clobbered by PET BASIC/Kernal calls like CHROUT ($ffd2).
// User variables requiring persistent storage must NOT be placed here.
free.addAll(listOf(0xb1u, 0xb2u, 0xb3u, 0xb4u, 0xb6u, 0xb7u, 0xb8u, 0xb9u, 0xfbu, 0xfcu, 0xfdu, 0xfeu))
}
ZeropageType.DONTUSE -> {
free.clear() // don't use zeropage at all
@@ -47,6 +47,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,45 +3,61 @@ 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>()
val infos = mutableListOf<String>()
val printedErrors = mutableListOf<String>()
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) {
printedErrors.add(errormessage)
}
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,311 @@
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()
BaseDataType.STR.largerSizeThan(BaseDataType.UBYTE).shouldBeTrue()
BaseDataType.STR.largerSizeThan(BaseDataType.BOOL).shouldBeTrue()
BaseDataType.STR.largerSizeThan(BaseDataType.UWORD).shouldBeFalse()
BaseDataType.STR.largerSizeThan(BaseDataType.WORD).shouldBeFalse()
BaseDataType.STR.largerSizeThan(BaseDataType.POINTER).shouldBeFalse()
BaseDataType.STR.largerSizeThan(BaseDataType.LONG).shouldBeFalse()
BaseDataType.POINTER.largerSizeThan(BaseDataType.UBYTE).shouldBeTrue()
BaseDataType.POINTER.largerSizeThan(BaseDataType.BOOL).shouldBeTrue()
BaseDataType.POINTER.largerSizeThan(BaseDataType.UWORD).shouldBeFalse()
BaseDataType.POINTER.largerSizeThan(BaseDataType.STR).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()
BaseDataType.STR.equalsSize(BaseDataType.STR).shouldBeTrue()
BaseDataType.STR.equalsSize(BaseDataType.UWORD).shouldBeTrue()
BaseDataType.STR.equalsSize(BaseDataType.BYTE).shouldBeFalse()
BaseDataType.POINTER.equalsSize(BaseDataType.UWORD).shouldBeTrue()
BaseDataType.POINTER.equalsSize(BaseDataType.STR).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,729 @@
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 free locations") {
val options = createOptions(PETTarget(), ZeropageType.BASICSAFE)
val zp = PETZeropage(options)
// BASICSAFE includes scratch register addresses ($b1-$b4, $b6-$b9)
// and $fb-$fe. After scratch removal, $fb-$fe remain usable.
zp.free.size shouldBeLessThan 20
zp.free.contains(0xfbu) shouldBe true
zp.free.contains(0xfcu) shouldBe true
zp.free.contains(0xfdu) shouldBe true
zp.free.contains(0xfeu) shouldBe true
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
setOf(0xfbu, 0xfcu, 0xfdu, 0xfeu).forEach { addr ->
floatZp.free.contains(addr) shouldBe true
}
}
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 - extra fb-fe free after scratch removal") {
val options = createOptions(PETTarget(), ZeropageType.BASICSAFE)
val zp = PETZeropage(options)
// BASICSAFE includes scratch register addresses ($b1-$b4, $b6-$b9)
// and $fb-$fe. After removing scratch registers from the pool,
// only $fb-$fe remain free.
zp.free.size shouldBeLessThan 15
setOf(0xfbu, 0xfcu, 0xfdu, 0xfeu).forEach { addr ->
zp.free.contains(addr) shouldBe true
}
}
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")
}
}
+3 -34
View File
@@ -5,39 +5,8 @@ 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.1.0")
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")
}
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")
}
-19
View File
@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<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="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.runner.junit5.jvm" level="project" />
<orderEntry type="library" name="io.kotest.framework.datatest" level="project" />
</component>
</module>
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -1,30 +1,43 @@
package prog8.codegen.cpu6502
import prog8.code.ast.PtAsmSub
import prog8.code.ast.PtSubroutineParameter
import prog8.code.core.CombinedLongRegisters
import prog8.code.core.Cx16VirtualRegisters
import prog8.code.core.RegisterOrPair
import prog8.code.core.RegisterOrStatusflag
fun asmsub6502ArgsEvalOrder(sub: PtAsmSub): List<Int> {
fun asmsub6502ArgsEvalOrder(sub: PtAsmSub, argComplexity: List<Boolean>): 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
// Within each group, complex expressions are evaluated first (they may clobber registers,
// so running them before simple expressions avoids needing to save/restore already-loaded values).
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 }
cx16regs.forEach { order += it.index }
pairedRegs.forEach { order += it.index }
singleCpuRegs.sortedBy { it.value.first.registerOrPair!!.asCpuRegister() }.asReversed().forEach { order += it.index }
combinedLongRegs.sortedByDescending { argComplexity[it.index] }.forEach { order += it.index }
cx16regs.sortedByDescending { argComplexity[it.index] }.forEach { order += it.index }
pairedRegs.sortedByDescending { argComplexity[it.index] }.forEach { order += it.index }
singleCpuRegs
.sortedWith(
compareByDescending<IndexedValue<Pair<RegisterOrStatusflag, PtSubroutineParameter>>> { argComplexity[it.index] }
.thenByDescending { it.value.first.registerOrPair!!.asCpuRegister() }
)
.forEach { order += it.index }
require(rest.all { it.value.first.registerOrPair==null && it.value.first.statusflag!=null})
floatRegs.forEach { order += it.index }
floatRegs.sortedByDescending { argComplexity[it.index] }.forEach { order += it.index }
rest.forEach { order += it.index }
require(order.size==sub.parameters.size)
@@ -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
@@ -132,9 +136,27 @@ internal class AssemblyProgram(
}
val proc = ProcessBuilder(assemblerCommand)
if(!options.quiet)
proc.inheritIO()
val process = proc.start()
.redirectErrorStream(true)
if (options.quiet) {
proc.redirectOutput(ProcessBuilder.Redirect.DISCARD)
}
val process = try {
proc.start()
} catch (e: Exception) {
System.err.println("daemon: assembler failed to start: ${e.message}")
return false
}
if (!options.quiet) {
process.inputStream.bufferedReader().use { reader ->
reader.forEachLine {
println(it)
}
}
}
val result = process.waitFor()
if (result == 0) {
removeGeneratedLabelsFromMonlist()
File diff suppressed because it is too large. Load diff
@@ -42,12 +42,129 @@ internal class ForLoopsAsmGen(
when {
iterableDt.isByteArray -> forOverNonconstByteRange(stmt, iterableDt, range)
iterableDt.isWordArray && !iterableDt.isSplitWordArray -> forOverNonconstWordRange(stmt, iterableDt, range)
else -> throw AssemblyError("range expression can only be byte or word")
iterableDt.isLongArray -> forOverNonconstLongRange(stmt, iterableDt, range)
else -> throw AssemblyError("range expression can only be byte, word or long")
}
asmgen.loopEndLabels.removeLast()
}
private fun forOverNonconstLongRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) {
val stepsize = range.step.asConstInteger()!!
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.add(endLabel)
val varname = asmgen.asmVariableName(stmt.variable)
asmgen.assignExpressionToVariable(range.from, varname, iterableDt.elementType())
if (stepsize == 1 || stepsize == -1)
forOverLongsRangeStepOne(range, varname, iterableDt, loopLabel, endLabel, stmt)
else
forOverLongsRangeStepGreaterOne(range, varname, iterableDt, loopLabel, endLabel, stmt)
}
private fun forOverLongsRangeStepOne(range: PtRange, varname: String, iterableDt: DataType, loopLabel: String, endLabel: String, forloop: PtForLoop) {
val stepsize = range.step.asConstInteger()!!
// Use temporary variable to store the limit
val toValueVar = asmgen.createTempVarReused(iterableDt.elementType().base, false, range)
asmgen.assignExpressionToVariable(range.to, toValueVar, iterableDt.elementType())
if (stepsize == 1) {
// Increment toValueVar to make the loop inclusive (0 to N runs N+1 times)
asmgen.out("""
inc $toValueVar
bne +
inc $toValueVar+1
bne +
inc $toValueVar+2
bne +
inc $toValueVar+3
+
""")
} else {
// Decrement toValueVar to make the loop inclusive (N downto 0 runs N+1 times)
val label1 = asmgen.makeLabel("dec_1")
val label2 = asmgen.makeLabel("dec_2")
val label3 = asmgen.makeLabel("dec_3")
asmgen.out("""
lda $toValueVar
bne $label1
lda $toValueVar+1
bne $label2
lda $toValueVar+2
bne $label3
dec $toValueVar+3
$label3: dec $toValueVar+2
$label2: dec $toValueVar+1
$label1: dec $toValueVar
""")
}
asmgen.out(loopLabel)
asmgen.translate(forloop.statements)
if (stepsize == 1) {
asmgen.out("""
inc $varname
bne +
inc $varname+1
bne +
inc $varname+2
bne +
inc $varname+3
+ lda $varname
cmp $toValueVar
bne $loopLabel
lda $varname+1
cmp $toValueVar+1
bne $loopLabel
lda $varname+2
cmp $toValueVar+2
bne $loopLabel
lda $varname+3
cmp $toValueVar+3
bne $loopLabel
""")
} else {
asmgen.out("""
lda $varname
bne +++
lda $varname+1
bne ++
lda $varname+2
bne +
dec $varname+3
+ dec $varname+2
+ dec $varname+1
+ dec $varname
lda $varname
cmp $toValueVar
bne $loopLabel
lda $varname+1
cmp $toValueVar+1
bne $loopLabel
lda $varname+2
cmp $toValueVar+2
bne $loopLabel
lda $varname+3
cmp $toValueVar+3
bne $loopLabel
""")
}
asmgen.out(endLabel)
}
private fun forOverLongsRangeStepGreaterOne(range: PtRange, varname: String, iterableDt: DataType, loopLabel: String, endLabel: String, forloop: PtForLoop) {
// TODO: implement 32-bit loop
asmgen.romableError("for loops over long ranges with step != 1 not yet fully implemented", forloop.position)
asmgen.out(loopLabel)
asmgen.translate(forloop.statements)
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
}
private fun forOverNonconstByteRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) {
val stepsize = range.step.asConstInteger()!!
val loopLabel = asmgen.makeLabel("for_loop")
@@ -727,23 +844,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) {
@@ -800,10 +922,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
@@ -821,7 +942,6 @@ $loopLabel""")
bne +
lda $varname+1
cmp #>${range.last}
bne +
beq $endLabel
+ lda $varname
clc
@@ -835,7 +955,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()
}
@@ -904,6 +1078,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)
@@ -931,6 +1106,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)
@@ -967,6 +1143,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,
@@ -9,6 +9,16 @@ import prog8.codegen.cpu6502.assignment.AsmAssignment
import prog8.codegen.cpu6502.assignment.TargetStorageKind
/*
* CRITICAL: `inline asmsub` subroutines MUST be inlined at the call site.
* They are NOT called with jsr - their assembly body is copied directly into the caller.
* This means: no .proc/.pend, no rts, no jsr. The return value is left in the appropriate
* CPU register (A/X/Y) and the caller picks it up.
*
* This is handled at line 68: `if (sub.inline) { ... }`.
* Any new codegen backend MUST replicate this behavior.
*/
internal class FunctionCallAsmGen(private val program: PtProgram, private val asmgen: AsmGen6502Internal) {
internal fun translateFunctionCallStatement(stmt: PtFunctionCall) {
@@ -33,7 +43,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
// NOTE: does NOT output code to save/restore the X register for this call! Every caller should deal with this in their own way!!
// (you can use subroutine.shouldSaveX() and saveX()/restoreX() routines as a help for this)
val symbol = asmgen.symbolTable.lookup(call.name)!!
val symbol = asmgen.symbolTable.lookup(call.name) ?: asmgen.symbolTable.lookupUnscoped(call.name) ?: throw IllegalStateException("Symbol not found: ${call.name}")
if(symbol.type == StNodeType.LABEL) {
require(call.void)
asmgen.out(" jsr ${asmgen.asmSymbolName(symbol.scopedNameString)}")
@@ -44,6 +54,26 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
val subAsmName = asmgen.asmSymbolName(call.name)
if(sub is PtAsmSub) {
val varBank = sub.address?.varbank
var varbank: String? = null
var bankFixLabel: String? = null
if (varBank != null) {
// the bank variable can also be the name of a subroutine that should be called to return the bank byte dynamically
val bankSymbol = asmgen.symbolTable.lookup(varBank.name) ?: asmgen.symbolTable.lookupUnscoped(varBank.name)
if (bankSymbol?.type == StNodeType.SUBROUTINE || bankSymbol?.type == StNodeType.EXTSUB) {
val extsubId = asmgen.extsubCallSiteIds[sub]
if (extsubId != null) {
asmgen.out(" lda #${extsubId.toInt()}")
}
asmgen.out(" jsr ${asmgen.asmSymbolName(varBank.name)}")
// store bank byte directly into the JSRFAR instruction data (forward label reference)
bankFixLabel = asmgen.makeLabel("bank")
asmgen.out(" sta $bankFixLabel")
} else {
varbank = asmgen.asmVariableName(varBank)
}
}
argumentsViaRegisters(sub, call)
if (sub.inline) {
// inline the subroutine. (regardless of optimization settings!)
@@ -56,54 +86,57 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
} else {
val bank = sub.address?.constbank?.toString()
if(bank==null) {
val varbank = if(sub.address?.varbank==null) null else asmgen.asmVariableName(sub.address!!.varbank!!)
if(varbank!=null) {
if(varbank!=null || bankFixLabel!=null) {
if(asmgen.options.romable)
TODO("no codegen yet for non-const bank in subroutine call that's usable in ROM ${call.position}")
// variable bank case: save/restore registers around the self-modifying sta
if(varbank!=null) {
asmgen.out("""
php
pha
lda $varbank
sta +
pla
plp""")
}
val label = bankFixLabel ?: "+"
// self-modifying code: set jsrfar bank argument
when(asmgen.options.compTarget.name) {
"cx16" -> {
// JSRFAR can jump to a banked RAM address as well!
asmgen.out("""
php
pha
lda $varbank
sta +
pla
plp
jsr cx16.JSRFAR
.word $subAsmName ; ${sub.address!!.address.toHex()}
+ .byte 0 ; modified"""
$label .byte 0 ; modified"""
)
}
"c64" -> {
asmgen.out("""
php
pha
lda $varbank
sta +
pla
plp
jsr c64.x16jsrfar
.word $subAsmName ; ${sub.address!!.address.toHex()}
+ .byte 0 ; modified"""
$label .byte 0 ; modified"""
)
}
"c128" -> {
asmgen.out("""
php
pha
lda $varbank
sta +
pla
plp
jsr c128.x16jsrfar
.word $subAsmName ; ${sub.address!!.address.toHex()}
+ .byte 0 ; modified"""
$label .byte 0 ; modified"""
)
}
else -> throw AssemblyError("callfar is not supported on the selected compilation target")
else -> {
if(asmgen.options.compTarget.supportsBankedCalls) {
val targetName = asmgen.options.compTarget.name
asmgen.out("""
jsr $targetName.x16jsrfar
.word $subAsmName ; ${sub.address!!.address.toHex()}
$label .byte 0 ; modified"""
)
} else
throw AssemblyError("callfar is not supported on the selected compilation target")
}
}
} else {
asmgen.out(" jsr $subAsmName")
@@ -133,7 +166,17 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
.byte $bank"""
)
}
else -> throw AssemblyError("callfar is not supported on the selected compilation target")
else -> {
if(asmgen.options.compTarget.supportsBankedCalls) {
val targetName = asmgen.options.compTarget.name
asmgen.out("""
jsr $targetName.x16jsrfar
.word $subAsmName ; ${sub.address!!.address.toHex()}
.byte $bank"""
)
} else
throw AssemblyError("callfar is not supported on the selected compilation target")
}
}
}
}
@@ -203,15 +246,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" || arg.name == "mklong2")
return usesOtherRegistersWhileEvaluating(arg.args[0]) || usesOtherRegistersWhileEvaluating(arg.args[1])
if (arg.name == "mklong")
return usesOtherRegistersWhileEvaluating(arg.args[0]) || usesOtherRegistersWhileEvaluating(arg.args[1]) ||
usesOtherRegistersWhileEvaluating(arg.args[2]) || usesOtherRegistersWhileEvaluating(arg.args[3])
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
@@ -233,7 +277,8 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
if(sub.parameters.size==1) {
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single().second), call.args[0])
} else {
val optimalEvalOrder = asmsub6502ArgsEvalOrder(sub)
val argComplexity = call.args.map { usesOtherRegistersWhileEvaluating(it) }
val optimalEvalOrder = asmsub6502ArgsEvalOrder(sub, argComplexity)
optimalEvalOrder.forEach {
val param = sub.parameters[it]
val arg = call.args[it]
@@ -267,8 +312,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)
@@ -279,7 +324,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)
@@ -290,11 +335,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
@@ -322,7 +367,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 {
@@ -332,7 +377,7 @@ 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 =
@@ -342,8 +387,8 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
AsmAssignTarget.fromRegisters(register, parameter.value.type.isSigned, value.position, scope, asmgen)
}
val src = if(value.type.isPassByRef) {
if(value is PtIdentifier) {
val addr = PtAddressOf(value.type.typeForAddressOf(false), false, Position.DUMMY)
if(value is PtIdentifier || value is PtPointerDeref) {
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)
@@ -362,12 +407,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
@@ -376,6 +426,9 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
if(argType.isUnsignedWord && paramType.isString)
return true
if (argType.isArray && (paramType.isString || paramType.isPointerToByte))
return true
return false
}
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -352,7 +352,7 @@ internal class ProgramAndVarsGen(
val varsInBlock = getVars(scope)
// Zeropage Variables
val varnames = varsInBlock.filter { it.value.type==StNodeType.STATICVAR }.map { it.value.scopedNameString }.toSet()
val varnames = varsInBlock.filter { it.value.type==StNodeType.STATICVAR }.mapTo(mutableSetOf()) { it.value.scopedNameString }
zeropagevars2asm(varnames)
// MemDefs and Consts
@@ -372,13 +372,14 @@ internal class ProgramAndVarsGen(
}
private fun asmTypeString(dt: DataType): String {
val effectiveDt = if(dt.isArray) dt.elementType() else dt
return when {
dt.isBool || dt.isUnsignedByte -> ".byte"
dt.isSignedByte -> ".char"
dt.isUnsignedWord || dt.isPointer -> ".word"
dt.isSignedWord -> ".sint"
dt.isLong -> ".dint"
dt.isFloat -> ".byte"
effectiveDt.isBool || effectiveDt.isUnsignedByte -> ".byte"
effectiveDt.isSignedByte -> ".char"
effectiveDt.isUnsignedWord || effectiveDt.isPointer -> ".word"
effectiveDt.isSignedWord -> ".sint"
effectiveDt.isLong -> ".dint"
effectiveDt.isFloat -> ".byte"
else -> {
throw AssemblyError("weird dt")
}
@@ -387,58 +388,67 @@ internal class ProgramAndVarsGen(
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) {
"["+compTarget.getFloatAsmBytes(value.number!!)+"]"
} else {
when {
value.number!=null -> {
if(field.first.isPointer)
"$"+value.number!!.toInt().toString(16)
else if(field.first.isInteger)
value.number!!.toInt().toString()
else
value.number.toString()
}
value.addressOfSymbol!=null -> value.addressOfSymbol!!
value.boolean!=null -> if(value.boolean==true) "1" else "0"
else -> throw AssemblyError("weird struct initial value $value")
}
fun formatValue(value: StArrayElement, dt: DataType): String {
if(dt.isFloat) {
val numValue = (value as? StArrayElement.Number)?.value
?: throw AssemblyError("expected number for float field")
return "["+compTarget.getFloatAsmBytes(numValue)+"]"
}
return when (value) {
is StArrayElement.Number -> {
if(dt.isPointer)
"$"+value.value.toInt().toString(16)
else if(dt.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")
}
}
fun flatInitValues(instance: StStructInstance, structtype: StStruct): List<String> {
var valueIndex = 0
return structtype.fields.flatMap { field ->
val arraySz = field.arraySize
val count = arraySz ?: 1
val elemDt = if(arraySz!=null) field.type.elementType() else field.type
(0 until count).map {
val value = instance.initialValues[valueIndex++]
formatValue(value, elemDt)
}
}
}
asmgen.out("; struct types")
symboltable.allStructInstances.distinctBy { it.structName }.forEach {
val structtype: StStruct = symboltable.lookup(it.structName) as StStruct
val structargs = structtype.fields.withIndex().joinToString(",") { field -> "f${field.index}" }
asmgen.out("${it.structName} .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
symboltable.allStructTypes().distinctBy { it.name }.forEach { structtype ->
val paramFields = structtype.fields.filter { !it.isArray }
val structargs = paramFields.indices.joinToString(",") { "f$it" }
asmgen.out("${structtype.scopedNameString} .struct $structargs\n")
var paramIdx = 0
for(field in structtype.fields) {
val type = asmTypeString(field.type)
if(field.isArray) {
val anonymous = List(field.arraySize!!) { "?" }.joinToString(",")
asmgen.out("p8v_${field.name} $type $anonymous")
} else {
asmgen.out("p8v_${field.name} $type \\f$paramIdx")
paramIdx++
}
}
asmgen.out(" .endstruct\n")
}
val (instancesNoInit, instances) = symboltable.allStructInstances.partition { it.initialValues.isEmpty() }
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("${it.name} .fill ${structtype.size}\n")
}
asmgen.out(" .endblock\n")
asmgen.out(" .send BSS\n")
@@ -446,9 +456,28 @@ internal class ProgramAndVarsGen(
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")
instances.forEach { instance ->
val instancename = instance.name.substringAfter('.')
val structtype: StStruct = symboltable.lookup(instance.structName) as StStruct
val values = flatInitValues(instance, structtype)
val hasArrays = structtype.fields.any { fld -> fld.isArray }
if(hasArrays) {
asmgen.out(instancename)
var valueIndex = 0
for(field in structtype.fields) {
val arraySz = field.arraySize
val count = arraySz ?: 1
val elemDt = if(arraySz!=null) field.type.elementType() else field.type
val type = asmTypeString(elemDt)
val fieldValues = (0 until count).joinToString(",") {
formatValue(instance.initialValues[valueIndex++], elemDt)
}
asmgen.out(" $type $fieldValues")
}
asmgen.out("\n")
} else {
asmgen.out("$instancename .dstruct ${instance.structName}, ${values.joinToString(",")}\n")
}
}
asmgen.out(" .endblock\n")
asmgen.out(" .send STRUCTINSTANCES\n")
@@ -503,7 +532,7 @@ internal class ProgramAndVarsGen(
val varsInSubroutine = getVars(scope)
// Zeropage Variables
val varnames = varsInSubroutine.filter { it.value.type==StNodeType.STATICVAR }.map { it.value.scopedNameString }.toSet()
val varnames = varsInSubroutine.filter { it.value.type==StNodeType.STATICVAR }.mapTo(mutableSetOf()) { it.value.scopedNameString }
zeropagevars2asm(varnames)
// MemDefs and Consts
@@ -615,18 +644,26 @@ internal class ProgramAndVarsGen(
arrayVarsWithInitInZp.forEach {
val size = it.alloc.size
val name = asmgen.asmVariableName(it.name)
asmgen.out("""
lda #<${name}_init_value
ldy #>${name}_init_value
sta cx16.r0
sty cx16.r0+1
lda #<${name}
ldy #>${name}
sta cx16.r1
sty cx16.r1+1
lda #<$size
ldy #>$size
jsr sys.memcopy""")
if(it.alloc.dt.isSplitWordArray) {
val halfSize = size / 2
if(halfSize>0) {
asmgen.out("""
ldx #${halfSize-1}
- lda ${name}_init_value_lsb,x
sta ${name}_lsb,x
lda ${name}_init_value_msb,x
sta ${name}_msb,x
dex
bpl -""")
}
} else {
asmgen.out("""
ldx #${size-1}
- lda ${name}_init_value,x
sta ${name},x
dex
bpl -""")
}
}
asmgen.out(" jmp +")
}
@@ -740,7 +777,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(
@@ -749,22 +786,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)
}
}
}
@@ -872,14 +909,14 @@ internal class ProgramAndVarsGen(
dt.isSplitWordArray -> {
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")
asmgen.out("${varname}_msb\t.byte >_array_$varname")
asmgen.out("${varname}_words := ${data.joinToString()}")
asmgen.out("${varname}_lsb\t.byte <${varname}_words")
asmgen.out("${varname}_msb\t.byte >${varname}_words")
} else {
val data = makeArrayFillDataSigned(dt, value, orNumberOfZeros)
asmgen.out("_array_$varname := ${data.joinToString()}")
asmgen.out("${varname}_lsb\t.byte <_array_$varname")
asmgen.out("${varname}_msb\t.byte >_array_$varname")
asmgen.out("${varname}_words := ${data.joinToString()}")
asmgen.out("${varname}_lsb\t.byte <${varname}_words")
asmgen.out("${varname}_msb\t.byte >${varname}_words")
}
}
dt.isUnsignedWordArray -> {
@@ -915,7 +952,9 @@ internal class ProgramAndVarsGen(
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))
@@ -926,11 +965,7 @@ internal class ProgramAndVarsGen(
}
private fun zeroFilledArray(numElts: Int): StArray {
val values = mutableListOf<StArrayElement>()
repeat(numElts) {
values.add(StArrayElement(0.0, null, null,null,null))
}
return values
return List(numElts) { StArrayElement.Number(0.0) }
}
private fun memdefsAndConsts2asm(memvars: Collection<StMemVar>, consts: Collection<StConstant>) {
@@ -938,10 +973,17 @@ internal class ProgramAndVarsGen(
asmgen.out(" ${it.name} = ${it.address.toHex()}")
}
consts.sortedBy { it.name }.forEach {
val nameWithoutColons = it.name.replace("::", "_")
if(it.dt.isFloat)
asmgen.out(" ${it.name} = ${it.value}")
else
asmgen.out(" ${it.name} = ${it.value.toHex()}")
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?")
}
}
}
@@ -971,41 +1013,35 @@ 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 || dt.elementType().isPointer) -> array.map {
if(it.number!=null) {
"$" + it.number!!.toInt().toString(16).padStart(4, '0')
}
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 if(it.structInstance!=null) {
asmgen.asmSymbolName("${StStructInstanceBlockName}.${it.structInstance!!}")
}
else if(it.structInstanceUninitialized!=null) {
asmgen.asmSymbolName("${StStructInstanceBlockName}_bss.${it.structInstanceUninitialized!!}")
}
else {
throw AssemblyError("weird array elt")
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 -> throw AssemblyError("invalid dt")
@@ -1015,41 +1051,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 || dt.elementType().isPointer) -> array.map {
val number = it.number!!.toInt()
"$" + number.toString(16).padStart(4, '0')
}
dt.isSignedWordArray -> array.map {
val number = it.number!!.toInt()
val hexnum = number.absoluteValue.toString(16).padStart(4, '0')
if(number>=0)
"$$hexnum"
else
"-$$hexnum"
}
dt.isLongArray -> array.map {
val number = it.number!!.toInt()
val hexnum = number.absoluteValue.toLongHex()
if(number>=0)
"$$hexnum"
else
"-$$hexnum"
}
else -> throw AssemblyError("invalid dt")
}
}
Loaded 100 of 507 files, more files were not shown because too many files have changed in this diff. Show more