mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 05:02:58 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
No files matched your search
Generated
+10
@@ -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>
|
||||
Generated
+5
@@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
||||
+1
@@ -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" />
|
||||
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Kotlin2JvmCompilerArguments">
|
||||
<option name="jvmTarget" value="11" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
-10
@@ -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
@@ -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
@@ -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
@@ -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>
|
||||
Generated
-17
@@ -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>
|
||||
Generated
+5
-2
@@ -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>
|
||||
+13
-23
@@ -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,35 +1,42 @@
|
||||
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.ast.base.*
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.statements.RegisterOrStatusflag
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.compiler.target.c64.C64MachineDefinition
|
||||
import prog8.compiler.target.cbm.Petscii
|
||||
import prog8.compiler.target.cpu6502.codegen.asmsub6502ArgsEvalOrder
|
||||
import prog8.compiler.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<Short> {
|
||||
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<Short>, altEncoding: Boolean) =
|
||||
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 -> 2
|
||||
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
|
||||
in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE
|
||||
else -> -9999999
|
||||
else -> Int.MIN_VALUE
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,31 +5,41 @@ 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.compiler.target.cbm.Petscii
|
||||
import prog8.compiler.target.cpu6502.codegen.asmsub6502ArgsEvalOrder
|
||||
import prog8.compiler.target.cpu6502.codegen.asmsub6502ArgsHaveRegisterClobberRisk
|
||||
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)
|
||||
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<Short>, altEncoding: Boolean) =
|
||||
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 -> 2
|
||||
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
|
||||
in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE
|
||||
else -> -9999999
|
||||
else -> Int.MIN_VALUE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
package prog8.compiler.target.c64
|
||||
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.compiler.target.cbm.Mflpt5
|
||||
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 {
|
||||
|
||||
class 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 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 POINTER_MEM_SIZE = 2
|
||||
override val BASIC_LOAD_ADDRESS = 0x0801
|
||||
override val RAW_LOAD_ADDRESS = 0xc000
|
||||
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 = 0xce00 // $ce00-$ceff inclusive
|
||||
override val ESTACK_HI = 0xcf00 // $ce00-$ceff inclusive
|
||||
override val ESTACK_LO = 0xce00u // $ce00-$ceff inclusive
|
||||
override val ESTACK_HI = 0xcf00u // $ce00-$ceff inclusive
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
|
||||
@@ -56,7 +56,8 @@ object C64MachineDefinition: IMachineDefinition {
|
||||
}
|
||||
}
|
||||
|
||||
override fun isRegularRAMaddress(address: Int): Boolean = address<0xa000 || address in 0xc000..0xcfff
|
||||
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)
|
||||
@@ -74,133 +75,4 @@ object C64MachineDefinition: IMachineDefinition {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package prog8.compiler.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 $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(0x04u..0xf9u)
|
||||
free.add(0xffu)
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package prog8.compiler.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"
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,10 +1,16 @@
|
||||
package prog8.compiler.target.cpu6502.codegen
|
||||
|
||||
|
||||
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.compilerinterface.IMachineDefinition
|
||||
|
||||
|
||||
fun optimizeAssembly(lines: MutableList<String>): Int {
|
||||
// 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
|
||||
|
||||
@@ -31,7 +37,7 @@ fun optimizeAssembly(lines: MutableList<String>): Int {
|
||||
numberOfOptimizations++
|
||||
}
|
||||
|
||||
mods = optimizeStoreLoadSame(linesByFour)
|
||||
mods = optimizeStoreLoadSame(linesByFour, machine, program)
|
||||
if(mods.isNotEmpty()) {
|
||||
apply(mods, lines)
|
||||
linesByFour = getLinesBy(lines, 4)
|
||||
@@ -46,7 +52,7 @@ fun optimizeAssembly(lines: MutableList<String>): Int {
|
||||
}
|
||||
|
||||
var linesByFourteen = getLinesBy(lines, 14)
|
||||
mods = optimizeSameAssignments(linesByFourteen)
|
||||
mods = optimizeSameAssignments(linesByFourteen, machine, program)
|
||||
if(mods.isNotEmpty()) {
|
||||
apply(mods, lines)
|
||||
linesByFourteen = getLinesBy(lines, 14)
|
||||
@@ -111,22 +117,22 @@ private fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<S
|
||||
return mods
|
||||
}
|
||||
|
||||
private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>): List<Modification> {
|
||||
private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>, machine: IMachineDefinition, program: Program): List<Modification> {
|
||||
|
||||
// optimize sequential assignments of the isSameAs value to various targets (bytes, words, floats)
|
||||
// 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...
|
||||
// @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...
|
||||
// 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 (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()
|
||||
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")) {
|
||||
@@ -135,9 +141,13 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
||||
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))
|
||||
// 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,8 +155,10 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
||||
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))
|
||||
// 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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,12 +166,12 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
||||
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()
|
||||
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") &&
|
||||
@@ -167,24 +179,147 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
||||
|
||||
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))
|
||||
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>>>): List<Modification> {
|
||||
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
|
||||
// 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()
|
||||
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 ")) ||
|
||||
@@ -196,32 +331,101 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>)
|
||||
(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 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(pair[1].index, true, null))
|
||||
}
|
||||
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 (pair in linesByFour) {
|
||||
val first = pair[0].value
|
||||
val second = pair[1].value
|
||||
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)) {
|
||||
mods.add(Modification(pair[0].index, true, null))
|
||||
mods.add(Modification(pair[1].index, true, null))
|
||||
|| (" 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
|
||||
@@ -230,12 +434,12 @@ private fun optimizeIncDec(linesByFour: List<List<IndexedValue<String>>>): List<
|
||||
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
|
||||
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(pair[0].index, false, pair[0].value.replace("jsr", "jmp"))
|
||||
mods += Modification(pair[1].index, true, null)
|
||||
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.compiler.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 FunctionCall -> {
|
||||
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) }
|
||||
}
|
||||
+183
-12
@@ -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) {
|
||||
@@ -248,7 +418,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
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()
|
||||
val size = (fcall.args[1] as NumericLiteralValue).number.toUInt()
|
||||
|
||||
val existingSize = asmgen.slabs[name]
|
||||
if(existingSize!=null && existingSize!=size)
|
||||
@@ -283,11 +453,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 +992,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 +1397,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 +1644,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) {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -22,13 +22,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 +43,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 +589,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)
|
||||
}
|
||||
@@ -5,10 +5,7 @@ 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.ast.statements.*
|
||||
import prog8.compiler.target.AssemblyError
|
||||
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignSource
|
||||
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignTarget
|
||||
@@ -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,76 @@ 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 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)
|
||||
}
|
||||
if(!isExpression && !sub.isAsmSubroutine)
|
||||
throw AssemblyError("functioncall statements to non-asmsub should have been replaced by GoSub $call")
|
||||
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
asmgen.out(" jsr $subAsmName")
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
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}")
|
||||
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 +214,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,22 +254,18 @@ 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) {
|
||||
// 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")
|
||||
|
||||
@@ -303,11 +292,10 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
val sourceName = asmgen.asmVariableName(value)
|
||||
asmgen.out("""
|
||||
pha
|
||||
clc
|
||||
lda $sourceName
|
||||
beq +
|
||||
sec
|
||||
bcs ++
|
||||
+ clc
|
||||
+ pla
|
||||
""")
|
||||
}
|
||||
@@ -335,8 +323,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)
|
||||
|
||||
+35
-23
@@ -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,7 +164,7 @@ 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 -> {
|
||||
@@ -161,9 +177,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||
}
|
||||
is BuiltinFunctionStatementPlaceholder -> {
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
+72
-82
@@ -7,17 +7,14 @@ 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.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 -> {
|
||||
@@ -162,7 +163,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
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) {
|
||||
@@ -261,11 +262,12 @@ 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")
|
||||
}
|
||||
}
|
||||
@@ -273,7 +275,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
// 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)
|
||||
@@ -312,9 +314,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 +336,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,9 +442,35 @@ 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) {
|
||||
@@ -779,7 +802,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 +1012,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 +1067,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 +1101,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 +1241,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 +1294,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 +1362,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 +1522,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 +1652,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 +1743,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 +1977,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 +2061,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 +2132,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 +2149,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))
|
||||
|
||||
+140
-93
@@ -7,12 +7,11 @@ 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.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")
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -657,14 +709,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 +725,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 +746,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 +756,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 +768,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 +782,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 +1155,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
sta $name
|
||||
lda P8ZP_SCRATCH_W2+1
|
||||
sta $name+1
|
||||
""") }
|
||||
""")
|
||||
}
|
||||
"<<" -> {
|
||||
asmgen.out("""
|
||||
ldy $otherName
|
||||
@@ -1235,9 +1288,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 +1805,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")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1790,8 +1840,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
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 +1893,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 +1935,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 +1948,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 +1968,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 +1993,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 +2062,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 +2080,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,31 @@
|
||||
package prog8.compiler.target.cx16
|
||||
|
||||
import prog8.compiler.target.c64.C64MachineDefinition
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.compiler.target.cbm.Mflpt5
|
||||
import prog8.compiler.target.cbm.viceMonListPostfix
|
||||
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 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 POINTER_MEM_SIZE = 2
|
||||
override val BASIC_LOAD_ADDRESS = 0x0801
|
||||
override val RAW_LOAD_ADDRESS = 0x8000
|
||||
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")
|
||||
@@ -67,7 +67,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 +99,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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package prog8.compiler.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,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()
|
||||
}
|
||||
}
|
||||
+13
-23
@@ -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,7 +4,10 @@ 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.IdentifierReference
|
||||
import prog8.ast.expressions.TypecastExpression
|
||||
import prog8.ast.expressions.augmentAssignmentOperators
|
||||
import prog8.ast.statements.AssignTarget
|
||||
import prog8.ast.statements.Assignment
|
||||
@@ -12,39 +15,19 @@ 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,
|
||||
@@ -63,10 +46,34 @@ X = BinExpr X = LeftExpr
|
||||
|
||||
*/
|
||||
if(binExpr.operator in augmentAssignmentOperators && isSimpleTarget(assignment.target)) {
|
||||
if(assignment.target isSameAs binExpr.left || assignment.target isSameAs binExpr.right)
|
||||
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.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 +85,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)
|
||||
}
|
||||
|
||||
@@ -40,7 +40,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 +48,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 +79,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 +101,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 +155,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 +186,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
|
||||
}
|
||||
|
||||
@@ -312,7 +420,7 @@ 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.
|
||||
|
||||
|
||||
@@ -24,11 +24,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 +43,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 +110,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 +136,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 +146,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 +189,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 +214,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 {
|
||||
|
||||
@@ -19,17 +19,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))
|
||||
|
||||
|
||||
*/
|
||||
|
||||
|
||||
@@ -160,7 +149,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 +159,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 +179,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))
|
||||
@@ -231,52 +232,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)
|
||||
@@ -320,7 +331,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
// 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),
|
||||
NumericLiteralValue(valueDt.getOr(DataType.UBYTE), 0.0, arg.expression.position),
|
||||
parent))
|
||||
}
|
||||
} else {
|
||||
@@ -329,7 +340,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
// useless msb() of byte value, replace with 0
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
functionCall,
|
||||
NumericLiteralValue(argDt.getOr(DataType.UBYTE), 0, arg.position),
|
||||
NumericLiteralValue(argDt.getOr(DataType.UBYTE), 0.0, arg.position),
|
||||
parent))
|
||||
}
|
||||
}
|
||||
@@ -362,7 +373,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 +382,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 +403,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 +417,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 +436,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,7 +456,7 @@ 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)
|
||||
@@ -467,18 +478,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 +511,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 +531,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 +566,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 +598,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,12 +646,12 @@ 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)
|
||||
if (amount == 8) {
|
||||
@@ -687,7 +698,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
val msb = FunctionCall(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)
|
||||
val zero = NumericLiteralValue(DataType.UBYTE, 0.0, expr.position)
|
||||
return FunctionCall(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)
|
||||
|
||||
@@ -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,9 +1,6 @@
|
||||
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.*
|
||||
@@ -15,8 +12,6 @@ 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 +19,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> {
|
||||
// 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.
|
||||
|
||||
fun scopePrefix(variable: IdentifierReference): IdentifierReference {
|
||||
val target = variable.targetStatement(program) as INamedStatement
|
||||
return IdentifierReference(target.scopedName, variable.position)
|
||||
}
|
||||
|
||||
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?.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,7 +43,7 @@ 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
|
||||
@@ -70,13 +54,10 @@ class StatementOptimizer(private val program: Program,
|
||||
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 BuiltinFunctionStatementPlaceholder) {
|
||||
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 +82,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 +90,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,23 +115,25 @@ 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> {
|
||||
// remove empty if statements
|
||||
if(ifStatement.truepart.isEmpty() && ifStatement.elsepart.isEmpty())
|
||||
@@ -215,7 +198,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)
|
||||
@@ -296,12 +279,13 @@ class StatementOptimizer(private val program: Program,
|
||||
}
|
||||
|
||||
override fun after(jump: Jump, parent: Node): Iterable<IAstModification> {
|
||||
// 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)
|
||||
return listOf(IAstModification.Remove(jump, scope))
|
||||
|
||||
if(!jump.isGosub) {
|
||||
// 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)
|
||||
return listOf(IAstModification.Remove(jump, scope))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@@ -324,22 +308,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(
|
||||
@@ -374,14 +358,26 @@ class StatementOptimizer(private val program: Program,
|
||||
throw FatalAstException("can't infer type of assignment target")
|
||||
|
||||
// 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 +390,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 +435,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)
|
||||
|
||||
@@ -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,28 @@ 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)
|
||||
if(!jump.isGosub)
|
||||
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 +57,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> {
|
||||
@@ -75,8 +73,7 @@ class UnusedCodeRemover(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
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 FunctionCall -> { /* don't remove */ }
|
||||
else -> linesToRemove.add(assign1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return modifications + linesToRemove.map { IAstModification.Remove(it, scope) }
|
||||
}
|
||||
}
|
||||
+15
-5
@@ -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>
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
; 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 mimimics 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 number of bytes loaded (truncated to 16 bits, if the file is larger than 64 Kb,
|
||||
; you'll have to compensate yourself by checking the ram banks).
|
||||
sub load(ubyte drivenumber, uword filenameptr, ubyte bank, uword address_override) -> uword {
|
||||
cx16.rambank(bank)
|
||||
uword size = diskio.load(drivenumber, filenameptr, address_override)
|
||||
if size
|
||||
return size + $2000 * (cx16.getrambank() - bank)
|
||||
return 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 number of bytes loaded.
|
||||
; Returns the number of bytes loaded (truncated to 16 bits, if the file is larger than 64 Kb,
|
||||
; you'll have to compensate yourself by checking the ram banks).
|
||||
sub load_raw(ubyte drivenumber, uword filenameptr, ubyte bank, uword address) -> uword {
|
||||
cx16.rambank(bank)
|
||||
uword size = diskio.load_raw(drivenumber, filenameptr, address)
|
||||
if size
|
||||
return size + $2000 * (cx16.getrambank() - bank)
|
||||
return 0
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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 {{
|
||||
@@ -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
|
||||
}}
|
||||
|
||||
@@ -433,6 +433,16 @@ io_error:
|
||||
return first_byte
|
||||
}
|
||||
|
||||
; Use kernal LOAD routine to load the given program file in memory.
|
||||
; This mimimics 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 number of bytes loaded.
|
||||
; 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
|
||||
@@ -456,13 +466,32 @@ io_error:
|
||||
c64.CLOSE(1)
|
||||
|
||||
if end_of_load
|
||||
return end_of_load - address_override
|
||||
return end_of_load - address_override ; not correct when the file spans multiple RAM banks
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
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 number of bytes loaded.
|
||||
; 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
|
||||
uword size = f_read(address, 2)
|
||||
f_close()
|
||||
if size!=2
|
||||
return 0
|
||||
address += 2
|
||||
size = load(drivenumber, filenameptr, address)
|
||||
if size
|
||||
return size+2
|
||||
return 0
|
||||
}
|
||||
|
||||
sub delete(ubyte drivenumber, uword filenameptr) {
|
||||
; -- delete a file on the drive
|
||||
@@ -489,4 +518,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
|
||||
|
||||
@@ -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.5
|
||||
@@ -3,6 +3,7 @@ package prog8
|
||||
import kotlinx.cli.*
|
||||
import prog8.ast.base.AstException
|
||||
import prog8.compiler.CompilationResult
|
||||
import prog8.compiler.CompilerArguments
|
||||
import prog8.compiler.compileProgram
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.Cx16Target
|
||||
@@ -66,6 +67,11 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
if(srcdirs.firstOrNull()!=".")
|
||||
srcdirs.add(0, ".")
|
||||
|
||||
if (compilationTarget != C64Target.name && compilationTarget != 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 +81,18 @@ 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,
|
||||
dontWriteAssembly != true,
|
||||
slowCodegenWarnings == true,
|
||||
quietAssembler == true,
|
||||
compilationTarget,
|
||||
srcdirs,
|
||||
outputPath
|
||||
)
|
||||
val compilationResult = compileProgram(compilerArgs)
|
||||
results.add(compilationResult)
|
||||
}
|
||||
|
||||
@@ -115,11 +129,19 @@ 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,
|
||||
dontWriteAssembly != true,
|
||||
slowCodegenWarnings == true,
|
||||
quietAssembler == true,
|
||||
compilationTarget,
|
||||
srcdirs,
|
||||
outputPath
|
||||
)
|
||||
compilationResult = compileProgram(compilerArgs)
|
||||
if (!compilationResult.success)
|
||||
return false
|
||||
} catch (x: AstException) {
|
||||
return false
|
||||
|
||||
@@ -11,17 +11,38 @@ import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.compiler.astprocessing.isSubroutineParameter
|
||||
import prog8.compiler.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(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,7 +52,7 @@ 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)
|
||||
@@ -39,20 +60,24 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
|
||||
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) {
|
||||
// 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 +88,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 +105,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 +124,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.isGosub)
|
||||
&& 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
|
||||
@@ -201,12 +197,79 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
return listOf(IAstModification.ReplaceNode(ifStatement.condition, booleanExpr, ifStatement))
|
||||
}
|
||||
|
||||
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(ifStatement, simplify.rightVarAssignment, parent as IStatementContainer)
|
||||
}
|
||||
if(simplify.leftVarAssignment!=null) {
|
||||
modifications += IAstModification.ReplaceNode(binExpr.left, simplify.leftOperandReplacement!!, binExpr)
|
||||
modifications += IAstModification.InsertBefore(ifStatement, simplify.leftVarAssignment, parent as IStatementContainer)
|
||||
}
|
||||
|
||||
return modifications
|
||||
}
|
||||
|
||||
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
|
||||
val separateRightExpr = !expr.right.isSimple && expr.right !is IFunctionCall
|
||||
|
||||
if(separateLeftExpr) {
|
||||
val name = getTempVarName(expr.left.inferType(program))
|
||||
leftOperandReplacement = IdentifierReference(name, expr.position)
|
||||
leftAssignment = Assignment(
|
||||
AssignTarget(IdentifierReference(name, expr.position), null, null, expr.position),
|
||||
expr.left,
|
||||
expr.position
|
||||
)
|
||||
}
|
||||
if(separateRightExpr) {
|
||||
val dt = expr.right.inferType(program)
|
||||
val name = when {
|
||||
dt.istype(DataType.UBYTE) -> listOf("prog8_lib","retval_interm_ub")
|
||||
dt.istype(DataType.UWORD) -> listOf("prog8_lib","retval_interm_uw")
|
||||
dt.istype(DataType.BYTE) -> listOf("prog8_lib","retval_interm_b2")
|
||||
dt.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
|
||||
)
|
||||
}
|
||||
return CondExprSimplificationResult(
|
||||
leftAssignment, leftOperandReplacement,
|
||||
rightAssignment, rightOperandReplacement
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("DuplicatedCode")
|
||||
@@ -224,7 +287,26 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
val booleanExpr = BinaryExpression(untilLoop.condition, "!=", NumericLiteralValue.optimalInteger(0, untilLoop.condition.position), untilLoop.condition.position)
|
||||
return listOf(IAstModification.ReplaceNode(untilLoop.condition, booleanExpr, untilLoop))
|
||||
}
|
||||
return noModifications
|
||||
|
||||
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")
|
||||
|
||||
// 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.InsertLast(simplify.rightVarAssignment, untilLoop.body)
|
||||
}
|
||||
if(simplify.leftVarAssignment!=null) {
|
||||
modifications += IAstModification.ReplaceNode(binExpr.left, simplify.leftOperandReplacement!!, binExpr)
|
||||
modifications += IAstModification.InsertLast(simplify.leftVarAssignment, untilLoop.body)
|
||||
}
|
||||
|
||||
return modifications
|
||||
}
|
||||
|
||||
@Suppress("DuplicatedCode")
|
||||
@@ -242,6 +324,19 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
|
||||
val booleanExpr = BinaryExpression(whileLoop.condition, "!=", NumericLiteralValue.optimalInteger(0, whileLoop.condition.position), whileLoop.condition.position)
|
||||
return listOf(IAstModification.ReplaceNode(whileLoop.condition, booleanExpr, whileLoop))
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
// TODO simplify the conditional expression, introduce simple assignments if required.
|
||||
// make sure to evaluate it only once, but also right at entry of the while loop
|
||||
// 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.
|
||||
// NOTE: this is nasty for a while-statement as the condition occurs at the top of the loop
|
||||
// so the expression needs to be evaluated also before the loop is entered...
|
||||
// but I don't want to duplicate the expression.
|
||||
// val simplify = simplifyConditionalExpression(binExpr)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@@ -255,13 +350,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
|
||||
@@ -325,10 +422,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))
|
||||
|
||||
@@ -28,24 +28,27 @@ class CompilationResult(val success: Boolean,
|
||||
val compTarget: ICompilationTarget,
|
||||
val importedFiles: List<Path>)
|
||||
|
||||
class CompilerArguments(val filepath: Path,
|
||||
val optimize: Boolean,
|
||||
val optimizeFloatExpressions: Boolean,
|
||||
val writeAssembly: Boolean,
|
||||
val slowCodegenWarnings: Boolean,
|
||||
val quietAssembler: 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
|
||||
Cx16Target.name -> Cx16Target
|
||||
else -> throw IllegalArgumentException("invalid compilation target")
|
||||
@@ -54,31 +57,30 @@ 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
|
||||
this.slowCodegenWarnings = args.slowCodegenWarnings
|
||||
this.optimize = args.optimize
|
||||
this.optimizeFloatExpressions = optimizeFloatExpr
|
||||
}
|
||||
program = programresult
|
||||
importedFiles = imported
|
||||
processAst(program, errors, compilationOptions)
|
||||
processAst(program, args.errors, compilationOptions)
|
||||
if (compilationOptions.optimize)
|
||||
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, args.quietAssembler, compilationOptions)) {
|
||||
is WriteAssemblyResult.Ok -> programName = result.filename
|
||||
is WriteAssemblyResult.Fail -> {
|
||||
System.err.println(result.error)
|
||||
@@ -253,16 +255,17 @@ 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)
|
||||
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.reorderStatements(errors, compilerOptions)
|
||||
errors.report()
|
||||
program.addTypecasts(errors)
|
||||
errors.report()
|
||||
@@ -270,7 +273,7 @@ private fun processAst(program: Program, errors: IErrorReporter, compilerOptions
|
||||
errors.report()
|
||||
program.checkValid(errors, compilerOptions)
|
||||
errors.report()
|
||||
program.checkIdentifiers(errors, compilerOptions)
|
||||
program.checkIdentifiers(errors, program, compilerOptions)
|
||||
errors.report()
|
||||
}
|
||||
|
||||
@@ -325,7 +328,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,
|
||||
@@ -348,7 +351,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)
|
||||
@@ -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,7 +160,6 @@ internal class AstChecker(private val program: Program,
|
||||
super.visit(forLoop)
|
||||
}
|
||||
|
||||
|
||||
override fun visit(jump: Jump) {
|
||||
val ident = jump.identifier
|
||||
if(ident!=null) {
|
||||
@@ -170,17 +168,20 @@ internal class AstChecker(private val program: Program,
|
||||
if(targetStatement is BuiltinFunctionStatementPlaceholder)
|
||||
errors.err("can't jump to a builtin function", jump.position)
|
||||
}
|
||||
if(!jump.isGosub && 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(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)
|
||||
}
|
||||
|
||||
@@ -216,7 +217,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
|
||||
@@ -225,7 +226,15 @@ internal class AstChecker(private val program: Program,
|
||||
count++
|
||||
}
|
||||
override fun visit(jump: Jump) {
|
||||
count++
|
||||
if(!jump.isGosub)
|
||||
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++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,13 +269,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 +387,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 +418,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) {
|
||||
@@ -490,8 +491,8 @@ internal class AstChecker(private val program: Program,
|
||||
if (assignment.value !is FunctionCall)
|
||||
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 +510,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)
|
||||
@@ -707,7 +708,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 +828,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=="%") {
|
||||
@@ -967,40 +968,46 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -1054,7 +1061,12 @@ internal class AstChecker(private val program: Program,
|
||||
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) {
|
||||
@@ -1209,7 +1221,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 +1240,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 +1259,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 +1286,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 +1362,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 +1392,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 BuiltinFunctionStatementPlaceholder) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,8 +28,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 +44,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
|
||||
))
|
||||
}
|
||||
@@ -64,17 +64,17 @@ internal fun Program.verifyFunctionArgTypes() {
|
||||
fixer.visit(this)
|
||||
}
|
||||
|
||||
internal fun Program.preprocessAst() {
|
||||
val transforms = AstPreprocessor()
|
||||
internal fun Program.preprocessAst(program: Program) {
|
||||
val transforms = AstPreprocessor(program)
|
||||
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()) {
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.FunctionCall
|
||||
import prog8.ast.expressions.StringLiteralValue
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
@@ -8,7 +13,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 +82,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 +128,33 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter, private
|
||||
|
||||
super.visit(string)
|
||||
}
|
||||
|
||||
override fun visit(functionCall: FunctionCall) = visitFunctionCall(functionCall)
|
||||
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 BuiltinFunctionStatementPlaceholder -> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
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 -> throw FatalAstException("weird call target")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.NumericDatatypes
|
||||
import prog8.ast.base.SyntaxError
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.expressions.RangeExpr
|
||||
import prog8.ast.statements.AnonymousScope
|
||||
import prog8.ast.statements.AssignTarget
|
||||
import prog8.ast.statements.Assignment
|
||||
@@ -12,7 +16,40 @@ import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
|
||||
|
||||
class AstPreprocessor : AstWalker() {
|
||||
class AstPreprocessor(val program: Program) : 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> {
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,13 @@ 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 +24,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 +57,12 @@ 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
|
||||
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 +119,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> {
|
||||
@@ -170,8 +198,9 @@ 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 -> {
|
||||
@@ -179,8 +208,9 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -196,7 +226,7 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
||||
// 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))
|
||||
@@ -226,6 +256,13 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
||||
}
|
||||
|
||||
override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
|
||||
val lastChoiceValues = whenStatement.choices.lastOrNull()?.values
|
||||
if(lastChoiceValues?.isNotEmpty()==true) {
|
||||
val elseChoice = whenStatement.choices.indexOfFirst { it.values==null || it.values?.isEmpty()==true }
|
||||
if(elseChoice>=0)
|
||||
errors.err("else choice must be the last one", whenStatement.choices[elseChoice].position)
|
||||
}
|
||||
|
||||
val choices = whenStatement.choiceValues(program).sortedBy {
|
||||
it.first?.first() ?: Int.MAX_VALUE
|
||||
}
|
||||
@@ -334,4 +371,111 @@ 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))
|
||||
}
|
||||
|
||||
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)...
|
||||
if (options.slowCodegenWarnings)
|
||||
errors.warn("slow argument passing used to avoid register clobbering", call.position)
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -46,8 +46,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 +108,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)
|
||||
@@ -130,10 +151,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
|
||||
@@ -171,6 +194,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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,14 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter)
|
||||
if(sourceDt istype typecast.type)
|
||||
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@@ -86,6 +94,13 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter)
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ 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)
|
||||
@@ -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
|
||||
@@ -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,169 @@ 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 +172,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 +207,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 +253,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -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
|
||||
// }
|
||||
// }){}
|
||||
// )
|
||||
// }
|
||||
//}
|
||||
@@ -1,21 +1,19 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.maps.shouldContainKey
|
||||
import io.kotest.matchers.maps.shouldNotContainKey
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.ast.statements.Block
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compilerinterface.CallGraph
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import prog8tests.helpers.compileText
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestCallgraph {
|
||||
@Test
|
||||
fun testGraphForEmptySubs() {
|
||||
class TestCallgraph: FunSpec({
|
||||
test("testGraphForEmptySubs") {
|
||||
val sourcecode = """
|
||||
%import string
|
||||
main {
|
||||
@@ -28,32 +26,33 @@ class TestCallgraph {
|
||||
val result = compileText(C64Target, false, sourcecode).assertSuccess()
|
||||
val graph = CallGraph(result.program)
|
||||
|
||||
assertEquals(1, graph.imports.size)
|
||||
assertEquals(1, graph.importedBy.size)
|
||||
graph.imports.size shouldBe 1
|
||||
graph.importedBy.size shouldBe 1
|
||||
val toplevelModule = result.program.toplevelModule
|
||||
val importedModule = graph.imports.getValue(toplevelModule).single()
|
||||
assertEquals("string", importedModule.name)
|
||||
importedModule.name shouldBe "string"
|
||||
val importedBy = graph.importedBy.getValue(importedModule).single()
|
||||
assertTrue(importedBy.name.startsWith("on_the_fly_test"))
|
||||
importedBy.name.startsWith("on_the_fly_test") shouldBe true
|
||||
|
||||
assertFalse(graph.unused(toplevelModule))
|
||||
assertFalse(graph.unused(importedModule))
|
||||
graph.unused(toplevelModule) shouldBe false
|
||||
graph.unused(importedModule) shouldBe false
|
||||
|
||||
val mainBlock = toplevelModule.statements.filterIsInstance<Block>().single()
|
||||
for(stmt in mainBlock.statements) {
|
||||
val sub = stmt as Subroutine
|
||||
assertFalse(sub in graph.calls)
|
||||
assertFalse(sub in graph.calledBy)
|
||||
graph.calls shouldNotContainKey sub
|
||||
graph.calledBy shouldNotContainKey sub
|
||||
|
||||
if(sub === result.program.entrypoint)
|
||||
assertFalse(graph.unused(sub), "start() should always be marked as used to avoid having it removed")
|
||||
withClue("start() should always be marked as used to avoid having it removed") {
|
||||
graph.unused(sub) shouldBe false
|
||||
}
|
||||
else
|
||||
assertTrue(graph.unused(sub))
|
||||
graph.unused(sub) shouldBe true
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGraphForEmptyButReferencedSub() {
|
||||
test("testGraphForEmptyButReferencedSub") {
|
||||
val sourcecode = """
|
||||
%import string
|
||||
main {
|
||||
@@ -68,24 +67,32 @@ class TestCallgraph {
|
||||
val result = compileText(C64Target, false, sourcecode).assertSuccess()
|
||||
val graph = CallGraph(result.program)
|
||||
|
||||
assertEquals(1, graph.imports.size)
|
||||
assertEquals(1, graph.importedBy.size)
|
||||
graph.imports.size shouldBe 1
|
||||
graph.importedBy.size shouldBe 1
|
||||
val toplevelModule = result.program.toplevelModule
|
||||
val importedModule = graph.imports.getValue(toplevelModule).single()
|
||||
assertEquals("string", importedModule.name)
|
||||
importedModule.name shouldBe "string"
|
||||
val importedBy = graph.importedBy.getValue(importedModule).single()
|
||||
assertTrue(importedBy.name.startsWith("on_the_fly_test"))
|
||||
importedBy.name.startsWith("on_the_fly_test") shouldBe true
|
||||
|
||||
assertFalse(graph.unused(toplevelModule))
|
||||
assertFalse(graph.unused(importedModule))
|
||||
graph.unused(toplevelModule) shouldBe false
|
||||
graph.unused(importedModule) shouldBe false
|
||||
|
||||
val mainBlock = toplevelModule.statements.filterIsInstance<Block>().single()
|
||||
val startSub = mainBlock.statements.filterIsInstance<Subroutine>().single{it.name=="start"}
|
||||
val emptySub = mainBlock.statements.filterIsInstance<Subroutine>().single{it.name=="empty"}
|
||||
|
||||
assertTrue(startSub in graph.calls, "start 'calls' (references) empty")
|
||||
assertFalse(emptySub in graph.calls, "empty doesn't call anything")
|
||||
assertTrue(emptySub in graph.calledBy, "empty gets 'called'")
|
||||
assertFalse(startSub in graph.calledBy, "start doesn't get called (except as entrypoint ofc.)")
|
||||
withClue("start 'calls' (references) empty") {
|
||||
graph.calls shouldContainKey startSub
|
||||
}
|
||||
withClue("empty doesn't call anything") {
|
||||
graph.calls shouldNotContainKey emptySub
|
||||
}
|
||||
withClue("empty gets 'called'") {
|
||||
graph.calledBy shouldContainKey emptySub
|
||||
}
|
||||
withClue( "start doesn't get called (except as entrypoint ofc.)") {
|
||||
graph.calledBy shouldNotContainKey startSub
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,7 +1,10 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import io.kotest.assertions.fail
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.types.instanceOf
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.VarDeclType
|
||||
@@ -11,9 +14,6 @@ import prog8.ast.statements.Assignment
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import prog8tests.helpers.compileText
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertIs
|
||||
import kotlin.test.assertNull
|
||||
|
||||
|
||||
/**
|
||||
@@ -21,11 +21,9 @@ import kotlin.test.assertNull
|
||||
* They are not really unit tests, but rather tests of the whole process,
|
||||
* from source file loading all the way through to running 64tass.
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestCompilerOnCharLit {
|
||||
class TestCompilerOnCharLit: FunSpec({
|
||||
|
||||
@Test
|
||||
fun testCharLitAsRomsubArg() {
|
||||
test("testCharLitAsRomsubArg") {
|
||||
val platform = Cx16Target
|
||||
val result = compileText(platform, false, """
|
||||
main {
|
||||
@@ -40,15 +38,15 @@ class TestCompilerOnCharLit {
|
||||
val startSub = program.entrypoint
|
||||
val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0]
|
||||
|
||||
assertIs<NumericLiteralValue>(funCall.args[0],
|
||||
"char literal should have been replaced by ubyte literal")
|
||||
withClue("char literal should have been replaced by ubyte literal") {
|
||||
funCall.args[0] shouldBe instanceOf<NumericLiteralValue>()
|
||||
}
|
||||
val arg = funCall.args[0] as NumericLiteralValue
|
||||
assertEquals(DataType.UBYTE, arg.type)
|
||||
assertEquals(platform.encodeString("\n", false)[0], arg.number.toShort())
|
||||
arg.type shouldBe DataType.UBYTE
|
||||
arg.number shouldBe platform.encodeString("\n", false)[0].toDouble()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCharVarAsRomsubArg() {
|
||||
test("testCharVarAsRomsubArg") {
|
||||
val platform = Cx16Target
|
||||
val result = compileText(platform, false, """
|
||||
main {
|
||||
@@ -64,28 +62,31 @@ class TestCompilerOnCharLit {
|
||||
val startSub = program.entrypoint
|
||||
val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0]
|
||||
|
||||
assertIs<IdentifierReference>(funCall.args[0])
|
||||
funCall.args[0] shouldBe instanceOf<IdentifierReference>()
|
||||
val arg = funCall.args[0] as IdentifierReference
|
||||
val decl = arg.targetVarDecl(program)!!
|
||||
assertEquals(VarDeclType.VAR, decl.type)
|
||||
assertEquals(DataType.UBYTE, decl.datatype)
|
||||
decl.type shouldBe VarDeclType.VAR
|
||||
decl.datatype shouldBe DataType.UBYTE
|
||||
|
||||
// TODO: assertIs<CharLiteral>(decl.value,
|
||||
// "char literals should be kept until code gen")
|
||||
// val initializerValue = decl.value as CharLiteral
|
||||
// assertEquals('\n', (initializerValue as CharLiteral).value)
|
||||
|
||||
assertNull(decl.value, "initializer value should have been moved to separate assignment")
|
||||
withClue("initializer value should have been moved to separate assignment"){
|
||||
decl.value shouldBe null
|
||||
}
|
||||
val assignInitialValue = decl.nextSibling() as Assignment
|
||||
assertEquals(listOf("ch"), assignInitialValue.target.identifier!!.nameInSource)
|
||||
assertIs<NumericLiteralValue>(assignInitialValue.value, "char literal should have been replaced by ubyte literal")
|
||||
assignInitialValue.target.identifier!!.nameInSource shouldBe listOf("ch")
|
||||
withClue("char literal should have been replaced by ubyte literal") {
|
||||
assignInitialValue.value shouldBe instanceOf<NumericLiteralValue>()
|
||||
}
|
||||
val initializerValue = assignInitialValue.value as NumericLiteralValue
|
||||
assertEquals(DataType.UBYTE, initializerValue.type)
|
||||
assertEquals(platform.encodeString("\n", false)[0], initializerValue.number.toShort())
|
||||
initializerValue.type shouldBe DataType.UBYTE
|
||||
initializerValue.number shouldBe platform.encodeString("\n", false)[0].toDouble()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCharConstAsRomsubArg() {
|
||||
test("testCharConstAsRomsubArg") {
|
||||
val platform = Cx16Target
|
||||
val result = compileText(platform, false, """
|
||||
main {
|
||||
@@ -105,20 +106,15 @@ class TestCompilerOnCharLit {
|
||||
when (val arg = funCall.args[0]) {
|
||||
is IdentifierReference -> {
|
||||
val decl = arg.targetVarDecl(program)!!
|
||||
assertEquals(VarDeclType.CONST, decl.type)
|
||||
assertEquals(DataType.UBYTE, decl.datatype)
|
||||
assertEquals(
|
||||
platform.encodeString("\n", false)[0],
|
||||
(decl.value as NumericLiteralValue).number.toShort())
|
||||
decl.type shouldBe VarDeclType.CONST
|
||||
decl.datatype shouldBe DataType.UBYTE
|
||||
(decl.value as NumericLiteralValue).number shouldBe platform.encodeString("\n", false)[0]
|
||||
}
|
||||
is NumericLiteralValue -> {
|
||||
assertEquals(
|
||||
platform.encodeString("\n", false)[0],
|
||||
arg.number.toShort())
|
||||
arg.number shouldBe platform.encodeString("\n", false)[0].toDouble()
|
||||
}
|
||||
else -> assertIs<IdentifierReference>(funCall.args[0]) // make test fail
|
||||
else -> fail("invalid arg type") // funCall.args[0] shouldBe instanceOf<IdentifierReference>() // make test fail
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
@@ -1,18 +1,15 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.DynamicTest
|
||||
import org.junit.jupiter.api.DynamicTest.dynamicTest
|
||||
import org.junit.jupiter.api.TestFactory
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import prog8.compiler.CompilationResult
|
||||
import prog8.compiler.CompilerArguments
|
||||
import prog8.compiler.compileProgram
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
import prog8tests.ast.helpers.assumeDirectory
|
||||
import prog8tests.ast.helpers.mapCombinations
|
||||
import prog8tests.ast.helpers.outputDir
|
||||
import prog8tests.ast.helpers.workingDir
|
||||
import prog8tests.helpers.*
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.absolute
|
||||
import kotlin.io.path.exists
|
||||
|
||||
@@ -22,41 +19,99 @@ import kotlin.io.path.exists
|
||||
* They are not really unit tests, but rather tests of the whole process,
|
||||
* from source file loading all the way through to running 64tass.
|
||||
*/
|
||||
// @Disabled("disable to save some time")
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestCompilerOnExamples {
|
||||
private val examplesDir = assumeDirectory(workingDir, "../examples")
|
||||
|
||||
private fun makeDynamicCompilerTest(name: String, platform: ICompilationTarget, optimize: Boolean) : DynamicTest {
|
||||
val searchIn = mutableListOf(examplesDir)
|
||||
if (platform == Cx16Target) {
|
||||
searchIn.add(0, assumeDirectory(examplesDir, "cx16"))
|
||||
}
|
||||
val filepath = searchIn
|
||||
.map { it.resolve("$name.p8") }
|
||||
.map { it.normalize().absolute() }
|
||||
.map { workingDir.relativize(it) }
|
||||
.first { it.exists() }
|
||||
val displayName = "${examplesDir.relativize(filepath.absolute())}: ${platform.name}, optimize=$optimize"
|
||||
return dynamicTest(displayName) {
|
||||
compileProgram(
|
||||
filepath,
|
||||
optimize,
|
||||
optimizeFloatExpressions = false,
|
||||
writeAssembly = true,
|
||||
slowCodegenWarnings = false,
|
||||
quietAssembler = true,
|
||||
compilationTarget = platform.name,
|
||||
sourceDirs = listOf(),
|
||||
outputDir
|
||||
).assertSuccess("; $displayName")
|
||||
private val examplesDir = assumeDirectory(workingDir, "../examples")
|
||||
|
||||
private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilationTarget): CompilationResult {
|
||||
val args = CompilerArguments(
|
||||
filepath,
|
||||
optimize,
|
||||
optimizeFloatExpressions = true,
|
||||
writeAssembly = true,
|
||||
slowCodegenWarnings = false,
|
||||
quietAssembler = true,
|
||||
compilationTarget = target.name,
|
||||
outputDir = outputDir
|
||||
)
|
||||
return compileProgram(args)
|
||||
}
|
||||
|
||||
private fun prepareTestFiles(source: String, optimize: Boolean, target: ICompilationTarget): Pair<String, Path> {
|
||||
val searchIn = mutableListOf(examplesDir)
|
||||
if (target == Cx16Target) {
|
||||
searchIn.add(0, assumeDirectory(examplesDir, "cx16"))
|
||||
}
|
||||
val filepath = searchIn
|
||||
.map { it.resolve("$source.p8") }
|
||||
.map { it.normalize().absolute() }
|
||||
.map { workingDir.relativize(it) }
|
||||
.first { it.exists() }
|
||||
val displayName = "${examplesDir.relativize(filepath.absolute())}: ${target.name}, optimize=$optimize"
|
||||
return Pair(displayName, filepath)
|
||||
}
|
||||
|
||||
|
||||
class TestCompilerOnExamplesC64: FunSpec({
|
||||
|
||||
val onlyC64 = cartesianProduct(
|
||||
listOf(
|
||||
"balloonflight",
|
||||
"bdmusic",
|
||||
"bdmusic-irq",
|
||||
"charset",
|
||||
"cube3d-sprites",
|
||||
"plasma",
|
||||
"sprites",
|
||||
"turtle-gfx",
|
||||
"wizzine",
|
||||
),
|
||||
listOf(false, true)
|
||||
)
|
||||
|
||||
onlyC64.forEach {
|
||||
val (source, optimize) = it
|
||||
val (displayName, filepath) = prepareTestFiles(source, optimize, C64Target)
|
||||
test(displayName) {
|
||||
compileTheThing(filepath, optimize, C64Target).assertSuccess()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@TestFactory
|
||||
// @Disabled("disable to save some time")
|
||||
fun bothCx16AndC64() = mapCombinations(
|
||||
dim1 = listOf(
|
||||
class TestCompilerOnExamplesCx16: FunSpec({
|
||||
|
||||
val onlyCx16 = cartesianProduct(
|
||||
listOf(
|
||||
"vtui/testvtui",
|
||||
"amiga",
|
||||
"bobs",
|
||||
"cobramk3-gfx",
|
||||
"colorbars",
|
||||
"datetime",
|
||||
"highresbitmap",
|
||||
"kefrenbars",
|
||||
"mandelbrot-gfx-colors",
|
||||
"multipalette",
|
||||
"rasterbars",
|
||||
"sincos",
|
||||
"tehtriz",
|
||||
"testgfx2",
|
||||
),
|
||||
listOf(false, true)
|
||||
)
|
||||
|
||||
onlyCx16.forEach {
|
||||
val (source, optimize) = it
|
||||
val (displayName, filepath) = prepareTestFiles(source, optimize, Cx16Target)
|
||||
test(displayName) {
|
||||
compileTheThing(filepath, optimize, Cx16Target).assertSuccess()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
class TestCompilerOnExamplesBothC64andCx16: FunSpec({
|
||||
|
||||
val bothCx16AndC64 = cartesianProduct(
|
||||
listOf(
|
||||
"animals",
|
||||
"balls",
|
||||
"cube3d",
|
||||
@@ -79,48 +134,18 @@ class TestCompilerOnExamples {
|
||||
"tehtriz",
|
||||
"textelite",
|
||||
),
|
||||
dim2 = listOf(Cx16Target, C64Target),
|
||||
dim3 = listOf(false, true),
|
||||
combine3 = ::makeDynamicCompilerTest
|
||||
listOf(false, true)
|
||||
)
|
||||
|
||||
@TestFactory
|
||||
// @Disabled("disable to save some time")
|
||||
fun onlyC64() = mapCombinations(
|
||||
dim1 = listOf(
|
||||
"balloonflight",
|
||||
"bdmusic",
|
||||
"bdmusic-irq",
|
||||
"charset",
|
||||
"cube3d-sprites",
|
||||
"plasma",
|
||||
"sprites",
|
||||
"turtle-gfx",
|
||||
"wizzine",
|
||||
),
|
||||
dim2 = listOf(C64Target),
|
||||
dim3 = listOf(false, true),
|
||||
combine3 = ::makeDynamicCompilerTest
|
||||
)
|
||||
|
||||
@TestFactory
|
||||
// @Disabled("disable to save some time")
|
||||
fun onlyCx16() = mapCombinations(
|
||||
dim1 = listOf(
|
||||
"vtui/testvtui",
|
||||
"amiga",
|
||||
"bobs",
|
||||
"cobramk3-gfx",
|
||||
"colorbars",
|
||||
"datetime",
|
||||
"highresbitmap",
|
||||
"kefrenbars",
|
||||
"mandelbrot-gfx-colors",
|
||||
"multipalette",
|
||||
"testgfx2",
|
||||
),
|
||||
dim2 = listOf(Cx16Target),
|
||||
dim3 = listOf(false, true),
|
||||
combine3 = ::makeDynamicCompilerTest
|
||||
)
|
||||
}
|
||||
bothCx16AndC64.forEach {
|
||||
val (source, optimize) = it
|
||||
val (displayNameC64, filepathC64) = prepareTestFiles(source, optimize, C64Target)
|
||||
val (displayNameCx16, filepathCx16) = prepareTestFiles(source, optimize, Cx16Target)
|
||||
test(displayNameC64) {
|
||||
compileTheThing(filepathC64, optimize, C64Target).assertSuccess()
|
||||
}
|
||||
test(displayNameCx16) {
|
||||
compileTheThing(filepathCx16, optimize, Cx16Target).assertSuccess()
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,20 +1,20 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.*
|
||||
import org.junit.jupiter.api.DynamicTest.dynamicTest
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import prog8.ast.expressions.AddressOf
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.StringLiteralValue
|
||||
import prog8.ast.statements.FunctionCallStatement
|
||||
import prog8.ast.statements.Label
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8tests.ast.helpers.*
|
||||
import prog8tests.helpers.*
|
||||
import prog8tests.helpers.assertFailure
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import prog8tests.helpers.compileFile
|
||||
import kotlin.io.path.name
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotEquals
|
||||
|
||||
|
||||
/**
|
||||
@@ -22,14 +22,11 @@ import kotlin.test.assertNotEquals
|
||||
* They are not really unit tests, but rather tests of the whole process,
|
||||
* from source file loading all the way through to running 64tass.
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestCompilerOnImportsAndIncludes {
|
||||
class TestCompilerOnImportsAndIncludes: FunSpec({
|
||||
|
||||
@Nested
|
||||
inner class Import {
|
||||
context("Import") {
|
||||
|
||||
@Test
|
||||
fun testImportFromSameFolder() {
|
||||
test("testImportFromSameFolder") {
|
||||
val filepath = assumeReadableFile(fixturesDir, "importFromSameFolder.p8")
|
||||
assumeReadableFile(fixturesDir, "foo_bar.p8")
|
||||
|
||||
@@ -44,17 +41,15 @@ class TestCompilerOnImportsAndIncludes {
|
||||
.map { it.args[0] as IdentifierReference }
|
||||
.map { it.targetVarDecl(program)!!.value as StringLiteralValue }
|
||||
|
||||
assertEquals("main.bar", strLits[0].value)
|
||||
assertEquals("foo.bar", strLits[1].value)
|
||||
assertEquals("main", strLits[0].definingScope.name)
|
||||
assertEquals("foo", strLits[1].definingScope.name)
|
||||
strLits[0].value shouldBe "main.bar"
|
||||
strLits[1].value shouldBe "foo.bar"
|
||||
strLits[0].definingScope.name shouldBe "main"
|
||||
strLits[1].definingScope.name shouldBe "foo"
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class AsmInclude {
|
||||
@Test
|
||||
fun testAsmIncludeFromSameFolder() {
|
||||
context("AsmInclude") {
|
||||
test("testAsmIncludeFromSameFolder") {
|
||||
val filepath = assumeReadableFile(fixturesDir, "asmIncludeFromSameFolder.p8")
|
||||
assumeReadableFile(fixturesDir, "foo_bar.asm")
|
||||
|
||||
@@ -69,20 +64,18 @@ class TestCompilerOnImportsAndIncludes {
|
||||
.map { it.args[0] }
|
||||
|
||||
val str0 = (args[0] as IdentifierReference).targetVarDecl(program)!!.value as StringLiteralValue
|
||||
assertEquals("main.bar", str0.value)
|
||||
assertEquals("main", str0.definingScope.name)
|
||||
str0.value shouldBe "main.bar"
|
||||
str0.definingScope.name shouldBe "main"
|
||||
|
||||
val id1 = (args[1] as AddressOf).identifier
|
||||
val lbl1 = id1.targetStatement(program) as Label
|
||||
assertEquals("foo_bar", lbl1.name)
|
||||
assertEquals("start", lbl1.definingScope.name)
|
||||
lbl1.name shouldBe "foo_bar"
|
||||
lbl1.definingScope.name shouldBe "start"
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class Asmbinary {
|
||||
@Test
|
||||
fun testAsmbinaryDirectiveWithNonExistingFile() {
|
||||
context("Asmbinary") {
|
||||
test("testAsmbinaryDirectiveWithNonExistingFile") {
|
||||
val p8Path = assumeReadableFile(fixturesDir, "asmBinaryNonExisting.p8")
|
||||
assumeNotExists(fixturesDir, "i_do_not_exist.bin")
|
||||
|
||||
@@ -90,8 +83,7 @@ class TestCompilerOnImportsAndIncludes {
|
||||
.assertFailure()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAsmbinaryDirectiveWithNonReadableFile() {
|
||||
test("testAsmbinaryDirectiveWithNonReadableFile") {
|
||||
val p8Path = assumeReadableFile(fixturesDir, "asmBinaryNonReadable.p8")
|
||||
assumeDirectory(fixturesDir, "subFolder")
|
||||
|
||||
@@ -99,31 +91,30 @@ class TestCompilerOnImportsAndIncludes {
|
||||
.assertFailure()
|
||||
}
|
||||
|
||||
@TestFactory
|
||||
fun asmbinaryDirectiveWithExistingBinFile(): Iterable<DynamicTest> =
|
||||
listOf(
|
||||
val tests = listOf(
|
||||
Triple("same ", "asmBinaryFromSameFolder.p8", "do_nothing1.bin"),
|
||||
Triple("sub", "asmBinaryFromSubFolder.p8", "subFolder/do_nothing2.bin"),
|
||||
).map {
|
||||
val (where, p8Str, binStr) = it
|
||||
dynamicTest("%asmbinary from ${where}folder") {
|
||||
val p8Path = assumeReadableFile(fixturesDir, p8Str)
|
||||
// val binPath = assumeReadableFile(fixturesDir, binStr)
|
||||
assertNotEquals( // the bug we're testing for (#54) was hidden if outputDir == workingDir
|
||||
workingDir.normalize().toAbsolutePath(),
|
||||
outputDir.normalize().toAbsolutePath(),
|
||||
"sanity check: workingDir and outputDir should not be the same folder"
|
||||
)
|
||||
)
|
||||
|
||||
compileFile(Cx16Target, false, p8Path.parent, p8Path.name, outputDir)
|
||||
.assertSuccess(
|
||||
"argument to assembler directive .binary " +
|
||||
"should be relative to the generated .asm file (in output dir), " +
|
||||
"NOT relative to .p8 neither current working dir"
|
||||
)
|
||||
tests.forEach {
|
||||
val (where, p8Str, binStr) = it
|
||||
test("%asmbinary from ${where}folder") {
|
||||
val p8Path = assumeReadableFile(fixturesDir, p8Str)
|
||||
// val binPath = assumeReadableFile(fixturesDir, binStr)
|
||||
|
||||
// the bug we're testing for (#54) was hidden if outputDir == workingDir
|
||||
withClue("sanity check: workingDir and outputDir should not be the same folder") {
|
||||
outputDir.normalize().toAbsolutePath() shouldNotBe workingDir.normalize().toAbsolutePath()
|
||||
}
|
||||
}
|
||||
|
||||
compileFile(Cx16Target, false, p8Path.parent, p8Path.name, outputDir)
|
||||
.assertSuccess(
|
||||
"argument to assembler directive .binary " +
|
||||
"should be relative to the generated .asm file (in output dir), " +
|
||||
"NOT relative to .p8 neither current working dir"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
@@ -1,26 +1,24 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.DynamicTest.dynamicTest
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestFactory
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import io.kotest.matchers.types.instanceOf
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.ForLoop
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8.compilerinterface.size
|
||||
import prog8.compilerinterface.toConstantIntegerRange
|
||||
import prog8tests.ast.helpers.mapCombinations
|
||||
import prog8tests.helpers.*
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.assertFailure
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import prog8tests.helpers.compileText
|
||||
import kotlin.test.assertContains
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
|
||||
/**
|
||||
@@ -28,13 +26,11 @@ import kotlin.test.assertEquals
|
||||
* They are not really unit tests, but rather tests of the whole process,
|
||||
* from source file loading all the way through to running 64tass.
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestCompilerOnRanges {
|
||||
class TestCompilerOnRanges: FunSpec({
|
||||
|
||||
@Test
|
||||
fun testUByteArrayInitializerWithRange_char_to_char() {
|
||||
test("testUByteArrayInitializerWithRange_char_to_char") {
|
||||
val platform = Cx16Target
|
||||
val result = compileText(platform, true, """
|
||||
val result = compileText(platform, false, """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte[] cs = @'a' to 'z' ; values are computed at compile time
|
||||
@@ -55,12 +51,15 @@ class TestCompilerOnRanges {
|
||||
val expectedStr = "$expectedStart .. $expectedEnd"
|
||||
|
||||
val actualStr = "${rhsValues.first()} .. ${rhsValues.last()}"
|
||||
assertEquals(expectedStr, actualStr,".first .. .last")
|
||||
assertEquals(expectedEnd - expectedStart + 1, rhsValues.last() - rhsValues.first() + 1, "rangeExpr.size()")
|
||||
withClue(".first .. .last") {
|
||||
actualStr shouldBe expectedStr
|
||||
}
|
||||
withClue("rangeExpr.size()") {
|
||||
(rhsValues.last() - rhsValues.first() + 1) shouldBe (expectedEnd - expectedStart + 1)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFloatArrayInitializerWithRange_char_to_char() {
|
||||
test("testFloatArrayInitializerWithRange_char_to_char") {
|
||||
val platform = C64Target
|
||||
val result = compileText(platform, optimize = false, """
|
||||
%option enable_floats
|
||||
@@ -84,50 +83,39 @@ class TestCompilerOnRanges {
|
||||
val expectedStr = "$expectedStart .. $expectedEnd"
|
||||
|
||||
val actualStr = "${rhsValues.first()} .. ${rhsValues.last()}"
|
||||
assertEquals(expectedStr, actualStr,".first .. .last")
|
||||
assertEquals(expectedEnd - expectedStart + 1, rhsValues.size, "rangeExpr.size()")
|
||||
withClue(".first .. .last") {
|
||||
actualStr shouldBe expectedStr
|
||||
}
|
||||
withClue("rangeExpr.size()") {
|
||||
rhsValues.size shouldBe (expectedEnd - expectedStart + 1)
|
||||
}
|
||||
}
|
||||
|
||||
fun Subroutine.decl(varName: String): VarDecl {
|
||||
return statements.filterIsInstance<VarDecl>()
|
||||
.first { it.name == varName }
|
||||
}
|
||||
inline fun <reified T : Expression> VarDecl.rhs() : T {
|
||||
return value as T
|
||||
}
|
||||
inline fun <reified T : Expression> ArrayLiteralValue.elements() : List<T> {
|
||||
return value.map { it as T }
|
||||
}
|
||||
context("floatArrayInitializerWithRange") {
|
||||
val combos = cartesianProduct(
|
||||
listOf("", "42", "41"), // sizeInDecl
|
||||
listOf("%option enable_floats", ""), // optEnableFloats
|
||||
listOf(Cx16Target, C64Target), // platform
|
||||
listOf(false, true) // optimize
|
||||
)
|
||||
|
||||
fun <N : Number> assertEndpoints(expFirst: N, expLast: N, actual: Iterable<N>, msg: String = ".first .. .last") {
|
||||
val expectedStr = "$expFirst .. $expLast"
|
||||
val actualStr = "${actual.first()} .. ${actual.last()}"
|
||||
assertEquals(expectedStr, actualStr,".first .. .last")
|
||||
}
|
||||
|
||||
|
||||
@TestFactory
|
||||
fun floatArrayInitializerWithRange() = mapCombinations(
|
||||
dim1 = listOf("", "42", "41"), // sizeInDecl
|
||||
dim2 = listOf("%option enable_floats", ""), // optEnableFloats
|
||||
dim3 = listOf(Cx16Target, C64Target), // platform
|
||||
dim4 = listOf(false, true), // optimize
|
||||
combine4 = { sizeInDecl, optEnableFloats, platform, optimize ->
|
||||
combos.forEach {
|
||||
val (sizeInDecl, optEnableFloats, platform, optimize) = it
|
||||
val displayName =
|
||||
"test failed for: " +
|
||||
when (sizeInDecl) {
|
||||
"" -> "no"
|
||||
"42" -> "correct"
|
||||
else -> "wrong"
|
||||
} + " array size given" +
|
||||
", " + (if (optEnableFloats == "") "without" else "with") + " %option enable_floats" +
|
||||
", ${platform.name}, optimize: $optimize"
|
||||
dynamicTest(displayName) {
|
||||
", " + (if (optEnableFloats == "") "without" else "with") + " %option enable_floats" +
|
||||
", ${platform.name}, optimize: $optimize"
|
||||
|
||||
test(displayName) {
|
||||
val result = compileText(platform, optimize, """
|
||||
$optEnableFloats
|
||||
main {
|
||||
sub start() {
|
||||
float[$sizeInDecl] cs = 1 to 42 ; values are computed at compile time
|
||||
float[$sizeInDecl] cs = 1 to 42 ; values are computed at compile time
|
||||
cs[0] = 23 ; keep optimizer from removing it
|
||||
}
|
||||
}
|
||||
@@ -136,12 +124,12 @@ class TestCompilerOnRanges {
|
||||
result.assertSuccess()
|
||||
else
|
||||
result.assertFailure()
|
||||
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testForLoopWithRange_char_to_char() {
|
||||
test("testForLoopWithRange_char_to_char") {
|
||||
val platform = Cx16Target
|
||||
val result = compileText(platform, optimize = true, """
|
||||
main {
|
||||
@@ -167,12 +155,15 @@ class TestCompilerOnRanges {
|
||||
|
||||
val intProgression = rangeExpr.toConstantIntegerRange()
|
||||
val actualStr = "${intProgression?.first} .. ${intProgression?.last}"
|
||||
assertEquals(expectedStr, actualStr,".first .. .last")
|
||||
assertEquals(expectedEnd - expectedStart + 1, rangeExpr.size(), "rangeExpr.size()")
|
||||
withClue(".first .. .last") {
|
||||
actualStr shouldBe expectedStr
|
||||
}
|
||||
withClue("rangeExpr.size()") {
|
||||
rangeExpr.size() shouldBe (expectedEnd - expectedStart + 1)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testForLoopWithRange_bool_to_bool() {
|
||||
test("testForLoopWithRange_bool_to_bool") {
|
||||
val platform = Cx16Target
|
||||
val result = compileText(platform, optimize = true, """
|
||||
main {
|
||||
@@ -192,14 +183,13 @@ class TestCompilerOnRanges {
|
||||
.map { it.iterable }
|
||||
.filterIsInstance<RangeExpr>()[0]
|
||||
|
||||
assertEquals(2, rangeExpr.size())
|
||||
rangeExpr.size() shouldBe 2
|
||||
val intProgression = rangeExpr.toConstantIntegerRange()
|
||||
assertEquals(0, intProgression?.first)
|
||||
assertEquals(1, intProgression?.last)
|
||||
intProgression?.first shouldBe 0
|
||||
intProgression?.last shouldBe 1
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testForLoopWithRange_ubyte_to_ubyte() {
|
||||
test("testForLoopWithRange_ubyte_to_ubyte") {
|
||||
val platform = Cx16Target
|
||||
val result = compileText(platform, optimize = true, """
|
||||
main {
|
||||
@@ -219,14 +209,13 @@ class TestCompilerOnRanges {
|
||||
.map { it.iterable }
|
||||
.filterIsInstance<RangeExpr>()[0]
|
||||
|
||||
assertEquals(9, rangeExpr.size())
|
||||
rangeExpr.size() shouldBe 9
|
||||
val intProgression = rangeExpr.toConstantIntegerRange()
|
||||
assertEquals(1, intProgression?.first)
|
||||
assertEquals(9, intProgression?.last)
|
||||
intProgression?.first shouldBe 1
|
||||
intProgression?.last shouldBe 9
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testForLoopWithRange_str_downto_str() {
|
||||
test("testForLoopWithRange_str_downto_str") {
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(Cx16Target, true, """
|
||||
main {
|
||||
@@ -238,13 +227,12 @@ class TestCompilerOnRanges {
|
||||
}
|
||||
}
|
||||
""", errors, false).assertFailure()
|
||||
assertEquals(2, errors.errors.size)
|
||||
assertContains(errors.errors[0], ".p8:5:29: range expression from value must be integer")
|
||||
assertContains(errors.errors[1], ".p8:5:44: range expression to value must be integer")
|
||||
errors.errors.size shouldBe 2
|
||||
errors.errors[0] shouldContain ".p8:5:30) range expression from value must be integer"
|
||||
errors.errors[1] shouldContain ".p8:5:45) range expression to value must be integer"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testForLoopWithIterable_str() {
|
||||
test("testForLoopWithIterable_str") {
|
||||
val result = compileText(Cx16Target, false, """
|
||||
main {
|
||||
sub start() {
|
||||
@@ -263,18 +251,54 @@ class TestCompilerOnRanges {
|
||||
.map { it.iterable }
|
||||
.filterIsInstance<IdentifierReference>()[0]
|
||||
|
||||
assertEquals(DataType.STR, iterable.inferType(program).getOr(DataType.UNDEFINED))
|
||||
iterable.inferType(program).getOr(DataType.UNDEFINED) shouldBe DataType.STR
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRangeExprNumericSize() {
|
||||
test("testRangeExprNumericSize") {
|
||||
val expr = RangeExpr(
|
||||
NumericLiteralValue.optimalInteger(10, Position.DUMMY),
|
||||
NumericLiteralValue.optimalInteger(20, Position.DUMMY),
|
||||
NumericLiteralValue.optimalInteger(2, Position.DUMMY),
|
||||
Position.DUMMY)
|
||||
assertEquals(6, expr.size())
|
||||
expr.size() shouldBe 6
|
||||
expr.toConstantIntegerRange()
|
||||
}
|
||||
}
|
||||
|
||||
test("range with negative step should be constvalue") {
|
||||
val result = compileText(C64Target, false, """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte[] array = 100 to 50 step -2
|
||||
ubyte xx
|
||||
for xx in 100 to 50 step -2 {
|
||||
}
|
||||
}
|
||||
}
|
||||
""").assertSuccess()
|
||||
val statements = result.program.entrypoint.statements
|
||||
val array = (statements[0] as VarDecl).value
|
||||
array shouldBe instanceOf<ArrayLiteralValue>()
|
||||
(array as ArrayLiteralValue).value.size shouldBe 26
|
||||
val forloop = (statements.dropLast(1).last() as ForLoop)
|
||||
forloop.iterable shouldBe instanceOf<RangeExpr>()
|
||||
(forloop.iterable as RangeExpr).step shouldBe NumericLiteralValue(DataType.UBYTE, -2.0, Position.DUMMY)
|
||||
}
|
||||
|
||||
test("range with start/end variables should be ok") {
|
||||
val result = compileText(C64Target, false, """
|
||||
main {
|
||||
sub start() {
|
||||
byte from = 100
|
||||
byte end = 50
|
||||
byte xx
|
||||
for xx in from to end step -2 {
|
||||
}
|
||||
}
|
||||
}
|
||||
""").assertSuccess()
|
||||
val statements = result.program.entrypoint.statements
|
||||
val forloop = (statements.dropLast(1).last() as ForLoop)
|
||||
forloop.iterable shouldBe instanceOf<RangeExpr>()
|
||||
(forloop.iterable as RangeExpr).step shouldBe NumericLiteralValue(DataType.UBYTE, -2.0, Position.DUMMY)
|
||||
}
|
||||
})
|
||||
@@ -1,15 +1,11 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.AfterAll
|
||||
import org.junit.jupiter.api.BeforeAll
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import prog8.compiler.CompilationResult
|
||||
import prog8.compiler.CompilerArguments
|
||||
import prog8.compiler.compileProgram
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8tests.ast.helpers.assumeReadableFile
|
||||
import prog8tests.ast.helpers.fixturesDir
|
||||
import prog8tests.ast.helpers.outputDir
|
||||
import prog8tests.ast.helpers.workingDir
|
||||
import prog8tests.helpers.*
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.absolute
|
||||
@@ -22,13 +18,11 @@ import kotlin.io.path.writeText
|
||||
* They are not really unit tests, but rather tests of the whole process,
|
||||
* from source file loading all the way through to running 64tass.
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestCompilerOptionSourcedirs {
|
||||
class TestCompilerOptionSourcedirs: FunSpec({
|
||||
|
||||
private lateinit var tempFileInWorkingDir: Path
|
||||
lateinit var tempFileInWorkingDir: Path
|
||||
|
||||
@BeforeAll
|
||||
fun setUp() {
|
||||
beforeSpec {
|
||||
tempFileInWorkingDir = createTempFile(directory = workingDir, prefix = "tmp_", suffix = ".p8")
|
||||
.also { it.writeText("""
|
||||
main {
|
||||
@@ -38,13 +32,12 @@ class TestCompilerOptionSourcedirs {
|
||||
""")}
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
fun tearDown() {
|
||||
afterSpec {
|
||||
tempFileInWorkingDir.deleteExisting()
|
||||
}
|
||||
|
||||
private fun compileFile(filePath: Path, sourceDirs: List<String>) =
|
||||
compileProgram(
|
||||
fun compileFile(filePath: Path, sourceDirs: List<String>): CompilationResult {
|
||||
val args = CompilerArguments(
|
||||
filepath = filePath,
|
||||
optimize = false,
|
||||
optimizeFloatExpressions = false,
|
||||
@@ -55,48 +48,44 @@ class TestCompilerOptionSourcedirs {
|
||||
sourceDirs,
|
||||
outputDir
|
||||
)
|
||||
return compileProgram(args)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAbsoluteFilePathInWorkingDir() {
|
||||
test("testAbsoluteFilePathInWorkingDir") {
|
||||
val filepath = assumeReadableFile(tempFileInWorkingDir.absolute())
|
||||
compileFile(filepath, listOf())
|
||||
.assertSuccess()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFilePathInWorkingDirRelativeToWorkingDir() {
|
||||
test("testFilePathInWorkingDirRelativeToWorkingDir") {
|
||||
val filepath = assumeReadableFile(workingDir.relativize(tempFileInWorkingDir.absolute()))
|
||||
compileFile(filepath, listOf())
|
||||
.assertSuccess()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFilePathInWorkingDirRelativeTo1stInSourcedirs() {
|
||||
test("testFilePathInWorkingDirRelativeTo1stInSourcedirs") {
|
||||
val filepath = assumeReadableFile(tempFileInWorkingDir)
|
||||
compileFile(filepath.fileName, listOf(workingDir.toString()))
|
||||
.assertSuccess()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAbsoluteFilePathOutsideWorkingDir() {
|
||||
val filepath = assumeReadableFile(fixturesDir, "simple_main.p8")
|
||||
test("testAbsoluteFilePathOutsideWorkingDir") {
|
||||
val filepath = assumeReadableFile(fixturesDir, "ast_simple_main.p8")
|
||||
compileFile(filepath.absolute(), listOf())
|
||||
.assertSuccess()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFilePathOutsideWorkingDirRelativeToWorkingDir() {
|
||||
val filepath = workingDir.relativize(assumeReadableFile(fixturesDir, "simple_main.p8").absolute())
|
||||
test("testFilePathOutsideWorkingDirRelativeToWorkingDir") {
|
||||
val filepath = workingDir.relativize(assumeReadableFile(fixturesDir, "ast_simple_main.p8").absolute())
|
||||
compileFile(filepath, listOf())
|
||||
.assertSuccess()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFilePathOutsideWorkingDirRelativeTo1stInSourcedirs() {
|
||||
val filepath = assumeReadableFile(fixturesDir, "simple_main.p8")
|
||||
test("testFilePathOutsideWorkingDirRelativeTo1stInSourcedirs") {
|
||||
val filepath = assumeReadableFile(fixturesDir, "ast_simple_main.p8")
|
||||
val sourcedirs = listOf("$fixturesDir")
|
||||
compileFile(filepath.fileName, sourcedirs)
|
||||
.assertSuccess()
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
@@ -1,25 +1,23 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.string.shouldStartWith
|
||||
import prog8.ast.internedStringsModuleName
|
||||
import prog8.compiler.determineCompilationOptions
|
||||
import prog8.compiler.parseImports
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compilerinterface.ZeropageType
|
||||
import prog8tests.ast.helpers.outputDir
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import prog8tests.helpers.compileText
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
import prog8tests.helpers.outputDir
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestImportedModulesOrderAndOptions {
|
||||
class TestImportedModulesOrderAndOptions: FunSpec({
|
||||
|
||||
@Test
|
||||
fun testImportedModuleOrderAndMainModuleCorrect() {
|
||||
test("testImportedModuleOrderAndMainModuleCorrect") {
|
||||
val result = compileText(C64Target, false, """
|
||||
%import textio
|
||||
%import floats
|
||||
@@ -30,25 +28,27 @@ main {
|
||||
}
|
||||
}
|
||||
""").assertSuccess()
|
||||
assertTrue(result.program.toplevelModule.name.startsWith("on_the_fly_test"))
|
||||
result.program.toplevelModule.name shouldStartWith "on_the_fly_test"
|
||||
|
||||
val moduleNames = result.program.modules.map { it.name }
|
||||
assertTrue(moduleNames[0].startsWith("on_the_fly_test"), "main module must be first")
|
||||
assertEquals(listOf(
|
||||
"prog8_interned_strings",
|
||||
"textio",
|
||||
"syslib",
|
||||
"conv",
|
||||
"floats",
|
||||
"math",
|
||||
"prog8_lib"
|
||||
), moduleNames.drop(1), "module order in parse tree")
|
||||
|
||||
assertTrue(result.program.toplevelModule.name.startsWith("on_the_fly_test"))
|
||||
withClue("main module must be first") {
|
||||
moduleNames[0] shouldStartWith "on_the_fly_test"
|
||||
}
|
||||
withClue("module order in parse tree") {
|
||||
moduleNames.drop(1) shouldBe listOf(
|
||||
"prog8_interned_strings",
|
||||
"textio",
|
||||
"syslib",
|
||||
"conv",
|
||||
"floats",
|
||||
"math",
|
||||
"prog8_lib"
|
||||
)
|
||||
}
|
||||
result.program.toplevelModule.name shouldStartWith "on_the_fly_test"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCompilationOptionsCorrectFromMain() {
|
||||
test("testCompilationOptionsCorrectFromMain") {
|
||||
val result = compileText(C64Target, false, """
|
||||
%import textio
|
||||
%import floats
|
||||
@@ -61,15 +61,14 @@ main {
|
||||
}
|
||||
}
|
||||
""").assertSuccess()
|
||||
assertTrue(result.program.toplevelModule.name.startsWith("on_the_fly_test"))
|
||||
result.program.toplevelModule.name shouldStartWith "on_the_fly_test"
|
||||
val options = determineCompilationOptions(result.program, C64Target)
|
||||
assertTrue(options.floats)
|
||||
assertEquals(ZeropageType.DONTUSE, options.zeropage)
|
||||
assertTrue(options.noSysInit)
|
||||
options.floats shouldBe true
|
||||
options.zeropage shouldBe ZeropageType.DONTUSE
|
||||
options.noSysInit shouldBe true
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testModuleOrderAndCompilationOptionsCorrectWithJustImports() {
|
||||
test("testModuleOrderAndCompilationOptionsCorrectWithJustImports") {
|
||||
val errors = ErrorReporterForTests()
|
||||
val sourceText = """
|
||||
%import textio
|
||||
@@ -88,17 +87,23 @@ main {
|
||||
filepath.toFile().writeText(sourceText)
|
||||
val (program, options, importedfiles) = parseImports(filepath, errors, C64Target, emptyList())
|
||||
|
||||
assertEquals(filenameBase, program.toplevelModule.name)
|
||||
assertEquals(1, importedfiles.size, "all imports other than the test source must have been internal resources library files")
|
||||
assertEquals(listOf(
|
||||
internedStringsModuleName,
|
||||
filenameBase,
|
||||
"textio", "syslib", "conv", "floats", "math", "prog8_lib"
|
||||
), program.modules.map {it.name}, "module order in parse tree")
|
||||
assertTrue(options.floats)
|
||||
assertEquals(ZeropageType.DONTUSE, options.zeropage, "zeropage option must be correctly taken from main module, not from float module import logic")
|
||||
assertTrue(options.noSysInit)
|
||||
program.toplevelModule.name shouldBe filenameBase
|
||||
withClue("all imports other than the test source must have been internal resources library files") {
|
||||
importedfiles.size shouldBe 1
|
||||
}
|
||||
withClue("module order in parse tree") {
|
||||
program.modules.map { it.name } shouldBe
|
||||
listOf(
|
||||
internedStringsModuleName,
|
||||
filenameBase,
|
||||
"textio", "syslib", "conv", "floats", "math", "prog8_lib"
|
||||
)
|
||||
}
|
||||
options.floats shouldBe true
|
||||
options.noSysInit shouldBe true
|
||||
withClue("zeropage option must be correctly taken from main module, not from float module import logic") {
|
||||
options.zeropage shouldBe ZeropageType.DONTUSE
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
})
|
||||
+127
-94
@@ -1,7 +1,7 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
@@ -12,179 +12,212 @@ import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.expressions.PrefixExpression
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.printProgram
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compilerinterface.isInRegularRAMof
|
||||
import prog8.compilerinterface.isIOAddress
|
||||
import prog8.parser.SourceCode
|
||||
import prog8tests.helpers.DummyFunctions
|
||||
import prog8tests.helpers.DummyMemsizer
|
||||
import prog8tests.helpers.DummyStringEncoder
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestMemory {
|
||||
class TestMemory: FunSpec({
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_memory_addresses() {
|
||||
fun wrapWithProgram(statements: List<Statement>): Program {
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
val subroutine = Subroutine("test", mutableListOf(), emptyList(), statements.toMutableList(), false, Position.DUMMY)
|
||||
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||
program.addModule(module)
|
||||
return program
|
||||
}
|
||||
|
||||
var memexpr = NumericLiteralValue.optimalInteger(0x0000, Position.DUMMY)
|
||||
test("assignment target not in mapped IO space C64") {
|
||||
|
||||
var memexpr = NumericLiteralValue.optimalInteger(0x0002, Position.DUMMY)
|
||||
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
||||
var assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0x9fff, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xa000, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xc000, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xcfff, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
||||
}
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
|
||||
@Test
|
||||
fun testNotInValidRamC64_memory_addresses() {
|
||||
|
||||
var memexpr = NumericLiteralValue.optimalInteger(0xa000, Position.DUMMY)
|
||||
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assertFalse(target.isInRegularRAMof(C64Target.machine))
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xafff, Position.DUMMY)
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xeeee, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assertFalse(target.isInRegularRAMof(C64Target.machine))
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xd000, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assertFalse(target.isInRegularRAMof(C64Target.machine))
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xffff, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assertFalse(target.isInRegularRAMof(C64Target.machine))
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_memory_identifiers() {
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
var target = createTestProgramForMemoryRefViaVar(program, 0x1000, VarDeclType.VAR)
|
||||
test("assign target in mapped IO space C64") {
|
||||
|
||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
||||
target = createTestProgramForMemoryRefViaVar(program, 0xd020, VarDeclType.VAR)
|
||||
assertFalse(target.isInRegularRAMof(C64Target.machine))
|
||||
target = createTestProgramForMemoryRefViaVar(program, 0x1000, VarDeclType.CONST)
|
||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
||||
target = createTestProgramForMemoryRefViaVar(program, 0xd020, VarDeclType.CONST)
|
||||
assertFalse(target.isInRegularRAMof(C64Target.machine))
|
||||
target = createTestProgramForMemoryRefViaVar(program, 0x1000, VarDeclType.MEMORY)
|
||||
assertFalse(target.isInRegularRAMof(C64Target.machine))
|
||||
var memexpr = NumericLiteralValue.optimalInteger(0x0000, Position.DUMMY)
|
||||
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
var assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe true
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0x0001, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe true
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xd000, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe true
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xdfff, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe true
|
||||
}
|
||||
|
||||
private fun createTestProgramForMemoryRefViaVar(program: Program, address: Int, vartype: VarDeclType): AssignTarget {
|
||||
val decl = VarDecl(vartype, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
|
||||
fun createTestProgramForMemoryRefViaVar(address: UInt, vartype: VarDeclType): AssignTarget {
|
||||
val decl = VarDecl(vartype, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, null, Position.DUMMY)
|
||||
val memexpr = IdentifierReference(listOf("address"), Position.DUMMY)
|
||||
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||
module.linkIntoProgram(program)
|
||||
wrapWithProgram(listOf(decl, assignment))
|
||||
return target
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_memory_expression() {
|
||||
val memexpr = PrefixExpression("+", NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY), Position.DUMMY)
|
||||
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assertFalse(target.isInRegularRAMof(C64Target.machine))
|
||||
test("identifier mapped to IO memory on C64") {
|
||||
var target = createTestProgramForMemoryRefViaVar(0x1000u, VarDeclType.VAR)
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
target = createTestProgramForMemoryRefViaVar(0xd020u, VarDeclType.VAR)
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
target = createTestProgramForMemoryRefViaVar(0x1000u, VarDeclType.CONST)
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
target = createTestProgramForMemoryRefViaVar(0xd020u, VarDeclType.CONST)
|
||||
target.isIOAddress(C64Target.machine) shouldBe true
|
||||
target = createTestProgramForMemoryRefViaVar(0x1000u, VarDeclType.MEMORY)
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
target = createTestProgramForMemoryRefViaVar(0xd020u, VarDeclType.MEMORY)
|
||||
target.isIOAddress(C64Target.machine) shouldBe true
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_variable() {
|
||||
val decl = VarDecl(VarDeclType.VAR, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, false, false, false, Position.DUMMY)
|
||||
test("memory expression mapped to IO memory on C64") {
|
||||
var memexpr = PrefixExpression("+", NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY), Position.DUMMY)
|
||||
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
var assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
|
||||
memexpr = PrefixExpression("+", NumericLiteralValue.optimalInteger(0xd020, Position.DUMMY), Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
wrapWithProgram(listOf(assign))
|
||||
printProgram(target.definingModule.program)
|
||||
target.isIOAddress(C64Target.machine) shouldBe true
|
||||
}
|
||||
|
||||
test("regular variable not in mapped IO ram on C64") {
|
||||
val decl = VarDecl(VarDeclType.VAR, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, false, false, false, null, Position.DUMMY)
|
||||
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
.addModule(module)
|
||||
module.linkIntoProgram(program)
|
||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_memmap_variable() {
|
||||
val address = 0x1000
|
||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
|
||||
test("memory mapped variable not in mapped IO ram on C64") {
|
||||
val address = 0x1000u
|
||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, null, Position.DUMMY)
|
||||
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
.addModule(module)
|
||||
module.linkIntoProgram(program)
|
||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotInValidRamC64_memmap_variable() {
|
||||
val address = 0xd020
|
||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
|
||||
test("memory mapped variable in mapped IO ram on C64") {
|
||||
val address = 0xd020u
|
||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, null, Position.DUMMY)
|
||||
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
.addModule(module)
|
||||
module.linkIntoProgram(program)
|
||||
assertFalse(target.isInRegularRAMof(C64Target.machine))
|
||||
target.isIOAddress(C64Target.machine) shouldBe true
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_array() {
|
||||
val decl = VarDecl(VarDeclType.VAR, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, false, false, false, Position.DUMMY)
|
||||
test("array not in mapped IO ram") {
|
||||
val decl = VarDecl(VarDeclType.VAR, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, false, false, false, null, Position.DUMMY)
|
||||
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
.addModule(module)
|
||||
module.linkIntoProgram(program)
|
||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_array_memmapped() {
|
||||
val address = 0x1000
|
||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
|
||||
test("memory mapped array not in mapped IO ram") {
|
||||
val address = 0x1000u
|
||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, null, Position.DUMMY)
|
||||
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
.addModule(module)
|
||||
module.linkIntoProgram(program)
|
||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
||||
target.isIOAddress(C64Target.machine) shouldBe false
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotValidRamC64_array_memmapped() {
|
||||
val address = 0xe000
|
||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
|
||||
test("memory mapped array in mapped IO ram") {
|
||||
val address = 0xd800u
|
||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, null, Position.DUMMY)
|
||||
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
.addModule(module)
|
||||
module.linkIntoProgram(program)
|
||||
assertFalse(target.isInRegularRAMof(C64Target.machine))
|
||||
target.isIOAddress(C64Target.machine) shouldBe true
|
||||
}
|
||||
}
|
||||
})
|
||||
+95
-103
@@ -1,116 +1,108 @@
|
||||
package prog8tests
|
||||
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.Matchers.closeTo
|
||||
import org.hamcrest.Matchers.equalTo
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.doubles.plusOrMinus
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.ast.toHex
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_NEGATIVE
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_POSITIVE
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.Mflpt5
|
||||
import prog8.compiler.target.cbm.Mflpt5
|
||||
import prog8.compilerinterface.InternalCompilerException
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestNumbers {
|
||||
@Test
|
||||
fun testToHex() {
|
||||
assertEquals("0", 0.toHex())
|
||||
assertEquals("1", 1.toHex())
|
||||
assertEquals("1", 1.234.toHex())
|
||||
assertEquals("10", 10.toHex())
|
||||
assertEquals("10", 10.99.toHex())
|
||||
assertEquals("15", 15.toHex())
|
||||
assertEquals("\$10", 16.toHex())
|
||||
assertEquals("\$ff", 255.toHex())
|
||||
assertEquals("\$0100", 256.toHex())
|
||||
assertEquals("\$4e5c", 20060.toHex())
|
||||
assertEquals("\$c382", 50050.toHex())
|
||||
assertEquals("\$ffff", 65535.toHex())
|
||||
assertEquals("\$ffff", 65535L.toHex())
|
||||
assertEquals("0", 0.toHex())
|
||||
assertEquals("-1", (-1).toHex())
|
||||
assertEquals("-1", (-1.234).toHex())
|
||||
assertEquals("-10", (-10).toHex())
|
||||
assertEquals("-10", (-10.99).toHex())
|
||||
assertEquals("-15", (-15).toHex())
|
||||
assertEquals("-\$10", (-16).toHex())
|
||||
assertEquals("-\$ff", (-255).toHex())
|
||||
assertEquals("-\$0100", (-256).toHex())
|
||||
assertEquals("-\$4e5c", (-20060).toHex())
|
||||
assertEquals("-\$c382", (-50050).toHex())
|
||||
assertEquals("-\$ffff", (-65535).toHex())
|
||||
assertEquals("-\$ffff", (-65535L).toHex())
|
||||
assertFailsWith<IllegalArgumentException> { 65536.toHex() }
|
||||
assertFailsWith<IllegalArgumentException> { 65536L.toHex() }
|
||||
|
||||
class TestNumbers: FunSpec({
|
||||
test("testToHex") {
|
||||
0.toHex() shouldBe "0"
|
||||
1.toHex() shouldBe "1"
|
||||
1.234.toHex() shouldBe "1"
|
||||
10.toHex() shouldBe "10"
|
||||
10.99.toHex() shouldBe "10"
|
||||
15.toHex() shouldBe "15"
|
||||
16.toHex() shouldBe "\$10"
|
||||
255.toHex() shouldBe "\$ff"
|
||||
256.toHex() shouldBe "\$0100"
|
||||
20060.toHex() shouldBe "\$4e5c"
|
||||
50050.toHex() shouldBe "\$c382"
|
||||
65535.toHex() shouldBe "\$ffff"
|
||||
65535L.toHex() shouldBe "\$ffff"
|
||||
0.toHex() shouldBe "0"
|
||||
(-1).toHex() shouldBe "-1"
|
||||
(-1.234).toHex() shouldBe "-1"
|
||||
(-10).toHex() shouldBe "-10"
|
||||
(-10.99).toHex() shouldBe "-10"
|
||||
(-15).toHex() shouldBe "-15"
|
||||
(-16).toHex() shouldBe "-\$10"
|
||||
(-255).toHex() shouldBe "-\$ff"
|
||||
(-256).toHex() shouldBe "-\$0100"
|
||||
(-20060).toHex() shouldBe "-\$4e5c"
|
||||
(-50050).toHex() shouldBe "-\$c382"
|
||||
(-65535).toHex() shouldBe "-\$ffff"
|
||||
(-65535L).toHex() shouldBe "-\$ffff"
|
||||
shouldThrow<IllegalArgumentException> { 65536.toHex() }
|
||||
shouldThrow<IllegalArgumentException> { 65536L.toHex() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFloatToMflpt5() {
|
||||
assertThat(Mflpt5.fromNumber(0), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)))
|
||||
assertThat(Mflpt5.fromNumber(3.141592653), equalTo(Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA1)))
|
||||
assertThat(Mflpt5.fromNumber(3.141592653589793), equalTo(Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA2)))
|
||||
assertThat(Mflpt5.fromNumber(32768), equalTo(Mflpt5(0x90, 0x00, 0x00, 0x00, 0x00)))
|
||||
assertThat(Mflpt5.fromNumber(-32768), equalTo(Mflpt5(0x90, 0x80, 0x00, 0x00, 0x00)))
|
||||
assertThat(Mflpt5.fromNumber(1), equalTo(Mflpt5(0x81, 0x00, 0x00, 0x00, 0x00)))
|
||||
assertThat(Mflpt5.fromNumber(0.7071067812), equalTo(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x34)))
|
||||
assertThat(Mflpt5.fromNumber(0.7071067811865476), equalTo(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x33)))
|
||||
assertThat(Mflpt5.fromNumber(1.4142135624), equalTo(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x34)))
|
||||
assertThat(Mflpt5.fromNumber(1.4142135623730951), equalTo(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x33)))
|
||||
assertThat(Mflpt5.fromNumber(-.5), equalTo(Mflpt5(0x80, 0x80, 0x00, 0x00, 0x00)))
|
||||
assertThat(Mflpt5.fromNumber(0.69314718061), equalTo(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF8)))
|
||||
assertThat(Mflpt5.fromNumber(0.6931471805599453), equalTo(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF7)))
|
||||
assertThat(Mflpt5.fromNumber(10), equalTo(Mflpt5(0x84, 0x20, 0x00, 0x00, 0x00)))
|
||||
assertThat(Mflpt5.fromNumber(1000000000), equalTo(Mflpt5(0x9E, 0x6E, 0x6B, 0x28, 0x00)))
|
||||
assertThat(Mflpt5.fromNumber(.5), equalTo(Mflpt5(0x80, 0x00, 0x00, 0x00, 0x00)))
|
||||
assertThat(Mflpt5.fromNumber(1.4426950408889634), equalTo(Mflpt5(0x81, 0x38, 0xAA, 0x3B, 0x29)))
|
||||
assertThat(Mflpt5.fromNumber(1.5707963267948966), equalTo(Mflpt5(0x81, 0x49, 0x0F, 0xDA, 0xA2)))
|
||||
assertThat(Mflpt5.fromNumber(6.283185307179586), equalTo(Mflpt5(0x83, 0x49, 0x0F, 0xDA, 0xA2)))
|
||||
assertThat(Mflpt5.fromNumber(.25), equalTo(Mflpt5(0x7F, 0x00, 0x00, 0x00, 0x00)))
|
||||
assertThat(Mflpt5.fromNumber(123.45678e22), equalTo(Mflpt5(0xd1, 0x02, 0xb7, 0x06, 0xfb)))
|
||||
assertThat(Mflpt5.fromNumber(-123.45678e-22), equalTo(Mflpt5(0x3e, 0xe9, 0x34, 0x09, 0x1b)))
|
||||
test("testFloatToMflpt5") {
|
||||
Mflpt5.fromNumber(0) shouldBe Mflpt5(0x00u, 0x00u, 0x00u, 0x00u, 0x00u)
|
||||
Mflpt5.fromNumber(3.141592653) shouldBe Mflpt5(0x82u, 0x49u, 0x0Fu, 0xDAu, 0xA1u)
|
||||
Mflpt5.fromNumber(3.141592653589793) shouldBe Mflpt5(0x82u, 0x49u, 0x0Fu, 0xDAu, 0xA2u)
|
||||
Mflpt5.fromNumber(32768) shouldBe Mflpt5(0x90u, 0x00u, 0x00u, 0x00u, 0x00u)
|
||||
Mflpt5.fromNumber(-32768) shouldBe Mflpt5(0x90u, 0x80u, 0x00u, 0x00u, 0x00u)
|
||||
Mflpt5.fromNumber(1) shouldBe Mflpt5(0x81u, 0x00u, 0x00u, 0x00u, 0x00u)
|
||||
Mflpt5.fromNumber(0.7071067812) shouldBe Mflpt5(0x80u, 0x35u, 0x04u, 0xF3u, 0x34u)
|
||||
Mflpt5.fromNumber(0.7071067811865476) shouldBe Mflpt5(0x80u, 0x35u, 0x04u, 0xF3u, 0x33u)
|
||||
Mflpt5.fromNumber(1.4142135624) shouldBe Mflpt5(0x81u, 0x35u, 0x04u, 0xF3u, 0x34u)
|
||||
Mflpt5.fromNumber(1.4142135623730951) shouldBe Mflpt5(0x81u, 0x35u, 0x04u, 0xF3u, 0x33u)
|
||||
Mflpt5.fromNumber(-.5) shouldBe Mflpt5(0x80u, 0x80u, 0x00u, 0x00u, 0x00u)
|
||||
Mflpt5.fromNumber(0.69314718061) shouldBe Mflpt5(0x80u, 0x31u, 0x72u, 0x17u, 0xF8u)
|
||||
Mflpt5.fromNumber(0.6931471805599453) shouldBe Mflpt5(0x80u, 0x31u, 0x72u, 0x17u, 0xF7u)
|
||||
Mflpt5.fromNumber(10) shouldBe Mflpt5(0x84u, 0x20u, 0x00u, 0x00u, 0x00u)
|
||||
Mflpt5.fromNumber(1000000000) shouldBe Mflpt5(0x9Eu, 0x6Eu, 0x6Bu, 0x28u, 0x00u)
|
||||
Mflpt5.fromNumber(.5) shouldBe Mflpt5(0x80u, 0x00u, 0x00u, 0x00u, 0x00u)
|
||||
Mflpt5.fromNumber(1.4426950408889634) shouldBe Mflpt5(0x81u, 0x38u, 0xAAu, 0x3Bu, 0x29u)
|
||||
Mflpt5.fromNumber(1.5707963267948966) shouldBe Mflpt5(0x81u, 0x49u, 0x0Fu, 0xDAu, 0xA2u)
|
||||
Mflpt5.fromNumber(6.283185307179586) shouldBe Mflpt5(0x83u, 0x49u, 0x0Fu, 0xDAu, 0xA2u)
|
||||
Mflpt5.fromNumber(.25) shouldBe Mflpt5(0x7Fu, 0x00u, 0x00u, 0x00u, 0x00u)
|
||||
Mflpt5.fromNumber(123.45678e22) shouldBe Mflpt5(0xd1u, 0x02u, 0xb7u, 0x06u, 0xfbu)
|
||||
Mflpt5.fromNumber(-123.45678e-22) shouldBe Mflpt5(0x3eu, 0xe9u, 0x34u, 0x09u, 0x1bu)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFloatRange() {
|
||||
assertThat(Mflpt5.fromNumber(FLOAT_MAX_POSITIVE), equalTo(Mflpt5(0xff, 0x7f, 0xff, 0xff, 0xff)))
|
||||
assertThat(Mflpt5.fromNumber(FLOAT_MAX_NEGATIVE), equalTo(Mflpt5(0xff, 0xff, 0xff, 0xff, 0xff)))
|
||||
assertThat(Mflpt5.fromNumber(1.7e-38), equalTo(Mflpt5(0x03, 0x39, 0x1d, 0x15, 0x63)))
|
||||
assertThat(Mflpt5.fromNumber(1.7e-39), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)))
|
||||
assertThat(Mflpt5.fromNumber(-1.7e-38), equalTo(Mflpt5(0x03, 0xb9, 0x1d, 0x15, 0x63)))
|
||||
assertThat(Mflpt5.fromNumber(-1.7e-39), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)))
|
||||
assertFailsWith<InternalCompilerException> { Mflpt5.fromNumber(1.7014118346e+38) }
|
||||
assertFailsWith<InternalCompilerException> { Mflpt5.fromNumber(-1.7014118346e+38) }
|
||||
assertFailsWith<InternalCompilerException> { Mflpt5.fromNumber(1.7014118347e+38) }
|
||||
assertFailsWith<InternalCompilerException> { Mflpt5.fromNumber(-1.7014118347e+38) }
|
||||
test("testFloatRange") {
|
||||
Mflpt5.fromNumber(Mflpt5.FLOAT_MAX_POSITIVE) shouldBe Mflpt5(0xffu, 0x7fu, 0xffu, 0xffu, 0xffu)
|
||||
Mflpt5.fromNumber(Mflpt5.FLOAT_MAX_NEGATIVE) shouldBe Mflpt5(0xffu, 0xffu, 0xffu, 0xffu, 0xffu)
|
||||
Mflpt5.fromNumber(1.7e-38) shouldBe Mflpt5(0x03u, 0x39u, 0x1du, 0x15u, 0x63u)
|
||||
Mflpt5.fromNumber(1.7e-39) shouldBe Mflpt5(0x00u, 0x00u, 0x00u, 0x00u, 0x00u)
|
||||
Mflpt5.fromNumber(-1.7e-38) shouldBe Mflpt5(0x03u, 0xb9u, 0x1du, 0x15u, 0x63u)
|
||||
Mflpt5.fromNumber(-1.7e-39) shouldBe Mflpt5(0x00u, 0x00u, 0x00u, 0x00u, 0x00u)
|
||||
shouldThrow<InternalCompilerException> { Mflpt5.fromNumber(1.7014118346e+38) }
|
||||
shouldThrow<InternalCompilerException> { Mflpt5.fromNumber(-1.7014118346e+38) }
|
||||
shouldThrow<InternalCompilerException> { Mflpt5.fromNumber(1.7014118347e+38) }
|
||||
shouldThrow<InternalCompilerException> { Mflpt5.fromNumber(-1.7014118347e+38) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMflpt5ToFloat() {
|
||||
test("testMflpt5ToFloat") {
|
||||
val epsilon=0.000000001
|
||||
assertThat(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(0.0))
|
||||
assertThat(Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA1).toDouble(), closeTo(3.141592653, epsilon))
|
||||
assertThat(Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA2).toDouble(), closeTo(3.141592653589793, epsilon))
|
||||
assertThat(Mflpt5(0x90, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(32768.0))
|
||||
assertThat(Mflpt5(0x90, 0x80, 0x00, 0x00, 0x00).toDouble(), equalTo(-32768.0))
|
||||
assertThat(Mflpt5(0x81, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(1.0))
|
||||
assertThat(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x34).toDouble(), closeTo(0.7071067812, epsilon))
|
||||
assertThat(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x33).toDouble(), closeTo(0.7071067811865476, epsilon))
|
||||
assertThat(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x34).toDouble(), closeTo(1.4142135624, epsilon))
|
||||
assertThat(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x33).toDouble(), closeTo(1.4142135623730951, epsilon))
|
||||
assertThat(Mflpt5(0x80, 0x80, 0x00, 0x00, 0x00).toDouble(), equalTo(-.5))
|
||||
assertThat(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF8).toDouble(), closeTo(0.69314718061, epsilon))
|
||||
assertThat(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF7).toDouble(), closeTo(0.6931471805599453, epsilon))
|
||||
assertThat(Mflpt5(0x84, 0x20, 0x00, 0x00, 0x00).toDouble(), equalTo(10.0))
|
||||
assertThat(Mflpt5(0x9E, 0x6E, 0x6B, 0x28, 0x00).toDouble(), equalTo(1000000000.0))
|
||||
assertThat(Mflpt5(0x80, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(.5))
|
||||
assertThat(Mflpt5(0x81, 0x38, 0xAA, 0x3B, 0x29).toDouble(), closeTo(1.4426950408889634, epsilon))
|
||||
assertThat(Mflpt5(0x81, 0x49, 0x0F, 0xDA, 0xA2).toDouble(), closeTo(1.5707963267948966, epsilon))
|
||||
assertThat(Mflpt5(0x83, 0x49, 0x0F, 0xDA, 0xA2).toDouble(), closeTo(6.283185307179586, epsilon))
|
||||
assertThat(Mflpt5(0x7F, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(.25))
|
||||
assertThat(Mflpt5(0xd1, 0x02, 0xb7, 0x06, 0xfb).toDouble(), closeTo(123.45678e22, 1.0e15))
|
||||
assertThat(Mflpt5(0x3e, 0xe9, 0x34, 0x09, 0x1b).toDouble(), closeTo(-123.45678e-22, epsilon))
|
||||
|
||||
Mflpt5(0x00u, 0x00u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe 0.0
|
||||
Mflpt5(0x82u, 0x49u, 0x0Fu, 0xDAu, 0xA1u).toDouble() shouldBe(3.141592653 plusOrMinus epsilon)
|
||||
Mflpt5(0x82u, 0x49u, 0x0Fu, 0xDAu, 0xA2u).toDouble() shouldBe(3.141592653589793 plusOrMinus epsilon)
|
||||
Mflpt5(0x90u, 0x00u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe 32768.0
|
||||
Mflpt5(0x90u, 0x80u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe -32768.0
|
||||
Mflpt5(0x81u, 0x00u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe 1.0
|
||||
Mflpt5(0x80u, 0x35u, 0x04u, 0xF3u, 0x34u).toDouble() shouldBe(0.7071067812 plusOrMinus epsilon)
|
||||
Mflpt5(0x80u, 0x35u, 0x04u, 0xF3u, 0x33u).toDouble() shouldBe(0.7071067811865476 plusOrMinus epsilon)
|
||||
Mflpt5(0x81u, 0x35u, 0x04u, 0xF3u, 0x34u).toDouble() shouldBe(1.4142135624 plusOrMinus epsilon)
|
||||
Mflpt5(0x81u, 0x35u, 0x04u, 0xF3u, 0x33u).toDouble() shouldBe(1.4142135623730951 plusOrMinus epsilon)
|
||||
Mflpt5(0x80u, 0x80u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe -.5
|
||||
Mflpt5(0x80u, 0x31u, 0x72u, 0x17u, 0xF8u).toDouble() shouldBe(0.69314718061 plusOrMinus epsilon)
|
||||
Mflpt5(0x80u, 0x31u, 0x72u, 0x17u, 0xF7u).toDouble() shouldBe(0.6931471805599453 plusOrMinus epsilon)
|
||||
Mflpt5(0x84u, 0x20u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe 10.0
|
||||
Mflpt5(0x9Eu, 0x6Eu, 0x6Bu, 0x28u, 0x00u).toDouble() shouldBe 1000000000.0
|
||||
Mflpt5(0x80u, 0x00u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe .5
|
||||
Mflpt5(0x81u, 0x38u, 0xAAu, 0x3Bu, 0x29u).toDouble() shouldBe(1.4426950408889634 plusOrMinus epsilon)
|
||||
Mflpt5(0x81u, 0x49u, 0x0Fu, 0xDAu, 0xA2u).toDouble() shouldBe(1.5707963267948966 plusOrMinus epsilon)
|
||||
Mflpt5(0x83u, 0x49u, 0x0Fu, 0xDAu, 0xA2u).toDouble() shouldBe(6.283185307179586 plusOrMinus epsilon)
|
||||
Mflpt5(0x7Fu, 0x00u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe .25
|
||||
Mflpt5(0xd1u, 0x02u, 0xb7u, 0x06u, 0xfbu).toDouble() shouldBe(123.45678e22 plusOrMinus 1.0e15)
|
||||
Mflpt5(0x3eu, 0xe9u, 0x34u, 0x09u, 0x1bu).toDouble() shouldBe(-123.45678e-22 plusOrMinus epsilon)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,144 +1,153 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.ExpressionError
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.ArrayLiteralValue
|
||||
import prog8.ast.expressions.InferredTypes
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.expressions.StringLiteralValue
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertNotEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestNumericLiteralValue {
|
||||
class TestNumericLiteralValue: FunSpec({
|
||||
|
||||
private fun sameValueAndType(lv1: NumericLiteralValue, lv2: NumericLiteralValue): Boolean {
|
||||
fun sameValueAndType(lv1: NumericLiteralValue, lv2: NumericLiteralValue): Boolean {
|
||||
return lv1.type==lv2.type && lv1==lv2
|
||||
}
|
||||
|
||||
private val dummyPos = Position("test", 0, 0, 0)
|
||||
val dummyPos = Position("test", 0, 0, 0)
|
||||
|
||||
@Test
|
||||
fun testIdentity() {
|
||||
val v = NumericLiteralValue(DataType.UWORD, 12345, dummyPos)
|
||||
assertEquals(v, v)
|
||||
assertFalse(v != v)
|
||||
assertTrue(v <= v)
|
||||
assertTrue(v >= v)
|
||||
assertFalse(v < v)
|
||||
assertFalse(v > v)
|
||||
test("testIdentity") {
|
||||
val v = NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos)
|
||||
(v==v) shouldBe true
|
||||
(v != v) shouldBe false
|
||||
(v <= v) shouldBe true
|
||||
(v >= v) shouldBe true
|
||||
(v < v ) shouldBe false
|
||||
(v > v ) shouldBe false
|
||||
|
||||
assertTrue(sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12345, dummyPos)))
|
||||
sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos), NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos)) shouldBe true
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEqualsAndNotEquals() {
|
||||
assertEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
|
||||
assertEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UWORD, 100, dummyPos))
|
||||
assertEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos))
|
||||
assertEquals(NumericLiteralValue(DataType.UWORD, 254, dummyPos), NumericLiteralValue(DataType.UBYTE, 254, dummyPos))
|
||||
assertEquals(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12345, dummyPos))
|
||||
assertEquals(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.FLOAT, 12345.0, dummyPos))
|
||||
assertEquals(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
|
||||
assertEquals(NumericLiteralValue(DataType.FLOAT, 22239.0, dummyPos), NumericLiteralValue(DataType.UWORD, 22239, dummyPos))
|
||||
assertEquals(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos))
|
||||
test("test rounding") {
|
||||
shouldThrow<ExpressionError> {
|
||||
NumericLiteralValue(DataType.BYTE, -2.345, dummyPos)
|
||||
}.message shouldContain "refused silent rounding"
|
||||
shouldThrow<ExpressionError> {
|
||||
NumericLiteralValue(DataType.BYTE, -2.6, dummyPos)
|
||||
}.message shouldContain "refused silent rounding"
|
||||
shouldThrow<ExpressionError> {
|
||||
NumericLiteralValue(DataType.UWORD, 2222.345, dummyPos)
|
||||
}.message shouldContain "refused silent rounding"
|
||||
NumericLiteralValue(DataType.UBYTE, 2.0, dummyPos).number shouldBe 2.0
|
||||
NumericLiteralValue(DataType.BYTE, -2.0, dummyPos).number shouldBe -2.0
|
||||
NumericLiteralValue(DataType.UWORD, 2222.0, dummyPos).number shouldBe 2222.0
|
||||
NumericLiteralValue(DataType.FLOAT, 123.456, dummyPos)
|
||||
}
|
||||
|
||||
assertTrue(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UBYTE, 100, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UWORD, 100, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 254, dummyPos), NumericLiteralValue(DataType.UBYTE, 254, dummyPos)))
|
||||
assertTrue(sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12345, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.FLOAT, 12345.0, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 100, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 22239.0, dummyPos), NumericLiteralValue(DataType.UWORD, 22239, dummyPos)))
|
||||
assertTrue(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos)))
|
||||
test("testEqualsAndNotEquals") {
|
||||
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) == NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) == NumericLiteralValue(DataType.UWORD, 100.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) == NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) == NumericLiteralValue(DataType.UBYTE, 254.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos) == NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos) == NumericLiteralValue(DataType.FLOAT, 12345.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) == NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.FLOAT, 22239.0, dummyPos) == NumericLiteralValue(DataType.UWORD, 22239.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos) == NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos)) shouldBe true
|
||||
|
||||
assertNotEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UBYTE, 101, dummyPos))
|
||||
assertNotEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UWORD, 101, dummyPos))
|
||||
assertNotEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.FLOAT, 101.0, dummyPos))
|
||||
assertNotEquals(NumericLiteralValue(DataType.UWORD, 245, dummyPos), NumericLiteralValue(DataType.UBYTE, 246, dummyPos))
|
||||
assertNotEquals(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12346, dummyPos))
|
||||
assertNotEquals(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.FLOAT, 12346.0, dummyPos))
|
||||
assertNotEquals(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UBYTE, 9, dummyPos))
|
||||
assertNotEquals(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UWORD, 9, dummyPos))
|
||||
assertNotEquals(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.FLOAT, 9.0, dummyPos))
|
||||
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe true
|
||||
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos), NumericLiteralValue(DataType.UWORD, 100.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos), NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 254.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos), NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos)) shouldBe true
|
||||
sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos), NumericLiteralValue(DataType.FLOAT, 12345.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 22239.0, dummyPos), NumericLiteralValue(DataType.UWORD, 22239.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos)) shouldBe true
|
||||
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UBYTE, 101, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UWORD, 101, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.FLOAT, 101.0, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 245, dummyPos), NumericLiteralValue(DataType.UBYTE, 246, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12346, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.FLOAT, 12346.0, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UBYTE, 9, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UWORD, 9, dummyPos)))
|
||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.FLOAT, 9.0, dummyPos)))
|
||||
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) != NumericLiteralValue(DataType.UBYTE, 101.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) != NumericLiteralValue(DataType.UWORD, 101.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) != NumericLiteralValue(DataType.FLOAT, 101.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.UWORD, 245.0, dummyPos) != NumericLiteralValue(DataType.UBYTE, 246.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos) != NumericLiteralValue(DataType.UWORD, 12346.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos) != NumericLiteralValue(DataType.FLOAT, 12346.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos) != NumericLiteralValue(DataType.UBYTE, 9.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos) != NumericLiteralValue(DataType.UWORD, 9.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos) != NumericLiteralValue(DataType.FLOAT, 9.0, dummyPos)) shouldBe true
|
||||
|
||||
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 101.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos), NumericLiteralValue(DataType.UWORD, 101.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos), NumericLiteralValue(DataType.FLOAT, 101.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(NumericLiteralValue(DataType.UWORD, 245.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 246.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos), NumericLiteralValue(DataType.UWORD, 12346.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos), NumericLiteralValue(DataType.FLOAT, 12346.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UBYTE, 9.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UWORD, 9.0, dummyPos)) shouldBe false
|
||||
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.FLOAT, 9.0, dummyPos)) shouldBe false
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEqualsRef() {
|
||||
assertEquals(StringLiteralValue("hello", false, dummyPos), StringLiteralValue("hello", false, dummyPos))
|
||||
assertNotEquals(StringLiteralValue("hello", false, dummyPos), StringLiteralValue("bye", false, dummyPos))
|
||||
assertEquals(StringLiteralValue("hello", true, dummyPos), StringLiteralValue("hello", true, dummyPos))
|
||||
assertNotEquals(StringLiteralValue("hello", true, dummyPos), StringLiteralValue("bye", true, dummyPos))
|
||||
assertNotEquals(StringLiteralValue("hello", true, dummyPos), StringLiteralValue("hello", false, dummyPos))
|
||||
test("testEqualsRef") {
|
||||
(StringLiteralValue("hello", false, dummyPos) == StringLiteralValue("hello", false, dummyPos)) shouldBe true
|
||||
(StringLiteralValue("hello", false, dummyPos) != StringLiteralValue("bye", false, dummyPos)) shouldBe true
|
||||
(StringLiteralValue("hello", true, dummyPos) == StringLiteralValue("hello", true, dummyPos)) shouldBe true
|
||||
(StringLiteralValue("hello", true, dummyPos) != StringLiteralValue("bye", true, dummyPos)) shouldBe true
|
||||
(StringLiteralValue("hello", true, dummyPos) != StringLiteralValue("hello", false, dummyPos)) shouldBe true
|
||||
|
||||
val lvOne = NumericLiteralValue(DataType.UBYTE, 1, dummyPos)
|
||||
val lvTwo = NumericLiteralValue(DataType.UBYTE, 2, dummyPos)
|
||||
val lvThree = NumericLiteralValue(DataType.UBYTE, 3, dummyPos)
|
||||
val lvOneR = NumericLiteralValue(DataType.UBYTE, 1, dummyPos)
|
||||
val lvTwoR = NumericLiteralValue(DataType.UBYTE, 2, dummyPos)
|
||||
val lvThreeR = NumericLiteralValue(DataType.UBYTE, 3, dummyPos)
|
||||
val lvFour= NumericLiteralValue(DataType.UBYTE, 4, dummyPos)
|
||||
val lvOne = NumericLiteralValue(DataType.UBYTE, 1.0, dummyPos)
|
||||
val lvTwo = NumericLiteralValue(DataType.UBYTE, 2.0, dummyPos)
|
||||
val lvThree = NumericLiteralValue(DataType.UBYTE, 3.0, dummyPos)
|
||||
val lvOneR = NumericLiteralValue(DataType.UBYTE, 1.0, dummyPos)
|
||||
val lvTwoR = NumericLiteralValue(DataType.UBYTE, 2.0, dummyPos)
|
||||
val lvThreeR = NumericLiteralValue(DataType.UBYTE, 3.0, dummyPos)
|
||||
val lvFour= NumericLiteralValue(DataType.UBYTE, 4.0, dummyPos)
|
||||
val lv1 = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_UB), arrayOf(lvOne, lvTwo, lvThree), dummyPos)
|
||||
val lv2 = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_UB), arrayOf(lvOneR, lvTwoR, lvThreeR), dummyPos)
|
||||
val lv3 = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_UB), arrayOf(lvOneR, lvTwoR, lvFour), dummyPos)
|
||||
assertEquals(lv1, lv2)
|
||||
assertNotEquals(lv1, lv3)
|
||||
lv1 shouldBe lv2
|
||||
lv1 shouldNotBe lv3
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGreaterThan(){
|
||||
assertTrue(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) > NumericLiteralValue(DataType.UBYTE, 99, dummyPos))
|
||||
assertTrue(NumericLiteralValue(DataType.UWORD, 254, dummyPos) > NumericLiteralValue(DataType.UWORD, 253, dummyPos))
|
||||
assertTrue(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) > NumericLiteralValue(DataType.FLOAT, 99.9, dummyPos))
|
||||
test("testGreaterThan") {
|
||||
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) > NumericLiteralValue(DataType.UBYTE, 99.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) > NumericLiteralValue(DataType.UWORD, 253.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) > NumericLiteralValue(DataType.FLOAT, 99.9, dummyPos)) shouldBe true
|
||||
|
||||
assertTrue(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) >= NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
|
||||
assertTrue(NumericLiteralValue(DataType.UWORD, 254, dummyPos) >= NumericLiteralValue(DataType.UWORD, 254, dummyPos))
|
||||
assertTrue(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) >= NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos))
|
||||
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) >= NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) >= NumericLiteralValue(DataType.UWORD, 254.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) >= NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)) shouldBe true
|
||||
|
||||
assertFalse(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) > NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
|
||||
assertFalse(NumericLiteralValue(DataType.UWORD, 254, dummyPos) > NumericLiteralValue(DataType.UWORD, 254, dummyPos))
|
||||
assertFalse(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) > NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos))
|
||||
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) > NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe false
|
||||
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) > NumericLiteralValue(DataType.UWORD, 254.0, dummyPos)) shouldBe false
|
||||
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) > NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)) shouldBe false
|
||||
|
||||
assertFalse(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) >= NumericLiteralValue(DataType.UBYTE, 101, dummyPos))
|
||||
assertFalse(NumericLiteralValue(DataType.UWORD, 254, dummyPos) >= NumericLiteralValue(DataType.UWORD, 255, dummyPos))
|
||||
assertFalse(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) >= NumericLiteralValue(DataType.FLOAT, 100.1, dummyPos))
|
||||
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) >= NumericLiteralValue(DataType.UBYTE, 101.0, dummyPos)) shouldBe false
|
||||
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) >= NumericLiteralValue(DataType.UWORD, 255.0, dummyPos)) shouldBe false
|
||||
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) >= NumericLiteralValue(DataType.FLOAT, 100.1, dummyPos)) shouldBe false
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLessThan() {
|
||||
assertTrue(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) < NumericLiteralValue(DataType.UBYTE, 101, dummyPos))
|
||||
assertTrue(NumericLiteralValue(DataType.UWORD, 254, dummyPos) < NumericLiteralValue(DataType.UWORD, 255, dummyPos))
|
||||
assertTrue(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) < NumericLiteralValue(DataType.FLOAT, 100.1, dummyPos))
|
||||
test("testLessThan") {
|
||||
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) < NumericLiteralValue(DataType.UBYTE, 101.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) < NumericLiteralValue(DataType.UWORD, 255.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) < NumericLiteralValue(DataType.FLOAT, 100.1, dummyPos)) shouldBe true
|
||||
|
||||
assertTrue(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) <= NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
|
||||
assertTrue(NumericLiteralValue(DataType.UWORD, 254, dummyPos) <= NumericLiteralValue(DataType.UWORD, 254, dummyPos))
|
||||
assertTrue(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) <= NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos))
|
||||
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) <= NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) <= NumericLiteralValue(DataType.UWORD, 254.0, dummyPos)) shouldBe true
|
||||
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) <= NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)) shouldBe true
|
||||
|
||||
assertFalse(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) < NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
|
||||
assertFalse(NumericLiteralValue(DataType.UWORD, 254, dummyPos) < NumericLiteralValue(DataType.UWORD, 254, dummyPos))
|
||||
assertFalse(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) < NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos))
|
||||
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) < NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe false
|
||||
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) < NumericLiteralValue(DataType.UWORD, 254.0, dummyPos)) shouldBe false
|
||||
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) < NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)) shouldBe false
|
||||
|
||||
assertFalse(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) <= NumericLiteralValue(DataType.UBYTE, 99, dummyPos))
|
||||
assertFalse(NumericLiteralValue(DataType.UWORD, 254, dummyPos) <= NumericLiteralValue(DataType.UWORD, 253, dummyPos))
|
||||
assertFalse(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) <= NumericLiteralValue(DataType.FLOAT, 99.9, dummyPos))
|
||||
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) <= NumericLiteralValue(DataType.UBYTE, 99.0, dummyPos)) shouldBe false
|
||||
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) <= NumericLiteralValue(DataType.UWORD, 253.0, dummyPos)) shouldBe false
|
||||
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) <= NumericLiteralValue(DataType.FLOAT, 99.9, dummyPos)) shouldBe false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
@@ -1,26 +1,34 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import io.kotest.assertions.fail
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import io.kotest.matchers.types.instanceOf
|
||||
import io.kotest.matchers.types.shouldBeSameInstanceAs
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.ParentSentinel
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.expressions.TypecastExpression
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.BeforeAsmGenerationAstChanger
|
||||
import prog8.compiler.printProgram
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compilerinterface.*
|
||||
import prog8tests.helpers.*
|
||||
import prog8tests.helpers.DummyFunctions
|
||||
import prog8tests.helpers.DummyMemsizer
|
||||
import prog8tests.helpers.DummyStringEncoder
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import prog8tests.helpers.compileText
|
||||
import kotlin.test.*
|
||||
import prog8tests.helpers.generateAssembly
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestOptimization {
|
||||
@Test
|
||||
fun testRemoveEmptySubroutineExceptStart() {
|
||||
class TestOptimization: FunSpec({
|
||||
test("remove empty subroutine except start") {
|
||||
val sourcecode = """
|
||||
main {
|
||||
sub start() {
|
||||
@@ -34,13 +42,16 @@ class TestOptimization {
|
||||
val toplevelModule = result.program.toplevelModule
|
||||
val mainBlock = toplevelModule.statements.single() as Block
|
||||
val startSub = mainBlock.statements.single() as Subroutine
|
||||
assertSame(result.program.entrypoint, startSub)
|
||||
assertEquals("start", startSub.name, "only start sub should remain")
|
||||
assertTrue(startSub.statements.single() is Return, "compiler has inserted return in empty subroutines")
|
||||
result.program.entrypoint shouldBeSameInstanceAs startSub
|
||||
withClue("only start sub should remain") {
|
||||
startSub.name shouldBe "start"
|
||||
}
|
||||
withClue("compiler has inserted return in empty subroutines") {
|
||||
startSub.statements.single() shouldBe instanceOf<Return>()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDontRemoveEmptySubroutineIfItsReferenced() {
|
||||
test("don't remove empty subroutine if it's referenced") {
|
||||
val sourcecode = """
|
||||
main {
|
||||
sub start() {
|
||||
@@ -57,45 +68,173 @@ class TestOptimization {
|
||||
val mainBlock = toplevelModule.statements.single() as Block
|
||||
val startSub = mainBlock.statements[0] as Subroutine
|
||||
val emptySub = mainBlock.statements[1] as Subroutine
|
||||
assertSame(result.program.entrypoint, startSub)
|
||||
assertEquals("start", startSub.name)
|
||||
assertEquals("empty", emptySub.name)
|
||||
assertTrue(emptySub.statements.single() is Return, "compiler has inserted return in empty subroutines")
|
||||
result.program.entrypoint shouldBeSameInstanceAs startSub
|
||||
startSub.name shouldBe "start"
|
||||
emptySub.name shouldBe "empty"
|
||||
withClue("compiler has inserted return in empty subroutines") {
|
||||
emptySub.statements.single() shouldBe instanceOf<Return>()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGeneratedConstvalueInheritsProperParentLinkage()
|
||||
{
|
||||
val number = NumericLiteralValue(DataType.UBYTE, 11, Position.DUMMY)
|
||||
test("generated constvalue from typecast inherits proper parent linkage") {
|
||||
val number = NumericLiteralValue(DataType.UBYTE, 11.0, Position.DUMMY)
|
||||
val tc = TypecastExpression(number, DataType.BYTE, false, Position.DUMMY)
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
tc.linkParents(ParentSentinel)
|
||||
assertNotNull(tc.parent)
|
||||
assertNotNull(number.parent)
|
||||
assertSame(tc, number.parent)
|
||||
tc.parent shouldNotBe null
|
||||
number.parent shouldNotBe null
|
||||
tc shouldBeSameInstanceAs number.parent
|
||||
val constvalue = tc.constValue(program)!!
|
||||
assertIs<NumericLiteralValue>(constvalue)
|
||||
assertEquals(11, constvalue.number.toInt())
|
||||
assertEquals(DataType.BYTE, constvalue.type)
|
||||
assertSame(tc, constvalue.parent)
|
||||
constvalue shouldBe instanceOf<NumericLiteralValue>()
|
||||
constvalue.number shouldBe 11.0
|
||||
constvalue.type shouldBe DataType.BYTE
|
||||
constvalue.parent shouldBeSameInstanceAs tc.parent
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testConstantFoldedAndSilentlyTypecastedForInitializerValues() {
|
||||
test("generated constvalue from prefixexpr inherits proper parent linkage") {
|
||||
val number = NumericLiteralValue(DataType.UBYTE, 11.0, Position.DUMMY)
|
||||
val pfx = PrefixExpression("-", number, Position.DUMMY)
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
pfx.linkParents(ParentSentinel)
|
||||
pfx.parent shouldNotBe null
|
||||
number.parent shouldNotBe null
|
||||
pfx shouldBeSameInstanceAs number.parent
|
||||
val constvalue = pfx.constValue(program)!!
|
||||
constvalue shouldBe instanceOf<NumericLiteralValue>()
|
||||
constvalue.number shouldBe -11.0
|
||||
constvalue.type shouldBe DataType.BYTE
|
||||
constvalue.parent shouldBeSameInstanceAs pfx.parent
|
||||
}
|
||||
|
||||
test("const folding multiple scenarios +/-") {
|
||||
val source = """
|
||||
main {
|
||||
const ubyte boardHeightC = 20
|
||||
const ubyte boardOffsetC = 3
|
||||
|
||||
sub start() {
|
||||
uword load_location = 12345
|
||||
word llw = 12345
|
||||
cx16.r0 = load_location + 8000 + 1000 + 1000
|
||||
cx16.r2 = 8000 + 1000 + 1000 + load_location
|
||||
cx16.r4 = load_location + boardOffsetC + boardHeightC - 1
|
||||
cx16.r5s = llw - 900 - 999
|
||||
cx16.r7s = llw - 900 + 999
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target, true, source, writeAssembly = false).assertSuccess()
|
||||
// expected:
|
||||
// uword load_location
|
||||
// load_location = 12345
|
||||
// word llw
|
||||
// llw = 12345
|
||||
// cx16.r0 = load_location
|
||||
// cx16.r0 += 10000
|
||||
// cx16.r2 = load_location
|
||||
// cx16.r2 += 10000
|
||||
// cx16.r4 = load_location
|
||||
// cx16.r4 += 22
|
||||
// cx16.r5s = llw
|
||||
// cx16.r5s -= 1899
|
||||
// cx16.r7s = llw
|
||||
// cx16.r7s += 99
|
||||
val stmts = result.program.entrypoint.statements
|
||||
stmts.size shouldBe 14
|
||||
|
||||
val addR0value = (stmts[5] as Assignment).value
|
||||
val binexpr0 = addR0value as BinaryExpression
|
||||
binexpr0.operator shouldBe "+"
|
||||
binexpr0.right shouldBe NumericLiteralValue(DataType.UWORD, 10000.0, Position.DUMMY)
|
||||
val addR2value = (stmts[7] as Assignment).value
|
||||
val binexpr2 = addR2value as BinaryExpression
|
||||
binexpr2.operator shouldBe "+"
|
||||
binexpr2.right shouldBe NumericLiteralValue(DataType.UWORD, 10000.0, Position.DUMMY)
|
||||
val addR4value = (stmts[9] as Assignment).value
|
||||
val binexpr4 = addR4value as BinaryExpression
|
||||
binexpr4.operator shouldBe "+"
|
||||
binexpr4.right shouldBe NumericLiteralValue(DataType.UWORD, 22.0, Position.DUMMY)
|
||||
val subR5value = (stmts[11] as Assignment).value
|
||||
val binexpr5 = subR5value as BinaryExpression
|
||||
binexpr5.operator shouldBe "-"
|
||||
binexpr5.right shouldBe NumericLiteralValue(DataType.UWORD, 1899.0, Position.DUMMY)
|
||||
val subR7value = (stmts[13] as Assignment).value
|
||||
val binexpr7 = subR7value as BinaryExpression
|
||||
binexpr7.operator shouldBe "+"
|
||||
binexpr7.right shouldBe NumericLiteralValue(DataType.UWORD, 99.0, Position.DUMMY)
|
||||
}
|
||||
|
||||
test("const folding multiple scenarios * and /") {
|
||||
val source = """
|
||||
main {
|
||||
sub start() {
|
||||
word llw = 300
|
||||
cx16.r0s = 9 * 2 * 10 * llw
|
||||
cx16.r1s = llw * 9 * 2 * 10
|
||||
cx16.r2s = llw / 30 / 3
|
||||
cx16.r3s = llw / 2 * 10
|
||||
cx16.r4s = llw * 90 / 5 ; not optimized because of loss of integer division precision
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target, true, source, writeAssembly = false).assertSuccess()
|
||||
printProgram(result.program)
|
||||
// expected:
|
||||
// word llw
|
||||
// llw = 300
|
||||
// cx16.r0s = llw
|
||||
// cx16.r0s *= 180
|
||||
// cx16.r1s = llw
|
||||
// cx16.r1s *= 180
|
||||
// cx16.r2s = llw
|
||||
// cx16.r2s /= 90
|
||||
// cx16.r3s = llw
|
||||
// cx16.r3s *= 5
|
||||
// cx16.r4s = llw
|
||||
// cx16.r4s *= 90
|
||||
// cx16.r4s /= 5
|
||||
val stmts = result.program.entrypoint.statements
|
||||
stmts.size shouldBe 13
|
||||
|
||||
val mulR0Value = (stmts[3] as Assignment).value
|
||||
val binexpr0 = mulR0Value as BinaryExpression
|
||||
binexpr0.operator shouldBe "*"
|
||||
binexpr0.right shouldBe NumericLiteralValue(DataType.UWORD, 180.0, Position.DUMMY)
|
||||
val mulR1Value = (stmts[5] as Assignment).value
|
||||
val binexpr1 = mulR1Value as BinaryExpression
|
||||
binexpr1.operator shouldBe "*"
|
||||
binexpr1.right shouldBe NumericLiteralValue(DataType.UWORD, 180.0, Position.DUMMY)
|
||||
val divR2Value = (stmts[7] as Assignment).value
|
||||
val binexpr2 = divR2Value as BinaryExpression
|
||||
binexpr2.operator shouldBe "/"
|
||||
binexpr2.right shouldBe NumericLiteralValue(DataType.UWORD, 90.0, Position.DUMMY)
|
||||
val mulR3Value = (stmts[9] as Assignment).value
|
||||
val binexpr3 = mulR3Value as BinaryExpression
|
||||
binexpr3.operator shouldBe "*"
|
||||
binexpr3.right shouldBe NumericLiteralValue(DataType.UWORD, 5.0, Position.DUMMY)
|
||||
val mulR4Value = (stmts[11] as Assignment).value
|
||||
val binexpr4 = mulR4Value as BinaryExpression
|
||||
binexpr4.operator shouldBe "*"
|
||||
binexpr4.right shouldBe NumericLiteralValue(DataType.UWORD, 90.0, Position.DUMMY)
|
||||
val divR4Value = (stmts[12] as Assignment).value
|
||||
val binexpr4b = divR4Value as BinaryExpression
|
||||
binexpr4b.operator shouldBe "/"
|
||||
binexpr4b.right shouldBe NumericLiteralValue(DataType.UWORD, 5.0, Position.DUMMY)
|
||||
}
|
||||
|
||||
test("constantfolded and silently typecasted for initializervalues") {
|
||||
val sourcecode = """
|
||||
main {
|
||||
sub start() {
|
||||
const ubyte TEST = 10
|
||||
byte x1 = TEST as byte + 1
|
||||
byte x2 = 1 + TEST as byte
|
||||
ubyte y1 = TEST + 1 as byte
|
||||
ubyte y2 = 1 as byte + TEST
|
||||
byte @shared x1 = TEST as byte + 1
|
||||
byte @shared x2 = 1 + TEST as byte
|
||||
ubyte @shared y1 = TEST + 1 as byte
|
||||
ubyte @shared y2 = 1 as byte + TEST
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, true, sourcecode).assertSuccess()
|
||||
val result = compileText(C64Target, false, sourcecode).assertSuccess()
|
||||
val mainsub = result.program.entrypoint
|
||||
assertEquals(10, mainsub.statements.size)
|
||||
mainsub.statements.size shouldBe 10
|
||||
val declTest = mainsub.statements[0] as VarDecl
|
||||
val declX1 = mainsub.statements[1] as VarDecl
|
||||
val initX1 = mainsub.statements[2] as Assignment
|
||||
@@ -105,19 +244,440 @@ class TestOptimization {
|
||||
val initY1 = mainsub.statements[6] as Assignment
|
||||
val declY2 = mainsub.statements[7] as VarDecl
|
||||
val initY2 = mainsub.statements[8] as Assignment
|
||||
assertIs<Return>(mainsub.statements[9])
|
||||
assertEquals(10.0, (declTest.value as NumericLiteralValue).number.toDouble())
|
||||
assertNull(declX1.value)
|
||||
assertNull(declX2.value)
|
||||
assertNull(declY1.value)
|
||||
assertNull(declY2.value)
|
||||
assertEquals(DataType.BYTE, (initX1.value as NumericLiteralValue).type)
|
||||
assertEquals(11.0, (initX1.value as NumericLiteralValue).number.toDouble())
|
||||
assertEquals(DataType.BYTE, (initX2.value as NumericLiteralValue).type)
|
||||
assertEquals(11.0, (initX2.value as NumericLiteralValue).number.toDouble())
|
||||
assertEquals(DataType.UBYTE, (initY1.value as NumericLiteralValue).type)
|
||||
assertEquals(11.0, (initY1.value as NumericLiteralValue).number.toDouble())
|
||||
assertEquals(DataType.UBYTE, (initY2.value as NumericLiteralValue).type)
|
||||
assertEquals(11.0, (initY2.value as NumericLiteralValue).number.toDouble())
|
||||
mainsub.statements[9] shouldBe instanceOf<Return>()
|
||||
(declTest.value as NumericLiteralValue).number shouldBe 10.0
|
||||
declX1.value shouldBe null
|
||||
declX2.value shouldBe null
|
||||
declY1.value shouldBe null
|
||||
declY2.value shouldBe null
|
||||
(initX1.value as NumericLiteralValue).type shouldBe DataType.BYTE
|
||||
(initX1.value as NumericLiteralValue).number shouldBe 11.0
|
||||
(initX2.value as NumericLiteralValue).type shouldBe DataType.BYTE
|
||||
(initX2.value as NumericLiteralValue).number shouldBe 11.0
|
||||
(initY1.value as NumericLiteralValue).type shouldBe DataType.UBYTE
|
||||
(initY1.value as NumericLiteralValue).number shouldBe 11.0
|
||||
(initY2.value as NumericLiteralValue).type shouldBe DataType.UBYTE
|
||||
(initY2.value as NumericLiteralValue).number shouldBe 11.0
|
||||
}
|
||||
}
|
||||
|
||||
test("typecasted assignment from ubyte logical expressoin to uword var") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte bb
|
||||
uword ww
|
||||
ww = not bb or not ww ; expression combining ubyte and uword
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, false, src, writeAssembly = false).assertSuccess()
|
||||
|
||||
// ww = ((( not bb as uword) or not ww) as uword)
|
||||
val wwAssign = result.program.entrypoint.statements.last() as Assignment
|
||||
val expr = wwAssign.value as TypecastExpression
|
||||
|
||||
wwAssign.target.identifier?.nameInSource shouldBe listOf("ww")
|
||||
expr.type shouldBe DataType.UWORD
|
||||
expr.expression.inferType(result.program).istype(DataType.UBYTE) shouldBe true
|
||||
}
|
||||
|
||||
test("intermediate assignment steps have correct types for codegen phase (BeforeAsmGenerationAstChanger)") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte bb
|
||||
uword ww
|
||||
bb = not bb or not ww ; expression combining ubyte and uword
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, false, src, writeAssembly = false).assertSuccess()
|
||||
|
||||
// bb = (( not bb as uword) or not ww)
|
||||
val bbAssign = result.program.entrypoint.statements.last() as Assignment
|
||||
val expr = bbAssign.value as BinaryExpression
|
||||
expr.operator shouldBe "or"
|
||||
expr.left shouldBe instanceOf<TypecastExpression>() // casted to word
|
||||
expr.right shouldBe instanceOf<PrefixExpression>()
|
||||
expr.left.inferType(result.program).getOrElse { fail("dt") } shouldBe DataType.UWORD
|
||||
expr.right.inferType(result.program).getOrElse { fail("dt") } shouldBe DataType.UWORD
|
||||
expr.inferType(result.program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
|
||||
|
||||
val options = CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, true, C64Target)
|
||||
val changer = BeforeAsmGenerationAstChanger(result.program,
|
||||
options,
|
||||
ErrorReporterForTests()
|
||||
)
|
||||
|
||||
changer.visit(result.program)
|
||||
while(changer.applyModifications()>0) {
|
||||
changer.visit(result.program)
|
||||
}
|
||||
|
||||
// assignment is now split into:
|
||||
// bb = not bb
|
||||
// bb = (bb or not ww)
|
||||
|
||||
val assigns = result.program.entrypoint.statements.filterIsInstance<Assignment>()
|
||||
val bbAssigns = assigns.filter { it.value !is NumericLiteralValue }
|
||||
bbAssigns.size shouldBe 2
|
||||
|
||||
bbAssigns[0].target.identifier!!.nameInSource shouldBe listOf("bb")
|
||||
bbAssigns[0].value shouldBe instanceOf<PrefixExpression>()
|
||||
(bbAssigns[0].value as PrefixExpression).operator shouldBe "not"
|
||||
(bbAssigns[0].value as PrefixExpression).expression shouldBe IdentifierReference(listOf("bb"), Position.DUMMY)
|
||||
bbAssigns[0].value.inferType(result.program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
|
||||
|
||||
bbAssigns[1].target.identifier!!.nameInSource shouldBe listOf("bb")
|
||||
val bbAssigns1expr = bbAssigns[1].value as BinaryExpression
|
||||
bbAssigns1expr.operator shouldBe "or"
|
||||
bbAssigns1expr.left shouldBe IdentifierReference(listOf("bb"), Position.DUMMY)
|
||||
bbAssigns1expr.right shouldBe instanceOf<PrefixExpression>()
|
||||
(bbAssigns1expr.right as PrefixExpression).operator shouldBe "not"
|
||||
(bbAssigns1expr.right as PrefixExpression).expression shouldBe IdentifierReference(listOf("ww"), Position.DUMMY)
|
||||
bbAssigns1expr.inferType(result.program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
|
||||
|
||||
val asm = generateAssembly(result.program, options)
|
||||
asm.valid shouldBe true
|
||||
}
|
||||
|
||||
test("intermediate assignment steps generated for typecasted expression") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte r
|
||||
ubyte @shared bb = (cos8(r)/2 + 100) as ubyte
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, true, src, writeAssembly = true).assertSuccess()
|
||||
/* turned into:
|
||||
ubyte r
|
||||
r = 0
|
||||
ubyte bb
|
||||
prog8_lib.retval_interm_b = cos8(r)
|
||||
prog8_lib.retval_interm_b >>= 1
|
||||
prog8_lib.retval_interm_b += 100
|
||||
bb = prog8_lib.retval_interm_b
|
||||
return
|
||||
*/
|
||||
val st = result.program.entrypoint.statements
|
||||
st.size shouldBe 8
|
||||
st.last() shouldBe instanceOf<Return>()
|
||||
var assign = st[3] as Assignment
|
||||
assign.target.identifier!!.nameInSource shouldBe listOf("prog8_lib","retval_interm_b")
|
||||
assign = st[4] as Assignment
|
||||
assign.target.identifier!!.nameInSource shouldBe listOf("prog8_lib","retval_interm_b")
|
||||
assign = st[5] as Assignment
|
||||
assign.target.identifier!!.nameInSource shouldBe listOf("prog8_lib","retval_interm_b")
|
||||
assign = st[6] as Assignment
|
||||
assign.target.identifier!!.nameInSource shouldBe listOf("bb")
|
||||
}
|
||||
|
||||
test("asmgen correctly deals with float typecasting in augmented assignment") {
|
||||
val src="""
|
||||
%option enable_floats
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
ubyte ub
|
||||
float ff = 1.0
|
||||
ff += (ub as float) ; operator doesn't matter
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, optimize=false, src, writeAssembly = false).assertSuccess()
|
||||
val assignFF = result.program.entrypoint.statements.last() as Assignment
|
||||
assignFF.isAugmentable shouldBe true
|
||||
assignFF.target.identifier!!.nameInSource shouldBe listOf("ff")
|
||||
val value = assignFF.value as BinaryExpression
|
||||
value.operator shouldBe "+"
|
||||
value.left shouldBe IdentifierReference(listOf("ff"), Position.DUMMY)
|
||||
value.right shouldBe instanceOf<TypecastExpression>()
|
||||
|
||||
val asm = generateAssembly(result.program)
|
||||
asm.valid shouldBe true
|
||||
}
|
||||
|
||||
test("unused variable removal") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
ubyte unused
|
||||
ubyte @shared unused_but_shared ; this one should remain
|
||||
ubyte usedvar_only_written
|
||||
usedvar_only_written=2
|
||||
usedvar_only_written++
|
||||
ubyte usedvar ; and this one too
|
||||
usedvar = msb(usedvar)
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, optimize=true, src, writeAssembly=false).assertSuccess()
|
||||
result.program.entrypoint.statements.size shouldBe 4 // unused_but_shared decl, unused_but_shared=0, usedvar decl, usedvar assign
|
||||
val (decl, assign, decl2, assign2) = result.program.entrypoint.statements
|
||||
decl shouldBe instanceOf<VarDecl>()
|
||||
(decl as VarDecl).name shouldBe "unused_but_shared"
|
||||
assign shouldBe instanceOf<Assignment>()
|
||||
decl2 shouldBe instanceOf<VarDecl>()
|
||||
(decl2 as VarDecl).name shouldBe "usedvar"
|
||||
assign2 shouldBe instanceOf<Assignment>()
|
||||
}
|
||||
|
||||
test("unused variable removal from subscope") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
if cx16.r0 {
|
||||
uword xx = 42 ; to be removed
|
||||
xx=99 ; to be removed
|
||||
cx16.r0 = 0
|
||||
}
|
||||
func2()
|
||||
|
||||
sub func2() {
|
||||
uword yy = 33 ; to be removed
|
||||
yy=99 ; to be removed
|
||||
cx16.r0 = 0
|
||||
}
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target, optimize=true, src, writeAssembly=false).assertSuccess()
|
||||
result.program.entrypoint.statements.size shouldBe 3
|
||||
val ifstmt = result.program.entrypoint.statements[0] as IfStatement
|
||||
ifstmt.truepart.statements.size shouldBe 1
|
||||
(ifstmt.truepart.statements[0] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "r0")
|
||||
val func2 = result.program.entrypoint.statements[2] as Subroutine
|
||||
func2.statements.size shouldBe 1
|
||||
(func2.statements[0] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "r0")
|
||||
}
|
||||
|
||||
test("test simple augmented assignment optimization correctly initializes all variables") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
ubyte @shared z1
|
||||
z1 = 10
|
||||
ubyte @shared z2
|
||||
z2 = ~z2
|
||||
ubyte @shared z3
|
||||
z3 = not z3
|
||||
uword @shared z4
|
||||
z4 = (z4 as ubyte)
|
||||
ubyte @shared z5
|
||||
z5 = z1+z5+5
|
||||
ubyte @shared z6
|
||||
z6 = z1+z6-5
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target, optimize=true, src, writeAssembly=false).assertSuccess()
|
||||
/* expected:
|
||||
ubyte z1
|
||||
z1 = 10
|
||||
ubyte z2
|
||||
z2 = 255
|
||||
ubyte z3
|
||||
z3 = 1
|
||||
uword z4
|
||||
z4 = 0
|
||||
ubyte z5
|
||||
z5 = z1
|
||||
z5 += 5
|
||||
ubyte z6
|
||||
z6 = z1
|
||||
z6 -= 5
|
||||
*/
|
||||
val statements = result.program.entrypoint.statements
|
||||
statements.size shouldBe 14
|
||||
val z1decl = statements[0] as VarDecl
|
||||
val z1init = statements[1] as Assignment
|
||||
val z2decl = statements[2] as VarDecl
|
||||
val z2init = statements[3] as Assignment
|
||||
val z3decl = statements[4] as VarDecl
|
||||
val z3init = statements[5] as Assignment
|
||||
val z4decl = statements[6] as VarDecl
|
||||
val z4init = statements[7] as Assignment
|
||||
val z5decl = statements[8] as VarDecl
|
||||
val z5init = statements[9] as Assignment
|
||||
val z5plus = statements[10] as Assignment
|
||||
val z6decl = statements[11] as VarDecl
|
||||
val z6init = statements[12] as Assignment
|
||||
val z6plus = statements[13] as Assignment
|
||||
|
||||
z1decl.name shouldBe "z1"
|
||||
z1init.value shouldBe NumericLiteralValue(DataType.UBYTE, 10.0, Position.DUMMY)
|
||||
z2decl.name shouldBe "z2"
|
||||
z2init.value shouldBe NumericLiteralValue(DataType.UBYTE, 255.0, Position.DUMMY)
|
||||
z3decl.name shouldBe "z3"
|
||||
z3init.value shouldBe NumericLiteralValue(DataType.UBYTE, 1.0, Position.DUMMY)
|
||||
z4decl.name shouldBe "z4"
|
||||
z4init.value shouldBe NumericLiteralValue(DataType.UBYTE, 0.0, Position.DUMMY)
|
||||
z5decl.name shouldBe "z5"
|
||||
z5init.value shouldBe IdentifierReference(listOf("z1"), Position.DUMMY)
|
||||
z5plus.isAugmentable shouldBe true
|
||||
(z5plus.value as BinaryExpression).operator shouldBe "+"
|
||||
(z5plus.value as BinaryExpression).right shouldBe NumericLiteralValue(DataType.UBYTE, 5.0, Position.DUMMY)
|
||||
z6decl.name shouldBe "z6"
|
||||
z6init.value shouldBe IdentifierReference(listOf("z1"), Position.DUMMY)
|
||||
z6plus.isAugmentable shouldBe true
|
||||
(z6plus.value as BinaryExpression).operator shouldBe "-"
|
||||
(z6plus.value as BinaryExpression).right shouldBe NumericLiteralValue(DataType.UBYTE, 5.0, Position.DUMMY)
|
||||
}
|
||||
|
||||
test("force_output option should work with optimizing memwrite assignment") {
|
||||
val src="""
|
||||
main {
|
||||
%option force_output
|
||||
|
||||
sub start() {
|
||||
uword aa
|
||||
ubyte zz
|
||||
@(aa) = zz + 32
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
val result = compileText(C64Target, optimize=true, src, writeAssembly=false).assertSuccess()
|
||||
val stmts = result.program.entrypoint.statements
|
||||
stmts.size shouldBe 6
|
||||
val assign=stmts.last() as Assignment
|
||||
(assign.target.memoryAddress?.addressExpression as IdentifierReference).nameInSource shouldBe listOf("aa")
|
||||
}
|
||||
|
||||
test("don't optimize memory writes away") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
uword aa
|
||||
ubyte zz
|
||||
@(aa) = zz + 32 ; do not optimize this away!
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, optimize=true, src, writeAssembly=false).assertSuccess()
|
||||
val stmts = result.program.entrypoint.statements
|
||||
stmts.size shouldBe 6
|
||||
val assign=stmts.last() as Assignment
|
||||
(assign.target.memoryAddress?.addressExpression as IdentifierReference).nameInSource shouldBe listOf("aa")
|
||||
}
|
||||
|
||||
test("correctly process constant prefix numbers") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
ubyte @shared z1 = 1
|
||||
ubyte @shared z2 = + 1
|
||||
ubyte @shared z3 = ~ 1
|
||||
ubyte @shared z4 = not 1
|
||||
byte @shared z5 = - 1
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, optimize=true, src, writeAssembly=false).assertSuccess()
|
||||
val stmts = result.program.entrypoint.statements
|
||||
stmts.size shouldBe 10
|
||||
stmts.filterIsInstance<VarDecl>().size shouldBe 5
|
||||
stmts.filterIsInstance<Assignment>().size shouldBe 5
|
||||
}
|
||||
|
||||
test("correctly process constant prefix numbers with type mismatch and give error") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
ubyte @shared z1 = - 1
|
||||
}
|
||||
}
|
||||
"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(C64Target, optimize=true, src, writeAssembly=false, errors = errors).assertFailure()
|
||||
errors.errors.size shouldBe 2
|
||||
errors.errors[0] shouldContain "type of value BYTE doesn't match target UBYTE"
|
||||
errors.errors[1] shouldContain "value '-1' out of range for unsigned byte"
|
||||
}
|
||||
|
||||
test("test augmented expression asmgen") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte c
|
||||
ubyte r
|
||||
ubyte q
|
||||
r = (q+r)-c
|
||||
q=r
|
||||
r = q+(r-c)
|
||||
q=r
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target, optimize=false, src, writeAssembly=true).assertSuccess()
|
||||
result.program.entrypoint.statements.size shouldBe 11
|
||||
result.program.entrypoint.statements.last() shouldBe instanceOf<Return>()
|
||||
}
|
||||
|
||||
test("keep the value initializer assignment if the next one depends on it") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
uword @shared yy
|
||||
yy = 20 ; ok to remove =0 initializer before this
|
||||
uword @shared zz
|
||||
zz += 60 ; NOT ok to remove initializer, should evaluate to 60
|
||||
ubyte @shared xx
|
||||
xx = 6+sin8u(xx) ; NOT ok to remove initializer
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, optimize=true, src, writeAssembly=false).assertSuccess()
|
||||
/* expected result:
|
||||
uword yy
|
||||
yy = 20
|
||||
uword zz
|
||||
zz = 60
|
||||
ubyte xx
|
||||
xx = 0
|
||||
xx = sin8u(xx)
|
||||
xx += 6
|
||||
*/
|
||||
val stmts = result.program.entrypoint.statements
|
||||
stmts.size shouldBe 8
|
||||
stmts.filterIsInstance<VarDecl>().size shouldBe 3
|
||||
stmts.filterIsInstance<Assignment>().size shouldBe 5
|
||||
}
|
||||
|
||||
test("only substitue assignments with 0 after a =0 initializer if it is the same variable") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
uword @shared xx
|
||||
xx = xx + 20 ; is same var so can be changed just fine into xx=20
|
||||
uword @shared yy
|
||||
xx = 20
|
||||
yy = 0 ; is other var..
|
||||
xx = xx+10 ; so this should not be changed into xx=10
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target, optimize=true, src, writeAssembly=false).assertSuccess()
|
||||
/*
|
||||
expected result:
|
||||
uword xx
|
||||
xx = 20
|
||||
uword yy
|
||||
yy = 0
|
||||
xx = 20
|
||||
yy = 0
|
||||
xx += 10
|
||||
*/
|
||||
val stmts = result.program.entrypoint.statements
|
||||
stmts.size shouldBe 7
|
||||
stmts.filterIsInstance<VarDecl>().size shouldBe 2
|
||||
stmts.filterIsInstance<Assignment>().size shouldBe 5
|
||||
val assignXX1 = stmts[1] as Assignment
|
||||
assignXX1.target.identifier!!.nameInSource shouldBe listOf("xx")
|
||||
assignXX1.value shouldBe NumericLiteralValue(DataType.UBYTE, 20.0, Position.DUMMY)
|
||||
val assignXX2 = stmts.last() as Assignment
|
||||
assignXX2.target.identifier!!.nameInSource shouldBe listOf("xx")
|
||||
val xxValue = assignXX2.value as BinaryExpression
|
||||
xxValue.operator shouldBe "+"
|
||||
xxValue.left shouldBe IdentifierReference(listOf("xx"), Position.DUMMY)
|
||||
xxValue.right shouldBe NumericLiteralValue(DataType.UBYTE, 10.0, Position.DUMMY)
|
||||
}
|
||||
})
|
||||
+118
-143
@@ -1,106 +1,83 @@
|
||||
package prog8tests
|
||||
|
||||
import com.github.michaelbull.result.Ok
|
||||
import com.github.michaelbull.result.expect
|
||||
import com.github.michaelbull.result.expectError
|
||||
import com.github.michaelbull.result.getOrElse
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.Matchers.equalTo
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.compiler.target.cbm.Petscii
|
||||
import java.io.CharConversionException
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestPetscii {
|
||||
class TestPetscii: FunSpec({
|
||||
|
||||
@Test
|
||||
fun testZero() {
|
||||
assertThat(Petscii.encodePetscii("\u0000", true), equalTo(Ok(listOf<Short>(0))))
|
||||
assertThat(Petscii.encodePetscii("\u0000", false), equalTo(Ok(listOf<Short>(0))))
|
||||
assertThat(Petscii.decodePetscii(listOf(0), true), equalTo("\u0000"))
|
||||
assertThat(Petscii.decodePetscii(listOf(0), false), equalTo("\u0000"))
|
||||
test("testZero") {
|
||||
Petscii.encodePetscii("\u0000", true) shouldBe Ok(listOf<UByte>(0u))
|
||||
Petscii.encodePetscii("\u0000", false) shouldBe Ok(listOf<UByte>(0u))
|
||||
Petscii.decodePetscii(listOf(0u), true) shouldBe "\u0000"
|
||||
Petscii.decodePetscii(listOf(0u), false) shouldBe "\u0000"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLowercase() {
|
||||
assertThat(Petscii.encodePetscii("hello WORLD 123 @!£", true), equalTo(
|
||||
Ok(listOf<Short>(72, 69, 76, 76, 79, 32, 0xd7, 0xcf, 0xd2, 0xcc, 0xc4, 32, 49, 50, 51, 32, 64, 33, 0x5c))))
|
||||
assertThat(Petscii.encodePetscii("\uf11a", true), equalTo(Ok(listOf<Short>(0x12)))) // reverse vid
|
||||
assertThat(Petscii.encodePetscii("✓", true), equalTo(Ok(listOf<Short>(0xfa))))
|
||||
assertThat("expect lowercase error fallback", Petscii.encodePetscii("π", true), equalTo(Ok(listOf<Short>(255))))
|
||||
assertThat("expect lowercase error fallback", Petscii.encodePetscii("♥", true), equalTo(Ok(listOf<Short>(0xd3))))
|
||||
test("testLowercase") {
|
||||
Petscii.encodePetscii("hello WORLD 123 @!£", true) shouldBe
|
||||
Ok(listOf<UByte>(72u, 69u, 76u, 76u, 79u, 32u, 0xd7u, 0xcfu, 0xd2u, 0xccu, 0xc4u, 32u, 49u, 50u, 51u, 32u, 64u, 33u, 0x5cu))
|
||||
Petscii.encodePetscii("\uf11a", true) shouldBe Ok(listOf<UByte>(0x12u)) // reverse vid
|
||||
Petscii.encodePetscii("✓", true) shouldBe Ok(listOf<UByte>(0xfau))
|
||||
withClue("expect lowercase error fallback") {
|
||||
Petscii.encodePetscii("π", true) shouldBe Ok(listOf<UByte>(255u))
|
||||
Petscii.encodePetscii("♥", true) shouldBe Ok(listOf<UByte>(0xd3u))
|
||||
}
|
||||
|
||||
assertThat(Petscii.decodePetscii(listOf(72, 0xd7, 0x5c, 0xfa, 0x12), true), equalTo("hW£✓\uF11A"))
|
||||
Petscii.decodePetscii(listOf(72u, 0xd7u, 0x5cu, 0xfau, 0x12u), true) shouldBe "hW£✓\uF11A"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUppercase() {
|
||||
assertThat(Petscii.encodePetscii("HELLO 123 @!£"), equalTo(
|
||||
Ok(listOf<Short>(72, 69, 76, 76, 79, 32, 49, 50, 51, 32, 64, 33, 0x5c))))
|
||||
assertThat(Petscii.encodePetscii("\uf11a"), equalTo(Ok(listOf<Short>(0x12)))) // reverse vid
|
||||
assertThat(Petscii.encodePetscii("♥"), equalTo(Ok(listOf<Short>(0xd3))))
|
||||
assertThat(Petscii.encodePetscii("π"), equalTo(Ok(listOf<Short>(0xff))))
|
||||
assertThat("expecting fallback", Petscii.encodePetscii("✓"), equalTo(Ok(listOf<Short>(250))))
|
||||
test("testUppercase") {
|
||||
Petscii.encodePetscii("HELLO 123 @!£") shouldBe
|
||||
Ok(listOf<UByte>(72u, 69u, 76u, 76u, 79u, 32u, 49u, 50u, 51u, 32u, 64u, 33u, 0x5cu))
|
||||
Petscii.encodePetscii("\uf11a") shouldBe Ok(listOf<UByte>(0x12u)) // reverse vid
|
||||
Petscii.encodePetscii("♥") shouldBe Ok(listOf<UByte>(0xd3u))
|
||||
Petscii.encodePetscii("π") shouldBe Ok(listOf<UByte>(0xffu))
|
||||
withClue("expecting fallback") {
|
||||
Petscii.encodePetscii("✓") shouldBe Ok(listOf<UByte>(250u))
|
||||
}
|
||||
|
||||
assertThat(Petscii.decodePetscii(listOf(72, 0x5c, 0xd3, 0xff)), equalTo("H£♥π"))
|
||||
Petscii.decodePetscii(listOf(72u, 0x5cu, 0xd3u, 0xffu)) shouldBe "H£♥π"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testScreencodeLowercase() {
|
||||
assertThat(Petscii.encodeScreencode("hello WORLD 123 @!£", true), equalTo(
|
||||
Ok(listOf<Short>(0x08, 0x05, 0x0c, 0x0c, 0x0f, 0x20, 0x57, 0x4f, 0x52, 0x4c, 0x44, 0x20, 0x31, 0x32, 0x33, 0x20, 0x00, 0x21, 0x1c))
|
||||
))
|
||||
assertThat(Petscii.encodeScreencode("✓", true), equalTo(Ok(listOf<Short>(0x7a))))
|
||||
assertThat("expect fallback", Petscii.encodeScreencode("♥", true), equalTo(Ok(listOf<Short>(83))))
|
||||
assertThat("expect fallback", Petscii.encodeScreencode("π", true), equalTo(Ok(listOf<Short>(94))))
|
||||
test("testScreencodeLowercase") {
|
||||
Petscii.encodeScreencode("hello WORLD 123 @!£", true) shouldBe
|
||||
Ok(listOf<UByte>(0x08u, 0x05u, 0x0cu, 0x0cu, 0x0fu, 0x20u, 0x57u, 0x4fu, 0x52u, 0x4cu, 0x44u, 0x20u, 0x31u, 0x32u, 0x33u, 0x20u, 0x00u, 0x21u, 0x1cu))
|
||||
Petscii.encodeScreencode("✓", true) shouldBe Ok(listOf<UByte>(0x7au))
|
||||
withClue("expect fallback") {
|
||||
Petscii.encodeScreencode("♥", true) shouldBe Ok(listOf<UByte>(83u))
|
||||
Petscii.encodeScreencode("π", true) shouldBe Ok(listOf<UByte>(94u))
|
||||
}
|
||||
|
||||
assertThat(Petscii.decodeScreencode(listOf(0x08, 0x57, 0x1c, 0x7a), true), equalTo("hW£✓"))
|
||||
Petscii.decodeScreencode(listOf(0x08u, 0x57u, 0x1cu, 0x7au), true) shouldBe "hW£✓"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testScreencodeUppercase() {
|
||||
assertThat(Petscii.encodeScreencode("WORLD 123 @!£"), equalTo(
|
||||
Ok(listOf<Short>(0x17, 0x0f, 0x12, 0x0c, 0x04, 0x20, 0x31, 0x32, 0x33, 0x20, 0x00, 0x21, 0x1c))))
|
||||
assertThat(Petscii.encodeScreencode("♥"), equalTo(Ok(listOf<Short>(0x53))))
|
||||
assertThat(Petscii.encodeScreencode("π"), equalTo(Ok(listOf<Short>(0x5e))))
|
||||
assertThat(Petscii.encodeScreencode("HELLO"), equalTo(Ok(listOf<Short>(8, 5, 12, 12, 15))))
|
||||
assertThat("expecting fallback", Petscii.encodeScreencode("hello"), equalTo(Ok(listOf<Short>(8, 5, 12, 12, 15))))
|
||||
assertThat("expecting fallback", Petscii.encodeScreencode("✓"), equalTo(Ok(listOf<Short>(122))))
|
||||
test("testScreencodeUppercase") {
|
||||
Petscii.encodeScreencode("WORLD 123 @!£") shouldBe
|
||||
Ok(listOf<UByte>(0x17u, 0x0fu, 0x12u, 0x0cu, 0x04u, 0x20u, 0x31u, 0x32u, 0x33u, 0x20u, 0x00u, 0x21u, 0x1cu))
|
||||
Petscii.encodeScreencode("♥") shouldBe Ok(listOf<UByte>(0x53u))
|
||||
Petscii.encodeScreencode("π") shouldBe Ok(listOf<UByte>(0x5eu))
|
||||
Petscii.encodeScreencode("HELLO") shouldBe Ok(listOf<UByte>(8u, 5u, 12u, 12u, 15u))
|
||||
withClue("expecting fallback") {
|
||||
Petscii.encodeScreencode("hello") shouldBe Ok(listOf<UByte>(8u, 5u, 12u, 12u, 15u))
|
||||
Petscii.encodeScreencode("✓") shouldBe Ok(listOf<UByte>(122u))
|
||||
}
|
||||
|
||||
assertThat(Petscii.decodeScreencode(listOf(0x17, 0x1c, 0x53, 0x5e)), equalTo("W£♥π"))
|
||||
Petscii.decodeScreencode(listOf(0x17u, 0x1cu, 0x53u, 0x5eu)) shouldBe "W£♥π"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testErrorCases() {
|
||||
test("testErrorCases") {
|
||||
Petscii.encodePetscii("~", true).expectError { "shouldn't be able to encode tilde" }
|
||||
Petscii.encodePetscii("~", false).expectError { "shouldn't be able to encode tilde" }
|
||||
Petscii.encodeScreencode("~", true).expectError { "shouldn't be able to encode tilde" }
|
||||
Petscii.encodeScreencode("~", false).expectError { "shouldn't be able to encode tilde" }
|
||||
|
||||
assertFailsWith<CharConversionException> { Petscii.decodePetscii(listOf<Short>(-1), true) }
|
||||
assertFailsWith<CharConversionException> { Petscii.decodePetscii(listOf<Short>(256), true) }
|
||||
assertFailsWith<CharConversionException> { Petscii.decodePetscii(listOf<Short>(-1), false) }
|
||||
assertFailsWith<CharConversionException> { Petscii.decodePetscii(listOf<Short>(256), false) }
|
||||
assertFailsWith<CharConversionException> { Petscii.decodeScreencode(listOf<Short>(-1), true) }
|
||||
assertFailsWith<CharConversionException> { Petscii.decodeScreencode(listOf<Short>(256), true) }
|
||||
assertFailsWith<CharConversionException> { Petscii.decodeScreencode(listOf<Short>(-1), false) }
|
||||
assertFailsWith<CharConversionException> { Petscii.decodeScreencode(listOf<Short>(256), false) }
|
||||
|
||||
Petscii.scr2petscii(-1).expectError { "-1 should error" }
|
||||
Petscii.scr2petscii(256).expectError { "256 should error" }
|
||||
Petscii.petscii2scr(-1, true).expectError { "-1 should error" }
|
||||
Petscii.petscii2scr(256, true).expectError { "256 should error" }
|
||||
Petscii.petscii2scr(-1, false).expectError { "-1 should error" }
|
||||
Petscii.petscii2scr(256, false).expectError { "256 should error" }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSpecialReplacements()
|
||||
{
|
||||
test("testSpecialReplacements") {
|
||||
fun encodeP(c: Char, lower: Boolean) = Petscii.encodePetscii(c.toString(), lower).getOrElse { throw it }.single()
|
||||
fun encodeS(c: Char, lower: Boolean) = Petscii.encodeScreencode(c.toString(), lower).getOrElse { throw it }.single()
|
||||
|
||||
@@ -109,92 +86,90 @@ class TestPetscii {
|
||||
Petscii.encodePetscii("~", false).expectError { "shouldn't have translation for tilde" }
|
||||
Petscii.encodePetscii("~", true).expectError { "shouldn't have translation for tilde" }
|
||||
|
||||
assertEquals(94, encodeP('^', false))
|
||||
assertEquals(94, encodeP('^', true))
|
||||
assertEquals(30, encodeS('^', false))
|
||||
assertEquals(30, encodeS('^', true))
|
||||
assertEquals(228, encodeP('_', false))
|
||||
assertEquals(228, encodeP('_', true))
|
||||
assertEquals(100, encodeS('_', false))
|
||||
assertEquals(100, encodeS('_', true))
|
||||
assertEquals(243, encodeP('{', false))
|
||||
assertEquals(243, encodeP('{', true))
|
||||
assertEquals(115, encodeS('{', false))
|
||||
assertEquals(115, encodeS('{', true))
|
||||
assertEquals(235, encodeP('}', false))
|
||||
assertEquals(235, encodeP('}', true))
|
||||
assertEquals(107, encodeS('}', false))
|
||||
assertEquals(107, encodeS('}', true))
|
||||
assertEquals(221, encodeP('|', false))
|
||||
assertEquals(221, encodeP('|', true))
|
||||
assertEquals(93, encodeS('|', false))
|
||||
assertEquals(93, encodeS('|', true))
|
||||
assertEquals(205, encodeP('\\', false))
|
||||
assertEquals(205, encodeP('\\', true))
|
||||
assertEquals(77, encodeS('\\', false))
|
||||
assertEquals(77, encodeS('\\', true))
|
||||
encodeP('^', false) shouldBe 94u
|
||||
encodeP('^', true) shouldBe 94u
|
||||
encodeS('^', false) shouldBe 30u
|
||||
encodeS('^', true) shouldBe 30u
|
||||
encodeP('_', false) shouldBe 228u
|
||||
encodeP('_', true) shouldBe 228u
|
||||
encodeS('_', false) shouldBe 100u
|
||||
encodeS('_', true) shouldBe 100u
|
||||
encodeP('{', false) shouldBe 243u
|
||||
encodeP('{', true) shouldBe 243u
|
||||
encodeS('{', false) shouldBe 115u
|
||||
encodeS('{', true) shouldBe 115u
|
||||
encodeP('}', false) shouldBe 235u
|
||||
encodeP('}', true) shouldBe 235u
|
||||
encodeS('}', false) shouldBe 107u
|
||||
encodeS('}', true) shouldBe 107u
|
||||
encodeP('|', false) shouldBe 221u
|
||||
encodeP('|', true) shouldBe 221u
|
||||
encodeS('|', false) shouldBe 93u
|
||||
encodeS('|', true) shouldBe 93u
|
||||
encodeP('\\', false) shouldBe 205u
|
||||
encodeP('\\', true) shouldBe 205u
|
||||
encodeS('\\', false) shouldBe 77u
|
||||
encodeS('\\', true) shouldBe 77u
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBoxDrawingCharsEncoding() {
|
||||
test("testBoxDrawingCharsEncoding") {
|
||||
fun encodeP(c: Char, lower: Boolean) = Petscii.encodePetscii(c.toString(), lower).getOrElse { throw it }.single()
|
||||
fun encodeS(c: Char, lower: Boolean) = Petscii.encodeScreencode(c.toString(), lower).getOrElse { throw it }.single()
|
||||
|
||||
// pipe char
|
||||
assertEquals(221, encodeP('|', false))
|
||||
assertEquals(221, encodeP('|', true))
|
||||
assertEquals(93, encodeS('|', false))
|
||||
assertEquals(93, encodeS('|', true))
|
||||
encodeP('|', false) shouldBe 221u
|
||||
encodeP('|', true) shouldBe 221u
|
||||
encodeS('|', false) shouldBe 93u
|
||||
encodeS('|', true) shouldBe 93u
|
||||
// ... same as '│', 0x7D -> BOX DRAWINGS LIGHT VERTICAL
|
||||
assertEquals(221, encodeP('│', false))
|
||||
assertEquals(221, encodeP('│', true))
|
||||
assertEquals(93, encodeS('│', false))
|
||||
assertEquals(93, encodeS('│', true))
|
||||
encodeP('│', false) shouldBe 221u
|
||||
encodeP('│', true) shouldBe 221u
|
||||
encodeS('│', false) shouldBe 93u
|
||||
encodeS('│', true) shouldBe 93u
|
||||
|
||||
// underscore
|
||||
assertEquals(228, encodeP('_', false))
|
||||
assertEquals(228, encodeP('_', true))
|
||||
assertEquals(100, encodeS('_', false))
|
||||
assertEquals(100, encodeS('_', true))
|
||||
encodeP('_', false) shouldBe 228u
|
||||
encodeP('_', true) shouldBe 228u
|
||||
encodeS('_', false) shouldBe 100u
|
||||
encodeS('_', true) shouldBe 100u
|
||||
// ... same as '▁', 0xE4 LOWER ONE EIGHTH BLOCK
|
||||
assertEquals(228, encodeP('▁', false))
|
||||
assertEquals(228, encodeP('▁', true))
|
||||
assertEquals(100, encodeS('▁', false))
|
||||
assertEquals(100, encodeS('▁', true))
|
||||
encodeP('▁', false) shouldBe 228u
|
||||
encodeP('▁', true) shouldBe 228u
|
||||
encodeS('▁', false) shouldBe 100u
|
||||
encodeS('▁', true) shouldBe 100u
|
||||
|
||||
// ─ 0xC0 -> BOX DRAWINGS LIGHT HORIZONTAL
|
||||
assertEquals(192, encodeP('─', false))
|
||||
assertEquals(192, encodeP('─', true))
|
||||
assertEquals(64, encodeS('─', false))
|
||||
assertEquals(64, encodeS('─', true))
|
||||
encodeP('─', false) shouldBe 192u
|
||||
encodeP('─', true) shouldBe 192u
|
||||
encodeS('─', false) shouldBe 64u
|
||||
encodeS('─', true) shouldBe 64u
|
||||
// │ 0x62 -> BOX DRAWINGS LIGHT VERTICAL
|
||||
assertEquals(221, encodeP('│', false))
|
||||
assertEquals(221, encodeP('│', true))
|
||||
assertEquals(93, encodeS('│', false))
|
||||
assertEquals(93, encodeS('│', true))
|
||||
encodeP('│', false) shouldBe 221u
|
||||
encodeP('│', true) shouldBe 221u
|
||||
encodeS('│', false) shouldBe 93u
|
||||
encodeS('│', true) shouldBe 93u
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBoxDrawingCharsDecoding() {
|
||||
test("testBoxDrawingCharsDecoding") {
|
||||
// ─ 0xC0 -> BOX DRAWINGS LIGHT HORIZONTAL
|
||||
assertEquals('\uf13b', Petscii.decodePetscii(listOf(195), false).single(), "BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH UP (CUS)")
|
||||
assertEquals('C', Petscii.decodePetscii(listOf(195), true).single())
|
||||
assertEquals('─', Petscii.decodePetscii(listOf(192), false).single())
|
||||
assertEquals('─', Petscii.decodePetscii(listOf(192), true).single())
|
||||
assertEquals('\uf13b', Petscii.decodeScreencode(listOf(67), false).single(), "BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH UP (CUS)")
|
||||
assertEquals('C', Petscii.decodeScreencode(listOf(67), true).single())
|
||||
assertEquals('─', Petscii.decodeScreencode(listOf(64), false).single())
|
||||
assertEquals('─', Petscii.decodeScreencode(listOf(64), true).single())
|
||||
Petscii.decodePetscii(listOf(195u), false).single() shouldBe '\uf13b' //"BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH UP (CUS)"
|
||||
Petscii.decodePetscii(listOf(195u), true).single() shouldBe 'C'
|
||||
Petscii.decodePetscii(listOf(192u), false).single() shouldBe '─'
|
||||
Petscii.decodePetscii(listOf(192u), true).single() shouldBe '─'
|
||||
Petscii.decodeScreencode(listOf(67u), false).single() shouldBe '\uf13b' //"BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH UP (CUS)"
|
||||
Petscii.decodeScreencode(listOf(67u), true).single() shouldBe 'C'
|
||||
Petscii.decodeScreencode(listOf(64u), false).single() shouldBe '─'
|
||||
Petscii.decodeScreencode(listOf(64u), true).single() shouldBe '─'
|
||||
|
||||
// │ 0x62 -> BOX DRAWINGS LIGHT VERTICAL
|
||||
assertEquals('│', Petscii.decodePetscii(listOf(125), false).single())
|
||||
assertEquals('│', Petscii.decodePetscii(listOf(125), true).single())
|
||||
assertEquals('│', Petscii.decodePetscii(listOf(221), false).single())
|
||||
assertEquals('│', Petscii.decodePetscii(listOf(221), true).single())
|
||||
assertEquals('│', Petscii.decodeScreencode(listOf(93), false).single())
|
||||
assertEquals('│', Petscii.decodeScreencode(listOf(93), true).single())
|
||||
assertEquals('\uf13c', Petscii.decodeScreencode(listOf(66), false).single(), "BOX DRAWINGS LIGHT VERTICAL ONE EIGHTH LEFT (CUS)")
|
||||
assertEquals('B', Petscii.decodeScreencode(listOf(66), true).single())
|
||||
Petscii.decodePetscii(listOf(125u), false).single() shouldBe '│'
|
||||
Petscii.decodePetscii(listOf(125u), true).single() shouldBe '│'
|
||||
Petscii.decodePetscii(listOf(221u), false).single() shouldBe '│'
|
||||
Petscii.decodePetscii(listOf(221u), true).single() shouldBe '│'
|
||||
Petscii.decodeScreencode(listOf(93u), false).single() shouldBe '│'
|
||||
Petscii.decodeScreencode(listOf(93u), true).single() shouldBe '│'
|
||||
Petscii.decodeScreencode(listOf(66u), false).single() shouldBe '\uf13c' // "BOX DRAWINGS LIGHT VERTICAL ONE EIGHTH LEFT (CUS)"
|
||||
Petscii.decodeScreencode(listOf(66u), true).single() shouldBe 'B'
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
+238
-29
@@ -1,22 +1,25 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import io.kotest.matchers.types.instanceOf
|
||||
import io.kotest.matchers.types.shouldBeSameInstanceAs
|
||||
import prog8.ast.GlobalNamespace
|
||||
import prog8.ast.base.ParentSentinel
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.assertFailure
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import prog8tests.helpers.compileText
|
||||
import kotlin.test.*
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestScoping {
|
||||
class TestScoping: FunSpec({
|
||||
|
||||
@Test
|
||||
fun testModulesParentIsGlobalNamespace() {
|
||||
test("modules parent is global namespace") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
@@ -26,13 +29,12 @@ class TestScoping {
|
||||
|
||||
val result = compileText(C64Target, false, src, writeAssembly = false).assertSuccess()
|
||||
val module = result.program.toplevelModule
|
||||
assertIs<GlobalNamespace>(module.parent)
|
||||
assertSame(result.program, module.program)
|
||||
assertIs<ParentSentinel>(module.parent.parent)
|
||||
module.parent shouldBe instanceOf<GlobalNamespace>()
|
||||
module.program shouldBeSameInstanceAs result.program
|
||||
module.parent.parent shouldBe instanceOf<ParentSentinel>()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAnonScopeVarsMovedIntoSubroutineScope() {
|
||||
test("anon scope vars moved into subroutine scope") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
@@ -49,19 +51,28 @@ class TestScoping {
|
||||
val mainBlock = module.statements.single() as Block
|
||||
val start = mainBlock.statements.single() as Subroutine
|
||||
val repeatbody = start.statements.filterIsInstance<RepeatLoop>().single().body
|
||||
assertFalse(mainBlock.statements.any { it is VarDecl }, "no vars moved to main block")
|
||||
withClue("no vars moved to main block") {
|
||||
mainBlock.statements.any { it is VarDecl } shouldBe false
|
||||
}
|
||||
val subroutineVars = start.statements.filterIsInstance<VarDecl>()
|
||||
assertEquals(1, subroutineVars.size, "var from repeat anonscope must be moved up to subroutine")
|
||||
assertEquals("xx", subroutineVars[0].name)
|
||||
assertFalse(repeatbody.statements.any { it is VarDecl }, "var should have been removed from repeat anonscope")
|
||||
withClue("var from repeat anonscope must be moved up to subroutine") {
|
||||
subroutineVars.size shouldBe 1
|
||||
}
|
||||
subroutineVars[0].name shouldBe "xx"
|
||||
withClue("var should have been removed from repeat anonscope") {
|
||||
repeatbody.statements.any { it is VarDecl } shouldBe false
|
||||
}
|
||||
val initassign = repeatbody.statements[0] as? Assignment
|
||||
assertEquals(listOf("xx"), initassign?.target?.identifier?.nameInSource, "vardecl in repeat should be replaced by init assignment")
|
||||
assertEquals(99, (initassign?.value as? NumericLiteralValue)?.number?.toInt(), "vardecl in repeat should be replaced by init assignment")
|
||||
assertTrue(repeatbody.statements[1] is PostIncrDecr)
|
||||
withClue("vardecl in repeat should be replaced by init assignment") {
|
||||
initassign?.target?.identifier?.nameInSource shouldBe listOf("xx")
|
||||
}
|
||||
withClue("vardecl in repeat should be replaced by init assignment") {
|
||||
(initassign?.value as? NumericLiteralValue)?.number?.toInt() shouldBe 99
|
||||
}
|
||||
repeatbody.statements[1] shouldBe instanceOf<PostIncrDecr>()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLabelsWithAnonScopes() {
|
||||
test("labels with anon scopes") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
@@ -74,11 +85,9 @@ class TestScoping {
|
||||
addr = &labelinside
|
||||
addr = &labeloutside
|
||||
addr = &main.start.nested.nestedlabel
|
||||
addr = &nested.nestedlabel
|
||||
goto labeloutside
|
||||
goto iflabel
|
||||
goto main.start.nested.nestedlabel
|
||||
goto nested.nestedlabel
|
||||
}
|
||||
iflabel:
|
||||
}
|
||||
@@ -88,11 +97,9 @@ class TestScoping {
|
||||
addr = &labelinside
|
||||
addr = &labeloutside
|
||||
addr = &main.start.nested.nestedlabel
|
||||
addr = &nested.nestedlabel
|
||||
goto iflabel
|
||||
goto labelinside
|
||||
goto main.start.nested.nestedlabel
|
||||
goto nested.nestedlabel
|
||||
labelinside:
|
||||
}
|
||||
|
||||
@@ -108,9 +115,7 @@ class TestScoping {
|
||||
addr = &labelinside
|
||||
addr = &labeloutside
|
||||
addr = &main.start.nested.nestedlabel
|
||||
addr = &nested.nestedlabel
|
||||
goto main.start.nested.nestedlabel
|
||||
goto nested.nestedlabel
|
||||
}
|
||||
}
|
||||
"""
|
||||
@@ -120,7 +125,211 @@ class TestScoping {
|
||||
val mainBlock = module.statements.single() as Block
|
||||
val start = mainBlock.statements.single() as Subroutine
|
||||
val labels = start.statements.filterIsInstance<Label>()
|
||||
assertEquals(1, labels.size, "only one label in subroutine scope")
|
||||
withClue("only one label in subroutine scope") {
|
||||
labels.size shouldBe 1
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
test("good subroutine call without qualified names") {
|
||||
val text="""
|
||||
main {
|
||||
sub start() {
|
||||
routine()
|
||||
routine2()
|
||||
|
||||
sub routine2() {
|
||||
}
|
||||
}
|
||||
sub routine() {
|
||||
start()
|
||||
}
|
||||
}
|
||||
"""
|
||||
compileText(C64Target, false, text, writeAssembly = false).assertSuccess()
|
||||
}
|
||||
|
||||
test("wrong subroutine call without qualified names") {
|
||||
val text="""
|
||||
main {
|
||||
sub start() {
|
||||
sub routine2() {
|
||||
}
|
||||
}
|
||||
sub routine() {
|
||||
routine2()
|
||||
}
|
||||
}
|
||||
"""
|
||||
val errors= ErrorReporterForTests()
|
||||
compileText(C64Target, false, text, writeAssembly = false, errors = errors).assertFailure()
|
||||
errors.errors.size shouldBe 1
|
||||
errors.errors[0] shouldContain "undefined symbol: routine2"
|
||||
}
|
||||
|
||||
test("good subroutine calls with qualified names (from root)") {
|
||||
val text="""
|
||||
main {
|
||||
sub start() {
|
||||
main.routine()
|
||||
main.start.routine2()
|
||||
|
||||
sub routine2() {
|
||||
}
|
||||
}
|
||||
sub routine() {
|
||||
main.start.routine2()
|
||||
}
|
||||
}
|
||||
"""
|
||||
compileText(C64Target, false, text, writeAssembly = false).assertSuccess()
|
||||
}
|
||||
|
||||
test("wrong subroutine calls with qualified names (not from root)") {
|
||||
val text="""
|
||||
main {
|
||||
sub start() {
|
||||
start.routine2()
|
||||
wrong.start.routine2()
|
||||
sub routine2() {
|
||||
}
|
||||
}
|
||||
sub routine() {
|
||||
start.routine2()
|
||||
wrong.start.routine2()
|
||||
}
|
||||
}
|
||||
"""
|
||||
val errors= ErrorReporterForTests()
|
||||
compileText(C64Target, false, text, writeAssembly = false, errors=errors).assertFailure()
|
||||
errors.errors.size shouldBe 4
|
||||
errors.errors[0] shouldContain "undefined symbol: start.routine2"
|
||||
errors.errors[1] shouldContain "undefined symbol: wrong.start.routine2"
|
||||
errors.errors[2] shouldContain "undefined symbol: start.routine2"
|
||||
errors.errors[3] shouldContain "undefined symbol: wrong.start.routine2"
|
||||
}
|
||||
|
||||
test("good variables without qualified names") {
|
||||
val text="""
|
||||
main {
|
||||
ubyte v1
|
||||
|
||||
sub start() {
|
||||
ubyte v2
|
||||
v1=1
|
||||
v2=2
|
||||
|
||||
sub routine2() {
|
||||
ubyte v3
|
||||
v1=1
|
||||
v2=2
|
||||
v3=3
|
||||
}
|
||||
}
|
||||
sub routine() {
|
||||
ubyte v4
|
||||
v1=1
|
||||
v4=4
|
||||
}
|
||||
}
|
||||
"""
|
||||
compileText(C64Target, false, text, writeAssembly = false).assertSuccess()
|
||||
}
|
||||
|
||||
test("wrong variables without qualified names") {
|
||||
val text="""
|
||||
main {
|
||||
ubyte v1
|
||||
|
||||
sub start() {
|
||||
ubyte v2
|
||||
v1=1
|
||||
v2=2
|
||||
v3=3 ; can't access
|
||||
v4=4 ; can't access
|
||||
sub routine2() {
|
||||
ubyte v3
|
||||
v1=1
|
||||
v2=2
|
||||
v3=3
|
||||
v4=3 ;can't access
|
||||
}
|
||||
}
|
||||
sub routine() {
|
||||
ubyte v4
|
||||
v1=1
|
||||
v2=2 ; can't access
|
||||
v3=3 ; can't access
|
||||
v4=4
|
||||
}
|
||||
}
|
||||
"""
|
||||
val errors= ErrorReporterForTests()
|
||||
compileText(C64Target, false, text, writeAssembly = false, errors=errors).assertFailure()
|
||||
errors.errors.size shouldBe 5
|
||||
errors.errors[0] shouldContain "undefined symbol: v3"
|
||||
errors.errors[1] shouldContain "undefined symbol: v4"
|
||||
errors.errors[2] shouldContain "undefined symbol: v4"
|
||||
errors.errors[3] shouldContain "undefined symbol: v2"
|
||||
errors.errors[4] shouldContain "undefined symbol: v3"
|
||||
}
|
||||
|
||||
test("good variable refs with qualified names (from root)") {
|
||||
val text="""
|
||||
main {
|
||||
sub start() {
|
||||
uword xx
|
||||
xx = &main.routine
|
||||
main.routine(5)
|
||||
main.routine.value = 5
|
||||
main.routine.arg = 5
|
||||
xx = &main.routine.nested
|
||||
main.routine.nested(5)
|
||||
main.routine.nested.nestedvalue = 5
|
||||
main.routine.nested.arg2 = 5
|
||||
}
|
||||
|
||||
sub routine(ubyte arg) {
|
||||
ubyte value
|
||||
|
||||
sub nested(ubyte arg2) {
|
||||
ubyte nestedvalue
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
compileText(C64Target, false, text, writeAssembly = false).assertSuccess()
|
||||
}
|
||||
|
||||
test("wrong variable refs with qualified names 1 (not from root)") {
|
||||
val text="""
|
||||
main {
|
||||
sub start() {
|
||||
uword xx
|
||||
xx = &routine
|
||||
routine(5)
|
||||
routine.value = 5
|
||||
routine.arg = 5
|
||||
routine.nested.arg2 = 5
|
||||
routine.nested.nestedvalue = 5
|
||||
nested.nestedvalue = 5
|
||||
}
|
||||
|
||||
sub routine(ubyte arg) {
|
||||
ubyte value
|
||||
|
||||
sub nested(ubyte arg2) {
|
||||
ubyte nestedvalue
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
val errors= ErrorReporterForTests()
|
||||
compileText(C64Target, false, text, writeAssembly = false, errors=errors).assertFailure()
|
||||
errors.errors.size shouldBe 5
|
||||
errors.errors[0] shouldContain "undefined symbol: routine.value"
|
||||
errors.errors[1] shouldContain "undefined symbol: routine.arg"
|
||||
errors.errors[2] shouldContain "undefined symbol: routine.nested.arg2"
|
||||
errors.errors[3] shouldContain "undefined symbol: routine.nested.nestedvalue"
|
||||
errors.errors[4] shouldContain "undefined symbol: nested.nestedvalue"
|
||||
}
|
||||
})
|
||||
+195
-108
@@ -1,24 +1,25 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.Disabled
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import io.kotest.matchers.types.instanceOf
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.printProgram
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.assertFailure
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import prog8tests.helpers.compileText
|
||||
import kotlin.test.*
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestSubroutines {
|
||||
class TestSubroutines: FunSpec({
|
||||
|
||||
@Test
|
||||
fun stringParameter() {
|
||||
test("stringParameter") {
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
@@ -46,27 +47,31 @@ class TestSubroutines {
|
||||
val mainBlock = module.statements.single() as Block
|
||||
val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"}
|
||||
val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"}
|
||||
assertTrue(asmfunc.isAsmSubroutine)
|
||||
assertEquals(DataType.STR, asmfunc.parameters.single().type)
|
||||
assertTrue(asmfunc.statements.isEmpty())
|
||||
assertFalse(func.isAsmSubroutine)
|
||||
assertEquals(DataType.STR, func.parameters.single().type)
|
||||
assertEquals(4, func.statements.size)
|
||||
val paramvar = func.statements[0] as VarDecl
|
||||
assertEquals("thing", paramvar.name)
|
||||
assertEquals(DataType.STR, paramvar.datatype)
|
||||
asmfunc.isAsmSubroutine shouldBe true
|
||||
asmfunc.statements.isEmpty() shouldBe true
|
||||
func.isAsmSubroutine shouldBe false
|
||||
withClue("str param for subroutines should be changed into UWORD") {
|
||||
asmfunc.parameters.single().type shouldBe DataType.UWORD
|
||||
func.parameters.single().type shouldBe DataType.UWORD
|
||||
func.statements.size shouldBe 4
|
||||
val paramvar = func.statements[0] as VarDecl
|
||||
paramvar.name shouldBe "thing"
|
||||
paramvar.datatype shouldBe DataType.UWORD
|
||||
}
|
||||
val assign = func.statements[2] as Assignment
|
||||
assertEquals(listOf("t2"), assign.target.identifier!!.nameInSource)
|
||||
assertTrue(assign.value is TypecastExpression, "str param in function body should not be transformed by normal compiler steps")
|
||||
assertEquals(DataType.UWORD, (assign.value as TypecastExpression).type)
|
||||
assign.target.identifier!!.nameInSource shouldBe listOf("t2")
|
||||
withClue("str param in function body should have been transformed into just uword assignment") {
|
||||
assign.value shouldBe instanceOf<IdentifierReference>()
|
||||
}
|
||||
val call = func.statements[3] as FunctionCallStatement
|
||||
assertEquals("asmfunc", call.target.nameInSource.single())
|
||||
assertTrue(call.args.single() is IdentifierReference, "str param in function body should not be transformed by normal compiler steps")
|
||||
assertEquals("thing", (call.args.single() as IdentifierReference).nameInSource.single())
|
||||
call.target.nameInSource.single() shouldBe "asmfunc"
|
||||
withClue("str param in function body should not be transformed by normal compiler steps") {
|
||||
call.args.single() shouldBe instanceOf<IdentifierReference>()
|
||||
}
|
||||
(call.args.single() as IdentifierReference).nameInSource.single() shouldBe "thing"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun stringParameterAsmGen() {
|
||||
test("stringParameterAsmGen") {
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
@@ -94,73 +99,54 @@ class TestSubroutines {
|
||||
val mainBlock = module.statements.single() as Block
|
||||
val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"}
|
||||
val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"}
|
||||
assertTrue(asmfunc.isAsmSubroutine)
|
||||
assertEquals(DataType.STR, asmfunc.parameters.single().type)
|
||||
assertTrue(asmfunc.statements.single() is Return)
|
||||
assertFalse(func.isAsmSubroutine)
|
||||
assertEquals(DataType.UWORD, func.parameters.single().type, "asmgen should have changed str to uword type")
|
||||
assertTrue(asmfunc.statements.last() is Return)
|
||||
asmfunc.isAsmSubroutine shouldBe true
|
||||
asmfunc.statements.single() shouldBe instanceOf<Return>()
|
||||
func.isAsmSubroutine shouldBe false
|
||||
withClue("str param should have been changed to uword") {
|
||||
asmfunc.parameters.single().type shouldBe DataType.UWORD
|
||||
func.parameters.single().type shouldBe DataType.UWORD
|
||||
}
|
||||
asmfunc.statements.last() shouldBe instanceOf<Return>()
|
||||
|
||||
assertEquals(5, func.statements.size)
|
||||
assertTrue(func.statements[4] is Return)
|
||||
func.statements.size shouldBe 5
|
||||
func.statements[4] shouldBe instanceOf<Return>()
|
||||
val paramvar = func.statements[0] as VarDecl
|
||||
assertEquals("thing", paramvar.name)
|
||||
assertEquals(DataType.UWORD, paramvar.datatype, "pre-asmgen should have changed str to uword type")
|
||||
paramvar.name shouldBe "thing"
|
||||
withClue("pre-asmgen should have changed str to uword type") {
|
||||
paramvar.datatype shouldBe DataType.UWORD
|
||||
}
|
||||
val assign = func.statements[2] as Assignment
|
||||
assertEquals(listOf("t2"), assign.target.identifier!!.nameInSource)
|
||||
assertTrue(assign.value is IdentifierReference, "str param in function body should be treated as plain uword before asmgen")
|
||||
assertEquals("thing", (assign.value as IdentifierReference).nameInSource.single())
|
||||
assign.target.identifier!!.nameInSource shouldBe listOf("t2")
|
||||
withClue("str param in function body should be treated as plain uword before asmgen") {
|
||||
assign.value shouldBe instanceOf<IdentifierReference>()
|
||||
}
|
||||
(assign.value as IdentifierReference).nameInSource.single() shouldBe "thing"
|
||||
val call = func.statements[3] as FunctionCallStatement
|
||||
assertEquals("asmfunc", call.target.nameInSource.single())
|
||||
assertTrue(call.args.single() is IdentifierReference, "str param in function body should be treated as plain uword and not been transformed")
|
||||
assertEquals("thing", (call.args.single() as IdentifierReference).nameInSource.single())
|
||||
call.target.nameInSource.single() shouldBe "asmfunc"
|
||||
withClue("str param in function body should be treated as plain uword and not been transformed") {
|
||||
call.args.single() shouldBe instanceOf<IdentifierReference>()
|
||||
}
|
||||
(call.args.single() as IdentifierReference).nameInSource.single() shouldBe "thing"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun arrayParameterNotYetAllowed_ButShouldPerhapsBe() {
|
||||
// note: the *parser* accepts this as it is valid *syntax*,
|
||||
// however, it's not (yet) valid for the compiler
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
}
|
||||
|
||||
asmsub asmfunc(ubyte[] thing @AY) {
|
||||
}
|
||||
|
||||
sub func(ubyte[22] thing) {
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(C64Target, false, text, errors, false).assertFailure("currently array dt in signature is invalid") // TODO should not be invalid?
|
||||
assertEquals(0, errors.warnings.size)
|
||||
assertContains(errors.errors.single(), ".p8:9:16: Non-string pass-by-reference types cannot occur as a parameter type directly")
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("TODO: allow array parameter in signature") // TODO allow this?
|
||||
fun arrayParameter() {
|
||||
test("ubyte[] array parameters") {
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte[] array = [1,2,3]
|
||||
|
||||
asmfunc(array)
|
||||
asmfunc([4,5,6])
|
||||
asmfunc($2000)
|
||||
asmfunc(12.345)
|
||||
asmfunc("zzzz")
|
||||
func(array)
|
||||
func([4,5,6])
|
||||
func($2000)
|
||||
func(12.345)
|
||||
func("zzzz")
|
||||
}
|
||||
|
||||
asmsub asmfunc(ubyte[] thing @AY) {
|
||||
}
|
||||
|
||||
sub func(ubyte[22] thing) {
|
||||
sub func(ubyte[] thing) {
|
||||
}
|
||||
}
|
||||
"""
|
||||
@@ -170,23 +156,41 @@ class TestSubroutines {
|
||||
val mainBlock = module.statements.single() as Block
|
||||
val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"}
|
||||
val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"}
|
||||
assertTrue(asmfunc.isAsmSubroutine)
|
||||
assertEquals(DataType.ARRAY_UB, asmfunc.parameters.single().type)
|
||||
assertTrue(asmfunc.statements.isEmpty())
|
||||
assertFalse(func.isAsmSubroutine)
|
||||
assertEquals(DataType.ARRAY_UB, func.parameters.single().type)
|
||||
assertTrue(func.statements.isEmpty())
|
||||
withClue("ubyte array param should have been replaced by UWORD pointer") {
|
||||
asmfunc.parameters.single().type shouldBe DataType.UWORD
|
||||
func.parameters.single().type shouldBe DataType.UWORD
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUwordParameterAndNormalVarIndexedAsArrayWorkAsDirectMemoryRead() {
|
||||
test("not ubyte[] array parameters not allowed") {
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
}
|
||||
|
||||
asmsub func1(uword[] thing @AY) {
|
||||
}
|
||||
|
||||
sub func(byte[] thing) {
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(C64Target, false, text, writeAssembly = false, errors=errors).assertFailure()
|
||||
errors.errors.size shouldBe 2
|
||||
errors.errors[0] shouldContain "pass-by-reference type can't be used"
|
||||
errors.errors[1] shouldContain "pass-by-reference type can't be used"
|
||||
}
|
||||
|
||||
test("uword param and normal varindexed as array work as DirectMemoryRead") {
|
||||
val text="""
|
||||
main {
|
||||
sub thing(uword rr) {
|
||||
ubyte xx = rr[1] ; should still work as var initializer that will be rewritten
|
||||
ubyte yy
|
||||
ubyte @shared xx = rr[1] ; should still work as var initializer that will be rewritten
|
||||
ubyte @shared yy
|
||||
yy = rr[2]
|
||||
uword other
|
||||
uword @shared other
|
||||
ubyte zz = other[3]
|
||||
}
|
||||
|
||||
@@ -201,29 +205,30 @@ class TestSubroutines {
|
||||
val module = result.program.toplevelModule
|
||||
val block = module.statements.single() as Block
|
||||
val thing = block.statements.filterIsInstance<Subroutine>().single {it.name=="thing"}
|
||||
assertEquals("main", block.name)
|
||||
assertEquals(10, thing.statements.size, "rr, xx, xx assign, yy, yy assign, other, other assign 0, zz, zz assign, return")
|
||||
block.name shouldBe "main"
|
||||
thing.statements.size shouldBe 11 // rr paramdecl, xx, xx assign, yy decl, yy init 0, yy assign, other, other assign 0, zz, zz assign, return
|
||||
val xx = thing.statements[1] as VarDecl
|
||||
assertNull(xx.value, "vardecl init values must have been moved to separate assignments")
|
||||
withClue("vardecl init values must have been moved to separate assignments") {
|
||||
xx.value shouldBe null
|
||||
}
|
||||
val assignXX = thing.statements[2] as Assignment
|
||||
val assignYY = thing.statements[4] as Assignment
|
||||
val assignZZ = thing.statements[8] as Assignment
|
||||
assertEquals(listOf("xx"), assignXX.target.identifier!!.nameInSource)
|
||||
assertEquals(listOf("yy"), assignYY.target.identifier!!.nameInSource)
|
||||
assertEquals(listOf("zz"), assignZZ.target.identifier!!.nameInSource)
|
||||
val assignYY = thing.statements[5] as Assignment
|
||||
val assignZZ = thing.statements[9] as Assignment
|
||||
assignXX.target.identifier!!.nameInSource shouldBe listOf("xx")
|
||||
assignYY.target.identifier!!.nameInSource shouldBe listOf("yy")
|
||||
assignZZ.target.identifier!!.nameInSource shouldBe listOf("zz")
|
||||
val valueXXexpr = (assignXX.value as DirectMemoryRead).addressExpression as BinaryExpression
|
||||
val valueYYexpr = (assignYY.value as DirectMemoryRead).addressExpression as BinaryExpression
|
||||
val valueZZexpr = (assignZZ.value as DirectMemoryRead).addressExpression as BinaryExpression
|
||||
assertEquals(listOf("rr"), (valueXXexpr.left as IdentifierReference).nameInSource)
|
||||
assertEquals(listOf("rr"), (valueYYexpr.left as IdentifierReference).nameInSource)
|
||||
assertEquals(listOf("other"), (valueZZexpr.left as IdentifierReference).nameInSource)
|
||||
assertEquals(1, (valueXXexpr.right as NumericLiteralValue).number.toInt())
|
||||
assertEquals(2, (valueYYexpr.right as NumericLiteralValue).number.toInt())
|
||||
assertEquals(3, (valueZZexpr.right as NumericLiteralValue).number.toInt())
|
||||
(valueXXexpr.left as IdentifierReference).nameInSource shouldBe listOf("rr")
|
||||
(valueYYexpr.left as IdentifierReference).nameInSource shouldBe listOf("rr")
|
||||
(valueZZexpr.left as IdentifierReference).nameInSource shouldBe listOf("other")
|
||||
(valueXXexpr.right as NumericLiteralValue).number.toInt() shouldBe 1
|
||||
(valueYYexpr.right as NumericLiteralValue).number.toInt() shouldBe 2
|
||||
(valueZZexpr.right as NumericLiteralValue).number.toInt() shouldBe 3
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUwordParameterAndNormalVarIndexedAsArrayWorkAsMemoryWrite() {
|
||||
test("uword param and normal varindexed as array work as MemoryWrite") {
|
||||
val text="""
|
||||
main {
|
||||
sub thing(uword rr) {
|
||||
@@ -241,15 +246,97 @@ class TestSubroutines {
|
||||
val module = result.program.toplevelModule
|
||||
val block = module.statements.single() as Block
|
||||
val thing = block.statements.filterIsInstance<Subroutine>().single {it.name=="thing"}
|
||||
assertEquals("main", block.name)
|
||||
assertEquals(3, thing.statements.size, "rr, rr assign, return void")
|
||||
block.name shouldBe "main"
|
||||
thing.statements.size shouldBe 3 // "rr, rr assign, return void"
|
||||
val assignRR = thing.statements[1] as Assignment
|
||||
assertEquals(42, (assignRR.value as NumericLiteralValue).number.toInt())
|
||||
(assignRR.value as NumericLiteralValue).number.toInt() shouldBe 42
|
||||
val memwrite = assignRR.target.memoryAddress
|
||||
assertNotNull(memwrite, "memwrite to set byte array value")
|
||||
val addressExpr = memwrite.addressExpression as BinaryExpression
|
||||
assertEquals(listOf("rr"), (addressExpr.left as IdentifierReference).nameInSource)
|
||||
assertEquals("+", addressExpr.operator)
|
||||
assertEquals(10, (addressExpr.right as NumericLiteralValue).number.toInt())
|
||||
memwrite shouldNotBe null
|
||||
val addressExpr = memwrite!!.addressExpression as BinaryExpression
|
||||
(addressExpr.left as IdentifierReference).nameInSource shouldBe listOf("rr")
|
||||
addressExpr.operator shouldBe "+"
|
||||
(addressExpr.right as NumericLiteralValue).number.toInt() shouldBe 10
|
||||
}
|
||||
}
|
||||
|
||||
test("invalid number of args check on normal subroutine") {
|
||||
val text="""
|
||||
main {
|
||||
sub thing(ubyte a1, ubyte a2) {
|
||||
}
|
||||
|
||||
sub start() {
|
||||
thing(1)
|
||||
thing(1,2)
|
||||
thing(1,2,3)
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(C64Target, false, text, writeAssembly = false, errors=errors).assertFailure()
|
||||
errors.errors.size shouldBe 2
|
||||
errors.errors[0] shouldContain "7:25) invalid number of arguments"
|
||||
errors.errors[1] shouldContain "9:25) invalid number of arguments"
|
||||
}
|
||||
|
||||
test("invalid number of args check on asm subroutine") {
|
||||
val text="""
|
||||
main {
|
||||
asmsub thing(ubyte a1 @A, ubyte a2 @Y) {
|
||||
}
|
||||
|
||||
sub start() {
|
||||
thing(1)
|
||||
thing(1,2)
|
||||
thing(1,2,3)
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(C64Target, false, text, writeAssembly = false, errors=errors).assertFailure()
|
||||
errors.errors.size shouldBe 2
|
||||
errors.errors[0] shouldContain "7:25) invalid number of arguments"
|
||||
errors.errors[1] shouldContain "9:25) invalid number of arguments"
|
||||
}
|
||||
|
||||
test("invalid number of args check on call to label and builtin func") {
|
||||
val text="""
|
||||
main {
|
||||
label:
|
||||
sub start() {
|
||||
label()
|
||||
label(1)
|
||||
void rnd()
|
||||
void rnd(1)
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(C64Target, false, text, writeAssembly = false, errors=errors).assertFailure()
|
||||
errors.errors.size shouldBe 2
|
||||
errors.errors[0] shouldContain "cannot use arguments"
|
||||
errors.errors[1] shouldContain "invalid number of arguments"
|
||||
}
|
||||
|
||||
test("fallthrough prevented") {
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
func(1)
|
||||
|
||||
sub func(ubyte a) {
|
||||
a++
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, false, text, writeAssembly = true).assertSuccess()
|
||||
val stmts = result.program.entrypoint.statements
|
||||
|
||||
stmts.last() shouldBe instanceOf<Subroutine>()
|
||||
stmts.dropLast(1).last() shouldBe instanceOf<Return>() // this prevents the fallthrough
|
||||
stmts.dropLast(2).last() shouldBe instanceOf<Jump>()
|
||||
}
|
||||
})
|
||||
+161
-162
@@ -1,50 +1,45 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
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.collections.shouldNotBeIn
|
||||
import io.kotest.matchers.comparables.shouldBeGreaterThan
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.statements.RegisterOrStatusflag
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
|
||||
import prog8.compiler.target.cx16.CX16MachineDefinition.CX16Zeropage
|
||||
import prog8.compiler.target.c64.C64Zeropage
|
||||
import prog8.compiler.target.cx16.CX16Zeropage
|
||||
import prog8.compilerinterface.*
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestAbstractZeropage {
|
||||
|
||||
@Test
|
||||
fun testAbstractZeropage() {
|
||||
val compTarget = DummyCompilationTarget()
|
||||
val zp = DummyZeropage(
|
||||
CompilationOptions(
|
||||
OutputType.RAW,
|
||||
LauncherType.NONE,
|
||||
ZeropageType.FULL,
|
||||
listOf((0x50..0x5f)),
|
||||
false,
|
||||
false,
|
||||
compTarget
|
||||
)
|
||||
)
|
||||
assertEquals(256-6-16, zp.free.size)
|
||||
}
|
||||
class TestAbstractZeropage: FunSpec({
|
||||
|
||||
class DummyCompilationTarget: ICompilationTarget {
|
||||
override val name: String = "dummy"
|
||||
override val machine: IMachineDefinition
|
||||
get() = throw NotImplementedError("dummy")
|
||||
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
|
||||
throw NotImplementedError("dummy")
|
||||
}
|
||||
|
||||
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String {
|
||||
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean): String {
|
||||
throw NotImplementedError("dummy")
|
||||
}
|
||||
|
||||
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> {
|
||||
throw NotImplementedError("dummy")
|
||||
}
|
||||
|
||||
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>,
|
||||
paramRegisters: List<RegisterOrStatusflag>): Boolean {
|
||||
throw NotImplementedError("dummy")
|
||||
}
|
||||
|
||||
@@ -55,177 +50,184 @@ class TestAbstractZeropage {
|
||||
}
|
||||
|
||||
class DummyZeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
override val SCRATCH_B1: Int = 0x10
|
||||
override val SCRATCH_REG: Int = 0x11
|
||||
override val SCRATCH_W1: Int= 0x20
|
||||
override val SCRATCH_W2: Int = 0x30
|
||||
override val SCRATCH_B1 = 0x10u
|
||||
override val SCRATCH_REG = 0x11u
|
||||
override val SCRATCH_W1 = 0x20u
|
||||
override val SCRATCH_W2 = 0x30u
|
||||
|
||||
init {
|
||||
free.addAll(0..255)
|
||||
free.addAll(0u..255u)
|
||||
|
||||
removeReservedFromFreePool()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
test("testAbstractZeropage") {
|
||||
val compTarget = DummyCompilationTarget()
|
||||
val zp = DummyZeropage(
|
||||
CompilationOptions(
|
||||
OutputType.RAW,
|
||||
LauncherType.NONE,
|
||||
ZeropageType.FULL,
|
||||
listOf((0x50u..0x5fu)),
|
||||
false,
|
||||
false,
|
||||
compTarget
|
||||
)
|
||||
)
|
||||
zp.free.size shouldBe 256-6-16
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestC64Zeropage {
|
||||
class TestC64Zeropage: FunSpec({
|
||||
|
||||
private val errors = ErrorReporterForTests()
|
||||
val errors = ErrorReporterForTests()
|
||||
|
||||
@Test
|
||||
fun testNames() {
|
||||
test("testNames") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target))
|
||||
|
||||
zp.allocate("", DataType.UBYTE, null, errors)
|
||||
zp.allocate("", DataType.UBYTE, null, errors)
|
||||
zp.allocate("varname", DataType.UBYTE, null, errors)
|
||||
assertFailsWith<AssertionError> {
|
||||
shouldThrow<IllegalArgumentException> {
|
||||
zp.allocate("varname", DataType.UBYTE, null, errors)
|
||||
}
|
||||
zp.allocate("varname2", DataType.UBYTE, null, errors)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testZpFloatEnable() {
|
||||
test("testZpFloatEnable") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
|
||||
assertFailsWith<InternalCompilerException> {
|
||||
shouldThrow<InternalCompilerException> {
|
||||
zp.allocate("", DataType.FLOAT, null, errors)
|
||||
}
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false, C64Target))
|
||||
assertFailsWith<InternalCompilerException> {
|
||||
shouldThrow<InternalCompilerException> {
|
||||
zp2.allocate("", DataType.FLOAT, null, errors)
|
||||
}
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, C64Target))
|
||||
zp3.allocate("", DataType.FLOAT, null, errors)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testZpModesWithFloats() {
|
||||
test("testZpModesWithFloats") {
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, C64Target))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, C64Target))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, C64Target))
|
||||
assertFailsWith<InternalCompilerException> {
|
||||
shouldThrow<InternalCompilerException> {
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true, false, C64Target))
|
||||
}
|
||||
assertFailsWith<InternalCompilerException> {
|
||||
shouldThrow<InternalCompilerException> {
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, false, C64Target))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testZpDontuse() {
|
||||
test("testZpDontuse") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false, C64Target))
|
||||
println(zp.free)
|
||||
assertEquals(0, zp.availableBytes())
|
||||
assertFailsWith<InternalCompilerException> {
|
||||
zp.availableBytes() shouldBe 0
|
||||
shouldThrow<InternalCompilerException> {
|
||||
zp.allocate("", DataType.BYTE, null, errors)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFreeSpacesBytes() {
|
||||
test("testFreeSpacesBytes") {
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
|
||||
assertEquals(18, zp1.availableBytes())
|
||||
zp1.availableBytes() shouldBe 18
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, C64Target))
|
||||
assertEquals(85, zp2.availableBytes())
|
||||
zp2.availableBytes() shouldBe 85
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, C64Target))
|
||||
assertEquals(125, zp3.availableBytes())
|
||||
zp3.availableBytes() shouldBe 125
|
||||
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
|
||||
assertEquals(238, zp4.availableBytes())
|
||||
zp4.availableBytes() shouldBe 238
|
||||
zp4.allocate("test", DataType.UBYTE, null, errors)
|
||||
assertEquals(237, zp4.availableBytes())
|
||||
zp4.availableBytes() shouldBe 237
|
||||
zp4.allocate("test2", DataType.UBYTE, null, errors)
|
||||
assertEquals(236, zp4.availableBytes())
|
||||
zp4.availableBytes() shouldBe 236
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFreeSpacesWords() {
|
||||
test("testFreeSpacesWords") {
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
|
||||
assertEquals(6, zp1.availableWords())
|
||||
zp1.availableWords() shouldBe 6
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, C64Target))
|
||||
assertEquals(38, zp2.availableWords())
|
||||
zp2.availableWords() shouldBe 38
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, C64Target))
|
||||
assertEquals(57, zp3.availableWords())
|
||||
zp3.availableWords() shouldBe 57
|
||||
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
|
||||
assertEquals(116, zp4.availableWords())
|
||||
zp4.availableWords() shouldBe 116
|
||||
zp4.allocate("test", DataType.UWORD, null, errors)
|
||||
assertEquals(115, zp4.availableWords())
|
||||
zp4.availableWords() shouldBe 115
|
||||
zp4.allocate("test2", DataType.UWORD, null, errors)
|
||||
assertEquals(114, zp4.availableWords())
|
||||
zp4.availableWords() shouldBe 114
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testReservedSpace() {
|
||||
test("testReservedSpace") {
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
|
||||
assertEquals(238, zp1.availableBytes())
|
||||
assertTrue(50 in zp1.free)
|
||||
assertTrue(100 in zp1.free)
|
||||
assertTrue(49 in zp1.free)
|
||||
assertTrue(101 in zp1.free)
|
||||
assertTrue(200 in zp1.free)
|
||||
assertTrue(255 in zp1.free)
|
||||
assertTrue(199 in zp1.free)
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50 .. 100, 200..255), false, false, C64Target))
|
||||
assertEquals(139, zp2.availableBytes())
|
||||
assertFalse(50 in zp2.free)
|
||||
assertFalse(100 in zp2.free)
|
||||
assertTrue(49 in zp2.free)
|
||||
assertTrue(101 in zp2.free)
|
||||
assertFalse(200 in zp2.free)
|
||||
assertFalse(255 in zp2.free)
|
||||
assertTrue(199 in zp2.free)
|
||||
zp1.availableBytes() shouldBe 238
|
||||
50u shouldBeIn zp1.free
|
||||
100u shouldBeIn zp1.free
|
||||
49u shouldBeIn zp1.free
|
||||
101u shouldBeIn zp1.free
|
||||
200u shouldBeIn zp1.free
|
||||
255u shouldBeIn zp1.free
|
||||
199u shouldBeIn zp1.free
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50u .. 100u, 200u..255u), false, false, C64Target))
|
||||
zp2.availableBytes() shouldBe 139
|
||||
50u shouldNotBeIn zp2.free
|
||||
100u shouldNotBeIn zp2.free
|
||||
49u shouldBeIn zp2.free
|
||||
101u shouldBeIn zp2.free
|
||||
200u shouldNotBeIn zp2.free
|
||||
255u shouldNotBeIn zp2.free
|
||||
199u shouldBeIn zp2.free
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBasicsafeAllocation() {
|
||||
test("testBasicsafeAllocation") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
|
||||
assertEquals(18, zp.availableBytes())
|
||||
assertTrue(zp.hasByteAvailable())
|
||||
assertTrue(zp.hasWordAvailable())
|
||||
zp.availableBytes() shouldBe 18
|
||||
zp.hasByteAvailable() shouldBe true
|
||||
zp.hasWordAvailable() shouldBe true
|
||||
|
||||
assertFailsWith<ZeropageDepletedError> {
|
||||
shouldThrow<ZeropageDepletedError> {
|
||||
// in regular zp there aren't 5 sequential bytes free
|
||||
zp.allocate("", DataType.FLOAT, null, errors)
|
||||
}
|
||||
|
||||
for (i in 0 until zp.availableBytes()) {
|
||||
val loc = zp.allocate("", DataType.UBYTE, null, errors)
|
||||
assertTrue(loc > 0)
|
||||
loc shouldBeGreaterThan 0u
|
||||
}
|
||||
assertEquals(0, zp.availableBytes())
|
||||
assertFalse(zp.hasByteAvailable())
|
||||
assertFalse(zp.hasWordAvailable())
|
||||
assertFailsWith<ZeropageDepletedError> {
|
||||
zp.availableBytes() shouldBe 0
|
||||
zp.hasByteAvailable() shouldBe false
|
||||
zp.hasWordAvailable() shouldBe false
|
||||
shouldThrow<ZeropageDepletedError> {
|
||||
zp.allocate("", DataType.UBYTE, null, errors)
|
||||
}
|
||||
assertFailsWith<ZeropageDepletedError> {
|
||||
shouldThrow<ZeropageDepletedError> {
|
||||
zp.allocate("", DataType.UWORD, null, errors)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFullAllocation() {
|
||||
test("testFullAllocation") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
|
||||
assertEquals(238, zp.availableBytes())
|
||||
assertTrue(zp.hasByteAvailable())
|
||||
assertTrue(zp.hasWordAvailable())
|
||||
zp.availableBytes() shouldBe 238
|
||||
zp.hasByteAvailable() shouldBe true
|
||||
zp.hasWordAvailable() shouldBe true
|
||||
val loc = zp.allocate("", DataType.UWORD, null, errors)
|
||||
assertTrue(loc > 3)
|
||||
assertFalse(loc in zp.free)
|
||||
loc shouldBeGreaterThan 3u
|
||||
loc shouldNotBeIn zp.free
|
||||
val num = zp.availableBytes() / 2
|
||||
|
||||
for(i in 0..num-4) {
|
||||
zp.allocate("", DataType.UWORD, null, errors)
|
||||
}
|
||||
assertEquals(6,zp.availableBytes())
|
||||
zp.availableBytes() shouldBe 6
|
||||
|
||||
assertFailsWith<ZeropageDepletedError> {
|
||||
shouldThrow<ZeropageDepletedError> {
|
||||
// can't allocate because no more sequential bytes, only fragmented
|
||||
zp.allocate("", DataType.UWORD, null, errors)
|
||||
}
|
||||
@@ -234,88 +236,85 @@ class TestC64Zeropage {
|
||||
zp.allocate("", DataType.UBYTE, null, errors)
|
||||
}
|
||||
|
||||
assertEquals(0, zp.availableBytes())
|
||||
assertFalse(zp.hasByteAvailable())
|
||||
assertFalse(zp.hasWordAvailable())
|
||||
assertFailsWith<ZeropageDepletedError> {
|
||||
zp.availableBytes() shouldBe 0
|
||||
zp.hasByteAvailable() shouldBe false
|
||||
zp.hasWordAvailable() shouldBe false
|
||||
shouldThrow<ZeropageDepletedError> {
|
||||
// no more space
|
||||
zp.allocate("", DataType.UBYTE, null, errors)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEfficientAllocation() {
|
||||
test("testEfficientAllocation") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
|
||||
assertEquals(18, zp.availableBytes())
|
||||
assertEquals(0x04, zp.allocate("", DataType.WORD, null, errors))
|
||||
assertEquals(0x06, zp.allocate("", DataType.UBYTE, null, errors))
|
||||
assertEquals(0x0a, zp.allocate("", DataType.UBYTE, null, errors))
|
||||
assertEquals(0x9b, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0x9e, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0xa5, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0xb0, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0xbe, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0x0e, zp.allocate("", DataType.UBYTE, null, errors))
|
||||
assertEquals(0x92, zp.allocate("", DataType.UBYTE, null, errors))
|
||||
assertEquals(0x96, zp.allocate("", DataType.UBYTE, null, errors))
|
||||
assertEquals(0xf9, zp.allocate("", DataType.UBYTE, null, errors))
|
||||
assertEquals(0, zp.availableBytes())
|
||||
zp.availableBytes() shouldBe 18
|
||||
zp.allocate("", DataType.WORD, null, errors) shouldBe 0x04u
|
||||
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x06u
|
||||
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x0au
|
||||
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0x9bu
|
||||
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0x9eu
|
||||
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0xa5u
|
||||
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0xb0u
|
||||
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0xbeu
|
||||
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x0eu
|
||||
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x92u
|
||||
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x96u
|
||||
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0xf9u
|
||||
zp.availableBytes() shouldBe 0
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testReservedLocations() {
|
||||
test("testReservedLocations") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target))
|
||||
assertEquals(zp.SCRATCH_REG, zp.SCRATCH_B1+1, "zp _B1 and _REG must be next to each other to create a word")
|
||||
withClue("zp _B1 and _REG must be next to each other to create a word") {
|
||||
zp.SCRATCH_B1 + 1u shouldBe zp.SCRATCH_REG
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestCx16Zeropage {
|
||||
private val errors = ErrorReporterForTests()
|
||||
class TestCx16Zeropage: FunSpec({
|
||||
val errors = ErrorReporterForTests()
|
||||
|
||||
@Test
|
||||
fun testReservedLocations() {
|
||||
test("testReservedLocations") {
|
||||
val zp = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, Cx16Target))
|
||||
assertEquals(zp.SCRATCH_REG, zp.SCRATCH_B1+1, "zp _B1 and _REG must be next to each other to create a word")
|
||||
withClue("zp _B1 and _REG must be next to each other to create a word") {
|
||||
zp.SCRATCH_B1 + 1u shouldBe zp.SCRATCH_REG
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFreeSpacesBytes() {
|
||||
test("testFreeSpacesBytes") {
|
||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, Cx16Target))
|
||||
assertEquals(88, zp1.availableBytes())
|
||||
zp1.availableBytes() shouldBe 88
|
||||
val zp2 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, Cx16Target))
|
||||
assertEquals(175, zp2.availableBytes())
|
||||
zp2.availableBytes() shouldBe 175
|
||||
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target))
|
||||
assertEquals(216, zp3.availableBytes())
|
||||
zp3.availableBytes() shouldBe 216
|
||||
zp3.allocate("test", DataType.UBYTE, null, errors)
|
||||
assertEquals(215, zp3.availableBytes())
|
||||
zp3.availableBytes() shouldBe 215
|
||||
zp3.allocate("test2", DataType.UBYTE, null, errors)
|
||||
assertEquals(214, zp3.availableBytes())
|
||||
zp3.availableBytes() shouldBe 214
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFreeSpacesWords() {
|
||||
test("testFreeSpacesWords") {
|
||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target))
|
||||
assertEquals(108, zp1.availableWords())
|
||||
zp1.availableWords() shouldBe 108
|
||||
val zp2 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, Cx16Target))
|
||||
assertEquals(87, zp2.availableWords())
|
||||
zp2.availableWords() shouldBe 87
|
||||
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, Cx16Target))
|
||||
assertEquals(44, zp3.availableWords())
|
||||
zp3.availableWords() shouldBe 44
|
||||
zp3.allocate("test", DataType.UWORD, null, errors)
|
||||
assertEquals(43, zp3.availableWords())
|
||||
zp3.availableWords() shouldBe 43
|
||||
zp3.allocate("test2", DataType.UWORD, null, errors)
|
||||
assertEquals(42, zp3.availableWords())
|
||||
zp3.availableWords() shouldBe 42
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testReservedSpace() {
|
||||
test("testReservedSpace") {
|
||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target))
|
||||
assertEquals(216, zp1.availableBytes())
|
||||
assertTrue(0x22 in zp1.free)
|
||||
assertTrue(0x80 in zp1.free)
|
||||
assertTrue(0xff in zp1.free)
|
||||
assertFalse(0x02 in zp1.free)
|
||||
assertFalse(0x21 in zp1.free)
|
||||
zp1.availableBytes() shouldBe 216
|
||||
0x22u shouldBeIn zp1.free
|
||||
0x80u shouldBeIn zp1.free
|
||||
0xffu shouldBeIn zp1.free
|
||||
0x02u shouldNotBeIn zp1.free
|
||||
0x21u shouldNotBeIn zp1.free
|
||||
}
|
||||
}
|
||||
})
|
||||
+15
-24
@@ -1,7 +1,8 @@
|
||||
package prog8tests.ast
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import io.kotest.assertions.fail
|
||||
import io.kotest.core.spec.style.AnnotationSpec
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import prog8.ast.AstToSourceTextConverter
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
@@ -9,14 +10,12 @@ import prog8.ast.internedStringsModuleName
|
||||
import prog8.parser.ParseError
|
||||
import prog8.parser.Prog8Parser.parseModule
|
||||
import prog8.parser.SourceCode
|
||||
import prog8tests.ast.helpers.DummyFunctions
|
||||
import prog8tests.ast.helpers.DummyMemsizer
|
||||
import prog8tests.ast.helpers.DummyStringEncoder
|
||||
import kotlin.test.assertContains
|
||||
import prog8tests.helpers.DummyFunctions
|
||||
import prog8tests.helpers.DummyMemsizer
|
||||
import prog8tests.helpers.DummyStringEncoder
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestAstToSourceText {
|
||||
class TestAstToSourceText: AnnotationSpec() {
|
||||
|
||||
private fun generateP8(module: Module) : String {
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
@@ -35,8 +34,7 @@ class TestAstToSourceText {
|
||||
val parsedAgain = parseModule(SourceCode.Text(generatedText))
|
||||
return Pair(generatedText, parsedAgain)
|
||||
} catch (e: ParseError) {
|
||||
assert(false) { "should produce valid Prog8 but threw $e" }
|
||||
throw e
|
||||
fail("should produce valid Prog8 but threw $e")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,24 +42,21 @@ class TestAstToSourceText {
|
||||
fun testMentionsInternedStringsModule() {
|
||||
val orig = SourceCode.Text("\n")
|
||||
val (txt, _) = roundTrip(parseModule(orig))
|
||||
// assertContains has *actual* first!
|
||||
assertContains(txt, Regex(";.*$internedStringsModuleName"))
|
||||
txt shouldContain Regex(";.*$internedStringsModuleName")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testImportDirectiveWithLib() {
|
||||
val orig = SourceCode.Text("%import textio\n")
|
||||
val (txt, _) = roundTrip(parseModule(orig))
|
||||
// assertContains has *actual* first!
|
||||
assertContains(txt, Regex("%import +textio"))
|
||||
txt shouldContain Regex("%import +textio")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testImportDirectiveWithUserModule() {
|
||||
val orig = SourceCode.Text("%import my_own_stuff\n")
|
||||
val (txt, _) = roundTrip(parseModule(orig))
|
||||
// assertContains has *actual* first!
|
||||
assertContains(txt, Regex("%import +my_own_stuff"))
|
||||
txt shouldContain Regex("%import +my_own_stuff")
|
||||
}
|
||||
|
||||
|
||||
@@ -73,8 +68,7 @@ class TestAstToSourceText {
|
||||
}
|
||||
""")
|
||||
val (txt, _) = roundTrip(parseModule(orig))
|
||||
// assertContains has *actual* first!
|
||||
assertContains(txt, Regex("str +s += +\"fooBar\\\\n\""))
|
||||
txt shouldContain Regex("str +s += +\"fooBar\\\\n\"")
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -85,8 +79,7 @@ class TestAstToSourceText {
|
||||
}
|
||||
""")
|
||||
val (txt, _) = roundTrip(parseModule(orig))
|
||||
// assertContains has *actual* first!
|
||||
assertContains(txt, Regex("str +sAlt += +@\"fooBar\\\\n\""))
|
||||
txt shouldContain Regex("str +sAlt += +@\"fooBar\\\\n\"")
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -97,8 +90,7 @@ class TestAstToSourceText {
|
||||
}
|
||||
""")
|
||||
val (txt, _) = roundTrip(parseModule(orig))
|
||||
// assertContains has *actual* first!
|
||||
assertContains(txt, Regex("ubyte +c += +'x'"), "char literal")
|
||||
txt shouldContain Regex("ubyte +c += +'x'")
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -109,8 +101,7 @@ class TestAstToSourceText {
|
||||
}
|
||||
""")
|
||||
val (txt, _) = roundTrip(parseModule(orig))
|
||||
// assertContains has *actual* first!
|
||||
assertContains(txt, Regex("ubyte +cAlt += +@'x'"), "alt char literal")
|
||||
txt shouldContain Regex("ubyte +cAlt += +@'x'")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,804 @@
|
||||
package prog8tests.ast
|
||||
|
||||
import com.github.michaelbull.result.getOrElse
|
||||
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.or
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import io.kotest.matchers.string.shouldStartWith
|
||||
import io.kotest.matchers.types.instanceOf
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.printProgram
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.cbm.Petscii
|
||||
import prog8.parser.ParseError
|
||||
import prog8.parser.Prog8Parser.parseModule
|
||||
import prog8.parser.SourceCode
|
||||
import prog8tests.helpers.*
|
||||
import prog8tests.helpers.DummyFunctions
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.isRegularFile
|
||||
import kotlin.io.path.name
|
||||
import kotlin.io.path.nameWithoutExtension
|
||||
|
||||
|
||||
class TestProg8Parser: FunSpec( {
|
||||
|
||||
context("Newline at end") {
|
||||
test("is not required - #40, fixed by #45") {
|
||||
val nl = "\n" // say, Unix-style (different flavours tested elsewhere)
|
||||
val src = SourceCode.Text("foo {" + nl + "}") // source ends with '}' (= NO newline, issue #40)
|
||||
|
||||
// #40: Prog8ANTLRParser would report (throw) "missing <EOL> at '<EOF>'"
|
||||
val module = parseModule(src)
|
||||
module.statements.size shouldBe 1
|
||||
}
|
||||
|
||||
test("is still accepted - #40, fixed by #45") {
|
||||
val nl = "\n" // say, Unix-style (different flavours tested elsewhere)
|
||||
val srcText = "foo {" + nl + "}" + nl // source does end with a newline (issue #40)
|
||||
val module = parseModule(SourceCode.Text(srcText))
|
||||
module.statements.size shouldBe 1
|
||||
}
|
||||
}
|
||||
|
||||
context("Newline") {
|
||||
test("is required after each block except the last") {
|
||||
val nl = "\n" // say, Unix-style (different flavours tested elsewhere)
|
||||
|
||||
// BAD: 2nd block `bar` does NOT start on new line; however, there's is a nl at the very end
|
||||
val srcBad = "foo {" + nl + "}" + " bar {" + nl + "}" + nl
|
||||
|
||||
// GOOD: 2nd block `bar` does start on a new line; however, a nl at the very end ain't needed
|
||||
val srcGood = "foo {" + nl + "}" + nl + "bar {" + nl + "}"
|
||||
|
||||
shouldThrow<ParseError> { parseModule(SourceCode.Text(srcBad)) }
|
||||
val module = parseModule(SourceCode.Text(srcGood))
|
||||
module.statements.size shouldBe 2
|
||||
}
|
||||
|
||||
test("is required between two Blocks or Directives - #47") {
|
||||
// block and block
|
||||
shouldThrow<ParseError>{ parseModule(SourceCode.Text("""
|
||||
blockA {
|
||||
} blockB {
|
||||
}
|
||||
""")) }
|
||||
|
||||
// block and directive
|
||||
shouldThrow<ParseError>{ parseModule(SourceCode.Text("""
|
||||
blockB {
|
||||
} %import textio
|
||||
""")) }
|
||||
|
||||
// The following two are bogus due to directive *args* expected to follow the directive name.
|
||||
// Leaving them in anyways.
|
||||
|
||||
// dir and block
|
||||
shouldThrow<ParseError>{ parseModule(SourceCode.Text("""
|
||||
%import textio blockB {
|
||||
}
|
||||
""")) }
|
||||
|
||||
shouldThrow<ParseError>{ parseModule(SourceCode.Text("""
|
||||
%import textio %import syslib
|
||||
""")) }
|
||||
}
|
||||
|
||||
test("can be Win, Unix or mixed, even mixed") {
|
||||
val nlWin = "\r\n"
|
||||
val nlUnix = "\n"
|
||||
val nlMac = "\r"
|
||||
|
||||
//parseModule(Paths.get("test", "fixtures", "mac_newlines.p8").toAbsolutePath())
|
||||
|
||||
// a good mix of all kinds of newlines:
|
||||
val srcText =
|
||||
"foo {" +
|
||||
nlMac +
|
||||
nlWin +
|
||||
"}" +
|
||||
nlMac + // <-- do test a single \r (!) where an EOL is expected
|
||||
"bar {" +
|
||||
nlUnix +
|
||||
"}" +
|
||||
nlUnix + nlMac // both should be "eaten up" by just one EOL token
|
||||
"combi {" +
|
||||
nlMac + nlWin + nlUnix // all three should be "eaten up" by just one EOL token
|
||||
"}" +
|
||||
nlUnix // end with newline (see testModuleSourceNeedNotEndWithNewline)
|
||||
|
||||
val module = parseModule(SourceCode.Text(srcText))
|
||||
module.statements.size shouldBe 2
|
||||
}
|
||||
}
|
||||
|
||||
context("EOLsInterleavedWithComments") {
|
||||
|
||||
test("are ok before first block - #47") {
|
||||
// issue: #47
|
||||
val srcText = """
|
||||
; comment
|
||||
|
||||
; comment
|
||||
|
||||
blockA {
|
||||
}
|
||||
"""
|
||||
val module = parseModule(SourceCode.Text(srcText))
|
||||
module.statements.size shouldBe 1
|
||||
}
|
||||
|
||||
test("are ok between blocks - #47") {
|
||||
// issue: #47
|
||||
val srcText = """
|
||||
blockA {
|
||||
}
|
||||
; comment
|
||||
|
||||
; comment
|
||||
|
||||
blockB {
|
||||
}
|
||||
"""
|
||||
val module = parseModule(SourceCode.Text(srcText))
|
||||
module.statements.size shouldBe 2
|
||||
}
|
||||
|
||||
test("are ok after last block - #47") {
|
||||
// issue: #47
|
||||
val srcText = """
|
||||
blockA {
|
||||
}
|
||||
; comment
|
||||
|
||||
; comment
|
||||
|
||||
"""
|
||||
val module = parseModule(SourceCode.Text(srcText))
|
||||
module.statements.size shouldBe 1
|
||||
}
|
||||
}
|
||||
|
||||
context("ImportDirectives") {
|
||||
test("should not be looked into by the parser") {
|
||||
val importedNoExt = assumeNotExists(fixturesDir, "i_do_not_exist")
|
||||
assumeNotExists(fixturesDir, "i_do_not_exist.p8")
|
||||
val text = "%import ${importedNoExt.name}"
|
||||
val module = parseModule(SourceCode.Text(text))
|
||||
|
||||
module.statements.size shouldBe 1
|
||||
}
|
||||
}
|
||||
|
||||
context("EmptySourcecode") {
|
||||
test("from an empty string should result in empty Module") {
|
||||
val module = parseModule(SourceCode.Text(""))
|
||||
module.statements.size shouldBe 0
|
||||
}
|
||||
|
||||
test("from an empty file should result in empty Module") {
|
||||
val path = assumeReadableFile(fixturesDir, "ast_empty.p8")
|
||||
val module = parseModule(SourceCode.File(path))
|
||||
module.statements.size shouldBe 0
|
||||
}
|
||||
}
|
||||
|
||||
context("NameOfModule") {
|
||||
test("parsed from a string") {
|
||||
val srcText = """
|
||||
main {
|
||||
}
|
||||
""".trimIndent()
|
||||
val module = parseModule(SourceCode.Text(srcText))
|
||||
|
||||
// Note: assertContains has *actual* as first param
|
||||
module.name shouldContain Regex("^<String@[0-9a-f\\-]+>$")
|
||||
}
|
||||
|
||||
test("parsed from a file") {
|
||||
val path = assumeReadableFile(fixturesDir, "ast_simple_main.p8")
|
||||
val module = parseModule(SourceCode.File(path))
|
||||
module.name shouldBe path.nameWithoutExtension
|
||||
}
|
||||
}
|
||||
|
||||
context("PositionOfAstNodesAndParseErrors") {
|
||||
|
||||
fun assertPosition(
|
||||
actual: Position,
|
||||
expFile: String? = null,
|
||||
expLine: Int? = null,
|
||||
expStartCol: Int? = null,
|
||||
expEndCol: Int? = null
|
||||
) {
|
||||
require(!listOf(expLine, expStartCol, expEndCol).all { it == null })
|
||||
if (expLine != null) actual.line shouldBe expLine
|
||||
if (expStartCol != null) actual.startCol shouldBe expStartCol
|
||||
if (expEndCol != null) actual.endCol shouldBe expEndCol
|
||||
if (expFile != null) actual.file shouldBe expFile
|
||||
}
|
||||
|
||||
fun assertPosition(
|
||||
actual: Position,
|
||||
expFile: Regex? = null,
|
||||
expLine: Int? = null,
|
||||
expStartCol: Int? = null,
|
||||
expEndCol: Int? = null
|
||||
) {
|
||||
require(!listOf(expLine, expStartCol, expEndCol).all { it == null })
|
||||
if (expLine != null) actual.line shouldBe expLine
|
||||
if (expStartCol != null) actual.startCol shouldBe expStartCol
|
||||
if (expEndCol != null) actual.endCol shouldBe expEndCol
|
||||
if (expFile != null) actual.file shouldContain expFile
|
||||
}
|
||||
|
||||
fun assertPositionOf(
|
||||
actual: Node,
|
||||
expFile: String? = null,
|
||||
expLine: Int? = null,
|
||||
expStartCol: Int? = null,
|
||||
expEndCol: Int? = null
|
||||
) =
|
||||
assertPosition(actual.position, expFile, expLine, expStartCol, expEndCol)
|
||||
|
||||
fun assertPositionOf(
|
||||
actual: Node,
|
||||
expFile: Regex? = null,
|
||||
expLine: Int? = null,
|
||||
expStartCol: Int? = null,
|
||||
expEndCol: Int? = null
|
||||
) =
|
||||
assertPosition(actual.position, expFile, expLine, expStartCol, expEndCol)
|
||||
|
||||
|
||||
test("in ParseError from bad string source code") {
|
||||
val srcText = "bad * { }\n"
|
||||
|
||||
val e = shouldThrow<ParseError> { parseModule(SourceCode.Text(srcText)) }
|
||||
assertPosition(e.position, Regex("^<String@[0-9a-f\\-]+>$"), 1, 4, 4)
|
||||
}
|
||||
|
||||
test("in ParseError from bad file source code") {
|
||||
val path = assumeReadableFile(fixturesDir, "ast_file_with_syntax_error.p8")
|
||||
|
||||
val e = shouldThrow<ParseError> { parseModule(SourceCode.File(path)) }
|
||||
assertPosition(e.position, SourceCode.relative(path).toString(), 2, 6)
|
||||
}
|
||||
|
||||
test("of Module parsed from a string") {
|
||||
val srcText = """
|
||||
main {
|
||||
}
|
||||
"""
|
||||
val module = parseModule(SourceCode.Text(srcText))
|
||||
assertPositionOf(module, Regex("^<String@[0-9a-f\\-]+>$"), 1, 0)
|
||||
}
|
||||
|
||||
test("of Module parsed from a file") {
|
||||
val path = assumeReadableFile(fixturesDir, "ast_simple_main.p8")
|
||||
val module = parseModule(SourceCode.File(path))
|
||||
assertPositionOf(module, SourceCode.relative(path).toString(), 1, 0)
|
||||
}
|
||||
|
||||
test("of non-root Nodes parsed from file") {
|
||||
val path = assumeReadableFile(fixturesDir, "ast_simple_main.p8")
|
||||
|
||||
val module = parseModule(SourceCode.File(path))
|
||||
val mpf = module.position.file
|
||||
assertPositionOf(module, SourceCode.relative(path).toString(), 1, 0)
|
||||
val mainBlock = module.statements.filterIsInstance<Block>()[0]
|
||||
assertPositionOf(mainBlock, mpf, 2, 1, 4)
|
||||
val startSub = mainBlock.statements.filterIsInstance<Subroutine>()[0]
|
||||
assertPositionOf(startSub, mpf, 3, 5, 7)
|
||||
}
|
||||
|
||||
test("of non-root Nodes parsed from a string") {
|
||||
val srcText = """
|
||||
%zeropage basicsafe
|
||||
main {
|
||||
sub start() {
|
||||
ubyte foo = 42
|
||||
ubyte bar
|
||||
when (foo) {
|
||||
23 -> bar = 'x'
|
||||
42 -> bar = 'y'
|
||||
else -> bar = 'z'
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val module = parseModule(SourceCode.Text(srcText))
|
||||
val mpf = module.position.file
|
||||
|
||||
val targetDirective = module.statements.filterIsInstance<Directive>()[0]
|
||||
assertPositionOf(targetDirective, mpf, 1, 1, 9)
|
||||
val mainBlock = module.statements.filterIsInstance<Block>()[0]
|
||||
assertPositionOf(mainBlock, mpf, 2, 1, 4)
|
||||
val startSub = mainBlock.statements.filterIsInstance<Subroutine>()[0]
|
||||
assertPositionOf(startSub, mpf, 3, 5, 7)
|
||||
val declFoo = startSub.statements.filterIsInstance<VarDecl>()[0]
|
||||
assertPositionOf(declFoo, mpf, 4, 9, 13)
|
||||
val rhsFoo = declFoo.value!!
|
||||
assertPositionOf(rhsFoo, mpf, 4, 21, 22)
|
||||
val declBar = startSub.statements.filterIsInstance<VarDecl>()[1]
|
||||
assertPositionOf(declBar, mpf, 5, 9, 13)
|
||||
val whenStmt = startSub.statements.filterIsInstance<WhenStatement>()[0]
|
||||
assertPositionOf(whenStmt, mpf, 6, 9, 12)
|
||||
assertPositionOf(whenStmt.choices[0], mpf, 7, 13, 14)
|
||||
assertPositionOf(whenStmt.choices[1], mpf, 8, 13, 14)
|
||||
assertPositionOf(whenStmt.choices[2], mpf, 9, 13, 16)
|
||||
}
|
||||
}
|
||||
|
||||
context("PositionFile") {
|
||||
fun assertSomethingForAllNodes(module: Module, asserter: (Node) -> Unit) {
|
||||
asserter(module)
|
||||
module.statements.forEach(asserter)
|
||||
module.statements.filterIsInstance<Block>().forEach { b ->
|
||||
asserter(b)
|
||||
b.statements.forEach(asserter)
|
||||
b.statements.filterIsInstance<Subroutine>().forEach { s ->
|
||||
asserter(s)
|
||||
s.statements.forEach(asserter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test("isn't absolute for filesystem paths") {
|
||||
val path = assumeReadableFile(fixturesDir, "ast_simple_main.p8")
|
||||
val module = parseModule(SourceCode.File(path))
|
||||
assertSomethingForAllNodes(module) {
|
||||
Path(it.position.file).isAbsolute shouldBe false
|
||||
Path(it.position.file).isRegularFile() shouldBe true
|
||||
}
|
||||
}
|
||||
|
||||
test("is mangled string id for string sources")
|
||||
{
|
||||
val srcText="""
|
||||
%zeropage basicsafe
|
||||
main {
|
||||
sub start() {
|
||||
ubyte aa=99
|
||||
aa++
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val module = parseModule(SourceCode.Text(srcText))
|
||||
assertSomethingForAllNodes(module) {
|
||||
it.position.file shouldStartWith SourceCode.stringSourcePrefix
|
||||
}
|
||||
}
|
||||
|
||||
test("is library prefixed path for resources")
|
||||
{
|
||||
val resource = SourceCode.Resource("prog8lib/math.p8")
|
||||
val module = parseModule(resource)
|
||||
assertSomethingForAllNodes(module) {
|
||||
it.position.file shouldStartWith SourceCode.libraryFilePrefix
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("CharLiterals") {
|
||||
|
||||
test("in argument position, no altEnc") {
|
||||
val src = SourceCode.Text("""
|
||||
main {
|
||||
sub start() {
|
||||
chrout('\n')
|
||||
}
|
||||
}
|
||||
""")
|
||||
val module = parseModule(src)
|
||||
|
||||
val startSub = module
|
||||
.statements.filterIsInstance<Block>()[0]
|
||||
.statements.filterIsInstance<Subroutine>()[0]
|
||||
val funCall = startSub.statements.filterIsInstance<IFunctionCall>().first()
|
||||
|
||||
funCall.args[0] shouldBe(instanceOf<CharLiteral>())
|
||||
val char = funCall.args[0] as CharLiteral
|
||||
char.value shouldBe '\n'
|
||||
}
|
||||
|
||||
test("on rhs of block-level var decl, no AltEnc") {
|
||||
val src = SourceCode.Text("""
|
||||
main {
|
||||
ubyte c = 'x'
|
||||
}
|
||||
""")
|
||||
val module = parseModule(src)
|
||||
val decl = module
|
||||
.statements.filterIsInstance<Block>()[0]
|
||||
.statements.filterIsInstance<VarDecl>()[0]
|
||||
|
||||
val rhs = decl.value as CharLiteral
|
||||
rhs.value shouldBe 'x'
|
||||
rhs.altEncoding shouldBe false
|
||||
}
|
||||
|
||||
test("on rhs of block-level const decl, with AltEnc") {
|
||||
val src = SourceCode.Text("""
|
||||
main {
|
||||
const ubyte c = @'x'
|
||||
}
|
||||
""")
|
||||
val module = parseModule(src)
|
||||
val decl = module
|
||||
.statements.filterIsInstance<Block>()[0]
|
||||
.statements.filterIsInstance<VarDecl>()[0]
|
||||
|
||||
val rhs = decl.value as CharLiteral
|
||||
rhs.value shouldBe 'x'
|
||||
rhs.altEncoding shouldBe true
|
||||
}
|
||||
|
||||
test("on rhs of subroutine-level var decl, no AltEnc") {
|
||||
val src = SourceCode.Text("""
|
||||
main {
|
||||
sub start() {
|
||||
ubyte c = 'x'
|
||||
}
|
||||
}
|
||||
""")
|
||||
val module = parseModule(src)
|
||||
val decl = module
|
||||
.statements.filterIsInstance<Block>()[0]
|
||||
.statements.filterIsInstance<Subroutine>()[0]
|
||||
.statements.filterIsInstance<VarDecl>()[0]
|
||||
|
||||
val rhs = decl.value as CharLiteral
|
||||
rhs.value shouldBe 'x'
|
||||
rhs.altEncoding shouldBe false
|
||||
}
|
||||
|
||||
test("on rhs of subroutine-level const decl, with AltEnc") {
|
||||
val src = SourceCode.Text("""
|
||||
main {
|
||||
sub start() {
|
||||
const ubyte c = @'x'
|
||||
}
|
||||
}
|
||||
""")
|
||||
val module = parseModule(src)
|
||||
val decl = module
|
||||
.statements.filterIsInstance<Block>()[0]
|
||||
.statements.filterIsInstance<Subroutine>()[0]
|
||||
.statements.filterIsInstance<VarDecl>()[0]
|
||||
|
||||
val rhs = decl.value as CharLiteral
|
||||
rhs.value shouldBe 'x'
|
||||
rhs.altEncoding shouldBe true
|
||||
}
|
||||
}
|
||||
|
||||
context("Ranges") {
|
||||
|
||||
test("in for-loops") {
|
||||
val module = parseModule(SourceCode.Text("""
|
||||
main {
|
||||
sub start() {
|
||||
ubyte ub
|
||||
for ub in "start" downto "end" { ; #0
|
||||
}
|
||||
for ub in "something" { ; #1
|
||||
}
|
||||
for ub in @'a' to 'f' { ; #2
|
||||
}
|
||||
for ub in false to true { ; #3
|
||||
}
|
||||
for ub in 9 to 1 { ; #4 - yes, *parser* should NOT check!
|
||||
}
|
||||
}
|
||||
}
|
||||
"""))
|
||||
val iterables = module
|
||||
.statements.filterIsInstance<Block>()[0]
|
||||
.statements.filterIsInstance<Subroutine>()[0]
|
||||
.statements.filterIsInstance<ForLoop>()
|
||||
.map { it.iterable }
|
||||
|
||||
iterables.size shouldBe 5
|
||||
|
||||
val it0 = iterables[0] as RangeExpr
|
||||
it0.from shouldBe instanceOf<StringLiteralValue>()
|
||||
it0.to shouldBe instanceOf<StringLiteralValue>()
|
||||
|
||||
val it1 = iterables[1] as StringLiteralValue
|
||||
it1.value shouldBe "something"
|
||||
|
||||
val it2 = iterables[2] as RangeExpr
|
||||
it2.from shouldBe instanceOf<CharLiteral>()
|
||||
it2.to shouldBe instanceOf<CharLiteral>()
|
||||
|
||||
val it3 = iterables[3] as RangeExpr
|
||||
it3.from shouldBe instanceOf<NumericLiteralValue>()
|
||||
it3.to shouldBe instanceOf<NumericLiteralValue>()
|
||||
|
||||
val it4 = iterables[4] as RangeExpr
|
||||
it4.from shouldBe instanceOf<NumericLiteralValue>()
|
||||
it4.to shouldBe instanceOf<NumericLiteralValue>()
|
||||
}
|
||||
}
|
||||
|
||||
test("testCharLiteralConstValue") {
|
||||
val char1 = CharLiteral('A', false, Position.DUMMY)
|
||||
val char2 = CharLiteral('z', true, Position.DUMMY)
|
||||
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, AsciiStringEncoder)
|
||||
char1.constValue(program).number.toInt() shouldBe 65
|
||||
char2.constValue(program).number.toInt() shouldBe 122
|
||||
}
|
||||
|
||||
test("testLiteralValueComparisons") {
|
||||
val ten = NumericLiteralValue(DataType.UWORD, 10.0, Position.DUMMY)
|
||||
val nine = NumericLiteralValue(DataType.UBYTE, 9.0, Position.DUMMY)
|
||||
ten shouldBe ten
|
||||
nine shouldNotBe ten
|
||||
(ten != ten) shouldBe false
|
||||
(ten != nine) shouldBe true
|
||||
|
||||
(ten > nine) shouldBe true
|
||||
(ten >= nine) shouldBe true
|
||||
(ten >= ten) shouldBe true
|
||||
(ten > ten) shouldBe false
|
||||
|
||||
(ten < nine) shouldBe false
|
||||
(ten <= nine) shouldBe false
|
||||
(ten <= ten) shouldBe true
|
||||
(ten < ten) shouldBe false
|
||||
|
||||
val abc = StringLiteralValue("abc", false, Position.DUMMY)
|
||||
val abd = StringLiteralValue("abd", false, Position.DUMMY)
|
||||
abc shouldBe abc
|
||||
(abc!=abd) shouldBe true
|
||||
(abc!=abc) shouldBe false
|
||||
}
|
||||
|
||||
test("testAnonScopeStillContainsVarsDirectlyAfterParse") {
|
||||
val src = SourceCode.Text("""
|
||||
main {
|
||||
sub start() {
|
||||
repeat {
|
||||
ubyte xx = 99
|
||||
xx++
|
||||
}
|
||||
}
|
||||
}
|
||||
""")
|
||||
val module = parseModule(src)
|
||||
val mainBlock = module.statements.single() as Block
|
||||
val start = mainBlock.statements.single() as Subroutine
|
||||
val repeatbody = (start.statements.single() as RepeatLoop).body
|
||||
withClue("no vars moved to main block") {
|
||||
mainBlock.statements.any { it is VarDecl } shouldBe false
|
||||
}
|
||||
withClue("no vars moved to start sub") {
|
||||
start.statements.any { it is VarDecl } shouldBe false
|
||||
}
|
||||
withClue("\"var is still in repeat block (anonymousscope") {
|
||||
repeatbody.statements[0] shouldBe instanceOf<VarDecl>()
|
||||
}
|
||||
val initvalue = (repeatbody.statements[0] as VarDecl).value as? NumericLiteralValue
|
||||
initvalue?.number?.toInt() shouldBe 99
|
||||
repeatbody.statements[1] shouldBe instanceOf<PostIncrDecr>()
|
||||
// the ast processing steps used in the compiler, will eventually move the var up to the containing scope (subroutine).
|
||||
}
|
||||
|
||||
test("testLabelsWithAnonScopesParsesFine") {
|
||||
val src = SourceCode.Text("""
|
||||
main {
|
||||
sub start() {
|
||||
goto mylabeloutside
|
||||
|
||||
if true {
|
||||
if true {
|
||||
goto labeloutside
|
||||
goto iflabel
|
||||
}
|
||||
iflabel:
|
||||
}
|
||||
|
||||
repeat {
|
||||
goto labelinside
|
||||
labelinside:
|
||||
}
|
||||
|
||||
labeloutside:
|
||||
}
|
||||
}
|
||||
""")
|
||||
val module = parseModule(src)
|
||||
val mainBlock = module.statements.single() as Block
|
||||
val start = mainBlock.statements.single() as Subroutine
|
||||
val labels = start.statements.filterIsInstance<Label>()
|
||||
labels.size shouldBe 1
|
||||
}
|
||||
|
||||
test("logical operator 'not' priority") {
|
||||
val src = SourceCode.Text("""
|
||||
main {
|
||||
sub start() {
|
||||
ubyte xx
|
||||
xx = not 4 and not 5
|
||||
xx = not 4 or not 5
|
||||
xx = not 4 xor not 5
|
||||
}
|
||||
}
|
||||
""")
|
||||
val module = parseModule(src)
|
||||
val start = (module.statements.single() as Block).statements.single() as Subroutine
|
||||
val andAssignmentExpr = (start.statements[1] as Assignment).value
|
||||
val orAssignmentExpr = (start.statements[2] as Assignment).value
|
||||
val xorAssignmentExpr = (start.statements[3] as Assignment).value
|
||||
|
||||
fun correctPrios(expr: Expression, operator: String) {
|
||||
withClue("not should have higher prio as the other logical operators") {
|
||||
expr shouldBe instanceOf<BinaryExpression>()
|
||||
val binExpr = expr as BinaryExpression
|
||||
binExpr.operator shouldBe operator
|
||||
(binExpr.left as PrefixExpression).operator shouldBe "not"
|
||||
(binExpr.right as PrefixExpression).operator shouldBe "not"
|
||||
}
|
||||
}
|
||||
|
||||
correctPrios(andAssignmentExpr, "and")
|
||||
correctPrios(orAssignmentExpr, "or")
|
||||
correctPrios(xorAssignmentExpr, "xor")
|
||||
}
|
||||
|
||||
test("inferred type correct for binaryexpression") {
|
||||
val src = SourceCode.Text("""
|
||||
main {
|
||||
ubyte bb
|
||||
uword ww
|
||||
ubyte bb2 = not bb or not ww ; expression combining ubyte and uword
|
||||
}
|
||||
""")
|
||||
val module = parseModule(src)
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
program.addModule(module)
|
||||
val bb2 = (module.statements.single() as Block).statements[2] as VarDecl
|
||||
val expr = bb2.value as BinaryExpression
|
||||
println(expr)
|
||||
expr.operator shouldBe "or"
|
||||
expr.left.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
|
||||
expr.right.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UWORD
|
||||
expr.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
|
||||
}
|
||||
|
||||
test("inferred type for typecasted expressions with logical operators") {
|
||||
val src=SourceCode.Text("""
|
||||
main {
|
||||
ubyte bb
|
||||
uword ww
|
||||
uword qq = (not bb as uword)
|
||||
uword zz = not bb or not ww
|
||||
ubyte bb2 = not bb or not ww
|
||||
uword zz2 = (not bb as uword) or not ww
|
||||
}
|
||||
""")
|
||||
val module = parseModule(src)
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
program.addModule(module)
|
||||
val stmts = (module.statements.single() as Block).statements
|
||||
stmts.size shouldBe 6
|
||||
val qq = (stmts[2] as VarDecl).value as TypecastExpression
|
||||
val zz = (stmts[3] as VarDecl).value as BinaryExpression
|
||||
val bb2 = (stmts[4] as VarDecl).value as BinaryExpression
|
||||
val zz2 = (stmts[5] as VarDecl).value as BinaryExpression
|
||||
qq.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UWORD
|
||||
zz.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
|
||||
bb2.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
|
||||
|
||||
zz2.operator shouldBe "or"
|
||||
val left = zz2.left as TypecastExpression
|
||||
val right = zz2.right as PrefixExpression
|
||||
left.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UWORD
|
||||
right.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UWORD
|
||||
zz2.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UBYTE // 'or' causes UBYTE result
|
||||
}
|
||||
|
||||
test("type cast from byte to ubyte as desired target type") {
|
||||
val src = SourceCode.Text("""
|
||||
main {
|
||||
ubyte r
|
||||
ubyte ub = (cos8(r)/2 + 100) as ubyte
|
||||
}""")
|
||||
val module = parseModule(src)
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
program.addModule(module)
|
||||
val stmts = (module.statements.single() as Block).statements
|
||||
stmts.size shouldBe 2
|
||||
val ubexpr = (stmts[1] as VarDecl).value as TypecastExpression
|
||||
ubexpr.inferType(program).getOrElse { fail("dt") } shouldBe DataType.UBYTE
|
||||
}
|
||||
|
||||
|
||||
test("assignment isAugmented correctness") {
|
||||
val src = SourceCode.Text("""
|
||||
main {
|
||||
sub start() {
|
||||
ubyte r
|
||||
ubyte q
|
||||
r = q*3 ; #1 no
|
||||
r = r*3 ; #2 yes
|
||||
r = 3*r ; #3 yes
|
||||
r = 3*q ; #4 no
|
||||
r = 5+r ; #5 yes
|
||||
r = 5-r ; #6 no
|
||||
r = r-5 ; #7 yes
|
||||
r = not r ; #8 yes
|
||||
r = not q ; #9 no
|
||||
r = (q+r)+5 ; #10 yes
|
||||
r = q+(r+5) ; #11 yes
|
||||
r = (q+r)-5 ; #12 yes
|
||||
r = q+(r-5) ; #13 yes
|
||||
}
|
||||
}""")
|
||||
|
||||
val module = parseModule(src)
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
program.addModule(module)
|
||||
val stmts = program.entrypoint.statements
|
||||
val expectedResults = listOf(
|
||||
false, true, true,
|
||||
false, true, false,
|
||||
true, true, false,
|
||||
true, true, true,
|
||||
true
|
||||
)
|
||||
stmts.size shouldBe 15
|
||||
expectedResults.size shouldBe stmts.size-2
|
||||
for((idx, pp) in stmts.drop(2).zip(expectedResults).withIndex()) {
|
||||
val assign = pp.first as Assignment
|
||||
val expected = pp.second
|
||||
withClue("#${idx+1}: should${if(expected) "" else "n't"} be augmentable: $assign") {
|
||||
assign.isAugmentable shouldBe expected
|
||||
assign.value shouldBe (instanceOf<PrefixExpression>() or instanceOf<BinaryExpression>() or instanceOf<TypecastExpression>())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test("test string x and u escape sequences") {
|
||||
val text="""
|
||||
main {
|
||||
sub start() {
|
||||
str string = "\x00\xff\u0041"
|
||||
ubyte zero = '\x00'
|
||||
ubyte ff = '\xff'
|
||||
ubyte letter = '\u0041'
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, false, text, writeAssembly = false).assertSuccess()
|
||||
val start = result.program.entrypoint
|
||||
val string = (start.statements[0] as VarDecl).value as StringLiteralValue
|
||||
withClue("x-escapes are hacked to range 0x8000-0x80ff") {
|
||||
string.value[0].code shouldBe 0x8000
|
||||
string.value[1].code shouldBe 0x80ff
|
||||
}
|
||||
string.value[2].code shouldBe 65
|
||||
val zero = start.statements[2] as Assignment
|
||||
zero.value shouldBe NumericLiteralValue(DataType.UBYTE, 0.0, Position.DUMMY)
|
||||
val ff = start.statements[4] as Assignment
|
||||
ff.value shouldBe NumericLiteralValue(DataType.UBYTE, 255.0, Position.DUMMY)
|
||||
val letter = start.statements[6] as Assignment
|
||||
// TODO characters should not be encoded until code generation, like strings...
|
||||
val encodedletter = Petscii.encodePetscii("A", true).getOrElse { fail("petscii error") }.single()
|
||||
letter.value shouldBe NumericLiteralValue(DataType.UBYTE, encodedletter.toDouble(), Position.DUMMY)
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,109 @@
|
||||
package prog8tests.ast
|
||||
|
||||
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 io.kotest.matchers.types.shouldBeSameInstanceAs
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.internedStringsModuleName
|
||||
import prog8.parser.SourceCode
|
||||
import prog8tests.helpers.DummyFunctions
|
||||
import prog8tests.helpers.DummyMemsizer
|
||||
import prog8tests.helpers.DummyStringEncoder
|
||||
|
||||
class TestProgram: FunSpec({
|
||||
|
||||
context("Constructor") {
|
||||
test("withNameBuiltinsAndMemsizer") {
|
||||
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
program.modules.size shouldBe 1
|
||||
program.modules[0].name shouldBe internedStringsModuleName
|
||||
program.modules[0].program shouldBeSameInstanceAs program
|
||||
program.modules[0].parent shouldBeSameInstanceAs program.namespace
|
||||
}
|
||||
}
|
||||
|
||||
context("AddModule") {
|
||||
test("withEmptyModule") {
|
||||
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
val m1 = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("bar"))
|
||||
|
||||
val retVal = program.addModule(m1)
|
||||
|
||||
retVal shouldBeSameInstanceAs program
|
||||
program.modules.size shouldBe 2
|
||||
m1 shouldBeIn program.modules
|
||||
m1.program shouldBeSameInstanceAs program
|
||||
m1.parent shouldBeSameInstanceAs program.namespace
|
||||
|
||||
withClue("module may not occur multiple times") {
|
||||
val ex = shouldThrow<IllegalArgumentException> { program.addModule(m1) }
|
||||
ex.message shouldContain m1.name
|
||||
}
|
||||
|
||||
val m2 = Module(mutableListOf(), m1.position, m1.source)
|
||||
withClue("other module but with same name may not occur multiple times") {
|
||||
val ex = shouldThrow<IllegalArgumentException> { program.addModule(m2) }
|
||||
ex.message shouldContain m1.name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context("MoveModuleToFront") {
|
||||
test("withInternedStringsModule") {
|
||||
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
val m = program.modules[0]
|
||||
m.name shouldBe internedStringsModuleName
|
||||
|
||||
val retVal = program.moveModuleToFront(m)
|
||||
retVal shouldBeSameInstanceAs program
|
||||
program.modules[0] shouldBeSameInstanceAs m
|
||||
}
|
||||
|
||||
test("withForeignModule") {
|
||||
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
val m = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("bar"))
|
||||
|
||||
shouldThrow<IllegalArgumentException> { program.moveModuleToFront(m) }
|
||||
}
|
||||
|
||||
test("withFirstOfPreviouslyAddedModules") {
|
||||
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
val m1 = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("bar"))
|
||||
val m2 = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("qmbl"))
|
||||
program.addModule(m1)
|
||||
program.addModule(m2)
|
||||
|
||||
val retVal = program.moveModuleToFront(m1)
|
||||
retVal shouldBeSameInstanceAs program
|
||||
program.modules.indexOf(m1) shouldBe 0
|
||||
}
|
||||
|
||||
test("withSecondOfPreviouslyAddedModules") {
|
||||
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
val m1 = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("bar"))
|
||||
val m2 = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated("qmbl"))
|
||||
program.addModule(m1)
|
||||
program.addModule(m2)
|
||||
|
||||
val retVal = program.moveModuleToFront(m2)
|
||||
retVal shouldBeSameInstanceAs program
|
||||
program.modules.indexOf(m2) shouldBe 0
|
||||
}
|
||||
}
|
||||
|
||||
context("Properties") {
|
||||
test("modules") {
|
||||
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
|
||||
val ms1 = program.modules
|
||||
val ms2 = program.modules
|
||||
ms2 shouldBeSameInstanceAs ms1
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,21 +1,19 @@
|
||||
package prog8tests.ast
|
||||
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.core.StringStartsWith
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.core.spec.style.AnnotationSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import prog8.parser.SourceCode
|
||||
import prog8.parser.SourceCode.Companion.libraryFilePrefix
|
||||
import prog8tests.ast.helpers.assumeNotExists
|
||||
import prog8tests.ast.helpers.assumeReadableFile
|
||||
import prog8tests.ast.helpers.fixturesDir
|
||||
import prog8tests.ast.helpers.resourcesDir
|
||||
import prog8tests.helpers.assumeNotExists
|
||||
import prog8tests.helpers.assumeReadableFile
|
||||
import prog8tests.helpers.fixturesDir
|
||||
import prog8tests.helpers.resourcesDir
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.test.*
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestSourceCode {
|
||||
class TestSourceCode: AnnotationSpec() {
|
||||
|
||||
@Test
|
||||
fun testFromString() {
|
||||
@@ -25,53 +23,53 @@ class TestSourceCode {
|
||||
val src = SourceCode.Text(text)
|
||||
val actualText = src.getCharStream().toString()
|
||||
|
||||
assertContains(src.origin, Regex("^<String@[0-9a-f\\-]+>$"))
|
||||
assertEquals(text, actualText)
|
||||
assertFalse(src.isFromResources)
|
||||
assertFalse(src.isFromFilesystem)
|
||||
assertThat(src.toString(), StringStartsWith("prog8.parser.SourceCode"))
|
||||
src.origin shouldContain Regex("^<String@[0-9a-f\\-]+>$")
|
||||
actualText shouldBe text
|
||||
src.isFromResources shouldBe false
|
||||
src.isFromFilesystem shouldBe false
|
||||
src.toString().startsWith("prog8.parser.SourceCode") shouldBe true
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFromPathWithNonExistingPath() {
|
||||
val filename = "i_do_not_exist.p8"
|
||||
val path = assumeNotExists(fixturesDir, filename)
|
||||
assertFailsWith<NoSuchFileException> { SourceCode.File(path) }
|
||||
shouldThrow<NoSuchFileException> { SourceCode.File(path) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFromPathWithMissingExtension_p8() {
|
||||
val pathWithoutExt = assumeNotExists(fixturesDir,"simple_main")
|
||||
assumeReadableFile(fixturesDir,"simple_main.p8")
|
||||
assertFailsWith<NoSuchFileException> { SourceCode.File(pathWithoutExt) }
|
||||
assumeReadableFile(fixturesDir,"ast_simple_main.p8")
|
||||
shouldThrow<NoSuchFileException> { SourceCode.File(pathWithoutExt) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFromPathWithDirectory() {
|
||||
assertFailsWith<AccessDeniedException> { SourceCode.File(fixturesDir) }
|
||||
shouldThrow<AccessDeniedException> { SourceCode.File(fixturesDir) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFromPathWithExistingPath() {
|
||||
val filename = "simple_main.p8"
|
||||
val filename = "ast_simple_main.p8"
|
||||
val path = assumeReadableFile(fixturesDir, filename)
|
||||
val src = SourceCode.File(path)
|
||||
val expectedOrigin = SourceCode.relative(path).toString()
|
||||
assertEquals(expectedOrigin, src.origin)
|
||||
assertEquals(path.toFile().readText(), src.readText())
|
||||
assertFalse(src.isFromResources)
|
||||
assertTrue(src.isFromFilesystem)
|
||||
src.origin shouldBe expectedOrigin
|
||||
src.readText() shouldBe path.toFile().readText()
|
||||
src.isFromResources shouldBe false
|
||||
src.isFromFilesystem shouldBe true
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFromPathWithExistingNonNormalizedPath() {
|
||||
val filename = "simple_main.p8"
|
||||
val filename = "ast_simple_main.p8"
|
||||
val path = Path(".", "test", "..", "test", "fixtures", filename)
|
||||
val srcFile = assumeReadableFile(path).toFile()
|
||||
val src = SourceCode.File(path)
|
||||
val expectedOrigin = SourceCode.relative(path).toString()
|
||||
assertEquals(expectedOrigin, src.origin)
|
||||
assertEquals(srcFile.readText(), src.readText())
|
||||
src.origin shouldBe expectedOrigin
|
||||
src.readText() shouldBe srcFile.readText()
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -80,10 +78,10 @@ class TestSourceCode {
|
||||
val srcFile = assumeReadableFile(resourcesDir, pathString).toFile()
|
||||
val src = SourceCode.Resource(pathString)
|
||||
|
||||
assertEquals("$libraryFilePrefix/$pathString", src.origin)
|
||||
assertEquals(srcFile.readText(), src.readText())
|
||||
assertTrue(src.isFromResources)
|
||||
assertFalse(src.isFromFilesystem)
|
||||
src.origin shouldBe "$libraryFilePrefix/$pathString"
|
||||
src.readText() shouldBe srcFile.readText()
|
||||
src.isFromResources shouldBe true
|
||||
src.isFromFilesystem shouldBe false
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -92,8 +90,8 @@ class TestSourceCode {
|
||||
val srcFile = assumeReadableFile(resourcesDir, pathString.substring(1)).toFile()
|
||||
val src = SourceCode.Resource(pathString)
|
||||
|
||||
assertEquals("$libraryFilePrefix$pathString", src.origin)
|
||||
assertEquals(srcFile.readText(), src.readText())
|
||||
src.origin shouldBe "$libraryFilePrefix$pathString"
|
||||
src.readText() shouldBe srcFile.readText()
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -102,9 +100,9 @@ class TestSourceCode {
|
||||
val srcFile = assumeReadableFile(resourcesDir, pathString).toFile()
|
||||
val src = SourceCode.Resource(pathString)
|
||||
|
||||
assertEquals("$libraryFilePrefix/$pathString", src.origin)
|
||||
assertEquals(srcFile.readText(), src.readText())
|
||||
assertTrue(src.isFromResources, ".isFromResources")
|
||||
src.origin shouldBe "$libraryFilePrefix/$pathString"
|
||||
src.readText() shouldBe srcFile.readText()
|
||||
src.isFromResources shouldBe true
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -113,8 +111,8 @@ class TestSourceCode {
|
||||
val srcFile = assumeReadableFile(resourcesDir, pathString.substring(1)).toFile()
|
||||
val src = SourceCode.Resource(pathString)
|
||||
|
||||
assertEquals("$libraryFilePrefix$pathString", src.origin)
|
||||
assertEquals(srcFile.readText(), src.readText())
|
||||
src.origin shouldBe "$libraryFilePrefix$pathString"
|
||||
src.readText() shouldBe srcFile.readText()
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -123,9 +121,9 @@ class TestSourceCode {
|
||||
val srcFile = assumeReadableFile(resourcesDir, pathString.substring(1)).toFile()
|
||||
val src = SourceCode.Resource(pathString)
|
||||
|
||||
assertEquals("$libraryFilePrefix/prog8lib/math.p8", src.origin)
|
||||
assertEquals(srcFile.readText(), src.readText())
|
||||
assertTrue(src.isFromResources, ".isFromResources")
|
||||
src.origin shouldBe "$libraryFilePrefix/prog8lib/math.p8"
|
||||
src.readText() shouldBe srcFile.readText()
|
||||
src.isFromResources shouldBe true
|
||||
}
|
||||
|
||||
|
||||
@@ -134,13 +132,13 @@ class TestSourceCode {
|
||||
val pathString = "/prog8lib/i_do_not_exist"
|
||||
assumeNotExists(resourcesDir, pathString.substring(1))
|
||||
|
||||
assertFailsWith<NoSuchFileException> { SourceCode.Resource(pathString) }
|
||||
shouldThrow<NoSuchFileException> { SourceCode.Resource(pathString) }
|
||||
}
|
||||
@Test
|
||||
fun testFromResourcesWithNonExistingFile_withoutLeadingSlash() {
|
||||
val pathString = "prog8lib/i_do_not_exist"
|
||||
assumeNotExists(resourcesDir, pathString)
|
||||
|
||||
assertFailsWith<NoSuchFileException> { SourceCode.Resource(pathString) }
|
||||
shouldThrow<NoSuchFileException> { SourceCode.Resource(pathString) }
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,15 @@
|
||||
package prog8tests.ast
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import io.kotest.core.spec.style.AnnotationSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.statements.Block
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.parser.Prog8Parser.parseModule
|
||||
import prog8.parser.SourceCode
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestSubroutines {
|
||||
class TestSubroutines: AnnotationSpec() {
|
||||
|
||||
@Test
|
||||
fun stringParameterAcceptedInParser() {
|
||||
@@ -33,12 +29,12 @@ class TestSubroutines {
|
||||
val mainBlock = module.statements.single() as Block
|
||||
val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"}
|
||||
val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"}
|
||||
assertTrue(asmfunc.isAsmSubroutine)
|
||||
assertEquals(DataType.STR, asmfunc.parameters.single().type)
|
||||
assertTrue(asmfunc.statements.isEmpty())
|
||||
assertFalse(func.isAsmSubroutine)
|
||||
assertEquals(DataType.STR, func.parameters.single().type)
|
||||
assertTrue(func.statements.isEmpty())
|
||||
asmfunc.isAsmSubroutine shouldBe true
|
||||
asmfunc.parameters.single().type shouldBe DataType.STR
|
||||
asmfunc.statements.isEmpty() shouldBe true
|
||||
func.isAsmSubroutine shouldBe false
|
||||
func.parameters.single().type shouldBe DataType.STR
|
||||
func.statements.isEmpty() shouldBe true
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -59,11 +55,11 @@ class TestSubroutines {
|
||||
val mainBlock = module.statements.single() as Block
|
||||
val asmfunc = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="asmfunc"}
|
||||
val func = mainBlock.statements.filterIsInstance<Subroutine>().single { it.name=="func"}
|
||||
assertTrue(asmfunc.isAsmSubroutine)
|
||||
assertEquals(DataType.ARRAY_UB, asmfunc.parameters.single().type)
|
||||
assertTrue(asmfunc.statements.isEmpty())
|
||||
assertFalse(func.isAsmSubroutine)
|
||||
assertEquals(DataType.ARRAY_UB, func.parameters.single().type)
|
||||
assertTrue(func.statements.isEmpty())
|
||||
asmfunc.isAsmSubroutine shouldBe true
|
||||
asmfunc.parameters.single().type shouldBe DataType.ARRAY_UB
|
||||
asmfunc.statements.isEmpty() shouldBe true
|
||||
func.isAsmSubroutine shouldBe false
|
||||
func.parameters.single().type shouldBe DataType.ARRAY_UB
|
||||
func.statements.isEmpty() shouldBe true
|
||||
}
|
||||
}
|
||||
+81
-60
@@ -1,46 +1,39 @@
|
||||
package prog8tests.asmgen
|
||||
package prog8tests.codegeneration
|
||||
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.Matchers.equalTo
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import io.kotest.assertions.withClue
|
||||
import io.kotest.core.spec.style.StringSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.base.RegisterOrPair
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.AddressOf
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.c64.C64MachineDefinition
|
||||
import prog8.compiler.target.c64.C64Zeropage
|
||||
import prog8.compiler.target.cpu6502.codegen.AsmGen
|
||||
import prog8.compilerinterface.*
|
||||
import prog8.parser.SourceCode
|
||||
import prog8tests.asmgen.helpers.DummyFunctions
|
||||
import prog8tests.asmgen.helpers.DummyMemsizer
|
||||
import prog8tests.asmgen.helpers.DummyStringEncoder
|
||||
import prog8tests.asmgen.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.DummyFunctions
|
||||
import prog8tests.helpers.DummyMemsizer
|
||||
import prog8tests.helpers.DummyStringEncoder
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestAsmGen6502 {
|
||||
|
||||
private fun createTestProgram(): Program {
|
||||
class AsmGenSymbolsTests: StringSpec({
|
||||
fun createTestProgram(): Program {
|
||||
/*
|
||||
main {
|
||||
main {
|
||||
|
||||
label_outside:
|
||||
label_outside:
|
||||
uword var_outside
|
||||
|
||||
sub start () {
|
||||
uword localvar = 1234
|
||||
uword tgt
|
||||
|
||||
locallabel:
|
||||
locallabel:
|
||||
tgt = localvar
|
||||
tgt = &locallabel
|
||||
tgt = &var_outside
|
||||
@@ -50,11 +43,11 @@ locallabel:
|
||||
tgt = &main.var_outside
|
||||
tgt = &main.label_outside
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
val varInSub = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "localvar", NumericLiteralValue.optimalInteger(1234, Position.DUMMY), false, false, false, Position.DUMMY)
|
||||
val var2InSub = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "tgt", null, false, false, false, Position.DUMMY)
|
||||
val varInSub = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "localvar", NumericLiteralValue.optimalInteger(1234, Position.DUMMY), false, false, false, null, Position.DUMMY)
|
||||
val var2InSub = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "tgt", null, false, false, false, null, Position.DUMMY)
|
||||
val labelInSub = Label("locallabel", Position.DUMMY)
|
||||
|
||||
val tgt = AssignTarget(IdentifierReference(listOf("tgt"), Position.DUMMY), null, null, Position.DUMMY)
|
||||
@@ -70,82 +63,110 @@ locallabel:
|
||||
val statements = mutableListOf(varInSub, var2InSub, labelInSub, assign1, assign2, assign3, assign4, assign5, assign6, assign7, assign8)
|
||||
val subroutine = Subroutine("start", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, statements, Position.DUMMY)
|
||||
val labelInBlock = Label("label_outside", Position.DUMMY)
|
||||
val varInBlock = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "var_outside", null, false, false, false, Position.DUMMY)
|
||||
val varInBlock = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "var_outside", null, false, false, false, null, Position.DUMMY)
|
||||
val block = Block("main", null, mutableListOf(labelInBlock, varInBlock, subroutine), false, Position.DUMMY)
|
||||
|
||||
val module = Module(mutableListOf(block), Position.DUMMY, SourceCode.Generated("test"))
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
.addModule(module)
|
||||
module.linkIntoProgram(program)
|
||||
return program
|
||||
}
|
||||
|
||||
private fun createTestAsmGen(program: Program): AsmGen {
|
||||
fun createTestAsmGen(program: Program): AsmGen {
|
||||
val errors = ErrorReporterForTests()
|
||||
val options = CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, true, C64Target)
|
||||
val zp = C64MachineDefinition.C64Zeropage(options)
|
||||
val zp = C64Zeropage(options)
|
||||
val asmgen = AsmGen(program, errors, zp, options, C64Target, Path.of(""))
|
||||
return asmgen
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSymbolNameFromStrings() {
|
||||
"symbol and variable names from strings" {
|
||||
val program = createTestProgram()
|
||||
val asmgen = createTestAsmGen(program)
|
||||
|
||||
assertThat(asmgen.asmSymbolName("name"), equalTo("name"))
|
||||
assertThat(asmgen.asmSymbolName("<name>"), equalTo("prog8_name"))
|
||||
assertThat(asmgen.asmSymbolName(RegisterOrPair.R15), equalTo("cx16.r15"))
|
||||
assertThat(asmgen.asmSymbolName(listOf("a", "b", "name")), equalTo("a.b.name"))
|
||||
assertThat(asmgen.asmVariableName("name"), equalTo("name"))
|
||||
assertThat(asmgen.asmVariableName("<name>"), equalTo("prog8_name"))
|
||||
assertThat(asmgen.asmVariableName(listOf("a", "b", "name")), equalTo("a.b.name"))
|
||||
asmgen.asmSymbolName("name") shouldBe "name"
|
||||
asmgen.asmSymbolName("name") shouldBe "name"
|
||||
asmgen.asmSymbolName("<name>") shouldBe "prog8_name"
|
||||
asmgen.asmSymbolName(RegisterOrPair.R15) shouldBe "cx16.r15"
|
||||
asmgen.asmSymbolName(listOf("a", "b", "name")) shouldBe "a.b.name"
|
||||
asmgen.asmVariableName("name") shouldBe "name"
|
||||
asmgen.asmVariableName("<name>") shouldBe "prog8_name"
|
||||
asmgen.asmVariableName(listOf("a", "b", "name")) shouldBe "a.b.name"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSymbolNameFromVarIdentifier() {
|
||||
"symbol and variable names from variable identifiers" {
|
||||
val program = createTestProgram()
|
||||
val asmgen = createTestAsmGen(program)
|
||||
val sub = program.entrypoint
|
||||
|
||||
// local variable
|
||||
val localvarIdent = sub.statements.filterIsInstance<Assignment>().first { it.value is IdentifierReference }.value as IdentifierReference
|
||||
assertThat(asmgen.asmSymbolName(localvarIdent), equalTo("localvar"))
|
||||
assertThat(asmgen.asmVariableName(localvarIdent), equalTo("localvar"))
|
||||
asmgen.asmSymbolName(localvarIdent) shouldBe "localvar"
|
||||
asmgen.asmVariableName(localvarIdent) shouldBe "localvar"
|
||||
val localvarIdentScoped = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("main", "start", "localvar") }.value as AddressOf).identifier
|
||||
assertThat(asmgen.asmSymbolName(localvarIdentScoped), equalTo("main.start.localvar"))
|
||||
assertThat(asmgen.asmVariableName(localvarIdentScoped), equalTo("main.start.localvar"))
|
||||
asmgen.asmSymbolName(localvarIdentScoped) shouldBe "main.start.localvar"
|
||||
asmgen.asmVariableName(localvarIdentScoped) shouldBe "main.start.localvar"
|
||||
|
||||
// variable from outer scope (note that for Variables, no scoping prefix symbols are required,
|
||||
// because they're not outputted as locally scoped symbols for the assembler
|
||||
val scopedVarIdent = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("var_outside") }.value as AddressOf).identifier
|
||||
assertThat(asmgen.asmSymbolName(scopedVarIdent), equalTo("main.var_outside"))
|
||||
assertThat(asmgen.asmVariableName(scopedVarIdent), equalTo("var_outside"))
|
||||
asmgen.asmSymbolName(scopedVarIdent) shouldBe "main.var_outside"
|
||||
asmgen.asmVariableName(scopedVarIdent) shouldBe "var_outside"
|
||||
val scopedVarIdentScoped = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("main", "var_outside") }.value as AddressOf).identifier
|
||||
assertThat(asmgen.asmSymbolName(scopedVarIdentScoped), equalTo("main.var_outside"))
|
||||
assertThat(asmgen.asmVariableName(scopedVarIdentScoped), equalTo("main.var_outside"))
|
||||
asmgen.asmSymbolName(scopedVarIdentScoped) shouldBe "main.var_outside"
|
||||
asmgen.asmVariableName(scopedVarIdentScoped) shouldBe "main.var_outside"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSymbolNameFromLabelIdentifier() {
|
||||
"symbol and variable names from label identifiers" {
|
||||
val program = createTestProgram()
|
||||
val asmgen = createTestAsmGen(program)
|
||||
val sub = program.entrypoint
|
||||
|
||||
// local label
|
||||
val localLabelIdent = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("locallabel") }.value as AddressOf).identifier
|
||||
assertThat(asmgen.asmSymbolName(localLabelIdent), equalTo("_locallabel"))
|
||||
assertThat("as a variable it uses different naming rules (no underscore prefix)", asmgen.asmVariableName(localLabelIdent), equalTo("locallabel"))
|
||||
asmgen.asmSymbolName(localLabelIdent) shouldBe "_locallabel"
|
||||
withClue("as a variable it uses different naming rules (no underscore prefix)") {
|
||||
asmgen.asmVariableName(localLabelIdent) shouldBe "locallabel"
|
||||
}
|
||||
val localLabelIdentScoped = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("main","start","locallabel") }.value as AddressOf).identifier
|
||||
assertThat(asmgen.asmSymbolName(localLabelIdentScoped), equalTo("main.start._locallabel"))
|
||||
assertThat("as a variable it uses different naming rules (no underscore prefix)", asmgen.asmVariableName(localLabelIdentScoped), equalTo("main.start.locallabel"))
|
||||
asmgen.asmSymbolName(localLabelIdentScoped) shouldBe "main.start._locallabel"
|
||||
withClue("as a variable it uses different naming rules (no underscore prefix)") {
|
||||
asmgen.asmVariableName(localLabelIdentScoped) shouldBe "main.start.locallabel"
|
||||
}
|
||||
|
||||
// label from outer scope needs sope prefixes because it is outputted as a locally scoped symbol for the assembler
|
||||
val scopedLabelIdent = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("label_outside") }.value as AddressOf).identifier
|
||||
assertThat(asmgen.asmSymbolName(scopedLabelIdent), equalTo("main._label_outside"))
|
||||
assertThat("as a variable it uses different naming rules (no underscore prefix)", asmgen.asmVariableName(scopedLabelIdent), equalTo("label_outside"))
|
||||
asmgen.asmSymbolName(scopedLabelIdent) shouldBe "main._label_outside"
|
||||
withClue("as a variable it uses different naming rules (no underscore prefix)") {
|
||||
asmgen.asmVariableName(scopedLabelIdent) shouldBe "label_outside"
|
||||
}
|
||||
val scopedLabelIdentScoped = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("main","label_outside") }.value as AddressOf).identifier
|
||||
assertThat(asmgen.asmSymbolName(scopedLabelIdentScoped), equalTo("main._label_outside"))
|
||||
assertThat("as a variable it uses different naming rules (no underscore prefix)", asmgen.asmVariableName(scopedLabelIdentScoped), equalTo("main.label_outside"))
|
||||
asmgen.asmSymbolName(scopedLabelIdentScoped) shouldBe "main._label_outside"
|
||||
withClue("as a variable it uses different naming rules (no underscore prefix)") {
|
||||
asmgen.asmVariableName(scopedLabelIdentScoped) shouldBe "main.label_outside"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"asm names for hooks to zp temp vars" {
|
||||
/*
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
prog8_lib.P8ZP_SCRATCH_REG = 1
|
||||
prog8_lib.P8ZP_SCRATCH_B1 = 1
|
||||
prog8_lib.P8ZP_SCRATCH_W1 = 1
|
||||
prog8_lib.P8ZP_SCRATCH_W2 = 1
|
||||
*/
|
||||
val program = createTestProgram()
|
||||
val asmgen = createTestAsmGen(program)
|
||||
asmgen.asmSymbolName("prog8_lib.P8ZP_SCRATCH_REG") shouldBe "P8ZP_SCRATCH_REG"
|
||||
asmgen.asmSymbolName("prog8_lib.P8ZP_SCRATCH_W2") shouldBe "P8ZP_SCRATCH_W2"
|
||||
asmgen.asmSymbolName(listOf("prog8_lib","P8ZP_SCRATCH_REG")) shouldBe "P8ZP_SCRATCH_REG"
|
||||
asmgen.asmSymbolName(listOf("prog8_lib","P8ZP_SCRATCH_W2")) shouldBe "P8ZP_SCRATCH_W2"
|
||||
val id1 = IdentifierReference(listOf("prog8_lib","P8ZP_SCRATCH_REG"), Position.DUMMY)
|
||||
id1.linkParents(program.toplevelModule)
|
||||
val id2 = IdentifierReference(listOf("prog8_lib","P8ZP_SCRATCH_W2"), Position.DUMMY)
|
||||
id2.linkParents(program.toplevelModule)
|
||||
asmgen.asmSymbolName(id1) shouldBe "P8ZP_SCRATCH_REG"
|
||||
asmgen.asmSymbolName(id2) shouldBe "P8ZP_SCRATCH_W2"
|
||||
}
|
||||
})
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -22,15 +22,23 @@ internal val DummyFunctions = object : IBuiltinFunctions {
|
||||
}
|
||||
|
||||
internal val DummyMemsizer = object : IMemSizer {
|
||||
override fun memorySize(dt: DataType): Int = 0
|
||||
override fun memorySize(dt: DataType) = 0
|
||||
}
|
||||
|
||||
internal val DummyStringEncoder = object : IStringEncoding {
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String {
|
||||
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean): String {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
internal val AsciiStringEncoder = object : IStringEncoding {
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> = str.map { it.code.toUByte() }
|
||||
|
||||
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean): String {
|
||||
return bytes.joinToString()
|
||||
}
|
||||
}
|
||||
Loaded 100 of 162 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user