Compare commits

...
87 Commits
Author SHA1 Message Date
Irmen de Jong e98e951834 fix chained assignment and multi-vardecl RHS evaluation 2023-12-10 16:44:51 +01:00
Irmen de Jong 2668bf8519 fix void optimization issue 2023-12-09 21:48:22 +01:00
Irmen de Jong dd4c073e18 version 9.7 2023-12-09 18:54:54 +01:00
Irmen de Jong c7c72f00c7 document underscores in numeric literals for grouping 2023-12-09 14:07:42 +01:00
Irmen de Jong ef1c665b9a allow underscores for numerical grouping 2023-12-09 13:13:34 +01:00
Irmen de Jong d56565be25 fix multi-var decl 2023-12-09 12:32:41 +01:00
Irmen de Jong e076b3aedc fix multi-var decl in nested scopes 2023-12-09 12:07:09 +01:00
Irmen de Jong ae3b2ddf5f allow multi var declarations for floats too 2023-12-08 23:29:13 +01:00
Irmen de Jong 1bdc427d73 multi var declarations ubyte x,y,z 2023-12-08 22:18:21 +01:00
Irmen de Jong 6a639ce533 chained assignments x=y=z=42 2023-12-08 01:07:16 +01:00
Irmen de Jong d91ca8b197 vm: added floats.str_f() 2023-12-07 23:10:27 +01:00
Irmen de Jong a01c0a283d add check for memory() args to be const, added floats.str_f()
add check for memory() args to be const
2023-12-07 22:39:53 +01:00
Irmen de Jong 5c393091a0 unit test for %encoding 2023-12-07 21:54:01 +01:00
Frosty-J 01b680504b Add %encoding to syntax files (#113)
* IDEA `%encoding` syntax

* N++ `%encoding` syntax

* Vim `%encoding` syntax
2023-12-07 21:53:33 +01:00
Irmen de Jong 8e4319cd5a module directive %encoding to set the text encoding for that whole file (iso, petscii, etc.) 2023-12-06 23:54:08 +01:00
Irmen de Jong 5a776dd690 improve KotlinJavaRuntime library ref 2023-12-06 22:52:39 +01:00
Irmen de Jong cce08d95db unused subroutine warning only for main compilation module 2023-12-06 21:48:56 +01:00
Irmen de Jong 28c1b208c1 optimized calls for float *10 and +0.5 2023-12-06 01:18:07 +01:00
Irmen de Jong 3844bf1f72 fix string.isupper() 2023-12-05 23:52:43 +01:00
Irmen de Jong 745d192563 added floats.normalize() 2023-12-05 22:54:35 +01:00
Irmen de Jong ee782e92ac fix cast error and vm float parsing 2023-12-05 22:51:15 +01:00
Irmen de Jong afbc91d1fc added string.isdigit, islower, isupper, isletter 2023-12-05 22:50:20 +01:00
Irmen de Jong f998888d6d fix some unicode identifier issues 2023-12-05 17:38:23 +01:00
Irmen de Jong 7d8b42d63e allow Unicode letters in identifiers: things like 'knäckebröd' and 'π' are now valid identifiers. Added floats.π constant. 2023-12-05 01:36:54 +01:00
Irmen de Jong 6ebd4e821f improved docs about subroutine scoping, fix possible optimizer crash for inlined sub 2023-12-04 23:23:52 +01:00
Irmen de Jong d1806bfdc3 added remaining verafx registers 2023-12-03 22:15:29 +01:00
Irmen de Jong 1d2d7155da palette: changed some of the available presets. Also fix sizeof(array) crash. 2023-12-03 17:14:40 +01:00
Irmen de Jong b09e0a05bf some tweaks to errors about long integer literals 2023-12-03 02:45:26 +01:00
Irmen de Jong c609e982fe allow const expression intermediate values to be 32 bits integers to avoid needless overflow errors. 2023-12-03 01:48:12 +01:00
Irmen de Jong 2b227b43fe bmx: error for unsupported file version 2023-12-02 23:56:59 +01:00
Irmen de Jong 48f09f71ab fix TODO crash on uword[0] = uword[0] or 128 (byte register assign to word array) 2023-12-02 21:29:14 +01:00
Irmen de Jong ead8c59bda allow all character encodings on all compilation targets. 2023-12-02 20:59:50 +01:00
Irmen de Jong db52a9466c fix weird compiler warning for while 1 {..} 2023-12-02 20:24:45 +01:00
Irmen de Jong 1509de390e various fixes
print_f() no longer prints a leading space.
Better error message if using float in for loop.
Fix crash when using non-const as when choice value.
VM print_f() more closely resembles the CBM version.
2023-12-02 18:23:54 +01:00
Irmen de Jong 88a1aa4f3d fix invalid optimization for integers (X/C1)*C2 , only ok for floats because of int rounding 2023-12-01 23:17:49 +01:00
Irmen de Jong 172e78e8f2 ir: ignore empty chunks instead of crashing 2023-12-01 22:49:20 +01:00
Irmen de Jong 36bfef567d comments 2023-12-01 20:20:18 +01:00
Irmen de Jong e40ebd75a2 floats.parse_f uses kernal VAL if it's present 2023-11-30 23:50:50 +01:00
Irmen de Jong 992732f2cb bmx support to save partial screens ('stamps') 2023-11-30 22:17:57 +01:00
Irmen de Jong b58a3ba1bb added cx16 sprites.move , movex and movey routines to move sprite by deltas 2023-11-30 20:49:47 +01:00
Irmen de Jong afe521b0c9 simplify bmx loading 2023-11-29 21:57:17 +01:00
Irmen de Jong 5d9caef45f bmx can load "stamps" 2023-11-29 21:07:22 +01:00
Irmen de Jong 278e2f5605 preparing for working kernal FP VAL_1() call 2023-11-29 00:27:02 +01:00
Irmen de Jong 1e299bf360 better pokef() code 2023-11-28 23:04:27 +01:00
Irmen de Jong 8dfa0bc38c fix a compiler crash in certain vardecl initialization expressions 2023-11-28 21:01:58 +01:00
Irmen de Jong fde136fb7b bmx module only supports 320 or 640 image widths for now 2023-11-28 20:38:52 +01:00
Irmen de Jong ee4da1a757 fix floats.parse_f() to use new kernal routine address for VAL
gives error message if it detects issues f.ex. with new kernal version that moves the routine
2023-11-27 23:58:28 +01:00
Irmen de Jong ae2d96c455 added peekf and pokef builtin functions. Fixed sizeof() to allow number argument as well. 2023-11-27 23:36:02 +01:00
Irmen de Jong 6d8fbe0877 fixed float array indexing with an expression 2023-11-27 20:54:49 +01:00
Irmen de Jong 2fa1d8f2e8 fix vm string hash 2023-11-27 01:27:50 +01:00
Irmen de Jong 533090a68e fix expression result register of square, callfar, string compare functions in certain situations 2023-11-26 23:02:10 +01:00
Irmen de Jong 1dff59e1d6 added string.hash() 2023-11-26 22:14:08 +01:00
Irmen de Jong 44d232f52a optimize for x in something downto 0 2023-11-26 02:24:18 +01:00
Irmen de Jong 5f6cff739a fix bmx palette writing from buffer 2023-11-26 01:50:25 +01:00
Irmen de Jong 2764d235a9 optimizing for x in 0 to something 2023-11-25 21:37:27 +01:00
Irmen de Jong 45debff89f bmx: allow palette to be loaded into memory buffer instead of vram 2023-11-25 17:58:43 +01:00
Irmen de Jong c45fbe6310 continue stmt added 2023-11-25 01:14:35 +01:00
Irmen de Jong 9ef9c24388 IR: optimize redundant labels 2023-11-25 01:10:17 +01:00
Irmen de Jong 6a40f23578 cx16: added bmx library module and showbmx example 2023-11-24 23:39:05 +01:00
Irmen de Jong 6a0a6b4751 todo 2023-11-24 01:20:10 +01:00
Irmen de Jong 0bee6f6b41 cx16: reorder processing of IRQ handlers 2023-11-24 01:12:27 +01:00
Irmen de Jong 82a15b5a16 65C02 cpu: use TRB and TSB instructions for in-place AND/OR. 2023-11-24 00:50:52 +01:00
Irmen de Jong 11b7c4459e omission 2023-11-23 00:29:31 +01:00
Irmen de Jong 98570ac456 cx16: optimized sys.set_rasterline() 2023-11-23 00:02:04 +01:00
Irmen de Jong 1b2296ad5b move cx16 irq examples to new API, fix some bugs in the handler 2023-11-22 23:25:39 +01:00
Irmen de Jong 16851746d6 new X16 irq handler routines and examples 2023-11-22 20:03:21 +01:00
Irmen de Jong 935450a45f update kotest library 2023-11-22 18:40:07 +01:00
Irmen de Jong ba67fd318b renamed cx16.VERA_IRQ_LINE_L to VERA_IRQLINE_L and added VERA_SCANLINE_L, to align with official register naming.
Also added a multi-irq example for the X16 to show the updated irq handler semantics.
2023-11-22 18:36:24 +01:00
Irmen de Jong 08ac459a41 breaking change: sys.set_irq() and sys.set_rasterirq() no longer have useKernal parameter! The irq handler routine must return a boolean instead in the A register.
When it returns true it means run the system IRQ handler afterwards. When it returns false, the system handler is NOT ran afterwards.
2023-11-21 23:22:53 +01:00
Irmen de Jong a83e9d9a0a added sys.save_prog8_internals() and restore_prog8_internals() 2023-11-21 22:00:43 +01:00
Irmen de Jong 62d3f01948 fix name check in inline asm
this no longer removes a subroutine that is otherwise only called from inlined asm.
2023-11-21 01:26:50 +01:00
Irmen de Jong af5ca2d0b8 vm: treat floats as 64 bits doubles. 0.0 printed as "0". 2023-11-21 00:57:56 +01:00
Irmen de Jong ab4bcdf12d emudbg no longer clobbers r1 2023-11-20 00:20:48 +01:00
Irmen de Jong a6756d2cea removed diskio.set_drive(), just set the diskio.drivenumber variable directly
there already wasn't a getter
2023-11-19 22:15:56 +01:00
Irmen de Jong f81061dd42 error msg and comments 2023-11-18 01:03:34 +01:00
Irmen de Jong 8e2c304b3c txt.waitkey() now returns the key that was pressed 2023-11-17 20:31:19 +01:00
Irmen de Jong f21adaa3ef fix compiler error caused by removal of string symbol in txt.print() optimization 2023-11-17 19:51:48 +01:00
Irmen de Jong 2637939e62 cx16.vaddr_clone now leaves vera CTRL selected port intact 2023-11-17 19:22:23 +01:00
Irmen de Jong faf05582f8 improved cx16 emudbg library 2023-11-17 15:07:21 +01:00
Irmen de Jong 161c02ced3 message 2023-11-17 00:37:12 +01:00
Irmen de Jong ff8de8e42d removing redundant compares 2023-11-16 22:56:19 +01:00
Irmen de Jong 09d506194f note 2023-11-15 22:27:16 +01:00
Irmen de Jong 42db3085df improve the way %option merge works, you can now merge your own code with library code for instance. 2023-11-14 23:04:13 +01:00
Irmen de Jong ad14c88fde give error when using %option merge in module scope 2023-11-14 21:53:50 +01:00
Irmen de Jong 0c9daf6eaf fix compiler crash on ptrvar[n+1] = ptrvar[2] 2023-11-14 21:46:11 +01:00
Irmen de Jong 86c6530e46 palette: more accurate color conversion from 8 to 4 bits channels
set_rgb8(), color8to4(), channel8to4()
2023-11-14 20:40:48 +01:00
Irmen de Jong 159f80d629 next version 2023-11-14 19:06:47 +01:00
149 changed files with 3692 additions and 1418 deletions

No files matched your search

+24 -17
View File
@@ -1,19 +1,26 @@
<component name="libraryTable">
<library name="KotlinJavaRuntime">
<CLASSES>
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib.jar!/" />
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-reflect.jar!/" />
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-test.jar!/" />
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-jdk7.jar!/" />
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-jdk8.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-sources.jar!/" />
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-reflect-sources.jar!/" />
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-test-sources.jar!/" />
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-jdk7-sources.jar!/" />
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-jdk8-sources.jar!/" />
</SOURCES>
</library>
<library name="KotlinJavaRuntime" type="repository">
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.0" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.9.0/kotlin-stdlib-jdk8-1.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.0/kotlin-stdlib-1.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.9.0/kotlin-stdlib-common-1.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.9.0/kotlin-stdlib-jdk7-1.9.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.9.0/kotlin-stdlib-jdk8-1.9.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.0/kotlin-stdlib-1.9.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.9.0/kotlin-stdlib-common-1.9.0-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/1.9.0/kotlin-stdlib-jdk7-1.9.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.9.0/kotlin-stdlib-jdk8-1.9.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.0/kotlin-stdlib-1.9.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.9.0/kotlin-stdlib-common-1.9.0-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/1.9.0/kotlin-stdlib-jdk7-1.9.0-sources.jar!/" />
</SOURCES>
</library>
</component>
+5 -5
View File
@@ -1,19 +1,19 @@
<component name="libraryTable">
<library name="io.kotest.assertions.core.jvm" type="repository">
<properties maven-id="io.kotest:kotest-assertions-core-jvm:5.7.2" />
<properties maven-id="io.kotest:kotest-assertions-core-jvm:5.8.0" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.7.2/kotest-assertions-core-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.8.0/kotest-assertions-core-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.8.10/kotlin-stdlib-jdk8-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.8.10/kotlin-stdlib-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.8.10/kotlin-stdlib-jdk7-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.7.2/kotest-assertions-shared-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.8.0/kotest-assertions-shared-jvm-5.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/kotlin/kotlin-stdlib-common/1.8.10/kotlin-stdlib-common-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.7.0/kotlinx-coroutines-jdk8-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.8.10/kotlin-reflect-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.7.2/kotest-common-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.7.2/kotest-assertions-api-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.8.0/kotest-common-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.8.0/kotest-assertions-api-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.7.0/kotlinx-coroutines-core-jvm-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/23.0.0/annotations-23.0.0.jar!/" />
</CLASSES>
+11 -11
View File
@@ -1,14 +1,14 @@
<component name="libraryTable">
<library name="io.kotest.runner.junit5.jvm" type="repository">
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:5.7.2" />
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:5.8.0" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/5.7.2/kotest-runner-junit5-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.7.2/kotest-framework-api-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.7.2/kotest-assertions-shared-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/5.8.0/kotest-runner-junit5-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.8.0/kotest-framework-api-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.8.0/kotest-assertions-shared-jvm-5.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/jetbrains/kotlinx/kotlinx-coroutines-test-jvm/1.7.0/kotlinx-coroutines-test-jvm-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.7.2/kotest-common-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/5.7.2/kotest-framework-engine-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.8.0/kotest-common-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/5.8.0/kotest-framework-engine-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.162/classgraph-4.8.162.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!/" />
@@ -18,12 +18,12 @@
<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.7.2/kotest-framework-discovery-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.7.2/kotest-assertions-core-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-discovery-jvm/5.8.0/kotest-framework-discovery-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.8.0/kotest-assertions-core-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.7.0/kotlinx-coroutines-jdk8-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.7.2/kotest-assertions-api-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/5.7.2/kotest-extensions-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/5.7.2/kotest-framework-concurrency-jvm-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.8.0/kotest-assertions-api-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/5.8.0/kotest-extensions-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/5.8.0/kotest-framework-concurrency-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.7.0/kotlinx-coroutines-core-jvm-1.7.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!/" />
@@ -120,8 +120,10 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
"max__uword" to FSignature(true, listOf(FParam("val1", arrayOf(DataType.UWORD)), FParam("val2", arrayOf(DataType.UWORD))), DataType.UWORD),
"peek" to FSignature(true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UBYTE),
"peekw" to FSignature(true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UWORD),
"peekf" to FSignature(true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.FLOAT),
"poke" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), null),
"pokew" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UWORD))), null),
"pokef" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.FLOAT))), null),
"pokemon" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), DataType.UBYTE),
"pop" to FSignature(false, listOf(FParam("target", ByteDatatypes)), null),
"popw" to FSignature(false, listOf(FParam("target", WordDatatypes)), null),
+20 -17
View File
@@ -1,12 +1,13 @@
package prog8.code.core
enum class DataType {
UBYTE, // pass by value
BYTE, // pass by value
UWORD, // pass by value
WORD, // pass by value
FLOAT, // pass by value
BOOL, // pass by value
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 a 8 bit byte
STR, // pass by reference
ARRAY_UB, // pass by reference
ARRAY_B, // pass by reference
@@ -23,11 +24,12 @@ enum class DataType {
*/
infix fun isAssignableTo(targetType: DataType) =
when(this) {
BOOL -> targetType.oneOf(BOOL, BYTE, UBYTE, WORD, UWORD, FLOAT)
UBYTE -> targetType.oneOf(UBYTE, WORD, UWORD, FLOAT, BOOL)
BYTE -> targetType.oneOf(BYTE, WORD, FLOAT)
UWORD -> targetType.oneOf(UWORD, FLOAT)
WORD -> targetType.oneOf(WORD, FLOAT)
BOOL -> targetType.oneOf(BOOL, BYTE, UBYTE, WORD, UWORD, LONG, FLOAT)
UBYTE -> targetType.oneOf(UBYTE, WORD, UWORD, LONG, FLOAT, BOOL)
BYTE -> targetType.oneOf(BYTE, WORD, LONG, FLOAT)
UWORD -> targetType.oneOf(UWORD, LONG, FLOAT)
WORD -> targetType.oneOf(WORD, LONG, FLOAT)
LONG -> targetType.oneOf(LONG, FLOAT)
FLOAT -> targetType.oneOf(FLOAT)
STR -> targetType.oneOf(STR, UWORD)
in ArrayDatatypes -> targetType == this
@@ -41,7 +43,8 @@ enum class DataType {
this == other -> false
this in ByteDatatypes -> false
this in WordDatatypes -> other in ByteDatatypes
this== STR && other== UWORD || this== UWORD && other== STR -> false
this == LONG -> other in ByteDatatypes+WordDatatypes
this == STR && other == UWORD || this == UWORD && other == STR -> false
else -> true
}
@@ -123,11 +126,11 @@ enum class BranchCondition {
val ByteDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.BOOL)
val WordDatatypes = arrayOf(DataType.UWORD, DataType.WORD)
val IntegerDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.BOOL)
val IntegerDatatypesNoBool = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD)
val NumericDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT, DataType.BOOL)
val NumericDatatypesNoBool = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT)
val SignedDatatypes = arrayOf(DataType.BYTE, DataType.WORD, DataType.FLOAT)
val IntegerDatatypesNoBool = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.LONG)
val IntegerDatatypes = IntegerDatatypesNoBool + DataType.BOOL
val NumericDatatypesNoBool = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.LONG, DataType.FLOAT)
val NumericDatatypes = NumericDatatypesNoBool + DataType.BOOL
val SignedDatatypes = arrayOf(DataType.BYTE, DataType.WORD, DataType.LONG, DataType.FLOAT)
val ArrayDatatypes = arrayOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_UW_SPLIT, DataType.ARRAY_W, DataType.ARRAY_W_SPLIT, DataType.ARRAY_F, DataType.ARRAY_BOOL)
val StringlyDatatypes = arrayOf(DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B, DataType.UWORD)
val SplitWordArrayTypes = arrayOf(DataType.ARRAY_UW_SPLIT, DataType.ARRAY_W_SPLIT)
@@ -3,8 +3,6 @@ package prog8.code.core
interface ICompilationTarget: IStringEncoding, IMemSizer {
val name: String
val machine: IMachineDefinition
val supportedEncodings: Set<Encoding>
val defaultEncoding: Encoding
override fun encodeString(str: String, encoding: Encoding): List<UByte>
override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String
@@ -9,6 +9,8 @@ enum class Encoding(val prefix: String) {
}
interface IStringEncoding {
val defaultEncoding: Encoding
fun encodeString(str: String, encoding: Encoding): List<UByte>
fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String
}
+3 -2
View File
@@ -3,6 +3,7 @@ package prog8.code.core
import java.io.File
import java.io.IOException
import java.nio.file.Path
import java.text.Normalizer
import kotlin.io.path.Path
import kotlin.io.path.readText
@@ -94,7 +95,7 @@ sealed class SourceCode {
val normalized = path.normalize()
origin = relative(normalized).toString()
try {
text = normalized.readText()
text = Normalizer.normalize(normalized.readText(), Normalizer.Form.NFC)
name = normalized.toFile().nameWithoutExtension
} catch (nfx: java.nio.file.NoSuchFileException) {
throw NoSuchFileException(normalized.toFile()).also { it.initCause(nfx) }
@@ -126,7 +127,7 @@ sealed class SourceCode {
)
}
val stream = object {}.javaClass.getResourceAsStream(normalized)
text = stream!!.reader().use { it.readText() }
text = stream!!.reader().use { Normalizer.normalize(it.readText(), Normalizer.Form.NFC) }
name = Path(pathString).toFile().nameWithoutExtension
}
}
@@ -7,7 +7,6 @@ import prog8.code.target.atari.AtariMachineDefinition
class AtariTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer {
override val name = NAME
override val machine = AtariMachineDefinition()
override val supportedEncodings = setOf(Encoding.ATASCII)
override val defaultEncoding = Encoding.ATASCII
companion object {
@@ -11,7 +11,6 @@ import prog8.code.target.cbm.CbmMemorySizer
class C128Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
override val name = NAME
override val machine = C128MachineDefinition()
override val supportedEncodings = setOf(Encoding.PETSCII, Encoding.SCREENCODES)
override val defaultEncoding = Encoding.PETSCII
companion object {
@@ -11,7 +11,6 @@ import prog8.code.target.cbm.CbmMemorySizer
class C64Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
override val name = NAME
override val machine = C64MachineDefinition()
override val supportedEncodings = setOf(Encoding.PETSCII, Encoding.SCREENCODES)
override val defaultEncoding = Encoding.PETSCII
companion object {
@@ -11,7 +11,6 @@ import prog8.code.target.cx16.CX16MachineDefinition
class Cx16Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
override val name = NAME
override val machine = CX16MachineDefinition()
override val supportedEncodings = setOf(Encoding.PETSCII, Encoding.SCREENCODES, Encoding.ISO)
override val defaultEncoding = Encoding.PETSCII
companion object {
@@ -10,6 +10,8 @@ import prog8.code.target.cbm.PetsciiEncoding
object Encoder: IStringEncoding {
override val defaultEncoding: Encoding = Encoding.ISO
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
val coded = when(encoding) {
Encoding.PETSCII -> PetsciiEncoding.encodePetscii(str, true)
@@ -11,7 +11,6 @@ import prog8.code.target.pet.PETMachineDefinition
class PETTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
override val name = NAME
override val machine = PETMachineDefinition()
override val supportedEncodings = setOf(Encoding.PETSCII, Encoding.SCREENCODES)
override val defaultEncoding = Encoding.PETSCII
companion object {
@@ -6,7 +6,6 @@ import prog8.code.target.virtual.VirtualMachineDefinition
class VMTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer {
override val name = NAME
override val machine = VirtualMachineDefinition()
override val supportedEncodings = setOf(Encoding.ISO)
override val defaultEncoding = Encoding.ISO
companion object {
@@ -12,7 +12,7 @@ class VirtualMachineDefinition: IMachineDefinition {
override val FLOAT_MAX_POSITIVE = Float.MAX_VALUE.toDouble()
override val FLOAT_MAX_NEGATIVE = -Float.MAX_VALUE.toDouble()
override val FLOAT_MEM_SIZE = 4 // 32-bits floating point
override val FLOAT_MEM_SIZE = 8 // 64-bits double
override val PROGRAM_LOAD_ADDRESS = 0u // not actually used
override val BSSHIGHRAM_START = 0u // not actually used
@@ -22,8 +22,8 @@ class VirtualMachineDefinition: IMachineDefinition {
override fun getFloatAsmBytes(num: Number): String {
// little endian binary representation
val bits = num.toFloat().toBits().toUInt()
val hexStr = bits.toString(16).padStart(8, '0')
val bits = num.toDouble().toBits().toULong()
val hexStr = bits.toString(16).padStart(16, '0')
val parts = hexStr.chunked(2).map { "\$" + it }
return parts.joinToString(", ")
}
+1 -1
View File
@@ -30,7 +30,7 @@ dependencies {
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.7.2'
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.8.0'
}
sourceSets {
@@ -3107,6 +3107,17 @@ $repeatLabel""")
return "prog8_label_asm_${generatedLabelSequenceNumber}_$postfix"
}
fun assignConstFloatToPointerAY(number: PtNumber) {
val floatConst = allocator.getFloatAsmConst(number.number)
out("""
pha
lda #<$floatConst
sta P8ZP_SCRATCH_W1
lda #>$floatConst
sta P8ZP_SCRATCH_W1+1
pla
jsr floats.copy_float""")
}
}
/**
@@ -47,8 +47,10 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
"reverse" -> funcReverse(fcall)
"memory" -> funcMemory(fcall, discardResult, resultRegister)
"peekw" -> funcPeekW(fcall, resultRegister)
"peekf" -> funcPeekF(fcall, resultRegister)
"peek" -> throw AssemblyError("peek() should have been replaced by @()")
"pokew" -> funcPokeW(fcall)
"pokef" -> funcPokeF(fcall)
"pokemon" -> {
val memread = PtMemoryByte(fcall.position)
memread.add(fcall.args[0])
@@ -81,26 +83,32 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
"rsave" -> funcRsave()
"rrestore" -> funcRrestore()
"cmp" -> funcCmp(fcall)
"callfar" -> funcCallFar(fcall)
"prog8_lib_stringcompare" -> funcStringCompare(fcall)
"prog8_lib_square_byte" -> funcSquare(fcall, DataType.UBYTE)
"prog8_lib_square_word" -> funcSquare(fcall, DataType.UWORD)
"callfar" -> funcCallFar(fcall, resultRegister)
"prog8_lib_stringcompare" -> funcStringCompare(fcall, resultRegister)
"prog8_lib_square_byte" -> funcSquare(fcall, DataType.UBYTE, resultRegister)
"prog8_lib_square_word" -> funcSquare(fcall, DataType.UWORD, resultRegister)
else -> throw AssemblyError("missing asmgen for builtin func ${fcall.name}")
}
return BuiltinFunctions.getValue(fcall.name).returnType
}
private fun funcSquare(fcall: PtBuiltinFunctionCall, resultType: DataType) {
private fun funcSquare(fcall: PtBuiltinFunctionCall, resultType: DataType, resultRegister: RegisterOrPair?) {
// square of word value is faster with dedicated routine, square of byte just use the regular multiplication routine.
when (resultType) {
DataType.UBYTE -> {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A)
asmgen.out(" tay | jsr math.multiply_bytes")
if(resultRegister!=null) {
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister, false, fcall.position, null, asmgen), CpuRegister.A, false, false)
}
}
DataType.UWORD -> {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
asmgen.out(" jsr math.square")
if(resultRegister!=null) {
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister, false, fcall.position, null, asmgen), RegisterOrPair.AY)
}
}
else -> {
throw AssemblyError("optimized square only for integer types")
@@ -140,9 +148,12 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
sty $remainderVar+1""")
}
private fun funcStringCompare(fcall: PtBuiltinFunctionCall) {
private fun funcStringCompare(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
asmgen.assignWordOperandsToAYAndVar(fcall.args[0], fcall.args[1], "P8ZP_SCRATCH_W2")
asmgen.out(" jsr prog8_lib.strcmp_mem")
if(resultRegister!=null) {
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister, false, fcall.position, null, asmgen), CpuRegister.A, false, false)
}
}
private fun funcRsave() {
@@ -182,7 +193,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
plp""")
}
private fun funcCallFar(fcall: PtBuiltinFunctionCall) {
private fun funcCallFar(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
if(asmgen.options.compTarget.name != "cx16")
throw AssemblyError("callfar only works on cx16 target at this time")
@@ -196,6 +207,9 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
+ .word 0
+ .byte 0""")
// note that by convention the values in A+Y registers are now the return value of the call.
if(resultRegister!=null) {
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister, false, fcall.position, null, asmgen), RegisterOrPair.AY)
}
}
private fun funcCmp(fcall: PtBuiltinFunctionCall) {
@@ -752,6 +766,41 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
}
private fun funcPokeF(fcall: PtBuiltinFunctionCall) {
when(val number = fcall.args[1]) {
is PtIdentifier -> {
val varName = asmgen.asmVariableName(number)
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
asmgen.out("""
pha
lda #<$varName
sta P8ZP_SCRATCH_W1
lda #>$varName
sta P8ZP_SCRATCH_W1+1
pla
jsr floats.copy_float""")
}
is PtNumber -> {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
asmgen.assignConstFloatToPointerAY(number)
}
else -> {
val tempvar = asmgen.getTempVarName(DataType.FLOAT)
asmgen.assignExpressionTo(fcall.args[1],
AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.FLOAT, fcall.definingISub(), fcall.position, tempvar, null, null, null, null))
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
asmgen.out("""
pha
lda #<$tempvar
sta P8ZP_SCRATCH_W1
lda #>$tempvar
sta P8ZP_SCRATCH_W1+1
pla
jsr floats.copy_float""")
}
}
}
private fun funcPokeW(fcall: PtBuiltinFunctionCall) {
when(val addrExpr = fcall.args[0]) {
is PtNumber -> {
@@ -808,6 +857,15 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
asmgen.out(" jsr prog8_lib.func_pokew")
}
private fun funcPeekF(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
asmgen.out(" jsr floats.MOVFM")
if(resultRegister!=null) {
assignAsmGen.assignFAC1float(
AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.FLOAT, fcall.definingISub(), fcall.position, null, null, null, resultRegister, null))
}
}
private fun funcPeekW(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?) {
fun fallback() {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
@@ -2612,19 +2612,13 @@ internal class AssignmentAsmGen(private val program: PtProgram,
""")
}
TargetStorageKind.ARRAY -> {
asmgen.assignExpressionToRegister(target.array!!.index, RegisterOrPair.A, false)
asmgen.out("""
lda #<${target.asmVarname}
ldy #<${target.asmVarname}
sty P8ZP_SCRATCH_W1
ldy #>${target.asmVarname}
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1""")
val constIndex = target.array!!.index.asConstInteger()
if(constIndex!=null) {
asmgen.out(" lda #$constIndex")
} else {
val asmvarname = asmgen.asmVariableName(target.array.index as PtIdentifier)
asmgen.out(" lda $asmvarname")
}
asmgen.out(" jsr floats.set_array_float_from_fac1")
sty P8ZP_SCRATCH_W1+1
jsr floats.set_array_float_from_fac1""")
}
TargetStorageKind.MEMORY -> throw AssemblyError("can't assign float to mem byte")
TargetStorageKind.REGISTER -> {
@@ -2647,21 +2641,19 @@ internal class AssignmentAsmGen(private val program: PtProgram,
jsr floats.copy_float""")
}
TargetStorageKind.ARRAY -> {
asmgen.saveRegisterStack(CpuRegister.A, false)
asmgen.saveRegisterStack(CpuRegister.Y, false)
asmgen.assignExpressionToRegister(target.array!!.index, RegisterOrPair.A, false)
asmgen.restoreRegisterStack(CpuRegister.Y, false)
asmgen.restoreRegisterStack(CpuRegister.A, false)
asmgen.out("""
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<${target.asmVarname}
ldy #>${target.asmVarname}
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1""")
val constIndex = target.array!!.index.asConstInteger()
if(constIndex!=null) {
asmgen.out(" lda #$constIndex")
} else {
val asmvarname = asmgen.asmVariableName(target.array.index as PtIdentifier)
asmgen.out(" lda $asmvarname")
}
asmgen.out(" jsr floats.set_array_float")
sty P8ZP_SCRATCH_W2+1
jsr floats.set_array_float""")
}
TargetStorageKind.MEMORY -> throw AssemblyError("can't assign float to mem byte")
TargetStorageKind.REGISTER -> {
@@ -2687,23 +2679,17 @@ internal class AssignmentAsmGen(private val program: PtProgram,
jsr floats.copy_float""")
}
TargetStorageKind.ARRAY -> {
asmgen.assignExpressionToRegister(target.array!!.index, RegisterOrPair.A, false)
asmgen.out("""
lda #<$sourceName
ldy #<$sourceName
sty P8ZP_SCRATCH_W1
ldy #>$sourceName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<${target.asmVarname}
ldy #<${target.asmVarname}
sty P8ZP_SCRATCH_W2
ldy #>${target.asmVarname}
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1""")
val constIndex = target.array!!.index.asConstInteger()
if(constIndex!=null) {
asmgen.out(" lda #$constIndex")
} else {
val asmvarname = asmgen.asmVariableName(target.array.index as PtIdentifier)
asmgen.out(" lda $asmvarname")
}
asmgen.out(" jsr floats.set_array_float")
sty P8ZP_SCRATCH_W2+1
jsr floats.set_array_float""")
}
TargetStorageKind.MEMORY -> throw AssemblyError("can't assign float to mem byte")
TargetStorageKind.REGISTER -> {
@@ -2966,9 +2952,21 @@ internal class AssignmentAsmGen(private val program: PtProgram,
storeRegisterAInMemoryAddress(target.memory!!)
}
TargetStorageKind.ARRAY -> {
if(assignAsWord)
TODO("assign register byte as word into Array not yet supported")
assignRegisterByteToByteArray(target, register)
if(assignAsWord) {
when(register) {
CpuRegister.A -> {}
CpuRegister.X -> asmgen.out(" txa")
CpuRegister.Y -> asmgen.out(" tya")
}
if(extendWord) {
asmgen.signExtendAYlsb(if(target.datatype in SignedDatatypes) DataType.BYTE else DataType.UBYTE)
} else {
asmgen.out(" ldy #0")
}
assignRegisterpairWord(target, RegisterOrPair.AY)
} else {
assignRegisterByteToByteArray(target, register)
}
}
TargetStorageKind.REGISTER -> {
when(register) {
@@ -3138,6 +3136,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
throw AssemblyError("cannot assign byte to split word array here ${target.position}")
if(assignsIndexedPointerVar(target)) {
// assign byte to a byte array via an uword pointervariable instead of a regular array variable
if (target.constArrayIndexValue!=null) {
when (register) {
CpuRegister.A -> {}
@@ -3162,17 +3161,33 @@ internal class AssignmentAsmGen(private val program: PtProgram,
CpuRegister.X -> asmgen.out(" txa")
CpuRegister.Y -> asmgen.out(" tya")
}
val indexVar = target.array.index as PtIdentifier
if(asmgen.isZpVar(target.origAstTarget!!.array!!.variable)) {
asmgen.out(" ldy ${asmgen.asmVariableName(indexVar)} | sta (${target.asmVarname}),y")
val indexVar = target.array.index as? PtIdentifier
if(indexVar!=null) {
if(asmgen.isZpVar(target.origAstTarget!!.array!!.variable)) {
asmgen.out(" ldy ${asmgen.asmVariableName(indexVar)} | sta (${target.asmVarname}),y")
} else {
asmgen.out("""
ldy ${target.asmVarname}
sty P8ZP_SCRATCH_W1
ldy ${target.asmVarname}+1
sty P8ZP_SCRATCH_W1+1
ldy ${asmgen.asmVariableName(indexVar)}
sta (P8ZP_SCRATCH_W1),y""")
}
} else {
asmgen.out("""
ldy ${target.asmVarname}
sty P8ZP_SCRATCH_W1
ldy ${target.asmVarname}+1
sty P8ZP_SCRATCH_W1+1
ldy ${asmgen.asmVariableName(indexVar)}
sta (P8ZP_SCRATCH_W1),y""")
asmgen.saveRegisterStack(register, false)
asmgen.assignExpressionToRegister(target.array.index, RegisterOrPair.Y, false)
asmgen.restoreRegisterStack(CpuRegister.A, false)
if(asmgen.isZpVar(target.origAstTarget!!.array!!.variable)) {
asmgen.out(" sta (${target.asmVarname}),y")
} else {
asmgen.out("""
ldx ${target.asmVarname}
stx P8ZP_SCRATCH_W1
ldx ${target.asmVarname}+1
stx P8ZP_SCRATCH_W1+1
sta (P8ZP_SCRATCH_W1),y""")
}
}
}
return
@@ -3624,37 +3639,13 @@ internal class AssignmentAsmGen(private val program: PtProgram,
""")
}
TargetStorageKind.ARRAY -> {
val constIndex = target.array!!.index.asConstInteger()
if (constIndex!=null) {
val indexValue = constIndex * program.memsizer.memorySize(DataType.FLOAT)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out("""
stz ${target.asmVarname}+$indexValue
stz ${target.asmVarname}+$indexValue+1
stz ${target.asmVarname}+$indexValue+2
stz ${target.asmVarname}+$indexValue+3
stz ${target.asmVarname}+$indexValue+4
""")
else
asmgen.out("""
lda #0
sta ${target.asmVarname}+$indexValue
sta ${target.asmVarname}+$indexValue+1
sta ${target.asmVarname}+$indexValue+2
sta ${target.asmVarname}+$indexValue+3
sta ${target.asmVarname}+$indexValue+4
""")
} else {
val asmvarname = asmgen.asmVariableName(target.array.index as PtIdentifier)
asmgen.out("""
lda #<${target.asmVarname}
sta P8ZP_SCRATCH_W1
lda #>${target.asmVarname}
sta P8ZP_SCRATCH_W1+1
lda $asmvarname
jsr floats.set_0_array_float
""")
}
asmgen.assignExpressionToRegister(target.array!!.index, RegisterOrPair.A, false)
asmgen.out("""
ldy #<${target.asmVarname}
sty P8ZP_SCRATCH_W1
ldy #>${target.asmVarname}
sty P8ZP_SCRATCH_W1+1
jsr floats.set_0_array_float""")
}
TargetStorageKind.MEMORY -> throw AssemblyError("can't assign float to memory byte")
TargetStorageKind.REGISTER -> {
@@ -3681,33 +3672,18 @@ internal class AssignmentAsmGen(private val program: PtProgram,
jsr floats.copy_float""")
}
TargetStorageKind.ARRAY -> {
val arrayVarName = target.asmVarname
val constIndex = target.array!!.index.asConstInteger()
if (constIndex!=null) {
val indexValue = constIndex * program.memsizer.memorySize(DataType.FLOAT)
asmgen.out("""
lda #<$constFloat
ldy #>$constFloat
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<($arrayVarName+$indexValue)
ldy #>($arrayVarName+$indexValue)
jsr floats.copy_float""")
} else {
val asmvarname = asmgen.asmVariableName(target.array.index as PtIdentifier)
asmgen.out("""
lda #<${constFloat}
sta P8ZP_SCRATCH_W1
lda #>${constFloat}
sta P8ZP_SCRATCH_W1+1
lda #<${arrayVarName}
sta P8ZP_SCRATCH_W2
lda #>${arrayVarName}
sta P8ZP_SCRATCH_W2+1
lda $asmvarname
jsr floats.set_array_float
""")
}
asmgen.assignExpressionToRegister(target.array!!.index, RegisterOrPair.A, false)
asmgen.out("""
ldy #<${constFloat}
sty P8ZP_SCRATCH_W1
ldy #>${constFloat}
sty P8ZP_SCRATCH_W1+1
ldy #<${target.asmVarname}
sty P8ZP_SCRATCH_W2
ldy #>${target.asmVarname}
sty P8ZP_SCRATCH_W2+1
jsr floats.set_array_float
""")
}
TargetStorageKind.MEMORY -> throw AssemblyError("can't assign float to memory byte")
TargetStorageKind.REGISTER -> {
@@ -1192,8 +1192,8 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
}
}
"&" -> asmgen.out(" lda $name | and #$value | sta $name")
"|" -> asmgen.out(" lda $name | ora #$value | sta $name")
"&" -> immediateAndInplace(name, value)
"|" -> immediateOrInplace(name, value)
"^" -> asmgen.out(" lda $name | eor #$value | sta $name")
"==" -> {
asmgen.out("""
@@ -1314,6 +1314,22 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
}
private fun immediateAndInplace(name: String, value: Int) {
if(asmgen.isTargetCpu(CpuType.CPU65c02)) {
asmgen.out(" lda #${value xor 255} | trb $name") // reset bit
} else {
asmgen.out(" lda $name | and #$value | sta $name")
}
}
private fun immediateOrInplace(name: String, value: Int) {
if(asmgen.isTargetCpu(CpuType.CPU65c02) && ((value and (value-1))==0)) {
asmgen.out(" lda #$value | tsb $name") // set bit
} else {
asmgen.out(" lda $name | ora #$value | sta $name")
}
}
private fun inplacemodificationWordWithMemread(name: String, dt: DataType, operator: String, memread: PtMemoryByte) {
when (operator) {
"+" -> {
@@ -1611,7 +1627,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
asmgen.out(" lda $name+1 | and #>$value | sta $name+1")
}
value < 0x0100 -> {
asmgen.out(" lda $name | and #$value | sta $name")
immediateAndInplace(name, value)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name+1")
else
@@ -1624,7 +1640,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
when {
value == 0 -> {}
value and 255 == 0 -> asmgen.out(" lda $name+1 | ora #>$value | sta $name+1")
value < 0x0100 -> asmgen.out(" lda $name | ora #$value | sta $name")
value < 0x0100 -> immediateOrInplace(name, value)
else -> asmgen.out(" lda $name | ora #<$value | sta $name | lda $name+1 | ora #>$value | sta $name+1")
}
}
@@ -2728,16 +2744,23 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
val constValueName = allocator.getFloatAsmConst(value)
when (operator) {
"+" -> {
if (value == 0.0)
return
asmgen.out("""
lda #<$name
ldy #>$name
jsr floats.MOVFM
lda #<$constValueName
ldy #>$constValueName
jsr floats.FADD
""")
when (value) {
0.0 -> return
0.5 -> asmgen.out("""
lda #<$name
ldy #>$name
jsr floats.MOVFM
jsr floats.FADDH
""")
else -> asmgen.out("""
lda #<$name
ldy #>$name
jsr floats.MOVFM
lda #<$constValueName
ldy #>$constValueName
jsr floats.FADD
""")
}
}
"-" -> {
if (value == 0.0)
@@ -2753,14 +2776,23 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
"*" -> {
// assume that code optimization is already done on the AST level for special cases such as 0, 1, 2...
asmgen.out("""
lda #<$name
ldy #>$name
jsr floats.MOVFM
lda #<$constValueName
ldy #>$constValueName
jsr floats.FMULT
""")
if(value==10.0) {
asmgen.out("""
lda #<$name
ldy #>$name
jsr floats.MOVFM
jsr floats.MUL10
""")
} else {
asmgen.out("""
lda #<$name
ldy #>$name
jsr floats.MOVFM
lda #<$constValueName
ldy #>$constValueName
jsr floats.FMULT
""")
}
}
"/" -> {
if (value == 0.0)
+2
View File
@@ -18,6 +18,8 @@ internal object DummyMemsizer : IMemSizer {
}
internal object DummyStringEncoder : IStringEncoding {
override val defaultEncoding: Encoding = Encoding.ISO
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
return emptyList()
}
+1 -1
View File
@@ -30,7 +30,7 @@ dependencies {
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.7.2'
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.8.0'
}
sourceSets {
@@ -29,10 +29,12 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
"msb" -> funcMsb(call)
"lsb" -> funcLsb(call)
"memory" -> funcMemory(call)
"peek" -> funcPeek(call)
"peekw" -> funcPeekW(call)
"poke" -> funcPoke(call)
"pokew" -> funcPokeW(call)
"peek" -> funcPeek(call, IRDataType.BYTE)
"peekw" -> funcPeek(call, IRDataType.WORD)
"peekf" -> funcPeek(call, IRDataType.FLOAT)
"poke" -> funcPoke(call, IRDataType.BYTE)
"pokew" -> funcPoke(call, IRDataType.WORD)
"pokef" -> funcPoke(call, IRDataType.FLOAT)
"pokemon" -> funcPokemon(call)
"mkword" -> funcMkword(call)
"clamp__byte", "clamp__ubyte", "clamp__word", "clamp__uword" -> funcClamp(call)
@@ -440,116 +442,93 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
return ExpressionCodeResult(result, type, leftTr.resultReg, -1)
}
private fun funcPokeW(call: PtBuiltinFunctionCall): ExpressionCodeResult {
private fun funcPoke(call: PtBuiltinFunctionCall, dt: IRDataType): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
if(codeGen.isZero(call.args[1])) {
if (call.args[0] is PtNumber) {
val address = (call.args[0] as PtNumber).number.toInt()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZM, IRDataType.WORD, address = address)
it += IRInstruction(Opcode.STOREZM, dt, address = address)
}
} else {
val tr = exprGen.translateExpression(call.args[0])
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZI, IRDataType.WORD, reg1 = tr.resultReg)
it += IRInstruction(Opcode.STOREZI, dt, reg1 = tr.resultReg)
}
}
} else {
if (call.args[0] is PtNumber) {
val address = (call.args[0] as PtNumber).number.toInt()
val tr = exprGen.translateExpression(call.args[1])
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREM, IRDataType.WORD, reg1 = tr.resultReg, address = address)
if(dt==IRDataType.FLOAT) {
addToResult(result, tr, -1, tr.resultFpReg)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREM, dt, fpReg1 = tr.resultFpReg, address = address)
}
} else {
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREM, dt, reg1 = tr.resultReg, address = address)
}
}
} else {
val addressTr = exprGen.translateExpression(call.args[0])
addToResult(result, addressTr, addressTr.resultReg, -1)
val valueTr = exprGen.translateExpression(call.args[1])
addToResult(result, valueTr, valueTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREI, IRDataType.WORD, reg1 = valueTr.resultReg, reg2 = addressTr.resultReg)
if(dt==IRDataType.FLOAT) {
addToResult(result, valueTr, -1, valueTr.resultFpReg)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREI, IRDataType.FLOAT, reg1 = addressTr.resultReg, fpReg1 = valueTr.resultFpReg)
}
} else {
addToResult(result, valueTr, valueTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREI, dt, reg1 = valueTr.resultReg, reg2 = addressTr.resultReg)
}
}
}
}
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
private fun funcPoke(call: PtBuiltinFunctionCall): ExpressionCodeResult {
private fun funcPeek(call: PtBuiltinFunctionCall, dt: IRDataType): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
if(codeGen.isZero(call.args[1])) {
if (call.args[0] is PtNumber) {
return if(dt==IRDataType.FLOAT) {
if(call.args[0] is PtNumber) {
val resultFpRegister = codeGen.registers.nextFreeFloat()
val address = (call.args[0] as PtNumber).number.toInt()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZM, IRDataType.BYTE, address = address)
it += IRInstruction(Opcode.LOADM, IRDataType.FLOAT, fpReg1 = resultFpRegister, address = address)
}
ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultFpRegister)
} else {
val tr = exprGen.translateExpression(call.args[0])
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
val resultFpReg = codeGen.registers.nextFreeFloat()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg1 = tr.resultReg)
it += IRInstruction(Opcode.LOADI, IRDataType.FLOAT, reg1 = tr.resultReg, fpReg1 = resultFpReg)
}
ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultFpReg)
}
} else {
if (call.args[0] is PtNumber) {
val resultRegister = codeGen.registers.nextFree()
val address = (call.args[0] as PtNumber).number.toInt()
val tr = exprGen.translateExpression(call.args[1])
addToResult(result, tr, tr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = tr.resultReg, address = address)
it += IRInstruction(Opcode.LOADM, dt, reg1 = resultRegister, address = address)
}
ExpressionCodeResult(result, dt, resultRegister, -1)
} else {
val addressTr = exprGen.translateExpression(call.args[0])
addToResult(result, addressTr, addressTr.resultReg, -1)
val valueTr = exprGen.translateExpression(call.args[1])
addToResult(result, valueTr, valueTr.resultReg, -1)
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.nextFree()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = valueTr.resultReg, reg2 = addressTr.resultReg)
it += IRInstruction(Opcode.LOADI, dt, reg1 = resultReg, reg2 = tr.resultReg)
}
ExpressionCodeResult(result, dt, resultReg, -1)
}
}
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
private fun funcPeekW(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
return if(call.args[0] is PtNumber) {
val resultRegister = codeGen.registers.nextFree()
val address = (call.args[0] as PtNumber).number.toInt()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, IRDataType.WORD, reg1 = resultRegister, address = address)
}
ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
} else {
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.nextFree()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = resultReg, reg2 = tr.resultReg)
}
ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
}
}
private fun funcPeek(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
return if(call.args[0] is PtNumber) {
val resultRegister = codeGen.registers.nextFree()
val address = (call.args[0] as PtNumber).number.toInt()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = resultRegister, address = address)
}
ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
} else {
val tr = exprGen.translateExpression(call.args.single())
addToResult(result, tr, tr.resultReg, -1)
val resultReg = codeGen.registers.nextFree()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1 = resultReg, reg2 = tr.resultReg)
}
ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
}
private fun funcPokemon(call: PtBuiltinFunctionCall): ExpressionCodeResult {
@@ -17,6 +17,8 @@ internal class ExpressionCodeResult(val chunks: IRCodeChunks, val dt: IRDataType
if(resultReg!=-1) require(resultFpReg==-1)
if(resultFpReg!=-1) require(resultReg==-1)
}
fun lastInstruction() = chunks.last().instructions.last()
}
internal class ExpressionGen(private val codeGen: IRCodeGen) {
@@ -31,7 +33,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val code = IRCodeChunk(null, null)
if(vmDt==IRDataType.FLOAT) {
val resultFpRegister = codeGen.registers.nextFreeFloat()
code += IRInstruction(Opcode.LOAD, vmDt, fpReg1 = resultFpRegister, immediateFp = expr.number.toFloat())
code += IRInstruction(Opcode.LOAD, vmDt, fpReg1 = resultFpRegister, immediateFp = expr.number)
ExpressionCodeResult(code, vmDt,-1, resultFpRegister)
}
else {
@@ -711,7 +713,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
if(constFactorRight!=null && constFactorRight.type!=DataType.FLOAT) {
val tr = translateExpression(binExpr.left)
addToResult(result, tr, -1, tr.resultFpReg)
val factor = constFactorRight.number.toFloat()
val factor = constFactorRight.number
result += codeGen.divideByConstFloat(tr.resultFpReg, factor)
return ExpressionCodeResult(result, vmDt, -1, tr.resultFpReg)
} else {
@@ -767,13 +769,13 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
return if(constFactorLeft!=null) {
val tr = translateExpression(binExpr.right)
addToResult(result, tr, -1, tr.resultFpReg)
val factor = constFactorLeft.number.toFloat()
val factor = constFactorLeft.number
result += codeGen.multiplyByConstFloat(tr.resultFpReg, factor)
ExpressionCodeResult(result, vmDt, -1, tr.resultFpReg)
} else if(constFactorRight!=null) {
val tr = translateExpression(binExpr.left)
addToResult(result, tr, -1, tr.resultFpReg)
val factor = constFactorRight.number.toFloat()
val factor = constFactorRight.number
result += codeGen.multiplyByConstFloat(tr.resultFpReg, factor)
ExpressionCodeResult(result, vmDt, -1, tr.resultFpReg)
} else {
@@ -821,7 +823,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
return if(binExpr.right is PtNumber) {
val tr = translateExpression(binExpr.left)
addToResult(result, tr, -1, tr.resultFpReg)
addInstr(result, IRInstruction(Opcode.SUB, vmDt, fpReg1 = tr.resultFpReg, immediateFp = (binExpr.right as PtNumber).number.toFloat()), null)
addInstr(result, IRInstruction(Opcode.SUB, vmDt, fpReg1 = tr.resultFpReg, immediateFp = (binExpr.right as PtNumber).number), null)
ExpressionCodeResult(result, vmDt, -1, tr.resultFpReg)
} else {
val leftTr = translateExpression(binExpr.left)
@@ -876,7 +878,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
return if(binExpr.right is PtNumber) {
val tr = translateExpression(binExpr.left)
addToResult(result, tr, -1, tr.resultFpReg)
addInstr(result, IRInstruction(Opcode.ADD, vmDt, fpReg1 = tr.resultFpReg, immediateFp = (binExpr.right as PtNumber).number.toFloat()), null)
addInstr(result, IRInstruction(Opcode.ADD, vmDt, fpReg1 = tr.resultFpReg, immediateFp = (binExpr.right as PtNumber).number), null)
ExpressionCodeResult(result, vmDt, -1, tr.resultFpReg)
} else {
val leftTr = translateExpression(binExpr.left)
@@ -948,7 +950,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val constFactorRight = operand as? PtNumber
if(vmDt==IRDataType.FLOAT) {
if(constFactorRight!=null && constFactorRight.type!=DataType.FLOAT) {
val factor = constFactorRight.number.toFloat()
val factor = constFactorRight.number
result += codeGen.divideByConstFloatInplace(knownAddress, symbol, factor)
} else {
val tr = translateExpression(operand)
@@ -997,7 +999,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val constFactorRight = operand as? PtNumber
if(vmDt==IRDataType.FLOAT) {
if(constFactorRight!=null) {
val factor = constFactorRight.number.toFloat()
val factor = constFactorRight.number
result += codeGen.multiplyByConstFloatInplace(knownAddress, symbol, factor)
} else {
val tr = translateExpression(operand)
@@ -1322,7 +1324,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
// in-place modify a memory location
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, IRDataType.FLOAT, fpReg1 = valueReg, address = knownAddress)
it += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = numberReg, immediateFp = operand.number.toFloat())
it += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = numberReg, immediateFp = operand.number)
it += IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=cmpReg, fpReg1 = valueReg, fpReg2 = numberReg)
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=zeroReg, immediate = 0)
it += IRInstruction(compareAndSetOpcode, IRDataType.BYTE, reg1=cmpResultReg, reg2=cmpReg, reg3=zeroReg)
@@ -1333,7 +1335,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
// in-place modify a symbol (variable)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, IRDataType.FLOAT, fpReg1 = valueReg, labelSymbol = symbol)
it += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = numberReg, immediateFp = operand.number.toFloat())
it += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = numberReg, immediateFp = operand.number)
it += IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=cmpReg, fpReg1 = valueReg, fpReg2 = numberReg)
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=zeroReg, immediate = 0)
it += IRInstruction(compareAndSetOpcode, IRDataType.BYTE, reg1=cmpResultReg, reg2=cmpReg, reg3=zeroReg)
@@ -283,15 +283,10 @@ class IRCodeGen(
else -> TODO("missing codegen for $node")
}
chunks.forEach { chunk ->
require(chunk.isNotEmpty() || chunk.label != null) {
"chunk should have instructions and/or a label"
}
}
val nonEmptyChunks = chunks.filter { it.isNotEmpty() || it.label != null }
nonEmptyChunks.filterIsInstance<IRCodeChunk>().firstOrNull()?.appendSrcPosition(node.position)
chunks.filterIsInstance<IRCodeChunk>().firstOrNull()?.appendSrcPosition(node.position)
return chunks
return nonEmptyChunks
}
private fun readBinaryData(node: PtIncludeBinary): Collection<UByte> {
@@ -470,7 +465,6 @@ class IRCodeGen(
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = indexReg, immediate = 0), null)
result += IRCodeChunk(loopLabel, null).also {
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1 = tmpReg, reg2 = indexReg, labelSymbol = iterable.name)
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = tmpReg, immediate = 0)
it += IRInstruction(Opcode.BSTEQ, labelSymbol = endLabel)
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = tmpReg, labelSymbol = loopvarSymbol)
}
@@ -499,7 +493,10 @@ class IRCodeGen(
result += translateNode(forLoop.statements)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=indexReg)
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=indexReg, immediate = if(iterableLength==256) 0 else iterableLength)
if(iterableLength!=256) {
// for length 256, the compare is actually against 0, which doesn't require a separate CMP instruction
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=indexReg, immediate = iterableLength)
}
it += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
}
}
@@ -516,7 +513,10 @@ class IRCodeGen(
result += translateNode(forLoop.statements)
result += addConstReg(IRDataType.BYTE, indexReg, elementSize)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=indexReg, immediate = if(lengthBytes==256) 0 else lengthBytes)
if(lengthBytes!=256) {
// for length 256, the compare is actually against 0, which doesn't require a separate CMP instruction
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=indexReg, immediate = lengthBytes)
}
it += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
}
}
@@ -705,23 +705,23 @@ class IRCodeGen(
return code
}
internal fun multiplyByConstFloat(fpReg: Int, factor: Float): IRCodeChunk {
internal fun multiplyByConstFloat(fpReg: Int, factor: Double): IRCodeChunk {
val code = IRCodeChunk(null, null)
if(factor==1f)
if(factor==1.0)
return code
code += if(factor==0f) {
IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = fpReg, immediateFp = 0f)
code += if(factor==0.0) {
IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = fpReg, immediateFp = 0.0)
} else {
IRInstruction(Opcode.MUL, IRDataType.FLOAT, fpReg1 = fpReg, immediateFp = factor)
}
return code
}
internal fun multiplyByConstFloatInplace(knownAddress: Int?, symbol: String?, factor: Float): IRCodeChunk {
internal fun multiplyByConstFloatInplace(knownAddress: Int?, symbol: String?, factor: Double): IRCodeChunk {
val code = IRCodeChunk(null, null)
if(factor==1f)
if(factor==1.0)
return code
if(factor==0f) {
if(factor==0.0) {
code += if(knownAddress!=null)
IRInstruction(Opcode.STOREZM, IRDataType.FLOAT, address = knownAddress)
else
@@ -802,25 +802,25 @@ class IRCodeGen(
return code
}
internal fun divideByConstFloat(fpReg: Int, factor: Float): IRCodeChunk {
internal fun divideByConstFloat(fpReg: Int, factor: Double): IRCodeChunk {
val code = IRCodeChunk(null, null)
if(factor==1f)
if(factor==1.0)
return code
code += if(factor==0f) {
IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = fpReg, immediateFp = Float.MAX_VALUE)
code += if(factor==0.0) {
IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = fpReg, immediateFp = Double.MAX_VALUE)
} else {
IRInstruction(Opcode.DIVS, IRDataType.FLOAT, fpReg1 = fpReg, immediateFp = factor)
}
return code
}
internal fun divideByConstFloatInplace(knownAddress: Int?, symbol: String?, factor: Float): IRCodeChunk {
internal fun divideByConstFloatInplace(knownAddress: Int?, symbol: String?, factor: Double): IRCodeChunk {
val code = IRCodeChunk(null, null)
if(factor==1f)
if(factor==1.0)
return code
if(factor==0f) {
if(factor==0.0) {
val maxvalueReg = registers.nextFreeFloat()
code += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = maxvalueReg, immediateFp = Float.MAX_VALUE)
code += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = maxvalueReg, immediateFp = Double.MAX_VALUE)
code += if(knownAddress!=null)
IRInstruction(Opcode.STOREM, IRDataType.FLOAT, fpReg1 = maxvalueReg, address = knownAddress)
else
@@ -976,7 +976,6 @@ class IRCodeGen(
fpReg2 = rightTr.resultFpReg
)
when(condition.operator) {
// TODO: the converted list of operators
"==" -> {
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = compResultReg, immediate = 0)
it += branchInstr(goto, Opcode.BSTEQ)
@@ -986,7 +985,6 @@ class IRCodeGen(
it += branchInstr(goto, Opcode.BSTNE)
}
else -> {
// TODO: the old list of operators, still to be converted
val gotoOpcode = when (condition.operator) {
"<" -> Opcode.BLTS
">" -> Opcode.BGTS
@@ -1016,7 +1014,6 @@ class IRCodeGen(
}
}
// TODO use this everywhere
private fun branchInstr(goto: PtJump, branchOpcode: Opcode) = if (goto.address != null)
IRInstruction(branchOpcode, address = goto.address?.toInt())
else if (goto.generatedLabel != null)
@@ -1034,18 +1031,19 @@ class IRCodeGen(
val condition = ifElse.condition as PtBinaryExpression
val leftTr = expressionEval.translateExpression(condition.left)
addToResult(result, leftTr, leftTr.resultReg, -1)
val requireCompareZero = leftTr.lastInstruction().opcode !in OpcodesThatSetStatusbits
when(condition.operator) {
// TODO: converted list of operators
"==" -> {
addInstr(result, IRInstruction(Opcode.CMPI, irDtLeft, reg1 = leftTr.resultReg, immediate = 0), null)
if(requireCompareZero)
addInstr(result, IRInstruction(Opcode.CMPI, irDtLeft, reg1 = leftTr.resultReg, immediate = 0), null)
addInstr(result, branchInstr(goto, Opcode.BSTEQ), null)
}
"!=" -> {
addInstr(result, IRInstruction(Opcode.CMPI, irDtLeft, reg1 = leftTr.resultReg, immediate = 0), null)
if(requireCompareZero)
addInstr(result, IRInstruction(Opcode.CMPI, irDtLeft, reg1 = leftTr.resultReg, immediate = 0), null)
addInstr(result, branchInstr(goto, Opcode.BSTNE), null)
}
else -> {
// TODO: to-be converted operators
val opcode = when (condition.operator) {
"<" -> if (signed) Opcode.BLTS else Opcode.BLT
">" -> if (signed) Opcode.BGTS else Opcode.BGT
@@ -1072,12 +1070,13 @@ class IRCodeGen(
) {
val condition = ifElse.condition as? PtBinaryExpression
if(condition==null) {
val tr = expressionEval.translateExpression(ifElse.condition)
result += tr.chunks
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, irDtLeft, reg1 = tr.resultReg, immediate = 0)
it += branchInstr(goto, Opcode.BSTNE)
}
throw AssemblyError("expected condition")
// val tr = expressionEval.translateExpression(ifElse.condition)
// result += tr.chunks
// result += IRCodeChunk(null, null).also {
// it += IRInstruction(Opcode.CMPI, irDtLeft, reg1 = tr.resultReg, immediate = 0) // was redundant CMP most likely
// it += branchInstr(goto, Opcode.BSTNE)
// }
} else {
val leftTr = expressionEval.translateExpression(condition.left)
addToResult(result, leftTr, leftTr.resultReg, -1)
@@ -1085,7 +1084,6 @@ class IRCodeGen(
if(number!=null) {
val firstReg = leftTr.resultReg
when(condition.operator) {
// TODO: the converted operators
"==" -> {
addInstr(result, IRInstruction(Opcode.CMPI, irDtLeft, reg1 = firstReg, immediate = number), null)
addInstr(result, branchInstr(goto, Opcode.BSTEQ), null)
@@ -1095,7 +1093,6 @@ class IRCodeGen(
addInstr(result, branchInstr(goto, Opcode.BSTNE), null)
}
else -> {
// TODO: to-be converted operators
val opcode = when (condition.operator) {
"<" -> if(signed) Opcode.BLTS else Opcode.BLT
">" -> if(signed) Opcode.BGTS else Opcode.BGT
@@ -1187,7 +1184,7 @@ class IRCodeGen(
addToResult(result, leftTr, -1, leftTr.resultFpReg)
result += IRCodeChunk(null, null).also {
val rightFpReg = registers.nextFreeFloat()
it += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = rightFpReg, immediateFp = 0f)
it += IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = rightFpReg, immediateFp = 0.0)
it += IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=compResultReg, fpReg1 = leftTr.resultFpReg, fpReg2 = rightFpReg)
}
when (condition.operator) {
@@ -1267,33 +1264,6 @@ class IRCodeGen(
val condition = ifElse.condition as? PtBinaryExpression
if(condition==null) {
throw AssemblyError("if-else condition is not a binaryexpression, should have been converted?")
// if(irDtLeft==IRDataType.FLOAT)
// throw AssemblyError("condition value should not be float")
// val tr = expressionEval.translateExpression(ifElse.condition)
// result += tr.chunks
// if(ifElse.elseScope.children.isNotEmpty()) {
// val elseLabel = createLabelName()
// result += IRCodeChunk(null, null).also {
// it += IRInstruction(Opcode.CMPI, irDtLeft, reg1=tr.resultReg, immediate = 0)
// it += IRInstruction(Opcode.BSTEQ, labelSymbol = elseLabel)
// TODO("test this")
// }
// result += translateNode(ifElse.ifScope)
// val afterIfLabel = createLabelName()
// addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = afterIfLabel), null)
// result += labelFirstChunk(translateNode(ifElse.elseScope), elseLabel)
// result += IRCodeChunk(afterIfLabel, null)
// } else {
// val afterIfLabel = createLabelName()
// result += IRCodeChunk(null, null).also {
// it += IRInstruction(Opcode.CMPI, irDtLeft, reg1=tr.resultReg, immediate = 0)
// it += IRInstruction(Opcode.BSTEQ, labelSymbol = afterIfLabel)
// TODO("test this")
// }
// result += translateNode(ifElse.ifScope)
// result += IRCodeChunk(afterIfLabel, null)
// }
// return result
} else {
if (irDtLeft == IRDataType.FLOAT) {
val leftTr = expressionEval.translateExpression(condition.left)
@@ -1641,10 +1611,7 @@ class IRCodeGen(
addToResult(result, countTr, countTr.resultReg, -1)
if(constIntValue(repeat.count)==null) {
// check if the counter is already zero
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, irDt, reg1=countTr.resultReg, immediate = 0)
it += IRInstruction(Opcode.BSTEQ, labelSymbol = skipRepeatLabel)
}
addInstr(result, IRInstruction(Opcode.BSTEQ, labelSymbol = skipRepeatLabel), null)
}
result += labelFirstChunk(translateNode(repeat.statements), repeatLabel)
result += IRCodeChunk(null, null).also {
@@ -87,7 +87,7 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
/*
Empty Code chunk with label ->
If next chunk has no label -> move label to next chunk, remove original
If next chunk has label -> label name should be the same, remove original. Otherwise FOR NOW leave it in place. (TODO: merge both labels into 1)
If next chunk has label -> label name should be the same, remove original. Otherwise merge both labels into 1.
If is last chunk -> keep chunk in place because of the label.
Empty Code chunk without label ->
should not have been generated! ERROR.
@@ -96,6 +96,7 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
val relabelChunks = mutableListOf<Pair<Int, String>>()
val removeChunks = mutableListOf<Int>()
val replaceLabels = mutableMapOf<String, String>()
sub.chunks.withIndex().forEach { (index, chunk) ->
if(chunk is IRCodeChunk && chunk.instructions.isEmpty()) {
@@ -109,10 +110,18 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
relabelChunks += Pair(index + 1, chunk.label!!)
removeChunks += index
} else {
if (chunk.label == nextchunk.label)
removeChunks += index
else {
// TODO: merge labels on same chunk
// merge both labels into 1 except if this is the label chunk at the start of the subroutine
if(index>0) {
if (chunk.label == nextchunk.label)
removeChunks += index
else {
removeChunks += index
replaceLabels[chunk.label!!] = nextchunk.label!!
replaceLabels.entries.forEach { (key, value) ->
if (value == chunk.label)
replaceLabels[key] = nextchunk.label!!
}
}
}
}
}
@@ -129,6 +138,25 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
sub.chunks[index] = chunk
}
removeChunks.reversed().forEach { index -> sub.chunks.removeAt(index) }
sub.chunks.forEach { chunk ->
chunk.instructions.withIndex().forEach { (idx, instr) ->
instr.labelSymbol?.let {
if(instr.opcode in OpcodesThatBranch) {
replaceLabels.forEach { (from, to) ->
if (it == from) {
chunk.instructions[idx] = instr.copy(labelSymbol = to)
}
else {
val actualPrefix = "$from."
if (it.startsWith(actualPrefix))
chunk.instructions[idx] = instr.copy(labelSymbol = "$to.")
}
}
}
}
}
}
}
private fun joinChunks(sub: IRSubroutine) {
@@ -380,6 +408,9 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
}
private fun removeDoubleLoadsAndStores(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
return false
/*
var changed = false
indexedInstructions.forEach { (idx, ins) ->
@@ -393,5 +424,6 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
// ...
}
return changed
*/
}
}
@@ -37,9 +37,9 @@ class IRUnusedCodeRemover(
}
}
irprog.st.allVariables().forEach { stVar->
val initValue = stVar.onetimeInitializationArrayValue
if(initValue!=null && !initValue.isEmpty()) {
irprog.st.allVariables().forEach { variable ->
val initValue = variable.onetimeInitializationArrayValue
if(!initValue.isNullOrEmpty()) {
if(initValue.any {
it.addressOfSymbol?.startsWith(blockLabel)==true
})
@@ -61,9 +61,6 @@ class IRUnusedCodeRemover(
irprog.blocks.forEach { block ->
block.children.filterIsInstance<IRSubroutine>().reversed().forEach { sub ->
if(sub.isEmpty()) {
if(!sub.position.file.startsWith(LIBRARYFILEPREFIX)) {
errors.warn("unused subroutine ${sub.label}", sub.position)
}
block.children.remove(sub)
irprog.st.removeTree(sub.label)
numRemoved++
+2
View File
@@ -16,6 +16,8 @@ internal object DummyMemsizer : IMemSizer {
}
internal object DummyStringEncoder : IStringEncoding {
override val defaultEncoding: Encoding = Encoding.ISO
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
return emptyList()
}
@@ -53,9 +53,9 @@ class TestIRPeepholeOpt: FunSpec({
test("remove jmp to label below") {
val c1 = IRCodeChunk("main.start", null)
c1 += IRInstruction(Opcode.JUMP, labelSymbol = "label") // removed, but chunk stays because of label
c1 += IRInstruction(Opcode.JUMP, labelSymbol = "label")
val c2 = IRCodeChunk("label", null)
c2 += IRInstruction(Opcode.JUMP, labelSymbol = "label2") // removed, but chunk stays because of label
c2 += IRInstruction(Opcode.JUMP, labelSymbol = "label2")
c2 += IRInstruction(Opcode.NOP) // removed
val c3 = IRCodeChunk("label2", null)
c3 += IRInstruction(Opcode.JUMP, labelSymbol = "label3")
@@ -67,15 +67,13 @@ class TestIRPeepholeOpt: FunSpec({
irProg.chunks().flatMap { it.instructions }.size shouldBe 5
val opt = IRPeepholeOptimizer(irProg)
opt.optimize(true, ErrorReporterForTests())
irProg.chunks().size shouldBe 4
irProg.chunks().size shouldBe 3
irProg.chunks()[0].label shouldBe "main.start"
irProg.chunks()[1].label shouldBe "label"
irProg.chunks()[2].label shouldBe "label2"
irProg.chunks()[3].label shouldBe "label3"
irProg.chunks()[1].label shouldBe "label2"
irProg.chunks()[2].label shouldBe "label3"
irProg.chunks()[0].isEmpty() shouldBe true
irProg.chunks()[1].isEmpty() shouldBe true
irProg.chunks()[2].isEmpty() shouldBe false
irProg.chunks()[3].isEmpty() shouldBe true
irProg.chunks()[1].isEmpty() shouldBe false
irProg.chunks()[2].isEmpty() shouldBe true
val instr = irProg.chunks().flatMap { it.instructions }
instr.size shouldBe 2
instr[0].opcode shouldBe Opcode.JUMP
@@ -2,7 +2,6 @@ package prog8.optimizer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.maySwapOperandOrder
import prog8.ast.statements.ForLoop
@@ -185,10 +184,12 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
val newExpr = BinaryExpression(leftBinExpr.left, "*", constants, expr.position)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
} else if (leftBinExpr.operator=="/") {
// (X / C2) * rightConst --> X * (rightConst/C2)
val constants = BinaryExpression(rightconst, "/", c2, c2.position)
val newExpr = BinaryExpression(leftBinExpr.left, "*", constants, expr.position)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
if(expr.inferType(program).istype(DataType.FLOAT)) {
// (X / C2) * rightConst --> X * (rightConst/C2) only valid for floating point
val constants = BinaryExpression(rightconst, "/", c2, c2.position)
val newExpr = BinaryExpression(leftBinExpr.left, "*", constants, expr.position)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
}
}
}
}
@@ -196,7 +197,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
val c2 = leftBinExpr.right.constValue(program)
if(c2!=null && leftBinExpr.operator=="/") {
// (X / C1) / C2 --> X / (C1*C2)
// NOTE: do not optimize (X * C1) / C2 --> X * (C1/C2) because this causes precision loss on integers
// NOTE: do not optimize (X * C1) / C2 for integers, --> X * (C1/C2) because this causes precision loss on integers
val constants = BinaryExpression(c2, "*", rightconst, c2.position)
val newExpr = BinaryExpression(leftBinExpr.left, "/", constants, expr.position)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
@@ -387,7 +388,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
}
}
else -> throw FatalAstException("invalid loopvar datatype $loopvar")
else -> { /* nothing for floats, these are not allowed in for loops and will give an error elsewhere */ }
}
return noModifications
@@ -527,7 +528,8 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
return IAstModification.ReplaceNode(expr, change, expr.parent)
}
}
else if(expr.operator=="*" && subExpr.operator=="/") {
else if(expr.operator=="*" && subExpr.operator=="/" && subExpr.inferType(program).istype(DataType.FLOAT)) {
// division optimizations only valid for floats
if(leftIsConst) {
val change = if(subleftIsConst) {
// C1*(C2/V) -> (C1*C2)/V
@@ -28,6 +28,7 @@ class VarConstantValueTypeAdjuster(private val program: Program, private val err
errors.err("refused rounding of float to avoid loss of precision", decl.value!!.position)
} else {
// cast the numeric literal to the appropriate datatype of the variable
declConstValue.linkParents(decl)
val cast = declConstValue.cast(decl.datatype)
if (cast.isValid)
return listOf(IAstModification.ReplaceNode(decl.value!!, cast.valueOrZero(), decl))
+104 -85
View File
@@ -34,85 +34,95 @@ class Inliner(private val program: Program, private val options: CompilationOpti
val containsSubsOrVariables = subroutine.statements.any { it is VarDecl || it is Subroutine}
if(!containsSubsOrVariables) {
if(subroutine.statements.size==1 || (subroutine.statements.size==2 && isEmptyReturn(subroutine.statements[1]))) {
// subroutine is possible candidate to be inlined
subroutine.inline =
when(val stmt=subroutine.statements[0]) {
is Return -> {
if(stmt.value is NumericLiteral)
true
else if(stmt.value==null)
true
else if (stmt.value is IdentifierReference) {
makeFullyScoped(stmt.value as IdentifierReference)
true
} else if(stmt.value!! is IFunctionCall && (stmt.value as IFunctionCall).args.size<=1 && (stmt.value as IFunctionCall).args.all { it is NumericLiteral || it is IdentifierReference }) {
when (stmt.value) {
is BuiltinFunctionCall -> {
makeFullyScoped(stmt.value as BuiltinFunctionCall)
true
if(subroutine !== program.entrypoint) {
// subroutine is possible candidate to be inlined
subroutine.inline =
when (val stmt = subroutine.statements[0]) {
is Return -> {
if (stmt.value is NumericLiteral)
true
else if (stmt.value == null)
true
else if (stmt.value is IdentifierReference) {
makeFullyScoped(stmt.value as IdentifierReference)
true
} else if (stmt.value!! is IFunctionCall && (stmt.value as IFunctionCall).args.size <= 1 && (stmt.value as IFunctionCall).args.all { it is NumericLiteral || it is IdentifierReference }) {
when (stmt.value) {
is BuiltinFunctionCall -> {
makeFullyScoped(stmt.value as BuiltinFunctionCall)
true
}
is FunctionCallExpression -> {
makeFullyScoped(stmt.value as FunctionCallExpression)
true
}
else -> false
}
is FunctionCallExpression -> {
makeFullyScoped(stmt.value as FunctionCallExpression)
true
}
else -> false
}
} else
false
}
is Assignment -> {
if(stmt.value.isSimple) {
val targetInline =
if(stmt.target.identifier!=null) {
makeFullyScoped(stmt.target.identifier!!)
true
} else if(stmt.target.memoryAddress?.addressExpression is NumericLiteral || stmt.target.memoryAddress?.addressExpression is IdentifierReference) {
if(stmt.target.memoryAddress?.addressExpression is IdentifierReference)
makeFullyScoped(stmt.target.memoryAddress?.addressExpression as IdentifierReference)
true
} else
false
val valueInline =
if(stmt.value is IdentifierReference) {
makeFullyScoped(stmt.value as IdentifierReference)
true
} else if((stmt.value as? DirectMemoryRead)?.addressExpression is NumericLiteral || (stmt.value as? DirectMemoryRead)?.addressExpression is IdentifierReference) {
if((stmt.value as? DirectMemoryRead)?.addressExpression is IdentifierReference)
makeFullyScoped((stmt.value as? DirectMemoryRead)?.addressExpression as IdentifierReference)
true
} else
false
targetInline || valueInline
} else
false
}
is BuiltinFunctionCallStatement -> {
val inline = stmt.args.size<=1 && stmt.args.all { it is NumericLiteral || it is IdentifierReference }
if(inline)
makeFullyScoped(stmt)
inline
}
is FunctionCallStatement -> {
val inline = stmt.args.size<=1 && stmt.args.all { it is NumericLiteral || it is IdentifierReference }
if(inline)
makeFullyScoped(stmt)
inline
}
is PostIncrDecr -> {
if(stmt.target.identifier!=null) {
makeFullyScoped(stmt.target.identifier!!)
true
} else
false
}
else if(stmt.target.memoryAddress?.addressExpression is NumericLiteral || stmt.target.memoryAddress?.addressExpression is IdentifierReference) {
if(stmt.target.memoryAddress?.addressExpression is IdentifierReference)
makeFullyScoped(stmt.target.memoryAddress?.addressExpression as IdentifierReference)
true
} else
false
is Assignment -> {
if (stmt.value.isSimple) {
val targetInline =
if (stmt.target.identifier != null) {
makeFullyScoped(stmt.target.identifier!!)
true
} else if (stmt.target.memoryAddress?.addressExpression is NumericLiteral || stmt.target.memoryAddress?.addressExpression is IdentifierReference) {
if (stmt.target.memoryAddress?.addressExpression is IdentifierReference)
makeFullyScoped(stmt.target.memoryAddress?.addressExpression as IdentifierReference)
true
} else
false
val valueInline =
if (stmt.value is IdentifierReference) {
makeFullyScoped(stmt.value as IdentifierReference)
true
} else if ((stmt.value as? DirectMemoryRead)?.addressExpression is NumericLiteral || (stmt.value as? DirectMemoryRead)?.addressExpression is IdentifierReference) {
if ((stmt.value as? DirectMemoryRead)?.addressExpression is IdentifierReference)
makeFullyScoped((stmt.value as? DirectMemoryRead)?.addressExpression as IdentifierReference)
true
} else
false
targetInline || valueInline
} else
false
}
is BuiltinFunctionCallStatement -> {
val inline =
stmt.args.size <= 1 && stmt.args.all { it is NumericLiteral || it is IdentifierReference }
if (inline)
makeFullyScoped(stmt)
inline
}
is FunctionCallStatement -> {
val inline =
stmt.args.size <= 1 && stmt.args.all { it is NumericLiteral || it is IdentifierReference }
if (inline)
makeFullyScoped(stmt)
inline
}
is PostIncrDecr -> {
if (stmt.target.identifier != null) {
makeFullyScoped(stmt.target.identifier!!)
true
} else if (stmt.target.memoryAddress?.addressExpression is NumericLiteral || stmt.target.memoryAddress?.addressExpression is IdentifierReference) {
if (stmt.target.memoryAddress?.addressExpression is IdentifierReference)
makeFullyScoped(stmt.target.memoryAddress?.addressExpression as IdentifierReference)
true
} else
false
}
is Jump -> true
else -> false
}
is Jump -> true
else -> false
}
}
}
if(subroutine.inline && subroutine.statements.size>1) {
@@ -134,16 +144,20 @@ class Inliner(private val program: Program, private val options: CompilationOpti
private fun makeFullyScoped(call: BuiltinFunctionCallStatement) {
val scopedArgs = makeScopedArgs(call.args)
val scopedCall = BuiltinFunctionCallStatement(call.target.copy(), scopedArgs.toMutableList(), call.position)
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
if(scopedArgs.any()) {
val scopedCall = BuiltinFunctionCallStatement(call.target.copy(), scopedArgs.toMutableList(), call.position)
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
}
}
private fun makeFullyScoped(call: FunctionCallStatement) {
call.target.targetSubroutine(program)?.let { sub ->
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
val scopedArgs = makeScopedArgs(call.args)
val scopedCall = FunctionCallStatement(scopedName, scopedArgs.toMutableList(), call.void, call.position)
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
if(scopedArgs.any()) {
val scopedCall = FunctionCallStatement(scopedName, scopedArgs.toMutableList(), call.void, call.position)
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
}
}
}
@@ -151,8 +165,10 @@ class Inliner(private val program: Program, private val options: CompilationOpti
call.target.targetSubroutine(program)?.let { sub ->
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
val scopedArgs = makeScopedArgs(call.args)
val scopedCall = BuiltinFunctionCall(scopedName, scopedArgs.toMutableList(), call.position)
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
if(scopedArgs.any()) {
val scopedCall = BuiltinFunctionCall(scopedName, scopedArgs.toMutableList(), call.position)
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
}
}
}
@@ -160,8 +176,10 @@ class Inliner(private val program: Program, private val options: CompilationOpti
call.target.targetSubroutine(program)?.let { sub ->
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
val scopedArgs = makeScopedArgs(call.args)
val scopedCall = FunctionCallExpression(scopedName, scopedArgs.toMutableList(), call.position)
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
if(scopedArgs.any()) {
val scopedCall = FunctionCallExpression(scopedName, scopedArgs.toMutableList(), call.position)
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
}
}
}
@@ -170,7 +188,8 @@ class Inliner(private val program: Program, private val options: CompilationOpti
when (it) {
is NumericLiteral -> it.copy()
is IdentifierReference -> {
val scoped = (it.targetStatement(program)!! as INamedStatement).scopedName
val target = it.targetStatement(program) ?: return emptyList()
val scoped = (target as INamedStatement).scopedName
IdentifierReference(scoped, it.position)
}
else -> throw InternalCompilerException("expected only number or identifier arg, otherwise too complex")
@@ -45,11 +45,7 @@ class StatementOptimizer(private val program: Program,
mutableListOf(NumericLiteral(DataType.UBYTE, firstCharEncoded.toDouble(), pos)),
functionCallStatement.void, pos
)
val stringDecl = string.parent as VarDecl
return listOf(
IAstModification.ReplaceNode(functionCallStatement, chrout, parent),
IAstModification.Remove(stringDecl, stringDecl.parent as IStatementContainer)
)
return listOf(IAstModification.ReplaceNode(functionCallStatement, chrout, parent))
} else if (string.value.length == 2) {
val firstTwoCharsEncoded = options.compTarget.encodeString(string.value.take(2), string.encoding)
val chrout1 = FunctionCallStatement(
@@ -62,11 +58,9 @@ class StatementOptimizer(private val program: Program,
mutableListOf(NumericLiteral(DataType.UBYTE, firstTwoCharsEncoded[1].toDouble(), pos)),
functionCallStatement.void, pos
)
val stringDecl = string.parent as VarDecl
return listOf(
IAstModification.InsertBefore(functionCallStatement, chrout1, parent as IStatementContainer),
IAstModification.ReplaceNode(functionCallStatement, chrout2, parent),
IAstModification.Remove(stringDecl, stringDecl.parent as IStatementContainer)
IAstModification.ReplaceNode(functionCallStatement, chrout2, parent)
)
}
}
@@ -77,6 +71,11 @@ class StatementOptimizer(private val program: Program,
}
override fun after(ifElse: IfElse, parent: Node): Iterable<IAstModification> {
val constvalue = ifElse.condition.constValue(program)
if(constvalue!=null) {
errors.warn("condition is always ${constvalue.asBooleanValue}", ifElse.condition.position)
}
// remove empty if statements
if(ifElse.truepart.isEmpty() && ifElse.elsepart.isEmpty())
return listOf(IAstModification.Remove(ifElse, parent as IStatementContainer))
@@ -93,17 +92,12 @@ class StatementOptimizer(private val program: Program,
)
}
val constvalue = ifElse.condition.constValue(program)
if(constvalue!=null) {
return if(constvalue.asBooleanValue){
// always true -> keep only if-part
if(!ifElse.definingModule.isLibrary)
errors.warn("condition is always true", ifElse.condition.position)
listOf(IAstModification.ReplaceNode(ifElse, ifElse.truepart, parent))
} else {
// always false -> keep only else-part
if(!ifElse.definingModule.isLibrary)
errors.warn("condition is always false", ifElse.condition.position)
listOf(IAstModification.ReplaceNode(ifElse, ifElse.elsepart, parent))
}
}
@@ -174,6 +168,70 @@ class StatementOptimizer(private val program: Program,
}
}
val loopvarDt = forLoop.loopVarDt(program)
if(loopvarDt.istype(DataType.UWORD) || loopvarDt.istype(DataType.UBYTE)) {
if (range != null && range.from.constValue(program)?.number == 0.0 && range.step.constValue(program)?.number==1.0) {
val toBinExpr = range.to as? BinaryExpression
if(toBinExpr!=null && toBinExpr.operator=="-" && toBinExpr.right.constValue(program)?.number==1.0) {
// FOR var IN 0 TO X-1 .... ---> var=0, DO {... , var++} UNTIL var==X
val pos = forLoop.position
val condition = BinaryExpression(forLoop.loopVar.copy(), "==", toBinExpr.left, pos)
val incOne = PostIncrDecr(AssignTarget(forLoop.loopVar.copy(), null, null, pos), "++", pos)
forLoop.body.statements.add(incOne)
val replacement = AnonymousScope(mutableListOf(
Assignment(AssignTarget(forLoop.loopVar.copy(), null, null, pos),
NumericLiteral.optimalNumeric(0.0, pos),
AssignmentOrigin.OPTIMIZER, pos),
UntilLoop(forLoop.body, condition, pos)
), pos)
return listOf(IAstModification.ReplaceNode(forLoop, replacement, parent))
}
if(options.compTarget.name!=VMTarget.NAME) {
// this optimization is not effective for the VM target.
val toConst = range.to.constValue(program)
if (toConst == null) {
// FOR var in 0 TO X ... ---> var=0, REPEAT { ... , IF var==X break , var++ }
val pos = forLoop.position
val incOne = PostIncrDecr(AssignTarget(forLoop.loopVar.copy(), null, null, pos), "++", pos)
val breakCondition = IfElse(
BinaryExpression(forLoop.loopVar, "==", range.to, pos),
AnonymousScope(mutableListOf(Break(pos)), pos),
AnonymousScope(mutableListOf(), pos),
pos
)
forLoop.body.statements.add(breakCondition)
forLoop.body.statements.add(incOne)
val replacement = AnonymousScope(mutableListOf(
Assignment(AssignTarget(forLoop.loopVar.copy(), null, null, pos),
NumericLiteral.optimalNumeric(0.0, pos),
AssignmentOrigin.OPTIMIZER, pos),
RepeatLoop(null, forLoop.body, pos)
), pos)
return listOf(IAstModification.ReplaceNode(forLoop, replacement, parent))
}
}
}
if (range != null && range.to.constValue(program)?.number == 0.0 && range.step.constValue(program)?.number==-1.0) {
val fromExpr = range.from
if(fromExpr.constValue(program)==null) {
// FOR X = something DOWNTO 0 {...} --> X=something, DO { ... , X-- } UNTIL X=255 (or 65535 if uword)
val pos = forLoop.position
val checkValue = NumericLiteral(loopvarDt.getOr(DataType.UNDEFINED), if(loopvarDt.istype(DataType.UBYTE)) 255.0 else 65535.0, pos)
val condition = BinaryExpression(forLoop.loopVar.copy(), "==", checkValue, pos)
val decOne = PostIncrDecr(AssignTarget(forLoop.loopVar.copy(), null, null, pos), "--", pos)
forLoop.body.statements.add(decOne)
val replacement = AnonymousScope(mutableListOf(
Assignment(AssignTarget(forLoop.loopVar.copy(), null, null, pos),
fromExpr, AssignmentOrigin.OPTIMIZER, pos),
UntilLoop(forLoop.body, condition, pos)
), pos)
return listOf(IAstModification.ReplaceNode(forLoop, replacement, parent))
}
}
}
return noModifications
}
@@ -99,7 +99,7 @@ class UnusedCodeRemover(private val program: Program,
}
return removals
}
if(!subroutine.definingModule.isLibrary && !subroutine.hasBeenInlined) {
if(!subroutine.hasBeenInlined && subroutine.definingModule===program.toplevelModule) {
errors.warn("unused subroutine '${subroutine.name}'", subroutine.position)
}
if(!subroutine.inline) {
@@ -139,13 +139,20 @@ class UnusedCodeRemover(private val program: Program,
)
} else if(assignment.value is IFunctionCall) {
// replace the unused variable's initializer function call by a void
errors.warn("replaced unused variable '${decl.name}' with void call, maybe this can be removed altogether", decl.position)
val fcall = assignment.value as IFunctionCall
val voidCall = FunctionCallStatement(fcall.target, fcall.args, true, fcall.position)
return listOf(
IAstModification.ReplaceNode(decl, voidCall, parent),
IAstModification.Remove(assignment, assignment.parent as IStatementContainer)
)
// but only if the vardecl immediately precedes it!
if(singleUse.parent.parent === parent) {
val declIndex = (parent as IStatementContainer).statements.indexOf(decl)
val singleUseIndex = (parent as IStatementContainer).statements.indexOf(singleUse.parent)
if(declIndex==singleUseIndex-1) {
errors.warn("replaced unused variable '${decl.name}' with void call, maybe this can be removed altogether", decl.position)
val fcall = assignment.value as IFunctionCall
val voidCall = FunctionCallStatement(fcall.target, fcall.args, true, fcall.position)
return listOf(
IAstModification.ReplaceNode(decl, voidCall, parent),
IAstModification.Remove(assignment, assignment.parent as IStatementContainer)
)
}
}
} else {
errors.warn("variable '${decl.name}' is unused but has non-trivial initialization assignment. Leaving this in but maybe it can be removed altogether", decl.position)
}
+1 -1
View File
@@ -40,7 +40,7 @@ dependencies {
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.18"
testImplementation project(':intermediate')
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.7.2'
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.8.0'
}
configurations.all {
+47
View File
@@ -8,6 +8,13 @@ atari {
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
&uword COLCRS = 85
&ubyte ROWCRS = 84
romsub $F24A = getchar() -> ubyte @A
romsub $F2B0 = outchar(ubyte character @ A)
romsub $F2FD = waitkey() -> ubyte @A
}
sys {
@@ -206,6 +213,46 @@ _longcopy
; no-op
}
asmsub save_prog8_internals() {
%asm {{
lda P8ZP_SCRATCH_B1
sta save_SCRATCH_ZPB1
lda P8ZP_SCRATCH_REG
sta save_SCRATCH_ZPREG
lda P8ZP_SCRATCH_W1
sta save_SCRATCH_ZPWORD1
lda P8ZP_SCRATCH_W1+1
sta save_SCRATCH_ZPWORD1+1
lda P8ZP_SCRATCH_W2
sta save_SCRATCH_ZPWORD2
lda P8ZP_SCRATCH_W2+1
sta save_SCRATCH_ZPWORD2+1
rts
save_SCRATCH_ZPB1 .byte 0
save_SCRATCH_ZPREG .byte 0
save_SCRATCH_ZPWORD1 .word 0
save_SCRATCH_ZPWORD2 .word 0
}}
}
asmsub restore_prog8_internals() {
%asm {{
lda save_prog8_internals.save_SCRATCH_ZPB1
sta P8ZP_SCRATCH_B1
lda save_prog8_internals.save_SCRATCH_ZPREG
sta P8ZP_SCRATCH_REG
lda save_prog8_internals.save_SCRATCH_ZPWORD1
sta P8ZP_SCRATCH_W1
lda save_prog8_internals.save_SCRATCH_ZPWORD1+1
sta P8ZP_SCRATCH_W1+1
lda save_prog8_internals.save_SCRATCH_ZPWORD2
sta P8ZP_SCRATCH_W2
lda save_prog8_internals.save_SCRATCH_ZPWORD2+1
sta P8ZP_SCRATCH_W2+1
rts
}}
}
inline asmsub exit(ubyte returnvalue @A) {
; -- immediately exit the program with a return code in the A register
; TODO
+16 -14
View File
@@ -1,4 +1,5 @@
; Prog8 definitions for the Text I/O and Screen routines for the Atari 800XL
; Reference: https://www.atariarchives.org/mapping/appendix12.php
%import syslib
%import conv
@@ -24,24 +25,22 @@ sub spc() {
txt.chrout(' ')
}
const uword COLCRS = 85
const uword ROWCRS = 84
sub column(ubyte col) {
; ---- set the cursor on the given column (starting with 0) on the current line
pokew(COLCRS, col as uword)
atari.COLCRS = col
}
sub get_column() -> ubyte {
return peekw(COLCRS) as ubyte
return atari.COLCRS as ubyte
}
sub row(ubyte rownum) {
; ---- set the cursor on the given row (starting with 0) on the current line
@(ROWCRS) = rownum
atari.ROWCRS = rownum
}
sub get_row() -> ubyte {
return @(ROWCRS)
return atari.ROWCRS
}
asmsub fill_screen (ubyte character @ A, ubyte color @ Y) clobbers(A) {
@@ -126,9 +125,6 @@ asmsub scroll_down (bool alsocolors @ Pc) clobbers(A) {
}
romsub $F2B0 = outchar(ubyte character @ A)
romsub $F2Fd = waitkey()
asmsub chrout(ubyte character @ A) {
%asm {{
sta _tmp_outchar+1
@@ -138,7 +134,7 @@ asmsub chrout(ubyte character @ A) {
pha
_tmp_outchar
lda #0
jsr outchar
jsr atari.outchar
pla
tay
pla
@@ -385,13 +381,19 @@ sub setcc (ubyte col, ubyte row, ubyte char, ubyte charcolor) {
}
sub plot(ubyte col, ubyte rownum) {
column(col)
row(rownum)
column(col)
row(rownum)
}
sub get_cursor(uword colptr, uword rowptr) {
@(colptr) = get_column()
@(rowptr) = get_row()
@(colptr) = get_column()
@(rowptr) = get_row()
}
asmsub waitkey() -> ubyte @A {
%asm {{
jmp atari.waitkey
}}
}
asmsub width() clobbers(X,Y) -> ubyte @A {
+76 -76
View File
@@ -391,77 +391,78 @@ asmsub enable_runstop_and_charsetswitch() clobbers(A) {
}}
}
asmsub set_irq(uword handler @AY, bool useKernal @Pc) clobbers(A) {
asmsub save_prog8_internals() {
%asm {{
lda P8ZP_SCRATCH_B1
sta save_SCRATCH_ZPB1
lda P8ZP_SCRATCH_REG
sta save_SCRATCH_ZPREG
lda P8ZP_SCRATCH_W1
sta save_SCRATCH_ZPWORD1
lda P8ZP_SCRATCH_W1+1
sta save_SCRATCH_ZPWORD1+1
lda P8ZP_SCRATCH_W2
sta save_SCRATCH_ZPWORD2
lda P8ZP_SCRATCH_W2+1
sta save_SCRATCH_ZPWORD2+1
rts
save_SCRATCH_ZPB1 .byte 0
save_SCRATCH_ZPREG .byte 0
save_SCRATCH_ZPWORD1 .word 0
save_SCRATCH_ZPWORD2 .word 0
}}
}
asmsub restore_prog8_internals() {
%asm {{
lda save_prog8_internals.save_SCRATCH_ZPB1
sta P8ZP_SCRATCH_B1
lda save_prog8_internals.save_SCRATCH_ZPREG
sta P8ZP_SCRATCH_REG
lda save_prog8_internals.save_SCRATCH_ZPWORD1
sta P8ZP_SCRATCH_W1
lda save_prog8_internals.save_SCRATCH_ZPWORD1+1
sta P8ZP_SCRATCH_W1+1
lda save_prog8_internals.save_SCRATCH_ZPWORD2
sta P8ZP_SCRATCH_W2
lda save_prog8_internals.save_SCRATCH_ZPWORD2+1
sta P8ZP_SCRATCH_W2+1
rts
}}
}
asmsub set_irq(uword handler @AY) clobbers(A) {
%asm {{
sta _modified+1
sty _modified+2
lda #0
rol a
sta _use_kernal
sei
sta _modified+1
sty _modified+2
lda #<_irq_handler
sta cbm.CINV
lda #>_irq_handler
sta cbm.CINV+1
cli
rts
_irq_handler jsr _irq_handler_init
_modified jsr $ffff ; modified
jsr _irq_handler_end
lda _use_kernal
bne +
lda #$ff
_irq_handler
jsr sys.save_prog8_internals
cld
_modified
jsr $ffff ; modified
pha
jsr sys.restore_prog8_internals
pla
beq +
jmp cbm.IRQDFRT ; continue with normal kernal irq routine
+ lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
lda c64.CIA1ICR ; acknowledge CIA1 interrupt
; end irq processing - don't use kernal's irq handling
pla
tay
pla
tax
pla
rti
+ jmp cbm.IRQDFRT ; continue with normal kernal irq routine
_use_kernal .byte 0
_irq_handler_init
; save all zp scratch registers as these might be clobbered by the irq routine
lda P8ZP_SCRATCH_B1
sta IRQ_SCRATCH_ZPB1
lda P8ZP_SCRATCH_REG
sta IRQ_SCRATCH_ZPREG
lda P8ZP_SCRATCH_W1
sta IRQ_SCRATCH_ZPWORD1
lda P8ZP_SCRATCH_W1+1
sta IRQ_SCRATCH_ZPWORD1+1
lda P8ZP_SCRATCH_W2
sta IRQ_SCRATCH_ZPWORD2
lda P8ZP_SCRATCH_W2+1
sta IRQ_SCRATCH_ZPWORD2+1
cld
rts
_irq_handler_end
; restore all zp scratch registers
lda IRQ_SCRATCH_ZPB1
sta P8ZP_SCRATCH_B1
lda IRQ_SCRATCH_ZPREG
sta P8ZP_SCRATCH_REG
lda IRQ_SCRATCH_ZPWORD1
sta P8ZP_SCRATCH_W1
lda IRQ_SCRATCH_ZPWORD1+1
sta P8ZP_SCRATCH_W1+1
lda IRQ_SCRATCH_ZPWORD2
sta P8ZP_SCRATCH_W2
lda IRQ_SCRATCH_ZPWORD2+1
sta P8ZP_SCRATCH_W2+1
rts
IRQ_SCRATCH_ZPB1 .byte 0
IRQ_SCRATCH_ZPREG .byte 0
IRQ_SCRATCH_ZPWORD1 .word 0
IRQ_SCRATCH_ZPWORD2 .word 0
}}
}
@@ -481,40 +482,39 @@ asmsub restore_irq() clobbers(A) {
}}
}
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0, bool useKernal @Pc) clobbers(A) {
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
%asm {{
sta _modified+1
sty _modified+2
lda #0
rol a
sta set_irq._use_kernal
lda cx16.r0
ldy cx16.r0+1
sei
jsr _setup_raster_irq
lda #<_raster_irq_handler
sta cbm.CINV
lda #>_raster_irq_handler
sta cbm.CINV+1
cli
rts
sei
sta _modified+1
sty _modified+2
lda cx16.r0
ldy cx16.r0+1
jsr _setup_raster_irq
lda #<_raster_irq_handler
sta cbm.CINV
lda #>_raster_irq_handler
sta cbm.CINV+1
cli
rts
_raster_irq_handler
jsr set_irq._irq_handler_init
_modified jsr $ffff ; modified
jsr set_irq._irq_handler_end
jsr sys.save_prog8_internals
cld
_modified
jsr $ffff ; modified
pha
jsr sys.restore_prog8_internals
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
lda set_irq._use_kernal
bne +
; end irq processing - don't use kernal's irq handling
pla
beq +
jmp cbm.IRQDFRT ; continue with kernal irq routine
+ pla
tay
pla
tax
pla
rti
+ jmp cbm.IRQDFRT ; continue with kernal irq routine
_setup_raster_irq
pha
+1 -1
View File
@@ -626,7 +626,7 @@ asmsub height() clobbers(X, Y) -> ubyte @A {
}}
}
asmsub waitkey() {
asmsub waitkey() -> ubyte @A {
%asm {{
- jsr cbm.GETIN
beq -
+11 -3
View File
@@ -7,8 +7,9 @@ floats {
; ---- this block contains C-64 floating point related functions ----
%option no_symbol_prefixing
const float PI = 3.141592653589793
const float TWOPI = 6.283185307179586
const float π = 3.141592653589793
const float PI = π
const float TWOPI = 2*π
; ---- C64 basic and kernal ROM float constants and functions ----
@@ -52,7 +53,7 @@ romsub $bddd = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, ad
romsub $b849 = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for rounding- call this before INT
romsub $bae2 = MUL10() clobbers(A,X,Y) ; fac1 *= 10
romsub $bafe = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive!
romsub $bafe = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive! You have to fix sign manually!
romsub $bc5b = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
romsub $b86a = FADDT() clobbers(A,X,Y) ; fac1 += fac2
@@ -77,6 +78,7 @@ romsub $bf71 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
romsub $bf74 = SQRA() clobbers(A,X,Y) ; fac1 = SQRT(fac2)
romsub $bfed = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
romsub $bfb4 = NEGOP() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1)
romsub $b8d7 = NORMAL() clobbers(A) ; normalize FAC1
romsub $e097 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
romsub $e264 = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
romsub $e26b = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
@@ -175,6 +177,12 @@ asmsub parse_f(str value @AY) -> float @FAC1 {
}}
}
asmsub normalize(float value @FAC1) -> float @ FAC1 {
%asm {{
jmp floats.NORMAL
}}
}
%asminclude "library:c64/floats.asm"
%asminclude "library:c64/floats_funcs.asm"
+79 -81
View File
@@ -393,78 +393,77 @@ asmsub enable_runstop_and_charsetswitch() clobbers(A) {
}}
}
asmsub set_irq(uword handler @AY, bool useKernal @Pc) clobbers(A) {
asmsub save_prog8_internals() {
%asm {{
lda P8ZP_SCRATCH_B1
sta save_SCRATCH_ZPB1
lda P8ZP_SCRATCH_REG
sta save_SCRATCH_ZPREG
lda P8ZP_SCRATCH_W1
sta save_SCRATCH_ZPWORD1
lda P8ZP_SCRATCH_W1+1
sta save_SCRATCH_ZPWORD1+1
lda P8ZP_SCRATCH_W2
sta save_SCRATCH_ZPWORD2
lda P8ZP_SCRATCH_W2+1
sta save_SCRATCH_ZPWORD2+1
rts
save_SCRATCH_ZPB1 .byte 0
save_SCRATCH_ZPREG .byte 0
save_SCRATCH_ZPWORD1 .word 0
save_SCRATCH_ZPWORD2 .word 0
}}
}
asmsub restore_prog8_internals() {
%asm {{
lda save_prog8_internals.save_SCRATCH_ZPB1
sta P8ZP_SCRATCH_B1
lda save_prog8_internals.save_SCRATCH_ZPREG
sta P8ZP_SCRATCH_REG
lda save_prog8_internals.save_SCRATCH_ZPWORD1
sta P8ZP_SCRATCH_W1
lda save_prog8_internals.save_SCRATCH_ZPWORD1+1
sta P8ZP_SCRATCH_W1+1
lda save_prog8_internals.save_SCRATCH_ZPWORD2
sta P8ZP_SCRATCH_W2
lda save_prog8_internals.save_SCRATCH_ZPWORD2+1
sta P8ZP_SCRATCH_W2+1
rts
}}
}
asmsub set_irq(uword handler @AY) clobbers(A) {
%asm {{
sta _modified+1
sty _modified+2
lda #0
rol a
sta _use_kernal
sei
sei
sta _modified+1
sty _modified+2
lda #<_irq_handler
sta cbm.CINV
lda #>_irq_handler
sta cbm.CINV+1
cli
rts
_irq_handler jsr _irq_handler_init
_modified jsr $ffff ; modified
jsr _irq_handler_end
lda _use_kernal
bne +
lda #$ff
_irq_handler
jsr sys.save_prog8_internals
cld
_modified
jsr $ffff ; modified
pha
jsr sys.restore_prog8_internals
pla
beq +
jmp cbm.IRQDFRT ; continue with normal kernal irq routine
+ lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
lda c64.CIA1ICR ; acknowledge CIA1 interrupt
; end irq processing - don't use kernal's irq handling
pla
tay
pla
tax
pla
rti
+ jmp cbm.IRQDFRT ; continue with normal kernal irq routine
_use_kernal .byte 0
_irq_handler_init
; save all zp scratch registers as these might be clobbered by the irq routine
lda P8ZP_SCRATCH_B1
sta IRQ_SCRATCH_ZPB1
lda P8ZP_SCRATCH_REG
sta IRQ_SCRATCH_ZPREG
lda P8ZP_SCRATCH_W1
sta IRQ_SCRATCH_ZPWORD1
lda P8ZP_SCRATCH_W1+1
sta IRQ_SCRATCH_ZPWORD1+1
lda P8ZP_SCRATCH_W2
sta IRQ_SCRATCH_ZPWORD2
lda P8ZP_SCRATCH_W2+1
sta IRQ_SCRATCH_ZPWORD2+1
cld
rts
_irq_handler_end
; restore all zp scratch registers
lda IRQ_SCRATCH_ZPB1
sta P8ZP_SCRATCH_B1
lda IRQ_SCRATCH_ZPREG
sta P8ZP_SCRATCH_REG
lda IRQ_SCRATCH_ZPWORD1
sta P8ZP_SCRATCH_W1
lda IRQ_SCRATCH_ZPWORD1+1
sta P8ZP_SCRATCH_W1+1
lda IRQ_SCRATCH_ZPWORD2
sta P8ZP_SCRATCH_W2
lda IRQ_SCRATCH_ZPWORD2+1
sta P8ZP_SCRATCH_W2+1
rts
IRQ_SCRATCH_ZPB1 .byte 0
IRQ_SCRATCH_ZPREG .byte 0
IRQ_SCRATCH_ZPWORD1 .word 0
IRQ_SCRATCH_ZPWORD2 .word 0
}}
}}
}
asmsub restore_irq() clobbers(A) {
@@ -483,40 +482,39 @@ asmsub restore_irq() clobbers(A) {
}}
}
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0, bool useKernal @Pc) clobbers(A) {
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
%asm {{
sta _modified+1
sty _modified+2
lda #0
rol a
sta set_irq._use_kernal
lda cx16.r0
ldy cx16.r0+1
sei
jsr _setup_raster_irq
lda #<_raster_irq_handler
sta cbm.CINV
lda #>_raster_irq_handler
sta cbm.CINV+1
cli
rts
sei
sta _modified+1
sty _modified+2
lda cx16.r0
ldy cx16.r0+1
jsr _setup_raster_irq
lda #<_raster_irq_handler
sta cbm.CINV
lda #>_raster_irq_handler
sta cbm.CINV+1
cli
rts
_raster_irq_handler
jsr set_irq._irq_handler_init
_modified jsr $ffff ; modified
jsr set_irq._irq_handler_end
jsr sys.save_prog8_internals
cld
_modified
jsr $ffff ; modified
pha
jsr sys.restore_prog8_internals
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
lda set_irq._use_kernal
bne +
; end irq processing - don't use kernal's irq handling
pla
pla
beq +
jmp cbm.IRQDFRT ; continue with kernal irq routine
+ pla
tay
pla
tax
pla
rti
+ jmp cbm.IRQDFRT ; continue with kernal irq routine
_setup_raster_irq
pha
+1 -1
View File
@@ -625,7 +625,7 @@ asmsub height() clobbers(X, Y) -> ubyte @A {
}}
}
asmsub waitkey() {
asmsub waitkey() -> ubyte @A {
%asm {{
- jsr cbm.GETIN
beq -
+361
View File
@@ -0,0 +1,361 @@
; Routines to load and save "BMX" files (commander X16 bitmap format) Version 1.
; Only uncompressed images are supported for now.
; BMX Specification: https://cx16forum.com/forum/viewtopic.php?t=6945
%import diskio
bmx {
ubyte[32] header
str FILEID = petscii:"bmx"
ubyte bitsperpixel ; consider using set_bpp() when setting this
ubyte vera_colordepth ; consider using set_vera_colordepth() when setting this
uword width
uword height
ubyte border
uword palette_entries ; 1-256
ubyte palette_start
ubyte compression
uword palette_buffer_ptr = 0 ; should you want to load or save the palette into main memory instead of directly into vram
uword error_message ; pointer to error message, or 0 if all ok
ubyte old_drivenumber
sub open(ubyte drivenumber, str filename) -> bool {
; Open a BMX bitmap file and reads the header information.
; Returns true if all is ok, false otherwise + error_message will be set.
error_message = 0
old_drivenumber = diskio.drivenumber
diskio.drivenumber = drivenumber
if diskio.f_open(filename) {
diskio.reset_read_channel()
if read_header() {
if parse_header() {
if palette_entries>0 {
if width<=640 {
return true
} else
error_message = "image too large"
} else
error_message = "invalid bmx file"
} ; note: parse_header sets the error message by itself
} else
error_message = "invalid bmx file"
} else
error_message = diskio.status()
close()
return false
}
sub close() {
; if you want to close the file before actually loading palette or bitmap data.
diskio.f_close()
diskio.drivenumber = old_drivenumber
}
sub continue_load(ubyte vbank, uword vaddr) -> bool {
; Continues loading the palette and bitmap data from the opened BMX file.
; Parameters: the vram bank and address where the bitmap data should go.
; You can set palette_buffer_ptr if you want the palette buffered rather than directly into vram.
; Note: does not change vera screen mode or colordepth! You have to do that yourself!
; Returns true if all is ok, false otherwise + error_message will be set.
error_message = 0
diskio.reset_read_channel()
if width==320 or width==640 {
if compression==0 {
if read_palette() {
if not read_bitmap(vbank, vaddr)
error_message = "bitmap error"
} else
error_message = "palette error"
} else
error_message = "compression not supported"
} else
error_message = "width not 320 or 640" ; note: use continue_load_stamp() to read other sizes
close()
return error_message==0
}
sub continue_load_stamp(ubyte vbank, uword vaddr, uword screenwidth) -> bool {
; Continues loading the palette and bitmap "stamp" data from the opened BMX file.
; "Stamp" means: load an image that is smaller than the screen (so we need to pad around it)
; Parameters:the vram bank and address where the bitmap data should go,
; and the screen width that the stamp image is loaded into.
; You can set palette_buffer_ptr if you want the palette buffered rather than directly into vram.
; Note: does not change vera screen mode or colordepth! You have to do that yourself!
; Returns true if all is ok, false otherwise + error_message will be set.
error_message = 0
diskio.reset_read_channel()
if compression==0 {
if read_palette() {
if not read_bitmap_padded(vbank, vaddr, screenwidth)
error_message = "bitmap error"
} else
error_message = "palette error"
} else
error_message = "compression not supported"
close()
return error_message==0
}
sub continue_load_only_palette() -> bool {
; Continues loading the palette but not the bitmap data from the opened BMX file.
; You can set palette_buffer_ptr if you want the palette buffered rather than directly into vram.
; Returns true if all is ok, false otherwise + error_message will be set.
; Afterwards the file is closed and you can no longer read additional data from it!
error_message = 0
diskio.reset_read_channel()
if not read_palette()
error_message = "palette error"
close()
return error_message==0
}
sub save(ubyte drivenumber, str filename, ubyte vbank, uword vaddr, uword screenwidth) -> bool {
; Save bitmap and palette data from vram into a BMX file.
; First you must have set all bmx.* variables to the correct values! (like width, height..)
; Parameters:
; drive number and filename to save to,
; vram bank and address of the bitmap data to save,
; and optionally the screen width if you want to save a "stamp" that's smaller than the screen.
; If you're saving the whole screen width, you can leave screenwidth at 0.
; Returns: success status. If false, error_message points to the error message string.
error_message = 0
if compression {
error_message = "compression not supported"
return false
}
if screenwidth==0 {
if width!=320 and width!=640 {
error_message = "width not 320 or 640"
return false
}
} else {
if width>screenwidth {
error_message = "image too large"
return false
}
}
old_drivenumber = diskio.drivenumber
diskio.drivenumber = drivenumber
if diskio.f_open_w(filename) {
cx16.r0 = diskio.status()
if cx16.r0[0]!='0' {
error_message = cx16.r0
goto save_end
}
diskio.reset_write_channel()
if write_header() {
if write_palette() {
if not write_bitmap(vbank, vaddr, screenwidth)
error_message = "bitmap error"
} else
error_message = "palette error"
} else
error_message = "header error"
} else
error_message = diskio.status()
save_end:
diskio.f_close_w()
diskio.drivenumber = old_drivenumber
return error_message==0
}
sub set_bpp(ubyte bpp) {
ubyte[8] depths = [0,1,1,2,2,2,2,3]
vera_colordepth = depths[bpp-1]
}
sub set_vera_colordepth(ubyte depth) {
vera_colordepth = depth
bitsperpixel = 1 << depth
}
; ------------------- helper routines, usually not needed to call yourself -------------------------
sub read_header() -> bool {
; load a BMX header from the currently active input file
for cx16.r0L in 0 to sizeof(header)-1 {
header[cx16.r0L] = cbm.CHRIN()
}
return not cbm.READST()
}
sub read_palette() -> bool {
; load palette data from the currently active input file
; if palette_buffer_ptr is not 0, the palette data is read into that memory buffer,
; otherwise it is read directly into the palette in vram.
cx16.vaddr(1, $fa00+palette_start*2, 0, 1)
cx16.r3 = palette_buffer_ptr
cx16.r2L = lsb(palette_entries)
do {
cx16.r4L = cbm.CHRIN()
cx16.r4H = cbm.CHRIN()
if cx16.r3 {
pokew(cx16.r3, cx16.r4) ; into memory
cx16.r3+=2
} else {
cx16.VERA_DATA0 = cx16.r4L ; into vram
cx16.VERA_DATA0 = cx16.r4H
}
cx16.r2L--
} until cx16.r2L==0
return cbm.READST()==0 or cbm.READST()&$40 ; no error or eof?
}
sub read_bitmap_padded(ubyte vbank, uword vaddr, uword screenwidth) -> bool {
; load bitmap "stamp" into vram from the currently active input file
cx16.r3 = bytes_per_scanline(width) ; num bytes per image scanline
cx16.r2 = bytes_per_scanline(screenwidth) ; num bytes per screen scanline
repeat height {
cx16.vaddr(vbank, vaddr, 0, 1)
read_scanline(cx16.r3)
vaddr += cx16.r2
if_cs
vbank++
}
return cbm.READST()==0 or cbm.READST()&$40 ; no error or eof?
}
sub read_bitmap(ubyte vbank, uword vaddr) -> bool {
; load contiguous bitmap into vram from the currently active input file
cx16.r3 = bytes_per_scanline(width)
cx16.vaddr(vbank, vaddr, 0, 1)
repeat height
read_scanline(cx16.r3)
return cbm.READST()==0 or cbm.READST()&$40 ; no error or eof?
}
sub read_scanline(uword size) {
while size {
cx16.r0 = cx16.MACPTR(min(255, size) as ubyte, &cx16.VERA_DATA0, true)
if_cs {
; no MACPTR support
repeat size
cx16.VERA_DATA0 = cbm.CHRIN()
return
}
size -= cx16.r0
}
}
sub write_header() -> bool {
; save out the BMX header to the currently active output file
build_header()
for cx16.r0L in 0 to sizeof(header)-1 {
cbm.CHROUT(header[cx16.r0L])
}
return not cbm.READST()
}
sub write_palette() -> bool {
; write palette data to the currently active input file
; if palette_buffer_ptr is not 0, the palette data is read from that memory buffer,
; otherwise it is read directly from the palette in vram.
cx16.r3 = palette_buffer_ptr
cx16.r2L = lsb(palette_entries)
cx16.vaddr(1, $fa00+palette_start*2, 0, 1)
do {
if cx16.r3 {
cbm.CHROUT(@(cx16.r3)) ; from memory
cx16.r3++
cbm.CHROUT(@(cx16.r3))
cx16.r3++
} else {
cbm.CHROUT(cx16.VERA_DATA0) ; from vram
cbm.CHROUT(cx16.VERA_DATA0)
}
cx16.r2L--
} until cx16.r2L==0
return not cbm.READST()
}
sub write_bitmap(ubyte vbank, uword vaddr, uword screenwidth) -> bool {
; screenwidth=0: save contiguous bitmap from vram to the currently active output file
; screenwidth>0: save "stamp" bitmap from vram to the currently active output file
cx16.vaddr(vbank, vaddr, 0, 1)
cx16.r3 = bytes_per_scanline(width) ; num bytes per image scanline
cx16.r2 = 0
if screenwidth
cx16.r2 = bytes_per_scanline(screenwidth-width) ; num bytes padding per screen scanline
repeat height {
write_scanline(cx16.r3)
repeat cx16.r2
cx16.r0L = cx16.VERA_DATA0
}
return not cbm.READST()
sub write_scanline(uword size) {
while size {
cx16.r0L = lsb(size)
if msb(size)
cx16.r0L = 0 ; 256 bytes
cx16.r0 = cx16.MCIOUT(cx16.r0L, &cx16.VERA_DATA0, true)
if_cs {
; no MCIOUT support
repeat size
cbm.CHROUT(cx16.VERA_DATA0)
return
}
size -= cx16.r0
}
}
}
sub bytes_per_scanline(uword w) -> uword {
ubyte[4] shifts = [3,2,1,0]
return w >> shifts[vera_colordepth]
}
sub parse_header() -> bool {
if header[0]==FILEID[0] and header[1]==FILEID[1] and header[2]==FILEID[2] {
if header[3]==1 { ; only version 1 supported for now
bitsperpixel = header[4]
vera_colordepth = header[5]
width = peekw(&header+6)
height = peekw(&header+8)
palette_entries = header[10]
if palette_entries==0
palette_entries=256
palette_start = header[11]
; the data offset is not needed: data_offset = peekw(&header+12)
compression = header[14]
border = header[15]
return true
} else
error_message = "unsupported bmx file version"
} else
error_message = "invalid bmx file"
return false
}
sub build_header() {
; build the internal BMX header structure
; normally you don't have to call this yourself
sys.memset(header, sizeof(header), 0)
uword data_offset = palette_entries*$0002
data_offset += sizeof(header)
header[0] = FILEID[0]
header[1] = FILEID[1]
header[2] = FILEID[2]
header[3] = 1 ; version 1
header[4] = bitsperpixel
header[5] = vera_colordepth
header[6] = lsb(width)
header[7] = msb(width)
header[8] = lsb(height)
header[9] = msb(height)
header[10] = lsb(palette_entries)
header[11] = palette_start
header[12] = lsb(data_offset)
header[13] = msb(data_offset)
header[14] = compression
header[15] = border
}
}
+4 -5
View File
@@ -21,11 +21,7 @@ diskio {
const ubyte READ_IO_CHANNEL=12
const ubyte WRITE_IO_CHANNEL=13
ubyte drivenumber = 8
sub set_drive(ubyte number) {
drivenumber = number
}
ubyte drivenumber = 8 ; user programs can set this to the drive number they want to load/save to!
sub reset_read_channel() {
void cbm.CHKIN(READ_IO_CHANNEL)
@@ -151,6 +147,7 @@ io_error:
; Also sets carry on exit: Carry clear = all files returned, Carry set = directory has more files that didn't fit in the buffer.
uword buffer_start = filenames_buffer
ubyte files_found = 0
filenames_buffer[0]=0
if lf_start_list(pattern_ptr) {
while lf_next_entry() {
if list_filetype!="dir" {
@@ -285,6 +282,8 @@ close_end:
; note: only a single iteration loop can be active at a time!
; Returns true if the file is successfully opened and readable.
; No need to check status(), unlike f_open_w() !
; NOTE: the default input isn't yet set to this logical file, you must use reset_read_channel() to do this,
; if you're going to read from it yourself instead of using f_read()!
f_close()
cbm.SETNAM(string.length(filenameptr), filenameptr)
+21 -23
View File
@@ -24,39 +24,37 @@ emudbg {
sub is_emulator() -> bool {
; Test for emulator presence.
; It is recommended to only use the other debug routines if this returns true.
; It is recommended to only use the debug registers if this returns true.
return EMU_EMU_DETECT1=='1' and EMU_EMU_DETECT2=='6'
}
asmsub console_write(str isoString @R0) clobbers(Y) {
sub console_write(str isoString) {
; note: make sure the text is in Iso encoding.
%asm {{
ldy #0
- lda (cx16.r0),y
beq +
sta p8_EMU_CPUCLK_U
iny
bne -
+
}}
if is_emulator() {
ubyte chr
repeat {
chr = @(isoString)
if_z
break
EMU_CPUCLK_U = chr
isoString++
}
}
}
asmsub console_chrout(ubyte char @A) {
sub console_chrout(ubyte char) {
; note: make sure the character is in Iso encoding.
%asm {{
sta p8_EMU_CPUCLK_U
}}
if is_emulator()
EMU_CPUCLK_U = char
}
asmsub console_value1(ubyte value @A) {
%asm {{
sta p8_EMU_CPUCLK_M
}}
sub console_value1(ubyte value) {
if is_emulator()
EMU_CPUCLK_M = value
}
asmsub console_value2(ubyte value @A) {
%asm {{
sta p8_EMU_CPUCLK_H
}}
sub console_value2(ubyte value) {
if is_emulator()
EMU_CPUCLK_H = value
}
}
+42 -11
View File
@@ -8,8 +8,9 @@ floats {
%option no_symbol_prefixing
const float PI = 3.141592653589793
const float TWOPI = 6.283185307179586
const float π = 3.141592653589793
const float PI = π
const float TWOPI = 2*π
; ---- ROM float functions (same as on C128 except base page) ----
@@ -28,7 +29,7 @@ romsub $fe00 = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 100-101
romsub $fe03 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
romsub $fe06 = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY
; romsub $fe09 = VAL_1() clobbers(A,X,Y) ; convert ASCII string to floating point [not yet implemented!!!] see parse_f() instead
romsub $fe09 = VAL_1(uword string @XY, ubyte length @A) clobbers(A,X,Y) -> float @FAC1 ; convert ASCII string in XY and length in A, to floating point in FAC1. WARNING: not implemented in the ROM yet!
; GETADR: fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
; (tip: use GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
@@ -71,10 +72,10 @@ romsub $fe6c = MOVAF() clobbers(A,X) ; copy fac1 to fac2
; X16 additions
romsub $fe6f = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for rounding- call this before INT
romsub $fe72 = ZEROFC() clobbers(A,X,Y) ; fac1 = 0
romsub $fe75 = NORMAL() clobbers(A,X,Y) ; normalize fac1 (?)
romsub $fe78 = NEGFAC() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1) (juse use NEGOP() instead!)
romsub $fe75 = NORMAL() clobbers(A,X,Y) ; normalize fac1
romsub $fe78 = NEGFAC() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1) (doesn't work, juse use NEGOP() instead!)
romsub $fe7b = MUL10() clobbers(A,X,Y) ; fac1 *= 10
romsub $fe7e = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive!
romsub $fe7e = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive! Have to restore sign manually!
romsub $fe81 = MOVEF() clobbers(A,X) ; copy fac1 to fac2
romsub $fe84 = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
romsub $fe87 = FLOAT() clobbers(A,X,Y) ; FAC = (s8).A
@@ -138,14 +139,38 @@ asmsub FREADUY (ubyte value @Y) {
asmsub parse_f(str value @AY) -> float @FAC1 {
; -- parse a string value of a number to float in FAC1
; warning: uses an internal BASIC routine that may be rom version dependent
; ($ddf2 is inside the routine for VAL at $ddef)
; warning: on older <R47 kernals it uses an internal BASIC routine that is ROM version dependent,
; ($deb6 is inside the routine for VAL at $deb3) See basic.sym from x16-rom
; TODO once VAL_1 is merged into the kernal properly, remove all the workarounds here
%asm {{
sta $a9
sty $aa
ldx VAL_1
cpx #$4c ; is there an implementation in VAL_1? (test for JMP)
bne + ; no, do it ourselves
pha ; yes, count the length and call rom VAL_1.
phy
jsr prog8_lib.strlen
tya
jmp $ddf2
ply
plx
jmp VAL_1
+ sta $a9 ; 'index' variable
sty $aa
jsr prog8_lib.strlen
lda $deb6
cmp #$d0 ; sanity check for kernal routine correct
bne +
tya
jmp $deb6 ; kernal version dependent...
+ ; print error message if routine is borked in kernal, and exit program
ldy #0
- lda _msg,y
beq +
jsr cbm.CHROUT
iny
bne -
+ jmp sys.exit
_msg .text 13,"?val kaputt",13,0
}}
}
@@ -159,6 +184,12 @@ sub rndf() -> float {
}}
}
asmsub normalize(float value @FAC1) -> float @ FAC1 {
%asm {{
jmp floats.NORMAL
}}
}
%asminclude "library:c64/floats.asm"
%asminclude "library:c64/floats_funcs.asm"
+2
View File
@@ -936,6 +936,8 @@ skip:
}
asmsub cs_innerloop640(ubyte color @A) clobbers(Y) {
; using verafx 32 bits writes here would make this faster but it's safer to
; use verafx only explicitly when you know what you're doing.
%asm {{
ldy #80
- sta cx16.VERA_DATA0
+2
View File
@@ -890,6 +890,8 @@ skip:
}
asmsub cs_innerloop640(ubyte color @A) clobbers(Y) {
; using verafx 32 bits writes here would make this faster but it's safer to
; use verafx only explicitly when you know what you're doing.
%asm {{
cmp #0
beq +
+88 -98
View File
@@ -1,11 +1,11 @@
; Manipulate the Commander X16's display color palette.
; Should you want to restore the default palette, you have to reinitialize the Vera yourself.
; Should you want to restore the full default palette, you can call cbm.CINT()
; The first 16 colors can be restored to their default with set_default16()
palette {
%option no_symbol_prefixing
uword vera_palette_ptr
ubyte cc
sub set_color(ubyte index, uword color) {
vera_palette_ptr = $fa00+(index as uword * 2)
@@ -42,19 +42,30 @@ palette {
ubyte red
ubyte greenblue
repeat num_colors {
red = @(palette_bytes_ptr) >> 4
palette_bytes_ptr++
greenblue = @(palette_bytes_ptr) & %11110000
palette_bytes_ptr++
greenblue |= @(palette_bytes_ptr) >> 4 ; add Blue
palette_bytes_ptr++
cx16.vpoke(1, vera_palette_ptr, greenblue)
cx16.r1 = color8to4(palette_bytes_ptr)
palette_bytes_ptr+=3
cx16.vpoke(1, vera_palette_ptr, cx16.r1H) ; $GB
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, red)
cx16.vpoke(1, vera_palette_ptr, cx16.r1L) ; $0R
vera_palette_ptr++
}
}
sub color8to4(uword colorpointer) -> uword {
; accurately convert 24 bits (3 bytes) RGB color, in that order in memory, to 16 bits $GB;$0R colorvalue
cx16.r1 = colorpointer
cx16.r0 = channel8to4(@(cx16.r1)) ; (red) -> $00:0R
cx16.r0H = channel8to4(@(cx16.r1+1))<<4 ; (green) -> $G0:0R
cx16.r0H |= channel8to4(@(cx16.r1+2)) ; (blue) -> $GB:0R
return cx16.r0
}
sub channel8to4(ubyte channelvalue) -> ubyte {
; accurately convert a single 8 bit color channel value to 4 bits, see https://threadlocalmutex.com/?p=48
return msb(channelvalue * $000f + 135)
}
sub set_monochrome(uword screencolorRGB, uword drawcolorRGB) {
vera_palette_ptr = $fa00
cx16.vpoke(1, vera_palette_ptr, lsb(screencolorRGB)) ; G,B
@@ -78,105 +89,84 @@ palette {
}
sub set_grayscale() {
; set first 16 colors to a grayscale gradient from black to white
vera_palette_ptr = $fa00
cc=0
cx16.r2L=0
repeat 16 {
cx16.vpoke(1, vera_palette_ptr, cc)
cx16.vpoke(1, vera_palette_ptr, cx16.r2L)
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, cc)
cx16.vpoke(1, vera_palette_ptr, cx16.r2L)
vera_palette_ptr++
cc += $11
cx16.r2L += $11
}
}
uword[] C64_colorpalette_dark = [ ; this is a darker palette with more contrast
$000, ; 0 = black
$FFF, ; 1 = white
$632, ; 2 = red
$7AB, ; 3 = cyan
$638, ; 4 = purple
$584, ; 5 = green
$327, ; 6 = blue
$BC6, ; 7 = yellow
$642, ; 8 = orange
$430, ; 9 = brown
$965, ; 10 = light red
$444, ; 11 = dark grey
$666, ; 12 = medium grey
$9D8, ; 13 = light green
$65B, ; 14 = light blue
$999 ; 15 = light grey
]
uword[] C64_colorpalette_pepto = [ ; # this is Pepto's Commodore-64 palette http://www.pepto.de/projects/colorvic/
$000, ; 0 = black
$FFF, ; 1 = white
$833, ; 2 = red
$7cc, ; 3 = cyan
$839, ; 4 = purple
$5a4, ; 5 = green
$229, ; 6 = blue
$ef7, ; 7 = yellow
$852, ; 8 = orange
$530, ; 9 = brown
$c67, ; 10 = light red
$444, ; 11 = dark grey
$777, ; 12 = medium grey
$af9, ; 13 = light green
$76e, ; 14 = light blue
$bbb ; 15 = light grey
]
uword[] C64_colorpalette_light = [ ; this is a lighter palette
$000, ; 0 = black
$FFF, ; 1 = white
$944, ; 2 = red
$7CC, ; 3 = cyan
$95A, ; 4 = purple
$6A5, ; 5 = green
$549, ; 6 = blue
$CD8, ; 7 = yellow
$963, ; 8 = orange
$650, ; 9 = brown
$C77, ; 10 = light red
$666, ; 11 = dark grey
$888, ; 12 = medium grey
$AE9, ; 13 = light green
$87C, ; 14 = light blue
$AAA ; 15 = light grey
]
sub set_c64pepto() {
vera_palette_ptr = $fa00
for cc in 0 to 15 {
uword ccp = C64_colorpalette_pepto[cc]
cx16.vpoke(1, vera_palette_ptr, lsb(ccp)) ; G, B
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, msb(ccp)) ; R
vera_palette_ptr++
}
; set first 16 colors to the "Pepto" PAL commodore-64 palette http://www.pepto.de/projects/colorvic/
uword[] colors = [
$000, ; 0 = black
$FFF, ; 1 = white
$833, ; 2 = red
$7cc, ; 3 = cyan
$839, ; 4 = purple
$5a4, ; 5 = green
$229, ; 6 = blue
$ef7, ; 7 = yellow
$852, ; 8 = orange
$530, ; 9 = brown
$c67, ; 10 = light red
$444, ; 11 = dark grey
$777, ; 12 = medium grey
$af9, ; 13 = light green
$76e, ; 14 = light blue
$bbb ; 15 = light grey
]
set_rgb(colors, len(colors))
}
sub set_c64light() {
vera_palette_ptr = $fa00
for cc in 0 to 15 {
uword ccp = C64_colorpalette_light[cc]
cx16.vpoke(1, vera_palette_ptr, lsb(ccp)) ; G, B
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, msb(ccp)) ; R
vera_palette_ptr++
}
sub set_c64ntsc() {
; set first 16 colors to a NTSC commodore-64 palette
uword[] colors = [
$000, ; 0 = black
$FFF, ; 1 = white
$934, ; 2 = red
$9ff, ; 3 = cyan
$73f, ; 4 = purple
$4b1, ; 5 = green
$20c, ; 6 = blue
$ee6, ; 7 = yellow
$b53, ; 8 = orange
$830, ; 9 = brown
$f8a, ; 10 = light red
$444, ; 11 = dark grey
$999, ; 12 = medium grey
$9f9, ; 13 = light green
$36f, ; 14 = light blue
$ccc ; 15 = light grey
]
set_rgb(colors, len(colors))
}
sub set_c64dark() {
vera_palette_ptr = $fa00
for cc in 0 to 15 {
uword ccp = C64_colorpalette_dark[cc]
cx16.vpoke(1, vera_palette_ptr, lsb(ccp)) ; G, B
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, msb(ccp)) ; R
vera_palette_ptr++
}
sub set_default16() {
; set first 16 colors to the defaults on the X16
uword[] colors = [
$000, ; 0 = black
$fff, ; 1 = white
$800, ; 2 = red
$afe, ; 3 = cyan
$c4c, ; 4 = purple
$0c5, ; 5 = green
$00a, ; 6 = blue
$ee7, ; 7 = yellow
$d85, ; 8 = orange
$640, ; 9 = brown
$f77, ; 10 = light red
$333, ; 11 = dark grey
$777, ; 12 = medium grey
$af6, ; 13 = light green
$08f, ; 14 = light blue
$bbb ; 15 = light grey
]
set_rgb(colors, len(colors))
}
}
+3 -2
View File
@@ -104,11 +104,11 @@ psg {
}
}
sub envelopes_irq() {
sub envelopes_irq() -> bool {
; If you want to use real-time volume envelopes (Attack-Sustain-Release),
; you have to call this routine every 1/60th second, for example from your vsync irq handler,
; or just install this routine as the only irq handler if you don't have to do other things there.
; Example: cx16.set_irq(&psg.envelopes_irq, true)
; Example: cx16.set_vsync_irq_handler(&psg.envelopes_irq)
; NOTE: this routine calls save/restore_vera_context() for you, don't nest this or call it yourself!
; cx16.r0 = the volume word (volume scaled by 256)
@@ -170,6 +170,7 @@ psg {
pop(cx16.r2L)
pop(cx16.r1L)
popw(cx16.r0)
return true ; run the system IRQ handler afterwards
}
ubyte[16] envelope_states
+27
View File
@@ -81,6 +81,33 @@ sprites {
cx16.vpoke(1, sprite_reg+1, msb(ypos))
}
sub move(ubyte spritenum, word dx, word dy) {
; move a sprite based on its current position
sprite_reg = VERA_SPRITEREGS + 2 + spritenum*$0008
cx16.r1 = mkword(cx16.vpeek(1, sprite_reg+1), cx16.vpeek(1, sprite_reg)) as word + dx
cx16.r2 = mkword(cx16.vpeek(1, sprite_reg+3), cx16.vpeek(1, sprite_reg+2)) as word + dy
cx16.vpoke(1, sprite_reg, cx16.r1L)
cx16.vpoke(1, sprite_reg+1, cx16.r1H)
cx16.vpoke(1, sprite_reg+2, cx16.r2L)
cx16.vpoke(1, sprite_reg+3, cx16.r2H)
}
sub movex(ubyte spritenum, word dx) {
; move a sprite horizontally based on its current position
sprite_reg = VERA_SPRITEREGS + 2 + spritenum*$0008
cx16.r1 = mkword(cx16.vpeek(1, sprite_reg+1), cx16.vpeek(1, sprite_reg)) as word + dx
cx16.vpoke(1, sprite_reg, cx16.r1L)
cx16.vpoke(1, sprite_reg+1, cx16.r1H)
}
sub movey(ubyte spritenum, word dy) {
; move a sprite vertically based on its current position
sprite_reg = VERA_SPRITEREGS + 4 + spritenum*$0008
cx16.r1 = mkword(cx16.vpeek(1, sprite_reg+1), cx16.vpeek(1, sprite_reg)) as word + dy
cx16.vpoke(1, sprite_reg, cx16.r1L)
cx16.vpoke(1, sprite_reg+1, cx16.r1H)
}
sub getx(ubyte spritenum) -> word {
sprite_reg = VERA_SPRITEREGS + 2 + spritenum*$0008
return mkword(cx16.vpeek(1, sprite_reg+1), cx16.vpeek(1, sprite_reg)) as word
+314 -101
View File
@@ -21,7 +21,7 @@ romsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA
romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
romsub $FF99 = MEMTOP(uword address @ XY, bool dir @ Pc) -> uword @ XY ; read/set top of memory pointer. NOTE: as a Cx16 extension, also returns the number of RAM memory banks in register A ! See cx16.numbanks()
romsub $FF9C = MEMBOT(uword address @ XY, bool dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard, also called kbd_scan
romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
romsub $FFA5 = ACPTR() -> ubyte @ A ; (alias: IECIN) input byte from serial bus
romsub $FFA8 = CIOUT(ubyte databyte @ A) ; (alias: IECOUT) output byte to serial bus
@@ -120,8 +120,8 @@ cx16 {
&uword RESET_VEC = $FFFC ; 65c02 reset vector, determined by the kernal if banked in
&uword IRQ_VEC = $FFFE ; 65c02 interrupt vector, determined by the kernal if banked in
&uword edkeyvec = $ac03 ; for intercepting BASIN/CHRIN key strokes. See set_basin_handler()
&uword edkeybk = $ac05 ; ...the RAM bank of this routine if not in low ram
&uword edkeyvec = $ac03 ; (ram bank 0): for intercepting BASIN/CHRIN key strokes. See set_chrin_keyhandler()
&ubyte edkeybk = $ac05 ; ...the RAM bank of the handler routine, if not in low ram
; the sixteen virtual 16-bit registers in both normal unsigned mode and signed mode (s)
@@ -238,7 +238,8 @@ cx16 {
&ubyte VERA_CTRL = VERA_BASE + $0005
&ubyte VERA_IEN = VERA_BASE + $0006
&ubyte VERA_ISR = VERA_BASE + $0007
&ubyte VERA_IRQ_LINE_L = VERA_BASE + $0008
&ubyte VERA_IRQLINE_L = VERA_BASE + $0008 ; write only
&ubyte VERA_SCANLINE_L = VERA_BASE + $0008 ; read only
&ubyte VERA_DC_VIDEO = VERA_BASE + $0009 ; DCSEL= 0
&ubyte VERA_DC_HSCALE = VERA_BASE + $000A ; DCSEL= 0
&ubyte VERA_DC_VSCALE = VERA_BASE + $000B ; DCSEL= 0
@@ -271,14 +272,29 @@ cx16 {
&ubyte VERA_SPI_DATA = VERA_BASE + $001E
&ubyte VERA_SPI_CTRL = VERA_BASE + $001F
; experimental Vera FX registers: (depends on particular value set in VERA_CTRL!!!)
; Vera FX registers: (accessing depends on particular DCSEL value set in VERA_CTRL!)
&ubyte VERA_FX_CTRL = VERA_BASE + $0009
&ubyte VERA_FX_MULT = VERA_BASE + $000C
&ubyte VERA_FX_TILEBASE = VERA_BASE + $000a
&ubyte VERA_FX_MAPBASE = VERA_BASE + $000b
&ubyte VERA_FX_MULT = VERA_BASE + $000c
&ubyte VERA_FX_X_INCR_L = VERA_BASE + $0009
&ubyte VERA_FX_X_INCR_H = VERA_BASE + $000a
&ubyte VERA_FX_Y_INCR_L = VERA_BASE + $000b
&ubyte VERA_FX_Y_INCR_H = VERA_BASE + $000c
&ubyte VERA_FX_X_POS_L = VERA_BASE + $0009
&ubyte VERA_FX_X_POS_H = VERA_BASE + $000a
&ubyte VERA_FX_Y_POS_L = VERA_BASE + $000b
&ubyte VERA_FX_Y_POS_H = VERA_BASE + $000c
&ubyte VERA_FX_X_POS_S = VERA_BASE + $0009
&ubyte VERA_FX_Y_POS_S = VERA_BASE + $000a
&ubyte VERA_FX_POLY_FILL_L = VERA_BASE + $000b
&ubyte VERA_FX_POLY_FILL_H = VERA_BASE + $000c
&ubyte VERA_FX_CACHE_L = VERA_BASE + $0009
&ubyte VERA_FX_CACHE_M = VERA_BASE + $000A
&ubyte VERA_FX_CACHE_H = VERA_BASE + $000B
&ubyte VERA_FX_CACHE_U = VERA_BASE + $000C
&ubyte VERA_FX_ACCUM_RESET = VERA_BASE + $0009 ; (DCSEL=6)
&ubyte VERA_FX_CACHE_M = VERA_BASE + $000a
&ubyte VERA_FX_CACHE_H = VERA_BASE + $000b
&ubyte VERA_FX_CACHE_U = VERA_BASE + $000c
&ubyte VERA_FX_ACCUM = VERA_BASE + $000a
&ubyte VERA_FX_ACCUM_RESET = VERA_BASE + $0009
; VERA_PSG_BASE = $1F9C0
@@ -399,8 +415,8 @@ romsub $fecc = monitor() clobbers(A,X,Y)
romsub $ff44 = MACPTR(ubyte length @A, uword buffer @XY, bool dontAdvance @Pc) clobbers(A) -> bool @Pc, uword @XY
romsub $feb1 = MCIOUT(ubyte length @A, uword buffer @XY, bool dontAdvance @Pc) clobbers(A) -> bool @Pc, uword @XY
romsub $ff47 = enter_basic(bool cold_or_warm @Pc) clobbers(A,X,Y)
romsub $ff4d = clock_set_date_time(uword yearmonth @R0, uword dayhours @R1, uword minsecs @R2, ubyte jiffies @R3) clobbers(A, X, Y)
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) -> uword @R0, uword @R1, uword @R2, ubyte @R3 ; result registers see clock_set_date_time()
romsub $ff4d = clock_set_date_time(uword yearmonth @R0, uword dayhours @R1, uword minsecs @R2, uword jiffiesweekday @R3) clobbers(A, X, Y)
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) -> uword @R0, uword @R1, uword @R2, uword @R3 ; result registers see clock_set_date_time()
; keyboard, mouse, joystick
; note: also see the kbdbuf_clear() helper routine below!
@@ -567,7 +583,6 @@ asmsub vaddr(ubyte bank @A, uword address @R0, ubyte addrsel @R1, byte autoIncrO
asmsub vaddr_clone(ubyte port @A) clobbers (A,X,Y) {
; -- clones Vera addresses from the given source port to the other one.
; leaves CTRL on the destination port.
%asm {{
sta VERA_CTRL
ldx VERA_ADDR_L
@@ -580,6 +595,8 @@ asmsub vaddr_clone(ubyte port @A) clobbers (A,X,Y) {
sty VERA_ADDR_M
ply
sty VERA_ADDR_H
eor #1
sta VERA_CTRL
rts
}}
}
@@ -832,14 +849,16 @@ asmsub restore_vera_context() clobbers(A) {
asmsub set_chrin_keyhandler(ubyte handlerbank @A, uword handler @XY) clobbers(A) {
; Install a custom CHRIN (BASIN) key handler. Call this before each line you want to read.
; Install a custom CHRIN (BASIN) key handler in a safe manner. Call this before each line you want to read.
; See https://github.com/X16Community/x16-docs/blob/master/X16%20Reference%20-%2002%20-%20Editor.md#custom-basin-petscii-code-override-handler
%asm {{
sei
sta cx16.edkeybk
sta P8ZP_SCRATCH_REG
lda $00
pha
stz $00
lda P8ZP_SCRATCH_REG
sta cx16.edkeybk
stx cx16.edkeyvec
sty cx16.edkeyvec+1
pla
@@ -848,8 +867,199 @@ asmsub restore_vera_context() clobbers(A) {
rts
}}
}
asmsub get_chrin_keyhandler() -> ubyte @R0, uword @R1 {
; --- retrieve the currently set CHRIN keyhandler in a safe manner, bank in r0L, handler address in R1.
%asm {{
sei
lda $00
pha
stz $00
lda cx16.edkeybk
sta cx16.r0L
lda cx16.edkeyvec
ldy cx16.edkeyvec+1
sta cx16.r1
sty cx16.r1+1
pla
sta $00
cli
rts
}}
}
; Commander X16 IRQ dispatcher routines
inline asmsub disable_irqs() clobbers(A) {
; Disable all Vera IRQ sources. Note that it does NOT set the CPU IRQ disabled status bit!
%asm {{
lda #%00001111
trb cx16.VERA_IEN
}}
}
asmsub enable_irq_handlers(bool disable_all_irq_sources @Pc) clobbers(A,Y) {
; Install the "master IRQ handler" that will dispatch IRQs
; to the registered handler for each type. (Only Vera IRQs supported for now).
; The handlers don't need to clear its ISR bit, but have to return 0 or 1 in A,
; where 1 means: continue with the system IRQ handler, 0 means: don't call that.
%asm {{
php
sei
bcc +
lda #%00001111
trb cx16.VERA_IEN ; disable all IRQ sources
+ lda #<_irq_dispatcher
ldy #>_irq_dispatcher
sta cx16.CINV
sty cx16.CINV+1
plp
rts
_irq_dispatcher
; order of handling: LINE, SPRCOL, AFLOW, VSYNC.
jsr sys.save_prog8_internals
cld
lda cx16.VERA_ISR
and cx16.VERA_IEN ; only consider the bits for sources that can actually raise the IRQ
bit #2
beq +
_mod_line_jump
jsr _default_line_handler ; modified
ldy #2
sty cx16.VERA_ISR
bra _dispatch_end
+
bit #4
beq +
_mod_sprcol_jump
jsr _default_sprcol_handler ; modified
ldy #4
sty cx16.VERA_ISR
bra _dispatch_end
+
bit #8
beq +
_mod_aflow_jump
jsr _default_aflow_handler ; modified
; note: AFLOW can only be cleared by filling the audio FIFO for at least 1/4. Not via the ISR bit.
bra _dispatch_end
+
bit #1
beq +
_mod_vsync_jump
jsr _default_vsync_handler ; modified
cmp #0
bne _dispatch_end
ldy #1
sty cx16.VERA_ISR
bra _return_irq
+
lda #0
_dispatch_end
cmp #0
beq _return_irq
jsr sys.restore_prog8_internals
jmp (sys.restore_irq._orig_irqvec) ; continue with normal kernal irq routine
_return_irq
jsr sys.restore_prog8_internals
ply
plx
pla
rti
_default_vsync_handler
lda #1
rts
_default_line_handler
lda #0
rts
_default_sprcol_handler
lda #0
rts
_default_aflow_handler
lda #0
rts
}}
}
asmsub set_vsync_irq_handler(uword address @AY) clobbers(A) {
; Sets the VSYNC irq handler to use with enable_irq_handlers(). Also enables VSYNC irqs.
; NOTE: unless a proper irq handler is already running, you should enclose this call in set_irqd() / clear_irqd() to avoid system crashes.
%asm {{
php
sei
sta enable_irq_handlers._mod_vsync_jump+1
sty enable_irq_handlers._mod_vsync_jump+2
lda #1
tsb cx16.VERA_IEN
plp
rts
}}
}
asmsub set_line_irq_handler(uword rasterline @R0, uword address @AY) clobbers(A,Y) {
; Sets the LINE irq handler to use with enable_irq_handlers(), for the given rasterline. Also enables LINE irqs.
; You can use sys.set_rasterline() later to adjust the rasterline on which to trigger.
; NOTE: unless a proper irq handler is already running, you should enclose this call in set_irqd() / clear_irqd() to avoid system crashes.
%asm {{
php
sei
sta enable_irq_handlers._mod_line_jump+1
sty enable_irq_handlers._mod_line_jump+2
lda cx16.r0
ldy cx16.r0+1
jsr sys.set_rasterline
lda #2
tsb cx16.VERA_IEN
plp
rts
}}
}
asmsub set_sprcol_irq_handler(uword address @AY) clobbers(A) {
; Sets the SPRCOL irq handler to use with enable_irq_handlers(). Also enables SPRCOL irqs.
; NOTE: unless a proper irq handler is already running, you should enclose this call in set_irqd() / clear_irqd() to avoid system crashes.
%asm {{
php
sei
sta enable_irq_handlers._mod_sprcol_jump+1
sty enable_irq_handlers._mod_sprcol_jump+2
lda #4
tsb cx16.VERA_IEN
plp
rts
}}
}
asmsub set_aflow_irq_handler(uword address @AY) clobbers(A) {
; Sets the AFLOW irq handler to use with enable_irq_handlers(). Also enables AFLOW irqs.
; NOTE: unless a proper irq handler is already running, you should enclose this call in set_irqd() / clear_irqd() to avoid system crashes.
%asm {{
php
sei
sta enable_irq_handlers._mod_aflow_jump+1
sty enable_irq_handlers._mod_aflow_jump+2
lda #8
tsb cx16.VERA_IEN
plp
rts
}}
}
inline asmsub disable_irq_handlers() {
; back to the system default IRQ handler.
%asm {{
jsr sys.restore_irq
}}
}
}
sys {
; ------- lowlevel system routines --------
@@ -929,79 +1139,38 @@ asmsub cleanup_at_exit() {
}}
}
asmsub set_irq(uword handler @AY, bool useKernal @Pc) clobbers(A) {
asmsub set_irq(uword handler @AY) clobbers(A) {
; Sets the handler for the VSYNC interrupt, and enable that interrupt.
%asm {{
sta _modified+1
sty _modified+2
lda #0
rol a
sta _use_kernal
sei
lda #<_irq_handler
sta cx16.CINV
lda #>_irq_handler
sta cx16.CINV+1
lda cx16.VERA_IEN
ora #%00000001 ; enable the vsync irq
sta cx16.VERA_IEN
cli
rts
sei
sta _modified+1
sty _modified+2
lda #<_irq_handler
sta cx16.CINV
lda #>_irq_handler
sta cx16.CINV+1
lda #1
tsb cx16.VERA_IEN ; enable the vsync irq
cli
rts
_irq_handler jsr _irq_handler_init
_modified jsr $ffff ; modified
jsr _irq_handler_end
lda _use_kernal
bne +
; end irq processing - don't use kernal's irq handling
lda #1
_irq_handler
jsr sys.save_prog8_internals
cld
_modified
jsr $ffff ; modified
pha
jsr sys.restore_prog8_internals
pla
beq +
jmp (restore_irq._orig_irqvec) ; continue with normal kernal irq routine
+ lda #1
sta cx16.VERA_ISR ; clear Vera Vsync irq status
ply
plx
pla
rti
+ jmp (restore_irq._orig_irqvec) ; continue with normal kernal irq routine
_use_kernal .byte 0
_irq_handler_init
; save all zp scratch registers as these might be clobbered by the irq routine
lda P8ZP_SCRATCH_B1
sta IRQ_SCRATCH_ZPB1
lda P8ZP_SCRATCH_REG
sta IRQ_SCRATCH_ZPREG
lda P8ZP_SCRATCH_W1
sta IRQ_SCRATCH_ZPWORD1
lda P8ZP_SCRATCH_W1+1
sta IRQ_SCRATCH_ZPWORD1+1
lda P8ZP_SCRATCH_W2
sta IRQ_SCRATCH_ZPWORD2
lda P8ZP_SCRATCH_W2+1
sta IRQ_SCRATCH_ZPWORD2+1
cld
rts
_irq_handler_end
; restore all zp scratch registers
lda IRQ_SCRATCH_ZPB1
sta P8ZP_SCRATCH_B1
lda IRQ_SCRATCH_ZPREG
sta P8ZP_SCRATCH_REG
lda IRQ_SCRATCH_ZPWORD1
sta P8ZP_SCRATCH_W1
lda IRQ_SCRATCH_ZPWORD1+1
sta P8ZP_SCRATCH_W1+1
lda IRQ_SCRATCH_ZPWORD2
sta P8ZP_SCRATCH_W2
lda IRQ_SCRATCH_ZPWORD2+1
sta P8ZP_SCRATCH_W2+1
rts
IRQ_SCRATCH_ZPB1 .byte 0
IRQ_SCRATCH_ZPREG .byte 0
IRQ_SCRATCH_ZPWORD1 .word 0
IRQ_SCRATCH_ZPWORD2 .word 0
}}
}}
}
asmsub restore_irq() clobbers(A) {
@@ -1012,8 +1181,8 @@ asmsub restore_irq() clobbers(A) {
lda _orig_irqvec+1
sta cx16.CINV+1
lda cx16.VERA_IEN
and #%11110000 ; disable all Vera IRQs
ora #%00000001 ; enable only the vsync Irq
and #%11110000 ; disable all Vera IRQs but the vsync
ora #%00000001
sta cx16.VERA_IEN
cli
rts
@@ -1022,15 +1191,16 @@ _orig_irqvec .word 0
}
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
; Sets the handler for the LINE interrupt, and enable (only) that interrupt.
%asm {{
sei
sta _modified+1
sty _modified+2
lda cx16.r0
ldy cx16.r0+1
sei
lda cx16.VERA_IEN
and #%11110000 ; clear other IRQs
ora #%00000010 ; enable the line (raster) irq
and #%11110000 ; disable all irqs but the line(raster) one
ora #%00000010
sta cx16.VERA_IEN
lda cx16.r0
ldy cx16.r0+1
@@ -1043,13 +1213,13 @@ asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
rts
_raster_irq_handler
jsr set_irq._irq_handler_init
_modified jsr $ffff ; modified
jsr set_irq._irq_handler_end
jsr sys.save_prog8_internals
cld
_modified jsr $ffff ; modified
jsr sys.restore_prog8_internals
; end irq processing - don't use kernal's irq handling
lda cx16.VERA_ISR
ora #%00000010
sta cx16.VERA_ISR ; clear Vera line irq status
lda #2
tsb cx16.VERA_ISR ; clear Vera line irq status
ply
plx
pla
@@ -1059,16 +1229,19 @@ _modified jsr $ffff ; modified
asmsub set_rasterline(uword line @AY) {
%asm {{
sta cx16.VERA_IRQ_LINE_L
lda cx16.VERA_IEN
and #%01111111
sta cx16.VERA_IEN
php
sei
sta cx16.VERA_IRQLINE_L
tya
lsr a
ror a
and #%10000000
ora cx16.VERA_IEN
sta cx16.VERA_IEN
bcs +
lda #%10000000
trb cx16.VERA_IEN
plp
rts
+ lda #%10000000
tsb cx16.VERA_IEN
plp
rts
}}
}
@@ -1282,7 +1455,47 @@ _longcopy
}}
}
inline asmsub exit(ubyte returnvalue @A) {
asmsub save_prog8_internals() {
%asm {{
lda P8ZP_SCRATCH_B1
sta save_SCRATCH_ZPB1
lda P8ZP_SCRATCH_REG
sta save_SCRATCH_ZPREG
lda P8ZP_SCRATCH_W1
sta save_SCRATCH_ZPWORD1
lda P8ZP_SCRATCH_W1+1
sta save_SCRATCH_ZPWORD1+1
lda P8ZP_SCRATCH_W2
sta save_SCRATCH_ZPWORD2
lda P8ZP_SCRATCH_W2+1
sta save_SCRATCH_ZPWORD2+1
rts
save_SCRATCH_ZPB1 .byte 0
save_SCRATCH_ZPREG .byte 0
save_SCRATCH_ZPWORD1 .word 0
save_SCRATCH_ZPWORD2 .word 0
}}
}
asmsub restore_prog8_internals() {
%asm {{
lda save_prog8_internals.save_SCRATCH_ZPB1
sta P8ZP_SCRATCH_B1
lda save_prog8_internals.save_SCRATCH_ZPREG
sta P8ZP_SCRATCH_REG
lda save_prog8_internals.save_SCRATCH_ZPWORD1
sta P8ZP_SCRATCH_W1
lda save_prog8_internals.save_SCRATCH_ZPWORD1+1
sta P8ZP_SCRATCH_W1+1
lda save_prog8_internals.save_SCRATCH_ZPWORD2
sta P8ZP_SCRATCH_W2
lda save_prog8_internals.save_SCRATCH_ZPWORD2+1
sta P8ZP_SCRATCH_W2+1
rts
}}
}
asmsub exit(ubyte returnvalue @A) {
; -- immediately exit the program with a return code in the A register
%asm {{
jsr cbm.CLRCHN ; reset i/o channels
+1 -1
View File
@@ -796,7 +796,7 @@ asmsub height() clobbers(X, Y) -> ubyte @A {
}}
}
asmsub waitkey() {
asmsub waitkey() -> ubyte @A {
%asm {{
- jsr cbm.GETIN
beq -
+4 -4
View File
@@ -39,19 +39,19 @@ verafx {
if (amountof32bits & %1111110000000011) == 0 {
repeat lsb(amountof32bits >> 2)
unroll 4 cx16.VERA_DATA0=0 ; write 4 bytes at a time, unrolled
unroll 4 cx16.VERA_DATA0=0 ; write 4*4 bytes at a time, unrolled
}
else if (amountof32bits & %1111111000000001) == 0 {
repeat lsb(amountof32bits >> 1)
unroll 2 cx16.VERA_DATA0=0 ; write 4 bytes at a time, unrolled
unroll 2 cx16.VERA_DATA0=0 ; write 2*4 bytes at a time, unrolled
}
else if (lsb(amountof32bits) & 3) == 0 {
repeat amountof32bits >> 2
unroll 4 cx16.VERA_DATA0=0 ; write 4 bytes at a time, unrolled
unroll 4 cx16.VERA_DATA0=0 ; write 4*4 bytes at a time, unrolled
}
else if (lsb(amountof32bits) & 1) == 0 {
repeat amountof32bits >> 1
unroll 2 cx16.VERA_DATA0=0 ; write 4 bytes at a time, unrolled
unroll 2 cx16.VERA_DATA0=0 ; write 2*4 bytes at a time, unrolled
}
else {
repeat amountof32bits
+10 -5
View File
@@ -10,11 +10,7 @@ diskio {
const ubyte READ_IO_CHANNEL=12
const ubyte WRITE_IO_CHANNEL=13
ubyte drivenumber = 8
sub set_drive(ubyte number) {
drivenumber = number
}
ubyte drivenumber = 8 ; user programs can set this to the drive number they want to load/save to!
sub reset_read_channel() {
void cbm.CHKIN(READ_IO_CHANNEL)
@@ -275,6 +271,10 @@ close_end:
sub f_open(uword filenameptr) -> bool {
; -- open a file for iterative reading with f_read
; note: only a single iteration loop can be active at a time!
; Returns true if the file is successfully opened and readable.
; No need to check status(), unlike f_open_w() !
; NOTE: the default input isn't yet set to this logical file, you must use reset_read_channel() to do this,
; if you're going to read from it yourself instead of using f_read()!
f_close()
cbm.SETNAM(string.length(filenameptr), filenameptr)
@@ -390,6 +390,11 @@ _end rts
sub f_open_w(uword filenameptr) -> bool {
; -- open a file for iterative writing with f_write
; WARNING: returns true if the open command was received by the device,
; but this can still mean the file wasn't successfully opened for writing!
; (for example, if it already exists). This is different than f_open()!
; To be 100% sure if this call was successful, you have to use status()
; and check the drive's status message!
f_close_w()
cbm.SETNAM(string.length(filenameptr), filenameptr)
+22 -8
View File
@@ -2,15 +2,10 @@ floats {
; the floating point functions shared across compiler targets
%option merge, no_symbol_prefixing
sub print_f(float value) {
; ---- prints the floating point value (without a newline).
asmsub print_f(float value @FAC1) clobbers(A,X,Y) {
; ---- prints the floating point value (without a newline). No leading space (unlike BASIC)!
%asm {{
lda #<value
ldy #>value
jsr MOVFM ; load float into fac1
jsr FOUT ; fac1 to string in A/Y
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
jsr str_f
ldy #0
- lda (P8ZP_SCRATCH_W1),y
beq +
@@ -21,6 +16,25 @@ sub print_f(float value) {
}}
}
asmsub str_f(float value @FAC1) clobbers(X) -> str @AY {
; ---- converts the floating point value to a string. No leading space!
%asm {{
jsr FOUT
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
lda (P8ZP_SCRATCH_W1),y
cmp #' '
bne +
inc P8ZP_SCRATCH_W1
bne +
inc P8ZP_SCRATCH_W1+1
+ lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
rts
}}
}
sub pow(float value, float power) -> float {
%asm {{
stx P8ZP_SCRATCH_W1
+40
View File
@@ -305,6 +305,46 @@ _longcopy
; PET doesn't have a key to swap case, so no-op
}
asmsub save_prog8_internals() {
%asm {{
lda P8ZP_SCRATCH_B1
sta save_SCRATCH_ZPB1
lda P8ZP_SCRATCH_REG
sta save_SCRATCH_ZPREG
lda P8ZP_SCRATCH_W1
sta save_SCRATCH_ZPWORD1
lda P8ZP_SCRATCH_W1+1
sta save_SCRATCH_ZPWORD1+1
lda P8ZP_SCRATCH_W2
sta save_SCRATCH_ZPWORD2
lda P8ZP_SCRATCH_W2+1
sta save_SCRATCH_ZPWORD2+1
rts
save_SCRATCH_ZPB1 .byte 0
save_SCRATCH_ZPREG .byte 0
save_SCRATCH_ZPWORD1 .word 0
save_SCRATCH_ZPWORD2 .word 0
}}
}
asmsub restore_prog8_internals() {
%asm {{
lda save_prog8_internals.save_SCRATCH_ZPB1
sta P8ZP_SCRATCH_B1
lda save_prog8_internals.save_SCRATCH_ZPREG
sta P8ZP_SCRATCH_REG
lda save_prog8_internals.save_SCRATCH_ZPWORD1
sta P8ZP_SCRATCH_W1
lda save_prog8_internals.save_SCRATCH_ZPWORD1+1
sta P8ZP_SCRATCH_W1+1
lda save_prog8_internals.save_SCRATCH_ZPWORD2
sta P8ZP_SCRATCH_W2
lda save_prog8_internals.save_SCRATCH_ZPWORD2+1
sta P8ZP_SCRATCH_W2+1
rts
}}
}
inline asmsub exit(ubyte returnvalue @A) {
; -- immediately exit the program with a return code in the A register
%asm {{
+1 -1
View File
@@ -495,7 +495,7 @@ asmsub height() clobbers(X, Y) -> ubyte @A {
}}
}
asmsub waitkey() {
asmsub waitkey() -> ubyte @A {
%asm {{
- jsr cbm.GETIN
beq -
+1 -1
View File
@@ -441,7 +441,7 @@ _return_minusone
strlen .proc
; -- returns the number of bytes in the string in AY, in Y.
; -- returns the number of bytes in the string in AY, in Y. Clobbers A.
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
+79
View File
@@ -361,4 +361,83 @@ fail clc ; yes, no match found, return with c=0
rts
}}
}
asmsub hash(str string @R0) -> ubyte @A {
; experimental 8 bit hashing function.
; hash(-1)=179; clear carry; hash(i) = ROL hash(i-1) XOR string[i]
; On the English word list in /usr/share/dict/words it seems to have a pretty even distribution
%asm {{
lda #179
sta P8ZP_SCRATCH_REG
ldy #0
clc
- lda (cx16.r0),y
beq +
rol P8ZP_SCRATCH_REG
eor P8ZP_SCRATCH_REG
sta P8ZP_SCRATCH_REG
iny
bne -
+ lda P8ZP_SCRATCH_REG
rts
}}
}
asmsub isdigit(ubyte character @A) -> bool @Pc {
%asm {{
cmp #'0'
bcs +
rts
+ cmp #'9'+1
bcc +
clc
rts
+ sec
rts
}}
}
asmsub isupper(ubyte character @A) -> bool @Pc {
; shifted petscii has 2 ranges that contain the upper case letters...
%asm {{
cmp #97
bcs +
rts
+ cmp #122+1
bcc _yes
cmp #193
bcs +
rts
+ cmp #218+1
bcc _yes
clc
rts
_yes sec
rts
}}
}
asmsub islower(ubyte character @A) -> bool @Pc {
%asm {{
cmp #'a'
bcs +
rts
+ cmp #'z'+1
bcc +
clc
rts
+ sec
rts
}}
}
asmsub isletter(ubyte character @A) -> bool @Pc {
%asm {{
jsr islower
bcs +
jmp isupper
+ rts
}}
}
}
+34
View File
@@ -0,0 +1,34 @@
; Emulator debug interface. (reflecting the Commander X16 emudbg library module)
; Docs: https://github.com/X16Community/x16-emulator#emulator-io-registers
%import textio
emudbg {
sub is_emulator() -> bool {
; Test for emulator presence.
return true ; VM is always 'emulator'
}
sub console_write(str message) {
txt.print("[EMUDBG: ")
txt.print(message)
txt.chrout(']')
}
sub console_chrout(ubyte char) {
txt.chrout(char)
}
sub console_value1(ubyte value) {
txt.print("[EMUDBG debug 1: ")
txt.print_uwhex(value, true)
txt.print("]\n")
}
sub console_value2(ubyte value) {
txt.print("[EMUDBG debug 2: ")
txt.print_uwhex(value, true)
txt.print("]\n")
}
}
+21 -3
View File
@@ -4,11 +4,13 @@
floats {
const float PI = 3.141592653589793
const float TWOPI = 6.283185307179586
const float π = 3.141592653589793
const float PI = π
const float TWOPI = 2*π
sub print_f(float value) {
; ---- prints the floating point value (without a newline).
; ---- prints the floating point value (without a newline and no leading spaces).
%ir {{
loadm.f fr65535,floats.print_f.value
syscall 25 (fr65535.f)
@@ -16,6 +18,18 @@ sub print_f(float value) {
}}
}
sub str_f(float value) -> str {
; ---- converts the floating point value to a string (no leading spaces)
str @shared buffer=" "*20
%ir {{
load.w r65535,floats.str_f.buffer
loadm.f fr65535,floats.str_f.value
syscall 47 (r65535.w, fr65535.f)
load.w r0,floats.str_f.buffer
returnr.w r0
}}
}
sub parse_f(str value) -> float {
; -- parse a string value of a number to float
%ir {{
@@ -155,4 +169,8 @@ sub clampf(float value, float minimum, float maximum) -> float {
return maximum
}
sub normalize(float value) -> float {
return value
}
}
+34 -1
View File
@@ -157,4 +157,37 @@ string {
return false
return compare(st + str_len - suffix_len, suffix) == 0
}
}
sub hash(str st) -> ubyte {
; experimental 8 bit hashing function.
; hash(-1)=179; hash(i) = ROL hash(i-1) XOR string[i]
; (experimental because the quality of the resulting hash value still has to be determined)
ubyte hashcode = 179
ubyte ix
sys.clear_carry()
repeat {
if st[ix] {
rol(hashcode)
hashcode ^= st[ix]
ix++
} else
return hashcode
}
}
sub isdigit(ubyte character) -> bool {
return character>='0' and character<='9'
}
sub isupper(ubyte character) -> bool {
return character>='A' and character<='Z'
}
sub islower(ubyte character) -> bool {
return character>='a' and character<='z'
}
sub isletter(ubyte character) -> bool {
return islower(character) or isupper(character)
}
}
+8
View File
@@ -87,6 +87,14 @@ sys {
; no-op
}
sub save_prog8_internals() {
; no-op
}
sub restore_prog8_internals() {
; no-op
}
sub gfx_enable(ubyte mode) {
%ir {{
loadm.b r65535,sys.gfx_enable.mode
@@ -5,11 +5,11 @@ package prog8.buildversion
*/
const val MAVEN_GROUP = "prog8"
const val MAVEN_NAME = "compiler"
const val VERSION = "9.6-SNAPSHOT"
const val GIT_REVISION = 4159
const val GIT_SHA = "335213b55f971452a05c904216cb0fd72f5518a1"
const val GIT_DATE = "2023-10-21T00:16:58Z"
const val VERSION = "9.7"
const val GIT_REVISION = 4286
const val GIT_SHA = "cfc9c15f1e9fe96940e170fb14ad3a957109977c"
const val GIT_DATE = "2023-12-10T15:22:00Z"
const val GIT_BRANCH = "master"
const val BUILD_DATE = "2023-10-21T20:22:14Z"
const val BUILD_UNIX_TIME = 1697919734197L
const val BUILD_DATE = "2023-12-10T15:44:19Z"
const val BUILD_UNIX_TIME = 1702223059842L
const val DIRTY = 1
@@ -105,20 +105,23 @@ private fun builtinAbs(args: List<Expression>, position: Position, program: Prog
}
private fun builtinSizeof(args: List<Expression>, position: Position, program: Program): NumericLiteral {
// 1 arg, type = anything, result type = ubyte
// 1 arg, type = anything, result type = ubyte or uword
if(args.size!=1)
throw SyntaxError("sizeof requires one argument", position)
if(args[0] !is IdentifierReference)
throw SyntaxError("sizeof argument should be an identifier", position)
if(args[0] !is IdentifierReference && args[0] !is NumericLiteral)
throw SyntaxError("sizeof argument should be an identifier or number", position)
val dt = args[0].inferType(program)
if(dt.isKnown) {
if(args[0] is NumericLiteral)
return NumericLiteral.optimalInteger(program.memsizer.memorySize(dt.getOr(DataType.UNDEFINED)), position)
val target = (args[0] as IdentifierReference).targetStatement(program)
?: throw CannotEvaluateException("sizeof", "no target")
return when {
dt.isArray -> {
val length = (target as VarDecl).arraysize!!.constIndex() ?: throw CannotEvaluateException("sizeof", "unknown array size")
val length = (target as VarDecl).arraysize?.constIndex() ?: throw CannotEvaluateException("sizeof", "unknown array size")
val elementDt = ArrayToElementTypes.getValue(dt.getOr(DataType.UNDEFINED))
NumericLiteral.optimalInteger(program.memsizer.memorySize(elementDt) * length, position)
}
+10 -6
View File
@@ -110,14 +110,18 @@ class ModuleImporter(private val program: Program,
// their content has to be merged into already existing other block with the same name.
val blocks = importedModule.statements.filterIsInstance<Block>()
for(block in blocks) {
if("merge" in block.options()) {
val existingBlock = program.allBlocks.firstOrNull { it.name==block.name && it !== block}
if(existingBlock!=null) {
val blockHasMergeOption = "merge" in block.options()
val existingBlock = program.allBlocks.firstOrNull { it.name==block.name && it !== block}
if(existingBlock!=null) {
val existingBlockHasMergeOption = "merge" in existingBlock.options()
if (blockHasMergeOption || existingBlockHasMergeOption) {
// transplant the contents
existingBlock.statements.addAll(block.statements.filter { it !is Directive })
importedModule.statements.remove(block)
} else {
val merges = block.statements.filter { it is Directive && it.directive=="%option" && it.args.any { a->a.name=="merge" } }
block.statements.removeAll(merges.toSet())
if(blockHasMergeOption && !existingBlockHasMergeOption) {
existingBlock.statements.add(0, Directive("%option", listOf(DirectiveArg(null, "merge", null, block.position)), block.position))
}
}
}
}
@@ -7,7 +7,6 @@ import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.code.core.*
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8.code.target.VMTarget
import prog8.compiler.builtinFunctionReturnType
@@ -56,13 +55,14 @@ internal class AstChecker(private val program: Program,
val directives = module.statements.filterIsInstance<Directive>().groupBy { it.directive }
directives.filter { it.value.size > 1 }.forEach{ entry ->
when(entry.key) {
"%output", "%launcher", "%zeropage", "%address" ->
"%output", "%launcher", "%zeropage", "%address", "%encoding" ->
entry.value.forEach { errors.err("directive can just occur once", it.position) }
}
}
}
override fun visit(identifier: IdentifierReference) {
checkLongType(identifier)
val stmt = identifier.targetStatement(program)
if(stmt==null)
errors.undefined(identifier.nameInSource, identifier.position)
@@ -280,6 +280,10 @@ internal class AstChecker(private val program: Program,
super.visit(label)
}
override fun visit(numLiteral: NumericLiteral) {
checkLongType(numLiteral)
}
private fun hasReturnOrJumpOrRts(scope: IStatementContainer): Boolean {
class Searcher: IAstVisitor
{
@@ -314,7 +318,7 @@ internal class AstChecker(private val program: Program,
err("cannot redefine a built-in function")
if(subroutine.parameters.size>6 && !subroutine.isAsmSubroutine && !subroutine.definingBlock.isInLibrary)
errors.warn("subroutine has a large number of parameters, this slows down code execution a lot", subroutine.position)
errors.warn("subroutine has a large number of parameters, this is slow if called often", subroutine.position)
val uniqueNames = subroutine.parameters.asSequence().map { it.name }.toSet()
if(uniqueNames.size!=subroutine.parameters.size)
@@ -529,6 +533,9 @@ internal class AstChecker(private val program: Program,
}
override fun visit(assignTarget: AssignTarget) {
if(assignTarget.inferType(program).istype(DataType.LONG))
errors.err("integer overflow", assignTarget.position)
super.visit(assignTarget)
val memAddr = assignTarget.memoryAddress?.addressExpression?.constValue(program)?.number?.toInt()
@@ -578,6 +585,7 @@ internal class AstChecker(private val program: Program,
}
override fun visit(addressOf: AddressOf) {
checkLongType(addressOf)
val variable=addressOf.identifier.targetVarDecl(program)
if(variable!=null && variable.type==VarDeclType.CONST)
errors.err("invalid pointer-of operand type", addressOf.position)
@@ -585,6 +593,12 @@ internal class AstChecker(private val program: Program,
}
override fun visit(decl: VarDecl) {
if(decl.names.size>1)
throw InternalCompilerException("vardecls with multiple names should have been converted into individual vardecls")
if(decl.datatype==DataType.LONG)
errors.err("integer overflow", decl.position)
fun err(msg: String) = errors.err(msg, decl.position)
// the initializer value can't refer to the variable itself (recursive definition)
@@ -594,7 +608,7 @@ internal class AstChecker(private val program: Program,
// CONST can only occur on simple types (byte, word, float)
if(decl.type== VarDeclType.CONST) {
if (decl.datatype !in NumericDatatypes)
err("const modifier can only be used on numeric types (byte, word, float)")
err("const can only be used on numeric types (byte, word, float)")
}
// FLOATS enabled?
@@ -832,9 +846,20 @@ internal class AstChecker(private val program: Program,
if(directive.args.any {it.name !in arrayOf("align_word", "align_page", "no_symbol_prefixing", "force_output", "merge", "splitarrays", "verafxmuls")})
err("using an option that is not valid for blocks")
}
if(directive.parent is Module) {
if(directive.args.any {it.name !in arrayOf("enable_floats", "no_sysinit", "splitarrays")})
err("using an option that is not valid for modules")
}
if(directive.args.any { it.name=="verafxmuls" } && compilerOptions.compTarget.name != Cx16Target.NAME)
err("verafx option is only valid on cx16 target")
}
"%encoding" -> {
if(directive.parent !is Module)
err("this directive may only occur at module level")
val allowedEncodings = Encoding.entries.map {it.prefix}
if(directive.args.size!=1 || directive.args[0].name !in allowedEncodings)
err("invalid encoding directive, expected one of ${allowedEncodings}")
}
else -> throw SyntaxError("invalid directive ${directive.directive}", directive.position)
}
super.visit(directive)
@@ -912,6 +937,7 @@ internal class AstChecker(private val program: Program,
}
override fun visit(expr: PrefixExpression) {
checkLongType(expr)
val dt = expr.expression.inferType(program).getOr(DataType.UNDEFINED)
if(dt==DataType.UNDEFINED)
return // any error should be reported elsewhere
@@ -932,6 +958,7 @@ internal class AstChecker(private val program: Program,
override fun visit(expr: BinaryExpression) {
super.visit(expr)
checkLongType(expr)
val leftIDt = expr.left.inferType(program)
val rightIDt = expr.right.inferType(program)
@@ -950,7 +977,7 @@ internal class AstChecker(private val program: Program,
if(expr.operator=="+" || expr.operator=="-") {
if(leftDt == DataType.STR || rightDt == DataType.STR || leftDt in ArrayDatatypes || rightDt in ArrayDatatypes) {
errors.err("missing & (address-of) on the string operand", expr.position)
errors.err("missing & (address-of) on the operand", expr.position)
return
}
}
@@ -1033,6 +1060,7 @@ internal class AstChecker(private val program: Program,
}
override fun visit(typecast: TypecastExpression) {
checkLongType(typecast)
if(typecast.type in IterableDatatypes)
errors.err("cannot type cast to string or array type", typecast.position)
@@ -1079,6 +1107,7 @@ internal class AstChecker(private val program: Program,
}
override fun visit(functionCallExpr: FunctionCallExpression) {
checkLongType(functionCallExpr)
// this function call is (part of) an expression, which should be in a statement somewhere.
val stmtOfExpression = findParentNode<Statement>(functionCallExpr)
?: throw FatalAstException("cannot determine statement scope of function call expression at ${functionCallExpr.position}")
@@ -1134,6 +1163,11 @@ internal class AstChecker(private val program: Program,
super.visit(functionCallExpr)
}
override fun visit(bfc: BuiltinFunctionCall) {
checkLongType(bfc)
super.visit(bfc)
}
override fun visit(functionCallStatement: FunctionCallStatement) {
val targetStatement = functionCallStatement.target.checkFunctionOrLabelExists(program, functionCallStatement, errors)
if(targetStatement!=null) {
@@ -1213,6 +1247,13 @@ internal class AstChecker(private val program: Program,
}
private fun checkFunctionCall(target: Statement, args: List<Expression>, position: Position) {
if(target is BuiltinFunctionPlaceholder) {
if(!compilerOptions.floats) {
if (target.name == "peekf" || target.name == "pokef")
errors.err("floating point used, but that is not enabled via options", position)
}
}
if(target is Label && args.isNotEmpty())
errors.err("cannot use arguments when calling a label", position)
@@ -1263,6 +1304,10 @@ internal class AstChecker(private val program: Program,
}
}
}
args.forEach{
checkLongType(it)
}
}
override fun visit(postIncrDecr: PostIncrDecr) {
@@ -1300,6 +1345,7 @@ internal class AstChecker(private val program: Program,
}
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
checkLongType(arrayIndexedExpression)
val target = arrayIndexedExpression.arrayvar.targetStatement(program)
if(target is VarDecl) {
if(target.datatype !in IterableDatatypes && target.datatype!=DataType.UWORD)
@@ -1338,7 +1384,7 @@ internal class AstChecker(private val program: Program,
if(choices!=null) {
for (c in choices) {
if(c in tally)
errors.err("choice value already occurs earlier", choiceNode.position)
errors.err("choice value already occurs elsewhere", choiceNode.position)
else
tally.add(c)
}
@@ -1358,18 +1404,18 @@ internal class AstChecker(private val program: Program,
val whenStmt = whenChoice.parent as When
if(whenChoice.values!=null) {
val conditionType = whenStmt.condition.inferType(program)
val constvalues = whenChoice.values!!.map { it.constValue(program) }
for(constvalue in constvalues) {
val constvalues = whenChoice.values!!.map { it.constValue(program) to it.position }
for((constvalue, pos) in constvalues) {
when {
constvalue == null -> errors.err("choice value must be a constant", whenChoice.position)
constvalue.type !in IntegerDatatypes -> errors.err("choice value must be a byte or word", whenChoice.position)
constvalue == null -> errors.err("choice value must be a constant", pos)
constvalue.type !in IntegerDatatypes -> errors.err("choice value must be a byte or word", pos)
conditionType isnot constvalue.type -> {
if(conditionType.isKnown) {
if(conditionType.istype(DataType.BOOL)) {
if(constvalue.number!=0.0 && constvalue.number!=1.0)
errors.err("choice value datatype differs from condition value", whenChoice.position)
errors.err("choice value datatype differs from condition value", pos)
} else {
errors.err("choice value datatype differs from condition value", whenChoice.position)
errors.err("choice value datatype differs from condition value", pos)
}
}
}
@@ -1444,6 +1490,12 @@ internal class AstChecker(private val program: Program,
errors.err("%asm containing IR code cannot be translated to 6502 assembly", inlineAssembly.position)
}
private fun checkLongType(expression: Expression) {
if(expression.inferType(program).istype(DataType.LONG)) {
errors.err("integer overflow", expression.position)
}
}
private fun checkValueTypeAndRangeString(targetDt: DataType, value: StringLiteral) : Boolean {
return if (targetDt == DataType.STR) {
when {
@@ -61,10 +61,6 @@ internal fun Program.charLiteralsToUByteLiterals(target: ICompilationTarget, err
val walker = object : AstWalker() {
override fun after(char: CharLiteral, parent: Node): Iterable<IAstModification> {
require(char.encoding != Encoding.DEFAULT)
if(char.encoding != Encoding.DEFAULT && char.encoding !in target.supportedEncodings) {
errors.err("compilation target doesn't support this text encoding", char.position)
return noModifications
}
return try {
val encoded = target.encodeString(char.value.toString(), char.encoding)
listOf(IAstModification.ReplaceNode(
@@ -81,10 +77,6 @@ internal fun Program.charLiteralsToUByteLiterals(target: ICompilationTarget, err
override fun after(string: StringLiteral, parent: Node): Iterable<IAstModification> {
// this only *checks* for errors for string encoding. The actual encoding is done much later
require(string.encoding != Encoding.DEFAULT)
if(string.encoding != Encoding.DEFAULT && string.encoding !in target.supportedEncodings) {
errors.err("compilation target doesn't support this text encoding", string.position)
return noModifications
}
try {
target.encodeString(string.value, string.encoding)
} catch (x: CharConversionException) {
@@ -33,10 +33,13 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
override fun visit(block: Block) {
val existing = blocks[block.name]
if(existing!=null) {
if(block.isInLibrary)
nameError(existing.name, existing.position, block)
else
nameError(block.name, block.position, existing)
// we allow duplicates if at least one of them has %option merge
if("merge" !in existing.options() + block.options()) {
if (block.isInLibrary)
nameError(existing.name, existing.position, block)
else
nameError(block.name, block.position, existing)
}
}
else
blocks[block.name] = block
@@ -50,16 +53,18 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
if(decl.name in BuiltinFunctions)
errors.err("builtin function cannot be redefined", decl.position)
val existingInSameScope = decl.definingScope.lookup(listOf(decl.name))
if(existingInSameScope!=null && existingInSameScope!==decl)
nameError(decl.name, decl.position, existingInSameScope)
if(decl.names.size<2) {
val existingInSameScope = decl.definingScope.lookup(listOf(decl.name))
if (existingInSameScope != null && existingInSameScope !== decl)
nameError(decl.name, decl.position, existingInSameScope)
val existingOuter = decl.parent.definingScope.lookup(listOf(decl.name))
if (existingOuter != null && existingOuter !== decl && existingOuter is VarDecl) {
if(existingOuter.parent!==decl.parent)
nameShadowWarning(decl.name, decl.position, existingOuter)
else
nameError(decl.name, decl.position, existingOuter)
val existingOuter = decl.parent.definingScope.lookup(listOf(decl.name))
if (existingOuter != null && existingOuter !== decl && existingOuter is VarDecl) {
if (existingOuter.parent !== decl.parent)
nameShadowWarning(decl.name, decl.position, existingOuter)
else
nameError(decl.name, decl.position, existingOuter)
}
}
if(decl.definingBlock.name==decl.name)
@@ -174,7 +179,8 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
else
'_'
}.joinToString("")
call.args[0] = StringLiteral(processed, compTarget.defaultEncoding, name.position)
val textEncoding = (call as Node).definingModule.textEncoding
call.args[0] = StringLiteral(processed, textEncoding, name.position)
call.args[0].linkParents(call as Node)
}
}
@@ -58,13 +58,13 @@ class AstPreprocessor(val program: Program,
override fun before(char: CharLiteral, parent: Node): Iterable<IAstModification> {
if(char.encoding== Encoding.DEFAULT)
char.encoding = options.compTarget.defaultEncoding
char.encoding = char.definingModule.textEncoding
return noModifications
}
override fun before(string: StringLiteral, parent: Node): Iterable<IAstModification> {
if(string.encoding==Encoding.DEFAULT)
string.encoding = options.compTarget.defaultEncoding
string.encoding = string.definingModule.textEncoding
return super.before(string, parent)
}
@@ -117,16 +117,36 @@ class AstPreprocessor(val program: Program,
replacements.add(IAstModification.Remove(decl, scope))
} else {
val declToInsert: VarDecl
if(decl.value!=null && decl.datatype in NumericDatatypes) {
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
val assign = Assignment(target, decl.value!!, AssignmentOrigin.VARINIT, decl.position)
replacements.add(IAstModification.ReplaceNode(decl, assign, scope))
decl.value = null
decl.allowInitializeWithZero = false
declToInsert = decl.copy()
if(decl.names.size>1) {
// we need to handle multi-decl here too, the desugarer maybe has not processed it here yet...
if(decl.value!=null) {
decl.names.forEach { name ->
val target = AssignTarget(IdentifierReference(listOf(name), decl.position), null, null, decl.position)
val assign = Assignment(target.copy(), decl.value!!.copy(), AssignmentOrigin.VARINIT, decl.position)
replacements.add(IAstModification.InsertAfter(decl, assign, scope))
}
replacements.add(IAstModification.Remove(decl, scope))
decl.value = null
decl.allowInitializeWithZero = false
declToInsert = decl
} else {
// just move it to the defining scope
replacements.add(IAstModification.Remove(decl, scope))
declToInsert = decl
}
} else {
replacements.add(IAstModification.Remove(decl, scope))
declToInsert = decl
// handle declaration of a single variable
if(decl.value!=null && decl.datatype in NumericDatatypes) {
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
val assign = Assignment(target, decl.value!!, AssignmentOrigin.VARINIT, decl.position)
replacements.add(IAstModification.ReplaceNode(decl, assign, scope))
decl.value = null
decl.allowInitializeWithZero = false
declToInsert = decl.copy()
} else {
replacements.add(IAstModification.Remove(decl, scope))
declToInsert = decl
}
}
movements.add(IAstModification.InsertFirst(declToInsert, parentscope))
}
@@ -177,7 +197,7 @@ class AstPreprocessor(val program: Program,
private fun makeSplitArray(decl: VarDecl): Iterable<IAstModification> {
val splitDt = ArrayToElementTypes.getValue(decl.datatype)
val newDecl = VarDecl(
decl.type, decl.origin, splitDt, decl.zeropage, decl.arraysize, decl.name,
decl.type, decl.origin, splitDt, decl.zeropage, decl.arraysize, decl.name, emptyList(),
decl.value, true, decl.sharedWithAsm, true, decl.position
)
return listOf(IAstModification.ReplaceNode(decl, newDecl, decl.parent))
@@ -29,7 +29,7 @@ internal class BoolRemover(val program: Program) : AstWalker() {
if(newvalue.number!=0.0)
newvalue = NumericLiteral(DataType.UBYTE, 1.0, newvalue.position)
}
val ubyteDecl = VarDecl(decl.type, decl.origin, DataType.UBYTE, decl.zeropage, decl.arraysize, decl.name,
val ubyteDecl = VarDecl(decl.type, decl.origin, DataType.UBYTE, decl.zeropage, decl.arraysize, decl.name, emptyList(),
newvalue, decl.isArray, decl.sharedWithAsm, decl.splitArray, decl.position)
return listOf(IAstModification.ReplaceNode(decl, ubyteDecl, parent))
}
@@ -46,7 +46,7 @@ internal class BoolRemover(val program: Program) : AstWalker() {
}.toTypedArray()
newarray = ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_UB), convertedArray, decl.position)
}
val ubyteArrayDecl = VarDecl(decl.type, decl.origin, DataType.ARRAY_UB, decl.zeropage, decl.arraysize, decl.name,
val ubyteArrayDecl = VarDecl(decl.type, decl.origin, DataType.ARRAY_UB, decl.zeropage, decl.arraysize, decl.name, emptyList(),
newarray, true, decl.sharedWithAsm, decl.splitArray, decl.position)
return listOf(IAstModification.ReplaceNode(decl, ubyteArrayDecl, parent))
}
@@ -19,13 +19,13 @@ internal class CodeDesugarer(val program: Program, private val errors: IErrorRep
// have been performed (because those could re-introduce nodes that have to be desugared)
//
// List of modifications:
// - replace 'break' statements by a goto + generated after label.
// - replace 'break' and 'continue' statements by a goto + generated after label.
// - replace while and do-until loops by just jumps.
// - replace peek() and poke() by direct memory accesses.
// - repeat-forever loops replaced by label+jump.
// - pointer[word] replaced by @(pointer+word)
// - @(&var) and @(&var+1) replaced by lsb(var) and msb(var) if var is a word
// - flatten chained assignments
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
fun jumpAfter(stmt: Statement): Iterable<IAstModification> {
@@ -52,6 +52,39 @@ internal class CodeDesugarer(val program: Program, private val errors: IErrorRep
}
}
override fun before(continueStmt: Continue, parent: Node): Iterable<IAstModification> {
fun jumpToBottom(scope: IStatementContainer): Iterable<IAstModification> {
val label = program.makeLabel("cont", continueStmt.position)
return listOf(
IAstModification.ReplaceNode(continueStmt, program.jumpLabel(label), parent),
IAstModification.InsertLast(label, scope)
)
}
fun jumpToBefore(loop: WhileLoop): Iterable<IAstModification> {
val label = program.makeLabel("cont", continueStmt.position)
return listOf(
IAstModification.ReplaceNode(continueStmt, program.jumpLabel(label), parent),
IAstModification.InsertBefore(loop, label, loop.parent as IStatementContainer)
)
}
var partof = parent
while(true) {
when (partof) {
is Subroutine, is Block, is ParentSentinel -> {
errors.err("continue in wrong scope", continueStmt.position)
return noModifications
}
is ForLoop -> return jumpToBottom(partof.body)
is RepeatLoop -> return jumpToBottom(partof.body)
is UntilLoop -> return jumpToBottom(partof.body)
is WhileLoop -> return jumpToBefore(partof)
else -> partof = partof.parent
}
}
}
override fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
/*
do { STUFF } until CONDITION
@@ -77,6 +110,22 @@ if CONDITION==0
}
override fun after(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
/*
while true -> repeat
while false -> discard
*/
val constCondition = whileLoop.condition.constValue(program)?.asBooleanValue
if(constCondition==true) {
errors.warn("condition is always true", whileLoop.condition.position)
val repeat = RepeatLoop(null, whileLoop.body, whileLoop.position)
return listOf(IAstModification.ReplaceNode(whileLoop, repeat, parent))
} else if(constCondition==false) {
errors.warn("condition is always false", whileLoop.condition.position)
return listOf(IAstModification.Remove(whileLoop, parent as IStatementContainer))
}
/*
while CONDITION { STUFF }
==>
@@ -238,4 +287,33 @@ _after:
return noModifications
}
override fun after(chainedAssignment: ChainedAssignment, parent: Node): Iterable<IAstModification> {
val assign = chainedAssignment.nested as? Assignment
if(assign!=null) {
// unpack starting from last in the chain
val assigns = mutableListOf<Statement>(assign)
var lastChained: ChainedAssignment = chainedAssignment
var pc: ChainedAssignment? = chainedAssignment
if(assign.value.isSimple) {
// simply copy the RHS value to each component's assignment
while (pc != null) {
lastChained = pc
assigns.add(Assignment(pc.target.copy(), assign.value.copy(), assign.origin, pc.position))
pc = pc.parent as? ChainedAssignment
}
} else if(pc!=null) {
// need to evaluate RHS once and reuse that in each component's assignment
val firstComponentAsValue = assign.target.toExpression()
while (pc != null) {
lastChained = pc
assigns.add(Assignment(pc.target.copy(), firstComponentAsValue.copy(), assign.origin, pc.position))
pc = pc.parent as? ChainedAssignment
}
}
return listOf(IAstModification.ReplaceNode(lastChained,
AnonymousScope(assigns, chainedAssignment.position), lastChained.parent))
}
return noModifications
}
}
@@ -37,9 +37,11 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr
private fun transformStatement(statement: Statement): PtNode {
return when (statement) {
is AnonymousScope -> throw FatalAstException("AnonymousScopes should have been flattened")
is ChainedAssignment -> throw FatalAstException("ChainedAssignment should have been flattened")
is Assignment -> transform(statement)
is Block -> transform(statement)
is Break -> throw FatalAstException("break should have been replaced by Goto")
is Continue -> throw FatalAstException("continue should have been replaced by Goto")
is BuiltinFunctionCallStatement -> transform(statement)
is BuiltinFunctionPlaceholder -> throw FatalAstException("BuiltinFunctionPlaceholder should not occur in Ast here")
is ConditionalBranch -> transform(statement)
@@ -1,6 +1,7 @@
package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall
import prog8.ast.IStatementContainer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.expressions.ArrayLiteral
@@ -21,10 +22,6 @@ internal class LiteralsToAutoVars(private val program: Program,
) : AstWalker() {
override fun after(string: StringLiteral, parent: Node): Iterable<IAstModification> {
if(string.encoding != Encoding.DEFAULT && string.encoding !in target.supportedEncodings) {
errors.err("compilation target doesn't support this text encoding", string.position)
return noModifications
}
if(string.parent !is VarDecl && string.parent !is WhenChoice) {
val binExpr = string.parent as? BinaryExpression
if(binExpr!=null &&(binExpr.operator=="+" || binExpr.operator=="*"))
@@ -74,4 +71,20 @@ internal class LiteralsToAutoVars(private val program: Program,
}
return noModifications
}
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if(decl.names.size>1) {
// note: the desugaring of a multi-variable vardecl has to be done here
// and not in CodeDesugarer, that one is too late (identifiers can't be found otherwise)
if(decl.datatype !in NumericDatatypes)
errors.err("can only multi declare numeric variables", decl.position)
if(errors.noErrors()) {
// desugar into individual vardecl per name.
return decl.desugarMultiDecl().map {
IAstModification.InsertBefore(decl, it, parent as IStatementContainer)
} + IAstModification.Remove(decl, parent as IStatementContainer)
}
}
return noModifications
}
}
@@ -22,7 +22,7 @@ internal class StatementReorderer(
// - sorts the choices in when statement.
// - insert AddressOf (&) expression where required (string params to a UWORD function param etc.).
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%zpallowed", "%address", "%option")
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%zpallowed", "%address", "%option", "%encoding")
override fun after(module: Module, parent: Node): Iterable<IAstModification> {
val (blocks, other) = module.statements.partition { it is Block }
@@ -155,6 +155,7 @@ internal class StatementReorderer(
it.zeropage,
null,
it.name,
emptyList(),
null,
false,
it.sharedWithAsm,
@@ -191,11 +192,6 @@ internal class StatementReorderer(
errors.err("else choice must be the last one", whenStmt.choices[elseChoice].position)
}
val choices = whenStmt.choiceValues(program).sortedBy {
it.first?.first() ?: Int.MAX_VALUE
}
whenStmt.choices.clear()
choices.mapTo(whenStmt.choices) { it.second }
return noModifications
}
@@ -364,34 +364,17 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
override fun after(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> {
val conditionDt = (whenChoice.parent as When).condition.inferType(program)
if((parent as When).condition.inferType(program).isWords) {
val values = whenChoice.values
values?.toTypedArray()?.withIndex()?.forEach { (index, value) ->
val valueDt = value.inferType(program)
if(valueDt!=conditionDt) {
val castedValue = (value as NumericLiteral).cast(conditionDt.getOr(DataType.UNDEFINED))
if(castedValue.isValid) {
values[index] = castedValue.valueOrZero()
} else {
errors.err("choice value datatype differs from condition value", value.position)
}
}
}
} else {
val values = whenChoice.values
values?.toTypedArray()?.withIndex()?.forEach { (index, value) ->
val valueDt = value.inferType(program)
if(valueDt!=conditionDt) {
val castedValue = (value as NumericLiteral).cast(conditionDt.getOr(DataType.UNDEFINED))
if(castedValue.isValid) {
values[index] = castedValue.valueOrZero()
} else {
errors.err("choice value datatype differs from condition value", value.position)
}
val values = whenChoice.values
values?.toTypedArray()?.withIndex()?.forEach { (index, value) ->
val valueDt = value.inferType(program)
if(valueDt!=conditionDt) {
val castedValue = value.typecastTo(conditionDt.getOr(DataType.UNDEFINED), valueDt.getOr(DataType.UNDEFINED), true)
if(castedValue.first) {
castedValue.second.linkParents(whenChoice)
values[index] = castedValue.second
}
}
}
return noModifications
}
@@ -47,9 +47,14 @@ internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorRe
else {
if(functionCallExpr.target.nameInSource==listOf("memory")) {
val name = (functionCallExpr.args[0] as StringLiteral).value
val size = (functionCallExpr.args[1] as NumericLiteral).number.toInt()
val align = (functionCallExpr.args[2] as NumericLiteral).number.toInt()
memorySlabs.add(Slab(name, size, align, functionCallExpr.position))
val size = (functionCallExpr.args[1] as? NumericLiteral)?.number?.toInt()
val align = (functionCallExpr.args[2] as? NumericLiteral)?.number?.toInt()
if(size==null)
errors.err("argument must be a constant", functionCallExpr.args[1].position)
if(align==null)
errors.err("argument must be a constant", functionCallExpr.args[2].position)
if(size!=null && align!=null)
memorySlabs.add(Slab(name, size, align, functionCallExpr.position))
}
}
+30 -1
View File
@@ -5,6 +5,8 @@ import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8.code.target.VMTarget
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText
@@ -110,7 +112,7 @@ class TestAstChecks: FunSpec({
compileText(C64Target(), true, text, writeAssembly = true, errors=errors)
errors.errors.size shouldBe 1
errors.warnings.size shouldBe 0
errors.errors[0] shouldContain "const modifier can only be used"
errors.errors[0] shouldContain "const can only be used"
}
test("array indexing is not allowed on a memory mapped variable") {
@@ -145,4 +147,31 @@ class TestAstChecks: FunSpec({
errors.errors.size shouldBe 0
errors.warnings.size shouldBe 0
}
test("unicode in identifier names is working") {
val text = """
%import floats
main {
ubyte приблизительно = 99
ubyte นี่คือตัวอักษรภาษาไท = 42
sub start() {
str knäckebröd = "crunchy" ; with composed form
prt(knäckebröd) ; with decomposed form
printf(2*floats.π)
}
sub prt(str message) {
приблизительно++
}
sub printf(float fl) {
นี่คือตัวอักษรภาษาไท++
}
}"""
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
compileText(Cx16Target(), false, text, writeAssembly = true) shouldNotBe null
compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null
}
})
+11 -5
View File
@@ -5,6 +5,7 @@ import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.Assignment
import prog8.ast.statements.FunctionCallStatement
import prog8.code.core.BuiltinFunctions
import prog8.code.core.DataType
import prog8.code.core.NumericDatatypesNoBool
@@ -85,26 +86,31 @@ class TestBuiltinFunctions: FunSpec({
val src="""
main {
sub start() {
uword[] array = [1,2,3]
str name = "hello"
cx16.r0L = len(array)
cx16.r0L = len(name)
cx16.r0L = sizeof(array)
cx16.r0 = mkword(200,100)
cx16.r1L = len(name)
cx16.r2L = sizeof(array)
cx16.r4 = mkword(200,100)
test(sizeof(array))
}
sub test(uword value) {
value++
}
}"""
val result = compileText(Cx16Target(), false, src, writeAssembly = false)
val statements = result!!.compilerAst.entrypoint.statements
statements.size shouldBe 6
statements.size shouldBe 7
val a1 = statements[2] as Assignment
val a2 = statements[3] as Assignment
val a3 = statements[4] as Assignment
val a4 = statements[5] as Assignment
val a5 = statements[6] as FunctionCallStatement
(a1.value as NumericLiteral).number shouldBe 3.0
(a2.value as NumericLiteral).number shouldBe 5.0
(a3.value as NumericLiteral).number shouldBe 6.0
(a4.value as NumericLiteral).number shouldBe 200*256+100
(a5.args[0] as NumericLiteral).number shouldBe 6.0
}
})
+3
View File
@@ -117,8 +117,11 @@ class TestCompilerOnExamplesCx16: FunSpec({
"kefrenbars",
"keyboardhandler",
"mandelbrot",
"multi-irq-old",
"multi-irq-new",
"plasma",
"rasterbars",
"showbmx",
"snow",
"spotlight",
"tehtriz",
@@ -3,10 +3,12 @@ package prog8tests
import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldStartWith
import prog8.code.core.ZeropageType
import prog8.code.core.internedStringsModuleName
import prog8.code.target.C64Target
import prog8.code.target.VMTarget
import prog8.compiler.determineCompilationOptions
import prog8.compiler.parseMainModule
import prog8tests.helpers.ErrorReporterForTests
@@ -108,4 +110,29 @@ main {
}
}
test("merge option works on library modules") {
val src="""
%zeropage basicsafe
%import textio
txt {
%option merge
sub println(uword string) {
txt.print(string)
txt.nl()
}
}
main {
sub start() {
txt.lowercase()
txt.println("Hello, world1")
txt.println("Hello, world2")
txt.println("Hello, world3")
}
}"""
compileText(VMTarget(), optimize = false, src) shouldNotBe null
}
})
+7 -7
View File
@@ -112,7 +112,7 @@ class TestMemory: FunSpec({
}
fun createTestProgramForMemoryRefViaVar(address: UInt, vartype: VarDeclType): AssignTarget {
val decl = VarDecl(vartype, VarDeclOrigin.USERCODE, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
val decl = VarDecl(vartype, VarDeclOrigin.USERCODE, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", emptyList(), NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
val memexpr = IdentifierReference(listOf("address"), Position.DUMMY)
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
@@ -150,7 +150,7 @@ class TestMemory: FunSpec({
}
test("regular variable not in mapped IO ram on C64") {
val decl = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, false, false, false, Position.DUMMY)
val decl = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", emptyList(), null, false, false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, false, mutableListOf(decl, assignment), Position.DUMMY)
@@ -162,7 +162,7 @@ class TestMemory: FunSpec({
test("memory mapped variable not in mapped IO ram on C64") {
val address = 0x1000u
val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", emptyList(), NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, false, mutableListOf(decl, assignment), Position.DUMMY)
@@ -174,7 +174,7 @@ class TestMemory: FunSpec({
test("memory mapped variable in mapped IO ram on C64") {
val address = 0xd020u
val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", emptyList(), NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, false, mutableListOf(decl, assignment), Position.DUMMY)
@@ -185,7 +185,7 @@ class TestMemory: FunSpec({
}
test("array not in mapped IO ram") {
val decl = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, false, false, false, Position.DUMMY)
val decl = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", emptyList(), null, false, false, false, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteral.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
@@ -198,7 +198,7 @@ class TestMemory: FunSpec({
test("memory mapped array not in mapped IO ram") {
val address = 0x1000u
val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", emptyList(), NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteral.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
@@ -211,7 +211,7 @@ class TestMemory: FunSpec({
test("memory mapped array in mapped IO ram") {
val address = 0xd800u
val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
val decl = VarDecl(VarDeclType.MEMORY, VarDeclOrigin.USERCODE, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", emptyList(), NumericLiteral.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteral.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteral.optimalInteger(0, Position.DUMMY), AssignmentOrigin.USERCODE, Position.DUMMY)
+2 -2
View File
@@ -123,7 +123,7 @@ class TestNumbers: FunSpec({
uword xx = 10
if xx+99 == 1.23456
xx++
if xx+99 == 1234567
if xx+99 == 1234567.8
xx++
}
}
@@ -143,7 +143,7 @@ class TestNumbers: FunSpec({
uword xx = 10
if xx+99 == 1.23456
xx++
if xx+99 == 1234567
if xx+99 == 1234567.8
xx++
}
}
+123 -4
View File
@@ -16,6 +16,7 @@ import prog8.code.core.DataType
import prog8.code.core.Position
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8.code.target.VMTarget
import prog8tests.helpers.*
@@ -150,7 +151,66 @@ class TestOptimization: FunSpec({
binexpr7.right shouldBe NumericLiteral(DataType.UWORD, 99.0, Position.DUMMY)
}
test("const folding multiple scenarios * and /") {
test("const folding multiple scenarios * and / (floats)") {
val source = """
%option enable_floats
main {
sub start() {
float llw = 300.0
float result
result = 9 * 2 * 10 * llw
result++
result = llw * 9 * 2 * 10
result++
result = llw / 30 / 3
result++
result = llw / 2 * 10
result++
result = llw * 90 / 5
}
}"""
val result = compileText(C64Target(), true, source, writeAssembly = false)!!
// expected:
// float llw
// llw = 300.0
// float result
// result = llw * 180.0
// result++
// result = llw * 180.0
// result++
// result = llw / 90.0
// result++
// result = llw * 5.0
// result++
// result = llw * 18.0
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 12
val mulR0Value = (stmts[3] as Assignment).value
val binexpr0 = mulR0Value as BinaryExpression
binexpr0.operator shouldBe "*"
binexpr0.right shouldBe NumericLiteral(DataType.FLOAT, 180.0, Position.DUMMY)
val mulR1Value = (stmts[5] as Assignment).value
val binexpr1 = mulR1Value as BinaryExpression
binexpr1.operator shouldBe "*"
binexpr1.right shouldBe NumericLiteral(DataType.FLOAT, 180.0, Position.DUMMY)
val divR2Value = (stmts[7] as Assignment).value
val binexpr2 = divR2Value as BinaryExpression
binexpr2.operator shouldBe "/"
binexpr2.right shouldBe NumericLiteral(DataType.FLOAT, 90.0, Position.DUMMY)
val mulR3Value = (stmts[9] as Assignment).value
val binexpr3 = mulR3Value as BinaryExpression
binexpr3.operator shouldBe "*"
binexpr3.right shouldBe NumericLiteral(DataType.FLOAT, 5.0, Position.DUMMY)
binexpr3.left shouldBe instanceOf<IdentifierReference>()
val mulR4Value = (stmts[11] as Assignment).value
val binexpr4 = mulR4Value as BinaryExpression
binexpr4.operator shouldBe "*"
binexpr4.right shouldBe NumericLiteral(DataType.FLOAT, 18.0, Position.DUMMY)
binexpr4.left shouldBe instanceOf<IdentifierReference>()
}
test("const folding multiple scenarios * and / (integers)") {
val source = """
main {
sub start() {
@@ -169,8 +229,8 @@ class TestOptimization: FunSpec({
// cx16.r0s = llw * 180
// cx16.r1s = llw * 180
// cx16.r2s = llw / 90
// cx16.r3s = llw * 5
// cx16.r4s = llw * 90 / 5
// cx16.r3s = llw /2 *10
// cx16.r4s = llw *90 /5
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 7
@@ -189,11 +249,13 @@ class TestOptimization: FunSpec({
val mulR3Value = (stmts[5] as Assignment).value
val binexpr3 = mulR3Value as BinaryExpression
binexpr3.operator shouldBe "*"
binexpr3.right shouldBe NumericLiteral(DataType.UWORD, 5.0, Position.DUMMY)
binexpr3.right shouldBe NumericLiteral(DataType.UWORD, 10.0, Position.DUMMY)
binexpr3.left shouldBe instanceOf<BinaryExpression>()
val mulR4Value = (stmts[6] as Assignment).value
val binexpr4 = mulR4Value as BinaryExpression
binexpr4.operator shouldBe "/"
binexpr4.right shouldBe NumericLiteral(DataType.UWORD, 5.0, Position.DUMMY)
binexpr4.left shouldBe instanceOf<BinaryExpression>()
}
test("constantfolded and silently typecasted for initializervalues") {
@@ -758,4 +820,61 @@ main {
}"""
compileText(C64Target(), true, text, writeAssembly = false) shouldNotBe null
}
test("replacing string print by chrout with referenced string elsewhere shouldn't give string symbol error") {
val text="""
%import textio
main {
sub start() {
str key = "test"
txt.print(":")
if key != ":" {
cx16.r0++
}
}
}
"""
compileText(VMTarget(), true, text, writeAssembly = false) shouldNotBe null
}
test("sub only called by asm should not be optimized away") {
val src="""
main {
sub start() {
%asm{{
jsr p8_test
}}
}
sub test() {
cx16.r0++
}
}"""
compileText(Cx16Target(), true, src, writeAssembly = true) shouldNotBe null
}
test("no crash for making var in removed/inlined subroutine fully scoped") {
val src="""
main {
sub start() {
test()
}
sub test() {
sub nested() {
ubyte counter
counter++
}
test2(main.test.nested.counter) ; shouldn't crash but just give nice error
}
sub test2(ubyte value) {
value++
}
}"""
val errors = ErrorReporterForTests()
compileText(Cx16Target(), true, src, writeAssembly = false, errors = errors) shouldBe null
errors.errors.single() shouldContain "undefined symbol"
}
})
+65 -9
View File
@@ -7,7 +7,10 @@ import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain
import prog8.ast.expressions.NumericLiteral
import prog8.ast.expressions.StringLiteral
import prog8.ast.statements.Assignment
import prog8.ast.statements.VarDecl
import prog8.code.core.Encoding
import prog8.code.core.unescape
import prog8.code.target.C64Target
@@ -260,7 +263,7 @@ class TestStringEncodings: FunSpec({
errors.errors.size shouldBe 0
}
test("unsupported string encoding iso for C64 compilationtarget") {
test("iso string encoding also on C64 compilationtarget") {
val source="""
main {
str string1 = "default"
@@ -271,12 +274,11 @@ class TestStringEncodings: FunSpec({
}
}"""
val errors = ErrorReporterForTests()
compileText(C64Target(), false, source, errors, writeAssembly = false) shouldBe null
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "text encoding"
compileText(C64Target(), false, source, errors, writeAssembly = false) shouldNotBe null
errors.errors.size shouldBe 0
}
test("unsupported char encoding iso for C64 compilationtarget") {
test("iso char encoding also on C64 compilationtarget") {
val source="""
main {
ubyte char1 = 'd'
@@ -287,9 +289,8 @@ class TestStringEncodings: FunSpec({
}
}"""
val errors = ErrorReporterForTests()
compileText(C64Target(), false, source, errors, writeAssembly = false) shouldBe null
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "text encoding"
compileText(C64Target(), false, source, errors, writeAssembly = false) shouldNotBe null
errors.errors.size shouldBe 0
}
test("all encodings supported for Cx16 target") {
@@ -310,5 +311,60 @@ class TestStringEncodings: FunSpec({
}"""
compileText(Cx16Target(), false, source, writeAssembly = false) shouldNotBe null
}
test("module level no default encoding thus petscii") {
val source="""
main {
str string1 = "default"
str string2 = petscii:"petscii"
ubyte char1 = 'd'
ubyte char2 = petscii:'p'
sub start() {
}
}"""
val result = compileText(Cx16Target(), false, source, writeAssembly = false)!!
val main = result.compilerAst.entrypoint.definingBlock
main.statements.size shouldBe 7
val string1 = (main.statements[0] as VarDecl).value as StringLiteral
string1.encoding shouldBe Encoding.PETSCII
string1.value shouldBe "default"
val string2 = (main.statements[1] as VarDecl).value as StringLiteral
string2.encoding shouldBe Encoding.PETSCII
string2.value shouldBe "petscii"
val char1 = (main.statements[3] as Assignment).value as NumericLiteral
char1.number shouldBe 68.0
val char2 = (main.statements[5] as Assignment).value as NumericLiteral
char2.number shouldBe 80.0
}
test("module level default encoding iso") {
val source="""
%encoding iso
main {
str string1 = "default"
str string2 = petscii:"petscii"
ubyte char1 = 'd'
ubyte char2 = petscii:'p'
sub start() {
}
}"""
val result = compileText(Cx16Target(), false, source, writeAssembly = false)!!
val main = result.compilerAst.entrypoint.definingBlock
main.statements.size shouldBe 7
val string1 = (main.statements[0] as VarDecl).value as StringLiteral
string1.encoding shouldBe Encoding.ISO
string1.value shouldBe "default"
val string2 = (main.statements[1] as VarDecl).value as StringLiteral
string2.encoding shouldBe Encoding.PETSCII
string2.value shouldBe "petscii"
val char1 = (main.statements[3] as Assignment).value as NumericLiteral
char1.number shouldBe 100.0
val char2 = (main.statements[5] as Assignment).value as NumericLiteral
char2.number shouldBe 80.0
}
})
+27
View File
@@ -1043,4 +1043,31 @@ main {
errors.errors.single() shouldContain "doesn't match"
}
test("long type okay in const expr but otherwise overflow") {
val src="""
main {
sub start() {
const ubyte HEIGHT=240
uword large = 320*240/8/8
thing(large)
thing(320*240/8/8)
thing(320*HEIGHT/8/8)
thing(320*HEIGHT) ; overflow
large = 12345678 ; overflow
}
sub thing(uword value) {
value++
}
}"""
val errors=ErrorReporterForTests()
compileText(C64Target(), false, src, writeAssembly = false, errors=errors) shouldBe null
errors.errors.size shouldBe 5
errors.errors[0] shouldContain "can't cast"
errors.errors[1] shouldContain "overflow"
errors.errors[2] shouldContain "LONG doesn't match"
errors.errors[3] shouldContain "out of range"
errors.errors[4] shouldContain "overflow"
}
})
+21
View File
@@ -1025,4 +1025,25 @@ main {
compileText(VMTarget(), false, src, writeAssembly = false) shouldNotBe null
}
test("underscores for numeric groupings") {
val src="""
%option enable_floats
main {
sub start() {
uword w1 = 000_1234_5__
uword w2 = ${'$'}ff_ee
uword w3 = %11_0000_111111__0000
float fl = 3_000_001.141_592_654
}
}"""
val result = compileText(VMTarget(), false, src, writeAssembly = false)!!
val st = result.compilerAst.entrypoint.statements
st.size shouldBe 8
val assigns = st.filterIsInstance<Assignment>()
(assigns[0].value as NumericLiteral).number shouldBe 12345
(assigns[1].value as NumericLiteral).number shouldBe 0xffee
(assigns[2].value as NumericLiteral).number shouldBe 0b1100001111110000
(assigns[3].value as NumericLiteral).number shouldBe 3000001.141592654
}
})
@@ -8,6 +8,7 @@ import io.kotest.matchers.types.instanceOf
import prog8.ast.IFunctionCall
import prog8.ast.expressions.*
import prog8.ast.statements.Assignment
import prog8.ast.statements.ForLoop
import prog8.ast.statements.InlineAssembly
import prog8.ast.statements.VarDecl
import prog8.code.core.DataType
@@ -379,5 +380,65 @@ main {
}"""
compileText(VMTarget(), optimize=false, src, writeAssembly=false) shouldNotBe null
}
test("sizeof number const evaluation in vardecl") {
val src="""
main {
sub start() {
uword @shared size1 = sizeof(22222)
uword @shared size2 = sizeof(2.2)
}
}"""
compileText(VMTarget(), optimize=false, src, writeAssembly=false) shouldNotBe null
}
test("multi-var decls in scope with initializer") {
val src="""
main {
sub start() {
ubyte w
for w in 0 to 20 {
ubyte @zp x,y,z=13
ubyte q,r,s
x++
y++
z++
}
}
}"""
val result = compileText(VMTarget(), optimize = false, src, writeAssembly = false)!!
val st = result.compilerAst.entrypoint.statements
/*
sub start () {
ubyte s
s = 0
ubyte r
r = 0
ubyte q
q = 0
ubyte @zp z
ubyte @zp y
ubyte @zp x
ubyte w
for w in 0 to 20 step 1 {
z = 13
y = 13
x = 13
x++
y++
z++
}
}
*/
val vars = st.filterIsInstance<VarDecl>()
vars.size shouldBe 7
vars.all { it.names.size<=1 } shouldBe true
vars.map { it.name }.toSet() shouldBe setOf("s","r","q","z","y","x","w")
val forloop = st.single { it is ForLoop } as ForLoop
forloop.body.statements[0] shouldBe instanceOf<Assignment>()
forloop.body.statements[1] shouldBe instanceOf<Assignment>()
forloop.body.statements[2] shouldBe instanceOf<Assignment>()
}
})
@@ -175,5 +175,23 @@ main {
assembly shouldContain "thearray_lsb"
assembly shouldContain "thearray_msb"
}
test("indexing str or pointervar with expression") {
val text = """
main {
sub start() {
str name = "thing"
modify(name)
sub modify(str arg) {
ubyte n=1
uword pointervar
arg[n+1] = arg[1]
pointervar[n+1] = pointervar[1]
}
}
}"""
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
}
})
@@ -46,8 +46,8 @@ class TestAsmGenSymbols: StringSpec({
}
*/
val varInSub = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.UWORD, ZeropageWish.DONTCARE, null, "localvar", NumericLiteral.optimalInteger(1234, Position.DUMMY), false, false, false, Position.DUMMY)
val var2InSub = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.UWORD, ZeropageWish.DONTCARE, null, "tgt", null, false, false, false, Position.DUMMY)
val varInSub = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.UWORD, ZeropageWish.DONTCARE, null, "localvar", emptyList(), NumericLiteral.optimalInteger(1234, Position.DUMMY), false, false, false, Position.DUMMY)
val var2InSub = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.UWORD, ZeropageWish.DONTCARE, null, "tgt", emptyList(), null, false, false, false, Position.DUMMY)
val labelInSub = Label("locallabel", Position.DUMMY)
val tgt = AssignTarget(IdentifierReference(listOf("tgt"), Position.DUMMY), null, null, Position.DUMMY)
@@ -63,7 +63,7 @@ class TestAsmGenSymbols: StringSpec({
val statements = mutableListOf(varInSub, var2InSub, labelInSub, assign1, assign2, assign3, assign4, assign5, assign6, assign7, assign8)
val subroutine = Subroutine("start", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, false, statements, Position.DUMMY)
val labelInBlock = Label("label_outside", Position.DUMMY)
val varInBlock = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.UWORD, ZeropageWish.DONTCARE, null, "var_outside", null, false, false, false, Position.DUMMY)
val varInBlock = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, DataType.UWORD, ZeropageWish.DONTCARE, null, "var_outside", emptyList(),null, false, false, false, Position.DUMMY)
val block = Block("main", null, mutableListOf(labelInBlock, varInBlock, subroutine), false, Position.DUMMY)
val module = Module(mutableListOf(block), Position.DUMMY, SourceCode.Generated("test"))
+4 -1
View File
@@ -35,6 +35,8 @@ internal object DummyMemsizer : IMemSizer {
}
internal object DummyStringEncoder : IStringEncoding {
override val defaultEncoding: Encoding = Encoding.ISO
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
return emptyList()
}
@@ -45,6 +47,8 @@ internal object DummyStringEncoder : IStringEncoding {
}
internal 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 {
@@ -55,7 +59,6 @@ internal object AsciiStringEncoder : IStringEncoding {
internal object DummyCompilationTarget : ICompilationTarget {
override val name: String = "dummy"
override val machine: IMachineDefinition = VirtualMachineDefinition() // not really true but I don't want to implement a full dummy machinedef
override val supportedEncodings = setOf(Encoding.PETSCII, Encoding.SCREENCODES, Encoding.ISO)
override val defaultEncoding = Encoding.PETSCII
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
@@ -131,7 +131,10 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
output(" @zp")
if(decl.sharedWithAsm)
output(" @shared")
output(" ${decl.name} ")
if(decl.names.size>1)
output(decl.names.joinToString(prefix=" "))
else
output(" ${decl.name} ")
if(decl.value!=null) {
output("= ")
decl.value?.accept(this)
@@ -343,6 +346,12 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
}
}
override fun visit(chainedAssignment: ChainedAssignment) {
chainedAssignment.target.accept(this)
output(" = ")
chainedAssignment.nested.accept(this)
}
override fun visit(postIncrDecr: PostIncrDecr) {
postIncrDecr.target.accept(this)
output(postIncrDecr.operator)
+9
View File
@@ -8,6 +8,7 @@ import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstVisitor
import prog8.code.core.DataType
import prog8.code.core.Encoding
import prog8.code.core.Position
import prog8.code.core.SourceCode
@@ -317,6 +318,14 @@ open class Module(final override var statements: MutableList<Statement>,
fun accept(visitor: IAstVisitor) = visitor.visit(this)
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
val textEncoding: Encoding by lazy {
val encoding = (statements.singleOrNull { it is Directive && it.directive == "%encoding" } as? Directive)
if(encoding!=null)
Encoding.entries.first { it.prefix==encoding.args[0].name }
else
program.encoding.defaultEncoding
}
val isLibrary get() = source.isFromResources
}
+1 -1
View File
@@ -86,7 +86,7 @@ class Program(val name: String,
fun addNewInternedStringvar(string: StringLiteral): Pair<List<String>, VarDecl> {
val varName = "string_${internedStringsBlock.statements.size}"
val decl = VarDecl(
VarDeclType.VAR, VarDeclOrigin.STRINGLITERAL, DataType.STR, ZeropageWish.NOT_IN_ZEROPAGE, null, varName, string,
VarDeclType.VAR, VarDeclOrigin.STRINGLITERAL, DataType.STR, ZeropageWish.NOT_IN_ZEROPAGE, null, varName, emptyList(), string,
isArray = false, sharedWithAsm = false, splitArray = false, position = string.position
)
internedStringsBlock.statements.add(decl)
+51 -23
View File
@@ -7,7 +7,6 @@ import prog8.ast.base.SyntaxError
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.code.core.*
import prog8.parser.Prog8ANTLRParser
import prog8.parser.Prog8ANTLRParser.*
import kotlin.io.path.Path
import kotlin.io.path.isRegularFile
@@ -87,17 +86,11 @@ private fun StatementContext.toAst() : Statement {
val vardecl = variabledeclaration()?.toAst()
if(vardecl!=null) return vardecl
assignment()?.let {
return Assignment(it.assign_target().toAst(), it.expression().toAst(), AssignmentOrigin.USERCODE, it.toPosition())
}
val assignment = assignment()?.toAst()
if(assignment!=null) return assignment
augassignment()?.let {
// replace A += X with A = A + X
val target = it.assign_target().toAst()
val oper = it.operator.text.substringBefore('=')
val expression = BinaryExpression(target.toExpression(), oper, it.expression().toAst(), it.expression().toPosition())
return Assignment(it.assign_target().toAst(), expression, AssignmentOrigin.USERCODE, it.toPosition())
}
val augassign = augassignment()?.toAst()
if(augassign!=null) return augassign
postincrdecr()?.let {
return PostIncrDecr(it.assign_target().toAst(), it.operator.text, it.toPosition())
@@ -145,11 +138,14 @@ private fun StatementContext.toAst() : Statement {
val repeatloop = repeatloop()?.toAst()
if(repeatloop!=null) return repeatloop
val whenstmt = whenstmt()?.toAst()
if(whenstmt!=null) return whenstmt
val breakstmt = breakstmt()?.toAst()
if(breakstmt!=null) return breakstmt
val whenstmt = whenstmt()?.toAst()
if(whenstmt!=null) return whenstmt
val continuestmt = continuestmt()?.toAst()
if(continuestmt!=null) return continuestmt
val unrollstmt = unrollloop()?.toAst()
if(unrollstmt!=null) return unrollstmt
@@ -241,7 +237,10 @@ private fun Asmsub_paramsContext.toAst(): List<AsmSubroutineParameter>
}
}
}
AsmSubroutineParameter(vardecl.varname.text, datatype, registerorpair, statusregister, toPosition())
val identifiers = vardecl.identifier()
if(identifiers.size>1)
throw SyntaxError("parameter name must be singular", identifiers[0].toPosition())
AsmSubroutineParameter(identifiers[0].NAME().text, datatype, registerorpair, statusregister, toPosition())
}
private fun Functioncall_stmtContext.toAst(): Statement {
@@ -307,7 +306,11 @@ private fun Sub_paramsContext.toAst(): List<SubroutineParameter> =
var datatype = it.datatype()?.toAst() ?: DataType.UNDEFINED
if(it.ARRAYSIG()!=null || it.arrayindex()!=null)
datatype = ElementToArrayTypes.getValue(datatype)
SubroutineParameter(it.varname.text, datatype, it.toPosition())
val identifiers = it.identifier()
if(identifiers.size>1)
throw SyntaxError("parameter name must be singular", identifiers[0].toPosition())
SubroutineParameter(identifiers[0].NAME().text, datatype, it.toPosition())
}
private fun Assign_targetContext.toAst() : AssignTarget {
@@ -336,6 +339,22 @@ private fun ClobberContext.toAst() : Set<CpuRegister> {
}
}
private fun AssignmentContext.toAst(): Statement {
val nestedAssign = assignment()
if(nestedAssign==null)
return Assignment(assign_target().toAst(), expression().toAst(), AssignmentOrigin.USERCODE, toPosition())
else
return ChainedAssignment(assign_target().toAst(), nestedAssign.toAst(), toPosition())
}
private fun AugassignmentContext.toAst(): Assignment {
// replace A += X with A = A + X
val target = assign_target().toAst()
val oper = operator.text.substringBefore('=')
val expression = BinaryExpression(target.toExpression(), oper, expression().toAst(), expression().toPosition())
return Assignment(assign_target().toAst(), expression, AssignmentOrigin.USERCODE, toPosition())
}
private fun DatatypeContext.toAst() = DataType.valueOf(text.uppercase())
private fun ArrayindexContext.toAst() : ArrayIndex =
@@ -352,13 +371,14 @@ private fun DirectiveargContext.toAst() : DirectiveArg {
}
private fun IntegerliteralContext.toAst(): NumericLiteralNode {
fun makeLiteral(text: String, radix: Int): NumericLiteralNode {
fun makeLiteral(literalTextWithGrouping: String, radix: Int): NumericLiteralNode {
val literalText = literalTextWithGrouping.replace("_", "")
val integer: Int
var datatype = DataType.UBYTE
when (radix) {
10 -> {
integer = try {
text.toInt()
literalText.toInt()
} catch(x: NumberFormatException) {
throw SyntaxError("invalid decimal literal ${x.message}", toPosition())
}
@@ -367,23 +387,24 @@ private fun IntegerliteralContext.toAst(): NumericLiteralNode {
in -128..127 -> DataType.BYTE
in 0..65535 -> DataType.UWORD
in -32768..32767 -> DataType.WORD
in -2147483647..2147483647 -> DataType.LONG
else -> DataType.FLOAT
}
}
2 -> {
if(text.length>8)
if(literalText.length>8)
datatype = DataType.UWORD
try {
integer = text.toInt(2)
integer = literalText.toInt(2)
} catch(x: NumberFormatException) {
throw SyntaxError("invalid binary literal ${x.message}", toPosition())
}
}
16 -> {
if(text.length>2)
if(literalText.length>2)
datatype = DataType.UWORD
try {
integer = text.toInt(16)
integer = literalText.toInt(16)
} catch(x: NumberFormatException) {
throw SyntaxError("invalid hexadecimal literal ${x.message}", toPosition())
}
@@ -418,6 +439,7 @@ private fun ExpressionContext.toAst() : Expression {
DataType.BYTE -> NumericLiteral(DataType.BYTE, intLit.number, litval.toPosition())
DataType.UWORD -> NumericLiteral(DataType.UWORD, intLit.number, litval.toPosition())
DataType.WORD -> NumericLiteral(DataType.WORD, intLit.number, litval.toPosition())
DataType.LONG -> NumericLiteral(DataType.LONG, intLit.number, litval.toPosition())
DataType.FLOAT -> NumericLiteral(DataType.FLOAT, intLit.number, litval.toPosition())
else -> throw FatalAstException("invalid datatype for numeric literal")
}
@@ -521,7 +543,7 @@ private fun Expression_listContext.toAst() = expression().map{ it.toAst() }
private fun Scoped_identifierContext.toAst() : IdentifierReference =
IdentifierReference(NAME().map { it.text }, toPosition())
private fun FloatliteralContext.toAst() = text.toDouble()
private fun FloatliteralContext.toAst() = text.replace("_","").toDouble()
private fun BooleanliteralContext.toAst() = when(text) {
"true" -> true
@@ -573,6 +595,8 @@ private fun ForloopContext.toAst(): ForLoop {
private fun BreakstmtContext.toAst() = Break(toPosition())
private fun ContinuestmtContext.toAst() = Continue(toPosition())
private fun WhileloopContext.toAst(): WhileLoop {
val condition = expression().toAst()
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
@@ -628,12 +652,16 @@ private fun VardeclContext.toAst(type: VarDeclType, value: Expression?): VarDecl
options.ZEROPAGE().isNotEmpty() -> ZeropageWish.PREFER_ZEROPAGE
else -> ZeropageWish.DONTCARE
}
val identifiers = identifier()
val name = if(identifiers.size==1) identifiers[0].NAME().text else "<multiple>"
return VarDecl(
type, VarDeclOrigin.USERCODE,
datatype()?.toAst() ?: DataType.UNDEFINED,
zp,
arrayindex()?.toAst(),
varname.text,
name,
if(identifiers.size==1) emptyList() else identifiers.map { it.NAME().text },
value,
ARRAYSIG() != null || arrayindex() != null,
options.SHARED().isNotEmpty(),
@@ -472,6 +472,7 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
in -128..127 -> NumericLiteral(DataType.BYTE, dvalue, position)
in 0..65535 -> NumericLiteral(DataType.UWORD, dvalue, position)
in -32768..32767 -> NumericLiteral(DataType.WORD, dvalue, position)
in -2147483647..2147483647 -> NumericLiteral(DataType.LONG, dvalue, position)
else -> NumericLiteral(DataType.FLOAT, dvalue, position)
}
}
@@ -484,6 +485,7 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
in -128..127 -> NumericLiteral(DataType.BYTE, dvalue, position)
in 0..65535 -> NumericLiteral(DataType.UWORD, dvalue, position)
in -32768..32767 -> NumericLiteral(DataType.WORD, dvalue, position)
in -2147483647..2147483647 -> NumericLiteral(DataType.LONG, dvalue, position)
else -> throw FatalAstException("integer overflow: $dvalue")
}
}
@@ -492,6 +494,7 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
return when (value) {
in 0u..255u -> NumericLiteral(DataType.UBYTE, value.toDouble(), position)
in 0u..65535u -> NumericLiteral(DataType.UWORD, value.toDouble(), position)
in 0u..2147483647u -> NumericLiteral(DataType.LONG, value.toDouble(), position)
else -> throw FatalAstException("unsigned integer overflow: $value")
}
}
@@ -561,6 +564,8 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
return CastValue(true, NumericLiteral(targettype, number, position))
if(targettype== DataType.FLOAT)
return CastValue(true, NumericLiteral(targettype, number, position))
if(targettype==DataType.LONG)
return CastValue(true, NumericLiteral(targettype, number, position))
if(targettype==DataType.BOOL)
return CastValue(true, fromBoolean(number!=0.0, position))
}
@@ -583,6 +588,8 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
return CastValue(true, NumericLiteral(targettype, number, position))
if(targettype==DataType.BOOL)
return CastValue(true, fromBoolean(number!=0.0, position))
if(targettype==DataType.LONG)
return CastValue(true, NumericLiteral(targettype, number, position))
}
DataType.UWORD -> {
if(targettype== DataType.BYTE && number <= 127)
@@ -595,6 +602,8 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
return CastValue(true, NumericLiteral(targettype, number, position))
if(targettype==DataType.BOOL)
return CastValue(true, fromBoolean(number!=0.0, position))
if(targettype==DataType.LONG)
return CastValue(true, NumericLiteral(targettype, number, position))
}
DataType.WORD -> {
if(targettype== DataType.BYTE && number >= -128 && number <=127)
@@ -615,6 +624,8 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
return CastValue(true, NumericLiteral(targettype, number, position))
if(targettype==DataType.BOOL)
return CastValue(true, fromBoolean(number!=0.0, position))
if(targettype==DataType.LONG)
return CastValue(true, NumericLiteral(targettype, number, position))
}
DataType.FLOAT -> {
try {
@@ -626,6 +637,8 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
return CastValue(true, NumericLiteral(targettype, number, position))
if (targettype == DataType.UWORD && number >= 0 && number <= 65535)
return CastValue(true, NumericLiteral(targettype, number, position))
if(targettype==DataType.LONG && number >=0 && number <= 2147483647)
return CastValue(true, NumericLiteral(targettype, number, position))
if(targettype==DataType.BOOL)
return CastValue(true, fromBoolean(number!=0.0, position))
} catch (x: ExpressionError) {
@@ -635,6 +648,24 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
DataType.BOOL -> {
return CastValue(true, NumericLiteral(targettype, number, position))
}
DataType.LONG -> {
try {
if (targettype == DataType.BYTE && number >= -128 && number <= 127)
return CastValue(true, NumericLiteral(targettype, number, position))
if (targettype == DataType.UBYTE && number >= 0 && number <= 255)
return CastValue(true, NumericLiteral(targettype, number, position))
if (targettype == DataType.WORD && number >= -32768 && number <= 32767)
return CastValue(true, NumericLiteral(targettype, number, position))
if (targettype == DataType.UWORD && number >= 0 && number <= 65535)
return CastValue(true, NumericLiteral(targettype, number, position))
if(targettype==DataType.BOOL)
return CastValue(true, fromBoolean(number!=0.0, position))
if(targettype== DataType.FLOAT)
return CastValue(true, NumericLiteral(targettype, number, position))
} catch (x: ExpressionError) {
return CastValue(false, null)
}
}
else -> {
throw FatalAstException("type cast of weird type $type")
}
@@ -67,6 +67,7 @@ object InferredTypes {
DataType.BYTE to InferredType.known(DataType.BYTE),
DataType.UWORD to InferredType.known(DataType.UWORD),
DataType.WORD to InferredType.known(DataType.WORD),
DataType.LONG to InferredType.known(DataType.LONG),
DataType.FLOAT to InferredType.known(DataType.FLOAT),
DataType.BOOL to InferredType.known(DataType.BOOL),
DataType.STR to InferredType.known(DataType.STR),
@@ -173,6 +173,20 @@ class Break(override val position: Position) : Statement() {
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
class Continue(override val position: Position) : Statement() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent=parent
}
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun referencesIdentifier(nameInSource: List<String>): Boolean = false
override fun copy() = Break(position)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
enum class VarDeclOrigin {
USERCODE,
@@ -194,6 +208,7 @@ class VarDecl(val type: VarDeclType,
var zeropage: ZeropageWish,
var arraysize: ArrayIndex?,
override val name: String,
val names: List<String>,
var value: Expression?,
val isArray: Boolean,
val sharedWithAsm: Boolean,
@@ -208,7 +223,7 @@ class VarDecl(val type: VarDeclType,
private var autoHeapValueSequenceNumber = 0
fun fromParameter(param: SubroutineParameter): VarDecl {
return VarDecl(VarDeclType.VAR, VarDeclOrigin.SUBROUTINEPARAM, param.type, ZeropageWish.DONTCARE, null, param.name, null,
return VarDecl(VarDeclType.VAR, VarDeclOrigin.SUBROUTINEPARAM, param.type, ZeropageWish.DONTCARE, null, param.name, emptyList(), null,
isArray = false,
sharedWithAsm = false,
splitArray = false,
@@ -221,7 +236,7 @@ class VarDecl(val type: VarDeclType,
val arrayDt = array.type.getOrElse { throw FatalAstException("unknown dt") }
val declaredType = ArrayToElementTypes.getValue(arrayDt)
val arraysize = ArrayIndex.forArray(array)
return VarDecl(VarDeclType.VAR, VarDeclOrigin.ARRAYLITERAL, declaredType, ZeropageWish.NOT_IN_ZEROPAGE, arraysize, autoVarName, array,
return VarDecl(VarDeclType.VAR, VarDeclOrigin.ARRAYLITERAL, declaredType, ZeropageWish.NOT_IN_ZEROPAGE, arraysize, autoVarName, emptyList(), array,
isArray = true, sharedWithAsm = false, splitArray = splitArray, position = array.position)
}
}
@@ -283,7 +298,9 @@ class VarDecl(val type: VarDeclType,
}
override fun copy(): VarDecl {
val copy = VarDecl(type, origin, declaredDatatype, zeropage, arraysize?.copy(), name, value?.copy(),
if(names.size>1)
throw FatalAstException("should not copy a vardecl that still has multiple names")
val copy = VarDecl(type, origin, declaredDatatype, zeropage, arraysize?.copy(), name, names, value?.copy(),
isArray, sharedWithAsm, splitArray, position)
copy.allowInitializeWithZero = this.allowInitializeWithZero
return copy
@@ -298,6 +315,36 @@ class VarDecl(val type: VarDeclType,
override fun referencesIdentifier(nameInSource: List<String>): Boolean =
value?.referencesIdentifier(nameInSource)==true ||
this.arraysize?.referencesIdentifier(nameInSource)==true
fun desugarMultiDecl(): List<VarDecl> {
if(value?.isSimple==true) {
// just copy the initialization value to a separata vardecl for each component
return names.map {
val copy = VarDecl(type, origin, declaredDatatype, zeropage, arraysize?.copy(), it, emptyList(), value?.copy(),
isArray, sharedWithAsm, splitArray, position)
copy.allowInitializeWithZero = this.allowInitializeWithZero
copy
}
} else {
// evaluate the value once in the vardecl for the first component, and set the other components to the first
val first = VarDecl(type, origin, declaredDatatype, zeropage, arraysize?.copy(), names[0], emptyList(), value?.copy(),
isArray, sharedWithAsm, splitArray, position)
first.allowInitializeWithZero = this.allowInitializeWithZero
val firstVar = firstVarAsValue(first)
return listOf(first) + names.drop(1 ).map {
val copy = VarDecl(type, origin, declaredDatatype, zeropage, arraysize?.copy(), it, emptyList(), firstVar.copy(),
isArray, sharedWithAsm, splitArray, position)
copy.allowInitializeWithZero = this.allowInitializeWithZero
copy
}
}
}
private fun firstVarAsValue(first: VarDecl): Expression {
if(first.isArray || first.type!=VarDeclType.VAR)
throw FatalAstException("invalid multi decl type $first")
return IdentifierReference(listOf(first.name), first.position)
}
}
class ArrayIndex(var indexExpr: Expression,
@@ -336,8 +383,34 @@ class ArrayIndex(var indexExpr: Expression,
enum class AssignmentOrigin {
USERCODE,
VARINIT,
OPTIMIZER,
ASMGEN
OPTIMIZER
}
class ChainedAssignment(var target: AssignTarget, var nested: Statement, override val position: Position): Statement() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
target.linkParents(this)
nested.linkParents(this)
}
override fun copy() = throw FatalAstException("you're not supposed to copy a ChainedAssignment")
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===target -> target = replacement as AssignTarget
node===nested -> nested = replacement as Statement
else -> throw FatalAstException("invalid replace")
}
replacement.parent = this
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString() = "ChainedAssignment(target: $target, nested: $nested, pos=$position)"
override fun referencesIdentifier(nameInSource: List<String>): Boolean = target.referencesIdentifier(nameInSource) || nested.referencesIdentifier(nameInSource)
}
class Assignment(var target: AssignTarget, var value: Expression, var origin: AssignmentOrigin, override val position: Position) : Statement() {
@@ -345,7 +418,7 @@ class Assignment(var target: AssignTarget, var value: Expression, var origin: As
override fun linkParents(parent: Node) {
this.parent = parent
this.target.linkParents(this)
target.linkParents(this)
value.linkParents(this)
}
Loaded 100 of 149 files, more files were not shown because too many files have changed in this diff. Show more