mirror of
https://github.com/irmen/prog8.git
synced 2025-06-15 02:23:36 +00:00
Compare commits
230 Commits
Author | SHA1 | Date | |
---|---|---|---|
c57ef7725e | |||
3ae07503f2 | |||
9a0341adde | |||
96225efd96 | |||
c3bd904f41 | |||
74257163b1 | |||
7bc75fd220 | |||
a23281afab | |||
9e90dbdde6 | |||
1e8d8e40a2 | |||
583e208c1e | |||
9b91c427a1 | |||
196c5e9c24 | |||
d8f7feb672 | |||
7c889f17b9 | |||
c15a75556d | |||
5275c2e35f | |||
5267e06969 | |||
b62183adcb | |||
5d2dec1803 | |||
4a98dab948 | |||
9f8c70b326 | |||
a65404e63a | |||
05a1ddad05 | |||
4be3d63c0e | |||
de6ce4a46e | |||
7a9e5afb93 | |||
b2876b0a03 | |||
b66f66fe6a | |||
30f04962d4 | |||
0feeb88024 | |||
b799f2e05b | |||
56d21de001 | |||
7b54aa0c7d | |||
6e11b8ada1 | |||
98d25fc4e9 | |||
79405f47f6 | |||
1c7c4fc3b0 | |||
97e84d0977 | |||
9906b58818 | |||
371f084884 | |||
1c10839c14 | |||
c55fdd9834 | |||
67b0890a6e | |||
4da4f96669 | |||
60a64c2558 | |||
d4153da8b9 | |||
fc33ab8905 | |||
a123c64f59 | |||
a090fe3834 | |||
8fa84de28e | |||
3e3da38de1 | |||
a3be8ccc87 | |||
cdfef30c22 | |||
cabf1e82e8 | |||
608dc5e284 | |||
836d40072f | |||
431401d90e | |||
6da83e2bd7 | |||
7bccfc0006 | |||
e051e09c1d | |||
1462c57d0c | |||
77c2b2b326 | |||
3cf9b9d9a5 | |||
629117e594 | |||
08f87c321f | |||
1ff13723fe | |||
510bda1b28 | |||
890327b381 | |||
b21f7411dd | |||
5df623bd2e | |||
1e9d249f71 | |||
a7b5949e6a | |||
02010170ce | |||
35998142fe | |||
75ea453bf4 | |||
33061aaa0d | |||
e342311bef | |||
3d743a1ba1 | |||
abca618008 | |||
0d2c3901a3 | |||
d901a1531f | |||
d8d56b195f | |||
a52699717c | |||
98315de723 | |||
68d2f7d4c0 | |||
c812b5ee09 | |||
dcf487bdc1 | |||
547b1d3720 | |||
84f75f4156 | |||
ff69da3fa2 | |||
edffe92a24 | |||
b6fe40ada4 | |||
837804b231 | |||
81deed143b | |||
900cdd3fa1 | |||
0018dc6ce7 | |||
c92f914081 | |||
0498444ef2 | |||
ce3c34e458 | |||
20401b99d8 | |||
397f98513b | |||
e545ea9504 | |||
b867d8f731 | |||
9a68864b67 | |||
72d7178762 | |||
fbcd9a0c1d | |||
c3144a20db | |||
5b56e0462d | |||
b7fffbb6df | |||
1f346230e3 | |||
a2860a7c8c | |||
df997e5d3b | |||
a67a82c921 | |||
ea0fe8d3d2 | |||
2560042ac7 | |||
3d1d0696b9 | |||
83f893f50b | |||
9ecf95b075 | |||
7748c261da | |||
a2db44f80c | |||
b438d8aec0 | |||
4ac169b210 | |||
56dc6d7f1e | |||
45b8762188 | |||
cafab98d10 | |||
9256f910f0 | |||
32068a832a | |||
47c2c0376a | |||
f0dadc4a43 | |||
960b60cd2d | |||
d6abd72e55 | |||
0a568f2530 | |||
c52aa648c0 | |||
3d23b39f4c | |||
f3a4048ebf | |||
1b07637cc4 | |||
68b75fd558 | |||
7c5ec1853d | |||
e8f4686430 | |||
02348924d0 | |||
69dcb4dbda | |||
c838821615 | |||
8b4ac7801f | |||
64a411628d | |||
e8e25c6fd6 | |||
62485b6851 | |||
54025d2bf5 | |||
f5ebf79e71 | |||
66d5490702 | |||
42fe052f9f | |||
58d9c46a9b | |||
e4648e2138 | |||
110e047681 | |||
17d403d812 | |||
0a53bd4956 | |||
e52d05c7db | |||
b00db4f8a2 | |||
0c2f30fd45 | |||
e08871c637 | |||
ff715881bc | |||
0e2e5ffa52 | |||
8095c4c155 | |||
e86246a985 | |||
625aaa02eb | |||
787e35c9f3 | |||
8887e6af91 | |||
dde4c751da | |||
3c39baf1d6 | |||
b292124f3c | |||
c0035ba1a2 | |||
2491509c6a | |||
107935ed31 | |||
31491c62c5 | |||
eacf8b896a | |||
7936fc5bd8 | |||
adfaddbcf4 | |||
74db5c6be7 | |||
f9399bcce7 | |||
87600b23db | |||
cedfb17b18 | |||
fa4c83df6b | |||
42c8720e8b | |||
b334d89715 | |||
4f5d36a84d | |||
8f379e2262 | |||
fa11a6e18b | |||
52bedce8f4 | |||
4c82af36e6 | |||
dafa0d9138 | |||
2e0450d7ed | |||
6af3209d4d | |||
5d362047e2 | |||
f48d6ca9f8 | |||
964e8e0a17 | |||
1f60a2d8b9 | |||
5fd83f2757 | |||
c80df4140b | |||
53e1729e2f | |||
ab2d1122a9 | |||
5190594c8a | |||
c858ceeb58 | |||
f0f52b9166 | |||
00c6f74481 | |||
2177ba0ed2 | |||
3483515346 | |||
75a06d2a40 | |||
53ac11983b | |||
69f4a4d4f8 | |||
222bcb808f | |||
686483f51a | |||
8df3da11e3 | |||
84dafda0e4 | |||
b909facfe5 | |||
7780d94de1 | |||
f2c440e466 | |||
4937e004b5 | |||
4cb383dccb | |||
c8a4b6f23c | |||
857724c7e6 | |||
a9b0400d13 | |||
2d1e5bbc7e | |||
60627ce756 | |||
7961a09d16 | |||
613efcacc7 | |||
7e8db16e18 | |||
1fbbed7e23 | |||
984272beb4 | |||
b9ce94bb68 | |||
f4c4ee78d9 |
10
.idea/codeStyles/Project.xml
generated
Normal file
10
.idea/codeStyles/Project.xml
generated
Normal file
@ -0,0 +1,10 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<JetCodeStyleSettings>
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
1
.idea/inspectionProfiles/Project_Default.xml
generated
1
.idea/inspectionProfiles/Project_Default.xml
generated
@ -7,6 +7,7 @@
|
||||
<language isEnabled="false" name="Groovy" />
|
||||
</Languages>
|
||||
</inspection_tool>
|
||||
<inspection_tool class="IncompleteDestructuring" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="PyInterpreterInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SpellCheckingInspection" enabled="true" level="TYPO" enabled_by_default="true">
|
||||
<option name="processCode" value="false" />
|
||||
|
6
.idea/kotlinc.xml
generated
Normal file
6
.idea/kotlinc.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Kotlin2JvmCompilerArguments">
|
||||
<option name="jvmTarget" value="11" />
|
||||
</component>
|
||||
</project>
|
10
.idea/libraries/hamcrest.xml
generated
10
.idea/libraries/hamcrest.xml
generated
@ -1,10 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="hamcrest" type="repository">
|
||||
<properties maven-id="org.hamcrest:hamcrest:2.2" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/hamcrest/hamcrest/2.2/hamcrest-2.2.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
22
.idea/libraries/io_kotest_assertions_core_jvm.xml
generated
Normal file
22
.idea/libraries/io_kotest_assertions_core_jvm.xml
generated
Normal file
@ -0,0 +1,22 @@
|
||||
<component name="libraryTable">
|
||||
<library name="io.kotest.assertions.core.jvm" type="repository">
|
||||
<properties maven-id="io.kotest:kotest-assertions-core-jvm:4.6.3" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/4.6.3/kotest-assertions-core-jvm-4.6.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.5.0/kotlin-stdlib-jdk8-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.5.0/kotlin-stdlib-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.5.0/kotlin-stdlib-jdk7-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/4.6.3/kotest-assertions-shared-jvm-4.6.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.9/java-diff-utils-4.9.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.5.0/kotlinx-coroutines-jdk8-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.5.0/kotlin-reflect-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.5.0/kotlinx-coroutines-core-jvm-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.5.0/kotlin-stdlib-common-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/4.6.3/kotest-common-jvm-4.6.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/4.6.3/kotest-assertions-api-jvm-4.6.3.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
24
.idea/libraries/io_kotest_property_jvm.xml
generated
Normal file
24
.idea/libraries/io_kotest_property_jvm.xml
generated
Normal file
@ -0,0 +1,24 @@
|
||||
<component name="libraryTable">
|
||||
<library name="io.kotest.property.jvm" type="repository">
|
||||
<properties maven-id="io.kotest:kotest-property-jvm:4.6.3" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-property-jvm/4.6.3/kotest-property-jvm-4.6.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.5.0/kotlin-stdlib-jdk8-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.5.0/kotlin-stdlib-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.5.0/kotlin-stdlib-jdk7-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/4.6.3/kotest-common-jvm-4.6.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/4.6.3/kotest-assertions-shared-jvm-4.6.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/4.6.3/kotest-assertions-api-jvm-4.6.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.5.0/kotlinx-coroutines-jdk8-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.9/java-diff-utils-4.9.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/mifmif/generex/1.0.2/generex-1.0.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/dk/brics/automaton/automaton/1.11-8/automaton-1.11-8.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.5.0/kotlin-reflect-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.5.0/kotlinx-coroutines-core-jvm-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.5.0/kotlin-stdlib-common-1.5.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
56
.idea/libraries/io_kotest_runner_junit5_jvm.xml
generated
Normal file
56
.idea/libraries/io_kotest_runner_junit5_jvm.xml
generated
Normal file
@ -0,0 +1,56 @@
|
||||
<component name="libraryTable">
|
||||
<library name="io.kotest.runner.junit5.jvm" type="repository">
|
||||
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:4.6.3" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/4.6.3/kotest-runner-junit5-jvm-4.6.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/4.6.3/kotest-framework-api-jvm-4.6.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/4.6.3/kotest-assertions-shared-jvm-4.6.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.9/java-diff-utils-4.9.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/4.6.3/kotest-common-jvm-4.6.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/4.6.3/kotest-framework-engine-jvm-4.6.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.105/classgraph-4.8.105.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/mordant/1.2.1/mordant-1.2.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/colormath/1.2.0/colormath-1.2.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-script-util/1.5.0/kotlin-script-util-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/intellij/deps/trove4j/1.0.20181211/trove4j-1.0.20181211.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-daemon-client/1.5.0/kotlin-daemon-client-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core/1.3.8/kotlinx-coroutines-core-1.3.8.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-scripting-jvm/1.5.0/kotlin-scripting-jvm-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-scripting-common/1.5.0/kotlin-scripting-common-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-discovery-jvm/4.6.3/kotest-framework-discovery-jvm-4.6.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/4.6.3/kotest-assertions-core-jvm-4.6.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.5.0/kotlinx-coroutines-jdk8-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/4.6.3/kotest-assertions-api-jvm-4.6.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/4.6.3/kotest-extensions-jvm-4.6.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/commons-io/commons-io/2.6/commons-io-2.6.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk/1.9.3/mockk-1.9.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-common/1.9.3/mockk-common-1.9.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-dsl/1.9.3/mockk-dsl-1.9.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-dsl-jvm/1.9.3/mockk-dsl-jvm-1.9.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-agent-jvm/1.9.3/mockk-agent-jvm-1.9.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-agent-api/1.9.3/mockk-agent-api-1.9.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-agent-common/1.9.3/mockk-agent-common-1.9.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/objenesis/objenesis/3.0.1/objenesis-3.0.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.9.10/byte-buddy-1.9.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy-agent/1.9.10/byte-buddy-agent-1.9.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/4.6.3/kotest-framework-concurrency-jvm-4.6.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.5.0/kotlinx-coroutines-core-jvm-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.5.0/kotlin-stdlib-common-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.6.2/junit-platform-engine-1.6.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.0/apiguardian-api-1.1.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.6.2/junit-platform-commons-1.6.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-suite-api/1.6.2/junit-platform-suite-api-1.6.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-launcher/1.6.2/junit-platform-launcher-1.6.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.6.2/junit-jupiter-api-5.6.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.5.0/kotlin-stdlib-jdk8-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.5.0/kotlin-stdlib-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.5.0/kotlin-stdlib-jdk7-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-script-runtime/1.5.0/kotlin-script-runtime-1.5.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.5.0/kotlin-reflect-1.5.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
17
.idea/libraries/junit_jupiter.xml
generated
17
.idea/libraries/junit_jupiter.xml
generated
@ -1,17 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="junit.jupiter" type="repository">
|
||||
<properties maven-id="org.junit.jupiter:junit-jupiter:5.7.2" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter/5.7.2/junit-jupiter-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.7.2/junit-jupiter-api-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.0/apiguardian-api-1.1.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.7.2/junit-platform-commons-1.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-params/5.7.2/junit-jupiter-params-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-engine/5.7.2/junit-jupiter-engine-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.7.2/junit-platform-engine-1.7.2.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
7
.idea/misc.xml
generated
7
.idea/misc.xml
generated
@ -16,7 +16,10 @@
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="Kotlin SDK" project-jdk-type="KotlinSDK">
|
||||
<component name="FrameworkDetectionExcludesConfiguration">
|
||||
<type id="Python" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="11" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
</project>
|
10
README.md
10
README.md
@ -37,7 +37,8 @@ What does Prog8 provide?
|
||||
- small program boilerplate/compilersupport overhead
|
||||
- programs can be run multiple times without reloading because of automatic variable (re)initializations.
|
||||
- conditional branches
|
||||
- 'when' statement to provide a concise jump table alternative to if/elseif chains
|
||||
- ``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``
|
||||
- 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
|
||||
@ -52,11 +53,12 @@ What does Prog8 provide?
|
||||
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them
|
||||
- source code labels automatically loaded in Vice emulator so it can show them in disassembly
|
||||
|
||||
*Two supported compiler targets* (contributions to improve these or to add support for other machines are welcome!):
|
||||
*Multiple supported compiler targets* (contributions to improve these or to add support for other machines are welcome!):
|
||||
|
||||
- "c64": Commodore-64 (6510 CPU = almost a 6502)
|
||||
- "c64": Commodore-64 (6502 like CPU)
|
||||
- "c128": Commodore-128 (6502 like CPU - the Z80 cpu mode is not supported)
|
||||
- "cx16": [CommanderX16](https://www.commanderx16.com) (65c02 CPU)
|
||||
- If you only use standard kernal and prog8 library routines, it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
|
||||
- If you only use standard kernal and prog8 library routines, it is possible to compile the *exact same program* for different machines (just change the compiler target flag)
|
||||
|
||||
|
||||
|
||||
|
48
c128floatproblem.asm
Normal file
48
c128floatproblem.asm
Normal file
@ -0,0 +1,48 @@
|
||||
|
||||
; this program adds 2 floats 1.2345 + 4.4444 and prints the result 5.67879
|
||||
; it works fine on C64 and CX16 but fails to add the right number on C128
|
||||
; I think it has something to do with the banking on the C128
|
||||
; because several rom fp routines mention "value in bank 1"
|
||||
; but I don't know what this means and why the program as given doesn't work
|
||||
|
||||
.cpu '6502'
|
||||
|
||||
* = $1300 ; sys 4864
|
||||
|
||||
P8ZP_SCRATCH_W1 = 251 ; free zp word
|
||||
CHROUT = $ffd2
|
||||
|
||||
; c128:
|
||||
MOVFM = $af63
|
||||
FADD = $af18
|
||||
FOUT = $af06
|
||||
|
||||
;cx16:
|
||||
;MOVFM = $fe42
|
||||
;FADD = $fe12
|
||||
;FOUT = $fe7b
|
||||
|
||||
;c64:
|
||||
;MOVFM = $bba2
|
||||
;FADD = $b867
|
||||
;FOUT = $bddd
|
||||
|
||||
lda #<f1
|
||||
ldy #>f1
|
||||
jsr MOVFM ; load FAC1
|
||||
lda #<f2
|
||||
ldy #>f2
|
||||
jsr FADD ; add mem float to FAC1 DOESN'T WORK ON C128!?
|
||||
jsr FOUT ; convert to string
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
_printloop lda (P8ZP_SCRATCH_W1),y
|
||||
beq _done
|
||||
jsr CHROUT
|
||||
iny
|
||||
bne _printloop
|
||||
_done jmp _done
|
||||
|
||||
f1 .byte $81, $1e, $04, $18, $93 ; float 1.2345
|
||||
f2 .byte $83, $0e, $38, $86, $59 ; float 4.4444
|
@ -11,6 +11,18 @@ java {
|
||||
}
|
||||
}
|
||||
|
||||
compileKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = javaVersion
|
||||
}
|
||||
}
|
||||
|
||||
compileTestKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = javaVersion
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':compilerInterfaces')
|
||||
implementation project(':compilerAst')
|
||||
@ -18,11 +30,6 @@ dependencies {
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.12"
|
||||
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2'
|
||||
testImplementation 'org.hamcrest:hamcrest:2.2'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2'
|
||||
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
@ -34,23 +41,6 @@ sourceSets {
|
||||
srcDirs = ["${project.projectDir}/res"]
|
||||
}
|
||||
}
|
||||
test {
|
||||
java {
|
||||
srcDirs = ["${project.projectDir}/test"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
test {
|
||||
// Enable JUnit 5 (Gradle 4.6+).
|
||||
useJUnitPlatform()
|
||||
|
||||
// Always run tests, even when nothing changed.
|
||||
dependsOn 'cleanTest'
|
||||
|
||||
// Show test results.
|
||||
testLogging {
|
||||
events "skipped", "failed"
|
||||
}
|
||||
}
|
||||
// note: there are no unit tests in this module!
|
||||
|
@ -4,16 +4,13 @@
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
<orderEntry type="module" module-name="compilerInterfaces" />
|
||||
<orderEntry type="module" module-name="compilerAst" />
|
||||
<orderEntry type="library" scope="TEST" name="hamcrest" level="project" />
|
||||
<orderEntry type="library" name="junit.jupiter" level="project" />
|
||||
</component>
|
||||
</module>
|
@ -1,2 +1,2 @@
|
||||
Unittests for things in this module are located in the Compiler module instead,
|
||||
for convenience sake - and to not spread the test cases around too much.
|
||||
for convenience sake, and to not spread the test cases around too much.
|
@ -1,3 +1,3 @@
|
||||
package prog8.compiler.target
|
||||
package prog8.codegen.target
|
||||
|
||||
class AssemblyError(msg: String) : RuntimeException(msg)
|
41
codeGeneration/src/prog8/codegen/target/C128Target.kt
Normal file
41
codeGeneration/src/prog8/codegen/target/C128Target.kt
Normal file
@ -0,0 +1,41 @@
|
||||
package prog8.codegen.target
|
||||
|
||||
import com.github.michaelbull.result.fold
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.statements.RegisterOrStatusflag
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.codegen.target.c128.C128MachineDefinition
|
||||
import prog8.codegen.target.cbm.Petscii
|
||||
import prog8.codegen.target.cpu6502.codegen.asmsub6502ArgsEvalOrder
|
||||
import prog8.codegen.target.cpu6502.codegen.asmsub6502ArgsHaveRegisterClobberRisk
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
|
||||
|
||||
object C128Target: ICompilationTarget {
|
||||
override val name = "c128"
|
||||
override val machine = C128MachineDefinition()
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
|
||||
val coded = if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||
return coded.fold(
|
||||
failure = { throw it },
|
||||
success = { it }
|
||||
)
|
||||
}
|
||||
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean) =
|
||||
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||
|
||||
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> =
|
||||
asmsub6502ArgsEvalOrder(sub)
|
||||
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>, paramRegisters: List<RegisterOrStatusflag>) =
|
||||
asmsub6502ArgsHaveRegisterClobberRisk(args, paramRegisters)
|
||||
|
||||
override fun memorySize(dt: DataType): Int {
|
||||
return when(dt) {
|
||||
in ByteDatatypes -> 1
|
||||
in WordDatatypes, in PassByReferenceDatatypes -> 2
|
||||
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
|
||||
else -> Int.MIN_VALUE
|
||||
}
|
||||
}
|
||||
}
|
41
codeGeneration/src/prog8/codegen/target/C64Target.kt
Normal file
41
codeGeneration/src/prog8/codegen/target/C64Target.kt
Normal file
@ -0,0 +1,41 @@
|
||||
package prog8.codegen.target
|
||||
|
||||
import com.github.michaelbull.result.fold
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.statements.RegisterOrStatusflag
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.codegen.target.c64.C64MachineDefinition
|
||||
import prog8.codegen.target.cbm.Petscii
|
||||
import prog8.codegen.target.cpu6502.codegen.asmsub6502ArgsEvalOrder
|
||||
import prog8.codegen.target.cpu6502.codegen.asmsub6502ArgsHaveRegisterClobberRisk
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
|
||||
|
||||
object C64Target: ICompilationTarget {
|
||||
override val name = "c64"
|
||||
override val machine = C64MachineDefinition()
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
|
||||
val coded = if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||
return coded.fold(
|
||||
failure = { throw it },
|
||||
success = { it }
|
||||
)
|
||||
}
|
||||
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean) =
|
||||
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||
|
||||
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> =
|
||||
asmsub6502ArgsEvalOrder(sub)
|
||||
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>, paramRegisters: List<RegisterOrStatusflag>) =
|
||||
asmsub6502ArgsHaveRegisterClobberRisk(args, paramRegisters)
|
||||
|
||||
override fun memorySize(dt: DataType): Int {
|
||||
return when(dt) {
|
||||
in ByteDatatypes -> 1
|
||||
in WordDatatypes, in PassByReferenceDatatypes -> 2
|
||||
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
|
||||
else -> Int.MIN_VALUE
|
||||
}
|
||||
}
|
||||
}
|
45
codeGeneration/src/prog8/codegen/target/Cx16Target.kt
Normal file
45
codeGeneration/src/prog8/codegen/target/Cx16Target.kt
Normal file
@ -0,0 +1,45 @@
|
||||
package prog8.codegen.target
|
||||
|
||||
import com.github.michaelbull.result.fold
|
||||
import prog8.ast.base.ByteDatatypes
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.PassByReferenceDatatypes
|
||||
import prog8.ast.base.WordDatatypes
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.statements.RegisterOrStatusflag
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.codegen.target.cbm.Petscii
|
||||
import prog8.codegen.target.cpu6502.codegen.asmsub6502ArgsEvalOrder
|
||||
import prog8.codegen.target.cpu6502.codegen.asmsub6502ArgsHaveRegisterClobberRisk
|
||||
import prog8.codegen.target.cx16.CX16MachineDefinition
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
|
||||
|
||||
object Cx16Target: ICompilationTarget {
|
||||
override val name = "cx16"
|
||||
override val machine = CX16MachineDefinition()
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
|
||||
val coded = if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||
return coded.fold(
|
||||
failure = { throw it },
|
||||
success = { it }
|
||||
)
|
||||
}
|
||||
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean) =
|
||||
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||
|
||||
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> =
|
||||
asmsub6502ArgsEvalOrder(sub)
|
||||
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>, paramRegisters: List<RegisterOrStatusflag>) =
|
||||
asmsub6502ArgsHaveRegisterClobberRisk(args, paramRegisters)
|
||||
|
||||
override fun memorySize(dt: DataType): Int {
|
||||
return when(dt) {
|
||||
in ByteDatatypes -> 1
|
||||
in WordDatatypes, in PassByReferenceDatatypes -> 2
|
||||
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
|
||||
else -> Int.MIN_VALUE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,68 @@
|
||||
package prog8.codegen.target.c128
|
||||
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.codegen.target.c64.normal6502instructions
|
||||
import prog8.codegen.target.cbm.Mflpt5
|
||||
import prog8.codegen.target.cbm.viceMonListName
|
||||
import prog8.compilerinterface.*
|
||||
import java.io.IOException
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
class C128MachineDefinition: IMachineDefinition {
|
||||
|
||||
override val cpu = CpuType.CPU6502
|
||||
|
||||
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
|
||||
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
||||
override val BASIC_LOAD_ADDRESS = 0x1c01u
|
||||
override val RAW_LOAD_ADDRESS = 0x1300u
|
||||
|
||||
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
||||
override val ESTACK_LO = 0x1a00u // $1a00-$1aff inclusive
|
||||
override val ESTACK_HI = 0x1b00u // $1b00-$1bff inclusive
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
|
||||
override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
|
||||
|
||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||
return if (compilerOptions.launcher == LauncherType.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).")
|
||||
return
|
||||
}
|
||||
|
||||
for(emulator in listOf("x128")) {
|
||||
println("\nStarting C-128 emulator $emulator...")
|
||||
val viceMonlist = viceMonListName(programNameWithPath.toString())
|
||||
val cmdline = listOf(emulator, "-silent", "-moncommands", viceMonlist,
|
||||
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg")
|
||||
val processb = ProcessBuilder(cmdline).inheritIO()
|
||||
val process: Process
|
||||
try {
|
||||
process=processb.start()
|
||||
} catch(x: IOException) {
|
||||
continue // try the next emulator executable
|
||||
}
|
||||
process.waitFor()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu
|
||||
override fun getPreallocatedZeropageVars(): Map<String, Pair<UInt, DataType>> = emptyMap()
|
||||
|
||||
override fun initializeZeropage(compilerOptions: CompilationOptions) {
|
||||
zeropage = C128Zeropage(compilerOptions)
|
||||
}
|
||||
|
||||
override val opcodeNames = normal6502instructions
|
||||
}
|
42
codeGeneration/src/prog8/codegen/target/c128/C128Zeropage.kt
Normal file
42
codeGeneration/src/prog8/codegen/target/c128/C128Zeropage.kt
Normal file
@ -0,0 +1,42 @@
|
||||
package prog8.codegen.target.c128
|
||||
|
||||
import prog8.compilerinterface.CompilationOptions
|
||||
import prog8.compilerinterface.InternalCompilerException
|
||||
import prog8.compilerinterface.Zeropage
|
||||
import prog8.compilerinterface.ZeropageType
|
||||
|
||||
class C128Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
|
||||
override val SCRATCH_B1 = 0x9bu // temp storage for a single byte
|
||||
override val SCRATCH_REG = 0x9cu // temp storage for a register, must be B1+1
|
||||
override val SCRATCH_W1 = 0xfbu // temp storage 1 for a word $fb+$fc
|
||||
override val SCRATCH_W2 = 0xfdu // temp storage 2 for a word $fd+$fe
|
||||
|
||||
|
||||
init {
|
||||
if (options.floats && options.zeropage !in arrayOf(
|
||||
ZeropageType.FLOATSAFE,
|
||||
ZeropageType.BASICSAFE,
|
||||
ZeropageType.DONTUSE
|
||||
))
|
||||
throw InternalCompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
|
||||
|
||||
when (options.zeropage) {
|
||||
ZeropageType.FULL -> {
|
||||
// TODO all c128 usable zero page locations, except the ones used by the system's IRQ routine
|
||||
free.addAll(0x0au..0xffu) // TODO c128 what about $02-$09?
|
||||
// TODO c128 free.removeAll(setOf(0xa0u, 0xa1u, 0xa2u, 0x91u, 0xc0u, 0xc5u, 0xcbu, 0xf5u, 0xf6u)) // these are updated by IRQ
|
||||
}
|
||||
ZeropageType.KERNALSAFE,
|
||||
ZeropageType.FLOATSAFE,
|
||||
ZeropageType.BASICSAFE -> {
|
||||
free.clear() // TODO c128 usable zero page addresses
|
||||
}
|
||||
ZeropageType.DONTUSE -> {
|
||||
free.clear() // don't use zeropage at all
|
||||
}
|
||||
}
|
||||
|
||||
removeReservedFromFreePool()
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
package prog8.codegen.target.c64
|
||||
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.codegen.target.cbm.Mflpt5
|
||||
import prog8.codegen.target.cbm.viceMonListName
|
||||
import prog8.compilerinterface.*
|
||||
import java.io.IOException
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
class C64MachineDefinition: IMachineDefinition {
|
||||
|
||||
override val cpu = CpuType.CPU6502
|
||||
|
||||
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
|
||||
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
||||
override val BASIC_LOAD_ADDRESS = 0x0801u
|
||||
override val RAW_LOAD_ADDRESS = 0xc000u
|
||||
|
||||
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
||||
override val ESTACK_LO = 0xce00u // $ce00-$ceff inclusive
|
||||
override val ESTACK_HI = 0xcf00u // $ce00-$ceff inclusive
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
|
||||
override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
|
||||
|
||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||
return if (compilerOptions.launcher == LauncherType.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).")
|
||||
return
|
||||
}
|
||||
|
||||
for(emulator in listOf("x64sc", "x64")) {
|
||||
println("\nStarting C-64 emulator $emulator...")
|
||||
val viceMonlist = viceMonListName(programNameWithPath.toString())
|
||||
val cmdline = listOf(emulator, "-silent", "-moncommands", viceMonlist,
|
||||
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg")
|
||||
val processb = ProcessBuilder(cmdline).inheritIO()
|
||||
val process: Process
|
||||
try {
|
||||
process=processb.start()
|
||||
} catch(x: IOException) {
|
||||
continue // try the next emulator executable
|
||||
}
|
||||
process.waitFor()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu
|
||||
override fun getPreallocatedZeropageVars(): Map<String, Pair<UInt, DataType>> = emptyMap()
|
||||
|
||||
override fun initializeZeropage(compilerOptions: CompilationOptions) {
|
||||
zeropage = C64Zeropage(compilerOptions)
|
||||
}
|
||||
|
||||
override val opcodeNames = normal6502instructions
|
||||
}
|
||||
|
||||
|
||||
// 6502 opcodes (including aliases and illegal opcodes), these cannot be used as variable or label names
|
||||
internal val normal6502instructions = setOf(
|
||||
"adc", "ahx", "alr", "anc", "and", "ane", "arr", "asl", "asr", "axs", "bcc", "bcs",
|
||||
"beq", "bge", "bit", "blt", "bmi", "bne", "bpl", "brk", "bvc", "bvs", "clc",
|
||||
"cld", "cli", "clv", "cmp", "cpx", "cpy", "dcm", "dcp", "dec", "dex", "dey",
|
||||
"eor", "gcc", "gcs", "geq", "gge", "glt", "gmi", "gne", "gpl", "gvc", "gvs",
|
||||
"inc", "ins", "inx", "iny", "isb", "isc", "jam", "jmp", "jsr", "lae", "las",
|
||||
"lax", "lda", "lds", "ldx", "ldy", "lsr", "lxa", "nop", "ora", "pha", "php",
|
||||
"pla", "plp", "rla", "rol", "ror", "rra", "rti", "rts", "sax", "sbc", "sbx",
|
||||
"sec", "sed", "sei", "sha", "shl", "shr", "shs", "shx", "shy", "slo", "sre",
|
||||
"sta", "stx", "sty", "tas", "tax", "tay", "tsx", "txa", "txs", "tya", "xaa")
|
73
codeGeneration/src/prog8/codegen/target/c64/C64Zeropage.kt
Normal file
73
codeGeneration/src/prog8/codegen/target/c64/C64Zeropage.kt
Normal file
@ -0,0 +1,73 @@
|
||||
package prog8.codegen.target.c64
|
||||
|
||||
import prog8.compilerinterface.CompilationOptions
|
||||
import prog8.compilerinterface.InternalCompilerException
|
||||
import prog8.compilerinterface.Zeropage
|
||||
import prog8.compilerinterface.ZeropageType
|
||||
|
||||
class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
|
||||
override val SCRATCH_B1 = 0x02u // temp storage for a single byte
|
||||
override val SCRATCH_REG = 0x03u // temp storage for a register, must be B1+1
|
||||
override val SCRATCH_W1 = 0xfbu // temp storage 1 for a word $fb+$fc
|
||||
override val SCRATCH_W2 = 0xfdu // temp storage 2 for a word $fd+$fe
|
||||
|
||||
|
||||
init {
|
||||
if (options.floats && options.zeropage !in arrayOf(
|
||||
ZeropageType.FLOATSAFE,
|
||||
ZeropageType.BASICSAFE,
|
||||
ZeropageType.DONTUSE
|
||||
))
|
||||
throw InternalCompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
|
||||
|
||||
if (options.zeropage == ZeropageType.FULL) {
|
||||
free.addAll(0x02u..0xffu)
|
||||
free.removeAll(setOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1+1u, SCRATCH_W2, SCRATCH_W2+1u))
|
||||
free.removeAll(setOf(0xa0u, 0xa1u, 0xa2u, 0x91u, 0xc0u, 0xc5u, 0xcbu, 0xf5u, 0xf6u)) // these are updated by IRQ
|
||||
} else {
|
||||
if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
|
||||
free.addAll(listOf(
|
||||
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
|
||||
0x16, 0x17, 0x18, 0x19, 0x1a,
|
||||
0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
|
||||
0x22, 0x23, 0x24, 0x25,
|
||||
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
|
||||
0x47, 0x48, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x51, 0x52, 0x53,
|
||||
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
|
||||
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
||||
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
|
||||
0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c,
|
||||
0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
|
||||
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
|
||||
// 0x90-0xfa is 'kernal work storage area'
|
||||
).map{it.toUInt()})
|
||||
}
|
||||
|
||||
if (options.zeropage == ZeropageType.FLOATSAFE) {
|
||||
// remove the zeropage locations used for floating point operations from the free list
|
||||
free.removeAll(listOf(
|
||||
0x22, 0x23, 0x24, 0x25,
|
||||
0x10, 0x11, 0x12, 0x26, 0x27, 0x28, 0x29, 0x2a,
|
||||
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
|
||||
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
||||
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
|
||||
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
|
||||
).map{it.toUInt()})
|
||||
}
|
||||
|
||||
if(options.zeropage!= ZeropageType.DONTUSE) {
|
||||
// add the free Zp addresses
|
||||
// these are valid for the C-64 but allow BASIC to keep running fully *as long as you don't use tape I/O*
|
||||
free.addAll(listOf(0x04, 0x05, 0x06, 0x0a, 0x0e,
|
||||
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa5, 0xa6,
|
||||
0xb0, 0xb1, 0xbe, 0xbf, 0xf9).map{it.toUInt()})
|
||||
} else {
|
||||
// don't use the zeropage at all
|
||||
free.clear()
|
||||
}
|
||||
}
|
||||
|
||||
removeReservedFromFreePool()
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package prog8.compiler.target.cbm
|
||||
package prog8.codegen.target.cbm
|
||||
|
||||
import com.github.michaelbull.result.Ok
|
||||
import com.github.michaelbull.result.Result
|
||||
@ -14,7 +14,7 @@ import kotlin.io.path.Path
|
||||
import kotlin.io.path.isRegularFile
|
||||
|
||||
|
||||
internal const val viceMonListPostfix = "vice-mon-list"
|
||||
internal fun viceMonListName(baseFilename: String) = "$baseFilename.vice-mon-list"
|
||||
|
||||
class AssemblyProgram(
|
||||
override val valid: Boolean,
|
||||
@ -25,17 +25,22 @@ class AssemblyProgram(
|
||||
private val assemblyFile = outputDir.resolve("$name.asm")
|
||||
private val prgFile = outputDir.resolve("$name.prg")
|
||||
private val binFile = outputDir.resolve("$name.bin")
|
||||
private val viceMonListFile = outputDir.resolve("$name.$viceMonListPostfix")
|
||||
private val viceMonListFile = outputDir.resolve(viceMonListName(name))
|
||||
private val listFile = outputDir.resolve("$name.list")
|
||||
|
||||
override fun assemble(quiet: Boolean, options: CompilationOptions): Int {
|
||||
override fun assemble(options: CompilationOptions): Int {
|
||||
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps (default = do this silently)
|
||||
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
|
||||
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
|
||||
"--dump-labels", "--vice-labels", "-l", viceMonListFile.toString(), "--no-monitor")
|
||||
"--dump-labels", "--vice-labels", "--labels=$viceMonListFile", "--no-monitor"
|
||||
)
|
||||
|
||||
if(quiet)
|
||||
if(options.asmQuiet)
|
||||
command.add("--quiet")
|
||||
|
||||
if(options.asmListfile)
|
||||
command.add("--list=$listFile")
|
||||
|
||||
val outFile = when (options.output) {
|
||||
OutputType.PRG -> {
|
||||
command.add("--cbm-prg")
|
78
codeGeneration/src/prog8/codegen/target/cbm/Mflpt5.kt
Normal file
78
codeGeneration/src/prog8/codegen/target/cbm/Mflpt5.kt
Normal file
@ -0,0 +1,78 @@
|
||||
package prog8.codegen.target.cbm
|
||||
|
||||
import prog8.compilerinterface.IMachineFloat
|
||||
import prog8.compilerinterface.InternalCompilerException
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.math.pow
|
||||
|
||||
|
||||
data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, val b4: UByte): IMachineFloat {
|
||||
|
||||
companion object {
|
||||
const val FLOAT_MAX_POSITIVE = 1.7014118345e+38 // bytes: 255,127,255,255,255
|
||||
const val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255
|
||||
const val FLOAT_MEM_SIZE = 5
|
||||
|
||||
val zero = Mflpt5(0u, 0u, 0u, 0u, 0u)
|
||||
fun fromNumber(num: Number): Mflpt5 {
|
||||
// see https://en.wikipedia.org/wiki/Microsoft_Binary_Format
|
||||
// and https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a
|
||||
// and https://en.wikipedia.org/wiki/IEEE_754-1985
|
||||
|
||||
val flt = num.toDouble()
|
||||
if (flt < FLOAT_MAX_NEGATIVE || flt > FLOAT_MAX_POSITIVE)
|
||||
throw InternalCompilerException("floating point number out of 5-byte mflpt range: $this")
|
||||
if (flt == 0.0)
|
||||
return zero
|
||||
|
||||
val sign = if (flt < 0.0) 0x80L else 0x00L
|
||||
var exponent = 128 + 32 // 128 is cbm's bias, 32 is this algo's bias
|
||||
var mantissa = flt.absoluteValue
|
||||
|
||||
// if mantissa is too large, shift right and adjust exponent
|
||||
while (mantissa >= 0x100000000) {
|
||||
mantissa /= 2.0
|
||||
exponent++
|
||||
}
|
||||
// if mantissa is too small, shift left and adjust exponent
|
||||
while (mantissa < 0x80000000) {
|
||||
mantissa *= 2.0
|
||||
exponent--
|
||||
}
|
||||
|
||||
return when {
|
||||
exponent < 0 -> zero // underflow, use zero instead
|
||||
exponent > 255 -> throw InternalCompilerException("floating point overflow: $this")
|
||||
exponent == 0 -> zero
|
||||
else -> {
|
||||
val mantLong = mantissa.toLong()
|
||||
Mflpt5(
|
||||
exponent.toUByte(),
|
||||
(mantLong.and(0x7f000000L) ushr 24).or(sign).toUByte(),
|
||||
(mantLong.and(0x00ff0000L) ushr 16).toUByte(),
|
||||
(mantLong.and(0x0000ff00L) ushr 8).toUByte(),
|
||||
(mantLong.and(0x000000ffL)).toUByte()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun toDouble(): Double {
|
||||
if (this == zero) return 0.0
|
||||
val exp = b0.toInt() - 128
|
||||
val sign = (b1.toInt() and 0x80) > 0
|
||||
val number = 0x80000000L.or(b1.toLong() shl 24).or(b2.toLong() shl 16).or(b3.toLong() shl 8).or(b4.toLong())
|
||||
val result = number.toDouble() * (2.0).pow(exp) / 0x100000000
|
||||
return if (sign) -result else result
|
||||
}
|
||||
|
||||
override fun makeFloatFillAsm(): String {
|
||||
val b0 = "$" + b0.toString(16).padStart(2, '0')
|
||||
val b1 = "$" + b1.toString(16).padStart(2, '0')
|
||||
val b2 = "$" + b2.toString(16).padStart(2, '0')
|
||||
val b3 = "$" + b3.toString(16).padStart(2, '0')
|
||||
val b4 = "$" + b4.toString(16).padStart(2, '0')
|
||||
return "$b0, $b1, $b2, $b3, $b4"
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package prog8.compiler.target.cbm
|
||||
package prog8.codegen.target.cbm
|
||||
|
||||
import com.github.michaelbull.result.Err
|
||||
import com.github.michaelbull.result.Ok
|
||||
@ -1065,15 +1065,15 @@ object Petscii {
|
||||
else -> chr
|
||||
}
|
||||
|
||||
fun encodePetscii(text: String, lowercase: Boolean = false): Result<List<Short>, CharConversionException> {
|
||||
fun encodeChar(chr3: Char, lowercase: Boolean): Short {
|
||||
fun encodePetscii(text: String, lowercase: Boolean = false): Result<List<UByte>, CharConversionException> {
|
||||
fun encodeChar(chr3: Char, lowercase: Boolean): UByte {
|
||||
val chr = replaceSpecial(chr3)
|
||||
val screencode = if(lowercase) encodingPetsciiLowercase[chr] else encodingPetsciiUppercase[chr]
|
||||
return screencode?.toShort() ?: when (chr) {
|
||||
'\u0000' -> 0.toShort()
|
||||
return screencode?.toUByte() ?: when (chr) {
|
||||
'\u0000' -> 0u
|
||||
in '\u8000'..'\u80ff' -> {
|
||||
// special case: take the lower 8 bit hex value directly
|
||||
(chr.code - 0x8000).toShort()
|
||||
(chr.code - 0x8000).toUByte()
|
||||
}
|
||||
else -> {
|
||||
val case = if (lowercase) "lower" else "upper"
|
||||
@ -1095,7 +1095,7 @@ object Petscii {
|
||||
}
|
||||
}
|
||||
|
||||
fun decodePetscii(petscii: Iterable<Short>, lowercase: Boolean = false): String {
|
||||
fun decodePetscii(petscii: Iterable<UByte>, lowercase: Boolean = false): String {
|
||||
return petscii.map {
|
||||
val code = it.toInt()
|
||||
if(code<0 || code>= decodingPetsciiLowercase.size)
|
||||
@ -1104,15 +1104,15 @@ object Petscii {
|
||||
}.joinToString("")
|
||||
}
|
||||
|
||||
fun encodeScreencode(text: String, lowercase: Boolean = false): Result<List<Short>, CharConversionException> {
|
||||
fun encodeChar(chr3: Char, lowercase: Boolean): Short {
|
||||
fun encodeScreencode(text: String, lowercase: Boolean = false): Result<List<UByte>, CharConversionException> {
|
||||
fun encodeChar(chr3: Char, lowercase: Boolean): UByte {
|
||||
val chr = replaceSpecial(chr3)
|
||||
val screencode = if(lowercase) encodingScreencodeLowercase[chr] else encodingScreencodeUppercase[chr]
|
||||
return screencode?.toShort() ?: when (chr) {
|
||||
'\u0000' -> 0.toShort()
|
||||
return screencode?.toUByte() ?: when (chr) {
|
||||
'\u0000' -> 0u
|
||||
in '\u8000'..'\u80ff' -> {
|
||||
// special case: take the lower 8 bit hex value directly
|
||||
(chr.code - 0x8000).toShort()
|
||||
(chr.code - 0x8000).toUByte()
|
||||
}
|
||||
else -> {
|
||||
val case = if (lowercase) "lower" else "upper"
|
||||
@ -1134,7 +1134,7 @@ object Petscii {
|
||||
}
|
||||
}
|
||||
|
||||
fun decodeScreencode(screencode: Iterable<Short>, lowercase: Boolean = false): String {
|
||||
fun decodeScreencode(screencode: Iterable<UByte>, lowercase: Boolean = false): String {
|
||||
return screencode.map {
|
||||
val code = it.toInt()
|
||||
if(code<0 || code>= decodingScreencodeLowercase.size)
|
||||
@ -1143,38 +1143,37 @@ object Petscii {
|
||||
}.joinToString("")
|
||||
}
|
||||
|
||||
fun petscii2scr(petscii_code: Short, inverseVideo: Boolean): Result<Short, CharConversionException> {
|
||||
val code = when {
|
||||
petscii_code < 0 -> return Err(CharConversionException("petscii code out of range"))
|
||||
petscii_code <= 0x1f -> petscii_code + 128
|
||||
petscii_code <= 0x3f -> petscii_code.toInt()
|
||||
petscii_code <= 0x5f -> petscii_code - 64
|
||||
petscii_code <= 0x7f -> petscii_code - 32
|
||||
petscii_code <= 0x9f -> petscii_code + 64
|
||||
petscii_code <= 0xbf -> petscii_code - 64
|
||||
petscii_code <= 0xfe -> petscii_code - 128
|
||||
petscii_code == 255.toShort() -> 95
|
||||
fun petscii2scr(petscii_code: UByte, inverseVideo: Boolean): Result<UByte, CharConversionException> {
|
||||
val code: UInt = when {
|
||||
petscii_code <= 0x1fu -> petscii_code + 128u
|
||||
petscii_code <= 0x3fu -> petscii_code.toUInt()
|
||||
petscii_code <= 0x5fu -> petscii_code - 64u
|
||||
petscii_code <= 0x7fu -> petscii_code - 32u
|
||||
petscii_code <= 0x9fu -> petscii_code + 64u
|
||||
petscii_code <= 0xbfu -> petscii_code - 64u
|
||||
petscii_code <= 0xfeu -> petscii_code - 128u
|
||||
petscii_code == 255.toUByte() -> 95u
|
||||
else -> return Err(CharConversionException("petscii code out of range"))
|
||||
}
|
||||
if(inverseVideo)
|
||||
return Ok((code or 0x80).toShort())
|
||||
return Ok(code.toShort())
|
||||
if(inverseVideo) {
|
||||
return Ok((code or 0x80u).toUByte())
|
||||
}
|
||||
return Ok(code.toUByte())
|
||||
}
|
||||
|
||||
fun scr2petscii(screencode: Short): Result<Short, CharConversionException> {
|
||||
val petscii = when {
|
||||
screencode < 0 -> return Err(CharConversionException("screencode out of range"))
|
||||
screencode <= 0x1f -> screencode + 64
|
||||
screencode <= 0x3f -> screencode.toInt()
|
||||
screencode <= 0x5d -> screencode +123
|
||||
screencode == 0x5e.toShort() -> 255
|
||||
screencode == 0x5f.toShort() -> 223
|
||||
screencode <= 0x7f -> screencode + 64
|
||||
screencode <= 0xbf -> screencode - 128
|
||||
screencode <= 0xfe -> screencode - 64
|
||||
screencode == 255.toShort() -> 191
|
||||
fun scr2petscii(screencode: UByte): Result<UByte, CharConversionException> {
|
||||
val petscii: UInt = when {
|
||||
screencode <= 0x1fu -> screencode + 64u
|
||||
screencode <= 0x3fu -> screencode.toUInt()
|
||||
screencode <= 0x5du -> screencode +123u
|
||||
screencode == 0x5e.toUByte() -> 255u
|
||||
screencode == 0x5f.toUByte() -> 223u
|
||||
screencode <= 0x7fu -> screencode + 64u
|
||||
screencode <= 0xbfu -> screencode - 128u
|
||||
screencode <= 0xfeu -> screencode - 64u
|
||||
screencode == 255.toUByte() -> 191u
|
||||
else -> return Err(CharConversionException("screencode out of range"))
|
||||
}
|
||||
return Ok(petscii.toShort())
|
||||
return Ok(petscii.toUByte())
|
||||
}
|
||||
}
|
3386
codeGeneration/src/prog8/codegen/target/cpu6502/codegen/AsmGen.kt
Normal file
3386
codeGeneration/src/prog8/codegen/target/cpu6502/codegen/AsmGen.kt
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,446 @@
|
||||
package prog8.codegen.target.cpu6502.codegen
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.compilerinterface.IMachineDefinition
|
||||
|
||||
|
||||
// note: see https://wiki.nesdev.org/w/index.php/6502_assembly_optimisations
|
||||
|
||||
|
||||
fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefinition, program: Program): Int {
|
||||
|
||||
var numberOfOptimizations = 0
|
||||
|
||||
var linesByFour = getLinesBy(lines, 4)
|
||||
|
||||
var mods = optimizeUselessStackByteWrites(linesByFour)
|
||||
if(mods.isNotEmpty()) {
|
||||
apply(mods, lines)
|
||||
linesByFour = getLinesBy(lines, 4)
|
||||
numberOfOptimizations++
|
||||
}
|
||||
|
||||
mods = optimizeIncDec(linesByFour)
|
||||
if(mods.isNotEmpty()) {
|
||||
apply(mods, lines)
|
||||
linesByFour = getLinesBy(lines, 4)
|
||||
numberOfOptimizations++
|
||||
}
|
||||
|
||||
mods = optimizeCmpSequence(linesByFour)
|
||||
if(mods.isNotEmpty()) {
|
||||
apply(mods, lines)
|
||||
linesByFour = getLinesBy(lines, 4)
|
||||
numberOfOptimizations++
|
||||
}
|
||||
|
||||
mods = optimizeStoreLoadSame(linesByFour, machine, program)
|
||||
if(mods.isNotEmpty()) {
|
||||
apply(mods, lines)
|
||||
linesByFour = getLinesBy(lines, 4)
|
||||
numberOfOptimizations++
|
||||
}
|
||||
|
||||
mods= optimizeJsrRts(linesByFour)
|
||||
if(mods.isNotEmpty()) {
|
||||
apply(mods, lines)
|
||||
linesByFour = getLinesBy(lines, 4)
|
||||
numberOfOptimizations++
|
||||
}
|
||||
|
||||
var linesByFourteen = getLinesBy(lines, 14)
|
||||
mods = optimizeSameAssignments(linesByFourteen, machine, program)
|
||||
if(mods.isNotEmpty()) {
|
||||
apply(mods, lines)
|
||||
linesByFourteen = getLinesBy(lines, 14)
|
||||
numberOfOptimizations++
|
||||
}
|
||||
|
||||
// TODO more assembly optimizations
|
||||
|
||||
return numberOfOptimizations
|
||||
}
|
||||
|
||||
private class Modification(val lineIndex: Int, val remove: Boolean, val replacement: String?)
|
||||
|
||||
private fun apply(modifications: List<Modification>, lines: MutableList<String>) {
|
||||
for (modification in modifications.sortedBy { it.lineIndex }.reversed()) {
|
||||
if(modification.remove)
|
||||
lines.removeAt(modification.lineIndex)
|
||||
else
|
||||
lines[modification.lineIndex] = modification.replacement!!
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLinesBy(lines: MutableList<String>, windowSize: Int) =
|
||||
// all lines (that aren't empty or comments) in sliding windows of certain size
|
||||
lines.withIndex().filter { it.value.isNotBlank() && !it.value.trimStart().startsWith(';') }.windowed(windowSize, partialWindows = false)
|
||||
|
||||
private fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||
// when statement (on bytes) generates a sequence of:
|
||||
// lda $ce01,x
|
||||
// cmp #$20
|
||||
// beq check_prog8_s72choice_32
|
||||
// lda $ce01,x
|
||||
// cmp #$21
|
||||
// beq check_prog8_s73choice_33
|
||||
// the repeated lda can be removed
|
||||
val mods = mutableListOf<Modification>()
|
||||
for(lines in linesByFour) {
|
||||
if(lines[0].value.trim()=="lda P8ESTACK_LO+1,x" &&
|
||||
lines[1].value.trim().startsWith("cmp ") &&
|
||||
lines[2].value.trim().startsWith("beq ") &&
|
||||
lines[3].value.trim()=="lda P8ESTACK_LO+1,x") {
|
||||
mods.add(Modification(lines[3].index, true, null)) // remove the second lda
|
||||
}
|
||||
}
|
||||
return mods
|
||||
}
|
||||
|
||||
private fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||
// sta on stack, dex, inx, lda from stack -> eliminate this useless stack byte write
|
||||
// this is a lot harder for word values because the instruction sequence varies.
|
||||
val mods = mutableListOf<Modification>()
|
||||
for(lines in linesByFour) {
|
||||
if(lines[0].value.trim()=="sta P8ESTACK_LO,x" &&
|
||||
lines[1].value.trim()=="dex" &&
|
||||
lines[2].value.trim()=="inx" &&
|
||||
lines[3].value.trim()=="lda P8ESTACK_LO,x") {
|
||||
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 optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>, machine: IMachineDefinition, program: Program): List<Modification> {
|
||||
|
||||
// Optimize sequential assignments of the same value to various targets (bytes, words, floats)
|
||||
// the float one is the one that requires 2*7=14 lines of code to check...
|
||||
// The better place to do this is in the Compiler instead and never create these types of assembly, but hey
|
||||
|
||||
val mods = mutableListOf<Modification>()
|
||||
for (lines in linesByFourteen) {
|
||||
val first = lines[0].value.trimStart()
|
||||
val second = lines[1].value.trimStart()
|
||||
val third = lines[2].value.trimStart()
|
||||
val fourth = lines[3].value.trimStart()
|
||||
val fifth = lines[4].value.trimStart()
|
||||
val sixth = lines[5].value.trimStart()
|
||||
val seventh = lines[6].value.trimStart()
|
||||
val eighth = lines[7].value.trimStart()
|
||||
|
||||
if(first.startsWith("lda") && second.startsWith("ldy") && third.startsWith("sta") && fourth.startsWith("sty") &&
|
||||
fifth.startsWith("lda") && sixth.startsWith("ldy") && seventh.startsWith("sta") && eighth.startsWith("sty")) {
|
||||
val firstvalue = first.substring(4)
|
||||
val secondvalue = second.substring(4)
|
||||
val thirdvalue = fifth.substring(4)
|
||||
val fourthvalue = sixth.substring(4)
|
||||
if(firstvalue==thirdvalue && secondvalue==fourthvalue) {
|
||||
// lda/ldy sta/sty twice the same word --> remove second lda/ldy pair (fifth and sixth lines)
|
||||
val address1 = getAddressArg(first, program)
|
||||
val address2 = getAddressArg(second, program)
|
||||
if(address1==null || address2==null || (!machine.isIOAddress(address1) && !machine.isIOAddress(address2))) {
|
||||
mods.add(Modification(lines[4].index, true, null))
|
||||
mods.add(Modification(lines[5].index, true, null))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(first.startsWith("lda") && second.startsWith("sta") && third.startsWith("lda") && fourth.startsWith("sta")) {
|
||||
val firstvalue = first.substring(4)
|
||||
val secondvalue = third.substring(4)
|
||||
if(firstvalue==secondvalue) {
|
||||
// lda value / sta ? / lda same-value / sta ? -> remove second lda (third line)
|
||||
val address = getAddressArg(first, program)
|
||||
if(address==null || !machine.isIOAddress(address))
|
||||
mods.add(Modification(lines[2].index, true, null))
|
||||
}
|
||||
}
|
||||
|
||||
if(first.startsWith("lda") && second.startsWith("ldy") && third.startsWith("sta") && fourth.startsWith("sty") &&
|
||||
fifth.startsWith("lda") && sixth.startsWith("ldy") &&
|
||||
(seventh.startsWith("jsr floats.copy_float") || seventh.startsWith("jsr cx16flt.copy_float"))) {
|
||||
|
||||
val nineth = lines[8].value.trimStart()
|
||||
val tenth = lines[9].value.trimStart()
|
||||
val eleventh = lines[10].value.trimStart()
|
||||
val twelveth = lines[11].value.trimStart()
|
||||
val thirteenth = lines[12].value.trimStart()
|
||||
val fourteenth = lines[13].value.trimStart()
|
||||
|
||||
if(eighth.startsWith("lda") && nineth.startsWith("ldy") && tenth.startsWith("sta") && eleventh.startsWith("sty") &&
|
||||
twelveth.startsWith("lda") && thirteenth.startsWith("ldy") &&
|
||||
(fourteenth.startsWith("jsr floats.copy_float") || fourteenth.startsWith("jsr cx16flt.copy_float"))) {
|
||||
|
||||
if(first.substring(4) == eighth.substring(4) && second.substring(4)==nineth.substring(4)) {
|
||||
// identical float init
|
||||
mods.add(Modification(lines[7].index, true, null))
|
||||
mods.add(Modification(lines[8].index, true, null))
|
||||
mods.add(Modification(lines[9].index, true, null))
|
||||
mods.add(Modification(lines[10].index, true, null))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var overlappingMods = false
|
||||
/*
|
||||
sta prog8_lib.retval_intermX ; remove
|
||||
sty prog8_lib.retval_intermY ; remove
|
||||
lda prog8_lib.retval_intermX ; remove
|
||||
ldy prog8_lib.retval_intermY ; remove
|
||||
sta A1
|
||||
sty A2
|
||||
*/
|
||||
if(first.startsWith("st") && second.startsWith("st")
|
||||
&& third.startsWith("ld") && fourth.startsWith("ld")
|
||||
&& fifth.startsWith("st") && sixth.startsWith("st")) {
|
||||
val reg1 = first[2]
|
||||
val reg2 = second[2]
|
||||
val reg3 = third[2]
|
||||
val reg4 = fourth[2]
|
||||
val reg5 = fifth[2]
|
||||
val reg6 = sixth[2]
|
||||
if (reg1 == reg3 && reg1 == reg5 && reg2 == reg4 && reg2 == reg6) {
|
||||
val firstvalue = first.substring(4)
|
||||
val secondvalue = second.substring(4)
|
||||
val thirdvalue = third.substring(4)
|
||||
val fourthvalue = fourth.substring(4)
|
||||
if(firstvalue.contains("prog8_lib.retval_interm") && secondvalue.contains("prog8_lib.retval_interm")
|
||||
&& firstvalue==thirdvalue && secondvalue==fourthvalue) {
|
||||
mods.add(Modification(lines[0].index, true, null))
|
||||
mods.add(Modification(lines[1].index, true, null))
|
||||
mods.add(Modification(lines[2].index, true, null))
|
||||
mods.add(Modification(lines[3].index, true, null))
|
||||
overlappingMods = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
sta A1
|
||||
sty A2
|
||||
lda A1 ; can be removed
|
||||
ldy A2 ; can be removed if not followed by a branch instuction
|
||||
*/
|
||||
if(!overlappingMods && first.startsWith("st") && second.startsWith("st")
|
||||
&& third.startsWith("ld") && fourth.startsWith("ld")) {
|
||||
val reg1 = first[2]
|
||||
val reg2 = second[2]
|
||||
val reg3 = third[2]
|
||||
val reg4 = fourth[2]
|
||||
if(reg1==reg3 && reg2==reg4) {
|
||||
val firstvalue = first.substring(4)
|
||||
val secondvalue = second.substring(4)
|
||||
val thirdvalue = third.substring(4)
|
||||
val fourthvalue = fourth.substring(4)
|
||||
if(firstvalue==thirdvalue && secondvalue == fourthvalue) {
|
||||
val address = getAddressArg(first, program)
|
||||
if(address==null || !machine.isIOAddress(address)) {
|
||||
overlappingMods = true
|
||||
mods.add(Modification(lines[2].index, true, null))
|
||||
if (!fifth.startsWith('b'))
|
||||
mods.add(Modification(lines[3].index, true, null))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
sta A1
|
||||
sty A2
|
||||
lda A1 ; can be removed if not followed by a branch instruction
|
||||
*/
|
||||
if(!overlappingMods && first.startsWith("st") && second.startsWith("st")
|
||||
&& third.startsWith("ld") && !fourth.startsWith("b")) {
|
||||
val reg1 = first[2]
|
||||
val reg3 = third[2]
|
||||
if(reg1==reg3) {
|
||||
val firstvalue = first.substring(4)
|
||||
val thirdvalue = third.substring(4)
|
||||
if(firstvalue==thirdvalue) {
|
||||
val address = getAddressArg(first, program)
|
||||
if(address==null || !machine.isIOAddress(address)) {
|
||||
overlappingMods = true
|
||||
mods.add(Modification(lines[2].index, true, null))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
sta A1
|
||||
ldy A1 ; make tay
|
||||
sta A1 ; remove
|
||||
*/
|
||||
if(!overlappingMods && first.startsWith("sta") && second.startsWith("ld")
|
||||
&& third.startsWith("sta") && second.length>4) {
|
||||
val firstvalue = first.substring(4)
|
||||
val secondvalue = second.substring(4)
|
||||
val thirdvalue = third.substring(4)
|
||||
if(firstvalue==secondvalue && firstvalue==thirdvalue) {
|
||||
val address = getAddressArg(first, program)
|
||||
if(address==null || !machine.isIOAddress(address)) {
|
||||
overlappingMods = true
|
||||
val reg2 = second[2]
|
||||
mods.add(Modification(lines[1].index, false, " ta$reg2"))
|
||||
mods.add(Modification(lines[2].index, true, null))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
sta A
|
||||
sta A
|
||||
*/
|
||||
if(!overlappingMods && first.startsWith("st") && second.startsWith("st")) {
|
||||
if(first[2]==second[2]) {
|
||||
val firstvalue = first.substring(4)
|
||||
val secondvalue = second.substring(4)
|
||||
if(firstvalue==secondvalue) {
|
||||
val address = getAddressArg(first, program)
|
||||
if(address==null || !machine.isIOAddress(address)) {
|
||||
overlappingMods = true
|
||||
mods.add(Modification(lines[1].index, true, null))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mods
|
||||
}
|
||||
|
||||
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>, machine: IMachineDefinition, program: Program): List<Modification> {
|
||||
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can OFTEN be eliminated
|
||||
val mods = mutableListOf<Modification>()
|
||||
for (lines in linesByFour) {
|
||||
val first = lines[1].value.trimStart()
|
||||
val second = lines[2].value.trimStart()
|
||||
|
||||
if ((first.startsWith("sta ") && second.startsWith("lda ")) ||
|
||||
(first.startsWith("stx ") && second.startsWith("ldx ")) ||
|
||||
(first.startsWith("sty ") && second.startsWith("ldy ")) ||
|
||||
(first.startsWith("lda ") && second.startsWith("lda ")) ||
|
||||
(first.startsWith("ldy ") && second.startsWith("ldy ")) ||
|
||||
(first.startsWith("ldx ") && second.startsWith("ldx ")) ||
|
||||
(first.startsWith("sta ") && second.startsWith("lda ")) ||
|
||||
(first.startsWith("sty ") && second.startsWith("ldy ")) ||
|
||||
(first.startsWith("stx ") && second.startsWith("ldx "))
|
||||
) {
|
||||
val third = lines[3].value.trimStart()
|
||||
val attemptRemove =
|
||||
if(third.startsWith("b")) {
|
||||
// a branch instruction follows, we can only remove the load instruction if
|
||||
// another load instruction of the same register precedes the store instruction
|
||||
// (otherwise wrong cpu flags are used)
|
||||
val loadinstruction = second.substring(0, 3)
|
||||
lines[0].value.trimStart().startsWith(loadinstruction)
|
||||
}
|
||||
else {
|
||||
// no branch instruction follows, we can remove the load instruction
|
||||
val address = getAddressArg(lines[2].value, program)
|
||||
address==null || !machine.isIOAddress(address)
|
||||
}
|
||||
|
||||
if(attemptRemove) {
|
||||
val firstLoc = first.substring(4).trimStart()
|
||||
val secondLoc = second.substring(4).trimStart()
|
||||
if (firstLoc == secondLoc)
|
||||
mods.add(Modification(lines[2].index, true, null))
|
||||
}
|
||||
}
|
||||
else if(first=="pha" && second=="pla" ||
|
||||
first=="phx" && second=="plx" ||
|
||||
first=="phy" && second=="ply" ||
|
||||
first=="php" && second=="plp") {
|
||||
mods.add(Modification(lines[1].index, true, null))
|
||||
mods.add(Modification(lines[2].index, true, null))
|
||||
} else if(first=="pha" && second=="plx") {
|
||||
mods.add(Modification(lines[1].index, true, null))
|
||||
mods.add(Modification(lines[2].index, false, " tax"))
|
||||
} else if(first=="pha" && second=="ply") {
|
||||
mods.add(Modification(lines[1].index, true, null))
|
||||
mods.add(Modification(lines[2].index, false, " tay"))
|
||||
} else if(first=="phx" && second=="pla") {
|
||||
mods.add(Modification(lines[1].index, true, null))
|
||||
mods.add(Modification(lines[2].index, false, " txa"))
|
||||
} else if(first=="phx" && second=="ply") {
|
||||
mods.add(Modification(lines[1].index, true, null))
|
||||
mods.add(Modification(lines[2].index, false, " txy"))
|
||||
} else if(first=="phy" && second=="pla") {
|
||||
mods.add(Modification(lines[1].index, true, null))
|
||||
mods.add(Modification(lines[2].index, false, " tya"))
|
||||
} else if(first=="phy" && second=="plx") {
|
||||
mods.add(Modification(lines[1].index, true, null))
|
||||
mods.add(Modification(lines[2].index, false, " tyx"))
|
||||
}
|
||||
}
|
||||
return mods
|
||||
}
|
||||
|
||||
private val identifierRegex = Regex("""^([a-zA-Z_$][a-zA-Z\d_\.$]*)""")
|
||||
|
||||
private fun getAddressArg(line: String, program: Program): UInt? {
|
||||
val loadArg = line.trimStart().substring(3).trim()
|
||||
return when {
|
||||
loadArg.startsWith('$') -> loadArg.substring(1).toUIntOrNull(16)
|
||||
loadArg.startsWith('%') -> loadArg.substring(1).toUIntOrNull(2)
|
||||
loadArg.startsWith('#') -> null
|
||||
loadArg.startsWith('(') -> null
|
||||
loadArg[0].isLetter() -> {
|
||||
val identMatch = identifierRegex.find(loadArg)
|
||||
if(identMatch!=null) {
|
||||
val identifier = identMatch.value
|
||||
val decl = program.toplevelModule.lookup(identifier.split(".")) as? VarDecl
|
||||
if(decl!=null) {
|
||||
when(decl.type){
|
||||
VarDeclType.VAR -> null
|
||||
VarDeclType.CONST,
|
||||
VarDeclType.MEMORY -> (decl.value as NumericLiteralValue).number.toUInt()
|
||||
}
|
||||
}
|
||||
else null
|
||||
} else null
|
||||
}
|
||||
else -> loadArg.substring(1).toUIntOrNull()
|
||||
}
|
||||
}
|
||||
|
||||
private fun optimizeIncDec(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||
// sometimes, iny+dey / inx+dex / dey+iny / dex+inx sequences are generated, these can be eliminated.
|
||||
val mods = mutableListOf<Modification>()
|
||||
for (lines in linesByFour) {
|
||||
val first = lines[0].value
|
||||
val second = lines[1].value
|
||||
if ((" iny" in first || "\tiny" in first) && (" dey" in second || "\tdey" in second)
|
||||
|| (" inx" in first || "\tinx" in first) && (" dex" in second || "\tdex" in second)
|
||||
|| (" ina" in first || "\tina" in first) && (" dea" in second || "\tdea" in second)
|
||||
|| (" inc a" in first || "\tinc a" in first) && (" dec a" in second || "\tdec a" in second)
|
||||
|| (" dey" in first || "\tdey" in first) && (" iny" in second || "\tiny" in second)
|
||||
|| (" dex" in first || "\tdex" in first) && (" inx" in second || "\tinx" in second)
|
||||
|| (" dea" in first || "\tdea" in first) && (" ina" in second || "\tina" in second)
|
||||
|| (" dec a" in first || "\tdec a" in first) && (" inc a" in second || "\tinc a" in second)) {
|
||||
mods.add(Modification(lines[0].index, true, null))
|
||||
mods.add(Modification(lines[1].index, true, null))
|
||||
}
|
||||
}
|
||||
return mods
|
||||
}
|
||||
|
||||
private fun optimizeJsrRts(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||
// jsr Sub + rts -> jmp Sub
|
||||
val mods = mutableListOf<Modification>()
|
||||
for (lines in linesByFour) {
|
||||
val first = lines[0].value
|
||||
val second = lines[1].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)
|
||||
}
|
||||
}
|
||||
return mods
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package prog8.codegen.target.cpu6502.codegen
|
||||
|
||||
import prog8.ast.base.Cx16VirtualRegisters
|
||||
import prog8.ast.base.RegisterOrPair
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.RegisterOrStatusflag
|
||||
import prog8.ast.statements.Subroutine
|
||||
|
||||
|
||||
internal fun asmsub6502ArgsEvalOrder(sub: Subroutine): List<Int> {
|
||||
val order = mutableListOf<Int>()
|
||||
// order is:
|
||||
// 1) cx16 virtual word registers,
|
||||
// 2) paired CPU registers,
|
||||
// 3) single CPU registers (X last), except A,
|
||||
// 4) CPU Carry status flag
|
||||
// 5) the A register itself last (so everything before it can use the accumulator without having to save its value)
|
||||
val args = sub.parameters.zip(sub.asmParameterRegisters).withIndex()
|
||||
val (cx16regs, args2) = args.partition { it.value.second.registerOrPair in Cx16VirtualRegisters }
|
||||
val pairedRegisters = arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)
|
||||
val (pairedRegs , args3) = args2.partition { it.value.second.registerOrPair in pairedRegisters }
|
||||
val (regsWithoutA, args4) = args3.partition { it.value.second.registerOrPair != RegisterOrPair.A }
|
||||
val (regA, rest) = args4.partition { it.value.second.registerOrPair != null }
|
||||
|
||||
cx16regs.forEach { order += it.index }
|
||||
pairedRegs.forEach { order += it.index }
|
||||
regsWithoutA.forEach {
|
||||
if(it.value.second.registerOrPair != RegisterOrPair.X)
|
||||
order += it.index
|
||||
}
|
||||
regsWithoutA.firstOrNull { it.value.second.registerOrPair==RegisterOrPair.X } ?.let { order += it.index}
|
||||
rest.forEach { order += it.index }
|
||||
regA.forEach { order += it.index }
|
||||
require(order.size==sub.parameters.size)
|
||||
return order
|
||||
}
|
||||
|
||||
internal fun asmsub6502ArgsHaveRegisterClobberRisk(args: List<Expression>,
|
||||
paramRegisters: List<RegisterOrStatusflag>): Boolean {
|
||||
fun isClobberRisk(expr: Expression): Boolean {
|
||||
when (expr) {
|
||||
is ArrayIndexedExpression -> {
|
||||
return paramRegisters.any {
|
||||
it.registerOrPair in listOf(RegisterOrPair.Y, RegisterOrPair.AY, RegisterOrPair.XY)
|
||||
}
|
||||
}
|
||||
is FunctionCallExpr -> {
|
||||
if (expr.target.nameInSource == listOf("lsb") || expr.target.nameInSource == listOf("msb"))
|
||||
return isClobberRisk(expr.args[0])
|
||||
if (expr.target.nameInSource == listOf("mkword"))
|
||||
return isClobberRisk(expr.args[0]) && isClobberRisk(expr.args[1])
|
||||
return !expr.isSimple
|
||||
}
|
||||
else -> return !expr.isSimple
|
||||
}
|
||||
}
|
||||
|
||||
return args.size>1 && args.any { isClobberRisk(it) }
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package prog8.compiler.target.cpu6502.codegen
|
||||
package prog8.codegen.target.cpu6502.codegen
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Node
|
||||
@ -10,16 +10,16 @@ import prog8.ast.statements.DirectMemoryWrite
|
||||
import prog8.ast.statements.FunctionCallStatement
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.toHex
|
||||
import prog8.compiler.target.AssemblyError
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8.compiler.target.cpu6502.codegen.assignment.*
|
||||
import prog8.codegen.target.AssemblyError
|
||||
import prog8.codegen.target.Cx16Target
|
||||
import prog8.codegen.target.cpu6502.codegen.assignment.*
|
||||
import prog8.compilerinterface.CpuType
|
||||
import prog8.compilerinterface.FSignature
|
||||
import prog8.compilerinterface.subroutineFloatEvalResultVar2
|
||||
|
||||
internal class BuiltinFunctionsAsmGen(private val program: Program, private val asmgen: AsmGen, private val assignAsmGen: AssignmentAsmGen) {
|
||||
|
||||
internal fun translateFunctioncallExpression(fcall: FunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
|
||||
internal fun translateFunctioncallExpression(fcall: FunctionCallExpr, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
|
||||
translateFunctioncall(fcall, func, discardResult = false, resultToStack = resultToStack, resultRegister = resultRegister)
|
||||
}
|
||||
|
||||
@ -46,7 +46,9 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
"sum" -> funcSum(fcall, resultToStack, resultRegister, sscope)
|
||||
"any", "all" -> funcAnyAll(fcall, func, resultToStack, resultRegister, sscope)
|
||||
"sin8", "sin8u", "sin16", "sin16u",
|
||||
"cos8", "cos8u", "cos16", "cos16u" -> funcSinCosInt(fcall, func, resultToStack, resultRegister, sscope)
|
||||
"sinr8", "sinr8u", "sinr16", "sinr16u",
|
||||
"cos8", "cos8u", "cos16", "cos16u",
|
||||
"cosr8", "cosr8u", "cosr16", "cosr16u" -> funcSinCosInt(fcall, func, resultToStack, resultRegister, sscope)
|
||||
"sgn" -> funcSgn(fcall, func, resultToStack, resultRegister, sscope)
|
||||
"sin", "cos", "tan", "atan",
|
||||
"ln", "log2", "sqrt", "rad",
|
||||
@ -65,6 +67,12 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
"peek" -> throw AssemblyError("peek() should have been replaced by @()")
|
||||
"pokew" -> funcPokeW(fcall)
|
||||
"poke" -> throw AssemblyError("poke() should have been replaced by @()")
|
||||
"push", "pushw" -> funcPush(fcall, func)
|
||||
"pop", "popw" -> funcPop(fcall, func)
|
||||
"rsave" -> funcRsave()
|
||||
"rsavex" -> funcRsaveX()
|
||||
"rrestore" -> funcRrestore()
|
||||
"rrestorex" -> funcRrestoreX()
|
||||
"cmp" -> funcCmp(fcall)
|
||||
"callfar" -> funcCallFar(fcall)
|
||||
"callrom" -> funcCallRom(fcall)
|
||||
@ -72,6 +80,168 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcRsave() {
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out("""
|
||||
php
|
||||
pha
|
||||
phy
|
||||
phx""")
|
||||
else
|
||||
// see http://6502.org/tutorials/register_preservation.html
|
||||
asmgen.out("""
|
||||
php
|
||||
sta P8ZP_SCRATCH_REG
|
||||
pha
|
||||
txa
|
||||
pha
|
||||
tya
|
||||
pha
|
||||
lda P8ZP_SCRATCH_REG""")
|
||||
}
|
||||
|
||||
private fun funcRsaveX() {
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" phx")
|
||||
else
|
||||
asmgen.out(" txa | pha")
|
||||
}
|
||||
|
||||
private fun funcRrestore() {
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out("""
|
||||
plx
|
||||
ply
|
||||
pla
|
||||
plp""")
|
||||
else
|
||||
asmgen.out("""
|
||||
pla
|
||||
tay
|
||||
pla
|
||||
tax
|
||||
pla
|
||||
plp""")
|
||||
}
|
||||
|
||||
private fun funcRrestoreX() {
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" plx")
|
||||
else
|
||||
asmgen.out(" sta P8ZP_SCRATCH_B1 | pla | tax | lda P8ZP_SCRATCH_B1")
|
||||
}
|
||||
|
||||
private fun funcPop(fcall: IFunctionCall, func: FSignature) {
|
||||
// note: because A is pushed first so popped last, saving A is often not required here.
|
||||
require(fcall.args[0] is IdentifierReference) {
|
||||
"attempt to pop a value into a differently typed variable, or in something else that isn't supported ${(fcall as Node).position}"
|
||||
}
|
||||
val target = (fcall.args[0] as IdentifierReference).targetVarDecl(program)!!
|
||||
val parameter = target.subroutineParameter
|
||||
if(parameter!=null) {
|
||||
val sub = parameter.definingSubroutine!!
|
||||
require(sub.isAsmSubroutine) {
|
||||
"push/pop arg passing only supported on asmsubs ${(fcall as Node).position}"
|
||||
}
|
||||
val shouldKeepA = sub.asmParameterRegisters.any { it.registerOrPair==RegisterOrPair.AX || it.registerOrPair==RegisterOrPair.AY }
|
||||
val reg = sub.asmParameterRegisters[sub.parameters.indexOf(parameter)]
|
||||
if(reg.statusflag!=null) {
|
||||
if(shouldKeepA)
|
||||
asmgen.out(" sta P8ZP_SCRATCH_REG")
|
||||
asmgen.out("""
|
||||
clc
|
||||
pla
|
||||
beq +
|
||||
sec
|
||||
+""")
|
||||
if(shouldKeepA)
|
||||
asmgen.out(" lda P8ZP_SCRATCH_REG")
|
||||
}
|
||||
else {
|
||||
if (func.name == "pop") {
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
||||
when (reg.registerOrPair) {
|
||||
RegisterOrPair.A -> asmgen.out(" pla")
|
||||
RegisterOrPair.X -> asmgen.out(" plx")
|
||||
RegisterOrPair.Y -> asmgen.out(" ply")
|
||||
in Cx16VirtualRegisters -> asmgen.out(" pla | sta cx16.${reg.registerOrPair!!.name.lowercase()}")
|
||||
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
|
||||
}
|
||||
} else {
|
||||
when (reg.registerOrPair) {
|
||||
RegisterOrPair.A -> asmgen.out(" pla")
|
||||
RegisterOrPair.X -> {
|
||||
if(shouldKeepA)
|
||||
asmgen.out(" sta P8ZP_SCRATCH_REG | pla | tax | lda P8ZP_SCRATCH_REG")
|
||||
else
|
||||
asmgen.out(" pla | tax")
|
||||
}
|
||||
RegisterOrPair.Y -> {
|
||||
if(shouldKeepA)
|
||||
asmgen.out(" sta P8ZP_SCRATCH_REG | pla | tay | lda P8ZP_SCRATCH_REG")
|
||||
else
|
||||
asmgen.out(" pla | tay")
|
||||
}
|
||||
in Cx16VirtualRegisters -> asmgen.out(" pla | sta cx16.${reg.registerOrPair!!.name.lowercase()}")
|
||||
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// word pop
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
when (reg.registerOrPair) {
|
||||
RegisterOrPair.AX -> asmgen.out(" plx | pla")
|
||||
RegisterOrPair.AY -> asmgen.out(" ply | pla")
|
||||
RegisterOrPair.XY -> asmgen.out(" ply | plx")
|
||||
in Cx16VirtualRegisters -> {
|
||||
val regname = reg.registerOrPair!!.name.lowercase()
|
||||
asmgen.out(" pla | sta cx16.$regname+1 | pla | sta cx16.$regname")
|
||||
}
|
||||
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
|
||||
}
|
||||
else {
|
||||
when (reg.registerOrPair) {
|
||||
RegisterOrPair.AX -> asmgen.out(" pla | tax | pla")
|
||||
RegisterOrPair.AY -> asmgen.out(" pla | tay | pla")
|
||||
RegisterOrPair.XY -> asmgen.out(" pla | tay | pla | tax")
|
||||
in Cx16VirtualRegisters -> {
|
||||
val regname = reg.registerOrPair!!.name.lowercase()
|
||||
asmgen.out(" pla | sta cx16.$regname+1 | pla | sta cx16.$regname")
|
||||
}
|
||||
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, target.datatype, (fcall as Node).definingSubroutine, variableAsmName = asmgen.asmVariableName(target.name))
|
||||
if (func.name == "pop") {
|
||||
asmgen.out(" pla")
|
||||
asmgen.assignRegister(RegisterOrPair.A, tgt)
|
||||
} else {
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" ply | pla")
|
||||
else
|
||||
asmgen.out(" pla | tay | pla")
|
||||
asmgen.assignRegister(RegisterOrPair.AY, tgt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcPush(fcall: IFunctionCall, func: FSignature) {
|
||||
val signed = fcall.args[0].inferType(program).oneOf(DataType.BYTE, DataType.WORD)
|
||||
if(func.name=="push") {
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A, signed)
|
||||
asmgen.out(" pha")
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY, signed)
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" pha | phy")
|
||||
else
|
||||
asmgen.out(" pha | tya | pha")
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcCallFar(fcall: IFunctionCall) {
|
||||
if(asmgen.options.compTarget !is Cx16Target)
|
||||
throw AssemblyError("callfar only works on cx16 target at this time")
|
||||
@ -87,7 +257,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
throw AssemblyError("callfar done on bank 0 which is reserved for the kernal")
|
||||
|
||||
val argAddrArg = fcall.args[2]
|
||||
if(argAddrArg.constValue(program)?.number == 0) {
|
||||
if(argAddrArg.constValue(program)?.number == 0.0) {
|
||||
asmgen.out("""
|
||||
jsr cx16.jsrfar
|
||||
.word ${address.toHex()}
|
||||
@ -132,7 +302,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
throw AssemblyError("callrom bank must be <32")
|
||||
|
||||
val argAddrArg = fcall.args[2]
|
||||
if(argAddrArg.constValue(program)?.number == 0) {
|
||||
if(argAddrArg.constValue(program)?.number == 0.0) {
|
||||
asmgen.out("""
|
||||
lda $01
|
||||
pha
|
||||
@ -177,8 +347,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
private fun funcCmp(fcall: IFunctionCall) {
|
||||
val arg1 = fcall.args[0]
|
||||
val arg2 = fcall.args[1]
|
||||
val dt1 = arg1.inferType(program).getOr(DataType.UNDEFINED)
|
||||
val dt2 = arg2.inferType(program).getOr(DataType.UNDEFINED)
|
||||
val dt1 = arg1.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
|
||||
val dt2 = arg2.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
|
||||
if(dt1 in ByteDatatypes) {
|
||||
if(dt2 in ByteDatatypes) {
|
||||
when (arg2) {
|
||||
@ -188,7 +358,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
}
|
||||
is NumericLiteralValue -> {
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
||||
asmgen.out(" cmp #${arg2.number}")
|
||||
asmgen.out(" cmp #${arg2.number.toInt()}")
|
||||
}
|
||||
is DirectMemoryRead -> {
|
||||
if(arg2.addressExpression is NumericLiteralValue) {
|
||||
@ -223,9 +393,9 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
is NumericLiteralValue -> {
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
|
||||
asmgen.out("""
|
||||
cpy #>${arg2.number}
|
||||
cpy #>${arg2.number.toInt()}
|
||||
bne +
|
||||
cmp #<${arg2.number}
|
||||
cmp #<${arg2.number.toInt()}
|
||||
+""")
|
||||
}
|
||||
else -> {
|
||||
@ -244,11 +414,12 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
}
|
||||
|
||||
private fun funcMemory(fcall: IFunctionCall, discardResult: Boolean, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
|
||||
if(discardResult || fcall !is FunctionCall)
|
||||
if(discardResult || fcall !is FunctionCallExpr)
|
||||
throw AssemblyError("should not discard result of memory allocation at $fcall")
|
||||
val nameRef = fcall.args[0] as IdentifierReference
|
||||
val name = (nameRef.targetVarDecl(program)!!.value as StringLiteralValue).value
|
||||
val size = (fcall.args[1] as NumericLiteralValue).number.toInt()
|
||||
require(name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name"}
|
||||
val size = (fcall.args[1] as NumericLiteralValue).number.toUInt()
|
||||
|
||||
val existingSize = asmgen.slabs[name]
|
||||
if(existingSize!=null && existingSize!=size)
|
||||
@ -283,11 +454,11 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
asmgen.out(" jsr prog8_lib.func_${func.name}_stack")
|
||||
else
|
||||
when(func.name) {
|
||||
"sin8", "sin8u", "cos8", "cos8u" -> {
|
||||
"sin8", "sin8u", "sinr8", "sinr8u", "cos8", "cos8u", "cosr8", "cosr8u" -> {
|
||||
asmgen.out(" jsr prog8_lib.func_${func.name}_into_A")
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
|
||||
}
|
||||
"sin16", "sin16u", "cos16", "cos16u" -> {
|
||||
"sin16", "sin16u", "sinr16", "sinr16u", "cos16", "cos16u", "cosr16", "cosr16u" -> {
|
||||
asmgen.out(" jsr prog8_lib.func_${func.name}_into_AY")
|
||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
|
||||
}
|
||||
@ -822,9 +993,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
val arrayVarName1 = asmgen.asmVariableName(first.arrayvar)
|
||||
val arrayVarName2 = asmgen.asmVariableName(second.arrayvar)
|
||||
val elementIDt = first.inferType(program)
|
||||
if(!elementIDt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
val elementDt = elementIDt.getOr(DataType.UNDEFINED)
|
||||
val elementDt = elementIDt.getOrElse { throw AssemblyError("unknown dt") }
|
||||
|
||||
val firstNum = first.indexer.indexExpr as? NumericLiteralValue
|
||||
val firstVar = first.indexer.indexExpr as? IdentifierReference
|
||||
@ -1229,6 +1398,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("wrong pokew arg type")
|
||||
}
|
||||
|
||||
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", DataType.UWORD, null)
|
||||
@ -1475,7 +1645,9 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
}
|
||||
|
||||
private fun translateArguments(args: MutableList<Expression>, signature: FSignature, scope: Subroutine?) {
|
||||
val callConv = signature.callConvention(args.map { it.inferType(program).getOr(DataType.UNDEFINED) })
|
||||
val callConv = signature.callConvention(args.map {
|
||||
it.inferType(program).getOrElse { throw AssemblyError("unknown dt")}
|
||||
})
|
||||
|
||||
fun getSourceForFloat(value: Expression): AsmAssignSource {
|
||||
return when (value) {
|
@ -0,0 +1,792 @@
|
||||
package prog8.codegen.target.cpu6502.codegen
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.BuiltinFunctionPlaceholder
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.toHex
|
||||
import prog8.codegen.target.AssemblyError
|
||||
import prog8.compilerinterface.BuiltinFunctions
|
||||
import prog8.compilerinterface.CpuType
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
internal class ExpressionsAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||
|
||||
@Deprecated("avoid calling this as it generates slow evalstack based code")
|
||||
internal fun translateExpression(expression:Expression) {
|
||||
if (this.asmgen.options.slowCodegenWarnings) {
|
||||
asmgen.errors.warn("slow stack evaluation used for expression $expression", expression.position)
|
||||
}
|
||||
translateExpressionInternal(expression)
|
||||
}
|
||||
|
||||
|
||||
// the rest of the methods are all PRIVATE
|
||||
|
||||
|
||||
private fun translateExpressionInternal(expression: Expression) {
|
||||
|
||||
when(expression) {
|
||||
is PrefixExpression -> translateExpression(expression)
|
||||
is BinaryExpression -> translateExpression(expression)
|
||||
is ArrayIndexedExpression -> translateExpression(expression)
|
||||
is TypecastExpression -> translateExpression(expression)
|
||||
is AddressOf -> translateExpression(expression)
|
||||
is DirectMemoryRead -> asmgen.translateDirectMemReadExpressionToRegAorStack(expression, true)
|
||||
is NumericLiteralValue -> translateExpression(expression)
|
||||
is IdentifierReference -> translateExpression(expression)
|
||||
is FunctionCallExpr -> translateFunctionCallResultOntoStack(expression)
|
||||
is ContainmentCheck -> throw AssemblyError("containment check as complex expression value is not supported")
|
||||
is ArrayLiteralValue, is StringLiteralValue -> throw AssemblyError("no asm gen for string/array literal value assignment - should have been replaced by a variable")
|
||||
is RangeExpr -> throw AssemblyError("range expression should have been changed into array values")
|
||||
is CharLiteral -> throw AssemblyError("charliteral should have been replaced by ubyte using certain encoding")
|
||||
else -> TODO("missing expression asmgen for $expression")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateFunctionCallResultOntoStack(call: FunctionCallExpr) {
|
||||
// only for use in nested expression evaluation
|
||||
|
||||
val sub = call.target.targetStatement(program)
|
||||
if(sub is BuiltinFunctionPlaceholder) {
|
||||
val builtinFunc = BuiltinFunctions.getValue(sub.name)
|
||||
asmgen.translateBuiltinFunctionCallExpression(call, builtinFunc, true, null)
|
||||
} else {
|
||||
sub as Subroutine
|
||||
asmgen.saveXbeforeCall(call)
|
||||
asmgen.translateFunctionCall(call, true)
|
||||
if(sub.regXasResult()) {
|
||||
// store the return value in X somewhere that we can acces again below
|
||||
asmgen.out(" stx P8ZP_SCRATCH_REG")
|
||||
}
|
||||
asmgen.restoreXafterCall(call)
|
||||
|
||||
val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)
|
||||
for ((_, reg) in returns) {
|
||||
// result value is in cpu or status registers, put it on the stack instead (as we're evaluating an expression tree)
|
||||
if (reg.registerOrPair != null) {
|
||||
when (reg.registerOrPair!!) {
|
||||
RegisterOrPair.A -> asmgen.out(" sta P8ESTACK_LO,x | dex")
|
||||
RegisterOrPair.Y -> asmgen.out(" tya | sta P8ESTACK_LO,x | dex")
|
||||
RegisterOrPair.AY -> asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
|
||||
RegisterOrPair.X -> asmgen.out(" lda P8ZP_SCRATCH_REG | sta P8ESTACK_LO,x | dex")
|
||||
RegisterOrPair.AX -> asmgen.out(" sta P8ESTACK_LO,x | lda P8ZP_SCRATCH_REG | sta P8ESTACK_HI,x | dex")
|
||||
RegisterOrPair.XY -> asmgen.out(" tya | sta P8ESTACK_HI,x | lda P8ZP_SCRATCH_REG | sta P8ESTACK_LO,x | dex")
|
||||
RegisterOrPair.FAC1 -> asmgen.out(" jsr floats.push_fac1")
|
||||
RegisterOrPair.FAC2 -> asmgen.out(" jsr floats.push_fac2")
|
||||
RegisterOrPair.R0,
|
||||
RegisterOrPair.R1,
|
||||
RegisterOrPair.R2,
|
||||
RegisterOrPair.R3,
|
||||
RegisterOrPair.R4,
|
||||
RegisterOrPair.R5,
|
||||
RegisterOrPair.R6,
|
||||
RegisterOrPair.R7,
|
||||
RegisterOrPair.R8,
|
||||
RegisterOrPair.R9,
|
||||
RegisterOrPair.R10,
|
||||
RegisterOrPair.R11,
|
||||
RegisterOrPair.R12,
|
||||
RegisterOrPair.R13,
|
||||
RegisterOrPair.R14,
|
||||
RegisterOrPair.R15 -> {
|
||||
asmgen.out(
|
||||
"""
|
||||
lda cx16.${reg.registerOrPair.toString().lowercase()}
|
||||
sta P8ESTACK_LO,x
|
||||
lda cx16.${reg.registerOrPair.toString().lowercase()}+1
|
||||
sta P8ESTACK_HI,x
|
||||
dex
|
||||
""")
|
||||
}
|
||||
}
|
||||
} else when(reg.statusflag) {
|
||||
Statusflag.Pc -> {
|
||||
asmgen.out("""
|
||||
lda #0
|
||||
rol a
|
||||
sta P8ESTACK_LO,x
|
||||
dex""")
|
||||
}
|
||||
Statusflag.Pz -> {
|
||||
asmgen.out("""
|
||||
beq +
|
||||
lda #0
|
||||
beq ++
|
||||
+ lda #1
|
||||
+ sta P8ESTACK_LO,x
|
||||
dex""")
|
||||
}
|
||||
Statusflag.Pv -> {
|
||||
asmgen.out("""
|
||||
bvs +
|
||||
lda #0
|
||||
beq ++
|
||||
+ lda #1
|
||||
+ sta P8ESTACK_LO,x
|
||||
dex""")
|
||||
}
|
||||
Statusflag.Pn -> {
|
||||
asmgen.out("""
|
||||
bmi +
|
||||
lda #0
|
||||
beq ++
|
||||
+ lda #1
|
||||
+ sta P8ESTACK_LO,x
|
||||
dex""")
|
||||
}
|
||||
null -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateExpression(typecast: TypecastExpression) {
|
||||
translateExpressionInternal(typecast.expression)
|
||||
when(typecast.expression.inferType(program).getOr(DataType.UNDEFINED)) {
|
||||
DataType.UBYTE -> {
|
||||
when(typecast.type) {
|
||||
DataType.UBYTE, DataType.BYTE -> {}
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz P8ESTACK_HI+1,x")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta P8ESTACK_HI+1,x")
|
||||
}
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.stack_ub2float")
|
||||
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
when(typecast.type) {
|
||||
DataType.UBYTE, DataType.BYTE -> {}
|
||||
DataType.UWORD, DataType.WORD -> asmgen.signExtendStackLsb(DataType.BYTE)
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.stack_b2float")
|
||||
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
when(typecast.type) {
|
||||
DataType.BYTE, DataType.UBYTE -> {}
|
||||
DataType.WORD, DataType.UWORD -> {}
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.stack_uw2float")
|
||||
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
DataType.WORD -> {
|
||||
when(typecast.type) {
|
||||
DataType.BYTE, DataType.UBYTE -> {}
|
||||
DataType.WORD, DataType.UWORD -> {}
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.stack_w2float")
|
||||
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
when(typecast.type) {
|
||||
DataType.UBYTE -> asmgen.out(" jsr floats.stack_float2uw")
|
||||
DataType.BYTE -> asmgen.out(" jsr floats.stack_float2w")
|
||||
DataType.UWORD -> asmgen.out(" jsr floats.stack_float2uw")
|
||||
DataType.WORD -> asmgen.out(" jsr floats.stack_float2w")
|
||||
DataType.FLOAT -> {}
|
||||
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
DataType.STR -> {
|
||||
if (typecast.type != DataType.UWORD && typecast.type == DataType.STR)
|
||||
throw AssemblyError("cannot typecast a string into another incompatitble type")
|
||||
}
|
||||
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast pass-by-reference value into another type")
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateExpression(expr: AddressOf) {
|
||||
val name = asmgen.asmVariableName(expr.identifier)
|
||||
asmgen.out(" lda #<$name | sta P8ESTACK_LO,x | lda #>$name | sta P8ESTACK_HI,x | dex")
|
||||
}
|
||||
|
||||
private fun translateExpression(expr: NumericLiteralValue) {
|
||||
when(expr.type) {
|
||||
DataType.UBYTE, DataType.BYTE -> asmgen.out(" lda #${expr.number.toHex()} | sta P8ESTACK_LO,x | dex")
|
||||
DataType.UWORD, DataType.WORD -> asmgen.out("""
|
||||
lda #<${expr.number.toHex()}
|
||||
sta P8ESTACK_LO,x
|
||||
lda #>${expr.number.toHex()}
|
||||
sta P8ESTACK_HI,x
|
||||
dex
|
||||
""")
|
||||
DataType.FLOAT -> {
|
||||
val floatConst = asmgen.getFloatAsmConst(expr.number)
|
||||
asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.push_float")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateExpression(expr: IdentifierReference) {
|
||||
val varname = asmgen.asmVariableName(expr)
|
||||
when(expr.inferType(program).getOr(DataType.UNDEFINED)) {
|
||||
DataType.UBYTE, DataType.BYTE -> {
|
||||
asmgen.out(" lda $varname | sta P8ESTACK_LO,x | dex")
|
||||
}
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
asmgen.out(" lda $varname | sta P8ESTACK_LO,x | lda $varname+1 | sta P8ESTACK_HI,x | dex")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out(" lda #<$varname | ldy #>$varname| jsr floats.push_float")
|
||||
}
|
||||
in IterableDatatypes -> {
|
||||
asmgen.out(" lda #<$varname | sta P8ESTACK_LO,x | lda #>$varname | sta P8ESTACK_HI,x | dex")
|
||||
}
|
||||
else -> throw AssemblyError("stack push weird variable type $expr")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateExpression(expr: BinaryExpression) {
|
||||
// Uses evalstack to evaluate the given expression.
|
||||
// TODO we're slowly reducing the number of places where this is called and instead replace that by more efficient assignment-form code (using temp var or register for instance).
|
||||
val leftIDt = expr.left.inferType(program)
|
||||
val rightIDt = expr.right.inferType(program)
|
||||
if(!leftIDt.isKnown || !rightIDt.isKnown)
|
||||
throw AssemblyError("can't infer type of both expression operands")
|
||||
|
||||
val leftDt = leftIDt.getOrElse { throw AssemblyError("unknown dt") }
|
||||
val rightDt = rightIDt.getOrElse { throw AssemblyError("unknown dt") }
|
||||
// see if we can apply some optimized routines
|
||||
when(expr.operator) {
|
||||
"+" -> {
|
||||
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
|
||||
val leftVal = expr.left.constValue(program)?.number?.toInt()
|
||||
val rightVal = expr.right.constValue(program)?.number?.toInt()
|
||||
if (leftVal!=null && leftVal in -4..4) {
|
||||
translateExpressionInternal(expr.right)
|
||||
if(rightDt in ByteDatatypes) {
|
||||
val incdec = if(leftVal<0) "dec" else "inc"
|
||||
repeat(leftVal.absoluteValue) {
|
||||
asmgen.out(" $incdec P8ESTACK_LO+1,x")
|
||||
}
|
||||
} else {
|
||||
// word
|
||||
if(leftVal<0) {
|
||||
repeat(leftVal.absoluteValue) {
|
||||
asmgen.out("""
|
||||
lda P8ESTACK_LO+1,x
|
||||
bne +
|
||||
dec P8ESTACK_HI+1,x
|
||||
+ dec P8ESTACK_LO+1,x""")
|
||||
}
|
||||
} else {
|
||||
repeat(leftVal) {
|
||||
asmgen.out("""
|
||||
inc P8ESTACK_LO+1,x
|
||||
bne +
|
||||
inc P8ESTACK_HI+1,x
|
||||
+""")
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
else if (rightVal!=null && rightVal in -4..4)
|
||||
{
|
||||
translateExpressionInternal(expr.left)
|
||||
if(leftDt in ByteDatatypes) {
|
||||
val incdec = if(rightVal<0) "dec" else "inc"
|
||||
repeat(rightVal.absoluteValue) {
|
||||
asmgen.out(" $incdec P8ESTACK_LO+1,x")
|
||||
}
|
||||
} else {
|
||||
// word
|
||||
if(rightVal<0) {
|
||||
repeat(rightVal.absoluteValue) {
|
||||
asmgen.out("""
|
||||
lda P8ESTACK_LO+1,x
|
||||
bne +
|
||||
dec P8ESTACK_HI+1,x
|
||||
+ dec P8ESTACK_LO+1,x""")
|
||||
}
|
||||
} else {
|
||||
repeat(rightVal) {
|
||||
asmgen.out("""
|
||||
inc P8ESTACK_LO+1,x
|
||||
bne +
|
||||
inc P8ESTACK_HI+1,x
|
||||
+""")
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
"-" -> {
|
||||
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
|
||||
val rightVal = expr.right.constValue(program)?.number?.toInt()
|
||||
if (rightVal!=null && rightVal in -4..4)
|
||||
{
|
||||
translateExpressionInternal(expr.left)
|
||||
if(leftDt in ByteDatatypes) {
|
||||
val incdec = if(rightVal<0) "inc" else "dec"
|
||||
repeat(rightVal.absoluteValue) {
|
||||
asmgen.out(" $incdec P8ESTACK_LO+1,x")
|
||||
}
|
||||
} else {
|
||||
// word
|
||||
if(rightVal>0) {
|
||||
repeat(rightVal.absoluteValue) {
|
||||
asmgen.out("""
|
||||
lda P8ESTACK_LO+1,x
|
||||
bne +
|
||||
dec P8ESTACK_HI+1,x
|
||||
+ dec P8ESTACK_LO+1,x""")
|
||||
}
|
||||
} else {
|
||||
repeat(rightVal) {
|
||||
asmgen.out("""
|
||||
inc P8ESTACK_LO+1,x
|
||||
bne +
|
||||
inc P8ESTACK_HI+1,x
|
||||
+""")
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
">>" -> {
|
||||
val amount = expr.right.constValue(program)?.number?.toInt()
|
||||
if(amount!=null) {
|
||||
translateExpressionInternal(expr.left)
|
||||
when (leftDt) {
|
||||
DataType.UBYTE -> {
|
||||
if (amount <= 2)
|
||||
repeat(amount) { asmgen.out(" lsr P8ESTACK_LO+1,x") }
|
||||
else {
|
||||
asmgen.out(" lda P8ESTACK_LO+1,x")
|
||||
repeat(amount) { asmgen.out(" lsr a") }
|
||||
asmgen.out(" sta P8ESTACK_LO+1,x")
|
||||
}
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
if (amount <= 2)
|
||||
repeat(amount) { asmgen.out(" lda P8ESTACK_LO+1,x | asl a | ror P8ESTACK_LO+1,x") }
|
||||
else {
|
||||
asmgen.out(" lda P8ESTACK_LO+1,x | sta P8ZP_SCRATCH_B1")
|
||||
repeat(amount) { asmgen.out(" asl a | ror P8ZP_SCRATCH_B1 | lda P8ZP_SCRATCH_B1") }
|
||||
asmgen.out(" sta P8ESTACK_LO+1,x")
|
||||
}
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
if(amount>=16) {
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz P8ESTACK_LO+1,x | stz P8ESTACK_HI+1,x")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta P8ESTACK_LO+1,x | sta P8ESTACK_HI+1,x")
|
||||
return
|
||||
}
|
||||
var left = amount
|
||||
while (left >= 7) {
|
||||
asmgen.out(" jsr math.shift_right_uw_7")
|
||||
left -= 7
|
||||
}
|
||||
if (left in 0..2)
|
||||
repeat(left) { asmgen.out(" lsr P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x") }
|
||||
else
|
||||
asmgen.out(" jsr math.shift_right_uw_$left")
|
||||
}
|
||||
DataType.WORD -> {
|
||||
if(amount>=16) {
|
||||
asmgen.out("""
|
||||
lda P8ESTACK_HI+1,x
|
||||
bmi +
|
||||
lda #0
|
||||
sta P8ESTACK_LO+1,x
|
||||
sta P8ESTACK_HI+1,x
|
||||
beq ++
|
||||
+ lda #255
|
||||
sta P8ESTACK_LO+1,x
|
||||
sta P8ESTACK_HI+1,x
|
||||
+""")
|
||||
return
|
||||
}
|
||||
var left = amount
|
||||
while (left >= 7) {
|
||||
asmgen.out(" jsr math.shift_right_w_7")
|
||||
left -= 7
|
||||
}
|
||||
if (left in 0..2)
|
||||
repeat(left) { asmgen.out(" lda P8ESTACK_HI+1,x | asl a | ror P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x") }
|
||||
else
|
||||
asmgen.out(" jsr math.shift_right_w_$left")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
"<<" -> {
|
||||
val amount = expr.right.constValue(program)?.number?.toInt()
|
||||
if(amount!=null) {
|
||||
translateExpressionInternal(expr.left)
|
||||
if (leftDt in ByteDatatypes) {
|
||||
if (amount <= 2)
|
||||
repeat(amount) { asmgen.out(" asl P8ESTACK_LO+1,x") }
|
||||
else {
|
||||
asmgen.out(" lda P8ESTACK_LO+1,x")
|
||||
repeat(amount) { asmgen.out(" asl a") }
|
||||
asmgen.out(" sta P8ESTACK_LO+1,x")
|
||||
}
|
||||
} else {
|
||||
var left = amount
|
||||
while (left >= 7) {
|
||||
asmgen.out(" jsr math.shift_left_w_7")
|
||||
left -= 7
|
||||
}
|
||||
if (left in 0..2)
|
||||
repeat(left) { asmgen.out(" asl P8ESTACK_LO+1,x | rol P8ESTACK_HI+1,x") }
|
||||
else
|
||||
asmgen.out(" jsr math.shift_left_w_$left")
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
"*" -> {
|
||||
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
|
||||
val leftVar = expr.left as? IdentifierReference
|
||||
val rightVar = expr.right as? IdentifierReference
|
||||
if(leftVar!=null && rightVar!=null && leftVar==rightVar)
|
||||
return translateSquared(leftVar, leftDt)
|
||||
}
|
||||
|
||||
val value = expr.right.constValue(program)
|
||||
if(value!=null) {
|
||||
if(rightDt in IntegerDatatypes) {
|
||||
val amount = value.number.toInt()
|
||||
if(amount==2) {
|
||||
// optimize x*2 common case
|
||||
translateExpressionInternal(expr.left)
|
||||
if(leftDt in ByteDatatypes) {
|
||||
asmgen.out(" asl P8ESTACK_LO+1,x")
|
||||
} else {
|
||||
asmgen.out(" asl P8ESTACK_LO+1,x | rol P8ESTACK_HI+1,x")
|
||||
}
|
||||
return
|
||||
}
|
||||
when(rightDt) {
|
||||
DataType.UBYTE -> {
|
||||
if(amount in asmgen.optimizedByteMultiplications) {
|
||||
translateExpressionInternal(expr.left)
|
||||
asmgen.out(" jsr math.stack_mul_byte_$amount")
|
||||
return
|
||||
}
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
if(amount in asmgen.optimizedByteMultiplications) {
|
||||
translateExpressionInternal(expr.left)
|
||||
asmgen.out(" jsr math.stack_mul_byte_$amount")
|
||||
return
|
||||
}
|
||||
if(amount.absoluteValue in asmgen.optimizedByteMultiplications) {
|
||||
translateExpressionInternal(expr.left)
|
||||
asmgen.out(" jsr prog8_lib.neg_b | jsr math.stack_mul_byte_${amount.absoluteValue}")
|
||||
return
|
||||
}
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
if(amount in asmgen.optimizedWordMultiplications) {
|
||||
translateExpressionInternal(expr.left)
|
||||
asmgen.out(" jsr math.stack_mul_word_$amount")
|
||||
return
|
||||
}
|
||||
}
|
||||
DataType.WORD -> {
|
||||
if(amount in asmgen.optimizedWordMultiplications) {
|
||||
translateExpressionInternal(expr.left)
|
||||
asmgen.out(" jsr math.stack_mul_word_$amount")
|
||||
return
|
||||
}
|
||||
if(amount.absoluteValue in asmgen.optimizedWordMultiplications) {
|
||||
translateExpressionInternal(expr.left)
|
||||
asmgen.out(" jsr prog8_lib.neg_w | jsr math.stack_mul_word_${amount.absoluteValue}")
|
||||
return
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"/" -> {
|
||||
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
|
||||
val rightVal = expr.right.constValue(program)?.number?.toInt()
|
||||
if(rightVal!=null && rightVal==2) {
|
||||
translateExpressionInternal(expr.left)
|
||||
when(leftDt) {
|
||||
DataType.UBYTE -> asmgen.out(" lsr P8ESTACK_LO+1,x")
|
||||
DataType.BYTE -> asmgen.out(" lda P8ESTACK_LO+1,x | asl a | ror P8ESTACK_LO+1,x")
|
||||
DataType.UWORD -> asmgen.out(" lsr P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x")
|
||||
DataType.WORD -> asmgen.out(" lda P8ESTACK_HI+1,x | asl a | ror P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x")
|
||||
else -> throw AssemblyError("wrong dt")
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if((leftDt in ByteDatatypes && rightDt !in ByteDatatypes)
|
||||
|| (leftDt in WordDatatypes && rightDt !in WordDatatypes))
|
||||
throw AssemblyError("binary operator ${expr.operator} left/right dt not identical")
|
||||
|
||||
if(leftDt==DataType.STR && rightDt==DataType.STR && expr.operator in ComparisonOperators) {
|
||||
translateCompareStrings(expr.left, expr.operator, expr.right)
|
||||
}
|
||||
else {
|
||||
// the general, non-optimized cases TODO optimize more cases.... (or one day just don't use the evalstack at all anymore)
|
||||
translateExpressionInternal(expr.left)
|
||||
translateExpressionInternal(expr.right)
|
||||
when (leftDt) {
|
||||
in ByteDatatypes -> translateBinaryOperatorBytes(expr.operator, leftDt)
|
||||
in WordDatatypes -> translateBinaryOperatorWords(expr.operator, leftDt)
|
||||
DataType.FLOAT -> translateBinaryOperatorFloats(expr.operator)
|
||||
else -> throw AssemblyError("non-numerical datatype")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateSquared(variable: IdentifierReference, dt: DataType) {
|
||||
val asmVar = asmgen.asmVariableName(variable)
|
||||
when(dt) {
|
||||
DataType.BYTE, DataType.UBYTE -> {
|
||||
asmgen.out(" lda $asmVar")
|
||||
asmgen.signExtendAYlsb(dt)
|
||||
asmgen.out(" jsr math.square")
|
||||
}
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
asmgen.out(" lda $asmVar | ldy $asmVar+1 | jsr math.square")
|
||||
}
|
||||
else -> throw AssemblyError("require integer dt for square")
|
||||
}
|
||||
asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
|
||||
}
|
||||
|
||||
private fun translateExpression(expr: PrefixExpression) {
|
||||
translateExpressionInternal(expr.expression)
|
||||
val itype = expr.inferType(program)
|
||||
val type = itype.getOrElse { throw AssemblyError("unknown dt") }
|
||||
when(expr.operator) {
|
||||
"+" -> {}
|
||||
"-" -> {
|
||||
when(type) {
|
||||
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.neg_b")
|
||||
in WordDatatypes -> asmgen.out(" jsr prog8_lib.neg_w")
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.neg_f")
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
"~" -> {
|
||||
when(type) {
|
||||
in ByteDatatypes ->
|
||||
asmgen.out("""
|
||||
lda P8ESTACK_LO+1,x
|
||||
eor #255
|
||||
sta P8ESTACK_LO+1,x
|
||||
""")
|
||||
in WordDatatypes -> asmgen.out(" jsr prog8_lib.inv_word")
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
"not" -> {
|
||||
when(type) {
|
||||
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.not_byte")
|
||||
in WordDatatypes -> asmgen.out(" jsr prog8_lib.not_word")
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("invalid prefix operator ${expr.operator}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateExpression(arrayExpr: ArrayIndexedExpression) {
|
||||
val elementIDt = arrayExpr.inferType(program)
|
||||
if(!elementIDt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
val elementDt = elementIDt.getOr(DataType.UNDEFINED)
|
||||
val arrayVarName = asmgen.asmVariableName(arrayExpr.arrayvar)
|
||||
val constIndexNum = arrayExpr.indexer.constIndex()
|
||||
if(constIndexNum!=null) {
|
||||
val indexValue = constIndexNum * program.memsizer.memorySize(elementDt)
|
||||
when(elementDt) {
|
||||
in ByteDatatypes -> {
|
||||
asmgen.out(" lda $arrayVarName+$indexValue | sta P8ESTACK_LO,x | dex")
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
asmgen.out(" lda $arrayVarName+$indexValue | sta P8ESTACK_LO,x | lda $arrayVarName+$indexValue+1 | sta P8ESTACK_HI,x | dex")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out(" lda #<$arrayVarName+$indexValue | ldy #>$arrayVarName+$indexValue | jsr floats.push_float")
|
||||
}
|
||||
else -> throw AssemblyError("weird element type")
|
||||
}
|
||||
} else {
|
||||
when(elementDt) {
|
||||
in ByteDatatypes -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(arrayExpr, elementDt, CpuRegister.Y)
|
||||
asmgen.out(" lda $arrayVarName,y | sta P8ESTACK_LO,x | dex")
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(arrayExpr, elementDt, CpuRegister.Y)
|
||||
asmgen.out(" lda $arrayVarName,y | sta P8ESTACK_LO,x | lda $arrayVarName+1,y | sta P8ESTACK_HI,x | dex")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(arrayExpr, elementDt, CpuRegister.A)
|
||||
asmgen.out("""
|
||||
ldy #>$arrayVarName
|
||||
clc
|
||||
adc #<$arrayVarName
|
||||
bcc +
|
||||
iny
|
||||
+ jsr floats.push_float""")
|
||||
}
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateBinaryOperatorBytes(operator: String, types: DataType) {
|
||||
when(operator) {
|
||||
"**" -> throw AssemblyError("** operator requires floats")
|
||||
"*" -> asmgen.out(" jsr prog8_lib.mul_byte") // the optimized routines should have been checked earlier
|
||||
"/" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.idiv_ub" else " jsr prog8_lib.idiv_b")
|
||||
"%" -> {
|
||||
if(types==DataType.BYTE)
|
||||
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
|
||||
asmgen.out(" jsr prog8_lib.remainder_ub")
|
||||
}
|
||||
"+" -> asmgen.out("""
|
||||
lda P8ESTACK_LO+2,x
|
||||
clc
|
||||
adc P8ESTACK_LO+1,x
|
||||
inx
|
||||
sta P8ESTACK_LO+1,x
|
||||
""")
|
||||
"-" -> asmgen.out("""
|
||||
lda P8ESTACK_LO+2,x
|
||||
sec
|
||||
sbc P8ESTACK_LO+1,x
|
||||
inx
|
||||
sta P8ESTACK_LO+1,x
|
||||
""")
|
||||
"<<" -> asmgen.out(" jsr prog8_lib.shiftleft_b")
|
||||
">>" -> asmgen.out(" jsr prog8_lib.shiftright_b")
|
||||
"<" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.less_ub" else " jsr prog8_lib.less_b")
|
||||
">" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.greater_ub" else " jsr prog8_lib.greater_b")
|
||||
"<=" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.lesseq_ub" else " jsr prog8_lib.lesseq_b")
|
||||
">=" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.greatereq_ub" else " jsr prog8_lib.greatereq_b")
|
||||
"==" -> asmgen.out(" jsr prog8_lib.equal_b")
|
||||
"!=" -> asmgen.out(" jsr prog8_lib.notequal_b")
|
||||
"&" -> asmgen.out(" jsr prog8_lib.bitand_b")
|
||||
"^" -> asmgen.out(" jsr prog8_lib.bitxor_b")
|
||||
"|" -> asmgen.out(" jsr prog8_lib.bitor_b")
|
||||
"and" -> asmgen.out(" jsr prog8_lib.and_b")
|
||||
"or" -> asmgen.out(" jsr prog8_lib.or_b")
|
||||
"xor" -> asmgen.out(" jsr prog8_lib.xor_b")
|
||||
else -> throw AssemblyError("invalid operator $operator")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateBinaryOperatorWords(operator: String, dt: DataType) {
|
||||
when(operator) {
|
||||
"**" -> throw AssemblyError("** operator requires floats")
|
||||
"*" -> asmgen.out(" jsr prog8_lib.mul_word")
|
||||
"/" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.idiv_uw" else " jsr prog8_lib.idiv_w")
|
||||
"%" -> {
|
||||
if(dt==DataType.WORD)
|
||||
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
|
||||
asmgen.out(" jsr prog8_lib.remainder_uw")
|
||||
}
|
||||
"+" -> asmgen.out(" jsr prog8_lib.add_w")
|
||||
"-" -> asmgen.out(" jsr prog8_lib.sub_w")
|
||||
"<<" -> asmgen.out(" jsr math.shift_left_w")
|
||||
">>" -> {
|
||||
if(dt==DataType.UWORD)
|
||||
asmgen.out(" jsr math.shift_right_uw")
|
||||
else
|
||||
asmgen.out(" jsr math.shift_right_w")
|
||||
}
|
||||
"<" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.less_uw" else " jsr prog8_lib.less_w")
|
||||
">" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.greater_uw" else " jsr prog8_lib.greater_w")
|
||||
"<=" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.lesseq_uw" else " jsr prog8_lib.lesseq_w")
|
||||
">=" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.greatereq_uw" else " jsr prog8_lib.greatereq_w")
|
||||
"==" -> asmgen.out(" jsr prog8_lib.equal_w")
|
||||
"!=" -> asmgen.out(" jsr prog8_lib.notequal_w") "&" -> asmgen.out(" jsr prog8_lib.bitand_w")
|
||||
"^" -> asmgen.out(" jsr prog8_lib.bitxor_w")
|
||||
"|" -> asmgen.out(" jsr prog8_lib.bitor_w")
|
||||
"and" -> asmgen.out(" jsr prog8_lib.and_w")
|
||||
"or" -> asmgen.out(" jsr prog8_lib.or_w")
|
||||
"xor" -> asmgen.out(" jsr prog8_lib.xor_w")
|
||||
else -> throw AssemblyError("invalid operator $operator")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateBinaryOperatorFloats(operator: String) {
|
||||
when(operator) {
|
||||
"**" -> asmgen.out(" jsr floats.pow_f")
|
||||
"*" -> asmgen.out(" jsr floats.mul_f")
|
||||
"/" -> asmgen.out(" jsr floats.div_f")
|
||||
"+" -> asmgen.out(" jsr floats.add_f")
|
||||
"-" -> asmgen.out(" jsr floats.sub_f")
|
||||
"<" -> asmgen.out(" jsr floats.less_f")
|
||||
">" -> asmgen.out(" jsr floats.greater_f")
|
||||
"<=" -> asmgen.out(" jsr floats.lesseq_f")
|
||||
">=" -> asmgen.out(" jsr floats.greatereq_f")
|
||||
"==" -> asmgen.out(" jsr floats.equal_f")
|
||||
"!=" -> asmgen.out(" jsr floats.notequal_f")
|
||||
"%", "<<", ">>", "&", "^", "|", "and", "or", "xor" -> throw AssemblyError("requires integer datatype")
|
||||
else -> throw AssemblyError("invalid operator $operator")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateCompareStrings(s1: Expression, operator: String, s2: Expression) {
|
||||
asmgen.assignExpressionToVariable(s1, "prog8_lib.strcmp_expression._arg_s1", DataType.UWORD, null)
|
||||
asmgen.assignExpressionToVariable(s2, "prog8_lib.strcmp_expression._arg_s2", DataType.UWORD, null)
|
||||
asmgen.out(" jsr prog8_lib.strcmp_expression") // result of compare is in A
|
||||
when(operator) {
|
||||
"==" -> asmgen.out(" and #1 | eor #1 | sta P8ESTACK_LO,x")
|
||||
"!=" -> asmgen.out(" and #1 | sta P8ESTACK_LO,x")
|
||||
"<=" -> asmgen.out("""
|
||||
bpl +
|
||||
lda #1
|
||||
bne ++
|
||||
+ lda #0
|
||||
+ sta P8ESTACK_LO,x""")
|
||||
">=" -> asmgen.out("""
|
||||
bmi +
|
||||
lda #1
|
||||
bne ++
|
||||
+ lda #0
|
||||
+ sta P8ESTACK_LO,x""")
|
||||
"<" -> asmgen.out("""
|
||||
bmi +
|
||||
lda #0
|
||||
beq ++
|
||||
+ lda #1
|
||||
+ sta P8ESTACK_LO,x""")
|
||||
">" -> asmgen.out("""
|
||||
bpl +
|
||||
lda #0
|
||||
beq ++
|
||||
+ lda #1
|
||||
+ sta P8ESTACK_LO,x""")
|
||||
}
|
||||
asmgen.out(" dex")
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package prog8.compiler.target.cpu6502.codegen
|
||||
package prog8.codegen.target.cpu6502.codegen
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.ArrayToElementTypes
|
||||
@ -8,8 +8,7 @@ import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.RangeExpr
|
||||
import prog8.ast.statements.ForLoop
|
||||
import prog8.ast.toHex
|
||||
import prog8.compiler.target.AssemblyError
|
||||
import prog8.compilerinterface.toConstantIntegerRange
|
||||
import prog8.codegen.target.AssemblyError
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||
@ -22,13 +21,13 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
|
||||
is RangeExpr -> {
|
||||
val range = (stmt.iterable as RangeExpr).toConstantIntegerRange()
|
||||
if(range==null) {
|
||||
translateForOverNonconstRange(stmt, iterableDt.getOr(DataType.UNDEFINED), stmt.iterable as RangeExpr)
|
||||
translateForOverNonconstRange(stmt, iterableDt.getOrElse { throw AssemblyError("unknown dt") }, stmt.iterable as RangeExpr)
|
||||
} else {
|
||||
translateForOverConstRange(stmt, iterableDt.getOr(DataType.UNDEFINED), range)
|
||||
translateForOverConstRange(stmt, iterableDt.getOrElse { throw AssemblyError("unknown dt") }, range)
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
translateForOverIterableVar(stmt, iterableDt.getOr(DataType.UNDEFINED), stmt.iterable as IdentifierReference)
|
||||
translateForOverIterableVar(stmt, iterableDt.getOrElse { throw AssemblyError("unknown dt") }, stmt.iterable as IdentifierReference)
|
||||
}
|
||||
else -> throw AssemblyError("can't iterate over ${stmt.iterable.javaClass} - should have been replaced by a variable")
|
||||
}
|
||||
@ -43,7 +42,7 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
|
||||
val stepsize=range.step.constValue(program)!!.number.toInt()
|
||||
|
||||
if(stepsize < -1) {
|
||||
val limit = range.to.constValue(program)?.number?.toDouble()
|
||||
val limit = range.to.constValue(program)?.number
|
||||
if(limit==0.0)
|
||||
throw AssemblyError("for unsigned loop variable it's not possible to count down with step != -1 from a non-const value to exactly zero due to value wrapping")
|
||||
}
|
||||
@ -589,5 +588,9 @@ $loopLabel""")
|
||||
}
|
||||
|
||||
private fun assignLoopvar(stmt: ForLoop, range: RangeExpr) =
|
||||
asmgen.assignExpressionToVariable(range.from, asmgen.asmVariableName(stmt.loopVar), stmt.loopVarDt(program).getOr(DataType.UNDEFINED), stmt.definingSubroutine)
|
||||
asmgen.assignExpressionToVariable(
|
||||
range.from,
|
||||
asmgen.asmVariableName(stmt.loopVar),
|
||||
stmt.loopVarDt(program).getOrElse { throw AssemblyError("unknown dt") },
|
||||
stmt.definingSubroutine)
|
||||
}
|
@ -1,19 +1,16 @@
|
||||
package prog8.compiler.target.cpu6502.codegen
|
||||
package prog8.codegen.target.cpu6502.codegen
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.InlineAssembly
|
||||
import prog8.ast.statements.RegisterOrStatusflag
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.statements.SubroutineParameter
|
||||
import prog8.compiler.target.AssemblyError
|
||||
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignSource
|
||||
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignTarget
|
||||
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignment
|
||||
import prog8.compiler.target.cpu6502.codegen.assignment.TargetStorageKind
|
||||
import prog8.ast.statements.*
|
||||
import prog8.codegen.target.AssemblyError
|
||||
import prog8.codegen.target.cpu6502.codegen.assignment.AsmAssignSource
|
||||
import prog8.codegen.target.cpu6502.codegen.assignment.AsmAssignTarget
|
||||
import prog8.codegen.target.cpu6502.codegen.assignment.AsmAssignment
|
||||
import prog8.codegen.target.cpu6502.codegen.assignment.TargetStorageKind
|
||||
import prog8.compilerinterface.CpuType
|
||||
|
||||
|
||||
@ -21,7 +18,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
|
||||
internal fun translateFunctionCallStatement(stmt: IFunctionCall) {
|
||||
saveXbeforeCall(stmt)
|
||||
translateFunctionCall(stmt)
|
||||
translateFunctionCall(stmt, false)
|
||||
restoreXafterCall(stmt)
|
||||
// just ignore any result values from the function call.
|
||||
}
|
||||
@ -37,6 +34,17 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
}
|
||||
}
|
||||
|
||||
internal fun saveXbeforeCall(gosub: GoSub) {
|
||||
val sub = gosub.identifier?.targetSubroutine(program)
|
||||
if(sub?.shouldSaveX()==true) {
|
||||
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
|
||||
if(regSaveOnStack)
|
||||
asmgen.saveRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnEntry)
|
||||
else
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, gosub.definingSubroutine!!)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun restoreXafterCall(stmt: IFunctionCall) {
|
||||
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
|
||||
if(sub.shouldSaveX()) {
|
||||
@ -48,90 +56,97 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
}
|
||||
}
|
||||
|
||||
internal fun translateFunctionCall(stmt: IFunctionCall) {
|
||||
internal fun restoreXafterCall(gosub: GoSub) {
|
||||
val sub = gosub.identifier?.targetSubroutine(program)
|
||||
if(sub?.shouldSaveX()==true) {
|
||||
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
|
||||
if(regSaveOnStack)
|
||||
asmgen.restoreRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnReturn)
|
||||
else
|
||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun optimizeIntArgsViaRegisters(sub: Subroutine) =
|
||||
(sub.parameters.size==1 && sub.parameters[0].type in IntegerDatatypes)
|
||||
|| (sub.parameters.size==2 && sub.parameters[0].type in ByteDatatypes && sub.parameters[1].type in ByteDatatypes)
|
||||
|
||||
internal fun translateFunctionCall(call: IFunctionCall, isExpression: Boolean) {
|
||||
// Output only the code to set up the parameters and perform the actual call
|
||||
// NOTE: does NOT output the code to deal with the result values!
|
||||
// NOTE: does NOT output code to save/restore the X register for this call! Every caller should deal with this in their own way!!
|
||||
// (you can use subroutine.shouldSaveX() and saveX()/restoreX() routines as a help for this)
|
||||
|
||||
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
|
||||
val subName = asmgen.asmSymbolName(stmt.target)
|
||||
if(stmt.args.isNotEmpty()) {
|
||||
val sub = call.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${call.target}")
|
||||
val subAsmName = asmgen.asmSymbolName(call.target)
|
||||
|
||||
if(sub.asmParameterRegisters.isEmpty()) {
|
||||
// via variables
|
||||
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
|
||||
argumentViaVariable(sub, arg.first, arg.second)
|
||||
}
|
||||
} else {
|
||||
require(sub.isAsmSubroutine)
|
||||
if(sub.parameters.size==1) {
|
||||
// just a single parameter, no risk of clobbering registers
|
||||
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single()), stmt.args[0])
|
||||
} else {
|
||||
|
||||
fun isNoClobberRisk(expr: Expression): Boolean {
|
||||
if(expr is AddressOf ||
|
||||
expr is NumericLiteralValue ||
|
||||
expr is StringLiteralValue ||
|
||||
expr is ArrayLiteralValue ||
|
||||
expr is IdentifierReference)
|
||||
return true
|
||||
|
||||
if(expr is FunctionCall) {
|
||||
if(expr.target.nameInSource==listOf("lsb") || expr.target.nameInSource==listOf("msb"))
|
||||
return isNoClobberRisk(expr.args[0])
|
||||
if(expr.target.nameInSource==listOf("mkword"))
|
||||
return isNoClobberRisk(expr.args[0]) && isNoClobberRisk(expr.args[1])
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
when {
|
||||
stmt.args.all {isNoClobberRisk(it)} -> {
|
||||
// There's no risk of clobbering for these simple argument types. Optimize the register loading directly from these values.
|
||||
// register assignment order: 1) cx16 virtual word registers, 2) actual CPU registers, 3) CPU Carry status flag.
|
||||
val argsInfo = sub.parameters.withIndex().zip(stmt.args).zip(sub.asmParameterRegisters)
|
||||
val (cx16virtualRegs, args2) = argsInfo.partition { it.second.registerOrPair in Cx16VirtualRegisters }
|
||||
val (cpuRegs, statusRegs) = args2.partition { it.second.registerOrPair!=null }
|
||||
for(arg in cx16virtualRegs)
|
||||
argumentViaRegister(sub, arg.first.first, arg.first.second)
|
||||
for(arg in cpuRegs)
|
||||
argumentViaRegister(sub, arg.first.first, arg.first.second)
|
||||
for(arg in statusRegs)
|
||||
argumentViaRegister(sub, arg.first.first, arg.first.second)
|
||||
}
|
||||
else -> {
|
||||
// Risk of clobbering due to complex expression args. Evaluate first, then assign registers.
|
||||
registerArgsViaStackEvaluation(stmt, sub)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!isExpression && !sub.isAsmSubroutine) {
|
||||
if(!optimizeIntArgsViaRegisters(sub))
|
||||
throw AssemblyError("functioncall statements to non-asmsub should have been replaced by GoSub $call")
|
||||
}
|
||||
|
||||
if(!sub.inline || !asmgen.options.optimize) {
|
||||
asmgen.out(" jsr $subName")
|
||||
} else {
|
||||
// inline the subroutine.
|
||||
// we do this by copying the subroutine's statements at the call site.
|
||||
// NOTE: *if* there is a return statement, it will be the only one, and the very last statement of the subroutine
|
||||
// (this condition has been enforced by an ast check earlier)
|
||||
|
||||
// note: for now, this is only reliably supported for asmsubs.
|
||||
if(!sub.isAsmSubroutine)
|
||||
if(sub.isAsmSubroutine) {
|
||||
argumentsViaRegisters(sub, call)
|
||||
if (sub.inline && asmgen.options.optimize) {
|
||||
// inline the subroutine.
|
||||
// we do this by copying the subroutine's statements at the call site.
|
||||
// NOTE: *if* there is a return statement, it will be the only one, and the very last statement of the subroutine
|
||||
// (this condition has been enforced by an ast check earlier)
|
||||
asmgen.out(" \t; inlined routine follows: ${sub.name}")
|
||||
val assembly = sub.statements.single() as InlineAssembly
|
||||
asmgen.translate(assembly)
|
||||
asmgen.out(" \t; inlined routine end: ${sub.name}")
|
||||
} else {
|
||||
asmgen.out(" jsr $subAsmName")
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(sub.inline)
|
||||
throw AssemblyError("can only reliably inline asmsub routines at this time")
|
||||
|
||||
asmgen.out(" \t; inlined routine follows: ${sub.name}")
|
||||
val assembly = sub.statements.single() as InlineAssembly
|
||||
asmgen.translate(assembly)
|
||||
asmgen.out(" \t; inlined routine end: ${sub.name}")
|
||||
if(optimizeIntArgsViaRegisters(sub)) {
|
||||
if(sub.parameters.size==1) {
|
||||
val register = if (sub.parameters[0].type in ByteDatatypes) RegisterOrPair.A else RegisterOrPair.AY
|
||||
argumentViaRegister(sub, IndexedValue(0, sub.parameters[0]), call.args[0], register)
|
||||
} else {
|
||||
// 2 byte params, second in Y, first in A
|
||||
argumentViaRegister(sub, IndexedValue(0, sub.parameters[0]), call.args[0], RegisterOrPair.A)
|
||||
if(!call.args[1].isSimple)
|
||||
asmgen.out(" pha")
|
||||
argumentViaRegister(sub, IndexedValue(1, sub.parameters[1]), call.args[1], RegisterOrPair.Y)
|
||||
if(!call.args[1].isSimple)
|
||||
asmgen.out(" pla")
|
||||
}
|
||||
} else {
|
||||
argumentsViaVariables(sub, call)
|
||||
}
|
||||
asmgen.out(" jsr $subAsmName")
|
||||
}
|
||||
|
||||
// remember: dealing with the X register and/or dealing with return values is the responsibility of the caller
|
||||
}
|
||||
|
||||
private fun argumentsViaVariables(sub: Subroutine, call: IFunctionCall) {
|
||||
for(arg in sub.parameters.withIndex().zip(call.args))
|
||||
argumentViaVariable(sub, arg.first, arg.second)
|
||||
}
|
||||
|
||||
private fun argumentsViaRegisters(sub: Subroutine, call: IFunctionCall) {
|
||||
if(sub.parameters.size==1) {
|
||||
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single()), call.args[0])
|
||||
} else {
|
||||
if(asmgen.asmsubArgsHaveRegisterClobberRisk(call.args, sub.asmParameterRegisters)) {
|
||||
registerArgsViaStackEvaluation(call, sub)
|
||||
} else {
|
||||
asmgen.asmsubArgsEvalOrder(sub).forEach {
|
||||
val param = sub.parameters[it]
|
||||
val arg = call.args[it]
|
||||
argumentViaRegister(sub, IndexedValue(it, param), arg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun registerArgsViaStackEvaluation(stmt: IFunctionCall, sub: Subroutine) {
|
||||
// this is called when one or more of the arguments are 'complex' and
|
||||
// cannot be assigned to a register easily or risk clobbering other registers.
|
||||
@ -220,11 +235,10 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
if(argForCarry!=null) {
|
||||
val plusIdxStr = if(argForCarry.index==0) "" else "+${argForCarry.index}"
|
||||
asmgen.out("""
|
||||
clc
|
||||
lda P8ESTACK_LO$plusIdxStr,x
|
||||
beq +
|
||||
sec
|
||||
bcs ++
|
||||
+ clc
|
||||
+ php""") // push the status flags
|
||||
}
|
||||
|
||||
@ -261,26 +275,22 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
private fun argumentViaVariable(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
|
||||
// pass parameter via a regular variable (not via registers)
|
||||
val valueIDt = value.inferType(program)
|
||||
if(!valueIDt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
val valueDt = valueIDt.getOr(DataType.UNDEFINED)
|
||||
val valueDt = valueIDt.getOrElse { throw AssemblyError("unknown dt") }
|
||||
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
|
||||
throw AssemblyError("argument type incompatible")
|
||||
|
||||
val varName = asmgen.asmVariableName(sub.scopedname+"."+parameter.value.name)
|
||||
val varName = asmgen.asmVariableName(sub.scopedName + parameter.value.name)
|
||||
asmgen.assignExpressionToVariable(value, varName, parameter.value.type, sub)
|
||||
}
|
||||
|
||||
private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
|
||||
private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression, registerOverride: RegisterOrPair? = null) {
|
||||
// pass argument via a register parameter
|
||||
val valueIDt = value.inferType(program)
|
||||
if(!valueIDt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
val valueDt = valueIDt.getOr(DataType.UNDEFINED)
|
||||
val valueDt = valueIDt.getOrElse { throw AssemblyError("unknown dt") }
|
||||
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
|
||||
throw AssemblyError("argument type incompatible")
|
||||
|
||||
val paramRegister = sub.asmParameterRegisters[parameter.index]
|
||||
val paramRegister = if(registerOverride==null) sub.asmParameterRegisters[parameter.index] else RegisterOrStatusflag(registerOverride, null)
|
||||
val statusflag = paramRegister.statusflag
|
||||
val register = paramRegister.registerOrPair
|
||||
val requiredDt = parameter.value.type
|
||||
@ -302,23 +312,21 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
is IdentifierReference -> {
|
||||
val sourceName = asmgen.asmVariableName(value)
|
||||
asmgen.out("""
|
||||
pha
|
||||
lda $sourceName
|
||||
beq +
|
||||
sec
|
||||
bcs ++
|
||||
+ clc
|
||||
+ pla
|
||||
""")
|
||||
pha
|
||||
clc
|
||||
lda $sourceName
|
||||
beq +
|
||||
sec
|
||||
+ pla""")
|
||||
}
|
||||
else -> {
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
|
||||
asmgen.out("""
|
||||
beq +
|
||||
sec
|
||||
bcs ++
|
||||
+ clc
|
||||
+""")
|
||||
beq +
|
||||
sec
|
||||
bcs ++
|
||||
+ clc
|
||||
+""")
|
||||
}
|
||||
}
|
||||
} else throw AssemblyError("can only use Carry as status flag parameter")
|
||||
@ -335,8 +343,10 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
val target: AsmAssignTarget =
|
||||
if(parameter.value.type in ByteDatatypes && (register==RegisterOrPair.AX || register == RegisterOrPair.AY || register==RegisterOrPair.XY || register in Cx16VirtualRegisters))
|
||||
AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, parameter.value.type, sub, register = register)
|
||||
else
|
||||
AsmAssignTarget.fromRegisters(register, false, sub, program, asmgen)
|
||||
else {
|
||||
val signed = parameter.value.type == DataType.BYTE || parameter.value.type == DataType.WORD
|
||||
AsmAssignTarget.fromRegisters(register, signed, sub, program, asmgen)
|
||||
}
|
||||
val src = if(valueDt in PassByReferenceDatatypes) {
|
||||
if(value is IdentifierReference) {
|
||||
val addr = AddressOf(value, Position.DUMMY)
|
@ -1,4 +1,4 @@
|
||||
package prog8.compiler.target.cpu6502.codegen
|
||||
package prog8.codegen.target.cpu6502.codegen
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
@ -6,7 +6,7 @@ import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.statements.PostIncrDecr
|
||||
import prog8.ast.toHex
|
||||
import prog8.compiler.target.AssemblyError
|
||||
import prog8.codegen.target.AssemblyError
|
||||
|
||||
|
||||
internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
@ -1,12 +1,12 @@
|
||||
package prog8.compiler.target.cpu6502.codegen.assignment
|
||||
package prog8.codegen.target.cpu6502.codegen.assignment
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compilerinterface.IMemSizer
|
||||
import prog8.compiler.target.AssemblyError
|
||||
import prog8.compiler.target.cpu6502.codegen.AsmGen
|
||||
import prog8.codegen.target.AssemblyError
|
||||
import prog8.codegen.target.cpu6502.codegen.AsmGen
|
||||
|
||||
|
||||
internal enum class TargetStorageKind {
|
||||
@ -39,8 +39,8 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
|
||||
val origAstTarget: AssignTarget? = null
|
||||
)
|
||||
{
|
||||
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0}
|
||||
val constArrayIndexValue by lazy { array?.indexer?.constIndex() }
|
||||
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toUInt() ?: 0u}
|
||||
val constArrayIndexValue by lazy { array?.indexer?.constIndex()?.toUInt() }
|
||||
val asmVarname: String by lazy {
|
||||
if (array == null)
|
||||
variableAsmName!!
|
||||
@ -56,16 +56,29 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromAstAssignment(assign: Assignment, program: Program, asmgen: AsmGen): AsmAssignTarget = with(assign.target) {
|
||||
val idt = inferType(program)
|
||||
if(!idt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
val dt = idt.getOr(DataType.UNDEFINED)
|
||||
when {
|
||||
identifier != null -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, assign.definingSubroutine, variableAsmName = asmgen.asmVariableName(identifier!!), origAstTarget = this)
|
||||
arrayindexed != null -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, dt, assign.definingSubroutine, array = arrayindexed, origAstTarget = this)
|
||||
memoryAddress != null -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, dt, assign.definingSubroutine, memory = memoryAddress, origAstTarget = this)
|
||||
else -> throw AssemblyError("weird target")
|
||||
fun fromAstAssignment(assign: Assignment, program: Program, asmgen: AsmGen): AsmAssignTarget {
|
||||
with(assign.target) {
|
||||
val idt = inferType(program)
|
||||
val dt = idt.getOrElse { throw AssemblyError("unknown dt") }
|
||||
when {
|
||||
identifier != null -> {
|
||||
val parameter = identifier!!.targetVarDecl(program)?.subroutineParameter
|
||||
if (parameter!=null) {
|
||||
val sub = parameter.definingSubroutine!!
|
||||
if (sub.isAsmSubroutine) {
|
||||
val reg = sub.asmParameterRegisters[sub.parameters.indexOf(parameter)]
|
||||
if(reg.statusflag!=null)
|
||||
throw AssemblyError("can't assign value to processor statusflag directly")
|
||||
else
|
||||
return AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, dt, assign.definingSubroutine, register=reg.registerOrPair, origAstTarget = this)
|
||||
}
|
||||
}
|
||||
return AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, assign.definingSubroutine, variableAsmName = asmgen.asmVariableName(identifier!!), origAstTarget = this)
|
||||
}
|
||||
arrayindexed != null -> return AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, dt, assign.definingSubroutine, array = arrayindexed, origAstTarget = this)
|
||||
memoryAddress != null -> return AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, dt, assign.definingSubroutine, memory = memoryAddress, origAstTarget = this)
|
||||
else -> throw AssemblyError("weird target")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,8 +124,8 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||
val expression: Expression? = null
|
||||
)
|
||||
{
|
||||
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0}
|
||||
val constArrayIndexValue by lazy { array?.indexer?.constIndex() }
|
||||
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toUInt() ?: 0u}
|
||||
val constArrayIndexValue by lazy { array?.indexer?.constIndex()?.toUInt() }
|
||||
|
||||
val asmVarname: String
|
||||
get() = if(array==null)
|
||||
@ -129,10 +142,13 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||
return AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, cv.type, number = cv)
|
||||
|
||||
return when(value) {
|
||||
is NumericLiteralValue -> AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, value.type, number = cv)
|
||||
is NumericLiteralValue -> throw AssemblyError("should have been constant value")
|
||||
is StringLiteralValue -> throw AssemblyError("string literal value should not occur anymore for asm generation")
|
||||
is ArrayLiteralValue -> throw AssemblyError("array literal value should not occur anymore for asm generation")
|
||||
is IdentifierReference -> {
|
||||
val parameter = value.targetVarDecl(program)?.subroutineParameter
|
||||
if(parameter!=null && parameter.definingSubroutine!!.isAsmSubroutine)
|
||||
throw AssemblyError("can't assign from a asmsub register parameter $value ${value.position}")
|
||||
val dt = value.inferType(program).getOr(DataType.UNDEFINED)
|
||||
val varName=asmgen.asmVariableName(value)
|
||||
// special case: "cx16.r[0-15]" are 16-bits virtual registers of the commander X16 system
|
||||
@ -148,10 +164,10 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||
AsmAssignSource(SourceStorageKind.MEMORY, program, asmgen, DataType.UBYTE, memory = value)
|
||||
}
|
||||
is ArrayIndexedExpression -> {
|
||||
val dt = value.inferType(program).getOr(DataType.UNDEFINED)
|
||||
val dt = value.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
|
||||
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, dt, array = value)
|
||||
}
|
||||
is FunctionCall -> {
|
||||
is FunctionCallExpr -> {
|
||||
when (val sub = value.target.targetStatement(program)) {
|
||||
is Subroutine -> {
|
||||
val returnType = sub.returntypes.zip(sub.asmReturnvaluesRegisters).firstOrNull { rr -> rr.second.registerOrPair != null || rr.second.statusflag!=null }?.first
|
||||
@ -159,11 +175,9 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value)
|
||||
}
|
||||
is BuiltinFunctionStatementPlaceholder -> {
|
||||
is BuiltinFunctionPlaceholder -> {
|
||||
val returnType = value.inferType(program)
|
||||
if(!returnType.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType.getOr(DataType.UNDEFINED), expression = value)
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType.getOrElse { throw AssemblyError("unknown dt") }, expression = value)
|
||||
}
|
||||
else -> {
|
||||
throw AssemblyError("weird call")
|
||||
@ -172,9 +186,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||
}
|
||||
else -> {
|
||||
val dt = value.inferType(program)
|
||||
if(!dt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, dt.getOr(DataType.UNDEFINED), expression = value)
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, dt.getOrElse { throw AssemblyError("unknown dt") }, expression = value)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -209,7 +221,7 @@ internal class AsmAssignment(val source: AsmAssignSource,
|
||||
if(target.register !in arrayOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY))
|
||||
require(source.datatype != DataType.UNDEFINED) { "must not be placeholder/undefined datatype" }
|
||||
require(memsizer.memorySize(source.datatype) <= memsizer.memorySize(target.datatype)) {
|
||||
"source storage size must be less or equal to target datatype storage size"
|
||||
"source dt size must be less or equal to target dt size at $position"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,23 +1,20 @@
|
||||
package prog8.compiler.target.cpu6502.codegen.assignment
|
||||
package prog8.codegen.target.cpu6502.codegen.assignment
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.toHex
|
||||
import prog8.compiler.target.AssemblyError
|
||||
import prog8.compiler.target.cpu6502.codegen.AsmGen
|
||||
import prog8.compiler.target.cpu6502.codegen.ExpressionsAsmGen
|
||||
import prog8.codegen.target.AssemblyError
|
||||
import prog8.codegen.target.cpu6502.codegen.AsmGen
|
||||
import prog8.compilerinterface.BuiltinFunctions
|
||||
import prog8.compilerinterface.CpuType
|
||||
import prog8.compilerinterface.builtinFunctionReturnType
|
||||
|
||||
|
||||
internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen,
|
||||
private val exprAsmgen: ExpressionsAsmGen
|
||||
) {
|
||||
internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||
|
||||
private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, this, exprAsmgen, asmgen)
|
||||
private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, this, asmgen)
|
||||
|
||||
fun translate(assignment: Assignment) {
|
||||
val target = AsmAssignTarget.fromAstAssignment(assignment, program, asmgen)
|
||||
@ -32,6 +29,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
translateNormalAssignment(assign)
|
||||
}
|
||||
|
||||
internal fun virtualRegsToVariables(origtarget: AsmAssignTarget): AsmAssignTarget {
|
||||
return if(origtarget.kind==TargetStorageKind.REGISTER && origtarget.register in Cx16VirtualRegisters) {
|
||||
AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, origtarget.datatype, origtarget.scope,
|
||||
variableAsmName = "cx16.${origtarget.register!!.name.lowercase()}", origAstTarget = origtarget.origAstTarget)
|
||||
} else origtarget
|
||||
}
|
||||
|
||||
fun translateNormalAssignment(assign: AsmAssignment) {
|
||||
if(assign.isAugmentable) {
|
||||
augmentableAsmGen.translate(assign)
|
||||
@ -43,9 +47,9 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
// simple case: assign a constant number
|
||||
val num = assign.source.number!!.number
|
||||
when (assign.target.datatype) {
|
||||
DataType.UBYTE, DataType.BYTE -> assignConstantByte(assign.target, num.toShort())
|
||||
DataType.UBYTE, DataType.BYTE -> assignConstantByte(assign.target, num.toInt())
|
||||
DataType.UWORD, DataType.WORD -> assignConstantWord(assign.target, num.toInt())
|
||||
DataType.FLOAT -> assignConstantFloat(assign.target, num.toDouble())
|
||||
DataType.FLOAT -> assignConstantFloat(assign.target, num)
|
||||
else -> throw AssemblyError("weird numval type")
|
||||
}
|
||||
}
|
||||
@ -121,17 +125,14 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
SourceStorageKind.MEMORY -> {
|
||||
fun assignViaExprEval(expression: Expression) {
|
||||
assignExpressionToVariable(expression, "P8ZP_SCRATCH_W2", DataType.UWORD, assign.target.scope)
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" lda (P8ZP_SCRATCH_W2)")
|
||||
else
|
||||
asmgen.out(" ldy #0 | lda (P8ZP_SCRATCH_W2),y")
|
||||
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2")
|
||||
assignRegisterByte(assign.target, CpuRegister.A)
|
||||
}
|
||||
|
||||
val value = assign.source.memory!!
|
||||
when (value.addressExpression) {
|
||||
is NumericLiteralValue -> {
|
||||
val address = (value.addressExpression as NumericLiteralValue).number.toInt()
|
||||
val address = (value.addressExpression as NumericLiteralValue).number.toUInt()
|
||||
assignMemoryByte(assign.target, address, null)
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
@ -158,11 +159,11 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
is ArrayIndexedExpression -> throw AssemblyError("source kind should have been array")
|
||||
is DirectMemoryRead -> throw AssemblyError("source kind should have been memory")
|
||||
is TypecastExpression -> assignTypeCastedValue(assign.target, value.type, value.expression, value)
|
||||
is FunctionCall -> {
|
||||
is FunctionCallExpr -> {
|
||||
when (val sub = value.target.targetStatement(program)) {
|
||||
is Subroutine -> {
|
||||
asmgen.saveXbeforeCall(value)
|
||||
asmgen.translateFunctionCall(value)
|
||||
asmgen.translateFunctionCall(value, true)
|
||||
val returnValue = sub.returntypes.zip(sub.asmReturnvaluesRegisters).singleOrNull { it.second.registerOrPair!=null } ?:
|
||||
sub.returntypes.zip(sub.asmReturnvaluesRegisters).single { it.second.statusflag!=null }
|
||||
when (returnValue.first) {
|
||||
@ -214,7 +215,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
}
|
||||
}
|
||||
is BuiltinFunctionStatementPlaceholder -> {
|
||||
is BuiltinFunctionPlaceholder -> {
|
||||
val signature = BuiltinFunctions.getValue(sub.name)
|
||||
asmgen.translateBuiltinFunctionCallExpression(value, signature, false, assign.target.register)
|
||||
if(assign.target.register==null) {
|
||||
@ -261,19 +262,24 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
assign.target,
|
||||
false, program.memsizer, assign.position
|
||||
))
|
||||
val target = virtualRegsToVariables(assign.target)
|
||||
when(value.operator) {
|
||||
"+" -> {}
|
||||
"-" -> augmentableAsmGen.inplaceNegate(assign.target, assign.target.datatype)
|
||||
"~" -> augmentableAsmGen.inplaceInvert(assign.target, assign.target.datatype)
|
||||
"not" -> augmentableAsmGen.inplaceBooleanNot(assign.target, assign.target.datatype)
|
||||
"-" -> augmentableAsmGen.inplaceNegate(target, target.datatype)
|
||||
"~" -> augmentableAsmGen.inplaceInvert(target, target.datatype)
|
||||
"not" -> augmentableAsmGen.inplaceBooleanNot(target, target.datatype)
|
||||
else -> throw AssemblyError("invalid prefix operator")
|
||||
}
|
||||
}
|
||||
is ContainmentCheck -> {
|
||||
containmentCheckIntoA(value)
|
||||
assignRegisterByte(assign.target, CpuRegister.A)
|
||||
}
|
||||
else -> {
|
||||
// Everything else just evaluate via the stack.
|
||||
// (we can't use the assignment helper functions (assignExpressionTo...) to do it via registers here,
|
||||
// because the code here is the implementation of exactly that...)
|
||||
// TODO FIX THIS... by using a temp var? so that it becomes augmentable assignment expression?
|
||||
// TODO DON'T STACK-EVAL THIS... by using a temp var? so that it becomes augmentable assignment expression?
|
||||
asmgen.translateExpression(value)
|
||||
if (assign.target.datatype in WordDatatypes && assign.source.datatype in ByteDatatypes)
|
||||
asmgen.signExtendStackLsb(assign.source.datatype)
|
||||
@ -292,6 +298,124 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
}
|
||||
|
||||
private fun containmentCheckIntoA(containment: ContainmentCheck) {
|
||||
val elementDt = containment.element.inferType(program)
|
||||
val range = containment.iterable as? RangeExpr
|
||||
if(range!=null) {
|
||||
val constRange = range.toConstantIntegerRange()
|
||||
if(constRange!=null)
|
||||
return containmentCheckIntoA(containment.element, elementDt.getOr(DataType.UNDEFINED), constRange.toList())
|
||||
TODO("non-const range containment check ${containment.position}")
|
||||
}
|
||||
val variable = (containment.iterable as? IdentifierReference)?.targetVarDecl(program)
|
||||
if(variable!=null) {
|
||||
if(elementDt istype DataType.FLOAT)
|
||||
throw AssemblyError("containment check of floats not supported")
|
||||
if(variable.autogeneratedDontRemove) {
|
||||
when(variable.datatype) {
|
||||
DataType.STR -> {
|
||||
require(elementDt.isBytes)
|
||||
val stringVal = variable.value as StringLiteralValue
|
||||
val encoded = program.encoding.encodeString(stringVal.value, stringVal.altEncoding)
|
||||
return containmentCheckIntoA(containment.element, elementDt.getOr(DataType.UNDEFINED), encoded.map { it.toInt() })
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
// require(elementDt istype DataType.FLOAT)
|
||||
throw AssemblyError("containment check of floats not supported")
|
||||
}
|
||||
in ArrayDatatypes -> {
|
||||
require(elementDt.isInteger)
|
||||
val arrayVal = variable.value as ArrayLiteralValue
|
||||
val values = arrayVal.value.map { it.constValue(program)!!.number.toInt() }
|
||||
return containmentCheckIntoA(containment.element, elementDt.getOr(DataType.UNDEFINED), values)
|
||||
}
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
}
|
||||
}
|
||||
val varname = asmgen.asmVariableName(containment.iterable as IdentifierReference)
|
||||
when(variable.datatype) {
|
||||
DataType.STR -> {
|
||||
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname)
|
||||
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE)
|
||||
val stringVal = variable.value as StringLiteralValue
|
||||
asmgen.out(" ldy #${stringVal.value.length}")
|
||||
asmgen.out(" jsr prog8_lib.containment_bytearray")
|
||||
return
|
||||
}
|
||||
DataType.ARRAY_F -> throw AssemblyError("containment check of floats not supported")
|
||||
DataType.ARRAY_B, DataType.ARRAY_UB -> {
|
||||
val arrayVal = variable.value as ArrayLiteralValue
|
||||
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname)
|
||||
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE)
|
||||
asmgen.out(" ldy #${arrayVal.value.size}")
|
||||
asmgen.out(" jsr prog8_lib.containment_bytearray")
|
||||
return
|
||||
}
|
||||
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
||||
val arrayVal = variable.value as ArrayLiteralValue
|
||||
assignExpressionToVariable(containment.element, "P8ZP_SCRATCH_W1", elementDt.getOr(DataType.UNDEFINED), containment.definingSubroutine)
|
||||
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W2"), varname)
|
||||
asmgen.out(" ldy #${arrayVal.value.size}")
|
||||
asmgen.out(" jsr prog8_lib.containment_wordarray")
|
||||
return
|
||||
}
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
}
|
||||
}
|
||||
val stringVal = containment.iterable as? StringLiteralValue
|
||||
if(stringVal!=null) {
|
||||
require(elementDt.isBytes)
|
||||
val encoded = program.encoding.encodeString(stringVal.value, stringVal.altEncoding)
|
||||
return containmentCheckIntoA(containment.element, elementDt.getOr(DataType.UNDEFINED), encoded.map { it.toInt() })
|
||||
}
|
||||
val arrayVal = containment.iterable as? ArrayLiteralValue
|
||||
if(arrayVal!=null) {
|
||||
require(elementDt.isInteger)
|
||||
val values = arrayVal.value.map { it.constValue(program)!!.number.toInt() }
|
||||
return containmentCheckIntoA(containment.element, elementDt.getOr(DataType.UNDEFINED), values)
|
||||
}
|
||||
|
||||
throw AssemblyError("invalid containment iterable type")
|
||||
}
|
||||
|
||||
private fun containmentCheckIntoA(element: Expression, dt: DataType, values: List<Number>) {
|
||||
if(values.size<2)
|
||||
throw AssemblyError("containment check against 0 or 1 values should have been optimized away")
|
||||
|
||||
// TODO don't generate a huge cmp-list when we go over a certain number of values
|
||||
val containsLabel = asmgen.makeLabel("contains")
|
||||
when(dt) {
|
||||
in ByteDatatypes -> {
|
||||
asmgen.assignExpressionToRegister(element, RegisterOrPair.A, dt==DataType.BYTE)
|
||||
for (value in values) {
|
||||
asmgen.out(" cmp #$value | beq +")
|
||||
}
|
||||
asmgen.out("""
|
||||
lda #0
|
||||
beq ++
|
||||
+ lda #1
|
||||
+""")
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
asmgen.assignExpressionToRegister(element, RegisterOrPair.AY, dt==DataType.WORD)
|
||||
for (value in values) {
|
||||
asmgen.out("""
|
||||
cmp #<$value
|
||||
bne +
|
||||
cpy #>$value
|
||||
beq $containsLabel
|
||||
+""")
|
||||
}
|
||||
asmgen.out("""
|
||||
lda #0
|
||||
beq +
|
||||
$containsLabel lda #1
|
||||
+""")
|
||||
}
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignStatusFlagByte(target: AsmAssignTarget, statusflag: Statusflag) {
|
||||
when(statusflag) {
|
||||
Statusflag.Pc -> {
|
||||
@ -312,9 +436,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
|
||||
private fun assignTypeCastedValue(target: AsmAssignTarget, targetDt: DataType, value: Expression, origTypeCastExpression: TypecastExpression) {
|
||||
val valueIDt = value.inferType(program)
|
||||
if(!valueIDt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
val valueDt = valueIDt.getOr(DataType.UNDEFINED)
|
||||
val valueDt = valueIDt.getOrElse { throw AssemblyError("unknown dt") }
|
||||
if(valueDt==targetDt)
|
||||
throw AssemblyError("type cast to identical dt should have been removed")
|
||||
|
||||
@ -336,16 +458,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
|
||||
fun assignViaExprEval(addressExpression: Expression) {
|
||||
asmgen.assignExpressionToVariable(addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" lda (P8ZP_SCRATCH_W2)")
|
||||
else
|
||||
asmgen.out(" ldy #0 | lda (P8ZP_SCRATCH_W2),y")
|
||||
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2")
|
||||
assignRegisterByte(target, CpuRegister.A)
|
||||
}
|
||||
|
||||
when (value.addressExpression) {
|
||||
is NumericLiteralValue -> {
|
||||
val address = (value.addressExpression as NumericLiteralValue).number.toInt()
|
||||
val address = (value.addressExpression as NumericLiteralValue).number.toUInt()
|
||||
assignMemoryByteIntoWord(target, address, null)
|
||||
return
|
||||
}
|
||||
@ -445,13 +564,39 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
}
|
||||
|
||||
// No more special optmized cases yet. Do the rest via more complex evaluation
|
||||
// note: cannot use assignTypeCastedValue because that is ourselves :P
|
||||
asmgen.assignExpressionTo(origTypeCastExpression, target)
|
||||
if(targetDt==DataType.FLOAT && (target.register==RegisterOrPair.FAC1 || target.register==RegisterOrPair.FAC2)) {
|
||||
when(valueDt) {
|
||||
DataType.UBYTE -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.Y, false)
|
||||
asmgen.out(" jsr floats.FREADUY")
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.A, true)
|
||||
asmgen.out(" jsr floats.FREADSA")
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.AY, false)
|
||||
asmgen.out(" jsr floats.GIVUAYFAY")
|
||||
}
|
||||
DataType.WORD -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.AY, true)
|
||||
asmgen.out(" jsr floats.GIVAYFAY")
|
||||
}
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
}
|
||||
if(target.register==RegisterOrPair.FAC2) {
|
||||
asmgen.out(" jsr floats.MOVEF")
|
||||
}
|
||||
} else {
|
||||
// No more special optmized cases yet. Do the rest via more complex evaluation
|
||||
// note: cannot use assignTypeCastedValue because that is ourselves :P
|
||||
// NOTE: THIS MAY TURN INTO A STACK OVERFLOW ERROR IF IT CAN'T SIMPLIFY THE TYPECAST..... :-/
|
||||
asmgen.assignExpressionTo(origTypeCastExpression, target)
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignCastViaLsbFunc(value: Expression, target: AsmAssignTarget) {
|
||||
val lsb = FunctionCall(IdentifierReference(listOf("lsb"), value.position), mutableListOf(value), value.position)
|
||||
val lsb = FunctionCallExpr(IdentifierReference(listOf("lsb"), value.position), mutableListOf(value), value.position)
|
||||
lsb.linkParents(value.parent)
|
||||
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UBYTE, expression = lsb)
|
||||
val assign = AsmAssignment(src, target, false, program.memsizer, value.position)
|
||||
@ -779,7 +924,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
if(target.constArrayIndexValue!=null) {
|
||||
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype)
|
||||
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype).toUInt()
|
||||
when(target.datatype) {
|
||||
in ByteDatatypes -> {
|
||||
asmgen.out(" inx | lda P8ESTACK_LO,x | sta ${target.asmVarname}+$scaledIdx")
|
||||
@ -989,7 +1134,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
TargetStorageKind.ARRAY -> {
|
||||
target.array!!
|
||||
if(target.constArrayIndexValue!=null) {
|
||||
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype)
|
||||
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype).toUInt()
|
||||
when(target.datatype) {
|
||||
in ByteDatatypes -> {
|
||||
asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx")
|
||||
@ -1044,7 +1189,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
adc #<${target.asmVarname}
|
||||
bcc +
|
||||
iny
|
||||
+ jsr floats.copy_float""")
|
||||
+ jsr floats.copy_float""")
|
||||
}
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
@ -1078,6 +1223,11 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignFAC2float(target: AsmAssignTarget) {
|
||||
asmgen.out(" jsr floats.MOVFA") // fac2 -> fac1
|
||||
assignFAC1float(target)
|
||||
}
|
||||
|
||||
internal fun assignFAC1float(target: AsmAssignTarget) {
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
@ -1213,7 +1363,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
if (target.constArrayIndexValue!=null) {
|
||||
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype)
|
||||
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype).toUInt()
|
||||
asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx")
|
||||
}
|
||||
else {
|
||||
@ -1266,7 +1416,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
if (wordtarget.constArrayIndexValue!=null) {
|
||||
val scaledIdx = wordtarget.constArrayIndexValue!! * 2
|
||||
val scaledIdx = wordtarget.constArrayIndexValue!! * 2u
|
||||
asmgen.out(" lda $sourceName")
|
||||
asmgen.signExtendAYlsb(DataType.BYTE)
|
||||
asmgen.out(" sta ${wordtarget.asmVarname}+$scaledIdx | sty ${wordtarget.asmVarname}+$scaledIdx+1")
|
||||
@ -1334,7 +1484,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
if (wordtarget.constArrayIndexValue!=null) {
|
||||
val scaledIdx = wordtarget.constArrayIndexValue!! * 2
|
||||
val scaledIdx = wordtarget.constArrayIndexValue!! * 2u
|
||||
asmgen.out(" lda $sourceName | sta ${wordtarget.asmVarname}+$scaledIdx")
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz ${wordtarget.asmVarname}+$scaledIdx+1")
|
||||
@ -1494,7 +1644,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
if (target.constArrayIndexValue!=null) {
|
||||
val idx = target.constArrayIndexValue!! * 2
|
||||
val idx = target.constArrayIndexValue!! * 2u
|
||||
when (regs) {
|
||||
RegisterOrPair.AX -> asmgen.out(" sta ${target.asmVarname}+$idx | stx ${target.asmVarname}+$idx+1")
|
||||
RegisterOrPair.AY -> asmgen.out(" sta ${target.asmVarname}+$idx | sty ${target.asmVarname}+$idx+1")
|
||||
@ -1624,7 +1774,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
asmgen.out(" stz ${target.asmVarname} | stz ${target.asmVarname}+1")
|
||||
}
|
||||
TargetStorageKind.MEMORY -> {
|
||||
throw AssemblyError("no asm gen for assign word $word to memory ${target.memory}")
|
||||
throw AssemblyError("memory is bytes not words")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array!!, DataType.UWORD, CpuRegister.Y)
|
||||
@ -1715,15 +1865,15 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignConstantByte(target: AsmAssignTarget, byte: Short) {
|
||||
if(byte==0.toShort() && asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
||||
private fun assignConstantByte(target: AsmAssignTarget, byte: Int) {
|
||||
if(byte==0 && asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
||||
// optimize setting zero value for this cpu
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
asmgen.out(" stz ${target.asmVarname} ")
|
||||
}
|
||||
TargetStorageKind.MEMORY -> {
|
||||
asmgen.out(" lda #${byte.toHex()}")
|
||||
asmgen.out(" lda #0")
|
||||
storeRegisterAInMemoryAddress(target.memory!!)
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
@ -1949,7 +2099,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignMemoryByte(target: AsmAssignTarget, address: Int?, identifier: IdentifierReference?) {
|
||||
private fun assignMemoryByte(target: AsmAssignTarget, address: UInt?, identifier: IdentifierReference?) {
|
||||
if (address != null) {
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
@ -2033,7 +2183,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignMemoryByteIntoWord(wordtarget: AsmAssignTarget, address: Int?, identifier: IdentifierReference?) {
|
||||
private fun assignMemoryByteIntoWord(wordtarget: AsmAssignTarget, address: UInt?, identifier: IdentifierReference?) {
|
||||
if (address != null) {
|
||||
when(wordtarget.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
@ -2104,52 +2254,14 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
when(addressExpr) {
|
||||
is NumericLiteralValue, is IdentifierReference -> {
|
||||
assignExpressionToVariable(addressExpr, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W2)")
|
||||
else
|
||||
asmgen.out(" ldy #0 | sta (P8ZP_SCRATCH_W2),y")
|
||||
asmgen.storeAIntoZpPointerVar("P8ZP_SCRATCH_W2")
|
||||
}
|
||||
else -> {
|
||||
// same as above but we need to save the A register
|
||||
asmgen.out(" pha")
|
||||
assignExpressionToVariable(addressExpr, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
asmgen.out(" pla")
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W2)")
|
||||
else
|
||||
asmgen.out(" ldy #0 | sta (P8ZP_SCRATCH_W2),y")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun storeAIntoPointerVar(pointervar: IdentifierReference) {
|
||||
val sourceName = asmgen.asmVariableName(pointervar)
|
||||
val vardecl = pointervar.targetVarDecl(program)!!
|
||||
val scopedName = vardecl.makeScopedName(vardecl.name)
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
||||
if (asmgen.isZpVar(scopedName)) {
|
||||
// pointervar is already in the zero page, no need to copy
|
||||
asmgen.out(" sta ($sourceName)")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy $sourceName
|
||||
sty P8ZP_SCRATCH_W2
|
||||
ldy $sourceName+1
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
sta (P8ZP_SCRATCH_W2)""")
|
||||
}
|
||||
} else {
|
||||
if (asmgen.isZpVar(scopedName)) {
|
||||
// pointervar is already in the zero page, no need to copy
|
||||
asmgen.out(" ldy #0 | sta ($sourceName),y")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy $sourceName
|
||||
sty P8ZP_SCRATCH_W2
|
||||
ldy $sourceName+1
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #0
|
||||
sta (P8ZP_SCRATCH_W2),y""")
|
||||
asmgen.storeAIntoZpPointerVar("P8ZP_SCRATCH_W2")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2159,7 +2271,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
asmgen.out(" sta ${addressLv.number.toHex()}")
|
||||
}
|
||||
addressExpr is IdentifierReference -> {
|
||||
storeAIntoPointerVar(addressExpr)
|
||||
asmgen.storeAIntoPointerVar(addressExpr)
|
||||
}
|
||||
addressExpr is BinaryExpression -> {
|
||||
if(!asmgen.tryOptimizedPointerAccessWithA(addressExpr, true))
|
@ -1,18 +1,17 @@
|
||||
package prog8.compiler.target.cpu6502.codegen.assignment
|
||||
package prog8.codegen.target.cpu6502.codegen.assignment
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.toHex
|
||||
import prog8.compiler.target.AssemblyError
|
||||
import prog8.compiler.target.cpu6502.codegen.AsmGen
|
||||
import prog8.compiler.target.cpu6502.codegen.ExpressionsAsmGen
|
||||
import prog8.codegen.target.AssemblyError
|
||||
import prog8.codegen.target.cpu6502.codegen.AsmGen
|
||||
import prog8.compilerinterface.CpuType
|
||||
|
||||
|
||||
internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
private val assignmentAsmGen: AssignmentAsmGen,
|
||||
private val exprAsmGen: ExpressionsAsmGen,
|
||||
private val asmgen: AsmGen
|
||||
) {
|
||||
fun translate(assign: AsmAssignment) {
|
||||
@ -22,15 +21,14 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
when (val value = assign.source.expression!!) {
|
||||
is PrefixExpression -> {
|
||||
// A = -A , A = +A, A = ~A, A = not A
|
||||
val target = assignmentAsmGen.virtualRegsToVariables(assign.target)
|
||||
val itype = value.inferType(program)
|
||||
if(!itype.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
val type = itype.getOr(DataType.UNDEFINED)
|
||||
val type = itype.getOrElse { throw AssemblyError("unknown dt") }
|
||||
when (value.operator) {
|
||||
"+" -> {}
|
||||
"-" -> inplaceNegate(assign.target, type)
|
||||
"~" -> inplaceInvert(assign.target, type)
|
||||
"not" -> inplaceBooleanNot(assign.target, type)
|
||||
"-" -> inplaceNegate(target, type)
|
||||
"~" -> inplaceInvert(target, type)
|
||||
"not" -> inplaceBooleanNot(target, type)
|
||||
else -> throw AssemblyError("invalid prefix operator")
|
||||
}
|
||||
}
|
||||
@ -47,7 +45,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
return inplaceModification(target, binExpr.operator, binExpr.right)
|
||||
}
|
||||
|
||||
if (binExpr.operator in associativeOperators) {
|
||||
if (binExpr.operator in AssociativeOperators) {
|
||||
if (binExpr.right isSameAs astTarget) {
|
||||
// A = 5 <operator> A
|
||||
return inplaceModification(target, binExpr.operator, binExpr.left)
|
||||
@ -102,10 +100,66 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
throw FatalAstException("assignment should be augmentable $binExpr")
|
||||
val leftBinExpr = binExpr.left as? BinaryExpression
|
||||
val rightBinExpr = binExpr.right as? BinaryExpression
|
||||
if(leftBinExpr!=null && rightBinExpr==null) {
|
||||
if(leftBinExpr.left isSameAs astTarget) {
|
||||
// X = (X <oper> Right) <oper> Something
|
||||
inplaceModification(target, leftBinExpr.operator, leftBinExpr.right)
|
||||
inplaceModification(target, binExpr.operator, binExpr.right)
|
||||
return
|
||||
}
|
||||
if(leftBinExpr.right isSameAs astTarget) {
|
||||
// X = (Left <oper> X) <oper> Something
|
||||
if(leftBinExpr.operator in AssociativeOperators) {
|
||||
inplaceModification(target, leftBinExpr.operator, leftBinExpr.left)
|
||||
inplaceModification(target, binExpr.operator, binExpr.right)
|
||||
return
|
||||
} else {
|
||||
throw AssemblyError("operands in wrong order for non-associative operator")
|
||||
}
|
||||
}
|
||||
}
|
||||
if(leftBinExpr==null && rightBinExpr!=null) {
|
||||
if(rightBinExpr.left isSameAs astTarget) {
|
||||
// X = Something <oper> (X <oper> Right)
|
||||
if(binExpr.operator in AssociativeOperators) {
|
||||
inplaceModification(target, rightBinExpr.operator, rightBinExpr.right)
|
||||
inplaceModification(target, binExpr.operator, binExpr.left)
|
||||
return
|
||||
} else {
|
||||
throw AssemblyError("operands in wrong order for non-associative operator")
|
||||
}
|
||||
}
|
||||
if(rightBinExpr.right isSameAs astTarget) {
|
||||
// X = Something <oper> (Left <oper> X)
|
||||
if(binExpr.operator in AssociativeOperators && rightBinExpr.operator in AssociativeOperators) {
|
||||
inplaceModification(target, rightBinExpr.operator, rightBinExpr.left)
|
||||
inplaceModification(target, binExpr.operator, binExpr.left)
|
||||
return
|
||||
} else {
|
||||
throw AssemblyError("operands in wrong order for non-associative operator")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw FatalAstException("assignment should follow augmentable rules $binExpr")
|
||||
}
|
||||
|
||||
private fun inplaceModification(target: AsmAssignTarget, operator: String, value: Expression) {
|
||||
private fun inplaceModification(target: AsmAssignTarget, operator: String, origValue: Expression) {
|
||||
|
||||
// the asm-gen code can deal with situations where you want to assign a byte into a word.
|
||||
// it will create the most optimized code to do this (so it type-extends for us).
|
||||
// But we can't deal with writing a word into a byte - explicit typeconversion is required
|
||||
val value = if(program.memsizer.memorySize(origValue.inferType(program).getOrElse { throw AssemblyError("unknown dt") }) > program.memsizer.memorySize(target.datatype)) {
|
||||
val typecast = TypecastExpression(origValue, target.datatype, true, origValue.position)
|
||||
typecast.linkParents(origValue.parent)
|
||||
typecast
|
||||
}
|
||||
else {
|
||||
origValue
|
||||
}
|
||||
|
||||
val valueLv = (value as? NumericLiteralValue)?.number
|
||||
val ident = value as? IdentifierReference
|
||||
val memread = value as? DirectMemoryRead
|
||||
@ -181,9 +235,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// TODO OTHER EVALUATION HERE, don't use the estack
|
||||
// TODO use some other evaluation here; don't use the estack to transfer the address to read/write from
|
||||
asmgen.assignExpressionTo(memory.addressExpression, AsmAssignTarget(TargetStorageKind.STACK, program, asmgen, DataType.UWORD, memory.definingSubroutine))
|
||||
asmgen.out(" jsr prog8_lib.read_byte_from_address_on_stack | sta P8ZP_SCRATCH_B1") // TODO don't use estack to transfer the address to read from
|
||||
asmgen.out(" jsr prog8_lib.read_byte_from_address_on_stack | sta P8ZP_SCRATCH_B1")
|
||||
when {
|
||||
valueLv != null -> inplaceModification_byte_litval_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, valueLv.toInt())
|
||||
ident != null -> inplaceModification_byte_variable_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, ident)
|
||||
@ -194,7 +248,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
else -> inplaceModification_byte_value_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, value)
|
||||
}
|
||||
asmgen.out(" lda P8ZP_SCRATCH_B1 | jsr prog8_lib.write_byte_to_address_on_stack | inx") // TODO don't use estack to transfer the address to read from
|
||||
asmgen.out(" lda P8ZP_SCRATCH_B1 | jsr prog8_lib.write_byte_to_address_on_stack | inx")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -289,17 +343,15 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
}
|
||||
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg in-place modification")
|
||||
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack in-place modification")
|
||||
TargetStorageKind.REGISTER -> throw AssemblyError("no asm gen for reg in-place modification")
|
||||
TargetStorageKind.STACK -> throw AssemblyError("no asm gen for stack in-place modification")
|
||||
}
|
||||
}
|
||||
|
||||
private fun tryRemoveRedundantCast(value: TypecastExpression, target: AsmAssignTarget, operator: String): Boolean {
|
||||
if (target.datatype == value.type) {
|
||||
val childIDt = value.expression.inferType(program)
|
||||
if(!childIDt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
val childDt = childIDt.getOr(DataType.UNDEFINED)
|
||||
val childDt = childIDt.getOrElse { throw AssemblyError("unknown dt") }
|
||||
if (value.type!=DataType.FLOAT && (value.type.equalsSize(childDt) || value.type.largerThan(childDt))) {
|
||||
// this typecast is redundant here; the rest of the code knows how to deal with the uncasted value.
|
||||
// (works for integer types, not for float.)
|
||||
@ -312,13 +364,14 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
|
||||
private fun inplaceModification_byte_value_to_pointer(pointervar: IdentifierReference, operator: String, value: Expression) {
|
||||
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
when (operator) {
|
||||
// note: ** (power) operator requires floats.
|
||||
"+" -> asmgen.out(" clc | adc P8ZP_SCRATCH_B1")
|
||||
"-" -> asmgen.out(" sec | sbc P8ZP_SCRATCH_B1")
|
||||
"*" -> asmgen.out(" ldy P8ZP_SCRATCH_B1 | jsr math.multiply_bytes | ldy #0")
|
||||
"/" -> asmgen.out(" ldy P8ZP_SCRATCH_B1 | jsr math.divmod_ub_asm | tya | ldy #0")
|
||||
"%" -> asmgen.out(" ldy P8ZP_SCRATCH_B1 | jsr math.divmod_ub_asm | ldy #0")
|
||||
"*" -> asmgen.out(" ldy P8ZP_SCRATCH_B1 | jsr math.multiply_bytes")
|
||||
"/" -> asmgen.out(" ldy P8ZP_SCRATCH_B1 | jsr math.divmod_ub_asm | tya")
|
||||
"%" -> asmgen.out(" ldy P8ZP_SCRATCH_B1 | jsr math.divmod_ub_asm")
|
||||
"<<" -> {
|
||||
asmgen.out("""
|
||||
ldy P8ZP_SCRATCH_B1
|
||||
@ -342,8 +395,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
"^", "xor" -> asmgen.out(" eor P8ZP_SCRATCH_B1")
|
||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||
}
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName)
|
||||
}
|
||||
|
||||
private fun inplaceModification_byte_variable_to_pointer(pointervar: IdentifierReference, operator: String, value: IdentifierReference) {
|
||||
@ -354,9 +406,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
// note: ** (power) operator requires floats.
|
||||
"+" -> asmgen.out(" clc | adc $otherName")
|
||||
"-" -> asmgen.out(" sec | sbc $otherName")
|
||||
"*" -> asmgen.out(" ldy $otherName | jsr math.multiply_bytes | ldy #0")
|
||||
"/" -> asmgen.out(" ldy $otherName | jsr math.divmod_ub_asm | tya | ldy #0")
|
||||
"%" -> asmgen.out(" ldy $otherName | jsr math.divmod_ub_asm | ldy #0")
|
||||
"*" -> asmgen.out(" ldy $otherName | jsr math.multiply_bytes")
|
||||
"/" -> asmgen.out(" ldy $otherName | jsr math.divmod_ub_asm | tya")
|
||||
"%" -> asmgen.out(" ldy $otherName | jsr math.divmod_ub_asm")
|
||||
"<<" -> {
|
||||
asmgen.out("""
|
||||
ldy $otherName
|
||||
@ -380,7 +432,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
"^", "xor" -> asmgen.out(" eor $otherName")
|
||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||
}
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName)
|
||||
}
|
||||
|
||||
private fun inplaceModification_byte_litval_to_pointer(pointervar: IdentifierReference, operator: String, value: Int) {
|
||||
@ -389,63 +441,63 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
"+" -> {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" clc | adc #$value")
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName)
|
||||
}
|
||||
"-" -> {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" sec | sbc #$value")
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName)
|
||||
}
|
||||
"*" -> {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
if(value in asmgen.optimizedByteMultiplications)
|
||||
asmgen.out(" jsr math.mul_byte_${value}")
|
||||
else
|
||||
asmgen.out(" ldy #$value | jsr math.multiply_bytes | ldy #0")
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
asmgen.out(" ldy #$value | jsr math.multiply_bytes")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName)
|
||||
}
|
||||
"/" -> {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
if(value==0)
|
||||
throw AssemblyError("division by zero")
|
||||
asmgen.out(" ldy #$value | jsr math.divmod_ub_asm | tya | ldy #0")
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
asmgen.out(" ldy #$value | jsr math.divmod_ub_asm | tya")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName)
|
||||
}
|
||||
"%" -> {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
if(value==0)
|
||||
throw AssemblyError("division by zero")
|
||||
asmgen.out(" ldy #$value | jsr math.divmod_ub_asm | ldy #0")
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
asmgen.out(" ldy #$value | jsr math.divmod_ub_asm")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName)
|
||||
}
|
||||
"<<" -> {
|
||||
if (value > 0) {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
repeat(value) { asmgen.out(" asl a") }
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
repeat(value) { asmgen.out(" asl a") }
|
||||
asmgen.storeAIntoZpPointerVar(sourceName)
|
||||
}
|
||||
}
|
||||
">>" -> {
|
||||
if (value > 0) {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
repeat(value) { asmgen.out(" lsr a") }
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
repeat(value) { asmgen.out(" lsr a") }
|
||||
asmgen.storeAIntoZpPointerVar(sourceName)
|
||||
}
|
||||
}
|
||||
"&", "and" -> {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" and #$value")
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName)
|
||||
}
|
||||
"|", "or" -> {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" ora #$value")
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName)
|
||||
}
|
||||
"^", "xor" -> {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" eor #$value")
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName)
|
||||
}
|
||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||
}
|
||||
@ -522,6 +574,26 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
|
||||
asmgen.out(" eor $name | sta $name")
|
||||
}
|
||||
"==" -> {
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
|
||||
asmgen.out("""
|
||||
cmp $name
|
||||
beq +
|
||||
lda #0
|
||||
beq ++
|
||||
+ lda #1
|
||||
+ sta $name""")
|
||||
}
|
||||
"!=" -> {
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
|
||||
asmgen.out("""
|
||||
cmp $name
|
||||
beq +
|
||||
lda #1
|
||||
bne ++
|
||||
+ lda #0
|
||||
+ sta $name""")
|
||||
}
|
||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||
}
|
||||
}
|
||||
@ -579,6 +651,26 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
"&", "and" -> asmgen.out(" lda $name | and $otherName | sta $name")
|
||||
"|", "or" -> asmgen.out(" lda $name | ora $otherName | sta $name")
|
||||
"^", "xor" -> asmgen.out(" lda $name | eor $otherName | sta $name")
|
||||
"==" -> {
|
||||
asmgen.out("""
|
||||
lda $otherName
|
||||
cmp $name
|
||||
beq +
|
||||
lda #0
|
||||
bne ++
|
||||
+ lda #1
|
||||
+ sta $name""")
|
||||
}
|
||||
"!=" -> {
|
||||
asmgen.out("""
|
||||
lda $otherName
|
||||
cmp $name
|
||||
beq +
|
||||
lda #1
|
||||
bne ++
|
||||
+ lda #0
|
||||
+ sta $name""")
|
||||
}
|
||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||
}
|
||||
}
|
||||
@ -650,6 +742,26 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
"&", "and" -> asmgen.out(" lda $name | and #$value | sta $name")
|
||||
"|", "or" -> asmgen.out(" lda $name | ora #$value | sta $name")
|
||||
"^", "xor" -> asmgen.out(" lda $name | eor #$value | sta $name")
|
||||
"==" -> {
|
||||
asmgen.out("""
|
||||
lda #$value
|
||||
cmp $name
|
||||
beq +
|
||||
lda #0
|
||||
bne ++
|
||||
+ lda #1
|
||||
+ sta $name""")
|
||||
}
|
||||
"!=" -> {
|
||||
asmgen.out("""
|
||||
lda #$value
|
||||
cmp $name
|
||||
beq +
|
||||
lda #1
|
||||
bne ++
|
||||
+ lda #0
|
||||
+ sta $name""")
|
||||
}
|
||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||
}
|
||||
}
|
||||
@ -657,14 +769,14 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
private fun inplaceModification_byte_memread_to_variable(name: String, dt: DataType, operator: String, memread: DirectMemoryRead) {
|
||||
when (operator) {
|
||||
"+" -> {
|
||||
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
||||
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
|
||||
asmgen.out("""
|
||||
clc
|
||||
adc $name
|
||||
sta $name""")
|
||||
}
|
||||
"-" -> {
|
||||
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
||||
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
|
||||
asmgen.out("""
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda $name
|
||||
@ -673,15 +785,15 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
sta $name""")
|
||||
}
|
||||
"|", "or" -> {
|
||||
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
||||
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
|
||||
asmgen.out(" ora $name | sta $name")
|
||||
}
|
||||
"&", "and" -> {
|
||||
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
||||
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
|
||||
asmgen.out(" and $name | sta $name")
|
||||
}
|
||||
"^", "xor" -> {
|
||||
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
||||
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
|
||||
asmgen.out(" eor $name | sta $name")
|
||||
}
|
||||
// TODO: tuned code for more operators
|
||||
@ -694,7 +806,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
private fun inplaceModification_word_memread_to_variable(name: String, dt: DataType, operator: String, memread: DirectMemoryRead) {
|
||||
when (operator) {
|
||||
"+" -> {
|
||||
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
||||
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
|
||||
asmgen.out("""
|
||||
clc
|
||||
adc $name
|
||||
@ -704,7 +816,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
+""")
|
||||
}
|
||||
"-" -> {
|
||||
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
||||
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
|
||||
asmgen.out("""
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda $name
|
||||
@ -716,11 +828,11 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
+""")
|
||||
}
|
||||
"|", "or" -> {
|
||||
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
||||
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
|
||||
asmgen.out(" ora $name | sta $name")
|
||||
}
|
||||
"&", "and" -> {
|
||||
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
||||
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
|
||||
asmgen.out(" and $name | sta $name")
|
||||
if(dt in WordDatatypes) {
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
@ -730,7 +842,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
"^", "xor" -> {
|
||||
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
||||
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
|
||||
asmgen.out(" eor $name | sta $name")
|
||||
}
|
||||
// TODO: tuned code for more operators
|
||||
@ -1103,7 +1215,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
sta $name
|
||||
lda P8ZP_SCRATCH_W2+1
|
||||
sta $name+1
|
||||
""") }
|
||||
""")
|
||||
}
|
||||
"<<" -> {
|
||||
asmgen.out("""
|
||||
ldy $otherName
|
||||
@ -1235,9 +1348,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
// because the value is evaluated onto the eval stack (=slow).
|
||||
|
||||
val valueiDt = value.inferType(program)
|
||||
if(!valueiDt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
val valueDt = valueiDt.getOr(DataType.UNDEFINED)
|
||||
val valueDt = valueiDt.getOrElse { throw AssemblyError("unknown dt") }
|
||||
|
||||
fun multiplyVarByWordInAY() {
|
||||
asmgen.out("""
|
||||
@ -1754,17 +1865,16 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
beq +
|
||||
lda #1
|
||||
+ eor #1""")
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
asmgen.storeAIntoZpPointerVar(sourceName)
|
||||
}
|
||||
else -> {
|
||||
asmgen.assignExpressionToVariable(mem.addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD, target.scope)
|
||||
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2")
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
beq +
|
||||
lda #1
|
||||
+ eor #1
|
||||
sta (P8ZP_SCRATCH_W2),y""")
|
||||
+ eor #1""")
|
||||
asmgen.storeAIntoZpPointerVar("P8ZP_SCRATCH_W2")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1774,24 +1884,24 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
cmp #0
|
||||
beq +
|
||||
lda #1
|
||||
+ eor #1""")
|
||||
+ eor #1""")
|
||||
RegisterOrPair.X -> asmgen.out("""
|
||||
txa
|
||||
beq +
|
||||
lda #1
|
||||
+ eor #1
|
||||
+ eor #1
|
||||
tax""")
|
||||
RegisterOrPair.Y -> asmgen.out("""
|
||||
tya
|
||||
beq +
|
||||
lda #1
|
||||
+ eor #1
|
||||
+ eor #1
|
||||
tay""")
|
||||
else -> throw AssemblyError("invalid reg dt for byte not")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.STACK -> TODO("missing codegen for byte stack not")
|
||||
else -> throw AssemblyError("missing codegen for in-place not of ubyte ${target.kind}")
|
||||
TargetStorageKind.STACK -> TODO("no asm gen for byte stack not")
|
||||
else -> throw AssemblyError("no asm gen for in-place not of ubyte ${target.kind}")
|
||||
}
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
@ -1843,13 +1953,12 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
+ ldx #1
|
||||
+""")
|
||||
}
|
||||
in Cx16VirtualRegisters -> TODO()
|
||||
in Cx16VirtualRegisters -> throw AssemblyError("cx16 virtual regs should be variables, not real registers")
|
||||
else -> throw AssemblyError("invalid reg dt for word not")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.MEMORY -> TODO("no asm gen for uword-memory not")
|
||||
TargetStorageKind.STACK -> TODO("missing codegen for word stack not")
|
||||
else -> throw AssemblyError("missing codegen for in-place not of uword for ${target.kind}")
|
||||
TargetStorageKind.STACK -> TODO("no asm gen for word stack not")
|
||||
else -> throw AssemblyError("no asm gen for in-place not of uword for ${target.kind}")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("boolean-not of invalid type")
|
||||
@ -1886,8 +1995,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
eor #255
|
||||
sta (P8ZP_SCRATCH_W2),y""")
|
||||
eor #255""")
|
||||
asmgen.storeAIntoZpPointerVar("P8ZP_SCRATCH_W2")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1899,8 +2008,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
else -> throw AssemblyError("invalid reg dt for byte invert")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.STACK -> TODO("missing codegen for byte stack invert")
|
||||
else -> throw AssemblyError("missing codegen for in-place invert ubyte for ${target.kind}")
|
||||
TargetStorageKind.STACK -> TODO("no asm gen for byte stack invert")
|
||||
else -> throw AssemblyError("no asm gen for in-place invert ubyte for ${target.kind}")
|
||||
}
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
@ -1919,15 +2028,12 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
RegisterOrPair.AX -> asmgen.out(" pha | txa | eor #255 | tax | pla | eor #255")
|
||||
RegisterOrPair.AY -> asmgen.out(" pha | tya | eor #255 | tay | pla | eor #255")
|
||||
RegisterOrPair.XY -> asmgen.out(" txa | eor #255 | tax | tya | eor #255 | tay")
|
||||
in Cx16VirtualRegisters -> {
|
||||
TODO("codegen for cx16 word register invert")
|
||||
}
|
||||
in Cx16VirtualRegisters -> throw AssemblyError("cx16 virtual regs should be variables, not real registers")
|
||||
else -> throw AssemblyError("invalid reg dt for word invert")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.MEMORY -> TODO("no asm gen for uword-memory invert")
|
||||
TargetStorageKind.STACK -> TODO("missing codegen for word stack invert")
|
||||
else -> throw AssemblyError("missing codegen for in-place invert uword for ${target.kind}")
|
||||
TargetStorageKind.STACK -> TODO("no asm gen for word stack invert")
|
||||
else -> throw AssemblyError("no asm gen for in-place invert uword for ${target.kind}")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("invert of invalid type")
|
||||
@ -1947,15 +2053,21 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
when(target.register!!) {
|
||||
RegisterOrPair.A -> asmgen.out(" sta P8ZP_SCRATCH_B1 | lda #0 | sec | sbc P8ZP_SCRATCH_B1")
|
||||
RegisterOrPair.X -> asmgen.out(" stx P8ZP_SCRATCH_B1 | lda #0 | sec | sbc P8ZP_SCRATCH_B1 | tax")
|
||||
RegisterOrPair.Y -> asmgen.out(" sty P8ZP_SCRATCH_B1 | lda #0 | sec | sbc P8ZP_SCRATCH_B1 | tay")
|
||||
RegisterOrPair.A -> {
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" eor #255 | ina")
|
||||
else
|
||||
asmgen.out(" eor #255 | clc | adc #1")
|
||||
|
||||
}
|
||||
RegisterOrPair.X -> asmgen.out(" txa | eor #255 | tax | inx")
|
||||
RegisterOrPair.Y -> asmgen.out(" tya | eor #255 | tay | iny")
|
||||
else -> throw AssemblyError("invalid reg dt for byte negate")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.MEMORY -> TODO("can't in-place negate memory ubyte")
|
||||
TargetStorageKind.STACK -> TODO("missing codegen for byte stack negate")
|
||||
else -> throw AssemblyError("missing codegen for in-place negate byte array")
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't in-place negate")
|
||||
TargetStorageKind.STACK -> TODO("no asm gen for byte stack negate")
|
||||
else -> throw AssemblyError("no asm gen for in-place negate byte")
|
||||
}
|
||||
}
|
||||
DataType.WORD -> {
|
||||
@ -2010,15 +2122,12 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
sbc P8ZP_SCRATCH_REG+1
|
||||
tay""")
|
||||
}
|
||||
in Cx16VirtualRegisters -> {
|
||||
TODO("codegen for cx16 word register negate")
|
||||
}
|
||||
in Cx16VirtualRegisters -> throw AssemblyError("cx16 virtual regs should be variables, not real registers")
|
||||
else -> throw AssemblyError("invalid reg dt for word neg")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.MEMORY -> TODO("no asm gen for word memory negate")
|
||||
TargetStorageKind.STACK -> TODO("missing codegen for word stack negate")
|
||||
else -> throw AssemblyError("missing codegen for in-place negate word array")
|
||||
TargetStorageKind.STACK -> TODO("no asm gen for word stack negate")
|
||||
else -> throw AssemblyError("no asm gen for in-place negate word")
|
||||
}
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
@ -2031,13 +2140,11 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
sta ${target.asmVarname}+1
|
||||
""")
|
||||
}
|
||||
TargetStorageKind.REGISTER -> TODO("missing codegen for float reg negate")
|
||||
TargetStorageKind.MEMORY -> TODO("missing codegen for float memory negate")
|
||||
TargetStorageKind.STACK -> TODO("missing codegen for stack float negate")
|
||||
TargetStorageKind.STACK -> TODO("no asm gen for float stack negate")
|
||||
else -> throw AssemblyError("weird target kind for inplace negate float ${target.kind}")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("negate of invalid type")
|
||||
else -> throw AssemblyError("negate of invalid type $dt")
|
||||
}
|
||||
}
|
||||
|
@ -1,31 +1,30 @@
|
||||
package prog8.compiler.target.cx16
|
||||
package prog8.codegen.target.cx16
|
||||
|
||||
import prog8.compiler.target.c64.C64MachineDefinition
|
||||
import prog8.compiler.target.cbm.viceMonListPostfix
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.codegen.target.cbm.Mflpt5
|
||||
import prog8.codegen.target.cbm.viceMonListName
|
||||
import prog8.compilerinterface.*
|
||||
import java.io.IOException
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
object CX16MachineDefinition: IMachineDefinition {
|
||||
class CX16MachineDefinition: IMachineDefinition {
|
||||
|
||||
override val cpu = CpuType.CPU65c02
|
||||
|
||||
// 5-byte cbm MFLPT format limitations:
|
||||
override val FLOAT_MAX_POSITIVE = 1.7014118345e+38 // bytes: 255,127,255,255,255
|
||||
override val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255
|
||||
override val FLOAT_MEM_SIZE = 5
|
||||
override val POINTER_MEM_SIZE = 2
|
||||
override val BASIC_LOAD_ADDRESS = 0x0801
|
||||
override val RAW_LOAD_ADDRESS = 0x8000
|
||||
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
|
||||
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
||||
override val BASIC_LOAD_ADDRESS = 0x0801u
|
||||
override val RAW_LOAD_ADDRESS = 0x8000u
|
||||
|
||||
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
||||
override val ESTACK_LO = 0x0400 // $0400-$04ff inclusive
|
||||
override val ESTACK_HI = 0x0500 // $0500-$05ff inclusive
|
||||
override val ESTACK_LO = 0x0400u // $0400-$04ff inclusive
|
||||
override val ESTACK_HI = 0x0500u // $0500-$05ff inclusive
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
|
||||
override fun getFloat(num: Number) = C64MachineDefinition.Mflpt5.fromNumber(num)
|
||||
override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
|
||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||
return if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
||||
listOf("syslib")
|
||||
@ -44,7 +43,7 @@ object CX16MachineDefinition: IMachineDefinition {
|
||||
}
|
||||
2 -> {
|
||||
emulatorName = "box16"
|
||||
extraArgs = listOf("-sym", "${programNameWithPath}.$viceMonListPostfix")
|
||||
extraArgs = listOf("-sym", viceMonListName(programNameWithPath.toString()))
|
||||
}
|
||||
else -> {
|
||||
System.err.println("Cx16 target only supports x16emu and box16 emulators.")
|
||||
@ -67,7 +66,19 @@ object CX16MachineDefinition: IMachineDefinition {
|
||||
}
|
||||
}
|
||||
|
||||
override fun isRegularRAMaddress(address: Int): Boolean = address < 0x9f00 || address in 0xa000..0xbfff
|
||||
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0x9f00u..0x9fffu
|
||||
override fun getPreallocatedZeropageVars(): Map<String, Pair<UInt, DataType>> {
|
||||
val vars = mutableMapOf<String, Pair<UInt, DataType>>()
|
||||
for(reg in 0..15) {
|
||||
vars["cx16.r${reg}"] = (2+reg*2).toUInt() to DataType.UWORD // cx16.r0 .. cx16.r15
|
||||
vars["cx16.r${reg}s"] = (2+reg*2).toUInt() to DataType.WORD // cx16.r0s .. cx16.r15s
|
||||
vars["cx16.r${reg}L"] = (2+reg*2).toUInt() to DataType.UBYTE // cx16.r0L .. cx16.r15L
|
||||
vars["cx16.r${reg}H"] = (3+reg*2).toUInt() to DataType.UBYTE // cx16.r0H .. cx16.r15H
|
||||
vars["cx16.r${reg}sL"] = (2+reg*2).toUInt() to DataType.BYTE // cx16.r0sL .. cx16.r15sL
|
||||
vars["cx16.r${reg}sH"] = (3+reg*2).toUInt() to DataType.BYTE // cx16.r0sH .. cx16.r15sH
|
||||
}
|
||||
return vars
|
||||
}
|
||||
|
||||
override fun initializeZeropage(compilerOptions: CompilationOptions) {
|
||||
zeropage = CX16Zeropage(compilerOptions)
|
||||
@ -87,38 +98,4 @@ object CX16MachineDefinition: IMachineDefinition {
|
||||
"rmb", "smb", "stp", "wai")
|
||||
|
||||
|
||||
class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
|
||||
override val SCRATCH_B1 = 0x7a // temp storage for a single byte
|
||||
override val SCRATCH_REG = 0x7b // temp storage for a register, must be B1+1
|
||||
override val SCRATCH_W1 = 0x7c // temp storage 1 for a word $7c+$7d
|
||||
override val SCRATCH_W2 = 0x7e // temp storage 2 for a word $7e+$7f
|
||||
|
||||
|
||||
init {
|
||||
if (options.floats && options.zeropage !in arrayOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
|
||||
throw InternalCompilerException("when floats are enabled, zero page type should be 'basicsafe' or 'dontuse'")
|
||||
|
||||
// the addresses 0x02 to 0x21 (inclusive) are taken for sixteen virtual 16-bit api registers.
|
||||
|
||||
when (options.zeropage) {
|
||||
ZeropageType.FULL -> {
|
||||
free.addAll(0x22..0xff)
|
||||
}
|
||||
ZeropageType.KERNALSAFE -> {
|
||||
free.addAll(0x22..0x7f)
|
||||
free.addAll(0xa9..0xff)
|
||||
}
|
||||
ZeropageType.BASICSAFE -> {
|
||||
free.addAll(0x22..0x7f)
|
||||
}
|
||||
ZeropageType.DONTUSE -> {
|
||||
free.clear() // don't use zeropage at all
|
||||
}
|
||||
else -> throw InternalCompilerException("for this machine target, zero page type 'floatsafe' is not available. ${options.zeropage}")
|
||||
}
|
||||
|
||||
removeReservedFromFreePool()
|
||||
}
|
||||
}
|
||||
}
|
41
codeGeneration/src/prog8/codegen/target/cx16/CX16Zeropage.kt
Normal file
41
codeGeneration/src/prog8/codegen/target/cx16/CX16Zeropage.kt
Normal file
@ -0,0 +1,41 @@
|
||||
package prog8.codegen.target.cx16
|
||||
|
||||
import prog8.compilerinterface.CompilationOptions
|
||||
import prog8.compilerinterface.InternalCompilerException
|
||||
import prog8.compilerinterface.Zeropage
|
||||
import prog8.compilerinterface.ZeropageType
|
||||
|
||||
class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
|
||||
override val SCRATCH_B1 = 0x7au // temp storage for a single byte
|
||||
override val SCRATCH_REG = 0x7bu // temp storage for a register, must be B1+1
|
||||
override val SCRATCH_W1 = 0x7cu // temp storage 1 for a word $7c+$7d
|
||||
override val SCRATCH_W2 = 0x7eu // temp storage 2 for a word $7e+$7f
|
||||
|
||||
|
||||
init {
|
||||
if (options.floats && options.zeropage !in arrayOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE))
|
||||
throw InternalCompilerException("when floats are enabled, zero page type should be 'basicsafe' or 'dontuse'")
|
||||
|
||||
// the addresses 0x02 to 0x21 (inclusive) are taken for sixteen virtual 16-bit api registers.
|
||||
|
||||
when (options.zeropage) {
|
||||
ZeropageType.FULL -> {
|
||||
free.addAll(0x22u..0xffu)
|
||||
}
|
||||
ZeropageType.KERNALSAFE -> {
|
||||
free.addAll(0x22u..0x7fu)
|
||||
free.addAll(0xa9u..0xffu)
|
||||
}
|
||||
ZeropageType.BASICSAFE -> {
|
||||
free.addAll(0x22u..0x7fu)
|
||||
}
|
||||
ZeropageType.DONTUSE -> {
|
||||
free.clear() // don't use zeropage at all
|
||||
}
|
||||
else -> throw InternalCompilerException("for this machine target, zero page type 'floatsafe' is not available. ${options.zeropage}")
|
||||
}
|
||||
|
||||
removeReservedFromFreePool()
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
package prog8.compiler.target
|
||||
|
||||
import com.github.michaelbull.result.fold
|
||||
import prog8.ast.base.ByteDatatypes
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.PassByReferenceDatatypes
|
||||
import prog8.ast.base.WordDatatypes
|
||||
import prog8.compiler.target.c64.C64MachineDefinition
|
||||
import prog8.compiler.target.cbm.Petscii
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
|
||||
|
||||
object C64Target: ICompilationTarget {
|
||||
override val name = "c64"
|
||||
override val machine = C64MachineDefinition
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
|
||||
val coded = if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||
return coded.fold(
|
||||
failure = { throw it },
|
||||
success = { it }
|
||||
)
|
||||
}
|
||||
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
|
||||
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||
|
||||
override fun memorySize(dt: DataType): Int {
|
||||
return when(dt) {
|
||||
in ByteDatatypes -> 1
|
||||
in WordDatatypes -> 2
|
||||
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
|
||||
in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE
|
||||
else -> -9999999
|
||||
}
|
||||
}
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
package prog8.compiler.target
|
||||
|
||||
import com.github.michaelbull.result.fold
|
||||
import prog8.ast.base.ByteDatatypes
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.PassByReferenceDatatypes
|
||||
import prog8.ast.base.WordDatatypes
|
||||
import prog8.compiler.target.cbm.Petscii
|
||||
import prog8.compiler.target.cx16.CX16MachineDefinition
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
|
||||
|
||||
object Cx16Target: ICompilationTarget {
|
||||
override val name = "cx16"
|
||||
override val machine = CX16MachineDefinition
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
|
||||
val coded= if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||
return coded.fold(
|
||||
failure = { throw it },
|
||||
success = { it }
|
||||
)
|
||||
}
|
||||
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
|
||||
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||
|
||||
override fun memorySize(dt: DataType): Int {
|
||||
return when(dt) {
|
||||
in ByteDatatypes -> 1
|
||||
in WordDatatypes -> 2
|
||||
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
|
||||
in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE
|
||||
else -> -9999999
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,206 +0,0 @@
|
||||
package prog8.compiler.target.c64
|
||||
|
||||
import prog8.compiler.target.cbm.viceMonListPostfix
|
||||
import prog8.compilerinterface.*
|
||||
import java.io.IOException
|
||||
import java.nio.file.Path
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.math.pow
|
||||
|
||||
object C64MachineDefinition: IMachineDefinition {
|
||||
|
||||
override val cpu = CpuType.CPU6502
|
||||
|
||||
// 5-byte cbm MFLPT format limitations:
|
||||
override val FLOAT_MAX_POSITIVE = 1.7014118345e+38 // bytes: 255,127,255,255,255
|
||||
override val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255
|
||||
override val FLOAT_MEM_SIZE = 5
|
||||
override val POINTER_MEM_SIZE = 2
|
||||
override val BASIC_LOAD_ADDRESS = 0x0801
|
||||
override val RAW_LOAD_ADDRESS = 0xc000
|
||||
|
||||
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
||||
override val ESTACK_LO = 0xce00 // $ce00-$ceff inclusive
|
||||
override val ESTACK_HI = 0xcf00 // $ce00-$ceff inclusive
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
|
||||
override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
|
||||
|
||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||
return if (compilerOptions.launcher == LauncherType.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).")
|
||||
return
|
||||
}
|
||||
|
||||
for(emulator in listOf("x64sc", "x64")) {
|
||||
println("\nStarting C-64 emulator $emulator...")
|
||||
val cmdline = listOf(emulator, "-silent", "-moncommands", "${programNameWithPath}.$viceMonListPostfix",
|
||||
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg")
|
||||
val processb = ProcessBuilder(cmdline).inheritIO()
|
||||
val process: Process
|
||||
try {
|
||||
process=processb.start()
|
||||
} catch(x: IOException) {
|
||||
continue // try the next emulator executable
|
||||
}
|
||||
process.waitFor()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
override fun isRegularRAMaddress(address: Int): Boolean = address<0xa000 || address in 0xc000..0xcfff
|
||||
|
||||
override fun initializeZeropage(compilerOptions: CompilationOptions) {
|
||||
zeropage = C64Zeropage(compilerOptions)
|
||||
}
|
||||
|
||||
// 6502 opcodes (including aliases and illegal opcodes), these cannot be used as variable or label names
|
||||
override val opcodeNames = setOf("adc", "ahx", "alr", "anc", "and", "ane", "arr", "asl", "asr", "axs", "bcc", "bcs",
|
||||
"beq", "bge", "bit", "blt", "bmi", "bne", "bpl", "brk", "bvc", "bvs", "clc",
|
||||
"cld", "cli", "clv", "cmp", "cpx", "cpy", "dcm", "dcp", "dec", "dex", "dey",
|
||||
"eor", "gcc", "gcs", "geq", "gge", "glt", "gmi", "gne", "gpl", "gvc", "gvs",
|
||||
"inc", "ins", "inx", "iny", "isb", "isc", "jam", "jmp", "jsr", "lae", "las",
|
||||
"lax", "lda", "lds", "ldx", "ldy", "lsr", "lxa", "nop", "ora", "pha", "php",
|
||||
"pla", "plp", "rla", "rol", "ror", "rra", "rti", "rts", "sax", "sbc", "sbx",
|
||||
"sec", "sed", "sei", "sha", "shl", "shr", "shs", "shx", "shy", "slo", "sre",
|
||||
"sta", "stx", "sty", "tas", "tax", "tay", "tsx", "txa", "txs", "tya", "xaa")
|
||||
|
||||
|
||||
class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
|
||||
override val SCRATCH_B1 = 0x02 // temp storage for a single byte
|
||||
override val SCRATCH_REG = 0x03 // temp storage for a register, must be B1+1
|
||||
override val SCRATCH_W1 = 0xfb // temp storage 1 for a word $fb+$fc
|
||||
override val SCRATCH_W2 = 0xfd // temp storage 2 for a word $fb+$fc
|
||||
|
||||
|
||||
init {
|
||||
if (options.floats && options.zeropage !in arrayOf(ZeropageType.FLOATSAFE, ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
|
||||
throw InternalCompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
|
||||
|
||||
if (options.zeropage == ZeropageType.FULL) {
|
||||
free.addAll(0x04..0xf9)
|
||||
free.add(0xff)
|
||||
free.removeAll(listOf(0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6)) // these are updated by IRQ
|
||||
} else {
|
||||
if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
|
||||
free.addAll(listOf(
|
||||
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
|
||||
0x16, 0x17, 0x18, 0x19, 0x1a,
|
||||
0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
|
||||
0x22, 0x23, 0x24, 0x25,
|
||||
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
|
||||
0x47, 0x48, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x51, 0x52, 0x53,
|
||||
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
|
||||
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
||||
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
|
||||
0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c,
|
||||
0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
|
||||
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
|
||||
// 0x90-0xfa is 'kernal work storage area'
|
||||
))
|
||||
}
|
||||
|
||||
if (options.zeropage == ZeropageType.FLOATSAFE) {
|
||||
// remove the zeropage locations used for floating point operations from the free list
|
||||
free.removeAll(listOf(
|
||||
0x22, 0x23, 0x24, 0x25,
|
||||
0x10, 0x11, 0x12, 0x26, 0x27, 0x28, 0x29, 0x2a,
|
||||
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
|
||||
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
||||
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
|
||||
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
|
||||
))
|
||||
}
|
||||
|
||||
if(options.zeropage!= ZeropageType.DONTUSE) {
|
||||
// add the free Zp addresses
|
||||
// these are valid for the C-64 but allow BASIC to keep running fully *as long as you don't use tape I/O*
|
||||
free.addAll(listOf(0x04, 0x05, 0x06, 0x0a, 0x0e,
|
||||
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa5, 0xa6,
|
||||
0xb0, 0xb1, 0xbe, 0xbf, 0xf9))
|
||||
} else {
|
||||
// don't use the zeropage at all
|
||||
free.clear()
|
||||
}
|
||||
}
|
||||
|
||||
removeReservedFromFreePool()
|
||||
}
|
||||
}
|
||||
|
||||
data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short):
|
||||
IMachineFloat {
|
||||
|
||||
companion object {
|
||||
val zero = Mflpt5(0, 0, 0, 0, 0)
|
||||
fun fromNumber(num: Number): Mflpt5 {
|
||||
// see https://en.wikipedia.org/wiki/Microsoft_Binary_Format
|
||||
// and https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a
|
||||
// and https://en.wikipedia.org/wiki/IEEE_754-1985
|
||||
|
||||
val flt = num.toDouble()
|
||||
if (flt < FLOAT_MAX_NEGATIVE || flt > FLOAT_MAX_POSITIVE)
|
||||
throw InternalCompilerException("floating point number out of 5-byte mflpt range: $this")
|
||||
if (flt == 0.0)
|
||||
return zero
|
||||
|
||||
val sign = if (flt < 0.0) 0x80L else 0x00L
|
||||
var exponent = 128 + 32 // 128 is cbm's bias, 32 is this algo's bias
|
||||
var mantissa = flt.absoluteValue
|
||||
|
||||
// if mantissa is too large, shift right and adjust exponent
|
||||
while (mantissa >= 0x100000000) {
|
||||
mantissa /= 2.0
|
||||
exponent++
|
||||
}
|
||||
// if mantissa is too small, shift left and adjust exponent
|
||||
while (mantissa < 0x80000000) {
|
||||
mantissa *= 2.0
|
||||
exponent--
|
||||
}
|
||||
|
||||
return when {
|
||||
exponent < 0 -> zero // underflow, use zero instead
|
||||
exponent > 255 -> throw InternalCompilerException("floating point overflow: $this")
|
||||
exponent == 0 -> zero
|
||||
else -> {
|
||||
val mantLong = mantissa.toLong()
|
||||
Mflpt5(
|
||||
exponent.toShort(),
|
||||
(mantLong.and(0x7f000000L) ushr 24).or(sign).toShort(),
|
||||
(mantLong.and(0x00ff0000L) ushr 16).toShort(),
|
||||
(mantLong.and(0x0000ff00L) ushr 8).toShort(),
|
||||
(mantLong.and(0x000000ffL)).toShort())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun toDouble(): Double {
|
||||
if (this == zero) return 0.0
|
||||
val exp = b0 - 128
|
||||
val sign = (b1.toInt() and 0x80) > 0
|
||||
val number = 0x80000000L.or(b1.toLong() shl 24).or(b2.toLong() shl 16).or(b3.toLong() shl 8).or(b4.toLong())
|
||||
val result = number.toDouble() * (2.0).pow(exp) / 0x100000000
|
||||
return if (sign) -result else result
|
||||
}
|
||||
|
||||
override fun makeFloatFillAsm(): String {
|
||||
val b0 = "$" + b0.toString(16).padStart(2, '0')
|
||||
val b1 = "$" + b1.toString(16).padStart(2, '0')
|
||||
val b2 = "$" + b2.toString(16).padStart(2, '0')
|
||||
val b3 = "$" + b3.toString(16).padStart(2, '0')
|
||||
val b4 = "$" + b4.toString(16).padStart(2, '0')
|
||||
return "$b0, $b1, $b2, $b3, $b4"
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,242 +0,0 @@
|
||||
package prog8.compiler.target.cpu6502.codegen
|
||||
|
||||
|
||||
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
|
||||
|
||||
|
||||
fun optimizeAssembly(lines: MutableList<String>): Int {
|
||||
|
||||
var numberOfOptimizations = 0
|
||||
|
||||
var linesByFour = getLinesBy(lines, 4)
|
||||
|
||||
var mods = optimizeUselessStackByteWrites(linesByFour)
|
||||
if(mods.isNotEmpty()) {
|
||||
apply(mods, lines)
|
||||
linesByFour = getLinesBy(lines, 4)
|
||||
numberOfOptimizations++
|
||||
}
|
||||
|
||||
mods = optimizeIncDec(linesByFour)
|
||||
if(mods.isNotEmpty()) {
|
||||
apply(mods, lines)
|
||||
linesByFour = getLinesBy(lines, 4)
|
||||
numberOfOptimizations++
|
||||
}
|
||||
|
||||
mods = optimizeCmpSequence(linesByFour)
|
||||
if(mods.isNotEmpty()) {
|
||||
apply(mods, lines)
|
||||
linesByFour = getLinesBy(lines, 4)
|
||||
numberOfOptimizations++
|
||||
}
|
||||
|
||||
mods = optimizeStoreLoadSame(linesByFour)
|
||||
if(mods.isNotEmpty()) {
|
||||
apply(mods, lines)
|
||||
linesByFour = getLinesBy(lines, 4)
|
||||
numberOfOptimizations++
|
||||
}
|
||||
|
||||
mods= optimizeJsrRts(linesByFour)
|
||||
if(mods.isNotEmpty()) {
|
||||
apply(mods, lines)
|
||||
linesByFour = getLinesBy(lines, 4)
|
||||
numberOfOptimizations++
|
||||
}
|
||||
|
||||
var linesByFourteen = getLinesBy(lines, 14)
|
||||
mods = optimizeSameAssignments(linesByFourteen)
|
||||
if(mods.isNotEmpty()) {
|
||||
apply(mods, lines)
|
||||
linesByFourteen = getLinesBy(lines, 14)
|
||||
numberOfOptimizations++
|
||||
}
|
||||
|
||||
// TODO more assembly optimizations
|
||||
|
||||
return numberOfOptimizations
|
||||
}
|
||||
|
||||
private class Modification(val lineIndex: Int, val remove: Boolean, val replacement: String?)
|
||||
|
||||
private fun apply(modifications: List<Modification>, lines: MutableList<String>) {
|
||||
for (modification in modifications.sortedBy { it.lineIndex }.reversed()) {
|
||||
if(modification.remove)
|
||||
lines.removeAt(modification.lineIndex)
|
||||
else
|
||||
lines[modification.lineIndex] = modification.replacement!!
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLinesBy(lines: MutableList<String>, windowSize: Int) =
|
||||
// all lines (that aren't empty or comments) in sliding windows of certain size
|
||||
lines.withIndex().filter { it.value.isNotBlank() && !it.value.trimStart().startsWith(';') }.windowed(windowSize, partialWindows = false)
|
||||
|
||||
private fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||
// when statement (on bytes) generates a sequence of:
|
||||
// lda $ce01,x
|
||||
// cmp #$20
|
||||
// beq check_prog8_s72choice_32
|
||||
// lda $ce01,x
|
||||
// cmp #$21
|
||||
// beq check_prog8_s73choice_33
|
||||
// the repeated lda can be removed
|
||||
val mods = mutableListOf<Modification>()
|
||||
for(lines in linesByFour) {
|
||||
if(lines[0].value.trim()=="lda P8ESTACK_LO+1,x" &&
|
||||
lines[1].value.trim().startsWith("cmp ") &&
|
||||
lines[2].value.trim().startsWith("beq ") &&
|
||||
lines[3].value.trim()=="lda P8ESTACK_LO+1,x") {
|
||||
mods.add(Modification(lines[3].index, true, null)) // remove the second lda
|
||||
}
|
||||
}
|
||||
return mods
|
||||
}
|
||||
|
||||
private fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||
// sta on stack, dex, inx, lda from stack -> eliminate this useless stack byte write
|
||||
// this is a lot harder for word values because the instruction sequence varies.
|
||||
val mods = mutableListOf<Modification>()
|
||||
for(lines in linesByFour) {
|
||||
if(lines[0].value.trim()=="sta P8ESTACK_LO,x" &&
|
||||
lines[1].value.trim()=="dex" &&
|
||||
lines[2].value.trim()=="inx" &&
|
||||
lines[3].value.trim()=="lda P8ESTACK_LO,x") {
|
||||
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 optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>): List<Modification> {
|
||||
|
||||
// optimize sequential assignments of the isSameAs value to various targets (bytes, words, floats)
|
||||
// the float one is the one that requires 2*7=14 lines of code to check...
|
||||
// @todo a better place to do this is in the Compiler instead and transform the Ast, or the AsmGen, and never even create the inefficient asm in the first place...
|
||||
|
||||
val mods = mutableListOf<Modification>()
|
||||
for (pair in linesByFourteen) {
|
||||
val first = pair[0].value.trimStart()
|
||||
val second = pair[1].value.trimStart()
|
||||
val third = pair[2].value.trimStart()
|
||||
val fourth = pair[3].value.trimStart()
|
||||
val fifth = pair[4].value.trimStart()
|
||||
val sixth = pair[5].value.trimStart()
|
||||
val seventh = pair[6].value.trimStart()
|
||||
val eighth = pair[7].value.trimStart()
|
||||
|
||||
if(first.startsWith("lda") && second.startsWith("ldy") && third.startsWith("sta") && fourth.startsWith("sty") &&
|
||||
fifth.startsWith("lda") && sixth.startsWith("ldy") && seventh.startsWith("sta") && eighth.startsWith("sty")) {
|
||||
val firstvalue = first.substring(4)
|
||||
val secondvalue = second.substring(4)
|
||||
val thirdvalue = fifth.substring(4)
|
||||
val fourthvalue = sixth.substring(4)
|
||||
if(firstvalue==thirdvalue && secondvalue==fourthvalue) {
|
||||
// lda/ldy sta/sty twice the isSameAs word --> remove second lda/ldy pair (fifth and sixth lines)
|
||||
mods.add(Modification(pair[4].index, true, null))
|
||||
mods.add(Modification(pair[5].index, true, null))
|
||||
}
|
||||
}
|
||||
|
||||
if(first.startsWith("lda") && second.startsWith("sta") && third.startsWith("lda") && fourth.startsWith("sta")) {
|
||||
val firstvalue = first.substring(4)
|
||||
val secondvalue = third.substring(4)
|
||||
if(firstvalue==secondvalue) {
|
||||
// lda value / sta ? / lda isSameAs-value / sta ? -> remove second lda (third line)
|
||||
mods.add(Modification(pair[2].index, true, null))
|
||||
}
|
||||
}
|
||||
|
||||
if(first.startsWith("lda") && second.startsWith("ldy") && third.startsWith("sta") && fourth.startsWith("sty") &&
|
||||
fifth.startsWith("lda") && sixth.startsWith("ldy") &&
|
||||
(seventh.startsWith("jsr floats.copy_float") || seventh.startsWith("jsr cx16flt.copy_float"))) {
|
||||
|
||||
val nineth = pair[8].value.trimStart()
|
||||
val tenth = pair[9].value.trimStart()
|
||||
val eleventh = pair[10].value.trimStart()
|
||||
val twelveth = pair[11].value.trimStart()
|
||||
val thirteenth = pair[12].value.trimStart()
|
||||
val fourteenth = pair[13].value.trimStart()
|
||||
|
||||
if(eighth.startsWith("lda") && nineth.startsWith("ldy") && tenth.startsWith("sta") && eleventh.startsWith("sty") &&
|
||||
twelveth.startsWith("lda") && thirteenth.startsWith("ldy") &&
|
||||
(fourteenth.startsWith("jsr floats.copy_float") || fourteenth.startsWith("jsr cx16flt.copy_float"))) {
|
||||
|
||||
if(first.substring(4) == eighth.substring(4) && second.substring(4)==nineth.substring(4)) {
|
||||
// identical float init
|
||||
mods.add(Modification(pair[7].index, true, null))
|
||||
mods.add(Modification(pair[8].index, true, null))
|
||||
mods.add(Modification(pair[9].index, true, null))
|
||||
mods.add(Modification(pair[10].index, true, null))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return mods
|
||||
}
|
||||
|
||||
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can OFTEN be eliminated
|
||||
// TODO this is not true if X is not a regular RAM memory address (but instead mapped I/O or ROM) but how does this code know?
|
||||
val mods = mutableListOf<Modification>()
|
||||
for (pair in linesByFour) {
|
||||
val first = pair[0].value.trimStart()
|
||||
val second = pair[1].value.trimStart()
|
||||
|
||||
if ((first.startsWith("sta ") && second.startsWith("lda ")) ||
|
||||
(first.startsWith("stx ") && second.startsWith("ldx ")) ||
|
||||
(first.startsWith("sty ") && second.startsWith("ldy ")) ||
|
||||
(first.startsWith("lda ") && second.startsWith("lda ")) ||
|
||||
(first.startsWith("ldy ") && second.startsWith("ldy ")) ||
|
||||
(first.startsWith("ldx ") && second.startsWith("ldx ")) ||
|
||||
(first.startsWith("sta ") && second.startsWith("lda ")) ||
|
||||
(first.startsWith("sty ") && second.startsWith("ldy ")) ||
|
||||
(first.startsWith("stx ") && second.startsWith("ldx "))
|
||||
) {
|
||||
val third = pair[2].value.trimStart()
|
||||
if(!third.startsWith("b")) {
|
||||
// no branch instruction follows, we can potentiall remove the load instruction
|
||||
val firstLoc = first.substring(4).trimStart()
|
||||
val secondLoc = second.substring(4).trimStart()
|
||||
if (firstLoc == secondLoc) {
|
||||
mods.add(Modification(pair[1].index, true, null))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return mods
|
||||
}
|
||||
|
||||
private fun optimizeIncDec(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||
// sometimes, iny+dey / inx+dex / dey+iny / dex+inx sequences are generated, these can be eliminated.
|
||||
val mods = mutableListOf<Modification>()
|
||||
for (pair in linesByFour) {
|
||||
val first = pair[0].value
|
||||
val second = pair[1].value
|
||||
if ((" iny" in first || "\tiny" in first) && (" dey" in second || "\tdey" in second)
|
||||
|| (" inx" in first || "\tinx" in first) && (" dex" in second || "\tdex" in second)
|
||||
|| (" dey" in first || "\tdey" in first) && (" iny" in second || "\tiny" in second)
|
||||
|| (" dex" in first || "\tdex" in first) && (" inx" in second || "\tinx" in second)) {
|
||||
mods.add(Modification(pair[0].index, true, null))
|
||||
mods.add(Modification(pair[1].index, true, null))
|
||||
}
|
||||
}
|
||||
return mods
|
||||
}
|
||||
|
||||
private fun optimizeJsrRts(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||
// jsr Sub + rts -> jmp Sub
|
||||
val mods = mutableListOf<Modification>()
|
||||
for (pair in linesByFour) {
|
||||
val first = pair[0].value
|
||||
val second = pair[1].value
|
||||
if ((" jsr" in first || "\tjsr" in first ) && (" rts" in second || "\trts" in second)) {
|
||||
mods += Modification(pair[0].index, false, pair[0].value.replace("jsr", "jmp"))
|
||||
mods += Modification(pair[1].index, true, null)
|
||||
}
|
||||
}
|
||||
return mods
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,37 +0,0 @@
|
||||
package prog8tests.asmgen.helpers
|
||||
|
||||
import prog8.ast.IBuiltinFunctions
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.InferredTypes
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.compilerinterface.IMemSizer
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.compilerinterface.IStringEncoding
|
||||
|
||||
|
||||
internal val DummyFunctions = object : IBuiltinFunctions {
|
||||
override val names: Set<String> = emptySet()
|
||||
override val purefunctionNames: Set<String> = emptySet()
|
||||
override fun constValue(
|
||||
name: String,
|
||||
args: List<Expression>,
|
||||
position: Position,
|
||||
): NumericLiteralValue? = null
|
||||
|
||||
override fun returnType(name: String, args: MutableList<Expression>) = InferredTypes.InferredType.unknown()
|
||||
}
|
||||
|
||||
internal val DummyMemsizer = object : IMemSizer {
|
||||
override fun memorySize(dt: DataType): Int = 0
|
||||
}
|
||||
|
||||
internal val DummyStringEncoder = object : IStringEncoding {
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String {
|
||||
return ""
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
package prog8tests.asmgen.helpers
|
||||
|
||||
import prog8.ast.base.Position
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
|
||||
internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors: Boolean=true): IErrorReporter {
|
||||
|
||||
|
||||
val errors = mutableListOf<String>()
|
||||
val warnings = mutableListOf<String>()
|
||||
|
||||
override fun err(msg: String, position: Position) {
|
||||
errors.add("${position.toClickableStr()} $msg")
|
||||
}
|
||||
|
||||
override fun warn(msg: String, position: Position) {
|
||||
warnings.add("${position.toClickableStr()} $msg")
|
||||
}
|
||||
|
||||
override fun noErrors(): Boolean = errors.isEmpty()
|
||||
|
||||
override fun report() {
|
||||
warnings.forEach { println("UNITTEST COMPILATION REPORT: WARNING: $it") }
|
||||
errors.forEach { println("UNITTEST COMPILATION REPORT: ERROR: $it") }
|
||||
if(throwExceptionAtReportIfErrors)
|
||||
finalizeNumErrors(errors.size, warnings.size)
|
||||
errors.clear()
|
||||
warnings.clear()
|
||||
}
|
||||
}
|
@ -11,17 +11,23 @@ java {
|
||||
}
|
||||
}
|
||||
|
||||
compileKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = javaVersion
|
||||
}
|
||||
}
|
||||
|
||||
compileTestKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = javaVersion
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':compilerInterfaces')
|
||||
implementation project(':compilerAst')
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2'
|
||||
testImplementation 'org.hamcrest:hamcrest:2.2'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2'
|
||||
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
@ -33,22 +39,6 @@ sourceSets {
|
||||
srcDirs = ["${project.projectDir}/res"]
|
||||
}
|
||||
}
|
||||
test {
|
||||
java {
|
||||
srcDirs = ["${project.projectDir}/test"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
// Enable JUnit 5 (Gradle 4.6+).
|
||||
useJUnitPlatform()
|
||||
|
||||
// Always run tests, even when nothing changed.
|
||||
dependsOn 'cleanTest'
|
||||
|
||||
// Show test results.
|
||||
testLogging {
|
||||
events "skipped", "failed"
|
||||
}
|
||||
}
|
||||
// note: there are no unit tests in this module!
|
||||
|
@ -4,15 +4,12 @@
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="module" module-name="compilerInterfaces" />
|
||||
<orderEntry type="module" module-name="compilerAst" />
|
||||
<orderEntry type="library" scope="TEST" name="hamcrest" level="project" />
|
||||
<orderEntry type="library" name="junit.jupiter" level="project" />
|
||||
</component>
|
||||
</module>
|
@ -1,2 +1,2 @@
|
||||
Unittests for things in this module are located in the Compiler module instead,
|
||||
for convenience sake - and to not spread the test cases around too much.
|
||||
for convenience sake, and to not spread the test cases around too much.
|
@ -4,47 +4,30 @@ import prog8.ast.IStatementContainer
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.BinaryExpression
|
||||
import prog8.ast.expressions.augmentAssignmentOperators
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.TypecastExpression
|
||||
import prog8.ast.expressions.AugmentAssignmentOperators
|
||||
import prog8.ast.statements.AssignTarget
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compilerinterface.CompilationOptions
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
import prog8.compilerinterface.isInRegularRAMof
|
||||
import prog8.compilerinterface.isIOAddress
|
||||
|
||||
|
||||
class BinExprSplitter(private val program: Program, private val options: CompilationOptions, private val compTarget: ICompilationTarget) : AstWalker() {
|
||||
|
||||
// override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
// TODO somehow if we do this, the resulting code for some programs (cube3d.p8) gets hundreds of bytes larger...: [ IS THIS STILL TRUE AFTER ALL CHANGES? ]
|
||||
// if(decl.type==VarDeclType.VAR ) {
|
||||
// val binExpr = decl.value as? BinaryExpression
|
||||
// if (binExpr != null && binExpr.operator in augmentAssignmentOperators) {
|
||||
// // split into a vardecl with just the left expression, and an aug. assignment with the right expression.
|
||||
// val augExpr = BinaryExpression(IdentifierReference(listOf(decl.name), decl.position), binExpr.operator, binExpr.right, binExpr.position)
|
||||
// val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
|
||||
// val assign = Assignment(target, augExpr, binExpr.position)
|
||||
// println("SPLIT VARDECL $decl")
|
||||
// return listOf(
|
||||
// IAstModification.SetExpression({ decl.value = it }, binExpr.left, decl),
|
||||
// IAstModification.InsertAfter(decl, assign, parent)
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// return noModifications
|
||||
// }
|
||||
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
|
||||
if(assignment.value.inferType(program) istype DataType.FLOAT && !options.optimizeFloatExpressions)
|
||||
return noModifications
|
||||
|
||||
val binExpr = assignment.value as? BinaryExpression
|
||||
if (binExpr != null) {
|
||||
|
||||
if(binExpr.inferType(program).istype(DataType.FLOAT) && !options.optimizeFloatExpressions)
|
||||
return noModifications
|
||||
|
||||
|
||||
/*
|
||||
|
||||
Reduce the complexity of a (binary) expression that has to be evaluated on the eval stack,
|
||||
@ -62,11 +45,44 @@ X = BinExpr X = LeftExpr
|
||||
|
||||
|
||||
*/
|
||||
if(binExpr.operator in augmentAssignmentOperators && isSimpleTarget(assignment.target)) {
|
||||
if(assignment.target isSameAs binExpr.left || assignment.target isSameAs binExpr.right)
|
||||
if(binExpr.operator in AugmentAssignmentOperators + listOf("==", "!=") && isSimpleTarget(assignment.target)) {
|
||||
if(assignment.target isSameAs binExpr.right)
|
||||
return noModifications
|
||||
if(assignment.target isSameAs binExpr.left) {
|
||||
if(binExpr.right.isSimple)
|
||||
return noModifications
|
||||
val leftBx = binExpr.left as? BinaryExpression
|
||||
if(leftBx!=null && (!leftBx.left.isSimple || !leftBx.right.isSimple))
|
||||
return noModifications
|
||||
val rightBx = binExpr.right as? BinaryExpression
|
||||
if(rightBx!=null && (!rightBx.left.isSimple || !rightBx.right.isSimple))
|
||||
return noModifications
|
||||
|
||||
if(binExpr.right.isSimple && !assignment.isAugmentable) {
|
||||
// TODO below attempts to remove stack-based evaluated expressions, but often the resulting code is BIGGER, and SLOWER.
|
||||
// val dt = assignment.target.inferType(program)
|
||||
// if(!dt.isInteger)
|
||||
// return noModifications
|
||||
// val tempVar = IdentifierReference(getTempVarName(dt), binExpr.right.position)
|
||||
// val assignTempVar = Assignment(
|
||||
// AssignTarget(tempVar, null, null, binExpr.right.position),
|
||||
// binExpr.right, binExpr.right.position
|
||||
// )
|
||||
// return listOf(
|
||||
// IAstModification.InsertBefore(assignment, assignTempVar, assignment.parent as IStatementContainer),
|
||||
// IAstModification.ReplaceNode(binExpr.right, tempVar.copy(), binExpr)
|
||||
// )
|
||||
}
|
||||
|
||||
if(binExpr.operator == "==" || binExpr.operator == "!=") {
|
||||
// don't split if the operand(s) don't fit the type of the resulting variable
|
||||
val targetDt = assignment.target.inferType(program)
|
||||
val leftDt = binExpr.left.inferType(program)
|
||||
val rightDt = binExpr.right.inferType(program)
|
||||
if(leftDt isNotAssignableTo targetDt || rightDt isNotAssignableTo targetDt)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
if(binExpr.right.isSimple) {
|
||||
val firstAssign = Assignment(assignment.target.copy(), binExpr.left, binExpr.left.position)
|
||||
val targetExpr = assignment.target.toExpression()
|
||||
val augExpr = BinaryExpression(targetExpr, binExpr.operator, binExpr.right, binExpr.right.position)
|
||||
@ -78,16 +94,43 @@ X = BinExpr X = LeftExpr
|
||||
}
|
||||
|
||||
// TODO further unraveling of binary expression trees into flat statements.
|
||||
// however this should probably be done in a more generic way to also service
|
||||
// however this should probably be done in a more generic way to also work on
|
||||
// the expressiontrees that are not used in an assignment statement...
|
||||
}
|
||||
|
||||
val typecast = assignment.value as? TypecastExpression
|
||||
if(typecast!=null) {
|
||||
val origExpr = typecast.expression as? BinaryExpression
|
||||
if(origExpr!=null) {
|
||||
// it's a typecast of a binary expression.
|
||||
// we can see if we can unwrap the binary expression by working on a new temporary variable
|
||||
// (that has the type of the expression), and then finally doing the typecast.
|
||||
// Once it's outside the typecast, the regular splitting can commence.
|
||||
val tempVar = when(val tempDt = origExpr.inferType(program).getOr(DataType.UNDEFINED)) {
|
||||
DataType.UBYTE -> listOf("prog8_lib", "retval_interm_ub")
|
||||
DataType.BYTE -> listOf("prog8_lib", "retval_interm_b")
|
||||
DataType.UWORD -> listOf("prog8_lib", "retval_interm_uw")
|
||||
DataType.WORD -> listOf("prog8_lib", "retval_interm_w")
|
||||
DataType.FLOAT -> listOf("floats", "tempvar_swap_float")
|
||||
else -> throw FatalAstException("invalid dt $tempDt")
|
||||
}
|
||||
val assignTempVar = Assignment(
|
||||
AssignTarget(IdentifierReference(tempVar, typecast.position), null, null, typecast.position),
|
||||
typecast.expression, typecast.position
|
||||
)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(assignment, assignTempVar, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(typecast.expression, IdentifierReference(tempVar, typecast.position), typecast)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun isSimpleTarget(target: AssignTarget) =
|
||||
if (target.identifier!=null || target.memoryAddress!=null)
|
||||
target.isInRegularRAMof(compTarget.machine)
|
||||
!target.isIOAddress(compTarget.machine)
|
||||
else
|
||||
false
|
||||
|
||||
|
@ -46,14 +46,14 @@ class ConstExprEvaluator {
|
||||
left.number.toInt().ushr(amount.number.toInt())
|
||||
else
|
||||
left.number.toInt().shr(amount.number.toInt())
|
||||
return NumericLiteralValue(left.type, result, left.position)
|
||||
return NumericLiteralValue(left.type, result.toDouble(), left.position)
|
||||
}
|
||||
|
||||
private fun shiftedleft(left: NumericLiteralValue, amount: NumericLiteralValue): Expression {
|
||||
if(left.type !in IntegerDatatypes || amount.type !in IntegerDatatypes)
|
||||
throw ExpressionError("cannot compute $left << $amount", left.position)
|
||||
val result = left.number.toInt().shl(amount.number.toInt())
|
||||
return NumericLiteralValue(left.type, result, left.position)
|
||||
return NumericLiteralValue(left.type, result.toDouble(), left.position)
|
||||
}
|
||||
|
||||
private fun logicalxor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||
@ -61,12 +61,12 @@ class ConstExprEvaluator {
|
||||
return when (left.type) {
|
||||
in IntegerDatatypes -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number.toInt() != 0), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number.toDouble() != 0.0), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number != 0.0), left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
DataType.FLOAT -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number.toDouble() != 0.0) xor (right.number.toInt() != 0), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number.toDouble() != 0.0) xor (right.number.toDouble() != 0.0), left.position)
|
||||
in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number != 0.0) xor (right.number.toInt() != 0), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number != 0.0) xor (right.number != 0.0), left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
@ -78,12 +78,12 @@ class ConstExprEvaluator {
|
||||
return when (left.type) {
|
||||
in IntegerDatatypes -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number.toInt() != 0, left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number.toDouble() != 0.0, left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number != 0.0, left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
DataType.FLOAT -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 || right.number.toInt() != 0, left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 || right.number.toDouble() != 0.0, left.position)
|
||||
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number != 0.0 || right.number.toInt() != 0, left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number != 0.0 || right.number != 0.0, left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
@ -95,12 +95,12 @@ class ConstExprEvaluator {
|
||||
return when (left.type) {
|
||||
in IntegerDatatypes -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number.toInt() != 0, left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number.toDouble() != 0.0, left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number != 0.0, left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
DataType.FLOAT -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 && right.number.toInt() != 0, left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 && right.number.toDouble() != 0.0, left.position)
|
||||
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number != 0.0 && right.number.toInt() != 0, left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number != 0.0 && right.number != 0.0, left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
@ -110,11 +110,11 @@ class ConstExprEvaluator {
|
||||
private fun bitwisexor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||
if(left.type== DataType.UBYTE) {
|
||||
if(right.type in IntegerDatatypes) {
|
||||
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() xor (right.number.toInt() and 255)).toShort(), left.position)
|
||||
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() xor (right.number.toInt() and 255)).toDouble(), left.position)
|
||||
}
|
||||
} else if(left.type== DataType.UWORD) {
|
||||
if(right.type in IntegerDatatypes) {
|
||||
return NumericLiteralValue(DataType.UWORD, left.number.toInt() xor right.number.toInt(), left.position)
|
||||
return NumericLiteralValue(DataType.UWORD, (left.number.toInt() xor right.number.toInt()).toDouble(), left.position)
|
||||
}
|
||||
}
|
||||
throw ExpressionError("cannot calculate $left ^ $right", left.position)
|
||||
@ -123,11 +123,11 @@ class ConstExprEvaluator {
|
||||
private fun bitwiseor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||
if(left.type== DataType.UBYTE) {
|
||||
if(right.type in IntegerDatatypes) {
|
||||
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() or (right.number.toInt() and 255)).toShort(), left.position)
|
||||
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() or (right.number.toInt() and 255)).toDouble(), left.position)
|
||||
}
|
||||
} else if(left.type== DataType.UWORD) {
|
||||
if(right.type in IntegerDatatypes) {
|
||||
return NumericLiteralValue(DataType.UWORD, left.number.toInt() or right.number.toInt(), left.position)
|
||||
return NumericLiteralValue(DataType.UWORD, (left.number.toInt() or right.number.toInt()).toDouble(), left.position)
|
||||
}
|
||||
}
|
||||
throw ExpressionError("cannot calculate $left | $right", left.position)
|
||||
@ -136,11 +136,11 @@ class ConstExprEvaluator {
|
||||
private fun bitwiseand(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||
if(left.type== DataType.UBYTE) {
|
||||
if(right.type in IntegerDatatypes) {
|
||||
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() and (right.number.toInt() and 255)).toShort(), left.position)
|
||||
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() and (right.number.toInt() and 255)).toDouble(), left.position)
|
||||
}
|
||||
} else if(left.type== DataType.UWORD) {
|
||||
if(right.type in IntegerDatatypes) {
|
||||
return NumericLiteralValue(DataType.UWORD, left.number.toInt() and right.number.toInt(), left.position)
|
||||
return NumericLiteralValue(DataType.UWORD, (left.number.toInt() and right.number.toInt()).toDouble(), left.position)
|
||||
}
|
||||
}
|
||||
throw ExpressionError("cannot calculate $left & $right", left.position)
|
||||
@ -151,12 +151,12 @@ class ConstExprEvaluator {
|
||||
return when (left.type) {
|
||||
in IntegerDatatypes -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt().toDouble().pow(right.number.toInt()), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt().toDouble().pow(right.number.toDouble()), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt().toDouble().pow(right.number), left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
DataType.FLOAT -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble().pow(right.number.toInt()), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble().pow(right.number.toDouble()), left.position)
|
||||
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.pow(right.number.toInt()), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.pow(right.number), left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
@ -168,12 +168,12 @@ class ConstExprEvaluator {
|
||||
return when (left.type) {
|
||||
in IntegerDatatypes -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() + right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() + right.number.toDouble(), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() + right.number, left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
DataType.FLOAT -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() + right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() + right.number.toDouble(), left.position)
|
||||
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number + right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number + right.number, left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
@ -185,12 +185,12 @@ class ConstExprEvaluator {
|
||||
return when (left.type) {
|
||||
in IntegerDatatypes -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() - right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() - right.number.toDouble(), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() - right.number, left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
DataType.FLOAT -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() - right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() - right.number.toDouble(), left.position)
|
||||
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number - right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number - right.number, left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
@ -202,12 +202,12 @@ class ConstExprEvaluator {
|
||||
return when (left.type) {
|
||||
in IntegerDatatypes -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() * right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() * right.number.toDouble(), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() * right.number, left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
DataType.FLOAT -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() * right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() * right.number.toDouble(), left.position)
|
||||
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number * right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number * right.number, left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
@ -227,19 +227,19 @@ class ConstExprEvaluator {
|
||||
NumericLiteralValue.optimalInteger(result, left.position)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
|
||||
NumericLiteralValue(DataType.FLOAT, left.number.toInt() / right.number.toDouble(), left.position)
|
||||
if(right.number==0.0) divideByZeroError(right.position)
|
||||
NumericLiteralValue(DataType.FLOAT, left.number.toInt() / right.number, left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
DataType.FLOAT -> when (right.type) {
|
||||
in IntegerDatatypes -> {
|
||||
if(right.number.toInt()==0) divideByZeroError(right.position)
|
||||
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() / right.number.toInt(), left.position)
|
||||
NumericLiteralValue(DataType.FLOAT, left.number / right.number.toInt(), left.position)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
|
||||
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() / right.number.toDouble(), left.position)
|
||||
if(right.number ==0.0) divideByZeroError(right.position)
|
||||
NumericLiteralValue(DataType.FLOAT, left.number / right.number, left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
@ -256,19 +256,19 @@ class ConstExprEvaluator {
|
||||
NumericLiteralValue.optimalNumeric(left.number.toInt().toDouble() % right.number.toInt().toDouble(), left.position)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
|
||||
NumericLiteralValue(DataType.FLOAT, left.number.toInt() % right.number.toDouble(), left.position)
|
||||
if(right.number ==0.0) divideByZeroError(right.position)
|
||||
NumericLiteralValue(DataType.FLOAT, left.number.toInt() % right.number, left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
DataType.FLOAT -> when (right.type) {
|
||||
in IntegerDatatypes -> {
|
||||
if(right.number.toInt()==0) divideByZeroError(right.position)
|
||||
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() % right.number.toInt(), left.position)
|
||||
NumericLiteralValue(DataType.FLOAT, left.number % right.number.toInt(), left.position)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
|
||||
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() % right.number.toDouble(), left.position)
|
||||
if(right.number ==0.0) divideByZeroError(right.position)
|
||||
NumericLiteralValue(DataType.FLOAT, left.number % right.number, left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
|
@ -23,6 +23,13 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
noModifications
|
||||
}
|
||||
|
||||
override fun after(containment: ContainmentCheck, parent: Node): Iterable<IAstModification> {
|
||||
val result = containment.constValue(program)
|
||||
if(result!=null)
|
||||
return listOf(IAstModification.ReplaceNode(containment, result, parent))
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> {
|
||||
// Try to turn a unary prefix expression into a single constant value.
|
||||
// Compile-time constant sub expressions will be evaluated on the spot.
|
||||
@ -40,7 +47,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteralValue(DataType.FLOAT, -subexpr.number.toDouble(), subexpr.position),
|
||||
NumericLiteralValue(DataType.FLOAT, -subexpr.number, subexpr.position),
|
||||
parent))
|
||||
}
|
||||
else -> throw ExpressionError("can only take negative of int or float", subexpr.position)
|
||||
@ -48,29 +55,29 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
"~" -> when (subexpr.type) {
|
||||
DataType.BYTE -> {
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteralValue(DataType.BYTE, subexpr.number.toInt().inv(), subexpr.position),
|
||||
NumericLiteralValue(DataType.BYTE, subexpr.number.toInt().inv().toDouble(), subexpr.position),
|
||||
parent))
|
||||
}
|
||||
DataType.UBYTE -> {
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteralValue(DataType.UBYTE, subexpr.number.toInt().inv() and 255, subexpr.position),
|
||||
NumericLiteralValue(DataType.UBYTE, (subexpr.number.toInt().inv() and 255).toDouble(), subexpr.position),
|
||||
parent))
|
||||
}
|
||||
DataType.WORD -> {
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteralValue(DataType.WORD, subexpr.number.toInt().inv(), subexpr.position),
|
||||
NumericLiteralValue(DataType.WORD, subexpr.number.toInt().inv().toDouble(), subexpr.position),
|
||||
parent))
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteralValue(DataType.UWORD, subexpr.number.toInt().inv() and 65535, subexpr.position),
|
||||
NumericLiteralValue(DataType.UWORD, (subexpr.number.toInt().inv() and 65535).toDouble(), subexpr.position),
|
||||
parent))
|
||||
}
|
||||
else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position)
|
||||
}
|
||||
"not" -> {
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteralValue.fromBoolean(subexpr.number.toDouble() == 0.0, subexpr.position),
|
||||
NumericLiteralValue.fromBoolean(subexpr.number == 0.0, subexpr.position),
|
||||
parent))
|
||||
}
|
||||
else -> throw ExpressionError(expr.operator, subexpr.position)
|
||||
@ -79,7 +86,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
return noModifications
|
||||
}
|
||||
|
||||
/**
|
||||
/*
|
||||
* Try to constfold a binary expression.
|
||||
* Compile-time constant sub expressions will be evaluated on the spot.
|
||||
* For instance, "9 * (4 + 2)" will be optimized into the integer literal 54.
|
||||
@ -101,23 +108,50 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
val rightconst = expr.right.constValue(program)
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
|
||||
if(expr.operator=="==" && rightconst!=null) {
|
||||
val leftExpr = expr.left as? BinaryExpression
|
||||
if(leftExpr!=null) {
|
||||
val leftRightConst = leftExpr.right.constValue(program)
|
||||
if(leftRightConst!=null) {
|
||||
when (leftExpr.operator) {
|
||||
"+" -> {
|
||||
// X + С1 == C2 --> X == C2 - C1
|
||||
val newRightConst = NumericLiteralValue(rightconst.type, rightconst.number - leftRightConst.number, rightconst.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(leftExpr, leftExpr.left, expr),
|
||||
IAstModification.ReplaceNode(expr.right, newRightConst, expr)
|
||||
)
|
||||
}
|
||||
"-" -> {
|
||||
// X - С1 == C2 --> X == C2 + C1
|
||||
val newRightConst = NumericLiteralValue(rightconst.type, rightconst.number + leftRightConst.number, rightconst.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(leftExpr, leftExpr.left, expr),
|
||||
IAstModification.ReplaceNode(expr.right, newRightConst, expr)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(expr.operator == "**" && leftconst!=null) {
|
||||
// optimize various simple cases of ** :
|
||||
// optimize away 1 ** x into just 1 and 0 ** x into just 0
|
||||
// optimize 2 ** x into (1<<x) if both operands are integer.
|
||||
val leftDt = leftconst.inferType(program).getOr(DataType.UNDEFINED)
|
||||
when (leftconst.number.toDouble()) {
|
||||
when (leftconst.number) {
|
||||
0.0 -> {
|
||||
val value = NumericLiteralValue(leftDt, 0, expr.position)
|
||||
val value = NumericLiteralValue(leftDt, 0.0, expr.position)
|
||||
modifications += IAstModification.ReplaceNode(expr, value, parent)
|
||||
}
|
||||
1.0 -> {
|
||||
val value = NumericLiteralValue(leftDt, 1, expr.position)
|
||||
val value = NumericLiteralValue(leftDt, 1.0, expr.position)
|
||||
modifications += IAstModification.ReplaceNode(expr, value, parent)
|
||||
}
|
||||
2.0 -> {
|
||||
if(rightconst!=null) {
|
||||
val value = NumericLiteralValue(leftDt, 2.0.pow(rightconst.number.toDouble()), expr.position)
|
||||
val value = NumericLiteralValue(leftDt, 2.0.pow(rightconst.number), expr.position)
|
||||
modifications += IAstModification.ReplaceNode(expr, value, parent)
|
||||
} else {
|
||||
val rightDt = expr.right.inferType(program).getOr(DataType.UNDEFINED)
|
||||
@ -128,7 +162,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
is VarDecl -> parent.datatype
|
||||
else -> leftDt
|
||||
}
|
||||
val one = NumericLiteralValue(targetDt, 1, expr.position)
|
||||
val one = NumericLiteralValue(targetDt, 1.0, expr.position)
|
||||
val shift = BinaryExpression(one, "<<", expr.right, expr.position)
|
||||
modifications += IAstModification.ReplaceNode(expr, shift, parent)
|
||||
}
|
||||
@ -159,13 +193,94 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
}
|
||||
}
|
||||
|
||||
val evaluator = ConstExprEvaluator()
|
||||
|
||||
// const fold when both operands are a const
|
||||
if(leftconst != null && rightconst != null) {
|
||||
val evaluator = ConstExprEvaluator()
|
||||
val result = evaluator.evaluate(leftconst, expr.operator, rightconst)
|
||||
modifications += IAstModification.ReplaceNode(expr, result, parent)
|
||||
}
|
||||
|
||||
|
||||
val leftBinExpr = expr.left as? BinaryExpression
|
||||
val rightBinExpr = expr.right as? BinaryExpression
|
||||
|
||||
if(leftBinExpr!=null && rightconst!=null) {
|
||||
if(expr.operator=="+" || expr.operator=="-") {
|
||||
if(leftBinExpr.operator in listOf("+", "-")) {
|
||||
val c2 = leftBinExpr.right.constValue(program)
|
||||
if(c2!=null) {
|
||||
// (X + C2) +/- rightConst --> X + (C2 +/- rightConst)
|
||||
// (X - C2) +/- rightConst --> X - (C2 +/- rightConst) mind the flipped right operator
|
||||
val operator = if(leftBinExpr.operator=="+") expr.operator else if(expr.operator=="-") "+" else "-"
|
||||
val constants = BinaryExpression(c2, operator, rightconst, c2.position)
|
||||
val newExpr = BinaryExpression(leftBinExpr.left, leftBinExpr.operator, constants, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(expr.operator=="*") {
|
||||
val c2 = leftBinExpr.right.constValue(program)
|
||||
if(c2!=null) {
|
||||
if(leftBinExpr.operator=="*") {
|
||||
// (X * C2) * rightConst --> X * (rightConst*C2)
|
||||
val constants = BinaryExpression(rightconst, "*", c2, c2.position)
|
||||
val newExpr = BinaryExpression(leftBinExpr.left, "*", constants, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||
} else if (leftBinExpr.operator=="/") {
|
||||
// (X / C2) * rightConst --> X * (rightConst/C2)
|
||||
val constants = BinaryExpression(rightconst, "/", c2, c2.position)
|
||||
val newExpr = BinaryExpression(leftBinExpr.left, "*", constants, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(expr.operator=="/") {
|
||||
val c2 = leftBinExpr.right.constValue(program)
|
||||
if(c2!=null && leftBinExpr.operator=="/") {
|
||||
// (X / C1) / C2 --> X / (C1*C2)
|
||||
// NOTE: do not optimize (X * C1) / C2 --> X * (C1/C2) because this causes precision loss on integers
|
||||
val constants = BinaryExpression(c2, "*", rightconst, c2.position)
|
||||
val newExpr = BinaryExpression(leftBinExpr.left, "/", constants, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(expr.operator=="+" || expr.operator=="-") {
|
||||
if(leftBinExpr!=null && rightBinExpr!=null) {
|
||||
val c1 = leftBinExpr.right.constValue(program)
|
||||
val c2 = rightBinExpr.right.constValue(program)
|
||||
if(leftBinExpr.operator=="+" && rightBinExpr.operator=="+") {
|
||||
if (c1 != null && c2 != null) {
|
||||
// (X + C1) <plusmin> (Y + C2) => (X <plusmin> Y) + (C1 <plusmin> C2)
|
||||
val c3 = evaluator.evaluate(c1, expr.operator, c2)
|
||||
val xwithy = BinaryExpression(leftBinExpr.left, expr.operator, rightBinExpr.left, expr.position)
|
||||
val newExpr = BinaryExpression(xwithy, "+", c3, expr.position)
|
||||
modifications += IAstModification.ReplaceNode(expr, newExpr, parent)
|
||||
}
|
||||
}
|
||||
else if(leftBinExpr.operator=="-" && rightBinExpr.operator=="-") {
|
||||
if (c1 != null && c2 != null) {
|
||||
// (X - C1) <plusmin> (Y - C2) => (X <plusmin> Y) - (C1 <plusmin> C2)
|
||||
val c3 = evaluator.evaluate(c1, expr.operator, c2)
|
||||
val xwithy = BinaryExpression(leftBinExpr.left, expr.operator, rightBinExpr.left, expr.position)
|
||||
val newExpr = BinaryExpression(xwithy, "-", c3, expr.position)
|
||||
modifications += IAstModification.ReplaceNode(expr, newExpr, parent)
|
||||
}
|
||||
}
|
||||
else if(leftBinExpr.operator=="*" && rightBinExpr.operator=="*"){
|
||||
if (c1 != null && c2 != null && c1==c2) {
|
||||
//(X * C) <plusmin> (Y * C) => (X <plusmin> Y) * C
|
||||
val xwithy = BinaryExpression(leftBinExpr.left, expr.operator, rightBinExpr.left, expr.position)
|
||||
val newExpr = BinaryExpression(xwithy, "*", c1, expr.position)
|
||||
modifications += IAstModification.ReplaceNode(expr, newExpr, parent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return modifications
|
||||
}
|
||||
|
||||
@ -195,11 +310,11 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||
override fun after(functionCallExpr: FunctionCallExpr, parent: Node): Iterable<IAstModification> {
|
||||
// the args of a fuction are constfolded via recursion already.
|
||||
val constvalue = functionCall.constValue(program)
|
||||
val constvalue = functionCallExpr.constValue(program)
|
||||
return if(constvalue!=null)
|
||||
listOf(IAstModification.ReplaceNode(functionCall, constvalue, parent))
|
||||
listOf(IAstModification.ReplaceNode(functionCallExpr, constvalue, parent))
|
||||
else
|
||||
noModifications
|
||||
}
|
||||
@ -312,12 +427,12 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
{
|
||||
// NOTE: THIS IS ONLY VALID ON FLOATING POINT CONSTANTS
|
||||
|
||||
// todo: this implements only a small set of possible reorderings at this time
|
||||
// todo: this implements only a small set of possible reorderings at this time, we could think of more
|
||||
if(expr.operator==subExpr.operator) {
|
||||
// both operators are the same.
|
||||
|
||||
// If associative, we can simply shuffle the const operands around to optimize.
|
||||
if(expr.operator in associativeOperators) {
|
||||
if(expr.operator in AssociativeOperators) {
|
||||
return if(leftIsConst) {
|
||||
if(subleftIsConst)
|
||||
ShuffleOperands(expr, null, subExpr, subExpr.right, null, null, expr.left)
|
||||
|
@ -9,8 +9,6 @@ import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
import prog8.compilerinterface.size
|
||||
import prog8.compilerinterface.toConstantIntegerRange
|
||||
|
||||
// Fix up the literal value's type to match that of the vardecl
|
||||
// (also check range literal operands types before they get expanded into arrays for instance)
|
||||
@ -24,11 +22,16 @@ class VarConstantValueTypeAdjuster(private val program: Program, private val err
|
||||
try {
|
||||
val declConstValue = decl.value?.constValue(program)
|
||||
if(declConstValue!=null && (decl.type==VarDeclType.VAR || decl.type==VarDeclType.CONST)
|
||||
&& declConstValue.inferType(program) isnot decl.datatype) {
|
||||
// cast the numeric literal to the appropriate datatype of the variable
|
||||
val cast = declConstValue.cast(decl.datatype)
|
||||
if(cast.isValid)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, cast.valueOrZero(), decl))
|
||||
&& declConstValue.type != decl.datatype) {
|
||||
// avoid silent float roundings
|
||||
if(decl.datatype in IntegerDatatypes && declConstValue.type==DataType.FLOAT) {
|
||||
errors.err("refused silent rounding of float to avoid loss of precision", decl.value!!.position)
|
||||
} else {
|
||||
// cast the numeric literal to the appropriate datatype of the variable
|
||||
val cast = declConstValue.cast(decl.datatype)
|
||||
if (cast.isValid)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, cast.valueOrZero(), decl))
|
||||
}
|
||||
}
|
||||
} catch (x: UndefinedSymbolError) {
|
||||
errors.err(x.message, x.position)
|
||||
@ -38,9 +41,9 @@ class VarConstantValueTypeAdjuster(private val program: Program, private val err
|
||||
}
|
||||
|
||||
override fun after(range: RangeExpr, parent: Node): Iterable<IAstModification> {
|
||||
val from = range.from.constValue(program)?.number?.toDouble()
|
||||
val to = range.to.constValue(program)?.number?.toDouble()
|
||||
val step = range.step.constValue(program)?.number?.toDouble()
|
||||
val from = range.from.constValue(program)?.number
|
||||
val to = range.to.constValue(program)?.number
|
||||
val step = range.step.constValue(program)?.number
|
||||
|
||||
if(from==null) {
|
||||
if(!range.from.inferType(program).isInteger)
|
||||
@ -105,8 +108,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
|
||||
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
// the initializer value can't refer to the variable itself (recursive definition)
|
||||
// TODO: use call graph for this?
|
||||
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.indexExpr?.referencesIdentifier(decl.name) == true) {
|
||||
if(decl.value?.referencesIdentifier(listOf(decl.name)) == true || decl.arraysize?.indexExpr?.referencesIdentifier(listOf(decl.name)) == true) {
|
||||
errors.err("recursive var declaration", decl.position)
|
||||
return noModifications
|
||||
}
|
||||
@ -132,7 +134,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
// vardecl: for scalar float vars, promote constant integer initialization values to floats
|
||||
val litval = decl.value as? NumericLiteralValue
|
||||
if (litval!=null && litval.type in IntegerDatatypes) {
|
||||
val newValue = NumericLiteralValue(DataType.FLOAT, litval.number.toDouble(), litval.position)
|
||||
val newValue = NumericLiteralValue(DataType.FLOAT, litval.number, litval.position)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||
}
|
||||
}
|
||||
@ -142,17 +144,17 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
// convert the initializer range expression to an actual array
|
||||
val declArraySize = decl.arraysize?.constIndex()
|
||||
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
||||
errors.err("range expression size doesn't match declared array size", decl.value?.position!!)
|
||||
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
|
||||
val constRange = rangeExpr.toConstantIntegerRange()
|
||||
if(constRange!=null) {
|
||||
val eltType = rangeExpr.inferType(program).getOr(DataType.UBYTE)
|
||||
val newValue = if(eltType in ByteDatatypes) {
|
||||
ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
|
||||
constRange.map { NumericLiteralValue(eltType, it.toShort(), decl.value!!.position) }.toTypedArray(),
|
||||
constRange.map { NumericLiteralValue(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
} else {
|
||||
ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
|
||||
constRange.map { NumericLiteralValue(eltType, it, decl.value!!.position) }.toTypedArray(),
|
||||
constRange.map { NumericLiteralValue(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||
@ -185,7 +187,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
else -> {}
|
||||
}
|
||||
// create the array itself, filled with the fillvalue.
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteralValue(ArrayToElementTypes.getValue(decl.datatype), it, numericLv.position) }.toTypedArray<Expression>()
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteralValue(ArrayToElementTypes.getValue(decl.datatype), it.toDouble(), numericLv.position) }.toTypedArray<Expression>()
|
||||
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
|
||||
}
|
||||
@ -210,7 +212,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
val size = decl.arraysize?.constIndex() ?: return noModifications
|
||||
if(rangeExpr==null && numericLv!=null) {
|
||||
// arraysize initializer is a single int, and we know the size.
|
||||
val fillvalue = numericLv.number.toDouble()
|
||||
val fillvalue = numericLv.number
|
||||
if (fillvalue < compTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > compTarget.machine.FLOAT_MAX_POSITIVE)
|
||||
errors.err("float value overflow", numericLv.position)
|
||||
else {
|
||||
|
@ -1,5 +1,6 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.IStatementContainer
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
@ -7,7 +8,10 @@ import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.base.IntegerDatatypes
|
||||
import prog8.ast.base.NumericDatatypes
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.AnonymousScope
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.IfElse
|
||||
import prog8.ast.statements.Jump
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import kotlin.math.abs
|
||||
@ -19,17 +23,6 @@ import kotlin.math.pow
|
||||
|
||||
Investigate what optimizations binaryen has, also see https://egorbo.com/peephole-optimizations.html
|
||||
|
||||
*(&X) => X
|
||||
X % 1 => 0
|
||||
X / 1 => X
|
||||
X ^ -1 => ~x
|
||||
X >= 1 => X > 0
|
||||
X < 1 => X <= 0
|
||||
X + С1 == C2 => X == C2 - C1
|
||||
((X + C1) + C2) => (X + (C1 + C2))
|
||||
((X + C1) + (Y + C2)) => ((X + Y) + (C1 + C2))
|
||||
|
||||
|
||||
*/
|
||||
|
||||
|
||||
@ -65,35 +58,26 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
return mods
|
||||
}
|
||||
|
||||
override fun before(expr: PrefixExpression, parent: Node): Iterable<IAstModification> {
|
||||
if (expr.operator == "+") {
|
||||
// +X --> X
|
||||
return listOf(IAstModification.ReplaceNode(expr, expr.expression, parent))
|
||||
} else if (expr.operator == "not") {
|
||||
when(expr.expression) {
|
||||
is PrefixExpression -> {
|
||||
// NOT(NOT(...)) -> ...
|
||||
val pe = expr.expression as PrefixExpression
|
||||
if(pe.operator == "not")
|
||||
return listOf(IAstModification.ReplaceNode(expr, pe.expression, parent))
|
||||
override fun after(ifElse: IfElse, parent: Node): Iterable<IAstModification> {
|
||||
val truepart = ifElse.truepart
|
||||
val elsepart = ifElse.elsepart
|
||||
if(truepart.isNotEmpty() && elsepart.isNotEmpty()) {
|
||||
if(truepart.statements.singleOrNull() is Jump) {
|
||||
return listOf(
|
||||
IAstModification.InsertAfter(ifElse, elsepart, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(elsepart, AnonymousScope(mutableListOf(), elsepart.position), ifElse)
|
||||
)
|
||||
}
|
||||
if(elsepart.statements.singleOrNull() is Jump) {
|
||||
val invertedCondition = invertCondition(ifElse.condition)
|
||||
if(invertedCondition!=null) {
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(ifElse.condition, invertedCondition, ifElse),
|
||||
IAstModification.InsertAfter(ifElse, truepart, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(elsepart, AnonymousScope(mutableListOf(), elsepart.position), ifElse),
|
||||
IAstModification.ReplaceNode(truepart, elsepart, ifElse)
|
||||
)
|
||||
}
|
||||
is BinaryExpression -> {
|
||||
// NOT (xxxx) -> invert the xxxx
|
||||
val be = expr.expression as BinaryExpression
|
||||
val newExpr = when (be.operator) {
|
||||
"<" -> BinaryExpression(be.left, ">=", be.right, be.position)
|
||||
">" -> BinaryExpression(be.left, "<=", be.right, be.position)
|
||||
"<=" -> BinaryExpression(be.left, ">", be.right, be.position)
|
||||
">=" -> BinaryExpression(be.left, "<", be.right, be.position)
|
||||
"==" -> BinaryExpression(be.left, "!=", be.right, be.position)
|
||||
"!=" -> BinaryExpression(be.left, "==", be.right, be.position)
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (newExpr != null)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||
}
|
||||
else -> return noModifications
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
@ -109,11 +93,11 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
throw FatalAstException("can't determine datatype of both expression operands $expr")
|
||||
|
||||
// ConstValue <associativeoperator> X --> X <associativeoperator> ConstValue
|
||||
if (leftVal != null && expr.operator in associativeOperators && rightVal == null)
|
||||
if (leftVal != null && expr.operator in AssociativeOperators && rightVal == null)
|
||||
return listOf(IAstModification.SwapOperands(expr))
|
||||
|
||||
// NonBinaryExpression <associativeoperator> BinaryExpression --> BinaryExpression <associativeoperator> NonBinaryExpression
|
||||
if (expr.operator in associativeOperators && expr.left !is BinaryExpression && expr.right is BinaryExpression) {
|
||||
if (expr.operator in AssociativeOperators && expr.left !is BinaryExpression && expr.right is BinaryExpression) {
|
||||
if(parent !is Assignment || !(expr.left isSameAs parent.target))
|
||||
return listOf(IAstModification.SwapOperands(expr))
|
||||
}
|
||||
@ -160,7 +144,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
val x = expr.right
|
||||
val y = determineY(x, leftBinExpr)
|
||||
if (y != null) {
|
||||
val yPlus1 = BinaryExpression(y, "+", NumericLiteralValue(leftDt, 1, y.position), y.position)
|
||||
val yPlus1 = BinaryExpression(y, "+", NumericLiteralValue(leftDt, 1.0, y.position), y.position)
|
||||
val newExpr = BinaryExpression(x, "*", yPlus1, x.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||
}
|
||||
@ -170,7 +154,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
val x = expr.right
|
||||
val y = determineY(x, leftBinExpr)
|
||||
if (y != null) {
|
||||
val yMinus1 = BinaryExpression(y, "-", NumericLiteralValue(leftDt, 1, y.position), y.position)
|
||||
val yMinus1 = BinaryExpression(y, "-", NumericLiteralValue(leftDt, 1.0, y.position), y.position)
|
||||
val newExpr = BinaryExpression(x, "*", yMinus1, x.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||
}
|
||||
@ -190,14 +174,26 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
}
|
||||
}
|
||||
|
||||
if(expr.operator == ">=" && rightVal?.number == 0) {
|
||||
if(leftDt!=DataType.FLOAT && expr.operator == ">=" && rightVal?.number == 1.0) {
|
||||
// for integers: x >= 1 --> x > 0
|
||||
expr.operator = ">"
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteralValue.optimalInteger(0, expr.right.position), expr))
|
||||
}
|
||||
|
||||
if(expr.operator == ">=" && rightVal?.number == 0.0) {
|
||||
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
|
||||
// unsigned >= 0 --> true
|
||||
return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(true, expr.position), parent))
|
||||
}
|
||||
}
|
||||
|
||||
if(expr.operator == "<" && rightVal?.number == 0) {
|
||||
if(leftDt!=DataType.FLOAT && expr.operator == "<" && rightVal?.number == 1.0) {
|
||||
// for integers: x < 1 --> x <= 0
|
||||
expr.operator = "<="
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteralValue.optimalInteger(0, expr.right.position), expr))
|
||||
}
|
||||
|
||||
if(expr.operator == "<" && rightVal?.number == 0.0) {
|
||||
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
|
||||
// unsigned < 0 --> false
|
||||
return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(false, expr.position), parent))
|
||||
@ -215,7 +211,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
// signedw < 0 --> msb(signedw) & $80
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr,
|
||||
BinaryExpression(FunctionCall(IdentifierReference(listOf("msb"), expr.position),
|
||||
BinaryExpression(FunctionCallExpr(IdentifierReference(listOf("msb"), expr.position),
|
||||
mutableListOf(expr.left),
|
||||
expr.position
|
||||
), "&", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
|
||||
@ -231,52 +227,62 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
val constFalse = NumericLiteralValue.fromBoolean(false, expr.position)
|
||||
val newExpr: Expression? = when (expr.operator) {
|
||||
"or" -> {
|
||||
if ((leftVal != null && leftVal.asBooleanValue) || (rightVal != null && rightVal.asBooleanValue))
|
||||
constTrue
|
||||
else if (leftVal != null && !leftVal.asBooleanValue)
|
||||
expr.right
|
||||
else if (rightVal != null && !rightVal.asBooleanValue)
|
||||
expr.left
|
||||
else
|
||||
null
|
||||
when {
|
||||
leftVal != null && leftVal.asBooleanValue || rightVal != null && rightVal.asBooleanValue -> constTrue
|
||||
leftVal != null && !leftVal.asBooleanValue -> expr.right
|
||||
rightVal != null && !rightVal.asBooleanValue -> expr.left
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
"and" -> {
|
||||
if ((leftVal != null && !leftVal.asBooleanValue) || (rightVal != null && !rightVal.asBooleanValue))
|
||||
constFalse
|
||||
else if (leftVal != null && leftVal.asBooleanValue)
|
||||
expr.right
|
||||
else if (rightVal != null && rightVal.asBooleanValue)
|
||||
expr.left
|
||||
else
|
||||
null
|
||||
when {
|
||||
leftVal != null && !leftVal.asBooleanValue || rightVal != null && !rightVal.asBooleanValue -> constFalse
|
||||
leftVal != null && leftVal.asBooleanValue -> expr.right
|
||||
rightVal != null && rightVal.asBooleanValue -> expr.left
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
"xor" -> {
|
||||
if (leftVal != null && !leftVal.asBooleanValue)
|
||||
expr.right
|
||||
else if (rightVal != null && !rightVal.asBooleanValue)
|
||||
expr.left
|
||||
else if (leftVal != null && leftVal.asBooleanValue)
|
||||
PrefixExpression("not", expr.right, expr.right.position)
|
||||
else if (rightVal != null && rightVal.asBooleanValue)
|
||||
PrefixExpression("not", expr.left, expr.left.position)
|
||||
else
|
||||
null
|
||||
when {
|
||||
leftVal != null && !leftVal.asBooleanValue -> expr.right
|
||||
rightVal != null && !rightVal.asBooleanValue -> expr.left
|
||||
leftVal != null && leftVal.asBooleanValue -> PrefixExpression("not", expr.right, expr.right.position)
|
||||
rightVal != null && rightVal.asBooleanValue -> PrefixExpression("not", expr.left, expr.left.position)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
"|", "^" -> {
|
||||
if (leftVal != null && !leftVal.asBooleanValue)
|
||||
expr.right
|
||||
else if (rightVal != null && !rightVal.asBooleanValue)
|
||||
expr.left
|
||||
else
|
||||
null
|
||||
"|" -> {
|
||||
when {
|
||||
leftVal?.number==0.0 -> expr.right
|
||||
rightVal?.number==0.0 -> expr.left
|
||||
rightIDt.isBytes && rightVal?.number==255.0 -> NumericLiteralValue(DataType.UBYTE, 255.0, rightVal.position)
|
||||
rightIDt.isWords && rightVal?.number==65535.0 -> NumericLiteralValue(DataType.UWORD, 65535.0, rightVal.position)
|
||||
leftIDt.isBytes && leftVal?.number==255.0 -> NumericLiteralValue(DataType.UBYTE, 255.0, leftVal.position)
|
||||
leftIDt.isWords && leftVal?.number==65535.0 -> NumericLiteralValue(DataType.UWORD, 65535.0, leftVal.position)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
"^" -> {
|
||||
when {
|
||||
leftVal?.number==0.0 -> expr.right
|
||||
rightVal?.number==0.0 -> expr.left
|
||||
rightIDt.isBytes && rightVal?.number==255.0 -> PrefixExpression("~", expr.left, expr.left.position)
|
||||
rightIDt.isWords && rightVal?.number==65535.0 -> PrefixExpression("~", expr.left, expr.left.position)
|
||||
leftIDt.isBytes && leftVal?.number==255.0 -> PrefixExpression("~", expr.right, expr.right.position)
|
||||
leftIDt.isWords && leftVal?.number==65535.0 -> PrefixExpression("~", expr.right, expr.right.position)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
"&" -> {
|
||||
if (leftVal != null && !leftVal.asBooleanValue)
|
||||
constFalse
|
||||
else if (rightVal != null && !rightVal.asBooleanValue)
|
||||
constFalse
|
||||
else
|
||||
null
|
||||
when {
|
||||
leftVal?.number==0.0 -> constFalse
|
||||
rightVal?.number==0.0 -> constFalse
|
||||
rightIDt.isBytes && rightVal?.number==255.0 -> expr.left
|
||||
rightIDt.isWords && rightVal?.number==65535.0 -> expr.left
|
||||
leftIDt.isBytes && leftVal?.number==255.0 -> expr.right
|
||||
leftIDt.isWords && leftVal?.number==65535.0 -> expr.right
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
"*" -> optimizeMultiplication(expr, leftVal, rightVal)
|
||||
"/" -> optimizeDivision(expr, leftVal, rightVal)
|
||||
@ -295,32 +301,32 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||
if(functionCall.target.nameInSource == listOf("lsb")) {
|
||||
val arg = functionCall.args[0]
|
||||
override fun after(functionCallExpr: FunctionCallExpr, parent: Node): Iterable<IAstModification> {
|
||||
if(functionCallExpr.target.nameInSource == listOf("lsb")) {
|
||||
val arg = functionCallExpr.args[0]
|
||||
if(arg is TypecastExpression) {
|
||||
val valueDt = arg.expression.inferType(program)
|
||||
if (valueDt istype DataType.BYTE || valueDt istype DataType.UBYTE) {
|
||||
// useless lsb() of byte value that was typecasted to word
|
||||
return listOf(IAstModification.ReplaceNode(functionCall, arg.expression, parent))
|
||||
return listOf(IAstModification.ReplaceNode(functionCallExpr, arg.expression, parent))
|
||||
}
|
||||
} else {
|
||||
val argDt = arg.inferType(program)
|
||||
if (argDt istype DataType.BYTE || argDt istype DataType.UBYTE) {
|
||||
// useless lsb() of byte value
|
||||
return listOf(IAstModification.ReplaceNode(functionCall, arg, parent))
|
||||
return listOf(IAstModification.ReplaceNode(functionCallExpr, arg, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(functionCall.target.nameInSource == listOf("msb")) {
|
||||
val arg = functionCall.args[0]
|
||||
else if(functionCallExpr.target.nameInSource == listOf("msb")) {
|
||||
val arg = functionCallExpr.args[0]
|
||||
if(arg is TypecastExpression) {
|
||||
val valueDt = arg.expression.inferType(program)
|
||||
if (valueDt istype DataType.BYTE || valueDt istype DataType.UBYTE) {
|
||||
// useless msb() of byte value that was typecasted to word, replace with 0
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
functionCall,
|
||||
NumericLiteralValue(valueDt.getOr(DataType.UBYTE), 0, arg.expression.position),
|
||||
functionCallExpr,
|
||||
NumericLiteralValue(valueDt.getOr(DataType.UBYTE), 0.0, arg.expression.position),
|
||||
parent))
|
||||
}
|
||||
} else {
|
||||
@ -328,8 +334,8 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
if (argDt istype DataType.BYTE || argDt istype DataType.UBYTE) {
|
||||
// useless msb() of byte value, replace with 0
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
functionCall,
|
||||
NumericLiteralValue(argDt.getOr(DataType.UBYTE), 0, arg.position),
|
||||
functionCallExpr,
|
||||
NumericLiteralValue(argDt.getOr(DataType.UBYTE), 0.0, arg.position),
|
||||
parent))
|
||||
}
|
||||
}
|
||||
@ -338,6 +344,25 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(containment: ContainmentCheck, parent: Node): Iterable<IAstModification> {
|
||||
val range = containment.iterable as? RangeExpr
|
||||
if(range!=null && range.step.constValue(program)?.number==1.0) {
|
||||
val from = range.from.constValue(program)
|
||||
val to = range.to.constValue(program)
|
||||
val value = containment.element
|
||||
if(from!=null && to!=null && value.isSimple) {
|
||||
if(to.number-from.number>6.0) {
|
||||
// replace containment test with X>=from and X<=to
|
||||
val left = BinaryExpression(value, ">=", from, containment.position)
|
||||
val right = BinaryExpression(value.copy(), "<=", to, containment.position)
|
||||
val comparison = BinaryExpression(left, "and", right, containment.position)
|
||||
return listOf(IAstModification.ReplaceNode(containment, comparison, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun determineY(x: Expression, subBinExpr: BinaryExpression): Expression? {
|
||||
return when {
|
||||
subBinExpr.left isSameAs x -> subBinExpr.right
|
||||
@ -362,7 +387,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
if (rightVal2 != null) {
|
||||
// right value is a constant, see if we can optimize
|
||||
val rightConst: NumericLiteralValue = rightVal2
|
||||
when (rightConst.number.toDouble()) {
|
||||
when (rightConst.number) {
|
||||
0.0 -> {
|
||||
// left
|
||||
return expr2.left
|
||||
@ -371,7 +396,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
}
|
||||
// no need to check for left val constant (because of associativity)
|
||||
|
||||
val rnum = rightVal?.number?.toDouble()
|
||||
val rnum = rightVal?.number
|
||||
if(rnum!=null && rnum<0.0) {
|
||||
expr.operator = "-"
|
||||
expr.right = NumericLiteralValue(rightVal.type, -rnum, rightVal.position)
|
||||
@ -392,7 +417,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
|
||||
if (rightVal != null) {
|
||||
// right value is a constant, see if we can optimize
|
||||
val rnum = rightVal.number.toDouble()
|
||||
val rnum = rightVal.number
|
||||
if (rnum == 0.0) {
|
||||
// left
|
||||
return expr.left
|
||||
@ -406,7 +431,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
}
|
||||
if (leftVal != null) {
|
||||
// left value is a constant, see if we can optimize
|
||||
when (leftVal.number.toDouble()) {
|
||||
when (leftVal.number) {
|
||||
0.0 -> {
|
||||
// -right
|
||||
return PrefixExpression("-", expr.right, expr.position)
|
||||
@ -425,7 +450,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
if (rightVal != null) {
|
||||
// right value is a constant, see if we can optimize
|
||||
val rightConst: NumericLiteralValue = rightVal
|
||||
when (rightConst.number.toDouble()) {
|
||||
when (rightConst.number) {
|
||||
-3.0 -> {
|
||||
// -1/(left*left*left)
|
||||
return BinaryExpression(NumericLiteralValue(DataType.FLOAT, -1.0, expr.position), "/",
|
||||
@ -445,11 +470,11 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
}
|
||||
0.0 -> {
|
||||
// 1
|
||||
return NumericLiteralValue(rightConst.type, 1, expr.position)
|
||||
return NumericLiteralValue(rightConst.type, 1.0, expr.position)
|
||||
}
|
||||
0.5 -> {
|
||||
// sqrt(left)
|
||||
return FunctionCall(IdentifierReference(listOf("sqrt"), expr.position), mutableListOf(expr.left), expr.position)
|
||||
return FunctionCallExpr(IdentifierReference(listOf("sqrt"), expr.position), mutableListOf(expr.left), expr.position)
|
||||
}
|
||||
1.0 -> {
|
||||
// left
|
||||
@ -467,18 +492,18 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
}
|
||||
if (leftVal != null) {
|
||||
// left value is a constant, see if we can optimize
|
||||
when (leftVal.number.toDouble()) {
|
||||
when (leftVal.number) {
|
||||
-1.0 -> {
|
||||
// -1
|
||||
return NumericLiteralValue(DataType.FLOAT, -1.0, expr.position)
|
||||
}
|
||||
0.0 -> {
|
||||
// 0
|
||||
return NumericLiteralValue(leftVal.type, 0, expr.position)
|
||||
return NumericLiteralValue(leftVal.type, 0.0, expr.position)
|
||||
}
|
||||
1.0 -> {
|
||||
//1
|
||||
return NumericLiteralValue(leftVal.type, 1, expr.position)
|
||||
return NumericLiteralValue(leftVal.type, 1.0, expr.position)
|
||||
}
|
||||
|
||||
}
|
||||
@ -500,7 +525,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
val idt = expr.inferType(program)
|
||||
if(!idt.isKnown)
|
||||
throw FatalAstException("unknown dt")
|
||||
return NumericLiteralValue(idt.getOr(DataType.UNDEFINED), 0, expr.position)
|
||||
return NumericLiteralValue(idt.getOr(DataType.UNDEFINED), 0.0, expr.position)
|
||||
} else if (cv in powersOfTwo) {
|
||||
expr.operator = "&"
|
||||
expr.right = NumericLiteralValue.optimalInteger(cv!!.toInt()-1, expr.position)
|
||||
@ -520,7 +545,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
if (rightVal != null) {
|
||||
// right value is a constant, see if we can optimize
|
||||
val rightConst: NumericLiteralValue = rightVal
|
||||
val cv = rightConst.number.toDouble()
|
||||
val cv = rightConst.number
|
||||
val leftIDt = expr.left.inferType(program)
|
||||
if (!leftIDt.isKnown)
|
||||
return null
|
||||
@ -555,22 +580,22 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
}
|
||||
|
||||
if (leftDt == DataType.UBYTE) {
|
||||
if (abs(rightConst.number.toDouble()) >= 256.0) {
|
||||
return NumericLiteralValue(DataType.UBYTE, 0, expr.position)
|
||||
if (abs(rightConst.number) >= 256.0) {
|
||||
return NumericLiteralValue(DataType.UBYTE, 0.0, expr.position)
|
||||
}
|
||||
} else if (leftDt == DataType.UWORD) {
|
||||
if (abs(rightConst.number.toDouble()) >= 65536.0) {
|
||||
return NumericLiteralValue(DataType.UBYTE, 0, expr.position)
|
||||
if (abs(rightConst.number) >= 65536.0) {
|
||||
return NumericLiteralValue(DataType.UBYTE, 0.0, expr.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (leftVal != null) {
|
||||
// left value is a constant, see if we can optimize
|
||||
when (leftVal.number.toDouble()) {
|
||||
when (leftVal.number) {
|
||||
0.0 -> {
|
||||
// 0
|
||||
return NumericLiteralValue(leftVal.type, 0, expr.position)
|
||||
return NumericLiteralValue(leftVal.type, 0.0, expr.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -587,14 +612,14 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
// right value is a constant, see if we can optimize
|
||||
val leftValue: Expression = expr2.left
|
||||
val rightConst: NumericLiteralValue = rightVal2
|
||||
when (val cv = rightConst.number.toDouble()) {
|
||||
when (val cv = rightConst.number) {
|
||||
-1.0 -> {
|
||||
// -left
|
||||
return PrefixExpression("-", leftValue, expr.position)
|
||||
}
|
||||
0.0 -> {
|
||||
// 0
|
||||
return NumericLiteralValue(rightConst.type, 0, expr.position)
|
||||
return NumericLiteralValue(rightConst.type, 0.0, expr.position)
|
||||
}
|
||||
1.0 -> {
|
||||
// left
|
||||
@ -635,19 +660,19 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
when (val targetDt = targetIDt.getOr(DataType.UNDEFINED)) {
|
||||
DataType.UBYTE, DataType.BYTE -> {
|
||||
if (amount >= 8) {
|
||||
return NumericLiteralValue(targetDt, 0, expr.position)
|
||||
return NumericLiteralValue(targetDt, 0.0, expr.position)
|
||||
}
|
||||
}
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
if (amount >= 16) {
|
||||
return NumericLiteralValue(targetDt, 0, expr.position)
|
||||
return NumericLiteralValue(targetDt, 0.0, expr.position)
|
||||
} else if (amount >= 8) {
|
||||
val lsb = FunctionCall(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left), expr.position)
|
||||
val lsb = FunctionCallExpr(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left), expr.position)
|
||||
if (amount == 8) {
|
||||
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(lsb, NumericLiteralValue.optimalInteger(0, expr.position)), expr.position)
|
||||
return FunctionCallExpr(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(lsb, NumericLiteralValue.optimalInteger(0, expr.position)), expr.position)
|
||||
}
|
||||
val shifted = BinaryExpression(lsb, "<<", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position)
|
||||
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(shifted, NumericLiteralValue.optimalInteger(0, expr.position)), expr.position)
|
||||
return FunctionCallExpr(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(shifted, NumericLiteralValue.optimalInteger(0, expr.position)), expr.position)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
@ -684,11 +709,11 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
return NumericLiteralValue.optimalInteger(0, expr.position)
|
||||
}
|
||||
else if (amount >= 8) {
|
||||
val msb = FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
|
||||
val msb = FunctionCallExpr(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
|
||||
if (amount == 8) {
|
||||
// mkword(0, msb(v))
|
||||
val zero = NumericLiteralValue(DataType.UBYTE, 0, expr.position)
|
||||
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(zero, msb), expr.position)
|
||||
val zero = NumericLiteralValue(DataType.UBYTE, 0.0, expr.position)
|
||||
return FunctionCallExpr(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(zero, msb), expr.position)
|
||||
}
|
||||
return TypecastExpression(BinaryExpression(msb, ">>", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position), DataType.UWORD, true, expr.position)
|
||||
}
|
||||
@ -706,7 +731,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
}
|
||||
|
||||
private fun reorderAssociativeWithConstant(expr: BinaryExpression, leftVal: NumericLiteralValue?): BinExprWithConstants {
|
||||
if (expr.operator in associativeOperators && leftVal != null) {
|
||||
if (expr.operator in AssociativeOperators && leftVal != null) {
|
||||
// swap left and right so that right is always the constant
|
||||
val tmp = expr.left
|
||||
expr.left = expr.right
|
||||
@ -718,4 +743,4 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
|
||||
private data class BinExprWithConstants(val expr: BinaryExpression, val leftVal: NumericLiteralValue?, val rightVal: NumericLiteralValue?)
|
||||
|
||||
}
|
||||
}
|
@ -2,6 +2,9 @@ package prog8.optimizer
|
||||
|
||||
import prog8.ast.IBuiltinFunctions
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.InferredTypes
|
||||
import prog8.compilerinterface.CompilationOptions
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
@ -65,3 +68,15 @@ fun Program.splitBinaryExpressions(options: CompilationOptions, compTarget: ICom
|
||||
opti.visit(this)
|
||||
return opti.applyModifications()
|
||||
}
|
||||
|
||||
fun getTempVarName(dt: InferredTypes.InferredType): List<String> {
|
||||
return when {
|
||||
// TODO assume (hope) cx16.r9 isn't used for anything else during the use of this temporary variable...
|
||||
dt istype DataType.UBYTE -> listOf("cx16", "r9L")
|
||||
dt istype DataType.BYTE -> listOf("cx16", "r9sL")
|
||||
dt istype DataType.UWORD -> listOf("cx16", "r9")
|
||||
dt istype DataType.WORD -> listOf("cx16", "r9s")
|
||||
dt.isPassByReference -> listOf("cx16", "r9")
|
||||
else -> throw FatalAstException("invalid dt $dt")
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,15 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.IBuiltinFunctions
|
||||
import prog8.ast.IStatementContainer
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.*
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
import prog8.compilerinterface.size
|
||||
import kotlin.math.floor
|
||||
|
||||
internal const val retvarName = "prog8_retval"
|
||||
|
||||
|
||||
class StatementOptimizer(private val program: Program,
|
||||
private val errors: IErrorReporter,
|
||||
@ -24,33 +17,22 @@ class StatementOptimizer(private val program: Program,
|
||||
private val compTarget: ICompilationTarget
|
||||
) : AstWalker() {
|
||||
|
||||
private val subsThatNeedReturnVariable = mutableSetOf<Triple<IStatementContainer, DataType, Position>>()
|
||||
|
||||
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
for(returnvar in subsThatNeedReturnVariable) {
|
||||
val decl = VarDecl(VarDeclType.VAR, returnvar.second, ZeropageWish.DONTCARE, null, retvarName, null,
|
||||
isArray = false,
|
||||
autogeneratedDontRemove = true,
|
||||
sharedWithAsm = false,
|
||||
position = returnvar.third
|
||||
)
|
||||
returnvar.first.statements.add(0, decl)
|
||||
}
|
||||
subsThatNeedReturnVariable.clear()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||
override fun before(functionCallExpr: FunctionCallExpr, parent: Node): Iterable<IAstModification> {
|
||||
// if the first instruction in the called subroutine is a return statement with a simple value,
|
||||
// remove the jump altogeter and inline the returnvalue directly.
|
||||
val subroutine = functionCall.target.targetSubroutine(program)
|
||||
|
||||
fun scopePrefix(variable: IdentifierReference): IdentifierReference {
|
||||
val target = variable.targetStatement(program) as INamedStatement
|
||||
return IdentifierReference(target.scopedName, variable.position)
|
||||
}
|
||||
|
||||
val subroutine = functionCallExpr.target.targetSubroutine(program)
|
||||
if(subroutine!=null) {
|
||||
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
||||
if(first is Return && first.value?.isSimple==true) {
|
||||
val copy = when(val orig = first.value!!) {
|
||||
is AddressOf -> {
|
||||
val scoped = scopePrefix(orig.identifier, subroutine)
|
||||
val scoped = scopePrefix(orig.identifier)
|
||||
AddressOf(scoped, orig.position)
|
||||
}
|
||||
is DirectMemoryRead -> {
|
||||
@ -59,24 +41,21 @@ class StatementOptimizer(private val program: Program,
|
||||
else -> return noModifications
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> scopePrefix(orig, subroutine)
|
||||
is IdentifierReference -> scopePrefix(orig)
|
||||
is NumericLiteralValue -> orig.copy()
|
||||
is StringLiteralValue -> orig.copy()
|
||||
else -> return noModifications
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(functionCall, copy, parent))
|
||||
return listOf(IAstModification.ReplaceNode(functionCallExpr, copy, parent))
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun scopePrefix(variable: IdentifierReference, subroutine: Subroutine): IdentifierReference {
|
||||
val scoped = subroutine.makeScopedName(variable.nameInSource.last())
|
||||
return IdentifierReference(scoped.split('.'), variable.position)
|
||||
}
|
||||
|
||||
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in functions.names) {
|
||||
if(functionCallStatement.target.targetStatement(program) is BuiltinFunctionPlaceholder) {
|
||||
val functionName = functionCallStatement.target.nameInSource[0]
|
||||
if (functionName in functions.purefunctionNames) {
|
||||
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
|
||||
@ -101,7 +80,7 @@ class StatementOptimizer(private val program: Program,
|
||||
val firstCharEncoded = compTarget.encodeString(string.value, string.altEncoding)[0]
|
||||
val chrout = FunctionCallStatement(
|
||||
IdentifierReference(listOf("txt", "chrout"), pos),
|
||||
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstCharEncoded.toInt(), pos)),
|
||||
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstCharEncoded.toDouble(), pos)),
|
||||
functionCallStatement.void, pos
|
||||
)
|
||||
return listOf(IAstModification.ReplaceNode(functionCallStatement, chrout, parent))
|
||||
@ -109,12 +88,12 @@ class StatementOptimizer(private val program: Program,
|
||||
val firstTwoCharsEncoded = compTarget.encodeString(string.value.take(2), string.altEncoding)
|
||||
val chrout1 = FunctionCallStatement(
|
||||
IdentifierReference(listOf("txt", "chrout"), pos),
|
||||
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toInt(), pos)),
|
||||
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toDouble(), pos)),
|
||||
functionCallStatement.void, pos
|
||||
)
|
||||
val chrout2 = FunctionCallStatement(
|
||||
IdentifierReference(listOf("txt", "chrout"), pos),
|
||||
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[1].toInt(), pos)),
|
||||
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[1].toDouble(), pos)),
|
||||
functionCallStatement.void, pos
|
||||
)
|
||||
return listOf(
|
||||
@ -134,50 +113,52 @@ class StatementOptimizer(private val program: Program,
|
||||
return listOf(IAstModification.Remove(functionCallStatement, parent as IStatementContainer))
|
||||
}
|
||||
|
||||
// see if we can optimize any complex arguments
|
||||
// TODO for now, only works for single-argument functions because we use just 1 temp var: R9
|
||||
if(functionCallStatement.target.nameInSource !in listOf(listOf("pop"), listOf("popw")) && functionCallStatement.args.size==1) {
|
||||
val arg = functionCallStatement.args[0]
|
||||
if(!arg.isSimple && arg !is TypecastExpression && arg !is IFunctionCall) {
|
||||
val name = getTempVarName(arg.inferType(program))
|
||||
val tempvar = IdentifierReference(name, functionCallStatement.position)
|
||||
val assignTempvar = Assignment(AssignTarget(tempvar.copy(), null, null, functionCallStatement.position), arg, functionCallStatement.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(functionCallStatement, assignTempvar, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(arg, tempvar, functionCallStatement)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
// override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||
// // if the first instruction in the called subroutine is a return statement with constant value, replace with the constant value
|
||||
// val subroutine = functionCall.target.targetSubroutine(program)
|
||||
// if(subroutine!=null) {
|
||||
// val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
||||
// if(first is Return && first.value!=null) {
|
||||
// val constval = first.value?.constValue(program)
|
||||
// if(constval!=null)
|
||||
// return listOf(IAstModification.ReplaceNode(functionCall, constval, parent))
|
||||
// }
|
||||
// }
|
||||
// return noModifications
|
||||
// }
|
||||
|
||||
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
|
||||
override fun after(ifElse: IfElse, parent: Node): Iterable<IAstModification> {
|
||||
// remove empty if statements
|
||||
if(ifStatement.truepart.isEmpty() && ifStatement.elsepart.isEmpty())
|
||||
return listOf(IAstModification.Remove(ifStatement, parent as IStatementContainer))
|
||||
if(ifElse.truepart.isEmpty() && ifElse.elsepart.isEmpty())
|
||||
return listOf(IAstModification.Remove(ifElse, parent as IStatementContainer))
|
||||
|
||||
// empty true part? switch with the else part
|
||||
if(ifStatement.truepart.isEmpty() && ifStatement.elsepart.isNotEmpty()) {
|
||||
val invertedCondition = PrefixExpression("not", ifStatement.condition, ifStatement.condition.position)
|
||||
val emptyscope = AnonymousScope(mutableListOf(), ifStatement.elsepart.position)
|
||||
val truepart = AnonymousScope(ifStatement.elsepart.statements, ifStatement.truepart.position)
|
||||
if(ifElse.truepart.isEmpty() && ifElse.elsepart.isNotEmpty()) {
|
||||
val invertedCondition = PrefixExpression("not", ifElse.condition, ifElse.condition.position)
|
||||
val emptyscope = AnonymousScope(mutableListOf(), ifElse.elsepart.position)
|
||||
val truepart = AnonymousScope(ifElse.elsepart.statements, ifElse.truepart.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(ifStatement.condition, invertedCondition, ifStatement),
|
||||
IAstModification.ReplaceNode(ifStatement.truepart, truepart, ifStatement),
|
||||
IAstModification.ReplaceNode(ifStatement.elsepart, emptyscope, ifStatement)
|
||||
IAstModification.ReplaceNode(ifElse.condition, invertedCondition, ifElse),
|
||||
IAstModification.ReplaceNode(ifElse.truepart, truepart, ifElse),
|
||||
IAstModification.ReplaceNode(ifElse.elsepart, emptyscope, ifElse)
|
||||
)
|
||||
}
|
||||
|
||||
val constvalue = ifStatement.condition.constValue(program)
|
||||
val constvalue = ifElse.condition.constValue(program)
|
||||
if(constvalue!=null) {
|
||||
return if(constvalue.asBooleanValue){
|
||||
// always true -> keep only if-part
|
||||
errors.warn("condition is always true", ifStatement.position)
|
||||
listOf(IAstModification.ReplaceNode(ifStatement, ifStatement.truepart, parent))
|
||||
errors.warn("condition is always true", ifElse.condition.position)
|
||||
listOf(IAstModification.ReplaceNode(ifElse, ifElse.truepart, parent))
|
||||
} else {
|
||||
// always false -> keep only else-part
|
||||
errors.warn("condition is always false", ifStatement.position)
|
||||
listOf(IAstModification.ReplaceNode(ifStatement, ifStatement.elsepart, parent))
|
||||
errors.warn("condition is always false", ifElse.condition.position)
|
||||
listOf(IAstModification.ReplaceNode(ifElse, ifElse.elsepart, parent))
|
||||
}
|
||||
}
|
||||
|
||||
@ -215,7 +196,7 @@ class StatementOptimizer(private val program: Program,
|
||||
if(size==1) {
|
||||
// loop over string of length 1 -> just assign the single character
|
||||
val character = compTarget.encodeString(sv.value, sv.altEncoding)[0]
|
||||
val byte = NumericLiteralValue(DataType.UBYTE, character, iterable.position)
|
||||
val byte = NumericLiteralValue(DataType.UBYTE, character.toDouble(), iterable.position)
|
||||
val scope = AnonymousScope(mutableListOf(), forLoop.position)
|
||||
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), byte, forLoop.position))
|
||||
scope.statements.addAll(forLoop.body.statements)
|
||||
@ -245,15 +226,14 @@ class StatementOptimizer(private val program: Program,
|
||||
override fun before(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
|
||||
val constvalue = untilLoop.condition.constValue(program)
|
||||
if(constvalue!=null) {
|
||||
if(constvalue.asBooleanValue) {
|
||||
// always true -> keep only the statement block (if there are no break statements)
|
||||
return if(constvalue.asBooleanValue) {
|
||||
// always true -> keep only the statement block
|
||||
errors.warn("condition is always true", untilLoop.condition.position)
|
||||
if(!hasBreak(untilLoop.body))
|
||||
return listOf(IAstModification.ReplaceNode(untilLoop, untilLoop.body, parent))
|
||||
listOf(IAstModification.ReplaceNode(untilLoop, untilLoop.body, parent))
|
||||
} else {
|
||||
// always false
|
||||
val forever = RepeatLoop(null, untilLoop.body, untilLoop.position)
|
||||
return listOf(IAstModification.ReplaceNode(untilLoop, forever, parent))
|
||||
listOf(IAstModification.ReplaceNode(untilLoop, forever, parent))
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
@ -299,9 +279,23 @@ class StatementOptimizer(private val program: Program,
|
||||
// if the jump is to the next statement, remove the jump
|
||||
val scope = jump.parent as IStatementContainer
|
||||
val label = jump.identifier?.targetStatement(program)
|
||||
if(label!=null && scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1)
|
||||
if (label != null && scope.statements.indexOf(label) == scope.statements.indexOf(jump) + 1)
|
||||
return listOf(IAstModification.Remove(jump, scope))
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(gosub: GoSub, parent: Node): Iterable<IAstModification> {
|
||||
// if the next statement is return with no returnvalue, change into a regular jump if there are no parameters as well.
|
||||
val subroutineParams = gosub.identifier?.targetSubroutine(program)?.parameters
|
||||
if(subroutineParams!=null && subroutineParams.isEmpty()) {
|
||||
val returnstmt = gosub.nextSibling() as? Return
|
||||
if(returnstmt!=null && returnstmt.value==null) {
|
||||
return listOf(
|
||||
IAstModification.Remove(returnstmt, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(gosub, Jump(gosub.address, gosub.identifier, gosub.generatedLabel, gosub.position), parent)
|
||||
)
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@ -315,7 +309,7 @@ class StatementOptimizer(private val program: Program,
|
||||
val op1 = binExpr.operator
|
||||
val op2 = rExpr.operator
|
||||
|
||||
if(rExpr.left is NumericLiteralValue && op2 in associativeOperators) {
|
||||
if(rExpr.left is NumericLiteralValue && op2 in AssociativeOperators) {
|
||||
// associative operator, make sure the constant numeric value is second (right)
|
||||
return listOf(IAstModification.SwapOperands(rExpr))
|
||||
}
|
||||
@ -324,22 +318,22 @@ class StatementOptimizer(private val program: Program,
|
||||
if(rNum!=null) {
|
||||
if (op1 == "+" || op1 == "-") {
|
||||
if (op2 == "+") {
|
||||
// A = A +/- B + N
|
||||
// A = A +/- B + N ---> A = A +/- B ; A = A + N
|
||||
val expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position)
|
||||
val addConstant = Assignment(
|
||||
assignment.target,
|
||||
BinaryExpression(binExpr.left, "+", rExpr.right, rExpr.position),
|
||||
assignment.target.copy(),
|
||||
BinaryExpression(binExpr.left.copy(), "+", rExpr.right, rExpr.position),
|
||||
assignment.position
|
||||
)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
|
||||
IAstModification.InsertAfter(assignment, addConstant, parent as IStatementContainer))
|
||||
} else if (op2 == "-") {
|
||||
// A = A +/- B - N
|
||||
// A = A +/- B - N ---> A = A +/- B ; A = A - N
|
||||
val expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position)
|
||||
val subConstant = Assignment(
|
||||
assignment.target,
|
||||
BinaryExpression(binExpr.left, "-", rExpr.right, rExpr.position),
|
||||
assignment.target.copy(),
|
||||
BinaryExpression(binExpr.left.copy(), "-", rExpr.right, rExpr.position),
|
||||
assignment.position
|
||||
)
|
||||
return listOf(
|
||||
@ -351,7 +345,7 @@ class StatementOptimizer(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
if(binExpr.operator in associativeOperators && binExpr.right isSameAs assignment.target) {
|
||||
if(binExpr.operator in AssociativeOperators && binExpr.right isSameAs assignment.target) {
|
||||
// associative operator, swap the operands so that the assignment target is first (left)
|
||||
// unless the other operand is the same in which case we don't swap (endless loop!)
|
||||
if (!(binExpr.left isSameAs binExpr.right))
|
||||
@ -371,17 +365,29 @@ class StatementOptimizer(private val program: Program,
|
||||
|
||||
val targetIDt = assignment.target.inferType(program)
|
||||
if(!targetIDt.isKnown)
|
||||
throw FatalAstException("can't infer type of assignment target")
|
||||
return noModifications
|
||||
|
||||
// optimize binary expressions a bit
|
||||
val targetDt = targetIDt.getOr(DataType.UNDEFINED)
|
||||
val bexpr=assignment.value as? BinaryExpression
|
||||
if(bexpr!=null) {
|
||||
val rightCv = bexpr.right.constValue(program)?.number?.toDouble()
|
||||
val rightCv = bexpr.right.constValue(program)?.number
|
||||
if(bexpr.operator=="-" && rightCv==null) {
|
||||
if(bexpr.right isSameAs assignment.target) {
|
||||
// X = value - X --> X = -X ; X += value (to avoid need of stack-evaluation)
|
||||
val negation = PrefixExpression("-", bexpr.right.copy(), bexpr.position)
|
||||
val addValue = Assignment(assignment.target.copy(), BinaryExpression(bexpr.right, "+", bexpr.left, bexpr.position), assignment.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(bexpr, negation, assignment),
|
||||
IAstModification.InsertAfter(assignment, addValue, parent as IStatementContainer)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (rightCv != null && assignment.target isSameAs bexpr.left) {
|
||||
// assignments of the form: X = X <operator> <expr>
|
||||
// remove assignments that have no effect (such as X=X+0)
|
||||
// optimize/rewrite some other expressions
|
||||
val targetDt = targetIDt.getOr(DataType.UNDEFINED)
|
||||
val vardeclDt = (assignment.target.identifier?.targetVarDecl(program))?.type
|
||||
when (bexpr.operator) {
|
||||
"+" -> {
|
||||
@ -394,7 +400,7 @@ class StatementOptimizer(private val program: Program,
|
||||
repeat(rightCv.toInt()) {
|
||||
incs.statements.add(PostIncrDecr(assignment.target.copy(), "++", assignment.position))
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(assignment, incs, parent))
|
||||
listOf(IAstModification.ReplaceNode(assignment, if(incs.statements.size==1) incs.statements[0] else incs, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -439,12 +445,17 @@ class StatementOptimizer(private val program: Program,
|
||||
val returnDt = subr.returntypes.single()
|
||||
if (returnDt in IntegerDatatypes) {
|
||||
// first assign to intermediary variable, then return that
|
||||
subsThatNeedReturnVariable.add(Triple(subr, returnDt, returnStmt.position))
|
||||
val returnValueIntermediary1 = IdentifierReference(listOf(retvarName), returnStmt.position)
|
||||
val returnValueIntermediary2 = IdentifierReference(listOf(retvarName), returnStmt.position)
|
||||
val tgt = AssignTarget(returnValueIntermediary1, null, null, returnStmt.position)
|
||||
val returnVarName = "retval_interm_" + when(returnDt) {
|
||||
DataType.UBYTE -> "ub"
|
||||
DataType.BYTE -> "b"
|
||||
DataType.UWORD -> "uw"
|
||||
DataType.WORD -> "w"
|
||||
else -> "<undefined>"
|
||||
}
|
||||
val returnValueIntermediary = IdentifierReference(listOf("prog8_lib", returnVarName), returnStmt.position)
|
||||
val tgt = AssignTarget(returnValueIntermediary, null, null, returnStmt.position)
|
||||
val assign = Assignment(tgt, value, returnStmt.position)
|
||||
val returnReplacement = Return(returnValueIntermediary2, returnStmt.position)
|
||||
val returnReplacement = Return(returnValueIntermediary.copy(), returnStmt.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(returnStmt, assign, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(returnStmt, returnReplacement, parent)
|
||||
@ -453,41 +464,12 @@ class StatementOptimizer(private val program: Program,
|
||||
return null
|
||||
}
|
||||
|
||||
when(returnStmt.value) {
|
||||
is PrefixExpression -> {
|
||||
val mod = returnViaIntermediaryVar(returnStmt.value!!)
|
||||
if(mod!=null)
|
||||
return mod
|
||||
}
|
||||
is BinaryExpression -> {
|
||||
val mod = returnViaIntermediaryVar(returnStmt.value!!)
|
||||
if(mod!=null)
|
||||
return mod
|
||||
}
|
||||
else -> {}
|
||||
if(returnStmt.value is BinaryExpression) {
|
||||
val mod = returnViaIntermediaryVar(returnStmt.value!!)
|
||||
if(mod!=null)
|
||||
return mod
|
||||
}
|
||||
|
||||
return super.after(returnStmt, parent)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun hasBreak(scope: IStatementContainer): Boolean {
|
||||
|
||||
class Searcher: IAstVisitor
|
||||
{
|
||||
var count=0
|
||||
|
||||
override fun visit(breakStmt: Break) {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
val s=Searcher()
|
||||
for(stmt in scope.statements) {
|
||||
stmt.accept(s)
|
||||
if(s.count>0)
|
||||
return true
|
||||
}
|
||||
return s.count > 0
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,18 +1,16 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.expressions.BinaryExpression
|
||||
import prog8.ast.expressions.FunctionCall
|
||||
import prog8.ast.expressions.PrefixExpression
|
||||
import prog8.ast.expressions.TypecastExpression
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compilerinterface.CallGraph
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
import prog8.compilerinterface.isInRegularRAMof
|
||||
import prog8.compilerinterface.isIOAddress
|
||||
|
||||
|
||||
class UnusedCodeRemover(private val program: Program,
|
||||
@ -30,27 +28,27 @@ class UnusedCodeRemover(private val program: Program,
|
||||
}
|
||||
|
||||
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
|
||||
reportUnreachable(breakStmt, parent as IStatementContainer)
|
||||
reportUnreachable(breakStmt)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun before(jump: Jump, parent: Node): Iterable<IAstModification> {
|
||||
reportUnreachable(jump, parent as IStatementContainer)
|
||||
reportUnreachable(jump)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> {
|
||||
reportUnreachable(returnStmt, parent as IStatementContainer)
|
||||
reportUnreachable(returnStmt)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
if(functionCallStatement.target.nameInSource.last() == "exit")
|
||||
reportUnreachable(functionCallStatement, parent as IStatementContainer)
|
||||
reportUnreachable(functionCallStatement)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
private fun reportUnreachable(stmt: Statement, parent: IStatementContainer) {
|
||||
private fun reportUnreachable(stmt: Statement) {
|
||||
when(val next = stmt.nextSibling()) {
|
||||
null, is Label, is Directive, is VarDecl, is InlineAssembly, is Subroutine -> {}
|
||||
else -> errors.warn("unreachable code", next.position)
|
||||
@ -58,8 +56,7 @@ class UnusedCodeRemover(private val program: Program,
|
||||
}
|
||||
|
||||
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
val removeDoubleAssignments = deduplicateAssignments(scope.statements)
|
||||
return removeDoubleAssignments.map { IAstModification.Remove(it, scope) }
|
||||
return deduplicateAssignments(scope.statements, scope)
|
||||
}
|
||||
|
||||
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
|
||||
@ -70,13 +67,13 @@ class UnusedCodeRemover(private val program: Program,
|
||||
return listOf(IAstModification.Remove(block, parent as IStatementContainer))
|
||||
}
|
||||
if(callgraph.unused(block)) {
|
||||
errors.warn("removing unused block '${block.name}'", block.position)
|
||||
if(block.statements.any{ it !is VarDecl || it.type==VarDeclType.VAR})
|
||||
errors.warn("removing unused block '${block.name}'", block.position)
|
||||
return listOf(IAstModification.Remove(block, parent as IStatementContainer))
|
||||
}
|
||||
}
|
||||
|
||||
val removeDoubleAssignments = deduplicateAssignments(block.statements)
|
||||
return removeDoubleAssignments.map { IAstModification.Remove(it, block) }
|
||||
return deduplicateAssignments(block.statements, block)
|
||||
}
|
||||
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
@ -99,17 +96,34 @@ class UnusedCodeRemover(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
val removeDoubleAssignments = deduplicateAssignments(subroutine.statements)
|
||||
return removeDoubleAssignments.map { IAstModification.Remove(it, subroutine) }
|
||||
return deduplicateAssignments(subroutine.statements, subroutine)
|
||||
}
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
if(decl.type==VarDeclType.VAR) {
|
||||
val forceOutput = "force_output" in decl.definingBlock.options()
|
||||
if (!forceOutput && !decl.autogeneratedDontRemove && !decl.sharedWithAsm && !decl.definingBlock.isInLibrary) {
|
||||
if (callgraph.unused(decl)) {
|
||||
val usages = callgraph.usages(decl)
|
||||
if (usages.isEmpty()) {
|
||||
errors.warn("removing unused variable '${decl.name}'", decl.position)
|
||||
return listOf(IAstModification.Remove(decl, parent as IStatementContainer))
|
||||
} else {
|
||||
// if all usages are just an assignment to this vardecl,
|
||||
// and it is in regular RAM, then remove the var as well including all assignments
|
||||
val assignTargets = usages.mapNotNull {
|
||||
it.parent as? AssignTarget
|
||||
}.filter {
|
||||
!it.isIOAddress(compTarget.machine)
|
||||
}
|
||||
if(assignTargets.size==usages.size) {
|
||||
errors.warn("removing unused variable '${decl.name}'", decl.position)
|
||||
val assignmentsToRemove = assignTargets.map { it.parent to it.parent.parent as IStatementContainer}.toSet()
|
||||
return assignmentsToRemove.map {
|
||||
IAstModification.Remove(it.first, it.second)
|
||||
} + listOf(
|
||||
IAstModification.Remove(decl, parent as IStatementContainer)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -117,28 +131,117 @@ class UnusedCodeRemover(private val program: Program,
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun deduplicateAssignments(statements: List<Statement>): List<Assignment> {
|
||||
private fun deduplicateAssignments(statements: List<Statement>, scope: IStatementContainer): List<IAstModification> {
|
||||
// removes 'duplicate' assignments that assign the same target directly after another
|
||||
val linesToRemove = mutableListOf<Assignment>()
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
|
||||
for (stmtPairs in statements.windowed(2, step = 1)) {
|
||||
val assign1 = stmtPairs[0] as? Assignment
|
||||
val assign2 = stmtPairs[1] as? Assignment
|
||||
if (assign1 != null && assign2 != null && !assign2.isAugmentable) {
|
||||
if (assign1.target.isSameAs(assign2.target, program) && assign1.target.isInRegularRAMof(compTarget.machine)) {
|
||||
if(assign2.target.identifier==null || !assign2.value.referencesIdentifier(*(assign2.target.identifier!!.nameInSource.toTypedArray())))
|
||||
// only remove the second assignment if its value is a simple expression!
|
||||
when(assign2.value) {
|
||||
is PrefixExpression,
|
||||
is BinaryExpression,
|
||||
is TypecastExpression,
|
||||
is FunctionCall -> { /* don't remove */ }
|
||||
else -> linesToRemove.add(assign1)
|
||||
}
|
||||
fun substituteZeroInBinexpr(expr: BinaryExpression, zero: NumericLiteralValue, assign1: Assignment, assign2: Assignment) {
|
||||
if(expr.left isSameAs assign2.target) {
|
||||
// X = X <oper> Right
|
||||
linesToRemove.add(assign1)
|
||||
modifications.add(IAstModification.ReplaceNode(
|
||||
expr.left, zero, expr
|
||||
))
|
||||
}
|
||||
if(expr.right isSameAs assign2.target) {
|
||||
// X = Left <oper> X
|
||||
linesToRemove.add(assign1)
|
||||
modifications.add(IAstModification.ReplaceNode(
|
||||
expr.right, zero, expr
|
||||
))
|
||||
}
|
||||
val leftBinExpr = expr.left as? BinaryExpression
|
||||
val rightBinExpr = expr.right as? BinaryExpression
|
||||
if(leftBinExpr!=null && rightBinExpr==null) {
|
||||
if(leftBinExpr.left isSameAs assign2.target) {
|
||||
// X = (X <oper> Right) <oper> Something
|
||||
linesToRemove.add(assign1)
|
||||
modifications.add(IAstModification.ReplaceNode(
|
||||
leftBinExpr.left, zero, leftBinExpr
|
||||
))
|
||||
}
|
||||
if(leftBinExpr.right isSameAs assign2.target) {
|
||||
// X = (Left <oper> X) <oper> Something
|
||||
linesToRemove.add(assign1)
|
||||
modifications.add(IAstModification.ReplaceNode(
|
||||
leftBinExpr.right, zero, leftBinExpr
|
||||
))
|
||||
}
|
||||
}
|
||||
if(leftBinExpr==null && rightBinExpr!=null) {
|
||||
if(rightBinExpr.left isSameAs assign2.target) {
|
||||
// X = Something <oper> (X <oper> Right)
|
||||
linesToRemove.add(assign1)
|
||||
modifications.add(IAstModification.ReplaceNode(
|
||||
rightBinExpr.left, zero, rightBinExpr
|
||||
))
|
||||
}
|
||||
if(rightBinExpr.right isSameAs assign2.target) {
|
||||
// X = Something <oper> (Left <oper> X)
|
||||
linesToRemove.add(assign1)
|
||||
modifications.add(IAstModification.ReplaceNode(
|
||||
rightBinExpr.right, zero, rightBinExpr
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return linesToRemove
|
||||
fun substituteZeroInPrefixexpr(expr: PrefixExpression, zero: NumericLiteralValue, assign1: Assignment, assign2: Assignment) {
|
||||
if(expr.expression isSameAs assign2.target) {
|
||||
linesToRemove.add(assign1)
|
||||
modifications.add(IAstModification.ReplaceNode(
|
||||
expr.expression, zero, expr
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fun substituteZeroInTypecast(expr: TypecastExpression, zero: NumericLiteralValue, assign1: Assignment, assign2: Assignment) {
|
||||
if(expr.expression isSameAs assign2.target) {
|
||||
linesToRemove.add(assign1)
|
||||
modifications.add(IAstModification.ReplaceNode(
|
||||
expr.expression, zero, expr
|
||||
))
|
||||
}
|
||||
val subCast = expr.expression as? TypecastExpression
|
||||
if(subCast!=null && subCast.expression isSameAs assign2.target) {
|
||||
linesToRemove.add(assign1)
|
||||
modifications.add(IAstModification.ReplaceNode(
|
||||
subCast.expression, zero, subCast
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
for (stmtPairs in statements.windowed(2, step = 1)) {
|
||||
val assign1 = stmtPairs[0] as? Assignment
|
||||
val assign2 = stmtPairs[1] as? Assignment
|
||||
if (assign1 != null && assign2 != null) {
|
||||
val cvalue1 = assign1.value.constValue(program)
|
||||
if(cvalue1!=null && cvalue1.number==0.0 && assign2.target.isSameAs(assign1.target, program) && assign2.isAugmentable) {
|
||||
val value2 = assign2.value
|
||||
val zero = VarDecl.defaultZero(value2.inferType(program).getOr(DataType.UNDEFINED), value2.position)
|
||||
when(value2) {
|
||||
is BinaryExpression -> substituteZeroInBinexpr(value2, zero, assign1, assign2)
|
||||
is PrefixExpression -> substituteZeroInPrefixexpr(value2, zero, assign1, assign2)
|
||||
is TypecastExpression -> substituteZeroInTypecast(value2, zero, assign1, assign2)
|
||||
else -> {}
|
||||
}
|
||||
} else {
|
||||
if (assign1.target.isSameAs(assign2.target, program) && !assign1.target.isIOAddress(compTarget.machine)) {
|
||||
if(assign2.target.identifier==null || !assign2.value.referencesIdentifier(assign2.target.identifier!!.nameInSource))
|
||||
// only remove the second assignment if its value is a simple expression!
|
||||
when(assign2.value) {
|
||||
is PrefixExpression,
|
||||
is BinaryExpression,
|
||||
is TypecastExpression,
|
||||
is FunctionCallExpr -> { /* don't remove */ }
|
||||
else -> linesToRemove.add(assign1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return modifications + linesToRemove.map { IAstModification.Remove(it, scope) }
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ plugins {
|
||||
id 'application'
|
||||
id "org.jetbrains.kotlin.jvm"
|
||||
id 'com.github.johnrengelman.shadow' version '7.1.0'
|
||||
id "io.kotest" version "0.3.8"
|
||||
}
|
||||
|
||||
java {
|
||||
@ -11,6 +12,18 @@ java {
|
||||
}
|
||||
}
|
||||
|
||||
compileKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = javaVersion
|
||||
}
|
||||
}
|
||||
|
||||
compileTestKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = javaVersion
|
||||
}
|
||||
}
|
||||
|
||||
def prog8version = rootProject.file('compiler/res/version.txt').text.trim()
|
||||
|
||||
dependencies {
|
||||
@ -18,15 +31,13 @@ dependencies {
|
||||
implementation project(':codeOptimizers')
|
||||
implementation project(':compilerAst')
|
||||
implementation project(':codeGeneration')
|
||||
implementation 'org.antlr:antlr4-runtime:4.9.2'
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.3'
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.12"
|
||||
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2'
|
||||
testImplementation 'org.hamcrest:hamcrest:2.2'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2'
|
||||
testImplementation 'io.kotest:kotest-runner-junit5-jvm:4.6.3'
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
@ -42,7 +53,6 @@ configurations {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
|
@ -1,10 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="FacetManager">
|
||||
<facet type="Python" name="Python">
|
||||
<configuration sdkName="Python 3.9" />
|
||||
</facet>
|
||||
</component>
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
@ -13,17 +8,17 @@
|
||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="module" module-name="compilerAst" />
|
||||
<orderEntry type="library" name="Python 3.9 interpreter library" level="application" />
|
||||
<orderEntry type="library" name="hamcrest" level="project" />
|
||||
<orderEntry type="library" name="jetbrains.kotlinx.cli.jvm" level="project" />
|
||||
<orderEntry type="library" name="junit.jupiter" level="project" />
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
<orderEntry type="module" module-name="codeOptimizers" />
|
||||
<orderEntry type="module" module-name="compilerInterfaces" />
|
||||
<orderEntry type="module" module-name="codeGeneration" />
|
||||
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
|
||||
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
|
||||
<orderEntry type="library" name="antlr.antlr4" level="project" />
|
||||
</component>
|
||||
</module>
|
683
compiler/res/prog8lib/c128/floats.asm
Normal file
683
compiler/res/prog8lib/c128/floats.asm
Normal file
@ -0,0 +1,683 @@
|
||||
; --- low level floating point assembly routines for the C128
|
||||
; these are almost all identical to the C64 except for a few details
|
||||
; so we have to have a separate library file for the C128 unfortunately.
|
||||
|
||||
|
||||
FL_ONE_const .byte 129 ; 1.0
|
||||
FL_ZERO_const .byte 0,0,0,0,0 ; 0.0
|
||||
FL_LOG2_const .byte $80, $31, $72, $17, $f8 ; log(2)
|
||||
|
||||
|
||||
floats_store_reg .byte 0 ; temp storage
|
||||
|
||||
|
||||
ub2float .proc
|
||||
; -- convert ubyte in SCRATCH_ZPB1 to float at address A/Y
|
||||
; clobbers A, Y
|
||||
stx P8ZP_SCRATCH_REG
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy P8ZP_SCRATCH_B1
|
||||
lda #0
|
||||
jsr GIVAYF
|
||||
_fac_to_mem ldx P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
jsr MOVMF
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
b2float .proc
|
||||
; -- convert byte in SCRATCH_ZPB1 to float at address A/Y
|
||||
; clobbers A, Y
|
||||
stx P8ZP_SCRATCH_REG
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
lda P8ZP_SCRATCH_B1
|
||||
jsr FREADSA
|
||||
jmp ub2float._fac_to_mem
|
||||
.pend
|
||||
|
||||
uw2float .proc
|
||||
; -- convert uword in SCRATCH_ZPWORD1 to float at address A/Y
|
||||
stx P8ZP_SCRATCH_REG
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
jsr GIVUAYFAY
|
||||
jmp ub2float._fac_to_mem
|
||||
.pend
|
||||
|
||||
w2float .proc
|
||||
; -- convert word in SCRATCH_ZPWORD1 to float at address A/Y
|
||||
stx P8ZP_SCRATCH_REG
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy P8ZP_SCRATCH_W1
|
||||
lda P8ZP_SCRATCH_W1+1
|
||||
jsr GIVAYF
|
||||
jmp ub2float._fac_to_mem
|
||||
.pend
|
||||
|
||||
|
||||
cast_from_uw .proc
|
||||
; -- uword in A/Y into float var at (P8ZP_SCRATCH_W2)
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr GIVUAYFAY
|
||||
jmp ub2float._fac_to_mem
|
||||
.pend
|
||||
|
||||
|
||||
cast_from_w .proc
|
||||
; -- word in A/Y into float var at (P8ZP_SCRATCH_W2)
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr GIVAYFAY
|
||||
jmp ub2float._fac_to_mem
|
||||
.pend
|
||||
|
||||
|
||||
cast_from_ub .proc
|
||||
; -- ubyte in Y into float var at (P8ZP_SCRATCH_W2)
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr FREADUY
|
||||
jmp ub2float._fac_to_mem
|
||||
.pend
|
||||
|
||||
|
||||
cast_from_b .proc
|
||||
; -- byte in A into float var at (P8ZP_SCRATCH_W2)
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr FREADSA
|
||||
jmp ub2float._fac_to_mem
|
||||
.pend
|
||||
|
||||
cast_as_uw_into_ya .proc ; also used for float 2 ub
|
||||
; -- cast float at A/Y to uword into Y/A
|
||||
jsr MOVFM
|
||||
jmp cast_FAC1_as_uw_into_ya
|
||||
.pend
|
||||
|
||||
cast_as_w_into_ay .proc ; also used for float 2 b
|
||||
; -- cast float at A/Y to word into A/Y
|
||||
jsr MOVFM
|
||||
jmp cast_FAC1_as_w_into_ay
|
||||
.pend
|
||||
|
||||
cast_FAC1_as_uw_into_ya .proc ; also used for float 2 ub
|
||||
; -- cast fac1 to uword into Y/A
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr GETADR ; into Y/A
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
cast_FAC1_as_w_into_ay .proc ; also used for float 2 b
|
||||
; -- cast fac1 to word into A/Y
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr AYINT
|
||||
ldy $66
|
||||
lda $67
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
stack_b2float .proc
|
||||
; -- b2float operating on the stack
|
||||
inx
|
||||
lda P8ESTACK_LO,x
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr FREADSA
|
||||
jmp push_fac1._internal
|
||||
.pend
|
||||
|
||||
stack_w2float .proc
|
||||
; -- w2float operating on the stack
|
||||
inx
|
||||
ldy P8ESTACK_LO,x
|
||||
lda P8ESTACK_HI,x
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr GIVAYF
|
||||
jmp push_fac1._internal
|
||||
.pend
|
||||
|
||||
stack_ub2float .proc
|
||||
; -- ub2float operating on the stack
|
||||
inx
|
||||
lda P8ESTACK_LO,x
|
||||
stx P8ZP_SCRATCH_REG
|
||||
tay
|
||||
lda #0
|
||||
jsr GIVAYF
|
||||
jmp push_fac1._internal
|
||||
.pend
|
||||
|
||||
stack_uw2float .proc
|
||||
; -- uw2float operating on the stack
|
||||
inx
|
||||
lda P8ESTACK_LO,x
|
||||
ldy P8ESTACK_HI,x
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr GIVUAYFAY
|
||||
jmp push_fac1._internal
|
||||
.pend
|
||||
|
||||
stack_float2w .proc ; also used for float2b
|
||||
jsr pop_float_fac1
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr AYINT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
lda $66
|
||||
sta P8ESTACK_HI,x
|
||||
lda $67
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
stack_float2uw .proc ; also used for float2ub
|
||||
jsr pop_float_fac1
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr GETADR
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
sta P8ESTACK_HI,x
|
||||
tya
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
push_float .proc
|
||||
; ---- push mflpt5 in A/Y onto stack
|
||||
; (taking 3 stack positions = 6 bytes of which 1 is padding)
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sta P8ESTACK_LO,x
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sta P8ESTACK_HI,x
|
||||
dex
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sta P8ESTACK_LO,x
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sta P8ESTACK_HI,x
|
||||
dex
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
pop_float .proc
|
||||
; ---- pops mflpt5 from stack to memory A/Y
|
||||
; (frees 3 stack positions = 6 bytes of which 1 is padding)
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #4
|
||||
inx
|
||||
lda P8ESTACK_LO,x
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
inx
|
||||
lda P8ESTACK_HI,x
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
lda P8ESTACK_LO,x
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
inx
|
||||
lda P8ESTACK_HI,x
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
lda P8ESTACK_LO,x
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
rts
|
||||
.pend
|
||||
|
||||
pop_float_fac1 .proc
|
||||
; -- pops float from stack into FAC1
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr pop_float
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jmp MOVFM
|
||||
.pend
|
||||
|
||||
copy_float .proc
|
||||
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
|
||||
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
|
||||
sta _target+1
|
||||
sty _target+2
|
||||
ldy #4
|
||||
_loop lda (P8ZP_SCRATCH_W1),y
|
||||
_target sta $ffff,y ; modified
|
||||
dey
|
||||
bpl _loop
|
||||
rts
|
||||
.pend
|
||||
|
||||
inc_var_f .proc
|
||||
; -- add 1 to float pointed to by A/Y
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr MOVFM
|
||||
lda #<FL_ONE_const
|
||||
ldy #>FL_ONE_const
|
||||
jsr FADD
|
||||
ldx P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
jsr MOVMF
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
dec_var_f .proc
|
||||
; -- subtract 1 from float pointed to by A/Y
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda #<FL_ONE_const
|
||||
ldy #>FL_ONE_const
|
||||
jsr MOVFM
|
||||
lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
jsr FSUB
|
||||
ldx P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
jsr MOVMF
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
pop_2_floats_f2_in_fac1 .proc
|
||||
; -- pop 2 floats from stack, load the second one in FAC1 as well
|
||||
lda #<fmath_float2
|
||||
ldy #>fmath_float2
|
||||
jsr pop_float
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr pop_float
|
||||
lda #<fmath_float2
|
||||
ldy #>fmath_float2
|
||||
jmp MOVFM
|
||||
.pend
|
||||
|
||||
|
||||
fmath_float1 .byte 0,0,0,0,0 ; storage for a mflpt5 value
|
||||
fmath_float2 .byte 0,0,0,0,0 ; storage for a mflpt5 value
|
||||
|
||||
|
||||
push_fac1 .proc
|
||||
; -- push the float in FAC1 onto the stack
|
||||
stx P8ZP_SCRATCH_REG
|
||||
_internal ldx #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr MOVMF
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
jmp push_float
|
||||
.pend
|
||||
|
||||
|
||||
pow_f .proc
|
||||
; -- push f1 ** f2 on stack
|
||||
lda #<fmath_float2
|
||||
ldy #>fmath_float2
|
||||
jsr pop_float
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr pop_float
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr ROMUPK ; fac2 = float1
|
||||
lda #<fmath_float2
|
||||
ldy #>fmath_float2
|
||||
jsr FPWR
|
||||
jmp push_fac1._internal
|
||||
.pend
|
||||
|
||||
div_f .proc
|
||||
; -- push f1/f2 on stack
|
||||
jsr pop_2_floats_f2_in_fac1
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr FDIV
|
||||
jmp push_fac1._internal
|
||||
.pend
|
||||
|
||||
add_f .proc
|
||||
; -- push f1+f2 on stack
|
||||
jsr pop_2_floats_f2_in_fac1
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr FADD
|
||||
jmp push_fac1._internal
|
||||
.pend
|
||||
|
||||
sub_f .proc
|
||||
; -- push f1-f2 on stack
|
||||
jsr pop_2_floats_f2_in_fac1
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr FSUB
|
||||
jmp push_fac1._internal
|
||||
.pend
|
||||
|
||||
mul_f .proc
|
||||
; -- push f1*f2 on stack
|
||||
jsr pop_2_floats_f2_in_fac1
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr FMULT
|
||||
jmp push_fac1._internal
|
||||
.pend
|
||||
|
||||
neg_f .proc
|
||||
; -- toggle the sign bit on the stack
|
||||
lda P8ESTACK_HI+3,x
|
||||
eor #$80
|
||||
sta P8ESTACK_HI+3,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
var_fac1_less_f .proc
|
||||
; -- is the float in FAC1 < the variable AY?
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr FCOMP
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
cmp #255
|
||||
beq +
|
||||
lda #0
|
||||
rts
|
||||
+ lda #1
|
||||
rts
|
||||
.pend
|
||||
|
||||
var_fac1_lesseq_f .proc
|
||||
; -- is the float in FAC1 <= the variable AY?
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr FCOMP
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
cmp #0
|
||||
beq +
|
||||
cmp #255
|
||||
beq +
|
||||
lda #0
|
||||
rts
|
||||
+ lda #1
|
||||
rts
|
||||
.pend
|
||||
|
||||
var_fac1_greater_f .proc
|
||||
; -- is the float in FAC1 > the variable AY?
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr FCOMP
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
cmp #1
|
||||
beq +
|
||||
lda #0
|
||||
rts
|
||||
+ lda #1
|
||||
rts
|
||||
.pend
|
||||
|
||||
var_fac1_greatereq_f .proc
|
||||
; -- is the float in FAC1 >= the variable AY?
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr FCOMP
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
cmp #0
|
||||
beq +
|
||||
cmp #1
|
||||
beq +
|
||||
lda #0
|
||||
rts
|
||||
+ lda #1
|
||||
rts
|
||||
.pend
|
||||
|
||||
var_fac1_notequal_f .proc
|
||||
; -- are the floats numbers in FAC1 and the variable AY *not* identical?
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr FCOMP
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
and #1
|
||||
rts
|
||||
.pend
|
||||
|
||||
vars_equal_f .proc
|
||||
; -- are the mflpt5 numbers in P8ZP_SCRATCH_W1 and AY identical?
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #0
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
cmp (P8ZP_SCRATCH_W2),y
|
||||
bne _false
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
cmp (P8ZP_SCRATCH_W2),y
|
||||
bne _false
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
cmp (P8ZP_SCRATCH_W2),y
|
||||
bne _false
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
cmp (P8ZP_SCRATCH_W2),y
|
||||
bne _false
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
cmp (P8ZP_SCRATCH_W2),y
|
||||
bne _false
|
||||
lda #1
|
||||
rts
|
||||
_false lda #0
|
||||
rts
|
||||
.pend
|
||||
|
||||
equal_f .proc
|
||||
; -- are the two mflpt5 numbers on the stack identical?
|
||||
inx
|
||||
inx
|
||||
inx
|
||||
inx
|
||||
lda P8ESTACK_LO-3,x
|
||||
cmp P8ESTACK_LO,x
|
||||
bne _equals_false
|
||||
lda P8ESTACK_LO-2,x
|
||||
cmp P8ESTACK_LO+1,x
|
||||
bne _equals_false
|
||||
lda P8ESTACK_LO-1,x
|
||||
cmp P8ESTACK_LO+2,x
|
||||
bne _equals_false
|
||||
lda P8ESTACK_HI-2,x
|
||||
cmp P8ESTACK_HI+1,x
|
||||
bne _equals_false
|
||||
lda P8ESTACK_HI-1,x
|
||||
cmp P8ESTACK_HI+2,x
|
||||
bne _equals_false
|
||||
_equals_true lda #1
|
||||
_equals_store inx
|
||||
sta P8ESTACK_LO+1,x
|
||||
rts
|
||||
_equals_false lda #0
|
||||
beq _equals_store
|
||||
.pend
|
||||
|
||||
notequal_f .proc
|
||||
; -- are the two mflpt5 numbers on the stack different?
|
||||
jsr equal_f
|
||||
eor #1 ; invert the result
|
||||
sta P8ESTACK_LO+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
vars_less_f .proc
|
||||
; -- is float in AY < float in P8ZP_SCRATCH_W2 ?
|
||||
jsr MOVFM
|
||||
lda P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr FCOMP
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
cmp #255
|
||||
bne +
|
||||
lda #1
|
||||
rts
|
||||
+ lda #0
|
||||
rts
|
||||
.pend
|
||||
|
||||
vars_lesseq_f .proc
|
||||
; -- is float in AY <= float in P8ZP_SCRATCH_W2 ?
|
||||
jsr MOVFM
|
||||
lda P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr FCOMP
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
cmp #255
|
||||
bne +
|
||||
- lda #1
|
||||
rts
|
||||
+ cmp #0
|
||||
beq -
|
||||
lda #0
|
||||
rts
|
||||
.pend
|
||||
|
||||
less_f .proc
|
||||
; -- is f1 < f2?
|
||||
jsr compare_floats
|
||||
cmp #255
|
||||
beq compare_floats._return_true
|
||||
bne compare_floats._return_false
|
||||
.pend
|
||||
|
||||
|
||||
lesseq_f .proc
|
||||
; -- is f1 <= f2?
|
||||
jsr compare_floats
|
||||
cmp #255
|
||||
beq compare_floats._return_true
|
||||
cmp #0
|
||||
beq compare_floats._return_true
|
||||
bne compare_floats._return_false
|
||||
.pend
|
||||
|
||||
greater_f .proc
|
||||
; -- is f1 > f2?
|
||||
jsr compare_floats
|
||||
cmp #1
|
||||
beq compare_floats._return_true
|
||||
bne compare_floats._return_false
|
||||
.pend
|
||||
|
||||
greatereq_f .proc
|
||||
; -- is f1 >= f2?
|
||||
jsr compare_floats
|
||||
cmp #1
|
||||
beq compare_floats._return_true
|
||||
cmp #0
|
||||
beq compare_floats._return_true
|
||||
bne compare_floats._return_false
|
||||
.pend
|
||||
|
||||
compare_floats .proc
|
||||
lda #<fmath_float2
|
||||
ldy #>fmath_float2
|
||||
jsr pop_float
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr pop_float
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr MOVFM ; fac1 = flt1
|
||||
lda #<fmath_float2
|
||||
ldy #>fmath_float2
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr FCOMP ; A = flt1 compared with flt2 (0=equal, 1=flt1>flt2, 255=flt1<flt2)
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
_return_false lda #0
|
||||
_return_result sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
_return_true lda #1
|
||||
bne _return_result
|
||||
.pend
|
||||
|
||||
set_array_float_from_fac1 .proc
|
||||
; -- set the float in FAC1 in the array (index in A, array in P8ZP_SCRATCH_W1)
|
||||
sta P8ZP_SCRATCH_B1
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc P8ZP_SCRATCH_B1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
clc
|
||||
adc P8ZP_SCRATCH_W1
|
||||
bcc +
|
||||
iny
|
||||
+ stx floats_store_reg
|
||||
tax
|
||||
jsr MOVMF
|
||||
ldx floats_store_reg
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
set_0_array_float .proc
|
||||
; -- set a float in an array to zero (index in A, array in P8ZP_SCRATCH_W1)
|
||||
sta P8ZP_SCRATCH_B1
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc P8ZP_SCRATCH_B1
|
||||
tay
|
||||
lda #0
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
iny
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
iny
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
iny
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
iny
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
set_array_float .proc
|
||||
; -- set a float in an array to a value (index in A, float in P8ZP_SCRATCH_W1, array in P8ZP_SCRATCH_W2)
|
||||
sta P8ZP_SCRATCH_B1
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc P8ZP_SCRATCH_B1
|
||||
adc P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
bcc +
|
||||
iny
|
||||
+ jmp copy_float
|
||||
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
|
||||
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
|
||||
.pend
|
||||
|
||||
|
159
compiler/res/prog8lib/c128/floats.p8
Normal file
159
compiler/res/prog8lib/c128/floats.p8
Normal file
@ -0,0 +1,159 @@
|
||||
; Prog8 definitions for floating point handling on the Commodore 128
|
||||
;
|
||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
|
||||
%option enable_floats
|
||||
|
||||
floats {
|
||||
; ---- this block contains C-128 compatible floating point related functions ----
|
||||
|
||||
const float PI = 3.141592653589793
|
||||
const float TWOPI = 6.283185307179586
|
||||
|
||||
float tempvar_swap_float ; used for some swap() operations
|
||||
|
||||
|
||||
; ---- ROM float functions ----
|
||||
|
||||
; note: the fac1 and fac2 are working registers and take 6 bytes each,
|
||||
; floats in memory (and rom) are stored in 5-byte MFLPT packed format.
|
||||
|
||||
; note: fac1/2 might get clobbered even if not mentioned in the function's name.
|
||||
; note: for subtraction and division, the left operand is in fac2, the right operand in fac1.
|
||||
|
||||
romsub $af00 = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 102-103 ($66-$67) MSB FIRST. (might throw ILLEGAL QUANTITY)
|
||||
|
||||
; GIVAYF: signed word in Y/A (note different lsb/msb order) -> float in fac1
|
||||
; there is also floats.GIVUAYFAY - unsigned word in A/Y (lo/hi) to fac1
|
||||
; (tip: use GIVAYFAY to use A/Y input; lo/hi switched to normal order)
|
||||
romsub $af03 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
|
||||
romsub $af09 = VAL(ubyte length @ A) clobbers(A,X,Y) ; str -> fac1, $24/25 must point to string in bank1, A=string length Don't call this from bank0
|
||||
|
||||
; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $16/17)
|
||||
; (tip: use GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
|
||||
romsub $af0c = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
|
||||
|
||||
romsub $af12 = FSUB(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt from A/Y - fac1
|
||||
romsub $af15 = FSUBT() clobbers(A,X,Y) ; fac1 = fac2-fac1 mind the order of the operands
|
||||
romsub $af18 = FADD(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 += mflpt value from A/Y in bank 1
|
||||
romsub $af1b = FADDT() clobbers(A,X,Y) ; fac1 += fac2
|
||||
romsub $af2a = LOG() clobbers(A,X,Y) ; fac1 = LN(fac1) (natural log)
|
||||
romsub $af1e = FMULT(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 *= mflpt value from A/Y
|
||||
romsub $af21 = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2
|
||||
romsub $af5a = CONUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory bank 1 in A/Y into fac2
|
||||
romsub $af5d = ROMUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in current bank in A/Y into fac2
|
||||
romsub $af24 = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1 (remainder in fac2)
|
||||
romsub $af27 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 (remainder in fac2) mind the order of the operands
|
||||
|
||||
romsub $af63 = MOVFM(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac1
|
||||
romsub $af60 = MOVFRM() clobbers(A,X,Y) ; load mflpt value from memory in $24/$25 into fac2
|
||||
romsub $af66 = MOVMF(uword mflpt @ XY) clobbers(A,X,Y) ; store fac1 to memory X/Y as 5-byte mflpt
|
||||
romsub $af69 = MOVFA() clobbers(A,X) ; copy fac2 to fac1
|
||||
romsub $af6c = MOVAF() clobbers(A,X) ; copy fac1 to fac2
|
||||
romsub $af6c = MOVEF() clobbers(A,X) ; copy fac1 to fac2
|
||||
romsub $af51 = SIGN() clobbers(X,Y) -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
|
||||
romsub $af4e = ABS() clobbers(A,X,Y) ; fac1 = ABS(fac1)
|
||||
romsub $af54 = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
|
||||
romsub $af2d = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to round instead of trunc
|
||||
romsub $af06 = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY ($0100)
|
||||
romsub $af30 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
|
||||
romsub $af36 = FPWR(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = fac2 ** mflpt from A/Y
|
||||
romsub $af39 = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
|
||||
romsub $af33 = NEGOP() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1)
|
||||
romsub $af3c = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
|
||||
romsub $af57 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
|
||||
romsub $af3f = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
|
||||
romsub $af42 = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
|
||||
romsub $af45 = TAN() clobbers(A,X,Y) ; fac1 = TAN(fac1)
|
||||
romsub $af48 = ATN() clobbers(A,X,Y) ; fac1 = ATN(fac1)
|
||||
|
||||
|
||||
asmsub FREADSA (byte value @A) clobbers(A,X,Y) {
|
||||
; ---- 8 bit signed A -> float in fac1
|
||||
%asm {{
|
||||
tay
|
||||
bpl +
|
||||
lda #$ff
|
||||
jmp GIVAYF
|
||||
+ lda #0
|
||||
jmp GIVAYF
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
sta _tmp
|
||||
sty P8ZP_SCRATCH_B1
|
||||
tya
|
||||
ldy _tmp
|
||||
jsr GIVAYF ; load it as signed... correct afterwards
|
||||
lda P8ZP_SCRATCH_B1
|
||||
bpl +
|
||||
lda #<_flt65536
|
||||
ldy #>_flt65536
|
||||
jsr ROMUPK
|
||||
jsr FADDT
|
||||
+ ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
_tmp .byte 0
|
||||
_flt65536 .byte 145,0,0,0,0 ; 65536.0
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- signed 16 bit word in A/Y (lo/hi) to float in fac1
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_B1
|
||||
tya
|
||||
ldy P8ZP_SCRATCH_B1
|
||||
jmp GIVAYF ; this uses the inverse order, Y/A
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub GETADRAY () clobbers(X) -> uword @ AY {
|
||||
; ---- fac1 to unsigned word in A/Y
|
||||
%asm {{
|
||||
jsr GETADR ; this uses the inverse order, Y/A
|
||||
sta P8ZP_SCRATCH_B1
|
||||
tya
|
||||
ldy P8ZP_SCRATCH_B1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub FREADUY (ubyte value @Y) {
|
||||
; -- 8 bit unsigned Y -> float in fac1
|
||||
%asm {{
|
||||
lda #0
|
||||
jmp GIVAYF
|
||||
}}
|
||||
}
|
||||
|
||||
sub print_f (float value) {
|
||||
; ---- prints the floating point value (without a newline).
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda #<value
|
||||
ldy #>value
|
||||
jsr MOVFM ; load float into fac1
|
||||
jsr FOUT ; fac1 to string in A/Y
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
beq +
|
||||
jsr c64.CHROUT
|
||||
iny
|
||||
bne -
|
||||
+ ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
%asminclude "library:c128/floats.asm"
|
||||
%asminclude "library:c64/floats_funcs.asm"
|
||||
|
||||
}
|
60
compiler/res/prog8lib/c128/graphics.p8
Normal file
60
compiler/res/prog8lib/c128/graphics.p8
Normal file
@ -0,0 +1,60 @@
|
||||
%import syslib
|
||||
%import textio
|
||||
|
||||
; Bitmap pixel graphics module for the Commodore 128
|
||||
|
||||
; TODO c128 actually implement the graphics routines. Ideally a way to 'borrow' the code form the C64 version without just copy-pasting that here?
|
||||
|
||||
graphics {
|
||||
const uword WIDTH = 320
|
||||
const ubyte HEIGHT = 200
|
||||
|
||||
sub enable_bitmap_mode() {
|
||||
; enable bitmap screen, erase it and set colors to black/white.
|
||||
; TODO
|
||||
}
|
||||
|
||||
sub disable_bitmap_mode() {
|
||||
; enables text mode, erase the text screen, color white
|
||||
; TODO
|
||||
}
|
||||
|
||||
|
||||
sub clear_screen(ubyte pixelcolor, ubyte bgcolor) {
|
||||
; TODO
|
||||
}
|
||||
|
||||
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
|
||||
; TODO
|
||||
}
|
||||
|
||||
sub fillrect(uword x, uword y, uword width, uword height) {
|
||||
; TODO
|
||||
}
|
||||
|
||||
sub rect(uword x, uword y, uword width, uword height) {
|
||||
; TODO
|
||||
}
|
||||
|
||||
sub horizontal_line(uword x, uword y, uword length) {
|
||||
; TODO
|
||||
}
|
||||
|
||||
sub vertical_line(uword x, uword y, uword height) {
|
||||
; TODO
|
||||
}
|
||||
|
||||
sub circle(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||
; TODO
|
||||
}
|
||||
|
||||
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||
; TODO
|
||||
}
|
||||
|
||||
inline asmsub plot(uword plotx @R0, uword ploty @R1) clobbers(A, X, Y) {
|
||||
%asm {{
|
||||
nop ; TODO
|
||||
}}
|
||||
}
|
||||
}
|
796
compiler/res/prog8lib/c128/syslib.p8
Normal file
796
compiler/res/prog8lib/c128/syslib.p8
Normal file
@ -0,0 +1,796 @@
|
||||
; Prog8 definitions for the Commodore-128
|
||||
; Including memory registers, I/O registers, Basic and Kernal subroutines.
|
||||
;
|
||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
;
|
||||
|
||||
c64 {
|
||||
|
||||
&ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
|
||||
&ubyte TIME_MID = $a1 ; .. mid byte
|
||||
&ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
|
||||
&ubyte STATUS = $90 ; kernal status variable for I/O
|
||||
&ubyte STKEY = $91 ; various keyboard statuses (updated by IRQ)
|
||||
;;&ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ) // TODO c128 ??
|
||||
|
||||
&ubyte COLOR = $00f1 ; cursor color
|
||||
;;&ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address) // TODO c128 ??
|
||||
&uword CINV = $0314 ; IRQ vector (in ram)
|
||||
&uword CBINV = $0316 ; BRK vector (in ram)
|
||||
&uword NMINV = $0318 ; NMI vector (in ram)
|
||||
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
|
||||
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
|
||||
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
|
||||
|
||||
; the default addresses for the character screen chars and colors
|
||||
const uword Screen = $0400 ; to have this as an array[40*25] the compiler would have to support array size > 255
|
||||
const uword Colors = $d800 ; to have this as an array[40*25] the compiler would have to support array size > 255
|
||||
|
||||
; the default locations of the 8 sprite pointers (store address of sprite / 64)
|
||||
&ubyte SPRPTR0 = 2040
|
||||
&ubyte SPRPTR1 = 2041
|
||||
&ubyte SPRPTR2 = 2042
|
||||
&ubyte SPRPTR3 = 2043
|
||||
&ubyte SPRPTR4 = 2044
|
||||
&ubyte SPRPTR5 = 2045
|
||||
&ubyte SPRPTR6 = 2046
|
||||
&ubyte SPRPTR7 = 2047
|
||||
&ubyte[8] SPRPTR = 2040 ; the 8 sprite pointers as an array.
|
||||
|
||||
|
||||
; ---- VIC-II 6567/6569/856x registers ----
|
||||
|
||||
&ubyte SP0X = $d000
|
||||
&ubyte SP0Y = $d001
|
||||
&ubyte SP1X = $d002
|
||||
&ubyte SP1Y = $d003
|
||||
&ubyte SP2X = $d004
|
||||
&ubyte SP2Y = $d005
|
||||
&ubyte SP3X = $d006
|
||||
&ubyte SP3Y = $d007
|
||||
&ubyte SP4X = $d008
|
||||
&ubyte SP4Y = $d009
|
||||
&ubyte SP5X = $d00a
|
||||
&ubyte SP5Y = $d00b
|
||||
&ubyte SP6X = $d00c
|
||||
&ubyte SP6Y = $d00d
|
||||
&ubyte SP7X = $d00e
|
||||
&ubyte SP7Y = $d00f
|
||||
&ubyte[16] SPXY = $d000 ; the 8 sprite X and Y registers as an array.
|
||||
&uword[8] SPXYW = $d000 ; the 8 sprite X and Y registers as a combined xy word array.
|
||||
|
||||
&ubyte MSIGX = $d010
|
||||
&ubyte SCROLY = $d011
|
||||
&ubyte RASTER = $d012
|
||||
&ubyte LPENX = $d013
|
||||
&ubyte LPENY = $d014
|
||||
&ubyte SPENA = $d015
|
||||
&ubyte SCROLX = $d016
|
||||
&ubyte YXPAND = $d017
|
||||
&ubyte VMCSB = $d018
|
||||
&ubyte VICIRQ = $d019
|
||||
&ubyte IREQMASK = $d01a
|
||||
&ubyte SPBGPR = $d01b
|
||||
&ubyte SPMC = $d01c
|
||||
&ubyte XXPAND = $d01d
|
||||
&ubyte SPSPCL = $d01e
|
||||
&ubyte SPBGCL = $d01f
|
||||
|
||||
&ubyte EXTCOL = $d020 ; border color
|
||||
&ubyte BGCOL0 = $d021 ; screen color
|
||||
&ubyte BGCOL1 = $d022
|
||||
&ubyte BGCOL2 = $d023
|
||||
&ubyte BGCOL4 = $d024
|
||||
&ubyte SPMC0 = $d025
|
||||
&ubyte SPMC1 = $d026
|
||||
&ubyte SP0COL = $d027
|
||||
&ubyte SP1COL = $d028
|
||||
&ubyte SP2COL = $d029
|
||||
&ubyte SP3COL = $d02a
|
||||
&ubyte SP4COL = $d02b
|
||||
&ubyte SP5COL = $d02c
|
||||
&ubyte SP6COL = $d02d
|
||||
&ubyte SP7COL = $d02e
|
||||
&ubyte[8] SPCOL = $d027
|
||||
|
||||
|
||||
; ---- end of VIC-II registers ----
|
||||
|
||||
; ---- CIA 6526 1 & 2 registers ----
|
||||
|
||||
&ubyte CIA1PRA = $DC00 ; CIA 1 DRA, keyboard column drive (and joystick control port #2)
|
||||
&ubyte CIA1PRB = $DC01 ; CIA 1 DRB, keyboard row port (and joystick control port #1)
|
||||
&ubyte CIA1DDRA = $DC02 ; CIA 1 DDRA, keyboard column
|
||||
&ubyte CIA1DDRB = $DC03 ; CIA 1 DDRB, keyboard row
|
||||
&ubyte CIA1TAL = $DC04 ; CIA 1 timer A low byte
|
||||
&ubyte CIA1TAH = $DC05 ; CIA 1 timer A high byte
|
||||
&ubyte CIA1TBL = $DC06 ; CIA 1 timer B low byte
|
||||
&ubyte CIA1TBH = $DC07 ; CIA 1 timer B high byte
|
||||
&ubyte CIA1TOD10 = $DC08 ; time of day, 1/10 sec.
|
||||
&ubyte CIA1TODSEC = $DC09 ; time of day, seconds
|
||||
&ubyte CIA1TODMMIN = $DC0A ; time of day, minutes
|
||||
&ubyte CIA1TODHR = $DC0B ; time of day, hours
|
||||
&ubyte CIA1SDR = $DC0C ; Serial Data Register
|
||||
&ubyte CIA1ICR = $DC0D
|
||||
&ubyte CIA1CRA = $DC0E
|
||||
&ubyte CIA1CRB = $DC0F
|
||||
|
||||
&ubyte CIA2PRA = $DD00 ; CIA 2 DRA, serial port and video address
|
||||
&ubyte CIA2PRB = $DD01 ; CIA 2 DRB, RS232 port / USERPORT
|
||||
&ubyte CIA2DDRA = $DD02 ; CIA 2 DDRA, serial port and video address
|
||||
&ubyte CIA2DDRB = $DD03 ; CIA 2 DDRB, RS232 port / USERPORT
|
||||
&ubyte CIA2TAL = $DD04 ; CIA 2 timer A low byte
|
||||
&ubyte CIA2TAH = $DD05 ; CIA 2 timer A high byte
|
||||
&ubyte CIA2TBL = $DD06 ; CIA 2 timer B low byte
|
||||
&ubyte CIA2TBH = $DD07 ; CIA 2 timer B high byte
|
||||
&ubyte CIA2TOD10 = $DD08 ; time of day, 1/10 sec.
|
||||
&ubyte CIA2TODSEC = $DD09 ; time of day, seconds
|
||||
&ubyte CIA2TODMIN = $DD0A ; time of day, minutes
|
||||
&ubyte CIA2TODHR = $DD0B ; time of day, hours
|
||||
&ubyte CIA2SDR = $DD0C ; Serial Data Register
|
||||
&ubyte CIA2ICR = $DD0D
|
||||
&ubyte CIA2CRA = $DD0E
|
||||
&ubyte CIA2CRB = $DD0F
|
||||
|
||||
; ---- end of CIA registers ----
|
||||
|
||||
; ---- SID 6581/8580 registers ----
|
||||
|
||||
&ubyte FREQLO1 = $D400 ; channel 1 freq lo
|
||||
&ubyte FREQHI1 = $D401 ; channel 1 freq hi
|
||||
&uword FREQ1 = $D400 ; channel 1 freq (word)
|
||||
&ubyte PWLO1 = $D402 ; channel 1 pulse width lo (7-0)
|
||||
&ubyte PWHI1 = $D403 ; channel 1 pulse width hi (11-8)
|
||||
&uword PW1 = $D402 ; channel 1 pulse width (word)
|
||||
&ubyte CR1 = $D404 ; channel 1 voice control register
|
||||
&ubyte AD1 = $D405 ; channel 1 attack & decay
|
||||
&ubyte SR1 = $D406 ; channel 1 sustain & release
|
||||
&ubyte FREQLO2 = $D407 ; channel 2 freq lo
|
||||
&ubyte FREQHI2 = $D408 ; channel 2 freq hi
|
||||
&uword FREQ2 = $D407 ; channel 2 freq (word)
|
||||
&ubyte PWLO2 = $D409 ; channel 2 pulse width lo (7-0)
|
||||
&ubyte PWHI2 = $D40A ; channel 2 pulse width hi (11-8)
|
||||
&uword PW2 = $D409 ; channel 2 pulse width (word)
|
||||
&ubyte CR2 = $D40B ; channel 2 voice control register
|
||||
&ubyte AD2 = $D40C ; channel 2 attack & decay
|
||||
&ubyte SR2 = $D40D ; channel 2 sustain & release
|
||||
&ubyte FREQLO3 = $D40E ; channel 3 freq lo
|
||||
&ubyte FREQHI3 = $D40F ; channel 3 freq hi
|
||||
&uword FREQ3 = $D40E ; channel 3 freq (word)
|
||||
&ubyte PWLO3 = $D410 ; channel 3 pulse width lo (7-0)
|
||||
&ubyte PWHI3 = $D411 ; channel 3 pulse width hi (11-8)
|
||||
&uword PW3 = $D410 ; channel 3 pulse width (word)
|
||||
&ubyte CR3 = $D412 ; channel 3 voice control register
|
||||
&ubyte AD3 = $D413 ; channel 3 attack & decay
|
||||
&ubyte SR3 = $D414 ; channel 3 sustain & release
|
||||
&ubyte FCLO = $D415 ; filter cutoff lo (2-0)
|
||||
&ubyte FCHI = $D416 ; filter cutoff hi (10-3)
|
||||
&uword FC = $D415 ; filter cutoff (word)
|
||||
&ubyte RESFILT = $D417 ; filter resonance and routing
|
||||
&ubyte MVOL = $D418 ; filter mode and main volume control
|
||||
&ubyte POTX = $D419 ; potentiometer X
|
||||
&ubyte POTY = $D41A ; potentiometer Y
|
||||
&ubyte OSC3 = $D41B ; channel 3 oscillator value read
|
||||
&ubyte ENV3 = $D41C ; channel 3 envelope value read
|
||||
|
||||
; ---- end of SID registers ----
|
||||
|
||||
; ---- kernal routines, these are the same as on the Commodore-64 (hence the same block name) ----
|
||||
|
||||
; STROUT --> use txt.print
|
||||
; CLEARSCR -> use txt.clear_screen
|
||||
; HOMECRSR -> use txt.home or txt.plot
|
||||
|
||||
romsub $FA65 = IRQDFRT() clobbers(A,X,Y) ; default IRQ routine
|
||||
romsub $FF33 = IRQDFEND() clobbers(A,X,Y) ; default IRQ end/cleanup
|
||||
|
||||
; TODO c128 a bunch of kernal routines are missing here that are specific to the c128
|
||||
|
||||
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
|
||||
romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ)
|
||||
romsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, tape buffer, screen
|
||||
romsub $FF8A = RESTOR() clobbers(A,X,Y) ; restore default I/O vectors
|
||||
romsub $FF8D = VECTOR(uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y) ; read/set I/O vector table
|
||||
romsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
|
||||
romsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
|
||||
romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
|
||||
romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set top of memory pointer
|
||||
romsub $FF9C = MEMBOT(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
|
||||
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
|
||||
romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
|
||||
romsub $FFA5 = ACPTR() -> ubyte @ A ; (alias: IECIN) input byte from serial bus
|
||||
romsub $FFA8 = CIOUT(ubyte databyte @ A) ; (alias: IECOUT) output byte to serial bus
|
||||
romsub $FFAB = UNTLK() clobbers(A) ; command serial bus device to UNTALK
|
||||
romsub $FFAE = UNLSN() clobbers(A) ; command serial bus device to UNLISTEN
|
||||
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
|
||||
romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
|
||||
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word
|
||||
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte secondary @ Y) ; set logical file parameters
|
||||
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
|
||||
romsub $FFC0 = OPEN() clobbers(X,Y) -> ubyte @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
|
||||
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
|
||||
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> ubyte @Pc ; (via 798 ($31E)) define an input channel
|
||||
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
|
||||
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
|
||||
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
|
||||
romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326)) output a character
|
||||
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, uword @ XY ; (via 816 ($330)) load from device
|
||||
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
|
||||
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
|
||||
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (A=lo,X=mid,Y=high)
|
||||
romsub $FFE1 = STOP() clobbers(X) -> ubyte @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
|
||||
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @Pc, ubyte @ A ; (via 810 ($32A)) get a character
|
||||
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
|
||||
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
|
||||
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of screen rows and columns
|
||||
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
|
||||
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
|
||||
|
||||
; ---- end of C64 compatible ROM kernal routines ----
|
||||
|
||||
; ---- utilities -----
|
||||
|
||||
asmsub STOP2() -> ubyte @A {
|
||||
; -- check if STOP key was pressed, returns true if so. More convenient to use than STOP() because that only sets the carry status flag.
|
||||
%asm {{
|
||||
txa
|
||||
pha
|
||||
jsr c64.STOP
|
||||
beq +
|
||||
pla
|
||||
tax
|
||||
lda #0
|
||||
rts
|
||||
+ pla
|
||||
tax
|
||||
lda #1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub RDTIM16() -> uword @AY {
|
||||
; -- like RDTIM() but only returning the lower 16 bits in AY for convenience
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr c64.RDTIM
|
||||
pha
|
||||
txa
|
||||
tay
|
||||
pla
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
; ---- system utility routines that are essentially the same as on the C64: -----
|
||||
asmsub disable_runstop_and_charsetswitch() clobbers(A) {
|
||||
%asm {{
|
||||
lda #$80
|
||||
sta 247 ; disable charset switching
|
||||
lda #112
|
||||
sta 808 ; disable run/stop key
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_irq(uword handler @AY, ubyte useKernal @Pc) clobbers(A) {
|
||||
%asm {{
|
||||
sta _modified+1
|
||||
sty _modified+2
|
||||
lda #0
|
||||
adc #0
|
||||
sta _use_kernal
|
||||
sei
|
||||
lda #<_irq_handler
|
||||
sta c64.CINV
|
||||
lda #>_irq_handler
|
||||
sta c64.CINV+1
|
||||
cli
|
||||
rts
|
||||
_irq_handler jsr _irq_handler_init
|
||||
_modified jsr $ffff ; modified
|
||||
jsr _irq_handler_end
|
||||
lda _use_kernal
|
||||
bne +
|
||||
lda #$ff
|
||||
sta c64.VICIRQ ; acknowledge raster irq
|
||||
lda c64.CIA1ICR ; acknowledge CIA1 interrupt
|
||||
; end irq processing - don't use kernal's irq handling
|
||||
pla
|
||||
tay
|
||||
pla
|
||||
tax
|
||||
pla
|
||||
rti
|
||||
+ jmp c64.IRQDFRT ; continue with normal kernal irq routine
|
||||
|
||||
_use_kernal .byte 0
|
||||
|
||||
_irq_handler_init
|
||||
; save all zp scratch registers and the X register as these might be clobbered by the irq routine
|
||||
stx IRQ_X_REG
|
||||
lda P8ZP_SCRATCH_B1
|
||||
sta IRQ_SCRATCH_ZPB1
|
||||
lda P8ZP_SCRATCH_REG
|
||||
sta IRQ_SCRATCH_ZPREG
|
||||
lda P8ZP_SCRATCH_W1
|
||||
sta IRQ_SCRATCH_ZPWORD1
|
||||
lda P8ZP_SCRATCH_W1+1
|
||||
sta IRQ_SCRATCH_ZPWORD1+1
|
||||
lda P8ZP_SCRATCH_W2
|
||||
sta IRQ_SCRATCH_ZPWORD2
|
||||
lda P8ZP_SCRATCH_W2+1
|
||||
sta IRQ_SCRATCH_ZPWORD2+1
|
||||
; stack protector; make sure we don't clobber the top of the evaluation stack
|
||||
dex
|
||||
dex
|
||||
dex
|
||||
dex
|
||||
dex
|
||||
dex
|
||||
cld
|
||||
rts
|
||||
|
||||
_irq_handler_end
|
||||
; restore all zp scratch registers and the X register
|
||||
lda IRQ_SCRATCH_ZPB1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda IRQ_SCRATCH_ZPREG
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lda IRQ_SCRATCH_ZPWORD1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda IRQ_SCRATCH_ZPWORD1+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda IRQ_SCRATCH_ZPWORD2
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda IRQ_SCRATCH_ZPWORD2+1
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
ldx IRQ_X_REG
|
||||
rts
|
||||
|
||||
IRQ_X_REG .byte 0
|
||||
IRQ_SCRATCH_ZPB1 .byte 0
|
||||
IRQ_SCRATCH_ZPREG .byte 0
|
||||
IRQ_SCRATCH_ZPWORD1 .word 0
|
||||
IRQ_SCRATCH_ZPWORD2 .word 0
|
||||
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub restore_irq() clobbers(A) {
|
||||
%asm {{
|
||||
sei
|
||||
lda #<c64.IRQDFRT
|
||||
sta c64.CINV
|
||||
lda #>c64.IRQDFRT
|
||||
sta c64.CINV+1
|
||||
lda #0
|
||||
sta c64.IREQMASK ; disable raster irq
|
||||
lda #%10000001
|
||||
sta c64.CIA1ICR ; restore CIA1 irq
|
||||
cli
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0, ubyte useKernal @Pc) clobbers(A) {
|
||||
%asm {{
|
||||
sta _modified+1
|
||||
sty _modified+2
|
||||
lda #0
|
||||
adc #0
|
||||
sta set_irq._use_kernal
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
sei
|
||||
jsr _setup_raster_irq
|
||||
lda #<_raster_irq_handler
|
||||
sta c64.CINV
|
||||
lda #>_raster_irq_handler
|
||||
sta c64.CINV+1
|
||||
cli
|
||||
rts
|
||||
|
||||
_raster_irq_handler
|
||||
jsr set_irq._irq_handler_init
|
||||
_modified jsr $ffff ; modified
|
||||
jsr set_irq._irq_handler_end
|
||||
lda #$ff
|
||||
sta c64.VICIRQ ; acknowledge raster irq
|
||||
lda set_irq._use_kernal
|
||||
bne +
|
||||
; end irq processing - don't use kernal's irq handling
|
||||
pla
|
||||
tay
|
||||
pla
|
||||
tax
|
||||
pla
|
||||
rti
|
||||
+ jmp c64.IRQDFRT ; continue with kernal irq routine
|
||||
|
||||
_setup_raster_irq
|
||||
pha
|
||||
lda #%01111111
|
||||
sta c64.CIA1ICR ; "switch off" interrupts signals from cia-1
|
||||
sta c64.CIA2ICR ; "switch off" interrupts signals from cia-2
|
||||
and c64.SCROLY
|
||||
sta c64.SCROLY ; clear most significant bit of raster position
|
||||
lda c64.CIA1ICR ; ack previous irq
|
||||
lda c64.CIA2ICR ; ack previous irq
|
||||
pla
|
||||
sta c64.RASTER ; set the raster line number where interrupt should occur
|
||||
cpy #0
|
||||
beq +
|
||||
lda c64.SCROLY
|
||||
ora #%10000000
|
||||
sta c64.SCROLY ; set most significant bit of raster position
|
||||
+ lda #%00000001
|
||||
sta c64.IREQMASK ;enable raster interrupt signals from vic
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
c128 {
|
||||
; ---- C128 specific registers ----
|
||||
|
||||
&ubyte VM1 = $0A2C ; shadow for VUC $d018 in text mode
|
||||
&ubyte VM2 = $0A2D ; shadow for VIC $d018 in bitmap screen mode
|
||||
&ubyte VM3 = $0A2E ; starting page for VDC screen mem
|
||||
&ubyte VM4 = $0A2F ; starting page for VDC attribute mem
|
||||
|
||||
|
||||
; ---- C128 specific system utility routines: ----
|
||||
|
||||
asmsub init_system() {
|
||||
; Initializes the machine to a sane starting state.
|
||||
; Called automatically by the loader program logic.
|
||||
; This means that the BASIC, KERNAL and CHARGEN ROMs are banked in,
|
||||
; the VIC, SID and CIA chips are reset, screen is cleared, and the default IRQ is set.
|
||||
; Also a different color scheme is chosen to identify ourselves a little.
|
||||
; Uppercase charset is activated, and all three registers set to 0, status flags cleared.
|
||||
%asm {{
|
||||
sei
|
||||
cld
|
||||
;;lda #%00101111 ; TODO c128 ram and rom bank selection how?
|
||||
;;sta $00
|
||||
;;lda #%00100111
|
||||
;;sta $01
|
||||
jsr c64.IOINIT
|
||||
jsr c64.RESTOR
|
||||
jsr c64.CINT
|
||||
lda #6
|
||||
sta c64.EXTCOL
|
||||
lda #7
|
||||
sta c64.COLOR
|
||||
lda #0
|
||||
sta c64.BGCOL0
|
||||
jsr c64.disable_runstop_and_charsetswitch
|
||||
clc
|
||||
clv
|
||||
cli
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub init_system_phase2() {
|
||||
%asm {{
|
||||
rts ; no phase 2 steps on the C128
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub disable_basic() clobbers(A) {
|
||||
%asm {{
|
||||
lda $0a04 ; disable BASIC shadow registers
|
||||
and #$fe
|
||||
sta $0a04
|
||||
|
||||
lda #$01 ; disable BASIC IRQ service routine
|
||||
sta $12fd
|
||||
|
||||
lda #$ff ; disable screen editor IRQ setup
|
||||
sta $d8
|
||||
|
||||
lda #$b7 ; skip programmable function key check
|
||||
sta $033c
|
||||
|
||||
lda #$0e ; bank out BASIC ROM
|
||||
sta $ff00
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
; ---- end of C128 specific system utility routines ----
|
||||
|
||||
}
|
||||
|
||||
sys {
|
||||
; ------- lowlevel system routines --------
|
||||
|
||||
const ubyte target = 128 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16.
|
||||
|
||||
|
||||
asmsub reset_system() {
|
||||
; Soft-reset the system back to initial power-on Basic prompt.
|
||||
%asm {{
|
||||
sei
|
||||
;lda #14
|
||||
;sta $01 ; bank the kernal in TODO c128 how to do this?
|
||||
jmp (c64.RESET_VEC)
|
||||
}}
|
||||
}
|
||||
|
||||
sub wait(uword jiffies) {
|
||||
; --- wait approximately the given number of jiffies (1/60th seconds)
|
||||
; note: the system irq handler has to be active for this to work as it depends on the system jiffy clock
|
||||
repeat jiffies {
|
||||
ubyte jiff = lsb(c64.RDTIM16())
|
||||
while jiff==lsb(c64.RDTIM16()) {
|
||||
; wait until 1 jiffy has passed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
asmsub waitvsync() clobbers(A) {
|
||||
; --- busy wait till the next vsync has occurred (approximately), without depending on custom irq handling.
|
||||
; note: a more accurate way to wait for vsync is to set up a vsync irq handler instead.
|
||||
%asm {{
|
||||
- bit c64.SCROLY
|
||||
bpl -
|
||||
- bit c64.SCROLY
|
||||
bmi -
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub waitrastborder() {
|
||||
; --- busy wait till the raster position has reached the bottom screen border (approximately)
|
||||
; note: a more accurate way to do this is by using a raster irq handler instead.
|
||||
%asm {{
|
||||
- bit c64.SCROLY
|
||||
bpl -
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
|
||||
%asm {{
|
||||
ldx cx16.r0
|
||||
stx P8ZP_SCRATCH_W1 ; source in ZP
|
||||
ldx cx16.r0+1
|
||||
stx P8ZP_SCRATCH_W1+1
|
||||
ldx cx16.r1
|
||||
stx P8ZP_SCRATCH_W2 ; target in ZP
|
||||
ldx cx16.r1+1
|
||||
stx P8ZP_SCRATCH_W2+1
|
||||
cpy #0
|
||||
bne _longcopy
|
||||
|
||||
; copy <= 255 bytes
|
||||
tay
|
||||
bne _copyshort
|
||||
rts ; nothing to copy
|
||||
|
||||
_copyshort
|
||||
; decrease source and target pointers so we can simply index by Y
|
||||
lda P8ZP_SCRATCH_W1
|
||||
bne +
|
||||
dec P8ZP_SCRATCH_W1+1
|
||||
+ dec P8ZP_SCRATCH_W1
|
||||
lda P8ZP_SCRATCH_W2
|
||||
bne +
|
||||
dec P8ZP_SCRATCH_W2+1
|
||||
+ dec P8ZP_SCRATCH_W2
|
||||
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
dey
|
||||
bne -
|
||||
rts
|
||||
|
||||
_longcopy
|
||||
sta P8ZP_SCRATCH_B1 ; lsb(count) = remainder in last page
|
||||
tya
|
||||
tax ; x = num pages (1+)
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
iny
|
||||
bne -
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
inc P8ZP_SCRATCH_W2+1
|
||||
dex
|
||||
bne -
|
||||
ldy P8ZP_SCRATCH_B1
|
||||
bne _copyshort
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memset(uword mem @R0, uword numbytes @R1, ubyte value @A) clobbers(A,X,Y) {
|
||||
%asm {{
|
||||
ldy cx16.r0
|
||||
sty P8ZP_SCRATCH_W1
|
||||
ldy cx16.r0+1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldx cx16.r1
|
||||
ldy cx16.r1+1
|
||||
jmp prog8_lib.memset
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memsetw(uword mem @R0, uword numwords @R1, uword value @AY) clobbers(A,X,Y) {
|
||||
%asm {{
|
||||
ldx cx16.r0
|
||||
stx P8ZP_SCRATCH_W1
|
||||
ldx cx16.r0+1
|
||||
stx P8ZP_SCRATCH_W1+1
|
||||
ldx cx16.r1
|
||||
stx P8ZP_SCRATCH_W2
|
||||
ldx cx16.r1+1
|
||||
stx P8ZP_SCRATCH_W2+1
|
||||
jmp prog8_lib.memsetw
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub read_flags() -> ubyte @A {
|
||||
%asm {{
|
||||
php
|
||||
pla
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub clear_carry() {
|
||||
%asm {{
|
||||
clc
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub set_carry() {
|
||||
%asm {{
|
||||
sec
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub clear_irqd() {
|
||||
%asm {{
|
||||
cli
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub set_irqd() {
|
||||
%asm {{
|
||||
sei
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub exit(ubyte returnvalue @A) {
|
||||
; -- immediately exit the program with a return code in the A register
|
||||
%asm {{
|
||||
jsr c64.CLRCHN ; reset i/o channels
|
||||
ldx prog8_lib.orig_stackpointer
|
||||
txs
|
||||
rts ; return to original caller
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub progend() -> uword @AY {
|
||||
%asm {{
|
||||
lda #<prog8_program_end
|
||||
ldy #>prog8_program_end
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
cx16 {
|
||||
|
||||
; the sixteen virtual 16-bit registers that the CX16 has defined in the zeropage
|
||||
; they are simulated on the C128 as well but their location in memory is different
|
||||
; (because there's no room for them in the zeropage)
|
||||
; $1300-$1bff is unused RAM on C128. We'll use $1a00-$1bff as the lo/hi evalstack.
|
||||
; the virtual registers are allocated at the bottom of the eval-stack (should be ample space unless
|
||||
; you're doing insane nesting of expressions...)
|
||||
&uword r0 = $1b00
|
||||
&uword r1 = $1b02
|
||||
&uword r2 = $1b04
|
||||
&uword r3 = $1b06
|
||||
&uword r4 = $1b08
|
||||
&uword r5 = $1b0a
|
||||
&uword r6 = $1b0c
|
||||
&uword r7 = $1b0e
|
||||
&uword r8 = $1b10
|
||||
&uword r9 = $1b12
|
||||
&uword r10 = $1b14
|
||||
&uword r11 = $1b16
|
||||
&uword r12 = $1b18
|
||||
&uword r13 = $1b1a
|
||||
&uword r14 = $1b1c
|
||||
&uword r15 = $1b1e
|
||||
|
||||
&word r0s = $1b00
|
||||
&word r1s = $1b02
|
||||
&word r2s = $1b04
|
||||
&word r3s = $1b06
|
||||
&word r4s = $1b08
|
||||
&word r5s = $1b0a
|
||||
&word r6s = $1b0c
|
||||
&word r7s = $1b0e
|
||||
&word r8s = $1b10
|
||||
&word r9s = $1b12
|
||||
&word r10s = $1b14
|
||||
&word r11s = $1b16
|
||||
&word r12s = $1b18
|
||||
&word r13s = $1b1a
|
||||
&word r14s = $1b1c
|
||||
&word r15s = $1b1e
|
||||
|
||||
&ubyte r0L = $1b00
|
||||
&ubyte r1L = $1b02
|
||||
&ubyte r2L = $1b04
|
||||
&ubyte r3L = $1b06
|
||||
&ubyte r4L = $1b08
|
||||
&ubyte r5L = $1b0a
|
||||
&ubyte r6L = $1b0c
|
||||
&ubyte r7L = $1b0e
|
||||
&ubyte r8L = $1b10
|
||||
&ubyte r9L = $1b12
|
||||
&ubyte r10L = $1b14
|
||||
&ubyte r11L = $1b16
|
||||
&ubyte r12L = $1b18
|
||||
&ubyte r13L = $1b1a
|
||||
&ubyte r14L = $1b1c
|
||||
&ubyte r15L = $1b1e
|
||||
|
||||
&ubyte r0H = $1b01
|
||||
&ubyte r1H = $1b03
|
||||
&ubyte r2H = $1b05
|
||||
&ubyte r3H = $1b07
|
||||
&ubyte r4H = $1b09
|
||||
&ubyte r5H = $1b0b
|
||||
&ubyte r6H = $1b0d
|
||||
&ubyte r7H = $1b0f
|
||||
&ubyte r8H = $1b11
|
||||
&ubyte r9H = $1b13
|
||||
&ubyte r10H = $1b15
|
||||
&ubyte r11H = $1b17
|
||||
&ubyte r12H = $1b19
|
||||
&ubyte r13H = $1b1b
|
||||
&ubyte r14H = $1b1d
|
||||
&ubyte r15H = $1b1f
|
||||
|
||||
&byte r0sL = $1b00
|
||||
&byte r1sL = $1b02
|
||||
&byte r2sL = $1b04
|
||||
&byte r3sL = $1b06
|
||||
&byte r4sL = $1b08
|
||||
&byte r5sL = $1b0a
|
||||
&byte r6sL = $1b0c
|
||||
&byte r7sL = $1b0e
|
||||
&byte r8sL = $1b10
|
||||
&byte r9sL = $1b12
|
||||
&byte r10sL = $1b14
|
||||
&byte r11sL = $1b16
|
||||
&byte r12sL = $1b18
|
||||
&byte r13sL = $1b1a
|
||||
&byte r14sL = $1b1c
|
||||
&byte r15sL = $1b1e
|
||||
|
||||
&byte r0sH = $1b01
|
||||
&byte r1sH = $1b03
|
||||
&byte r2sH = $1b05
|
||||
&byte r3sH = $1b07
|
||||
&byte r4sH = $1b09
|
||||
&byte r5sH = $1b0b
|
||||
&byte r6sH = $1b0d
|
||||
&byte r7sH = $1b0f
|
||||
&byte r8sH = $1b11
|
||||
&byte r9sH = $1b13
|
||||
&byte r10sH = $1b15
|
||||
&byte r11sH = $1b17
|
||||
&byte r12sH = $1b19
|
||||
&byte r13sH = $1b1b
|
||||
&byte r14sH = $1b1d
|
||||
&byte r15sH = $1b1f
|
||||
}
|
619
compiler/res/prog8lib/c128/textio.p8
Normal file
619
compiler/res/prog8lib/c128/textio.p8
Normal file
@ -0,0 +1,619 @@
|
||||
; Prog8 definitions for the Text I/O and Screen routines for the Commodore-64
|
||||
;
|
||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
%import syslib
|
||||
%import conv
|
||||
|
||||
|
||||
txt {
|
||||
|
||||
const ubyte DEFAULT_WIDTH = 40
|
||||
const ubyte DEFAULT_HEIGHT = 25
|
||||
|
||||
|
||||
sub clear_screen() {
|
||||
txt.chrout(147)
|
||||
}
|
||||
|
||||
sub home() {
|
||||
txt.chrout(19)
|
||||
}
|
||||
|
||||
sub nl() {
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
sub spc() {
|
||||
txt.chrout(' ')
|
||||
}
|
||||
|
||||
asmsub column(ubyte col @A) clobbers(A, X, Y) {
|
||||
; ---- set the cursor on the given column (starting with 0) on the current line
|
||||
%asm {{
|
||||
sec
|
||||
jsr c64.PLOT
|
||||
tay
|
||||
clc
|
||||
jmp c64.PLOT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
|
||||
; ---- fill the character screen with the given fill character and character color.
|
||||
; (assumes screen and color matrix are at their default addresses)
|
||||
|
||||
%asm {{
|
||||
pha
|
||||
tya
|
||||
jsr clear_screencolors
|
||||
pla
|
||||
jsr clear_screenchars
|
||||
rts
|
||||
}}
|
||||
|
||||
}
|
||||
|
||||
asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
|
||||
; ---- clear the character screen with the given fill character (leaves colors)
|
||||
; (assumes screen matrix is at the default address)
|
||||
%asm {{
|
||||
ldy #250
|
||||
- sta c64.Screen+250*0-1,y
|
||||
sta c64.Screen+250*1-1,y
|
||||
sta c64.Screen+250*2-1,y
|
||||
sta c64.Screen+250*3-1,y
|
||||
dey
|
||||
bne -
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub clear_screencolors (ubyte color @ A) clobbers(Y) {
|
||||
; ---- clear the character screen colors with the given color (leaves characters).
|
||||
; (assumes color matrix is at the default address)
|
||||
%asm {{
|
||||
ldy #250
|
||||
- sta c64.Colors+250*0-1,y
|
||||
sta c64.Colors+250*1-1,y
|
||||
sta c64.Colors+250*2-1,y
|
||||
sta c64.Colors+250*3-1,y
|
||||
dey
|
||||
bne -
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub color (ubyte txtcol) {
|
||||
c64.COLOR = txtcol
|
||||
}
|
||||
|
||||
sub lowercase() {
|
||||
c64.VMCSB |= 2
|
||||
c128.VM1 |= 2
|
||||
}
|
||||
|
||||
sub uppercase() {
|
||||
c64.VMCSB &= ~2
|
||||
c128.VM1 &= ~2
|
||||
}
|
||||
|
||||
asmsub scroll_left (ubyte alsocolors @ Pc) clobbers(A, Y) {
|
||||
; ---- scroll the whole screen 1 character to the left
|
||||
; contents of the rightmost column are unchanged, you should clear/refill this yourself
|
||||
; Carry flag determines if screen color data must be scrolled too
|
||||
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
bcc _scroll_screen
|
||||
|
||||
+ ; scroll the screen and the color memory
|
||||
ldx #0
|
||||
ldy #38
|
||||
-
|
||||
.for row=0, row<=24, row+=1
|
||||
lda c64.Screen + 40*row + 1,x
|
||||
sta c64.Screen + 40*row + 0,x
|
||||
lda c64.Colors + 40*row + 1,x
|
||||
sta c64.Colors + 40*row + 0,x
|
||||
.next
|
||||
inx
|
||||
dey
|
||||
bpl -
|
||||
rts
|
||||
|
||||
_scroll_screen ; scroll only the screen memory
|
||||
ldx #0
|
||||
ldy #38
|
||||
-
|
||||
.for row=0, row<=24, row+=1
|
||||
lda c64.Screen + 40*row + 1,x
|
||||
sta c64.Screen + 40*row + 0,x
|
||||
.next
|
||||
inx
|
||||
dey
|
||||
bpl -
|
||||
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub scroll_right (ubyte alsocolors @ Pc) clobbers(A) {
|
||||
; ---- scroll the whole screen 1 character to the right
|
||||
; contents of the leftmost column are unchanged, you should clear/refill this yourself
|
||||
; Carry flag determines if screen color data must be scrolled too
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
bcc _scroll_screen
|
||||
|
||||
+ ; scroll the screen and the color memory
|
||||
ldx #38
|
||||
-
|
||||
.for row=0, row<=24, row+=1
|
||||
lda c64.Screen + 40*row + 0,x
|
||||
sta c64.Screen + 40*row + 1,x
|
||||
lda c64.Colors + 40*row + 0,x
|
||||
sta c64.Colors + 40*row + 1,x
|
||||
.next
|
||||
dex
|
||||
bpl -
|
||||
rts
|
||||
|
||||
_scroll_screen ; scroll only the screen memory
|
||||
ldx #38
|
||||
-
|
||||
.for row=0, row<=24, row+=1
|
||||
lda c64.Screen + 40*row + 0,x
|
||||
sta c64.Screen + 40*row + 1,x
|
||||
.next
|
||||
dex
|
||||
bpl -
|
||||
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub scroll_up (ubyte alsocolors @ Pc) clobbers(A) {
|
||||
; ---- scroll the whole screen 1 character up
|
||||
; contents of the bottom row are unchanged, you should refill/clear this yourself
|
||||
; Carry flag determines if screen color data must be scrolled too
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
bcc _scroll_screen
|
||||
|
||||
+ ; scroll the screen and the color memory
|
||||
ldx #39
|
||||
-
|
||||
.for row=1, row<=24, row+=1
|
||||
lda c64.Screen + 40*row,x
|
||||
sta c64.Screen + 40*(row-1),x
|
||||
lda c64.Colors + 40*row,x
|
||||
sta c64.Colors + 40*(row-1),x
|
||||
.next
|
||||
dex
|
||||
bpl -
|
||||
rts
|
||||
|
||||
_scroll_screen ; scroll only the screen memory
|
||||
ldx #39
|
||||
-
|
||||
.for row=1, row<=24, row+=1
|
||||
lda c64.Screen + 40*row,x
|
||||
sta c64.Screen + 40*(row-1),x
|
||||
.next
|
||||
dex
|
||||
bpl -
|
||||
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub scroll_down (ubyte alsocolors @ Pc) clobbers(A) {
|
||||
; ---- scroll the whole screen 1 character down
|
||||
; contents of the top row are unchanged, you should refill/clear this yourself
|
||||
; Carry flag determines if screen color data must be scrolled too
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
bcc _scroll_screen
|
||||
|
||||
+ ; scroll the screen and the color memory
|
||||
ldx #39
|
||||
-
|
||||
.for row=23, row>=0, row-=1
|
||||
lda c64.Colors + 40*row,x
|
||||
sta c64.Colors + 40*(row+1),x
|
||||
lda c64.Screen + 40*row,x
|
||||
sta c64.Screen + 40*(row+1),x
|
||||
.next
|
||||
dex
|
||||
bpl -
|
||||
rts
|
||||
|
||||
_scroll_screen ; scroll only the screen memory
|
||||
ldx #39
|
||||
-
|
||||
.for row=23, row>=0, row-=1
|
||||
lda c64.Screen + 40*row,x
|
||||
sta c64.Screen + 40*(row+1),x
|
||||
.next
|
||||
dex
|
||||
bpl -
|
||||
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
romsub $FFD2 = chrout(ubyte char @ A) ; for consistency. You can also use c64.CHROUT directly ofcourse.
|
||||
|
||||
asmsub print (str text @ AY) clobbers(A,Y) {
|
||||
; ---- print null terminated string from A/Y
|
||||
; note: the compiler contains an optimization that will replace
|
||||
; a call to this subroutine with a string argument of just one char,
|
||||
; by just one call to c64.CHROUT of that single char.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_B1
|
||||
sty P8ZP_SCRATCH_REG
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_B1),y
|
||||
beq +
|
||||
jsr c64.CHROUT
|
||||
iny
|
||||
bne -
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ub0 (ubyte value @ A) clobbers(A,Y) {
|
||||
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr conv.ubyte2decimal
|
||||
pha
|
||||
tya
|
||||
jsr c64.CHROUT
|
||||
pla
|
||||
jsr c64.CHROUT
|
||||
txa
|
||||
jsr c64.CHROUT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ub (ubyte value @ A) clobbers(A,Y) {
|
||||
; ---- print the ubyte in A in decimal form, without left padding 0s
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr conv.ubyte2decimal
|
||||
_print_byte_digits
|
||||
pha
|
||||
cpy #'0'
|
||||
beq +
|
||||
tya
|
||||
jsr c64.CHROUT
|
||||
pla
|
||||
jsr c64.CHROUT
|
||||
jmp _ones
|
||||
+ pla
|
||||
cmp #'0'
|
||||
beq _ones
|
||||
jsr c64.CHROUT
|
||||
_ones txa
|
||||
jsr c64.CHROUT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_b (byte value @ A) clobbers(A,Y) {
|
||||
; ---- print the byte in A in decimal form, without left padding 0s
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
pha
|
||||
cmp #0
|
||||
bpl +
|
||||
lda #'-'
|
||||
jsr c64.CHROUT
|
||||
+ pla
|
||||
jsr conv.byte2decimal
|
||||
jmp print_ub._print_byte_digits
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ubhex (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well)
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
bcc +
|
||||
pha
|
||||
lda #'$'
|
||||
jsr c64.CHROUT
|
||||
pla
|
||||
+ jsr conv.ubyte2hex
|
||||
jsr c64.CHROUT
|
||||
tya
|
||||
jsr c64.CHROUT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ubbin (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well)
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
sta P8ZP_SCRATCH_B1
|
||||
bcc +
|
||||
lda #'%'
|
||||
jsr c64.CHROUT
|
||||
+ ldy #8
|
||||
- lda #'0'
|
||||
asl P8ZP_SCRATCH_B1
|
||||
bcc +
|
||||
lda #'1'
|
||||
+ jsr c64.CHROUT
|
||||
dey
|
||||
bne -
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uwbin (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
|
||||
%asm {{
|
||||
pha
|
||||
tya
|
||||
jsr print_ubbin
|
||||
pla
|
||||
clc
|
||||
jmp print_ubbin
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uwhex (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
; ---- print the uword in A/Y in hexadecimal form (4 digits)
|
||||
; (if Carry is set, a radix prefix '$' is printed as well)
|
||||
%asm {{
|
||||
pha
|
||||
tya
|
||||
jsr print_ubhex
|
||||
pla
|
||||
clc
|
||||
jmp print_ubhex
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uw0 (uword value @ AY) clobbers(A,Y) {
|
||||
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr conv.uword2decimal
|
||||
ldy #0
|
||||
- lda conv.uword2decimal.decTenThousands,y
|
||||
beq +
|
||||
jsr c64.CHROUT
|
||||
iny
|
||||
bne -
|
||||
+ ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uw (uword value @ AY) clobbers(A,Y) {
|
||||
; ---- print the uword in A/Y in decimal form, without left padding 0s
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr conv.uword2decimal
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
ldy #0
|
||||
- lda conv.uword2decimal.decTenThousands,y
|
||||
beq _allzero
|
||||
cmp #'0'
|
||||
bne _gotdigit
|
||||
iny
|
||||
bne -
|
||||
|
||||
_gotdigit
|
||||
jsr c64.CHROUT
|
||||
iny
|
||||
lda conv.uword2decimal.decTenThousands,y
|
||||
bne _gotdigit
|
||||
rts
|
||||
_allzero
|
||||
lda #'0'
|
||||
jmp c64.CHROUT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_w (word value @ AY) clobbers(A,Y) {
|
||||
; ---- print the (signed) word in A/Y in decimal form, without left padding 0's
|
||||
%asm {{
|
||||
cpy #0
|
||||
bpl +
|
||||
pha
|
||||
lda #'-'
|
||||
jsr c64.CHROUT
|
||||
tya
|
||||
eor #255
|
||||
tay
|
||||
pla
|
||||
eor #255
|
||||
clc
|
||||
adc #1
|
||||
bcc +
|
||||
iny
|
||||
+ jmp print_uw
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
|
||||
; ---- Input a string (max. 80 chars) from the keyboard. Returns length in Y. (string is terminated with a 0 byte as well)
|
||||
; It assumes the keyboard is selected as I/O channel!
|
||||
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0 ; char counter = 0
|
||||
- jsr c64.CHRIN
|
||||
cmp #$0d ; return (ascii 13) pressed?
|
||||
beq + ; yes, end.
|
||||
sta (P8ZP_SCRATCH_W1),y ; else store char in buffer
|
||||
iny
|
||||
bne -
|
||||
+ lda #0
|
||||
sta (P8ZP_SCRATCH_W1),y ; finish string with 0 byte
|
||||
rts
|
||||
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A, Y) {
|
||||
; ---- sets the character in the screen matrix at the given position
|
||||
%asm {{
|
||||
pha
|
||||
tya
|
||||
asl a
|
||||
tay
|
||||
lda _screenrows+1,y
|
||||
sta _mod+2
|
||||
txa
|
||||
clc
|
||||
adc _screenrows,y
|
||||
sta _mod+1
|
||||
bcc +
|
||||
inc _mod+2
|
||||
+ pla
|
||||
_mod sta $ffff ; modified
|
||||
rts
|
||||
|
||||
_screenrows .word $0400 + range(0, 1000, 40)
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub getchr (ubyte col @A, ubyte row @Y) clobbers(Y) -> ubyte @ A {
|
||||
; ---- get the character in the screen matrix at the given location
|
||||
%asm {{
|
||||
pha
|
||||
tya
|
||||
asl a
|
||||
tay
|
||||
lda setchr._screenrows+1,y
|
||||
sta _mod+2
|
||||
pla
|
||||
clc
|
||||
adc setchr._screenrows,y
|
||||
sta _mod+1
|
||||
bcc _mod
|
||||
inc _mod+2
|
||||
_mod lda $ffff ; modified
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub setclr (ubyte col @X, ubyte row @Y, ubyte color @A) clobbers(A, Y) {
|
||||
; ---- set the color in A on the screen matrix at the given position
|
||||
%asm {{
|
||||
pha
|
||||
tya
|
||||
asl a
|
||||
tay
|
||||
lda _colorrows+1,y
|
||||
sta _mod+2
|
||||
txa
|
||||
clc
|
||||
adc _colorrows,y
|
||||
sta _mod+1
|
||||
bcc +
|
||||
inc _mod+2
|
||||
+ pla
|
||||
_mod sta $ffff ; modified
|
||||
rts
|
||||
|
||||
_colorrows .word $d800 + range(0, 1000, 40)
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub getclr (ubyte col @A, ubyte row @Y) clobbers(Y) -> ubyte @ A {
|
||||
; ---- get the color in the screen color matrix at the given location
|
||||
%asm {{
|
||||
pha
|
||||
tya
|
||||
asl a
|
||||
tay
|
||||
lda setclr._colorrows+1,y
|
||||
sta _mod+2
|
||||
pla
|
||||
clc
|
||||
adc setclr._colorrows,y
|
||||
sta _mod+1
|
||||
bcc _mod
|
||||
inc _mod+2
|
||||
_mod lda $ffff ; modified
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
|
||||
; ---- set char+color at the given position on the screen
|
||||
%asm {{
|
||||
lda row
|
||||
asl a
|
||||
tay
|
||||
lda setchr._screenrows+1,y
|
||||
sta _charmod+2
|
||||
adc #$d4
|
||||
sta _colormod+2
|
||||
lda setchr._screenrows,y
|
||||
clc
|
||||
adc column
|
||||
sta _charmod+1
|
||||
sta _colormod+1
|
||||
bcc +
|
||||
inc _charmod+2
|
||||
inc _colormod+2
|
||||
+ lda char
|
||||
_charmod sta $ffff ; modified
|
||||
lda charcolor
|
||||
_colormod sta $ffff ; modified
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub plot (ubyte col @ Y, ubyte row @ A) clobbers(A) {
|
||||
; ---- safe wrapper around PLOT kernal routine, to save the X register.
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
tax
|
||||
clc
|
||||
jsr c64.PLOT
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub width() clobbers(X,Y) -> ubyte @A {
|
||||
; -- returns the text screen width (number of columns)
|
||||
%asm {{
|
||||
jsr c64.SCREEN
|
||||
txa
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub height() clobbers(X, Y) -> ubyte @A {
|
||||
; -- returns the text screen height (number of rows)
|
||||
%asm {{
|
||||
jsr c64.SCREEN
|
||||
tya
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
@ -12,7 +12,7 @@ floats {
|
||||
const float PI = 3.141592653589793
|
||||
const float TWOPI = 6.283185307179586
|
||||
|
||||
ubyte[5] tempvar_swap_float ; used for some swap() operations
|
||||
float tempvar_swap_float ; used for some swap() operations
|
||||
|
||||
; ---- C64 basic and kernal ROM float constants and functions ----
|
||||
|
||||
|
@ -33,6 +33,9 @@ graphics {
|
||||
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
|
||||
; Bresenham algorithm.
|
||||
; This code special-cases various quadrant loops to allow simple ++ and -- operations.
|
||||
; TODO implement this as optimized assembly, for instance https://github.com/EgonOlsen71/bresenham/blob/main/src/asm/graphics.asm ??
|
||||
; or from here https://retro64.altervista.org/blog/an-introduction-to-vector-based-graphics-the-commodore-64-rotating-simple-3d-objects/
|
||||
|
||||
if y1>y2 {
|
||||
; make sure dy is always positive to have only 4 instead of 8 special cases
|
||||
swap(x1, x2)
|
||||
|
@ -15,7 +15,9 @@ c64 {
|
||||
|
||||
&ubyte COLOR = $0286 ; cursor color
|
||||
&ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address)
|
||||
&uword CINV = $0314 ; IRQ vector
|
||||
&uword CINV = $0314 ; IRQ vector (in ram)
|
||||
&uword CBINV = $0316 ; BRK vector (in ram)
|
||||
&uword NMINV = $0318 ; NMI vector (in ram)
|
||||
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
|
||||
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
|
||||
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
|
||||
@ -472,7 +474,7 @@ _setup_raster_irq
|
||||
sys {
|
||||
; ------- lowlevel system routines --------
|
||||
|
||||
const ubyte target = 64 ; compilation target specifier. 64 = C64, 16 = CommanderX16.
|
||||
const ubyte target = 64 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16.
|
||||
|
||||
|
||||
asmsub reset_system() {
|
||||
@ -597,62 +599,34 @@ _longcopy
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
inline asmsub rsave() {
|
||||
; save cpu status flag and all registers A, X, Y.
|
||||
; see http://6502.org/tutorials/register_preservation.html
|
||||
%asm {{
|
||||
php
|
||||
sta P8ZP_SCRATCH_REG
|
||||
pha
|
||||
txa
|
||||
pha
|
||||
tya
|
||||
pha
|
||||
lda P8ZP_SCRATCH_REG
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub rrestore() {
|
||||
; restore all registers and cpu status flag
|
||||
%asm {{
|
||||
pla
|
||||
tay
|
||||
pla
|
||||
tax
|
||||
pla
|
||||
plp
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub read_flags() -> ubyte @A {
|
||||
%asm {{
|
||||
php
|
||||
pla
|
||||
php
|
||||
pla
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub clear_carry() {
|
||||
%asm {{
|
||||
clc
|
||||
clc
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub set_carry() {
|
||||
%asm {{
|
||||
sec
|
||||
sec
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub clear_irqd() {
|
||||
%asm {{
|
||||
cli
|
||||
cli
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub set_irqd() {
|
||||
%asm {{
|
||||
sei
|
||||
sei
|
||||
}}
|
||||
}
|
||||
|
||||
@ -699,6 +673,23 @@ cx16 {
|
||||
&uword r14 = $cf1c
|
||||
&uword r15 = $cf1e
|
||||
|
||||
&word r0s = $cf00
|
||||
&word r1s = $cf02
|
||||
&word r2s = $cf04
|
||||
&word r3s = $cf06
|
||||
&word r4s = $cf08
|
||||
&word r5s = $cf0a
|
||||
&word r6s = $cf0c
|
||||
&word r7s = $cf0e
|
||||
&word r8s = $cf10
|
||||
&word r9s = $cf12
|
||||
&word r10s = $cf14
|
||||
&word r11s = $cf16
|
||||
&word r12s = $cf18
|
||||
&word r13s = $cf1a
|
||||
&word r14s = $cf1c
|
||||
&word r15s = $cf1e
|
||||
|
||||
&ubyte r0L = $cf00
|
||||
&ubyte r1L = $cf02
|
||||
&ubyte r2L = $cf04
|
||||
@ -732,4 +723,38 @@ cx16 {
|
||||
&ubyte r13H = $cf1b
|
||||
&ubyte r14H = $cf1d
|
||||
&ubyte r15H = $cf1f
|
||||
|
||||
&byte r0sL = $cf00
|
||||
&byte r1sL = $cf02
|
||||
&byte r2sL = $cf04
|
||||
&byte r3sL = $cf06
|
||||
&byte r4sL = $cf08
|
||||
&byte r5sL = $cf0a
|
||||
&byte r6sL = $cf0c
|
||||
&byte r7sL = $cf0e
|
||||
&byte r8sL = $cf10
|
||||
&byte r9sL = $cf12
|
||||
&byte r10sL = $cf14
|
||||
&byte r11sL = $cf16
|
||||
&byte r12sL = $cf18
|
||||
&byte r13sL = $cf1a
|
||||
&byte r14sL = $cf1c
|
||||
&byte r15sL = $cf1e
|
||||
|
||||
&byte r0sH = $cf01
|
||||
&byte r1sH = $cf03
|
||||
&byte r2sH = $cf05
|
||||
&byte r3sH = $cf07
|
||||
&byte r4sH = $cf09
|
||||
&byte r5sH = $cf0b
|
||||
&byte r6sH = $cf0d
|
||||
&byte r7sH = $cf0f
|
||||
&byte r8sH = $cf11
|
||||
&byte r9sH = $cf13
|
||||
&byte r10sH = $cf15
|
||||
&byte r11sH = $cf17
|
||||
&byte r12sH = $cf19
|
||||
&byte r13sH = $cf1b
|
||||
&byte r14sH = $cf1d
|
||||
&byte r15sH = $cf1f
|
||||
}
|
||||
|
@ -12,14 +12,14 @@ conv {
|
||||
asmsub str_ub0 (ubyte value @ A) clobbers(A,Y) {
|
||||
; ---- convert the ubyte in A in decimal string form, with left padding 0s (3 positions total)
|
||||
%asm {{
|
||||
phx
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr conv.ubyte2decimal
|
||||
sty string_out
|
||||
sta string_out+1
|
||||
stx string_out+2
|
||||
lda #0
|
||||
sta string_out+3
|
||||
plx
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
@ -27,7 +27,7 @@ asmsub str_ub0 (ubyte value @ A) clobbers(A,Y) {
|
||||
asmsub str_ub (ubyte value @ A) clobbers(A,Y) {
|
||||
; ---- convert the ubyte in A in decimal string form, without left padding 0s
|
||||
%asm {{
|
||||
phx
|
||||
stx P8ZP_SCRATCH_REG
|
||||
ldy #0
|
||||
sty P8ZP_SCRATCH_B1
|
||||
jsr conv.ubyte2decimal
|
||||
@ -53,7 +53,7 @@ _output_byte_digits
|
||||
iny
|
||||
lda #0
|
||||
sta string_out,y
|
||||
plx
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
@ -61,7 +61,7 @@ _output_byte_digits
|
||||
asmsub str_b (byte value @ A) clobbers(A,Y) {
|
||||
; ---- convert the byte in A in decimal string form, without left padding 0s
|
||||
%asm {{
|
||||
phx
|
||||
stx P8ZP_SCRATCH_REG
|
||||
ldy #0
|
||||
sty P8ZP_SCRATCH_B1
|
||||
cmp #0
|
||||
@ -149,7 +149,7 @@ asmsub str_uwhex (uword value @ AY) clobbers(A,Y) {
|
||||
asmsub str_uw0 (uword value @ AY) clobbers(A,Y) {
|
||||
; ---- convert the uword in A/Y in decimal string form, with left padding 0s (5 positions total)
|
||||
%asm {{
|
||||
phx
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr conv.uword2decimal
|
||||
ldy #0
|
||||
- lda conv.uword2decimal.decTenThousands,y
|
||||
@ -157,7 +157,7 @@ asmsub str_uw0 (uword value @ AY) clobbers(A,Y) {
|
||||
beq +
|
||||
iny
|
||||
bne -
|
||||
+ plx
|
||||
+ ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
@ -165,7 +165,7 @@ asmsub str_uw0 (uword value @ AY) clobbers(A,Y) {
|
||||
asmsub str_uw (uword value @ AY) clobbers(A,Y) {
|
||||
; ---- convert the uword in A/Y in decimal string form, without left padding 0s
|
||||
%asm {{
|
||||
phx
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr conv.uword2decimal
|
||||
ldx #0
|
||||
_output_digits
|
||||
@ -183,7 +183,7 @@ _gotdigit sta string_out,x
|
||||
bne _gotdigit
|
||||
_end lda #0
|
||||
sta string_out,x
|
||||
plx
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
|
||||
_allzero lda #'0'
|
||||
@ -198,7 +198,7 @@ asmsub str_w (word value @ AY) clobbers(A,Y) {
|
||||
%asm {{
|
||||
cpy #0
|
||||
bpl str_uw
|
||||
phx
|
||||
stx P8ZP_SCRATCH_REG
|
||||
pha
|
||||
lda #'-'
|
||||
sta string_out
|
||||
@ -516,7 +516,7 @@ asmsub uword2decimal (uword value @AY) -> ubyte @Y, ubyte @A, ubyte @X {
|
||||
|
||||
;Convert 16 bit Hex to Decimal (0-65535) Rev 2
|
||||
;By Omegamatrix Further optimizations by tepples
|
||||
; routine from http://forums.nesdev.com/viewtopic.php?f=2&t=11341&start=15
|
||||
; routine from https://forums.nesdev.org/viewtopic.php?f=2&t=11341&start=15
|
||||
|
||||
;HexToDec99
|
||||
; start in A
|
||||
|
37
compiler/res/prog8lib/cx16/cx16diskio.p8
Normal file
37
compiler/res/prog8lib/cx16/cx16diskio.p8
Normal file
@ -0,0 +1,37 @@
|
||||
; Cx16 specific disk drive I/O routines.
|
||||
;
|
||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
|
||||
%import diskio
|
||||
|
||||
cx16diskio {
|
||||
|
||||
; Use kernal LOAD routine to load the given program file in memory.
|
||||
; This is similar to Basic's LOAD "filename",drive / LOAD "filename",drive,1
|
||||
; If you don't give an address_override, the location in memory is taken from the 2-byte file header.
|
||||
; If you specify a custom address_override, the first 2 bytes in the file are ignored
|
||||
; and the rest is loaded at the given location in memory.
|
||||
; Returns the end load address+1 if successful or 0 if a load error occurred.
|
||||
; You can use the load_size() function to calcuate the size of the file that was loaded.
|
||||
sub load(ubyte drivenumber, uword filenameptr, ubyte bank, uword address_override) -> uword {
|
||||
cx16.rambank(bank)
|
||||
return diskio.load(drivenumber, filenameptr, address_override)
|
||||
}
|
||||
|
||||
; Use kernal LOAD routine to load the given file in memory.
|
||||
; INCLUDING the first 2 bytes in the file: no program header is assumed in the file.
|
||||
; This is different from Basic's LOAD instruction which always skips the first two bytes.
|
||||
; The load address is mandatory. Returns the number of bytes loaded.
|
||||
; Returns the end load address+1 if successful or 0 if a load error occurred.
|
||||
; You can use the load_size() function to calcuate the size of the file that was loaded.
|
||||
sub load_raw(ubyte drivenumber, uword filenameptr, ubyte bank, uword address) -> uword {
|
||||
cx16.rambank(bank)
|
||||
return diskio.load_raw(drivenumber, filenameptr, address)
|
||||
}
|
||||
|
||||
; For use directly after a load or load_raw call (don't mess with the ram bank yet):
|
||||
; Calculates the number of bytes loaded (files > 64Kb ar truncated to 16 bits)
|
||||
sub load_size(ubyte startbank, uword startaddress, uword endaddress) -> uword {
|
||||
return $2000 * (cx16.getrambank() - startbank) + endaddress - startaddress
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@ floats {
|
||||
const float PI = 3.141592653589793
|
||||
const float TWOPI = 6.283185307179586
|
||||
|
||||
ubyte[5] tempvar_swap_float ; used for some swap() operations
|
||||
float tempvar_swap_float ; used for some swap() operations
|
||||
|
||||
|
||||
; ---- ROM float functions ----
|
||||
|
@ -775,6 +775,7 @@ _done
|
||||
; -- Write some text at the given pixel position. The text string must be in screencode encoding (not petscii!).
|
||||
; You must also have called text_charset() first to select and prepare the character set to use.
|
||||
; NOTE: in monochrome (1bpp) screen modes, x position is currently constrained to multiples of 8 ! TODO allow per-pixel horizontal positioning
|
||||
; TODO draw whole horizontal spans using vera auto increment if possible, instead of per-character columns
|
||||
uword chardataptr
|
||||
when active_mode {
|
||||
1, 5 -> {
|
||||
@ -808,11 +809,8 @@ _done
|
||||
sta cx16.VERA_ADDR_L
|
||||
bcc +
|
||||
inc cx16.VERA_ADDR_M
|
||||
+ lda x
|
||||
clc
|
||||
adc #1
|
||||
sta x
|
||||
bcc +
|
||||
+ inc x
|
||||
bne +
|
||||
inc x+1
|
||||
+ dey
|
||||
bne -
|
||||
@ -826,7 +824,6 @@ _done
|
||||
chardataptr = charset_addr + (@(sctextptr) as uword)*8
|
||||
cx16.vaddr(charset_bank, chardataptr, 1, 1)
|
||||
repeat 8 {
|
||||
; TODO rewrite this inner loop fully in assembly
|
||||
position(x,y)
|
||||
y++
|
||||
%asm {{
|
||||
@ -855,7 +852,9 @@ _done
|
||||
while @(sctextptr) {
|
||||
chardataptr = charset_addr + (@(sctextptr) as uword)*8
|
||||
repeat 8 {
|
||||
; TODO rewrite this inner loop fully in assembly
|
||||
; TODO rewrite this inner loop partly in assembly
|
||||
; requires expanding the charbits to 2-bits per pixel (based on color)
|
||||
; also it's way more efficient to draw whole horizontal spans instead of per-character
|
||||
ubyte charbits = cx16.vpeek(charset_bank, chardataptr)
|
||||
repeat 8 {
|
||||
charbits <<= 1
|
||||
|
@ -11,7 +11,7 @@ c64 {
|
||||
|
||||
; STROUT --> use txt.print
|
||||
; CLEARSCR -> use txt.clear_screen
|
||||
; HOMECRSR -> use txt.plot
|
||||
; HOMECRSR -> use txt.home or txt.plot
|
||||
|
||||
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
|
||||
romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ)
|
||||
@ -90,12 +90,14 @@ cx16 {
|
||||
|
||||
; irq and hardware vectors:
|
||||
&uword CINV = $0314 ; IRQ vector (in ram)
|
||||
&uword CBINV = $0316 ; BRK vector (in ram)
|
||||
&uword NMINV = $0318 ; NMI vector (in ram)
|
||||
&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
|
||||
|
||||
|
||||
; the sixteen virtual 16-bit registers
|
||||
; the sixteen virtual 16-bit registers in both normal unsigned mode and signed mode (s)
|
||||
&uword r0 = $0002
|
||||
&uword r1 = $0004
|
||||
&uword r2 = $0006
|
||||
@ -113,6 +115,23 @@ cx16 {
|
||||
&uword r14 = $001e
|
||||
&uword r15 = $0020
|
||||
|
||||
&word r0s = $0002
|
||||
&word r1s = $0004
|
||||
&word r2s = $0006
|
||||
&word r3s = $0008
|
||||
&word r4s = $000a
|
||||
&word r5s = $000c
|
||||
&word r6s = $000e
|
||||
&word r7s = $0010
|
||||
&word r8s = $0012
|
||||
&word r9s = $0014
|
||||
&word r10s = $0016
|
||||
&word r11s = $0018
|
||||
&word r12s = $001a
|
||||
&word r13s = $001c
|
||||
&word r14s = $001e
|
||||
&word r15s = $0020
|
||||
|
||||
&ubyte r0L = $0002
|
||||
&ubyte r1L = $0004
|
||||
&ubyte r2L = $0006
|
||||
@ -147,6 +166,39 @@ cx16 {
|
||||
&ubyte r14H = $001f
|
||||
&ubyte r15H = $0021
|
||||
|
||||
&byte r0sL = $0002
|
||||
&byte r1sL = $0004
|
||||
&byte r2sL = $0006
|
||||
&byte r3sL = $0008
|
||||
&byte r4sL = $000a
|
||||
&byte r5sL = $000c
|
||||
&byte r6sL = $000e
|
||||
&byte r7sL = $0010
|
||||
&byte r8sL = $0012
|
||||
&byte r9sL = $0014
|
||||
&byte r10sL = $0016
|
||||
&byte r11sL = $0018
|
||||
&byte r12sL = $001a
|
||||
&byte r13sL = $001c
|
||||
&byte r14sL = $001e
|
||||
&byte r15sL = $0020
|
||||
|
||||
&byte r0sH = $0003
|
||||
&byte r1sH = $0005
|
||||
&byte r2sH = $0007
|
||||
&byte r3sH = $0009
|
||||
&byte r4sH = $000b
|
||||
&byte r5sH = $000d
|
||||
&byte r6sH = $000f
|
||||
&byte r7sH = $0011
|
||||
&byte r8sH = $0013
|
||||
&byte r9sH = $0015
|
||||
&byte r10sH = $0017
|
||||
&byte r11sH = $0019
|
||||
&byte r12sH = $001b
|
||||
&byte r13sH = $001d
|
||||
&byte r14sH = $001f
|
||||
&byte r15sH = $0021
|
||||
|
||||
; VERA registers
|
||||
|
||||
@ -262,7 +314,8 @@ romsub $ff56 = joystick_get(ubyte joynr @A) -> ubyte @A, ubyte @X, ubyte @Y
|
||||
romsub $ff4d = clock_set_date_time(uword yearmonth @R0, uword dayhours @R1, uword minsecs @R2, ubyte jiffies @R3) clobbers(A, X, Y)
|
||||
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) -> uword @R0, uword @R1, uword @R2, ubyte @R3 ; result registers see clock_set_date_time()
|
||||
|
||||
; TODO specify the correct clobbers for alle these functions below, we now assume all 3 regs are clobbered
|
||||
|
||||
; It's not documented what registers are clobbered, so we assume the worst for all following kernal routines...:
|
||||
|
||||
; high level graphics & fonts
|
||||
romsub $ff20 = GRAPH_init(uword vectors @R0) clobbers(A,X,Y)
|
||||
@ -317,20 +370,34 @@ romsub $fecc = monitor() clobbers(A,X,Y)
|
||||
|
||||
; ---- utilities -----
|
||||
|
||||
inline asmsub rombank(ubyte rombank @A) {
|
||||
inline asmsub rombank(ubyte bank @A) {
|
||||
; -- set the rom banks
|
||||
%asm {{
|
||||
sta $01 ; rom bank register (v39+, used to be cx16.d1prb $9f60 in v38)
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub rambank(ubyte rambank @A) {
|
||||
inline asmsub rambank(ubyte bank @A) {
|
||||
; -- set the ram bank
|
||||
%asm {{
|
||||
sta $00 ; ram bank register (v39+, used to be cx16.d1pra $9f61 in v38)
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub getrombank() -> ubyte @A {
|
||||
; -- get the current rom bank
|
||||
%asm {{
|
||||
lda $01 ; rom bank register (v39+, used to be cx16.d1prb $9f60 in v38)
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub getrambank() -> ubyte @A {
|
||||
; -- get the current ram bank
|
||||
%asm {{
|
||||
lda $00 ; ram bank register (v39+, used to be cx16.d1pra $9f61 in v38)
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub numbanks() -> ubyte @A {
|
||||
; -- uses MEMTOP's cx16 extension to query the number of available RAM banks. (each is 8 Kb)
|
||||
%asm {{
|
||||
@ -744,7 +811,7 @@ asmsub set_rasterline(uword line @AY) {
|
||||
sys {
|
||||
; ------- lowlevel system routines --------
|
||||
|
||||
const ubyte target = 16 ; compilation target specifier. 64 = C64, 16 = CommanderX16.
|
||||
const ubyte target = 16 ; compilation target specifier. 64 = C64, 128 = C128, 16 = CommanderX16.
|
||||
|
||||
|
||||
asmsub reset_system() {
|
||||
@ -809,27 +876,6 @@ sys {
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub rsave() {
|
||||
; save cpu status flag and all registers A, X, Y.
|
||||
; see http://6502.org/tutorials/register_preservation.html
|
||||
%asm {{
|
||||
php
|
||||
pha
|
||||
phy
|
||||
phx
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub rrestore() {
|
||||
; restore all registers and cpu status flag
|
||||
%asm {{
|
||||
plx
|
||||
ply
|
||||
pla
|
||||
plp
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub read_flags() -> ubyte @A {
|
||||
%asm {{
|
||||
php
|
||||
@ -839,25 +885,25 @@ sys {
|
||||
|
||||
inline asmsub clear_carry() {
|
||||
%asm {{
|
||||
clc
|
||||
clc
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub set_carry() {
|
||||
%asm {{
|
||||
sec
|
||||
sec
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub clear_irqd() {
|
||||
%asm {{
|
||||
cli
|
||||
cli
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub set_irqd() {
|
||||
%asm {{
|
||||
sei
|
||||
sei
|
||||
}}
|
||||
}
|
||||
|
||||
|
@ -565,9 +565,8 @@ asmsub print_w (word value @ AY) clobbers(A,Y) {
|
||||
tay
|
||||
pla
|
||||
eor #255
|
||||
clc
|
||||
adc #1
|
||||
bcc +
|
||||
ina
|
||||
bne +
|
||||
iny
|
||||
+ bra print_uw
|
||||
}}
|
||||
|
@ -20,12 +20,12 @@ cx16logo {
|
||||
}
|
||||
|
||||
str[] logo_lines = [
|
||||
"\uf10d\uf11a\uf139\uf11b \uf11a\uf13a\uf11b\n",
|
||||
"\uf10b\uf11a▎\uf139\uf11b \uf11a\uf13a\uf130\uf11b\n",
|
||||
"\uf10f\uf11a▌ \uf139\uf11b \uf11a\uf13a \uf11b▌\n",
|
||||
"\uf102 \uf132\uf11a▖\uf11b \uf11a▗\uf11b\uf132\n",
|
||||
"\uf10e ▂\uf11a▘\uf11b \uf11a▝\uf11b▂\n",
|
||||
"\uf104 \uf11a \uf11b\uf13a\uf11b \uf139\uf11a \uf11b\n",
|
||||
"\uf10d\uf11a\uf139\uf11b \uf11a\uf13a\uf11b",
|
||||
"\uf10b\uf11a▎\uf139\uf11b \uf11a\uf13a\uf130\uf11b",
|
||||
"\uf10f\uf11a▌ \uf139\uf11b \uf11a\uf13a \uf11b▌",
|
||||
"\uf102 \uf132\uf11a▖\uf11b \uf11a▗\uf11b\uf132",
|
||||
"\uf10e ▂\uf11a▘\uf11b \uf11a▝\uf11b▂",
|
||||
"\uf104 \uf11a \uf11b\uf13a\uf11b \uf139\uf11a \uf11b",
|
||||
"\uf101\uf130\uf13a \uf139▎\uf100"
|
||||
]
|
||||
}
|
||||
|
@ -433,10 +433,20 @@ io_error:
|
||||
return first_byte
|
||||
}
|
||||
|
||||
; Use kernal LOAD routine to load the given program file in memory.
|
||||
; This is similar to Basic's LOAD "filename",drive / LOAD "filename",drive,1
|
||||
; If you don't give an address_override, the location in memory is taken from the 2-byte file header.
|
||||
; If you specify a custom address_override, the first 2 bytes in the file are ignored
|
||||
; and the rest is loaded at the given location in memory.
|
||||
; Returns the end load address+1 if successful or 0 if a load error occurred.
|
||||
; NOTE: when the load is larger than 64Kb and/or spans multiple RAM banks
|
||||
; (which is possible on the Commander X16), the returned size is not correct,
|
||||
; because it doesn't take the number of ram banks into account.
|
||||
; Consider using cx16diskio.load() instead.
|
||||
sub load(ubyte drivenumber, uword filenameptr, uword address_override) -> uword {
|
||||
c64.SETNAM(string.length(filenameptr), filenameptr)
|
||||
ubyte secondary = 1
|
||||
uword end_of_load = 0
|
||||
cx16.r1 = 0
|
||||
if address_override
|
||||
secondary = 0
|
||||
c64.SETLFS(1, drivenumber, secondary)
|
||||
@ -447,22 +457,35 @@ io_error:
|
||||
ldy address_override+1
|
||||
jsr c64.LOAD
|
||||
bcs +
|
||||
stx end_of_load
|
||||
sty end_of_load+1
|
||||
stx cx16.r1
|
||||
sty cx16.r1+1
|
||||
+ ldx P8ZP_SCRATCH_REG
|
||||
}}
|
||||
|
||||
c64.CLRCHN()
|
||||
c64.CLOSE(1)
|
||||
|
||||
if end_of_load
|
||||
return end_of_load - address_override
|
||||
|
||||
return 0
|
||||
return cx16.r1
|
||||
}
|
||||
|
||||
|
||||
str filename = "0:??????????????????????????????????????"
|
||||
; Use kernal LOAD routine to load the given file in memory.
|
||||
; INCLUDING the first 2 bytes in the file: no program header is assumed in the file.
|
||||
; This is different from Basic's LOAD instruction which always skips the first two bytes.
|
||||
; The load address is mandatory.
|
||||
; Returns the end load address+1 if successful or 0 if a load error occurred.
|
||||
; NOTE: when the load is larger than 64Kb and/or spans multiple RAM banks
|
||||
; (which is possible on the Commander X16), the returned size is not correct,
|
||||
; because it doesn't take the number of ram banks into account.
|
||||
; Consider using cx16diskio.load_raw() instead.
|
||||
sub load_raw(ubyte drivenumber, uword filenameptr, uword address) -> uword {
|
||||
if not f_open(drivenumber, filenameptr)
|
||||
return 0
|
||||
cx16.r1 = f_read(address, 2)
|
||||
f_close()
|
||||
if cx16.r1!=2
|
||||
return 0
|
||||
address += 2
|
||||
return load(drivenumber, filenameptr, address)
|
||||
}
|
||||
|
||||
sub delete(ubyte drivenumber, uword filenameptr) {
|
||||
; -- delete a file on the drive
|
||||
@ -489,4 +512,6 @@ io_error:
|
||||
c64.CLRCHN()
|
||||
c64.CLOSE(1)
|
||||
}
|
||||
|
||||
str filename = "0:??????????????????????????????????????"
|
||||
}
|
||||
|
@ -91,6 +91,13 @@ func_sin8_into_A .proc
|
||||
_sinecos8 .char trunc(127.0 * sin(range(256+64) * rad(360.0/256.0)))
|
||||
.pend
|
||||
|
||||
func_sinr8_into_A .proc
|
||||
tay
|
||||
lda _sinecosR8,y
|
||||
rts
|
||||
_sinecosR8 .char trunc(127.0 * sin(range(180+45) * rad(360.0/180.0)))
|
||||
.pend
|
||||
|
||||
func_sin8u_into_A .proc
|
||||
tay
|
||||
lda _sinecos8u,y
|
||||
@ -98,6 +105,13 @@ func_sin8u_into_A .proc
|
||||
_sinecos8u .byte trunc(128.0 + 127.5 * sin(range(256+64) * rad(360.0/256.0)))
|
||||
.pend
|
||||
|
||||
func_sinr8u_into_A .proc
|
||||
tay
|
||||
lda _sinecosR8u,y
|
||||
rts
|
||||
_sinecosR8u .byte trunc(128.0 + 127.5 * sin(range(180+45) * rad(360.0/180.0)))
|
||||
.pend
|
||||
|
||||
func_sin8_stack .proc
|
||||
tay
|
||||
lda func_sin8_into_A._sinecos8,y
|
||||
@ -106,6 +120,14 @@ func_sin8_stack .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sinr8_stack .proc
|
||||
tay
|
||||
lda func_sinr8_into_A._sinecosR8,y
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sin8u_stack .proc
|
||||
tay
|
||||
lda func_sin8u_into_A._sinecos8u,y
|
||||
@ -114,18 +136,38 @@ func_sin8u_stack .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sinr8u_stack .proc
|
||||
tay
|
||||
lda func_sinr8u_into_A._sinecosR8u,y
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cos8_into_A .proc
|
||||
tay
|
||||
lda func_sin8_into_A._sinecos8+64,y
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cosr8_into_A .proc
|
||||
tay
|
||||
lda func_sinr8_into_A._sinecosR8+45,y
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cos8u_into_A .proc
|
||||
tay
|
||||
lda func_sin8u_into_A._sinecos8u+64,y
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cosr8u_into_A .proc
|
||||
tay
|
||||
lda func_sinr8u_into_A._sinecosR8u+45,y
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cos8_stack .proc
|
||||
tay
|
||||
lda func_sin8_into_A._sinecos8+64,y
|
||||
@ -134,6 +176,14 @@ func_cos8_stack .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cosr8_stack .proc
|
||||
tay
|
||||
lda func_sinr8_into_A._sinecosR8+45,y
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cos8u_stack .proc
|
||||
tay
|
||||
lda func_sin8u_into_A._sinecos8u+64,y
|
||||
@ -142,6 +192,14 @@ func_cos8u_stack .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cosr8u_stack .proc
|
||||
tay
|
||||
lda func_sinr8u_into_A._sinecosR8u+45,y
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sin16_into_AY .proc
|
||||
tay
|
||||
lda _sinecos8lo,y
|
||||
@ -155,6 +213,19 @@ _sinecos8lo .byte <_
|
||||
_sinecos8hi .byte >_
|
||||
.pend
|
||||
|
||||
func_sinr16_into_AY .proc
|
||||
tay
|
||||
lda _sinecosR8lo,y
|
||||
pha
|
||||
lda _sinecosR8hi,y
|
||||
tay
|
||||
pla
|
||||
rts
|
||||
_ := trunc(32767.0 * sin(range(180+45) * rad(360.0/180.0)))
|
||||
_sinecosR8lo .byte <_
|
||||
_sinecosR8hi .byte >_
|
||||
.pend
|
||||
|
||||
func_sin16u_into_AY .proc
|
||||
tay
|
||||
lda _sinecos8ulo,y
|
||||
@ -168,6 +239,18 @@ _sinecos8ulo .byte <_
|
||||
_sinecos8uhi .byte >_
|
||||
.pend
|
||||
|
||||
func_sinr16u_into_AY .proc
|
||||
tay
|
||||
lda _sinecosR8ulo,y
|
||||
pha
|
||||
lda _sinecosR8uhi,y
|
||||
tay
|
||||
pla
|
||||
rts
|
||||
_ := trunc(32768.0 + 32767.5 * sin(range(180+45) * rad(360.0/180.0)))
|
||||
_sinecosR8ulo .byte <_
|
||||
_sinecosR8uhi .byte >_
|
||||
.pend
|
||||
|
||||
func_sin16_stack .proc
|
||||
tay
|
||||
@ -179,6 +262,16 @@ func_sin16_stack .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sinr16_stack .proc
|
||||
tay
|
||||
lda func_sinr16_into_AY._sinecosR8lo,y
|
||||
sta P8ESTACK_LO,x
|
||||
lda func_sinr16_into_AY._sinecosR8hi,y
|
||||
sta P8ESTACK_HI,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sin16u_stack .proc
|
||||
tay
|
||||
lda func_sin16u_into_AY._sinecos8ulo,y
|
||||
@ -189,6 +282,16 @@ func_sin16u_stack .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_sinr16u_stack .proc
|
||||
tay
|
||||
lda func_sinr16u_into_AY._sinecosR8ulo,y
|
||||
sta P8ESTACK_LO,x
|
||||
lda func_sinr16u_into_AY._sinecosR8uhi,y
|
||||
sta P8ESTACK_HI,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cos16_into_AY .proc
|
||||
tay
|
||||
lda func_sin16_into_AY._sinecos8lo+64,y
|
||||
@ -199,6 +302,16 @@ func_cos16_into_AY .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cosr16_into_AY .proc
|
||||
tay
|
||||
lda func_sinr16_into_AY._sinecosR8lo+45,y
|
||||
pha
|
||||
lda func_sinr16_into_AY._sinecosR8hi+45,y
|
||||
tay
|
||||
pla
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cos16u_into_AY .proc
|
||||
tay
|
||||
lda func_sin16u_into_AY._sinecos8ulo+64,y
|
||||
@ -209,6 +322,16 @@ func_cos16u_into_AY .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cosr16u_into_AY .proc
|
||||
tay
|
||||
lda func_sinr16u_into_AY._sinecosR8ulo+45,y
|
||||
pha
|
||||
lda func_sinr16u_into_AY._sinecosR8uhi+45,y
|
||||
tay
|
||||
pla
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cos16_stack .proc
|
||||
tay
|
||||
lda func_sin16_into_AY._sinecos8lo+64,y
|
||||
@ -219,6 +342,16 @@ func_cos16_stack .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cosr16_stack .proc
|
||||
tay
|
||||
lda func_sinr16_into_AY._sinecosR8lo+45,y
|
||||
sta P8ESTACK_LO,x
|
||||
lda func_sinr16_into_AY._sinecosR8hi+45,y
|
||||
sta P8ESTACK_HI,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cos16u_stack .proc
|
||||
tay
|
||||
lda func_sin16u_into_AY._sinecos8ulo+64,y
|
||||
@ -229,6 +362,16 @@ func_cos16u_stack .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_cosr16u_stack .proc
|
||||
tay
|
||||
lda func_sinr16u_into_AY._sinecosR8ulo+45,y
|
||||
sta P8ESTACK_LO,x
|
||||
lda func_sinr16u_into_AY._sinecosR8uhi+45,y
|
||||
sta P8ESTACK_HI,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
abs_b_stack .proc
|
||||
; -- push abs(A) on stack (as byte)
|
||||
jsr abs_b_into_A
|
||||
|
@ -1083,3 +1083,45 @@ strlen .proc
|
||||
bne -
|
||||
+ rts
|
||||
.pend
|
||||
|
||||
|
||||
containment_bytearray .proc
|
||||
; -- check if a value exists in a byte array.
|
||||
; parameters: P8ZP_SCRATCH_W1: address of the byte array, A = byte to check, Y = length of array (>=1).
|
||||
; returns boolean 0/1 in A.
|
||||
dey
|
||||
- cmp (P8ZP_SCRATCH_W1),y
|
||||
beq +
|
||||
dey
|
||||
cpy #255
|
||||
bne -
|
||||
lda #0
|
||||
rts
|
||||
+ lda #1
|
||||
rts
|
||||
.pend
|
||||
|
||||
containment_wordarray .proc
|
||||
; -- check if a value exists in a word array.
|
||||
; parameters: P8ZP_SCRATCH_W1: value to check, P8ZP_SCRATCH_W2: address of the word array, Y = length of array (>=1).
|
||||
; returns boolean 0/1 in A.
|
||||
dey
|
||||
tya
|
||||
asl a
|
||||
tay
|
||||
- lda P8ZP_SCRATCH_W1
|
||||
cmp (P8ZP_SCRATCH_W2),y
|
||||
bne +
|
||||
lda P8ZP_SCRATCH_W1+1
|
||||
iny
|
||||
cmp (P8ZP_SCRATCH_W2),y
|
||||
beq _found
|
||||
+ dey
|
||||
dey
|
||||
cpy #254
|
||||
bne -
|
||||
lda #0
|
||||
rts
|
||||
_found lda #1
|
||||
rts
|
||||
.pend
|
||||
|
@ -6,12 +6,24 @@ prog8_lib {
|
||||
%asminclude "library:prog8_lib.asm"
|
||||
%asminclude "library:prog8_funcs.asm"
|
||||
|
||||
; TODO these retval variables are no longer used???
|
||||
uword @zp retval_interm_uw ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
|
||||
word @zp retval_interm_w ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
|
||||
ubyte @zp retval_interm_ub ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
|
||||
byte @zp retval_interm_b ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
|
||||
; NOTE: these variables are checked in the StatementReorderer (in fun after(decl: VarDecl)), for these exact names!
|
||||
; to store intermediary expression results for return values:
|
||||
; NOTE: these variables are used in the StatementReorderer and StatementOptimizer
|
||||
uword @zp retval_interm_uw
|
||||
word @zp retval_interm_w
|
||||
ubyte @zp retval_interm_ub
|
||||
byte @zp retval_interm_b
|
||||
word retval_interm_w2
|
||||
byte retval_interm_b2
|
||||
|
||||
; prog8 "hooks" to be able to access the temporary scratch variables
|
||||
; YOU SHOULD NOT USE THESE IN USER CODE - THESE ARE MEANT FOR INTERNAL COMPILER USE
|
||||
; NOTE: the assembly code generator will match these names and not generate
|
||||
; new variables/memdefs for them, rather, they'll point to the scratch variables directly.
|
||||
&ubyte P8ZP_SCRATCH_REG = $ff
|
||||
&byte P8ZP_SCRATCH_B1 = $ff
|
||||
&uword P8ZP_SCRATCH_W1 = $ff
|
||||
&word P8ZP_SCRATCH_W2 = $ff
|
||||
|
||||
|
||||
asmsub pattern_match(str string @AY, str pattern @R0) clobbers(Y) -> ubyte @A {
|
||||
%asm {{
|
||||
|
@ -1 +1 @@
|
||||
7.2
|
||||
7.6
|
||||
|
@ -2,10 +2,12 @@ package prog8
|
||||
|
||||
import kotlinx.cli.*
|
||||
import prog8.ast.base.AstException
|
||||
import prog8.codegen.target.C128Target
|
||||
import prog8.compiler.CompilationResult
|
||||
import prog8.compiler.CompilerArguments
|
||||
import prog8.compiler.compileProgram
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8.codegen.target.C64Target
|
||||
import prog8.codegen.target.Cx16Target
|
||||
import java.io.File
|
||||
import java.nio.file.FileSystems
|
||||
import java.nio.file.Path
|
||||
@ -35,11 +37,13 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".")
|
||||
val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code")
|
||||
val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform any optimizations")
|
||||
val dontReinitGlobals by cli.option(ArgType.Boolean, fullName = "noreinit", description = "don't create code to reinitialize globals on multiple runs of the program (experimental!)")
|
||||
val optimizeFloatExpressions by cli.option(ArgType.Boolean, fullName = "optfloatx", description = "optimize float expressions (warning: can increase program size)")
|
||||
val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watches for file changes), greatly increases compilation speed")
|
||||
val slowCodegenWarnings by cli.option(ArgType.Boolean, fullName = "slowwarn", description="show debug warnings about slow/problematic assembly code generation")
|
||||
val quietAssembler by cli.option(ArgType.Boolean, fullName = "quietasm", description = "don't print assembler output results")
|
||||
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available").default(C64Target.name)
|
||||
val asmListfile by cli.option(ArgType.Boolean, fullName = "asmlist", description = "make the assembler produce a listing file as well")
|
||||
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler (one of '${C64Target.name}', '${C128Target.name}', '${Cx16Target.name}')").default(C64Target.name)
|
||||
val sourceDirs by cli.option(ArgType.String, fullName="srcdirs", description = "list of extra paths, separated with ${File.pathSeparator}, to search in for imported modules").multiple().delimiter(File.pathSeparator)
|
||||
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999)
|
||||
|
||||
@ -66,6 +70,11 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
if(srcdirs.firstOrNull()!=".")
|
||||
srcdirs.add(0, ".")
|
||||
|
||||
if (compilationTarget !in setOf(C64Target.name, C128Target.name, Cx16Target.name)) {
|
||||
System.err.println("Invalid compilation target: $compilationTarget")
|
||||
return false
|
||||
}
|
||||
|
||||
if(watchMode==true) {
|
||||
val watchservice = FileSystems.getDefault().newWatchService()
|
||||
val allImportedFiles = mutableSetOf<Path>()
|
||||
@ -75,10 +84,20 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
val results = mutableListOf<CompilationResult>()
|
||||
for(filepathRaw in moduleFiles) {
|
||||
val filepath = pathFrom(filepathRaw).normalize()
|
||||
val compilationResult = compileProgram(filepath,
|
||||
dontOptimize!=true, optimizeFloatExpressions==true,
|
||||
dontWriteAssembly!=true, slowCodegenWarnings==true, quietAssembler==true,
|
||||
compilationTarget, srcdirs, outputPath)
|
||||
val compilerArgs = CompilerArguments(
|
||||
filepath,
|
||||
dontOptimize != true,
|
||||
optimizeFloatExpressions == true,
|
||||
dontReinitGlobals == true,
|
||||
dontWriteAssembly != true,
|
||||
slowCodegenWarnings == true,
|
||||
quietAssembler == true,
|
||||
asmListfile == true,
|
||||
compilationTarget,
|
||||
srcdirs,
|
||||
outputPath
|
||||
)
|
||||
val compilationResult = compileProgram(compilerArgs)
|
||||
results.add(compilationResult)
|
||||
}
|
||||
|
||||
@ -115,11 +134,21 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
val filepath = pathFrom(filepathRaw).normalize()
|
||||
val compilationResult: CompilationResult
|
||||
try {
|
||||
compilationResult = compileProgram(filepath,
|
||||
dontOptimize!=true, optimizeFloatExpressions==true,
|
||||
dontWriteAssembly!=true, slowCodegenWarnings==true, quietAssembler==true,
|
||||
compilationTarget, srcdirs, outputPath)
|
||||
if(!compilationResult.success)
|
||||
val compilerArgs = CompilerArguments(
|
||||
filepath,
|
||||
dontOptimize != true,
|
||||
optimizeFloatExpressions == true,
|
||||
dontReinitGlobals == true,
|
||||
dontWriteAssembly != true,
|
||||
slowCodegenWarnings == true,
|
||||
quietAssembler == true,
|
||||
asmListfile == true,
|
||||
compilationTarget,
|
||||
srcdirs,
|
||||
outputPath
|
||||
)
|
||||
compilationResult = compileProgram(compilerArgs)
|
||||
if (!compilationResult.success)
|
||||
return false
|
||||
} catch (x: AstException) {
|
||||
return false
|
||||
|
@ -11,17 +11,50 @@ import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.compiler.astprocessing.isSubroutineParameter
|
||||
import prog8.codegen.target.AssemblyError
|
||||
import prog8.compilerinterface.*
|
||||
import prog8.optimizer.getTempVarName
|
||||
|
||||
|
||||
internal class BeforeAsmGenerationAstChanger(val program: Program, private val options: CompilationOptions,
|
||||
private val errors: IErrorReporter) : AstWalker() {
|
||||
|
||||
private val subroutineVariables = mutableMapOf<Subroutine, MutableList<Pair<String, VarDecl>>>()
|
||||
|
||||
private fun rememberSubroutineVar(decl: VarDecl) {
|
||||
val sub = decl.definingSubroutine ?: return
|
||||
var varsList = subroutineVariables[sub]
|
||||
if(varsList==null) {
|
||||
varsList = mutableListOf()
|
||||
subroutineVariables[sub] = varsList
|
||||
}
|
||||
varsList.add(decl.name to decl)
|
||||
}
|
||||
|
||||
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
|
||||
throw FatalAstException("break should have been replaced by goto $breakStmt")
|
||||
}
|
||||
|
||||
override fun before(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
|
||||
throw FatalAstException("while should have been converted to jumps")
|
||||
}
|
||||
|
||||
override fun before(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
|
||||
throw FatalAstException("do..until should have been converted to jumps")
|
||||
}
|
||||
|
||||
override fun before(block: Block, parent: Node): Iterable<IAstModification> {
|
||||
// move all subroutines to the bottom of the block
|
||||
val subs = block.statements.filterIsInstance<Subroutine>()
|
||||
block.statements.removeAll(subs)
|
||||
block.statements.addAll(subs)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
if(decl.type==VarDeclType.VAR && decl.value != null && decl.datatype in NumericDatatypes)
|
||||
throw FatalAstException("vardecls for variables, with initial numerical value, should have been rewritten as plain vardecl + assignment $decl")
|
||||
|
||||
subroutineVariables.add(decl.name to decl)
|
||||
rememberSubroutineVar(decl)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@ -31,28 +64,32 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
// But it can only be done if the target variable IS NOT OCCURRING AS AN OPERAND ITSELF.
|
||||
if(!assignment.isAugmentable
|
||||
&& assignment.target.identifier != null
|
||||
&& assignment.target.isInRegularRAMof(options.compTarget.machine)) {
|
||||
&& !assignment.target.isIOAddress(options.compTarget.machine)) {
|
||||
val binExpr = assignment.value as? BinaryExpression
|
||||
|
||||
if(binExpr!=null && binExpr.inferType(program).istype(DataType.FLOAT) && !options.optimizeFloatExpressions)
|
||||
if(binExpr!=null && binExpr.inferType(program) istype DataType.FLOAT && !options.optimizeFloatExpressions)
|
||||
return noModifications
|
||||
|
||||
if (binExpr != null && binExpr.operator !in comparisonOperators) {
|
||||
if (binExpr != null && binExpr.operator !in ComparisonOperators) {
|
||||
if (binExpr.left !is BinaryExpression) {
|
||||
if (binExpr.right.referencesIdentifier(*assignment.target.identifier!!.nameInSource.toTypedArray())) {
|
||||
if (binExpr.right.referencesIdentifier(assignment.target.identifier!!.nameInSource)) {
|
||||
// the right part of the expression contains the target variable itself.
|
||||
// we can't 'split' it trivially because the variable will be changed halfway through.
|
||||
if(binExpr.operator in associativeOperators) {
|
||||
if(binExpr.operator in AssociativeOperators) {
|
||||
// A = <something-without-A> <associativeoperator> <otherthing-with-A>
|
||||
// use the other part of the expression to split.
|
||||
val assignRight = Assignment(assignment.target, binExpr.right, assignment.position)
|
||||
val sourceDt = binExpr.right.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
|
||||
val (_, right) = binExpr.right.typecastTo(assignment.target.inferType(program).getOrElse { throw AssemblyError("unknown dt") }, sourceDt, implicit=true)
|
||||
val assignRight = Assignment(assignment.target, right, assignment.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(assignment, assignRight, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(binExpr.right, binExpr.left, binExpr),
|
||||
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
|
||||
}
|
||||
} else {
|
||||
val assignLeft = Assignment(assignment.target, binExpr.left, assignment.position)
|
||||
val sourceDt = binExpr.left.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
|
||||
val (_, left) = binExpr.left.typecastTo(assignment.target.inferType(program).getOrElse { throw AssemblyError("unknown dt") }, sourceDt, implicit=true)
|
||||
val assignLeft = Assignment(assignment.target, left, assignment.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(assignment, assignLeft, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
|
||||
@ -63,47 +100,16 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private val subroutineVariables = mutableListOf<Pair<String, VarDecl>>()
|
||||
private val addedIfConditionVars = mutableSetOf<Pair<Subroutine, String>>()
|
||||
|
||||
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
subroutineVariables.clear()
|
||||
addedIfConditionVars.clear()
|
||||
|
||||
if(!subroutine.isAsmSubroutine) {
|
||||
// change 'str' parameters into 'uword' (just treat it as an address)
|
||||
val stringParams = subroutine.parameters.filter { it.type==DataType.STR }
|
||||
val parameterChanges = stringParams.map {
|
||||
val uwordParam = SubroutineParameter(it.name, DataType.UWORD, it.position)
|
||||
IAstModification.ReplaceNode(it, uwordParam, subroutine)
|
||||
}
|
||||
|
||||
val stringParamNames = stringParams.map { it.name }.toSet()
|
||||
val varsChanges = subroutine.statements
|
||||
.filterIsInstance<VarDecl>()
|
||||
.filter { it.autogeneratedDontRemove && it.name in stringParamNames }
|
||||
.map {
|
||||
val newvar = VarDecl(it.type, DataType.UWORD, it.zeropage, null, it.name, null, false, true, it.sharedWithAsm, it.position)
|
||||
IAstModification.ReplaceNode(it, newvar, subroutine)
|
||||
}
|
||||
|
||||
return parameterChanges + varsChanges
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
if(scope.statements.any { it is VarDecl || it is IStatementContainer })
|
||||
throw FatalAstException("anonymousscope may no longer contain any vardecls or subscopes")
|
||||
|
||||
val decls = scope.statements.filterIsInstance<VarDecl>().filter { it.type == VarDeclType.VAR }
|
||||
subroutineVariables.addAll(decls.map { it.name to it })
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
val firstDeclarations = mutableMapOf<String, VarDecl>()
|
||||
for(decl in subroutineVariables) {
|
||||
val rememberedSubroutineVars = subroutineVariables.getOrDefault(subroutine, mutableListOf())
|
||||
for(decl in rememberedSubroutineVars) {
|
||||
val existing = firstDeclarations[decl.first]
|
||||
if(existing!=null && existing !== decl.second) {
|
||||
errors.err("variable ${decl.first} already defined in subroutine ${subroutine.name} at ${existing.position}", decl.second.position)
|
||||
@ -111,7 +117,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
firstDeclarations[decl.first] = decl.second
|
||||
}
|
||||
}
|
||||
|
||||
rememberedSubroutineVars.clear()
|
||||
|
||||
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernal routine.
|
||||
// and if an assembly block doesn't contain a rts/rti, and some other situations.
|
||||
@ -130,18 +136,20 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
val outerScope = subroutine.definingScope
|
||||
val outerStatements = outerScope.statements
|
||||
val subroutineStmtIdx = outerStatements.indexOf(subroutine)
|
||||
if (subroutineStmtIdx > 0
|
||||
&& outerStatements[subroutineStmtIdx - 1] !is Jump
|
||||
&& outerStatements[subroutineStmtIdx - 1] !is Subroutine
|
||||
&& outerStatements[subroutineStmtIdx - 1] !is Return
|
||||
&& outerScope !is Block) {
|
||||
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope)
|
||||
if (subroutineStmtIdx > 0) {
|
||||
val prevStmt = outerStatements[subroutineStmtIdx-1]
|
||||
if(outerScope !is Block
|
||||
&& (prevStmt !is Jump)
|
||||
&& prevStmt !is Subroutine
|
||||
&& prevStmt !is Return) {
|
||||
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope)
|
||||
}
|
||||
}
|
||||
return mods
|
||||
}
|
||||
|
||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||
// see if we can remove superfluous typecasts (outside of expressions)
|
||||
// see if we can remove redundant typecasts (outside of expressions)
|
||||
// such as casting byte<->ubyte, word<->uword
|
||||
// Also the special typecast of a reference type (str, array) to an UWORD will be changed into address-of,
|
||||
// UNLESS it's a str parameter in the containing subroutine - then we remove the typecast altogether
|
||||
@ -185,64 +193,100 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@Suppress("DuplicatedCode")
|
||||
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
|
||||
val prefixExpr = ifStatement.condition as? PrefixExpression
|
||||
override fun after(ifElse: IfElse, parent: Node): Iterable<IAstModification> {
|
||||
val prefixExpr = ifElse.condition as? PrefixExpression
|
||||
if(prefixExpr!=null && prefixExpr.operator=="not") {
|
||||
// if not x -> if x==0
|
||||
val booleanExpr = BinaryExpression(prefixExpr.expression, "==", NumericLiteralValue.optimalInteger(0, ifStatement.condition.position), ifStatement.condition.position)
|
||||
return listOf(IAstModification.ReplaceNode(ifStatement.condition, booleanExpr, ifStatement))
|
||||
val booleanExpr = BinaryExpression(prefixExpr.expression, "==", NumericLiteralValue.optimalInteger(0, ifElse.condition.position), ifElse.condition.position)
|
||||
return listOf(IAstModification.ReplaceNode(ifElse.condition, booleanExpr, ifElse))
|
||||
}
|
||||
|
||||
val binExpr = ifStatement.condition as? BinaryExpression
|
||||
if(binExpr==null || binExpr.operator !in comparisonOperators) {
|
||||
val binExpr = ifElse.condition as? BinaryExpression
|
||||
if(binExpr==null || binExpr.operator !in ComparisonOperators) {
|
||||
// if x -> if x!=0, if x+5 -> if x+5 != 0
|
||||
val booleanExpr = BinaryExpression(ifStatement.condition, "!=", NumericLiteralValue.optimalInteger(0, ifStatement.condition.position), ifStatement.condition.position)
|
||||
return listOf(IAstModification.ReplaceNode(ifStatement.condition, booleanExpr, ifStatement))
|
||||
val booleanExpr = BinaryExpression(ifElse.condition, "!=", NumericLiteralValue.optimalInteger(0, ifElse.condition.position), ifElse.condition.position)
|
||||
return listOf(IAstModification.ReplaceNode(ifElse.condition, booleanExpr, ifElse))
|
||||
}
|
||||
|
||||
if((binExpr.operator=="==" || binExpr.operator=="!=") &&
|
||||
(binExpr.left as? NumericLiteralValue)?.number==0 &&
|
||||
(binExpr.right as? NumericLiteralValue)?.number!=0)
|
||||
throw InternalCompilerException("if 0==X should have been swapped to if X==0")
|
||||
if((binExpr.left as? NumericLiteralValue)?.number==0.0 &&
|
||||
(binExpr.right as? NumericLiteralValue)?.number!=0.0)
|
||||
throw FatalAstException("0==X should have been swapped to if X==0")
|
||||
|
||||
return noModifications
|
||||
// simplify the conditional expression, introduce simple assignments if required.
|
||||
// NOTE: sometimes this increases code size because additional stores/loads are generated for the
|
||||
// intermediate variables. We assume these are optimized away from the resulting assembly code later.
|
||||
val simplify = simplifyConditionalExpression(binExpr)
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
if(simplify.rightVarAssignment!=null) {
|
||||
modifications += IAstModification.ReplaceNode(binExpr.right, simplify.rightOperandReplacement!!, binExpr)
|
||||
modifications += IAstModification.InsertBefore(ifElse, simplify.rightVarAssignment, parent as IStatementContainer)
|
||||
}
|
||||
if(simplify.leftVarAssignment!=null) {
|
||||
modifications += IAstModification.ReplaceNode(binExpr.left, simplify.leftOperandReplacement!!, binExpr)
|
||||
modifications += IAstModification.InsertBefore(ifElse, simplify.leftVarAssignment, parent as IStatementContainer)
|
||||
}
|
||||
|
||||
return modifications
|
||||
}
|
||||
|
||||
@Suppress("DuplicatedCode")
|
||||
override fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
|
||||
val prefixExpr = untilLoop.condition as? PrefixExpression
|
||||
if(prefixExpr!=null && prefixExpr.operator=="not") {
|
||||
// until not x -> until x==0
|
||||
val booleanExpr = BinaryExpression(prefixExpr.expression, "==", NumericLiteralValue.optimalInteger(0, untilLoop.condition.position), untilLoop.condition.position)
|
||||
return listOf(IAstModification.ReplaceNode(untilLoop.condition, booleanExpr, untilLoop))
|
||||
private class CondExprSimplificationResult(
|
||||
val leftVarAssignment: Assignment?,
|
||||
val leftOperandReplacement: Expression?,
|
||||
val rightVarAssignment: Assignment?,
|
||||
val rightOperandReplacement: Expression?
|
||||
)
|
||||
|
||||
private fun simplifyConditionalExpression(expr: BinaryExpression): CondExprSimplificationResult {
|
||||
|
||||
// TODO: somehow figure out if the expr will result in stack-evaluation STILL after being split off,
|
||||
// in that case: do *not* split it off but just keep it as it is (otherwise code size increases)
|
||||
// TODO: do NOT move this to an earler ast transform phase (such as StatementReorderer or StatementOptimizer) - it WILL result in larger code.
|
||||
// TODO: this should be replaced by a general expression-evaluation optimization step.
|
||||
// the actual conditional expression in the statement should be no more than VARIABLE <COMPARISON-OPERATOR> SIMPLE-EXPRESSION
|
||||
|
||||
var leftAssignment: Assignment? = null
|
||||
var leftOperandReplacement: Expression? = null
|
||||
var rightAssignment: Assignment? = null
|
||||
var rightOperandReplacement: Expression? = null
|
||||
|
||||
val separateLeftExpr = !expr.left.isSimple && expr.left !is IFunctionCall && expr.left !is ContainmentCheck
|
||||
val separateRightExpr = !expr.right.isSimple && expr.right !is IFunctionCall && expr.right !is ContainmentCheck
|
||||
val leftDt = expr.left.inferType(program)
|
||||
val rightDt = expr.right.inferType(program)
|
||||
|
||||
if(!leftDt.isInteger || !rightDt.isInteger) {
|
||||
// we can't reasonably simplify non-integer expressions
|
||||
return CondExprSimplificationResult(null, null, null, null)
|
||||
}
|
||||
|
||||
val binExpr = untilLoop.condition as? BinaryExpression
|
||||
if(binExpr==null || binExpr.operator !in comparisonOperators) {
|
||||
// until x -> until x!=0, until x+5 -> until x+5 != 0
|
||||
val booleanExpr = BinaryExpression(untilLoop.condition, "!=", NumericLiteralValue.optimalInteger(0, untilLoop.condition.position), untilLoop.condition.position)
|
||||
return listOf(IAstModification.ReplaceNode(untilLoop.condition, booleanExpr, untilLoop))
|
||||
if(separateLeftExpr) {
|
||||
val name = getTempVarName(leftDt)
|
||||
leftOperandReplacement = IdentifierReference(name, expr.position)
|
||||
leftAssignment = Assignment(
|
||||
AssignTarget(IdentifierReference(name, expr.position), null, null, expr.position),
|
||||
expr.left,
|
||||
expr.position
|
||||
)
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@Suppress("DuplicatedCode")
|
||||
override fun after(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
|
||||
val prefixExpr = whileLoop.condition as? PrefixExpression
|
||||
if(prefixExpr!=null && prefixExpr.operator=="not") {
|
||||
// while not x -> while x==0
|
||||
val booleanExpr = BinaryExpression(prefixExpr.expression, "==", NumericLiteralValue.optimalInteger(0, whileLoop.condition.position), whileLoop.condition.position)
|
||||
return listOf(IAstModification.ReplaceNode(whileLoop.condition, booleanExpr, whileLoop))
|
||||
if(separateRightExpr) {
|
||||
val name = when {
|
||||
rightDt istype DataType.UBYTE -> listOf("prog8_lib","retval_interm_ub")
|
||||
rightDt istype DataType.UWORD -> listOf("prog8_lib","retval_interm_uw")
|
||||
rightDt istype DataType.BYTE -> listOf("prog8_lib","retval_interm_b2")
|
||||
rightDt istype DataType.WORD -> listOf("prog8_lib","retval_interm_w2")
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
}
|
||||
rightOperandReplacement = IdentifierReference(name, expr.position)
|
||||
rightAssignment = Assignment(
|
||||
AssignTarget(IdentifierReference(name, expr.position), null, null, expr.position),
|
||||
expr.right,
|
||||
expr.position
|
||||
)
|
||||
}
|
||||
|
||||
val binExpr = whileLoop.condition as? BinaryExpression
|
||||
if(binExpr==null || binExpr.operator !in comparisonOperators) {
|
||||
// while x -> while x!=0, while x+5 -> while x+5 != 0
|
||||
val booleanExpr = BinaryExpression(whileLoop.condition, "!=", NumericLiteralValue.optimalInteger(0, whileLoop.condition.position), whileLoop.condition.position)
|
||||
return listOf(IAstModification.ReplaceNode(whileLoop.condition, booleanExpr, whileLoop))
|
||||
}
|
||||
return noModifications
|
||||
return CondExprSimplificationResult(
|
||||
leftAssignment, leftOperandReplacement,
|
||||
rightAssignment, rightOperandReplacement
|
||||
)
|
||||
}
|
||||
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
@ -255,13 +299,15 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
if(dt1 in ByteDatatypes) {
|
||||
if(dt2 in ByteDatatypes)
|
||||
return noModifications
|
||||
val cast1 = TypecastExpression(arg1, if(dt1==DataType.UBYTE) DataType.UWORD else DataType.WORD, true, functionCallStatement.position)
|
||||
return listOf(IAstModification.ReplaceNode(arg1, cast1, functionCallStatement))
|
||||
val (replaced, cast) = arg1.typecastTo(if(dt1==DataType.UBYTE) DataType.UWORD else DataType.WORD, dt1, true)
|
||||
if(replaced)
|
||||
return listOf(IAstModification.ReplaceNode(arg1, cast, functionCallStatement))
|
||||
} else {
|
||||
if(dt2 in WordDatatypes)
|
||||
return noModifications
|
||||
val cast2 = TypecastExpression(arg2, if(dt2==DataType.UBYTE) DataType.UWORD else DataType.WORD, true, functionCallStatement.position)
|
||||
return listOf(IAstModification.ReplaceNode(arg2, cast2, functionCallStatement))
|
||||
val (replaced, cast) = arg2.typecastTo(if(dt2==DataType.UBYTE) DataType.UWORD else DataType.WORD, dt2, true)
|
||||
if(replaced)
|
||||
return listOf(IAstModification.ReplaceNode(arg2, cast, functionCallStatement))
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
@ -295,12 +341,12 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
complexArrayIndexedExpressions.add(arrayIndexedExpression)
|
||||
}
|
||||
|
||||
override fun visit(branchStatement: BranchStatement) {}
|
||||
override fun visit(branch: Branch) {}
|
||||
|
||||
override fun visit(forLoop: ForLoop) {}
|
||||
|
||||
override fun visit(ifStatement: IfStatement) {
|
||||
ifStatement.condition.accept(this)
|
||||
override fun visit(ifElse: IfElse) {
|
||||
ifElse.condition.accept(this)
|
||||
}
|
||||
|
||||
override fun visit(untilLoop: UntilLoop) {
|
||||
@ -325,10 +371,8 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
val statement = expr.containingStatement
|
||||
val dt = expr.indexer.indexExpr.inferType(program)
|
||||
val register = if(dt istype DataType.UBYTE || dt istype DataType.BYTE ) "r9L" else "r9"
|
||||
// replace the indexer with just the variable (simply use a cx16 virtual register r9, that we HOPE is not used for other things in the expression...)
|
||||
// assign the indexing expression to the helper variable, but only if that hasn't been done already
|
||||
val target = AssignTarget(IdentifierReference(listOf("cx16", register), expr.indexer.position), null, null, expr.indexer.position)
|
||||
val tempvar = if(dt.isBytes) listOf("prog8_lib","retval_interm_ub") else listOf("prog8_lib","retval_interm_b")
|
||||
val target = AssignTarget(IdentifierReference(tempvar, expr.indexer.position), null, null, expr.indexer.position)
|
||||
val assign = Assignment(target, expr.indexer.indexExpr, expr.indexer.position)
|
||||
modifications.add(IAstModification.InsertBefore(statement, assign, statement.parent as IStatementContainer))
|
||||
modifications.add(IAstModification.ReplaceNode(expr.indexer.indexExpr, target.identifier!!.copy(), expr.indexer))
|
||||
|
@ -9,10 +9,11 @@ import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.statements.Directive
|
||||
import prog8.codegen.target.C128Target
|
||||
import prog8.compiler.astprocessing.*
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8.compiler.target.cpu6502.codegen.AsmGen
|
||||
import prog8.codegen.target.C64Target
|
||||
import prog8.codegen.target.Cx16Target
|
||||
import prog8.codegen.target.cpu6502.codegen.AsmGen
|
||||
import prog8.compilerinterface.*
|
||||
import prog8.optimizer.*
|
||||
import prog8.parser.ParseError
|
||||
@ -28,25 +29,31 @@ class CompilationResult(val success: Boolean,
|
||||
val compTarget: ICompilationTarget,
|
||||
val importedFiles: List<Path>)
|
||||
|
||||
class CompilerArguments(val filepath: Path,
|
||||
val optimize: Boolean,
|
||||
val optimizeFloatExpressions: Boolean,
|
||||
val dontReinitGlobals: Boolean,
|
||||
val writeAssembly: Boolean,
|
||||
val slowCodegenWarnings: Boolean,
|
||||
val quietAssembler: Boolean,
|
||||
val asmListfile: Boolean,
|
||||
val compilationTarget: String,
|
||||
val sourceDirs: List<String> = emptyList(),
|
||||
val outputDir: Path = Path(""),
|
||||
val errors: IErrorReporter = ErrorReporter())
|
||||
|
||||
// TODO refactor the gigantic list of parameters
|
||||
fun compileProgram(filepath: Path,
|
||||
optimize: Boolean,
|
||||
optimizeFloatExpressions: Boolean,
|
||||
writeAssembly: Boolean,
|
||||
slowCodegenWarnings: Boolean,
|
||||
quietAssembler: Boolean,
|
||||
compilationTarget: String,
|
||||
sourceDirs: List<String>,
|
||||
outputDir: Path,
|
||||
errors: IErrorReporter = ErrorReporter()): CompilationResult {
|
||||
|
||||
fun compileProgram(args: CompilerArguments): CompilationResult {
|
||||
var programName = ""
|
||||
lateinit var program: Program
|
||||
lateinit var importedFiles: List<Path>
|
||||
|
||||
val optimizeFloatExpr = if(args.optimize) args.optimizeFloatExpressions else false
|
||||
|
||||
val compTarget =
|
||||
when(compilationTarget) {
|
||||
when(args.compilationTarget) {
|
||||
C64Target.name -> C64Target
|
||||
C128Target.name -> C128Target
|
||||
Cx16Target.name -> Cx16Target
|
||||
else -> throw IllegalArgumentException("invalid compilation target")
|
||||
}
|
||||
@ -54,31 +61,37 @@ fun compileProgram(filepath: Path,
|
||||
try {
|
||||
val totalTime = measureTimeMillis {
|
||||
// import main module and everything it needs
|
||||
val (programresult, compilationOptions, imported) = parseImports(filepath, errors, compTarget, sourceDirs)
|
||||
val (programresult, compilationOptions, imported) = parseImports(args.filepath, args.errors, compTarget, args.sourceDirs)
|
||||
with(compilationOptions) {
|
||||
this.slowCodegenWarnings = slowCodegenWarnings
|
||||
this.optimize = optimize
|
||||
this.optimizeFloatExpressions = optimizeFloatExpressions
|
||||
slowCodegenWarnings = args.slowCodegenWarnings
|
||||
optimize = args.optimize
|
||||
optimizeFloatExpressions = optimizeFloatExpr
|
||||
dontReinitGlobals = args.dontReinitGlobals
|
||||
asmQuiet = args.quietAssembler
|
||||
asmListfile = args.asmListfile
|
||||
}
|
||||
program = programresult
|
||||
importedFiles = imported
|
||||
processAst(program, errors, compilationOptions)
|
||||
if (compilationOptions.optimize)
|
||||
processAst(program, args.errors, compilationOptions)
|
||||
if (compilationOptions.optimize) {
|
||||
// println("*********** AST RIGHT BEFORE OPTIMIZING *************")
|
||||
// printProgram(program)
|
||||
|
||||
optimizeAst(
|
||||
program,
|
||||
compilationOptions,
|
||||
errors,
|
||||
args.errors,
|
||||
BuiltinFunctionsFacade(BuiltinFunctions),
|
||||
compTarget
|
||||
)
|
||||
postprocessAst(program, errors, compilationOptions)
|
||||
}
|
||||
postprocessAst(program, args.errors, compilationOptions)
|
||||
|
||||
// println("*********** AST BEFORE ASSEMBLYGEN *************")
|
||||
// printAst(program)
|
||||
// printProgram(program)
|
||||
|
||||
if (writeAssembly) {
|
||||
val result = writeAssembly(program, errors, outputDir, quietAssembler, compilationOptions)
|
||||
when (result) {
|
||||
if (args.writeAssembly) {
|
||||
when (val result = writeAssembly(program, args.errors, args.outputDir, compilationOptions)) {
|
||||
is WriteAssemblyResult.Ok -> programName = result.filename
|
||||
is WriteAssemblyResult.Fail -> {
|
||||
System.err.println(result.error)
|
||||
@ -253,24 +266,27 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget
|
||||
private fun processAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
||||
// perform initial syntax checks and processings
|
||||
println("Processing for target ${compilerOptions.compTarget.name}...")
|
||||
program.preprocessAst()
|
||||
program.checkIdentifiers(errors, compilerOptions)
|
||||
program.preprocessAst(program, errors)
|
||||
program.checkIdentifiers(errors, program, compilerOptions)
|
||||
errors.report()
|
||||
// TODO: turning char literals into UBYTEs via an encoding should really happen in code gen - but for that we'd need DataType.CHAR
|
||||
// ...but what do we gain from this? We can leave it as it is now: where a char literal is no more than syntactic sugar for an UBYTE value.
|
||||
// By introduction a CHAR dt, we will also lose the opportunity to do constant-folding on any expression containing a char literal.
|
||||
// Yes this is different from strings that are only encoded in the code gen phase.
|
||||
program.charLiteralsToUByteLiterals(compilerOptions.compTarget)
|
||||
program.constantFold(errors, compilerOptions.compTarget)
|
||||
errors.report()
|
||||
program.reorderStatements(errors)
|
||||
program.desugaring(errors)
|
||||
errors.report()
|
||||
program.addTypecasts(errors)
|
||||
program.reorderStatements(errors, compilerOptions)
|
||||
errors.report()
|
||||
program.addTypecasts(errors, compilerOptions)
|
||||
errors.report()
|
||||
program.variousCleanups(program, errors)
|
||||
errors.report()
|
||||
program.checkValid(errors, compilerOptions)
|
||||
errors.report()
|
||||
program.checkIdentifiers(errors, compilerOptions)
|
||||
program.checkIdentifiers(errors, program, compilerOptions)
|
||||
errors.report()
|
||||
}
|
||||
|
||||
@ -292,21 +308,21 @@ private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, e
|
||||
if (optsDone1 + optsDone2 + optsDone3 == 0)
|
||||
break
|
||||
}
|
||||
|
||||
errors.report()
|
||||
}
|
||||
|
||||
private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
||||
program.addTypecasts(errors)
|
||||
program.desugaring(errors)
|
||||
program.addTypecasts(errors, compilerOptions)
|
||||
errors.report()
|
||||
program.variousCleanups(program, errors)
|
||||
program.checkValid(errors, compilerOptions) // check if final tree is still valid
|
||||
errors.report()
|
||||
val callGraph = CallGraph(program)
|
||||
callGraph.checkRecursiveCalls(errors)
|
||||
errors.report()
|
||||
program.verifyFunctionArgTypes()
|
||||
program.moveMainAndStartToFirst()
|
||||
program.checkValid(errors, compilerOptions) // check if final tree is still valid
|
||||
errors.report()
|
||||
}
|
||||
|
||||
private sealed class WriteAssemblyResult {
|
||||
@ -317,7 +333,6 @@ private sealed class WriteAssemblyResult {
|
||||
private fun writeAssembly(program: Program,
|
||||
errors: IErrorReporter,
|
||||
outputDir: Path,
|
||||
quietAssembler: Boolean,
|
||||
compilerOptions: CompilationOptions
|
||||
): WriteAssemblyResult {
|
||||
// asm generation directly from the Ast
|
||||
@ -325,7 +340,7 @@ private fun writeAssembly(program: Program,
|
||||
errors.report()
|
||||
|
||||
// println("*********** AST RIGHT BEFORE ASM GENERATION *************")
|
||||
// printAst(program)
|
||||
// printProgram(program)
|
||||
|
||||
compilerOptions.compTarget.machine.initializeZeropage(compilerOptions)
|
||||
val assembly = asmGeneratorFor(compilerOptions.compTarget,
|
||||
@ -337,7 +352,7 @@ private fun writeAssembly(program: Program,
|
||||
errors.report()
|
||||
|
||||
return if(assembly.valid && errors.noErrors()) {
|
||||
val assemblerReturnStatus = assembly.assemble(quietAssembler, compilerOptions)
|
||||
val assemblerReturnStatus = assembly.assemble(compilerOptions)
|
||||
if(assemblerReturnStatus!=0)
|
||||
WriteAssemblyResult.Fail("assembler step failed with return code $assemblerReturnStatus")
|
||||
else {
|
||||
@ -348,7 +363,7 @@ private fun writeAssembly(program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
fun printAst(program: Program) {
|
||||
fun printProgram(program: Program) {
|
||||
println()
|
||||
val printer = AstToSourceTextConverter(::print, program)
|
||||
printer.visit(program)
|
||||
|
@ -32,7 +32,7 @@ internal class ErrorReporter: IErrorReporter {
|
||||
MessageSeverity.ERROR -> printer.print("\u001b[91m") // bright red
|
||||
MessageSeverity.WARNING -> printer.print("\u001b[93m") // bright yellow
|
||||
}
|
||||
val msg = "${it.position.toClickableStr()} ${it.severity} ${it.message}".trim()
|
||||
val msg = "${it.severity} ${it.position.toClickableStr()} ${it.message}".trim()
|
||||
if(msg !in alreadyReportedMessages) {
|
||||
printer.println(msg)
|
||||
alreadyReportedMessages.add(msg)
|
||||
|
@ -43,20 +43,17 @@ class ModuleImporter(private val program: Program,
|
||||
println(logMsg)
|
||||
|
||||
val module = importModule(SourceCode.File(srcPath))
|
||||
return if(module==null)
|
||||
Err(NoSuchFileException(srcPath.toFile()))
|
||||
else
|
||||
Ok(module)
|
||||
return Ok(module)
|
||||
}
|
||||
|
||||
fun importLibraryModule(name: String): Module? {
|
||||
val import = Directive("%import", listOf(
|
||||
DirectiveArg("", name, 42, position = Position("<<<implicit-import>>>", 0, 0, 0))
|
||||
DirectiveArg("", name, 42u, position = Position("<<<implicit-import>>>", 0, 0, 0))
|
||||
), Position("<<<implicit-import>>>", 0, 0, 0))
|
||||
return executeImportDirective(import, null)
|
||||
}
|
||||
|
||||
private fun importModule(src: SourceCode) : Module? {
|
||||
private fun importModule(src: SourceCode) : Module {
|
||||
val moduleAst = Prog8Parser.parseModule(src)
|
||||
program.addModule(moduleAst)
|
||||
|
||||
@ -113,8 +110,7 @@ class ModuleImporter(private val program: Program,
|
||||
}
|
||||
)
|
||||
|
||||
if(importedModule!=null)
|
||||
removeDirectivesFromImportedModule(importedModule)
|
||||
removeDirectivesFromImportedModule(importedModule)
|
||||
return importedModule
|
||||
}
|
||||
|
||||
@ -150,7 +146,7 @@ class ModuleImporter(private val program: Program,
|
||||
locations.forEach {
|
||||
try {
|
||||
return Ok(SourceCode.File(it.resolve(fileName)))
|
||||
} catch (e: NoSuchFileException) {
|
||||
} catch (_: NoSuchFileException) {
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,6 @@ import prog8.ast.walk.IAstVisitor
|
||||
import prog8.compilerinterface.*
|
||||
import java.io.CharConversionException
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import kotlin.io.path.Path
|
||||
|
||||
internal class AstChecker(private val program: Program,
|
||||
@ -20,7 +19,7 @@ internal class AstChecker(private val program: Program,
|
||||
) : IAstVisitor {
|
||||
|
||||
override fun visit(program: Program) {
|
||||
assert(program === this.program)
|
||||
require(program === this.program)
|
||||
// there must be a single 'main' block with a 'start' subroutine for the program entry point.
|
||||
val mainBlocks = program.modules.flatMap { it.statements }.filter { b -> b is Block && b.name=="main" }.map { it as Block }
|
||||
if(mainBlocks.size>1)
|
||||
@ -81,10 +80,10 @@ internal class AstChecker(private val program: Program,
|
||||
super.visit(returnStmt)
|
||||
}
|
||||
|
||||
override fun visit(ifStatement: IfStatement) {
|
||||
if(!ifStatement.condition.inferType(program).isInteger)
|
||||
errors.err("condition value should be an integer type", ifStatement.condition.position)
|
||||
super.visit(ifStatement)
|
||||
override fun visit(ifElse: IfElse) {
|
||||
if(!ifElse.condition.inferType(program).isInteger)
|
||||
errors.err("condition value should be an integer type", ifElse.condition.position)
|
||||
super.visit(ifElse)
|
||||
}
|
||||
|
||||
override fun visit(forLoop: ForLoop) {
|
||||
@ -92,9 +91,9 @@ internal class AstChecker(private val program: Program,
|
||||
fun checkUnsignedLoopDownto0(range: RangeExpr?) {
|
||||
if(range==null)
|
||||
return
|
||||
val step = range.step.constValue(program)?.number?.toDouble() ?: 1.0
|
||||
val step = range.step.constValue(program)?.number ?: 1.0
|
||||
if(step < -1.0) {
|
||||
val limit = range.to.constValue(program)?.number?.toDouble()
|
||||
val limit = range.to.constValue(program)?.number
|
||||
if(limit==0.0 && range.from.constValue(program)==null)
|
||||
errors.err("for unsigned loop variable it's not possible to count down with step != -1 from a non-const value to exactly zero due to value wrapping", forLoop.position)
|
||||
}
|
||||
@ -161,26 +160,44 @@ internal class AstChecker(private val program: Program,
|
||||
super.visit(forLoop)
|
||||
}
|
||||
|
||||
|
||||
override fun visit(jump: Jump) {
|
||||
val ident = jump.identifier
|
||||
if(ident!=null) {
|
||||
val targetStatement = checkFunctionOrLabelExists(ident, jump)
|
||||
if(targetStatement!=null) {
|
||||
if(targetStatement is BuiltinFunctionStatementPlaceholder)
|
||||
if(targetStatement is BuiltinFunctionPlaceholder)
|
||||
errors.err("can't jump to a builtin function", jump.position)
|
||||
}
|
||||
if(targetStatement is Subroutine && targetStatement.parameters.any()) {
|
||||
errors.err("can't jump to a subroutine that takes parameters", jump.position)
|
||||
}
|
||||
}
|
||||
|
||||
val addr = jump.address
|
||||
if(addr!=null && (addr < 0 || addr > 65535))
|
||||
if(addr!=null && addr > 65535u)
|
||||
errors.err("jump address must be valid integer 0..\$ffff", jump.position)
|
||||
super.visit(jump)
|
||||
}
|
||||
|
||||
override fun visit(gosub: GoSub) {
|
||||
val ident = gosub.identifier
|
||||
if(ident!=null) {
|
||||
val targetStatement = checkFunctionOrLabelExists(ident, gosub)
|
||||
if(targetStatement!=null) {
|
||||
if(targetStatement is BuiltinFunctionPlaceholder)
|
||||
errors.err("can't gosub to a builtin function", gosub.position)
|
||||
}
|
||||
}
|
||||
|
||||
val addr = gosub.address
|
||||
if(addr!=null && addr > 65535u)
|
||||
errors.err("gosub address must be valid integer 0..\$ffff", gosub.position)
|
||||
super.visit(gosub)
|
||||
}
|
||||
|
||||
override fun visit(block: Block) {
|
||||
val addr = block.address
|
||||
if(addr!=null && (addr<0 || addr>65535)) {
|
||||
if(addr!=null && addr>65535u) {
|
||||
errors.err("block memory address must be valid integer 0..\$ffff", block.position)
|
||||
}
|
||||
|
||||
@ -192,7 +209,7 @@ internal class AstChecker(private val program: Program,
|
||||
is VarDecl,
|
||||
is InlineAssembly,
|
||||
is IStatementContainer,
|
||||
is NopStatement -> true
|
||||
is Nop -> true
|
||||
is Assignment -> {
|
||||
val target = statement.target.identifier!!.targetStatement(program)
|
||||
target === statement.previousSibling() // an initializer assignment is okay
|
||||
@ -216,7 +233,7 @@ internal class AstChecker(private val program: Program,
|
||||
super.visit(label)
|
||||
}
|
||||
|
||||
private fun hasReturnOrJump(scope: IStatementContainer): Boolean {
|
||||
private fun hasReturnOrJumpOrRts(scope: IStatementContainer): Boolean {
|
||||
class Searcher: IAstVisitor
|
||||
{
|
||||
var count=0
|
||||
@ -227,6 +244,13 @@ internal class AstChecker(private val program: Program,
|
||||
override fun visit(jump: Jump) {
|
||||
count++
|
||||
}
|
||||
|
||||
override fun visit(inlineAssembly: InlineAssembly) {
|
||||
val assembly = inlineAssembly.assembly
|
||||
if(" rti" in assembly || "\trti" in assembly || " rts" in assembly || "\trts" in assembly ||
|
||||
" jmp" in assembly || "\tjmp" in assembly || " bra" in assembly || "\tbra" in assembly )
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
val s=Searcher()
|
||||
@ -260,13 +284,11 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
// subroutine must contain at least one 'return' or 'goto'
|
||||
// (or if it has an asm block, that must contain a 'rts' or 'jmp' or 'bra')
|
||||
if(!hasReturnOrJump(subroutine)) {
|
||||
if (subroutine.amountOfRtsInAsm() == 0) {
|
||||
if (subroutine.returntypes.isNotEmpty()) {
|
||||
// for asm subroutines with an address, no statement check is possible.
|
||||
if (subroutine.asmAddress == null && !subroutine.inline)
|
||||
err("non-inline subroutine has result value(s) and thus must have at least one 'return' or 'goto' in it (or rts/jmp/bra in case of %asm)")
|
||||
}
|
||||
if(!hasReturnOrJumpOrRts(subroutine)) {
|
||||
if (subroutine.returntypes.isNotEmpty()) {
|
||||
// for asm subroutines with an address, no statement check is possible.
|
||||
if (subroutine.asmAddress == null && !subroutine.inline)
|
||||
err("non-inline subroutine has result value(s) and thus must have at least one 'return' or 'goto' in it (or rts/jmp/bra in case of %asm)")
|
||||
}
|
||||
}
|
||||
|
||||
@ -380,13 +402,13 @@ internal class AstChecker(private val program: Program,
|
||||
if(statusFlagsNoCarry.isNotEmpty())
|
||||
err("can only use Carry as status flag parameter")
|
||||
|
||||
} else {
|
||||
// Non-string Pass-by-reference datatypes can not occur as parameters to a subroutine directly
|
||||
// Instead, their reference (address) should be passed (as an UWORD).
|
||||
for(p in subroutine.parameters) {
|
||||
if(p.type in PassByReferenceDatatypes && p.type != DataType.STR) {
|
||||
err("Non-string pass-by-reference types cannot occur as a parameter type directly. Instead, use an uword to receive their address, or access the variable from the outer scope directly.")
|
||||
}
|
||||
}
|
||||
|
||||
// Non-string and non-ubytearray Pass-by-reference datatypes can not occur as parameters to a subroutine directly
|
||||
// Instead, their reference (address) should be passed (as an UWORD).
|
||||
for(p in subroutine.parameters) {
|
||||
if(p.type in PassByReferenceDatatypes && p.type !in listOf(DataType.STR, DataType.ARRAY_UB)) {
|
||||
errors.err("this pass-by-reference type can't be used as a parameter type. Instead, use an uword to receive the address, or access the variable from the outer scope directly.", p.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -411,26 +433,20 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(assignment: Assignment) {
|
||||
if(assignment.value is FunctionCall) {
|
||||
val stmt = (assignment.value as FunctionCall).target.targetStatement(program)
|
||||
if (stmt is Subroutine) {
|
||||
val idt = assignment.target.inferType(program)
|
||||
if(!idt.isKnown) {
|
||||
errors.err("return type mismatch", assignment.value.position)
|
||||
}
|
||||
if(stmt.returntypes.isEmpty() || (stmt.returntypes.size == 1 && stmt.returntypes.single() isNotAssignableTo idt.getOr(DataType.BYTE))) {
|
||||
errors.err("return type mismatch", assignment.value.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val targetDt = assignment.target.inferType(program)
|
||||
val valueDt = assignment.value.inferType(program)
|
||||
if(valueDt.isKnown && !(valueDt isAssignableTo targetDt)) {
|
||||
if(targetDt.isIterable)
|
||||
errors.err("cannot assign value to string or array", assignment.value.position)
|
||||
else if(!(valueDt istype DataType.STR && targetDt istype DataType.UWORD))
|
||||
errors.err("type of value doesn't match target", assignment.value.position)
|
||||
else if(!(valueDt istype DataType.STR && targetDt istype DataType.UWORD)) {
|
||||
if(targetDt.isUnknown) {
|
||||
if(assignment.target.identifier?.targetStatement(program)!=null)
|
||||
errors.err("target datatype is unknown", assignment.target.position)
|
||||
// otherwise, another error about missing symbol is already reported.
|
||||
} else {
|
||||
errors.err("type of value $valueDt doesn't match target $targetDt", assignment.value.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(assignment.value is TypecastExpression) {
|
||||
@ -487,11 +503,11 @@ internal class AstChecker(private val program: Program,
|
||||
} else {
|
||||
val sourceDatatype = assignment.value.inferType(program)
|
||||
if (sourceDatatype.isUnknown) {
|
||||
if (assignment.value !is FunctionCall)
|
||||
if (assignment.value !is FunctionCallExpr)
|
||||
errors.err("assignment value is invalid or has no proper datatype", assignment.value.position)
|
||||
} else {
|
||||
checkAssignmentCompatible(targetDatatype.getOr(DataType.BYTE), assignTarget,
|
||||
sourceDatatype.getOr(DataType.BYTE), assignment.value, assignment.position)
|
||||
checkAssignmentCompatible(targetDatatype.getOr(DataType.BYTE),
|
||||
sourceDatatype.getOr(DataType.BYTE), assignment.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -509,7 +525,7 @@ internal class AstChecker(private val program: Program,
|
||||
fun err(msg: String) = errors.err(msg, decl.position)
|
||||
|
||||
// the initializer value can't refer to the variable itself (recursive definition)
|
||||
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.indexExpr?.referencesIdentifier(decl.name) == true)
|
||||
if(decl.value?.referencesIdentifier(listOf(decl.name)) == true || decl.arraysize?.indexExpr?.referencesIdentifier(listOf(decl.name)) == true)
|
||||
err("recursive var declaration")
|
||||
|
||||
// CONST can only occur on simple types (byte, word, float)
|
||||
@ -518,13 +534,18 @@ internal class AstChecker(private val program: Program,
|
||||
err("const modifier can only be used on numeric types (byte, word, float)")
|
||||
}
|
||||
|
||||
// @zp can only occur on integers
|
||||
if(decl.datatype !in IntegerDatatypes) {
|
||||
if(decl.zeropage==ZeropageWish.PREFER_ZEROPAGE)
|
||||
errors.warn("this datatype can't be placed in zeropage", decl.position)
|
||||
if(decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE)
|
||||
errors.err("this datatype can't be placed in zeropage", decl.position)
|
||||
}
|
||||
|
||||
// FLOATS enabled?
|
||||
if(!compilerOptions.floats && decl.datatype.oneOf(DataType.FLOAT, DataType.ARRAY_F) && decl.type!= VarDeclType.MEMORY)
|
||||
err("floating point used, but that is not enabled via options")
|
||||
|
||||
if(decl.datatype == DataType.FLOAT && (decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE || decl.zeropage==ZeropageWish.PREFER_ZEROPAGE))
|
||||
errors.warn("floating point values won't be placed in Zeropage due to size constraints", decl.position)
|
||||
|
||||
// ARRAY without size specifier MUST have an iterable initializer value
|
||||
if(decl.isArray && decl.arraysize==null) {
|
||||
if(decl.type== VarDeclType.MEMORY)
|
||||
@ -707,7 +728,7 @@ internal class AstChecker(private val program: Program,
|
||||
err("this directive can't be used here")
|
||||
val errormsg = "invalid asmbinary directive, expected arguments: \"filename\" [, offset [, length ] ]"
|
||||
if(directive.args.isEmpty()) err(errormsg)
|
||||
else if(directive.args.isNotEmpty() && directive.args[0].str==null) err(errormsg)
|
||||
else if(directive.args[0].str==null) err(errormsg)
|
||||
else if(directive.args.size>=2 && directive.args[1].int==null) err(errormsg)
|
||||
else if(directive.args.size==3 && directive.args[2].int==null) err(errormsg)
|
||||
else if(directive.args.size>3) err(errormsg)
|
||||
@ -827,7 +848,7 @@ internal class AstChecker(private val program: Program,
|
||||
when(expr.operator){
|
||||
"/", "%" -> {
|
||||
val constvalRight = expr.right.constValue(program)
|
||||
val divisor = constvalRight?.number?.toDouble()
|
||||
val divisor = constvalRight?.number
|
||||
if(divisor==0.0)
|
||||
errors.err("division by zero", expr.right.position)
|
||||
if(expr.operator=="%") {
|
||||
@ -853,6 +874,7 @@ internal class AstChecker(private val program: Program,
|
||||
if(leftDt !in IntegerDatatypes || rightDt !in IntegerDatatypes)
|
||||
errors.err("bitwise operator can only be used on integer operands", expr.right.position)
|
||||
}
|
||||
"in" -> throw FatalAstException("in expression should have been replaced by containmentcheck")
|
||||
}
|
||||
|
||||
if(leftDt !in NumericDatatypes && leftDt != DataType.STR)
|
||||
@ -861,6 +883,14 @@ internal class AstChecker(private val program: Program,
|
||||
errors.err("right operand is not numeric or str", expr.right.position)
|
||||
if(leftDt!=rightDt)
|
||||
errors.err("left and right operands aren't the same type", expr.left.position)
|
||||
|
||||
if(expr.operator !in ComparisonOperators) {
|
||||
if (leftDt == DataType.STR && rightDt == DataType.STR || leftDt in ArrayDatatypes && rightDt in ArrayDatatypes) {
|
||||
// str+str and str*number have already been const evaluated before we get here.
|
||||
errors.err("no computational expressions with strings or arrays are possible", expr.position)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun visit(typecast: TypecastExpression) {
|
||||
@ -906,28 +936,28 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(functionCall: FunctionCall) {
|
||||
override fun visit(functionCallExpr: FunctionCallExpr) {
|
||||
// this function call is (part of) an expression, which should be in a statement somewhere.
|
||||
val stmtOfExpression = findParentNode<Statement>(functionCall)
|
||||
?: throw FatalAstException("cannot determine statement scope of function call expression at ${functionCall.position}")
|
||||
val stmtOfExpression = findParentNode<Statement>(functionCallExpr)
|
||||
?: throw FatalAstException("cannot determine statement scope of function call expression at ${functionCallExpr.position}")
|
||||
|
||||
val targetStatement = checkFunctionOrLabelExists(functionCall.target, stmtOfExpression)
|
||||
val targetStatement = checkFunctionOrLabelExists(functionCallExpr.target, stmtOfExpression)
|
||||
if(targetStatement!=null)
|
||||
checkFunctionCall(targetStatement, functionCall.args, functionCall.position)
|
||||
checkFunctionCall(targetStatement, functionCallExpr.args, functionCallExpr.position)
|
||||
|
||||
// warn about sgn(unsigned) this is likely a mistake
|
||||
if(functionCall.target.nameInSource.last()=="sgn") {
|
||||
val sgnArgType = functionCall.args.first().inferType(program)
|
||||
if(functionCallExpr.target.nameInSource.last()=="sgn") {
|
||||
val sgnArgType = functionCallExpr.args.first().inferType(program)
|
||||
if(sgnArgType istype DataType.UBYTE || sgnArgType istype DataType.UWORD)
|
||||
errors.warn("sgn() of unsigned type is always 0 or 1, this is perhaps not what was intended", functionCall.args.first().position)
|
||||
errors.warn("sgn() of unsigned type is always 0 or 1, this is perhaps not what was intended", functionCallExpr.args.first().position)
|
||||
}
|
||||
|
||||
val error = VerifyFunctionArgTypes.checkTypes(functionCall, program)
|
||||
val error = VerifyFunctionArgTypes.checkTypes(functionCallExpr, program)
|
||||
if(error!=null)
|
||||
errors.err(error, functionCall.position)
|
||||
errors.err(error, functionCallExpr.position)
|
||||
|
||||
// check the functions that return multiple returnvalues.
|
||||
val stmt = functionCall.target.targetStatement(program)
|
||||
val stmt = functionCallExpr.target.targetStatement(program)
|
||||
if (stmt is Subroutine) {
|
||||
if (stmt.returntypes.size > 1) {
|
||||
// Currently, it's only possible to handle ONE (or zero) return values from a subroutine.
|
||||
@ -938,12 +968,9 @@ internal class AstChecker(private val program: Program,
|
||||
// if the asmsub returns multiple values and one of them is via a status register bit,
|
||||
// it *is* possible to handle them by just actually assigning the register value and
|
||||
// dealing with the status bit as just being that, the status bit after the call.
|
||||
val (returnRegisters, returnStatusflags) = stmt.asmReturnvaluesRegisters.partition { rr -> rr.registerOrPair != null }
|
||||
if (returnRegisters.isEmpty() || returnRegisters.size == 1) {
|
||||
if (returnStatusflags.any())
|
||||
errors.warn("this asmsub also has one or more return 'values' in one of the status flags", functionCall.position)
|
||||
} else {
|
||||
errors.err("It's not possible to store the multiple result values of this asmsub call; you should use a small block of custom inline assembly for this.", functionCall.position)
|
||||
val (returnRegisters, _) = stmt.asmReturnvaluesRegisters.partition { rr -> rr.registerOrPair != null }
|
||||
if (returnRegisters.size>1) {
|
||||
errors.err("It's not possible to store the multiple result values of this asmsub call; you should use a small block of custom inline assembly for this.", functionCallExpr.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -951,56 +978,62 @@ internal class AstChecker(private val program: Program,
|
||||
// functions that don't return a value, can't be used in an expression or assignment
|
||||
if(targetStatement is Subroutine) {
|
||||
if(targetStatement.returntypes.isEmpty()) {
|
||||
if(functionCall.parent is Expression || functionCall.parent is Assignment)
|
||||
errors.err("subroutine doesn't return a value", functionCall.position)
|
||||
if(functionCallExpr.parent is Expression || functionCallExpr.parent is Assignment)
|
||||
errors.err("subroutine doesn't return a value", functionCallExpr.position)
|
||||
}
|
||||
}
|
||||
else if(targetStatement is BuiltinFunctionStatementPlaceholder) {
|
||||
if(builtinFunctionReturnType(targetStatement.name, functionCall.args, program).isUnknown) {
|
||||
if(functionCall.parent is Expression || functionCall.parent is Assignment)
|
||||
errors.err("function doesn't return a value", functionCall.position)
|
||||
else if(targetStatement is BuiltinFunctionPlaceholder) {
|
||||
if(builtinFunctionReturnType(targetStatement.name, functionCallExpr.args, program).isUnknown) {
|
||||
if(functionCallExpr.parent is Expression || functionCallExpr.parent is Assignment)
|
||||
errors.err("function doesn't return a value", functionCallExpr.position)
|
||||
}
|
||||
}
|
||||
|
||||
super.visit(functionCall)
|
||||
super.visit(functionCallExpr)
|
||||
}
|
||||
|
||||
override fun visit(functionCallStatement: FunctionCallStatement) {
|
||||
val targetStatement = checkFunctionOrLabelExists(functionCallStatement.target, functionCallStatement)
|
||||
if(targetStatement!=null)
|
||||
if(targetStatement!=null) {
|
||||
checkFunctionCall(targetStatement, functionCallStatement.args, functionCallStatement.position)
|
||||
if (!functionCallStatement.void) {
|
||||
// check for unused return values
|
||||
if (targetStatement is Subroutine && targetStatement.returntypes.isNotEmpty()) {
|
||||
if(targetStatement.returntypes.size==1)
|
||||
errors.warn("result value of subroutine call is discarded (use void?)", functionCallStatement.position)
|
||||
else
|
||||
errors.warn("result values of subroutine call are discarded (use void?)", functionCallStatement.position)
|
||||
}
|
||||
else if(targetStatement is BuiltinFunctionStatementPlaceholder) {
|
||||
val rt = builtinFunctionReturnType(targetStatement.name, functionCallStatement.args, program)
|
||||
if(rt.isKnown)
|
||||
errors.warn("result value of a function call is discarded (use void?)", functionCallStatement.position)
|
||||
}
|
||||
checkUnusedReturnValues(functionCallStatement, targetStatement, program, errors)
|
||||
}
|
||||
|
||||
if(functionCallStatement.target.nameInSource.last() == "sort") {
|
||||
// sort is not supported on float arrays
|
||||
val idref = functionCallStatement.args.singleOrNull() as? IdentifierReference
|
||||
if(idref!=null && idref.inferType(program) istype DataType.ARRAY_F) {
|
||||
errors.err("sorting a floating point array is not supported", functionCallStatement.args.first().position)
|
||||
val funcName = functionCallStatement.target.nameInSource
|
||||
|
||||
if(funcName.size==1) {
|
||||
// check some builtin function calls
|
||||
if(funcName[0] == "sort") {
|
||||
// sort is not supported on float arrays
|
||||
val idref = functionCallStatement.args.singleOrNull() as? IdentifierReference
|
||||
if(idref!=null && idref.inferType(program) istype DataType.ARRAY_F) {
|
||||
errors.err("sorting a floating point array is not supported", functionCallStatement.args.first().position)
|
||||
}
|
||||
}
|
||||
else if(funcName[0] in arrayOf("pop", "popw")) {
|
||||
// can only pop into a variable, that has to have the correct type
|
||||
val idref = functionCallStatement.args[0]
|
||||
if(idref !is IdentifierReference) {
|
||||
if(idref is TypecastExpression) {
|
||||
val passByRef = idref.expression.inferType(program).isPassByReference
|
||||
if(idref.type!=DataType.UWORD || !passByRef)
|
||||
errors.err("invalid argument to pop, must be a variable with the correct type: ${functionCallStatement.args.first()}", functionCallStatement.args.first().position)
|
||||
} else {
|
||||
errors.err("invalid argument to pop, must be a variable with the correct type: ${functionCallStatement.args.first()}", functionCallStatement.args.first().position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(funcName[0] in arrayOf("rol", "ror", "rol2", "ror2", "swap", "sort", "reverse")) {
|
||||
// in-place modification, can't be done on literals
|
||||
if(functionCallStatement.args.any { it !is IdentifierReference && it !is ArrayIndexedExpression && it !is DirectMemoryRead }) {
|
||||
errors.err("invalid argument to a in-place modifying function", functionCallStatement.args.first().position)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if(functionCallStatement.target.nameInSource.last() in arrayOf("rol", "ror", "rol2", "ror2", "swap", "sort", "reverse")) {
|
||||
// in-place modification, can't be done on literals
|
||||
if(functionCallStatement.args.any { it !is IdentifierReference && it !is ArrayIndexedExpression && it !is DirectMemoryRead }) {
|
||||
errors.err("invalid argument to a in-place modifying function", functionCallStatement.args.first().position)
|
||||
}
|
||||
}
|
||||
|
||||
val error =
|
||||
VerifyFunctionArgTypes.checkTypes(functionCallStatement, program)
|
||||
val error = VerifyFunctionArgTypes.checkTypes(functionCallStatement, program)
|
||||
if(error!=null) {
|
||||
errors.err(error, functionCallStatement.args.firstOrNull()?.position ?: functionCallStatement.position)
|
||||
}
|
||||
@ -1012,7 +1045,7 @@ internal class AstChecker(private val program: Program,
|
||||
if(target is Label && args.isNotEmpty())
|
||||
errors.err("cannot use arguments when calling a label", position)
|
||||
|
||||
if(target is BuiltinFunctionStatementPlaceholder) {
|
||||
if(target is BuiltinFunctionPlaceholder) {
|
||||
if(target.name=="swap") {
|
||||
// swap() is a bit weird because this one is translated into an operations directly, instead of being a function call
|
||||
val dt1 = args[0].inferType(program)
|
||||
@ -1048,13 +1081,18 @@ internal class AstChecker(private val program: Program,
|
||||
var ident: IdentifierReference? = null
|
||||
if(arg.value is IdentifierReference)
|
||||
ident = arg.value as IdentifierReference
|
||||
else if(arg.value is FunctionCall) {
|
||||
val fcall = arg.value as FunctionCall
|
||||
else if(arg.value is FunctionCallExpr) {
|
||||
val fcall = arg.value as FunctionCallExpr
|
||||
if(fcall.target.nameInSource == listOf("lsb") || fcall.target.nameInSource == listOf("msb"))
|
||||
ident = fcall.args[0] as? IdentifierReference
|
||||
}
|
||||
if(ident!=null && ident.nameInSource[0] == "cx16" && ident.nameInSource[1].startsWith("r")) {
|
||||
val reg = RegisterOrPair.valueOf(ident.nameInSource[1].uppercase())
|
||||
var regname = ident.nameInSource[1].uppercase()
|
||||
if(regname.endsWith('L'))
|
||||
regname=regname.substring(0, regname.length-1)
|
||||
if(regname.endsWith('s'))
|
||||
regname=regname.substring(0, regname.length-1)
|
||||
val reg = RegisterOrPair.valueOf(regname)
|
||||
val same = params.filter { it.value.registerOrPair==reg }
|
||||
for(s in same) {
|
||||
if(s.index!=arg.index) {
|
||||
@ -1127,11 +1165,11 @@ internal class AstChecker(private val program: Program,
|
||||
super.visit(arrayIndexedExpression)
|
||||
}
|
||||
|
||||
override fun visit(whenStatement: WhenStatement) {
|
||||
if(!whenStatement.condition.inferType(program).isInteger)
|
||||
errors.err("when condition must be an integer value", whenStatement.position)
|
||||
override fun visit(whenStmt: When) {
|
||||
if(!whenStmt.condition.inferType(program).isInteger)
|
||||
errors.err("when condition must be an integer value", whenStmt.position)
|
||||
val tally = mutableSetOf<Int>()
|
||||
for((choices, choiceNode) in whenStatement.choiceValues(program)) {
|
||||
for((choices, choiceNode) in whenStmt.choiceValues(program)) {
|
||||
if(choices!=null) {
|
||||
for (c in choices) {
|
||||
if(c in tally)
|
||||
@ -1142,14 +1180,14 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
if(whenStatement.choices.isEmpty())
|
||||
errors.err("empty when statement", whenStatement.position)
|
||||
if(whenStmt.choices.isEmpty())
|
||||
errors.err("empty when statement", whenStmt.position)
|
||||
|
||||
super.visit(whenStatement)
|
||||
super.visit(whenStmt)
|
||||
}
|
||||
|
||||
override fun visit(whenChoice: WhenChoice) {
|
||||
val whenStmt = whenChoice.parent as WhenStatement
|
||||
val whenStmt = whenChoice.parent as When
|
||||
if(whenChoice.values!=null) {
|
||||
val conditionType = whenStmt.condition.inferType(program)
|
||||
if(!conditionType.isKnown)
|
||||
@ -1169,9 +1207,20 @@ internal class AstChecker(private val program: Program,
|
||||
super.visit(whenChoice)
|
||||
}
|
||||
|
||||
override fun visit(containment: ContainmentCheck) {
|
||||
if(!containment.iterable.inferType(program).isIterable)
|
||||
errors.err("value set for containment check must be an iterable type", containment.iterable.position)
|
||||
if(containment.parent is BinaryExpression)
|
||||
errors.err("containment check is currently not supported in complex expressions", containment.position)
|
||||
|
||||
// TODO check that iterable contains the same types as the element that is searched
|
||||
|
||||
super.visit(containment)
|
||||
}
|
||||
|
||||
private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: Statement): Statement? {
|
||||
when (val targetStatement = target.targetStatement(program)) {
|
||||
is Label, is Subroutine, is BuiltinFunctionStatementPlaceholder -> return targetStatement
|
||||
is Label, is Subroutine, is BuiltinFunctionPlaceholder -> return targetStatement
|
||||
null -> errors.err("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", statement.position)
|
||||
else -> errors.err("cannot call that: ${target.nameInSource.joinToString(".")}", statement.position)
|
||||
}
|
||||
@ -1209,7 +1258,7 @@ internal class AstChecker(private val program: Program,
|
||||
val arraySpecSize = arrayspec.constIndex()
|
||||
val arraySize = value.value.size
|
||||
if(arraySpecSize!=null && arraySpecSize>0) {
|
||||
if(arraySpecSize<1 || arraySpecSize>256)
|
||||
if(arraySpecSize>256)
|
||||
return err("byte array length must be 1-256")
|
||||
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
|
||||
if (arraySize != expectedSize)
|
||||
@ -1228,7 +1277,7 @@ internal class AstChecker(private val program: Program,
|
||||
val arraySpecSize = arrayspec.constIndex()
|
||||
val arraySize = value.value.size
|
||||
if(arraySpecSize!=null && arraySpecSize>0) {
|
||||
if(arraySpecSize<1 || arraySpecSize>128)
|
||||
if(arraySpecSize>128)
|
||||
return err("word array length must be 1-128")
|
||||
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
|
||||
if (arraySize != expectedSize)
|
||||
@ -1247,7 +1296,7 @@ internal class AstChecker(private val program: Program,
|
||||
val arraySize = value.value.size
|
||||
val arraySpecSize = arrayspec.constIndex()
|
||||
if(arraySpecSize!=null && arraySpecSize>0) {
|
||||
if(arraySpecSize < 1 || arraySpecSize>51)
|
||||
if(arraySpecSize>51)
|
||||
return err("float array length must be 1-51")
|
||||
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
|
||||
if (arraySize != expectedSize)
|
||||
@ -1274,7 +1323,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
when (targetDt) {
|
||||
DataType.FLOAT -> {
|
||||
val number=value.number.toDouble()
|
||||
val number=value.number
|
||||
if (number > 1.7014118345e+38 || number < -1.7014118345e+38)
|
||||
return err("value '$number' out of range for MFLPT format")
|
||||
}
|
||||
@ -1350,10 +1399,9 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
private fun checkAssignmentCompatible(targetDatatype: DataType,
|
||||
target: AssignTarget,
|
||||
sourceDatatype: DataType,
|
||||
sourceValue: Expression,
|
||||
position: Position) : Boolean {
|
||||
sourceValue: Expression) : Boolean {
|
||||
val position = sourceValue.position
|
||||
|
||||
if(sourceValue is RangeExpr)
|
||||
errors.err("can't assign a range value to something else", position)
|
||||
@ -1381,15 +1429,25 @@ internal class AstChecker(private val program: Program,
|
||||
errors.err("cannot assign float to ${targetDatatype.name.lowercase()}; possible loss of precision. Suggestion: round the value or revert to integer arithmetic", position)
|
||||
else {
|
||||
if(targetDatatype!=DataType.UWORD && sourceDatatype !in PassByReferenceDatatypes)
|
||||
errors.err(
|
||||
"cannot assign ${sourceDatatype.name.lowercase()} to ${
|
||||
targetDatatype.name.lowercase(
|
||||
Locale.getDefault()
|
||||
)
|
||||
}", position)
|
||||
errors.err("type of value $sourceDatatype doesn't match target $targetDatatype", position)
|
||||
}
|
||||
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
internal fun checkUnusedReturnValues(call: FunctionCallStatement, target: Statement, program: Program, errors: IErrorReporter) {
|
||||
if (!call.void) {
|
||||
// check for unused return values
|
||||
if (target is Subroutine && target.returntypes.isNotEmpty()) {
|
||||
if (target.returntypes.size == 1)
|
||||
errors.warn("result value of subroutine call is discarded (use void?)", call.position)
|
||||
else
|
||||
errors.warn("result values of subroutine call are discarded (use void?)", call.position)
|
||||
} else if (target is BuiltinFunctionPlaceholder) {
|
||||
val rt = builtinFunctionReturnType(target.name, call.args, program)
|
||||
if (rt.isKnown)
|
||||
errors.warn("result value of a function call is discarded (use void?)", call.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,8 @@ import prog8.compilerinterface.IStringEncoding
|
||||
|
||||
|
||||
internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
||||
val parentChecker = ParentNodeChecker()
|
||||
parentChecker.visit(this)
|
||||
val checker = AstChecker(this, errors, compilerOptions)
|
||||
checker.visit(this)
|
||||
}
|
||||
@ -28,8 +30,8 @@ internal fun Program.processAstBeforeAsmGeneration(compilerOptions: CompilationO
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Program.reorderStatements(errors: IErrorReporter) {
|
||||
val reorder = StatementReorderer(this, errors)
|
||||
internal fun Program.reorderStatements(errors: IErrorReporter, options: CompilationOptions) {
|
||||
val reorder = StatementReorderer(this, errors, options)
|
||||
reorder.visit(this)
|
||||
if(errors.noErrors()) {
|
||||
reorder.applyModifications()
|
||||
@ -44,7 +46,7 @@ internal fun Program.charLiteralsToUByteLiterals(enc: IStringEncoding) {
|
||||
override fun after(char: CharLiteral, parent: Node): Iterable<IAstModification> {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
char,
|
||||
NumericLiteralValue(DataType.UBYTE, enc.encodeString(char.value.toString(), char.altEncoding)[0].toInt(), char.position),
|
||||
NumericLiteralValue(DataType.UBYTE, enc.encodeString(char.value.toString(), char.altEncoding)[0].toDouble(), char.position),
|
||||
parent
|
||||
))
|
||||
}
|
||||
@ -53,28 +55,34 @@ internal fun Program.charLiteralsToUByteLiterals(enc: IStringEncoding) {
|
||||
walker.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.addTypecasts(errors: IErrorReporter) {
|
||||
val caster = TypecastsAdder(this, errors)
|
||||
internal fun Program.addTypecasts(errors: IErrorReporter, options: CompilationOptions) {
|
||||
val caster = TypecastsAdder(this, options, errors)
|
||||
caster.visit(this)
|
||||
caster.applyModifications()
|
||||
}
|
||||
|
||||
fun Program.desugaring(errors: IErrorReporter): Int {
|
||||
val desugar = CodeDesugarer(this, errors)
|
||||
desugar.visit(this)
|
||||
return desugar.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.verifyFunctionArgTypes() {
|
||||
val fixer = VerifyFunctionArgTypes(this)
|
||||
fixer.visit(this)
|
||||
}
|
||||
|
||||
internal fun Program.preprocessAst() {
|
||||
val transforms = AstPreprocessor()
|
||||
internal fun Program.preprocessAst(program: Program, errors: IErrorReporter) {
|
||||
val transforms = AstPreprocessor(program, errors)
|
||||
transforms.visit(this)
|
||||
var mods = transforms.applyModifications()
|
||||
while(mods>0)
|
||||
mods = transforms.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.checkIdentifiers(errors: IErrorReporter, options: CompilationOptions) {
|
||||
internal fun Program.checkIdentifiers(errors: IErrorReporter, program: Program, options: CompilationOptions) {
|
||||
|
||||
val checker2 = AstIdentifiersChecker(errors, options.compTarget)
|
||||
val checker2 = AstIdentifiersChecker(errors, program, options.compTarget)
|
||||
checker2.visit(this)
|
||||
|
||||
if(errors.noErrors()) {
|
||||
@ -83,7 +91,8 @@ internal fun Program.checkIdentifiers(errors: IErrorReporter, options: Compilati
|
||||
transforms.applyModifications()
|
||||
val lit2decl = LiteralsToAutoVars(this)
|
||||
lit2decl.visit(this)
|
||||
lit2decl.applyModifications()
|
||||
if(errors.noErrors())
|
||||
lit2decl.applyModifications()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,10 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.FunctionCallExpr
|
||||
import prog8.ast.expressions.StringLiteralValue
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
@ -8,7 +12,9 @@ import prog8.compilerinterface.BuiltinFunctions
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
|
||||
internal class AstIdentifiersChecker(private val errors: IErrorReporter, private val compTarget: ICompilationTarget) : IAstVisitor {
|
||||
internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
||||
private val program: Program,
|
||||
private val compTarget: ICompilationTarget) : IAstVisitor {
|
||||
private var blocks = mutableMapOf<String, Block>()
|
||||
|
||||
private fun nameError(name: String, position: Position, existing: Statement) {
|
||||
@ -75,7 +81,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter, private
|
||||
val paramsToCheck = paramNames.intersect(namesInSub)
|
||||
for(name in paramsToCheck) {
|
||||
val symbol = subroutine.searchSymbol(name)
|
||||
if(symbol!=null && symbol.position != subroutine.position)
|
||||
if(symbol!=null && (symbol as? VarDecl)?.subroutineParameter==null)
|
||||
nameError(name, symbol.position, subroutine)
|
||||
}
|
||||
|
||||
@ -121,4 +127,46 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter, private
|
||||
|
||||
super.visit(string)
|
||||
}
|
||||
|
||||
override fun visit(functionCallExpr: FunctionCallExpr) = visitFunctionCall(functionCallExpr)
|
||||
override fun visit(functionCallStatement: FunctionCallStatement) = visitFunctionCall(functionCallStatement)
|
||||
|
||||
private fun visitFunctionCall(call: IFunctionCall) {
|
||||
when (val target = call.target.targetStatement(program)) {
|
||||
is Subroutine -> {
|
||||
if(call.args.size != target.parameters.size) {
|
||||
val pos = (if(call.args.any()) call.args[0] else (call as Node)).position
|
||||
errors.err("invalid number of arguments", pos)
|
||||
}
|
||||
}
|
||||
is BuiltinFunctionPlaceholder -> {
|
||||
val func = BuiltinFunctions.getValue(target.name)
|
||||
if(call.args.size != func.parameters.size) {
|
||||
val pos = (if(call.args.any()) call.args[0] else (call as Node)).position
|
||||
errors.err("invalid number of arguments", pos)
|
||||
}
|
||||
if(func.name=="memory") {
|
||||
val name = call.args[0] as? StringLiteralValue
|
||||
if(name!=null) {
|
||||
val processed = name.value.map {
|
||||
if(it.isLetterOrDigit())
|
||||
it
|
||||
else
|
||||
'_'
|
||||
}.joinToString("")
|
||||
call.args[0] = StringLiteralValue(processed, false, name.position)
|
||||
call.args[0].linkParents(call as Node)
|
||||
}
|
||||
}
|
||||
}
|
||||
is Label -> {
|
||||
if(call.args.isNotEmpty()) {
|
||||
val pos = (if(call.args.any()) call.args[0] else (call as Node)).position
|
||||
errors.err("cannot use arguments when calling a label", pos)
|
||||
}
|
||||
}
|
||||
null -> {}
|
||||
else -> errors.err("cannot call this as a subroutine or function", call.target.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,49 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.base.NumericDatatypes
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.statements.AnonymousScope
|
||||
import prog8.ast.statements.AssignTarget
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
|
||||
|
||||
class AstPreprocessor : AstWalker() {
|
||||
class AstPreprocessor(val program: Program, val errors: IErrorReporter) : AstWalker() {
|
||||
|
||||
override fun after(range: RangeExpr, parent: Node): Iterable<IAstModification> {
|
||||
// has to be done before the constant folding, otherwise certain checks there will fail on invalid range sizes
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
if(range.from !is NumericLiteralValue) {
|
||||
try {
|
||||
val constval = range.from.constValue(program)
|
||||
if (constval != null)
|
||||
modifications += IAstModification.ReplaceNode(range.from, constval, range)
|
||||
} catch (x: SyntaxError) {
|
||||
// syntax errors will be reported later
|
||||
}
|
||||
}
|
||||
if(range.to !is NumericLiteralValue) {
|
||||
try {
|
||||
val constval = range.to.constValue(program)
|
||||
if(constval!=null)
|
||||
modifications += IAstModification.ReplaceNode(range.to, constval, range)
|
||||
} catch (x: SyntaxError) {
|
||||
// syntax errors will be reported later
|
||||
}
|
||||
}
|
||||
if(range.step !is NumericLiteralValue) {
|
||||
try {
|
||||
val constval = range.step.constValue(program)
|
||||
if(constval!=null)
|
||||
modifications += IAstModification.ReplaceNode(range.step, constval, range)
|
||||
} catch (x: SyntaxError) {
|
||||
// syntax errors will be reported later
|
||||
}
|
||||
}
|
||||
return modifications
|
||||
}
|
||||
|
||||
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
|
||||
@ -45,4 +76,13 @@ class AstPreprocessor : AstWalker() {
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
// this has to be done here becuse otherwise the string / range literal values will have been replaced by variables
|
||||
if(expr.operator=="in") {
|
||||
val containment = ContainmentCheck(expr.left, expr.right, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, containment, parent))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
|
@ -20,13 +20,13 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
|
||||
val symbolsInSub = subroutine.allDefinedSymbols
|
||||
val namesInSub = symbolsInSub.map{ it.first }.toSet()
|
||||
if(subroutine.asmAddress==null) {
|
||||
if(subroutine.asmParameterRegisters.isEmpty() && subroutine.parameters.isNotEmpty()) {
|
||||
if(!subroutine.isAsmSubroutine && subroutine.parameters.isNotEmpty()) {
|
||||
val vars = subroutine.statements.filterIsInstance<VarDecl>().map { it.name }.toSet()
|
||||
if(!vars.containsAll(subroutine.parameters.map{it.name})) {
|
||||
return subroutine.parameters
|
||||
.filter { it.name !in namesInSub }
|
||||
.map {
|
||||
val vardecl = ParameterVarDecl(it.name, it.type, subroutine.position)
|
||||
val vardecl = VarDecl.fromParameter(it)
|
||||
IAstModification.InsertFirst(vardecl, subroutine)
|
||||
}
|
||||
}
|
||||
|
144
compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt
Normal file
144
compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt
Normal file
@ -0,0 +1,144 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.IStatementContainer
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.ParentSentinel
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compilerinterface.*
|
||||
|
||||
|
||||
internal class CodeDesugarer(val program: Program, private val errors: IErrorReporter) : AstWalker() {
|
||||
|
||||
// Some more code shuffling to simplify the Ast that the codegenerator has to process.
|
||||
// Several changes have already been done by the StatementReorderer !
|
||||
// But the ones here are simpler and are repeated once again after all optimization steps
|
||||
// have been performed (because those could re-introduce nodes that have to be desugared)
|
||||
//
|
||||
// List of modifications:
|
||||
// - replace 'break' statements by a goto + generated after label.
|
||||
// - replace while and do-until loops by just jumps.
|
||||
// - replace peek() and poke() by direct memory accesses.
|
||||
|
||||
|
||||
private var generatedLabelSequenceNumber: Int = 0
|
||||
private val generatedLabelPrefix = "prog8_label_"
|
||||
|
||||
private fun makeLabel(postfix: String, position: Position): Label {
|
||||
generatedLabelSequenceNumber++
|
||||
return Label("${generatedLabelPrefix}${generatedLabelSequenceNumber}_$postfix", position)
|
||||
}
|
||||
|
||||
private fun jumpLabel(label: Label): Jump {
|
||||
val ident = IdentifierReference(listOf(label.name), label.position)
|
||||
return Jump(null, ident, null, label.position)
|
||||
}
|
||||
|
||||
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
|
||||
fun jumpAfter(stmt: Statement): Iterable<IAstModification> {
|
||||
val label = makeLabel("after", breakStmt.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(breakStmt, jumpLabel(label), parent),
|
||||
IAstModification.InsertAfter(stmt, label, stmt.parent as IStatementContainer)
|
||||
)
|
||||
}
|
||||
|
||||
var partof = parent
|
||||
while(true) {
|
||||
when (partof) {
|
||||
is Subroutine, is Block, is ParentSentinel -> {
|
||||
errors.err("break in wrong scope", breakStmt.position)
|
||||
return noModifications
|
||||
}
|
||||
is ForLoop,
|
||||
is RepeatLoop,
|
||||
is UntilLoop,
|
||||
is WhileLoop -> return jumpAfter(partof as Statement)
|
||||
else -> partof = partof.parent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
|
||||
/*
|
||||
do { STUFF } until CONDITION
|
||||
===>
|
||||
_loop:
|
||||
STUFF
|
||||
if not CONDITION
|
||||
goto _loop
|
||||
*/
|
||||
val pos = untilLoop.position
|
||||
val loopLabel = makeLabel("untilloop", pos)
|
||||
val notCondition = PrefixExpression("not", untilLoop.condition, pos)
|
||||
val replacement = AnonymousScope(mutableListOf(
|
||||
loopLabel,
|
||||
untilLoop.body,
|
||||
IfElse(notCondition,
|
||||
AnonymousScope(mutableListOf(jumpLabel(loopLabel)), pos),
|
||||
AnonymousScope(mutableListOf(), pos),
|
||||
pos)
|
||||
), pos)
|
||||
return listOf(IAstModification.ReplaceNode(untilLoop, replacement, parent))
|
||||
}
|
||||
|
||||
override fun after(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
|
||||
/*
|
||||
while CONDITION { STUFF }
|
||||
==>
|
||||
_whileloop:
|
||||
if NOT CONDITION goto _after
|
||||
STUFF
|
||||
goto _whileloop
|
||||
_after:
|
||||
*/
|
||||
val pos = whileLoop.position
|
||||
val loopLabel = makeLabel("whileloop", pos)
|
||||
val afterLabel = makeLabel("afterwhile", pos)
|
||||
val notCondition = PrefixExpression("not", whileLoop.condition, pos)
|
||||
val replacement = AnonymousScope(mutableListOf(
|
||||
loopLabel,
|
||||
IfElse(notCondition,
|
||||
AnonymousScope(mutableListOf(jumpLabel(afterLabel)), pos),
|
||||
AnonymousScope(mutableListOf(), pos),
|
||||
pos),
|
||||
whileLoop.body,
|
||||
jumpLabel(loopLabel),
|
||||
afterLabel
|
||||
), pos)
|
||||
return listOf(IAstModification.ReplaceNode(whileLoop, replacement, parent))
|
||||
}
|
||||
|
||||
override fun before(functionCallStatement: FunctionCallStatement, parent: Node) =
|
||||
before(functionCallStatement as IFunctionCall, parent, functionCallStatement.position)
|
||||
|
||||
override fun before(functionCallExpr: FunctionCallExpr, parent: Node) =
|
||||
before(functionCallExpr as IFunctionCall, parent, functionCallExpr.position)
|
||||
|
||||
private fun before(functionCall: IFunctionCall, parent: Node, position: Position): Iterable<IAstModification> {
|
||||
if(functionCall.target.nameInSource==listOf("peek")) {
|
||||
// peek(a) is synonymous with @(a)
|
||||
val memread = DirectMemoryRead(functionCall.args.single(), position)
|
||||
return listOf(IAstModification.ReplaceNode(functionCall as Node, memread, parent))
|
||||
}
|
||||
if(functionCall.target.nameInSource==listOf("poke")) {
|
||||
// poke(a, v) is synonymous with @(a) = v
|
||||
val tgt = AssignTarget(null, null, DirectMemoryWrite(functionCall.args[0], position), position)
|
||||
val assign = Assignment(tgt, functionCall.args[1], position)
|
||||
return listOf(IAstModification.ReplaceNode(functionCall as Node, assign, parent))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(expr.operator=="in") {
|
||||
println("IN-TEST: $expr\n in: $parent")
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.IStatementContainer
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.expressions.ArrayLiteralValue
|
||||
import prog8.ast.expressions.ContainmentCheck
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.StringLiteralValue
|
||||
import prog8.ast.statements.VarDecl
|
||||
@ -16,7 +16,7 @@ import prog8.ast.walk.IAstModification
|
||||
internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
|
||||
|
||||
override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||
if(string.parent !is VarDecl && string.parent !is WhenChoice) {
|
||||
if(string.parent !is VarDecl && string.parent !is WhenChoice && string.parent !is ContainmentCheck) {
|
||||
// replace the literal string by an identifier reference to the interned string
|
||||
val scopedName = program.internString(string)
|
||||
val identifier = IdentifierReference(scopedName, string.position)
|
||||
@ -28,26 +28,27 @@ internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
|
||||
override fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||
val vardecl = array.parent as? VarDecl
|
||||
if(vardecl!=null) {
|
||||
// adjust the datatype of the array (to an educated guess)
|
||||
// adjust the datatype of the array (to an educated guess from the vardecl type)
|
||||
val arrayDt = array.type
|
||||
if(arrayDt isnot vardecl.datatype) {
|
||||
val cast = array.cast(vardecl.datatype)
|
||||
if (cast != null && cast !== array)
|
||||
if(cast!=null && cast !== array)
|
||||
return listOf(IAstModification.ReplaceNode(vardecl.value!!, cast, vardecl))
|
||||
}
|
||||
} else {
|
||||
if(array.parent is ContainmentCheck)
|
||||
return noModifications
|
||||
|
||||
val arrayDt = array.guessDatatype(program)
|
||||
if(arrayDt.isKnown) {
|
||||
// this array literal is part of an expression, turn it into an identifier reference
|
||||
// turn the array literal it into an identifier reference
|
||||
val litval2 = array.cast(arrayDt.getOr(DataType.UNDEFINED))
|
||||
if(litval2!=null) {
|
||||
if(array.parent !is IStatementContainer)
|
||||
return noModifications
|
||||
val vardecl2 = VarDecl.createAuto(litval2)
|
||||
val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(array, identifier, parent),
|
||||
IAstModification.InsertFirst(vardecl2, array.parent as IStatementContainer)
|
||||
IAstModification.ReplaceNode(array, identifier, parent),
|
||||
IAstModification.InsertFirst(vardecl2, array.definingScope)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
247
compiler/src/prog8/compiler/astprocessing/ParentNodeChecker.kt
Normal file
247
compiler/src/prog8/compiler/astprocessing/ParentNodeChecker.kt
Normal file
@ -0,0 +1,247 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
|
||||
|
||||
internal class ParentNodeChecker: AstWalker() {
|
||||
|
||||
override fun before(addressOf: AddressOf, parent: Node): Iterable<IAstModification> {
|
||||
if(addressOf.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $addressOf")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||
if(array.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $array")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(arrayIndexedExpression.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $arrayIndexedExpression")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(assignTarget: AssignTarget, parent: Node): Iterable<IAstModification> {
|
||||
if(assignTarget.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $assignTarget")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
if(assignment.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $assignment")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(block: Block, parent: Node): Iterable<IAstModification> {
|
||||
if(block.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $block")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(branch: Branch, parent: Node): Iterable<IAstModification> {
|
||||
if(branch.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $branch")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
|
||||
if(breakStmt.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $breakStmt")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
if(decl.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $decl")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(directive: Directive, parent: Node): Iterable<IAstModification> {
|
||||
if(directive.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $directive")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(expr.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $expr")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(expr: PrefixExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(expr.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $expr")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
|
||||
if(forLoop.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $forLoop")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> {
|
||||
if(repeatLoop.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $repeatLoop")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
|
||||
if(identifier.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $identifier")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(ifElse: IfElse, parent: Node): Iterable<IAstModification> {
|
||||
if(ifElse.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $ifElse")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(inlineAssembly: InlineAssembly, parent: Node): Iterable<IAstModification> {
|
||||
if(inlineAssembly.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $inlineAssembly")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(jump: Jump, parent: Node): Iterable<IAstModification> {
|
||||
if(jump.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $jump")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(gosub: GoSub, parent: Node): Iterable<IAstModification> {
|
||||
if(gosub.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $gosub")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(label: Label, parent: Node): Iterable<IAstModification> {
|
||||
if(label.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $label")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
|
||||
if(memread.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $memread")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> {
|
||||
if(memwrite.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $memwrite")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(module: Module, parent: Node): Iterable<IAstModification> {
|
||||
if(module.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $module")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(numLiteral: NumericLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||
if(numLiteral.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $numLiteral")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(postIncrDecr: PostIncrDecr, parent: Node): Iterable<IAstModification> {
|
||||
if(postIncrDecr.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $postIncrDecr")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(range: RangeExpr, parent: Node): Iterable<IAstModification> {
|
||||
if(range.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $range")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
|
||||
if(untilLoop.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $untilLoop")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> {
|
||||
if(returnStmt.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $returnStmt")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(char: CharLiteral, parent: Node): Iterable<IAstModification> {
|
||||
if(char.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $char")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(string: StringLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||
if(string.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $string")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
if(subroutine.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $subroutine")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(typecast.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $typecast")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> {
|
||||
if(whenChoice.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $whenChoice")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(whenStmt: When, parent: Node): Iterable<IAstModification> {
|
||||
if(whenStmt.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $whenStmt")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
|
||||
if(whileLoop.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $whileLoop")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(functionCallExpr: FunctionCallExpr, parent: Node): Iterable<IAstModification> {
|
||||
if(functionCallExpr.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $functionCallExpr")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
if(functionCallStatement.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $functionCallStatement")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(nop: Nop, parent: Node): Iterable<IAstModification> {
|
||||
if(nop.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $nop")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
if(scope.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $scope")
|
||||
return noModifications
|
||||
}
|
||||
}
|
@ -7,10 +7,12 @@ import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compilerinterface.BuiltinFunctions
|
||||
import prog8.compilerinterface.CompilationOptions
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
|
||||
|
||||
internal class StatementReorderer(val program: Program, val errors: IErrorReporter) : AstWalker() {
|
||||
internal class StatementReorderer(val program: Program,
|
||||
val errors: IErrorReporter,
|
||||
private val options: CompilationOptions) : AstWalker() {
|
||||
// Reorders the statements in a way the compiler needs.
|
||||
// - 'main' block must be the very first statement UNLESS it has an address set.
|
||||
// - library blocks are put last.
|
||||
@ -21,12 +23,13 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
||||
// - in-place assignments are reordered a bit so that they are mostly of the form A = A <operator> <rest>
|
||||
// - sorts the choices in when statement.
|
||||
// - insert AddressOf (&) expression where required (string params to a UWORD function param etc.).
|
||||
// - replace subroutine calls (statement) by just assigning the arguments to the parameters and then a GoSub to the routine.
|
||||
|
||||
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option")
|
||||
|
||||
override fun after(module: Module, parent: Node): Iterable<IAstModification> {
|
||||
val (blocks, other) = module.statements.partition { it is Block }
|
||||
module.statements = other.asSequence().plus(blocks.sortedBy { (it as Block).address ?: Int.MAX_VALUE }).toMutableList()
|
||||
module.statements = other.asSequence().plus(blocks.sortedBy { (it as Block).address ?: UInt.MAX_VALUE }).toMutableList()
|
||||
|
||||
val mainBlock = module.statements.filterIsInstance<Block>().firstOrNull { it.name=="main" }
|
||||
if(mainBlock!=null && mainBlock.address==null) {
|
||||
@ -53,15 +56,20 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
||||
decl.value = null
|
||||
if(decl.name.startsWith("retval_interm_") && decl.definingScope.name=="prog8_lib") {
|
||||
// no need to zero out the special internal returnvalue intermediates.
|
||||
// TODO these variables are no longer used???
|
||||
return noModifications
|
||||
}
|
||||
val nextStmt = decl.nextSibling()
|
||||
val nextAssign = nextStmt as? Assignment
|
||||
if(nextAssign!=null && !nextAssign.isAugmentable) {
|
||||
val target = nextAssign.target.identifier?.targetStatement(program)
|
||||
if(target === decl) {
|
||||
// an initializer assignment for a vardecl is already here
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
val nextFor = nextStmt as? ForLoop
|
||||
val hasNextForWithThisLoopvar = nextFor?.loopVar?.nameInSource==listOf(decl.name)
|
||||
val hasNextAssignment = nextAssign!=null && nextAssign.target isSameAs IdentifierReference(listOf(decl.name), Position.DUMMY)
|
||||
if (!hasNextAssignment && !hasNextForWithThisLoopvar) {
|
||||
if (!hasNextForWithThisLoopvar) {
|
||||
// Add assignment to initialize with zero
|
||||
// Note: for block-level vars, this will introduce assignments in the block scope. These have to be dealt with correctly later.
|
||||
val identifier = IdentifierReference(listOf(decl.name), decl.position)
|
||||
@ -118,14 +126,41 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
||||
}
|
||||
}
|
||||
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
|
||||
val subs = subroutine.statements.filterIsInstance<Subroutine>()
|
||||
if(subs.isNotEmpty()) {
|
||||
// all subroutines defined within this subroutine are moved to the end
|
||||
return subs.map { IAstModification.Remove(it, subroutine) } +
|
||||
subs.map { IAstModification.InsertLast(it, subroutine) }
|
||||
// NOTE: this doesn't check if this has already been done!!!
|
||||
modifications +=
|
||||
subs.map { IAstModification.Remove(it, subroutine) } +
|
||||
subs.map { IAstModification.InsertLast(it, subroutine) }
|
||||
}
|
||||
|
||||
return noModifications
|
||||
// change 'str' and 'ubyte[]' parameters into 'uword' (just treat it as an address)
|
||||
val stringParams = subroutine.parameters.filter { it.type==DataType.STR || it.type==DataType.ARRAY_UB }
|
||||
val parameterChanges = stringParams.map {
|
||||
val uwordParam = SubroutineParameter(it.name, DataType.UWORD, it.position)
|
||||
IAstModification.ReplaceNode(it, uwordParam, subroutine)
|
||||
}
|
||||
|
||||
val varsChanges = mutableListOf<IAstModification>()
|
||||
if(!subroutine.isAsmSubroutine) {
|
||||
val stringParamsByNames = stringParams.associateBy { it.name }
|
||||
varsChanges +=
|
||||
if(stringParamsByNames.isNotEmpty()) {
|
||||
subroutine.statements
|
||||
.filterIsInstance<VarDecl>()
|
||||
.filter { it.subroutineParameter!=null && it.name in stringParamsByNames }
|
||||
.map {
|
||||
val newvar = VarDecl(it.type, DataType.UWORD, it.zeropage, null, it.name, null, false, true, it.sharedWithAsm, stringParamsByNames.getValue(it.name), it.position)
|
||||
IAstModification.ReplaceNode(it, newvar, subroutine)
|
||||
}
|
||||
}
|
||||
else emptyList()
|
||||
}
|
||||
|
||||
return modifications + parameterChanges + varsChanges
|
||||
}
|
||||
|
||||
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||
@ -142,7 +177,7 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
||||
// ConstValue <associativeoperator> X --> X <associativeoperator> ConstValue
|
||||
// (this should be done by the ExpressionSimplifier when optimizing is enabled,
|
||||
// but the current assembly code generator for IF statements now also depends on it, so we do it here regardless of optimization.)
|
||||
if (expr.left.constValue(program) != null && expr.operator in associativeOperators && expr.right.constValue(program) == null)
|
||||
if (expr.left.constValue(program) != null && expr.operator in AssociativeOperators && expr.right.constValue(program) == null)
|
||||
return listOf(IAstModification.SwapOperands(expr))
|
||||
|
||||
// when using a simple bit shift and assigning it to a variable of a different type,
|
||||
@ -170,17 +205,19 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
||||
is Subroutine -> {
|
||||
val paramType = callee.parameters[argnum].type
|
||||
if(leftDt isAssignableTo paramType) {
|
||||
val cast = TypecastExpression(expr.left, paramType, true, parent.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
||||
val (replaced, cast) = expr.left.typecastTo(paramType, leftDt.getOr(DataType.UNDEFINED), true)
|
||||
if(replaced)
|
||||
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
||||
}
|
||||
}
|
||||
is BuiltinFunctionStatementPlaceholder -> {
|
||||
is BuiltinFunctionPlaceholder -> {
|
||||
val func = BuiltinFunctions.getValue(callee.name)
|
||||
val paramTypes = func.parameters[argnum].possibleDatatypes
|
||||
for(type in paramTypes) {
|
||||
if(leftDt isAssignableTo type) {
|
||||
val cast = TypecastExpression(expr.left, type, true, parent.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
||||
val (replaced, cast) = expr.left.typecastTo(type, leftDt.getOr(DataType.UNDEFINED), true)
|
||||
if(replaced)
|
||||
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -190,18 +227,18 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
||||
else -> return noModifications
|
||||
}
|
||||
}
|
||||
else if(expr.operator in logicalOperators) {
|
||||
else if(expr.operator in LogicalOperators) {
|
||||
// make sure that logical expressions like "var and other-logical-expression
|
||||
// is rewritten as "var!=0 and other-logical-expression", to avoid bitwise boolean and
|
||||
// generating the wrong results later
|
||||
|
||||
fun wrapped(expr: Expression): Expression =
|
||||
BinaryExpression(expr, "!=", NumericLiteralValue(DataType.UBYTE, 0, expr.position), expr.position)
|
||||
BinaryExpression(expr, "!=", NumericLiteralValue(DataType.UBYTE, 0.0, expr.position), expr.position)
|
||||
|
||||
fun isLogicalExpr(expr: Expression?): Boolean {
|
||||
if(expr is BinaryExpression && expr.operator in (logicalOperators + comparisonOperators))
|
||||
if(expr is BinaryExpression && expr.operator in (LogicalOperators + ComparisonOperators))
|
||||
return true
|
||||
if(expr is PrefixExpression && expr.operator in logicalOperators)
|
||||
if(expr is PrefixExpression && expr.operator in LogicalOperators)
|
||||
return true
|
||||
return false
|
||||
}
|
||||
@ -225,12 +262,19 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
|
||||
val choices = whenStatement.choiceValues(program).sortedBy {
|
||||
override fun after(whenStmt: When, parent: Node): Iterable<IAstModification> {
|
||||
val lastChoiceValues = whenStmt.choices.lastOrNull()?.values
|
||||
if(lastChoiceValues?.isNotEmpty()==true) {
|
||||
val elseChoice = whenStmt.choices.indexOfFirst { it.values==null || it.values?.isEmpty()==true }
|
||||
if(elseChoice>=0)
|
||||
errors.err("else choice must be the last one", whenStmt.choices[elseChoice].position)
|
||||
}
|
||||
|
||||
val choices = whenStmt.choiceValues(program).sortedBy {
|
||||
it.first?.first() ?: Int.MAX_VALUE
|
||||
}
|
||||
whenStatement.choices.clear()
|
||||
choices.mapTo(whenStatement.choices) { it.second }
|
||||
whenStmt.choices.clear()
|
||||
choices.mapTo(whenStmt.choices) { it.second }
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@ -258,7 +302,7 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
||||
return noModifications
|
||||
}
|
||||
|
||||
if(binExpr.operator in associativeOperators) {
|
||||
if(binExpr.operator in AssociativeOperators) {
|
||||
if (binExpr.right isSameAs assignment.target) {
|
||||
// A = v <associative-operator> A ==> A = A <associative-operator> v
|
||||
return listOf(IAstModification.SwapOperands(binExpr))
|
||||
@ -334,4 +378,122 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
||||
)
|
||||
return listOf(IAstModification.ReplaceNode(assign, memcopy, assign.parent))
|
||||
}
|
||||
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
val function = functionCallStatement.target.targetStatement(program)!!
|
||||
checkUnusedReturnValues(functionCallStatement, function, program, errors)
|
||||
if(function is Subroutine) {
|
||||
if(function.inline)
|
||||
return noModifications
|
||||
return if(function.isAsmSubroutine)
|
||||
replaceCallAsmSubStatementWithGosub(function, functionCallStatement, parent)
|
||||
else
|
||||
replaceCallSubStatementWithGosub(function, functionCallStatement, parent)
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun replaceCallSubStatementWithGosub(function: Subroutine, call: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
if(function.parameters.isEmpty()) {
|
||||
// 0 params -> just GoSub
|
||||
return listOf(IAstModification.ReplaceNode(call, GoSub(null, call.target, null, call.position), parent))
|
||||
}
|
||||
|
||||
if(function.parameters.size==1) {
|
||||
if(function.parameters[0].type in IntegerDatatypes) {
|
||||
// optimization: 1 integer param is passed via register(s) directly, not by assignment to param variable
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
else if(function.parameters.size==2) {
|
||||
if(function.parameters[0].type in ByteDatatypes && function.parameters[1].type in ByteDatatypes) {
|
||||
// optimization: 2 simple byte param is passed via 2 registers directly, not by assignment to param variables
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
|
||||
val assignParams =
|
||||
function.parameters.zip(call.args).map {
|
||||
var argumentValue = it.second
|
||||
val paramIdentifier = IdentifierReference(function.scopedName + it.first.name, argumentValue.position)
|
||||
val argDt = argumentValue.inferType(program).getOrElse { throw FatalAstException("invalid dt") }
|
||||
if(argDt in ArrayDatatypes) {
|
||||
// pass the address of the array instead
|
||||
if(argumentValue is IdentifierReference)
|
||||
argumentValue = AddressOf(argumentValue, argumentValue.position)
|
||||
}
|
||||
Assignment(AssignTarget(paramIdentifier, null, null, argumentValue.position), argumentValue, argumentValue.position)
|
||||
}
|
||||
val scope = AnonymousScope(assignParams.toMutableList(), call.position)
|
||||
scope.statements += GoSub(null, call.target, null, call.position)
|
||||
return listOf(IAstModification.ReplaceNode(call, scope, parent))
|
||||
}
|
||||
|
||||
private fun replaceCallAsmSubStatementWithGosub(function: Subroutine, call: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
if(function.parameters.isEmpty()) {
|
||||
// 0 params -> just GoSub
|
||||
val scope = AnonymousScope(mutableListOf(), call.position)
|
||||
if(function.shouldSaveX()) {
|
||||
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rsavex"), call.position), mutableListOf(), true, call.position)
|
||||
}
|
||||
scope.statements += GoSub(null, call.target, null, call.position)
|
||||
if(function.shouldSaveX()) {
|
||||
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rrestorex"), call.position), mutableListOf(), true, call.position)
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(call, scope, parent))
|
||||
} else if(!options.compTarget.asmsubArgsHaveRegisterClobberRisk(call.args, function.asmParameterRegisters)) {
|
||||
// No register clobber risk, let the asmgen assign values to the registers directly.
|
||||
// this is more efficient than first evaluating them to the stack.
|
||||
// As complex expressions will be flagged as a clobber-risk, these will be simplified below.
|
||||
return noModifications
|
||||
} else {
|
||||
// clobber risk; evaluate the arguments on the CPU stack first (in reverse order)...
|
||||
val argOrder = options.compTarget.asmsubArgsEvalOrder(function)
|
||||
val scope = AnonymousScope(mutableListOf(), call.position)
|
||||
if(function.shouldSaveX()) {
|
||||
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rsavex"), call.position), mutableListOf(), true, call.position)
|
||||
}
|
||||
argOrder.reversed().forEach {
|
||||
val arg = call.args[it]
|
||||
val param = function.parameters[it]
|
||||
scope.statements += pushCall(arg, param.type, arg.position)
|
||||
}
|
||||
// ... and pop them off again into the registers.
|
||||
argOrder.forEach {
|
||||
val param = function.parameters[it]
|
||||
val targetName = function.scopedName + param.name
|
||||
scope.statements += popCall(targetName, param.type, call.position)
|
||||
}
|
||||
scope.statements += GoSub(null, call.target, null, call.position)
|
||||
if(function.shouldSaveX()) {
|
||||
scope.statements += FunctionCallStatement(IdentifierReference(listOf("rrestorex"), call.position), mutableListOf(), true, call.position)
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(call, scope, parent))
|
||||
}
|
||||
}
|
||||
|
||||
private fun popCall(targetName: List<String>, dt: DataType, position: Position): FunctionCallStatement {
|
||||
return FunctionCallStatement(
|
||||
IdentifierReference(listOf(if(dt in ByteDatatypes) "pop" else "popw"), position),
|
||||
mutableListOf(IdentifierReference(targetName, position)),
|
||||
true, position
|
||||
)
|
||||
}
|
||||
|
||||
private fun pushCall(value: Expression, dt: DataType, position: Position): FunctionCallStatement {
|
||||
val pushvalue = when(dt) {
|
||||
DataType.UBYTE, DataType.UWORD -> value
|
||||
in PassByReferenceDatatypes -> value
|
||||
DataType.BYTE -> TypecastExpression(value, DataType.UBYTE, true, position)
|
||||
DataType.WORD -> TypecastExpression(value, DataType.UWORD, true, position)
|
||||
else -> throw FatalAstException("invalid dt $dt $value")
|
||||
}
|
||||
|
||||
return FunctionCallStatement(
|
||||
IdentifierReference(listOf(if(dt in ByteDatatypes) "push" else "pushw"), position),
|
||||
mutableListOf(pushvalue),
|
||||
true, position
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,10 +9,11 @@ import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compilerinterface.BuiltinFunctions
|
||||
import prog8.compilerinterface.CompilationOptions
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
|
||||
|
||||
class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalker() {
|
||||
class TypecastsAdder(val program: Program, val options: CompilationOptions, val errors: IErrorReporter) : AstWalker() {
|
||||
/*
|
||||
* Make sure any value assignments get the proper type casts if needed to cast them into the target variable's type.
|
||||
* (this includes function call arguments)
|
||||
@ -46,8 +47,29 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
|
||||
val leftDt = expr.left.inferType(program)
|
||||
val rightDt = expr.right.inferType(program)
|
||||
if(leftDt.isKnown && rightDt.isKnown && leftDt!=rightDt) {
|
||||
|
||||
// convert a negative operand for bitwise operator to the 2's complement positive number instead
|
||||
if(expr.operator in BitwiseOperators && leftDt.isInteger && rightDt.isInteger) {
|
||||
val leftCv = expr.left.constValue(program)
|
||||
if(leftCv!=null && leftCv.number<0) {
|
||||
val value = if(rightDt.isBytes) 256+leftCv.number else 65536+leftCv.number
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr.left,
|
||||
NumericLiteralValue(rightDt.getOr(DataType.UNDEFINED), value, expr.left.position),
|
||||
expr))
|
||||
}
|
||||
val rightCv = expr.right.constValue(program)
|
||||
if(rightCv!=null && rightCv.number<0) {
|
||||
val value = if(leftDt.isBytes) 256+rightCv.number else 65536+rightCv.number
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr.right,
|
||||
NumericLiteralValue(leftDt.getOr(DataType.UNDEFINED), value, expr.right.position),
|
||||
expr))
|
||||
}
|
||||
}
|
||||
|
||||
// determine common datatype and add typecast as required to make left and right equal types
|
||||
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.getOr(DataType.UNDEFINED), rightDt.getOr(DataType.UNDEFINED), expr.left, expr.right)
|
||||
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.getOr(DataType.UNDEFINED), rightDt.getOr(DataType.UNDEFINED), expr.left, expr.operator, expr.right)
|
||||
if(toFix!=null) {
|
||||
return when {
|
||||
toFix===expr.left -> listOf(IAstModification.ReplaceNode(
|
||||
@ -87,7 +109,7 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
|
||||
}
|
||||
val cvalue = assignment.value.constValue(program)
|
||||
if(cvalue!=null) {
|
||||
val number = cvalue.number.toDouble()
|
||||
val number = cvalue.number
|
||||
// more complex comparisons if the type is different, but the constant value is compatible
|
||||
if (valuetype == DataType.BYTE && targettype == DataType.UBYTE) {
|
||||
if(number>0)
|
||||
@ -113,8 +135,8 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
|
||||
return afterFunctionCallArgs(functionCallStatement)
|
||||
}
|
||||
|
||||
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||
return afterFunctionCallArgs(functionCall)
|
||||
override fun after(functionCallExpr: FunctionCallExpr, parent: Node): Iterable<IAstModification> {
|
||||
return afterFunctionCallArgs(functionCallExpr)
|
||||
}
|
||||
|
||||
private fun afterFunctionCallArgs(call: IFunctionCall): Iterable<IAstModification> {
|
||||
@ -130,10 +152,12 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
|
||||
val requiredType = pair.first.type
|
||||
if (requiredType != argtype) {
|
||||
if (argtype isAssignableTo requiredType) {
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
call.args[index],
|
||||
TypecastExpression(pair.second, requiredType, true, pair.second.position),
|
||||
call as Node)
|
||||
// don't need a cast for pass-by-reference types that are assigned to UWORD
|
||||
if(requiredType!=DataType.UWORD || argtype !in PassByReferenceDatatypes)
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
call.args[index],
|
||||
TypecastExpression(pair.second, requiredType, true, pair.second.position),
|
||||
call as Node)
|
||||
} else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) {
|
||||
// We allow STR/ARRAY values in place of UWORD parameters.
|
||||
// Take their address instead, UNLESS it's a str parameter in the containing subroutine
|
||||
@ -156,7 +180,7 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
|
||||
}
|
||||
}
|
||||
}
|
||||
is BuiltinFunctionStatementPlaceholder -> {
|
||||
is BuiltinFunctionPlaceholder -> {
|
||||
val func = BuiltinFunctions.getValue(sub.name)
|
||||
func.parameters.zip(call.args).forEachIndexed { index, pair ->
|
||||
val argItype = pair.second.inferType(program)
|
||||
@ -171,6 +195,18 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
|
||||
call as Node)
|
||||
break
|
||||
}
|
||||
else if(DataType.UWORD in pair.first.possibleDatatypes && argtype in PassByReferenceDatatypes) {
|
||||
// We allow STR/ARRAY values in place of UWORD parameters.
|
||||
// Take their address instead, UNLESS it's a str parameter in the containing subroutine
|
||||
val identifier = pair.second as? IdentifierReference
|
||||
if(identifier?.isSubroutineParameter(program)==false) {
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
call.args[index],
|
||||
AddressOf(identifier, pair.second.position),
|
||||
call as Node)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -185,7 +221,10 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
|
||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||
// warn about any implicit type casts to Float, because that may not be intended
|
||||
if(typecast.implicit && typecast.type.oneOf(DataType.FLOAT, DataType.ARRAY_F)) {
|
||||
errors.warn("integer implicitly converted to float. Suggestion: use float literals, add an explicit cast, or revert to integer arithmetic", typecast.position)
|
||||
if(options.floats)
|
||||
errors.warn("integer implicitly converted to float. Suggestion: use float literals, add an explicit cast, or revert to integer arithmetic", typecast.position)
|
||||
else
|
||||
errors.err("integer implicitly converted to float but floating point is not enabled via options", typecast.position)
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.IStatementContainer
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.ArrayDatatypes
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
@ -15,18 +15,18 @@ import prog8.compilerinterface.IErrorReporter
|
||||
|
||||
internal class VariousCleanups(val program: Program, val errors: IErrorReporter): AstWalker() {
|
||||
|
||||
override fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> {
|
||||
return listOf(IAstModification.Remove(nopStatement, parent as IStatementContainer))
|
||||
override fun before(nop: Nop, parent: Node): Iterable<IAstModification> {
|
||||
return listOf(IAstModification.Remove(nop, parent as IStatementContainer))
|
||||
}
|
||||
|
||||
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
return if(parent is IStatementContainer)
|
||||
listOf(ScopeFlatten(scope, parent as IStatementContainer))
|
||||
else
|
||||
noModifications
|
||||
}
|
||||
|
||||
class ScopeFlatten(val scope: AnonymousScope, val into: IStatementContainer) : IAstModification {
|
||||
private class ScopeFlatten(val scope: AnonymousScope, val into: IStatementContainer) : IAstModification {
|
||||
override fun perform() {
|
||||
val idx = into.statements.indexOf(scope)
|
||||
if(idx>=0) {
|
||||
@ -37,33 +37,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter)
|
||||
}
|
||||
}
|
||||
|
||||
override fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
return before(functionCallStatement as IFunctionCall, parent, functionCallStatement.position)
|
||||
}
|
||||
|
||||
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||
return before(functionCall as IFunctionCall, parent, functionCall.position)
|
||||
}
|
||||
|
||||
private fun before(functionCall: IFunctionCall, parent: Node, position: Position): Iterable<IAstModification> {
|
||||
if(functionCall.target.nameInSource==listOf("peek")) {
|
||||
// peek(a) is synonymous with @(a)
|
||||
val memread = DirectMemoryRead(functionCall.args.single(), position)
|
||||
return listOf(IAstModification.ReplaceNode(functionCall as Node, memread, parent))
|
||||
}
|
||||
if(functionCall.target.nameInSource==listOf("poke")) {
|
||||
// poke(a, v) is synonymous with @(a) = v
|
||||
val tgt = AssignTarget(null, null, DirectMemoryWrite(functionCall.args[0], position), position)
|
||||
val assign = Assignment(tgt, functionCall.args[1], position)
|
||||
return listOf(IAstModification.ReplaceNode(functionCall as Node, assign, parent))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(typecast.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $typecast")
|
||||
|
||||
if(typecast.expression is NumericLiteralValue) {
|
||||
val value = (typecast.expression as NumericLiteralValue).cast(typecast.type)
|
||||
if(value.isValid)
|
||||
@ -74,48 +48,155 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter)
|
||||
if(sourceDt istype typecast.type)
|
||||
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
|
||||
|
||||
return noModifications
|
||||
}
|
||||
if(parent is Assignment) {
|
||||
val targetDt = (parent).target.inferType(program).getOrElse { throw FatalAstException("invalid dt") }
|
||||
if(sourceDt istype targetDt) {
|
||||
// we can get rid of this typecast because the type is already
|
||||
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
|
||||
}
|
||||
}
|
||||
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
if(subroutine.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $subroutine")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
if(assignment.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $assignment")
|
||||
val nextAssign = assignment.nextSibling() as? Assignment
|
||||
if(nextAssign!=null && nextAssign.target.isSameAs(assignment.target, program)) {
|
||||
if(nextAssign.value isSameAs assignment.value)
|
||||
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(assignTarget: AssignTarget, parent: Node): Iterable<IAstModification> {
|
||||
if(assignTarget.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $assignTarget")
|
||||
override fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(expr.operator=="+") {
|
||||
// +X --> X
|
||||
return listOf(IAstModification.ReplaceNode(expr, expr.expression, parent))
|
||||
}
|
||||
if(expr.operator=="not") {
|
||||
val nestedPrefix = expr.expression as? PrefixExpression
|
||||
if(nestedPrefix!=null && nestedPrefix.operator=="not") {
|
||||
// NOT NOT X --> X
|
||||
return listOf(IAstModification.ReplaceNode(expr, nestedPrefix.expression, parent))
|
||||
}
|
||||
val comparison = expr.expression as? BinaryExpression
|
||||
if (comparison != null) {
|
||||
// NOT COMPARISON ==> inverted COMPARISON
|
||||
val invertedOperator = invertedComparisonOperator(comparison.operator)
|
||||
if (invertedOperator != null) {
|
||||
comparison.operator = invertedOperator
|
||||
return listOf(IAstModification.ReplaceNode(expr, comparison, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
if(decl.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $decl")
|
||||
override fun before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(expr.operator == "or") {
|
||||
val leftBinExpr = expr.left as? BinaryExpression
|
||||
val rightBinExpr = expr.right as? BinaryExpression
|
||||
if(leftBinExpr!=null && leftBinExpr.operator=="==" && rightBinExpr!=null && rightBinExpr.operator=="==") {
|
||||
if(leftBinExpr.right is NumericLiteralValue && rightBinExpr.right is NumericLiteralValue) {
|
||||
if(leftBinExpr.left isSameAs rightBinExpr.left)
|
||||
errors.warn("consider using 'in' or 'when' to test for multiple values", expr.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
if(scope.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $scope")
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(expr.operator in ComparisonOperators) {
|
||||
val leftConstVal = expr.left.constValue(program)
|
||||
val rightConstVal = expr.right.constValue(program)
|
||||
// make sure the constant value is on the right of the comparison expression
|
||||
if(rightConstVal==null && leftConstVal!=null) {
|
||||
val newOperator =
|
||||
when(expr.operator) {
|
||||
"<" -> ">"
|
||||
"<=" -> ">="
|
||||
">" -> "<"
|
||||
">=" -> "<="
|
||||
else -> expr.operator
|
||||
}
|
||||
val replacement = BinaryExpression(expr.right, newOperator, expr.left, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, replacement, parent))
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> {
|
||||
if(returnStmt.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $returnStmt")
|
||||
return noModifications
|
||||
}
|
||||
override fun after(containment: ContainmentCheck, parent: Node): Iterable<IAstModification> {
|
||||
// replace trivial containment checks with just false or a single comparison
|
||||
fun replaceWithEquals(value: NumericLiteralValue): Iterable<IAstModification> {
|
||||
errors.warn("containment could be written as just a single comparison", containment.position)
|
||||
val equals = BinaryExpression(containment.element, "==", value, containment.position)
|
||||
return listOf(IAstModification.ReplaceNode(containment, equals, parent))
|
||||
}
|
||||
|
||||
override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
|
||||
if(identifier.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $identifier")
|
||||
fun replaceWithFalse(): Iterable<IAstModification> {
|
||||
errors.warn("condition is always false", containment.position)
|
||||
return listOf(IAstModification.ReplaceNode(containment, NumericLiteralValue.fromBoolean(false, containment.position), parent))
|
||||
}
|
||||
|
||||
fun checkArray(array: Array<Expression>): Iterable<IAstModification> {
|
||||
if(array.isEmpty())
|
||||
return replaceWithFalse()
|
||||
if(array.size==1) {
|
||||
val constVal = array[0].constValue(program)
|
||||
if(constVal!=null)
|
||||
return replaceWithEquals(constVal)
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
fun checkString(stringVal: StringLiteralValue): Iterable<IAstModification> {
|
||||
if(stringVal.value.isEmpty())
|
||||
return replaceWithFalse()
|
||||
if(stringVal.value.length==1) {
|
||||
val string = program.encoding.encodeString(stringVal.value, stringVal.altEncoding)
|
||||
return replaceWithEquals(NumericLiteralValue(DataType.UBYTE, string[0].toDouble(), stringVal.position))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
when(containment.iterable) {
|
||||
is ArrayLiteralValue -> {
|
||||
val array = (containment.iterable as ArrayLiteralValue).value
|
||||
return checkArray(array)
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val variable = (containment.iterable as IdentifierReference).targetVarDecl(program)!!
|
||||
when(variable.datatype) {
|
||||
DataType.STR -> {
|
||||
val stringVal = (variable.value as StringLiteralValue)
|
||||
return checkString(stringVal)
|
||||
}
|
||||
in ArrayDatatypes -> {
|
||||
val array = (variable.value as ArrayLiteralValue).value
|
||||
return checkArray(array)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
is RangeExpr -> {
|
||||
val constValues = (containment.iterable as RangeExpr).toConstantIntegerRange()
|
||||
if(constValues!=null) {
|
||||
if (constValues.isEmpty())
|
||||
return replaceWithFalse()
|
||||
if (constValues.count()==1)
|
||||
return replaceWithEquals(NumericLiteralValue.optimalNumeric(constValues.first, containment.position))
|
||||
}
|
||||
}
|
||||
is StringLiteralValue -> {
|
||||
val stringVal = containment.iterable as StringLiteralValue
|
||||
return checkString(stringVal)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,17 +4,17 @@ import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.FunctionCall
|
||||
import prog8.ast.expressions.FunctionCallExpr
|
||||
import prog8.ast.expressions.TypecastExpression
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.compilerinterface.BuiltinFunctions
|
||||
import prog8.compilerinterface.InternalCompilerException
|
||||
|
||||
class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
|
||||
internal class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
|
||||
|
||||
override fun visit(functionCall: FunctionCall) {
|
||||
val error = checkTypes(functionCall as IFunctionCall, program)
|
||||
override fun visit(functionCallExpr: FunctionCallExpr) {
|
||||
val error = checkTypes(functionCallExpr as IFunctionCall, program)
|
||||
if(error!=null)
|
||||
throw InternalCompilerException(error)
|
||||
}
|
||||
@ -33,7 +33,9 @@ class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
|
||||
|
||||
// there are some exceptions that are considered compatible, such as STR <> UWORD
|
||||
if(argDt==DataType.STR && paramDt==DataType.UWORD ||
|
||||
argDt==DataType.UWORD && paramDt==DataType.STR)
|
||||
argDt==DataType.UWORD && paramDt==DataType.STR ||
|
||||
argDt==DataType.UWORD && paramDt==DataType.ARRAY_UB ||
|
||||
argDt==DataType.STR && paramDt==DataType.ARRAY_UB)
|
||||
return true
|
||||
|
||||
return false
|
||||
@ -74,7 +76,7 @@ class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (target is BuiltinFunctionStatementPlaceholder) {
|
||||
else if (target is BuiltinFunctionPlaceholder) {
|
||||
val func = BuiltinFunctions.getValue(target.name)
|
||||
if(call.args.size != func.parameters.size)
|
||||
return "invalid number of arguments"
|
||||
@ -83,8 +85,13 @@ class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
|
||||
val anyCompatible = pair.second.any { argTypeCompatible(pair.first, it) }
|
||||
if (!anyCompatible) {
|
||||
val actual = pair.first.toString()
|
||||
val expected = pair.second.toString()
|
||||
return "argument ${index + 1} type mismatch, was: $actual expected: $expected"
|
||||
return if(pair.second.size==1) {
|
||||
val expected = pair.second[0].toString()
|
||||
"argument ${index + 1} type mismatch, was: $actual expected: $expected"
|
||||
} else {
|
||||
val expected = pair.second.toList().toString()
|
||||
"argument ${index + 1} type mismatch, was: $actual expected one of: $expected"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,221 +2,168 @@ package prog8tests
|
||||
|
||||
import com.github.michaelbull.result.getErrorOrElse
|
||||
import com.github.michaelbull.result.getOrElse
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.Matchers.equalTo
|
||||
import org.hamcrest.Matchers.nullValue
|
||||
import org.hamcrest.core.Is
|
||||
import org.junit.jupiter.api.*
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.internedStringsModuleName
|
||||
import prog8.compiler.ModuleImporter
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
import prog8.parser.ParseError
|
||||
import prog8.parser.SourceCode
|
||||
import prog8tests.ast.helpers.*
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import kotlin.io.path.*
|
||||
import io.kotest.assertions.fail
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.collections.shouldBeIn
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import prog8tests.helpers.*
|
||||
import prog8tests.helpers.DummyFunctions
|
||||
import prog8tests.helpers.DummyMemsizer
|
||||
import prog8tests.helpers.DummyStringEncoder
|
||||
import kotlin.io.path.*
|
||||
import kotlin.test.assertContains
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.fail
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestModuleImporter {
|
||||
private val count = listOf("1st", "2nd", "3rd", "4th", "5th")
|
||||
class TestModuleImporter: FunSpec({
|
||||
val count = listOf("1st", "2nd", "3rd", "4th", "5th")
|
||||
|
||||
private lateinit var program: Program
|
||||
@BeforeEach
|
||||
fun beforeEach() {
|
||||
lateinit var program: Program
|
||||
|
||||
beforeTest {
|
||||
program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
}
|
||||
|
||||
private fun makeImporter(errors: IErrorReporter?, vararg searchIn: String): ModuleImporter {
|
||||
fun makeImporter(errors: IErrorReporter? = null, searchIn: Iterable<String>) =
|
||||
ModuleImporter(program, "blah", errors ?: ErrorReporterForTests(false), searchIn.toList())
|
||||
|
||||
fun makeImporter(errors: IErrorReporter?, vararg searchIn: String): ModuleImporter {
|
||||
return makeImporter(errors, searchIn.asList())
|
||||
}
|
||||
|
||||
private fun makeImporter(errors: IErrorReporter? = null, searchIn: Iterable<String>) =
|
||||
ModuleImporter(program, "blah", errors ?: ErrorReporterForTests(false), searchIn.toList())
|
||||
context("ImportModule") {
|
||||
|
||||
@Nested
|
||||
inner class Constructor {
|
||||
|
||||
@Test
|
||||
@Disabled("TODO: invalid entries in search list")
|
||||
fun testInvalidEntriesInSearchList() {}
|
||||
|
||||
@Test
|
||||
@Disabled("TODO: literal duplicates in search list")
|
||||
fun testLiteralDuplicatesInSearchList() {}
|
||||
|
||||
@Test
|
||||
@Disabled("TODO: factual duplicates in search list")
|
||||
fun testFactualDuplicatesInSearchList() {}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class ImportModule {
|
||||
|
||||
@Nested
|
||||
inner class WithInvalidPath {
|
||||
@Test
|
||||
fun testNonexisting() {
|
||||
context("WithInvalidPath") {
|
||||
test("testNonexisting") {
|
||||
val dirRel = assumeDirectory(".", workingDir.relativize(fixturesDir))
|
||||
val importer = makeImporter(null, dirRel.invariantSeparatorsPathString)
|
||||
val srcPathRel = assumeNotExists(dirRel, "i_do_not_exist")
|
||||
val srcPathAbs = srcPathRel.absolute()
|
||||
val error1 = importer.importModule(srcPathRel).getErrorOrElse { fail("should have import error") }
|
||||
assertThat(
|
||||
".file should be normalized",
|
||||
"${error1.file}", equalTo("${error1.file.normalize()}")
|
||||
)
|
||||
assertThat(
|
||||
".file should point to specified path",
|
||||
error1.file.absolutePath, equalTo("${srcPathAbs.normalize()}")
|
||||
)
|
||||
assertThat(program.modules.size, equalTo(1))
|
||||
|
||||
withClue(".file should be normalized") {
|
||||
"${error1.file}" shouldBe "${error1.file.normalize()}"
|
||||
}
|
||||
withClue(".file should point to specified path") {
|
||||
error1.file.absolutePath shouldBe "${srcPathAbs.normalize()}"
|
||||
}
|
||||
program.modules.size shouldBe 1
|
||||
val error2 = importer.importModule(srcPathAbs).getErrorOrElse { fail("should have import error") }
|
||||
assertThat(
|
||||
".file should be normalized",
|
||||
"${error2.file}", equalTo("${error2.file.normalize()}")
|
||||
)
|
||||
assertThat(
|
||||
".file should point to specified path",
|
||||
error2.file.absolutePath, equalTo("${srcPathAbs.normalize()}")
|
||||
)
|
||||
assertThat(program.modules.size, equalTo(1))
|
||||
withClue(".file should be normalized") {
|
||||
"${error2.file}" shouldBe "${error2.file.normalize()}"
|
||||
}
|
||||
withClue(".file should point to specified path") {
|
||||
error2.file.absolutePath shouldBe "${srcPathAbs.normalize()}"
|
||||
}
|
||||
program.modules.size shouldBe 1
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDirectory() {
|
||||
test("testDirectory") {
|
||||
val srcPathRel = assumeDirectory(workingDir.relativize(fixturesDir))
|
||||
val srcPathAbs = srcPathRel.absolute()
|
||||
val searchIn = Path(".", "$srcPathRel").invariantSeparatorsPathString
|
||||
val importer = makeImporter(null, searchIn)
|
||||
|
||||
assertFailsWith<AccessDeniedException> { importer.importModule(srcPathRel) }
|
||||
shouldThrow<AccessDeniedException> { importer.importModule(srcPathRel) }
|
||||
.let {
|
||||
assertThat(
|
||||
".file should be normalized",
|
||||
"${it.file}", equalTo("${it.file.normalize()}")
|
||||
)
|
||||
assertThat(
|
||||
".file should point to specified path",
|
||||
it.file.absolutePath, equalTo("${srcPathAbs.normalize()}")
|
||||
)
|
||||
withClue(".file should be normalized") {
|
||||
"${it.file}" shouldBe "${it.file.normalize()}"
|
||||
}
|
||||
withClue(".file should point to specified path") {
|
||||
it.file.absolutePath shouldBe "${srcPathAbs.normalize()}"
|
||||
}
|
||||
}
|
||||
assertThat(program.modules.size, equalTo(1))
|
||||
program.modules.size shouldBe 1
|
||||
|
||||
assertFailsWith<AccessDeniedException> { importer.importModule(srcPathAbs) }
|
||||
shouldThrow<AccessDeniedException> { importer.importModule(srcPathAbs) }
|
||||
.let {
|
||||
assertThat(
|
||||
".file should be normalized",
|
||||
"${it.file}", equalTo("${it.file.normalize()}")
|
||||
)
|
||||
assertThat(
|
||||
".file should point to specified path",
|
||||
it.file.absolutePath, equalTo("${srcPathAbs.normalize()}")
|
||||
)
|
||||
withClue(".file should be normalized") {
|
||||
"${it.file}" shouldBe "${it.file.normalize()}"
|
||||
}
|
||||
withClue(".file should point to specified path") {
|
||||
it.file.absolutePath shouldBe "${srcPathAbs.normalize()}"
|
||||
}
|
||||
}
|
||||
assertThat(program.modules.size, equalTo(1))
|
||||
program.modules.size shouldBe 1
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class WithValidPath {
|
||||
context("WithValidPath") {
|
||||
|
||||
@Test
|
||||
fun testAbsolute() {
|
||||
test("testAbsolute") {
|
||||
val searchIn = listOf(
|
||||
Path(".").div(workingDir.relativize(fixturesDir)), // we do want a dot "." in front
|
||||
).map { it.invariantSeparatorsPathString }
|
||||
val importer = makeImporter(null, searchIn)
|
||||
val fileName = "simple_main.p8"
|
||||
val fileName = "ast_simple_main.p8"
|
||||
val path = assumeReadableFile(searchIn[0], fileName)
|
||||
|
||||
val module = importer.importModule(path.absolute()).getOrElse { throw it }
|
||||
assertThat(program.modules.size, equalTo(2))
|
||||
assertContains(program.modules, module)
|
||||
assertThat(module.program, equalTo(program))
|
||||
program.modules.size shouldBe 2
|
||||
module shouldBeIn program.modules
|
||||
module.program shouldBe program
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRelativeToWorkingDir() {
|
||||
test("testRelativeToWorkingDir") {
|
||||
val searchIn = listOf(
|
||||
Path(".").div(workingDir.relativize(fixturesDir)), // we do want a dot "." in front
|
||||
).map { it.invariantSeparatorsPathString }
|
||||
val importer = makeImporter(null, searchIn)
|
||||
val fileName = "simple_main.p8"
|
||||
val fileName = "ast_simple_main.p8"
|
||||
val path = assumeReadableFile(searchIn[0], fileName)
|
||||
assertThat("sanity check: path should NOT be absolute", path.isAbsolute, equalTo(false))
|
||||
withClue("sanity check: path should NOT be absolute") {
|
||||
path.isAbsolute shouldBe false
|
||||
}
|
||||
|
||||
val module = importer.importModule(path).getOrElse { throw it }
|
||||
assertThat(program.modules.size, equalTo(2))
|
||||
assertContains(program.modules, module)
|
||||
assertThat(module.program, equalTo(program))
|
||||
program.modules.size shouldBe 2
|
||||
module shouldBeIn program.modules
|
||||
module.program shouldBe program
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRelativeTo1stDirInSearchList() {
|
||||
test("testRelativeTo1stDirInSearchList") {
|
||||
val searchIn = Path(".")
|
||||
.div(workingDir.relativize(fixturesDir))
|
||||
.invariantSeparatorsPathString
|
||||
val importer = makeImporter(null, searchIn)
|
||||
val fileName = "simple_main.p8"
|
||||
val fileName = "ast_simple_main.p8"
|
||||
val path = Path(".", fileName)
|
||||
assumeReadableFile(searchIn, path)
|
||||
|
||||
val module = importer.importModule(path).getOrElse { throw it }
|
||||
assertThat(program.modules.size, equalTo(2))
|
||||
assertContains(program.modules, module)
|
||||
assertThat(module.program, equalTo(program))
|
||||
program.modules.size shouldBe 2
|
||||
module shouldBeIn program.modules
|
||||
module.program shouldBe program
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("TODO: relative to 2nd in search list")
|
||||
fun testRelativeTo2ndDirInSearchList() {}
|
||||
|
||||
@Test
|
||||
@Disabled("TODO: ambiguous - 2 or more really different candidates")
|
||||
fun testAmbiguousCandidates() {}
|
||||
|
||||
@Nested
|
||||
inner class WithBadFile {
|
||||
@Test
|
||||
fun testWithSyntaxError() {
|
||||
context("WithBadFile") {
|
||||
test("testWithSyntaxError") {
|
||||
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
||||
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
|
||||
val srcPath = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
|
||||
val srcPath = assumeReadableFile(fixturesDir, "ast_file_with_syntax_error.p8")
|
||||
|
||||
val act = { importer.importModule(srcPath) }
|
||||
|
||||
repeat(2) { n ->
|
||||
assertFailsWith<ParseError>(count[n] + " call") { act() }.let {
|
||||
assertThat(it.position.file, equalTo(SourceCode.relative(srcPath).toString()))
|
||||
assertThat("line; should be 1-based", it.position.line, equalTo(2))
|
||||
assertThat("startCol; should be 0-based", it.position.startCol, equalTo(6))
|
||||
assertThat("endCol; should be 0-based", it.position.endCol, equalTo(6))
|
||||
repeat(2) { n -> withClue(count[n] + " call") {
|
||||
shouldThrow<ParseError>() { act() }.let {
|
||||
it.position.file shouldBe SourceCode.relative(srcPath).toString()
|
||||
withClue("line; should be 1-based") { it.position.line shouldBe 2 }
|
||||
withClue("startCol; should be 0-based") { it.position.startCol shouldBe 6 }
|
||||
withClue("endCol; should be 0-based") { it.position.endCol shouldBe 6 }
|
||||
}
|
||||
}
|
||||
assertThat(program.modules.size, equalTo(1))
|
||||
program.modules.size shouldBe 1
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testImportingFileWithSyntaxError_once() {
|
||||
doTestImportingFileWithSyntaxError(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testImportingFileWithSyntaxError_twice() {
|
||||
doTestImportingFileWithSyntaxError(2)
|
||||
}
|
||||
|
||||
private fun doTestImportingFileWithSyntaxError(repetitions: Int) {
|
||||
fun doTestImportingFileWithSyntaxError(repetitions: Int) {
|
||||
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
||||
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
|
||||
val importing = assumeReadableFile(fixturesDir, "import_file_with_syntax_error.p8")
|
||||
@ -224,28 +171,33 @@ class TestModuleImporter {
|
||||
|
||||
val act = { importer.importModule(importing) }
|
||||
|
||||
repeat(repetitions) { n ->
|
||||
assertFailsWith<ParseError>(count[n] + " call") { act() }.let {
|
||||
assertThat(it.position.file, equalTo(SourceCode.relative(imported).toString()))
|
||||
assertThat("line; should be 1-based", it.position.line, equalTo(2))
|
||||
assertThat("startCol; should be 0-based", it.position.startCol, equalTo(6))
|
||||
assertThat("endCol; should be 0-based", it.position.endCol, equalTo(6))
|
||||
repeat(repetitions) { n -> withClue(count[n] + " call") {
|
||||
shouldThrow<ParseError> { act() }.let {
|
||||
it.position.file shouldBe SourceCode.relative(imported).toString()
|
||||
withClue("line; should be 1-based") { it.position.line shouldBe 2 }
|
||||
withClue("startCol; should be 0-based") { it.position.startCol shouldBe 6 }
|
||||
withClue("endCol; should be 0-based") { it.position.endCol shouldBe 6 }
|
||||
}
|
||||
assertThat("imported module with error in it should not be present", program.modules.size, equalTo(1))
|
||||
assertThat(program.modules[0].name, equalTo(internedStringsModuleName))
|
||||
}
|
||||
withClue("imported module with error in it should not be present") { program.modules.size shouldBe 1 }
|
||||
program.modules[0].name shouldBe internedStringsModuleName
|
||||
}
|
||||
}
|
||||
|
||||
test("testImportingFileWithSyntaxError_once") {
|
||||
doTestImportingFileWithSyntaxError(1)
|
||||
}
|
||||
|
||||
test("testImportingFileWithSyntaxError_twice") {
|
||||
doTestImportingFileWithSyntaxError(2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class ImportLibraryModule {
|
||||
@Nested
|
||||
inner class WithInvalidName {
|
||||
@Test
|
||||
fun testWithNonExistingName() {
|
||||
context("ImportLibraryModule") {
|
||||
context("WithInvalidName") {
|
||||
test("testWithNonExistingName") {
|
||||
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
||||
val errors = ErrorReporterForTests(false)
|
||||
val importer = makeImporter(errors, searchIn.invariantSeparatorsPathString)
|
||||
@ -254,46 +206,45 @@ class TestModuleImporter {
|
||||
|
||||
repeat(2) { n ->
|
||||
val result = importer.importLibraryModule(filenameNoExt)
|
||||
assertThat(count[n] + " call / NO .p8 extension", result, Is(nullValue()))
|
||||
assertFalse(errors.noErrors(), count[n] + " call / NO .p8 extension")
|
||||
assertContains(errors.errors.single(), "0:0: no module found with name i_do_not_exist")
|
||||
withClue(count[n] + " call / NO .p8 extension") { result shouldBe null }
|
||||
withClue(count[n] + " call / NO .p8 extension") { errors.noErrors() shouldBe false }
|
||||
errors.errors.single() shouldContain "0:0) no module found with name i_do_not_exist"
|
||||
errors.report()
|
||||
assertThat(program.modules.size, equalTo(1))
|
||||
program.modules.size shouldBe 1
|
||||
|
||||
val result2 = importer.importLibraryModule(filenameWithExt)
|
||||
assertThat(count[n] + " call / with .p8 extension", result2, Is(nullValue()))
|
||||
assertFalse(importer.errors.noErrors(), count[n] + " call / with .p8 extension")
|
||||
assertContains(errors.errors.single(), "0:0: no module found with name i_do_not_exist.p8")
|
||||
withClue(count[n] + " call / with .p8 extension") { result2 shouldBe null }
|
||||
withClue(count[n] + " call / with .p8 extension") { importer.errors.noErrors() shouldBe false }
|
||||
errors.errors.single() shouldContain "0:0) no module found with name i_do_not_exist.p8"
|
||||
errors.report()
|
||||
assertThat(program.modules.size, equalTo(1))
|
||||
program.modules.size shouldBe 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class WithValidName {
|
||||
@Nested
|
||||
inner class WithBadFile {
|
||||
@Test
|
||||
fun testWithSyntaxError() {
|
||||
context("WithValidName") {
|
||||
context("WithBadFile") {
|
||||
test("testWithSyntaxError") {
|
||||
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
||||
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
|
||||
val srcPath = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
|
||||
val srcPath = assumeReadableFile(fixturesDir, "ast_file_with_syntax_error.p8")
|
||||
|
||||
repeat(2) { n ->
|
||||
assertFailsWith<ParseError>(count[n] + " call")
|
||||
{ importer.importLibraryModule(srcPath.nameWithoutExtension) }.let {
|
||||
assertThat(it.position.file, equalTo(SourceCode.relative(srcPath).toString()))
|
||||
assertThat("line; should be 1-based", it.position.line, equalTo(2))
|
||||
assertThat("startCol; should be 0-based", it.position.startCol, equalTo(6))
|
||||
assertThat("endCol; should be 0-based", it.position.endCol, equalTo(6))
|
||||
repeat(2) { n -> withClue(count[n] + " call") {
|
||||
shouldThrow<ParseError>()
|
||||
{
|
||||
importer.importLibraryModule(srcPath.nameWithoutExtension) }.let {
|
||||
it.position.file shouldBe SourceCode.relative(srcPath).toString()
|
||||
withClue("line; should be 1-based") { it.position.line shouldBe 2 }
|
||||
withClue("startCol; should be 0-based") { it.position.startCol shouldBe 6 }
|
||||
withClue("endCol; should be 0-based") { it.position.endCol shouldBe 6 }
|
||||
}
|
||||
assertThat(program.modules.size, equalTo(1))
|
||||
}
|
||||
program.modules.size shouldBe 1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun doTestImportingFileWithSyntaxError(repetitions: Int) {
|
||||
fun doTestImportingFileWithSyntaxError(repetitions: Int) {
|
||||
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
||||
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
|
||||
val importing = assumeReadableFile(fixturesDir, "import_file_with_syntax_error.p8")
|
||||
@ -301,29 +252,29 @@ class TestModuleImporter {
|
||||
|
||||
val act = { importer.importLibraryModule(importing.nameWithoutExtension) }
|
||||
|
||||
repeat(repetitions) { n ->
|
||||
assertFailsWith<ParseError>(count[n] + " call") { act() }.let {
|
||||
assertThat(it.position.file, equalTo(SourceCode.relative(imported).toString()))
|
||||
assertThat("line; should be 1-based", it.position.line, equalTo(2))
|
||||
assertThat("startCol; should be 0-based", it.position.startCol, equalTo(6))
|
||||
assertThat("endCol; should be 0-based", it.position.endCol, equalTo(6))
|
||||
repeat(repetitions) { n -> withClue(count[n] + " call") {
|
||||
shouldThrow<ParseError>() {
|
||||
act() }.let {
|
||||
it.position.file shouldBe SourceCode.relative(imported).toString()
|
||||
withClue("line; should be 1-based") { it.position.line shouldBe 2 }
|
||||
withClue("startCol; should be 0-based") { it.position.startCol shouldBe 6 }
|
||||
withClue("endCol; should be 0-based") { it.position.endCol shouldBe 6 }
|
||||
}
|
||||
}
|
||||
assertThat("imported module with error in it should not be present", program.modules.size, equalTo(1))
|
||||
assertThat(program.modules[0].name, equalTo(internedStringsModuleName))
|
||||
withClue("imported module with error in it should not be present") { program.modules.size shouldBe 1 }
|
||||
program.modules[0].name shouldBe internedStringsModuleName
|
||||
importer.errors.report()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testImportingFileWithSyntaxError_once() {
|
||||
test("testImportingFileWithSyntaxError_once") {
|
||||
doTestImportingFileWithSyntaxError(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testImportingFileWithSyntaxError_twice() {
|
||||
test("testImportingFileWithSyntaxError_twice") {
|
||||
doTestImportingFileWithSyntaxError(2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
58
compiler/test/ProjectConfig.kt
Normal file
58
compiler/test/ProjectConfig.kt
Normal file
@ -0,0 +1,58 @@
|
||||
package prog8tests
|
||||
|
||||
import io.kotest.core.config.AbstractProjectConfig
|
||||
import io.kotest.core.listeners.Listener
|
||||
import io.kotest.core.listeners.TestListener
|
||||
import io.kotest.core.spec.Spec
|
||||
import io.kotest.extensions.system.NoSystemErrListener
|
||||
import io.kotest.extensions.system.NoSystemOutListener
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.PrintStream
|
||||
import kotlin.math.max
|
||||
|
||||
object ProjectConfig : AbstractProjectConfig() {
|
||||
override val parallelism = 2 // max(2, Runtime.getRuntime().availableProcessors() / 2)
|
||||
// override fun listeners() = listOf(SystemOutToNullListener)
|
||||
}
|
||||
|
||||
//object SystemOutToNullListener: TestListener {
|
||||
// override suspend fun beforeSpec(spec: Spec) = setup()
|
||||
//
|
||||
// private fun setup() {
|
||||
// System.setOut(object: PrintStream(object: ByteArrayOutputStream(){
|
||||
// override fun write(p0: Int) {
|
||||
// // do nothing
|
||||
// }
|
||||
//
|
||||
// override fun write(b: ByteArray, off: Int, len: Int) {
|
||||
// // do nothing
|
||||
// }
|
||||
//
|
||||
// override fun write(b: ByteArray) {
|
||||
// // do nothing
|
||||
// }
|
||||
// }){}
|
||||
// )
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//object SystemErrToNullListener: TestListener {
|
||||
// override suspend fun beforeSpec(spec: Spec) = setup()
|
||||
//
|
||||
// private fun setup() {
|
||||
// System.setErr(object: PrintStream(object: ByteArrayOutputStream(){
|
||||
// override fun write(p0: Int) {
|
||||
// // do nothing
|
||||
// }
|
||||
//
|
||||
// override fun write(b: ByteArray, off: Int, len: Int) {
|
||||
// // do nothing
|
||||
// }
|
||||
//
|
||||
// override fun write(b: ByteArray) {
|
||||
// // do nothing
|
||||
// }
|
||||
// }){}
|
||||
// )
|
||||
// }
|
||||
//}
|
33
compiler/test/TestAstChecks.kt
Normal file
33
compiler/test/TestAstChecks.kt
Normal file
@ -0,0 +1,33 @@
|
||||
package prog8tests
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import prog8.codegen.target.C64Target
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import prog8tests.helpers.compileText
|
||||
|
||||
|
||||
class TestAstChecks: FunSpec({
|
||||
|
||||
test("conditional expression w/float works even without tempvar to split it") {
|
||||
val text = """
|
||||
%import floats
|
||||
main {
|
||||
sub start() {
|
||||
uword xx
|
||||
if xx+99.99 == xx+1.234 {
|
||||
xx++
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
|
||||
compileText(C64Target, true, text, writeAssembly = true, errors=errors).assertSuccess()
|
||||
errors.errors.size shouldBe 0
|
||||
errors.warnings.size shouldBe 2
|
||||
errors.warnings[0] shouldContain "converted to float"
|
||||
errors.warnings[1] shouldContain "converted to float"
|
||||
}
|
||||
})
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user