Compare commits

...
60 Commits
Author SHA1 Message Date
Irmen de Jong 6516d7cb15 regenerate skeletons and set version 10.4 2024-08-25 16:56:33 +02:00
Irmen de Jong 31cf76042d scope 2024-08-25 15:28:45 +02:00
Irmen de Jong c4c4dcf2b3 optimizing gfx2.fill() 4 color mode 2024-08-25 15:05:24 +02:00
Irmen de Jong 03145630f8 optimizing gfx2.fill() 2024-08-25 14:26:49 +02:00
Irmen de Jong e2fcac322f optimizing gfx2.fill() 2024-08-25 13:51:01 +02:00
Irmen de Jong beaff4d650 moved non X16 specific variables and vector definitions from cx16 to cbm namespace.
This makes the naming consistent with the other cbm-like targets (c64, pet, c128). Only the x16 specific ones remain in the cx16 namespace, such as cx16.KEYHDL

Probably the most impactful is the move of cx16.CINV to cbm.CINV
2024-08-24 20:06:50 +02:00
Irmen de Jong 79af96ddde new kotlin version 2024-08-24 16:34:26 +02:00
Irmen de Jong e439720c9d optimized string compares 2024-08-24 14:53:18 +02:00
Irmen de Jong 48d0185ea4 increase flood fill stack size a bit 2024-08-23 20:55:30 +02:00
Irmen de Jong e2592b4e0b fix possible gfx2 color problem in 2bpp mode 2024-08-23 19:48:23 +02:00
Irmen de Jong 2967866e3d avoid self-modifying code to be compatible with IRQ handlers 2024-08-23 17:46:23 +02:00
Irmen de Jong b566ea5c3f added string.rfind() 2024-08-22 23:22:31 +02:00
Irmen de Jong 8f6eaeac2c half width katakana conversion 2024-08-21 18:51:34 +02:00
Irmen de Jong b4facaeb3c add "kata" string encoding (Katakana) 2024-08-20 21:40:43 +02:00
Irmen de Jong d12b7ccc6b fix syslib importing for raw outputs. fixes #144 2024-08-19 13:33:02 +02:00
Irmen de Jong 453e8bd0a0 update kotlin and antlr libs 2024-08-19 12:47:18 +02:00
Irmen de Jong 9204d390ae correct fetch() signature. fixes #148 2024-08-18 20:04:23 +02:00
Irmen de Jong b70ce0015c fix missing opportunities to use TSB instruction 2024-07-24 22:51:49 +02:00
Irmen de Jong d113827753 todo 2024-07-24 19:50:30 +02:00
Irmen de Jong c67f877857 Codegen: use BIT instruction for memory location bit 7 and 6 tests (use N and V flags) 2024-07-24 19:26:54 +02:00
Irmen de Jong 0ec719e429 cx16: added a polling pcm streaming example 2024-07-23 21:37:11 +02:00
Irmen de Jong 17f7b11148 tweaks cx16 sample streaming example, also added a new one 2024-07-23 02:10:05 +02:00
Irmen de Jong 966b017670 tweaks 2024-07-22 18:20:01 +02:00
Irmen de Jong 4c98070b3c optimize shifts by 1 by inlining it better 2024-07-21 22:08:41 +02:00
Irmen de Jong 3681d6ee1c optimize division by powers of 2 better (into bit shifts) 2024-07-21 21:34:38 +02:00
Irmen de Jong 0af17cdc33 todo's for division optimizations 2024-07-21 20:32:03 +02:00
Irmen de Jong 2aae1f5e30 stricter checks for negative array indexing 2024-07-20 22:37:03 +02:00
Irmen de Jong d18f2a7bfd improved codegen for some pointer+index expressions 2024-07-18 23:41:34 +02:00
Irmen de Jong 9046fe8d3a ringbuffer and pointer optimization todo 2024-07-16 22:59:31 +02:00
Irmen de Jong 78c7ee247a generate 65c02 TSB/TRB instructions in certain cases 2024-07-16 00:36:00 +02:00
Irmen de Jong d5adb85e5b IR: add SEC,CLC,SEI,CLI instructions for the sys function calls. 2024-07-14 21:01:19 +02:00
Irmen de Jong 69f953fd9b diskio.f_readline() now also returns I/O status as secondary return value in A 2024-07-06 22:25:01 +02:00
Irmen de Jong 484677b4b1 Get rid of any() and all() builtin functions.
Replaced by regular subroutines in the anyall module.
2024-07-06 18:49:03 +02:00
Irmen de Jong b10a8e728f update vim syntax too 2024-07-06 17:13:42 +02:00
Irmen de Jong 25f25a8767 Get rid of sort() and reverse() builtin functions.
Sort() had too many gotchas and reverse() is kinda redundant you can loop in decreasing order through an array too.
2024-07-06 17:07:58 +02:00
Irmen de Jong 0c053e4a2c IR: don't confuse symbol names starting with 'r', with register names
Added start of buffer.p8 (experimental)
2024-07-04 01:39:25 +02:00
Irmen de Jong 3f6521cc9b todo 2024-07-03 20:33:59 +02:00
Irmen de Jong a074491d5b fix doc build 2024-07-03 00:08:49 +02:00
Irmen de Jong a291164953 fix leaving Vera CTRL at 1 instead of 0, could lead to kernal text output errors etc
Fixes #143
2024-07-02 23:36:36 +02:00
Irmen de Jong 43c55b58d2 fix register overwriting for certain subroutine call parameter combinations. Fixes #136 2024-07-02 23:26:34 +02:00
Irmen de Jong e7298f8162 fix invalid code gen for if v1==0 or v2==0 2024-07-01 23:38:25 +02:00
Irmen de Jong ddf990296b fix subroutine inlining symbol scope error 2024-06-29 18:53:54 +02:00
Irmen de Jong ead8aa7800 asm optimization: bxx+jmp -> opposite bxx 2024-06-29 17:22:57 +02:00
Irmen de Jong 7a9dd1ac9b optimize trivial 65c02 stack instructions 2024-06-29 15:42:40 +02:00
Irmen de Jong 1c97c22eff optimize simple word and byte addition/subtraction better 2024-06-29 14:33:42 +02:00
Irmen de Jong bbf621a8c4 doc 2024-06-29 13:39:08 +02:00
Irmen de Jong 8efa89165c sprites.get_data_ptr() signature changed: now properly returns the 2 values 2024-06-27 22:22:26 +02:00
Irmen de Jong 4f8aaf9244 some tweaks and todos 2024-06-26 21:22:40 +02:00
Irmen de Jong a97edef380 update gradle wrapper to gradle 8.8 2024-06-25 22:43:04 +02:00
Irmen de Jong eefae24aa3 update gradle wrapper to gradle 8.8 2024-06-25 22:42:16 +02:00
Irmen de Jong 54bffc91ae properly generate PUSH and POP instructions for push() and pop() calls in IR.
Also switch to a fork of shadowJar to avoid Gradle deprecation errors.
2024-06-25 22:39:20 +02:00
Irmen de Jong 63f5ef9e14 fix typo for bool array storage size 2024-06-20 22:55:47 +02:00
Irmen de Jong 034f27a8dd added queens example, update kotest lib 2024-06-19 23:57:43 +02:00
Irmen de Jong c2f6311367 todo 2024-06-17 22:41:52 +02:00
markjreed 6f00a48772 fix: atan2(anything, 0) should return ±π/2 (#141)
* fix: atan2(anything, 0) should return pi/2

* fix: if y<0, x=0 maps to 3π/2, not π/2

* fix: standard seems to be atan2(0,0) == 0
2024-06-07 23:19:45 +02:00
Irmen de Jong b3dba67405 added cx16.rom_version() routine 2024-06-07 23:15:26 +02:00
Irmen de Jong c9a4235669 update to kotlin 2.0, fix several code style issues 2024-06-04 01:00:46 +02:00
Irmen de Jong ae0d52274c Merge branch 'refs/heads/fixwindowseolstests' 2024-06-04 00:13:55 +02:00
Irmen de Jong 8973763866 Fix line endings conversion errors on windows builds 2024-06-04 00:12:12 +02:00
Irmen de Jong 3d799ae7fe todo 2024-06-01 15:03:01 +02:00
130 changed files with 2723 additions and 13879 deletions

No files matched your search

+10 -10
View File
@@ -1,23 +1,23 @@
<component name="libraryTable">
<library name="KotlinJavaRuntime" type="repository">
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.24" />
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.0.20" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.9.24/kotlin-stdlib-jdk8-1.9.24.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.24/kotlin-stdlib-1.9.24.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.20/kotlin-stdlib-jdk8-2.0.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.20/kotlin-stdlib-2.0.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.9.24/kotlin-stdlib-jdk7-1.9.24.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.20/kotlin-stdlib-jdk7-2.0.20.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.9.24/kotlin-stdlib-jdk8-1.9.24-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.24/kotlin-stdlib-1.9.24-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.20/kotlin-stdlib-jdk8-2.0.20-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.20/kotlin-stdlib-2.0.20-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.24/kotlin-stdlib-jdk7-1.9.24-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.20/kotlin-stdlib-jdk7-2.0.20-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.9.24/kotlin-stdlib-jdk8-1.9.24-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.24/kotlin-stdlib-1.9.24-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.20/kotlin-stdlib-jdk8-2.0.20-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.20/kotlin-stdlib-2.0.20-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.24/kotlin-stdlib-jdk7-1.9.24-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.20/kotlin-stdlib-jdk7-2.0.20-sources.jar!/" />
</SOURCES>
</library>
</component>
+3 -3
View File
@@ -1,13 +1,13 @@
<component name="libraryTable">
<library name="antlr.antlr4" type="repository">
<properties maven-id="org.antlr:antlr4:4.13.1">
<properties maven-id="org.antlr:antlr4:4.13.2">
<exclude>
<dependency maven-id="com.ibm.icu:icu4j" />
</exclude>
</properties>
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.13.1/antlr4-4.13.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.13.1/antlr4-runtime-4.13.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.13.2/antlr4-4.13.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.13.2/antlr4-runtime-4.13.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr-runtime/3.5.3/antlr-runtime-3.5.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/ST4/4.3.4/ST4-4.3.4.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/abego/treelayout/org.abego.treelayout.core/1.0.3/org.abego.treelayout.core-1.0.3.jar!/" />
+5 -5
View File
@@ -1,17 +1,17 @@
<component name="libraryTable">
<library name="io.kotest.assertions.core.jvm" type="repository">
<properties maven-id="io.kotest:kotest-assertions-core-jvm:5.9.0" />
<properties maven-id="io.kotest:kotest-assertions-core-jvm:5.9.1" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.9.0/kotest-assertions-core-jvm-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.9.0/kotest-assertions-shared-jvm-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.9.1/kotest-assertions-core-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.9.1/kotest-assertions-shared-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.23/kotlin-stdlib-1.9.23.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.8.0/kotlinx-coroutines-jdk8-1.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.9.23/kotlin-reflect-1.9.23.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.9.0/kotest-common-jvm-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.9.0/kotest-assertions-api-jvm-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.9.1/kotest-common-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.9.1/kotest-assertions-api-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.8.0/kotlinx-coroutines-core-jvm-1.8.0.jar!/" />
</CLASSES>
<JAVADOC />
+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.9.0" />
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:5.9.1" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/5.9.0/kotest-runner-junit5-jvm-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.9.0/kotest-framework-api-jvm-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.9.0/kotest-assertions-shared-jvm-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/5.9.1/kotest-runner-junit5-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.9.1/kotest-framework-api-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.9.1/kotest-assertions-shared-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-test-jvm/1.8.0/kotlinx-coroutines-test-jvm-1.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.9.0/kotest-common-jvm-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/5.9.0/kotest-framework-engine-jvm-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.9.1/kotest-common-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/5.9.1/kotest-framework-engine-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.172/classgraph-4.8.172.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/mordant/1.2.1/mordant-1.2.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/colormath/1.2.0/colormath-1.2.0.jar!/" />
@@ -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.9.0/kotest-framework-discovery-jvm-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.9.0/kotest-assertions-core-jvm-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-discovery-jvm/5.9.1/kotest-framework-discovery-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.9.1/kotest-assertions-core-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.8.0/kotlinx-coroutines-jdk8-1.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.9.0/kotest-assertions-api-jvm-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/5.9.0/kotest-extensions-jvm-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/5.9.0/kotest-framework-concurrency-jvm-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.9.1/kotest-assertions-api-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/5.9.1/kotest-extensions-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/5.9.1/kotest-framework-concurrency-jvm-5.9.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.8.0/kotlinx-coroutines-core-jvm-1.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/23.0.0/annotations-23.0.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.8.2/junit-platform-engine-1.8.2.jar!/" />
+2 -1
View File
@@ -53,6 +53,7 @@ What does Prog8 provide?
- all advantages of a higher level language over having to write assembly code manually
- programs run very fast because compilation to native machine code
- code often is smaller and faster than equivalent C code compiled with CC65 or even LLVM-MOS
- modularity, symbol scoping, subroutines
- various data types other than just bytes (16-bit words, floats, strings)
- floating point math is supported if the target system provides floating point library routines (C64 and Cx16 both do)
@@ -66,7 +67,7 @@ What does Prog8 provide?
- conditional branches
- ``when`` statement to provide a concise jump table alternative to if/elseif chains
- ``in`` expression for concise and efficient multi-value/containment check
- many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``sort`` and ``reverse``
- several specialized built-in functions such as ``lsb``, ``msb``, ``min``, ``max``, ``rol``, ``ror``
- various powerful built-in libraries to do I/O, number conversions, graphics and more
- convenience abstractions for low level aspects such as ZeroPage handling, program startup, explicit memory addresses
- inline assembly allows you to have full control when every cycle or byte matters
+1 -1
View File
@@ -222,7 +222,7 @@ class StMemVar(name: String,
init{
require(dt!=DataType.BOOL && dt!=DataType.ARRAY_BOOL)
if(dt in ArrayDatatypes || dt == DataType.STR)
require(length!=null) { "memory mapped array or string must have known length" }
requireNotNull(length)
}
}
@@ -267,12 +267,12 @@ class PtNumber(type: DataType, val number: Double, position: Position) : PtExpre
override fun hashCode(): Int = Objects.hash(type, number)
override fun equals(other: Any?): Boolean {
if(other==null || other !is PtNumber)
return false
return if(other==null || other !is PtNumber)
false
else if(type!=DataType.BOOL && other.type!=DataType.BOOL)
return number==other.number
number==other.number
else
return type==other.type && number==other.number
type==other.type && number==other.number
}
operator fun compareTo(other: PtNumber): Int = number.compareTo(other.number)
@@ -76,14 +76,14 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
"ror" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
"rol2" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
"ror2" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
"sort" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
"reverse" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
// cmp returns a status in the carry flag, but not a proper return value
"cmp" to FSignature(false, listOf(FParam("value1", IntegerDatatypes), FParam("value2", NumericDatatypes)), null),
"prog8_lib_stringcompare" to FSignature(true, listOf(FParam("str1", arrayOf(DataType.STR)), FParam("str2", arrayOf(DataType.STR))), DataType.BYTE),
"prog8_lib_arraycopy" to FSignature(false, listOf(FParam("source", ArrayDatatypes), FParam("target", ArrayDatatypes)), null),
"prog8_lib_square_byte" to FSignature(true, listOf(FParam("value", arrayOf(DataType.BYTE, DataType.UBYTE))), DataType.UBYTE),
"prog8_lib_square_word" to FSignature(true, listOf(FParam("value", arrayOf(DataType.WORD, DataType.UWORD))), DataType.UWORD),
"prog8_ifelse_bittest_set" to FSignature(true, listOf(FParam("variable", ByteDatatypes), FParam("bitnumber", arrayOf(DataType.UBYTE))), DataType.BOOL),
"prog8_ifelse_bittest_notset" to FSignature(true, listOf(FParam("variable", ByteDatatypes), FParam("bitnumber", arrayOf(DataType.UBYTE))), DataType.BOOL),
"abs" to FSignature(true, listOf(FParam("value", NumericDatatypes)), null),
"abs__byte" to FSignature(true, listOf(FParam("value", arrayOf(DataType.BYTE))), DataType.BYTE),
"abs__word" to FSignature(true, listOf(FParam("value", arrayOf(DataType.WORD))), DataType.WORD),
@@ -99,8 +99,6 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
"divmod" to FSignature(false, listOf(FParam("dividend", arrayOf(DataType.UBYTE, DataType.UWORD)), FParam("divisor", arrayOf(DataType.UBYTE, DataType.UWORD)), FParam("quotient", arrayOf(DataType.UBYTE, DataType.UWORD)), FParam("remainder", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
"divmod__ubyte" to FSignature(false, listOf(FParam("dividend", arrayOf(DataType.UBYTE)), FParam("divisor", arrayOf(DataType.UBYTE)), FParam("quotient", arrayOf(DataType.UBYTE)), FParam("remainder", arrayOf(DataType.UBYTE))), null),
"divmod__uword" to FSignature(false, listOf(FParam("dividend", arrayOf(DataType.UWORD)), FParam("divisor", arrayOf(DataType.UWORD)), FParam("quotient", arrayOf(DataType.UWORD)), FParam("remainder", arrayOf(DataType.UWORD))), null),
"any" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.BOOL),
"all" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.BOOL),
"lsb" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE),
"msb" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE),
"mkword" to FSignature(true, listOf(FParam("msb", arrayOf(DataType.UBYTE)), FParam("lsb", arrayOf(DataType.UBYTE))), DataType.UWORD),
@@ -136,6 +134,5 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
val InplaceModifyingBuiltinFunctions = setOf(
"setlsb", "setmsb",
"rol", "ror", "rol2", "ror2",
"sort", "reverse",
"divmod", "divmod__ubyte", "divmod__uword"
)
@@ -1,6 +1,11 @@
package prog8.code.core
import kotlin.math.abs
import kotlin.math.pow
val powersOfTwoFloat = (0..16).map { (2.0).pow(it) }.toTypedArray()
val negativePowersOfTwoFloat = powersOfTwoFloat.map { -it }.toTypedArray()
val powersOfTwoInt = (0..16).map { 2.0.pow(it).toInt() }.toTypedArray()
fun Number.toHex(): String {
// 0..15 -> "0".."15"
@@ -29,7 +29,6 @@ interface IMachineDefinition {
fun convertFloatToBytes(num: Double): List<UByte>
fun convertBytesToFloat(bytes: List<UByte>): Double
fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String>
fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path)
fun isIOAddress(address: UInt): Boolean
}
@@ -8,7 +8,8 @@ enum class Encoding(val prefix: String) {
ISO("iso"), // cx16 (iso-8859-15)
ISO5("iso5"), // cx16 (iso-8859-5, cyrillic)
ISO16("iso16"), // cx16 (iso-8859-16, eastern european)
CP437("cp437") // cx16 (ibm pc, codepage 437)
CP437("cp437"), // cx16 (ibm pc, codepage 437)
KATAKANA("kata") // cx16 (katakana)
}
interface IStringEncoding {
@@ -11,6 +11,7 @@ fun optimizeIntermediateAst(program: PtProgram, options: CompilationOptions, st:
return
while (errors.noErrors() &&
(optimizeCommonSubExpressions(program, errors)
+ optimizeBitTest(program, options)
+ optimizeAssignTargets(program, st, errors)) > 0
) {
// keep rolling
@@ -164,6 +165,51 @@ private fun optimizeAssignTargets(program: PtProgram, st: SymbolTable, errors: I
return changes
}
private fun optimizeBitTest(program: PtProgram, options: CompilationOptions): Int {
if(options.compTarget.machine.cpu == CpuType.VIRTUAL)
return 0 // the special bittest optimization is not yet valid for the IR
var changes = 0
var recurse = true
walkAst(program) { node: PtNode, depth: Int ->
if(node is PtIfElse) {
val condition = node.condition as? PtBinaryExpression
if(condition!=null && (condition.operator=="==" || condition.operator=="!=")) {
if(condition.right.asConstInteger()==0) {
val and = condition.left as? PtBinaryExpression
if(and != null && and.operator=="&" && and.type == DataType.UBYTE) {
val variable = and.left as? PtIdentifier
val bitmask = and.right.asConstInteger()
if(variable!=null && variable.type in ByteDatatypes && (bitmask==128 || bitmask==64)) {
val setOrNot = if(condition.operator=="!=") "set" else "notset"
val index = node.parent.children.indexOf(node)
val bittestCall = PtBuiltinFunctionCall("prog8_ifelse_bittest_$setOrNot", false, true, DataType.BOOL, node.condition.position)
bittestCall.add(variable)
if(bitmask==128)
bittestCall.add(PtNumber(DataType.UBYTE, 7.0, and.right.position))
else
bittestCall.add(PtNumber(DataType.UBYTE, 6.0, and.right.position))
val ifElse = PtIfElse(node.position)
ifElse.add(bittestCall)
ifElse.add(node.ifScope)
if(node.hasElse())
ifElse.add(node.elseScope)
node.parent.children[index] = ifElse
ifElse.parent = node.parent
changes++
recurse = false
}
}
}
}
}
recurse
}
return changes
}
internal fun isSame(identifier: PtIdentifier, type: DataType, returnedRegister: RegisterOrPair): Boolean {
if(returnedRegister in Cx16VirtualRegisters) {
val regname = returnedRegister.name.lowercase()
@@ -19,6 +19,7 @@ object Encoder: IStringEncoding {
Encoding.ISO5 -> IsoCyrillicEncoding.encode(str)
Encoding.ISO16 -> IsoEasternEncoding.encode(str)
Encoding.CP437 -> Cp437Encoding.encode(str)
Encoding.KATAKANA -> KatakanaEncoding.encode(str)
else -> throw InternalCompilerException("unsupported encoding $encoding")
}
return coded.fold(
@@ -35,6 +36,7 @@ object Encoder: IStringEncoding {
Encoding.ISO5 -> IsoCyrillicEncoding.decode(bytes)
Encoding.ISO16 -> IsoEasternEncoding.decode(bytes)
Encoding.CP437 -> Cp437Encoding.decode(bytes)
Encoding.KATAKANA -> KatakanaEncoding.decode(bytes)
else -> throw InternalCompilerException("unsupported encoding $encoding")
}
return decoded.fold(
@@ -25,13 +25,6 @@ class AtariMachineDefinition: IMachineDefinition {
override fun convertFloatToBytes(num: Double): List<UByte> = TODO("atari float to bytes")
override fun convertBytesToFloat(bytes: List<UByte>): Double = TODO("atari bytes to float")
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return if (compilerOptions.output == OutputType.XEX)
listOf("syslib")
else
emptyList()
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
val emulatorName: String
val cmdline: List<String>
@@ -36,13 +36,6 @@ class C128MachineDefinition: IMachineDefinition {
return m5.toDouble()
}
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
listOf("syslib")
else
emptyList()
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
if(selectedEmulator!=1) {
System.err.println("The c128 target only supports the main emulator (Vice).")
@@ -37,13 +37,6 @@ class C64MachineDefinition: IMachineDefinition {
return m5.toDouble()
}
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
listOf("syslib")
else
emptyList()
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
if(selectedEmulator!=1) {
System.err.println("The c64 target only supports the main emulator (Vice).")
@@ -36,13 +36,6 @@ class CX16MachineDefinition: IMachineDefinition {
return m5.toDouble()
}
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
listOf("syslib")
else
emptyList()
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
val emulator: String
val extraArgs: List<String>
@@ -0,0 +1,122 @@
package prog8.code.target.encodings
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import java.io.CharConversionException
import java.nio.charset.Charset
object JapaneseCharacterConverter {
// adapted from https://github.com/raminduw/Japanese-Character-Converter
private val ZENKAKU_KATAKANA = charArrayOf(
'ァ', 'ア', 'ィ', 'イ', 'ゥ',
'ウ', 'ェ', 'エ', 'ォ', 'オ', 'カ', 'ガ', 'キ', 'ギ', 'ク', 'グ', 'ケ', 'ゲ',
'コ', 'ゴ', 'サ', 'ザ', 'シ', 'ジ', 'ス', 'ズ', 'セ', 'ゼ', 'ソ', 'ゾ', 'タ',
'ダ', 'チ', 'ヂ', 'ッ', 'ツ', 'ヅ', 'テ', 'デ', 'ト', 'ド', 'ナ', 'ニ', 'ヌ',
'ネ', 'ノ', 'ハ', 'バ', 'パ', 'ヒ', 'ビ', 'ピ', 'フ', 'ブ', 'プ', 'ヘ', 'ベ',
'ペ', 'ホ', 'ボ', 'ポ', 'マ', 'ミ', 'ム', 'メ', 'モ', 'ャ', 'ヤ', 'ュ', 'ユ',
'ョ', 'ヨ', 'ラ', 'リ', 'ル', 'レ', 'ロ', 'ヮ', 'ワ', 'ヰ', 'ヱ', 'ヲ', 'ン',
'ヴ', 'ヵ', 'ヶ'
)
private val HANKAKU_HIRAGANA = charArrayOf(
'ぁ', 'あ', 'ぃ', 'い', 'ぅ', 'う', 'ぇ', 'え',
'ぉ', 'お', 'か', 'が', 'き', 'ぎ', 'く', 'ぐ',
'け', 'げ', 'こ', 'ご', 'さ', 'ざ', 'し', 'じ',
'す', 'ず', 'せ', 'ぜ', 'そ', 'ぞ', 'た', 'だ',
'ち', 'ぢ', 'っ', 'つ', 'づ', 'て', 'で', 'と',
'ど', 'な', 'に', 'ぬ', 'ね', 'の', 'は', 'ば',
'ぱ', 'ひ', 'び', 'ぴ', 'ふ', 'ぶ', 'ぷ', 'へ',
'べ', 'ぺ', 'ほ', 'ぼ', 'ぽ', 'ま', 'み', 'む',
'め', 'も', 'ゃ', 'や', 'ゅ', 'ゆ', 'ょ', 'よ',
'ら', 'り', 'る', 'れ', 'ろ', 'ゎ', 'わ', 'ゐ',
'ゑ', 'を', 'ん', 'ゔ', 'ゕ', 'ゖ'
)
private val HANKAKU_KATAKANA = arrayOf(
"ァ", "ア", "ィ", "イ", "ゥ",
"ウ", "ェ", "エ", "ォ", "オ", "カ", "ガ", "キ", "ギ", "ク", "グ", "ケ",
"ゲ", "コ", "ゴ", "サ", "ザ", "シ", "ジ", "ス", "ズ", "セ", "ゼ", "ソ",
"ゾ", "タ", "ダ", "チ", "ヂ", "ッ", "ツ", "ヅ", "テ", "デ", "ト", "ド",
"ナ", "ニ", "ヌ", "ネ", "ノ", "ハ", "バ", "パ", "ヒ", "ビ", "ピ", "フ",
"ブ", "プ", "ヘ", "ベ", "ペ", "ホ", "ボ", "ポ", "マ", "ミ", "ム", "メ",
"モ", "ャ", "ヤ", "ュ", "ユ", "ョ", "ヨ", "ラ", "リ", "ル", "レ", "ロ", "ワ",
"ワ", "イ", "エ", "ヲ", "ン", "ヴ", "カ", "ケ"
)
private val ZENKAKU_KATAKANA_FIRST_CHAR_CODE = ZENKAKU_KATAKANA.first().code
private val HANKAKU_HIRAGANA_FIRST_CHAR_CODE = HANKAKU_HIRAGANA.first().code
private fun zenkakuKatakanaToHankakuKatakana(c: Char): String = if (c in ZENKAKU_KATAKANA) HANKAKU_KATAKANA[c.code - ZENKAKU_KATAKANA_FIRST_CHAR_CODE] else c.toString()
private fun hankakuKatakanaToZenkakuKatakana(c: Char): Char = if (c in HANKAKU_HIRAGANA) ZENKAKU_KATAKANA[c.code - HANKAKU_HIRAGANA_FIRST_CHAR_CODE] else c
fun zenkakuKatakanaToHankakuKatakana(s: String): String = buildString {
for (element in s) {
val converted = hankakuKatakanaToZenkakuKatakana(element)
val convertedChar = zenkakuKatakanaToHankakuKatakana(converted)
append(convertedChar)
}
}
}
object KatakanaEncoding {
val charset: Charset = Charset.forName("JIS_X0201")
fun encode(str: String): Result<List<UByte>, CharConversionException> {
return try {
val mapped = str.map { chr ->
when (chr) {
'\u0000' -> 0u
'\u00a0' -> 0xa0u // $a0 isn't technically a part of JIS X 0201 spec, and so we need to handle this ourselves
'♥' -> 0xe3u
'♦' -> 0xe4u
'♣' -> 0xe5u
'♠' -> 0xe6u
'大' -> 0xeau
'中' -> 0xebu
'小' -> 0xecu
'百' -> 0xedu
'千' -> 0xeeu
'万' -> 0xefu
'♪' -> 0xf0u
'土' -> 0xf1u
'金' -> 0xf2u
'木' -> 0xf3u
'水' -> 0xf4u
'火' -> 0xf5u
'月' -> 0xf6u
'日' -> 0xf7u
'時' -> 0xf8u
'分' -> 0xf9u
'秒' -> 0xfau
'年' -> 0xfbu
'円' -> 0xfcu
'人' -> 0xfdu
'生' -> 0xfeu
'〒' -> 0xffu
in '\u8000'..'\u80ff' -> {
// special case: take the lower 8 bit hex value directly
(chr.code - 0x8000).toUByte()
}
else -> charset.encode(chr.toString())[0].toUByte()
}
}
Ok(mapped)
} catch (ce: CharConversionException) {
Err(ce)
}
}
fun decode(bytes: Iterable<UByte>): Result<String, CharConversionException> {
return try {
Ok(String(bytes.map { it.toByte() }.toByteArray(), charset))
} catch (ce: CharConversionException) {
Err(ce)
}
}
}
@@ -36,13 +36,6 @@ class PETMachineDefinition: IMachineDefinition {
return m5.toDouble()
}
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
listOf("syslib")
else
emptyList()
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
if(selectedEmulator!=1) {
System.err.println("The pet target only supports the main emulator (Vice).")
@@ -50,10 +50,6 @@ class VirtualMachineDefinition: IMachineDefinition {
return Double.fromBits(b0 or b1 or b2 or b3 or b4 or b5 or b6 or b7)
}
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return listOf("syslib")
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
println("\nStarting Virtual Machine...")
// to not have external module dependencies in our own module, we launch the virtual machine via reflection
+1 -1
View File
@@ -27,7 +27,7 @@ dependencies {
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.0"
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.9.0'
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.9.1'
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
@@ -50,6 +50,13 @@ internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefin
numberOfOptimizations++
}
mods = optimizeTSBtoRegularOr(linesByFour)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFour = getLinesBy(lines, 4)
numberOfOptimizations++
}
var linesByFourteen = getLinesBy(lines, 14)
mods = optimizeSameAssignments(linesByFourteen, machine, symbolTable)
if(mods.isNotEmpty()) {
@@ -384,6 +391,7 @@ private fun optimizeStoreLoadSame(
for (lines in linesByFour) {
val first = lines[1].value.trimStart()
val second = lines[2].value.trimStart()
val third = lines[3].value.trimStart()
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can OFTEN be eliminated
if ((first.startsWith("sta ") && second.startsWith("lda ")) ||
@@ -393,7 +401,6 @@ private fun optimizeStoreLoadSame(
(first.startsWith("ldy ") && second.startsWith("ldy ")) ||
(first.startsWith("ldx ") && second.startsWith("ldx "))
) {
val third = lines[3].value.trimStart()
val attemptRemove =
if(third.isBranch()) {
// a branch instruction follows, we can only remove the load instruction if
@@ -508,6 +515,7 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<Indexe
for (lines in linesByFour) {
val first = lines[0].value
val second = lines[1].value
val third = lines[2].value
if ((" jsr" in first || "\tjsr" in first ) && (" rts" in second || "\trts" in second)) {
mods += Modification(lines[0].index, false, lines[0].value.replace("jsr", "jmp"))
mods += Modification(lines[1].index, true, null)
@@ -577,6 +585,58 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<Indexe
mods += Modification(lines[3].index, true, null)
}
}
fun sameLabel(branchInstr: String, jumpInstr: String, labelInstr: String): Boolean {
if('(' in jumpInstr) return false // indirect jump cannot be replaced
val label = labelInstr.trimEnd().substringBefore(':').substringBefore(' ').substringBefore('\t')
val branchLabel = branchInstr.trimStart().substring(3).trim()
return label==branchLabel
}
// beq Label + jmp Addr + Label -> bne Addr
if((" jmp" in second || "\tjmp " in second) && haslabel(third)) {
if((" beq " in first || "\tbeq " in first) && sameLabel(first, second, third)) {
val branch = second.replace("jmp", "bne")
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[1].index, false, branch))
}
else if((" bne " in first || "\tbne " in first) && sameLabel(first, second, third)) {
val branch = second.replace("jmp", "beq")
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[1].index, false, branch))
}
else if((" bcc " in first || "\tbcc " in first) && sameLabel(first, second, third)){
val branch = second.replace("jmp", "bcs")
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[1].index, false, branch))
}
else if((" bcs " in first || "\tbcs " in first) && sameLabel(first, second, third)) {
val branch = second.replace("jmp", "bcc")
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[1].index, false, branch))
}
else if((" bpl " in first || "\tbpl " in first) && sameLabel(first, second, third)) {
val branch = second.replace("jmp", "bmi")
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[1].index, false, branch))
}
else if((" bmi " in first || "\tbmi " in first) && sameLabel(first, second, third)) {
val branch = second.replace("jmp", "bpl")
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[1].index, false, branch))
}
else if((" bvc " in first || "\tbvc " in first) && sameLabel(first, second, third)) {
val branch = second.replace("jmp", "bvs")
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[1].index, false, branch))
}
else if((" bvs " in first || "\tbvs " in first) && sameLabel(first, second, third)) {
val branch = second.replace("jmp", "bvc")
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[1].index, false, branch))
}
}
}
return mods
}
@@ -614,6 +674,52 @@ private fun optimizeUselessPushPopStack(linesByFour: Sequence<List<IndexedValue<
optimize('a', lines)
optimize('x', lines)
optimize('y', lines)
val first = lines[1].value.trimStart()
val second = lines[2].value.trimStart()
val third = lines[3].value.trimStart()
// phy + ldy + pla -> tya + ldy
// phx + ldx + pla -> txa + ldx
// pha + lda + pla -> nop
if(first=="phy" && second.startsWith("ldy ") && third=="pla") {
mods.add(Modification(lines[3].index, true, null))
mods.add(Modification(lines[1].index, false, " tya"))
}
else if(first=="phx" && second.startsWith("ldx ") && third=="pla") {
mods.add(Modification(lines[3].index, true, null))
mods.add(Modification(lines[1].index, false, " txa"))
}
else if(first=="pha" && second.startsWith("lda ") && third=="pla") {
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, true, null))
mods.add(Modification(lines[3].index, true, null))
}
}
return mods
}
private fun optimizeTSBtoRegularOr(linesByFour: Sequence<List<IndexedValue<String>>>): List<Modification> {
// Asm peephole: lda var2 / tsb var1 / lda var1 Replace this with this to save 1 cycle: lda var1 / ora var2 / sta var1
val mods = mutableListOf<Modification>()
for(lines in linesByFour) {
val first = lines[0].value.trimStart()
val second = lines[1].value.trimStart()
val third = lines[2].value.trimStart()
if(first.startsWith("lda") && second.startsWith("tsb") && third.startsWith("lda")) {
val operand1 = first.substring(3)
val operand2 = second.substring(3)
val operand3 = third.substring(3)
if(operand1!=operand2 && operand2==operand3) {
mods.add(Modification(lines[0].index, false, " lda $operand2"))
mods.add(Modification(lines[1].index, false, " ora $operand1"))
mods.add(Modification(lines[2].index, false, " sta $operand2"))
}
}
}
return mods
}
@@ -33,7 +33,6 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
"min__byte", "min__ubyte", "min__word", "min__uword" -> funcMin(fcall, resultRegister)
"max__byte", "max__ubyte", "max__word", "max__uword" -> funcMax(fcall, resultRegister)
"abs__byte", "abs__word", "abs__float" -> funcAbs(fcall, resultRegister, sscope)
"any", "all" -> funcAnyAll(fcall, resultRegister, sscope)
"sgn" -> funcSgn(fcall, resultRegister, sscope)
"sqrt__ubyte", "sqrt__uword", "sqrt__float" -> funcSqrt(fcall, resultRegister, sscope)
"divmod__ubyte" -> funcDivmod(fcall)
@@ -44,8 +43,6 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
"ror2" -> funcRor2(fcall)
"setlsb" -> funcSetLsbMsb(fcall, false)
"setmsb" -> funcSetLsbMsb(fcall, true)
"sort" -> funcSort(fcall)
"reverse" -> funcReverse(fcall)
"memory" -> funcMemory(fcall, discardResult, resultRegister)
"peekw" -> funcPeekW(fcall, resultRegister)
"peekf" -> funcPeekF(fcall, resultRegister)
@@ -68,6 +65,8 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
"cmp" -> funcCmp(fcall)
"callfar" -> funcCallFar(fcall, resultRegister)
"call" -> funcCall(fcall)
"prog8_ifelse_bittest_set" -> throw AssemblyError("prog8_ifelse_bittest_set() should have been translated as part of an ifElse statement")
"prog8_ifelse_bittest_notset" -> throw AssemblyError("prog8_ifelse_bittest_notset() should have been translated as part of an ifElse statement")
"prog8_lib_stringcompare" -> funcStringCompare(fcall, resultRegister)
"prog8_lib_square_byte" -> funcSquare(fcall, DataType.UBYTE, resultRegister)
"prog8_lib_square_word" -> funcSquare(fcall, DataType.UWORD, resultRegister)
@@ -403,115 +402,6 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
}
private fun funcReverse(fcall: PtBuiltinFunctionCall) {
val variable = fcall.args.single() as PtIdentifier
val symbol = asmgen.symbolTable.lookup(variable.name)
val (dt, numElements) = when(symbol) {
is StStaticVariable -> symbol.dt to symbol.length!!
is StMemVar -> symbol.dt to symbol.length!!
else -> DataType.UNDEFINED to 0
}
val varName = asmgen.asmVariableName(variable)
when (dt) {
DataType.ARRAY_UB, DataType.ARRAY_B -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements
jsr prog8_lib.func_reverse_b""")
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements
jsr prog8_lib.func_reverse_w""")
}
DataType.STR -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #${numElements-1}
jsr prog8_lib.func_reverse_b""")
}
DataType.ARRAY_F -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements
jsr floats.func_reverse_f""")
}
in SplitWordArrayTypes -> {
// reverse the lsb and msb arrays both, independently
asmgen.out("""
lda #<${varName}_lsb
ldy #>${varName}_lsb
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements
jsr prog8_lib.func_reverse_b
lda #<${varName}_msb
ldy #>${varName}_msb
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements
jsr prog8_lib.func_reverse_b""")
}
else -> throw AssemblyError("weird type")
}
}
private fun funcSort(fcall: PtBuiltinFunctionCall) {
val variable = fcall.args.single() as PtIdentifier
val symbol = asmgen.symbolTable.lookup(variable.name)
val varName = asmgen.asmVariableName(variable)
val (dt, numElements) = when(symbol) {
is StStaticVariable -> symbol.dt to symbol.length!!
is StMemVar -> symbol.dt to symbol.length!!
else -> DataType.UNDEFINED to 0
}
when (dt) {
DataType.ARRAY_UB, DataType.ARRAY_B -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements""")
asmgen.out(if (dt == DataType.ARRAY_UB) " jsr prog8_lib.func_sort_ub" else " jsr prog8_lib.func_sort_b")
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements""")
asmgen.out(if (dt == DataType.ARRAY_UW) " jsr prog8_lib.func_sort_uw" else " jsr prog8_lib.func_sort_w")
}
DataType.STR -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #${numElements-1}
jsr prog8_lib.func_sort_ub""")
}
DataType.ARRAY_F -> throw AssemblyError("sorting of floating point array is not supported")
in SplitWordArrayTypes -> TODO("split words sort")
else -> throw AssemblyError("weird type")
}
}
private fun funcRor2(fcall: PtBuiltinFunctionCall) {
val what = fcall.args.single()
when (what.type) {
@@ -837,63 +727,6 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, true, true)
}
private fun funcAnyAll(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
val dt = fcall.args.single().type
val array = fcall.args[0] as PtIdentifier
when (dt) {
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> {
outputAddressAndLengthOfArray(array)
asmgen.out(" jsr prog8_lib.func_${fcall.name}_b_into_A")
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
outputAddressAndLengthOfArray(array)
asmgen.out(" jsr prog8_lib.func_${fcall.name}_w_into_A")
}
DataType.ARRAY_F -> {
outputAddressAndLengthOfArray(array)
asmgen.out(" jsr floats.func_${fcall.name}_f_into_A")
}
in SplitWordArrayTypes -> {
val numElements = (asmgen.symbolTable.lookup(array.name) as StStaticVariable).length
when(fcall.name) {
"any" -> {
// any(lsb-array) or any(msb-array)
val arrayName = asmgen.asmVariableName(array)
asmgen.out("""
lda #<${arrayName}_lsb
ldy #>${arrayName}_lsb
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements
""")
asmgen.out(" jsr prog8_lib.func_${fcall.name}_b_into_A")
asmgen.out(" bne +") // shortcircuit
asmgen.out("""
pha
lda #<${arrayName}_msb
ldy #>${arrayName}_msb
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements
""")
asmgen.out(" jsr prog8_lib.func_${fcall.name}_b_into_A")
asmgen.out("""
sta P8ZP_SCRATCH_REG
pla
ora P8ZP_SCRATCH_REG
+""")
}
"all" -> {
TODO("split words all")
}
else -> throw AssemblyError("weird call")
}
}
else -> throw AssemblyError("weird type $dt")
}
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, fcall.position, scope, asmgen), CpuRegister.A, dt in SignedDatatypes, true)
}
private fun funcAbs(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
translateArguments(fcall, scope)
val dt = fcall.args.single().type
@@ -82,7 +82,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
is PtAddressOf -> false
is PtIdentifier -> false
is PtIrRegister -> false
is PtMemoryByte -> return usesOtherRegistersWhileEvaluating(arg.address)
is PtMemoryByte -> true // TODO might not actually need extra registers if the value has to end up in A
is PtNumber -> false
is PtBool -> false
else -> true
@@ -110,9 +110,9 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
throw AssemblyError("call argument evaluation problem: can't save cpu statusregister parameter ${call.position}")
}
else {
if(usedX()) asmgen.saveRegisterStack(CpuRegister.X, false)
if(usedY()) asmgen.saveRegisterStack(CpuRegister.Y, false)
if(usedA()) asmgen.saveRegisterStack(CpuRegister.A, false)
if(usedX()) asmgen.saveRegisterStack(CpuRegister.X, usedA())
if(usedY()) asmgen.saveRegisterStack(CpuRegister.Y, usedA())
if(usedA()) asmgen.saveRegisterStack(CpuRegister.A, usedA())
val used = argumentViaRegister(sub, IndexedValue(it, param.second), arg)
if(usedA()) asmgen.restoreRegisterStack(CpuRegister.A, false)
if(usedY()) asmgen.restoreRegisterStack(CpuRegister.Y, true)
@@ -83,9 +83,80 @@ internal class IfElseAsmGen(private val program: PtProgram,
}
private fun fallbackTranslateForSimpleCondition(ifElse: PtIfElse) {
val bittest = ifElse.condition as? PtBuiltinFunctionCall
val jumpAfterIf = ifElse.ifScope.children.singleOrNull() as? PtJump
if(bittest!=null && bittest.name.startsWith("prog8_ifelse_bittest_")) {
val variable = bittest.args[0] as PtIdentifier
val bitnumber = (bittest.args[1] as PtNumber).number.toInt()
val testForBitSet = bittest.name.endsWith("_set")
when (bitnumber) {
7 -> {
// test via bit + N flag
asmgen.out(" bit ${variable.name}")
if(testForBitSet) {
if(jumpAfterIf!=null) {
val (asmLabel, indirect) = asmgen.getJumpTarget(jumpAfterIf)
if(indirect)
throw AssemblyError("cannot BIT to indirect label ${ifElse.position}")
if(ifElse.hasElse())
throw AssemblyError("didn't expect else part here ${ifElse.position}")
else
asmgen.out(" bmi $asmLabel")
}
else
translateIfElseBodies("bpl", ifElse)
} else {
if(jumpAfterIf!=null) {
val (asmLabel, indirect) = asmgen.getJumpTarget(jumpAfterIf)
if(indirect)
throw AssemblyError("cannot BIT to indirect label ${ifElse.position}")
if(ifElse.hasElse())
throw AssemblyError("didn't expect else part here ${ifElse.position}")
else
asmgen.out(" bpl $asmLabel")
}
else
translateIfElseBodies("bmi", ifElse)
}
return
}
6 -> {
// test via bit + V flag
asmgen.out(" bit ${variable.name}")
if(testForBitSet) {
if(jumpAfterIf!=null) {
val (asmLabel, indirect) = asmgen.getJumpTarget(jumpAfterIf)
if(indirect)
throw AssemblyError("cannot BIT to indirect label ${ifElse.position}")
if(ifElse.hasElse())
throw AssemblyError("didn't expect else part here ${ifElse.position}")
else
asmgen.out(" bvs $asmLabel")
}
else
translateIfElseBodies("bvc", ifElse)
} else {
if(jumpAfterIf!=null) {
val (asmLabel, indirect) = asmgen.getJumpTarget(jumpAfterIf)
if(indirect)
throw AssemblyError("cannot BIT to indirect label ${ifElse.position}")
if(ifElse.hasElse())
throw AssemblyError("didn't expect else part here ${ifElse.position}")
else
asmgen.out(" bvc $asmLabel")
}
else
translateIfElseBodies("bvs", ifElse)
}
return
}
else -> throw AssemblyError("prog8_ifelse_bittest can only work on bits 7 and 6")
}
}
// the condition is "simple" enough to just assign its 0/1 value to a register and branch on that
assignConditionValueToRegisterAndTest(ifElse.condition)
val jumpAfterIf = ifElse.ifScope.children.singleOrNull() as? PtJump
if(jumpAfterIf!=null)
translateJumpElseBodies("bne", "beq", jumpAfterIf, ifElse.elseScope)
else
@@ -405,14 +476,14 @@ internal class IfElseAsmGen(private val program: PtProgram,
val condition = stmt.condition as PtBinaryExpression
if(signed) {
// X>Y --> Y<X
asmgen.assignExpressionToRegister(condition.right, RegisterOrPair.A, signed)
asmgen.assignExpressionToRegister(condition.right, RegisterOrPair.A, true)
cmpAwithByteValue(condition.left, true)
if (jumpAfterIf != null)
translateJumpElseBodies("bmi", "bpl", jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bpl", stmt)
} else {
asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.A, signed)
asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.A, false)
cmpAwithByteValue(condition.right, false)
if(jumpAfterIf!=null) {
val (asmLabel, indirect) = asmgen.getJumpTarget(jumpAfterIf)
@@ -848,10 +919,10 @@ _jump jmp ($asmLabel)
}
} else {
asmgen.loadScaledArrayIndexIntoRegister(value, CpuRegister.Y)
if(value.splitWords) {
return compareLsbMsb("${varname}_lsb,y", "${varname}_msb,y")
return if(value.splitWords) {
compareLsbMsb("${varname}_lsb,y", "${varname}_msb,y")
} else {
return compareLsbMsb("$varname,y", "$varname+1,y")
compareLsbMsb("$varname,y", "$varname+1,y")
}
}
}
@@ -985,10 +1056,10 @@ _jump jmp ($asmLabel)
}
} else {
asmgen.loadScaledArrayIndexIntoRegister(value, CpuRegister.Y)
if(value.splitWords) {
return compareLsbMsb("${varname}_lsb,y", "${varname}_msb,y")
return if(value.splitWords) {
compareLsbMsb("${varname}_lsb,y", "${varname}_msb,y")
} else {
return compareLsbMsb("$varname,y", "$varname+1,y")
compareLsbMsb("$varname,y", "$varname+1,y")
}
}
}
@@ -284,12 +284,6 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
}
SourceStorageKind.MEMORY -> {
fun assignViaExprEval(expression: PtExpression) {
assignExpressionToVariable(expression, "P8ZP_SCRATCH_W2", DataType.UWORD)
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2", false)
assignRegisterByte(assign.target, CpuRegister.A, false, true)
}
val value = assign.source.memory!!
when (value.address) {
is PtNumber -> {
@@ -304,10 +298,10 @@ internal class AssignmentAsmGen(private val program: PtProgram,
if(asmgen.tryOptimizedPointerAccessWithA(addrExpr, false)) {
assignRegisterByte(assign.target, CpuRegister.A, false, true)
} else {
assignViaExprEval(value.address)
assignByteFromAddressExpression(value.address, assign.target)
}
}
else -> assignViaExprEval(value.address)
else -> assignByteFromAddressExpression(value.address, assign.target)
}
}
SourceStorageKind.EXPRESSION -> {
@@ -319,6 +313,106 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
}
private fun assignByteFromAddressExpression(address: PtExpression, target: AsmAssignTarget) {
if(address is PtBinaryExpression) {
if(address.operator=="+" && address.right.type==DataType.UWORD) {
if (address.left is PtIdentifier) {
// use (zp),Y instead of explicitly calculating the full zp pointer value
val pointer = (address.left as PtIdentifier).name
when(val index=address.right) {
is PtIdentifier -> {
val indexName = index.name
asmgen.out("""
lda $pointer
sta P8ZP_SCRATCH_W2
lda $pointer+1
clc
adc $indexName+1
sta P8ZP_SCRATCH_W2+1
ldy $indexName
lda (P8ZP_SCRATCH_W2),y""")
assignRegisterByte(target, CpuRegister.A, false, true)
return
}
is PtNumber -> {
val indexValue = index.number.toString()
asmgen.out("""
lda $pointer
sta P8ZP_SCRATCH_W2
lda $pointer+1
clc
adc #>$indexValue
sta P8ZP_SCRATCH_W2+1
ldy #<$indexValue
lda (P8ZP_SCRATCH_W2),y""")
assignRegisterByte(target, CpuRegister.A, false, true)
return
}
else -> {}
}
}
}
// else if(address.operator=="-") {
// // does this ever occur? we could optimize it too, but it seems like a pathological case
// }
}
assignExpressionToVariable(address, "P8ZP_SCRATCH_W2", DataType.UWORD)
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2", false)
assignRegisterByte(target, CpuRegister.A, false, true)
}
private fun storeByteInAToAddressExpression(address: PtExpression, saveA: Boolean) {
if(address is PtBinaryExpression) {
if(address.operator=="+") {
if (address.left is PtIdentifier && address.right.type==DataType.UWORD) {
// use (zp),Y instead of explicitly calculating the full zp pointer value
val pointer = (address.left as PtIdentifier).name
when(val index=address.right) {
is PtIdentifier -> {
val indexName = index.name
asmgen.out("""
tax
lda $pointer
sta P8ZP_SCRATCH_W2
lda $pointer+1
clc
adc $indexName+1
sta P8ZP_SCRATCH_W2+1
ldy $indexName
txa
sta (P8ZP_SCRATCH_W2),y""")
return
}
is PtNumber -> {
val indexValue = index.number.toString()
asmgen.out("""
tax
lda $pointer
sta P8ZP_SCRATCH_W2
lda $pointer+1
clc
adc #>$indexValue
sta P8ZP_SCRATCH_W2+1
ldy #<$indexValue
txa
sta (P8ZP_SCRATCH_W2),y""")
return
}
else -> {}
}
}
}
// else if(address.operator=="-") {
// // does this ever occur? we could optimize it too, but it seems like a pathological case
// }
}
if(saveA) asmgen.out(" pha")
assignExpressionToVariable(address, "P8ZP_SCRATCH_W2", DataType.UWORD)
if(saveA) asmgen.out(" pla")
asmgen.storeAIntoZpPointerVar("P8ZP_SCRATCH_W2", false)
}
private fun assignExpression(assign: AsmAssignment, scope: IPtSubroutine?) {
when(val value = assign.source.expression!!) {
is PtAddressOf -> {
@@ -703,8 +797,8 @@ internal class AssignmentAsmGen(private val program: PtProgram,
RegisterOrPair.Y -> "y"
else -> TODO("comparison to word register")
}
val assignTrue = PtInlineAssembly(" ld${reg} #1", false, assign.target.position)
val assignFalse = PtInlineAssembly(" ld${reg} #0", false, assign.target.position)
val assignTrue = PtInlineAssembly("\tld${reg} #1", false, assign.target.position)
val assignFalse = PtInlineAssembly("\tld${reg} #0", false, assign.target.position)
ifPart.add(assignTrue)
elsePart.add(assignFalse)
val ifelse = PtIfElse(assign.position)
@@ -754,7 +848,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
sta ${assign.target.asmVarname}+1""", false, assign.target.position)
}
} else {
assignTrue = PtInlineAssembly(" lda #1\n sta ${assign.target.asmVarname}", false, assign.target.position)
assignTrue = PtInlineAssembly("\tlda #1\n sta ${assign.target.asmVarname}", false, assign.target.position)
}
}
TargetStorageKind.MEMORY -> {
@@ -839,6 +933,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
private fun optimizedDivideExpr(expr: PtBinaryExpression, target: AsmAssignTarget): Boolean {
// replacing division by shifting is done in an optimizer step.
when(expr.type) {
DataType.UBYTE -> {
assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
@@ -1010,7 +1105,10 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
} else {
if (signed && shifts > 0) {
asmgen.out(" ldy #$shifts | jsr math.lsr_byte_A")
if(shifts==1)
asmgen.out(" cmp #$80 | ror a")
else
asmgen.out(" ldy #$shifts | jsr math.lsr_byte_A")
} else {
repeat(shifts) {
asmgen.out(" lsr a")
@@ -1044,7 +1142,18 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
} else {
if(signed && shifts>0) {
asmgen.out(" ldx #$shifts | jsr math.lsr_word_AY")
if(shifts==1) {
asmgen.out("""
pha
tya
cmp #$80
ror a
tay
pla
ror a""")
}
else
asmgen.out(" ldx #$shifts | jsr math.lsr_word_AY")
} else {
if(shifts>0) {
asmgen.out(" sty P8ZP_SCRATCH_B1")
@@ -1108,10 +1217,17 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
is PtNumber -> {
assignExpressionToRegister(left, RegisterOrPair.A, dt==DataType.BYTE)
if(expr.operator=="+")
asmgen.out(" clc | adc #${right.number.toHex()}")
else
asmgen.out(" sec | sbc #${right.number.toHex()}")
if(right.number==1.0 && asmgen.isTargetCpu(CpuType.CPU65c02)) {
if (expr.operator == "+")
asmgen.out(" inc a")
else
asmgen.out(" dec a")
} else {
if (expr.operator == "+")
asmgen.out(" clc | adc #${right.number.toHex()}")
else
asmgen.out(" sec | sbc #${right.number.toHex()}")
}
assignRegisterByte(target, CpuRegister.A, dt in SignedDatatypes, true)
return true
}
@@ -1246,8 +1362,39 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
is PtNumber -> {
assignExpressionToRegister(left, RegisterOrPair.AY, dt==DataType.WORD)
if(expr.operator=="+") {
asmgen.out("""
if(right.number==1.0 && asmgen.isTargetCpu(CpuType.CPU65c02)) {
if(expr.operator=="+") {
asmgen.out("""
inc a
bne +
iny
+""")
} else {
asmgen.out("""
dec a
cmp #255
bne +
dey
+""")
}
} else if(dt!=DataType.WORD && right.number.toInt() in 0..255) {
if(expr.operator=="+") {
asmgen.out("""
clc
adc #${right.number.toHex()}
bcc +
iny
+""") } else if(expr.operator=="-") {
asmgen.out("""
sec
sbc #${right.number.toHex()}
bcs +
dey
+""")
}
} else {
if(expr.operator=="+") {
asmgen.out("""
clc
adc #<${right.number.toHex()}
tax
@@ -1255,8 +1402,8 @@ internal class AssignmentAsmGen(private val program: PtProgram,
adc #>${right.number.toHex()}
tay
txa""")
} else if(expr.operator=="-") {
asmgen.out("""
} else if(expr.operator=="-") {
asmgen.out("""
sec
sbc #<${right.number.toHex()}
tax
@@ -1264,6 +1411,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
sbc #>${right.number.toHex()}
tay
txa""")
}
}
assignRegisterpairWord(target, RegisterOrPair.AY)
return true
@@ -3143,11 +3291,20 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
else {
if (regs !in Cx16VirtualRegisters) {
when (regs) {
RegisterOrPair.AX -> asmgen.out(" pha | txa | pha")
RegisterOrPair.AY -> asmgen.out(" pha | tya | pha")
RegisterOrPair.XY -> asmgen.out(" txa | pha | tya | pha")
else -> throw AssemblyError("expected reg pair")
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
when (regs) {
RegisterOrPair.AX -> asmgen.out(" pha | phx")
RegisterOrPair.AY -> asmgen.out(" pha | phy")
RegisterOrPair.XY -> asmgen.out(" phx | phy")
else -> throw AssemblyError("expected reg pair")
}
} else {
when (regs) {
RegisterOrPair.AX -> asmgen.out(" pha | txa | pha")
RegisterOrPair.AY -> asmgen.out(" pha | tya | pha")
RegisterOrPair.XY -> asmgen.out(" txa | pha | tya | pha")
else -> throw AssemblyError("expected reg pair")
}
}
asmgen.loadScaledArrayIndexIntoRegister(target.array, CpuRegister.Y)
asmgen.out("""
@@ -3186,11 +3343,20 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
else {
if (regs !in Cx16VirtualRegisters) {
when (regs) {
RegisterOrPair.AX -> asmgen.out(" pha | txa | pha")
RegisterOrPair.AY -> asmgen.out(" pha | tya | pha")
RegisterOrPair.XY -> asmgen.out(" txa | pha | tya | pha")
else -> throw AssemblyError("expected reg pair")
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
when (regs) {
RegisterOrPair.AX -> asmgen.out(" pha | phx")
RegisterOrPair.AY -> asmgen.out(" pha | phy")
RegisterOrPair.XY -> asmgen.out(" phx | phy")
else -> throw AssemblyError("expected reg pair")
}
} else {
when (regs) {
RegisterOrPair.AX -> asmgen.out(" pha | txa | pha")
RegisterOrPair.AY -> asmgen.out(" pha | tya | pha")
RegisterOrPair.XY -> asmgen.out(" txa | pha | tya | pha")
else -> throw AssemblyError("expected reg pair")
}
}
asmgen.loadScaledArrayIndexIntoRegister(target.array, CpuRegister.Y)
asmgen.out("""
@@ -3689,17 +3855,8 @@ internal class AssignmentAsmGen(private val program: PtProgram,
fun storeViaExprEval() {
when(addressExpr) {
is PtNumber, is PtIdentifier -> {
assignExpressionToVariable(addressExpr, "P8ZP_SCRATCH_W2", DataType.UWORD)
asmgen.storeAIntoZpPointerVar("P8ZP_SCRATCH_W2", false)
}
else -> {
// same as above but we need to save the A register
asmgen.out(" pha")
assignExpressionToVariable(addressExpr, "P8ZP_SCRATCH_W2", DataType.UWORD)
asmgen.out(" pla")
asmgen.storeAIntoZpPointerVar("P8ZP_SCRATCH_W2", false)
}
is PtNumber, is PtIdentifier -> storeByteInAToAddressExpression(addressExpr, false)
else -> storeByteInAToAddressExpression(addressExpr, true)
}
}
@@ -1086,6 +1086,21 @@ $shortcutLabel:""")
}
}
if(asmgen.isTargetCpu(CpuType.CPU65c02)) {
if(operator=="&" && value is PtPrefix && value.operator=="~") {
// M &= ~A --> use TRB 65c02 instruction for that
asmgen.assignExpressionToRegister(value.value, RegisterOrPair.A, dt in SignedDatatypes)
asmgen.out(" trb $name")
return
}
else if(operator=="|") {
// M |= A --> use TSB 65c02 instruction for that
asmgen.assignExpressionToRegister(value, RegisterOrPair.A, dt in SignedDatatypes)
asmgen.out(" tsb $name")
return
}
}
// normal evaluation
asmgen.assignExpressionToRegister(value, RegisterOrPair.A, dt in SignedDatatypes)
inplacemodificationRegisterAwithVariableWithSwappedOperands(operator, name, dt in SignedDatatypes)
@@ -1094,6 +1109,15 @@ $shortcutLabel:""")
private fun inplacemodificationByteVariableWithVariable(name: String, dt: DataType, operator: String, otherName: String) {
// note: no logical and/or shortcut here, not worth it due to simple right operand
if(asmgen.isTargetCpu(CpuType.CPU65c02)) {
if(operator=="|") {
// M |= A --> use TSB 65c02 instruction for that
asmgen.out(" lda $otherName | tsb $name")
return
}
}
asmgen.out(" lda $name")
inplacemodificationRegisterAwithVariable(operator, otherName, dt in SignedDatatypes)
asmgen.out(" sta $name")
@@ -1459,6 +1483,7 @@ $shortcutLabel:""")
asmgen.out(" lda $name | ldy #$value | jsr math.multiply_bytes | sta $name")
}
"/" -> {
// replacing division by shifting is done in an optimizer step.
if (dt == DataType.UBYTE)
asmgen.out(" lda $name | ldy #$value | jsr math.divmod_ub_asm | sty $name")
else
@@ -1642,7 +1667,7 @@ $shortcutLabel:""")
}
private fun immediateOrInplace(name: String, value: Int) {
if(asmgen.isTargetCpu(CpuType.CPU65c02) && ((value and (value-1))==0)) {
if(asmgen.isTargetCpu(CpuType.CPU65c02)) {
asmgen.out(" lda #$value | tsb $name") // set bit
} else {
asmgen.out(" lda $name | ora #$value | sta $name")
@@ -1807,33 +1832,36 @@ $shortcutLabel:""")
}
}
"/" -> {
if(value==0)
// replacing division by shifting is done in an optimizer step.
if(value==0) {
throw AssemblyError("division by zero")
if(dt==DataType.WORD) {
asmgen.out("""
lda $lsb
ldy $msb
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<$value
ldy #>$value
jsr math.divmod_w_asm
sta $lsb
sty $msb
""")
}
else {
asmgen.out("""
lda $lsb
ldy $msb
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<$value
ldy #>$value
jsr math.divmod_uw_asm
sta $lsb
sty $msb
""")
} else {
if(dt==DataType.WORD) {
asmgen.out("""
lda $lsb
ldy $msb
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<$value
ldy #>$value
jsr math.divmod_w_asm
sta $lsb
sty $msb
""")
}
else {
asmgen.out("""
lda $lsb
ldy $msb
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<$value
ldy #>$value
jsr math.divmod_uw_asm
sta $lsb
sty $msb
""")
}
}
}
"%" -> {
+1 -1
View File
@@ -29,7 +29,7 @@ dependencies {
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.0"
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.9.0'
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.9.1'
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
@@ -1390,9 +1390,9 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
val valueReg = codeGen.registers.nextFree()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, vmDt, reg1=valueReg, labelSymbol = array.variable.name, symbolOffset = constIndex*eltSize)
when(comparisonOperator) {
"==" -> it += IRInstruction(Opcode.SZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
"!=" -> it += IRInstruction(Opcode.SNZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
it += when(comparisonOperator) {
"==" -> IRInstruction(Opcode.SZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
"!=" -> IRInstruction(Opcode.SNZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
"<" -> return null // TODO("array <0 inplace")) // TODO?
"<=" -> return null // TODO("array <=0 inplace")) // TODO?
">" -> return null // TODO("array >0 inplace")) // TODO?
@@ -1413,9 +1413,9 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
addToResult(result, indexTr, indexTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADX, vmDt, reg1=valueReg, reg2=indexTr.resultReg, labelSymbol = array.variable.name)
when(comparisonOperator) {
"==" -> it += IRInstruction(Opcode.SZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
"!=" -> it += IRInstruction(Opcode.SNZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
it += when(comparisonOperator) {
"==" -> IRInstruction(Opcode.SZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
"!=" -> IRInstruction(Opcode.SNZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
"<" -> return null // TODO("array <0 inplace")) // TODO?
"<=" -> return null // TODO("array <=0 inplace")) // TODO?
">" -> return null // TODO("array >0 inplace")) // TODO?
@@ -9,8 +9,6 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
fun translate(call: PtBuiltinFunctionCall): ExpressionCodeResult {
return when(call.name) {
"any" -> funcAny(call)
"all" -> funcAll(call)
"abs__byte", "abs__word", "abs__float" -> funcAbs(call)
"cmp" -> funcCmp(call)
"sgn" -> funcSgn(call)
@@ -34,14 +32,14 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
"clamp__byte", "clamp__ubyte", "clamp__word", "clamp__uword" -> funcClamp(call)
"min__byte", "min__ubyte", "min__word", "min__uword" -> funcMin(call)
"max__byte", "max__ubyte", "max__word", "max__uword" -> funcMax(call)
"sort" -> funcSort(call)
"reverse" -> funcReverse(call)
"setlsb" -> funcSetLsbMsb(call, false)
"setmsb" -> funcSetLsbMsb(call, true)
"rol" -> funcRolRor(call)
"ror" -> funcRolRor(call)
"rol2" -> funcRolRor(call)
"ror2" -> funcRolRor(call)
"prog8_ifelse_bittest_set" -> throw AssemblyError("prog8_ifelse_bittest_set() should have been translated as part of an ifElse statement")
"prog8_ifelse_bittest_notset" -> throw AssemblyError("prog8_ifelse_bittest_notset() should have been translated as part of an ifElse statement")
"prog8_lib_stringcompare" -> funcStringCompare(call)
"prog8_lib_square_byte" -> funcSquare(call, IRDataType.BYTE)
"prog8_lib_square_word" -> funcSquare(call, IRDataType.WORD)
@@ -132,10 +130,10 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val addressTr = exprGen.translateExpression(call.args[0])
addToResult(result, addressTr, addressTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.CALLI, reg1 = addressTr.resultReg), null)
if(call.void)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
return if(call.void)
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
else
return ExpressionCodeResult(result, IRDataType.WORD, codeGen.registers.nextFree(), -1) // TODO actually the result is returned in CPU registers AY...
ExpressionCodeResult(result, IRDataType.WORD, codeGen.registers.nextFree(), -1) // TODO actually the result is returned in CPU registers AY...
}
private fun funcCallfar(call: PtBuiltinFunctionCall): ExpressionCodeResult {
@@ -204,79 +202,6 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
return ExpressionCodeResult(result, dt, leftTr.resultReg, -1)
}
private fun funcAny(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val arrayName = call.args[0] as PtIdentifier
val arrayLength = codeGen.symbolTable.getLength(arrayName.name)
val result = mutableListOf<IRCodeChunkBase>()
val lengthReg = codeGen.registers.nextFree()
if(arrayName.type in SplitWordArrayTypes) {
// any(lsb-array) or any(msb-array)
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val trLsb = exprGen.translateExpression(PtIdentifier(arrayName.name+"_lsb", DataType.ARRAY_UB, call.position))
addToResult(result, trLsb, trLsb.resultReg, -1)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = arrayLength), null)
result += codeGen.makeSyscall(IMSyscall.ANY_BYTE, listOf(IRDataType.WORD to trLsb.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to trLsb.resultReg)
val shortcircuitLabel = codeGen.createLabelName()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = trLsb.resultReg, immediate = 0)
it += IRInstruction(Opcode.BSTNE, labelSymbol = shortcircuitLabel)
it += IRInstruction(Opcode.PREPARECALL, immediate = 2)
}
val trMsb = exprGen.translateExpression(PtIdentifier(arrayName.name+"_msb", DataType.ARRAY_UB, call.position))
addToResult(result, trMsb, trMsb.resultReg, -1)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = arrayLength), null)
result += codeGen.makeSyscall(IMSyscall.ANY_BYTE, listOf(IRDataType.WORD to trMsb.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to trMsb.resultReg)
addInstr(result, IRInstruction(Opcode.ORR, IRDataType.BYTE, reg1=trLsb.resultReg, reg2=trMsb.resultReg), null)
result += IRCodeChunk(shortcircuitLabel, null)
return ExpressionCodeResult(result, IRDataType.BYTE, trLsb.resultReg, -1)
}
val syscall =
when (arrayName.type) {
DataType.ARRAY_UB,
DataType.ARRAY_B -> IMSyscall.ANY_BYTE
DataType.ARRAY_UW,
DataType.ARRAY_W -> IMSyscall.ANY_WORD
DataType.ARRAY_F -> IMSyscall.ANY_FLOAT
else -> throw IllegalArgumentException("weird type")
}
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val tr = exprGen.translateExpression(arrayName)
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = arrayLength!! and 255), null)
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to tr.resultReg)
return ExpressionCodeResult(result, IRDataType.BYTE, tr.resultReg, -1)
}
private fun funcAll(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val arrayName = call.args[0] as PtIdentifier
val arrayLength = codeGen.symbolTable.getLength(arrayName.name)
if(arrayName.type in SplitWordArrayTypes) {
// this is a bit complicated to calculate.... have to check all recombined (lsb,msb) words for $0000
TODO("all(split words $arrayName)")
}
val syscall =
when(arrayName.type) {
DataType.ARRAY_UB,
DataType.ARRAY_B -> IMSyscall.ALL_BYTE
DataType.ARRAY_UW,
DataType.ARRAY_W -> IMSyscall.ALL_WORD
DataType.ARRAY_F -> IMSyscall.ALL_FLOAT
else -> throw IllegalArgumentException("weird type")
}
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val tr = exprGen.translateExpression(arrayName)
addToResult(result, tr, tr.resultReg, -1)
val lengthReg = codeGen.registers.nextFree()
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = arrayLength!! and 255), null)
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to tr.resultReg)
return ExpressionCodeResult(result, IRDataType.BYTE, tr.resultReg, -1)
}
private fun funcAbs(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val sourceDt = call.args.single().type
val result = mutableListOf<IRCodeChunkBase>()
@@ -369,65 +294,6 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
}
}
private fun funcReverse(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val arrayName = call.args[0] as PtIdentifier
val arrayLength = codeGen.symbolTable.getLength(arrayName.name)
val lengthReg = codeGen.registers.nextFree()
val result = mutableListOf<IRCodeChunkBase>()
if(arrayName.type in SplitWordArrayTypes) {
// reverse the lsb and msb arrays both, independently
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val trLsb = exprGen.translateExpression(PtIdentifier(arrayName.name+"_lsb", DataType.ARRAY_UB, call.position))
addToResult(result, trLsb, trLsb.resultReg, -1)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(arrayName.type==DataType.STR) arrayLength!!-1 else arrayLength), null)
result += codeGen.makeSyscall(IMSyscall.REVERSE_BYTES, listOf(IRDataType.WORD to trLsb.resultReg, IRDataType.BYTE to lengthReg), null)
val trMsb = exprGen.translateExpression(PtIdentifier(arrayName.name+"_msb", DataType.ARRAY_UB, call.position))
addToResult(result, trMsb, trMsb.resultReg, -1)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(arrayName.type==DataType.STR) arrayLength!!-1 else arrayLength), null)
result += codeGen.makeSyscall(IMSyscall.REVERSE_BYTES, listOf(IRDataType.WORD to trMsb.resultReg, IRDataType.BYTE to lengthReg), null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
val syscall =
when(arrayName.type) {
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.STR -> IMSyscall.REVERSE_BYTES
DataType.ARRAY_UW, DataType.ARRAY_W -> IMSyscall.REVERSE_WORDS
DataType.ARRAY_F -> IMSyscall.REVERSE_FLOATS
else -> throw IllegalArgumentException("weird type to reverse")
}
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val tr = exprGen.translateExpression(arrayName)
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(arrayName.type==DataType.STR) arrayLength!!-1 else arrayLength), null)
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
private fun funcSort(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val arrayName = call.args[0] as PtIdentifier
val arrayLength = codeGen.symbolTable.getLength(arrayName.name)
val syscall =
when(arrayName.type) {
DataType.ARRAY_UB -> IMSyscall.SORT_UBYTE
DataType.ARRAY_B -> IMSyscall.SORT_BYTE
DataType.ARRAY_UW -> IMSyscall.SORT_UWORD
DataType.ARRAY_W -> IMSyscall.SORT_WORD
DataType.STR -> IMSyscall.SORT_UBYTE
DataType.ARRAY_F -> throw IllegalArgumentException("sorting a floating point array is not supported")
in SplitWordArrayTypes -> TODO("split word sort")
else -> throw IllegalArgumentException("weird type to sort")
}
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val tr = exprGen.translateExpression(arrayName)
addToResult(result, tr, tr.resultReg, -1)
val lengthReg = codeGen.registers.nextFree()
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(arrayName.type==DataType.STR) arrayLength!!-1 else arrayLength), null)
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
private fun funcMkword(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val resultReg = codeGen.registers.nextFree()
@@ -1,5 +1,6 @@
package prog8.codegen.intermediate
import prog8.code.StNode
import prog8.code.StRomSub
import prog8.code.StSub
import prog8.code.ast.*
@@ -451,7 +452,36 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
fun translate(fcall: PtFunctionCall): ExpressionCodeResult {
when (val callTarget = codeGen.symbolTable.flat.getValue(fcall.name)) {
val callTarget = codeGen.symbolTable.flat.getValue(fcall.name)
if(callTarget.scopedName in listOf("sys.push", "sys.pushw", "sys.pop", "sys.popw")) {
// special case, these should be inlined, or even use specialized instructions. Instead of doing a normal subroutine call.
return translateStackFunctions(fcall, callTarget)
}
when(callTarget.scopedName) {
"sys.clear_carry" -> {
val chunk = mutableListOf<IRCodeChunkBase>()
addInstr(chunk, IRInstruction(Opcode.CLC), null)
return ExpressionCodeResult(chunk, IRDataType.BYTE, -1, -1)
}
"sys.set_carry" -> {
val chunk = mutableListOf<IRCodeChunkBase>()
addInstr(chunk, IRInstruction(Opcode.SEC), null)
return ExpressionCodeResult(chunk, IRDataType.BYTE, -1, -1)
}
"sys.clear_irqd" -> {
val chunk = mutableListOf<IRCodeChunkBase>()
addInstr(chunk, IRInstruction(Opcode.CLI), null)
return ExpressionCodeResult(chunk, IRDataType.BYTE, -1, -1)
}
"sys.set_irqd" -> {
val chunk = mutableListOf<IRCodeChunkBase>()
addInstr(chunk, IRInstruction(Opcode.SEI), null)
return ExpressionCodeResult(chunk, IRDataType.BYTE, -1, -1)
}
}
when (callTarget) {
is StSub -> {
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = callTarget.parameters.size), null)
@@ -590,6 +620,39 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
}
private fun translateStackFunctions(fcall: PtFunctionCall, callTarget: StNode): ExpressionCodeResult {
val chunk = mutableListOf<IRCodeChunkBase>()
when(callTarget.scopedName) {
"sys.push" -> {
// push byte
val tr = translateExpression(fcall.args.single())
chunk += tr.chunks
addInstr(chunk, IRInstruction(Opcode.PUSH, IRDataType.BYTE, reg1=tr.resultReg), null)
return ExpressionCodeResult(chunk, IRDataType.BYTE, -1, -1)
}
"sys.pushw" -> {
// push word
val tr = translateExpression(fcall.args.single())
chunk += tr.chunks
addInstr(chunk, IRInstruction(Opcode.PUSH, IRDataType.WORD, reg1=tr.resultReg), null)
return ExpressionCodeResult(chunk, IRDataType.WORD, -1, -1)
}
"sys.pop" -> {
// pop byte
val popReg = codeGen.registers.nextFree()
addInstr(chunk, IRInstruction(Opcode.POP, IRDataType.BYTE, reg1=popReg), null)
return ExpressionCodeResult(chunk, IRDataType.BYTE, popReg, -1)
}
"sys.popw" -> {
// pop word
val popReg = codeGen.registers.nextFree()
addInstr(chunk, IRInstruction(Opcode.POP, IRDataType.WORD, reg1=popReg), null)
return ExpressionCodeResult(chunk, IRDataType.WORD, popReg, -1)
}
else -> throw AssemblyError("unknown stack subroutine called")
}
}
private fun callRomSubWithMultipleReturnValues(
callTarget: StRomSub,
fcall: PtFunctionCall,
@@ -5,7 +5,6 @@ import prog8.code.ast.*
import prog8.code.core.*
import prog8.intermediate.*
import kotlin.io.path.readBytes
import kotlin.math.pow
class IRCodeGen(
@@ -391,10 +390,10 @@ class IRCodeGen(
val result = mutableListOf<IRCodeChunkBase>()
when(iterable) {
is PtRange -> {
if(iterable.from is PtNumber && iterable.to is PtNumber)
result += translateForInConstantRange(forLoop, loopvar)
result += if(iterable.from is PtNumber && iterable.to is PtNumber)
translateForInConstantRange(forLoop, loopvar)
else
result += translateForInNonConstantRange(forLoop, loopvar)
translateForInNonConstantRange(forLoop, loopvar)
}
is PtIdentifier -> {
require(forLoop.variable.name == loopvar.scopedName)
@@ -682,13 +681,11 @@ class IRCodeGen(
return code
}
internal val powersOfTwo = (0..16).map { 2.0.pow(it.toDouble()).toInt() }
internal fun multiplyByConst(dt: IRDataType, reg: Int, factor: Int): IRCodeChunk {
val code = IRCodeChunk(null, null)
if(factor==1)
return code
val pow2 = powersOfTwo.indexOf(factor)
val pow2 = powersOfTwoInt.indexOf(factor)
if(pow2==1) {
// just shift 1 bit
code += IRInstruction(Opcode.LSL, dt, reg1=reg)
@@ -712,7 +709,7 @@ class IRCodeGen(
val code = IRCodeChunk(null, null)
if(factor==1)
return code
val pow2 = powersOfTwo.indexOf(factor)
val pow2 = powersOfTwoInt.indexOf(factor)
if(pow2==1) {
// just shift 1 bit
code += if(knownAddress!=null)
@@ -785,19 +782,32 @@ class IRCodeGen(
val code = IRCodeChunk(null, null)
if(factor==1)
return code
val pow2 = powersOfTwo.indexOf(factor)
if(pow2==1 && !signed) {
code += IRInstruction(Opcode.LSR, dt, reg1=reg) // simple single bit shift
}
else if(pow2>=1 &&!signed) {
// just shift multiple bits
val pow2reg = registers.nextFree()
code += IRInstruction(Opcode.LOAD, dt, reg1=pow2reg, immediate = pow2)
code += if(signed)
IRInstruction(Opcode.ASRN, dt, reg1=reg, reg2=pow2reg)
else
IRInstruction(Opcode.LSRN, dt, reg1=reg, reg2=pow2reg)
val pow2 = powersOfTwoInt.indexOf(factor)
if(pow2>=0) {
if(signed) {
if(pow2==1) {
// simple single bit shift (signed)
code += IRInstruction(Opcode.ASR, dt, reg1=reg)
} else {
// just shift multiple bits (signed)
val pow2reg = registers.nextFree()
code += IRInstruction(Opcode.LOAD, dt, reg1=pow2reg, immediate = pow2)
code += IRInstruction(Opcode.ASRN, dt, reg1=reg, reg2=pow2reg)
}
} else {
if(pow2==1) {
// simple single bit shift (unsigned)
code += IRInstruction(Opcode.LSR, dt, reg1=reg)
} else {
// just shift multiple bits (unsigned)
val pow2reg = registers.nextFree()
code += IRInstruction(Opcode.LOAD, dt, reg1 = pow2reg, immediate = pow2)
code += IRInstruction(Opcode.LSRN, dt, reg1 = reg, reg2 = pow2reg)
}
}
return code
} else {
// regular div
code += if (factor == 0) {
IRInstruction(Opcode.LOAD, dt, reg1=reg, immediate = 0xffff)
} else {
@@ -806,39 +816,56 @@ class IRCodeGen(
else
IRInstruction(Opcode.DIV, dt, reg1=reg, immediate = factor)
}
return code
}
return code
}
internal fun divideByConstInplace(dt: IRDataType, knownAddress: Int?, symbol: String?, factor: Int, signed: Boolean): IRCodeChunk {
val code = IRCodeChunk(null, null)
if(factor==1)
return code
val pow2 = powersOfTwo.indexOf(factor)
if(pow2==1 && !signed) {
// just simple bit shift
code += if(knownAddress!=null)
IRInstruction(Opcode.LSRM, dt, address = knownAddress)
else
IRInstruction(Opcode.LSRM, dt, labelSymbol = symbol)
val pow2 = powersOfTwoInt.indexOf(factor)
if(pow2>=0) {
// can do bit shift instead of division
if(signed) {
if(pow2==1) {
// just simple bit shift (signed)
code += if (knownAddress != null)
IRInstruction(Opcode.ASRM, dt, address = knownAddress)
else
IRInstruction(Opcode.ASRM, dt, labelSymbol = symbol)
} else {
// just shift multiple bits (signed)
val pow2reg = registers.nextFree()
code += IRInstruction(Opcode.LOAD, dt, reg1 = pow2reg, immediate = pow2)
code += if (knownAddress != null)
IRInstruction(Opcode.ASRNM, dt, reg1 = pow2reg, address = knownAddress)
else
IRInstruction(Opcode.ASRNM, dt, reg1 = pow2reg, labelSymbol = symbol)
}
} else {
if(pow2==1) {
// just simple bit shift (unsigned)
code += if(knownAddress!=null)
IRInstruction(Opcode.LSRM, dt, address = knownAddress)
else
IRInstruction(Opcode.LSRM, dt, labelSymbol = symbol)
}
else {
// just shift multiple bits (unsigned)
val pow2reg = registers.nextFree()
code += IRInstruction(Opcode.LOAD, dt, reg1=pow2reg, immediate = pow2)
code += if(knownAddress!=null)
IRInstruction(Opcode.LSRNM, dt, reg1 = pow2reg, address = knownAddress)
else
IRInstruction(Opcode.LSRNM, dt, reg1 = pow2reg, labelSymbol = symbol)
}
}
return code
}
else if(pow2>=1 && !signed) {
// just shift multiple bits
val pow2reg = registers.nextFree()
code += IRInstruction(Opcode.LOAD, dt, reg1=pow2reg, immediate = pow2)
code += if(signed) {
if(knownAddress!=null)
IRInstruction(Opcode.ASRNM, dt, reg1 = pow2reg, address = knownAddress)
else
IRInstruction(Opcode.ASRNM, dt, reg1 = pow2reg, labelSymbol = symbol)
}
else {
if(knownAddress!=null)
IRInstruction(Opcode.LSRNM, dt, reg1 = pow2reg, address = knownAddress)
else
IRInstruction(Opcode.LSRNM, dt, reg1 = pow2reg, labelSymbol = symbol)
}
} else {
else
{
// regular div
if (factor == 0) {
val reg = registers.nextFree()
code += IRInstruction(Opcode.LOAD, dt, reg1=reg, immediate = 0xffff)
@@ -863,8 +890,8 @@ class IRCodeGen(
IRInstruction(Opcode.DIVM, dt, reg1 = factorReg, labelSymbol = symbol)
}
}
return code
}
return code
}
private fun translate(ifElse: PtIfElse): IRCodeChunks {
@@ -1291,6 +1318,10 @@ class IRCodeGen(
val result = mutableListOf<IRCodeChunkBase>()
fun translateSimple(condition: PtExpression, jumpFalseOpcode: Opcode) {
if(condition is PtBuiltinFunctionCall && condition.name.startsWith("prog8_ifelse_bittest_"))
throw AssemblyError("IR codegen doesn't have special instructions for dedicated BIT tests and should just still use normal AND")
val tr = expressionEval.translateExpression(condition)
result += tr.chunks
if(ifElse.hasElse()) {
@@ -1503,17 +1534,16 @@ class IRCodeGen(
private fun translate(jump: PtJump): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
val chunk = IRCodeChunk(null, null)
if(jump.address!=null) {
chunk += IRInstruction(Opcode.JUMP, address = jump.address!!.toInt())
chunk += if(jump.address!=null) {
IRInstruction(Opcode.JUMP, address = jump.address!!.toInt())
} else {
if (jump.identifier != null) {
if(isIndirectJump(jump)) {
chunk += IRInstruction(Opcode.JUMPI, labelSymbol = jump.identifier!!.name)
IRInstruction(Opcode.JUMPI, labelSymbol = jump.identifier!!.name)
} else {
chunk += IRInstruction(Opcode.JUMP, labelSymbol = jump.identifier!!.name)
IRInstruction(Opcode.JUMP, labelSymbol = jump.identifier!!.name)
}
}
else
} else
throw AssemblyError("weird jump")
}
result += chunk
@@ -263,6 +263,24 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
}
}
}
if(ins.opcode== Opcode.SEI || ins.opcode== Opcode.CLI) {
if(idx < chunk.instructions.size-1) {
val insAfter = chunk.instructions[idx+1]
if(insAfter.opcode == ins.opcode) {
chunk.instructions.removeAt(idx)
changed = true
}
else if(ins.opcode== Opcode.SEI && insAfter.opcode== Opcode.CLI) {
chunk.instructions.removeAt(idx)
changed = true
}
else if(ins.opcode== Opcode.CLI && insAfter.opcode== Opcode.SEI) {
chunk.instructions.removeAt(idx)
changed = true
}
}
}
}
return changed
}
+35 -4
View File
@@ -80,21 +80,52 @@ class TestIRPeepholeOpt: FunSpec({
instr[1].opcode shouldBe Opcode.INC
}
test("remove double sec/clc") {
test("remove double sec/clc/sei/cli") {
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.SEC),
IRInstruction(Opcode.SEC),
IRInstruction(Opcode.SEC),
IRInstruction(Opcode.CLC),
IRInstruction(Opcode.CLC),
IRInstruction(Opcode.CLC)
IRInstruction(Opcode.CLC),
IRInstruction(Opcode.SEI),
IRInstruction(Opcode.SEI),
IRInstruction(Opcode.SEI),
IRInstruction(Opcode.CLI),
IRInstruction(Opcode.CLI),
IRInstruction(Opcode.CLI),
))
irProg.chunks().single().instructions.size shouldBe 6
irProg.chunks().single().instructions.size shouldBe 12
val opt = IRPeepholeOptimizer(irProg)
opt.optimize(true, ErrorReporterForTests())
val instr = irProg.chunks().single().instructions
instr.size shouldBe 1
instr.size shouldBe 2
instr[0].opcode shouldBe Opcode.CLC
instr[1].opcode shouldBe Opcode.CLI
}
test("remove double sec/clc/sei/cli reversed") {
val irProg = makeIRProgram(listOf(
IRInstruction(Opcode.CLC),
IRInstruction(Opcode.CLC),
IRInstruction(Opcode.CLC),
IRInstruction(Opcode.SEC),
IRInstruction(Opcode.SEC),
IRInstruction(Opcode.SEC),
IRInstruction(Opcode.CLI),
IRInstruction(Opcode.CLI),
IRInstruction(Opcode.CLI),
IRInstruction(Opcode.SEI),
IRInstruction(Opcode.SEI),
IRInstruction(Opcode.SEI),
))
irProg.chunks().single().instructions.size shouldBe 12
val opt = IRPeepholeOptimizer(irProg)
opt.optimize(true, ErrorReporterForTests())
val instr = irProg.chunks().single().instructions
instr.size shouldBe 2
instr[0].opcode shouldBe Opcode.SEC
instr[1].opcode shouldBe Opcode.SEI
}
test("push followed by pop") {
@@ -91,7 +91,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
program.encoding.encodeString(leftString.value, leftString.encoding) + program.encoding.encodeString(rightString.value, rightString.encoding),
leftString.encoding)
}
val concatStr = StringLiteral(concatenated, leftString.encoding, expr.position)
val concatStr = StringLiteral.create(concatenated, leftString.encoding, expr.position)
return listOf(IAstModification.ReplaceNode(expr, concatStr, parent))
}
else if(expr.operator=="*" && rightconst!=null && expr.left is StringLiteral) {
@@ -99,7 +99,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
val part = expr.left as StringLiteral
if(part.value.isEmpty())
errors.warn("resulting string has length zero", part.position)
val newStr = StringLiteral(part.value.repeat(rightconst.number.toInt()), part.encoding, expr.position)
val newStr = StringLiteral.create(part.value.repeat(rightconst.number.toInt()), part.encoding, expr.position)
return listOf(IAstModification.ReplaceNode(expr, newStr, parent))
}
}
@@ -154,11 +154,10 @@ class VarConstantValueTypeAdjuster(
if(func==listOf("clamp")) {
val t1 = functionCallExpr.args[0].inferType(program)
if(t1.isKnown) {
val replaceFunc: String
if(t1.isBytes) {
replaceFunc = if(t1.istype(DataType.BYTE)) "clamp__byte" else "clamp__ubyte"
val replaceFunc = if(t1.isBytes) {
if(t1.istype(DataType.BYTE)) "clamp__byte" else "clamp__ubyte"
} else if(t1.isInteger) {
replaceFunc = if(t1.istype(DataType.WORD)) "clamp__word" else "clamp__uword"
if(t1.istype(DataType.WORD)) "clamp__word" else "clamp__uword"
} else {
errors.err("clamp builtin not supported for floats, use floats.clamp", functionCallExpr.position)
return noModifications
@@ -14,9 +14,6 @@ import kotlin.math.log2
import kotlin.math.pow
class ExpressionSimplifier(private val program: Program, private val options: CompilationOptions, private val errors: IErrorReporter) : AstWalker() {
private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet()
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
val mods = mutableListOf<IAstModification>()
@@ -104,7 +101,7 @@ class ExpressionSimplifier(private val program: Program, private val options: Co
val leftIDt = expr.left.inferType(program)
val rightIDt = expr.right.inferType(program)
if (!leftIDt.isKnown || !rightIDt.isKnown)
throw FatalAstException("can't determine datatype of both expression operands $expr")
return noModifications
// X + (-A) --> X - A
if (expr.operator == "+" && (expr.right as? PrefixExpression)?.operator == "-") {
@@ -686,7 +683,7 @@ class ExpressionSimplifier(private val program: Program, private val options: Co
if(!idt.isKnown)
throw FatalAstException("unknown dt")
return NumericLiteral(idt.getOr(DataType.UNDEFINED), 0.0, expr.position)
} else if (cv in powersOfTwo) {
} else if (cv in powersOfTwoFloat) {
expr.operator = "&"
expr.right = NumericLiteral.optimalInteger(cv!!.toInt()-1, expr.position)
return null
@@ -711,6 +708,7 @@ class ExpressionSimplifier(private val program: Program, private val options: Co
return null
val leftDt = leftIDt.getOr(DataType.UNDEFINED)
when (cv) {
0.0 -> return null // fall through to regular float division to properly deal with division by zero
-1.0 -> {
// '/' -> -left
if (expr.operator == "/") {
@@ -738,12 +736,10 @@ class ExpressionSimplifier(private val program: Program, private val options: Co
else -> return null
}
}
in powersOfTwo -> {
if (leftDt==DataType.UBYTE || leftDt==DataType.UWORD) {
// Unsigned number divided by a power of two => shift right
// Signed number can't simply be bitshifted in this case (due to rounding issues for negative values),
// so we leave that as is and let the code generator deal with it.
val numshifts = log2(cv).toInt()
in powersOfTwoFloat -> {
val numshifts = powersOfTwoFloat.indexOf(cv)
if (leftDt in IntegerDatatypes) {
// division by a power of two => shift right (signed and unsigned)
return BinaryExpression(expr.left, ">>", NumericLiteral.optimalInteger(numshifts, expr.position), expr.position)
}
}
@@ -795,14 +791,14 @@ class ExpressionSimplifier(private val program: Program, private val options: Co
// left
return expr2.left
}
in powersOfTwo -> {
in powersOfTwoFloat -> {
if (leftValue.inferType(program).isInteger) {
// times a power of two => shift left
val numshifts = log2(cv).toInt()
return BinaryExpression(expr2.left, "<<", NumericLiteral.optimalInteger(numshifts, expr.position), expr.position)
}
}
in negativePowersOfTwo -> {
in negativePowersOfTwoFloat -> {
if (leftValue.inferType(program).isInteger) {
// times a negative power of two => negate, then shift
val numshifts = log2(-cv).toInt()
+11 -9
View File
@@ -30,11 +30,11 @@ class Inliner(private val program: Program, private val options: CompilationOpti
}
override fun visit(subroutine: Subroutine) {
if(!subroutine.isAsmSubroutine && !subroutine.inline && subroutine.parameters.isEmpty()) {
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]))) {
if(subroutine !== program.entrypoint) {
if (!subroutine.isAsmSubroutine && !subroutine.inline && subroutine.parameters.isEmpty()) {
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]))) {
if (subroutine !== program.entrypoint) {
// subroutine is possible candidate to be inlined
subroutine.inline =
when (val stmt = subroutine.statements[0]) {
@@ -87,9 +87,9 @@ class Inliner(private val program: Program, private val options: CompilationOpti
} else
false
targetInline || valueInline
} else if(stmt.target.identifier!=null && stmt.isAugmentable) {
} else if (stmt.target.identifier != null && stmt.isAugmentable) {
val binExpr = stmt.value as BinaryExpression
if(binExpr.operator in "+-" && binExpr.right.constValue(program)?.number==1.0) {
if (binExpr.operator in "+-" && binExpr.right.constValue(program)?.number == 1.0) {
makeFullyScoped(stmt.target.identifier!!)
makeFullyScoped(binExpr.left as IdentifierReference)
true
@@ -121,8 +121,8 @@ class Inliner(private val program: Program, private val options: CompilationOpti
}
}
if(subroutine.inline && subroutine.statements.size>1) {
require(subroutine.statements.size==2 && isEmptyReturn(subroutine.statements[1]))
if (subroutine.inline && subroutine.statements.size > 1) {
require(subroutine.statements.size == 2 && isEmptyReturn(subroutine.statements[1]))
subroutine.statements.removeLast() // get rid of the Return, to be able to inline the (single) statement preceding it.
}
}
@@ -147,6 +147,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
}
private fun makeFullyScoped(call: FunctionCallStatement) {
makeFullyScoped(call.target)
call.target.targetSubroutine(program)?.let { sub ->
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
val scopedArgs = makeScopedArgs(call.args)
@@ -169,6 +170,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
}
private fun makeFullyScoped(call: FunctionCallExpression) {
makeFullyScoped(call.target)
call.target.targetSubroutine(program)?.let { sub ->
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
val scopedArgs = makeScopedArgs(call.args)
@@ -21,7 +21,8 @@ class StatementOptimizer(private val program: Program,
if(functionCallStatement.target.nameInSource.size==1) {
val functionName = functionCallStatement.target.nameInSource[0]
if (functionName in functions.purefunctionNames) {
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
if("ignore_unused" !in parent.definingBlock.options())
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
return listOf(IAstModification.Remove(functionCallStatement, parent as IStatementContainer))
}
}
@@ -149,7 +149,8 @@ class UnusedCodeRemover(private val program: Program,
val declIndex = (parent as IStatementContainer).statements.indexOf(decl)
val singleUseIndex = (parent as IStatementContainer).statements.indexOf(singleUse.parent)
if(declIndex==singleUseIndex-1) {
errors.info("replaced unused variable '${decl.name}' with void call, maybe this can be removed altogether", decl.position)
if("ignore_unused" !in decl.definingBlock.options())
errors.info("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(
+4 -3
View File
@@ -2,7 +2,8 @@ plugins {
id 'java'
id 'application'
id 'org.jetbrains.kotlin.jvm'
id 'com.github.johnrengelman.shadow' version '8.1.1'
// id 'com.github.johnrengelman.shadow' version '8.1.1'
id 'io.github.goooler.shadow' version '8.1.7'
id 'com.peterabeles.gversion' version '1.10.2'
}
@@ -31,7 +32,7 @@ dependencies {
implementation project(':codeGenIntermediate')
implementation project(':codeGenExperimental')
implementation project(':virtualmachine')
implementation "org.antlr:antlr4-runtime:4.13.1"
implementation "org.antlr:antlr4-runtime:4.13.2"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.6'
@@ -39,7 +40,7 @@ dependencies {
testImplementation project(':codeCore')
testImplementation project(':intermediate')
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.9.0'
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.9.1'
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
+82
View File
@@ -0,0 +1,82 @@
; any() and all() checks on arrays/memory buffers.
; These were builtin functions in older versions of the language.
%option no_symbol_prefixing, ignore_unused
anyall {
sub any(uword arrayptr, uword num_elements) -> bool {
; -- returns true if any byte in the array is not zero.
cx16.r1 = arrayptr
if msb(num_elements)==0 {
for cx16.r0L in 0 to lsb(num_elements)-1 {
if cx16.r1[cx16.r0L]!=0
return true
}
return false
}
repeat num_elements {
if @(cx16.r1)!=0
return true
cx16.r1++
}
return false
}
sub all(uword arrayptr, uword num_elements) -> bool {
; -- returns true if all bytes in the array are not zero.
cx16.r1 = arrayptr
if msb(num_elements)==0 {
for cx16.r0L in 0 to lsb(num_elements)-1 {
if cx16.r1[cx16.r0L]==0
return false
}
return true
}
repeat num_elements {
if @(cx16.r1)==0
return false
cx16.r1++
}
return true
}
sub anyw(uword arrayptr, uword num_elements) -> bool {
; -- returns true if any word in the array is not zero.
; doesn't work on @split arrays.
cx16.r1 = arrayptr
if msb(num_elements)==0 {
repeat lsb(num_elements) {
if peekw(cx16.r1)!=0
return true
cx16.r1+=2
}
return false
}
repeat num_elements {
if peekw(cx16.r1)!=0
return true
cx16.r1+=2
}
return false
}
sub allw(uword arrayptr, uword num_elements) -> bool {
; -- returns true if all words in the array are not zero.
; doesn't work on @split arrays.
cx16.r1 = arrayptr
if msb(num_elements)==0 {
repeat lsb(num_elements) {
if peekw(cx16.r1)==0
return false
cx16.r1+=2
}
return true
}
repeat num_elements {
if peekw(cx16.r1)==0
return false
cx16.r1+=2
}
return true
}
}
+152
View File
@@ -0,0 +1,152 @@
; experimental buffer data structures
%option no_symbol_prefixing, ignore_unused
smallringbuffer {
; -- A ringbuffer (FIFO queue) that occupies a single page in memory, containing 255 bytes maximum.
; You can store and retrieve words too.
; It's optimized for speed and depends on the byte-wrap-around feature when doing incs and decs.
ubyte fill
ubyte head
ubyte tail
ubyte[256] buffer
sub init() {
; -- (re)initialize the ringbuffer, you must call this before using the other routines
head = fill = 0
tail = 255
}
sub put(ubyte value) -> bool {
; -- store a byte in the buffer, returns success
if fill==255
return false
buffer[head] = value
head++
fill++
return true
}
sub putw(uword value) -> bool {
; -- store a word in the buffer, returns success
if fill>=254
return false
fill += 2
buffer[head] = lsb(value)
head++
buffer[head] = msb(value)
head++
return true
}
sub get() -> ubyte {
; -- retrieves a byte from the buffer. Also sets Carry flag: set=success, clear=buffer was empty
if fill==0 {
sys.clear_carry()
return 0
}
fill--
tail++
sys.set_carry()
return buffer[tail]
}
sub getw() -> uword {
; -- retrieves a word from the buffer. Also sets Carry flag: set=success, clear=buffer was empty
if fill<2 {
sys.clear_carry()
return 0
}
fill -= 2
tail++
cx16.r0L = buffer[tail]
tail++
cx16.r0H = buffer[tail]
sys.set_carry()
return cx16.r0
}
}
ringbuffer {
; -- A ringbuffer (FIFO queue) that occupies a single page in memory, containing 8 KB maximum.
; You can store and retrieve words too.
uword fill
uword head
uword tail
uword buffer_ptr = memory("ringbuffer", 8192, 0)
sub init() {
; -- (re)initialize the ringbuffer, you must call this before using the other routines
head = fill = 0
tail = 8191
}
sub put(ubyte value) -> bool {
; -- store a byte in the buffer, returns success
if fill==8192
return false
buffer_ptr[head] = value
inc_head()
fill++
return true
}
sub putw(uword value) -> bool {
; -- store a word in the buffer, returns success
if fill>=8191
return false
fill += 2
buffer_ptr[head] = lsb(value)
inc_head()
buffer_ptr[head] = msb(value)
inc_head()
return true
}
sub get() -> ubyte {
; -- retrieves a byte from the buffer. Also sets Carry flag: set=success, clear=buffer was empty
if fill==0 {
sys.clear_carry()
return 0
}
fill--
inc_tail()
cx16.r0L = buffer_ptr[tail]
sys.set_carry()
return cx16.r0L
}
sub getw() -> uword {
; -- retrieves a word from the buffer. Also sets Carry flag: set=success, clear=buffer was empty
if fill<2 {
sys.clear_carry()
return 0
}
fill -= 2
inc_tail()
cx16.r0L = buffer_ptr[tail]
inc_tail()
cx16.r0H = buffer_ptr[tail]
sys.set_carry()
return cx16.r0
}
sub inc_head() {
head++
if msb(head)==$20
head=0
}
sub inc_tail() {
tail++
if msb(tail)==$20
tail=0
}
}
; TODO ringbuffer (FIFO queue) should use banked ram on the X16, but still work on virtual
; TODO stack (LIFO queue) using more than 1 page of ram (maybe even banked ram on the x16)
-122
View File
@@ -6,108 +6,6 @@ func_sign_f_into_A .proc
jmp SIGN
.pend
func_swap_f .proc
; -- swap floats pointed to by SCRATCH_ZPWORD1, SCRATCH_ZPWORD2
ldy #4
- lda (P8ZP_SCRATCH_W1),y
pha
lda (P8ZP_SCRATCH_W2),y
sta (P8ZP_SCRATCH_W1),y
pla
sta (P8ZP_SCRATCH_W2),y
dey
bpl -
rts
.pend
func_reverse_f .proc
; --- reverse an array of floats (array in P8ZP_SCRATCH_W1, num elements in A)
_left_index = P8ZP_SCRATCH_W2
_right_index = P8ZP_SCRATCH_W2+1
_loop_count = P8ZP_SCRATCH_REG
pha
jsr a_times_5
sec
sbc #5
sta _right_index
lda #0
sta _left_index
pla
lsr a
sta _loop_count
_loop ; push the left indexed float on the stack
ldy _left_index
lda (P8ZP_SCRATCH_W1),y
pha
iny
lda (P8ZP_SCRATCH_W1),y
pha
iny
lda (P8ZP_SCRATCH_W1),y
pha
iny
lda (P8ZP_SCRATCH_W1),y
pha
iny
lda (P8ZP_SCRATCH_W1),y
pha
; copy right index float to left index float
ldy _right_index
lda (P8ZP_SCRATCH_W1),y
ldy _left_index
sta (P8ZP_SCRATCH_W1),y
inc _left_index
inc _right_index
ldy _right_index
lda (P8ZP_SCRATCH_W1),y
ldy _left_index
sta (P8ZP_SCRATCH_W1),y
inc _left_index
inc _right_index
ldy _right_index
lda (P8ZP_SCRATCH_W1),y
ldy _left_index
sta (P8ZP_SCRATCH_W1),y
inc _left_index
inc _right_index
ldy _right_index
lda (P8ZP_SCRATCH_W1),y
ldy _left_index
sta (P8ZP_SCRATCH_W1),y
inc _left_index
inc _right_index
ldy _right_index
lda (P8ZP_SCRATCH_W1),y
ldy _left_index
sta (P8ZP_SCRATCH_W1),y
; pop the float off the stack into the right index float
ldy _right_index
pla
sta (P8ZP_SCRATCH_W1),y
dey
pla
sta (P8ZP_SCRATCH_W1),y
dey
pla
sta (P8ZP_SCRATCH_W1),y
dey
pla
sta (P8ZP_SCRATCH_W1),y
dey
pla
sta (P8ZP_SCRATCH_W1),y
inc _left_index
lda _right_index
sec
sbc #9
sta _right_index
dec _loop_count
bne _loop
rts
.pend
a_times_5 .proc
sta P8ZP_SCRATCH_B1
@@ -118,26 +16,6 @@ a_times_5 .proc
rts
.pend
func_any_f_into_A .proc
jsr a_times_5
jmp prog8_lib.func_any_b_into_A
.pend
func_all_f_into_A .proc
jsr a_times_5
jmp prog8_lib.func_all_b_into_A
.pend
func_any_f_stack .proc
jsr a_times_5
jmp prog8_lib.func_any_b_stack
.pend
func_all_f_stack .proc
jsr a_times_5
jmp prog8_lib.func_all_b_stack
.pend
func_abs_f_into_FAC1 .proc
jsr MOVFM
jmp ABS
+7 -7
View File
@@ -517,16 +517,16 @@ _stop
asmsub internal_ubyte2decimal(ubyte value @A) -> ubyte @Y, ubyte @X, ubyte @A {
%asm {{
ldy #'0'-1
ldx #'9'+1
ldy #'0'-1
ldx #'9'+1
sec
- iny
sbc #100
bcs -
sbc #100
bcs -
- dex
adc #10
bmi -
adc #'0'-1
adc #10
bmi -
adc #'0'-1
rts
}}
}
+7 -3
View File
@@ -379,6 +379,7 @@ m_in_buffer sta $ffff
; optimized for Commander X16 to use MACPTR block read kernal call
sub f_read_all(uword bufferpointer) -> uword {
; -- read the full contents of the file, returns number of bytes read.
; It is assumed the file size is less than 64 K.
; NOTE: cannot be used to load into VRAM. Use vload() or call cx16.MACPTR() yourself with the vera data register as address.
if not iteration_in_progress
return 0
@@ -393,12 +394,12 @@ m_in_buffer sta $ffff
return total_read
}
asmsub f_readline(uword bufptr @AY) clobbers(X) -> ubyte @Y {
asmsub f_readline(uword bufptr @AY) clobbers(X) -> ubyte @Y, ubyte @A {
; Routine to read text lines from a text file. Lines must be less than 255 characters.
; Reads characters from the input file UNTIL a newline or return character (or EOF).
; The line read will be 0-terminated in the buffer (and not contain the end of line character).
; The length of the line is returned in Y. Note that an empty line is okay and is length 0!
; I/O error status should be checked by the caller itself via READST() routine.
; The I/O error status byte is returned in A.
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
@@ -415,7 +416,8 @@ _loop jsr cbm.CHRIN
_line_end dey ; get rid of the trailing end-of-line char
lda #0
sta (P8ZP_SCRATCH_W1),y
_end rts
_end jsr cbm.READST
rts
}}
}
@@ -467,6 +469,7 @@ _end rts
sub f_write(uword bufferpointer, uword num_bytes) -> bool {
; -- write the given number of bytes to the currently open file
; you can call this multiple times to append more data
if num_bytes!=0 {
reset_write_channel()
do {
@@ -863,6 +866,7 @@ io_error:
sub f_seek(uword pos_hiword, uword pos_loword) {
; -- seek in the reading file opened with f_open, to the given 32-bits position
; Note: this will not work if you have already read the last byte of the file! Then you must close and reopen the file first.
ubyte[6] command = ['p',0,0,0,0,0]
command[1] = READ_IO_CHANNEL ; f_open uses this secondary address
command[2] = lsb(pos_loword)
+148 -22
View File
@@ -13,6 +13,7 @@
; NOTE: For sake of speed, NO BOUNDS CHECKING is performed in most routines!
; You'll have to make sure yourself that you're not writing outside of bitmap boundaries!
;
; NOTE: the bitmap screen data is positioned in vram at $0:0000
;
; SCREEN MODE LIST:
; mode 0 = reset back to default text mode
@@ -254,6 +255,7 @@ gfx2 {
;; color <<= gfx2.plot.shift4c[lsb(xx) & 3]
cx16.r2L = lsb(xx) & 3
when color & 3 {
0 -> color = 0
1 -> color = gfx2.plot.shiftedleft_4c_1[cx16.r2L]
2 -> color = gfx2.plot.shiftedleft_4c_2[cx16.r2L]
3 -> color = gfx2.plot.shiftedleft_4c_3[cx16.r2L]
@@ -272,10 +274,10 @@ gfx2 {
sub set_both_strides(ubyte stride) {
stride <<= 4
cx16.VERA_CTRL = 0
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | stride
cx16.VERA_CTRL = 1
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | stride
cx16.VERA_CTRL = 0
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | stride
}
}
@@ -547,6 +549,7 @@ gfx2 {
; color &= 3
; color <<= shift4c[cx16.r2L]
when color & 3 {
0 -> color = 0
1 -> color = shiftedleft_4c_1[cx16.r2L]
2 -> color = shiftedleft_4c_2[cx16.r2L]
3 -> color = shiftedleft_4c_3[cx16.r2L]
@@ -626,10 +629,18 @@ gfx2 {
}
sub fill(uword x, uword y, ubyte new_color) {
; reuse a few virtual registers in ZP for variables
&ubyte fillm = &cx16.r7L
&ubyte seedm = &cx16.r8L
&ubyte cmask = &cx16.r8H
&ubyte vub = &cx16.r13L
&ubyte nvub = &cx16.r13H
ubyte[4] amask = [$c0,$30,$0c,$03] ; array of cmask bytes
; Non-recursive scanline flood fill.
; based loosely on code found here https://www.codeproject.com/Articles/6017/QuickFill-An-efficient-flood-fill-algorithm
; with the fixes applied to the seedfill_4 routine as mentioned in the comments.
const ubyte MAXDEPTH = 64
const ubyte MAXDEPTH = 100
word @zp xx = x as word
word @zp yy = y as word
word[MAXDEPTH] @split @shared stack_xl
@@ -641,6 +652,7 @@ gfx2 {
word x2
byte dy
cx16.r10L = new_color
sub push_stack(word sxl, word sxr, word sy, byte sdy) {
if cx16.r12L==MAXDEPTH
return
@@ -704,39 +716,27 @@ gfx2 {
return
if xx<0 or xx>width-1 or yy<0 or yy>height-1
return
if gfx2.active_mode == 2 set_color_masks()
push_stack(xx, xx, yy, 1)
push_stack(xx, xx, yy + 1, -1)
word left = 0
while cx16.r12L!=0 {
pop_stack()
xx = x1
; possible speed optimization: if mode==1 (256c) use vera autodecrement instead of pget(), but code bloat not worth it?
while xx >= 0 {
if pget(xx as uword, yy as uword) != cx16.r11L
break
xx--
when active_mode {
1 -> if fill_scanline_left_8bpp() goto skip
2 -> if fill_scanline_left_2bpp() goto skip
}
if x1!=xx
horizontal_line(xx as uword+1, yy as uword, x1-xx as uword, cx16.r10L)
else
goto skip
left = xx + 1
if left < x1
push_stack(left, x1 - 1, yy, -dy)
xx = x1 + 1
do {
cx16.r9s = xx
; possible speed optimization: if mode==1 (256c) use vera autoincrement instead of pget(), but code bloat not worth it?
while xx <= width-1 {
if pget(xx as uword, yy as uword) != cx16.r11L
break
xx++
when active_mode {
1 -> fill_scanline_right_8bpp()
2 -> fill_scanline_right_2bpp()
}
if cx16.r9s!=xx
horizontal_line(cx16.r9, yy as uword, xx-cx16.r9s as uword, cx16.r10L)
push_stack(left, xx - 1, yy, dy)
if xx > x2 + 1
push_stack(x2 + 1, xx - 1, yy, -dy)
@@ -750,6 +750,132 @@ skip:
left = xx
} until xx>x2
}
sub set_vera_address() {
; set both data0 and data1 addresses (expects H in R1L, M/L in R0)
cx16.VERA_CTRL = 0
cx16.VERA_ADDR_H = cx16.r1L
cx16.VERA_ADDR = cx16.r0
cx16.VERA_CTRL = 1
cx16.VERA_ADDR_H = cx16.r1L
cx16.VERA_ADDR = cx16.r0
}
sub fill_scanline_left_8bpp() -> bool {
void addr_mul_24_for_lores_256c(yy as uword, xx as uword) ; 24 bits result is in r0 and r1L (highest byte)
cx16.r1L |= %00011000 ; auto decrement enabled
set_vera_address()
cx16.r9s = xx
while xx >= 0 {
if cx16.VERA_DATA0 != cx16.r11L
break
cx16.VERA_DATA1 = cx16.r10L
xx--
}
return xx==cx16.r9s
}
sub fill_scanline_right_8bpp() {
void addr_mul_24_for_lores_256c(yy as uword, xx as uword) ; 24 bits result is in r0 and r1L (highest byte)
cx16.r1L |= %00010000 ; auto increment enabled
set_vera_address()
while xx <= width-1 {
if cx16.VERA_DATA0 != cx16.r11L
break
cx16.VERA_DATA1 = cx16.r10L
xx++
}
}
sub fill_scanline_left_2bpp() -> bool {
uword vx = xx as uword
void gfx2.addr_mul_24_for_highres_4c(yy as uword,vx)
cx16.r1L |= %0001_1000 ; auto decrement
set_vera_address()
cmask = amask[lsb(vx) & 3] ; set the color mask for the first color pel
repeat {
vub = cx16.VERA_DATA0 ; read the VERA color data for 4 pixels
if cmask == $03 { ; only speed fill from far right
; speed fill
if vub == seedm { ; all four colors match the seed
nvub = fillm ; replace all four colors at once
xx -= 4
goto set_byte ; go on
}
}
; replace one color at a time
nvub = vub
while cmask != 0 {
if vub & cmask == seedm & cmask {
nvub &= ~cmask
nvub |= cmask & fillm
; %asm{{
; lda p8v_cmask
; trb p8v_nvub
; and p8v_fillm
; tsb p8v_nvub
; }}
xx--
cmask <<= 2
} else { ; not the seed color, finish here
cx16.VERA_DATA1 = nvub
return vx == xx
}
}
set_byte:
cx16.VERA_DATA1 = nvub
if xx <= 0 break
cmask = $03
}
return vx == xx
}
sub fill_scanline_right_2bpp() {
void gfx2.addr_mul_24_for_highres_4c(yy as uword,xx as uword)
cx16.r1L |= %00010000 ; auto increment
set_vera_address()
cmask = amask[lsb(xx) & 3] ; set the color mask for the first color pel
repeat {
vub = cx16.VERA_DATA0 ; read the VERA color data for 4 pixels
; speed fill
if vub == seedm { ; all four colors match the seed
nvub = fillm ; replace all four colors at once
xx += 4
goto set_byte ; go on
}
; replace one color at a time
nvub = vub
while cmask != 0 {
if vub & cmask == seedm & cmask {
nvub &= ~cmask
nvub |= cmask & fillm
; %asm{{
; lda p8v_cmask
; trb p8v_nvub
; and p8v_fillm
; tsb p8v_nvub
; }}
xx++
cmask >>= 2
} else { ; not the seed color finish here
cx16.VERA_DATA1 = nvub
return
}
}
set_byte:
cx16.VERA_DATA1 = nvub
if xx >= gfx2.width-1 break
cmask = $C0
}
}
sub set_color_masks() {
seedm = cx16.r11L | (cx16.r11L<<2) | (cx16.r11L<<4) | (cx16.r11L<<6) ; seed mask
fillm = cx16.r10L | (cx16.r10L<<2) | (cx16.r10L<<4) | (cx16.r10L<<6) ; fill mask
}
}
sub position(uword @zp xx, uword yy) {
+7 -7
View File
@@ -358,10 +358,10 @@ drawmode: ora cx16.r15L
sub set_both_strides(ubyte stride) {
stride <<= 4
cx16.VERA_CTRL = 0
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | stride
cx16.VERA_CTRL = 1
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | stride
cx16.VERA_CTRL = 0
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | stride
}
}
@@ -708,7 +708,7 @@ invert:
; Non-recursive scanline flood fill.
; based loosely on code found here https://www.codeproject.com/Articles/6017/QuickFill-An-efficient-flood-fill-algorithm
; with the fixes applied to the seedfill_4 routine as mentioned in the comments.
const ubyte MAXDEPTH = 64
const ubyte MAXDEPTH = 100
word @zp xx = x as word
word @zp yy = y as word
word[MAXDEPTH] @split @shared stack_xl
@@ -957,15 +957,15 @@ cdraw_mod2 ora cx16.VERA_DATA1
sub set_autoincrs() {
; set autoincrements to go to next pixel row (40 or 80 increment)
if width==320 {
cx16.VERA_CTRL = 0
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & $0f | (11<<4)
cx16.VERA_CTRL = 1
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & $0f | (11<<4)
cx16.VERA_CTRL = 0
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & $0f | (11<<4)
} else {
cx16.VERA_CTRL = 0
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & $0f | (12<<4)
cx16.VERA_CTRL = 1
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & $0f | (12<<4)
cx16.VERA_CTRL = 0
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & $0f | (12<<4)
}
}
}
+8 -2
View File
@@ -42,9 +42,15 @@ sprites {
cx16.vpoke_mask(1, sprite_reg+1, %11110000, msb(addr)) ; address 16:13
}
sub get_data_ptr(ubyte spritenum) {
inline asmsub get_data_ptr(ubyte spritenum @A) -> ubyte @R1, uword @R0 {
; -- returns the VRAM address where the sprite's bitmap data is stored
; R1 (byte) = the vera bank (0 or 1), R0 (word) = the address.
%asm {{
jsr p8b_sprites.p8s_get_data_ptr_internal
}}
}
sub get_data_ptr_internal(ubyte spritenum) {
sprite_reg = VERA_SPRITEREGS + spritenum*$0008
cx16.r0L = cx16.vpeek(1, sprite_reg)
cx16.r0H = cx16.vpeek(1, sprite_reg+1)
@@ -176,7 +182,7 @@ sprites {
}
sub set_mousepointer_image(uword data, bool compressed) {
get_data_ptr(0) ; the mouse cursor is sprite 0
get_data_ptr_internal(0) ; the mouse cursor is sprite 0
if cx16.r1L==0 and cx16.r0==0
return ; mouse cursor not enabled
ubyte vbank = cx16.r1L
+66 -43
View File
@@ -6,6 +6,39 @@
cbm {
; Commodore (CBM) common variables, vectors and kernal routines
; irq, system and hardware vectors (common across cbm machines):
&uword IERROR = $0300
&uword IMAIN = $0302
&uword ICRNCH = $0304
&uword IQPLOP = $0306
&uword IGONE = $0308
&uword IEVAL = $030a
&ubyte SAREG = $030c ; register storage for A for SYS calls
&ubyte SXREG = $030d ; register storage for X for SYS calls
&ubyte SYREG = $030e ; register storage for Y for SYS calls
&ubyte SPREG = $030f ; register storage for P (status register) for SYS calls
&uword USRADD = $0311 ; vector for the USR() basic command
; $0313 is unused.
&uword CINV = $0314 ; IRQ vector (in ram)
&uword CBINV = $0316 ; BRK vector (in ram)
&uword NMINV = $0318 ; NMI vector (in ram)
&uword IOPEN = $031a
&uword ICLOSE = $031c
&uword ICHKIN = $031e
&uword ICKOUT = $0320
&uword ICLRCH = $0322
&uword IBASIN = $0324
&uword IBSOUT = $0326
&uword ISTOP = $0328
&uword IGETIN = $032a
&uword ICLALL = $032c
; $032e has a X16 specific function (KEYHDL) so you'll find this as cx16.KEYHDL
&uword ILOAD = $0330
&uword ISAVE = $0332
&uword NMI_VEC = $FFFA ; 65c02 nmi vector, determined by the kernal if banked in
&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
; STROUT --> use txt.print
; CLEARSCR -> use txt.clear_screen
@@ -117,42 +150,14 @@ asmsub kbdbuf_clear() {
cx16 {
; irq, system and hardware vectors:
&uword IERROR = $0300
&uword IMAIN = $0302
&uword ICRNCH = $0304
&uword IQPLOP = $0306
&uword IGONE = $0308
&uword IEVAL = $030a
&ubyte SAREG = $030c ; register storage for A for SYS calls
&ubyte SXREG = $030d ; register storage for X for SYS calls
&ubyte SYREG = $030e ; register storage for Y for SYS calls
&ubyte SPREG = $030f ; register storage for P (status register) for SYS calls
&uword USRADD = $0311 ; vector for the USR() basic command
; $0313 is unused.
&uword CINV = $0314 ; IRQ vector (in ram)
&uword CBINV = $0316 ; BRK vector (in ram)
&uword NMINV = $0318 ; NMI vector (in ram)
&uword IOPEN = $031a
&uword ICLOSE = $031c
&uword ICHKIN = $031e
&uword ICKOUT = $0320
&uword ICLRCH = $0322
&uword IBASIN = $0324
&uword IBSOUT = $0326
&uword ISTOP = $0328
&uword IGETIN = $032a
&uword ICLALL = $032c
; cx16 specific vectors and variables
&uword KEYHDL = $032e ; keyboard scan code handler see examples/cx16/keyboardhandler.p8
&uword ILOAD = $0330
&uword ISAVE = $0332
&uword NMI_VEC = $FFFA ; 65c02 nmi vector, determined by the kernal if banked in
&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 ; (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
&ubyte stavec = $03b2 ; argument for stash()
; the sixteen virtual 16-bit registers in both normal unsigned mode and signed mode (s)
&uword r0 = $0002
@@ -395,8 +400,8 @@ romsub $ff5c = LKUPSA(ubyte sa @Y) clobbers(A,X,Y)
romsub $ff5f = screen_mode(ubyte mode @A, bool getCurrent @Pc) -> ubyte @A, ubyte @X, ubyte @Y, bool @Pc ; also see SCREEN or get_screen_mode()
romsub $ff62 = screen_set_charset(ubyte charset @A, uword charsetptr @XY) clobbers(A,X,Y)
romsub $ff6e = JSRFAR() ; following word = address to call, byte after that=rom/ram bank it is in
romsub $ff74 = fetch(ubyte bank @X, ubyte index @Y) clobbers(X) -> ubyte @A
romsub $ff77 = stash(ubyte data @A, ubyte bank @X, ubyte index @Y) clobbers(X)
romsub $ff74 = fetch(ubyte zp_startaddr @A, ubyte bank @X, ubyte index @Y) clobbers(X) -> ubyte @A
romsub $ff77 = stash(ubyte data @A, ubyte bank @X, ubyte index @Y) clobbers(X) ; note: The the zero page address containing the base address is passed in stavec ($03B2)
romsub $ff7d = PRIMM()
; high level graphics & fonts
@@ -1070,8 +1075,8 @@ asmsub enable_irq_handlers(bool disable_all_irq_sources @Pc) clobbers(A,Y) {
trb cx16.VERA_IEN ; disable all IRQ sources
+ lda #<_irq_dispatcher
ldy #>_irq_dispatcher
sta cx16.CINV
sty cx16.CINV+1
sta cbm.CINV
sty cbm.CINV+1
plp
rts
@@ -1195,6 +1200,7 @@ asmsub set_sprcol_irq_handler(uword address @AY) clobbers(A) {
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.
; NOTE: the handler itself must fill the audio fifo buffer to at least 25% full again (1 KB) or the aflow irq will keep triggering!
%asm {{
php
sei
@@ -1322,6 +1328,23 @@ _continue iny
void cx16.i2c_write_byte($42, $05, cx16.r0L)
}
asmsub rom_version() clobbers(Y) -> ubyte @A, bool @Pc {
; Returns the KERNEL ROM version. Carry set if pre-release, clear if offical release.
%asm{{
; the ROM BANK is unknown on entry
ldy $01
stz $01 ; KERNEL ROM
clc ; prepare for released ROM
lda $FF80
bpl _final ; pre-release versions are negative
eor #$FF ; twos complement
ina
sec
_final:
sty $01
rts
}}
}
}
sys {
@@ -1378,9 +1401,9 @@ asmsub init_system() {
asmsub init_system_phase2() {
%asm {{
sei
lda cx16.CINV
lda cbm.CINV
sta restore_irq._orig_irqvec
lda cx16.CINV+1
lda cbm.CINV+1
sta restore_irq._orig_irqvec+1
lda #PROG8_VARSHIGH_RAMBANK
sta $00 ; select ram bank
@@ -1418,9 +1441,9 @@ asmsub set_irq(uword handler @AY) clobbers(A) {
sta _modified+1
sty _modified+2
lda #<_irq_handler
sta cx16.CINV
sta cbm.CINV
lda #>_irq_handler
sta cx16.CINV+1
sta cbm.CINV+1
lda #1
tsb cx16.VERA_IEN ; enable the vsync irq
cli
@@ -1449,9 +1472,9 @@ asmsub restore_irq() clobbers(A) {
%asm {{
sei
lda _orig_irqvec
sta cx16.CINV
sta cbm.CINV
lda _orig_irqvec+1
sta cx16.CINV+1
sta cbm.CINV+1
lda cx16.VERA_IEN
and #%11110000 ; disable all Vera IRQs but the vsync
ora #%00000001
@@ -1478,9 +1501,9 @@ asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
ldy cx16.r0+1
jsr set_rasterline
lda #<_raster_irq_handler
sta cx16.CINV
sta cbm.CINV
lda #>_raster_irq_handler
sta cx16.CINV+1
sta cbm.CINV+1
cli
rts
+6
View File
@@ -262,6 +262,12 @@ sub iso16() {
cx16.screen_set_charset(10, 0) ; charset
}
sub kata() {
; -- switch to katakana character set (requires rom 48+)
cbm.CHROUT($0f) ; iso mode
cx16.screen_set_charset(12, 0) ; charset
}
asmsub scroll_left() clobbers(A, X, Y) {
; ---- scroll the whole screen 1 character to the left
; contents of the rightmost column are unchanged, you should clear/refill this yourself
+3 -1
View File
@@ -1,6 +1,6 @@
; Somewhat experimental Vera FX support.
; Docs:
; https://github.com/X16Community/x16-docs/blob/101759f3bfa5e6cce4e8c5a0b67cb0f2f1c6341e/X16%20Reference%20-%2010%20-%20VERA%20FX%20Reference.md
; https://github.com/X16Community/x16-docs/blob/fb63156cca2d6de98be0577aacbe4ddef458f896/X16%20Reference%20-%2010%20-%20VERA%20FX%20Reference.md
; https://docs.google.com/document/d/1q34uWOiM3Be2pnaHRVgSdHySI-qsiQWPTo_gfE54PTg
verafx {
@@ -166,6 +166,8 @@ verafx {
}
sub transparency(bool enable) {
; Set transparent write mode for VeraFX cached writes and also for normal writes to DATA0/DATA.
; If enabled, pixels with value 0 do not modify VRAM when written (so they are "transparent")
cx16.VERA_CTRL = 2<<1 ; dcsel = 2
if enable
cx16.VERA_FX_CTRL |= %10000000
+6 -2
View File
@@ -333,6 +333,7 @@ m_in_buffer sta $ffff
sub f_read_all(uword bufferpointer) -> uword {
; -- read the full contents of the file, returns number of bytes read.
; It is assumed the file size is less than 64 K.
if not iteration_in_progress
return 0
@@ -346,12 +347,13 @@ m_in_buffer sta $ffff
return total_read
}
asmsub f_readline(uword bufptr @AY) clobbers(X) -> ubyte @Y {
asmsub f_readline(uword bufptr @AY) clobbers(X) -> ubyte @Y, ubyte @A {
; Routine to read text lines from a text file. Lines must be less than 255 characters.
; Reads characters from the input file UNTIL a newline or return character (or EOF).
; The line read will be 0-terminated in the buffer (and not contain the end of line character).
; The length of the line is returned in Y. Note that an empty line is okay and is length 0!
; I/O error status should be checked by the caller itself via READST() routine.
; The I/O error status byte is returned in A.
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
@@ -368,7 +370,8 @@ _loop jsr cbm.CHRIN
_line_end dey ; get rid of the trailing end-of-line char
lda #0
sta (P8ZP_SCRATCH_W1),y
_end rts
_end jsr cbm.READST
rts
}}
}
@@ -405,6 +408,7 @@ _end rts
sub f_write(uword bufferpointer, uword num_bytes) -> bool {
; -- write the given number of bytes to the currently open file
; you can call this multiple times to append more data
if num_bytes!=0 {
reset_write_channel()
repeat num_bytes {
-55
View File
@@ -1,60 +1,5 @@
; ---- builtin functions
func_any_b_into_A .proc
; -- any(array), array in P8ZP_SCRATCH_W1, num bytes in A
sta _cmp_mod+1 ; self-modifying code
ldy #0
- lda (P8ZP_SCRATCH_W1),y
bne _got_any
iny
_cmp_mod cpy #255 ; modified
bne -
lda #0
rts
_got_any lda #1
rts
.pend
func_all_b_into_A .proc
; -- all(array), array in P8ZP_SCRATCH_W1, num bytes in A
sta _cmp_mod+1 ; self-modifying code
ldy #0
- lda (P8ZP_SCRATCH_W1),y
beq _got_not_all
iny
_cmp_mod cpy #255 ; modified
bne -
lda #1
_got_not_all rts
.pend
func_any_w_into_A .proc
asl a
jmp func_any_b_into_A
.pend
func_all_w_into_A .proc
; -- all(warray), array in P8ZP_SCRATCH_W1, num bytes in A
asl a ; times 2 because of word
sta _cmp_mod+1 ; self-modifying code
ldy #0
- lda (P8ZP_SCRATCH_W1),y
bne +
iny
lda (P8ZP_SCRATCH_W1),y
bne ++
lda #0
rts
+ iny
+ iny
_cmp_mod cpy #255 ; modified
bne -
lda #1
rts
.pend
abs_b_into_A .proc
; -- A = abs(A)
cmp #0
+14 -20
View File
@@ -248,31 +248,25 @@ _arg_s2 .word 0
strcmp_mem .proc
; -- compares strings in s1 (AY) and s2 (P8ZP_SCRATCH_W2).
; Returns -1,0,1 in A, depeding on the ordering. Clobbers Y.
; Returns -1,0,1 in A, depending on the ordering. Clobbers Y.
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
_loop lda (P8ZP_SCRATCH_W1),y
bne +
lda (P8ZP_SCRATCH_W2),y
bne _return_minusone
beq _return
+ cmp (P8ZP_SCRATCH_W2),y
bcc _return_minusone
bne _return_one
inc P8ZP_SCRATCH_W1
bne +
inc P8ZP_SCRATCH_W1+1
+ inc P8ZP_SCRATCH_W2
bne _loop
inc P8ZP_SCRATCH_W2+1
bne _loop
_return_one
_loop lda (P8ZP_SCRATCH_W1),y
beq _c1_zero
cmp (P8ZP_SCRATCH_W2),y
beq _equal
bmi _less
lda #1
_return rts
_return_minusone
lda #-1
rts
_less lda #-1
rts
_equal iny
bne _loop
_c1_zero lda (P8ZP_SCRATCH_W2),y
beq +
lda #-1
+ rts
.pend
@@ -96,7 +96,16 @@ sub atan(float value) -> float {
; two-argument arctangent that returns an angle in the correct quadrant
; for the signs of x and y, normalized to the range [0, 2π]
sub atan2(float y, float x) -> float {
float atn = atan(y / x)
float atn
if x == 0 {
atn = π/2
if y == 0 return 0
if y < 0 {
atn += π
}
} else {
atn = atan(y / x)
}
if x < 0 atn += π
if atn < 0 atn += 2*π
return atn
+33 -2
View File
@@ -151,6 +151,37 @@ _found tya
}}
}
asmsub rfind(uword string @AY, ubyte character @X) -> ubyte @A, bool @Pc {
; Locates the first position of the given character in the string, starting from the right.
; returns Carry set if found + index in A, or Carry clear if not found (and A will be 255, an invalid index).
%asm {{
stx P8ZP_SCRATCH_B1
sta _str
sty _str+1
jsr string.length
dey
lda _str
sta P8ZP_SCRATCH_W1
lda _str+1
sta P8ZP_SCRATCH_W1+1
- lda (P8ZP_SCRATCH_W1),y
cmp P8ZP_SCRATCH_B1
beq _found
dey
cpy #255
bne -
_notfound lda #255
clc
rts
_found tya
sec
rts
_str .word 0
}}
}
asmsub contains(uword string @AY, ubyte character @X) -> bool @Pc {
; Just return true/false if the character is in the given string or not.
%asm {{
@@ -199,9 +230,9 @@ _found tya
asmsub compare(uword string1 @R0, uword string2 @AY) clobbers(Y) -> byte @A {
; Compares two strings for sorting.
; Returns -1 (255), 0 or 1 depending on wether string1 sorts before, equal or after string2.
; Returns -1 (255), 0 or 1, meaning: string1 sorts before, equal or after string2.
; Note that you can also directly compare strings and string values with eachother using
; comparison operators ==, < etcetera (it will use strcmp for you under water automatically).
; comparison operators ==, < etcetera (this will use strcmp automatically).
%asm {{
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
+10 -8
View File
@@ -11,7 +11,7 @@ diskio {
%ir {{
loadm.w r65534,diskio.load.filenameptr
loadm.w r65535,diskio.load.address_override
syscall 61 (): r0.b
syscall 48 (): r0.b
returnr.b r0
}}
}
@@ -72,6 +72,7 @@ diskio {
sub f_read_all(uword bufferpointer) -> uword {
; -- read the full contents of the file, returns number of bytes read.
; It is assumed the file size is less than 64 K.
txt.print("@TODO: f_read_all\n")
return 0
}
@@ -81,7 +82,7 @@ diskio {
; Reads characters from the input file UNTIL a newline or return character (or EOF).
; The line read will be 0-terminated in the buffer (and not contain the end of line character).
; The length of the line is returned in Y. Note that an empty line is okay and is length 0!
; I/O error status should be checked by the caller itself via READST() routine.
; This routine is not able here to return the status as well in a secondary return value, so you have to do that yourself.
txt.print("@TODO: f_readline\n")
return 0
}
@@ -107,6 +108,7 @@ diskio {
sub f_write(uword bufferpointer, uword num_bytes) -> bool {
; -- write the given number of bytes to the currently open file
; you can call this multiple times to append more data
txt.print("@TODO: f_write\n")
return false
}
@@ -157,7 +159,7 @@ diskio {
loadm.w r65533,diskio.save.filenameptr
loadm.w r65534,diskio.save.start_address
loadm.w r65535,diskio.save.savesize
syscall 58 (r65532.b, r65533.w, r65534.w, r65535.w): r0.b
syscall 45 (r65532.b, r65533.w, r65534.w, r65535.w): r0.b
returnr.b r0
}}
}
@@ -169,7 +171,7 @@ diskio {
loadm.w r65533,diskio.save.filenameptr
loadm.w r65534,diskio.save.start_address
loadm.w r65535,diskio.save.savesize
syscall 58 (r65532.b, r65533.w, r65534.w, r65535.w): r0.b
syscall 45 (r65532.b, r65533.w, r65534.w, r65535.w): r0.b
returnr.b r0
}}
}
@@ -184,7 +186,7 @@ diskio {
%ir {{
loadm.w r65534,diskio.load.filenameptr
loadm.w r65535,diskio.load.address_override
syscall 56 (r65534.w, r65535.w): r0.w
syscall 43 (r65534.w, r65535.w): r0.w
returnr.w r0
}}
}
@@ -196,7 +198,7 @@ diskio {
%ir {{
loadm.w r65534,diskio.load_raw.filenameptr
loadm.w r65535,diskio.load_raw.start_address
syscall 57 (r65534.w, r65535.w): r0.w
syscall 44 (r65534.w, r65535.w): r0.w
returnr.w r0
}}
}
@@ -205,7 +207,7 @@ diskio {
; -- delete a file on the drive
%ir {{
loadm.w r65535,diskio.delete.filenameptr
syscall 59 (r65535.w)
syscall 46 (r65535.w)
}}
}
@@ -214,7 +216,7 @@ diskio {
%ir {{
loadm.w r65534,diskio.rename.oldfileptr
loadm.w r65535,diskio.rename.newfileptr
syscall 60 (r65534.w, r65535.w)
syscall 47 (r65534.w, r65535.w)
}}
}
}
+5 -5
View File
@@ -13,7 +13,7 @@ sub print(float value) {
; ---- prints the floating point value (without a newline and no leading spaces).
%ir {{
loadm.f fr65535,floats.print.value
syscall 25 (fr65535.f)
syscall 15 (fr65535.f)
return
}}
}
@@ -24,7 +24,7 @@ sub tostr(float value) -> str {
%ir {{
load.w r65535,floats.tostr.buffer
loadm.f fr65535,floats.tostr.value
syscall 47 (r65535.w, fr65535.f)
syscall 34 (r65535.w, fr65535.f)
load.w r0,floats.tostr.buffer
returnr.w r0
}}
@@ -34,7 +34,7 @@ sub parse(str value) -> float {
; -- parse a string value of a number to float
%ir {{
loadm.w r65535,floats.parse.value
syscall 45 (r65535.w): fr0.f
syscall 32 (r65535.w): fr0.f
returnr.f fr0
}}
}
@@ -147,7 +147,7 @@ sub ceil(float value) -> float {
sub rnd() -> float {
%ir {{
syscall 35 () : fr0.f
syscall 22 () : fr0.f
returnr.f fr0
}}
}
@@ -155,7 +155,7 @@ sub rnd() -> float {
sub rndseed(float seed) {
%ir {{
loadm.f fr65535,floats.rndseed.seed
syscall 32 (fr65535.f)
syscall 19 (fr65535.f)
return
}}
}
+5 -5
View File
@@ -164,14 +164,14 @@ math {
sub rnd() -> ubyte {
%ir {{
syscall 33 (): r0.b
syscall 20 (): r0.b
returnr.b r0
}}
}
sub rndw() -> uword {
%ir {{
syscall 34 (): r0.w
syscall 21 (): r0.w
returnr.w r0
}}
}
@@ -199,7 +199,7 @@ math {
%ir {{
loadm.w r65534,math.rndseed.seed1
loadm.w r65535,math.rndseed.seed2
syscall 31 (r65534.w, r65535.w)
syscall 19 (r65534.w, r65535.w)
return
}}
}
@@ -280,7 +280,7 @@ math {
loadm.b r65533,math.atan2.y1
loadm.b r65534,math.atan2.x2
loadm.b r65535,math.atan2.y2
syscall 44 (r65532.b, r65533.b, r65534.b, r65535.b): r0.b
syscall 31 (r65532.b, r65533.b, r65534.b, r65535.b): r0.b
returnr.b r0
}}
}
@@ -294,7 +294,7 @@ math {
; - not all multiplications in the source code result in an actual multiplication call:
; some simpler multiplications will be optimized away into faster routines. These will not set the upper 16 bits at all!
%ir {{
syscall 46 (): r0.w
syscall 33 (): r0.w
returnr.w r0
}}
}
+4 -8
View File
@@ -377,7 +377,7 @@ monogfx {
; Non-recursive scanline flood fill.
; based loosely on code found here https://www.codeproject.com/Articles/6017/QuickFill-An-efficient-flood-fill-algorithm
; with the fixes applied to the seedfill_4 routine as mentioned in the comments.
const ubyte MAXDEPTH = 64
const ubyte MAXDEPTH = 100
word @zp xx = x as word
word @zp yy = y as word
word[MAXDEPTH] @split @shared stack_xl
@@ -423,11 +423,10 @@ monogfx {
while xx >= 0 {
if pget(xx as uword, yy as uword) as ubyte != cx16.r11L
break
plot(xx as uword, yy as uword, cx16.r10L as bool)
xx--
}
if x1!=xx
horizontal_line(xx as uword+1, yy as uword, x1-xx as uword, cx16.r10L as bool)
else
if x1==xx
goto skip
left = xx + 1
@@ -436,15 +435,12 @@ monogfx {
xx = x1 + 1
do {
cx16.r9 = xx as uword
while xx <= width-1 {
if pget(xx as uword, yy as uword) as ubyte != cx16.r11L
break
plot(xx as uword, yy as uword, cx16.r10L as bool)
xx++
}
if cx16.r9!=xx
horizontal_line(cx16.r9, yy as uword, (xx as uword)-cx16.r9, cx16.r10L as bool)
push_stack(left, xx - 1, yy, dy)
if xx > x2 + 1
push_stack(x2 + 1, xx - 1, yy, -dy)
+19 -4
View File
@@ -68,6 +68,21 @@ string {
return 255
}
sub rfind(uword stringptr, ubyte character) -> ubyte {
; Locates the first position of the given character in the string, starting from the right.
; returns Carry set if found + index in A, or Carry clear if not found (and A will be 255, an invalid index).
; NOTE: because this isn't an asmsub, there's only a SINGLE return value here. On the c64/cx16 targets etc there are 2 return values.
ubyte ix
for ix in string.length(stringptr)-1 downto 0 {
if stringptr[ix]==character {
sys.set_carry()
return ix
}
}
sys.clear_carry()
return 255
}
sub contains(str st, ubyte character) -> bool {
void find(st, character)
if_cs
@@ -83,7 +98,7 @@ string {
%ir {{
loadm.w r65534,string.copy.source
loadm.w r65535,string.copy.target
syscall 52 (r65534.w, r65535.w): r0.b
syscall 39 (r65534.w, r65535.w): r0.b
returnr.b r0
}}
}
@@ -97,13 +112,13 @@ string {
sub compare(str st1, str st2) -> byte {
; Compares two strings for sorting.
; Returns -1 (255), 0 or 1 depending on wether string1 sorts before, equal or after string2.
; Returns -1 (255), 0 or 1, meaning: string1 sorts before, equal or after string2.
; Note that you can also directly compare strings and string values with eachother using
; comparison operators ==, < etcetera (it will use strcmp for you under water automatically).
; comparison operators ==, < etcetera (this will use strcmp automatically).
%ir {{
loadm.w r65534,string.compare.st1
loadm.w r65535,string.compare.st2
syscall 29 (r65534.w, r65535.w) : r0.b
syscall 16 (r65534.w, r65535.w) : r0.b
returnr.b r0
}}
}
+17 -5
View File
@@ -34,7 +34,7 @@ sys {
%ir {{
loadm.w r65534,sys.internal_stringcopy.source
loadm.w r65535,sys.internal_stringcopy.tgt
syscall 52 (r65534.w, r65535.w): r0.b
syscall 39 (r65534.w, r65535.w): r0.b
}}
}
@@ -43,7 +43,7 @@ sys {
loadm.w r65533,sys.memcopy.source
loadm.w r65534,sys.memcopy.tgt
loadm.w r65535,sys.memcopy.count
syscall 49 (r65533.w, r65534.w, r65535.w)
syscall 36 (r65533.w, r65534.w, r65535.w)
}}
}
@@ -52,7 +52,7 @@ sys {
loadm.w r65533,sys.memset.mem
loadm.w r65534,sys.memset.numbytes
loadm.b r65535,sys.memset.value
syscall 50 (r65533.w, r65534.w, r65535.b)
syscall 37 (r65533.w, r65534.w, r65535.b)
}}
}
@@ -61,7 +61,7 @@ sys {
loadm.w r65533,sys.memsetw.mem
loadm.w r65534,sys.memsetw.numwords
loadm.w r65535,sys.memsetw.value
syscall 51 (r65533.w, r65534.w, r65535.w)
syscall 38 (r65533.w, r65534.w, r65535.w)
}}
}
@@ -85,6 +85,18 @@ sys {
}}
}
sub set_irqd() {
%ir {{
sei
}}
}
sub clear_irqd() {
%ir {{
cli
}}
}
sub disable_caseswitch() {
; no-op
}
@@ -128,7 +140,7 @@ sys {
%ir {{
loadm.w r65534,sys.gfx_getpixel.xx
loadm.w r65535,sys.gfx_getpixel.yy
syscall 30 (r65534.w, r65535.w): r0.b
syscall 17 (r65534.w, r65535.w): r0.b
returnr.b r0
}}
}
+2 -2
View File
@@ -7,14 +7,14 @@ txt {
sub width() -> ubyte {
%ir {{
syscall 62 (): r0.w
syscall 49 (): r0.w
returnr.b r0
}}
}
sub height() -> ubyte {
%ir {{
syscall 62 (): r0.w
syscall 49 (): r0.w
msig.b r1,r0
returnr.b r1
}}
+13 -11
View File
@@ -102,10 +102,14 @@ private fun compileMain(args: Array<String>): Boolean {
}
}
if(bytes2float!=null)
return convertBytesToFloat(bytes2float!!, compilationTarget!!)
if(float2bytes!=null)
return convertFloatToBytes(float2bytes!!, compilationTarget!!)
if(bytes2float!=null) {
convertBytesToFloat(bytes2float!!, compilationTarget!!)
return true
}
if(float2bytes!=null) {
convertFloatToBytes(float2bytes!!, compilationTarget!!)
return true
}
if(varsHighBank==0 && compilationTarget==Cx16Target.NAME) {
System.err.println("On the Commander X16, HiRAM bank 0 is used by the kernal and can't be used.")
@@ -139,7 +143,8 @@ private fun compileMain(args: Array<String>): Boolean {
}
if(startVm==true) {
return runVm(moduleFiles.first())
runVm(moduleFiles.first())
return true
}
val processedSymbols = processSymbolDefs(symbolDefs) ?: return false
@@ -297,21 +302,19 @@ private fun compileMain(args: Array<String>): Boolean {
return true
}
fun convertFloatToBytes(number: String, target: String): Boolean {
fun convertFloatToBytes(number: String, target: String) {
val tgt = getCompilationTargetByName(target)
val dbl = number.toDouble()
val bytes = tgt.machine.convertFloatToBytes(dbl)
print("$dbl in bytes on '$target': ")
println(bytes.joinToString(","))
return true
}
fun convertBytesToFloat(bytelist: String, target: String): Boolean {
fun convertBytesToFloat(bytelist: String, target: String) {
val tgt = getCompilationTargetByName(target)
val bytes = bytelist.split(',').map { it.trim().toUByte() }
val number = tgt.machine.convertBytesToFloat(bytes)
println("floating point value on '$target': $number")
return true
}
private fun processSymbolDefs(symbolDefs: List<String>): Map<String, String>? {
@@ -329,9 +332,8 @@ private fun processSymbolDefs(symbolDefs: List<String>): Map<String, String>? {
return result
}
fun runVm(irFilename: String): Boolean {
fun runVm(irFilename: String) {
val irFile = Path(irFilename)
val vmdef = VirtualMachineDefinition()
vmdef.launchEmulator(0, irFile)
return true
}
@@ -19,8 +19,6 @@ internal val constEvaluatorsForBuiltinFuncs: Map<String, ConstExpressionCaller>
"sqrt__ubyte" to { a, p, prg -> oneIntArgOutputInt(a, p, prg, false) { sqrt(it.toDouble()) } },
"sqrt__uword" to { a, p, prg -> oneIntArgOutputInt(a, p, prg, false) { sqrt(it.toDouble()) } },
"sqrt__float" to { a, p, prg -> oneFloatArgOutputFloat(a, p, prg) { sqrt(it) } },
"any" to { a, p, prg -> collectionArgBoolResult(a, p, prg) { array->array.any { it!=0.0 } } },
"all" to { a, p, prg -> collectionArgBoolResult(a, p, prg) { array->array.all { it!=0.0 } } },
"lsb" to { a, p, prg -> oneIntArgOutputInt(a, p, prg, true) { x: Int -> (x and 255).toDouble() } },
"msb" to { a, p, prg -> oneIntArgOutputInt(a, p, prg, true) { x: Int -> (x ushr 8 and 255).toDouble()} },
"mkword" to ::builtinMkword,
@@ -77,17 +75,6 @@ private fun oneFloatArgOutputFloat(args: List<Expression>, position: Position, p
return NumericLiteral(DataType.FLOAT, function(constval.number), args[0].position)
}
private fun collectionArgBoolResult(args: List<Expression>, position: Position, program: Program, function: (arg: List<Double>)->Boolean): NumericLiteral {
if(args.size!=1)
throw SyntaxError("builtin function requires one non-scalar argument", position)
val array= args[0] as? ArrayLiteral ?: throw NotConstArgumentException()
val constElements = array.value.map{it.constValue(program)?.number}
if(constElements.contains(null))
throw NotConstArgumentException()
return NumericLiteral.fromBoolean(function(constElements.mapNotNull { it }), args[0].position)
}
private fun builtinAbs(args: List<Expression>, position: Position, program: Program): NumericLiteral {
// 1 arg, type = int, result type= uword
if(args.size!=1)
@@ -140,6 +127,8 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
return NumericLiteral.optimalInteger(arraySize, position)
if(args[0] is ArrayLiteral)
return NumericLiteral.optimalInteger((args[0] as ArrayLiteral).value.size, position)
if(args[0] is StringLiteral)
return NumericLiteral.optimalInteger((args[0] as StringLiteral).value.length, position)
if(args[0] !is IdentifierReference)
throw SyntaxError("len argument should be an identifier", position)
val target = (args[0] as IdentifierReference).targetVarDecl(program)
+2 -3
View File
@@ -302,10 +302,9 @@ fun parseMainModule(filepath: Path,
.filter { it.isFromFilesystem }
.map { Path(it.origin) }
val compilerOptions = determineCompilationOptions(program, compTarget)
// depending on the machine and compiler options we may have to include some libraries
for(lib in compTarget.machine.importLibs(compilerOptions, compTarget.name))
importer.importImplicitLibraryModule(lib)
// import the default modules
importer.importImplicitLibraryModule("syslib")
if(compilerOptions.compTarget.name!=VMTarget.NAME && !compilerOptions.experimentalCodegen) {
importer.importImplicitLibraryModule("math")
}
@@ -1418,6 +1418,13 @@ internal class AstChecker(private val program: Program,
if(target is VarDecl) {
if(target.datatype !in IterableDatatypes && target.datatype!=DataType.UWORD)
errors.err("indexing requires an iterable or address uword variable", arrayIndexedExpression.position)
val indexVariable = arrayIndexedExpression.indexer.indexExpr as? IdentifierReference
if(indexVariable!=null) {
if(indexVariable.targetVarDecl(program)?.datatype in SignedDatatypes) {
errors.err("variable array indexing can't be performed with signed variables", indexVariable.position)
return
}
}
val arraysize = target.arraysize?.constIndex()
val index = arrayIndexedExpression.indexer.constIndex()
if(arraysize!=null) {
@@ -177,7 +177,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
'_'
}.joinToString("")
val textEncoding = (call as Node).definingModule.textEncoding
call.args[0] = StringLiteral(processed, textEncoding, name.position)
call.args[0] = StringLiteral.create(processed, textEncoding, name.position)
call.args[0].linkParents(call as Node)
}
}
@@ -59,16 +59,15 @@ internal class NotExpressionAndIfComparisonExprChanger(val program: Program, val
// integer case (only if both are the same type)
val leftC = expr.left as? BinaryExpression
val rightC = expr.right as? BinaryExpression
if(leftC!=null && rightC!=null && leftC.operator=="==" && rightC.operator=="==") {
if(expr.operator=="and" && leftC!=null && rightC!=null && leftC.operator=="==" && rightC.operator=="==") {
if (leftC.right.constValue(program)?.number == 0.0 && rightC.right.constValue(program)?.number == 0.0) {
val leftDt = leftC.left.inferType(program).getOr(DataType.UNDEFINED)
val rightDt = rightC.left.inferType(program).getOr(DataType.UNDEFINED)
if(leftDt==rightDt && leftDt in IntegerDatatypes) {
if (rightC.left.isSimple) {
// x==0 or y==0 -> (x & y)==0
// x==0 and y==0 -> (x | y)==0
val newOperator = if(expr.operator=="or") "&" else "|"
val inner = BinaryExpression(leftC.left, newOperator, rightC.left, expr.position)
// the 'or' case cannot be easily optimized with a binary and like this!
val inner = BinaryExpression(leftC.left, "|", rightC.left, expr.position)
val compare = BinaryExpression(inner, "==", NumericLiteral(leftDt, 0.0, expr.position), expr.position)
return listOf(IAstModification.ReplaceNode(expr, compare, parent))
}
@@ -277,6 +277,10 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
val target = arrayIndexedExpression.arrayvar.targetVarDecl(program)
val arraysize = target?.arraysize?.constIndex()
if(arraysize!=null) {
if(arraysize+index < 0) {
errors.err("index out of bounds", arrayIndexedExpression.position)
return noModifications
}
// replace the negative index by the normal index
val newIndex = NumericLiteral.optimalNumeric(arraysize+index, arrayIndexedExpression.indexer.position)
arrayIndexedExpression.indexer.indexExpr = newIndex
+3 -1
View File
@@ -108,6 +108,8 @@ class TestCompilerOnExamplesCx16: FunSpec({
"vtui/testvtui",
"pcmaudio/play-adpcm",
"pcmaudio/stream-wav",
"pcmaudio/stream-simple-aflow",
"pcmaudio/stream-simple-poll",
"pcmaudio/vumeter",
"sprites/dragon",
"sprites/dragons",
@@ -171,9 +173,9 @@ class TestCompilerOnExamplesBothC64andCx16: FunSpec({
"mandelbrot-gfx",
"numbergame",
"primes",
"queens",
"screencodes",
"sincos",
"sorting",
"swirl",
"swirl-float",
"tehtriz",
+5 -5
View File
@@ -96,11 +96,11 @@ class TestNumericLiteral: FunSpec({
}
test("testEqualsRef") {
(StringLiteral("hello", Encoding.PETSCII, dummyPos) == StringLiteral("hello", Encoding.PETSCII, dummyPos)) shouldBe true
(StringLiteral("hello", Encoding.PETSCII, dummyPos) != StringLiteral("bye", Encoding.PETSCII, dummyPos)) shouldBe true
(StringLiteral("hello", Encoding.SCREENCODES, dummyPos) == StringLiteral("hello", Encoding.SCREENCODES, dummyPos)) shouldBe true
(StringLiteral("hello", Encoding.SCREENCODES, dummyPos) != StringLiteral("bye", Encoding.SCREENCODES, dummyPos)) shouldBe true
(StringLiteral("hello", Encoding.SCREENCODES, dummyPos) != StringLiteral("hello", Encoding.PETSCII, dummyPos)) shouldBe true
(StringLiteral.create("hello", Encoding.PETSCII, dummyPos) == StringLiteral.create("hello", Encoding.PETSCII, dummyPos)) shouldBe true
(StringLiteral.create("hello", Encoding.PETSCII, dummyPos) != StringLiteral.create("bye", Encoding.PETSCII, dummyPos)) shouldBe true
(StringLiteral.create("hello", Encoding.SCREENCODES, dummyPos) == StringLiteral.create("hello", Encoding.SCREENCODES, dummyPos)) shouldBe true
(StringLiteral.create("hello", Encoding.SCREENCODES, dummyPos) != StringLiteral.create("bye", Encoding.SCREENCODES, dummyPos)) shouldBe true
(StringLiteral.create("hello", Encoding.SCREENCODES, dummyPos) != StringLiteral.create("hello", Encoding.PETSCII, dummyPos)) shouldBe true
val lvOne = NumericLiteral(DataType.UBYTE, 1.0, dummyPos)
val lvTwo = NumericLiteral(DataType.UBYTE, 2.0, dummyPos)
+26 -12
View File
@@ -688,18 +688,6 @@ main {
(statements[7] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.compilerAst)!!.number shouldBe 53281.0
}
test("no crash on sorting unused array") {
val text="""
main {
ubyte[5] cards = [ 14, 6, 29, 16, 3 ]
sub start() {
sort(cards)
}
}"""
compileText(C64Target(), true, text, writeAssembly = false) shouldNotBe null
}
test("no string error when inlining") {
val text="""
main {
@@ -1059,4 +1047,30 @@ main {
compileText(VMTarget(), true, src, writeAssembly = true) shouldNotBe null
compileText(C64Target(), true, src, writeAssembly = true) shouldNotBe null
}
test("optimizing inlined functions must reference proper scopes") {
val src="""
main {
sub start() {
void other.sub1()
cx16.r0L = other.sub1()+other.sub1()
}
}
other {
sub sub2() -> ubyte{
cx16.r0++
cx16.r1++
return cx16.r0L
}
sub sub1() -> ubyte {
return sub2()
}
}"""
compileText(VMTarget(), true, src, writeAssembly = true) shouldNotBe null
compileText(C64Target(), true, src, writeAssembly = true) shouldNotBe null
}
})
+32 -2
View File
@@ -3,15 +3,18 @@ package prog8tests
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.expectError
import com.github.michaelbull.result.getOrElse
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import prog8.ast.expressions.CharLiteral
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.Position
import prog8.code.core.unescape
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
@@ -21,6 +24,7 @@ import prog8.code.target.encodings.IsoEncoding
import prog8.code.target.encodings.PetsciiEncoding
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText
import java.io.CharConversionException
class TestStringEncodings: FunSpec({
@@ -231,6 +235,22 @@ class TestStringEncodings: FunSpec({
}
}
context("kata") {
test("kata translation to half width glyphs") {
val orig = "カ が ガ"
orig.length shouldBe 5
val str = StringLiteral.create(orig, Encoding.KATAKANA, Position.DUMMY)
str.value.length shouldBe 7
val character = CharLiteral.create('カ', Encoding.KATAKANA, Position.DUMMY)
character.value shouldBe 'カ'
shouldThrow<CharConversionException> {
CharLiteral.create('ガ', Encoding.KATAKANA, Position.DUMMY)
}
}
}
test("special pass-through") {
val passthroughEscaped= """\x00\x1b\x99\xff"""
val passthrough = passthroughEscaped.unescape()
@@ -299,12 +319,22 @@ class TestStringEncodings: FunSpec({
str string1 = "default"
str string2 = sc:"screencodes"
str string3 = iso:"iso"
str string4 = petscii:"petscii"
str string4 = iso5:"Хозяин и Работник"
str string5 = iso16:"zażółć gęślą jaźń"
str string6 = cp437:"≈ IBM Pc ≈ ♂♀♪☺¶"
str string7 = petscii:"petscii"
str string8 = atascii:"atascii"
str string9 = kata:"クジン。 # が # ガ"
ubyte char1 = 'd'
ubyte char2 = sc:'s'
ubyte char3 = iso:'i'
ubyte char4 = petscii:'p'
ubyte char4 = iso5:'и'
ubyte char5 = iso16:'ł'
ubyte char6 = cp437:'☺'
ubyte char7 = petscii:'p'
ubyte char8 = atascii:'p'
ubyte char9 = kata:'カ'
sub start() {
}
+1 -1
View File
@@ -209,7 +209,7 @@ main {
}
test("ubyte to word casts") {
var src="""
val src="""
main {
sub start() {
ubyte @shared bb = 255
-10
View File
@@ -406,15 +406,6 @@ main {
ub = zero+(all(warr) as ubyte)*1+zero
txt.print_ub(ub)
txt.nl()
sort(ubarr)
sort(barr)
sort(uwarr)
sort(warr)
reverse(ubarr)
reverse(barr)
reverse(uwarr)
reverse(warr)
}
sub floatingpoint() {
@@ -449,7 +440,6 @@ main {
txt.print_ub(ub)
txt.nl()
reverse(flarr)
for ub in 0 to len(flarr)-1 {
floats.print(flarr[ub])
txt.chrout(',')
+9 -9
View File
@@ -35,7 +35,7 @@ class TestProg8Parser: FunSpec( {
context("Newline at end") {
test("is not required - #40, fixed by #45") {
val nl = "\n" // say, Unix-style (different flavours tested elsewhere)
val src = SourceCode.Text("foo {" + nl + "}") // source ends with '}' (= NO newline, issue #40)
val src = SourceCode.Text("foo {$nl}") // source ends with '}' (= NO newline, issue #40)
// #40: Prog8ANTLRParser would report (throw) "missing <EOL> at '<EOF>'"
val module = parseModule(src)
@@ -44,7 +44,7 @@ class TestProg8Parser: FunSpec( {
test("is still accepted - #40, fixed by #45") {
val nl = "\n" // say, Unix-style (different flavours tested elsewhere)
val srcText = "foo {" + nl + "}" + nl // source does end with a newline (issue #40)
val srcText = "foo {$nl}$nl" // source does end with a newline (issue #40)
val module = parseModule(SourceCode.Text(srcText))
module.statements.size shouldBe 1
}
@@ -55,10 +55,10 @@ class TestProg8Parser: FunSpec( {
val nl = "\n" // say, Unix-style (different flavours tested elsewhere)
// BAD: 2nd block `bar` does NOT start on new line; however, there's is a nl at the very end
val srcBad = "foo {" + nl + "}" + " bar {" + nl + "}" + nl
val srcBad = "foo {$nl} bar {$nl}$nl"
// GOOD: 2nd block `bar` does start on a new line; however, a nl at the very end ain't needed
val srcGood = "foo {" + nl + "}" + nl + "bar {" + nl + "}"
val srcGood = "foo {$nl}${nl}bar {$nl}"
shouldThrow<ParseError> { parseModule(SourceCode.Text(srcBad)) }
val module = parseModule(SourceCode.Text(srcGood))
@@ -612,9 +612,9 @@ class TestProg8Parser: FunSpec( {
}
test("testCharLiteralConstValue") {
val char1 = CharLiteral('A', Encoding.PETSCII, Position.DUMMY)
val char2 = CharLiteral('z', Encoding.SCREENCODES, Position.DUMMY)
val char3 = CharLiteral('_', Encoding.ISO, Position.DUMMY)
val char1 = CharLiteral.create('A', Encoding.PETSCII, Position.DUMMY)
val char2 = CharLiteral.create('z', Encoding.SCREENCODES, Position.DUMMY)
val char3 = CharLiteral.create('_', Encoding.ISO, Position.DUMMY)
val program = Program("test", DummyFunctions, DummyMemsizer, AsciiStringEncoder)
char1.constValue(program).number.toInt() shouldBe 65
@@ -640,8 +640,8 @@ class TestProg8Parser: FunSpec( {
(ten <= ten) shouldBe true
(ten < ten) shouldBe false
val abc = StringLiteral("abc", Encoding.PETSCII, Position.DUMMY)
val abd = StringLiteral("abd", Encoding.PETSCII, Position.DUMMY)
val abc = StringLiteral.create("abc", Encoding.PETSCII, Position.DUMMY)
val abd = StringLiteral.create("abd", Encoding.PETSCII, Position.DUMMY)
abc shouldBe abc
(abc!=abd) shouldBe true
(abc!=abc) shouldBe false
+12 -7
View File
@@ -54,6 +54,11 @@ class TestSourceCode: AnnotationSpec() {
shouldThrow<FileSystemException> { SourceCode.File(fixturesDir) }
}
private fun normalizeLineEndings(text: String): String {
return text.replace("\\R".toRegex(), "\n")
}
@Test
fun testFromPathWithExistingPath() {
val filename = "ast_simple_main.p8"
@@ -61,7 +66,7 @@ class TestSourceCode: AnnotationSpec() {
val src = SourceCode.File(path)
val expectedOrigin = SourceCode.relative(path).toString()
src.origin shouldBe expectedOrigin
src.text shouldBe path.toFile().readText()
src.text shouldBe normalizeLineEndings(path.toFile().readText())
src.isFromResources shouldBe false
src.isFromFilesystem shouldBe true
}
@@ -85,7 +90,7 @@ class TestSourceCode: AnnotationSpec() {
val src = SourceCode.File(path)
val expectedOrigin = SourceCode.relative(path).toString()
src.origin shouldBe expectedOrigin
src.text shouldBe srcFile.readText()
src.text shouldBe normalizeLineEndings(srcFile.readText())
}
@Test
@@ -95,7 +100,7 @@ class TestSourceCode: AnnotationSpec() {
val src = SourceCode.Resource(pathString)
src.origin shouldBe "$LIBRARYFILEPREFIX/$pathString"
src.text shouldBe srcFile.readText()
src.text shouldBe normalizeLineEndings(srcFile.readText())
src.isFromResources shouldBe true
src.isFromFilesystem shouldBe false
}
@@ -107,7 +112,7 @@ class TestSourceCode: AnnotationSpec() {
val src = SourceCode.Resource(pathString)
src.origin shouldBe "$LIBRARYFILEPREFIX$pathString"
src.text shouldBe srcFile.readText()
src.text shouldBe normalizeLineEndings(srcFile.readText())
}
@Test
@@ -117,7 +122,7 @@ class TestSourceCode: AnnotationSpec() {
val src = SourceCode.Resource(pathString)
src.origin shouldBe "$LIBRARYFILEPREFIX/$pathString"
src.text shouldBe srcFile.readText()
src.text shouldBe normalizeLineEndings(srcFile.readText())
src.isFromResources shouldBe true
}
@@ -128,7 +133,7 @@ class TestSourceCode: AnnotationSpec() {
val src = SourceCode.Resource(pathString)
src.origin shouldBe "$LIBRARYFILEPREFIX$pathString"
src.text shouldBe srcFile.readText()
src.text shouldBe normalizeLineEndings(srcFile.readText())
}
@Test
@@ -138,7 +143,7 @@ class TestSourceCode: AnnotationSpec() {
val src = SourceCode.Resource(pathString)
src.origin shouldBe "$LIBRARYFILEPREFIX/prog8lib/math.p8"
src.text shouldBe srcFile.readText()
src.text shouldBe normalizeLineEndings(srcFile.readText())
src.isFromResources shouldBe true
}
+5 -5
View File
@@ -535,7 +535,7 @@ main {
byte @shared col = 20
col++
ubyte @shared ubb = lsb(col as uword)
uword @shared vaddr = bottom[col] as uword << 8 ; a mkword will get inserted here
uword @shared vaddr = bottom[cx16.r0L] as uword << 8 ; a mkword will get inserted here
}
}"""
val result = compileText(VMTarget(), optimize=true, src, writeAssembly=false)!!
@@ -597,10 +597,10 @@ main {
main {
sub start() {
uword @shared a,b
b = a ; works
cx16.r1L = lsb(a) ; works
funcw(a) ; works
funcb(lsb(a)) ; fails :-( TODO FIX
b = a
cx16.r1L = lsb(a)
funcw(a)
funcb(lsb(a))
}
sub funcw(uword arg) {
@@ -6,6 +6,7 @@ import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain
import prog8.code.target.C64Target
import prog8.code.target.VMTarget
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText
import kotlin.io.path.readText
@@ -220,5 +221,104 @@ main {
}"""
compileText(C64Target(), false, src, writeAssembly = true) shouldNotBe null
}
test("negative array index variables are not allowed, but ptr indexing is allowed") {
val src="""
main {
sub start() {
uword @shared pointer
ubyte[10] array
str name = "hello"
byte sindex
array[sindex] = 10
cx16.r0L = array[sindex]
name[sindex] = 0
cx16.r0L = name[sindex]
pointer[sindex] = 10
cx16.r0L=pointer[sindex]
}
}"""
val errors = ErrorReporterForTests()
compileText(VMTarget(), false, src, writeAssembly = false, errors = errors) shouldBe null
errors.errors.size shouldBe 4
errors.errors[0] shouldContain "signed variables"
errors.errors[1] shouldContain "signed variables"
errors.errors[2] shouldContain "signed variables"
errors.errors[3] shouldContain "signed variables"
}
test("bounds checking for both positive and negative indexes, correct cases") {
val src="""
main {
sub start() {
ubyte[10] array
array[0] = 0
array[9] = 0
array[-1] = 0
array[-10] = 0
}
}"""
compileText(VMTarget(), false, src, writeAssembly = false) shouldNotBe null
}
test("bounds checking on strings, correct cases") {
val src="""
main {
sub start() {
str name = "1234567890"
name[0] = 0
name[9] = 0
}
}"""
compileText(VMTarget(), false, src, writeAssembly = false) shouldNotBe null
}
test("bounds checking for positive indexes, invalid case") {
val src="""
main {
sub start() {
ubyte[10] array
array[10] = 0
}
}"""
val errors = ErrorReporterForTests()
compileText(VMTarget(), false, src, writeAssembly = false, errors = errors) shouldBe null
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "out of bounds"
}
test("bounds checking for negative indexes, invalid case") {
val src="""
main {
sub start() {
ubyte[10] array
array[-11] = 0
}
}"""
val errors = ErrorReporterForTests()
compileText(VMTarget(), false, src, writeAssembly = false, errors = errors) shouldBe null
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "out of bounds"
}
test("bounds checking on strings invalid cases") {
val src="""
main {
sub start() {
str name = "1234567890"
name[10] = 0
name[-1] = 0
name[-11] = 0
}
}"""
val errors = ErrorReporterForTests()
compileText(VMTarget(), false, src, writeAssembly = false, errors = errors) shouldBe null
errors.errors.size shouldBe 3
errors.errors[0] shouldContain "out of bounds"
errors.errors[1] shouldContain "out of bounds"
errors.errors[2] shouldContain "out of bounds"
}
})
@@ -11,9 +11,7 @@ import prog8.code.ast.PtAssignment
import prog8.code.ast.PtBinaryExpression
import prog8.code.ast.PtVariable
import prog8.code.core.DataType
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8.code.target.VMTarget
import prog8.code.target.*
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText
import kotlin.io.path.readText
@@ -67,9 +65,10 @@ main {
}"""
val result = compileText(C64Target(), false, text, writeAssembly = true)!!
result.compilerAst.name shouldStartWith "on_the_fly"
result.codegenAst!!.name shouldBe result.compilerAst.name
result.codegenAst!!.children.size shouldBeGreaterThan 2
val start = result.codegenAst!!.entrypoint()!!
val ast = result.codegenAst!!
ast.name shouldBe result.compilerAst.name
ast.children.size shouldBeGreaterThan 2
val start = ast.entrypoint()!!
start.name shouldBe "p8s_start"
start.children.size shouldBeGreaterThan 2
val seed = start.children[0] as PtVariable
@@ -363,4 +362,25 @@ main {
assembly shouldContain "inlined routine follows: pop"
assembly shouldContain "inlined routine follows: popw"
}
test("syslib correctly available for raw outputs") {
val text = """
%output raw
%launcher none
%address ${'$'}2000
main {
sub start() {
cx16.r0++
sys.clear_carry()
}
}
"""
compileText(Cx16Target(), false, text, writeAssembly = true) shouldNotBe null
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
compileText(C128Target(), false, text, writeAssembly = true) shouldNotBe null
compileText(PETTarget(), false, text, writeAssembly = true) shouldNotBe null
compileText(AtariTarget(), false, text, writeAssembly = true) shouldNotBe null
compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null
}
})
File diff suppressed because it is too large. Load diff
+30 -21
View File
@@ -11,6 +11,7 @@ import prog8.ast.statements.Assignment
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8.code.target.VMTarget
import prog8.intermediate.IRDataType
import prog8.intermediate.IRFileReader
import prog8.intermediate.IRSubroutine
import prog8.intermediate.Opcode
@@ -19,26 +20,6 @@ import prog8tests.helpers.compileText
import kotlin.io.path.readText
class TestCompilerVirtual: FunSpec({
test("compile virtual: any all sort reverse builtin funcs") {
val src = """
main {
sub start() {
uword[] words = [1111,2222,0,4444,3333]
bool result = all(words)
cx16.r0++
result = any(words)
cx16.r0++
sort(words)
reverse(words)
}
}"""
val target = VMTarget()
val result = compileText(target, true, src, writeAssembly = true)!!
val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
VmRunner().runProgram(virtfile.readText())
}
test("compile virtual: array with pointers") {
val src = """
main {
@@ -475,6 +456,34 @@ main {
compileText(VMTarget(), true, src, writeAssembly = true) shouldNotBe null
}
test("push() and pop() generate correct IR instructions") {
val src="""
main {
sub start() {
ubyte bb
uword ww
sys.push(42)
bb++
bb=sys.pop()
sys.pushw(9999)
ww++
ww=sys.popw()
}
}"""
val result = compileText(VMTarget(), true, src, writeAssembly = true)!!
val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
val irProgram = IRFileReader().read(virtfile)
val start = irProgram.blocks[0].children[0] as IRSubroutine
val instructions = start.chunks.flatMap { c->c.instructions }
instructions.size shouldBe 13
instructions[3].opcode shouldBe Opcode.PUSH
instructions[3].type shouldBe IRDataType.BYTE
instructions[5].opcode shouldBe Opcode.POP
instructions[5].type shouldBe IRDataType.BYTE
instructions[8].opcode shouldBe Opcode.PUSH
instructions[8].type shouldBe IRDataType.WORD
instructions[10].opcode shouldBe Opcode.POP
instructions[10].type shouldBe IRDataType.WORD
}
})
+1 -1
View File
@@ -23,7 +23,7 @@ compileTestKotlin {
dependencies {
implementation project(':codeCore')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
implementation "org.antlr:antlr4-runtime:4.13.1"
implementation "org.antlr:antlr4-runtime:4.13.2"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.0"
implementation project(':parser')
}
@@ -316,12 +316,12 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
}
override fun visit(array: ArrayLiteral) {
outputListMembers(array.value.asSequence(), '[', ']')
outputListMembers(array.value.asSequence())
}
private fun outputListMembers(array: Sequence<Expression>, openchar: Char, closechar: Char) {
private fun outputListMembers(array: Sequence<Expression>) {
var counter = 0
output(openchar.toString())
output("[")
scopelevel++
for (v in array) {
v.accept(this)
@@ -335,7 +335,7 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
}
}
scopelevel--
output(closechar.toString())
output("]")
}
override fun visit(assignment: Assignment) {
@@ -364,10 +364,10 @@ private fun AssignmentContext.toAst(): Statement {
}
val nestedAssign = assignment()
if(nestedAssign==null)
return Assignment(assign_target().toAst(), expression().toAst(), AssignmentOrigin.USERCODE, toPosition())
return if(nestedAssign==null)
Assignment(assign_target().toAst(), expression().toAst(), AssignmentOrigin.USERCODE, toPosition())
else
return ChainedAssignment(assign_target().toAst(), nestedAssign.toAst(), toPosition())
ChainedAssignment(assign_target().toAst(), nestedAssign.toAst(), toPosition())
}
private fun AugassignmentContext.toAst(): Assignment {
@@ -10,6 +10,8 @@ import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstVisitor
import prog8.code.core.*
import prog8.code.target.encodings.JapaneseCharacterConverter
import java.io.CharConversionException
import java.util.*
import kotlin.math.abs
import kotlin.math.floor
@@ -563,12 +565,12 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
override fun hashCode(): Int = Objects.hash(type, number)
override fun equals(other: Any?): Boolean {
if(other==null || other !is NumericLiteral)
return false
return if(other==null || other !is NumericLiteral)
false
else if(type!=DataType.BOOL && other.type!=DataType.BOOL)
return number==other.number
number==other.number
else
return type==other.type && number==other.number
type==other.type && number==other.number
}
operator fun compareTo(other: NumericLiteral): Int = number.compareTo(other.number)
@@ -713,7 +715,7 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
}
}
class CharLiteral(val value: Char,
class CharLiteral private constructor(val value: Char,
var encoding: Encoding,
override val position: Position) : Expression() {
override lateinit var parent: Node
@@ -723,9 +725,20 @@ class CharLiteral(val value: Char,
}
companion object {
fun create(character: Char, encoding: Encoding, position: Position): CharLiteral {
if(encoding==Encoding.KATAKANA) {
val processed = JapaneseCharacterConverter.zenkakuKatakanaToHankakuKatakana(character.toString())
if(processed.length==1)
return CharLiteral(processed[0], encoding, position)
else
throw CharConversionException("character literal encodes into multiple bytes at $position")
} else
return CharLiteral(character, encoding, position)
}
fun fromEscaped(raw: String, encoding: Encoding, position: Position): CharLiteral {
val unescaped = raw.unescape()
return CharLiteral(unescaped[0], encoding, position)
return create(unescaped[0], encoding, position)
}
}
@@ -756,7 +769,7 @@ class CharLiteral(val value: Char,
}
}
class StringLiteral(val value: String,
class StringLiteral private constructor(val value: String,
var encoding: Encoding,
override val position: Position) : Expression() {
override lateinit var parent: Node
@@ -766,10 +779,15 @@ class StringLiteral(val value: String,
}
companion object {
fun fromEscaped(raw: String, encoding: Encoding, position: Position): StringLiteral {
val unescaped = raw.unescape()
return StringLiteral(unescaped, encoding, position)
fun create(str: String, encoding: Encoding, position: Position): StringLiteral {
if (encoding == Encoding.KATAKANA) {
val processed = JapaneseCharacterConverter.zenkakuKatakanaToHankakuKatakana(str)
return StringLiteral(processed, encoding, position)
} else
return StringLiteral(str, encoding, position)
}
fun fromEscaped(raw: String, encoding: Encoding, position: Position): StringLiteral = create(raw.unescape(), encoding, position)
}
override val isSimple = true
@@ -1,6 +1,5 @@
Prog8 compiler v10.3-SNAPSHOT by Irmen de Jong (irmen@razorvine.net)
Prerelease version from git commit 42f4b06a in branch master
Prog8 compiler v10.4 by Irmen de Jong (irmen@razorvine.net)
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
Compiling program import-all-atari.p8
@@ -264,7 +263,7 @@ diskio {
f_open_w (uword filenameptr) -> bool
f_read (uword bufferpointer, uword num_bytes) -> uword
f_read_all (uword bufferpointer) -> uword
f_readline (uword bufptr @AY) -> clobbers (X) -> ubyte @Y
f_readline (uword bufptr @AY) -> clobbers (X) -> ubyte @Y, ubyte @A
f_write (uword bufferpointer, uword num_bytes) -> bool
lf_end_list ()
lf_next_entry () -> bool
@@ -308,6 +307,7 @@ string {
ltrim (str s)
ltrimmed (str s) -> str
pattern_match (str string @AY, str pattern @R0) -> clobbers (Y) -> bool @A
rfind (uword string @AY, ubyte character @X) -> ubyte @A, bool @Pc
right (uword source @AY, ubyte length @X, uword target @R1) -> clobbers (A,Y)
rstrip (str s)
rtrim (str s)
@@ -1,6 +1,5 @@
Prog8 compiler v10.3-SNAPSHOT by Irmen de Jong (irmen@razorvine.net)
Prerelease version from git commit 42f4b06a in branch master
Prog8 compiler v10.4 by Irmen de Jong (irmen@razorvine.net)
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
Compiling program import-all-c128.p8
@@ -490,7 +489,7 @@ diskio {
f_open_w (uword filenameptr) -> bool
f_read (uword bufferpointer, uword num_bytes) -> uword
f_read_all (uword bufferpointer) -> uword
f_readline (uword bufptr @AY) -> clobbers (X) -> ubyte @Y
f_readline (uword bufptr @AY) -> clobbers (X) -> ubyte @Y, ubyte @A
f_write (uword bufferpointer, uword num_bytes) -> bool
lf_end_list ()
lf_next_entry () -> bool
@@ -534,6 +533,7 @@ string {
ltrim (str s)
ltrimmed (str s) -> str
pattern_match (str string @AY, str pattern @R0) -> clobbers (Y) -> bool @A
rfind (uword string @AY, ubyte character @X) -> ubyte @A, bool @Pc
right (uword source @AY, ubyte length @X, uword target @R1) -> clobbers (A,Y)
rstrip (str s)
rtrim (str s)
@@ -1,5 +1,5 @@
Prog8 compiler v10.3-SNAPSHOT by Irmen de Jong (irmen@razorvine.net)
Prerelease version from git commit 42f4b06a in branch master
Prog8 compiler v10.4 by Irmen de Jong (irmen@razorvine.net)
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
Compiling program import-all-c64.p8
@@ -491,7 +491,7 @@ diskio {
f_open_w (uword filenameptr) -> bool
f_read (uword bufferpointer, uword num_bytes) -> uword
f_read_all (uword bufferpointer) -> uword
f_readline (uword bufptr @AY) -> clobbers (X) -> ubyte @Y
f_readline (uword bufptr @AY) -> clobbers (X) -> ubyte @Y, ubyte @A
f_write (uword bufferpointer, uword num_bytes) -> bool
lf_end_list ()
lf_next_entry () -> bool
@@ -535,6 +535,7 @@ string {
ltrim (str s)
ltrimmed (str s) -> str
pattern_match (str string @AY, str pattern @R0) -> clobbers (Y) -> bool @A
rfind (uword string @AY, ubyte character @X) -> ubyte @A, bool @Pc
right (uword source @AY, ubyte length @X, uword target @R1) -> clobbers (A,Y)
rstrip (str s)
rtrim (str s)
@@ -609,9 +610,12 @@ floats {
SQRA () -> clobbers (A,X,Y) = $bf74
TAN () -> clobbers (A,X,Y) = $e2b4
atan (float value) -> float
atan2 (float y, float x) -> float
ceil (float value) -> float
clampf (float value, float minimum, float maximum) -> float
cos (float angle) -> float
cot (float value) -> float
csc (float value) -> float
deg (float angle) -> float
floor (float value) -> float
ln (float value) -> float
@@ -628,6 +632,7 @@ floats {
rnd () -> float
rndseed (float seed)
round (float value) -> float
secant (float value) -> float
sin (float angle) -> float
tan (float value) -> float
tostr (float value @FAC1) -> clobbers (X) -> str @AY
@@ -1,6 +1,5 @@
Prog8 compiler v10.3-SNAPSHOT by Irmen de Jong (irmen@razorvine.net)
Prerelease version from git commit 42f4b06a in branch master
Prog8 compiler v10.4 by Irmen de Jong (irmen@razorvine.net)
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
Compiling program import-all-cx16.p8
@@ -69,12 +68,14 @@ diskio {
f_open_w_seek (str filename) -> bool
f_read (uword bufferpointer, uword num_bytes) -> uword
f_read_all (uword bufferpointer) -> uword
f_readline (uword bufptr @AY) -> clobbers (X) -> ubyte @Y
f_readline (uword bufptr @AY) -> clobbers (X) -> ubyte @Y, ubyte @A
f_seek (uword pos_hiword, uword pos_loword)
f_seek_w (uword pos_hiword, uword pos_loword)
f_tell () -> uword @R0, uword @R1, uword @R2, uword @R3
f_write (uword bufferpointer, uword num_bytes) -> bool
fastmode (ubyte mode) -> bool
internal_f_open_w (str filename, bool open_for_seeks) -> bool
internal_f_tell ()
internal_load_routine (uword filenameptr, uword address_override, bool headerless) -> uword
internal_save_routine (uword filenameptr, uword startaddress, uword savesize, bool headerless) -> bool
lf_end_list ()
@@ -132,6 +133,7 @@ txt {
iso16 ()
iso5 ()
iso_off ()
kata ()
lowercase ()
nl ()
petscii2scr (ubyte petscii_char @A) -> ubyte @A
@@ -169,6 +171,35 @@ txt {
LIBRARY MODULE NAME: syslib
---------------------------
cbm {
&uword CBINV
&uword CINV
&uword IBASIN
&uword IBSOUT
&uword ICHKIN
&uword ICKOUT
&uword ICLALL
&uword ICLOSE
&uword ICLRCH
&uword ICRNCH
&uword IERROR
&uword IEVAL
&uword IGETIN
&uword IGONE
&uword ILOAD
&uword IMAIN
&uword IOPEN
&uword IQPLOP
&uword IRQ_VEC
&uword ISAVE
&uword ISTOP
&uword NMINV
&uword NMI_VEC
&uword RESET_VEC
&ubyte SAREG
&ubyte SPREG
&ubyte SXREG
&ubyte SYREG
&uword USRADD
ACPTR () -> ubyte @A = $ffa5
CHKIN (ubyte logical @X) -> clobbers (A,X) -> bool @Pc = $ffc6
CHKOUT (ubyte logical @X) -> clobbers (A,X) = $ffc9
@@ -217,8 +248,6 @@ cbm {
}
cx16 {
&uword CBINV
&uword CINV
const ubyte EXTAPI16_stack_enter_kernal_stack
const ubyte EXTAPI16_stack_leave_kernal_stack
const ubyte EXTAPI16_stack_pop
@@ -236,34 +265,8 @@ cx16 {
const ubyte EXTAPI_ps2data_fetch
const ubyte EXTAPI_ps2data_raw
const ubyte EXTAPI_ps2kbd_typematic
&uword IBASIN
&uword IBSOUT
&uword ICHKIN
&uword ICKOUT
&uword ICLALL
&uword ICLOSE
&uword ICLRCH
&uword ICRNCH
&uword IERROR
&uword IEVAL
&uword IGETIN
&uword IGONE
&uword ILOAD
&uword IMAIN
&uword IOPEN
&uword IQPLOP
&uword IRQ_VEC
&uword ISAVE
&uword ISTOP
const ubyte EXTAPI_scnsiz
&uword KEYHDL
&uword NMINV
&uword NMI_VEC
&uword RESET_VEC
&ubyte SAREG
&ubyte SPREG
&ubyte SXREG
&ubyte SYREG
&uword USRADD
&uword VERA_ADDR
&ubyte VERA_ADDR_H
&ubyte VERA_ADDR_L
@@ -441,6 +444,7 @@ cx16 {
&word r9s
&byte r9sH
&byte r9sL
&ubyte stavec
&ubyte via1acr
&ubyte via1ddra
&ubyte via1ddrb
@@ -534,7 +538,7 @@ cx16 {
enter_basic (bool cold_or_warm @Pc) -> clobbers (A,X,Y) = $ff47
entropy_get () -> ubyte @A, ubyte @X, ubyte @Y = $fecf
extapi (ubyte callnumber @A) -> clobbers (A,X,Y) = $feab
fetch (ubyte bank @X, ubyte index @Y) -> clobbers (X) -> ubyte @A = $ff74
fetch (ubyte zp_startaddr @A, ubyte bank @X, ubyte index @Y) -> clobbers (X) -> ubyte @A = $ff74
get_chrin_keyhandler () -> ubyte @R0, uword @R1
get_program_args (uword buffer @R0, ubyte buf_size @R1, bool binary @Pc)
get_screen_mode () -> ubyte @A, ubyte @X, ubyte @Y
@@ -559,9 +563,9 @@ cx16 {
monitor () -> clobbers (A,X,Y) = $fecc
mouse_config (byte shape @A, ubyte resX @X, ubyte resY @Y) -> clobbers (A,X,Y) = $ff68
mouse_config2 (byte shape @A) -> clobbers (A,X,Y)
mouse_get (ubyte zpdataptr @X) -> ubyte @A = $ff6b
mouse_get (ubyte zdataptr @X) -> ubyte @A, byte @X = $ff6b
mouse_get_sprite_offset () -> clobbers (A,X,Y) -> word @R0, word @R1
mouse_pos () -> clobbers (X) -> ubyte @A, word @R0, word @R1
mouse_pos () -> ubyte @A, uword @R0, uword @R1, byte @X
mouse_scan () -> clobbers (A,X,Y) = $ff71
mouse_set_pos (uword xpos @R0, uword ypos @R1) -> clobbers (X)
mouse_set_sprite_offset (word xoffset @R0, word yoffset @R1) -> clobbers (A,X,Y)
@@ -598,9 +602,11 @@ cx16 {
reset_system ()
restore_vera_context () -> clobbers (A)
restore_virtual_registers () -> clobbers (A,Y)
rom_version () -> clobbers (Y) -> ubyte @A, bool @Pc
rombank (ubyte bank @A)
save_vera_context () -> clobbers (A)
save_virtual_registers () -> clobbers (A,Y)
scnsiz (ubyte width @X, ubyte heigth @Y) -> clobbers (A,X,Y)
screen_mode (ubyte mode @A, bool getCurrent @Pc) -> ubyte @A, ubyte @X, ubyte @Y, bool @Pc = $ff5f
screen_set_charset (ubyte charset @A, uword charsetptr @XY) -> clobbers (A,X,Y) = $ff62
search_x16edit () -> ubyte
@@ -741,6 +747,7 @@ string {
ltrim (str s)
ltrimmed (str s) -> str
pattern_match (str string @AY, str pattern @R0) -> clobbers (Y) -> bool @A
rfind (uword string @AY, ubyte character @X) -> ubyte @A, bool @Pc
right (uword source @AY, ubyte length @X, uword target @R1) -> clobbers (A,Y)
rstrip (str s)
rtrim (str s)
@@ -856,9 +863,12 @@ floats {
VAL_1 (uword string @XY, ubyte length @A) -> clobbers (A,X,Y) -> float @FAC1 = $fe09
ZEROFC () -> clobbers (A,X,Y) = $fe72
atan (float value) -> float
atan2 (float y, float x) -> float
ceil (float value) -> float
clampf (float value, float minimum, float maximum) -> float
cos (float angle) -> float
cot (float value) -> float
csc (float value) -> float
deg (float angle) -> float
floor (float value) -> float
ln (float value) -> float
@@ -875,6 +885,7 @@ floats {
rnd () -> float
rndseed (float seed)
round (float value) -> float
secant (float value) -> float
sin (float angle) -> float
tan (float value) -> float
tostr (float value @FAC1) -> clobbers (X) -> str @AY
@@ -1077,7 +1088,8 @@ sprites {
data (ubyte spritenum, ubyte bank, uword addr)
flipx (ubyte spritenum, bool flipped)
flipy (ubyte spritenum, bool flipped)
get_data_ptr (ubyte spritenum)
get_data_ptr (ubyte spritenum @A) -> ubyte @R1, uword @R0
get_data_ptr_internal (ubyte spritenum)
getx (ubyte spritenum) -> word
gety (ubyte spritenum) -> word
hide (ubyte spritenum)
@@ -1,6 +1,5 @@
Prog8 compiler v10.3-SNAPSHOT by Irmen de Jong (irmen@razorvine.net)
Prerelease version from git commit 42f4b06a in branch master
Prog8 compiler v10.4 by Irmen de Jong (irmen@razorvine.net)
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
Compiling program import-all-pet32.p8
@@ -281,7 +280,7 @@ diskio {
f_open_w (uword filenameptr) -> bool
f_read (uword bufferpointer, uword num_bytes) -> uword
f_read_all (uword bufferpointer) -> uword
f_readline (uword bufptr @AY) -> clobbers (X) -> ubyte @Y
f_readline (uword bufptr @AY) -> clobbers (X) -> ubyte @Y, ubyte @A
f_write (uword bufferpointer, uword num_bytes) -> bool
lf_end_list ()
lf_next_entry () -> bool
@@ -325,6 +324,7 @@ string {
ltrim (str s)
ltrimmed (str s) -> str
pattern_match (str string @AY, str pattern @R0) -> clobbers (Y) -> bool @A
rfind (uword string @AY, ubyte character @X) -> ubyte @A, bool @Pc
right (uword source @AY, ubyte length @X, uword target @R1) -> clobbers (A,Y)
rstrip (str s)
rtrim (str s)
@@ -1,6 +1,5 @@
Prog8 compiler v10.3-SNAPSHOT by Irmen de Jong (irmen@razorvine.net)
Prerelease version from git commit 42f4b06a in branch master
Prog8 compiler v10.4 by Irmen de Jong (irmen@razorvine.net)
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
Compiling program import-all-virtual.p8
@@ -114,6 +113,7 @@ LIBRARY MODULE NAME: syslib
sys {
const ubyte target
clear_carry ()
clear_irqd ()
disable_caseswitch ()
enable_caseswitch ()
exit (ubyte returnvalue)
@@ -134,6 +134,7 @@ sys {
restore_prog8_internals ()
save_prog8_internals ()
set_carry ()
set_irqd ()
wait (uword jiffies)
waitvsync ()
}
@@ -258,9 +259,12 @@ floats {
const float TWOPI
const float π
atan (float value) -> float
atan2 (float y, float x) -> float
ceil (float value) -> float
clampf (float value, float minimum, float maximum) -> float
cos (float angle) -> float
cot (float value) -> float
csc (float value) -> float
deg (float angle) -> float
floor (float value) -> float
ln (float value) -> float
@@ -277,6 +281,7 @@ floats {
rnd () -> float
rndseed (float seed)
round (float value) -> float
secant (float value) -> float
sin (float angle) -> float
tan (float value) -> float
tostr (float value) -> str
@@ -377,6 +382,7 @@ string {
lstripped (str s) -> str
ltrim (str s)
ltrimmed (str s) -> str
rfind (uword stringptr, ubyte character) -> ubyte
right (str source, ubyte slen, str target)
rstrip (str s)
rtrim (str s)
+16 -15
View File
@@ -10,27 +10,27 @@ Prog8 documentation - |version|
What is Prog8?
--------------
This is a compiled programming language targeting the 8-bit
`6502 <https://en.wikipedia.org/wiki/MOS_Technology_6502>`_ /
`6510 <https://en.wikipedia.org/wiki/MOS_Technology_6510>`_ /
`65c02 <https://en.wikipedia.org/wiki/MOS_Technology_65C02>`_ microprocessors.
This CPU is from the late 1970's and early 1980's and was used in many home computers from that era,
such as the `Commodore 64 <https://en.wikipedia.org/wiki/Commodore_64>`_.
This is a compiled programming language targeting the 8-bit 6502 CPU family.
The language aims to provide many conveniences over raw assembly code (even when using a macro assembler),
while still being low level enough to create high performance programs.
You can compile programs for various machines with this CPU:
* Commander X16 (with 6502 cpu, 65816 cpu specifics are currently not supported)
They targeted CPUs are the `6502 <https://en.wikipedia.org/wiki/MOS_Technology_6502>`_ /
`6510 <https://en.wikipedia.org/wiki/MOS_Technology_6510>`_ /
`65c02 <https://en.wikipedia.org/wiki/MOS_Technology_65C02>`_ microprocessors.
They are from the late 1970's and early 1980's and was used in many home computers from that era,
such as the `Commodore 64 <https://en.wikipedia.org/wiki/Commodore_64>`_.
You can compile programs for various machines:
* Commander X16 (with 65c02 cpu, 65816 cpu specifics are currently not supported)
* Commodore 64
* Commodore 128 (limited support)
* Commodore PET (limited support)
* Atari 800 XL (limited support)
The source code is on github: https://github.com/irmen/prog8.git
Software License
^^^^^^^^^^^^^^^^
Open source Software License
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Full source code is on github: https://github.com/irmen/prog8.git
Prog8 is copyright © Irmen de Jong (irmen@razorvine.net | http://www.razorvine.net).
This is free software, as defined in the GNU GPL 3.0 (https://www.gnu.org/licenses/gpl.html)
@@ -71,7 +71,8 @@ Features
- it is a cross-compiler running on modern machines (Linux, MacOS, Windows, ...)
- the compiled programs run very fast, because compilation to highly efficient native machine code.
- Provides a convenient and fast edit/compile/run cycle by being able to directly launch
- code often is smaller and faster than equivalent C code compiled with CC65 or even LLVM-MOS
- provides a convenient and fast edit/compile/run cycle by being able to directly launch
the compiled program in an emulator and provide debugging information to this emulator.
- the language looks like a mix of Python and C so should be quite easy to learn
- Modular programming, scoping via modules, code blocks, and subroutines. No need for forward declarations.
@@ -84,7 +85,7 @@ Features
- Conditional branches for status flags that map 1:1 to processor branch instructions for optimal efficiency
- ``when`` statement to avoid if-else chains
- ``in`` expression for concise and efficient multi-value/containment test
- Several powerful built-in functions, such as ``lsb``, ``msb``, ``min``, ``max``, ``rol``, ``ror``, ``sort`` and ``reverse``
- Several specialized built-in functions, such as ``lsb``, ``msb``, ``min``, ``max``, ``rol``, ``ror``
- Variable data types include signed and unsigned bytes and words, arrays, strings.
- Various powerful built-in libraries to do I/O, number conversions, graphics and more
- Floating point math is supported on select compiler targets.
+228 -13
View File
@@ -1,10 +1,11 @@
===============
Library modules
===============
=====================================
Library modules and builtin functions
=====================================
The compiler provides several "built-in" library modules with useful subroutine and variables.
The compiler provides several library modules with useful subroutine and variables.
There are also a bunch of builtin functions.
Some of these may be specific for a certain compilation target, or work slightly different,
Some of the libraries may be specific for a certain compilation target, or work slightly different,
but some effort is put into making them available across compilation targets.
This means that as long as your program is only using the subroutines from these
@@ -29,6 +30,193 @@ of these library modules automatically as required.
code publicly available on https://www.codebase64.org/
.. _builtinfunctions:
Built-in Functions
------------------
There's a set of predefined functions in the language. These are fixed and can't be redefined in user code.
You can use them in expressions and the compiler will evaluate them at compile-time if possible.
Math
^^^^
abs (x)
Returns the absolute value of a number (integer or floating point).
min (x, y)
Returns the smallest of x and y. Supported for integer types only, for floats use ``floats.minf()`` instead.
max (x, y)
Returns the largest of x and y. Supported for integer types only, for floats use ``floats.maxf()`` instead.
clamp (value, minimum, maximum)
Returns the value restricted to the given minimum and maximum.
Supported for integer types only, for floats use ``floats.clampf()`` instead.
sgn (x)
Get the sign of the value (integer or floating point).
The result is a byte: -1, 0 or 1 (negative, zero, positive).
sqrt (w)
Returns the square root of the number.
Supports unsigned integer (result is ubyte) and floating point numbers.
To do the reverse - squaring a number - just write ``x*x``.
divmod (dividend, divisor, quotient, remainder)
Performs division only once and returns both quotient and remainder in a single call, where using '/' and '%' separately
would perform the division operation twice.
All values are ubytes or all are uwords.
The last two arguments must be variables to receive the quotient and remainder results, respectively.
Array operations
^^^^^^^^^^^^^^^^
len (x)
Number of values in the array value x, or the number of characters in a string (excluding the 0-byte).
Note: this can be different from the number of *bytes* in memory if the datatype isn't a byte. See sizeof().
Note: lengths of strings and arrays are determined at compile-time! If your program modifies the actual
length of the string during execution, the value of len(s) may no longer be correct!
(use the ``string.length`` routine if you want to dynamically determine the length by counting to the
first 0-byte)
Miscellaneous
^^^^^^^^^^^^^
cmp (x,y)
Compare the integer value x to integer value y. Doesn't return a value or boolean result, only sets the processor's status bits!
You can use a conditional jumps (``if_cc`` etcetera) to act on this.
Normally you should just use a comparison expression (``x < y``)
lsb (x)
Get the least significant byte of the word x. Equivalent to the cast "x as ubyte".
msb (x)
Get the most significant byte of the word x.
mkword (msb, lsb)
Efficiently create a word value from two bytes (the msb and the lsb). Avoids multiplication and shifting.
So mkword($80, $22) results in $8022.
.. note::
The arguments to the mkword() function are in 'natural' order that is first the msb then the lsb.
Don't get confused by how the system actually stores this 16-bit word value in memory (which is
in little-endian format, so lsb first then msb)
peek (address)
same as @(address) - reads the byte at the given address in memory.
peekw (address)
reads the word value at the given address in memory. Word is read as usual little-endian lsb/msb byte order.
peekf (address)
reads the float value at the given address in memory. On CBM machines, this reads 5 bytes.
poke (address, value)
same as @(address)=value - writes the byte value at the given address in memory.
pokew (address, value)
writes the word value at the given address in memory, in usual little-endian lsb/msb byte order.
pokef (address, value)
writes the float value at the given address in memory. On CBM machines, this writes 5 bytes.
pokemon (address, value)
Like poke(), but also returns the previous value in the given address.
Also doesn't have anything to do with a certain video game.
rol (x)
Rotate the bits in x (byte or word) one position to the left.
This uses the CPU's rotate semantics: bit 0 will be set to the current value of the Carry flag,
while the highest bit will become the new Carry flag value.
(essentially, it is a 9-bit or 17-bit rotation)
Modifies in-place, doesn't return a value (so can't be used in an expression).
You can rol a memory location directly by using the direct memory access syntax, so like ``rol(@($5000))``
rol2 (x)
Like ``rol`` but now as 8-bit or 16-bit rotation.
It uses some extra logic to not consider the carry flag as extra rotation bit.
Modifies in-place, doesn't return a value (so can't be used in an expression).
You can rol a memory location directly by using the direct memory access syntax, so like ``rol2(@($5000))``
ror (x)
Rotate the bits in x (byte or word) one position to the right.
This uses the CPU's rotate semantics: the highest bit will be set to the current value of the Carry flag,
while bit 0 will become the new Carry flag value.
(essentially, it is a 9-bit or 17-bit rotation)
Modifies in-place, doesn't return a value (so can't be used in an expression).
You can ror a memory location directly by using the direct memory access syntax, so like ``ror(@($5000))``
ror2 (x)
Like ``ror`` but now as 8-bit or 16-bit rotation.
It uses some extra logic to not consider the carry flag as extra rotation bit.
Modifies in-place, doesn't return a value (so can't be used in an expression).
You can ror a memory location directly by using the direct memory access syntax, so like ``ror2(@($5000))``
setlsb (x, value)
Sets the least significant byte of word variable x to a new value. Leaves the MSB untouched.
setmsb (x, value)
Sets the most significant byte of word variable x to a new value. Leaves the LSB untouched.
sizeof (name) ; sizeof (number)
Number of bytes that the object 'name', or the number 'number' occupies in memory.
This is a constant determined by the data type of
the object. For instance, for a variable of type uword, the sizeof is 2.
For an 10 element array of floats, it is 50 (on the C64, where a float is 5 bytes).
Note: usually you will be interested in the number of elements in an array, use len() for that.
memory (name, size, alignment)
Returns the address of the first location of a statically "reserved" block of memory of the given size in bytes,
with the given name. The block is uninitialized memory, it is *not* set to zero!
If you specify an alignment value >1, it means the block of memory will
be aligned to such a dividable address in memory, for instance an alignment of $100 means the
memory block is aligned on a page boundary, and $2 means word aligned (even addresses).
Requesting the address of such a named memory block again later with
the same name, will result in the same address as before.
When reusing blocks in that way, it is required that the size argument is the same,
otherwise you'll get a compilation error.
This routine can be used to "reserve" parts of the memory where a normal byte array variable would
not suffice; for instance if you need more than 256 consecutive bytes.
The return value is just a simple uword address so it cannot be used as an array in your program.
You can only treat it as a pointer or use it in inline assembly.
call (address) -> uword
Calls a subroutine given by its memory address. You cannot pass arguments directly,
although it is ofcourse possible to do this via the global ``cx16.r0...`` registers for example.
It is assumed the subroutine returns a word value (in AY), if it does not, just add void to the call to ignore the result value.
This function effectively creates an "indirect JSR" if you use it on a ``uword`` pointer variable.
But because it doesn't handle bank switching etcetera by itself,
it is a lot faster than ``callfar``. And it works on other systems than just the Commander X16.
callfar (bank, address, argumentword) -> uword ; NOTE: specific to cx16 target for now
Calls an assembly routine in another bank on the Commander X16 (using its ``JSRFAR`` routine)
Be aware that ram OR rom bank may be changed depending on the address it jumps to!
The argumentword will be loaded into the A+Y registers before calling the routine.
The uword value that the routine returns in the A+Y registers, will be returned.
NOTE: this routine is very inefficient, so don't use it to call often. Set the bank yourself
or even write a custom tailored trampoline routine if you need to. Or use ``call`` if you can.
syscall (callnr), syscall1 (callnr, arg), syscall2 (callnr, arg1, arg2), syscall3 (callnr, arg1, arg2, arg3)
Functions for doing a system call on targets that support this. Currently no actual target
uses this though except, possibly, the experimental code generation target!
The regular 6502 based compiler targets just use a subroutine call to asmsub Kernal routines at
specific memory locations. So these builtin function calls are not useful yet except for
experimentation in new code generation targets.
rsave
Saves all registers including status (or only X) on the stack
Note: the 16 bit 'virtual' registers of the Commander X16 are *not* saved,
but you can use ``cx16.save_virtual_registers()`` for that.
rrestore
Restore all registers including status (or only X) back from the cpu hardware stack
Note: the 16 bit 'virtual' registers of the Commander X16 are *not* restored,
but you can use ``cx16.restore_virtual_registers()`` for that.
Low-fi variable and subroutine definitions in all available library modules
---------------------------------------------------------------------------
@@ -175,6 +363,25 @@ sys (part of syslib)
Low-level function that should normally not be used.
anyall
------
Routines to check if any or all values in an array or memory buffer are not zero.
``any (arrayptr, num_elements)``
true if any of the byte values in the array is not zero, else false.
``all (arrayptr, num_elements)``
true if all of the byte values in the array are not zero, else false.
``anyw (arrayptr, num_elements)``
true if any of the word values in the array is not zero, else false.
Doesn't work on split arrays.
``allw (arrayptr, num_elements)``
true if all of the word values in the array are not zero, else false.
Doesn't work on split arrays.
conv
----
Routines to convert strings to numbers or vice versa.
@@ -228,6 +435,12 @@ and to seek in open files.
Read the `diskio source code <https://github.com/irmen/prog8/tree/master/compiler/res/prog8lib/cx16/diskio.p8>`_
to see what's in there. (Note: slight variations for different compiler targets)
.. note::
For simplicity sake, this library is designed to work on a *single* open file
for reading, and a *single* open file for writing at any time only.
If you need to load or save to more than one file at a time, you'll have
to write your own I/O routines (or supplement the ones found here)
.. note::
If you are using the X16 emulator with HostFS, and are experiencing weird behavior with these
routines, please first try again with an SD-card image instead of HostFs.
@@ -270,14 +483,15 @@ Provides string manipulation routines.
Also, you have to make sure yourself that start and length are within bounds of the strings.
Modifies in-place, doesn't return a value (so can't be used in an expression).
``find (string, char) -> ubyte index + carry bit``
Locates the first position of the given character in the string, returns carry bit set if found
and the index in the string. Or 0+carry bit clear if the character was not found.
``find (string, char) -> ubyte index, bool found``
Locates the first index of the given character in the string, and a boolean (in Carry flag)
to say if it was found at all. If the character is not found, index 255 (and false) is returned.
You can consider this a safer way of checking if a character occurs
in a string than using an `in` containment check - because the find routine
properly stops at the first 0-byte string terminator it encounters.
Simply call this and only act on the carry status with ``if_cc`` for example.
Much like the difference between len(str) and length(str).
in a string than using an `in` containment check - because this find routine
properly stops at the first 0-byte string terminator it encounters in case the string was modified.
``rfind (string, char) -> ubyte index, bool found``
Like ``find``, but now looking from the *right* of the string instead.
``contains (string, char) -> bool``
Just returns true if the character is in the given string, or false if it's not.
@@ -765,7 +979,8 @@ the emulators already support it).
This routine is about 50% faster as a regular byte-by-byte copy.
``transparency``
Enable or disable transparent writes (color 0 will be transparent if enabled).
Set transparent write mode for VeraFX cached writes and also for normal writes to DATA0/DATA.
If enabled, pixels with value 0 do not modify VRAM when written (so they are "transparent")
Read the `verafx source code <https://github.com/irmen/prog8/tree/master/compiler/res/prog8lib/cx16/verafx.p8>`_
to see what's in there.
+10 -227
View File
@@ -337,21 +337,20 @@ An uword variable can be used in limited scenarios as a 'pointer' to a byte in m
dynamic, location. You can use array indexing on a pointer variable to use it as a byte array at
a dynamic location in memory: currently this is equivalent to directly referencing the bytes in
memory at the given index. In contrast to a real array variable, the index value can be the size of a word.
Unlike array variables, you cannot use a negative index to count from the end, because the size of the array is unknown.
Unlike array variables, negative indexing for pointer variables does *not* mean it will be counting from the end, because the size of the buffer is unknown.
Instead, it simply addresses memory that lies *before* the pointer variable.
See also :ref:`pointervars_programming`
**LSB/MSB split word arrays:**
For (u)word arrays, you can make the compiler layout the array in memory as two separate arrays,
one with the LSBs and one with the MSBs of the word values. This is more efficient when storing
and reading words from the array (the index can be used twice).
Add the ``@split`` tag to the variable declaration to do this.
In the assembly code, the array will be generated as two byte arrays namely ``name_lsb`` and ``name_msb``.
Note that the maximum length of a split word array is 256! (regular word arrays are limited to 128 elements).
one with the LSBs and one with the MSBs of the word values. This makes it more efficient to access
values from the array (smaller and faster code). It also doubles the maximum size of the array from 128 words to 256 words!
The ``@split`` tag should be added to the variable declaration to do this.
In the assembly code, the array will then be generated as two byte arrays namely ``name_lsb`` and ``name_msb``.
.. caution::
Not all array operations are supported yet on "split word arrays".
The compiler may give an unpleasant error or crash when you hit such a case in your code.
If this happens simply revert to a regular word array and please report the issue,
If you get an error message, simply revert to a regular word array and please report the issue,
so that more support can be added in the future where it is needed.
@@ -798,224 +797,8 @@ Otherwise the compiler will issue a warning about discarding a result value.
then undefined because the variables will get overwritten.
.. _builtinfunctions:
Built-in Functions
------------------
There's a set of predefined functions in the language. These are fixed and can't be redefined in user code.
You can use them in expressions and the compiler will evaluate them at compile-time if possible.
Math
^^^^
abs (x)
Returns the absolute value of a number (integer or floating point).
min (x, y)
Returns the smallest of x and y. Supported for integer types only, for floats use ``floats.minf()`` instead.
max (x, y)
Returns the largest of x and y. Supported for integer types only, for floats use ``floats.maxf()`` instead.
clamp (value, minimum, maximum)
Returns the value restricted to the given minimum and maximum.
Supported for integer types only, for floats use ``floats.clampf()`` instead.
sgn (x)
Get the sign of the value (integer or floating point).
The result is a byte: -1, 0 or 1 (negative, zero, positive).
sqrt (w)
Returns the square root of the number.
Supports unsigned integer (result is ubyte) and floating point numbers.
To do the reverse - squaring a number - just write ``x*x``.
divmod (dividend, divisor, quotient, remainder)
Performs division only once and returns both quotient and remainder in a single call, where using '/' and '%' separately
would perform the division operation twice.
All values are ubytes or all are uwords.
The last two arguments must be variables to receive the quotient and remainder results, respectively.
Array operations
^^^^^^^^^^^^^^^^
any (x)
true if any of the values in the array value x is 'true' (not zero), else false.
all (x)
true if all of the values in the array value x are 'true' (not zero), else false.
len (x)
Number of values in the array value x, or the number of characters in a string (excluding the 0-byte).
Note: this can be different from the number of *bytes* in memory if the datatype isn't a byte. See sizeof().
Note: lengths of strings and arrays are determined at compile-time! If your program modifies the actual
length of the string during execution, the value of len(s) may no longer be correct!
(use the ``string.length`` routine if you want to dynamically determine the length by counting to the
first 0-byte)
reverse (array)
Reverse the values in the array (in-place).
Can be used after sort() to sort an array in descending order.
sort (array)
Sort the array in ascending order (in-place)
Supported are arrays of bytes or word values.
Sorting a floating-point array is not supported right now, as a general sorting routine for this will
be extremely slow. Either build one yourself or find another solution that doesn't require sorting.
Finally, note that sorting an array with strings in it will not do what you might think;
it considers the array as just an array of integer words and sorts the string *pointers* accordingly.
Sorting strings alphabetically has to be programmed yourself if you need it.
Miscellaneous
^^^^^^^^^^^^^
cmp (x,y)
Compare the integer value x to integer value y. Doesn't return a value or boolean result, only sets the processor's status bits!
You can use a conditional jumps (``if_cc`` etcetera) to act on this.
Normally you should just use a comparison expression (``x < y``)
lsb (x)
Get the least significant byte of the word x. Equivalent to the cast "x as ubyte".
msb (x)
Get the most significant byte of the word x.
mkword (msb, lsb)
Efficiently create a word value from two bytes (the msb and the lsb). Avoids multiplication and shifting.
So mkword($80, $22) results in $8022.
.. note::
The arguments to the mkword() function are in 'natural' order that is first the msb then the lsb.
Don't get confused by how the system actually stores this 16-bit word value in memory (which is
in little-endian format, so lsb first then msb)
peek (address)
same as @(address) - reads the byte at the given address in memory.
peekw (address)
reads the word value at the given address in memory. Word is read as usual little-endian lsb/msb byte order.
peekf (address)
reads the float value at the given address in memory. On CBM machines, this reads 5 bytes.
poke (address, value)
same as @(address)=value - writes the byte value at the given address in memory.
pokew (address, value)
writes the word value at the given address in memory, in usual little-endian lsb/msb byte order.
pokef (address, value)
writes the float value at the given address in memory. On CBM machines, this writes 5 bytes.
pokemon (address, value)
Like poke(), but also returns the previous value in the given address.
Also doesn't have anything to do with a certain video game.
rol (x)
Rotate the bits in x (byte or word) one position to the left.
This uses the CPU's rotate semantics: bit 0 will be set to the current value of the Carry flag,
while the highest bit will become the new Carry flag value.
(essentially, it is a 9-bit or 17-bit rotation)
Modifies in-place, doesn't return a value (so can't be used in an expression).
You can rol a memory location directly by using the direct memory access syntax, so like ``rol(@($5000))``
rol2 (x)
Like ``rol`` but now as 8-bit or 16-bit rotation.
It uses some extra logic to not consider the carry flag as extra rotation bit.
Modifies in-place, doesn't return a value (so can't be used in an expression).
You can rol a memory location directly by using the direct memory access syntax, so like ``rol2(@($5000))``
ror (x)
Rotate the bits in x (byte or word) one position to the right.
This uses the CPU's rotate semantics: the highest bit will be set to the current value of the Carry flag,
while bit 0 will become the new Carry flag value.
(essentially, it is a 9-bit or 17-bit rotation)
Modifies in-place, doesn't return a value (so can't be used in an expression).
You can ror a memory location directly by using the direct memory access syntax, so like ``ror(@($5000))``
ror2 (x)
Like ``ror`` but now as 8-bit or 16-bit rotation.
It uses some extra logic to not consider the carry flag as extra rotation bit.
Modifies in-place, doesn't return a value (so can't be used in an expression).
You can ror a memory location directly by using the direct memory access syntax, so like ``ror2(@($5000))``
setlsb (x, value)
Sets the least significant byte of word variable x to a new value. Leaves the MSB untouched.
setmsb (x, value)
Sets the most significant byte of word variable x to a new value. Leaves the LSB untouched.
sizeof (name) ; sizeof (number)
Number of bytes that the object 'name', or the number 'number' occupies in memory.
This is a constant determined by the data type of
the object. For instance, for a variable of type uword, the sizeof is 2.
For an 10 element array of floats, it is 50 (on the C64, where a float is 5 bytes).
Note: usually you will be interested in the number of elements in an array, use len() for that.
memory (name, size, alignment)
Returns the address of the first location of a statically "reserved" block of memory of the given size in bytes,
with the given name. The block is uninitialized memory, it is *not* set to zero!
If you specify an alignment value >1, it means the block of memory will
be aligned to such a dividable address in memory, for instance an alignment of $100 means the
memory block is aligned on a page boundary, and $2 means word aligned (even addresses).
Requesting the address of such a named memory block again later with
the same name, will result in the same address as before.
When reusing blocks in that way, it is required that the size argument is the same,
otherwise you'll get a compilation error.
This routine can be used to "reserve" parts of the memory where a normal byte array variable would
not suffice; for instance if you need more than 256 consecutive bytes.
The return value is just a simple uword address so it cannot be used as an array in your program.
You can only treat it as a pointer or use it in inline assembly.
call (address) -> uword
Calls a subroutine given by its memory address. You cannot pass arguments directly,
although it is ofcourse possible to do this via the global ``cx16.r0...`` registers for example.
It is assumed the subroutine returns a word value (in AY), if it does not, just add void to the call to ignore the result value.
This function effectively creates an "indirect JSR" if you use it on a ``uword`` pointer variable.
But because it doesn't handle bank switching etcetera by itself,
it is a lot faster than ``callfar``. And it works on other systems than just the Commander X16.
callfar (bank, address, argumentword) -> uword ; NOTE: specific to cx16 target for now
Calls an assembly routine in another bank on the Commander X16 (using its ``JSRFAR`` routine)
Be aware that ram OR rom bank may be changed depending on the address it jumps to!
The argumentword will be loaded into the A+Y registers before calling the routine.
The uword value that the routine returns in the A+Y registers, will be returned.
NOTE: this routine is very inefficient, so don't use it to call often. Set the bank yourself
or even write a custom tailored trampoline routine if you need to. Or use ``call`` if you can.
syscall (callnr), syscall1 (callnr, arg), syscall2 (callnr, arg1, arg2), syscall3 (callnr, arg1, arg2, arg3)
Functions for doing a system call on targets that support this. Currently no actual target
uses this though except, possibly, the experimental code generation target!
The regular 6502 based compiler targets just use a subroutine call to asmsub Kernal routines at
specific memory locations. So these builtin function calls are not useful yet except for
experimentation in new code generation targets.
rsave
Saves all registers including status (or only X) on the stack
Note: the 16 bit 'virtual' registers of the Commander X16 are *not* saved,
but you can use ``cx16.save_virtual_registers()`` for that.
rrestore
Restore all registers including status (or only X) back from the cpu hardware stack
Note: the 16 bit 'virtual' registers of the Commander X16 are *not* restored,
but you can use ``cx16.restore_virtual_registers()`` for that.
Library routines
----------------
There are many routines available in the compiler libraries.
Some are used internally by the compiler as well.
Library routines and builtin functions
--------------------------------------
There are many routines available in the compiler libraries or as builtin functions.
The most important ones can be found in the :doc:`libraries` chapter.
There's too many to list here, just have a look through the source code
of the library modules to see what's there.
(They can be found in the compiler/res directory)
The example programs also use a small set of the library routines, you can study
their source code to see how they might be used.
+14 -12
View File
@@ -344,7 +344,7 @@ Tag Effect
@requirezp force the variable into Zero page. If ZP is full, compilation will fail.
@nozp force the variable to normal system ram, never place it into zeropage.
@shared means the variable is shared with some assembly code and that it cannot be optimized away if not used elsewhere.
@split (only valid on (u)word arrays) Makes the array to be placed in memory as 2 separate byte arrays; one with the LSBs one with the MSBs of the word values. May improve performance.
@split (only valid on (u)word arrays) Makes the array to be placed in memory as 2 separate byte arrays; one with the LSBs one with the MSBs of the word values. Usually improves performance and code size.
========== ======
@@ -390,8 +390,8 @@ type identifier type storage size example var declara
``ubyte[x]`` unsigned byte array x bytes ``ubyte[4] myvar``
``word[x]`` signed word array 2*x bytes ``word[4] myvar``
``uword[x]`` unsigned word array 2*x bytes ``uword[4] myvar``
``float[x]`` floating-point array 5*x bytes ``float[4] myvar``
``bool[x]`` boolean array 5*x bytes ``bool[4] myvar`` note: consider using bit flags in a byte or word instead to save space
``float[x]`` floating-point array 5*x bytes ``float[4] myvar``. The 5 bytes per float is on CBM targets.
``bool[x]`` boolean array x bytes ``bool[4] myvar`` note: consider using bit flags in a byte or word instead to save space
``byte[]`` signed byte array depends on value ``byte[] myvar = [1, 2, 3, 4]``
``ubyte[]`` unsigned byte array depends on value ``ubyte[] myvar = [1, 2, 3, 4]``
``word[]`` signed word array depends on value ``word[] myvar = [1, 2, 3, 4]``
@@ -525,7 +525,8 @@ Array indexing
Strings and arrays are a sequence of values. You can access the individual values by indexing.
Negative index means counted from the end of the array rather than the beginning, where -1 means
the last element in the array, -2 the second-to-last, etc. (Python uses this same scheme)
the last element in the array, -2 the second-to-last, etc. (Python uses this same scheme.
Note that this syntax is only valid for arrays, not for strings! Python does allow the latter, but prog8 does not right now.)
Use brackets to index into an array: ``arrayvar[x]`` ::
array[2] ; the third byte in the array (index is 0-based)
@@ -547,13 +548,14 @@ String length is limited to 255 characters.
Here are examples of the various encodings:
- ``"hello"`` a string translated into the default character encoding (PETSCII on the CBM machines)
- ``petscii:"hello"`` string in CBM PETSCII encoding
- ``sc:"my name is Alice"`` string in CBM screencode encoding
- ``iso:"Ich heiße François"`` string in iso-8859-15 encoding (Latin)
- ``iso5:"Хозяин и Работник"`` string in iso-8859-5 encoding (Cyrillic)
- ``iso16:"zażółć gęślą jaźń"`` string in iso-8859-16 encoding (Eastern Europe)
- ``atascii:"I am Atari!"`` string in "atascii" encoding (Atari 8-bit)
- ``cp437:"≈ IBM Pc ≈ ♂♀♪☺¶"`` string in "cp437" encoding (IBM PC codepage 437)
- ``petscii:"hello"`` string in CBM PETSCII encoding
- ``sc:"my name is Alice"`` string in CBM screencode encoding
- ``iso:"Ich heiße François"`` string in iso-8859-15 encoding (Latin)
- ``iso5:"Хозяин и Работник"`` string in iso-8859-5 encoding (Cyrillic)
- ``iso16:"zażółć gęślą jaźń"`` string in iso-8859-16 encoding (Eastern Europe)
- ``atascii:"I am Atari!"`` string in "atascii" encoding (Atari 8-bit)
- ``cp437:"≈ IBM Pc ≈ ♂♀♪☺¶"`` string in "cp437" encoding (IBM PC codepage 437)
- ``kata:"アノ ニホンジン ワ ガイコクジン。 # が # ガ"`` string in "kata" encoding (Katakana)
There are several escape sequences available to put special characters into your string value:
@@ -974,7 +976,7 @@ where <statements> can be just a single statement or a block again::
if_XX {
<statements>
} else {
<alternative statements>
<alternative statements>
}
The XX corresponds to one of the processor's branching instructions, so the possibilities are:
+2
View File
@@ -224,6 +224,8 @@ Here they are, all available in ``cx16``:
``set_aflow_irq_handler (uword address)``
Sets the audio buffer underrun interrupt handler routine. Also enables AFLOW interrupts.
Note: the handler must fill the Vera's audio fifo buffer by itself with at least 25% worth of data (1 kb)
otherwise the aflow irq keeps triggering.
``disable_irq_handlers ()``
Hand control back to the system default IRQ handler.
+7 -6
View File
@@ -1,9 +1,10 @@
TODO
====
https://github.com/irmen/prog8/issues/136 (string.find register order issue)
...
Improve register load order in subroutine call args assignments:
in certain situations, the "wrong" order of evaluation of function call arguments is done which results
in overwriting registers that already got their value, which requires a lot of stack juggling (especially on plain 6502 cpu!)
Maybe this routine can be made more intelligent. See usesOtherRegistersWhileEvaluating() and argumentsViaRegisters().
Future Things and Ideas
@@ -13,6 +14,7 @@ Compiler:
- Relax newline / bracket in parser so that you can put open and close brackets on the same line or on the next line if you so wish. For example be able to write a true one liner?
- Can we support signed % (remainder) somehow?
- IR: implement missing operators in AssignmentGen (array shifts etc)
- expand the kata encoding to somehow translate normal katana to half-widths? (see comment in KatakanaEncoding)
- instead of copy-pasting inline asmsubs, make them into a 64tass macro and use that instead.
that will allow them to be reused from custom user written assembly code as well.
- Multidimensional arrays and chained indexing, purely as syntactic sugar over regular arrays.
@@ -40,15 +42,14 @@ Compiler:
- ir: the @split arrays are currently also split in _lsb/_msb arrays in the IR, and operations take multiple (byte) instructions that may lead to verbose and slow operation and machine code generation down the line.
maybe another representation is needed once actual codegeneration is done from the IR...?
- ir: getting it in shape for code generation...
- ir: make sure that a 6502 codegen based off the IR, still generates BIT instructions when testing bit 7 or 6 of a byte var.
- [problematic due to using 64tass:] better support for building library programs, where unused .proc are NOT deleted from the assembly.
Perhaps replace all uses of .proc/.pend/.endproc by .block/.bend will fix that with a compiler flag?
But all library code written in asm uses .proc already..... (textual search/replace when writing the actual asm?)
Once new codegen is written that is based on the IR, this point is mostly moot anyway as that will have its own dead code removal on the IR level.
- Zig-like try-based error handling where the V flag could indicate error condition? and/or BRK to jump into monitor on failure? (has to set BRK vector for that) But the V flag is also set on certain normal instructions
- Zig-like defer to execute a statement/anonymousscope at subroutine exit?
- Zig-like defer to execute a statement/anonymousscope when subroutine exits? (problem is, we have jump instructions and inline asm , where we lose track of when exactly the subroutine exits...)
- generate WASM to eventually run prog8 on a browser canvas? Use binaryen toolkit and/or my binaryen kotlin library?
- implement split words arrays all()
- implement split words arrays sort()
Libraries:
Loaded 100 of 130 files, more files were not shown because too many files have changed in this diff. Show more