mirror of
https://github.com/irmen/prog8.git
synced 2025-06-20 21:23:54 +00:00
Compare commits
203 Commits
version7.1
...
v7.4
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
793596614e | |||
136280100c | |||
29f1e4d2c9 | |||
72a7e61fd0 | |||
381cfca67f | |||
f40620aa25 | |||
57a9fed42b | |||
18d820da94 | |||
26e66f046f | |||
4270c04856 | |||
74456d1135 | |||
62dc824bc0 | |||
1605791f1b | |||
37a46aa2cf | |||
1d2d217b94 | |||
23961f695d | |||
730b208617 | |||
f09c04eeac | |||
be73739c62 | |||
eea3fb48a8 | |||
b4fa72c058 | |||
b0a865b0f1 | |||
7f49731618 | |||
3410aea788 | |||
bc0a133bb1 | |||
7e287a5359 | |||
1110bd0851 | |||
1b576f826d | |||
fe17566370 | |||
e3c00669c1 | |||
33d17afc32 | |||
2388359a99 | |||
2df0c9503c | |||
61fa3bc77c | |||
03ac9b6956 | |||
dfbef8495d | |||
7b17c49d8f | |||
4b3f31c2ee | |||
9ccc65bf8f | |||
f9e22add03 | |||
846951cda7 | |||
97836e18b2 | |||
7b69df4db2 | |||
3767b4bbe7 | |||
d7d2eefa4f | |||
6737f28d1e | |||
3da9404c2d | |||
4d5bd0fa32 | |||
1137da37c3 | |||
495a18805c | |||
a226b82d0b | |||
0b5ddcdc9b | |||
82da8f4946 | |||
5ff481ce3c | |||
f21dcaa6fb | |||
2c940de598 | |||
ce75b776bb | |||
7d22b9b9f9 | |||
6cb8b3b5cd | |||
2bf4017f2b | |||
08d2f8568b | |||
ac5f45d2d4 | |||
3cc7ad7d20 | |||
d4513364fb | |||
9684f4e42a | |||
f4186981fd | |||
141689e697 | |||
743c8b44a2 | |||
5e1459564a | |||
69a8813a3d | |||
17175df835 | |||
6b32535cb6 | |||
2815a14bb5 | |||
f4dfa60790 | |||
35e88dd529 | |||
4d5094a517 | |||
dd5abae721 | |||
8f2fb20934 | |||
74555a32ed | |||
1a111b706e | |||
4668932bac | |||
e6c41eac93 | |||
14aad2358f |
6
.idea/kotlinc.xml
generated
6
.idea/kotlinc.xml
generated
@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="Kotlin2JvmCompilerArguments">
|
|
||||||
<option name="jvmTarget" value="11" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
22
.idea/libraries/github_hypfvieh_dbus_java.xml
generated
22
.idea/libraries/github_hypfvieh_dbus_java.xml
generated
@ -1,22 +1,22 @@
|
|||||||
<component name="libraryTable">
|
<component name="libraryTable">
|
||||||
<library name="github.hypfvieh.dbus.java" type="repository">
|
<library name="github.hypfvieh.dbus.java" type="repository">
|
||||||
<properties maven-id="com.github.hypfvieh:dbus-java:3.3.0" />
|
<properties maven-id="com.github.hypfvieh:dbus-java:3.3.1" />
|
||||||
<CLASSES>
|
<CLASSES>
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/hypfvieh/dbus-java/3.3.0/dbus-java-3.3.0.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/com/github/hypfvieh/dbus-java/3.3.1/dbus-java-3.3.1.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-unixsocket/0.38.5/jnr-unixsocket-0.38.5.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-unixsocket/0.38.6/jnr-unixsocket-0.38.6.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-ffi/2.2.1/jnr-ffi-2.2.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-ffi/2.2.2/jnr-ffi-2.2.2.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jffi/1.3.1/jffi-1.3.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jffi/1.3.1/jffi-1.3.1.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jffi/1.3.1/jffi-1.3.1-native.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jffi/1.3.1/jffi-1.3.1-native.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm/9.0/asm-9.0.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm/9.1/asm-9.1.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-commons/9.0/asm-commons-9.0.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-commons/9.1/asm-commons-9.1.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-analysis/9.0/asm-analysis-9.0.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-analysis/9.1/asm-analysis-9.1.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-tree/9.0/asm-tree-9.0.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-tree/9.1/asm-tree-9.1.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-util/9.0/asm-util-9.0.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-util/9.1/asm-util-9.1.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-a64asm/1.0.0/jnr-a64asm-1.0.0.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-a64asm/1.0.0/jnr-a64asm-1.0.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-x86asm/1.0.2/jnr-x86asm-1.0.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-x86asm/1.0.2/jnr-x86asm-1.0.2.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-constants/0.10.1/jnr-constants-0.10.1.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-constants/0.10.1/jnr-constants-0.10.1.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-enxio/0.32.3/jnr-enxio-0.32.3.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-enxio/0.32.4/jnr-enxio-0.32.4.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-posix/3.1.4/jnr-posix-3.1.4.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-posix/3.1.5/jnr-posix-3.1.5.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30.jar!/" />
|
||||||
</CLASSES>
|
</CLASSES>
|
||||||
<JAVADOC />
|
<JAVADOC />
|
||||||
|
22
.idea/libraries/io_kotest_assertions_core_jvm.xml
generated
Normal file
22
.idea/libraries/io_kotest_assertions_core_jvm.xml
generated
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<component name="libraryTable">
|
||||||
|
<library name="io.kotest.assertions.core.jvm" type="repository">
|
||||||
|
<properties maven-id="io.kotest:kotest-assertions-core-jvm:4.6.3" />
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/4.6.3/kotest-assertions-core-jvm-4.6.3.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.5.0/kotlin-stdlib-jdk8-1.5.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.5.0/kotlin-stdlib-1.5.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.5.0/kotlin-stdlib-jdk7-1.5.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/4.6.3/kotest-assertions-shared-jvm-4.6.3.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.9/java-diff-utils-4.9.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.5.0/kotlinx-coroutines-jdk8-1.5.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.5.0/kotlin-reflect-1.5.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.5.0/kotlinx-coroutines-core-jvm-1.5.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.5.0/kotlin-stdlib-common-1.5.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/4.6.3/kotest-common-jvm-4.6.3.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/4.6.3/kotest-assertions-api-jvm-4.6.3.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</component>
|
24
.idea/libraries/io_kotest_property_jvm.xml
generated
Normal file
24
.idea/libraries/io_kotest_property_jvm.xml
generated
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<component name="libraryTable">
|
||||||
|
<library name="io.kotest.property.jvm" type="repository">
|
||||||
|
<properties maven-id="io.kotest:kotest-property-jvm:4.6.3" />
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-property-jvm/4.6.3/kotest-property-jvm-4.6.3.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.5.0/kotlin-stdlib-jdk8-1.5.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.5.0/kotlin-stdlib-1.5.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.5.0/kotlin-stdlib-jdk7-1.5.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/4.6.3/kotest-common-jvm-4.6.3.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/4.6.3/kotest-assertions-shared-jvm-4.6.3.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/4.6.3/kotest-assertions-api-jvm-4.6.3.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.5.0/kotlinx-coroutines-jdk8-1.5.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.9/java-diff-utils-4.9.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/com/github/mifmif/generex/1.0.2/generex-1.0.2.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/dk/brics/automaton/automaton/1.11-8/automaton-1.11-8.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.5.0/kotlin-reflect-1.5.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.5.0/kotlinx-coroutines-core-jvm-1.5.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.5.0/kotlin-stdlib-common-1.5.0.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</component>
|
56
.idea/libraries/io_kotest_runner_junit5_jvm.xml
generated
Normal file
56
.idea/libraries/io_kotest_runner_junit5_jvm.xml
generated
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<component name="libraryTable">
|
||||||
|
<library name="io.kotest.runner.junit5.jvm" type="repository">
|
||||||
|
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:4.6.3" />
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/4.6.3/kotest-runner-junit5-jvm-4.6.3.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/4.6.3/kotest-framework-api-jvm-4.6.3.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/4.6.3/kotest-assertions-shared-jvm-4.6.3.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.9/java-diff-utils-4.9.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/4.6.3/kotest-common-jvm-4.6.3.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/4.6.3/kotest-framework-engine-jvm-4.6.3.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.105/classgraph-4.8.105.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/mordant/1.2.1/mordant-1.2.1.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/colormath/1.2.0/colormath-1.2.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-script-util/1.5.0/kotlin-script-util-1.5.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/intellij/deps/trove4j/1.0.20181211/trove4j-1.0.20181211.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-daemon-client/1.5.0/kotlin-daemon-client-1.5.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core/1.3.8/kotlinx-coroutines-core-1.3.8.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-scripting-jvm/1.5.0/kotlin-scripting-jvm-1.5.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-scripting-common/1.5.0/kotlin-scripting-common-1.5.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-discovery-jvm/4.6.3/kotest-framework-discovery-jvm-4.6.3.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/4.6.3/kotest-assertions-core-jvm-4.6.3.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.5.0/kotlinx-coroutines-jdk8-1.5.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/4.6.3/kotest-assertions-api-jvm-4.6.3.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/4.6.3/kotest-extensions-jvm-4.6.3.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/commons-io/commons-io/2.6/commons-io-2.6.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk/1.9.3/mockk-1.9.3.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-common/1.9.3/mockk-common-1.9.3.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-dsl/1.9.3/mockk-dsl-1.9.3.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-dsl-jvm/1.9.3/mockk-dsl-jvm-1.9.3.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-agent-jvm/1.9.3/mockk-agent-jvm-1.9.3.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-agent-api/1.9.3/mockk-agent-api-1.9.3.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-agent-common/1.9.3/mockk-agent-common-1.9.3.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/objenesis/objenesis/3.0.1/objenesis-3.0.1.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.9.10/byte-buddy-1.9.10.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy-agent/1.9.10/byte-buddy-agent-1.9.10.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/4.6.3/kotest-framework-concurrency-jvm-4.6.3.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.5.0/kotlinx-coroutines-core-jvm-1.5.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.5.0/kotlin-stdlib-common-1.5.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.6.2/junit-platform-engine-1.6.2.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.0/apiguardian-api-1.1.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.6.2/junit-platform-commons-1.6.2.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-suite-api/1.6.2/junit-platform-suite-api-1.6.2.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-launcher/1.6.2/junit-platform-launcher-1.6.2.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.6.2/junit-jupiter-api-5.6.2.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.5.0/kotlin-stdlib-jdk8-1.5.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.5.0/kotlin-stdlib-1.5.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.5.0/kotlin-stdlib-jdk7-1.5.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-script-runtime/1.5.0/kotlin-script-runtime-1.5.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.5.0/kotlin-reflect-1.5.0.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</component>
|
3
.idea/misc.xml
generated
3
.idea/misc.xml
generated
@ -16,6 +16,9 @@
|
|||||||
</list>
|
</list>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
|
<component name="FrameworkDetectionExcludesConfiguration">
|
||||||
|
<type id="Python" />
|
||||||
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="Kotlin SDK" project-jdk-type="KotlinSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="Kotlin SDK" project-jdk-type="KotlinSDK">
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
|
3
.idea/modules.xml
generated
3
.idea/modules.xml
generated
@ -2,8 +2,11 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectModuleManager">
|
<component name="ProjectModuleManager">
|
||||||
<modules>
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/codeGeneration/codeGeneration.iml" filepath="$PROJECT_DIR$/codeGeneration/codeGeneration.iml" />
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/codeOptimizers/codeOptimizers.iml" filepath="$PROJECT_DIR$/codeOptimizers/codeOptimizers.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" />
|
<module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/compilerAst/compilerAst.iml" filepath="$PROJECT_DIR$/compilerAst/compilerAst.iml" />
|
<module fileurl="file://$PROJECT_DIR$/compilerAst/compilerAst.iml" filepath="$PROJECT_DIR$/compilerAst/compilerAst.iml" />
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/compilerInterfaces/compilerInterfaces.iml" filepath="$PROJECT_DIR$/compilerInterfaces/compilerInterfaces.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/dbusCompilerService/dbusCompilerService.iml" filepath="$PROJECT_DIR$/dbusCompilerService/dbusCompilerService.iml" />
|
<module fileurl="file://$PROJECT_DIR$/dbusCompilerService/dbusCompilerService.iml" filepath="$PROJECT_DIR$/dbusCompilerService/dbusCompilerService.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
|
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />
|
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />
|
||||||
|
29
.readthedocs.yaml
Normal file
29
.readthedocs.yaml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# .readthedocs.yaml
|
||||||
|
# Read the Docs configuration file
|
||||||
|
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||||
|
|
||||||
|
# Required
|
||||||
|
version: 2
|
||||||
|
|
||||||
|
# Set the version of Python and other tools you might need
|
||||||
|
build:
|
||||||
|
os: ubuntu-20.04
|
||||||
|
tools:
|
||||||
|
python: "3.9"
|
||||||
|
# You can also specify other tool versions:
|
||||||
|
# nodejs: "16"
|
||||||
|
# rust: "1.55"
|
||||||
|
# golang: "1.17"
|
||||||
|
|
||||||
|
# Build documentation in the docs/ directory with Sphinx
|
||||||
|
sphinx:
|
||||||
|
configuration: docs/source/conf.py
|
||||||
|
|
||||||
|
# If using Sphinx, optionally build your docs in additional formats such as PDF
|
||||||
|
formats:
|
||||||
|
- pdf
|
||||||
|
|
||||||
|
# Optionally declare the Python requirements required to build your docs
|
||||||
|
python:
|
||||||
|
install:
|
||||||
|
- requirements: docs/requirements.txt
|
@ -1,3 +1,10 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "org.jetbrains.kotlin.jvm" version "1.5.30" apply false
|
id "org.jetbrains.kotlin.jvm" version "$kotlinVersion" apply false
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
mavenLocal()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
52
codeGeneration/build.gradle
Normal file
52
codeGeneration/build.gradle
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
|
||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
id 'application'
|
||||||
|
id "org.jetbrains.kotlin.jvm"
|
||||||
|
id "io.kotest" version "0.3.8"
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
toolchain {
|
||||||
|
languageVersion = JavaLanguageVersion.of(javaVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation project(':compilerInterfaces')
|
||||||
|
implementation project(':compilerAst')
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||||
|
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||||
|
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.12"
|
||||||
|
|
||||||
|
testImplementation 'io.kotest:kotest-runner-junit5-jvm:4.6.3'
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
java {
|
||||||
|
srcDirs = ["${project.projectDir}/src"]
|
||||||
|
}
|
||||||
|
resources {
|
||||||
|
srcDirs = ["${project.projectDir}/res"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
test {
|
||||||
|
java {
|
||||||
|
srcDirs = ["${project.projectDir}/test"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
|
||||||
|
// Always run tests, even when nothing changed.
|
||||||
|
dependsOn 'cleanTest'
|
||||||
|
|
||||||
|
// Show test results.
|
||||||
|
testLogging {
|
||||||
|
events "skipped", "failed"
|
||||||
|
}
|
||||||
|
}
|
19
codeGeneration/codeGeneration.iml
Normal file
19
codeGeneration/codeGeneration.iml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="JAVA_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
|
||||||
|
<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" name="io.kotest.runner.junit5.jvm" level="project" />
|
||||||
|
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
|
||||||
|
</component>
|
||||||
|
</module>
|
@ -0,0 +1,3 @@
|
|||||||
|
package prog8.compiler.target
|
||||||
|
|
||||||
|
class AssemblyError(msg: String) : RuntimeException(msg)
|
41
codeGeneration/src/prog8/compiler/target/C64Target.kt
Normal file
41
codeGeneration/src/prog8/compiler/target/C64Target.kt
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package prog8.compiler.target
|
||||||
|
|
||||||
|
import com.github.michaelbull.result.fold
|
||||||
|
import prog8.ast.base.*
|
||||||
|
import prog8.ast.expressions.Expression
|
||||||
|
import prog8.ast.statements.RegisterOrStatusflag
|
||||||
|
import prog8.ast.statements.Subroutine
|
||||||
|
import prog8.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<UByte> {
|
||||||
|
val coded = if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||||
|
return coded.fold(
|
||||||
|
failure = { throw it },
|
||||||
|
success = { it }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean) =
|
||||||
|
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||||
|
|
||||||
|
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> =
|
||||||
|
asmsub6502ArgsEvalOrder(sub)
|
||||||
|
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>, paramRegisters: List<RegisterOrStatusflag>) =
|
||||||
|
asmsub6502ArgsHaveRegisterClobberRisk(args, paramRegisters)
|
||||||
|
|
||||||
|
override fun memorySize(dt: DataType): Int {
|
||||||
|
return when(dt) {
|
||||||
|
in ByteDatatypes -> 1
|
||||||
|
in WordDatatypes -> 2
|
||||||
|
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
|
||||||
|
in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE
|
||||||
|
else -> Int.MIN_VALUE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
codeGeneration/src/prog8/compiler/target/Cx16Target.kt
Normal file
46
codeGeneration/src/prog8/compiler/target/Cx16Target.kt
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
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.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<UByte> {
|
||||||
|
val coded= if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||||
|
return coded.fold(
|
||||||
|
failure = { throw it },
|
||||||
|
success = { it }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean) =
|
||||||
|
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||||
|
|
||||||
|
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> =
|
||||||
|
asmsub6502ArgsEvalOrder(sub)
|
||||||
|
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>, paramRegisters: List<RegisterOrStatusflag>) =
|
||||||
|
asmsub6502ArgsHaveRegisterClobberRisk(args, paramRegisters)
|
||||||
|
|
||||||
|
override fun memorySize(dt: DataType): Int {
|
||||||
|
return when(dt) {
|
||||||
|
in ByteDatatypes -> 1
|
||||||
|
in WordDatatypes -> 2
|
||||||
|
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
|
||||||
|
in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE
|
||||||
|
else -> Int.MIN_VALUE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,16 +1,13 @@
|
|||||||
package prog8.compiler.target.c64
|
package prog8.compiler.target.c64
|
||||||
|
|
||||||
import prog8.compiler.*
|
|
||||||
import prog8.compiler.target.CpuType
|
|
||||||
import prog8.compiler.target.IMachineDefinition
|
|
||||||
import prog8.compiler.target.IMachineFloat
|
|
||||||
import prog8.compiler.target.cbm.viceMonListPostfix
|
import prog8.compiler.target.cbm.viceMonListPostfix
|
||||||
|
import prog8.compilerinterface.*
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
|
|
||||||
internal object C64MachineDefinition: IMachineDefinition {
|
object C64MachineDefinition: IMachineDefinition {
|
||||||
|
|
||||||
override val cpu = CpuType.CPU6502
|
override val cpu = CpuType.CPU6502
|
||||||
|
|
||||||
@ -19,18 +16,18 @@ internal object C64MachineDefinition: IMachineDefinition {
|
|||||||
override val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,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_MEM_SIZE = 5
|
||||||
override val POINTER_MEM_SIZE = 2
|
override val POINTER_MEM_SIZE = 2
|
||||||
override val BASIC_LOAD_ADDRESS = 0x0801
|
override val BASIC_LOAD_ADDRESS = 0x0801u
|
||||||
override val RAW_LOAD_ADDRESS = 0xc000
|
override val RAW_LOAD_ADDRESS = 0xc000u
|
||||||
|
|
||||||
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
// 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_LO = 0xce00u // $ce00-$ceff inclusive
|
||||||
override val ESTACK_HI = 0xcf00 // $ce00-$ceff inclusive
|
override val ESTACK_HI = 0xcf00u // $ce00-$ceff inclusive
|
||||||
|
|
||||||
override lateinit var zeropage: Zeropage
|
override lateinit var zeropage: Zeropage
|
||||||
|
|
||||||
override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
|
override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
|
||||||
|
|
||||||
override fun importLibs(compilerOptions: CompilationOptions,compilationTargetName: String): List<String> {
|
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||||
return if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
return if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
||||||
listOf("syslib")
|
listOf("syslib")
|
||||||
else
|
else
|
||||||
@ -59,7 +56,7 @@ internal 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 initializeZeropage(compilerOptions: CompilationOptions) {
|
override fun initializeZeropage(compilerOptions: CompilationOptions) {
|
||||||
zeropage = C64Zeropage(compilerOptions)
|
zeropage = C64Zeropage(compilerOptions)
|
||||||
@ -77,23 +74,22 @@ internal object C64MachineDefinition: IMachineDefinition {
|
|||||||
"sta", "stx", "sty", "tas", "tax", "tay", "tsx", "txa", "txs", "tya", "xaa")
|
"sta", "stx", "sty", "tas", "tax", "tay", "tsx", "txa", "txs", "tya", "xaa")
|
||||||
|
|
||||||
|
|
||||||
internal class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||||
|
|
||||||
override val SCRATCH_B1 = 0x02 // temp storage for a single byte
|
override val SCRATCH_B1 = 0x02u // temp storage for a single byte
|
||||||
override val SCRATCH_REG = 0x03 // temp storage for a register, must be B1+1
|
override val SCRATCH_REG = 0x03u // 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_W1 = 0xfbu // temp storage 1 for a word $fb+$fc
|
||||||
override val SCRATCH_W2 = 0xfd // temp storage 2 for a word $fb+$fc
|
override val SCRATCH_W2 = 0xfdu // temp storage 2 for a word $fb+$fc
|
||||||
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (options.floats && options.zeropage !in arrayOf(ZeropageType.FLOATSAFE, ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
|
if (options.floats && options.zeropage !in arrayOf(ZeropageType.FLOATSAFE, ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
|
||||||
throw CompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
|
throw InternalCompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
|
||||||
|
|
||||||
if (options.zeropage == ZeropageType.FULL) {
|
if (options.zeropage == ZeropageType.FULL) {
|
||||||
free.addAll(0x04..0xf9)
|
free.addAll(0x04u..0xf9u)
|
||||||
free.add(0xff)
|
free.add(0xffu)
|
||||||
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
|
free.removeAll(setOf(0xa0u, 0xa1u, 0xa2u, 0x91u, 0xc0u, 0xc5u, 0xcbu, 0xf5u, 0xf6u)) // these are updated by IRQ
|
||||||
free.removeAll(listOf(0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6)) // these are updated by IRQ
|
|
||||||
} else {
|
} else {
|
||||||
if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
|
if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
|
||||||
free.addAll(listOf(
|
free.addAll(listOf(
|
||||||
@ -110,7 +106,7 @@ internal object C64MachineDefinition: IMachineDefinition {
|
|||||||
0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
|
0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
|
||||||
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
|
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
|
||||||
// 0x90-0xfa is 'kernal work storage area'
|
// 0x90-0xfa is 'kernal work storage area'
|
||||||
))
|
).map{it.toUInt()})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.zeropage == ZeropageType.FLOATSAFE) {
|
if (options.zeropage == ZeropageType.FLOATSAFE) {
|
||||||
@ -122,34 +118,30 @@ internal object C64MachineDefinition: IMachineDefinition {
|
|||||||
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
||||||
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
|
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
|
||||||
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
|
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
|
||||||
))
|
).map{it.toUInt()})
|
||||||
}
|
}
|
||||||
|
|
||||||
if(options.zeropage!=ZeropageType.DONTUSE) {
|
if(options.zeropage!= ZeropageType.DONTUSE) {
|
||||||
// add the free Zp addresses
|
// 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*
|
// 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,
|
free.addAll(listOf(0x04, 0x05, 0x06, 0x0a, 0x0e,
|
||||||
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa5, 0xa6,
|
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa5, 0xa6,
|
||||||
0xb0, 0xb1, 0xbe, 0xbf, 0xf9))
|
0xb0, 0xb1, 0xbe, 0xbf, 0xf9).map{it.toUInt()})
|
||||||
} else {
|
} else {
|
||||||
// don't use the zeropage at all
|
// don't use the zeropage at all
|
||||||
free.clear()
|
free.clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
require(SCRATCH_B1 !in free)
|
|
||||||
require(SCRATCH_REG !in free)
|
|
||||||
require(SCRATCH_W1 !in free)
|
|
||||||
require(SCRATCH_W2 !in free)
|
|
||||||
|
|
||||||
for (reserved in options.zpReserved)
|
removeReservedFromFreePool()
|
||||||
reserve(reserved)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short): IMachineFloat {
|
data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, val b4: UByte):
|
||||||
|
IMachineFloat {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val zero = Mflpt5(0, 0, 0, 0, 0)
|
val zero = Mflpt5(0u, 0u, 0u, 0u, 0u)
|
||||||
fun fromNumber(num: Number): Mflpt5 {
|
fun fromNumber(num: Number): Mflpt5 {
|
||||||
// see https://en.wikipedia.org/wiki/Microsoft_Binary_Format
|
// 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://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a
|
||||||
@ -157,7 +149,7 @@ internal object C64MachineDefinition: IMachineDefinition {
|
|||||||
|
|
||||||
val flt = num.toDouble()
|
val flt = num.toDouble()
|
||||||
if (flt < FLOAT_MAX_NEGATIVE || flt > FLOAT_MAX_POSITIVE)
|
if (flt < FLOAT_MAX_NEGATIVE || flt > FLOAT_MAX_POSITIVE)
|
||||||
throw CompilerException("floating point number out of 5-byte mflpt range: $this")
|
throw InternalCompilerException("floating point number out of 5-byte mflpt range: $this")
|
||||||
if (flt == 0.0)
|
if (flt == 0.0)
|
||||||
return zero
|
return zero
|
||||||
|
|
||||||
@ -178,16 +170,16 @@ internal object C64MachineDefinition: IMachineDefinition {
|
|||||||
|
|
||||||
return when {
|
return when {
|
||||||
exponent < 0 -> zero // underflow, use zero instead
|
exponent < 0 -> zero // underflow, use zero instead
|
||||||
exponent > 255 -> throw CompilerException("floating point overflow: $this")
|
exponent > 255 -> throw InternalCompilerException("floating point overflow: $this")
|
||||||
exponent == 0 -> zero
|
exponent == 0 -> zero
|
||||||
else -> {
|
else -> {
|
||||||
val mantLong = mantissa.toLong()
|
val mantLong = mantissa.toLong()
|
||||||
Mflpt5(
|
Mflpt5(
|
||||||
exponent.toShort(),
|
exponent.toUByte(),
|
||||||
(mantLong.and(0x7f000000L) ushr 24).or(sign).toShort(),
|
(mantLong.and(0x7f000000L) ushr 24).or(sign).toUByte(),
|
||||||
(mantLong.and(0x00ff0000L) ushr 16).toShort(),
|
(mantLong.and(0x00ff0000L) ushr 16).toUByte(),
|
||||||
(mantLong.and(0x0000ff00L) ushr 8).toShort(),
|
(mantLong.and(0x0000ff00L) ushr 8).toUByte(),
|
||||||
(mantLong.and(0x000000ffL)).toShort())
|
(mantLong.and(0x000000ffL)).toUByte())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -195,7 +187,7 @@ internal object C64MachineDefinition: IMachineDefinition {
|
|||||||
|
|
||||||
override fun toDouble(): Double {
|
override fun toDouble(): Double {
|
||||||
if (this == zero) return 0.0
|
if (this == zero) return 0.0
|
||||||
val exp = b0 - 128
|
val exp = b0.toInt() - 128
|
||||||
val sign = (b1.toInt() and 0x80) > 0
|
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 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
|
val result = number.toDouble() * (2.0).pow(exp) / 0x100000000
|
@ -1,10 +1,17 @@
|
|||||||
package prog8.compiler.target.cbm
|
package prog8.compiler.target.cbm
|
||||||
|
|
||||||
import prog8.compiler.CompilationOptions
|
import com.github.michaelbull.result.Ok
|
||||||
import prog8.compiler.OutputType
|
import com.github.michaelbull.result.Result
|
||||||
import prog8.compiler.target.IAssemblyProgram
|
import com.github.michaelbull.result.mapError
|
||||||
import prog8.compiler.target.generatedLabelPrefix
|
import prog8.compilerinterface.CompilationOptions
|
||||||
|
import prog8.compilerinterface.IAssemblyProgram
|
||||||
|
import prog8.compilerinterface.OutputType
|
||||||
|
import prog8.compilerinterface.generatedLabelPrefix
|
||||||
|
import prog8.parser.SourceCode
|
||||||
|
import java.io.File
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
import kotlin.io.path.Path
|
||||||
|
import kotlin.io.path.isRegularFile
|
||||||
|
|
||||||
|
|
||||||
internal const val viceMonListPostfix = "vice-mon-list"
|
internal const val viceMonListPostfix = "vice-mon-list"
|
||||||
@ -20,12 +27,15 @@ class AssemblyProgram(
|
|||||||
private val binFile = outputDir.resolve("$name.bin")
|
private val binFile = outputDir.resolve("$name.bin")
|
||||||
private val viceMonListFile = outputDir.resolve("$name.$viceMonListPostfix")
|
private val viceMonListFile = outputDir.resolve("$name.$viceMonListPostfix")
|
||||||
|
|
||||||
override fun assemble(options: CompilationOptions): Int {
|
override fun assemble(quiet: Boolean, options: CompilationOptions): Int {
|
||||||
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps (default = do this silently)
|
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps (default = do this silently)
|
||||||
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
|
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
|
||||||
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
|
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
|
||||||
"--dump-labels", "--vice-labels", "-l", viceMonListFile.toString(), "--no-monitor")
|
"--dump-labels", "--vice-labels", "-l", viceMonListFile.toString(), "--no-monitor")
|
||||||
|
|
||||||
|
if(quiet)
|
||||||
|
command.add("--quiet")
|
||||||
|
|
||||||
val outFile = when (options.output) {
|
val outFile = when (options.output) {
|
||||||
OutputType.PRG -> {
|
OutputType.PRG -> {
|
||||||
command.add("--cbm-prg")
|
command.add("--cbm-prg")
|
||||||
@ -76,3 +86,18 @@ class AssemblyProgram(
|
|||||||
viceMonListFile.toFile().appendText(breakpoints.joinToString("\n") + "\n")
|
viceMonListFile.toFile().appendText(breakpoints.joinToString("\n") + "\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal fun loadAsmIncludeFile(filename: String, source: SourceCode): Result<String, NoSuchFileException> {
|
||||||
|
return if (filename.startsWith(SourceCode.libraryFilePrefix)) {
|
||||||
|
return com.github.michaelbull.result.runCatching {
|
||||||
|
SourceCode.Resource("/prog8lib/${filename.substring(SourceCode.libraryFilePrefix.length)}").readText()
|
||||||
|
}.mapError { NoSuchFileException(File(filename)) }
|
||||||
|
} else {
|
||||||
|
val sib = Path(source.origin).resolveSibling(filename)
|
||||||
|
if (sib.isRegularFile())
|
||||||
|
Ok(SourceCode.File(sib).readText())
|
||||||
|
else
|
||||||
|
Ok(SourceCode.File(Path(filename)).readText())
|
||||||
|
}
|
||||||
|
}
|
@ -11,7 +11,7 @@ object Petscii {
|
|||||||
// decoding: from Petscii/Screencodes (0-255) to unicode
|
// decoding: from Petscii/Screencodes (0-255) to unicode
|
||||||
// character tables used from https://github.com/dj51d/cbmcodecs
|
// character tables used from https://github.com/dj51d/cbmcodecs
|
||||||
|
|
||||||
private val decodingPetsciiLowercase = arrayOf(
|
private val decodingPetsciiLowercase = charArrayOf(
|
||||||
'\u0000', // 0x00 -> \u0000
|
'\u0000', // 0x00 -> \u0000
|
||||||
'\ufffe', // 0x01 -> UNDEFINED
|
'\ufffe', // 0x01 -> UNDEFINED
|
||||||
'\ufffe', // 0x02 -> UNDEFINED
|
'\ufffe', // 0x02 -> UNDEFINED
|
||||||
@ -270,7 +270,7 @@ object Petscii {
|
|||||||
'\u2592' // ▒ 0xFF -> MEDIUM SHADE
|
'\u2592' // ▒ 0xFF -> MEDIUM SHADE
|
||||||
)
|
)
|
||||||
|
|
||||||
private val decodingPetsciiUppercase = arrayOf(
|
private val decodingPetsciiUppercase = charArrayOf(
|
||||||
'\u0000', // 0x00 -> \u0000
|
'\u0000', // 0x00 -> \u0000
|
||||||
'\ufffe', // 0x01 -> UNDEFINED
|
'\ufffe', // 0x01 -> UNDEFINED
|
||||||
'\ufffe', // 0x02 -> UNDEFINED
|
'\ufffe', // 0x02 -> UNDEFINED
|
||||||
@ -369,13 +369,13 @@ object Petscii {
|
|||||||
'\u2190', // ← 0x5F -> LEFTWARDS ARROW
|
'\u2190', // ← 0x5F -> LEFTWARDS ARROW
|
||||||
'\u2500', // ─ 0x60 -> BOX DRAWINGS LIGHT HORIZONTAL
|
'\u2500', // ─ 0x60 -> BOX DRAWINGS LIGHT HORIZONTAL
|
||||||
'\u2660', // ♠ 0x61 -> BLACK SPADE SUIT
|
'\u2660', // ♠ 0x61 -> BLACK SPADE SUIT
|
||||||
'\u2502', // │ 0x62 -> BOX DRAWINGS LIGHT VERTICAL
|
'\uf13c', // │ 0x62 -> BOX DRAWINGS LIGHT VERTICAL ONE EIGHTH LEFT (CUS)
|
||||||
'\u2500', // ─ 0x63 -> BOX DRAWINGS LIGHT HORIZONTAL
|
'\uf13b', // ─ 0x63 -> BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH UP (CUS)
|
||||||
'\uf122', // 0x64 -> BOX DRAWINGS LIGHT HORIZONTAL ONE QUARTER UP (CUS)
|
'\uf122', // 0x64 -> BOX DRAWINGS LIGHT HORIZONTAL TWO EIGHTHS UP (CUS)
|
||||||
'\uf123', // 0x65 -> BOX DRAWINGS LIGHT HORIZONTAL TWO QUARTERS UP (CUS)
|
'\uf123', // 0x65 -> BOX DRAWINGS LIGHT HORIZONTAL THREE EIGHTHS UP (CUS)
|
||||||
'\uf124', // 0x66 -> BOX DRAWINGS LIGHT HORIZONTAL ONE QUARTER DOWN (CUS)
|
'\uf124', // 0x66 -> BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH DOWN (CUS)
|
||||||
'\uf126', // 0x67 -> BOX DRAWINGS LIGHT VERTICAL ONE QUARTER LEFT (CUS)
|
'\uf126', // 0x67 -> BOX DRAWINGS LIGHT VERTICAL TWO EIGHTHS LEFT (CUS)
|
||||||
'\uf128', // 0x68 -> BOX DRAWINGS LIGHT VERTICAL ONE QUARTER RIGHT (CUS)
|
'\uf128', // 0x68 -> BOX DRAWINGS LIGHT VERTICAL ONE EIGHTH RIGHT (CUS)
|
||||||
'\u256e', // ╮ 0x69 -> BOX DRAWINGS LIGHT ARC DOWN AND LEFT
|
'\u256e', // ╮ 0x69 -> BOX DRAWINGS LIGHT ARC DOWN AND LEFT
|
||||||
'\u2570', // ╰ 0x6A -> BOX DRAWINGS LIGHT ARC UP AND RIGHT
|
'\u2570', // ╰ 0x6A -> BOX DRAWINGS LIGHT ARC UP AND RIGHT
|
||||||
'\u256f', // ╯ 0x6B -> BOX DRAWINGS LIGHT ARC UP AND LEFT
|
'\u256f', // ╯ 0x6B -> BOX DRAWINGS LIGHT ARC UP AND LEFT
|
||||||
@ -385,14 +385,14 @@ object Petscii {
|
|||||||
'\uf12b', // 0x6F -> ONE EIGHTH BLOCK DOWN AND RIGHT (CUS)
|
'\uf12b', // 0x6F -> ONE EIGHTH BLOCK DOWN AND RIGHT (CUS)
|
||||||
'\uf12c', // 0x70 -> ONE EIGHTH BLOCK DOWN AND LEFT (CUS)
|
'\uf12c', // 0x70 -> ONE EIGHTH BLOCK DOWN AND LEFT (CUS)
|
||||||
'\u25cf', // ● 0x71 -> BLACK CIRCLE
|
'\u25cf', // ● 0x71 -> BLACK CIRCLE
|
||||||
'\uf125', // 0x72 -> BOX DRAWINGS LIGHT HORIZONTAL TWO QUARTERS DOWN (CUS)
|
'\uf125', // 0x72 -> BOX DRAWINGS LIGHT HORIZONTAL TWO EIGHTHS DOWN (CUS)
|
||||||
'\u2665', // ♥ 0x73 -> BLACK HEART SUIT
|
'\u2665', // ♥ 0x73 -> BLACK HEART SUIT
|
||||||
'\uf127', // 0x74 -> BOX DRAWINGS LIGHT VERTICAL TWO QUARTERS LEFT (CUS)
|
'\uf127', // 0x74 -> BOX DRAWINGS LIGHT VERTICAL THREE EIGHTHS LEFT (CUS)
|
||||||
'\u256d', // ╭ 0x75 -> BOX DRAWINGS LIGHT ARC DOWN AND RIGHT
|
'\u256d', // ╭ 0x75 -> BOX DRAWINGS LIGHT ARC DOWN AND RIGHT
|
||||||
'\u2573', // ╳ 0x76 -> BOX DRAWINGS LIGHT DIAGONAL CROSS
|
'\u2573', // ╳ 0x76 -> BOX DRAWINGS LIGHT DIAGONAL CROSS
|
||||||
'\u25cb', // ○ 0x77 -> WHITE CIRCLE
|
'\u25cb', // ○ 0x77 -> WHITE CIRCLE
|
||||||
'\u2663', // ♣ 0x78 -> BLACK CLUB SUIT
|
'\u2663', // ♣ 0x78 -> BLACK CLUB SUIT
|
||||||
'\uf129', // 0x79 -> BOX DRAWINGS LIGHT VERTICAL TWO QUARTERS RIGHT (CUS)
|
'\uf129', // 0x79 -> BOX DRAWINGS LIGHT VERTICAL TWO EIGHTS RIGHT (CUS)
|
||||||
'\u2666', // ♦ 0x7A -> BLACK DIAMOND SUIT
|
'\u2666', // ♦ 0x7A -> BLACK DIAMOND SUIT
|
||||||
'\u253c', // ┼ 0x7B -> BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
|
'\u253c', // ┼ 0x7B -> BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
|
||||||
'\uf12e', // 0x7C -> LEFT HALF BLOCK MEDIUM SHADE (CUS)
|
'\uf12e', // 0x7C -> LEFT HALF BLOCK MEDIUM SHADE (CUS)
|
||||||
@ -465,13 +465,13 @@ object Petscii {
|
|||||||
'\u259a', // ▚ 0xBF -> QUADRANT UPPER LEFT AND LOWER RIGHT
|
'\u259a', // ▚ 0xBF -> QUADRANT UPPER LEFT AND LOWER RIGHT
|
||||||
'\u2500', // ─ 0xC0 -> BOX DRAWINGS LIGHT HORIZONTAL
|
'\u2500', // ─ 0xC0 -> BOX DRAWINGS LIGHT HORIZONTAL
|
||||||
'\u2660', // ♠ 0xC1 -> BLACK SPADE SUIT
|
'\u2660', // ♠ 0xC1 -> BLACK SPADE SUIT
|
||||||
'\u2502', // │ 0xC2 -> BOX DRAWINGS LIGHT VERTICAL
|
'\uf13c', // │ 0xC2 -> BOX DRAWINGS LIGHT VERTICAL ONE EIGHTH LEFT (CUS)
|
||||||
'\u2500', // ─ 0xC3 -> BOX DRAWINGS LIGHT HORIZONTAL
|
'\uf13b', // ─ 0xC3 -> BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH UP (CUS)
|
||||||
'\uf122', // 0xC4 -> BOX DRAWINGS LIGHT HORIZONTAL ONE QUARTER UP (CUS)
|
'\uf122', // 0xC4 -> BOX DRAWINGS LIGHT HORIZONTAL TWO EIGHTHS UP (CUS)
|
||||||
'\uf123', // 0xC5 -> BOX DRAWINGS LIGHT HORIZONTAL TWO QUARTERS UP (CUS)
|
'\uf123', // 0xC5 -> BOX DRAWINGS LIGHT HORIZONTAL THREE EIGHTHS UP (CUS)
|
||||||
'\uf124', // 0xC6 -> BOX DRAWINGS LIGHT HORIZONTAL ONE QUARTER DOWN (CUS)
|
'\uf124', // 0xC6 -> BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH DOWN (CUS)
|
||||||
'\uf126', // 0xC7 -> BOX DRAWINGS LIGHT VERTICAL ONE QUARTER LEFT (CUS)
|
'\uf126', // 0xC7 -> BOX DRAWINGS LIGHT VERTICAL TWO EIGHTHS LEFT (CUS)
|
||||||
'\uf128', // 0xC8 -> BOX DRAWINGS LIGHT VERTICAL ONE QUARTER RIGHT (CUS)
|
'\uf128', // 0xC8 -> BOX DRAWINGS LIGHT VERTICAL ONE EIGHTH RIGHT (CUS)
|
||||||
'\u256e', // ╮ 0xC9 -> BOX DRAWINGS LIGHT ARC DOWN AND LEFT
|
'\u256e', // ╮ 0xC9 -> BOX DRAWINGS LIGHT ARC DOWN AND LEFT
|
||||||
'\u2570', // ╰ 0xCA -> BOX DRAWINGS LIGHT ARC UP AND RIGHT
|
'\u2570', // ╰ 0xCA -> BOX DRAWINGS LIGHT ARC UP AND RIGHT
|
||||||
'\u256f', // ╯ 0xCB -> BOX DRAWINGS LIGHT ARC UP AND LEFT
|
'\u256f', // ╯ 0xCB -> BOX DRAWINGS LIGHT ARC UP AND LEFT
|
||||||
@ -481,14 +481,14 @@ object Petscii {
|
|||||||
'\uf12b', // 0xCF -> ONE EIGHTH BLOCK DOWN AND RIGHT (CUS)
|
'\uf12b', // 0xCF -> ONE EIGHTH BLOCK DOWN AND RIGHT (CUS)
|
||||||
'\uf12c', // 0xD0 -> ONE EIGHTH BLOCK DOWN AND LEFT (CUS)
|
'\uf12c', // 0xD0 -> ONE EIGHTH BLOCK DOWN AND LEFT (CUS)
|
||||||
'\u25cf', // ● 0xD1 -> BLACK CIRCLE
|
'\u25cf', // ● 0xD1 -> BLACK CIRCLE
|
||||||
'\uf125', // 0xD2 -> BOX DRAWINGS LIGHT HORIZONTAL TWO QUARTERS DOWN (CUS)
|
'\uf125', // 0xD2 -> BOX DRAWINGS LIGHT HORIZONTAL TWO EIGHTS DOWN (CUS)
|
||||||
'\u2665', // ♥ 0xD3 -> BLACK HEART SUIT
|
'\u2665', // ♥ 0xD3 -> BLACK HEART SUIT
|
||||||
'\uf127', // 0xD4 -> BOX DRAWINGS LIGHT VERTICAL TWO QUARTERS LEFT (CUS)
|
'\uf127', // 0xD4 -> BOX DRAWINGS LIGHT VERTICAL THREE EIGHTS LEFT (CUS)
|
||||||
'\u256d', // ╭ 0xD5 -> BOX DRAWINGS LIGHT ARC DOWN AND LEFT
|
'\u256d', // ╭ 0xD5 -> BOX DRAWINGS LIGHT ARC DOWN AND LEFT
|
||||||
'\u2573', // ╳ 0xD6 -> BOX DRAWINGS LIGHT DIAGONAL CROSS
|
'\u2573', // ╳ 0xD6 -> BOX DRAWINGS LIGHT DIAGONAL CROSS
|
||||||
'\u25cb', // ○ 0xD7 -> WHITE CIRCLE
|
'\u25cb', // ○ 0xD7 -> WHITE CIRCLE
|
||||||
'\u2663', // ♣ 0xD8 -> BLACK CLUB SUIT
|
'\u2663', // ♣ 0xD8 -> BLACK CLUB SUIT
|
||||||
'\uf129', // 0xD9 -> BOX DRAWINGS LIGHT VERTICAL TWO QUARTERS RIGHT (CUS)
|
'\uf129', // 0xD9 -> BOX DRAWINGS LIGHT VERTICAL TWO EIGHTS RIGHT (CUS)
|
||||||
'\u2666', // ♦ 0xDA -> BLACK DIAMOND SUIT
|
'\u2666', // ♦ 0xDA -> BLACK DIAMOND SUIT
|
||||||
'\u253c', // ┼ 0xDB -> BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
|
'\u253c', // ┼ 0xDB -> BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
|
||||||
'\uf12e', // 0xDC -> LEFT HALF BLOCK MEDIUM SHADE (CUS)
|
'\uf12e', // 0xDC -> LEFT HALF BLOCK MEDIUM SHADE (CUS)
|
||||||
@ -529,7 +529,7 @@ object Petscii {
|
|||||||
'\u03c0' // π 0xFF -> GREEK SMALL LETTER PI
|
'\u03c0' // π 0xFF -> GREEK SMALL LETTER PI
|
||||||
)
|
)
|
||||||
|
|
||||||
private val decodingScreencodeLowercase = arrayOf(
|
private val decodingScreencodeLowercase = charArrayOf(
|
||||||
'@' , // @ 0x00 -> COMMERCIAL AT
|
'@' , // @ 0x00 -> COMMERCIAL AT
|
||||||
'a' , // a 0x01 -> LATIN SMALL LETTER A
|
'a' , // a 0x01 -> LATIN SMALL LETTER A
|
||||||
'b' , // b 0x02 -> LATIN SMALL LETTER B
|
'b' , // b 0x02 -> LATIN SMALL LETTER B
|
||||||
@ -788,7 +788,7 @@ object Petscii {
|
|||||||
'\ufffe' // 0xFF -> UNDEFINED
|
'\ufffe' // 0xFF -> UNDEFINED
|
||||||
)
|
)
|
||||||
|
|
||||||
private val decodingScreencodeUppercase = arrayOf(
|
private val decodingScreencodeUppercase = charArrayOf(
|
||||||
'@' , // @ 0x00 -> COMMERCIAL AT
|
'@' , // @ 0x00 -> COMMERCIAL AT
|
||||||
'A' , // A 0x01 -> LATIN CAPITAL LETTER A
|
'A' , // A 0x01 -> LATIN CAPITAL LETTER A
|
||||||
'B' , // B 0x02 -> LATIN CAPITAL LETTER B
|
'B' , // B 0x02 -> LATIN CAPITAL LETTER B
|
||||||
@ -855,13 +855,13 @@ object Petscii {
|
|||||||
'?' , // ? 0x3F -> QUESTION MARK
|
'?' , // ? 0x3F -> QUESTION MARK
|
||||||
'\u2500', // ─ 0x40 -> BOX DRAWINGS LIGHT HORIZONTAL
|
'\u2500', // ─ 0x40 -> BOX DRAWINGS LIGHT HORIZONTAL
|
||||||
'\u2660', // ♠ 0x41 -> BLACK SPADE SUIT
|
'\u2660', // ♠ 0x41 -> BLACK SPADE SUIT
|
||||||
'\u2502', // │ 0x42 -> BOX DRAWINGS LIGHT VERTICAL
|
'\uf13c', // │ 0x42 -> BOX DRAWINGS LIGHT VERTICAL ONE EIGHTH LEFT (CUS)
|
||||||
'\u2500', // ─ 0x43 -> BOX DRAWINGS LIGHT HORIZONTAL
|
'\uf13b', // ─ 0x43 -> BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH UP (CUS)
|
||||||
'\uf122', // 0x44 -> BOX DRAWINGS LIGHT HORIZONTAL ONE QUARTER UP (CUS)
|
'\uf122', // 0x44 -> BOX DRAWINGS LIGHT HORIZONTAL TWO EIGHTHS UP (CUS)
|
||||||
'\uf123', // 0x45 -> BOX DRAWINGS LIGHT HORIZONTAL TWO QUARTERS UP (CUS)
|
'\uf123', // 0x45 -> BOX DRAWINGS LIGHT HORIZONTAL THREE EIGHTHS UP (CUS
|
||||||
'\uf124', // 0x46 -> BOX DRAWINGS LIGHT HORIZONTAL ONE QUARTER DOWN (CUS)
|
'\uf124', // 0x46 -> BOX DRAWINGS LIGHT HORIZONTAL ONE EIGHTH DOWN (CUS)
|
||||||
'\uf126', // 0x47 -> BOX DRAWINGS LIGHT VERTICAL ONE QUARTER LEFT (CUS)
|
'\uf126', // 0x47 -> BOX DRAWINGS LIGHT VERTICAL TWO EIGHTHS LEFT (CUS)
|
||||||
'\uf128', // 0x48 -> BOX DRAWINGS LIGHT VERTICAL ONE QUARTER RIGHT (CUS)
|
'\uf128', // 0x48 -> BOX DRAWINGS LIGHT VERTICAL ONE EIGHTH RIGHT (CUS)
|
||||||
'\u256e', // ╮ 0x49 -> BOX DRAWINGS LIGHT ARC DOWN AND LEFT
|
'\u256e', // ╮ 0x49 -> BOX DRAWINGS LIGHT ARC DOWN AND LEFT
|
||||||
'\u2570', // ╰ 0x4A -> BOX DRAWINGS LIGHT ARC UP AND RIGHT
|
'\u2570', // ╰ 0x4A -> BOX DRAWINGS LIGHT ARC UP AND RIGHT
|
||||||
'\u256f', // ╯ 0x4B -> BOX DRAWINGS LIGHT ARC UP AND LEFT
|
'\u256f', // ╯ 0x4B -> BOX DRAWINGS LIGHT ARC UP AND LEFT
|
||||||
@ -871,14 +871,14 @@ object Petscii {
|
|||||||
'\uf12b', // 0x4F -> ONE EIGHTH BLOCK DOWN AND RIGHT (CUS)
|
'\uf12b', // 0x4F -> ONE EIGHTH BLOCK DOWN AND RIGHT (CUS)
|
||||||
'\uf12c', // 0x50 -> ONE EIGHTH BLOCK DOWN AND LEFT (CUS)
|
'\uf12c', // 0x50 -> ONE EIGHTH BLOCK DOWN AND LEFT (CUS)
|
||||||
'\u25cf', // ● 0x51 -> BLACK CIRCLE
|
'\u25cf', // ● 0x51 -> BLACK CIRCLE
|
||||||
'\uf125', // 0x52 -> BOX DRAWINGS LIGHT HORIZONTAL TWO QUARTERS DOWN (CUS)
|
'\uf125', // 0x52 -> BOX DRAWINGS LIGHT HORIZONTAL TWO EIGHTS DOWN (CUS)
|
||||||
'\u2665', // ♥ 0x53 -> BLACK HEART SUIT
|
'\u2665', // ♥ 0x53 -> BLACK HEART SUIT
|
||||||
'\uf127', // 0x54 -> BOX DRAWINGS LIGHT VERTICAL TWO QUARTERS LEFT (CUS)
|
'\uf127', // 0x54 -> BOX DRAWINGS LIGHT VERTICAL THREE EIGHTS LEFT (CUS)
|
||||||
'\u256d', // ╭ 0x55 -> BOX DRAWINGS LIGHT ARC DOWN AND RIGHT
|
'\u256d', // ╭ 0x55 -> BOX DRAWINGS LIGHT ARC DOWN AND RIGHT
|
||||||
'\u2573', // ╳ 0x56 -> BOX DRAWINGS LIGHT DIAGONAL CROSS
|
'\u2573', // ╳ 0x56 -> BOX DRAWINGS LIGHT DIAGONAL CROSS
|
||||||
'\u25cb', // ○ 0x57 -> WHITE CIRCLE
|
'\u25cb', // ○ 0x57 -> WHITE CIRCLE
|
||||||
'\u2663', // ♣ 0x58 -> BLACK CLUB SUIT
|
'\u2663', // ♣ 0x58 -> BLACK CLUB SUIT
|
||||||
'\uf129', // 0x59 -> BOX DRAWINGS LIGHT VERTICAL TWO QUARTERS RIGHT (CUS)
|
'\uf129', // 0x59 -> BOX DRAWINGS LIGHT VERTICAL TWO EIGHTS RIGHT (CUS)
|
||||||
'\u2666', // ♦ 0x5A -> BLACK DIAMOND SUIT
|
'\u2666', // ♦ 0x5A -> BLACK DIAMOND SUIT
|
||||||
'\u253c', // ┼ 0x5B -> BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
|
'\u253c', // ┼ 0x5B -> BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
|
||||||
'\uf12e', // 0x5C -> LEFT HALF BLOCK MEDIUM SHADE (CUS)
|
'\uf12e', // 0x5C -> LEFT HALF BLOCK MEDIUM SHADE (CUS)
|
||||||
@ -1065,15 +1065,15 @@ object Petscii {
|
|||||||
else -> chr
|
else -> chr
|
||||||
}
|
}
|
||||||
|
|
||||||
fun encodePetscii(text: String, lowercase: Boolean = false): Result<List<Short>, CharConversionException> {
|
fun encodePetscii(text: String, lowercase: Boolean = false): Result<List<UByte>, CharConversionException> {
|
||||||
fun encodeChar(chr3: Char, lowercase: Boolean): Short {
|
fun encodeChar(chr3: Char, lowercase: Boolean): UByte {
|
||||||
val chr = replaceSpecial(chr3)
|
val chr = replaceSpecial(chr3)
|
||||||
val screencode = if(lowercase) encodingPetsciiLowercase[chr] else encodingPetsciiUppercase[chr]
|
val screencode = if(lowercase) encodingPetsciiLowercase[chr] else encodingPetsciiUppercase[chr]
|
||||||
return screencode?.toShort() ?: when (chr) {
|
return screencode?.toUByte() ?: when (chr) {
|
||||||
'\u0000' -> 0.toShort()
|
'\u0000' -> 0u
|
||||||
in '\u8000'..'\u80ff' -> {
|
in '\u8000'..'\u80ff' -> {
|
||||||
// special case: take the lower 8 bit hex value directly
|
// special case: take the lower 8 bit hex value directly
|
||||||
(chr.code - 0x8000).toShort()
|
(chr.code - 0x8000).toUByte()
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val case = if (lowercase) "lower" else "upper"
|
val case = if (lowercase) "lower" else "upper"
|
||||||
@ -1095,27 +1095,24 @@ object Petscii {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun decodePetscii(petscii: Iterable<Short>, lowercase: Boolean = false): String {
|
fun decodePetscii(petscii: Iterable<UByte>, lowercase: Boolean = false): String {
|
||||||
return petscii.map {
|
return petscii.map {
|
||||||
val code = it.toInt()
|
val code = it.toInt()
|
||||||
try {
|
if(code<0 || code>= decodingPetsciiLowercase.size)
|
||||||
if(lowercase) decodingPetsciiLowercase[code] else decodingPetsciiUppercase[code]
|
throw CharConversionException("petscii $code out of range 0..${decodingPetsciiLowercase.size-1}")
|
||||||
} catch(x: CharConversionException) {
|
if(lowercase) decodingPetsciiLowercase[code] else decodingPetsciiUppercase[code]
|
||||||
// TODO this CharConversionException can never occur?? also clean up ICompilationTarget.decodeString?
|
|
||||||
if(lowercase) decodingPetsciiUppercase[code] else decodingPetsciiLowercase[code]
|
|
||||||
}
|
|
||||||
}.joinToString("")
|
}.joinToString("")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun encodeScreencode(text: String, lowercase: Boolean = false): Result<List<Short>, CharConversionException> {
|
fun encodeScreencode(text: String, lowercase: Boolean = false): Result<List<UByte>, CharConversionException> {
|
||||||
fun encodeChar(chr3: Char, lowercase: Boolean): Short {
|
fun encodeChar(chr3: Char, lowercase: Boolean): UByte {
|
||||||
val chr = replaceSpecial(chr3)
|
val chr = replaceSpecial(chr3)
|
||||||
val screencode = if(lowercase) encodingScreencodeLowercase[chr] else encodingScreencodeUppercase[chr]
|
val screencode = if(lowercase) encodingScreencodeLowercase[chr] else encodingScreencodeUppercase[chr]
|
||||||
return screencode?.toShort() ?: when (chr) {
|
return screencode?.toUByte() ?: when (chr) {
|
||||||
'\u0000' -> 0.toShort()
|
'\u0000' -> 0u
|
||||||
in '\u8000'..'\u80ff' -> {
|
in '\u8000'..'\u80ff' -> {
|
||||||
// special case: take the lower 8 bit hex value directly
|
// special case: take the lower 8 bit hex value directly
|
||||||
(chr.code - 0x8000).toShort()
|
(chr.code - 0x8000).toUByte()
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val case = if (lowercase) "lower" else "upper"
|
val case = if (lowercase) "lower" else "upper"
|
||||||
@ -1137,48 +1134,46 @@ object Petscii {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun decodeScreencode(screencode: Iterable<Short>, lowercase: Boolean = false): String {
|
fun decodeScreencode(screencode: Iterable<UByte>, lowercase: Boolean = false): String {
|
||||||
return screencode.map {
|
return screencode.map {
|
||||||
val code = it.toInt()
|
val code = it.toInt()
|
||||||
try {
|
if(code<0 || code>= decodingScreencodeLowercase.size)
|
||||||
if (lowercase) decodingScreencodeLowercase[code] else decodingScreencodeUppercase[code]
|
throw CharConversionException("screencode $code out of range 0..${decodingScreencodeLowercase.size-1}")
|
||||||
} catch (x: CharConversionException) {
|
if (lowercase) decodingScreencodeLowercase[code] else decodingScreencodeUppercase[code]
|
||||||
// TODO this CharConversionException can never occur?? also clean up ICompilationTarget.decodeString?
|
|
||||||
if (lowercase) decodingScreencodeUppercase[code] else decodingScreencodeLowercase[code]
|
|
||||||
}
|
|
||||||
}.joinToString("")
|
}.joinToString("")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun petscii2scr(petscii_code: Short, inverseVideo: Boolean): Result<Short, CharConversionException> {
|
fun petscii2scr(petscii_code: UByte, inverseVideo: Boolean): Result<UByte, CharConversionException> {
|
||||||
val code = when {
|
val code: UInt = when {
|
||||||
petscii_code <= 0x1f -> petscii_code + 128
|
petscii_code <= 0x1fu -> petscii_code + 128u
|
||||||
petscii_code <= 0x3f -> petscii_code.toInt()
|
petscii_code <= 0x3fu -> petscii_code.toUInt()
|
||||||
petscii_code <= 0x5f -> petscii_code - 64
|
petscii_code <= 0x5fu -> petscii_code - 64u
|
||||||
petscii_code <= 0x7f -> petscii_code - 32
|
petscii_code <= 0x7fu -> petscii_code - 32u
|
||||||
petscii_code <= 0x9f -> petscii_code + 64
|
petscii_code <= 0x9fu -> petscii_code + 64u
|
||||||
petscii_code <= 0xbf -> petscii_code - 64
|
petscii_code <= 0xbfu -> petscii_code - 64u
|
||||||
petscii_code <= 0xfe -> petscii_code - 128
|
petscii_code <= 0xfeu -> petscii_code - 128u
|
||||||
petscii_code == 255.toShort() -> 95
|
petscii_code == 255.toUByte() -> 95u
|
||||||
else -> return Err(CharConversionException("petscii code out of range"))
|
else -> return Err(CharConversionException("petscii code out of range"))
|
||||||
}
|
}
|
||||||
if(inverseVideo)
|
if(inverseVideo) {
|
||||||
return Ok((code or 0x80).toShort())
|
return Ok((code or 0x80u).toUByte())
|
||||||
return Ok(code.toShort())
|
}
|
||||||
|
return Ok(code.toUByte())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun scr2petscii(screencode: Short): Result<Short, CharConversionException> {
|
fun scr2petscii(screencode: UByte): Result<UByte, CharConversionException> {
|
||||||
val petscii = when {
|
val petscii: UInt = when {
|
||||||
screencode <= 0x1f -> screencode + 64
|
screencode <= 0x1fu -> screencode + 64u
|
||||||
screencode <= 0x3f -> screencode.toInt()
|
screencode <= 0x3fu -> screencode.toUInt()
|
||||||
screencode <= 0x5d -> screencode +123
|
screencode <= 0x5du -> screencode +123u
|
||||||
screencode == 0x5e.toShort() -> 255
|
screencode == 0x5e.toUByte() -> 255u
|
||||||
screencode == 0x5f.toShort() -> 223
|
screencode == 0x5f.toUByte() -> 223u
|
||||||
screencode <= 0x7f -> screencode + 64
|
screencode <= 0x7fu -> screencode + 64u
|
||||||
screencode <= 0xbf -> screencode - 128
|
screencode <= 0xbfu -> screencode - 128u
|
||||||
screencode <= 0xfe -> screencode - 64
|
screencode <= 0xfeu -> screencode - 64u
|
||||||
screencode == 255.toShort() -> 191
|
screencode == 255.toUByte() -> 191u
|
||||||
else -> return Err(CharConversionException("screencode out of range"))
|
else -> return Err(CharConversionException("screencode out of range"))
|
||||||
}
|
}
|
||||||
return Ok(petscii.toShort())
|
return Ok(petscii.toUByte())
|
||||||
}
|
}
|
||||||
}
|
}
|
3201
codeGeneration/src/prog8/compiler/target/cpu6502/codegen/AsmGen.kt
Normal file
3201
codeGeneration/src/prog8/compiler/target/cpu6502/codegen/AsmGen.kt
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,446 @@
|
|||||||
|
package prog8.compiler.target.cpu6502.codegen
|
||||||
|
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.VarDeclType
|
||||||
|
import prog8.ast.expressions.NumericLiteralValue
|
||||||
|
import prog8.ast.statements.VarDecl
|
||||||
|
import prog8.compilerinterface.IMachineDefinition
|
||||||
|
|
||||||
|
|
||||||
|
// note: see https://wiki.nesdev.org/w/index.php/6502_assembly_optimisations
|
||||||
|
|
||||||
|
|
||||||
|
fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefinition, program: Program): Int {
|
||||||
|
|
||||||
|
var numberOfOptimizations = 0
|
||||||
|
|
||||||
|
var linesByFour = getLinesBy(lines, 4)
|
||||||
|
|
||||||
|
var mods = optimizeUselessStackByteWrites(linesByFour)
|
||||||
|
if(mods.isNotEmpty()) {
|
||||||
|
apply(mods, lines)
|
||||||
|
linesByFour = getLinesBy(lines, 4)
|
||||||
|
numberOfOptimizations++
|
||||||
|
}
|
||||||
|
|
||||||
|
mods = optimizeIncDec(linesByFour)
|
||||||
|
if(mods.isNotEmpty()) {
|
||||||
|
apply(mods, lines)
|
||||||
|
linesByFour = getLinesBy(lines, 4)
|
||||||
|
numberOfOptimizations++
|
||||||
|
}
|
||||||
|
|
||||||
|
mods = optimizeCmpSequence(linesByFour)
|
||||||
|
if(mods.isNotEmpty()) {
|
||||||
|
apply(mods, lines)
|
||||||
|
linesByFour = getLinesBy(lines, 4)
|
||||||
|
numberOfOptimizations++
|
||||||
|
}
|
||||||
|
|
||||||
|
mods = optimizeStoreLoadSame(linesByFour, machine, program)
|
||||||
|
if(mods.isNotEmpty()) {
|
||||||
|
apply(mods, lines)
|
||||||
|
linesByFour = getLinesBy(lines, 4)
|
||||||
|
numberOfOptimizations++
|
||||||
|
}
|
||||||
|
|
||||||
|
mods= optimizeJsrRts(linesByFour)
|
||||||
|
if(mods.isNotEmpty()) {
|
||||||
|
apply(mods, lines)
|
||||||
|
linesByFour = getLinesBy(lines, 4)
|
||||||
|
numberOfOptimizations++
|
||||||
|
}
|
||||||
|
|
||||||
|
var linesByFourteen = getLinesBy(lines, 14)
|
||||||
|
mods = optimizeSameAssignments(linesByFourteen, machine, program)
|
||||||
|
if(mods.isNotEmpty()) {
|
||||||
|
apply(mods, lines)
|
||||||
|
linesByFourteen = getLinesBy(lines, 14)
|
||||||
|
numberOfOptimizations++
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO more assembly optimizations
|
||||||
|
|
||||||
|
return numberOfOptimizations
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Modification(val lineIndex: Int, val remove: Boolean, val replacement: String?)
|
||||||
|
|
||||||
|
private fun apply(modifications: List<Modification>, lines: MutableList<String>) {
|
||||||
|
for (modification in modifications.sortedBy { it.lineIndex }.reversed()) {
|
||||||
|
if(modification.remove)
|
||||||
|
lines.removeAt(modification.lineIndex)
|
||||||
|
else
|
||||||
|
lines[modification.lineIndex] = modification.replacement!!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getLinesBy(lines: MutableList<String>, windowSize: Int) =
|
||||||
|
// all lines (that aren't empty or comments) in sliding windows of certain size
|
||||||
|
lines.withIndex().filter { it.value.isNotBlank() && !it.value.trimStart().startsWith(';') }.windowed(windowSize, partialWindows = false)
|
||||||
|
|
||||||
|
private fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||||
|
// when statement (on bytes) generates a sequence of:
|
||||||
|
// lda $ce01,x
|
||||||
|
// cmp #$20
|
||||||
|
// beq check_prog8_s72choice_32
|
||||||
|
// lda $ce01,x
|
||||||
|
// cmp #$21
|
||||||
|
// beq check_prog8_s73choice_33
|
||||||
|
// the repeated lda can be removed
|
||||||
|
val mods = mutableListOf<Modification>()
|
||||||
|
for(lines in linesByFour) {
|
||||||
|
if(lines[0].value.trim()=="lda P8ESTACK_LO+1,x" &&
|
||||||
|
lines[1].value.trim().startsWith("cmp ") &&
|
||||||
|
lines[2].value.trim().startsWith("beq ") &&
|
||||||
|
lines[3].value.trim()=="lda P8ESTACK_LO+1,x") {
|
||||||
|
mods.add(Modification(lines[3].index, true, null)) // remove the second lda
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mods
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||||
|
// sta on stack, dex, inx, lda from stack -> eliminate this useless stack byte write
|
||||||
|
// this is a lot harder for word values because the instruction sequence varies.
|
||||||
|
val mods = mutableListOf<Modification>()
|
||||||
|
for(lines in linesByFour) {
|
||||||
|
if(lines[0].value.trim()=="sta P8ESTACK_LO,x" &&
|
||||||
|
lines[1].value.trim()=="dex" &&
|
||||||
|
lines[2].value.trim()=="inx" &&
|
||||||
|
lines[3].value.trim()=="lda P8ESTACK_LO,x") {
|
||||||
|
mods.add(Modification(lines[1].index, true, null))
|
||||||
|
mods.add(Modification(lines[2].index, true, null))
|
||||||
|
mods.add(Modification(lines[3].index, true, null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mods
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>, machine: IMachineDefinition, program: Program): List<Modification> {
|
||||||
|
|
||||||
|
// Optimize sequential assignments of the same value to various targets (bytes, words, floats)
|
||||||
|
// the float one is the one that requires 2*7=14 lines of code to check...
|
||||||
|
// The better place to do this is in the Compiler instead and never create these types of assembly, but hey
|
||||||
|
|
||||||
|
val mods = mutableListOf<Modification>()
|
||||||
|
for (lines in linesByFourteen) {
|
||||||
|
val first = lines[0].value.trimStart()
|
||||||
|
val second = lines[1].value.trimStart()
|
||||||
|
val third = lines[2].value.trimStart()
|
||||||
|
val fourth = lines[3].value.trimStart()
|
||||||
|
val fifth = lines[4].value.trimStart()
|
||||||
|
val sixth = lines[5].value.trimStart()
|
||||||
|
val seventh = lines[6].value.trimStart()
|
||||||
|
val eighth = lines[7].value.trimStart()
|
||||||
|
|
||||||
|
if(first.startsWith("lda") && second.startsWith("ldy") && third.startsWith("sta") && fourth.startsWith("sty") &&
|
||||||
|
fifth.startsWith("lda") && sixth.startsWith("ldy") && seventh.startsWith("sta") && eighth.startsWith("sty")) {
|
||||||
|
val firstvalue = first.substring(4)
|
||||||
|
val secondvalue = second.substring(4)
|
||||||
|
val thirdvalue = fifth.substring(4)
|
||||||
|
val fourthvalue = sixth.substring(4)
|
||||||
|
if(firstvalue==thirdvalue && secondvalue==fourthvalue) {
|
||||||
|
// lda/ldy sta/sty twice the same word --> remove second lda/ldy pair (fifth and sixth lines)
|
||||||
|
val address1 = getAddressArg(first, program)
|
||||||
|
val address2 = getAddressArg(second, program)
|
||||||
|
if(address1==null || address2==null || (!machine.isIOAddress(address1) && !machine.isIOAddress(address2))) {
|
||||||
|
mods.add(Modification(lines[4].index, true, null))
|
||||||
|
mods.add(Modification(lines[5].index, true, null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(first.startsWith("lda") && second.startsWith("sta") && third.startsWith("lda") && fourth.startsWith("sta")) {
|
||||||
|
val firstvalue = first.substring(4)
|
||||||
|
val secondvalue = third.substring(4)
|
||||||
|
if(firstvalue==secondvalue) {
|
||||||
|
// lda value / sta ? / lda same-value / sta ? -> remove second lda (third line)
|
||||||
|
val address = getAddressArg(first, program)
|
||||||
|
if(address==null || !machine.isIOAddress(address))
|
||||||
|
mods.add(Modification(lines[2].index, true, null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(first.startsWith("lda") && second.startsWith("ldy") && third.startsWith("sta") && fourth.startsWith("sty") &&
|
||||||
|
fifth.startsWith("lda") && sixth.startsWith("ldy") &&
|
||||||
|
(seventh.startsWith("jsr floats.copy_float") || seventh.startsWith("jsr cx16flt.copy_float"))) {
|
||||||
|
|
||||||
|
val nineth = lines[8].value.trimStart()
|
||||||
|
val tenth = lines[9].value.trimStart()
|
||||||
|
val eleventh = lines[10].value.trimStart()
|
||||||
|
val twelveth = lines[11].value.trimStart()
|
||||||
|
val thirteenth = lines[12].value.trimStart()
|
||||||
|
val fourteenth = lines[13].value.trimStart()
|
||||||
|
|
||||||
|
if(eighth.startsWith("lda") && nineth.startsWith("ldy") && tenth.startsWith("sta") && eleventh.startsWith("sty") &&
|
||||||
|
twelveth.startsWith("lda") && thirteenth.startsWith("ldy") &&
|
||||||
|
(fourteenth.startsWith("jsr floats.copy_float") || fourteenth.startsWith("jsr cx16flt.copy_float"))) {
|
||||||
|
|
||||||
|
if(first.substring(4) == eighth.substring(4) && second.substring(4)==nineth.substring(4)) {
|
||||||
|
// identical float init
|
||||||
|
mods.add(Modification(lines[7].index, true, null))
|
||||||
|
mods.add(Modification(lines[8].index, true, null))
|
||||||
|
mods.add(Modification(lines[9].index, true, null))
|
||||||
|
mods.add(Modification(lines[10].index, true, null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var overlappingMods = false
|
||||||
|
/*
|
||||||
|
sta prog8_lib.retval_intermX ; remove
|
||||||
|
sty prog8_lib.retval_intermY ; remove
|
||||||
|
lda prog8_lib.retval_intermX ; remove
|
||||||
|
ldy prog8_lib.retval_intermY ; remove
|
||||||
|
sta A1
|
||||||
|
sty A2
|
||||||
|
*/
|
||||||
|
if(first.startsWith("st") && second.startsWith("st")
|
||||||
|
&& third.startsWith("ld") && fourth.startsWith("ld")
|
||||||
|
&& fifth.startsWith("st") && sixth.startsWith("st")) {
|
||||||
|
val reg1 = first[2]
|
||||||
|
val reg2 = second[2]
|
||||||
|
val reg3 = third[2]
|
||||||
|
val reg4 = fourth[2]
|
||||||
|
val reg5 = fifth[2]
|
||||||
|
val reg6 = sixth[2]
|
||||||
|
if (reg1 == reg3 && reg1 == reg5 && reg2 == reg4 && reg2 == reg6) {
|
||||||
|
val firstvalue = first.substring(4)
|
||||||
|
val secondvalue = second.substring(4)
|
||||||
|
val thirdvalue = third.substring(4)
|
||||||
|
val fourthvalue = fourth.substring(4)
|
||||||
|
if(firstvalue.contains("prog8_lib.retval_interm") && secondvalue.contains("prog8_lib.retval_interm")
|
||||||
|
&& firstvalue==thirdvalue && secondvalue==fourthvalue) {
|
||||||
|
mods.add(Modification(lines[0].index, true, null))
|
||||||
|
mods.add(Modification(lines[1].index, true, null))
|
||||||
|
mods.add(Modification(lines[2].index, true, null))
|
||||||
|
mods.add(Modification(lines[3].index, true, null))
|
||||||
|
overlappingMods = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
sta A1
|
||||||
|
sty A2
|
||||||
|
lda A1 ; can be removed
|
||||||
|
ldy A2 ; can be removed if not followed by a branch instuction
|
||||||
|
*/
|
||||||
|
if(!overlappingMods && first.startsWith("st") && second.startsWith("st")
|
||||||
|
&& third.startsWith("ld") && fourth.startsWith("ld")) {
|
||||||
|
val reg1 = first[2]
|
||||||
|
val reg2 = second[2]
|
||||||
|
val reg3 = third[2]
|
||||||
|
val reg4 = fourth[2]
|
||||||
|
if(reg1==reg3 && reg2==reg4) {
|
||||||
|
val firstvalue = first.substring(4)
|
||||||
|
val secondvalue = second.substring(4)
|
||||||
|
val thirdvalue = third.substring(4)
|
||||||
|
val fourthvalue = fourth.substring(4)
|
||||||
|
if(firstvalue==thirdvalue && secondvalue == fourthvalue) {
|
||||||
|
val address = getAddressArg(first, program)
|
||||||
|
if(address==null || !machine.isIOAddress(address)) {
|
||||||
|
overlappingMods = true
|
||||||
|
mods.add(Modification(lines[2].index, true, null))
|
||||||
|
if (!fifth.startsWith('b'))
|
||||||
|
mods.add(Modification(lines[3].index, true, null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
sta A1
|
||||||
|
sty A2
|
||||||
|
lda A1 ; can be removed if not followed by a branch instruction
|
||||||
|
*/
|
||||||
|
if(!overlappingMods && first.startsWith("st") && second.startsWith("st")
|
||||||
|
&& third.startsWith("ld") && !fourth.startsWith("b")) {
|
||||||
|
val reg1 = first[2]
|
||||||
|
val reg3 = third[2]
|
||||||
|
if(reg1==reg3) {
|
||||||
|
val firstvalue = first.substring(4)
|
||||||
|
val thirdvalue = third.substring(4)
|
||||||
|
if(firstvalue==thirdvalue) {
|
||||||
|
val address = getAddressArg(first, program)
|
||||||
|
if(address==null || !machine.isIOAddress(address)) {
|
||||||
|
overlappingMods = true
|
||||||
|
mods.add(Modification(lines[2].index, true, null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
sta A1
|
||||||
|
ldy A1 ; make tay
|
||||||
|
sta A1 ; remove
|
||||||
|
*/
|
||||||
|
if(!overlappingMods && first.startsWith("sta") && second.startsWith("ld")
|
||||||
|
&& third.startsWith("sta") && second.length>4) {
|
||||||
|
val firstvalue = first.substring(4)
|
||||||
|
val secondvalue = second.substring(4)
|
||||||
|
val thirdvalue = third.substring(4)
|
||||||
|
if(firstvalue==secondvalue && firstvalue==thirdvalue) {
|
||||||
|
val address = getAddressArg(first, program)
|
||||||
|
if(address==null || !machine.isIOAddress(address)) {
|
||||||
|
overlappingMods = true
|
||||||
|
val reg2 = second[2]
|
||||||
|
mods.add(Modification(lines[1].index, false, " ta$reg2"))
|
||||||
|
mods.add(Modification(lines[2].index, true, null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
sta A
|
||||||
|
sta A
|
||||||
|
*/
|
||||||
|
if(!overlappingMods && first.startsWith("st") && second.startsWith("st")) {
|
||||||
|
if(first[2]==second[2]) {
|
||||||
|
val firstvalue = first.substring(4)
|
||||||
|
val secondvalue = second.substring(4)
|
||||||
|
if(firstvalue==secondvalue) {
|
||||||
|
val address = getAddressArg(first, program)
|
||||||
|
if(address==null || !machine.isIOAddress(address)) {
|
||||||
|
overlappingMods = true
|
||||||
|
mods.add(Modification(lines[1].index, true, null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mods
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>, machine: IMachineDefinition, program: Program): List<Modification> {
|
||||||
|
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can OFTEN be eliminated
|
||||||
|
val mods = mutableListOf<Modification>()
|
||||||
|
for (lines in linesByFour) {
|
||||||
|
val first = lines[1].value.trimStart()
|
||||||
|
val second = lines[2].value.trimStart()
|
||||||
|
|
||||||
|
if ((first.startsWith("sta ") && second.startsWith("lda ")) ||
|
||||||
|
(first.startsWith("stx ") && second.startsWith("ldx ")) ||
|
||||||
|
(first.startsWith("sty ") && second.startsWith("ldy ")) ||
|
||||||
|
(first.startsWith("lda ") && second.startsWith("lda ")) ||
|
||||||
|
(first.startsWith("ldy ") && second.startsWith("ldy ")) ||
|
||||||
|
(first.startsWith("ldx ") && second.startsWith("ldx ")) ||
|
||||||
|
(first.startsWith("sta ") && second.startsWith("lda ")) ||
|
||||||
|
(first.startsWith("sty ") && second.startsWith("ldy ")) ||
|
||||||
|
(first.startsWith("stx ") && second.startsWith("ldx "))
|
||||||
|
) {
|
||||||
|
val third = lines[3].value.trimStart()
|
||||||
|
val attemptRemove =
|
||||||
|
if(third.startsWith("b")) {
|
||||||
|
// a branch instruction follows, we can only remove the load instruction if
|
||||||
|
// another load instruction of the same register precedes the store instruction
|
||||||
|
// (otherwise wrong cpu flags are used)
|
||||||
|
val loadinstruction = second.substring(0, 3)
|
||||||
|
lines[0].value.trimStart().startsWith(loadinstruction)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// no branch instruction follows, we can remove the load instruction
|
||||||
|
val address = getAddressArg(lines[2].value, program)
|
||||||
|
address==null || !machine.isIOAddress(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
if(attemptRemove) {
|
||||||
|
val firstLoc = first.substring(4).trimStart()
|
||||||
|
val secondLoc = second.substring(4).trimStart()
|
||||||
|
if (firstLoc == secondLoc)
|
||||||
|
mods.add(Modification(lines[2].index, true, null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(first=="pha" && second=="pla" ||
|
||||||
|
first=="phx" && second=="plx" ||
|
||||||
|
first=="phy" && second=="ply" ||
|
||||||
|
first=="php" && second=="plp") {
|
||||||
|
mods.add(Modification(lines[1].index, true, null))
|
||||||
|
mods.add(Modification(lines[2].index, true, null))
|
||||||
|
} else if(first=="pha" && second=="plx") {
|
||||||
|
mods.add(Modification(lines[1].index, true, null))
|
||||||
|
mods.add(Modification(lines[2].index, false, " tax"))
|
||||||
|
} else if(first=="pha" && second=="ply") {
|
||||||
|
mods.add(Modification(lines[1].index, true, null))
|
||||||
|
mods.add(Modification(lines[2].index, false, " tay"))
|
||||||
|
} else if(first=="phx" && second=="pla") {
|
||||||
|
mods.add(Modification(lines[1].index, true, null))
|
||||||
|
mods.add(Modification(lines[2].index, false, " txa"))
|
||||||
|
} else if(first=="phx" && second=="ply") {
|
||||||
|
mods.add(Modification(lines[1].index, true, null))
|
||||||
|
mods.add(Modification(lines[2].index, false, " txy"))
|
||||||
|
} else if(first=="phy" && second=="pla") {
|
||||||
|
mods.add(Modification(lines[1].index, true, null))
|
||||||
|
mods.add(Modification(lines[2].index, false, " tya"))
|
||||||
|
} else if(first=="phy" && second=="plx") {
|
||||||
|
mods.add(Modification(lines[1].index, true, null))
|
||||||
|
mods.add(Modification(lines[2].index, false, " tyx"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mods
|
||||||
|
}
|
||||||
|
|
||||||
|
private val identifierRegex = Regex("""^([a-zA-Z_$][a-zA-Z\d_\.$]*)""")
|
||||||
|
|
||||||
|
private fun getAddressArg(line: String, program: Program): UInt? {
|
||||||
|
val loadArg = line.trimStart().substring(3).trim()
|
||||||
|
return when {
|
||||||
|
loadArg.startsWith('$') -> loadArg.substring(1).toUIntOrNull(16)
|
||||||
|
loadArg.startsWith('%') -> loadArg.substring(1).toUIntOrNull(2)
|
||||||
|
loadArg.startsWith('#') -> null
|
||||||
|
loadArg.startsWith('(') -> null
|
||||||
|
loadArg[0].isLetter() -> {
|
||||||
|
val identMatch = identifierRegex.find(loadArg)
|
||||||
|
if(identMatch!=null) {
|
||||||
|
val identifier = identMatch.value
|
||||||
|
val decl = program.toplevelModule.lookup(identifier.split(".")) as? VarDecl
|
||||||
|
if(decl!=null) {
|
||||||
|
when(decl.type){
|
||||||
|
VarDeclType.VAR -> null
|
||||||
|
VarDeclType.CONST,
|
||||||
|
VarDeclType.MEMORY -> (decl.value as NumericLiteralValue).number.toUInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else null
|
||||||
|
} else null
|
||||||
|
}
|
||||||
|
else -> loadArg.substring(1).toUIntOrNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun optimizeIncDec(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||||
|
// sometimes, iny+dey / inx+dex / dey+iny / dex+inx sequences are generated, these can be eliminated.
|
||||||
|
val mods = mutableListOf<Modification>()
|
||||||
|
for (lines in linesByFour) {
|
||||||
|
val first = lines[0].value
|
||||||
|
val second = lines[1].value
|
||||||
|
if ((" iny" in first || "\tiny" in first) && (" dey" in second || "\tdey" in second)
|
||||||
|
|| (" inx" in first || "\tinx" in first) && (" dex" in second || "\tdex" in second)
|
||||||
|
|| (" ina" in first || "\tina" in first) && (" dea" in second || "\tdea" in second)
|
||||||
|
|| (" inc a" in first || "\tinc a" in first) && (" dec a" in second || "\tdec a" in second)
|
||||||
|
|| (" dey" in first || "\tdey" in first) && (" iny" in second || "\tiny" in second)
|
||||||
|
|| (" dex" in first || "\tdex" in first) && (" inx" in second || "\tinx" in second)
|
||||||
|
|| (" dea" in first || "\tdea" in first) && (" ina" in second || "\tina" in second)
|
||||||
|
|| (" dec a" in first || "\tdec a" in first) && (" inc a" in second || "\tinc a" in second)) {
|
||||||
|
mods.add(Modification(lines[0].index, true, null))
|
||||||
|
mods.add(Modification(lines[1].index, true, null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mods
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun optimizeJsrRts(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||||
|
// jsr Sub + rts -> jmp Sub
|
||||||
|
val mods = mutableListOf<Modification>()
|
||||||
|
for (lines in linesByFour) {
|
||||||
|
val first = lines[0].value
|
||||||
|
val second = lines[1].value
|
||||||
|
if ((" jsr" in first || "\tjsr" in first ) && (" rts" in second || "\trts" in second)) {
|
||||||
|
mods += Modification(lines[0].index, false, lines[0].value.replace("jsr", "jmp"))
|
||||||
|
mods += Modification(lines[1].index, true, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mods
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
package prog8.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) }
|
||||||
|
}
|
@ -10,12 +10,12 @@ import prog8.ast.statements.DirectMemoryWrite
|
|||||||
import prog8.ast.statements.FunctionCallStatement
|
import prog8.ast.statements.FunctionCallStatement
|
||||||
import prog8.ast.statements.Subroutine
|
import prog8.ast.statements.Subroutine
|
||||||
import prog8.ast.toHex
|
import prog8.ast.toHex
|
||||||
import prog8.compiler.AssemblyError
|
import prog8.compiler.target.AssemblyError
|
||||||
import prog8.compiler.functions.FSignature
|
|
||||||
import prog8.compiler.target.CpuType
|
|
||||||
import prog8.compiler.target.Cx16Target
|
import prog8.compiler.target.Cx16Target
|
||||||
import prog8.compiler.target.cpu6502.codegen.assignment.*
|
import prog8.compiler.target.cpu6502.codegen.assignment.*
|
||||||
import prog8.compiler.target.subroutineFloatEvalResultVar2
|
import prog8.compilerinterface.CpuType
|
||||||
|
import prog8.compilerinterface.FSignature
|
||||||
|
import prog8.compilerinterface.subroutineFloatEvalResultVar2
|
||||||
|
|
||||||
internal class BuiltinFunctionsAsmGen(private val program: Program, private val asmgen: AsmGen, private val assignAsmGen: AssignmentAsmGen) {
|
internal class BuiltinFunctionsAsmGen(private val program: Program, private val asmgen: AsmGen, private val assignAsmGen: AssignmentAsmGen) {
|
||||||
|
|
||||||
@ -46,7 +46,9 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
"sum" -> funcSum(fcall, resultToStack, resultRegister, sscope)
|
"sum" -> funcSum(fcall, resultToStack, resultRegister, sscope)
|
||||||
"any", "all" -> funcAnyAll(fcall, func, resultToStack, resultRegister, sscope)
|
"any", "all" -> funcAnyAll(fcall, func, resultToStack, resultRegister, sscope)
|
||||||
"sin8", "sin8u", "sin16", "sin16u",
|
"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)
|
"sgn" -> funcSgn(fcall, func, resultToStack, resultRegister, sscope)
|
||||||
"sin", "cos", "tan", "atan",
|
"sin", "cos", "tan", "atan",
|
||||||
"ln", "log2", "sqrt", "rad",
|
"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 @()")
|
"peek" -> throw AssemblyError("peek() should have been replaced by @()")
|
||||||
"pokew" -> funcPokeW(fcall)
|
"pokew" -> funcPokeW(fcall)
|
||||||
"poke" -> throw AssemblyError("poke() should have been replaced by @()")
|
"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)
|
"cmp" -> funcCmp(fcall)
|
||||||
"callfar" -> funcCallFar(fcall)
|
"callfar" -> funcCallFar(fcall)
|
||||||
"callrom" -> funcCallRom(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) {
|
private fun funcCallFar(fcall: IFunctionCall) {
|
||||||
if(asmgen.options.compTarget !is Cx16Target)
|
if(asmgen.options.compTarget !is Cx16Target)
|
||||||
throw AssemblyError("callfar only works on cx16 target at this time")
|
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")
|
throw AssemblyError("callfar done on bank 0 which is reserved for the kernal")
|
||||||
|
|
||||||
val argAddrArg = fcall.args[2]
|
val argAddrArg = fcall.args[2]
|
||||||
if(argAddrArg.constValue(program)?.number == 0) {
|
if(argAddrArg.constValue(program)?.number == 0.0) {
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
jsr cx16.jsrfar
|
jsr cx16.jsrfar
|
||||||
.word ${address.toHex()}
|
.word ${address.toHex()}
|
||||||
@ -132,7 +302,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
throw AssemblyError("callrom bank must be <32")
|
throw AssemblyError("callrom bank must be <32")
|
||||||
|
|
||||||
val argAddrArg = fcall.args[2]
|
val argAddrArg = fcall.args[2]
|
||||||
if(argAddrArg.constValue(program)?.number == 0) {
|
if(argAddrArg.constValue(program)?.number == 0.0) {
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda $01
|
lda $01
|
||||||
pha
|
pha
|
||||||
@ -248,7 +418,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
throw AssemblyError("should not discard result of memory allocation at $fcall")
|
throw AssemblyError("should not discard result of memory allocation at $fcall")
|
||||||
val nameRef = fcall.args[0] as IdentifierReference
|
val nameRef = fcall.args[0] as IdentifierReference
|
||||||
val name = (nameRef.targetVarDecl(program)!!.value as StringLiteralValue).value
|
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]
|
val existingSize = asmgen.slabs[name]
|
||||||
if(existingSize!=null && existingSize!=size)
|
if(existingSize!=null && existingSize!=size)
|
||||||
@ -261,7 +431,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
if(resultToStack)
|
if(resultToStack)
|
||||||
AsmAssignTarget(TargetStorageKind.STACK, program, asmgen, DataType.UWORD, null)
|
AsmAssignTarget(TargetStorageKind.STACK, program, asmgen, DataType.UWORD, null)
|
||||||
else
|
else
|
||||||
AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, null, program, asmgen)
|
AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, null, program, asmgen)
|
||||||
val assign = AsmAssignment(src, target, false, program.memsizer, fcall.position)
|
val assign = AsmAssignment(src, target, false, program.memsizer, fcall.position)
|
||||||
asmgen.translateNormalAssignment(assign)
|
asmgen.translateNormalAssignment(assign)
|
||||||
asmgen.slabs[name] = size
|
asmgen.slabs[name] = size
|
||||||
@ -273,7 +443,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
asmgen.out(" jsr prog8_lib.func_sqrt16_stack")
|
asmgen.out(" jsr prog8_lib.func_sqrt16_stack")
|
||||||
else {
|
else {
|
||||||
asmgen.out(" jsr prog8_lib.func_sqrt16_into_A")
|
asmgen.out(" jsr prog8_lib.func_sqrt16_into_A")
|
||||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
|
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,13 +453,13 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
asmgen.out(" jsr prog8_lib.func_${func.name}_stack")
|
asmgen.out(" jsr prog8_lib.func_${func.name}_stack")
|
||||||
else
|
else
|
||||||
when(func.name) {
|
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")
|
asmgen.out(" jsr prog8_lib.func_${func.name}_into_A")
|
||||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.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")
|
asmgen.out(" jsr prog8_lib.func_${func.name}_into_AY")
|
||||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
|
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -578,7 +748,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
asmgen.out(" jsr floats.func_${func.name}_stack")
|
asmgen.out(" jsr floats.func_${func.name}_stack")
|
||||||
else {
|
else {
|
||||||
asmgen.out(" jsr floats.func_${func.name}_fac1")
|
asmgen.out(" jsr floats.func_${func.name}_fac1")
|
||||||
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, scope, program, asmgen))
|
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, true, scope, program, asmgen))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -603,7 +773,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
DataType.FLOAT -> asmgen.out(" jsr floats.func_sign_f_into_A")
|
DataType.FLOAT -> asmgen.out(" jsr floats.func_sign_f_into_A")
|
||||||
else -> throw AssemblyError("weird type $dt")
|
else -> throw AssemblyError("weird type $dt")
|
||||||
}
|
}
|
||||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
|
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -624,7 +794,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${function.name}_f_into_A")
|
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${function.name}_f_into_A")
|
||||||
else -> throw AssemblyError("weird type $dt")
|
else -> throw AssemblyError("weird type $dt")
|
||||||
}
|
}
|
||||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
|
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -644,23 +814,23 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
when (dt.getOr(DataType.UNDEFINED)) {
|
when (dt.getOr(DataType.UNDEFINED)) {
|
||||||
DataType.ARRAY_UB, DataType.STR -> {
|
DataType.ARRAY_UB, DataType.STR -> {
|
||||||
asmgen.out(" jsr prog8_lib.func_${function.name}_ub_into_A")
|
asmgen.out(" jsr prog8_lib.func_${function.name}_ub_into_A")
|
||||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
|
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
|
||||||
}
|
}
|
||||||
DataType.ARRAY_B -> {
|
DataType.ARRAY_B -> {
|
||||||
asmgen.out(" jsr prog8_lib.func_${function.name}_b_into_A")
|
asmgen.out(" jsr prog8_lib.func_${function.name}_b_into_A")
|
||||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
|
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
|
||||||
}
|
}
|
||||||
DataType.ARRAY_UW -> {
|
DataType.ARRAY_UW -> {
|
||||||
asmgen.out(" jsr prog8_lib.func_${function.name}_uw_into_AY")
|
asmgen.out(" jsr prog8_lib.func_${function.name}_uw_into_AY")
|
||||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
|
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
|
||||||
}
|
}
|
||||||
DataType.ARRAY_W -> {
|
DataType.ARRAY_W -> {
|
||||||
asmgen.out(" jsr prog8_lib.func_${function.name}_w_into_AY")
|
asmgen.out(" jsr prog8_lib.func_${function.name}_w_into_AY")
|
||||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
|
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
|
||||||
}
|
}
|
||||||
DataType.ARRAY_F -> {
|
DataType.ARRAY_F -> {
|
||||||
asmgen.out(" jsr floats.func_${function.name}_f_fac1")
|
asmgen.out(" jsr floats.func_${function.name}_f_fac1")
|
||||||
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, scope, program, asmgen))
|
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, true, scope, program, asmgen))
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("weird type $dt")
|
else -> throw AssemblyError("weird type $dt")
|
||||||
}
|
}
|
||||||
@ -683,23 +853,23 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
when (dt.getOr(DataType.UNDEFINED)) {
|
when (dt.getOr(DataType.UNDEFINED)) {
|
||||||
DataType.ARRAY_UB, DataType.STR -> {
|
DataType.ARRAY_UB, DataType.STR -> {
|
||||||
asmgen.out(" jsr prog8_lib.func_sum_ub_into_AY")
|
asmgen.out(" jsr prog8_lib.func_sum_ub_into_AY")
|
||||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
|
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
|
||||||
}
|
}
|
||||||
DataType.ARRAY_B -> {
|
DataType.ARRAY_B -> {
|
||||||
asmgen.out(" jsr prog8_lib.func_sum_b_into_AY")
|
asmgen.out(" jsr prog8_lib.func_sum_b_into_AY")
|
||||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
|
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
|
||||||
}
|
}
|
||||||
DataType.ARRAY_UW -> {
|
DataType.ARRAY_UW -> {
|
||||||
asmgen.out(" jsr prog8_lib.func_sum_uw_into_AY")
|
asmgen.out(" jsr prog8_lib.func_sum_uw_into_AY")
|
||||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
|
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
|
||||||
}
|
}
|
||||||
DataType.ARRAY_W -> {
|
DataType.ARRAY_W -> {
|
||||||
asmgen.out(" jsr prog8_lib.func_sum_w_into_AY")
|
asmgen.out(" jsr prog8_lib.func_sum_w_into_AY")
|
||||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
|
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
|
||||||
}
|
}
|
||||||
DataType.ARRAY_F -> {
|
DataType.ARRAY_F -> {
|
||||||
asmgen.out(" jsr floats.func_sum_f_fac1")
|
asmgen.out(" jsr floats.func_sum_f_fac1")
|
||||||
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, scope, program, asmgen))
|
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, true, scope, program, asmgen))
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("weird type $dt")
|
else -> throw AssemblyError("weird type $dt")
|
||||||
}
|
}
|
||||||
@ -785,7 +955,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
&& (firstOffset is NumericLiteralValue || firstOffset is IdentifierReference || firstOffset is TypecastExpression)
|
&& (firstOffset is NumericLiteralValue || firstOffset is IdentifierReference || firstOffset is TypecastExpression)
|
||||||
&& (secondOffset is NumericLiteralValue || secondOffset is IdentifierReference || secondOffset is TypecastExpression)
|
&& (secondOffset is NumericLiteralValue || secondOffset is IdentifierReference || secondOffset is TypecastExpression)
|
||||||
) {
|
) {
|
||||||
val pointerVar = firstExpr.left as IdentifierReference
|
|
||||||
if(firstOffset is NumericLiteralValue && secondOffset is NumericLiteralValue) {
|
if(firstOffset is NumericLiteralValue && secondOffset is NumericLiteralValue) {
|
||||||
if(firstOffset!=secondOffset) {
|
if(firstOffset!=secondOffset) {
|
||||||
swapArrayValues(
|
swapArrayValues(
|
||||||
@ -876,21 +1045,23 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
asmgen.translateNormalAssignment(assignSecond)
|
asmgen.translateNormalAssignment(assignSecond)
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> {
|
DataType.FLOAT -> {
|
||||||
// via evaluation stack
|
// via temp variable and FAC1
|
||||||
asmgen.translateExpression(first)
|
asmgen.assignExpressionTo(first, AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.FLOAT, first.definingSubroutine, "floats.tempvar_swap_float"))
|
||||||
asmgen.translateExpression(second)
|
asmgen.assignExpressionTo(second, AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.FLOAT, null, register=RegisterOrPair.FAC1))
|
||||||
val assignFirst = AsmAssignment(
|
asmgen.translateNormalAssignment(
|
||||||
AsmAssignSource(SourceStorageKind.STACK, program, asmgen, DataType.FLOAT),
|
AsmAssignment(
|
||||||
|
AsmAssignSource(SourceStorageKind.REGISTER, program, asmgen, datatype, register = RegisterOrPair.FAC1),
|
||||||
targetFromExpr(first, datatype),
|
targetFromExpr(first, datatype),
|
||||||
false, program.memsizer, first.position
|
false, program.memsizer, first.position
|
||||||
|
)
|
||||||
)
|
)
|
||||||
val assignSecond = AsmAssignment(
|
asmgen.translateNormalAssignment(
|
||||||
AsmAssignSource(SourceStorageKind.STACK, program, asmgen, DataType.FLOAT),
|
AsmAssignment(
|
||||||
|
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, datatype, "floats.tempvar_swap_float"),
|
||||||
targetFromExpr(second, datatype),
|
targetFromExpr(second, datatype),
|
||||||
false, program.memsizer, second.position
|
false, program.memsizer, second.position
|
||||||
|
)
|
||||||
)
|
)
|
||||||
asmgen.translateNormalAssignment(assignFirst)
|
|
||||||
asmgen.translateNormalAssignment(assignSecond)
|
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("weird swap dt")
|
else -> throw AssemblyError("weird swap dt")
|
||||||
}
|
}
|
||||||
@ -1140,15 +1311,15 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
when (dt) {
|
when (dt) {
|
||||||
in ByteDatatypes -> {
|
in ByteDatatypes -> {
|
||||||
asmgen.out(" jsr prog8_lib.abs_b_into_A")
|
asmgen.out(" jsr prog8_lib.abs_b_into_A")
|
||||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
|
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
|
||||||
}
|
}
|
||||||
in WordDatatypes -> {
|
in WordDatatypes -> {
|
||||||
asmgen.out(" jsr prog8_lib.abs_w_into_AY")
|
asmgen.out(" jsr prog8_lib.abs_w_into_AY")
|
||||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
|
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> {
|
DataType.FLOAT -> {
|
||||||
asmgen.out(" jsr floats.abs_f_fac1")
|
asmgen.out(" jsr floats.abs_f_fac1")
|
||||||
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, scope, program, asmgen))
|
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, true, scope, program, asmgen))
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("weird type")
|
else -> throw AssemblyError("weird type")
|
||||||
}
|
}
|
||||||
@ -1162,7 +1333,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
asmgen.out(" jsr prog8_lib.func_rnd_stack")
|
asmgen.out(" jsr prog8_lib.func_rnd_stack")
|
||||||
else {
|
else {
|
||||||
asmgen.out(" jsr math.randbyte")
|
asmgen.out(" jsr math.randbyte")
|
||||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
|
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"rndw" -> {
|
"rndw" -> {
|
||||||
@ -1170,7 +1341,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
asmgen.out(" jsr prog8_lib.func_rndw_stack")
|
asmgen.out(" jsr prog8_lib.func_rndw_stack")
|
||||||
else {
|
else {
|
||||||
asmgen.out(" jsr math.randword")
|
asmgen.out(" jsr math.randword")
|
||||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
|
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("wrong func")
|
else -> throw AssemblyError("wrong func")
|
||||||
@ -1228,6 +1399,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)
|
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", DataType.UWORD, null)
|
||||||
@ -1533,7 +1705,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
AsmAssignSource.fromAstSource(value, program, asmgen)
|
AsmAssignSource.fromAstSource(value, program, asmgen)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val tgt = AsmAssignTarget.fromRegisters(conv.reg, null, program, asmgen)
|
val tgt = AsmAssignTarget.fromRegisters(conv.reg!!, false, null, program, asmgen)
|
||||||
val assign = AsmAssignment(src, tgt, false, program.memsizer, value.position)
|
val assign = AsmAssignment(src, tgt, false, program.memsizer, value.position)
|
||||||
asmgen.translateNormalAssignment(assign)
|
asmgen.translateNormalAssignment(assign)
|
||||||
}
|
}
|
@ -0,0 +1,792 @@
|
|||||||
|
package prog8.compiler.target.cpu6502.codegen
|
||||||
|
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.*
|
||||||
|
import prog8.ast.expressions.*
|
||||||
|
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
|
||||||
|
import prog8.ast.statements.Subroutine
|
||||||
|
import prog8.ast.toHex
|
||||||
|
import prog8.compiler.target.AssemblyError
|
||||||
|
import prog8.compilerinterface.BuiltinFunctions
|
||||||
|
import prog8.compilerinterface.CpuType
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
|
internal class ExpressionsAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||||
|
|
||||||
|
@Deprecated("avoid calling this as it generates slow evalstack based code")
|
||||||
|
internal fun translateExpression(expression:Expression) {
|
||||||
|
if (this.asmgen.options.slowCodegenWarnings) {
|
||||||
|
asmgen.errors.warn("slow stack evaluation used for expression $expression", expression.position)
|
||||||
|
}
|
||||||
|
translateExpressionInternal(expression)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// the rest of the methods are all PRIVATE
|
||||||
|
|
||||||
|
|
||||||
|
private fun translateExpressionInternal(expression: Expression) {
|
||||||
|
|
||||||
|
when(expression) {
|
||||||
|
is PrefixExpression -> translateExpression(expression)
|
||||||
|
is BinaryExpression -> translateExpression(expression)
|
||||||
|
is ArrayIndexedExpression -> translateExpression(expression)
|
||||||
|
is TypecastExpression -> translateExpression(expression)
|
||||||
|
is AddressOf -> translateExpression(expression)
|
||||||
|
is DirectMemoryRead -> asmgen.translateDirectMemReadExpressionToRegAorStack(expression, true)
|
||||||
|
is NumericLiteralValue -> translateExpression(expression)
|
||||||
|
is IdentifierReference -> translateExpression(expression)
|
||||||
|
is FunctionCall -> translateFunctionCallResultOntoStack(expression)
|
||||||
|
is ArrayLiteralValue, is StringLiteralValue -> throw AssemblyError("no asm gen for string/array literal value assignment - should have been replaced by a variable")
|
||||||
|
is RangeExpr -> throw AssemblyError("range expression should have been changed into array values")
|
||||||
|
is CharLiteral -> throw AssemblyError("charliteral should have been replaced by ubyte using certain encoding")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateFunctionCallResultOntoStack(call: FunctionCall) {
|
||||||
|
// only for use in nested expression evaluation
|
||||||
|
|
||||||
|
val sub = call.target.targetStatement(program)
|
||||||
|
if(sub is BuiltinFunctionStatementPlaceholder) {
|
||||||
|
val builtinFunc = BuiltinFunctions.getValue(sub.name)
|
||||||
|
asmgen.translateBuiltinFunctionCallExpression(call, builtinFunc, true, null)
|
||||||
|
} else {
|
||||||
|
sub as Subroutine
|
||||||
|
asmgen.saveXbeforeCall(call)
|
||||||
|
asmgen.translateFunctionCall(call, true)
|
||||||
|
if(sub.regXasResult()) {
|
||||||
|
// store the return value in X somewhere that we can acces again below
|
||||||
|
asmgen.out(" stx P8ZP_SCRATCH_REG")
|
||||||
|
}
|
||||||
|
asmgen.restoreXafterCall(call)
|
||||||
|
|
||||||
|
val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)
|
||||||
|
for ((_, reg) in returns) {
|
||||||
|
// result value is in cpu or status registers, put it on the stack instead (as we're evaluating an expression tree)
|
||||||
|
if (reg.registerOrPair != null) {
|
||||||
|
when (reg.registerOrPair!!) {
|
||||||
|
RegisterOrPair.A -> asmgen.out(" sta P8ESTACK_LO,x | dex")
|
||||||
|
RegisterOrPair.Y -> asmgen.out(" tya | sta P8ESTACK_LO,x | dex")
|
||||||
|
RegisterOrPair.AY -> asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
|
||||||
|
RegisterOrPair.X -> asmgen.out(" lda P8ZP_SCRATCH_REG | sta P8ESTACK_LO,x | dex")
|
||||||
|
RegisterOrPair.AX -> asmgen.out(" sta P8ESTACK_LO,x | lda P8ZP_SCRATCH_REG | sta P8ESTACK_HI,x | dex")
|
||||||
|
RegisterOrPair.XY -> asmgen.out(" tya | sta P8ESTACK_HI,x | lda P8ZP_SCRATCH_REG | sta P8ESTACK_LO,x | dex")
|
||||||
|
RegisterOrPair.FAC1 -> asmgen.out(" jsr floats.push_fac1")
|
||||||
|
RegisterOrPair.FAC2 -> asmgen.out(" jsr floats.push_fac2")
|
||||||
|
RegisterOrPair.R0,
|
||||||
|
RegisterOrPair.R1,
|
||||||
|
RegisterOrPair.R2,
|
||||||
|
RegisterOrPair.R3,
|
||||||
|
RegisterOrPair.R4,
|
||||||
|
RegisterOrPair.R5,
|
||||||
|
RegisterOrPair.R6,
|
||||||
|
RegisterOrPair.R7,
|
||||||
|
RegisterOrPair.R8,
|
||||||
|
RegisterOrPair.R9,
|
||||||
|
RegisterOrPair.R10,
|
||||||
|
RegisterOrPair.R11,
|
||||||
|
RegisterOrPair.R12,
|
||||||
|
RegisterOrPair.R13,
|
||||||
|
RegisterOrPair.R14,
|
||||||
|
RegisterOrPair.R15 -> {
|
||||||
|
asmgen.out(
|
||||||
|
"""
|
||||||
|
lda cx16.${reg.registerOrPair.toString().lowercase()}
|
||||||
|
sta P8ESTACK_LO,x
|
||||||
|
lda cx16.${reg.registerOrPair.toString().lowercase()}+1
|
||||||
|
sta P8ESTACK_HI,x
|
||||||
|
dex
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else when(reg.statusflag) {
|
||||||
|
Statusflag.Pc -> {
|
||||||
|
asmgen.out("""
|
||||||
|
lda #0
|
||||||
|
rol a
|
||||||
|
sta P8ESTACK_LO,x
|
||||||
|
dex""")
|
||||||
|
}
|
||||||
|
Statusflag.Pz -> {
|
||||||
|
asmgen.out("""
|
||||||
|
beq +
|
||||||
|
lda #0
|
||||||
|
beq ++
|
||||||
|
+ lda #1
|
||||||
|
+ sta P8ESTACK_LO,x
|
||||||
|
dex""")
|
||||||
|
}
|
||||||
|
Statusflag.Pv -> {
|
||||||
|
asmgen.out("""
|
||||||
|
bvs +
|
||||||
|
lda #0
|
||||||
|
beq ++
|
||||||
|
+ lda #1
|
||||||
|
+ sta P8ESTACK_LO,x
|
||||||
|
dex""")
|
||||||
|
}
|
||||||
|
Statusflag.Pn -> {
|
||||||
|
asmgen.out("""
|
||||||
|
bmi +
|
||||||
|
lda #0
|
||||||
|
beq ++
|
||||||
|
+ lda #1
|
||||||
|
+ sta P8ESTACK_LO,x
|
||||||
|
dex""")
|
||||||
|
}
|
||||||
|
null -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateExpression(typecast: TypecastExpression) {
|
||||||
|
translateExpressionInternal(typecast.expression)
|
||||||
|
when(typecast.expression.inferType(program).getOr(DataType.UNDEFINED)) {
|
||||||
|
DataType.UBYTE -> {
|
||||||
|
when(typecast.type) {
|
||||||
|
DataType.UBYTE, DataType.BYTE -> {}
|
||||||
|
DataType.UWORD, DataType.WORD -> {
|
||||||
|
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||||
|
asmgen.out(" stz P8ESTACK_HI+1,x")
|
||||||
|
else
|
||||||
|
asmgen.out(" lda #0 | sta P8ESTACK_HI+1,x")
|
||||||
|
}
|
||||||
|
DataType.FLOAT -> asmgen.out(" jsr floats.stack_ub2float")
|
||||||
|
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.BYTE -> {
|
||||||
|
when(typecast.type) {
|
||||||
|
DataType.UBYTE, DataType.BYTE -> {}
|
||||||
|
DataType.UWORD, DataType.WORD -> asmgen.signExtendStackLsb(DataType.BYTE)
|
||||||
|
DataType.FLOAT -> asmgen.out(" jsr floats.stack_b2float")
|
||||||
|
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.UWORD -> {
|
||||||
|
when(typecast.type) {
|
||||||
|
DataType.BYTE, DataType.UBYTE -> {}
|
||||||
|
DataType.WORD, DataType.UWORD -> {}
|
||||||
|
DataType.FLOAT -> asmgen.out(" jsr floats.stack_uw2float")
|
||||||
|
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.WORD -> {
|
||||||
|
when(typecast.type) {
|
||||||
|
DataType.BYTE, DataType.UBYTE -> {}
|
||||||
|
DataType.WORD, DataType.UWORD -> {}
|
||||||
|
DataType.FLOAT -> asmgen.out(" jsr floats.stack_w2float")
|
||||||
|
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.FLOAT -> {
|
||||||
|
when(typecast.type) {
|
||||||
|
DataType.UBYTE -> asmgen.out(" jsr floats.stack_float2uw")
|
||||||
|
DataType.BYTE -> asmgen.out(" jsr floats.stack_float2w")
|
||||||
|
DataType.UWORD -> asmgen.out(" jsr floats.stack_float2uw")
|
||||||
|
DataType.WORD -> asmgen.out(" jsr floats.stack_float2w")
|
||||||
|
DataType.FLOAT -> {}
|
||||||
|
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.STR -> {
|
||||||
|
if (typecast.type != DataType.UWORD && typecast.type == DataType.STR)
|
||||||
|
throw AssemblyError("cannot typecast a string into another incompatitble type")
|
||||||
|
}
|
||||||
|
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast pass-by-reference value into another type")
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateExpression(expr: AddressOf) {
|
||||||
|
val name = asmgen.asmVariableName(expr.identifier)
|
||||||
|
asmgen.out(" lda #<$name | sta P8ESTACK_LO,x | lda #>$name | sta P8ESTACK_HI,x | dex")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateExpression(expr: NumericLiteralValue) {
|
||||||
|
when(expr.type) {
|
||||||
|
DataType.UBYTE, DataType.BYTE -> asmgen.out(" lda #${expr.number.toHex()} | sta P8ESTACK_LO,x | dex")
|
||||||
|
DataType.UWORD, DataType.WORD -> asmgen.out("""
|
||||||
|
lda #<${expr.number.toHex()}
|
||||||
|
sta P8ESTACK_LO,x
|
||||||
|
lda #>${expr.number.toHex()}
|
||||||
|
sta P8ESTACK_HI,x
|
||||||
|
dex
|
||||||
|
""")
|
||||||
|
DataType.FLOAT -> {
|
||||||
|
val floatConst = asmgen.getFloatAsmConst(expr.number)
|
||||||
|
asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.push_float")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateExpression(expr: IdentifierReference) {
|
||||||
|
val varname = asmgen.asmVariableName(expr)
|
||||||
|
when(expr.inferType(program).getOr(DataType.UNDEFINED)) {
|
||||||
|
DataType.UBYTE, DataType.BYTE -> {
|
||||||
|
asmgen.out(" lda $varname | sta P8ESTACK_LO,x | dex")
|
||||||
|
}
|
||||||
|
DataType.UWORD, DataType.WORD -> {
|
||||||
|
asmgen.out(" lda $varname | sta P8ESTACK_LO,x | lda $varname+1 | sta P8ESTACK_HI,x | dex")
|
||||||
|
}
|
||||||
|
DataType.FLOAT -> {
|
||||||
|
asmgen.out(" lda #<$varname | ldy #>$varname| jsr floats.push_float")
|
||||||
|
}
|
||||||
|
in IterableDatatypes -> {
|
||||||
|
asmgen.out(" lda #<$varname | sta P8ESTACK_LO,x | lda #>$varname | sta P8ESTACK_HI,x | dex")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("stack push weird variable type $expr")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateExpression(expr: BinaryExpression) {
|
||||||
|
// Uses evalstack to evaluate the given expression.
|
||||||
|
// TODO we're slowly reducing the number of places where this is called and instead replace that by more efficient assignment-form code (using temp var or register for instance).
|
||||||
|
val leftIDt = expr.left.inferType(program)
|
||||||
|
val rightIDt = expr.right.inferType(program)
|
||||||
|
if(!leftIDt.isKnown || !rightIDt.isKnown)
|
||||||
|
throw AssemblyError("can't infer type of both expression operands")
|
||||||
|
|
||||||
|
val leftDt = leftIDt.getOr(DataType.UNDEFINED)
|
||||||
|
val rightDt = rightIDt.getOr(DataType.UNDEFINED)
|
||||||
|
// see if we can apply some optimized routines
|
||||||
|
when(expr.operator) {
|
||||||
|
"+" -> {
|
||||||
|
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
|
||||||
|
val leftVal = expr.left.constValue(program)?.number?.toInt()
|
||||||
|
val rightVal = expr.right.constValue(program)?.number?.toInt()
|
||||||
|
if (leftVal!=null && leftVal in -4..4) {
|
||||||
|
translateExpressionInternal(expr.right)
|
||||||
|
if(rightDt in ByteDatatypes) {
|
||||||
|
val incdec = if(leftVal<0) "dec" else "inc"
|
||||||
|
repeat(leftVal.absoluteValue) {
|
||||||
|
asmgen.out(" $incdec P8ESTACK_LO+1,x")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// word
|
||||||
|
if(leftVal<0) {
|
||||||
|
repeat(leftVal.absoluteValue) {
|
||||||
|
asmgen.out("""
|
||||||
|
lda P8ESTACK_LO+1,x
|
||||||
|
bne +
|
||||||
|
dec P8ESTACK_HI+1,x
|
||||||
|
+ dec P8ESTACK_LO+1,x""")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
repeat(leftVal) {
|
||||||
|
asmgen.out("""
|
||||||
|
inc P8ESTACK_LO+1,x
|
||||||
|
bne +
|
||||||
|
inc P8ESTACK_HI+1,x
|
||||||
|
+""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
else if (rightVal!=null && rightVal in -4..4)
|
||||||
|
{
|
||||||
|
translateExpressionInternal(expr.left)
|
||||||
|
if(leftDt in ByteDatatypes) {
|
||||||
|
val incdec = if(rightVal<0) "dec" else "inc"
|
||||||
|
repeat(rightVal.absoluteValue) {
|
||||||
|
asmgen.out(" $incdec P8ESTACK_LO+1,x")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// word
|
||||||
|
if(rightVal<0) {
|
||||||
|
repeat(rightVal.absoluteValue) {
|
||||||
|
asmgen.out("""
|
||||||
|
lda P8ESTACK_LO+1,x
|
||||||
|
bne +
|
||||||
|
dec P8ESTACK_HI+1,x
|
||||||
|
+ dec P8ESTACK_LO+1,x""")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
repeat(rightVal) {
|
||||||
|
asmgen.out("""
|
||||||
|
inc P8ESTACK_LO+1,x
|
||||||
|
bne +
|
||||||
|
inc P8ESTACK_HI+1,x
|
||||||
|
+""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"-" -> {
|
||||||
|
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
|
||||||
|
val rightVal = expr.right.constValue(program)?.number?.toInt()
|
||||||
|
if (rightVal!=null && rightVal in -4..4)
|
||||||
|
{
|
||||||
|
translateExpressionInternal(expr.left)
|
||||||
|
if(leftDt in ByteDatatypes) {
|
||||||
|
val incdec = if(rightVal<0) "inc" else "dec"
|
||||||
|
repeat(rightVal.absoluteValue) {
|
||||||
|
asmgen.out(" $incdec P8ESTACK_LO+1,x")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// word
|
||||||
|
if(rightVal>0) {
|
||||||
|
repeat(rightVal.absoluteValue) {
|
||||||
|
asmgen.out("""
|
||||||
|
lda P8ESTACK_LO+1,x
|
||||||
|
bne +
|
||||||
|
dec P8ESTACK_HI+1,x
|
||||||
|
+ dec P8ESTACK_LO+1,x""")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
repeat(rightVal) {
|
||||||
|
asmgen.out("""
|
||||||
|
inc P8ESTACK_LO+1,x
|
||||||
|
bne +
|
||||||
|
inc P8ESTACK_HI+1,x
|
||||||
|
+""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
">>" -> {
|
||||||
|
val amount = expr.right.constValue(program)?.number?.toInt()
|
||||||
|
if(amount!=null) {
|
||||||
|
translateExpressionInternal(expr.left)
|
||||||
|
when (leftDt) {
|
||||||
|
DataType.UBYTE -> {
|
||||||
|
if (amount <= 2)
|
||||||
|
repeat(amount) { asmgen.out(" lsr P8ESTACK_LO+1,x") }
|
||||||
|
else {
|
||||||
|
asmgen.out(" lda P8ESTACK_LO+1,x")
|
||||||
|
repeat(amount) { asmgen.out(" lsr a") }
|
||||||
|
asmgen.out(" sta P8ESTACK_LO+1,x")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.BYTE -> {
|
||||||
|
if (amount <= 2)
|
||||||
|
repeat(amount) { asmgen.out(" lda P8ESTACK_LO+1,x | asl a | ror P8ESTACK_LO+1,x") }
|
||||||
|
else {
|
||||||
|
asmgen.out(" lda P8ESTACK_LO+1,x | sta P8ZP_SCRATCH_B1")
|
||||||
|
repeat(amount) { asmgen.out(" asl a | ror P8ZP_SCRATCH_B1 | lda P8ZP_SCRATCH_B1") }
|
||||||
|
asmgen.out(" sta P8ESTACK_LO+1,x")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.UWORD -> {
|
||||||
|
if(amount>=16) {
|
||||||
|
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||||
|
asmgen.out(" stz P8ESTACK_LO+1,x | stz P8ESTACK_HI+1,x")
|
||||||
|
else
|
||||||
|
asmgen.out(" lda #0 | sta P8ESTACK_LO+1,x | sta P8ESTACK_HI+1,x")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var left = amount
|
||||||
|
while (left >= 7) {
|
||||||
|
asmgen.out(" jsr math.shift_right_uw_7")
|
||||||
|
left -= 7
|
||||||
|
}
|
||||||
|
if (left in 0..2)
|
||||||
|
repeat(left) { asmgen.out(" lsr P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x") }
|
||||||
|
else
|
||||||
|
asmgen.out(" jsr math.shift_right_uw_$left")
|
||||||
|
}
|
||||||
|
DataType.WORD -> {
|
||||||
|
if(amount>=16) {
|
||||||
|
asmgen.out("""
|
||||||
|
lda P8ESTACK_HI+1,x
|
||||||
|
bmi +
|
||||||
|
lda #0
|
||||||
|
sta P8ESTACK_LO+1,x
|
||||||
|
sta P8ESTACK_HI+1,x
|
||||||
|
beq ++
|
||||||
|
+ lda #255
|
||||||
|
sta P8ESTACK_LO+1,x
|
||||||
|
sta P8ESTACK_HI+1,x
|
||||||
|
+""")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var left = amount
|
||||||
|
while (left >= 7) {
|
||||||
|
asmgen.out(" jsr math.shift_right_w_7")
|
||||||
|
left -= 7
|
||||||
|
}
|
||||||
|
if (left in 0..2)
|
||||||
|
repeat(left) { asmgen.out(" lda P8ESTACK_HI+1,x | asl a | ror P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x") }
|
||||||
|
else
|
||||||
|
asmgen.out(" jsr math.shift_right_w_$left")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"<<" -> {
|
||||||
|
val amount = expr.right.constValue(program)?.number?.toInt()
|
||||||
|
if(amount!=null) {
|
||||||
|
translateExpressionInternal(expr.left)
|
||||||
|
if (leftDt in ByteDatatypes) {
|
||||||
|
if (amount <= 2)
|
||||||
|
repeat(amount) { asmgen.out(" asl P8ESTACK_LO+1,x") }
|
||||||
|
else {
|
||||||
|
asmgen.out(" lda P8ESTACK_LO+1,x")
|
||||||
|
repeat(amount) { asmgen.out(" asl a") }
|
||||||
|
asmgen.out(" sta P8ESTACK_LO+1,x")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var left = amount
|
||||||
|
while (left >= 7) {
|
||||||
|
asmgen.out(" jsr math.shift_left_w_7")
|
||||||
|
left -= 7
|
||||||
|
}
|
||||||
|
if (left in 0..2)
|
||||||
|
repeat(left) { asmgen.out(" asl P8ESTACK_LO+1,x | rol P8ESTACK_HI+1,x") }
|
||||||
|
else
|
||||||
|
asmgen.out(" jsr math.shift_left_w_$left")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"*" -> {
|
||||||
|
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
|
||||||
|
val leftVar = expr.left as? IdentifierReference
|
||||||
|
val rightVar = expr.right as? IdentifierReference
|
||||||
|
if(leftVar!=null && rightVar!=null && leftVar==rightVar)
|
||||||
|
return translateSquared(leftVar, leftDt)
|
||||||
|
}
|
||||||
|
|
||||||
|
val value = expr.right.constValue(program)
|
||||||
|
if(value!=null) {
|
||||||
|
if(rightDt in IntegerDatatypes) {
|
||||||
|
val amount = value.number.toInt()
|
||||||
|
if(amount==2) {
|
||||||
|
// optimize x*2 common case
|
||||||
|
translateExpressionInternal(expr.left)
|
||||||
|
if(leftDt in ByteDatatypes) {
|
||||||
|
asmgen.out(" asl P8ESTACK_LO+1,x")
|
||||||
|
} else {
|
||||||
|
asmgen.out(" asl P8ESTACK_LO+1,x | rol P8ESTACK_HI+1,x")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
when(rightDt) {
|
||||||
|
DataType.UBYTE -> {
|
||||||
|
if(amount in asmgen.optimizedByteMultiplications) {
|
||||||
|
translateExpressionInternal(expr.left)
|
||||||
|
asmgen.out(" jsr math.stack_mul_byte_$amount")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.BYTE -> {
|
||||||
|
if(amount in asmgen.optimizedByteMultiplications) {
|
||||||
|
translateExpressionInternal(expr.left)
|
||||||
|
asmgen.out(" jsr math.stack_mul_byte_$amount")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if(amount.absoluteValue in asmgen.optimizedByteMultiplications) {
|
||||||
|
translateExpressionInternal(expr.left)
|
||||||
|
asmgen.out(" jsr prog8_lib.neg_b | jsr math.stack_mul_byte_${amount.absoluteValue}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.UWORD -> {
|
||||||
|
if(amount in asmgen.optimizedWordMultiplications) {
|
||||||
|
translateExpressionInternal(expr.left)
|
||||||
|
asmgen.out(" jsr math.stack_mul_word_$amount")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.WORD -> {
|
||||||
|
if(amount in asmgen.optimizedWordMultiplications) {
|
||||||
|
translateExpressionInternal(expr.left)
|
||||||
|
asmgen.out(" jsr math.stack_mul_word_$amount")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if(amount.absoluteValue in asmgen.optimizedWordMultiplications) {
|
||||||
|
translateExpressionInternal(expr.left)
|
||||||
|
asmgen.out(" jsr prog8_lib.neg_w | jsr math.stack_mul_word_${amount.absoluteValue}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"/" -> {
|
||||||
|
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
|
||||||
|
val rightVal = expr.right.constValue(program)?.number?.toInt()
|
||||||
|
if(rightVal!=null && rightVal==2) {
|
||||||
|
translateExpressionInternal(expr.left)
|
||||||
|
when(leftDt) {
|
||||||
|
DataType.UBYTE -> asmgen.out(" lsr P8ESTACK_LO+1,x")
|
||||||
|
DataType.BYTE -> asmgen.out(" lda P8ESTACK_LO+1,x | asl a | ror P8ESTACK_LO+1,x")
|
||||||
|
DataType.UWORD -> asmgen.out(" lsr P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x")
|
||||||
|
DataType.WORD -> asmgen.out(" lda P8ESTACK_HI+1,x | asl a | ror P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x")
|
||||||
|
else -> throw AssemblyError("wrong dt")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if((leftDt in ByteDatatypes && rightDt !in ByteDatatypes)
|
||||||
|
|| (leftDt in WordDatatypes && rightDt !in WordDatatypes))
|
||||||
|
throw AssemblyError("binary operator ${expr.operator} left/right dt not identical")
|
||||||
|
|
||||||
|
if(leftDt==DataType.STR && rightDt==DataType.STR && expr.operator in comparisonOperators) {
|
||||||
|
translateCompareStrings(expr.left, expr.operator, expr.right)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// the general, non-optimized cases TODO optimize more cases.... (or one day just don't use the evalstack at all anymore)
|
||||||
|
translateExpressionInternal(expr.left)
|
||||||
|
translateExpressionInternal(expr.right)
|
||||||
|
when (leftDt) {
|
||||||
|
in ByteDatatypes -> translateBinaryOperatorBytes(expr.operator, leftDt)
|
||||||
|
in WordDatatypes -> translateBinaryOperatorWords(expr.operator, leftDt)
|
||||||
|
DataType.FLOAT -> translateBinaryOperatorFloats(expr.operator)
|
||||||
|
else -> throw AssemblyError("non-numerical datatype")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateSquared(variable: IdentifierReference, dt: DataType) {
|
||||||
|
val asmVar = asmgen.asmVariableName(variable)
|
||||||
|
when(dt) {
|
||||||
|
DataType.BYTE, DataType.UBYTE -> {
|
||||||
|
asmgen.out(" lda $asmVar")
|
||||||
|
asmgen.signExtendAYlsb(dt)
|
||||||
|
asmgen.out(" jsr math.square")
|
||||||
|
}
|
||||||
|
DataType.UWORD, DataType.WORD -> {
|
||||||
|
asmgen.out(" lda $asmVar | ldy $asmVar+1 | jsr math.square")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("require integer dt for square")
|
||||||
|
}
|
||||||
|
asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateExpression(expr: PrefixExpression) {
|
||||||
|
translateExpressionInternal(expr.expression)
|
||||||
|
val itype = expr.inferType(program)
|
||||||
|
if(!itype.isKnown)
|
||||||
|
throw AssemblyError("unknown dt")
|
||||||
|
val type = itype.getOr(DataType.UNDEFINED)
|
||||||
|
when(expr.operator) {
|
||||||
|
"+" -> {}
|
||||||
|
"-" -> {
|
||||||
|
when(type) {
|
||||||
|
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.neg_b")
|
||||||
|
in WordDatatypes -> asmgen.out(" jsr prog8_lib.neg_w")
|
||||||
|
DataType.FLOAT -> asmgen.out(" jsr floats.neg_f")
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"~" -> {
|
||||||
|
when(type) {
|
||||||
|
in ByteDatatypes ->
|
||||||
|
asmgen.out("""
|
||||||
|
lda P8ESTACK_LO+1,x
|
||||||
|
eor #255
|
||||||
|
sta P8ESTACK_LO+1,x
|
||||||
|
""")
|
||||||
|
in WordDatatypes -> asmgen.out(" jsr prog8_lib.inv_word")
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"not" -> {
|
||||||
|
when(type) {
|
||||||
|
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.not_byte")
|
||||||
|
in WordDatatypes -> asmgen.out(" jsr prog8_lib.not_word")
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("invalid prefix operator ${expr.operator}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateExpression(arrayExpr: ArrayIndexedExpression) {
|
||||||
|
val elementIDt = arrayExpr.inferType(program)
|
||||||
|
if(!elementIDt.isKnown)
|
||||||
|
throw AssemblyError("unknown dt")
|
||||||
|
val elementDt = elementIDt.getOr(DataType.UNDEFINED)
|
||||||
|
val arrayVarName = asmgen.asmVariableName(arrayExpr.arrayvar)
|
||||||
|
val constIndexNum = arrayExpr.indexer.constIndex()
|
||||||
|
if(constIndexNum!=null) {
|
||||||
|
val indexValue = constIndexNum * program.memsizer.memorySize(elementDt)
|
||||||
|
when(elementDt) {
|
||||||
|
in ByteDatatypes -> {
|
||||||
|
asmgen.out(" lda $arrayVarName+$indexValue | sta P8ESTACK_LO,x | dex")
|
||||||
|
}
|
||||||
|
in WordDatatypes -> {
|
||||||
|
asmgen.out(" lda $arrayVarName+$indexValue | sta P8ESTACK_LO,x | lda $arrayVarName+$indexValue+1 | sta P8ESTACK_HI,x | dex")
|
||||||
|
}
|
||||||
|
DataType.FLOAT -> {
|
||||||
|
asmgen.out(" lda #<$arrayVarName+$indexValue | ldy #>$arrayVarName+$indexValue | jsr floats.push_float")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird element type")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
when(elementDt) {
|
||||||
|
in ByteDatatypes -> {
|
||||||
|
asmgen.loadScaledArrayIndexIntoRegister(arrayExpr, elementDt, CpuRegister.Y)
|
||||||
|
asmgen.out(" lda $arrayVarName,y | sta P8ESTACK_LO,x | dex")
|
||||||
|
}
|
||||||
|
in WordDatatypes -> {
|
||||||
|
asmgen.loadScaledArrayIndexIntoRegister(arrayExpr, elementDt, CpuRegister.Y)
|
||||||
|
asmgen.out(" lda $arrayVarName,y | sta P8ESTACK_LO,x | lda $arrayVarName+1,y | sta P8ESTACK_HI,x | dex")
|
||||||
|
}
|
||||||
|
DataType.FLOAT -> {
|
||||||
|
asmgen.loadScaledArrayIndexIntoRegister(arrayExpr, elementDt, CpuRegister.A)
|
||||||
|
asmgen.out("""
|
||||||
|
ldy #>$arrayVarName
|
||||||
|
clc
|
||||||
|
adc #<$arrayVarName
|
||||||
|
bcc +
|
||||||
|
iny
|
||||||
|
+ jsr floats.push_float""")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird dt")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateBinaryOperatorBytes(operator: String, types: DataType) {
|
||||||
|
when(operator) {
|
||||||
|
"**" -> throw AssemblyError("** operator requires floats")
|
||||||
|
"*" -> asmgen.out(" jsr prog8_lib.mul_byte") // the optimized routines should have been checked earlier
|
||||||
|
"/" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.idiv_ub" else " jsr prog8_lib.idiv_b")
|
||||||
|
"%" -> {
|
||||||
|
if(types==DataType.BYTE)
|
||||||
|
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
|
||||||
|
asmgen.out(" jsr prog8_lib.remainder_ub")
|
||||||
|
}
|
||||||
|
"+" -> asmgen.out("""
|
||||||
|
lda P8ESTACK_LO+2,x
|
||||||
|
clc
|
||||||
|
adc P8ESTACK_LO+1,x
|
||||||
|
inx
|
||||||
|
sta P8ESTACK_LO+1,x
|
||||||
|
""")
|
||||||
|
"-" -> asmgen.out("""
|
||||||
|
lda P8ESTACK_LO+2,x
|
||||||
|
sec
|
||||||
|
sbc P8ESTACK_LO+1,x
|
||||||
|
inx
|
||||||
|
sta P8ESTACK_LO+1,x
|
||||||
|
""")
|
||||||
|
"<<" -> asmgen.out(" jsr prog8_lib.shiftleft_b")
|
||||||
|
">>" -> asmgen.out(" jsr prog8_lib.shiftright_b")
|
||||||
|
"<" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.less_ub" else " jsr prog8_lib.less_b")
|
||||||
|
">" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.greater_ub" else " jsr prog8_lib.greater_b")
|
||||||
|
"<=" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.lesseq_ub" else " jsr prog8_lib.lesseq_b")
|
||||||
|
">=" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.greatereq_ub" else " jsr prog8_lib.greatereq_b")
|
||||||
|
"==" -> asmgen.out(" jsr prog8_lib.equal_b")
|
||||||
|
"!=" -> asmgen.out(" jsr prog8_lib.notequal_b")
|
||||||
|
"&" -> asmgen.out(" jsr prog8_lib.bitand_b")
|
||||||
|
"^" -> asmgen.out(" jsr prog8_lib.bitxor_b")
|
||||||
|
"|" -> asmgen.out(" jsr prog8_lib.bitor_b")
|
||||||
|
"and" -> asmgen.out(" jsr prog8_lib.and_b")
|
||||||
|
"or" -> asmgen.out(" jsr prog8_lib.or_b")
|
||||||
|
"xor" -> asmgen.out(" jsr prog8_lib.xor_b")
|
||||||
|
else -> throw AssemblyError("invalid operator $operator")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateBinaryOperatorWords(operator: String, dt: DataType) {
|
||||||
|
when(operator) {
|
||||||
|
"**" -> throw AssemblyError("** operator requires floats")
|
||||||
|
"*" -> asmgen.out(" jsr prog8_lib.mul_word")
|
||||||
|
"/" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.idiv_uw" else " jsr prog8_lib.idiv_w")
|
||||||
|
"%" -> {
|
||||||
|
if(dt==DataType.WORD)
|
||||||
|
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
|
||||||
|
asmgen.out(" jsr prog8_lib.remainder_uw")
|
||||||
|
}
|
||||||
|
"+" -> asmgen.out(" jsr prog8_lib.add_w")
|
||||||
|
"-" -> asmgen.out(" jsr prog8_lib.sub_w")
|
||||||
|
"<<" -> asmgen.out(" jsr math.shift_left_w")
|
||||||
|
">>" -> {
|
||||||
|
if(dt==DataType.UWORD)
|
||||||
|
asmgen.out(" jsr math.shift_right_uw")
|
||||||
|
else
|
||||||
|
asmgen.out(" jsr math.shift_right_w")
|
||||||
|
}
|
||||||
|
"<" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.less_uw" else " jsr prog8_lib.less_w")
|
||||||
|
">" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.greater_uw" else " jsr prog8_lib.greater_w")
|
||||||
|
"<=" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.lesseq_uw" else " jsr prog8_lib.lesseq_w")
|
||||||
|
">=" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.greatereq_uw" else " jsr prog8_lib.greatereq_w")
|
||||||
|
"==" -> asmgen.out(" jsr prog8_lib.equal_w")
|
||||||
|
"!=" -> asmgen.out(" jsr prog8_lib.notequal_w") "&" -> asmgen.out(" jsr prog8_lib.bitand_w")
|
||||||
|
"^" -> asmgen.out(" jsr prog8_lib.bitxor_w")
|
||||||
|
"|" -> asmgen.out(" jsr prog8_lib.bitor_w")
|
||||||
|
"and" -> asmgen.out(" jsr prog8_lib.and_w")
|
||||||
|
"or" -> asmgen.out(" jsr prog8_lib.or_w")
|
||||||
|
"xor" -> asmgen.out(" jsr prog8_lib.xor_w")
|
||||||
|
else -> throw AssemblyError("invalid operator $operator")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateBinaryOperatorFloats(operator: String) {
|
||||||
|
when(operator) {
|
||||||
|
"**" -> asmgen.out(" jsr floats.pow_f")
|
||||||
|
"*" -> asmgen.out(" jsr floats.mul_f")
|
||||||
|
"/" -> asmgen.out(" jsr floats.div_f")
|
||||||
|
"+" -> asmgen.out(" jsr floats.add_f")
|
||||||
|
"-" -> asmgen.out(" jsr floats.sub_f")
|
||||||
|
"<" -> asmgen.out(" jsr floats.less_f")
|
||||||
|
">" -> asmgen.out(" jsr floats.greater_f")
|
||||||
|
"<=" -> asmgen.out(" jsr floats.lesseq_f")
|
||||||
|
">=" -> asmgen.out(" jsr floats.greatereq_f")
|
||||||
|
"==" -> asmgen.out(" jsr floats.equal_f")
|
||||||
|
"!=" -> asmgen.out(" jsr floats.notequal_f")
|
||||||
|
"%", "<<", ">>", "&", "^", "|", "and", "or", "xor" -> throw AssemblyError("requires integer datatype")
|
||||||
|
else -> throw AssemblyError("invalid operator $operator")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateCompareStrings(s1: Expression, operator: String, s2: Expression) {
|
||||||
|
asmgen.assignExpressionToVariable(s1, "prog8_lib.strcmp_expression._arg_s1", DataType.UWORD, null)
|
||||||
|
asmgen.assignExpressionToVariable(s2, "prog8_lib.strcmp_expression._arg_s2", DataType.UWORD, null)
|
||||||
|
asmgen.out(" jsr prog8_lib.strcmp_expression") // result of compare is in A
|
||||||
|
when(operator) {
|
||||||
|
"==" -> asmgen.out(" and #1 | eor #1 | sta P8ESTACK_LO,x")
|
||||||
|
"!=" -> asmgen.out(" and #1 | sta P8ESTACK_LO,x")
|
||||||
|
"<=" -> asmgen.out("""
|
||||||
|
bpl +
|
||||||
|
lda #1
|
||||||
|
bne ++
|
||||||
|
+ lda #0
|
||||||
|
+ sta P8ESTACK_LO,x""")
|
||||||
|
">=" -> asmgen.out("""
|
||||||
|
bmi +
|
||||||
|
lda #1
|
||||||
|
bne ++
|
||||||
|
+ lda #0
|
||||||
|
+ sta P8ESTACK_LO,x""")
|
||||||
|
"<" -> asmgen.out("""
|
||||||
|
bmi +
|
||||||
|
lda #0
|
||||||
|
beq ++
|
||||||
|
+ lda #1
|
||||||
|
+ sta P8ESTACK_LO,x""")
|
||||||
|
">" -> asmgen.out("""
|
||||||
|
bpl +
|
||||||
|
lda #0
|
||||||
|
beq ++
|
||||||
|
+ lda #1
|
||||||
|
+ sta P8ESTACK_LO,x""")
|
||||||
|
}
|
||||||
|
asmgen.out(" dex")
|
||||||
|
}
|
||||||
|
}
|
@ -8,8 +8,8 @@ import prog8.ast.expressions.IdentifierReference
|
|||||||
import prog8.ast.expressions.RangeExpr
|
import prog8.ast.expressions.RangeExpr
|
||||||
import prog8.ast.statements.ForLoop
|
import prog8.ast.statements.ForLoop
|
||||||
import prog8.ast.toHex
|
import prog8.ast.toHex
|
||||||
import prog8.compiler.AssemblyError
|
import prog8.compiler.target.AssemblyError
|
||||||
import prog8.compiler.astprocessing.toConstantIntegerRange
|
import prog8.compilerinterface.toConstantIntegerRange
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||||
@ -20,7 +20,7 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
|
|||||||
throw AssemblyError("unknown dt")
|
throw AssemblyError("unknown dt")
|
||||||
when(stmt.iterable) {
|
when(stmt.iterable) {
|
||||||
is RangeExpr -> {
|
is RangeExpr -> {
|
||||||
val range = (stmt.iterable as RangeExpr).toConstantIntegerRange(asmgen.options.compTarget)
|
val range = (stmt.iterable as RangeExpr).toConstantIntegerRange()
|
||||||
if(range==null) {
|
if(range==null) {
|
||||||
translateForOverNonconstRange(stmt, iterableDt.getOr(DataType.UNDEFINED), stmt.iterable as RangeExpr)
|
translateForOverNonconstRange(stmt, iterableDt.getOr(DataType.UNDEFINED), stmt.iterable as RangeExpr)
|
||||||
} else {
|
} else {
|
||||||
@ -43,7 +43,7 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
|
|||||||
val stepsize=range.step.constValue(program)!!.number.toInt()
|
val stepsize=range.step.constValue(program)!!.number.toInt()
|
||||||
|
|
||||||
if(stepsize < -1) {
|
if(stepsize < -1) {
|
||||||
val limit = range.to.constValue(program)?.number?.toDouble()
|
val limit = range.to.constValue(program)?.number
|
||||||
if(limit==0.0)
|
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")
|
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")
|
||||||
}
|
}
|
@ -5,23 +5,20 @@ import prog8.ast.Node
|
|||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.statements.InlineAssembly
|
import prog8.ast.statements.*
|
||||||
import prog8.ast.statements.RegisterOrStatusflag
|
import prog8.compiler.target.AssemblyError
|
||||||
import prog8.ast.statements.Subroutine
|
|
||||||
import prog8.ast.statements.SubroutineParameter
|
|
||||||
import prog8.compiler.AssemblyError
|
|
||||||
import prog8.compiler.target.CpuType
|
|
||||||
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignSource
|
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignSource
|
||||||
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignTarget
|
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignTarget
|
||||||
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignment
|
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignment
|
||||||
import prog8.compiler.target.cpu6502.codegen.assignment.TargetStorageKind
|
import prog8.compiler.target.cpu6502.codegen.assignment.TargetStorageKind
|
||||||
|
import prog8.compilerinterface.CpuType
|
||||||
|
|
||||||
|
|
||||||
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||||
|
|
||||||
internal fun translateFunctionCallStatement(stmt: IFunctionCall) {
|
internal fun translateFunctionCallStatement(stmt: IFunctionCall) {
|
||||||
saveXbeforeCall(stmt)
|
saveXbeforeCall(stmt)
|
||||||
translateFunctionCall(stmt)
|
translateFunctionCall(stmt, false)
|
||||||
restoreXafterCall(stmt)
|
restoreXafterCall(stmt)
|
||||||
// just ignore any result values from the function call.
|
// 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) {
|
internal fun restoreXafterCall(stmt: IFunctionCall) {
|
||||||
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
|
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
|
||||||
if(sub.shouldSaveX()) {
|
if(sub.shouldSaveX()) {
|
||||||
@ -48,101 +56,98 @@ 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
|
// 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 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!!
|
// 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)
|
// (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 sub = call.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${call.target}")
|
||||||
val subName = asmgen.asmSymbolName(stmt.target)
|
val subAsmName = asmgen.asmSymbolName(call.target)
|
||||||
if(stmt.args.isNotEmpty()) {
|
|
||||||
|
|
||||||
if(sub.asmParameterRegisters.isEmpty()) {
|
if(!isExpression && !sub.isAsmSubroutine)
|
||||||
// via variables
|
throw AssemblyError("functioncall statements to non-asmsub should have been replaced by GoSub $call")
|
||||||
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
|
|
||||||
argumentViaVariable(sub, arg.first, arg.second)
|
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 {
|
} else {
|
||||||
// via registers
|
asmgen.out(" jsr $subAsmName")
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
if(!sub.inline || !asmgen.options.optimize) {
|
if(sub.inline)
|
||||||
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)
|
|
||||||
throw AssemblyError("can only reliably inline asmsub routines at this time")
|
throw AssemblyError("can only reliably inline asmsub routines at this time")
|
||||||
|
|
||||||
asmgen.out(" \t; inlined routine follows: ${sub.name}")
|
argumentsViaVariables(sub, call)
|
||||||
val assembly = sub.statements.single() as InlineAssembly
|
asmgen.out(" jsr $subAsmName")
|
||||||
asmgen.translate(assembly)
|
|
||||||
asmgen.out(" \t; inlined routine end: ${sub.name}")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// remember: dealing with the X register and/or dealing with return values is the responsibility of the caller
|
// 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) {
|
private fun registerArgsViaStackEvaluation(stmt: IFunctionCall, sub: Subroutine) {
|
||||||
// this is called when one or more of the arguments are 'complex' and
|
// 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.
|
// cannot be assigned to a register easily or risk clobbering other registers.
|
||||||
|
// TODO find another way to prepare the arguments, without using the eval stack
|
||||||
|
|
||||||
if(sub.parameters.isEmpty())
|
if(sub.parameters.isEmpty())
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
// 1. load all arguments reversed onto the stack: first arg goes last (is on top).
|
// 1. load all arguments reversed onto the stack: first arg goes last (is on top).
|
||||||
|
|
||||||
for (arg in stmt.args.reversed())
|
for (arg in stmt.args.reversed())
|
||||||
asmgen.translateExpression(arg)
|
asmgen.translateExpression(arg)
|
||||||
|
|
||||||
|
// TODO here's an alternative to the above, but for now generates bigger code due to intermediate register steps:
|
||||||
|
// for (arg in stmt.args.reversed()) {
|
||||||
|
// // note this stuff below is needed to (eventually) avoid calling asmgen.translateExpression()
|
||||||
|
// // TODO also This STILL requires the translateNormalAssignment() to be fixed to avoid stack eval for expressions...
|
||||||
|
// val dt = arg.inferType(program).getOr(DataType.UNDEFINED)
|
||||||
|
// asmgen.assignExpressionTo(arg, AsmAssignTarget(TargetStorageKind.STACK, program, asmgen, dt, sub))
|
||||||
|
// }
|
||||||
|
|
||||||
var argForCarry: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
|
var argForCarry: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
|
||||||
var argForXregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
|
var argForXregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
|
||||||
var argForAregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
|
var argForAregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
|
||||||
@ -209,11 +214,10 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
|||||||
if(argForCarry!=null) {
|
if(argForCarry!=null) {
|
||||||
val plusIdxStr = if(argForCarry.index==0) "" else "+${argForCarry.index}"
|
val plusIdxStr = if(argForCarry.index==0) "" else "+${argForCarry.index}"
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
|
clc
|
||||||
lda P8ESTACK_LO$plusIdxStr,x
|
lda P8ESTACK_LO$plusIdxStr,x
|
||||||
beq +
|
beq +
|
||||||
sec
|
sec
|
||||||
bcs ++
|
|
||||||
+ clc
|
|
||||||
+ php""") // push the status flags
|
+ php""") // push the status flags
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,7 +260,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
|||||||
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
|
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
|
||||||
throw AssemblyError("argument type incompatible")
|
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)
|
asmgen.assignExpressionToVariable(value, varName, parameter.value.type, sub)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,11 +296,10 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
|||||||
val sourceName = asmgen.asmVariableName(value)
|
val sourceName = asmgen.asmVariableName(value)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
pha
|
pha
|
||||||
|
clc
|
||||||
lda $sourceName
|
lda $sourceName
|
||||||
beq +
|
beq +
|
||||||
sec
|
sec
|
||||||
bcs ++
|
|
||||||
+ clc
|
|
||||||
+ pla
|
+ pla
|
||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
@ -324,8 +327,10 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
|||||||
val target: AsmAssignTarget =
|
val target: AsmAssignTarget =
|
||||||
if(parameter.value.type in ByteDatatypes && (register==RegisterOrPair.AX || register == RegisterOrPair.AY || register==RegisterOrPair.XY || register in Cx16VirtualRegisters))
|
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)
|
AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, parameter.value.type, sub, register = register)
|
||||||
else
|
else {
|
||||||
AsmAssignTarget.fromRegisters(register, sub, program, asmgen)
|
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) {
|
val src = if(valueDt in PassByReferenceDatatypes) {
|
||||||
if(value is IdentifierReference) {
|
if(value is IdentifierReference) {
|
||||||
val addr = AddressOf(value, Position.DUMMY)
|
val addr = AddressOf(value, Position.DUMMY)
|
@ -6,7 +6,7 @@ import prog8.ast.expressions.IdentifierReference
|
|||||||
import prog8.ast.expressions.NumericLiteralValue
|
import prog8.ast.expressions.NumericLiteralValue
|
||||||
import prog8.ast.statements.PostIncrDecr
|
import prog8.ast.statements.PostIncrDecr
|
||||||
import prog8.ast.toHex
|
import prog8.ast.toHex
|
||||||
import prog8.compiler.AssemblyError
|
import prog8.compiler.target.AssemblyError
|
||||||
|
|
||||||
|
|
||||||
internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
@ -1,11 +1,11 @@
|
|||||||
package prog8.compiler.target.cpu6502.codegen.assignment
|
package prog8.compiler.target.cpu6502.codegen.assignment
|
||||||
|
|
||||||
import prog8.ast.IMemSizer
|
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.compiler.AssemblyError
|
import prog8.compilerinterface.IMemSizer
|
||||||
|
import prog8.compiler.target.AssemblyError
|
||||||
import prog8.compiler.target.cpu6502.codegen.AsmGen
|
import prog8.compiler.target.cpu6502.codegen.AsmGen
|
||||||
|
|
||||||
|
|
||||||
@ -39,13 +39,14 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
|
|||||||
val origAstTarget: AssignTarget? = null
|
val origAstTarget: AssignTarget? = null
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0}
|
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toUInt() ?: 0u}
|
||||||
val constArrayIndexValue by lazy { array?.indexer?.constIndex() }
|
val constArrayIndexValue by lazy { array?.indexer?.constIndex()?.toUInt() }
|
||||||
val asmVarname: String
|
val asmVarname: String by lazy {
|
||||||
get() = if(array==null)
|
if (array == null)
|
||||||
variableAsmName!!
|
variableAsmName!!
|
||||||
else
|
else
|
||||||
asmgen.asmVariableName(array.arrayvar)
|
asmgen.asmVariableName(array.arrayvar)
|
||||||
|
}
|
||||||
|
|
||||||
lateinit var origAssign: AsmAssignment
|
lateinit var origAssign: AsmAssignment
|
||||||
|
|
||||||
@ -55,27 +56,42 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromAstAssignment(assign: Assignment, program: Program, asmgen: AsmGen): AsmAssignTarget = with(assign.target) {
|
fun fromAstAssignment(assign: Assignment, program: Program, asmgen: AsmGen): AsmAssignTarget {
|
||||||
val idt = inferType(program)
|
with(assign.target) {
|
||||||
if(!idt.isKnown)
|
val idt = inferType(program)
|
||||||
throw AssemblyError("unknown dt")
|
if(!idt.isKnown)
|
||||||
val dt = idt.getOr(DataType.UNDEFINED)
|
throw AssemblyError("unknown dt")
|
||||||
when {
|
val dt = idt.getOr(DataType.UNDEFINED)
|
||||||
identifier != null -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, assign.definingSubroutine, variableAsmName = asmgen.asmVariableName(identifier!!), origAstTarget = this)
|
when {
|
||||||
arrayindexed != null -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, dt, assign.definingSubroutine, array = arrayindexed, origAstTarget = this)
|
identifier != null -> {
|
||||||
memoryAddress != null -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, dt, assign.definingSubroutine, memory = memoryAddress, origAstTarget = this)
|
val parameter = identifier!!.targetVarDecl(program)?.subroutineParameter
|
||||||
else -> throw AssemblyError("weird target")
|
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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fromRegisters(registers: RegisterOrPair, scope: Subroutine?, program: Program, asmgen: AsmGen): AsmAssignTarget =
|
fun fromRegisters(registers: RegisterOrPair, signed: Boolean, scope: Subroutine?, program: Program, asmgen: AsmGen): AsmAssignTarget =
|
||||||
when(registers) {
|
when(registers) {
|
||||||
RegisterOrPair.A,
|
RegisterOrPair.A,
|
||||||
RegisterOrPair.X,
|
RegisterOrPair.X,
|
||||||
RegisterOrPair.Y -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UBYTE, scope, register = registers)
|
RegisterOrPair.Y -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, if(signed) DataType.BYTE else DataType.UBYTE, scope, register = registers)
|
||||||
RegisterOrPair.AX,
|
RegisterOrPair.AX,
|
||||||
RegisterOrPair.AY,
|
RegisterOrPair.AY,
|
||||||
RegisterOrPair.XY -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UWORD, scope, register = registers)
|
RegisterOrPair.XY -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, if(signed) DataType.WORD else DataType.UWORD, scope, register = registers)
|
||||||
RegisterOrPair.FAC1,
|
RegisterOrPair.FAC1,
|
||||||
RegisterOrPair.FAC2 -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.FLOAT, scope, register = registers)
|
RegisterOrPair.FAC2 -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.FLOAT, scope, register = registers)
|
||||||
RegisterOrPair.R0,
|
RegisterOrPair.R0,
|
||||||
@ -93,7 +109,7 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
|
|||||||
RegisterOrPair.R12,
|
RegisterOrPair.R12,
|
||||||
RegisterOrPair.R13,
|
RegisterOrPair.R13,
|
||||||
RegisterOrPair.R14,
|
RegisterOrPair.R14,
|
||||||
RegisterOrPair.R15 -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UWORD, scope, register = registers)
|
RegisterOrPair.R15 -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, if(signed) DataType.WORD else DataType.UWORD, scope, register = registers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -110,8 +126,8 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
|||||||
val expression: Expression? = null
|
val expression: Expression? = null
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0}
|
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toUInt() ?: 0u}
|
||||||
val constArrayIndexValue by lazy { array?.indexer?.constIndex() }
|
val constArrayIndexValue by lazy { array?.indexer?.constIndex()?.toUInt() }
|
||||||
|
|
||||||
val asmVarname: String
|
val asmVarname: String
|
||||||
get() = if(array==null)
|
get() = if(array==null)
|
||||||
@ -132,6 +148,9 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
|||||||
is StringLiteralValue -> throw AssemblyError("string literal value should not occur anymore for asm generation")
|
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 ArrayLiteralValue -> throw AssemblyError("array literal value should not occur anymore for asm generation")
|
||||||
is IdentifierReference -> {
|
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 dt = value.inferType(program).getOr(DataType.UNDEFINED)
|
||||||
val varName=asmgen.asmVariableName(value)
|
val varName=asmgen.asmVariableName(value)
|
||||||
// special case: "cx16.r[0-15]" are 16-bits virtual registers of the commander X16 system
|
// special case: "cx16.r[0-15]" are 16-bits virtual registers of the commander X16 system
|
||||||
@ -208,7 +227,7 @@ internal class AsmAssignment(val source: AsmAssignSource,
|
|||||||
if(target.register !in arrayOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY))
|
if(target.register !in arrayOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY))
|
||||||
require(source.datatype != DataType.UNDEFINED) { "must not be placeholder/undefined datatype" }
|
require(source.datatype != DataType.UNDEFINED) { "must not be placeholder/undefined datatype" }
|
||||||
require(memsizer.memorySize(source.datatype) <= memsizer.memorySize(target.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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,19 +5,16 @@ import prog8.ast.base.*
|
|||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.ast.toHex
|
import prog8.ast.toHex
|
||||||
import prog8.compiler.AssemblyError
|
import prog8.compiler.target.AssemblyError
|
||||||
import prog8.compiler.functions.BuiltinFunctions
|
|
||||||
import prog8.compiler.functions.builtinFunctionReturnType
|
|
||||||
import prog8.compiler.target.CpuType
|
|
||||||
import prog8.compiler.target.cpu6502.codegen.AsmGen
|
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,
|
internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||||
private val exprAsmgen: ExpressionsAsmGen
|
|
||||||
) {
|
|
||||||
|
|
||||||
private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, this, exprAsmgen, asmgen)
|
private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, this, asmgen)
|
||||||
|
|
||||||
fun translate(assignment: Assignment) {
|
fun translate(assignment: Assignment) {
|
||||||
val target = AsmAssignTarget.fromAstAssignment(assignment, program, asmgen)
|
val target = AsmAssignTarget.fromAstAssignment(assignment, program, asmgen)
|
||||||
@ -32,15 +29,27 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
translateNormalAssignment(assign)
|
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) {
|
fun translateNormalAssignment(assign: AsmAssignment) {
|
||||||
|
if(assign.isAugmentable) {
|
||||||
|
augmentableAsmGen.translate(assign)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
when(assign.source.kind) {
|
when(assign.source.kind) {
|
||||||
SourceStorageKind.LITERALNUMBER -> {
|
SourceStorageKind.LITERALNUMBER -> {
|
||||||
// simple case: assign a constant number
|
// simple case: assign a constant number
|
||||||
val num = assign.source.number!!.number
|
val num = assign.source.number!!.number
|
||||||
when (assign.target.datatype) {
|
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.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")
|
else -> throw AssemblyError("weird numval type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -126,7 +135,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
val value = assign.source.memory!!
|
val value = assign.source.memory!!
|
||||||
when (value.addressExpression) {
|
when (value.addressExpression) {
|
||||||
is NumericLiteralValue -> {
|
is NumericLiteralValue -> {
|
||||||
val address = (value.addressExpression as NumericLiteralValue).number.toInt()
|
val address = (value.addressExpression as NumericLiteralValue).number.toUInt()
|
||||||
assignMemoryByte(assign.target, address, null)
|
assignMemoryByte(assign.target, address, null)
|
||||||
}
|
}
|
||||||
is IdentifierReference -> {
|
is IdentifierReference -> {
|
||||||
@ -157,7 +166,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
when (val sub = value.target.targetStatement(program)) {
|
when (val sub = value.target.targetStatement(program)) {
|
||||||
is Subroutine -> {
|
is Subroutine -> {
|
||||||
asmgen.saveXbeforeCall(value)
|
asmgen.saveXbeforeCall(value)
|
||||||
asmgen.translateFunctionCall(value)
|
asmgen.translateFunctionCall(value, true)
|
||||||
val returnValue = sub.returntypes.zip(sub.asmReturnvaluesRegisters).singleOrNull { it.second.registerOrPair!=null } ?:
|
val returnValue = sub.returntypes.zip(sub.asmReturnvaluesRegisters).singleOrNull { it.second.registerOrPair!=null } ?:
|
||||||
sub.returntypes.zip(sub.asmReturnvaluesRegisters).single { it.second.statusflag!=null }
|
sub.returntypes.zip(sub.asmReturnvaluesRegisters).single { it.second.statusflag!=null }
|
||||||
when (returnValue.first) {
|
when (returnValue.first) {
|
||||||
@ -171,13 +180,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||||
// copy the actual string result into the target string variable
|
// copy the actual string result into the target string variable
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
pha
|
pha
|
||||||
lda #<${assign.target.asmVarname}
|
lda #<${assign.target.asmVarname}
|
||||||
sta P8ZP_SCRATCH_W1
|
sta P8ZP_SCRATCH_W1
|
||||||
lda #>${assign.target.asmVarname}
|
lda #>${assign.target.asmVarname}
|
||||||
sta P8ZP_SCRATCH_W1+1
|
sta P8ZP_SCRATCH_W1+1
|
||||||
pla
|
pla
|
||||||
jsr prog8_lib.strcpy""")
|
jsr prog8_lib.strcpy""")
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("weird target dt")
|
else -> throw AssemblyError("weird target dt")
|
||||||
}
|
}
|
||||||
@ -249,30 +258,41 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
is PrefixExpression -> {
|
||||||
|
// first assign the value to the target then apply the operator in place on the target.
|
||||||
|
translateNormalAssignment(AsmAssignment(
|
||||||
|
AsmAssignSource.fromAstSource(value.expression, program, asmgen),
|
||||||
|
assign.target,
|
||||||
|
false, program.memsizer, assign.position
|
||||||
|
))
|
||||||
|
val target = virtualRegsToVariables(assign.target)
|
||||||
|
when(value.operator) {
|
||||||
|
"+" -> {}
|
||||||
|
"-" -> augmentableAsmGen.inplaceNegate(target, target.datatype)
|
||||||
|
"~" -> augmentableAsmGen.inplaceInvert(target, target.datatype)
|
||||||
|
"not" -> augmentableAsmGen.inplaceBooleanNot(target, target.datatype)
|
||||||
|
else -> throw AssemblyError("invalid prefix operator")
|
||||||
|
}
|
||||||
|
}
|
||||||
else -> {
|
else -> {
|
||||||
// Everything else just evaluate via the stack.
|
// Everything else just evaluate via the stack.
|
||||||
// (we can't use the assignment helper functions to do it via registers here,
|
// (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...)
|
// because the code here is the implementation of exactly that...)
|
||||||
if (value.parent is Return) {
|
// TODO DON'T STACK-EVAL THIS... by using a temp var? so that it becomes augmentable assignment expression?
|
||||||
if (this.asmgen.options.slowCodegenWarnings)
|
asmgen.translateExpression(value)
|
||||||
println("warning: slow stack evaluation used for return: $value target=${assign.target.kind} at ${value.position}")
|
|
||||||
}
|
|
||||||
exprAsmgen.translateExpression(value)
|
|
||||||
if (assign.target.datatype in WordDatatypes && assign.source.datatype in ByteDatatypes)
|
if (assign.target.datatype in WordDatatypes && assign.source.datatype in ByteDatatypes)
|
||||||
asmgen.signExtendStackLsb(assign.source.datatype)
|
asmgen.signExtendStackLsb(assign.source.datatype)
|
||||||
assignStackValue(assign.target)
|
if(assign.target.kind!=TargetStorageKind.STACK || assign.target.datatype != assign.source.datatype)
|
||||||
|
assignStackValue(assign.target)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SourceStorageKind.REGISTER -> {
|
SourceStorageKind.REGISTER -> {
|
||||||
when(assign.source.datatype) {
|
asmgen.assignRegister(assign.source.register!!, assign.target)
|
||||||
DataType.UBYTE -> assignRegisterByte(assign.target, assign.source.register!!.asCpuRegister())
|
|
||||||
DataType.UWORD -> assignRegisterpairWord(assign.target, assign.source.register!!)
|
|
||||||
else -> throw AssemblyError("invalid register dt")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
SourceStorageKind.STACK -> {
|
SourceStorageKind.STACK -> {
|
||||||
assignStackValue(assign.target)
|
if(assign.target.kind!=TargetStorageKind.STACK || assign.target.datatype != assign.source.datatype)
|
||||||
|
assignStackValue(assign.target)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -330,7 +350,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
|
|
||||||
when (value.addressExpression) {
|
when (value.addressExpression) {
|
||||||
is NumericLiteralValue -> {
|
is NumericLiteralValue -> {
|
||||||
val address = (value.addressExpression as NumericLiteralValue).number.toInt()
|
val address = (value.addressExpression as NumericLiteralValue).number.toUInt()
|
||||||
assignMemoryByteIntoWord(target, address, null)
|
assignMemoryByteIntoWord(target, address, null)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -364,16 +384,16 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
|
|
||||||
when (valueDt) {
|
when (valueDt) {
|
||||||
in ByteDatatypes -> {
|
in ByteDatatypes -> {
|
||||||
assignExpressionToRegister(value, RegisterOrPair.A)
|
assignExpressionToRegister(value, RegisterOrPair.A, valueDt==DataType.BYTE)
|
||||||
assignTypeCastedRegisters(target.asmVarname, targetDt, RegisterOrPair.A, valueDt)
|
assignTypeCastedRegisters(target.asmVarname, targetDt, RegisterOrPair.A, valueDt)
|
||||||
}
|
}
|
||||||
in WordDatatypes -> {
|
in WordDatatypes -> {
|
||||||
assignExpressionToRegister(value, RegisterOrPair.AY)
|
assignExpressionToRegister(value, RegisterOrPair.AY, valueDt==DataType.WORD)
|
||||||
assignTypeCastedRegisters(target.asmVarname, targetDt, RegisterOrPair.AY, valueDt)
|
assignTypeCastedRegisters(target.asmVarname, targetDt, RegisterOrPair.AY, valueDt)
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> {
|
DataType.FLOAT -> {
|
||||||
assignExpressionToRegister(value, RegisterOrPair.FAC1)
|
assignExpressionToRegister(value, RegisterOrPair.FAC1, true)
|
||||||
assignTypecastedFloatFAC1(target.asmVarname, targetDt)
|
assignTypeCastedFloatFAC1(target.asmVarname, targetDt)
|
||||||
}
|
}
|
||||||
in PassByReferenceDatatypes -> {
|
in PassByReferenceDatatypes -> {
|
||||||
// str/array value cast (most likely to UWORD, take address-of)
|
// str/array value cast (most likely to UWORD, take address-of)
|
||||||
@ -399,14 +419,14 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
RegisterOrPair.X,
|
RegisterOrPair.X,
|
||||||
RegisterOrPair.Y -> {
|
RegisterOrPair.Y -> {
|
||||||
// 'cast' an ubyte value to a byte register; no cast needed at all
|
// 'cast' an ubyte value to a byte register; no cast needed at all
|
||||||
return assignExpressionToRegister(value, target.register)
|
return assignExpressionToRegister(value, target.register, false)
|
||||||
}
|
}
|
||||||
RegisterOrPair.AX,
|
RegisterOrPair.AX,
|
||||||
RegisterOrPair.AY,
|
RegisterOrPair.AY,
|
||||||
RegisterOrPair.XY,
|
RegisterOrPair.XY,
|
||||||
in Cx16VirtualRegisters -> {
|
in Cx16VirtualRegisters -> {
|
||||||
// cast an ubyte value to a 16 bits register, just assign it and make use of the value extension
|
// cast an ubyte value to a 16 bits register, just assign it and make use of the value extension
|
||||||
return assignExpressionToRegister(value, target.register!!)
|
return assignExpressionToRegister(value, target.register!!, false)
|
||||||
}
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
@ -424,19 +444,41 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
RegisterOrPair.XY,
|
RegisterOrPair.XY,
|
||||||
in Cx16VirtualRegisters -> {
|
in Cx16VirtualRegisters -> {
|
||||||
// 'cast' uword into a 16 bits register, just assign it
|
// 'cast' uword into a 16 bits register, just assign it
|
||||||
return assignExpressionToRegister(value, target.register!!)
|
return assignExpressionToRegister(value, target.register!!, false)
|
||||||
}
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// give up, do it via eval stack
|
if(targetDt==DataType.FLOAT && (target.register==RegisterOrPair.FAC1 || target.register==RegisterOrPair.FAC2)) {
|
||||||
// note: cannot use assignTypeCastedValue because that is ourselves :P
|
when(valueDt) {
|
||||||
// TODO optimize typecasts for more special cases?
|
DataType.UBYTE -> {
|
||||||
if(this.asmgen.options.slowCodegenWarnings)
|
assignExpressionToRegister(value, RegisterOrPair.Y, false)
|
||||||
println("warning: slow stack evaluation used for typecast: $value into $targetDt (target=${target.kind} at ${value.position}")
|
asmgen.out(" jsr floats.FREADUY")
|
||||||
asmgen.translateExpression(origTypeCastExpression) // this performs the actual type cast in translateExpression(Typecast)
|
}
|
||||||
assignStackValue(target)
|
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) {
|
private fun assignCastViaLsbFunc(value: Expression, target: AsmAssignTarget) {
|
||||||
@ -447,7 +489,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
translateNormalAssignment(assign)
|
translateNormalAssignment(assign)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun assignTypecastedFloatFAC1(targetAsmVarName: String, targetDt: DataType) {
|
private fun assignTypeCastedFloatFAC1(targetAsmVarName: String, targetDt: DataType) {
|
||||||
|
|
||||||
if(targetDt==DataType.FLOAT)
|
if(targetDt==DataType.FLOAT)
|
||||||
throw AssemblyError("typecast to identical type")
|
throw AssemblyError("typecast to identical type")
|
||||||
@ -768,7 +810,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
}
|
}
|
||||||
TargetStorageKind.ARRAY -> {
|
TargetStorageKind.ARRAY -> {
|
||||||
if(target.constArrayIndexValue!=null) {
|
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) {
|
when(target.datatype) {
|
||||||
in ByteDatatypes -> {
|
in ByteDatatypes -> {
|
||||||
asmgen.out(" inx | lda P8ESTACK_LO,x | sta ${target.asmVarname}+$scaledIdx")
|
asmgen.out(" inx | lda P8ESTACK_LO,x | sta ${target.asmVarname}+$scaledIdx")
|
||||||
@ -954,7 +996,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
lda #<$sourceName
|
lda #<$sourceName
|
||||||
ldy #>$sourceName+1
|
ldy #>$sourceName+1
|
||||||
sta P8ESTACK_LO,x
|
sta P8ESTACK_LO,x
|
||||||
sty P8ESTACK_HI,x
|
tya
|
||||||
|
sta P8ESTACK_HI,x
|
||||||
dex""")
|
dex""")
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("string-assign to weird target")
|
else -> throw AssemblyError("string-assign to weird target")
|
||||||
@ -977,7 +1020,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
TargetStorageKind.ARRAY -> {
|
TargetStorageKind.ARRAY -> {
|
||||||
target.array!!
|
target.array!!
|
||||||
if(target.constArrayIndexValue!=null) {
|
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) {
|
when(target.datatype) {
|
||||||
in ByteDatatypes -> {
|
in ByteDatatypes -> {
|
||||||
asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx")
|
asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx")
|
||||||
@ -1032,7 +1075,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
adc #<${target.asmVarname}
|
adc #<${target.asmVarname}
|
||||||
bcc +
|
bcc +
|
||||||
iny
|
iny
|
||||||
+ jsr floats.copy_float""")
|
+ jsr floats.copy_float""")
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("weird dt")
|
else -> throw AssemblyError("weird dt")
|
||||||
}
|
}
|
||||||
@ -1066,6 +1109,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) {
|
internal fun assignFAC1float(target: AsmAssignTarget) {
|
||||||
when(target.kind) {
|
when(target.kind) {
|
||||||
TargetStorageKind.VARIABLE -> {
|
TargetStorageKind.VARIABLE -> {
|
||||||
@ -1201,7 +1249,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
}
|
}
|
||||||
TargetStorageKind.ARRAY -> {
|
TargetStorageKind.ARRAY -> {
|
||||||
if (target.constArrayIndexValue!=null) {
|
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")
|
asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx")
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -1254,7 +1302,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
}
|
}
|
||||||
TargetStorageKind.ARRAY -> {
|
TargetStorageKind.ARRAY -> {
|
||||||
if (wordtarget.constArrayIndexValue!=null) {
|
if (wordtarget.constArrayIndexValue!=null) {
|
||||||
val scaledIdx = wordtarget.constArrayIndexValue!! * 2
|
val scaledIdx = wordtarget.constArrayIndexValue!! * 2u
|
||||||
asmgen.out(" lda $sourceName")
|
asmgen.out(" lda $sourceName")
|
||||||
asmgen.signExtendAYlsb(DataType.BYTE)
|
asmgen.signExtendAYlsb(DataType.BYTE)
|
||||||
asmgen.out(" sta ${wordtarget.asmVarname}+$scaledIdx | sty ${wordtarget.asmVarname}+$scaledIdx+1")
|
asmgen.out(" sta ${wordtarget.asmVarname}+$scaledIdx | sty ${wordtarget.asmVarname}+$scaledIdx+1")
|
||||||
@ -1322,7 +1370,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
}
|
}
|
||||||
TargetStorageKind.ARRAY -> {
|
TargetStorageKind.ARRAY -> {
|
||||||
if (wordtarget.constArrayIndexValue!=null) {
|
if (wordtarget.constArrayIndexValue!=null) {
|
||||||
val scaledIdx = wordtarget.constArrayIndexValue!! * 2
|
val scaledIdx = wordtarget.constArrayIndexValue!! * 2u
|
||||||
asmgen.out(" lda $sourceName | sta ${wordtarget.asmVarname}+$scaledIdx")
|
asmgen.out(" lda $sourceName | sta ${wordtarget.asmVarname}+$scaledIdx")
|
||||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||||
asmgen.out(" stz ${wordtarget.asmVarname}+$scaledIdx+1")
|
asmgen.out(" stz ${wordtarget.asmVarname}+$scaledIdx+1")
|
||||||
@ -1363,7 +1411,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
// these will be correctly typecasted from a byte to a word value
|
// these will be correctly typecasted from a byte to a word value
|
||||||
if(target.register !in Cx16VirtualRegisters &&
|
if(target.register !in Cx16VirtualRegisters &&
|
||||||
target.register!=RegisterOrPair.AX && target.register!=RegisterOrPair.AY && target.register!=RegisterOrPair.XY) {
|
target.register!=RegisterOrPair.AX && target.register!=RegisterOrPair.AY && target.register!=RegisterOrPair.XY) {
|
||||||
if(target.kind==TargetStorageKind.VARIABLE) {
|
if(target.kind== TargetStorageKind.VARIABLE) {
|
||||||
val parts = target.asmVarname.split('.')
|
val parts = target.asmVarname.split('.')
|
||||||
if (parts.size != 2 || parts[0] != "cx16")
|
if (parts.size != 2 || parts[0] != "cx16")
|
||||||
require(target.datatype in ByteDatatypes)
|
require(target.datatype in ByteDatatypes)
|
||||||
@ -1459,7 +1507,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal fun assignRegisterpairWord(target: AsmAssignTarget, regs: RegisterOrPair) {
|
internal fun assignRegisterpairWord(target: AsmAssignTarget, regs: RegisterOrPair) {
|
||||||
require(target.datatype in NumericDatatypes)
|
require(target.datatype in NumericDatatypes || target.datatype in PassByReferenceDatatypes)
|
||||||
if(target.datatype==DataType.FLOAT)
|
if(target.datatype==DataType.FLOAT)
|
||||||
throw AssemblyError("float value should be from FAC1 not from registerpair memory pointer")
|
throw AssemblyError("float value should be from FAC1 not from registerpair memory pointer")
|
||||||
|
|
||||||
@ -1482,7 +1530,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
}
|
}
|
||||||
TargetStorageKind.ARRAY -> {
|
TargetStorageKind.ARRAY -> {
|
||||||
if (target.constArrayIndexValue!=null) {
|
if (target.constArrayIndexValue!=null) {
|
||||||
val idx = target.constArrayIndexValue!! * 2
|
val idx = target.constArrayIndexValue!! * 2u
|
||||||
when (regs) {
|
when (regs) {
|
||||||
RegisterOrPair.AX -> asmgen.out(" sta ${target.asmVarname}+$idx | stx ${target.asmVarname}+$idx+1")
|
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")
|
RegisterOrPair.AY -> asmgen.out(" sta ${target.asmVarname}+$idx | sty ${target.asmVarname}+$idx+1")
|
||||||
@ -1586,7 +1634,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
}
|
}
|
||||||
TargetStorageKind.STACK -> {
|
TargetStorageKind.STACK -> {
|
||||||
when(regs) {
|
when(regs) {
|
||||||
RegisterOrPair.AY -> asmgen.out(" sta P8ESTACK_LO,x | sty P8ESTACK_HI,x | dex")
|
RegisterOrPair.AY -> asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
|
||||||
RegisterOrPair.AX, RegisterOrPair.XY -> throw AssemblyError("can't use X here")
|
RegisterOrPair.AX, RegisterOrPair.XY -> throw AssemblyError("can't use X here")
|
||||||
in Cx16VirtualRegisters -> {
|
in Cx16VirtualRegisters -> {
|
||||||
val srcReg = asmgen.asmSymbolName(regs)
|
val srcReg = asmgen.asmSymbolName(regs)
|
||||||
@ -1703,8 +1751,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun assignConstantByte(target: AsmAssignTarget, byte: Short) {
|
private fun assignConstantByte(target: AsmAssignTarget, byte: Int) {
|
||||||
if(byte==0.toShort() && asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
if(byte==0 && asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
||||||
// optimize setting zero value for this cpu
|
// optimize setting zero value for this cpu
|
||||||
when(target.kind) {
|
when(target.kind) {
|
||||||
TargetStorageKind.VARIABLE -> {
|
TargetStorageKind.VARIABLE -> {
|
||||||
@ -1937,7 +1985,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) {
|
if (address != null) {
|
||||||
when(target.kind) {
|
when(target.kind) {
|
||||||
TargetStorageKind.VARIABLE -> {
|
TargetStorageKind.VARIABLE -> {
|
||||||
@ -2021,7 +2069,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) {
|
if (address != null) {
|
||||||
when(wordtarget.kind) {
|
when(wordtarget.kind) {
|
||||||
TargetStorageKind.VARIABLE -> {
|
TargetStorageKind.VARIABLE -> {
|
||||||
@ -2113,7 +2161,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
fun storeAIntoPointerVar(pointervar: IdentifierReference) {
|
fun storeAIntoPointerVar(pointervar: IdentifierReference) {
|
||||||
val sourceName = asmgen.asmVariableName(pointervar)
|
val sourceName = asmgen.asmVariableName(pointervar)
|
||||||
val vardecl = pointervar.targetVarDecl(program)!!
|
val vardecl = pointervar.targetVarDecl(program)!!
|
||||||
val scopedName = vardecl.makeScopedName(vardecl.name)
|
val scopedName = vardecl.scopedName.joinToString(".")
|
||||||
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
||||||
if (asmgen.isZpVar(scopedName)) {
|
if (asmgen.isZpVar(scopedName)) {
|
||||||
// pointervar is already in the zero page, no need to copy
|
// pointervar is already in the zero page, no need to copy
|
||||||
@ -2157,9 +2205,9 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun assignExpressionToRegister(expr: Expression, register: RegisterOrPair) {
|
internal fun assignExpressionToRegister(expr: Expression, register: RegisterOrPair, signed: Boolean) {
|
||||||
val src = AsmAssignSource.fromAstSource(expr, program, asmgen)
|
val src = AsmAssignSource.fromAstSource(expr, program, asmgen)
|
||||||
val tgt = AsmAssignTarget.fromRegisters(register, null, program, asmgen)
|
val tgt = AsmAssignTarget.fromRegisters(register, signed, null, program, asmgen)
|
||||||
val assign = AsmAssignment(src, tgt, false, program.memsizer, expr.position)
|
val assign = AsmAssignment(src, tgt, false, program.memsizer, expr.position)
|
||||||
translateNormalAssignment(assign)
|
translateNormalAssignment(assign)
|
||||||
}
|
}
|
||||||
@ -2171,8 +2219,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
translateNormalAssignment(assign)
|
translateNormalAssignment(assign)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun assignVariableToRegister(asmVarName: String, register: RegisterOrPair) {
|
internal fun assignVariableToRegister(asmVarName: String, register: RegisterOrPair, signed: Boolean) {
|
||||||
val tgt = AsmAssignTarget.fromRegisters(register, null, program, asmgen)
|
val tgt = AsmAssignTarget.fromRegisters(register, signed, null, program, asmgen)
|
||||||
val src = AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, tgt.datatype, variableAsmName = asmVarName)
|
val src = AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, tgt.datatype, variableAsmName = asmVarName)
|
||||||
val assign = AsmAssignment(src, tgt, false, program.memsizer, Position.DUMMY)
|
val assign = AsmAssignment(src, tgt, false, program.memsizer, Position.DUMMY)
|
||||||
translateNormalAssignment(assign)
|
translateNormalAssignment(assign)
|
@ -5,14 +5,13 @@ import prog8.ast.base.*
|
|||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.statements.Subroutine
|
import prog8.ast.statements.Subroutine
|
||||||
import prog8.ast.toHex
|
import prog8.ast.toHex
|
||||||
import prog8.compiler.AssemblyError
|
import prog8.compiler.target.AssemblyError
|
||||||
import prog8.compiler.target.CpuType
|
|
||||||
import prog8.compiler.target.cpu6502.codegen.AsmGen
|
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,
|
internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||||
private val assignmentAsmGen: AssignmentAsmGen,
|
private val assignmentAsmGen: AssignmentAsmGen,
|
||||||
private val exprAsmGen: ExpressionsAsmGen,
|
|
||||||
private val asmgen: AsmGen
|
private val asmgen: AsmGen
|
||||||
) {
|
) {
|
||||||
fun translate(assign: AsmAssignment) {
|
fun translate(assign: AsmAssignment) {
|
||||||
@ -22,15 +21,16 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
when (val value = assign.source.expression!!) {
|
when (val value = assign.source.expression!!) {
|
||||||
is PrefixExpression -> {
|
is PrefixExpression -> {
|
||||||
// A = -A , A = +A, A = ~A, A = not A
|
// A = -A , A = +A, A = ~A, A = not A
|
||||||
|
val target = assignmentAsmGen.virtualRegsToVariables(assign.target)
|
||||||
val itype = value.inferType(program)
|
val itype = value.inferType(program)
|
||||||
if(!itype.isKnown)
|
if(!itype.isKnown)
|
||||||
throw AssemblyError("unknown dt")
|
throw AssemblyError("unknown dt")
|
||||||
val type = itype.getOr(DataType.UNDEFINED)
|
val type = itype.getOr(DataType.UNDEFINED)
|
||||||
when (value.operator) {
|
when (value.operator) {
|
||||||
"+" -> {}
|
"+" -> {}
|
||||||
"-" -> inplaceNegate(assign.target, type)
|
"-" -> inplaceNegate(target, type)
|
||||||
"~" -> inplaceInvert(assign.target, type)
|
"~" -> inplaceInvert(target, type)
|
||||||
"not" -> inplaceBooleanNot(assign.target, type)
|
"not" -> inplaceBooleanNot(target, type)
|
||||||
else -> throw AssemblyError("invalid prefix operator")
|
else -> throw AssemblyError("invalid prefix operator")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -102,10 +102,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).getOr(DataType.UNDEFINED)) > 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 valueLv = (value as? NumericLiteralValue)?.number
|
||||||
val ident = value as? IdentifierReference
|
val ident = value as? IdentifierReference
|
||||||
val memread = value as? DirectMemoryRead
|
val memread = value as? DirectMemoryRead
|
||||||
@ -181,7 +237,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
asmgen.translateExpression(memory.addressExpression)
|
// TODO 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")
|
asmgen.out(" jsr prog8_lib.read_byte_from_address_on_stack | sta P8ZP_SCRATCH_B1")
|
||||||
when {
|
when {
|
||||||
valueLv != null -> inplaceModification_byte_litval_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, valueLv.toInt())
|
valueLv != null -> inplaceModification_byte_litval_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, valueLv.toInt())
|
||||||
@ -246,19 +303,37 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
indexVar!=null -> {
|
indexVar!=null -> {
|
||||||
when (target.datatype) {
|
when (target.datatype) {
|
||||||
in ByteDatatypes -> {
|
in ByteDatatypes -> {
|
||||||
val tgt = AsmAssignTarget.fromRegisters(RegisterOrPair.A, null, program, asmgen)
|
val tgt =
|
||||||
|
AsmAssignTarget.fromRegisters(
|
||||||
|
RegisterOrPair.A,
|
||||||
|
target.datatype == DataType.BYTE, null,
|
||||||
|
program,
|
||||||
|
asmgen
|
||||||
|
)
|
||||||
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
|
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
|
||||||
assignmentAsmGen.translateNormalAssignment(assign)
|
assignmentAsmGen.translateNormalAssignment(assign)
|
||||||
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A)
|
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A)
|
||||||
}
|
}
|
||||||
in WordDatatypes -> {
|
in WordDatatypes -> {
|
||||||
val tgt = AsmAssignTarget.fromRegisters(RegisterOrPair.AY, null, program, asmgen)
|
val tgt =
|
||||||
|
AsmAssignTarget.fromRegisters(
|
||||||
|
RegisterOrPair.AY,
|
||||||
|
target.datatype == DataType.WORD, null,
|
||||||
|
program,
|
||||||
|
asmgen
|
||||||
|
)
|
||||||
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
|
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
|
||||||
assignmentAsmGen.translateNormalAssignment(assign)
|
assignmentAsmGen.translateNormalAssignment(assign)
|
||||||
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY)
|
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> {
|
DataType.FLOAT -> {
|
||||||
val tgt = AsmAssignTarget.fromRegisters(RegisterOrPair.FAC1, null, program, asmgen)
|
val tgt =
|
||||||
|
AsmAssignTarget.fromRegisters(
|
||||||
|
RegisterOrPair.FAC1,
|
||||||
|
true, null,
|
||||||
|
program,
|
||||||
|
asmgen
|
||||||
|
)
|
||||||
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
|
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
|
||||||
assignmentAsmGen.translateNormalAssignment(assign)
|
assignmentAsmGen.translateNormalAssignment(assign)
|
||||||
assignmentAsmGen.assignFAC1float(target)
|
assignmentAsmGen.assignFAC1float(target)
|
||||||
@ -270,8 +345,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg in-place modification")
|
TargetStorageKind.REGISTER -> throw AssemblyError("no asm gen for reg in-place modification")
|
||||||
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack in-place modification")
|
TargetStorageKind.STACK -> throw AssemblyError("no asm gen for stack in-place modification")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -638,14 +713,14 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
private fun inplaceModification_byte_memread_to_variable(name: String, dt: DataType, operator: String, memread: DirectMemoryRead) {
|
private fun inplaceModification_byte_memread_to_variable(name: String, dt: DataType, operator: String, memread: DirectMemoryRead) {
|
||||||
when (operator) {
|
when (operator) {
|
||||||
"+" -> {
|
"+" -> {
|
||||||
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
clc
|
clc
|
||||||
adc $name
|
adc $name
|
||||||
sta $name""")
|
sta $name""")
|
||||||
}
|
}
|
||||||
"-" -> {
|
"-" -> {
|
||||||
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
sta P8ZP_SCRATCH_B1
|
sta P8ZP_SCRATCH_B1
|
||||||
lda $name
|
lda $name
|
||||||
@ -654,15 +729,15 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
sta $name""")
|
sta $name""")
|
||||||
}
|
}
|
||||||
"|", "or" -> {
|
"|", "or" -> {
|
||||||
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
|
||||||
asmgen.out(" ora $name | sta $name")
|
asmgen.out(" ora $name | sta $name")
|
||||||
}
|
}
|
||||||
"&", "and" -> {
|
"&", "and" -> {
|
||||||
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
|
||||||
asmgen.out(" and $name | sta $name")
|
asmgen.out(" and $name | sta $name")
|
||||||
}
|
}
|
||||||
"^", "xor" -> {
|
"^", "xor" -> {
|
||||||
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
|
||||||
asmgen.out(" eor $name | sta $name")
|
asmgen.out(" eor $name | sta $name")
|
||||||
}
|
}
|
||||||
// TODO: tuned code for more operators
|
// TODO: tuned code for more operators
|
||||||
@ -675,7 +750,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
private fun inplaceModification_word_memread_to_variable(name: String, dt: DataType, operator: String, memread: DirectMemoryRead) {
|
private fun inplaceModification_word_memread_to_variable(name: String, dt: DataType, operator: String, memread: DirectMemoryRead) {
|
||||||
when (operator) {
|
when (operator) {
|
||||||
"+" -> {
|
"+" -> {
|
||||||
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
clc
|
clc
|
||||||
adc $name
|
adc $name
|
||||||
@ -685,7 +760,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
+""")
|
+""")
|
||||||
}
|
}
|
||||||
"-" -> {
|
"-" -> {
|
||||||
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
sta P8ZP_SCRATCH_B1
|
sta P8ZP_SCRATCH_B1
|
||||||
lda $name
|
lda $name
|
||||||
@ -697,11 +772,11 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
+""")
|
+""")
|
||||||
}
|
}
|
||||||
"|", "or" -> {
|
"|", "or" -> {
|
||||||
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
|
||||||
asmgen.out(" ora $name | sta $name")
|
asmgen.out(" ora $name | sta $name")
|
||||||
}
|
}
|
||||||
"&", "and" -> {
|
"&", "and" -> {
|
||||||
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
|
||||||
asmgen.out(" and $name | sta $name")
|
asmgen.out(" and $name | sta $name")
|
||||||
if(dt in WordDatatypes) {
|
if(dt in WordDatatypes) {
|
||||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||||
@ -711,7 +786,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"^", "xor" -> {
|
"^", "xor" -> {
|
||||||
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
|
||||||
asmgen.out(" eor $name | sta $name")
|
asmgen.out(" eor $name | sta $name")
|
||||||
}
|
}
|
||||||
// TODO: tuned code for more operators
|
// TODO: tuned code for more operators
|
||||||
@ -1084,7 +1159,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
sta $name
|
sta $name
|
||||||
lda P8ZP_SCRATCH_W2+1
|
lda P8ZP_SCRATCH_W2+1
|
||||||
sta $name+1
|
sta $name+1
|
||||||
""") }
|
""")
|
||||||
|
}
|
||||||
"<<" -> {
|
"<<" -> {
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
ldy $otherName
|
ldy $otherName
|
||||||
@ -1705,7 +1781,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun inplaceBooleanNot(target: AsmAssignTarget, dt: DataType) {
|
internal fun inplaceBooleanNot(target: AsmAssignTarget, dt: DataType) {
|
||||||
when (dt) {
|
when (dt) {
|
||||||
DataType.UBYTE -> {
|
DataType.UBYTE -> {
|
||||||
when (target.kind) {
|
when (target.kind) {
|
||||||
@ -1749,9 +1825,30 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place not of ubyte array")
|
TargetStorageKind.REGISTER -> {
|
||||||
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg not")
|
when(target.register!!) {
|
||||||
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack not")
|
RegisterOrPair.A -> asmgen.out("""
|
||||||
|
cmp #0
|
||||||
|
beq +
|
||||||
|
lda #1
|
||||||
|
+ eor #1""")
|
||||||
|
RegisterOrPair.X -> asmgen.out("""
|
||||||
|
txa
|
||||||
|
beq +
|
||||||
|
lda #1
|
||||||
|
+ eor #1
|
||||||
|
tax""")
|
||||||
|
RegisterOrPair.Y -> asmgen.out("""
|
||||||
|
tya
|
||||||
|
beq +
|
||||||
|
lda #1
|
||||||
|
+ eor #1
|
||||||
|
tay""")
|
||||||
|
else -> throw AssemblyError("invalid reg dt for byte not")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 -> {
|
DataType.UWORD -> {
|
||||||
@ -1767,17 +1864,55 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
lsr a
|
lsr a
|
||||||
sta ${target.asmVarname}+1""")
|
sta ${target.asmVarname}+1""")
|
||||||
}
|
}
|
||||||
TargetStorageKind.MEMORY -> throw AssemblyError("no asm gen for uword-memory not")
|
TargetStorageKind.REGISTER -> {
|
||||||
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place not of uword array")
|
when(target.register!!) {
|
||||||
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg not")
|
RegisterOrPair.AX -> {
|
||||||
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack not")
|
asmgen.out("""
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
ora P8ZP_SCRATCH_REG
|
||||||
|
beq +
|
||||||
|
lda #0
|
||||||
|
tax
|
||||||
|
beq ++
|
||||||
|
+ lda #1
|
||||||
|
+""")
|
||||||
|
}
|
||||||
|
RegisterOrPair.AY -> {
|
||||||
|
asmgen.out("""
|
||||||
|
sty P8ZP_SCRATCH_REG
|
||||||
|
ora P8ZP_SCRATCH_REG
|
||||||
|
beq +
|
||||||
|
lda #0
|
||||||
|
tay
|
||||||
|
beq ++
|
||||||
|
+ lda #1
|
||||||
|
+""")
|
||||||
|
}
|
||||||
|
RegisterOrPair.XY -> {
|
||||||
|
asmgen.out("""
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
tya
|
||||||
|
ora P8ZP_SCRATCH_REG
|
||||||
|
beq +
|
||||||
|
ldy #0
|
||||||
|
ldx #0
|
||||||
|
beq ++
|
||||||
|
+ ldx #1
|
||||||
|
+""")
|
||||||
|
}
|
||||||
|
in Cx16VirtualRegisters -> throw AssemblyError("cx16 virtual regs should be variables, not real registers")
|
||||||
|
else -> throw AssemblyError("invalid reg dt for word not")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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")
|
else -> throw AssemblyError("boolean-not of invalid type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun inplaceInvert(target: AsmAssignTarget, dt: DataType) {
|
internal fun inplaceInvert(target: AsmAssignTarget, dt: DataType) {
|
||||||
when (dt) {
|
when (dt) {
|
||||||
DataType.UBYTE -> {
|
DataType.UBYTE -> {
|
||||||
when (target.kind) {
|
when (target.kind) {
|
||||||
@ -1812,9 +1947,16 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place invert ubyte array")
|
TargetStorageKind.REGISTER -> {
|
||||||
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg invert")
|
when(target.register!!) {
|
||||||
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack invert")
|
RegisterOrPair.A -> asmgen.out(" eor #255")
|
||||||
|
RegisterOrPair.X -> asmgen.out(" txa | eor #255 | tax")
|
||||||
|
RegisterOrPair.Y -> asmgen.out(" tya | eor #255 | tay")
|
||||||
|
else -> throw AssemblyError("invalid reg dt for byte invert")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 -> {
|
DataType.UWORD -> {
|
||||||
@ -1828,17 +1970,24 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
eor #255
|
eor #255
|
||||||
sta ${target.asmVarname}+1""")
|
sta ${target.asmVarname}+1""")
|
||||||
}
|
}
|
||||||
TargetStorageKind.MEMORY -> throw AssemblyError("no asm gen for uword-memory invert")
|
TargetStorageKind.REGISTER -> {
|
||||||
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place invert uword array")
|
when(target.register!!) {
|
||||||
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg invert")
|
RegisterOrPair.AX -> asmgen.out(" pha | txa | eor #255 | tax | pla | eor #255")
|
||||||
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack invert")
|
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 -> throw AssemblyError("cx16 virtual regs should be variables, not real registers")
|
||||||
|
else -> throw AssemblyError("invalid reg dt for word invert")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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")
|
else -> throw AssemblyError("invert of invalid type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun inplaceNegate(target: AsmAssignTarget, dt: DataType) {
|
internal fun inplaceNegate(target: AsmAssignTarget, dt: DataType) {
|
||||||
when (dt) {
|
when (dt) {
|
||||||
DataType.BYTE -> {
|
DataType.BYTE -> {
|
||||||
when (target.kind) {
|
when (target.kind) {
|
||||||
@ -1849,10 +1998,23 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
sbc ${target.asmVarname}
|
sbc ${target.asmVarname}
|
||||||
sta ${target.asmVarname}""")
|
sta ${target.asmVarname}""")
|
||||||
}
|
}
|
||||||
TargetStorageKind.MEMORY -> throw AssemblyError("can't in-place negate memory ubyte")
|
TargetStorageKind.REGISTER -> {
|
||||||
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place negate byte array")
|
when(target.register!!) {
|
||||||
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg negate")
|
RegisterOrPair.A -> {
|
||||||
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack negate")
|
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 -> 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 -> {
|
DataType.WORD -> {
|
||||||
@ -1867,10 +2029,52 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
sbc ${target.asmVarname}+1
|
sbc ${target.asmVarname}+1
|
||||||
sta ${target.asmVarname}+1""")
|
sta ${target.asmVarname}+1""")
|
||||||
}
|
}
|
||||||
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place negate word array")
|
TargetStorageKind.REGISTER -> {
|
||||||
TargetStorageKind.MEMORY -> throw AssemblyError("no asm gen for word memory negate")
|
when(target.register!!) { //P8ZP_SCRATCH_REG
|
||||||
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg negate")
|
RegisterOrPair.AX -> {
|
||||||
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack negate")
|
asmgen.out("""
|
||||||
|
sta P8ZP_SCRATCH_REG
|
||||||
|
stx P8ZP_SCRATCH_REG+1
|
||||||
|
lda #0
|
||||||
|
sec
|
||||||
|
sbc P8ZP_SCRATCH_REG
|
||||||
|
pha
|
||||||
|
lda #0
|
||||||
|
sbc P8ZP_SCRATCH_REG+1
|
||||||
|
tax
|
||||||
|
pla""")
|
||||||
|
}
|
||||||
|
RegisterOrPair.AY -> {
|
||||||
|
asmgen.out("""
|
||||||
|
sta P8ZP_SCRATCH_REG
|
||||||
|
sty P8ZP_SCRATCH_REG+1
|
||||||
|
lda #0
|
||||||
|
sec
|
||||||
|
sbc P8ZP_SCRATCH_REG
|
||||||
|
pha
|
||||||
|
lda #0
|
||||||
|
sbc P8ZP_SCRATCH_REG+1
|
||||||
|
tay
|
||||||
|
pla""")
|
||||||
|
}
|
||||||
|
RegisterOrPair.XY -> {
|
||||||
|
asmgen.out("""
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
sty P8ZP_SCRATCH_REG+1
|
||||||
|
lda #0
|
||||||
|
sec
|
||||||
|
sbc P8ZP_SCRATCH_REG
|
||||||
|
tax
|
||||||
|
lda #0
|
||||||
|
sbc P8ZP_SCRATCH_REG+1
|
||||||
|
tay""")
|
||||||
|
}
|
||||||
|
in Cx16VirtualRegisters -> throw AssemblyError("cx16 virtual regs should be variables, not real registers")
|
||||||
|
else -> throw AssemblyError("invalid reg dt for word neg")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TargetStorageKind.STACK -> TODO("no asm gen for word stack negate")
|
||||||
|
else -> throw AssemblyError("no asm gen for in-place negate word")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> {
|
DataType.FLOAT -> {
|
||||||
@ -1883,12 +2087,11 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
sta ${target.asmVarname}+1
|
sta ${target.asmVarname}+1
|
||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place negate float array")
|
TargetStorageKind.STACK -> TODO("no asm gen for stack float negate")
|
||||||
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack float negate")
|
else -> throw AssemblyError("weird target kind for inplace negate float ${target.kind}")
|
||||||
else -> throw AssemblyError("weird target kind for float")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("negate of invalid type")
|
else -> throw AssemblyError("negate of invalid type $dt")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,14 +1,13 @@
|
|||||||
package prog8.compiler.target.cx16
|
package prog8.compiler.target.cx16
|
||||||
|
|
||||||
import prog8.compiler.*
|
|
||||||
import prog8.compiler.target.CpuType
|
|
||||||
import prog8.compiler.target.IMachineDefinition
|
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition
|
import prog8.compiler.target.c64.C64MachineDefinition
|
||||||
import prog8.compiler.target.cbm.viceMonListPostfix
|
import prog8.compiler.target.cbm.viceMonListPostfix
|
||||||
|
import prog8.compilerinterface.*
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
internal object CX16MachineDefinition: IMachineDefinition {
|
|
||||||
|
object CX16MachineDefinition: IMachineDefinition {
|
||||||
|
|
||||||
override val cpu = CpuType.CPU65c02
|
override val cpu = CpuType.CPU65c02
|
||||||
|
|
||||||
@ -17,12 +16,12 @@ internal object CX16MachineDefinition: IMachineDefinition {
|
|||||||
override val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,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_MEM_SIZE = 5
|
||||||
override val POINTER_MEM_SIZE = 2
|
override val POINTER_MEM_SIZE = 2
|
||||||
override val BASIC_LOAD_ADDRESS = 0x0801
|
override val BASIC_LOAD_ADDRESS = 0x0801u
|
||||||
override val RAW_LOAD_ADDRESS = 0x8000
|
override val RAW_LOAD_ADDRESS = 0x8000u
|
||||||
|
|
||||||
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
// 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_LO = 0x0400u // $0400-$04ff inclusive
|
||||||
override val ESTACK_HI = 0x0500 // $0500-$05ff inclusive
|
override val ESTACK_HI = 0x0500u // $0500-$05ff inclusive
|
||||||
|
|
||||||
override lateinit var zeropage: Zeropage
|
override lateinit var zeropage: Zeropage
|
||||||
|
|
||||||
@ -68,7 +67,7 @@ internal 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 initializeZeropage(compilerOptions: CompilationOptions) {
|
override fun initializeZeropage(compilerOptions: CompilationOptions) {
|
||||||
zeropage = CX16Zeropage(compilerOptions)
|
zeropage = CX16Zeropage(compilerOptions)
|
||||||
@ -88,47 +87,38 @@ internal object CX16MachineDefinition: IMachineDefinition {
|
|||||||
"rmb", "smb", "stp", "wai")
|
"rmb", "smb", "stp", "wai")
|
||||||
|
|
||||||
|
|
||||||
internal class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
|
class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||||
|
|
||||||
override val SCRATCH_B1 = 0x7a // temp storage for a single byte
|
override val SCRATCH_B1 = 0x7au // temp storage for a single byte
|
||||||
override val SCRATCH_REG = 0x7b // temp storage for a register, must be B1+1
|
override val SCRATCH_REG = 0x7bu // 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_W1 = 0x7cu // temp storage 1 for a word $7c+$7d
|
||||||
override val SCRATCH_W2 = 0x7e // temp storage 2 for a word $7e+$7f
|
override val SCRATCH_W2 = 0x7eu // temp storage 2 for a word $7e+$7f
|
||||||
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (options.floats && options.zeropage !in arrayOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
|
if (options.floats && options.zeropage !in arrayOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
|
||||||
throw CompilerException("when floats are enabled, zero page type should be 'basicsafe' or '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.
|
// the addresses 0x02 to 0x21 (inclusive) are taken for sixteen virtual 16-bit api registers.
|
||||||
|
|
||||||
when (options.zeropage) {
|
when (options.zeropage) {
|
||||||
ZeropageType.FULL -> {
|
ZeropageType.FULL -> {
|
||||||
free.addAll(0x22..0xff)
|
free.addAll(0x22u..0xffu)
|
||||||
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
|
|
||||||
}
|
}
|
||||||
ZeropageType.KERNALSAFE -> {
|
ZeropageType.KERNALSAFE -> {
|
||||||
free.addAll(0x22..0x7f)
|
free.addAll(0x22u..0x7fu)
|
||||||
free.addAll(0xa9..0xff)
|
free.addAll(0xa9u..0xffu)
|
||||||
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
|
|
||||||
}
|
}
|
||||||
ZeropageType.BASICSAFE -> {
|
ZeropageType.BASICSAFE -> {
|
||||||
free.addAll(0x22..0x7f)
|
free.addAll(0x22u..0x7fu)
|
||||||
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
|
|
||||||
}
|
}
|
||||||
ZeropageType.DONTUSE -> {
|
ZeropageType.DONTUSE -> {
|
||||||
free.clear() // don't use zeropage at all
|
free.clear() // don't use zeropage at all
|
||||||
}
|
}
|
||||||
else -> throw CompilerException("for this machine target, zero page type 'floatsafe' is not available. ${options.zeropage}")
|
else -> throw InternalCompilerException("for this machine target, zero page type 'floatsafe' is not available. ${options.zeropage}")
|
||||||
}
|
}
|
||||||
|
|
||||||
require(SCRATCH_B1 !in free)
|
removeReservedFromFreePool()
|
||||||
require(SCRATCH_REG !in free)
|
|
||||||
require(SCRATCH_W1 !in free)
|
|
||||||
require(SCRATCH_W2 !in free)
|
|
||||||
|
|
||||||
for (reserved in options.zpReserved)
|
|
||||||
reserve(reserved)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,9 +1,8 @@
|
|||||||
package prog8tests
|
package prog8tests.asmgen
|
||||||
|
|
||||||
import org.hamcrest.MatcherAssert.assertThat
|
import io.kotest.assertions.withClue
|
||||||
import org.hamcrest.Matchers.equalTo
|
import io.kotest.core.spec.style.StringSpec
|
||||||
import org.junit.jupiter.api.Test
|
import io.kotest.matchers.shouldBe
|
||||||
import org.junit.jupiter.api.TestInstance
|
|
||||||
import prog8.ast.Module
|
import prog8.ast.Module
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
@ -11,31 +10,30 @@ import prog8.ast.expressions.AddressOf
|
|||||||
import prog8.ast.expressions.IdentifierReference
|
import prog8.ast.expressions.IdentifierReference
|
||||||
import prog8.ast.expressions.NumericLiteralValue
|
import prog8.ast.expressions.NumericLiteralValue
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.compiler.*
|
|
||||||
import prog8.compiler.target.C64Target
|
import prog8.compiler.target.C64Target
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition
|
import prog8.compiler.target.c64.C64MachineDefinition
|
||||||
import prog8.compiler.target.cpu6502.codegen.AsmGen
|
import prog8.compiler.target.cpu6502.codegen.AsmGen
|
||||||
|
import prog8.compilerinterface.*
|
||||||
import prog8.parser.SourceCode
|
import prog8.parser.SourceCode
|
||||||
import prog8tests.helpers.DummyFunctions
|
import prog8tests.asmgen.helpers.DummyFunctions
|
||||||
import prog8tests.helpers.DummyMemsizer
|
import prog8tests.asmgen.helpers.DummyMemsizer
|
||||||
|
import prog8tests.asmgen.helpers.DummyStringEncoder
|
||||||
|
import prog8tests.asmgen.helpers.ErrorReporterForTests
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
class AsmGenSymbolsTests: StringSpec({
|
||||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
fun createTestProgram(): Program {
|
||||||
class TestAsmGen6502 {
|
|
||||||
|
|
||||||
private fun createTestProgram(): Program {
|
|
||||||
/*
|
/*
|
||||||
main {
|
main {
|
||||||
|
|
||||||
label_outside:
|
label_outside:
|
||||||
uword var_outside
|
uword var_outside
|
||||||
|
|
||||||
sub start () {
|
sub start () {
|
||||||
uword localvar = 1234
|
uword localvar = 1234
|
||||||
uword tgt
|
uword tgt
|
||||||
|
|
||||||
locallabel:
|
locallabel:
|
||||||
tgt = localvar
|
tgt = localvar
|
||||||
tgt = &locallabel
|
tgt = &locallabel
|
||||||
tgt = &var_outside
|
tgt = &var_outside
|
||||||
@ -45,11 +43,11 @@ locallabel:
|
|||||||
tgt = &main.var_outside
|
tgt = &main.var_outside
|
||||||
tgt = &main.label_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 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, 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 labelInSub = Label("locallabel", Position.DUMMY)
|
||||||
|
|
||||||
val tgt = AssignTarget(IdentifierReference(listOf("tgt"), Position.DUMMY), null, null, Position.DUMMY)
|
val tgt = AssignTarget(IdentifierReference(listOf("tgt"), Position.DUMMY), null, null, Position.DUMMY)
|
||||||
@ -63,84 +61,112 @@ locallabel:
|
|||||||
val assign8 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","label_outside"), Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
val assign8 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","label_outside"), Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||||
|
|
||||||
val statements = mutableListOf(varInSub, var2InSub, labelInSub, assign1, assign2, assign3, assign4, assign5, assign6, assign7, assign8)
|
val statements = mutableListOf(varInSub, var2InSub, labelInSub, assign1, assign2, assign3, assign4, assign5, assign6, assign7, assign8)
|
||||||
val subroutine = Subroutine("start", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, statements, Position.DUMMY)
|
val subroutine = Subroutine("start", mutableListOf(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, statements, Position.DUMMY)
|
||||||
val labelInBlock = Label("label_outside", 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 block = Block("main", null, mutableListOf(labelInBlock, varInBlock, subroutine), false, Position.DUMMY)
|
||||||
|
|
||||||
val module = Module(mutableListOf(block), Position.DUMMY, SourceCode.Generated("test"))
|
val module = Module(mutableListOf(block), Position.DUMMY, SourceCode.Generated("test"))
|
||||||
val program = Program("test", DummyFunctions, DummyMemsizer)
|
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||||
.addModule(module)
|
.addModule(module)
|
||||||
module.linkIntoProgram(program)
|
|
||||||
return program
|
return program
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createTestAsmGen(program: Program): AsmGen {
|
fun createTestAsmGen(program: Program): AsmGen {
|
||||||
val errors = ErrorReporter()
|
val errors = ErrorReporterForTests()
|
||||||
val options = CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, true, C64Target)
|
val options = CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, true, C64Target)
|
||||||
val zp = C64MachineDefinition.C64Zeropage(options)
|
val zp = C64MachineDefinition.C64Zeropage(options)
|
||||||
val asmgen = AsmGen(program, errors, zp, options, C64Target, Path.of(""))
|
val asmgen = AsmGen(program, errors, zp, options, C64Target, Path.of(""))
|
||||||
return asmgen
|
return asmgen
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
"symbol and variable names from strings" {
|
||||||
fun testSymbolNameFromStrings() {
|
|
||||||
val program = createTestProgram()
|
val program = createTestProgram()
|
||||||
val asmgen = createTestAsmGen(program)
|
val asmgen = createTestAsmGen(program)
|
||||||
|
asmgen.asmSymbolName("name") shouldBe "name"
|
||||||
assertThat(asmgen.asmSymbolName("name"), equalTo("name"))
|
asmgen.asmSymbolName("name") shouldBe "name"
|
||||||
assertThat(asmgen.asmSymbolName("<name>"), equalTo("prog8_name"))
|
asmgen.asmSymbolName("<name>") shouldBe "prog8_name"
|
||||||
assertThat(asmgen.asmSymbolName(RegisterOrPair.R15), equalTo("cx16.r15"))
|
asmgen.asmSymbolName(RegisterOrPair.R15) shouldBe "cx16.r15"
|
||||||
assertThat(asmgen.asmSymbolName(listOf("a", "b", "name")), equalTo("a.b.name"))
|
asmgen.asmSymbolName(listOf("a", "b", "name")) shouldBe "a.b.name"
|
||||||
assertThat(asmgen.asmVariableName("name"), equalTo("name"))
|
asmgen.asmVariableName("name") shouldBe "name"
|
||||||
assertThat(asmgen.asmVariableName("<name>"), equalTo("prog8_name"))
|
asmgen.asmVariableName("<name>") shouldBe "prog8_name"
|
||||||
assertThat(asmgen.asmVariableName(listOf("a", "b", "name")), equalTo("a.b.name"))
|
asmgen.asmVariableName(listOf("a", "b", "name")) shouldBe "a.b.name"
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
"symbol and variable names from variable identifiers" {
|
||||||
fun testSymbolNameFromVarIdentifier() {
|
|
||||||
val program = createTestProgram()
|
val program = createTestProgram()
|
||||||
val asmgen = createTestAsmGen(program)
|
val asmgen = createTestAsmGen(program)
|
||||||
val sub = program.entrypoint
|
val sub = program.entrypoint
|
||||||
|
|
||||||
// local variable
|
// local variable
|
||||||
val localvarIdent = sub.statements.filterIsInstance<Assignment>().first { it.value is IdentifierReference }.value as IdentifierReference
|
val localvarIdent = sub.statements.filterIsInstance<Assignment>().first { it.value is IdentifierReference }.value as IdentifierReference
|
||||||
assertThat(asmgen.asmSymbolName(localvarIdent), equalTo("localvar"))
|
asmgen.asmSymbolName(localvarIdent) shouldBe "localvar"
|
||||||
assertThat(asmgen.asmVariableName(localvarIdent), equalTo("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
|
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"))
|
asmgen.asmSymbolName(localvarIdentScoped) shouldBe "main.start.localvar"
|
||||||
assertThat(asmgen.asmVariableName(localvarIdentScoped), equalTo("main.start.localvar"))
|
asmgen.asmVariableName(localvarIdentScoped) shouldBe "main.start.localvar"
|
||||||
|
|
||||||
// variable from outer scope (note that for Variables, no scoping prefix symbols are required,
|
// 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
|
// 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
|
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"))
|
asmgen.asmSymbolName(scopedVarIdent) shouldBe "main.var_outside"
|
||||||
assertThat(asmgen.asmVariableName(scopedVarIdent), equalTo("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
|
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"))
|
asmgen.asmSymbolName(scopedVarIdentScoped) shouldBe "main.var_outside"
|
||||||
assertThat(asmgen.asmVariableName(scopedVarIdentScoped), equalTo("main.var_outside"))
|
asmgen.asmVariableName(scopedVarIdentScoped) shouldBe "main.var_outside"
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
"symbol and variable names from label identifiers" {
|
||||||
fun testSymbolNameFromLabelIdentifier() {
|
|
||||||
val program = createTestProgram()
|
val program = createTestProgram()
|
||||||
val asmgen = createTestAsmGen(program)
|
val asmgen = createTestAsmGen(program)
|
||||||
val sub = program.entrypoint
|
val sub = program.entrypoint
|
||||||
|
|
||||||
// local label
|
// local label
|
||||||
val localLabelIdent = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("locallabel") }.value as AddressOf).identifier
|
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"))
|
asmgen.asmSymbolName(localLabelIdent) shouldBe "_locallabel"
|
||||||
assertThat("as a variable it uses different naming rules (no underscore prefix)", asmgen.asmVariableName(localLabelIdent), equalTo("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
|
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"))
|
asmgen.asmSymbolName(localLabelIdentScoped) shouldBe "main.start._locallabel"
|
||||||
assertThat("as a variable it uses different naming rules (no underscore prefix)", asmgen.asmVariableName(localLabelIdentScoped), equalTo("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
|
// 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
|
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"))
|
asmgen.asmSymbolName(scopedLabelIdent) shouldBe "main._label_outside"
|
||||||
assertThat("as a variable it uses different naming rules (no underscore prefix)", asmgen.asmVariableName(scopedLabelIdent), equalTo("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
|
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"))
|
asmgen.asmSymbolName(scopedLabelIdentScoped) shouldBe "main._label_outside"
|
||||||
assertThat("as a variable it uses different naming rules (no underscore prefix)", asmgen.asmVariableName(scopedLabelIdentScoped), equalTo("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"
|
||||||
|
}
|
||||||
|
})
|
8
codeGeneration/test/ProjectConfig.kt
Normal file
8
codeGeneration/test/ProjectConfig.kt
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package prog8tests.asmgen
|
||||||
|
|
||||||
|
import io.kotest.core.config.AbstractProjectConfig
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
|
object ProjectConfig : AbstractProjectConfig() {
|
||||||
|
override val parallelism = max(2, Runtime.getRuntime().availableProcessors() / 2)
|
||||||
|
}
|
37
codeGeneration/test/helpers/Dummies.kt
Normal file
37
codeGeneration/test/helpers/Dummies.kt
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
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) = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val DummyStringEncoder = object : IStringEncoding {
|
||||||
|
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean): String {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
30
codeGeneration/test/helpers/ErrorReporterForTests.kt
Normal file
30
codeGeneration/test/helpers/ErrorReporterForTests.kt
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
32
codeOptimizers/build.gradle
Normal file
32
codeOptimizers/build.gradle
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
|
||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
id 'application'
|
||||||
|
id "org.jetbrains.kotlin.jvm"
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
toolchain {
|
||||||
|
languageVersion = JavaLanguageVersion.of(javaVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation project(':compilerInterfaces')
|
||||||
|
implementation project(':compilerAst')
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||||
|
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
java {
|
||||||
|
srcDirs = ["${project.projectDir}/src"]
|
||||||
|
}
|
||||||
|
resources {
|
||||||
|
srcDirs = ["${project.projectDir}/res"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// note: there are no unit tests in this module!
|
16
codeOptimizers/codeOptimizers.iml
Normal file
16
codeOptimizers/codeOptimizers.iml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="JAVA_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||||
|
<orderEntry type="module" module-name="compilerInterfaces" />
|
||||||
|
<orderEntry type="module" module-name="compilerAst" />
|
||||||
|
</component>
|
||||||
|
</module>
|
2
codeOptimizers/readme-tests.txt
Normal file
2
codeOptimizers/readme-tests.txt
Normal file
@ -0,0 +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.
|
128
codeOptimizers/src/prog8/optimizer/BinExprSplitter.kt
Normal file
128
codeOptimizers/src/prog8/optimizer/BinExprSplitter.kt
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
package prog8.optimizer
|
||||||
|
|
||||||
|
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
|
||||||
|
import prog8.ast.walk.AstWalker
|
||||||
|
import prog8.ast.walk.IAstModification
|
||||||
|
import prog8.compilerinterface.CompilationOptions
|
||||||
|
import prog8.compilerinterface.ICompilationTarget
|
||||||
|
import prog8.compilerinterface.isIOAddress
|
||||||
|
|
||||||
|
|
||||||
|
class BinExprSplitter(private val program: Program, private val options: CompilationOptions, private val compTarget: ICompilationTarget) : AstWalker() {
|
||||||
|
|
||||||
|
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) {
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Reduce the complexity of a (binary) expression that has to be evaluated on the eval stack,
|
||||||
|
by attempting to splitting it up into individual simple steps.
|
||||||
|
We only consider a binary expression *one* level deep (so the operands must not be a combined expression)
|
||||||
|
|
||||||
|
|
||||||
|
X = BinExpr X = LeftExpr
|
||||||
|
<operator> followed by
|
||||||
|
/ \ IF 'X' not used X = BinExpr
|
||||||
|
/ \ IN expression ==> <operator>
|
||||||
|
/ \ / \
|
||||||
|
LeftExpr. RightExpr. / \
|
||||||
|
X RightExpr.
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
if(binExpr.operator in augmentAssignmentOperators && isSimpleTarget(assignment.target)) {
|
||||||
|
if(assignment.target isSameAs binExpr.right)
|
||||||
|
return noModifications
|
||||||
|
if(assignment.target isSameAs binExpr.left) {
|
||||||
|
if(binExpr.right.isSimple)
|
||||||
|
return noModifications
|
||||||
|
val leftBx = binExpr.left as? BinaryExpression
|
||||||
|
if(leftBx!=null && (!leftBx.left.isSimple || !leftBx.right.isSimple))
|
||||||
|
return noModifications
|
||||||
|
val rightBx = binExpr.right as? BinaryExpression
|
||||||
|
if(rightBx!=null && (!rightBx.left.isSimple || !rightBx.right.isSimple))
|
||||||
|
return noModifications
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
return listOf(
|
||||||
|
IAstModification.ReplaceNode(binExpr, augExpr, assignment),
|
||||||
|
IAstModification.InsertBefore(assignment, firstAssign, assignment.parent as IStatementContainer)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO further unraveling of binary expression trees into flat statements.
|
||||||
|
// 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.isIOAddress(compTarget.machine)
|
||||||
|
else
|
||||||
|
false
|
||||||
|
|
||||||
|
}
|
@ -46,14 +46,14 @@ class ConstExprEvaluator {
|
|||||||
left.number.toInt().ushr(amount.number.toInt())
|
left.number.toInt().ushr(amount.number.toInt())
|
||||||
else
|
else
|
||||||
left.number.toInt().shr(amount.number.toInt())
|
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 {
|
private fun shiftedleft(left: NumericLiteralValue, amount: NumericLiteralValue): Expression {
|
||||||
if(left.type !in IntegerDatatypes || amount.type !in IntegerDatatypes)
|
if(left.type !in IntegerDatatypes || amount.type !in IntegerDatatypes)
|
||||||
throw ExpressionError("cannot compute $left << $amount", left.position)
|
throw ExpressionError("cannot compute $left << $amount", left.position)
|
||||||
val result = left.number.toInt().shl(amount.number.toInt())
|
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 {
|
private fun logicalxor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||||
@ -61,12 +61,12 @@ class ConstExprEvaluator {
|
|||||||
return when (left.type) {
|
return when (left.type) {
|
||||||
in IntegerDatatypes -> when (right.type) {
|
in IntegerDatatypes -> when (right.type) {
|
||||||
in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number.toInt() != 0), left.position)
|
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)
|
else -> throw ExpressionError(error, left.position)
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> when (right.type) {
|
DataType.FLOAT -> when (right.type) {
|
||||||
in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number.toDouble() != 0.0) xor (right.number.toInt() != 0), left.position)
|
in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number != 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)
|
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)
|
||||||
}
|
}
|
||||||
else -> throw ExpressionError(error, left.position)
|
else -> throw ExpressionError(error, left.position)
|
||||||
@ -78,12 +78,12 @@ class ConstExprEvaluator {
|
|||||||
return when (left.type) {
|
return when (left.type) {
|
||||||
in IntegerDatatypes -> when (right.type) {
|
in IntegerDatatypes -> when (right.type) {
|
||||||
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number.toInt() != 0, left.position)
|
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)
|
else -> throw ExpressionError(error, left.position)
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> when (right.type) {
|
DataType.FLOAT -> when (right.type) {
|
||||||
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 || right.number.toInt() != 0, left.position)
|
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number != 0.0 || right.number.toInt() != 0, left.position)
|
||||||
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 || right.number.toDouble() != 0.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)
|
||||||
}
|
}
|
||||||
else -> throw ExpressionError(error, left.position)
|
else -> throw ExpressionError(error, left.position)
|
||||||
@ -95,12 +95,12 @@ class ConstExprEvaluator {
|
|||||||
return when (left.type) {
|
return when (left.type) {
|
||||||
in IntegerDatatypes -> when (right.type) {
|
in IntegerDatatypes -> when (right.type) {
|
||||||
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number.toInt() != 0, left.position)
|
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)
|
else -> throw ExpressionError(error, left.position)
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> when (right.type) {
|
DataType.FLOAT -> when (right.type) {
|
||||||
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 && right.number.toInt() != 0, left.position)
|
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number != 0.0 && right.number.toInt() != 0, left.position)
|
||||||
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 && right.number.toDouble() != 0.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)
|
||||||
}
|
}
|
||||||
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 {
|
private fun bitwisexor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||||
if(left.type== DataType.UBYTE) {
|
if(left.type== DataType.UBYTE) {
|
||||||
if(right.type in IntegerDatatypes) {
|
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) {
|
} else if(left.type== DataType.UWORD) {
|
||||||
if(right.type in IntegerDatatypes) {
|
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)
|
throw ExpressionError("cannot calculate $left ^ $right", left.position)
|
||||||
@ -123,11 +123,11 @@ class ConstExprEvaluator {
|
|||||||
private fun bitwiseor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
private fun bitwiseor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||||
if(left.type== DataType.UBYTE) {
|
if(left.type== DataType.UBYTE) {
|
||||||
if(right.type in IntegerDatatypes) {
|
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) {
|
} else if(left.type== DataType.UWORD) {
|
||||||
if(right.type in IntegerDatatypes) {
|
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)
|
throw ExpressionError("cannot calculate $left | $right", left.position)
|
||||||
@ -136,11 +136,11 @@ class ConstExprEvaluator {
|
|||||||
private fun bitwiseand(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
private fun bitwiseand(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||||
if(left.type== DataType.UBYTE) {
|
if(left.type== DataType.UBYTE) {
|
||||||
if(right.type in IntegerDatatypes) {
|
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) {
|
} else if(left.type== DataType.UWORD) {
|
||||||
if(right.type in IntegerDatatypes) {
|
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)
|
throw ExpressionError("cannot calculate $left & $right", left.position)
|
||||||
@ -151,12 +151,12 @@ class ConstExprEvaluator {
|
|||||||
return when (left.type) {
|
return when (left.type) {
|
||||||
in IntegerDatatypes -> when (right.type) {
|
in IntegerDatatypes -> when (right.type) {
|
||||||
in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt().toDouble().pow(right.number.toInt()), left.position)
|
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)
|
else -> throw ExpressionError(error, left.position)
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> when (right.type) {
|
DataType.FLOAT -> when (right.type) {
|
||||||
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble().pow(right.number.toInt()), left.position)
|
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.pow(right.number.toInt()), left.position)
|
||||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble().pow(right.number.toDouble()), 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)
|
||||||
}
|
}
|
||||||
else -> throw ExpressionError(error, left.position)
|
else -> throw ExpressionError(error, left.position)
|
||||||
@ -168,12 +168,12 @@ class ConstExprEvaluator {
|
|||||||
return when (left.type) {
|
return when (left.type) {
|
||||||
in IntegerDatatypes -> when (right.type) {
|
in IntegerDatatypes -> when (right.type) {
|
||||||
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() + right.number.toInt(), left.position)
|
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)
|
else -> throw ExpressionError(error, left.position)
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> when (right.type) {
|
DataType.FLOAT -> when (right.type) {
|
||||||
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() + right.number.toInt(), left.position)
|
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number + right.number.toInt(), left.position)
|
||||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() + right.number.toDouble(), 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)
|
||||||
}
|
}
|
||||||
else -> throw ExpressionError(error, left.position)
|
else -> throw ExpressionError(error, left.position)
|
||||||
@ -185,12 +185,12 @@ class ConstExprEvaluator {
|
|||||||
return when (left.type) {
|
return when (left.type) {
|
||||||
in IntegerDatatypes -> when (right.type) {
|
in IntegerDatatypes -> when (right.type) {
|
||||||
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() - right.number.toInt(), left.position)
|
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)
|
else -> throw ExpressionError(error, left.position)
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> when (right.type) {
|
DataType.FLOAT -> when (right.type) {
|
||||||
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() - right.number.toInt(), left.position)
|
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number - right.number.toInt(), left.position)
|
||||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() - right.number.toDouble(), 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)
|
||||||
}
|
}
|
||||||
else -> throw ExpressionError(error, left.position)
|
else -> throw ExpressionError(error, left.position)
|
||||||
@ -202,12 +202,12 @@ class ConstExprEvaluator {
|
|||||||
return when (left.type) {
|
return when (left.type) {
|
||||||
in IntegerDatatypes -> when (right.type) {
|
in IntegerDatatypes -> when (right.type) {
|
||||||
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() * right.number.toInt(), left.position)
|
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)
|
else -> throw ExpressionError(error, left.position)
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> when (right.type) {
|
DataType.FLOAT -> when (right.type) {
|
||||||
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() * right.number.toInt(), left.position)
|
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number * right.number.toInt(), left.position)
|
||||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() * right.number.toDouble(), 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)
|
||||||
}
|
}
|
||||||
else -> throw ExpressionError(error, left.position)
|
else -> throw ExpressionError(error, left.position)
|
||||||
@ -227,19 +227,19 @@ class ConstExprEvaluator {
|
|||||||
NumericLiteralValue.optimalInteger(result, left.position)
|
NumericLiteralValue.optimalInteger(result, left.position)
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> {
|
DataType.FLOAT -> {
|
||||||
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
|
if(right.number==0.0) divideByZeroError(right.position)
|
||||||
NumericLiteralValue(DataType.FLOAT, left.number.toInt() / right.number.toDouble(), left.position)
|
NumericLiteralValue(DataType.FLOAT, left.number.toInt() / right.number, left.position)
|
||||||
}
|
}
|
||||||
else -> throw ExpressionError(error, left.position)
|
else -> throw ExpressionError(error, left.position)
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> when (right.type) {
|
DataType.FLOAT -> when (right.type) {
|
||||||
in IntegerDatatypes -> {
|
in IntegerDatatypes -> {
|
||||||
if(right.number.toInt()==0) divideByZeroError(right.position)
|
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 -> {
|
DataType.FLOAT -> {
|
||||||
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
|
if(right.number ==0.0) divideByZeroError(right.position)
|
||||||
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() / right.number.toDouble(), left.position)
|
NumericLiteralValue(DataType.FLOAT, left.number / right.number, left.position)
|
||||||
}
|
}
|
||||||
else -> throw ExpressionError(error, 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)
|
NumericLiteralValue.optimalNumeric(left.number.toInt().toDouble() % right.number.toInt().toDouble(), left.position)
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> {
|
DataType.FLOAT -> {
|
||||||
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
|
if(right.number ==0.0) divideByZeroError(right.position)
|
||||||
NumericLiteralValue(DataType.FLOAT, left.number.toInt() % right.number.toDouble(), left.position)
|
NumericLiteralValue(DataType.FLOAT, left.number.toInt() % right.number, left.position)
|
||||||
}
|
}
|
||||||
else -> throw ExpressionError(error, left.position)
|
else -> throw ExpressionError(error, left.position)
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> when (right.type) {
|
DataType.FLOAT -> when (right.type) {
|
||||||
in IntegerDatatypes -> {
|
in IntegerDatatypes -> {
|
||||||
if(right.number.toInt()==0) divideByZeroError(right.position)
|
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 -> {
|
DataType.FLOAT -> {
|
||||||
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
|
if(right.number ==0.0) divideByZeroError(right.position)
|
||||||
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() % right.number.toDouble(), left.position)
|
NumericLiteralValue(DataType.FLOAT, left.number % right.number, left.position)
|
||||||
}
|
}
|
||||||
else -> throw ExpressionError(error, left.position)
|
else -> throw ExpressionError(error, left.position)
|
||||||
}
|
}
|
@ -12,7 +12,7 @@ import prog8.ast.walk.IAstModification
|
|||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
|
|
||||||
|
|
||||||
internal class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||||
|
|
||||||
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
|
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
|
||||||
// @( &thing ) --> thing
|
// @( &thing ) --> thing
|
||||||
@ -40,7 +40,7 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
|
|||||||
}
|
}
|
||||||
DataType.FLOAT -> {
|
DataType.FLOAT -> {
|
||||||
listOf(IAstModification.ReplaceNode(expr,
|
listOf(IAstModification.ReplaceNode(expr,
|
||||||
NumericLiteralValue(DataType.FLOAT, -subexpr.number.toDouble(), subexpr.position),
|
NumericLiteralValue(DataType.FLOAT, -subexpr.number, subexpr.position),
|
||||||
parent))
|
parent))
|
||||||
}
|
}
|
||||||
else -> throw ExpressionError("can only take negative of int or float", subexpr.position)
|
else -> throw ExpressionError("can only take negative of int or float", subexpr.position)
|
||||||
@ -48,29 +48,29 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
|
|||||||
"~" -> when (subexpr.type) {
|
"~" -> when (subexpr.type) {
|
||||||
DataType.BYTE -> {
|
DataType.BYTE -> {
|
||||||
listOf(IAstModification.ReplaceNode(expr,
|
listOf(IAstModification.ReplaceNode(expr,
|
||||||
NumericLiteralValue(DataType.BYTE, subexpr.number.toInt().inv(), subexpr.position),
|
NumericLiteralValue(DataType.BYTE, subexpr.number.toInt().inv().toDouble(), subexpr.position),
|
||||||
parent))
|
parent))
|
||||||
}
|
}
|
||||||
DataType.UBYTE -> {
|
DataType.UBYTE -> {
|
||||||
listOf(IAstModification.ReplaceNode(expr,
|
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))
|
parent))
|
||||||
}
|
}
|
||||||
DataType.WORD -> {
|
DataType.WORD -> {
|
||||||
listOf(IAstModification.ReplaceNode(expr,
|
listOf(IAstModification.ReplaceNode(expr,
|
||||||
NumericLiteralValue(DataType.WORD, subexpr.number.toInt().inv(), subexpr.position),
|
NumericLiteralValue(DataType.WORD, subexpr.number.toInt().inv().toDouble(), subexpr.position),
|
||||||
parent))
|
parent))
|
||||||
}
|
}
|
||||||
DataType.UWORD -> {
|
DataType.UWORD -> {
|
||||||
listOf(IAstModification.ReplaceNode(expr,
|
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))
|
parent))
|
||||||
}
|
}
|
||||||
else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position)
|
else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position)
|
||||||
}
|
}
|
||||||
"not" -> {
|
"not" -> {
|
||||||
listOf(IAstModification.ReplaceNode(expr,
|
listOf(IAstModification.ReplaceNode(expr,
|
||||||
NumericLiteralValue.fromBoolean(subexpr.number.toDouble() == 0.0, subexpr.position),
|
NumericLiteralValue.fromBoolean(subexpr.number == 0.0, subexpr.position),
|
||||||
parent))
|
parent))
|
||||||
}
|
}
|
||||||
else -> throw ExpressionError(expr.operator, subexpr.position)
|
else -> throw ExpressionError(expr.operator, subexpr.position)
|
||||||
@ -79,7 +79,7 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
|
|||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Try to constfold a binary expression.
|
* Try to constfold a binary expression.
|
||||||
* Compile-time constant sub expressions will be evaluated on the spot.
|
* Compile-time constant sub expressions will be evaluated on the spot.
|
||||||
* For instance, "9 * (4 + 2)" will be optimized into the integer literal 54.
|
* For instance, "9 * (4 + 2)" will be optimized into the integer literal 54.
|
||||||
@ -101,23 +101,50 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
|
|||||||
val rightconst = expr.right.constValue(program)
|
val rightconst = expr.right.constValue(program)
|
||||||
val modifications = mutableListOf<IAstModification>()
|
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) {
|
if(expr.operator == "**" && leftconst!=null) {
|
||||||
// optimize various simple cases of ** :
|
// optimize various simple cases of ** :
|
||||||
// optimize away 1 ** x into just 1 and 0 ** x into just 0
|
// optimize away 1 ** x into just 1 and 0 ** x into just 0
|
||||||
// optimize 2 ** x into (1<<x) if both operands are integer.
|
// optimize 2 ** x into (1<<x) if both operands are integer.
|
||||||
val leftDt = leftconst.inferType(program).getOr(DataType.UNDEFINED)
|
val leftDt = leftconst.inferType(program).getOr(DataType.UNDEFINED)
|
||||||
when (leftconst.number.toDouble()) {
|
when (leftconst.number) {
|
||||||
0.0 -> {
|
0.0 -> {
|
||||||
val value = NumericLiteralValue(leftDt, 0, expr.position)
|
val value = NumericLiteralValue(leftDt, 0.0, expr.position)
|
||||||
modifications += IAstModification.ReplaceNode(expr, value, parent)
|
modifications += IAstModification.ReplaceNode(expr, value, parent)
|
||||||
}
|
}
|
||||||
1.0 -> {
|
1.0 -> {
|
||||||
val value = NumericLiteralValue(leftDt, 1, expr.position)
|
val value = NumericLiteralValue(leftDt, 1.0, expr.position)
|
||||||
modifications += IAstModification.ReplaceNode(expr, value, parent)
|
modifications += IAstModification.ReplaceNode(expr, value, parent)
|
||||||
}
|
}
|
||||||
2.0 -> {
|
2.0 -> {
|
||||||
if(rightconst!=null) {
|
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)
|
modifications += IAstModification.ReplaceNode(expr, value, parent)
|
||||||
} else {
|
} else {
|
||||||
val rightDt = expr.right.inferType(program).getOr(DataType.UNDEFINED)
|
val rightDt = expr.right.inferType(program).getOr(DataType.UNDEFINED)
|
||||||
@ -128,7 +155,7 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
|
|||||||
is VarDecl -> parent.datatype
|
is VarDecl -> parent.datatype
|
||||||
else -> leftDt
|
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)
|
val shift = BinaryExpression(one, "<<", expr.right, expr.position)
|
||||||
modifications += IAstModification.ReplaceNode(expr, shift, parent)
|
modifications += IAstModification.ReplaceNode(expr, shift, parent)
|
||||||
}
|
}
|
||||||
@ -159,13 +186,52 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val evaluator = ConstExprEvaluator()
|
||||||
|
|
||||||
// const fold when both operands are a const
|
// const fold when both operands are a const
|
||||||
if(leftconst != null && rightconst != null) {
|
if(leftconst != null && rightconst != null) {
|
||||||
val evaluator = ConstExprEvaluator()
|
|
||||||
val result = evaluator.evaluate(leftconst, expr.operator, rightconst)
|
val result = evaluator.evaluate(leftconst, expr.operator, rightconst)
|
||||||
modifications += IAstModification.ReplaceNode(expr, result, parent)
|
modifications += IAstModification.ReplaceNode(expr, result, parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val leftBinExpr = expr.left as? BinaryExpression
|
||||||
|
val rightBinExpr = expr.right as? BinaryExpression
|
||||||
|
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
|
return modifications
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,5 @@
|
|||||||
package prog8.optimizer
|
package prog8.optimizer
|
||||||
|
|
||||||
import prog8.ast.INameScope
|
|
||||||
import prog8.ast.Node
|
import prog8.ast.Node
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
@ -8,58 +7,45 @@ import prog8.ast.expressions.*
|
|||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.ast.walk.AstWalker
|
import prog8.ast.walk.AstWalker
|
||||||
import prog8.ast.walk.IAstModification
|
import prog8.ast.walk.IAstModification
|
||||||
import prog8.compiler.IErrorReporter
|
import prog8.compilerinterface.ICompilationTarget
|
||||||
import prog8.compiler.astprocessing.size
|
import prog8.compilerinterface.IErrorReporter
|
||||||
import prog8.compiler.astprocessing.toConstantIntegerRange
|
import prog8.compilerinterface.size
|
||||||
import prog8.compiler.target.ICompilationTarget
|
import prog8.compilerinterface.toConstantIntegerRange
|
||||||
|
|
||||||
// Fix up the literal value's type to match that of the vardecl
|
// Fix up the literal value's type to match that of the vardecl
|
||||||
// (also check range literal operands types before they get expanded into arrays for instance)
|
// (also check range literal operands types before they get expanded into arrays for instance)
|
||||||
internal class VarConstantValueTypeAdjuster(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
|
class VarConstantValueTypeAdjuster(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
|
||||||
|
|
||||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||||
|
|
||||||
|
if(decl.parent is AnonymousScope)
|
||||||
|
throw FatalAstException("vardecl may no longer occur in anonymousscope")
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val declConstValue = decl.value?.constValue(program)
|
val declConstValue = decl.value?.constValue(program)
|
||||||
if(declConstValue!=null && (decl.type==VarDeclType.VAR || decl.type==VarDeclType.CONST)
|
if(declConstValue!=null && (decl.type==VarDeclType.VAR || decl.type==VarDeclType.CONST)
|
||||||
&& declConstValue.inferType(program) isnot decl.datatype) {
|
&& declConstValue.type != decl.datatype) {
|
||||||
// cast the numeric literal to the appropriate datatype of the variable
|
// avoid silent float roundings
|
||||||
val cast = declConstValue.cast(decl.datatype)
|
if(decl.datatype in IntegerDatatypes && declConstValue.type==DataType.FLOAT) {
|
||||||
if(cast.isValid)
|
errors.err("refused silent rounding of float to avoid loss of precision", decl.value!!.position)
|
||||||
return listOf(IAstModification.ReplaceNode(decl.value!!, cast.valueOrZero(), decl))
|
} 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) {
|
} catch (x: UndefinedSymbolError) {
|
||||||
errors.err(x.message, x.position)
|
errors.err(x.message, x.position)
|
||||||
}
|
}
|
||||||
|
|
||||||
// move vardecl to the containing subroutine and add initialization assignment in its place if needed
|
|
||||||
if(decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
|
|
||||||
val subroutine = decl.definingSubroutine as? INameScope
|
|
||||||
if(subroutine!=null && subroutine!==parent) {
|
|
||||||
val declValue = decl.value
|
|
||||||
decl.value = null
|
|
||||||
decl.allowInitializeWithZero = false
|
|
||||||
return if (declValue == null) {
|
|
||||||
listOf(
|
|
||||||
IAstModification.Remove(decl, parent as INameScope),
|
|
||||||
IAstModification.InsertFirst(decl, subroutine)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
|
|
||||||
val assign = Assignment(target, declValue, decl.position)
|
|
||||||
listOf(
|
|
||||||
IAstModification.ReplaceNode(decl, assign, parent),
|
|
||||||
IAstModification.InsertFirst(decl, subroutine)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun after(range: RangeExpr, parent: Node): Iterable<IAstModification> {
|
override fun after(range: RangeExpr, parent: Node): Iterable<IAstModification> {
|
||||||
val from = range.from.constValue(program)?.number?.toDouble()
|
val from = range.from.constValue(program)?.number
|
||||||
val to = range.to.constValue(program)?.number?.toDouble()
|
val to = range.to.constValue(program)?.number
|
||||||
val step = range.step.constValue(program)?.number?.toDouble()
|
val step = range.step.constValue(program)?.number
|
||||||
|
|
||||||
if(from==null) {
|
if(from==null) {
|
||||||
if(!range.from.inferType(program).isInteger)
|
if(!range.from.inferType(program).isInteger)
|
||||||
@ -124,8 +110,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
|||||||
|
|
||||||
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||||
// the initializer value can't refer to the variable itself (recursive definition)
|
// the initializer value can't refer to the variable itself (recursive definition)
|
||||||
// TODO: use call graph for this?
|
if(decl.value?.referencesIdentifier(listOf(decl.name)) == true || decl.arraysize?.indexExpr?.referencesIdentifier(listOf(decl.name)) == true) {
|
||||||
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.indexExpr?.referencesIdentifier(decl.name) == true) {
|
|
||||||
errors.err("recursive var declaration", decl.position)
|
errors.err("recursive var declaration", decl.position)
|
||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
@ -151,7 +136,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
|||||||
// vardecl: for scalar float vars, promote constant integer initialization values to floats
|
// vardecl: for scalar float vars, promote constant integer initialization values to floats
|
||||||
val litval = decl.value as? NumericLiteralValue
|
val litval = decl.value as? NumericLiteralValue
|
||||||
if (litval!=null && litval.type in IntegerDatatypes) {
|
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))
|
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -160,18 +145,18 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
|||||||
if(rangeExpr!=null) {
|
if(rangeExpr!=null) {
|
||||||
// convert the initializer range expression to an actual array
|
// convert the initializer range expression to an actual array
|
||||||
val declArraySize = decl.arraysize?.constIndex()
|
val declArraySize = decl.arraysize?.constIndex()
|
||||||
if(declArraySize!=null && declArraySize!=rangeExpr.size(compTarget))
|
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(compTarget)
|
val constRange = rangeExpr.toConstantIntegerRange()
|
||||||
if(constRange!=null) {
|
if(constRange!=null) {
|
||||||
val eltType = rangeExpr.inferType(program).getOr(DataType.UBYTE)
|
val eltType = rangeExpr.inferType(program).getOr(DataType.UBYTE)
|
||||||
val newValue = if(eltType in ByteDatatypes) {
|
val newValue = if(eltType in ByteDatatypes) {
|
||||||
ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
|
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)
|
position = decl.value!!.position)
|
||||||
} else {
|
} else {
|
||||||
ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
|
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)
|
position = decl.value!!.position)
|
||||||
}
|
}
|
||||||
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||||
@ -204,7 +189,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
|||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
// create the array itself, filled with the fillvalue.
|
// 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)
|
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
|
||||||
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
|
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
|
||||||
}
|
}
|
||||||
@ -214,9 +199,9 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
|||||||
if(rangeExpr!=null) {
|
if(rangeExpr!=null) {
|
||||||
// convert the initializer range expression to an actual array of floats
|
// convert the initializer range expression to an actual array of floats
|
||||||
val declArraySize = decl.arraysize?.constIndex()
|
val declArraySize = decl.arraysize?.constIndex()
|
||||||
if(declArraySize!=null && declArraySize!=rangeExpr.size(compTarget))
|
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
||||||
errors.err("range expression size (${rangeExpr.size(compTarget)}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
|
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
|
||||||
val constRange = rangeExpr.toConstantIntegerRange(compTarget)
|
val constRange = rangeExpr.toConstantIntegerRange()
|
||||||
if(constRange!=null) {
|
if(constRange!=null) {
|
||||||
val newValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F),
|
val newValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F),
|
||||||
constRange.map { NumericLiteralValue(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
constRange.map { NumericLiteralValue(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||||
@ -229,7 +214,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
|||||||
val size = decl.arraysize?.constIndex() ?: return noModifications
|
val size = decl.arraysize?.constIndex() ?: return noModifications
|
||||||
if(rangeExpr==null && numericLv!=null) {
|
if(rangeExpr==null && numericLv!=null) {
|
||||||
// arraysize initializer is a single int, and we know the size.
|
// 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)
|
if (fillvalue < compTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > compTarget.machine.FLOAT_MAX_POSITIVE)
|
||||||
errors.err("float value overflow", numericLv.position)
|
errors.err("float value overflow", numericLv.position)
|
||||||
else {
|
else {
|
@ -15,14 +15,14 @@ import kotlin.math.log2
|
|||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
|
|
||||||
/*
|
/*
|
||||||
todo add more expression optimizations
|
todo add more peephole expression optimizations
|
||||||
|
|
||||||
Investigate what optimizations binaryen has, also see https://egorbo.com/peephole-optimizations.html
|
Investigate what optimizations binaryen has, also see https://egorbo.com/peephole-optimizations.html
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
internal class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||||
private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet()
|
private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet()
|
||||||
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
|
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
|
||||||
|
|
||||||
@ -149,7 +149,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
|||||||
val x = expr.right
|
val x = expr.right
|
||||||
val y = determineY(x, leftBinExpr)
|
val y = determineY(x, leftBinExpr)
|
||||||
if (y != null) {
|
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)
|
val newExpr = BinaryExpression(x, "*", yPlus1, x.position)
|
||||||
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||||
}
|
}
|
||||||
@ -159,7 +159,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
|||||||
val x = expr.right
|
val x = expr.right
|
||||||
val y = determineY(x, leftBinExpr)
|
val y = determineY(x, leftBinExpr)
|
||||||
if (y != null) {
|
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)
|
val newExpr = BinaryExpression(x, "*", yMinus1, x.position)
|
||||||
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||||
}
|
}
|
||||||
@ -179,14 +179,26 @@ internal 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) {
|
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
|
||||||
// unsigned >= 0 --> true
|
// unsigned >= 0 --> true
|
||||||
return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(true, expr.position), parent))
|
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) {
|
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
|
||||||
// unsigned < 0 --> false
|
// unsigned < 0 --> false
|
||||||
return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(false, expr.position), parent))
|
return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(false, expr.position), parent))
|
||||||
@ -220,52 +232,62 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
|||||||
val constFalse = NumericLiteralValue.fromBoolean(false, expr.position)
|
val constFalse = NumericLiteralValue.fromBoolean(false, expr.position)
|
||||||
val newExpr: Expression? = when (expr.operator) {
|
val newExpr: Expression? = when (expr.operator) {
|
||||||
"or" -> {
|
"or" -> {
|
||||||
if ((leftVal != null && leftVal.asBooleanValue) || (rightVal != null && rightVal.asBooleanValue))
|
when {
|
||||||
constTrue
|
leftVal != null && leftVal.asBooleanValue || rightVal != null && rightVal.asBooleanValue -> constTrue
|
||||||
else if (leftVal != null && !leftVal.asBooleanValue)
|
leftVal != null && !leftVal.asBooleanValue -> expr.right
|
||||||
expr.right
|
rightVal != null && !rightVal.asBooleanValue -> expr.left
|
||||||
else if (rightVal != null && !rightVal.asBooleanValue)
|
else -> null
|
||||||
expr.left
|
}
|
||||||
else
|
|
||||||
null
|
|
||||||
}
|
}
|
||||||
"and" -> {
|
"and" -> {
|
||||||
if ((leftVal != null && !leftVal.asBooleanValue) || (rightVal != null && !rightVal.asBooleanValue))
|
when {
|
||||||
constFalse
|
leftVal != null && !leftVal.asBooleanValue || rightVal != null && !rightVal.asBooleanValue -> constFalse
|
||||||
else if (leftVal != null && leftVal.asBooleanValue)
|
leftVal != null && leftVal.asBooleanValue -> expr.right
|
||||||
expr.right
|
rightVal != null && rightVal.asBooleanValue -> expr.left
|
||||||
else if (rightVal != null && rightVal.asBooleanValue)
|
else -> null
|
||||||
expr.left
|
}
|
||||||
else
|
|
||||||
null
|
|
||||||
}
|
}
|
||||||
"xor" -> {
|
"xor" -> {
|
||||||
if (leftVal != null && !leftVal.asBooleanValue)
|
when {
|
||||||
expr.right
|
leftVal != null && !leftVal.asBooleanValue -> expr.right
|
||||||
else if (rightVal != null && !rightVal.asBooleanValue)
|
rightVal != null && !rightVal.asBooleanValue -> expr.left
|
||||||
expr.left
|
leftVal != null && leftVal.asBooleanValue -> PrefixExpression("not", expr.right, expr.right.position)
|
||||||
else if (leftVal != null && leftVal.asBooleanValue)
|
rightVal != null && rightVal.asBooleanValue -> PrefixExpression("not", expr.left, expr.left.position)
|
||||||
PrefixExpression("not", expr.right, expr.right.position)
|
else -> null
|
||||||
else if (rightVal != null && rightVal.asBooleanValue)
|
}
|
||||||
PrefixExpression("not", expr.left, expr.left.position)
|
|
||||||
else
|
|
||||||
null
|
|
||||||
}
|
}
|
||||||
"|", "^" -> {
|
"|" -> {
|
||||||
if (leftVal != null && !leftVal.asBooleanValue)
|
when {
|
||||||
expr.right
|
leftVal?.number==0.0 -> expr.right
|
||||||
else if (rightVal != null && !rightVal.asBooleanValue)
|
rightVal?.number==0.0 -> expr.left
|
||||||
expr.left
|
rightIDt.isBytes && rightVal?.number==255.0 -> NumericLiteralValue(DataType.UBYTE, 255.0, rightVal.position)
|
||||||
else
|
rightIDt.isWords && rightVal?.number==65535.0 -> NumericLiteralValue(DataType.UWORD, 65535.0, rightVal.position)
|
||||||
null
|
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)
|
when {
|
||||||
constFalse
|
leftVal?.number==0.0 -> constFalse
|
||||||
else if (rightVal != null && !rightVal.asBooleanValue)
|
rightVal?.number==0.0 -> constFalse
|
||||||
constFalse
|
rightIDt.isBytes && rightVal?.number==255.0 -> expr.left
|
||||||
else
|
rightIDt.isWords && rightVal?.number==65535.0 -> expr.left
|
||||||
null
|
leftIDt.isBytes && leftVal?.number==255.0 -> expr.right
|
||||||
|
leftIDt.isWords && leftVal?.number==65535.0 -> expr.right
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"*" -> optimizeMultiplication(expr, leftVal, rightVal)
|
"*" -> optimizeMultiplication(expr, leftVal, rightVal)
|
||||||
"/" -> optimizeDivision(expr, leftVal, rightVal)
|
"/" -> optimizeDivision(expr, leftVal, rightVal)
|
||||||
@ -309,7 +331,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
|||||||
// useless msb() of byte value that was typecasted to word, replace with 0
|
// useless msb() of byte value that was typecasted to word, replace with 0
|
||||||
return listOf(IAstModification.ReplaceNode(
|
return listOf(IAstModification.ReplaceNode(
|
||||||
functionCall,
|
functionCall,
|
||||||
NumericLiteralValue(valueDt.getOr(DataType.UBYTE), 0, arg.expression.position),
|
NumericLiteralValue(valueDt.getOr(DataType.UBYTE), 0.0, arg.expression.position),
|
||||||
parent))
|
parent))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -318,7 +340,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
|||||||
// useless msb() of byte value, replace with 0
|
// useless msb() of byte value, replace with 0
|
||||||
return listOf(IAstModification.ReplaceNode(
|
return listOf(IAstModification.ReplaceNode(
|
||||||
functionCall,
|
functionCall,
|
||||||
NumericLiteralValue(argDt.getOr(DataType.UBYTE), 0, arg.position),
|
NumericLiteralValue(argDt.getOr(DataType.UBYTE), 0.0, arg.position),
|
||||||
parent))
|
parent))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -351,7 +373,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
|||||||
if (rightVal2 != null) {
|
if (rightVal2 != null) {
|
||||||
// right value is a constant, see if we can optimize
|
// right value is a constant, see if we can optimize
|
||||||
val rightConst: NumericLiteralValue = rightVal2
|
val rightConst: NumericLiteralValue = rightVal2
|
||||||
when (rightConst.number.toDouble()) {
|
when (rightConst.number) {
|
||||||
0.0 -> {
|
0.0 -> {
|
||||||
// left
|
// left
|
||||||
return expr2.left
|
return expr2.left
|
||||||
@ -360,7 +382,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
|||||||
}
|
}
|
||||||
// no need to check for left val constant (because of associativity)
|
// 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) {
|
if(rnum!=null && rnum<0.0) {
|
||||||
expr.operator = "-"
|
expr.operator = "-"
|
||||||
expr.right = NumericLiteralValue(rightVal.type, -rnum, rightVal.position)
|
expr.right = NumericLiteralValue(rightVal.type, -rnum, rightVal.position)
|
||||||
@ -381,7 +403,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
|||||||
|
|
||||||
if (rightVal != null) {
|
if (rightVal != null) {
|
||||||
// right value is a constant, see if we can optimize
|
// right value is a constant, see if we can optimize
|
||||||
val rnum = rightVal.number.toDouble()
|
val rnum = rightVal.number
|
||||||
if (rnum == 0.0) {
|
if (rnum == 0.0) {
|
||||||
// left
|
// left
|
||||||
return expr.left
|
return expr.left
|
||||||
@ -395,7 +417,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
|||||||
}
|
}
|
||||||
if (leftVal != null) {
|
if (leftVal != null) {
|
||||||
// left value is a constant, see if we can optimize
|
// left value is a constant, see if we can optimize
|
||||||
when (leftVal.number.toDouble()) {
|
when (leftVal.number) {
|
||||||
0.0 -> {
|
0.0 -> {
|
||||||
// -right
|
// -right
|
||||||
return PrefixExpression("-", expr.right, expr.position)
|
return PrefixExpression("-", expr.right, expr.position)
|
||||||
@ -414,7 +436,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
|||||||
if (rightVal != null) {
|
if (rightVal != null) {
|
||||||
// right value is a constant, see if we can optimize
|
// right value is a constant, see if we can optimize
|
||||||
val rightConst: NumericLiteralValue = rightVal
|
val rightConst: NumericLiteralValue = rightVal
|
||||||
when (rightConst.number.toDouble()) {
|
when (rightConst.number) {
|
||||||
-3.0 -> {
|
-3.0 -> {
|
||||||
// -1/(left*left*left)
|
// -1/(left*left*left)
|
||||||
return BinaryExpression(NumericLiteralValue(DataType.FLOAT, -1.0, expr.position), "/",
|
return BinaryExpression(NumericLiteralValue(DataType.FLOAT, -1.0, expr.position), "/",
|
||||||
@ -434,7 +456,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
|||||||
}
|
}
|
||||||
0.0 -> {
|
0.0 -> {
|
||||||
// 1
|
// 1
|
||||||
return NumericLiteralValue(rightConst.type, 1, expr.position)
|
return NumericLiteralValue(rightConst.type, 1.0, expr.position)
|
||||||
}
|
}
|
||||||
0.5 -> {
|
0.5 -> {
|
||||||
// sqrt(left)
|
// sqrt(left)
|
||||||
@ -456,18 +478,18 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
|||||||
}
|
}
|
||||||
if (leftVal != null) {
|
if (leftVal != null) {
|
||||||
// left value is a constant, see if we can optimize
|
// left value is a constant, see if we can optimize
|
||||||
when (leftVal.number.toDouble()) {
|
when (leftVal.number) {
|
||||||
-1.0 -> {
|
-1.0 -> {
|
||||||
// -1
|
// -1
|
||||||
return NumericLiteralValue(DataType.FLOAT, -1.0, expr.position)
|
return NumericLiteralValue(DataType.FLOAT, -1.0, expr.position)
|
||||||
}
|
}
|
||||||
0.0 -> {
|
0.0 -> {
|
||||||
// 0
|
// 0
|
||||||
return NumericLiteralValue(leftVal.type, 0, expr.position)
|
return NumericLiteralValue(leftVal.type, 0.0, expr.position)
|
||||||
}
|
}
|
||||||
1.0 -> {
|
1.0 -> {
|
||||||
//1
|
//1
|
||||||
return NumericLiteralValue(leftVal.type, 1, expr.position)
|
return NumericLiteralValue(leftVal.type, 1.0, expr.position)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -489,7 +511,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
|||||||
val idt = expr.inferType(program)
|
val idt = expr.inferType(program)
|
||||||
if(!idt.isKnown)
|
if(!idt.isKnown)
|
||||||
throw FatalAstException("unknown dt")
|
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) {
|
} else if (cv in powersOfTwo) {
|
||||||
expr.operator = "&"
|
expr.operator = "&"
|
||||||
expr.right = NumericLiteralValue.optimalInteger(cv!!.toInt()-1, expr.position)
|
expr.right = NumericLiteralValue.optimalInteger(cv!!.toInt()-1, expr.position)
|
||||||
@ -509,7 +531,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
|||||||
if (rightVal != null) {
|
if (rightVal != null) {
|
||||||
// right value is a constant, see if we can optimize
|
// right value is a constant, see if we can optimize
|
||||||
val rightConst: NumericLiteralValue = rightVal
|
val rightConst: NumericLiteralValue = rightVal
|
||||||
val cv = rightConst.number.toDouble()
|
val cv = rightConst.number
|
||||||
val leftIDt = expr.left.inferType(program)
|
val leftIDt = expr.left.inferType(program)
|
||||||
if (!leftIDt.isKnown)
|
if (!leftIDt.isKnown)
|
||||||
return null
|
return null
|
||||||
@ -544,22 +566,22 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (leftDt == DataType.UBYTE) {
|
if (leftDt == DataType.UBYTE) {
|
||||||
if (abs(rightConst.number.toDouble()) >= 256.0) {
|
if (abs(rightConst.number) >= 256.0) {
|
||||||
return NumericLiteralValue(DataType.UBYTE, 0, expr.position)
|
return NumericLiteralValue(DataType.UBYTE, 0.0, expr.position)
|
||||||
}
|
}
|
||||||
} else if (leftDt == DataType.UWORD) {
|
} else if (leftDt == DataType.UWORD) {
|
||||||
if (abs(rightConst.number.toDouble()) >= 65536.0) {
|
if (abs(rightConst.number) >= 65536.0) {
|
||||||
return NumericLiteralValue(DataType.UBYTE, 0, expr.position)
|
return NumericLiteralValue(DataType.UBYTE, 0.0, expr.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (leftVal != null) {
|
if (leftVal != null) {
|
||||||
// left value is a constant, see if we can optimize
|
// left value is a constant, see if we can optimize
|
||||||
when (leftVal.number.toDouble()) {
|
when (leftVal.number) {
|
||||||
0.0 -> {
|
0.0 -> {
|
||||||
// 0
|
// 0
|
||||||
return NumericLiteralValue(leftVal.type, 0, expr.position)
|
return NumericLiteralValue(leftVal.type, 0.0, expr.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -576,14 +598,14 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
|||||||
// right value is a constant, see if we can optimize
|
// right value is a constant, see if we can optimize
|
||||||
val leftValue: Expression = expr2.left
|
val leftValue: Expression = expr2.left
|
||||||
val rightConst: NumericLiteralValue = rightVal2
|
val rightConst: NumericLiteralValue = rightVal2
|
||||||
when (val cv = rightConst.number.toDouble()) {
|
when (val cv = rightConst.number) {
|
||||||
-1.0 -> {
|
-1.0 -> {
|
||||||
// -left
|
// -left
|
||||||
return PrefixExpression("-", leftValue, expr.position)
|
return PrefixExpression("-", leftValue, expr.position)
|
||||||
}
|
}
|
||||||
0.0 -> {
|
0.0 -> {
|
||||||
// 0
|
// 0
|
||||||
return NumericLiteralValue(rightConst.type, 0, expr.position)
|
return NumericLiteralValue(rightConst.type, 0.0, expr.position)
|
||||||
}
|
}
|
||||||
1.0 -> {
|
1.0 -> {
|
||||||
// left
|
// left
|
||||||
@ -624,12 +646,12 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
|||||||
when (val targetDt = targetIDt.getOr(DataType.UNDEFINED)) {
|
when (val targetDt = targetIDt.getOr(DataType.UNDEFINED)) {
|
||||||
DataType.UBYTE, DataType.BYTE -> {
|
DataType.UBYTE, DataType.BYTE -> {
|
||||||
if (amount >= 8) {
|
if (amount >= 8) {
|
||||||
return NumericLiteralValue(targetDt, 0, expr.position)
|
return NumericLiteralValue(targetDt, 0.0, expr.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.UWORD, DataType.WORD -> {
|
DataType.UWORD, DataType.WORD -> {
|
||||||
if (amount >= 16) {
|
if (amount >= 16) {
|
||||||
return NumericLiteralValue(targetDt, 0, expr.position)
|
return NumericLiteralValue(targetDt, 0.0, expr.position)
|
||||||
} else if (amount >= 8) {
|
} else if (amount >= 8) {
|
||||||
val lsb = FunctionCall(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left), expr.position)
|
val lsb = FunctionCall(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left), expr.position)
|
||||||
if (amount == 8) {
|
if (amount == 8) {
|
||||||
@ -676,7 +698,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
|||||||
val msb = FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
|
val msb = FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
|
||||||
if (amount == 8) {
|
if (amount == 8) {
|
||||||
// mkword(0, msb(v))
|
// 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 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)
|
return TypecastExpression(BinaryExpression(msb, ">>", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position), DataType.UWORD, true, expr.position)
|
@ -2,11 +2,15 @@ package prog8.optimizer
|
|||||||
|
|
||||||
import prog8.ast.IBuiltinFunctions
|
import prog8.ast.IBuiltinFunctions
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.compiler.IErrorReporter
|
import prog8.ast.base.DataType
|
||||||
import prog8.compiler.target.ICompilationTarget
|
import prog8.ast.base.FatalAstException
|
||||||
|
import prog8.ast.expressions.InferredTypes
|
||||||
|
import prog8.compilerinterface.CompilationOptions
|
||||||
|
import prog8.compilerinterface.ICompilationTarget
|
||||||
|
import prog8.compilerinterface.IErrorReporter
|
||||||
|
|
||||||
|
|
||||||
internal fun Program.constantFold(errors: IErrorReporter, compTarget: ICompilationTarget) {
|
fun Program.constantFold(errors: IErrorReporter, compTarget: ICompilationTarget) {
|
||||||
val valuetypefixer = VarConstantValueTypeAdjuster(this, errors)
|
val valuetypefixer = VarConstantValueTypeAdjuster(this, errors)
|
||||||
valuetypefixer.visit(this)
|
valuetypefixer.visit(this)
|
||||||
if(errors.noErrors()) {
|
if(errors.noErrors()) {
|
||||||
@ -40,9 +44,10 @@ internal fun Program.constantFold(errors: IErrorReporter, compTarget: ICompilati
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
internal fun Program.optimizeStatements(errors: IErrorReporter,
|
fun Program.optimizeStatements(errors: IErrorReporter,
|
||||||
functions: IBuiltinFunctions,
|
functions: IBuiltinFunctions,
|
||||||
compTarget: ICompilationTarget): Int {
|
compTarget: ICompilationTarget
|
||||||
|
): Int {
|
||||||
val optimizer = StatementOptimizer(this, errors, functions, compTarget)
|
val optimizer = StatementOptimizer(this, errors, functions, compTarget)
|
||||||
optimizer.visit(this)
|
optimizer.visit(this)
|
||||||
val optimizationCount = optimizer.applyModifications()
|
val optimizationCount = optimizer.applyModifications()
|
||||||
@ -52,14 +57,26 @@ internal fun Program.optimizeStatements(errors: IErrorReporter,
|
|||||||
return optimizationCount
|
return optimizationCount
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun Program.simplifyExpressions() : Int {
|
fun Program.simplifyExpressions() : Int {
|
||||||
val opti = ExpressionSimplifier(this)
|
val opti = ExpressionSimplifier(this)
|
||||||
opti.visit(this)
|
opti.visit(this)
|
||||||
return opti.applyModifications()
|
return opti.applyModifications()
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun Program.splitBinaryExpressions(compTarget: ICompilationTarget) : Int {
|
fun Program.splitBinaryExpressions(options: CompilationOptions, compTarget: ICompilationTarget) : Int {
|
||||||
val opti = BinExprSplitter(this, compTarget)
|
val opti = BinExprSplitter(this, options, compTarget)
|
||||||
opti.visit(this)
|
opti.visit(this)
|
||||||
return opti.applyModifications()
|
return opti.applyModifications()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getTempVarName(dt: InferredTypes.InferredType): List<String> {
|
||||||
|
return when {
|
||||||
|
// TODO assume (hope) cx16.r9 isn't used for anything else...
|
||||||
|
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,55 +1,40 @@
|
|||||||
package prog8.optimizer
|
package prog8.optimizer
|
||||||
|
|
||||||
import prog8.ast.IBuiltinFunctions
|
import prog8.ast.*
|
||||||
import prog8.ast.INameScope
|
|
||||||
import prog8.ast.Node
|
|
||||||
import prog8.ast.Program
|
|
||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.ast.walk.AstWalker
|
import prog8.ast.walk.AstWalker
|
||||||
import prog8.ast.walk.IAstModification
|
import prog8.ast.walk.IAstModification
|
||||||
import prog8.ast.walk.IAstVisitor
|
import prog8.ast.walk.IAstVisitor
|
||||||
import prog8.compiler.IErrorReporter
|
import prog8.compilerinterface.ICompilationTarget
|
||||||
import prog8.compiler.astprocessing.size
|
import prog8.compilerinterface.IErrorReporter
|
||||||
import prog8.compiler.target.ICompilationTarget
|
import prog8.compilerinterface.size
|
||||||
import kotlin.math.floor
|
import kotlin.math.floor
|
||||||
|
|
||||||
internal const val retvarName = "prog8_retval"
|
|
||||||
|
|
||||||
|
class StatementOptimizer(private val program: Program,
|
||||||
internal class StatementOptimizer(private val program: Program,
|
|
||||||
private val errors: IErrorReporter,
|
private val errors: IErrorReporter,
|
||||||
private val functions: IBuiltinFunctions,
|
private val functions: IBuiltinFunctions,
|
||||||
private val compTarget: ICompilationTarget) : AstWalker() {
|
private val compTarget: ICompilationTarget
|
||||||
|
) : AstWalker() {
|
||||||
private val subsThatNeedReturnVariable = mutableSetOf<Triple<INameScope, DataType, Position>>()
|
|
||||||
|
|
||||||
|
|
||||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
|
||||||
for(returnvar in subsThatNeedReturnVariable) {
|
|
||||||
val decl = VarDecl(VarDeclType.VAR, returnvar.second, ZeropageWish.DONTCARE, null, retvarName, null,
|
|
||||||
isArray = false,
|
|
||||||
autogeneratedDontRemove = true,
|
|
||||||
sharedWithAsm = false,
|
|
||||||
position = returnvar.third
|
|
||||||
)
|
|
||||||
returnvar.first.statements.add(0, decl)
|
|
||||||
}
|
|
||||||
subsThatNeedReturnVariable.clear()
|
|
||||||
return noModifications
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||||
// if the first instruction in the called subroutine is a return statement with a simple value,
|
// 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.
|
// 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)
|
val subroutine = functionCall.target.targetSubroutine(program)
|
||||||
if(subroutine!=null) {
|
if(subroutine!=null) {
|
||||||
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
||||||
if(first is Return && first.value?.isSimple==true) {
|
if(first is Return && first.value?.isSimple==true) {
|
||||||
val copy = when(val orig = first.value!!) {
|
val copy = when(val orig = first.value!!) {
|
||||||
is AddressOf -> {
|
is AddressOf -> {
|
||||||
val scoped = scopePrefix(orig.identifier, subroutine)
|
val scoped = scopePrefix(orig.identifier)
|
||||||
AddressOf(scoped, orig.position)
|
AddressOf(scoped, orig.position)
|
||||||
}
|
}
|
||||||
is DirectMemoryRead -> {
|
is DirectMemoryRead -> {
|
||||||
@ -58,7 +43,7 @@ internal class StatementOptimizer(private val program: Program,
|
|||||||
else -> return noModifications
|
else -> return noModifications
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is IdentifierReference -> scopePrefix(orig, subroutine)
|
is IdentifierReference -> scopePrefix(orig)
|
||||||
is NumericLiteralValue -> orig.copy()
|
is NumericLiteralValue -> orig.copy()
|
||||||
is StringLiteralValue -> orig.copy()
|
is StringLiteralValue -> orig.copy()
|
||||||
else -> return noModifications
|
else -> return noModifications
|
||||||
@ -69,17 +54,14 @@ internal class StatementOptimizer(private val program: Program,
|
|||||||
return noModifications
|
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> {
|
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]
|
val functionName = functionCallStatement.target.nameInSource[0]
|
||||||
if (functionName in functions.purefunctionNames) {
|
if (functionName in functions.purefunctionNames) {
|
||||||
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
|
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
|
||||||
return listOf(IAstModification.Remove(functionCallStatement, functionCallStatement.definingScope))
|
return listOf(IAstModification.Remove(functionCallStatement, parent as IStatementContainer))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,7 +82,7 @@ internal class StatementOptimizer(private val program: Program,
|
|||||||
val firstCharEncoded = compTarget.encodeString(string.value, string.altEncoding)[0]
|
val firstCharEncoded = compTarget.encodeString(string.value, string.altEncoding)[0]
|
||||||
val chrout = FunctionCallStatement(
|
val chrout = FunctionCallStatement(
|
||||||
IdentifierReference(listOf("txt", "chrout"), pos),
|
IdentifierReference(listOf("txt", "chrout"), pos),
|
||||||
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstCharEncoded.toInt(), pos)),
|
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstCharEncoded.toDouble(), pos)),
|
||||||
functionCallStatement.void, pos
|
functionCallStatement.void, pos
|
||||||
)
|
)
|
||||||
return listOf(IAstModification.ReplaceNode(functionCallStatement, chrout, parent))
|
return listOf(IAstModification.ReplaceNode(functionCallStatement, chrout, parent))
|
||||||
@ -108,16 +90,16 @@ internal class StatementOptimizer(private val program: Program,
|
|||||||
val firstTwoCharsEncoded = compTarget.encodeString(string.value.take(2), string.altEncoding)
|
val firstTwoCharsEncoded = compTarget.encodeString(string.value.take(2), string.altEncoding)
|
||||||
val chrout1 = FunctionCallStatement(
|
val chrout1 = FunctionCallStatement(
|
||||||
IdentifierReference(listOf("txt", "chrout"), pos),
|
IdentifierReference(listOf("txt", "chrout"), pos),
|
||||||
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toInt(), pos)),
|
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toDouble(), pos)),
|
||||||
functionCallStatement.void, pos
|
functionCallStatement.void, pos
|
||||||
)
|
)
|
||||||
val chrout2 = FunctionCallStatement(
|
val chrout2 = FunctionCallStatement(
|
||||||
IdentifierReference(listOf("txt", "chrout"), pos),
|
IdentifierReference(listOf("txt", "chrout"), pos),
|
||||||
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[1].toInt(), pos)),
|
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[1].toDouble(), pos)),
|
||||||
functionCallStatement.void, pos
|
functionCallStatement.void, pos
|
||||||
)
|
)
|
||||||
return listOf(
|
return listOf(
|
||||||
IAstModification.InsertBefore(functionCallStatement, chrout1, parent as INameScope),
|
IAstModification.InsertBefore(functionCallStatement, chrout1, parent as IStatementContainer),
|
||||||
IAstModification.ReplaceNode(functionCallStatement, chrout2, parent)
|
IAstModification.ReplaceNode(functionCallStatement, chrout2, parent)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -130,33 +112,35 @@ internal class StatementOptimizer(private val program: Program,
|
|||||||
if(subroutine!=null) {
|
if(subroutine!=null) {
|
||||||
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
||||||
if(first is Return)
|
if(first is Return)
|
||||||
return listOf(IAstModification.Remove(functionCallStatement, functionCallStatement.definingScope))
|
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
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
// override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
|
||||||
// // if the first instruction in the called subroutine is a return statement with constant value, replace with the constant value
|
|
||||||
// val subroutine = functionCall.target.targetSubroutine(program)
|
|
||||||
// if(subroutine!=null) {
|
|
||||||
// val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
|
||||||
// if(first is Return && first.value!=null) {
|
|
||||||
// val constval = first.value?.constValue(program)
|
|
||||||
// if(constval!=null)
|
|
||||||
// return listOf(IAstModification.ReplaceNode(functionCall, constval, parent))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return noModifications
|
|
||||||
// }
|
|
||||||
|
|
||||||
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
|
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
|
||||||
// remove empty if statements
|
// remove empty if statements
|
||||||
if(ifStatement.truepart.containsNoCodeNorVars && ifStatement.elsepart.containsNoCodeNorVars)
|
if(ifStatement.truepart.isEmpty() && ifStatement.elsepart.isEmpty())
|
||||||
return listOf(IAstModification.Remove(ifStatement, ifStatement.definingScope))
|
return listOf(IAstModification.Remove(ifStatement, parent as IStatementContainer))
|
||||||
|
|
||||||
// empty true part? switch with the else part
|
// empty true part? switch with the else part
|
||||||
if(ifStatement.truepart.containsNoCodeNorVars && ifStatement.elsepart.containsCodeOrVars) {
|
if(ifStatement.truepart.isEmpty() && ifStatement.elsepart.isNotEmpty()) {
|
||||||
val invertedCondition = PrefixExpression("not", ifStatement.condition, ifStatement.condition.position)
|
val invertedCondition = PrefixExpression("not", ifStatement.condition, ifStatement.condition.position)
|
||||||
val emptyscope = AnonymousScope(mutableListOf(), ifStatement.elsepart.position)
|
val emptyscope = AnonymousScope(mutableListOf(), ifStatement.elsepart.position)
|
||||||
val truepart = AnonymousScope(ifStatement.elsepart.statements, ifStatement.truepart.position)
|
val truepart = AnonymousScope(ifStatement.elsepart.statements, ifStatement.truepart.position)
|
||||||
@ -184,20 +168,20 @@ internal class StatementOptimizer(private val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
|
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
|
||||||
if(forLoop.body.containsNoCodeNorVars) {
|
if(forLoop.body.isEmpty()) {
|
||||||
errors.warn("removing empty for loop", forLoop.position)
|
errors.warn("removing empty for loop", forLoop.position)
|
||||||
return listOf(IAstModification.Remove(forLoop, forLoop.definingScope))
|
return listOf(IAstModification.Remove(forLoop, parent as IStatementContainer))
|
||||||
} else if(forLoop.body.statements.size==1) {
|
} else if(forLoop.body.statements.size==1) {
|
||||||
val loopvar = forLoop.body.statements[0] as? VarDecl
|
val loopvar = forLoop.body.statements[0] as? VarDecl
|
||||||
if(loopvar!=null && loopvar.name==forLoop.loopVar.nameInSource.singleOrNull()) {
|
if(loopvar!=null && loopvar.name==forLoop.loopVar.nameInSource.singleOrNull()) {
|
||||||
// remove empty for loop (only loopvar decl in it)
|
// remove empty for loop (only loopvar decl in it)
|
||||||
return listOf(IAstModification.Remove(forLoop, forLoop.definingScope))
|
return listOf(IAstModification.Remove(forLoop, parent as IStatementContainer))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val range = forLoop.iterable as? RangeExpr
|
val range = forLoop.iterable as? RangeExpr
|
||||||
if(range!=null) {
|
if(range!=null) {
|
||||||
if (range.size(compTarget) == 1) {
|
if (range.size() == 1) {
|
||||||
// for loop over a (constant) range of just a single value-- optimize the loop away
|
// for loop over a (constant) range of just a single value-- optimize the loop away
|
||||||
// loopvar/reg = range value , follow by block
|
// loopvar/reg = range value , follow by block
|
||||||
val scope = AnonymousScope(mutableListOf(), forLoop.position)
|
val scope = AnonymousScope(mutableListOf(), forLoop.position)
|
||||||
@ -214,7 +198,7 @@ internal class StatementOptimizer(private val program: Program,
|
|||||||
if(size==1) {
|
if(size==1) {
|
||||||
// loop over string of length 1 -> just assign the single character
|
// loop over string of length 1 -> just assign the single character
|
||||||
val character = compTarget.encodeString(sv.value, sv.altEncoding)[0]
|
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)
|
val scope = AnonymousScope(mutableListOf(), forLoop.position)
|
||||||
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), byte, forLoop.position))
|
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), byte, forLoop.position))
|
||||||
scope.statements.addAll(forLoop.body.statements)
|
scope.statements.addAll(forLoop.body.statements)
|
||||||
@ -268,7 +252,7 @@ internal class StatementOptimizer(private val program: Program,
|
|||||||
} else {
|
} else {
|
||||||
// always false -> remove the while statement altogether
|
// always false -> remove the while statement altogether
|
||||||
errors.warn("condition is always false", whileLoop.condition.position)
|
errors.warn("condition is always false", whileLoop.condition.position)
|
||||||
listOf(IAstModification.Remove(whileLoop, whileLoop.definingScope))
|
listOf(IAstModification.Remove(whileLoop, parent as IStatementContainer))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return noModifications
|
return noModifications
|
||||||
@ -277,14 +261,14 @@ internal class StatementOptimizer(private val program: Program,
|
|||||||
override fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> {
|
override fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> {
|
||||||
val iter = repeatLoop.iterations
|
val iter = repeatLoop.iterations
|
||||||
if(iter!=null) {
|
if(iter!=null) {
|
||||||
if(repeatLoop.body.containsNoCodeNorVars) {
|
if(repeatLoop.body.isEmpty()) {
|
||||||
errors.warn("empty loop removed", repeatLoop.position)
|
errors.warn("empty loop removed", repeatLoop.position)
|
||||||
return listOf(IAstModification.Remove(repeatLoop, repeatLoop.definingScope))
|
return listOf(IAstModification.Remove(repeatLoop, parent as IStatementContainer))
|
||||||
}
|
}
|
||||||
val iterations = iter.constValue(program)?.number?.toInt()
|
val iterations = iter.constValue(program)?.number?.toInt()
|
||||||
if (iterations == 0) {
|
if (iterations == 0) {
|
||||||
errors.warn("iterations is always 0, removed loop", iter.position)
|
errors.warn("iterations is always 0, removed loop", iter.position)
|
||||||
return listOf(IAstModification.Remove(repeatLoop, repeatLoop.definingScope))
|
return listOf(IAstModification.Remove(repeatLoop, parent as IStatementContainer))
|
||||||
}
|
}
|
||||||
if (iterations == 1) {
|
if (iterations == 1) {
|
||||||
errors.warn("iterations is always 1", iter.position)
|
errors.warn("iterations is always 1", iter.position)
|
||||||
@ -295,12 +279,13 @@ internal class StatementOptimizer(private val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun after(jump: Jump, parent: Node): Iterable<IAstModification> {
|
override fun after(jump: Jump, parent: Node): Iterable<IAstModification> {
|
||||||
// if the jump is to the next statement, remove the jump
|
if(!jump.isGosub) {
|
||||||
val scope = jump.definingScope
|
// if the jump is to the next statement, remove the jump
|
||||||
val label = jump.identifier?.targetStatement(program)
|
val scope = jump.parent as IStatementContainer
|
||||||
if(label!=null && scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1)
|
val label = jump.identifier?.targetStatement(program)
|
||||||
return listOf(IAstModification.Remove(jump, jump.definingScope))
|
if (label != null && scope.statements.indexOf(label) == scope.statements.indexOf(jump) + 1)
|
||||||
|
return listOf(IAstModification.Remove(jump, scope))
|
||||||
|
}
|
||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -323,27 +308,27 @@ internal class StatementOptimizer(private val program: Program,
|
|||||||
if(rNum!=null) {
|
if(rNum!=null) {
|
||||||
if (op1 == "+" || op1 == "-") {
|
if (op1 == "+" || op1 == "-") {
|
||||||
if (op2 == "+") {
|
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 expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position)
|
||||||
val addConstant = Assignment(
|
val addConstant = Assignment(
|
||||||
assignment.target,
|
assignment.target.copy(),
|
||||||
BinaryExpression(binExpr.left, "+", rExpr.right, rExpr.position),
|
BinaryExpression(binExpr.left.copy(), "+", rExpr.right, rExpr.position),
|
||||||
assignment.position
|
assignment.position
|
||||||
)
|
)
|
||||||
return listOf(
|
return listOf(
|
||||||
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
|
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
|
||||||
IAstModification.InsertAfter(assignment, addConstant, assignment.definingScope))
|
IAstModification.InsertAfter(assignment, addConstant, parent as IStatementContainer))
|
||||||
} else if (op2 == "-") {
|
} 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 expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position)
|
||||||
val subConstant = Assignment(
|
val subConstant = Assignment(
|
||||||
assignment.target,
|
assignment.target.copy(),
|
||||||
BinaryExpression(binExpr.left, "-", rExpr.right, rExpr.position),
|
BinaryExpression(binExpr.left.copy(), "-", rExpr.right, rExpr.position),
|
||||||
assignment.position
|
assignment.position
|
||||||
)
|
)
|
||||||
return listOf(
|
return listOf(
|
||||||
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
|
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
|
||||||
IAstModification.InsertAfter(assignment, subConstant, assignment.definingScope))
|
IAstModification.InsertAfter(assignment, subConstant, parent as IStatementContainer))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -365,7 +350,7 @@ internal class StatementOptimizer(private val program: Program,
|
|||||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||||
if(assignment.target isSameAs assignment.value) {
|
if(assignment.target isSameAs assignment.value) {
|
||||||
// remove assignment to self
|
// remove assignment to self
|
||||||
return listOf(IAstModification.Remove(assignment, assignment.definingScope))
|
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
|
||||||
}
|
}
|
||||||
|
|
||||||
val targetIDt = assignment.target.inferType(program)
|
val targetIDt = assignment.target.inferType(program)
|
||||||
@ -373,19 +358,31 @@ internal class StatementOptimizer(private val program: Program,
|
|||||||
throw FatalAstException("can't infer type of assignment target")
|
throw FatalAstException("can't infer type of assignment target")
|
||||||
|
|
||||||
// optimize binary expressions a bit
|
// optimize binary expressions a bit
|
||||||
val targetDt = targetIDt.getOr(DataType.UNDEFINED)
|
|
||||||
val bexpr=assignment.value as? BinaryExpression
|
val bexpr=assignment.value as? BinaryExpression
|
||||||
if(bexpr!=null) {
|
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) {
|
if (rightCv != null && assignment.target isSameAs bexpr.left) {
|
||||||
// assignments of the form: X = X <operator> <expr>
|
// assignments of the form: X = X <operator> <expr>
|
||||||
// remove assignments that have no effect (such as X=X+0)
|
// remove assignments that have no effect (such as X=X+0)
|
||||||
// optimize/rewrite some other expressions
|
// optimize/rewrite some other expressions
|
||||||
|
val targetDt = targetIDt.getOr(DataType.UNDEFINED)
|
||||||
val vardeclDt = (assignment.target.identifier?.targetVarDecl(program))?.type
|
val vardeclDt = (assignment.target.identifier?.targetVarDecl(program))?.type
|
||||||
when (bexpr.operator) {
|
when (bexpr.operator) {
|
||||||
"+" -> {
|
"+" -> {
|
||||||
if (rightCv == 0.0) {
|
if (rightCv == 0.0) {
|
||||||
return listOf(IAstModification.Remove(assignment, assignment.definingScope))
|
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
|
||||||
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
|
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
|
||||||
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..4.0) {
|
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..4.0) {
|
||||||
// replace by several INCs if it's not a memory address (inc on a memory mapped register doesn't work very well)
|
// replace by several INCs if it's not a memory address (inc on a memory mapped register doesn't work very well)
|
||||||
@ -393,13 +390,13 @@ internal class StatementOptimizer(private val program: Program,
|
|||||||
repeat(rightCv.toInt()) {
|
repeat(rightCv.toInt()) {
|
||||||
incs.statements.add(PostIncrDecr(assignment.target.copy(), "++", assignment.position))
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"-" -> {
|
"-" -> {
|
||||||
if (rightCv == 0.0) {
|
if (rightCv == 0.0) {
|
||||||
return listOf(IAstModification.Remove(assignment, assignment.definingScope))
|
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
|
||||||
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
|
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
|
||||||
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..4.0) {
|
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..4.0) {
|
||||||
// replace by several DECs if it's not a memory address (dec on a memory mapped register doesn't work very well)
|
// replace by several DECs if it's not a memory address (dec on a memory mapped register doesn't work very well)
|
||||||
@ -411,18 +408,18 @@ internal class StatementOptimizer(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"*" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope))
|
"*" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
|
||||||
"/" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope))
|
"/" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
|
||||||
"**" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope))
|
"**" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
|
||||||
"|" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope))
|
"|" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
|
||||||
"^" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope))
|
"^" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
|
||||||
"<<" -> {
|
"<<" -> {
|
||||||
if (rightCv == 0.0)
|
if (rightCv == 0.0)
|
||||||
return listOf(IAstModification.Remove(assignment, assignment.definingScope))
|
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
|
||||||
}
|
}
|
||||||
">>" -> {
|
">>" -> {
|
||||||
if (rightCv == 0.0)
|
if (rightCv == 0.0)
|
||||||
return listOf(IAstModification.Remove(assignment, assignment.definingScope))
|
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -438,14 +435,19 @@ internal class StatementOptimizer(private val program: Program,
|
|||||||
val returnDt = subr.returntypes.single()
|
val returnDt = subr.returntypes.single()
|
||||||
if (returnDt in IntegerDatatypes) {
|
if (returnDt in IntegerDatatypes) {
|
||||||
// first assign to intermediary variable, then return that
|
// first assign to intermediary variable, then return that
|
||||||
subsThatNeedReturnVariable.add(Triple(subr, returnDt, returnStmt.position))
|
val returnVarName = "retval_interm_" + when(returnDt) {
|
||||||
val returnValueIntermediary1 = IdentifierReference(listOf(retvarName), returnStmt.position)
|
DataType.UBYTE -> "ub"
|
||||||
val returnValueIntermediary2 = IdentifierReference(listOf(retvarName), returnStmt.position)
|
DataType.BYTE -> "b"
|
||||||
val tgt = AssignTarget(returnValueIntermediary1, null, null, returnStmt.position)
|
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 assign = Assignment(tgt, value, returnStmt.position)
|
||||||
val returnReplacement = Return(returnValueIntermediary2, returnStmt.position)
|
val returnReplacement = Return(returnValueIntermediary.copy(), returnStmt.position)
|
||||||
return listOf(
|
return listOf(
|
||||||
IAstModification.InsertBefore(returnStmt, assign, parent as INameScope),
|
IAstModification.InsertBefore(returnStmt, assign, parent as IStatementContainer),
|
||||||
IAstModification.ReplaceNode(returnStmt, returnReplacement, parent)
|
IAstModification.ReplaceNode(returnStmt, returnReplacement, parent)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -469,7 +471,7 @@ internal class StatementOptimizer(private val program: Program,
|
|||||||
return super.after(returnStmt, parent)
|
return super.after(returnStmt, parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun hasBreak(scope: INameScope): Boolean {
|
private fun hasBreak(scope: IStatementContainer): Boolean {
|
||||||
|
|
||||||
class Searcher: IAstVisitor
|
class Searcher: IAstVisitor
|
||||||
{
|
{
|
247
codeOptimizers/src/prog8/optimizer/UnusedCodeRemover.kt
Normal file
247
codeOptimizers/src/prog8/optimizer/UnusedCodeRemover.kt
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
package prog8.optimizer
|
||||||
|
|
||||||
|
import prog8.ast.*
|
||||||
|
import prog8.ast.base.DataType
|
||||||
|
import prog8.ast.base.VarDeclType
|
||||||
|
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.isIOAddress
|
||||||
|
|
||||||
|
|
||||||
|
class UnusedCodeRemover(private val program: Program,
|
||||||
|
private val errors: IErrorReporter,
|
||||||
|
private val compTarget: ICompilationTarget
|
||||||
|
): AstWalker() {
|
||||||
|
|
||||||
|
private val callgraph = CallGraph(program)
|
||||||
|
|
||||||
|
override fun before(module: Module, parent: Node): Iterable<IAstModification> {
|
||||||
|
return if (!module.isLibrary && (module.containsNoCodeNorVars || callgraph.unused(module)))
|
||||||
|
listOf(IAstModification.Remove(module, parent as IStatementContainer))
|
||||||
|
else
|
||||||
|
noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
|
||||||
|
reportUnreachable(breakStmt)
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun before(jump: Jump, parent: Node): Iterable<IAstModification> {
|
||||||
|
if(!jump.isGosub)
|
||||||
|
reportUnreachable(jump)
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> {
|
||||||
|
reportUnreachable(returnStmt)
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||||
|
if(functionCallStatement.target.nameInSource.last() == "exit")
|
||||||
|
reportUnreachable(functionCallStatement)
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||||
|
return deduplicateAssignments(scope.statements, scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
|
||||||
|
if("force_output" !in block.options()) {
|
||||||
|
if (block.containsNoCodeNorVars) {
|
||||||
|
if(block.name != internedStringsModuleName)
|
||||||
|
errors.warn("removing unused block '${block.name}'", block.position)
|
||||||
|
return listOf(IAstModification.Remove(block, parent as IStatementContainer))
|
||||||
|
}
|
||||||
|
if(callgraph.unused(block)) {
|
||||||
|
errors.warn("removing unused block '${block.name}'", block.position)
|
||||||
|
return listOf(IAstModification.Remove(block, parent as IStatementContainer))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return deduplicateAssignments(block.statements, block)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||||
|
val forceOutput = "force_output" in subroutine.definingBlock.options()
|
||||||
|
if (subroutine !== program.entrypoint && !forceOutput && !subroutine.inline && !subroutine.isAsmSubroutine) {
|
||||||
|
if(callgraph.unused(subroutine)) {
|
||||||
|
if(subroutine.containsNoCodeNorVars) {
|
||||||
|
if(!subroutine.definingModule.isLibrary)
|
||||||
|
errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position)
|
||||||
|
val removals = mutableListOf(IAstModification.Remove(subroutine, parent as IStatementContainer))
|
||||||
|
callgraph.calledBy[subroutine]?.let {
|
||||||
|
for(node in it)
|
||||||
|
removals.add(IAstModification.Remove(node, node.parent as IStatementContainer))
|
||||||
|
}
|
||||||
|
return removals
|
||||||
|
}
|
||||||
|
if(!subroutine.definingModule.isLibrary)
|
||||||
|
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
|
||||||
|
return listOf(IAstModification.Remove(subroutine, parent as IStatementContainer))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
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>()
|
||||||
|
|
||||||
|
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
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) }
|
||||||
|
}
|
||||||
|
}
|
@ -3,31 +3,28 @@ plugins {
|
|||||||
id 'application'
|
id 'application'
|
||||||
id "org.jetbrains.kotlin.jvm"
|
id "org.jetbrains.kotlin.jvm"
|
||||||
id 'com.github.johnrengelman.shadow' version '7.1.0'
|
id 'com.github.johnrengelman.shadow' version '7.1.0'
|
||||||
|
id "io.kotest" version "0.3.8"
|
||||||
}
|
}
|
||||||
|
|
||||||
targetCompatibility = 11
|
java {
|
||||||
sourceCompatibility = 11
|
toolchain {
|
||||||
|
languageVersion = JavaLanguageVersion.of(javaVersion)
|
||||||
|
}
|
||||||
repositories {
|
|
||||||
mavenLocal()
|
|
||||||
mavenCentral()
|
|
||||||
maven { url "https://kotlin.bintray.com/kotlinx" }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def prog8version = rootProject.file('compiler/res/version.txt').text.trim()
|
def prog8version = rootProject.file('compiler/res/version.txt').text.trim()
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation project(':compilerInterfaces')
|
||||||
|
implementation project(':codeOptimizers')
|
||||||
implementation project(':compilerAst')
|
implementation project(':compilerAst')
|
||||||
|
implementation project(':codeGeneration')
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.3'
|
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.3'
|
||||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.12"
|
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.12"
|
||||||
|
|
||||||
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
|
testImplementation 'io.kotest:kotest-runner-junit5-jvm:4.6.3'
|
||||||
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'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
configurations.all {
|
configurations.all {
|
||||||
@ -44,22 +41,6 @@ configurations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
compileKotlin {
|
|
||||||
kotlinOptions {
|
|
||||||
jvmTarget = "11"
|
|
||||||
useIR = true
|
|
||||||
// verbose = true
|
|
||||||
// freeCompilerArgs += "-XXLanguage:+NewInference"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
compileTestKotlin {
|
|
||||||
kotlinOptions {
|
|
||||||
jvmTarget = "11"
|
|
||||||
useIR = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main {
|
main {
|
||||||
java {
|
java {
|
||||||
@ -84,11 +65,6 @@ application {
|
|||||||
applicationName = 'p8compile'
|
applicationName = 'p8compile'
|
||||||
}
|
}
|
||||||
|
|
||||||
artifacts {
|
|
||||||
archives shadowJar
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
shadowJar {
|
shadowJar {
|
||||||
archiveBaseName = 'prog8compiler'
|
archiveBaseName = 'prog8compiler'
|
||||||
archiveVersion = prog8version
|
archiveVersion = prog8version
|
||||||
@ -108,3 +84,5 @@ test {
|
|||||||
events "skipped", "failed"
|
events "skipped", "failed"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
build.finalizedBy installDist, installShadowDist
|
||||||
|
@ -1,10 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<module type="JAVA_MODULE" version="4">
|
<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">
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
<exclude-output />
|
<exclude-output />
|
||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
@ -17,10 +12,12 @@
|
|||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||||
<orderEntry type="module" module-name="compilerAst" />
|
<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="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="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" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
@ -12,6 +12,7 @@ floats {
|
|||||||
const float PI = 3.141592653589793
|
const float PI = 3.141592653589793
|
||||||
const float TWOPI = 6.283185307179586
|
const float TWOPI = 6.283185307179586
|
||||||
|
|
||||||
|
float tempvar_swap_float ; used for some swap() operations
|
||||||
|
|
||||||
; ---- C64 basic and kernal ROM float constants and functions ----
|
; ---- 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) {
|
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
|
||||||
; Bresenham algorithm.
|
; Bresenham algorithm.
|
||||||
; This code special-cases various quadrant loops to allow simple ++ and -- operations.
|
; 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 {
|
if y1>y2 {
|
||||||
; make sure dy is always positive to have only 4 instead of 8 special cases
|
; make sure dy is always positive to have only 4 instead of 8 special cases
|
||||||
swap(x1, x2)
|
swap(x1, x2)
|
||||||
|
@ -597,62 +597,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 {
|
inline asmsub read_flags() -> ubyte @A {
|
||||||
%asm {{
|
%asm {{
|
||||||
php
|
php
|
||||||
pla
|
pla
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline asmsub clear_carry() {
|
inline asmsub clear_carry() {
|
||||||
%asm {{
|
%asm {{
|
||||||
clc
|
clc
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline asmsub set_carry() {
|
inline asmsub set_carry() {
|
||||||
%asm {{
|
%asm {{
|
||||||
sec
|
sec
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline asmsub clear_irqd() {
|
inline asmsub clear_irqd() {
|
||||||
%asm {{
|
%asm {{
|
||||||
cli
|
cli
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline asmsub set_irqd() {
|
inline asmsub set_irqd() {
|
||||||
%asm {{
|
%asm {{
|
||||||
sei
|
sei
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -699,6 +671,23 @@ cx16 {
|
|||||||
&uword r14 = $cf1c
|
&uword r14 = $cf1c
|
||||||
&uword r15 = $cf1e
|
&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 r0L = $cf00
|
||||||
&ubyte r1L = $cf02
|
&ubyte r1L = $cf02
|
||||||
&ubyte r2L = $cf04
|
&ubyte r2L = $cf04
|
||||||
@ -732,4 +721,38 @@ cx16 {
|
|||||||
&ubyte r13H = $cf1b
|
&ubyte r13H = $cf1b
|
||||||
&ubyte r14H = $cf1d
|
&ubyte r14H = $cf1d
|
||||||
&ubyte r15H = $cf1f
|
&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) {
|
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)
|
; ---- convert the ubyte in A in decimal string form, with left padding 0s (3 positions total)
|
||||||
%asm {{
|
%asm {{
|
||||||
phx
|
stx P8ZP_SCRATCH_REG
|
||||||
jsr conv.ubyte2decimal
|
jsr conv.ubyte2decimal
|
||||||
sty string_out
|
sty string_out
|
||||||
sta string_out+1
|
sta string_out+1
|
||||||
stx string_out+2
|
stx string_out+2
|
||||||
lda #0
|
lda #0
|
||||||
sta string_out+3
|
sta string_out+3
|
||||||
plx
|
ldx P8ZP_SCRATCH_REG
|
||||||
rts
|
rts
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
@ -27,7 +27,7 @@ asmsub str_ub0 (ubyte value @ A) clobbers(A,Y) {
|
|||||||
asmsub str_ub (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
|
; ---- convert the ubyte in A in decimal string form, without left padding 0s
|
||||||
%asm {{
|
%asm {{
|
||||||
phx
|
stx P8ZP_SCRATCH_REG
|
||||||
ldy #0
|
ldy #0
|
||||||
sty P8ZP_SCRATCH_B1
|
sty P8ZP_SCRATCH_B1
|
||||||
jsr conv.ubyte2decimal
|
jsr conv.ubyte2decimal
|
||||||
@ -53,7 +53,7 @@ _output_byte_digits
|
|||||||
iny
|
iny
|
||||||
lda #0
|
lda #0
|
||||||
sta string_out,y
|
sta string_out,y
|
||||||
plx
|
ldx P8ZP_SCRATCH_REG
|
||||||
rts
|
rts
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
@ -61,7 +61,7 @@ _output_byte_digits
|
|||||||
asmsub str_b (byte value @ A) clobbers(A,Y) {
|
asmsub str_b (byte value @ A) clobbers(A,Y) {
|
||||||
; ---- convert the byte in A in decimal string form, without left padding 0s
|
; ---- convert the byte in A in decimal string form, without left padding 0s
|
||||||
%asm {{
|
%asm {{
|
||||||
phx
|
stx P8ZP_SCRATCH_REG
|
||||||
ldy #0
|
ldy #0
|
||||||
sty P8ZP_SCRATCH_B1
|
sty P8ZP_SCRATCH_B1
|
||||||
cmp #0
|
cmp #0
|
||||||
@ -149,7 +149,7 @@ asmsub str_uwhex (uword value @ AY) clobbers(A,Y) {
|
|||||||
asmsub str_uw0 (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)
|
; ---- convert the uword in A/Y in decimal string form, with left padding 0s (5 positions total)
|
||||||
%asm {{
|
%asm {{
|
||||||
phx
|
stx P8ZP_SCRATCH_REG
|
||||||
jsr conv.uword2decimal
|
jsr conv.uword2decimal
|
||||||
ldy #0
|
ldy #0
|
||||||
- lda conv.uword2decimal.decTenThousands,y
|
- lda conv.uword2decimal.decTenThousands,y
|
||||||
@ -157,7 +157,7 @@ asmsub str_uw0 (uword value @ AY) clobbers(A,Y) {
|
|||||||
beq +
|
beq +
|
||||||
iny
|
iny
|
||||||
bne -
|
bne -
|
||||||
+ plx
|
+ ldx P8ZP_SCRATCH_REG
|
||||||
rts
|
rts
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
@ -165,7 +165,7 @@ asmsub str_uw0 (uword value @ AY) clobbers(A,Y) {
|
|||||||
asmsub str_uw (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
|
; ---- convert the uword in A/Y in decimal string form, without left padding 0s
|
||||||
%asm {{
|
%asm {{
|
||||||
phx
|
stx P8ZP_SCRATCH_REG
|
||||||
jsr conv.uword2decimal
|
jsr conv.uword2decimal
|
||||||
ldx #0
|
ldx #0
|
||||||
_output_digits
|
_output_digits
|
||||||
@ -183,7 +183,7 @@ _gotdigit sta string_out,x
|
|||||||
bne _gotdigit
|
bne _gotdigit
|
||||||
_end lda #0
|
_end lda #0
|
||||||
sta string_out,x
|
sta string_out,x
|
||||||
plx
|
ldx P8ZP_SCRATCH_REG
|
||||||
rts
|
rts
|
||||||
|
|
||||||
_allzero lda #'0'
|
_allzero lda #'0'
|
||||||
@ -198,7 +198,7 @@ asmsub str_w (word value @ AY) clobbers(A,Y) {
|
|||||||
%asm {{
|
%asm {{
|
||||||
cpy #0
|
cpy #0
|
||||||
bpl str_uw
|
bpl str_uw
|
||||||
phx
|
stx P8ZP_SCRATCH_REG
|
||||||
pha
|
pha
|
||||||
lda #'-'
|
lda #'-'
|
||||||
sta string_out
|
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
|
;Convert 16 bit Hex to Decimal (0-65535) Rev 2
|
||||||
;By Omegamatrix Further optimizations by tepples
|
;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
|
;HexToDec99
|
||||||
; start in A
|
; start in A
|
||||||
|
@ -10,10 +10,11 @@ floats {
|
|||||||
; ---- this block contains C-64 compatible floating point related functions ----
|
; ---- this block contains C-64 compatible floating point related functions ----
|
||||||
; the addresses are from cx16 V39 emulator and roms! they won't work on older versions.
|
; the addresses are from cx16 V39 emulator and roms! they won't work on older versions.
|
||||||
|
|
||||||
|
|
||||||
const float PI = 3.141592653589793
|
const float PI = 3.141592653589793
|
||||||
const float TWOPI = 6.283185307179586
|
const float TWOPI = 6.283185307179586
|
||||||
|
|
||||||
|
float tempvar_swap_float ; used for some swap() operations
|
||||||
|
|
||||||
|
|
||||||
; ---- ROM float functions ----
|
; ---- 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!).
|
; -- 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.
|
; 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
|
; 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
|
uword chardataptr
|
||||||
when active_mode {
|
when active_mode {
|
||||||
1, 5 -> {
|
1, 5 -> {
|
||||||
@ -808,11 +809,8 @@ _done
|
|||||||
sta cx16.VERA_ADDR_L
|
sta cx16.VERA_ADDR_L
|
||||||
bcc +
|
bcc +
|
||||||
inc cx16.VERA_ADDR_M
|
inc cx16.VERA_ADDR_M
|
||||||
+ lda x
|
+ inc x
|
||||||
clc
|
bne +
|
||||||
adc #1
|
|
||||||
sta x
|
|
||||||
bcc +
|
|
||||||
inc x+1
|
inc x+1
|
||||||
+ dey
|
+ dey
|
||||||
bne -
|
bne -
|
||||||
@ -826,7 +824,6 @@ _done
|
|||||||
chardataptr = charset_addr + (@(sctextptr) as uword)*8
|
chardataptr = charset_addr + (@(sctextptr) as uword)*8
|
||||||
cx16.vaddr(charset_bank, chardataptr, 1, 1)
|
cx16.vaddr(charset_bank, chardataptr, 1, 1)
|
||||||
repeat 8 {
|
repeat 8 {
|
||||||
; TODO rewrite this inner loop fully in assembly
|
|
||||||
position(x,y)
|
position(x,y)
|
||||||
y++
|
y++
|
||||||
%asm {{
|
%asm {{
|
||||||
@ -855,7 +852,9 @@ _done
|
|||||||
while @(sctextptr) {
|
while @(sctextptr) {
|
||||||
chardataptr = charset_addr + (@(sctextptr) as uword)*8
|
chardataptr = charset_addr + (@(sctextptr) as uword)*8
|
||||||
repeat 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)
|
ubyte charbits = cx16.vpeek(charset_bank, chardataptr)
|
||||||
repeat 8 {
|
repeat 8 {
|
||||||
charbits <<= 1
|
charbits <<= 1
|
||||||
|
@ -95,7 +95,7 @@ cx16 {
|
|||||||
&uword IRQ_VEC = $FFFE ; 65c02 interrupt 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 r0 = $0002
|
||||||
&uword r1 = $0004
|
&uword r1 = $0004
|
||||||
&uword r2 = $0006
|
&uword r2 = $0006
|
||||||
@ -113,6 +113,23 @@ cx16 {
|
|||||||
&uword r14 = $001e
|
&uword r14 = $001e
|
||||||
&uword r15 = $0020
|
&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 r0L = $0002
|
||||||
&ubyte r1L = $0004
|
&ubyte r1L = $0004
|
||||||
&ubyte r2L = $0006
|
&ubyte r2L = $0006
|
||||||
@ -147,6 +164,39 @@ cx16 {
|
|||||||
&ubyte r14H = $001f
|
&ubyte r14H = $001f
|
||||||
&ubyte r15H = $0021
|
&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
|
; VERA registers
|
||||||
|
|
||||||
@ -262,7 +312,7 @@ 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 $ff4d = clock_set_date_time(uword yearmonth @R0, uword dayhours @R1, uword minsecs @R2, ubyte jiffies @R3) clobbers(A, X, Y)
|
||||||
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) -> uword @R0, uword @R1, uword @R2, ubyte @R3 ; result registers see clock_set_date_time()
|
romsub $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
|
; TODO specify the correct clobbers for all functions below, we now assume all 3 regs are clobbered
|
||||||
|
|
||||||
; high level graphics & fonts
|
; high level graphics & fonts
|
||||||
romsub $ff20 = GRAPH_init(uword vectors @R0) clobbers(A,X,Y)
|
romsub $ff20 = GRAPH_init(uword vectors @R0) clobbers(A,X,Y)
|
||||||
@ -317,14 +367,14 @@ romsub $fecc = monitor() clobbers(A,X,Y)
|
|||||||
|
|
||||||
; ---- utilities -----
|
; ---- utilities -----
|
||||||
|
|
||||||
inline asmsub rombank(ubyte rombank @A) {
|
inline asmsub rombank(ubyte bank @A) {
|
||||||
; -- set the rom banks
|
; -- set the rom banks
|
||||||
%asm {{
|
%asm {{
|
||||||
sta $01 ; rom bank register (v39+, used to be cx16.d1prb $9f60 in v38)
|
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
|
; -- set the ram bank
|
||||||
%asm {{
|
%asm {{
|
||||||
sta $00 ; ram bank register (v39+, used to be cx16.d1pra $9f61 in v38)
|
sta $00 ; ram bank register (v39+, used to be cx16.d1pra $9f61 in v38)
|
||||||
@ -809,27 +859,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 {
|
inline asmsub read_flags() -> ubyte @A {
|
||||||
%asm {{
|
%asm {{
|
||||||
php
|
php
|
||||||
@ -839,25 +868,25 @@ sys {
|
|||||||
|
|
||||||
inline asmsub clear_carry() {
|
inline asmsub clear_carry() {
|
||||||
%asm {{
|
%asm {{
|
||||||
clc
|
clc
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline asmsub set_carry() {
|
inline asmsub set_carry() {
|
||||||
%asm {{
|
%asm {{
|
||||||
sec
|
sec
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline asmsub clear_irqd() {
|
inline asmsub clear_irqd() {
|
||||||
%asm {{
|
%asm {{
|
||||||
cli
|
cli
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline asmsub set_irqd() {
|
inline asmsub set_irqd() {
|
||||||
%asm {{
|
%asm {{
|
||||||
sei
|
sei
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -565,9 +565,8 @@ asmsub print_w (word value @ AY) clobbers(A,Y) {
|
|||||||
tay
|
tay
|
||||||
pla
|
pla
|
||||||
eor #255
|
eor #255
|
||||||
clc
|
ina
|
||||||
adc #1
|
bne +
|
||||||
bcc +
|
|
||||||
iny
|
iny
|
||||||
+ bra print_uw
|
+ bra print_uw
|
||||||
}}
|
}}
|
||||||
|
@ -91,6 +91,13 @@ func_sin8_into_A .proc
|
|||||||
_sinecos8 .char trunc(127.0 * sin(range(256+64) * rad(360.0/256.0)))
|
_sinecos8 .char trunc(127.0 * sin(range(256+64) * rad(360.0/256.0)))
|
||||||
.pend
|
.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
|
func_sin8u_into_A .proc
|
||||||
tay
|
tay
|
||||||
lda _sinecos8u,y
|
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)))
|
_sinecos8u .byte trunc(128.0 + 127.5 * sin(range(256+64) * rad(360.0/256.0)))
|
||||||
.pend
|
.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
|
func_sin8_stack .proc
|
||||||
tay
|
tay
|
||||||
lda func_sin8_into_A._sinecos8,y
|
lda func_sin8_into_A._sinecos8,y
|
||||||
@ -106,6 +120,14 @@ func_sin8_stack .proc
|
|||||||
rts
|
rts
|
||||||
.pend
|
.pend
|
||||||
|
|
||||||
|
func_sinr8_stack .proc
|
||||||
|
tay
|
||||||
|
lda func_sinr8_into_A._sinecosR8,y
|
||||||
|
sta P8ESTACK_LO,x
|
||||||
|
dex
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
func_sin8u_stack .proc
|
func_sin8u_stack .proc
|
||||||
tay
|
tay
|
||||||
lda func_sin8u_into_A._sinecos8u,y
|
lda func_sin8u_into_A._sinecos8u,y
|
||||||
@ -114,18 +136,38 @@ func_sin8u_stack .proc
|
|||||||
rts
|
rts
|
||||||
.pend
|
.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
|
func_cos8_into_A .proc
|
||||||
tay
|
tay
|
||||||
lda func_sin8_into_A._sinecos8+64,y
|
lda func_sin8_into_A._sinecos8+64,y
|
||||||
rts
|
rts
|
||||||
.pend
|
.pend
|
||||||
|
|
||||||
|
func_cosr8_into_A .proc
|
||||||
|
tay
|
||||||
|
lda func_sinr8_into_A._sinecosR8+45,y
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
func_cos8u_into_A .proc
|
func_cos8u_into_A .proc
|
||||||
tay
|
tay
|
||||||
lda func_sin8u_into_A._sinecos8u+64,y
|
lda func_sin8u_into_A._sinecos8u+64,y
|
||||||
rts
|
rts
|
||||||
.pend
|
.pend
|
||||||
|
|
||||||
|
func_cosr8u_into_A .proc
|
||||||
|
tay
|
||||||
|
lda func_sinr8u_into_A._sinecosR8u+45,y
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
func_cos8_stack .proc
|
func_cos8_stack .proc
|
||||||
tay
|
tay
|
||||||
lda func_sin8_into_A._sinecos8+64,y
|
lda func_sin8_into_A._sinecos8+64,y
|
||||||
@ -134,6 +176,14 @@ func_cos8_stack .proc
|
|||||||
rts
|
rts
|
||||||
.pend
|
.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
|
func_cos8u_stack .proc
|
||||||
tay
|
tay
|
||||||
lda func_sin8u_into_A._sinecos8u+64,y
|
lda func_sin8u_into_A._sinecos8u+64,y
|
||||||
@ -142,6 +192,14 @@ func_cos8u_stack .proc
|
|||||||
rts
|
rts
|
||||||
.pend
|
.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
|
func_sin16_into_AY .proc
|
||||||
tay
|
tay
|
||||||
lda _sinecos8lo,y
|
lda _sinecos8lo,y
|
||||||
@ -155,6 +213,19 @@ _sinecos8lo .byte <_
|
|||||||
_sinecos8hi .byte >_
|
_sinecos8hi .byte >_
|
||||||
.pend
|
.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
|
func_sin16u_into_AY .proc
|
||||||
tay
|
tay
|
||||||
lda _sinecos8ulo,y
|
lda _sinecos8ulo,y
|
||||||
@ -168,6 +239,18 @@ _sinecos8ulo .byte <_
|
|||||||
_sinecos8uhi .byte >_
|
_sinecos8uhi .byte >_
|
||||||
.pend
|
.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
|
func_sin16_stack .proc
|
||||||
tay
|
tay
|
||||||
@ -179,6 +262,16 @@ func_sin16_stack .proc
|
|||||||
rts
|
rts
|
||||||
.pend
|
.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
|
func_sin16u_stack .proc
|
||||||
tay
|
tay
|
||||||
lda func_sin16u_into_AY._sinecos8ulo,y
|
lda func_sin16u_into_AY._sinecos8ulo,y
|
||||||
@ -189,6 +282,16 @@ func_sin16u_stack .proc
|
|||||||
rts
|
rts
|
||||||
.pend
|
.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
|
func_cos16_into_AY .proc
|
||||||
tay
|
tay
|
||||||
lda func_sin16_into_AY._sinecos8lo+64,y
|
lda func_sin16_into_AY._sinecos8lo+64,y
|
||||||
@ -199,6 +302,16 @@ func_cos16_into_AY .proc
|
|||||||
rts
|
rts
|
||||||
.pend
|
.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
|
func_cos16u_into_AY .proc
|
||||||
tay
|
tay
|
||||||
lda func_sin16u_into_AY._sinecos8ulo+64,y
|
lda func_sin16u_into_AY._sinecos8ulo+64,y
|
||||||
@ -209,6 +322,16 @@ func_cos16u_into_AY .proc
|
|||||||
rts
|
rts
|
||||||
.pend
|
.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
|
func_cos16_stack .proc
|
||||||
tay
|
tay
|
||||||
lda func_sin16_into_AY._sinecos8lo+64,y
|
lda func_sin16_into_AY._sinecos8lo+64,y
|
||||||
@ -219,6 +342,16 @@ func_cos16_stack .proc
|
|||||||
rts
|
rts
|
||||||
.pend
|
.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
|
func_cos16u_stack .proc
|
||||||
tay
|
tay
|
||||||
lda func_sin16u_into_AY._sinecos8ulo+64,y
|
lda func_sin16u_into_AY._sinecos8ulo+64,y
|
||||||
@ -229,6 +362,16 @@ func_cos16u_stack .proc
|
|||||||
rts
|
rts
|
||||||
.pend
|
.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
|
abs_b_stack .proc
|
||||||
; -- push abs(A) on stack (as byte)
|
; -- push abs(A) on stack (as byte)
|
||||||
jsr abs_b_into_A
|
jsr abs_b_into_A
|
||||||
|
@ -6,10 +6,24 @@ prog8_lib {
|
|||||||
%asminclude "library:prog8_lib.asm"
|
%asminclude "library:prog8_lib.asm"
|
||||||
%asminclude "library:prog8_funcs.asm"
|
%asminclude "library:prog8_funcs.asm"
|
||||||
|
|
||||||
uword @zp retval_interm_uw ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
|
; to store intermediary expression results for return values:
|
||||||
word @zp retval_interm_w ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
|
; NOTE: these variables are used in the StatementReorderer and StatementOptimizer
|
||||||
ubyte @zp retval_interm_ub ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
|
uword @zp retval_interm_uw
|
||||||
byte @zp retval_interm_b ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
|
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 {
|
asmsub pattern_match(str string @AY, str pattern @R0) clobbers(Y) -> ubyte @A {
|
||||||
%asm {{
|
%asm {{
|
||||||
|
@ -1 +1 @@
|
|||||||
7.1
|
7.4
|
||||||
|
@ -3,10 +3,10 @@ package prog8
|
|||||||
import kotlinx.cli.*
|
import kotlinx.cli.*
|
||||||
import prog8.ast.base.AstException
|
import prog8.ast.base.AstException
|
||||||
import prog8.compiler.CompilationResult
|
import prog8.compiler.CompilationResult
|
||||||
|
import prog8.compiler.CompilerArguments
|
||||||
import prog8.compiler.compileProgram
|
import prog8.compiler.compileProgram
|
||||||
import prog8.compiler.target.C64Target
|
import prog8.compiler.target.C64Target
|
||||||
import prog8.compiler.target.Cx16Target
|
import prog8.compiler.target.Cx16Target
|
||||||
import prog8.parser.ParsingFailedError
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.nio.file.FileSystems
|
import java.nio.file.FileSystems
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
@ -36,10 +36,12 @@ private fun compileMain(args: Array<String>): Boolean {
|
|||||||
val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".")
|
val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".")
|
||||||
val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code")
|
val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code")
|
||||||
val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform any optimizations")
|
val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform any optimizations")
|
||||||
|
val optimizeFloatExpressions by cli.option(ArgType.Boolean, fullName = "optfloatx", description = "optimize float expressions (warning: can increase program size)")
|
||||||
val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watches for file changes), greatly increases compilation speed")
|
val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watches for file changes), greatly increases compilation speed")
|
||||||
val slowCodegenWarnings by cli.option(ArgType.Boolean, fullName = "slowwarn", description="show debug warnings about slow/problematic assembly code generation")
|
val slowCodegenWarnings by cli.option(ArgType.Boolean, fullName = "slowwarn", description="show debug warnings about slow/problematic assembly code generation")
|
||||||
|
val quietAssembler by cli.option(ArgType.Boolean, fullName = "quietasm", description = "don't print assembler output results")
|
||||||
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available").default(C64Target.name)
|
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available").default(C64Target.name)
|
||||||
val sourceDirs by cli.option(ArgType.String, fullName="srcdirs", description = "list of extra paths to search in for imported modules").multiple().delimiter(File.pathSeparator)
|
val sourceDirs by cli.option(ArgType.String, fullName="srcdirs", description = "list of extra paths, separated with ${File.pathSeparator}, to search in for imported modules").multiple().delimiter(File.pathSeparator)
|
||||||
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999)
|
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -65,6 +67,11 @@ private fun compileMain(args: Array<String>): Boolean {
|
|||||||
if(srcdirs.firstOrNull()!=".")
|
if(srcdirs.firstOrNull()!=".")
|
||||||
srcdirs.add(0, ".")
|
srcdirs.add(0, ".")
|
||||||
|
|
||||||
|
if (compilationTarget != C64Target.name && compilationTarget != Cx16Target.name) {
|
||||||
|
System.err.println("Invalid compilation target: $compilationTarget")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
if(watchMode==true) {
|
if(watchMode==true) {
|
||||||
val watchservice = FileSystems.getDefault().newWatchService()
|
val watchservice = FileSystems.getDefault().newWatchService()
|
||||||
val allImportedFiles = mutableSetOf<Path>()
|
val allImportedFiles = mutableSetOf<Path>()
|
||||||
@ -74,7 +81,18 @@ private fun compileMain(args: Array<String>): Boolean {
|
|||||||
val results = mutableListOf<CompilationResult>()
|
val results = mutableListOf<CompilationResult>()
|
||||||
for(filepathRaw in moduleFiles) {
|
for(filepathRaw in moduleFiles) {
|
||||||
val filepath = pathFrom(filepathRaw).normalize()
|
val filepath = pathFrom(filepathRaw).normalize()
|
||||||
val compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, srcdirs, outputPath)
|
val args = CompilerArguments(
|
||||||
|
filepath,
|
||||||
|
dontOptimize != true,
|
||||||
|
optimizeFloatExpressions == true,
|
||||||
|
dontWriteAssembly != true,
|
||||||
|
slowCodegenWarnings == true,
|
||||||
|
quietAssembler == true,
|
||||||
|
compilationTarget,
|
||||||
|
srcdirs,
|
||||||
|
outputPath
|
||||||
|
)
|
||||||
|
val compilationResult = compileProgram(args)
|
||||||
results.add(compilationResult)
|
results.add(compilationResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,11 +129,20 @@ private fun compileMain(args: Array<String>): Boolean {
|
|||||||
val filepath = pathFrom(filepathRaw).normalize()
|
val filepath = pathFrom(filepathRaw).normalize()
|
||||||
val compilationResult: CompilationResult
|
val compilationResult: CompilationResult
|
||||||
try {
|
try {
|
||||||
compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, srcdirs, outputPath)
|
val args = CompilerArguments(
|
||||||
if(!compilationResult.success)
|
filepath,
|
||||||
|
dontOptimize != true,
|
||||||
|
optimizeFloatExpressions == true,
|
||||||
|
dontWriteAssembly != true,
|
||||||
|
slowCodegenWarnings == true,
|
||||||
|
quietAssembler == true,
|
||||||
|
compilationTarget,
|
||||||
|
srcdirs,
|
||||||
|
outputPath
|
||||||
|
)
|
||||||
|
compilationResult = compileProgram(args)
|
||||||
|
if (!compilationResult.success)
|
||||||
return false
|
return false
|
||||||
} catch (x: ParsingFailedError) {
|
|
||||||
return false
|
|
||||||
} catch (x: AstException) {
|
} catch (x: AstException) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
package prog8.compiler
|
|
||||||
|
|
||||||
internal class AssemblyError(msg: String) : RuntimeException(msg)
|
|
@ -1,6 +1,7 @@
|
|||||||
package prog8.compiler
|
package prog8.compiler
|
||||||
|
|
||||||
import prog8.ast.IFunctionCall
|
import prog8.ast.IFunctionCall
|
||||||
|
import prog8.ast.IStatementContainer
|
||||||
import prog8.ast.Node
|
import prog8.ast.Node
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
@ -9,28 +10,31 @@ import prog8.ast.statements.*
|
|||||||
import prog8.ast.walk.AstWalker
|
import prog8.ast.walk.AstWalker
|
||||||
import prog8.ast.walk.IAstModification
|
import prog8.ast.walk.IAstModification
|
||||||
import prog8.ast.walk.IAstVisitor
|
import prog8.ast.walk.IAstVisitor
|
||||||
import prog8.compiler.astprocessing.isInRegularRAMof
|
import prog8.compiler.astprocessing.isSubroutineParameter
|
||||||
import prog8.compiler.target.ICompilationTarget
|
import prog8.compiler.target.AssemblyError
|
||||||
|
import prog8.compilerinterface.*
|
||||||
|
import prog8.optimizer.getTempVarName
|
||||||
|
|
||||||
|
|
||||||
internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: IErrorReporter, private val compTarget: ICompilationTarget) : AstWalker() {
|
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 after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||||
subroutineVariables.add(decl.name to decl)
|
if(decl.type==VarDeclType.VAR && decl.value != null && decl.datatype in NumericDatatypes)
|
||||||
if (decl.value == null && !decl.autogeneratedDontRemove && decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
|
throw FatalAstException("vardecls for variables, with initial numerical value, should have been rewritten as plain vardecl + assignment $decl")
|
||||||
// A numeric vardecl without an initial value is initialized with zero,
|
rememberSubroutineVar(decl)
|
||||||
// unless there's already an assignment below, that initializes the value.
|
|
||||||
// This allows you to restart the program and have the same starting values of the variables
|
|
||||||
if(decl.allowInitializeWithZero)
|
|
||||||
{
|
|
||||||
val nextAssign = decl.definingScope.nextSibling(decl) as? Assignment
|
|
||||||
if (nextAssign != null && nextAssign.target isSameAs IdentifierReference(listOf(decl.name), Position.DUMMY))
|
|
||||||
decl.value = null
|
|
||||||
else {
|
|
||||||
decl.value = decl.zeroElementValue()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,26 +44,34 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
|
|||||||
// But it can only be done if the target variable IS NOT OCCURRING AS AN OPERAND ITSELF.
|
// But it can only be done if the target variable IS NOT OCCURRING AS AN OPERAND ITSELF.
|
||||||
if(!assignment.isAugmentable
|
if(!assignment.isAugmentable
|
||||||
&& assignment.target.identifier != null
|
&& assignment.target.identifier != null
|
||||||
&& assignment.target.isInRegularRAMof(compTarget.machine)) {
|
&& !assignment.target.isIOAddress(options.compTarget.machine)) {
|
||||||
val binExpr = assignment.value as? BinaryExpression
|
val binExpr = assignment.value as? BinaryExpression
|
||||||
|
|
||||||
|
if(binExpr!=null && binExpr.inferType(program).istype(DataType.FLOAT) && !options.optimizeFloatExpressions)
|
||||||
|
return noModifications
|
||||||
|
|
||||||
if (binExpr != null && binExpr.operator !in comparisonOperators) {
|
if (binExpr != null && binExpr.operator !in comparisonOperators) {
|
||||||
if (binExpr.left !is BinaryExpression) {
|
if (binExpr.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.
|
// 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.
|
// we can't 'split' it trivially because the variable will be changed halfway through.
|
||||||
if(binExpr.operator in associativeOperators) {
|
if(binExpr.operator in associativeOperators) {
|
||||||
// A = <something-without-A> <associativeoperator> <otherthing-with-A>
|
// A = <something-without-A> <associativeoperator> <otherthing-with-A>
|
||||||
// use the other part of the expression to split.
|
// 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("invalid dt") }
|
||||||
|
val (_, right) = binExpr.right.typecastTo(assignment.target.inferType(program).getOr(DataType.UNDEFINED), sourceDt, implicit=true)
|
||||||
|
val assignRight = Assignment(assignment.target, right, assignment.position)
|
||||||
return listOf(
|
return listOf(
|
||||||
IAstModification.InsertBefore(assignment, assignRight, assignment.definingScope),
|
IAstModification.InsertBefore(assignment, assignRight, parent as IStatementContainer),
|
||||||
IAstModification.ReplaceNode(binExpr.right, binExpr.left, binExpr),
|
IAstModification.ReplaceNode(binExpr.right, binExpr.left, binExpr),
|
||||||
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
|
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val assignLeft = Assignment(assignment.target, binExpr.left, assignment.position)
|
val sourceDt = binExpr.left.inferType(program).getOrElse { throw AssemblyError("invalid dt") }
|
||||||
|
val (_, left) = binExpr.left.typecastTo(assignment.target.inferType(program).getOr(DataType.UNDEFINED), sourceDt, implicit=true)
|
||||||
|
val assignLeft = Assignment(assignment.target, left, assignment.position)
|
||||||
return listOf(
|
return listOf(
|
||||||
IAstModification.InsertBefore(assignment, assignLeft, assignment.definingScope),
|
IAstModification.InsertBefore(assignment, assignLeft, parent as IStatementContainer),
|
||||||
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
|
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -68,45 +80,16 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
|
|||||||
return noModifications
|
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()
|
|
||||||
return noModifications
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||||
val decls = scope.statements.filterIsInstance<VarDecl>().filter { it.type == VarDeclType.VAR }
|
if(scope.statements.any { it is VarDecl || it is IStatementContainer })
|
||||||
subroutineVariables.addAll(decls.map { it.name to it })
|
throw FatalAstException("anonymousscope may no longer contain any vardecls or subscopes")
|
||||||
|
|
||||||
val sub = scope.definingSubroutine
|
|
||||||
if (sub != null) {
|
|
||||||
// move any remaining vardecls of the scope into the upper scope. Make sure the position remains the same!
|
|
||||||
val replacements = mutableListOf<IAstModification>()
|
|
||||||
val movements = mutableListOf<IAstModification.InsertFirst>()
|
|
||||||
|
|
||||||
for(decl in decls) {
|
|
||||||
if(decl.value!=null && decl.datatype in NumericDatatypes) {
|
|
||||||
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
|
|
||||||
val assign = Assignment(target, decl.value!!, decl.position)
|
|
||||||
replacements.add(IAstModification.ReplaceNode(decl, assign, scope))
|
|
||||||
decl.value = null
|
|
||||||
decl.allowInitializeWithZero = false
|
|
||||||
} else {
|
|
||||||
replacements.add(IAstModification.Remove(decl, scope))
|
|
||||||
}
|
|
||||||
movements.add(IAstModification.InsertFirst(decl, sub))
|
|
||||||
}
|
|
||||||
return replacements + movements
|
|
||||||
}
|
|
||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||||
val firstDeclarations = mutableMapOf<String, VarDecl>()
|
val firstDeclarations = mutableMapOf<String, VarDecl>()
|
||||||
for(decl in subroutineVariables) {
|
val rememberedSubroutineVars = subroutineVariables.getOrDefault(subroutine, mutableListOf())
|
||||||
|
for(decl in rememberedSubroutineVars) {
|
||||||
val existing = firstDeclarations[decl.first]
|
val existing = firstDeclarations[decl.first]
|
||||||
if(existing!=null && existing !== decl.second) {
|
if(existing!=null && existing !== decl.second) {
|
||||||
errors.err("variable ${decl.first} already defined in subroutine ${subroutine.name} at ${existing.position}", decl.second.position)
|
errors.err("variable ${decl.first} already defined in subroutine ${subroutine.name} at ${existing.position}", decl.second.position)
|
||||||
@ -114,7 +97,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
|
|||||||
firstDeclarations[decl.first] = decl.second
|
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.
|
// 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.
|
// and if an assembly block doesn't contain a rts/rti, and some other situations.
|
||||||
@ -144,9 +127,10 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
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
|
// 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.
|
// 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
|
||||||
val sourceDt = typecast.expression.inferType(program).getOr(DataType.UNDEFINED)
|
val sourceDt = typecast.expression.inferType(program).getOr(DataType.UNDEFINED)
|
||||||
if (typecast.type in ByteDatatypes && sourceDt in ByteDatatypes
|
if (typecast.type in ByteDatatypes && sourceDt in ByteDatatypes
|
||||||
|| typecast.type in WordDatatypes && sourceDt in WordDatatypes) {
|
|| typecast.type in WordDatatypes && sourceDt in WordDatatypes) {
|
||||||
@ -155,22 +139,23 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Note: for various reasons (most importantly, code simplicity), the code generator assumes/requires
|
|
||||||
// that the types of assignment values and their target are the same,
|
|
||||||
// and that the types of both operands of a binaryexpression node are the same.
|
|
||||||
// So, it is not easily possible to remove the typecasts that are there to make these conditions true.
|
|
||||||
// The only place for now where we can do this is for:
|
|
||||||
// asmsub register pair parameter.
|
|
||||||
|
|
||||||
if(sourceDt in PassByReferenceDatatypes) {
|
if(sourceDt in PassByReferenceDatatypes) {
|
||||||
if(typecast.type==DataType.UWORD) {
|
if(typecast.type==DataType.UWORD) {
|
||||||
if(typecast.expression is IdentifierReference) {
|
val identifier = typecast.expression as? IdentifierReference
|
||||||
return listOf(IAstModification.ReplaceNode(
|
if(identifier!=null) {
|
||||||
|
return if(identifier.isSubroutineParameter(program)) {
|
||||||
|
listOf(IAstModification.ReplaceNode(
|
||||||
typecast,
|
typecast,
|
||||||
AddressOf(typecast.expression as IdentifierReference, typecast.position),
|
typecast.expression,
|
||||||
parent
|
parent
|
||||||
))
|
))
|
||||||
|
} else {
|
||||||
|
listOf(IAstModification.ReplaceNode(
|
||||||
|
typecast,
|
||||||
|
AddressOf(identifier, typecast.position),
|
||||||
|
parent
|
||||||
|
))
|
||||||
|
}
|
||||||
} else if(typecast.expression is IFunctionCall) {
|
} else if(typecast.expression is IFunctionCall) {
|
||||||
return listOf(IAstModification.ReplaceNode(
|
return listOf(IAstModification.ReplaceNode(
|
||||||
typecast,
|
typecast,
|
||||||
@ -186,7 +171,15 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
|
|||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("DuplicatedCode")
|
||||||
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
|
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
|
||||||
|
val prefixExpr = ifStatement.condition as? PrefixExpression
|
||||||
|
if(prefixExpr!=null && prefixExpr.operator=="not") {
|
||||||
|
// if not x -> if x==0
|
||||||
|
val booleanExpr = BinaryExpression(prefixExpr.expression, "==", NumericLiteralValue.optimalInteger(0, ifStatement.condition.position), ifStatement.condition.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(ifStatement.condition, booleanExpr, ifStatement))
|
||||||
|
}
|
||||||
|
|
||||||
val binExpr = ifStatement.condition as? BinaryExpression
|
val binExpr = ifStatement.condition as? BinaryExpression
|
||||||
if(binExpr==null || binExpr.operator !in comparisonOperators) {
|
if(binExpr==null || binExpr.operator !in comparisonOperators) {
|
||||||
// if x -> if x!=0, if x+5 -> if x+5 != 0
|
// if x -> if x!=0, if x+5 -> if x+5 != 0
|
||||||
@ -194,71 +187,142 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
|
|||||||
return listOf(IAstModification.ReplaceNode(ifStatement.condition, booleanExpr, ifStatement))
|
return listOf(IAstModification.ReplaceNode(ifStatement.condition, booleanExpr, ifStatement))
|
||||||
}
|
}
|
||||||
|
|
||||||
if((binExpr.operator=="==" || binExpr.operator=="!=") &&
|
if((binExpr.left as? NumericLiteralValue)?.number==0.0 &&
|
||||||
(binExpr.left as? NumericLiteralValue)?.number==0 &&
|
(binExpr.right as? NumericLiteralValue)?.number!=0.0)
|
||||||
(binExpr.right as? NumericLiteralValue)?.number!=0)
|
throw FatalAstException("0==X should have been swapped to if X==0")
|
||||||
throw CompilerException("if 0==X should have been swapped to if X==0")
|
|
||||||
|
|
||||||
// split the conditional expression into separate variables if the operand(s) is not simple.
|
// simplify the conditional expression, introduce simple assignments if required.
|
||||||
// DISABLED FOR NOW AS IT GENEREATES LARGER CODE IN THE SIMPLE CASES LIKE IF X {...} or IF NOT X {...}
|
// NOTE: sometimes this increases code size because additional stores/loads are generated for the
|
||||||
// val modifications = mutableListOf<IAstModification>()
|
// intermediate variables. We assume these are optimized away from the resulting assembly code later.
|
||||||
// if(!binExpr.left.isSimple) {
|
val simplify = simplifyConditionalExpression(binExpr)
|
||||||
// val sub = binExpr.definingSubroutine()!!
|
val modifications = mutableListOf<IAstModification>()
|
||||||
// val (variable, isNew, assignment) = addIfOperandVar(sub, "left", binExpr.left)
|
if(simplify.rightVarAssignment!=null) {
|
||||||
// if(isNew)
|
modifications += IAstModification.ReplaceNode(binExpr.right, simplify.rightOperandReplacement!!, binExpr)
|
||||||
// modifications.add(IAstModification.InsertFirst(variable, sub))
|
modifications += IAstModification.InsertBefore(ifStatement, simplify.rightVarAssignment, parent as IStatementContainer)
|
||||||
// modifications.add(IAstModification.InsertBefore(ifStatement, assignment, parent as INameScope))
|
}
|
||||||
// modifications.add(IAstModification.ReplaceNode(binExpr.left, IdentifierReference(listOf(variable.name), binExpr.position), binExpr))
|
if(simplify.leftVarAssignment!=null) {
|
||||||
// addedIfConditionVars.add(Pair(sub, variable.name))
|
modifications += IAstModification.ReplaceNode(binExpr.left, simplify.leftOperandReplacement!!, binExpr)
|
||||||
// }
|
modifications += IAstModification.InsertBefore(ifStatement, simplify.leftVarAssignment, parent as IStatementContainer)
|
||||||
// if(!binExpr.right.isSimple) {
|
}
|
||||||
// val sub = binExpr.definingSubroutine()!!
|
|
||||||
// val (variable, isNew, assignment) = addIfOperandVar(sub, "right", binExpr.right)
|
return modifications
|
||||||
// if(isNew)
|
|
||||||
// modifications.add(IAstModification.InsertFirst(variable, sub))
|
|
||||||
// modifications.add(IAstModification.InsertBefore(ifStatement, assignment, parent as INameScope))
|
|
||||||
// modifications.add(IAstModification.ReplaceNode(binExpr.right, IdentifierReference(listOf(variable.name), binExpr.position), binExpr))
|
|
||||||
// addedIfConditionVars.add(Pair(sub, variable.name))
|
|
||||||
// }
|
|
||||||
// return modifications
|
|
||||||
return noModifications
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// private fun addIfOperandVar(sub: Subroutine, side: String, operand: Expression): Triple<VarDecl, Boolean, Assignment> {
|
private class CondExprSimplificationResult(
|
||||||
// val dt = operand.inferType(program).typeOrElse(DataType.UNDEFINED)
|
val leftVarAssignment: Assignment?,
|
||||||
// val varname = "prog8_ifvar_${side}_${dt.name.toLowerCase()}"
|
val leftOperandReplacement: Expression?,
|
||||||
// val tgt = AssignTarget(IdentifierReference(listOf(varname), operand.position), null, null, operand.position)
|
val rightVarAssignment: Assignment?,
|
||||||
// val assign = Assignment(tgt, operand, operand.position)
|
val rightOperandReplacement: Expression?
|
||||||
// if(Pair(sub, varname) in addedIfConditionVars) {
|
)
|
||||||
// val vardecl = VarDecl(VarDeclType.VAR, dt, ZeropageWish.DONTCARE, null, varname, null, null, false, true, operand.position)
|
|
||||||
// return Triple(vardecl, false, assign)
|
|
||||||
// }
|
|
||||||
// val existing = sub.statements.firstOrNull { it is VarDecl && it.name == varname} as VarDecl?
|
|
||||||
// return if (existing == null) {
|
|
||||||
// val vardecl = VarDecl(VarDeclType.VAR, dt, ZeropageWish.DONTCARE, null, varname, null, null, false, true, operand.position)
|
|
||||||
// Triple(vardecl, true, assign)
|
|
||||||
// } else {
|
|
||||||
// Triple(existing, false, assign)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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")
|
||||||
override fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
|
override fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
|
||||||
|
val prefixExpr = untilLoop.condition as? PrefixExpression
|
||||||
|
if(prefixExpr!=null && prefixExpr.operator=="not") {
|
||||||
|
// until not x -> until x==0
|
||||||
|
val booleanExpr = BinaryExpression(prefixExpr.expression, "==", NumericLiteralValue.optimalInteger(0, untilLoop.condition.position), untilLoop.condition.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(untilLoop.condition, booleanExpr, untilLoop))
|
||||||
|
}
|
||||||
|
|
||||||
val binExpr = untilLoop.condition as? BinaryExpression
|
val binExpr = untilLoop.condition as? BinaryExpression
|
||||||
if(binExpr==null || binExpr.operator !in comparisonOperators) {
|
if(binExpr==null || binExpr.operator !in comparisonOperators) {
|
||||||
// until x -> until x!=0, until x+5 -> until x+5 != 0
|
// until x -> until x!=0, until x+5 -> until x+5 != 0
|
||||||
val booleanExpr = BinaryExpression(untilLoop.condition, "!=", NumericLiteralValue.optimalInteger(0, untilLoop.condition.position), untilLoop.condition.position)
|
val booleanExpr = BinaryExpression(untilLoop.condition, "!=", NumericLiteralValue.optimalInteger(0, untilLoop.condition.position), untilLoop.condition.position)
|
||||||
return listOf(IAstModification.ReplaceNode(untilLoop.condition, booleanExpr, untilLoop))
|
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")
|
||||||
override fun after(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
|
override fun after(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
|
||||||
|
val prefixExpr = whileLoop.condition as? PrefixExpression
|
||||||
|
if(prefixExpr!=null && prefixExpr.operator=="not") {
|
||||||
|
// while not x -> while x==0
|
||||||
|
val booleanExpr = BinaryExpression(prefixExpr.expression, "==", NumericLiteralValue.optimalInteger(0, whileLoop.condition.position), whileLoop.condition.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(whileLoop.condition, booleanExpr, whileLoop))
|
||||||
|
}
|
||||||
|
|
||||||
val binExpr = whileLoop.condition as? BinaryExpression
|
val binExpr = whileLoop.condition as? BinaryExpression
|
||||||
if(binExpr==null || binExpr.operator !in comparisonOperators) {
|
if(binExpr==null || binExpr.operator !in comparisonOperators) {
|
||||||
// while x -> while x!=0, while x+5 -> while x+5 != 0
|
// while x -> while x!=0, while x+5 -> while x+5 != 0
|
||||||
val booleanExpr = BinaryExpression(whileLoop.condition, "!=", NumericLiteralValue.optimalInteger(0, whileLoop.condition.position), whileLoop.condition.position)
|
val booleanExpr = BinaryExpression(whileLoop.condition, "!=", NumericLiteralValue.optimalInteger(0, whileLoop.condition.position), whileLoop.condition.position)
|
||||||
return listOf(IAstModification.ReplaceNode(whileLoop.condition, booleanExpr, whileLoop))
|
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.
|
||||||
|
// 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
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,13 +336,15 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
|
|||||||
if(dt1 in ByteDatatypes) {
|
if(dt1 in ByteDatatypes) {
|
||||||
if(dt2 in ByteDatatypes)
|
if(dt2 in ByteDatatypes)
|
||||||
return noModifications
|
return noModifications
|
||||||
val cast1 = TypecastExpression(arg1, if(dt1==DataType.UBYTE) DataType.UWORD else DataType.WORD, true, functionCallStatement.position)
|
val (replaced, cast) = arg1.typecastTo(if(dt1==DataType.UBYTE) DataType.UWORD else DataType.WORD, dt1, true)
|
||||||
return listOf(IAstModification.ReplaceNode(arg1, cast1, functionCallStatement))
|
if(replaced)
|
||||||
|
return listOf(IAstModification.ReplaceNode(arg1, cast, functionCallStatement))
|
||||||
} else {
|
} else {
|
||||||
if(dt2 in WordDatatypes)
|
if(dt2 in WordDatatypes)
|
||||||
return noModifications
|
return noModifications
|
||||||
val cast2 = TypecastExpression(arg2, if(dt2==DataType.UBYTE) DataType.UWORD else DataType.WORD, true, functionCallStatement.position)
|
val (replaced, cast) = arg2.typecastTo(if(dt2==DataType.UBYTE) DataType.UWORD else DataType.WORD, dt2, true)
|
||||||
return listOf(IAstModification.ReplaceNode(arg2, cast2, functionCallStatement))
|
if(replaced)
|
||||||
|
return listOf(IAstModification.ReplaceNode(arg2, cast, functionCallStatement))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return noModifications
|
return noModifications
|
||||||
@ -342,12 +408,12 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
|
|||||||
val modifications = mutableListOf<IAstModification>()
|
val modifications = mutableListOf<IAstModification>()
|
||||||
val statement = expr.containingStatement
|
val statement = expr.containingStatement
|
||||||
val dt = expr.indexer.indexExpr.inferType(program)
|
val dt = expr.indexer.indexExpr.inferType(program)
|
||||||
val register = if(dt istype DataType.UBYTE || dt istype DataType.BYTE ) "r9L" else "r9"
|
val register = if(dt istype DataType.UBYTE || dt istype DataType.BYTE ) "retval_interm_ub" else "retval_interm_b"
|
||||||
// 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...)
|
// 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
|
// 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 target = AssignTarget(IdentifierReference(listOf("prog8_lib", register), expr.indexer.position), null, null, expr.indexer.position)
|
||||||
val assign = Assignment(target, expr.indexer.indexExpr, expr.indexer.position)
|
val assign = Assignment(target, expr.indexer.indexExpr, expr.indexer.position)
|
||||||
modifications.add(IAstModification.InsertBefore(statement, assign, statement.definingScope))
|
modifications.add(IAstModification.InsertBefore(statement, assign, statement.parent as IStatementContainer))
|
||||||
modifications.add(IAstModification.ReplaceNode(expr.indexer.indexExpr, target.identifier!!.copy(), expr.indexer))
|
modifications.add(IAstModification.ReplaceNode(expr.indexer.indexExpr, target.identifier!!.copy(), expr.indexer))
|
||||||
return modifications
|
return modifications
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
package prog8.compiler
|
package prog8.compiler
|
||||||
|
|
||||||
import com.github.michaelbull.result.*
|
import com.github.michaelbull.result.*
|
||||||
import prog8.ast.AstToSourceCode
|
import prog8.ast.AstToSourceTextConverter
|
||||||
import prog8.ast.IBuiltinFunctions
|
import prog8.ast.IBuiltinFunctions
|
||||||
import prog8.ast.IMemSizer
|
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.AstException
|
import prog8.ast.base.AstException
|
||||||
import prog8.ast.base.Position
|
import prog8.ast.base.Position
|
||||||
@ -11,76 +10,45 @@ import prog8.ast.expressions.Expression
|
|||||||
import prog8.ast.expressions.NumericLiteralValue
|
import prog8.ast.expressions.NumericLiteralValue
|
||||||
import prog8.ast.statements.Directive
|
import prog8.ast.statements.Directive
|
||||||
import prog8.compiler.astprocessing.*
|
import prog8.compiler.astprocessing.*
|
||||||
import prog8.compiler.functions.*
|
|
||||||
import prog8.compiler.target.C64Target
|
import prog8.compiler.target.C64Target
|
||||||
import prog8.compiler.target.Cx16Target
|
import prog8.compiler.target.Cx16Target
|
||||||
import prog8.compiler.target.ICompilationTarget
|
import prog8.compiler.target.cpu6502.codegen.AsmGen
|
||||||
import prog8.compiler.target.asmGeneratorFor
|
import prog8.compilerinterface.*
|
||||||
import prog8.optimizer.*
|
import prog8.optimizer.*
|
||||||
import prog8.parser.ParseError
|
import prog8.parser.ParseError
|
||||||
import prog8.parser.ParsingFailedError
|
|
||||||
import prog8.parser.SourceCode
|
|
||||||
import prog8.parser.SourceCode.Companion.libraryFilePrefix
|
|
||||||
import java.io.File
|
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import kotlin.io.path.Path
|
import kotlin.io.path.Path
|
||||||
import kotlin.io.path.nameWithoutExtension
|
import kotlin.io.path.nameWithoutExtension
|
||||||
import kotlin.system.measureTimeMillis
|
import kotlin.system.measureTimeMillis
|
||||||
|
|
||||||
|
|
||||||
enum class OutputType {
|
|
||||||
RAW,
|
|
||||||
PRG
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class LauncherType {
|
|
||||||
BASIC,
|
|
||||||
NONE
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class ZeropageType {
|
|
||||||
BASICSAFE,
|
|
||||||
FLOATSAFE,
|
|
||||||
KERNALSAFE,
|
|
||||||
FULL,
|
|
||||||
DONTUSE
|
|
||||||
}
|
|
||||||
|
|
||||||
data class CompilationOptions(val output: OutputType,
|
|
||||||
val launcher: LauncherType,
|
|
||||||
val zeropage: ZeropageType,
|
|
||||||
val zpReserved: List<IntRange>,
|
|
||||||
val floats: Boolean,
|
|
||||||
val noSysInit: Boolean,
|
|
||||||
val compTarget: ICompilationTarget) {
|
|
||||||
var slowCodegenWarnings = false
|
|
||||||
var optimize = false
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class CompilerException(message: String?) : Exception(message)
|
|
||||||
|
|
||||||
class CompilationResult(val success: Boolean,
|
class CompilationResult(val success: Boolean,
|
||||||
val programAst: Program,
|
val program: Program,
|
||||||
val programName: String,
|
val programName: String,
|
||||||
val compTarget: ICompilationTarget,
|
val compTarget: ICompilationTarget,
|
||||||
val importedFiles: List<Path>)
|
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())
|
||||||
|
|
||||||
fun compileProgram(filepath: Path,
|
|
||||||
optimize: Boolean,
|
fun compileProgram(args: CompilerArguments): CompilationResult {
|
||||||
writeAssembly: Boolean,
|
|
||||||
slowCodegenWarnings: Boolean,
|
|
||||||
compilationTarget: String,
|
|
||||||
sourceDirs: List<String>,
|
|
||||||
outputDir: Path): CompilationResult {
|
|
||||||
var programName = ""
|
var programName = ""
|
||||||
lateinit var programAst: Program
|
lateinit var program: Program
|
||||||
lateinit var importedFiles: List<Path>
|
lateinit var importedFiles: List<Path>
|
||||||
val errors = ErrorReporter()
|
|
||||||
|
val optimizeFloatExpr = if(args.optimize) args.optimizeFloatExpressions else false
|
||||||
|
|
||||||
val compTarget =
|
val compTarget =
|
||||||
when(compilationTarget) {
|
when(args.compilationTarget) {
|
||||||
C64Target.name -> C64Target
|
C64Target.name -> C64Target
|
||||||
Cx16Target.name -> Cx16Target
|
Cx16Target.name -> Cx16Target
|
||||||
else -> throw IllegalArgumentException("invalid compilation target")
|
else -> throw IllegalArgumentException("invalid compilation target")
|
||||||
@ -89,31 +57,35 @@ fun compileProgram(filepath: Path,
|
|||||||
try {
|
try {
|
||||||
val totalTime = measureTimeMillis {
|
val totalTime = measureTimeMillis {
|
||||||
// import main module and everything it needs
|
// import main module and everything it needs
|
||||||
val (ast, compilationOptions, imported) = parseImports(filepath, errors, compTarget, sourceDirs)
|
val (programresult, compilationOptions, imported) = parseImports(args.filepath, args.errors, compTarget, args.sourceDirs)
|
||||||
compilationOptions.slowCodegenWarnings = slowCodegenWarnings
|
with(compilationOptions) {
|
||||||
compilationOptions.optimize = optimize
|
this.slowCodegenWarnings = args.slowCodegenWarnings
|
||||||
programAst = ast
|
this.optimize = args.optimize
|
||||||
|
this.optimizeFloatExpressions = optimizeFloatExpr
|
||||||
|
}
|
||||||
|
program = programresult
|
||||||
importedFiles = imported
|
importedFiles = imported
|
||||||
processAst(programAst, errors, compilationOptions)
|
processAst(program, args.errors, compilationOptions)
|
||||||
if (compilationOptions.optimize)
|
if (compilationOptions.optimize)
|
||||||
optimizeAst(
|
optimizeAst(
|
||||||
programAst,
|
program,
|
||||||
errors,
|
compilationOptions,
|
||||||
|
args.errors,
|
||||||
BuiltinFunctionsFacade(BuiltinFunctions),
|
BuiltinFunctionsFacade(BuiltinFunctions),
|
||||||
compTarget,
|
compTarget
|
||||||
compilationOptions
|
|
||||||
)
|
)
|
||||||
postprocessAst(programAst, errors, compilationOptions)
|
postprocessAst(program, args.errors, compilationOptions)
|
||||||
|
|
||||||
// printAst(programAst)
|
// println("*********** AST BEFORE ASSEMBLYGEN *************")
|
||||||
|
// printProgram(program)
|
||||||
|
|
||||||
if (writeAssembly) {
|
if (args.writeAssembly) {
|
||||||
val result = writeAssembly(programAst, errors, outputDir, compilationOptions)
|
val result = writeAssembly(program, args.errors, args.outputDir, args.quietAssembler, compilationOptions)
|
||||||
when (result) {
|
when (result) {
|
||||||
is WriteAssemblyResult.Ok -> programName = result.filename
|
is WriteAssemblyResult.Ok -> programName = result.filename
|
||||||
is WriteAssemblyResult.Fail -> {
|
is WriteAssemblyResult.Fail -> {
|
||||||
System.err.println(result.error)
|
System.err.println(result.error)
|
||||||
return CompilationResult(false, programAst, programName, compTarget, importedFiles)
|
return CompilationResult(false, program, programName, compTarget, importedFiles)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -121,14 +93,20 @@ fun compileProgram(filepath: Path,
|
|||||||
System.out.flush()
|
System.out.flush()
|
||||||
System.err.flush()
|
System.err.flush()
|
||||||
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
|
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
|
||||||
return CompilationResult(true, programAst, programName, compTarget, importedFiles)
|
return CompilationResult(true, program, programName, compTarget, importedFiles)
|
||||||
} catch (px: ParseError) {
|
} catch (px: ParseError) {
|
||||||
System.err.print("\u001b[91m") // bright red
|
System.err.print("\u001b[91m") // bright red
|
||||||
System.err.println("${px.position.toClickableStr()} parse error: ${px.message}".trim())
|
System.err.println("${px.position.toClickableStr()} parse error: ${px.message}".trim())
|
||||||
System.err.print("\u001b[0m") // reset
|
System.err.print("\u001b[0m") // reset
|
||||||
} catch (pfx: ParsingFailedError) {
|
} catch (ac: AbortCompilation) {
|
||||||
|
if(!ac.message.isNullOrEmpty()) {
|
||||||
|
System.err.print("\u001b[91m") // bright red
|
||||||
|
System.err.println(ac.message)
|
||||||
|
System.err.print("\u001b[0m") // reset
|
||||||
|
}
|
||||||
|
} catch (nsf: NoSuchFileException) {
|
||||||
System.err.print("\u001b[91m") // bright red
|
System.err.print("\u001b[91m") // bright red
|
||||||
System.err.println(pfx.message)
|
System.err.println("File not found: ${nsf.message}")
|
||||||
System.err.print("\u001b[0m") // reset
|
System.err.print("\u001b[0m") // reset
|
||||||
} catch (ax: AstException) {
|
} catch (ax: AstException) {
|
||||||
System.err.print("\u001b[91m") // bright red
|
System.err.print("\u001b[91m") // bright red
|
||||||
@ -148,7 +126,7 @@ fun compileProgram(filepath: Path,
|
|||||||
throw x
|
throw x
|
||||||
}
|
}
|
||||||
|
|
||||||
val failedProgram = Program("failed", BuiltinFunctionsFacade(BuiltinFunctions), compTarget)
|
val failedProgram = Program("failed", BuiltinFunctionsFacade(BuiltinFunctions), compTarget, compTarget)
|
||||||
return CompilationResult(false, failedProgram, programName, compTarget, emptyList())
|
return CompilationResult(false, failedProgram, programName, compTarget, emptyList())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,13 +136,13 @@ private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuilt
|
|||||||
override val names = functions.keys
|
override val names = functions.keys
|
||||||
override val purefunctionNames = functions.filter { it.value.pure }.map { it.key }.toSet()
|
override val purefunctionNames = functions.filter { it.value.pure }.map { it.key }.toSet()
|
||||||
|
|
||||||
override fun constValue(name: String, args: List<Expression>, position: Position, memsizer: IMemSizer): NumericLiteralValue? {
|
override fun constValue(name: String, args: List<Expression>, position: Position): NumericLiteralValue? {
|
||||||
val func = BuiltinFunctions[name]
|
val func = BuiltinFunctions[name]
|
||||||
if(func!=null) {
|
if(func!=null) {
|
||||||
val exprfunc = func.constExpressionFunc
|
val exprfunc = func.constExpressionFunc
|
||||||
if(exprfunc!=null) {
|
if(exprfunc!=null) {
|
||||||
return try {
|
return try {
|
||||||
exprfunc(args, position, program, memsizer)
|
exprfunc(args, position, program)
|
||||||
} catch(x: NotConstArgumentException) {
|
} catch(x: NotConstArgumentException) {
|
||||||
// const-evaluating the builtin function call failed.
|
// const-evaluating the builtin function call failed.
|
||||||
null
|
null
|
||||||
@ -188,21 +166,18 @@ fun parseImports(filepath: Path,
|
|||||||
sourceDirs: List<String>): Triple<Program, CompilationOptions, List<Path>> {
|
sourceDirs: List<String>): Triple<Program, CompilationOptions, List<Path>> {
|
||||||
println("Compiler target: ${compTarget.name}. Parsing...")
|
println("Compiler target: ${compTarget.name}. Parsing...")
|
||||||
val bf = BuiltinFunctionsFacade(BuiltinFunctions)
|
val bf = BuiltinFunctionsFacade(BuiltinFunctions)
|
||||||
val programAst = Program(filepath.nameWithoutExtension, bf, compTarget)
|
val program = Program(filepath.nameWithoutExtension, bf, compTarget, compTarget)
|
||||||
bf.program = programAst
|
bf.program = program
|
||||||
|
|
||||||
val importer = ModuleImporter(programAst, compTarget.name, errors, sourceDirs)
|
val importer = ModuleImporter(program, compTarget.name, errors, sourceDirs)
|
||||||
val importedModuleResult = importer.importModule(filepath)
|
val importedModuleResult = importer.importModule(filepath)
|
||||||
importedModuleResult.onFailure { throw it }
|
importedModuleResult.onFailure { throw it }
|
||||||
errors.report()
|
errors.report()
|
||||||
|
|
||||||
val importedFiles = programAst.modules.map { it.source }
|
val importedFiles = program.modules.map { it.source }
|
||||||
.filter { it.isFromFilesystem }
|
.filter { it.isFromFilesystem }
|
||||||
.map { Path(it.origin) }
|
.map { Path(it.origin) }
|
||||||
val compilerOptions = determineCompilationOptions(programAst, compTarget)
|
val compilerOptions = determineCompilationOptions(program, compTarget)
|
||||||
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
|
|
||||||
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
|
|
||||||
|
|
||||||
// depending on the machine and compiler options we may have to include some libraries
|
// depending on the machine and compiler options we may have to include some libraries
|
||||||
for(lib in compTarget.machine.importLibs(compilerOptions, compTarget.name))
|
for(lib in compTarget.machine.importLibs(compilerOptions, compTarget.name))
|
||||||
importer.importLibraryModule(lib)
|
importer.importLibraryModule(lib)
|
||||||
@ -210,8 +185,12 @@ fun parseImports(filepath: Path,
|
|||||||
// always import prog8_lib and math
|
// always import prog8_lib and math
|
||||||
importer.importLibraryModule("math")
|
importer.importLibraryModule("math")
|
||||||
importer.importLibraryModule("prog8_lib")
|
importer.importLibraryModule("prog8_lib")
|
||||||
|
|
||||||
|
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
|
||||||
|
errors.err("BASIC launcher requires output type PRG", program.toplevelModule.position)
|
||||||
errors.report()
|
errors.report()
|
||||||
return Triple(programAst, compilerOptions, importedFiles)
|
|
||||||
|
return Triple(program, compilerOptions, importedFiles)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget): CompilationOptions {
|
fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget): CompilationOptions {
|
||||||
@ -274,44 +253,45 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
private fun processAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
||||||
// perform initial syntax checks and processings
|
// perform initial syntax checks and processings
|
||||||
println("Processing for target ${compilerOptions.compTarget.name}...")
|
println("Processing for target ${compilerOptions.compTarget.name}...")
|
||||||
programAst.checkIdentifiers(errors, compilerOptions)
|
program.preprocessAst(program)
|
||||||
|
program.checkIdentifiers(errors, compilerOptions)
|
||||||
errors.report()
|
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
|
// TODO: turning char literals into UBYTEs via an encoding should really happen in code gen - but for that we'd need DataType.CHAR
|
||||||
// NOTE: we will then lose the opportunity to do constant-folding on any expression containing a char literal, but how often will those occur?
|
// ...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.
|
||||||
// Also they might be optimized away eventually in codegen or by the assembler even
|
// By introduction a CHAR dt, we will also lose the opportunity to do constant-folding on any expression containing a char literal.
|
||||||
programAst.charLiteralsToUByteLiterals(errors, compilerOptions.compTarget)
|
// 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()
|
errors.report()
|
||||||
programAst.constantFold(errors, compilerOptions.compTarget)
|
program.reorderStatements(errors, compilerOptions)
|
||||||
errors.report()
|
errors.report()
|
||||||
programAst.reorderStatements(errors)
|
program.addTypecasts(errors)
|
||||||
errors.report()
|
errors.report()
|
||||||
programAst.addTypecasts(errors)
|
program.variousCleanups(program, errors)
|
||||||
errors.report()
|
errors.report()
|
||||||
programAst.variousCleanups(programAst, errors)
|
program.checkValid(errors, compilerOptions)
|
||||||
errors.report()
|
errors.report()
|
||||||
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget)
|
program.checkIdentifiers(errors, compilerOptions)
|
||||||
errors.report()
|
|
||||||
programAst.checkIdentifiers(errors, compilerOptions)
|
|
||||||
errors.report()
|
errors.report()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun optimizeAst(programAst: Program, errors: IErrorReporter, functions: IBuiltinFunctions, compTarget: ICompilationTarget, options: CompilationOptions) {
|
private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, errors: IErrorReporter, functions: IBuiltinFunctions, compTarget: ICompilationTarget) {
|
||||||
// optimize the parse tree
|
// optimize the parse tree
|
||||||
println("Optimizing...")
|
println("Optimizing...")
|
||||||
|
|
||||||
val remover = UnusedCodeRemover(programAst, errors, compTarget)
|
val remover = UnusedCodeRemover(program, errors, compTarget)
|
||||||
remover.visit(programAst)
|
remover.visit(program)
|
||||||
remover.applyModifications()
|
remover.applyModifications()
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
// keep optimizing expressions and statements until no more steps remain
|
// keep optimizing expressions and statements until no more steps remain
|
||||||
val optsDone1 = programAst.simplifyExpressions()
|
val optsDone1 = program.simplifyExpressions()
|
||||||
val optsDone2 = programAst.splitBinaryExpressions(compTarget)
|
val optsDone2 = program.splitBinaryExpressions(compilerOptions, compTarget)
|
||||||
val optsDone3 = programAst.optimizeStatements(errors, functions, compTarget)
|
val optsDone3 = program.optimizeStatements(errors, functions, compTarget)
|
||||||
programAst.constantFold(errors, compTarget) // because simplified statements and expressions can result in more constants that can be folded away
|
program.constantFold(errors, compTarget) // because simplified statements and expressions can result in more constants that can be folded away
|
||||||
errors.report()
|
errors.report()
|
||||||
if (optsDone1 + optsDone2 + optsDone3 == 0)
|
if (optsDone1 + optsDone2 + optsDone3 == 0)
|
||||||
break
|
break
|
||||||
@ -320,17 +300,17 @@ private fun optimizeAst(programAst: Program, errors: IErrorReporter, functions:
|
|||||||
errors.report()
|
errors.report()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun postprocessAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
||||||
programAst.addTypecasts(errors)
|
program.addTypecasts(errors)
|
||||||
errors.report()
|
errors.report()
|
||||||
programAst.variousCleanups(programAst, errors)
|
program.variousCleanups(program, errors)
|
||||||
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget) // check if final tree is still valid
|
program.checkValid(errors, compilerOptions) // check if final tree is still valid
|
||||||
errors.report()
|
errors.report()
|
||||||
val callGraph = CallGraph(programAst)
|
val callGraph = CallGraph(program)
|
||||||
callGraph.checkRecursiveCalls(errors)
|
callGraph.checkRecursiveCalls(errors)
|
||||||
errors.report()
|
errors.report()
|
||||||
programAst.verifyFunctionArgTypes()
|
program.verifyFunctionArgTypes()
|
||||||
programAst.moveMainAndStartToFirst()
|
program.moveMainAndStartToFirst()
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class WriteAssemblyResult {
|
private sealed class WriteAssemblyResult {
|
||||||
@ -338,58 +318,56 @@ private sealed class WriteAssemblyResult {
|
|||||||
class Fail(val error: String): WriteAssemblyResult()
|
class Fail(val error: String): WriteAssemblyResult()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun writeAssembly(programAst: Program,
|
private fun writeAssembly(program: Program,
|
||||||
errors: IErrorReporter,
|
errors: IErrorReporter,
|
||||||
outputDir: Path,
|
outputDir: Path,
|
||||||
compilerOptions: CompilationOptions): WriteAssemblyResult {
|
quietAssembler: Boolean,
|
||||||
|
compilerOptions: CompilationOptions
|
||||||
|
): WriteAssemblyResult {
|
||||||
// asm generation directly from the Ast
|
// asm generation directly from the Ast
|
||||||
programAst.processAstBeforeAsmGeneration(errors, compilerOptions.compTarget)
|
program.processAstBeforeAsmGeneration(compilerOptions, errors)
|
||||||
errors.report()
|
errors.report()
|
||||||
|
|
||||||
// printAst(programAst)
|
// println("*********** AST RIGHT BEFORE ASM GENERATION *************")
|
||||||
|
// printProgram(program)
|
||||||
|
|
||||||
compilerOptions.compTarget.machine.initializeZeropage(compilerOptions)
|
compilerOptions.compTarget.machine.initializeZeropage(compilerOptions)
|
||||||
val assembly = asmGeneratorFor(compilerOptions.compTarget,
|
val assembly = asmGeneratorFor(compilerOptions.compTarget,
|
||||||
programAst,
|
program,
|
||||||
errors,
|
errors,
|
||||||
compilerOptions.compTarget.machine.zeropage,
|
compilerOptions.compTarget.machine.zeropage,
|
||||||
compilerOptions,
|
compilerOptions,
|
||||||
outputDir).compileToAssembly()
|
outputDir).compileToAssembly()
|
||||||
|
errors.report()
|
||||||
|
|
||||||
return if(assembly.valid && errors.noErrors()) {
|
return if(assembly.valid && errors.noErrors()) {
|
||||||
val assemblerReturnStatus = assembly.assemble(compilerOptions)
|
val assemblerReturnStatus = assembly.assemble(quietAssembler, compilerOptions)
|
||||||
if(assemblerReturnStatus!=0)
|
if(assemblerReturnStatus!=0)
|
||||||
WriteAssemblyResult.Fail("assembler step failed with return code $assemblerReturnStatus")
|
WriteAssemblyResult.Fail("assembler step failed with return code $assemblerReturnStatus")
|
||||||
else {
|
else {
|
||||||
errors.report()
|
|
||||||
WriteAssemblyResult.Ok(assembly.name)
|
WriteAssemblyResult.Ok(assembly.name)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
errors.report()
|
|
||||||
WriteAssemblyResult.Fail("compiler failed with errors")
|
WriteAssemblyResult.Fail("compiler failed with errors")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun printAst(programAst: Program) {
|
fun printProgram(program: Program) {
|
||||||
println()
|
println()
|
||||||
val printer = AstToSourceCode(::print, programAst)
|
val printer = AstToSourceTextConverter(::print, program)
|
||||||
printer.visit(programAst)
|
printer.visit(program)
|
||||||
println()
|
println()
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun loadAsmIncludeFile(filename: String, source: SourceCode): Result<String, NoSuchFileException> {
|
internal fun asmGeneratorFor(
|
||||||
return if (filename.startsWith(libraryFilePrefix)) {
|
compTarget: ICompilationTarget,
|
||||||
return runCatching {
|
program: Program,
|
||||||
val stream = object {}.javaClass.getResourceAsStream("/prog8lib/${filename.substring(libraryFilePrefix.length)}") // TODO handle via SourceCode
|
errors: IErrorReporter,
|
||||||
stream!!.bufferedReader().use { r -> r.readText() }
|
zp: Zeropage,
|
||||||
}.mapError { NoSuchFileException(File(filename)) }
|
options: CompilationOptions,
|
||||||
} else {
|
outputDir: Path
|
||||||
// first try in the isSameAs folder as where the containing file was imported from
|
): IAssemblyGenerator
|
||||||
val sib = Path(source.origin).resolveSibling(filename)
|
{
|
||||||
|
// at the moment we only have one code generation backend (for 6502 and 65c02)
|
||||||
if (sib.toFile().isFile)
|
return AsmGen(program, errors, zp, options, compTarget, outputDir)
|
||||||
Ok(sib.toFile().readText())
|
|
||||||
else
|
|
||||||
Ok(File(filename).readText())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,7 @@
|
|||||||
package prog8.compiler
|
package prog8.compiler
|
||||||
|
|
||||||
import prog8.ast.base.Position
|
import prog8.ast.base.Position
|
||||||
import prog8.parser.ParsingFailedError
|
import prog8.compilerinterface.IErrorReporter
|
||||||
|
|
||||||
|
|
||||||
interface IErrorReporter {
|
|
||||||
fun err(msg: String, position: Position)
|
|
||||||
fun warn(msg: String, position: Position)
|
|
||||||
fun noErrors(): Boolean
|
|
||||||
fun report()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
internal class ErrorReporter: IErrorReporter {
|
internal class ErrorReporter: IErrorReporter {
|
||||||
private enum class MessageSeverity {
|
private enum class MessageSeverity {
|
||||||
@ -33,24 +24,29 @@ internal class ErrorReporter: IErrorReporter {
|
|||||||
var numErrors = 0
|
var numErrors = 0
|
||||||
var numWarnings = 0
|
var numWarnings = 0
|
||||||
messages.forEach {
|
messages.forEach {
|
||||||
|
val printer = when(it.severity) {
|
||||||
|
MessageSeverity.WARNING -> System.out
|
||||||
|
MessageSeverity.ERROR -> System.err
|
||||||
|
}
|
||||||
when(it.severity) {
|
when(it.severity) {
|
||||||
MessageSeverity.ERROR -> System.err.print("\u001b[91m") // bright red
|
MessageSeverity.ERROR -> printer.print("\u001b[91m") // bright red
|
||||||
MessageSeverity.WARNING -> System.err.print("\u001b[93m") // bright yellow
|
MessageSeverity.WARNING -> printer.print("\u001b[93m") // bright yellow
|
||||||
}
|
}
|
||||||
val msg = "${it.position.toClickableStr()} ${it.severity} ${it.message}".trim()
|
val msg = "${it.position.toClickableStr()} ${it.severity} ${it.message}".trim()
|
||||||
if(msg !in alreadyReportedMessages) {
|
if(msg !in alreadyReportedMessages) {
|
||||||
System.err.println(msg)
|
printer.println(msg)
|
||||||
alreadyReportedMessages.add(msg)
|
alreadyReportedMessages.add(msg)
|
||||||
when(it.severity) {
|
when(it.severity) {
|
||||||
MessageSeverity.WARNING -> numWarnings++
|
MessageSeverity.WARNING -> numWarnings++
|
||||||
MessageSeverity.ERROR -> numErrors++
|
MessageSeverity.ERROR -> numErrors++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
System.err.print("\u001b[0m") // reset color
|
printer.print("\u001b[0m") // reset color
|
||||||
}
|
}
|
||||||
|
System.out.flush()
|
||||||
|
System.err.flush()
|
||||||
messages.clear()
|
messages.clear()
|
||||||
if(numErrors>0)
|
finalizeNumErrors(numErrors, numWarnings)
|
||||||
throw ParsingFailedError("There are $numErrors errors and $numWarnings warnings.")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun noErrors() = messages.none { it.severity==MessageSeverity.ERROR }
|
override fun noErrors() = messages.none { it.severity==MessageSeverity.ERROR }
|
@ -1,6 +0,0 @@
|
|||||||
package prog8.compiler
|
|
||||||
|
|
||||||
interface IStringEncoding {
|
|
||||||
fun encodeString(str: String, altEncoding: Boolean): List<Short>
|
|
||||||
fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
|
|
||||||
}
|
|
@ -7,6 +7,7 @@ import prog8.ast.base.Position
|
|||||||
import prog8.ast.base.SyntaxError
|
import prog8.ast.base.SyntaxError
|
||||||
import prog8.ast.statements.Directive
|
import prog8.ast.statements.Directive
|
||||||
import prog8.ast.statements.DirectiveArg
|
import prog8.ast.statements.DirectiveArg
|
||||||
|
import prog8.compilerinterface.IErrorReporter
|
||||||
import prog8.parser.Prog8Parser
|
import prog8.parser.Prog8Parser
|
||||||
import prog8.parser.SourceCode
|
import prog8.parser.SourceCode
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -33,7 +34,7 @@ class ModuleImporter(private val program: Program,
|
|||||||
val srcPath = when (candidates.size) {
|
val srcPath = when (candidates.size) {
|
||||||
0 -> return Err(NoSuchFileException(
|
0 -> return Err(NoSuchFileException(
|
||||||
file = filePath.normalize().toFile(),
|
file = filePath.normalize().toFile(),
|
||||||
reason = "searched in $searchIn"))
|
reason = "Searched in $searchIn"))
|
||||||
1 -> candidates.first()
|
1 -> candidates.first()
|
||||||
else -> candidates.first() // when more candiates, pick the one from the first location
|
else -> candidates.first() // when more candiates, pick the one from the first location
|
||||||
}
|
}
|
||||||
@ -50,7 +51,7 @@ class ModuleImporter(private val program: Program,
|
|||||||
|
|
||||||
fun importLibraryModule(name: String): Module? {
|
fun importLibraryModule(name: String): Module? {
|
||||||
val import = Directive("%import", listOf(
|
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))
|
), Position("<<<implicit-import>>>", 0, 0, 0))
|
||||||
return executeImportDirective(import, null)
|
return executeImportDirective(import, null)
|
||||||
}
|
}
|
||||||
@ -105,7 +106,7 @@ class ModuleImporter(private val program: Program,
|
|||||||
importModule(it)
|
importModule(it)
|
||||||
},
|
},
|
||||||
failure = {
|
failure = {
|
||||||
errors.err("no module found with name $moduleName", import.position)
|
errors.err("no module found with name $moduleName. Searched in: $sourcePaths (and internal libraries)", import.position)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -142,7 +143,6 @@ class ModuleImporter(private val program: Program,
|
|||||||
} else {
|
} else {
|
||||||
val dropCurDir = if(sourcePaths.isNotEmpty() && sourcePaths[0].name == ".") 1 else 0
|
val dropCurDir = if(sourcePaths.isNotEmpty() && sourcePaths[0].name == ".") 1 else 0
|
||||||
sourcePaths.drop(dropCurDir) +
|
sourcePaths.drop(dropCurDir) +
|
||||||
// TODO: won't work until Prog8Parser is fixed s.t. it fully initializes the modules it returns. // hm, what won't work?)
|
|
||||||
listOf(Path(importingModule.position.file).parent ?: Path("")) +
|
listOf(Path(importingModule.position.file).parent ?: Path("")) +
|
||||||
listOf(Path(".", "prog8lib"))
|
listOf(Path(".", "prog8lib"))
|
||||||
}
|
}
|
||||||
|
@ -1,95 +0,0 @@
|
|||||||
package prog8.compiler
|
|
||||||
|
|
||||||
import prog8.ast.base.*
|
|
||||||
|
|
||||||
|
|
||||||
class ZeropageDepletedError(message: String) : Exception(message)
|
|
||||||
|
|
||||||
|
|
||||||
abstract class Zeropage(protected val options: CompilationOptions) {
|
|
||||||
|
|
||||||
abstract val SCRATCH_B1 : Int // temp storage for a single byte
|
|
||||||
abstract val SCRATCH_REG : Int // temp storage for a register
|
|
||||||
abstract val SCRATCH_W1 : Int // temp storage 1 for a word $fb+$fc
|
|
||||||
abstract val SCRATCH_W2 : Int // temp storage 2 for a word $fb+$fc
|
|
||||||
|
|
||||||
|
|
||||||
private val allocations = mutableMapOf<Int, Pair<String, DataType>>()
|
|
||||||
val free = mutableListOf<Int>() // subclasses must set this to the appropriate free locations.
|
|
||||||
|
|
||||||
val allowedDatatypes = NumericDatatypes
|
|
||||||
|
|
||||||
fun availableBytes() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size
|
|
||||||
fun hasByteAvailable() = if(options.zeropage==ZeropageType.DONTUSE) false else free.isNotEmpty()
|
|
||||||
fun availableWords(): Int {
|
|
||||||
if(options.zeropage==ZeropageType.DONTUSE)
|
|
||||||
return 0
|
|
||||||
|
|
||||||
val words = free.windowed(2).filter { it[0] == it[1]-1 }
|
|
||||||
var nonOverlappingWordsCount = 0
|
|
||||||
var prevMsbLoc = -1
|
|
||||||
for(w in words) {
|
|
||||||
if(w[0]!=prevMsbLoc) {
|
|
||||||
nonOverlappingWordsCount++
|
|
||||||
prevMsbLoc = w[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nonOverlappingWordsCount
|
|
||||||
}
|
|
||||||
fun hasWordAvailable(): Boolean {
|
|
||||||
if(options.zeropage==ZeropageType.DONTUSE)
|
|
||||||
return false
|
|
||||||
|
|
||||||
return free.windowed(2).any { it[0] == it[1] - 1 }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: IErrorReporter): Int {
|
|
||||||
assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"scopedname can't be allocated twice"}
|
|
||||||
|
|
||||||
if(options.zeropage==ZeropageType.DONTUSE)
|
|
||||||
throw CompilerException("zero page usage has been disabled")
|
|
||||||
|
|
||||||
val size =
|
|
||||||
when (datatype) {
|
|
||||||
in ByteDatatypes -> 1
|
|
||||||
in WordDatatypes -> 2
|
|
||||||
DataType.FLOAT -> {
|
|
||||||
if (options.floats) {
|
|
||||||
if(position!=null)
|
|
||||||
errors.warn("allocated a large value (float) in zeropage", position)
|
|
||||||
else
|
|
||||||
errors.warn("$scopedname: allocated a large value (float) in zeropage", position ?: Position.DUMMY)
|
|
||||||
5
|
|
||||||
} else throw CompilerException("floating point option not enabled")
|
|
||||||
}
|
|
||||||
else -> throw CompilerException("cannot put datatype $datatype in zeropage")
|
|
||||||
}
|
|
||||||
|
|
||||||
if(free.size > 0) {
|
|
||||||
if(size==1) {
|
|
||||||
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1) {
|
|
||||||
if(loneByte(candidate))
|
|
||||||
return makeAllocation(candidate, 1, datatype, scopedname)
|
|
||||||
}
|
|
||||||
return makeAllocation(free[0], 1, datatype, scopedname)
|
|
||||||
}
|
|
||||||
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1) {
|
|
||||||
if (sequentialFree(candidate, size))
|
|
||||||
return makeAllocation(candidate, size, datatype, scopedname)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw ZeropageDepletedError("ERROR: no free space in ZP to allocate $size sequential bytes")
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun reserve(range: IntRange) = free.removeAll(range)
|
|
||||||
|
|
||||||
private fun makeAllocation(address: Int, size: Int, datatype: DataType, name: String?): Int {
|
|
||||||
free.removeAll(address until address+size)
|
|
||||||
allocations[address] = (name ?: "<unnamed>") to datatype
|
|
||||||
return address
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loneByte(address: Int) = address in free && address-1 !in free && address+1 !in free
|
|
||||||
private fun sequentialFree(address: Int, size: Int) = free.containsAll((address until address+size).toList())
|
|
||||||
}
|
|
@ -1,37 +1,32 @@
|
|||||||
package prog8.compiler.astprocessing
|
package prog8.compiler.astprocessing
|
||||||
|
|
||||||
import prog8.ast.INameScope
|
import prog8.ast.INameScope
|
||||||
|
import prog8.ast.IStatementContainer
|
||||||
import prog8.ast.Module
|
import prog8.ast.Module
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.ast.walk.IAstVisitor
|
import prog8.ast.walk.IAstVisitor
|
||||||
import prog8.compiler.CompilationOptions
|
import prog8.compilerinterface.*
|
||||||
import prog8.compiler.IErrorReporter
|
|
||||||
import prog8.compiler.ZeropageType
|
|
||||||
import prog8.compiler.functions.BuiltinFunctions
|
|
||||||
import prog8.compiler.functions.builtinFunctionReturnType
|
|
||||||
import prog8.compiler.target.ICompilationTarget
|
|
||||||
import java.io.CharConversionException
|
import java.io.CharConversionException
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.io.path.*
|
import kotlin.io.path.Path
|
||||||
|
|
||||||
internal class AstChecker(private val program: Program,
|
internal class AstChecker(private val program: Program,
|
||||||
private val compilerOptions: CompilationOptions,
|
|
||||||
private val errors: IErrorReporter,
|
private val errors: IErrorReporter,
|
||||||
private val compTarget: ICompilationTarget
|
private val compilerOptions: CompilationOptions
|
||||||
) : IAstVisitor {
|
) : IAstVisitor {
|
||||||
|
|
||||||
override fun visit(program: Program) {
|
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.
|
// 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 }
|
val mainBlocks = program.modules.flatMap { it.statements }.filter { b -> b is Block && b.name=="main" }.map { it as Block }
|
||||||
if(mainBlocks.size>1)
|
if(mainBlocks.size>1)
|
||||||
errors.err("more than one 'main' block", mainBlocks[0].position)
|
errors.err("more than one 'main' block", mainBlocks[0].position)
|
||||||
if(mainBlocks.isEmpty())
|
if(mainBlocks.isEmpty())
|
||||||
errors.err("there is no 'main' block", program.modules.firstOrNull()?.position ?: program.position)
|
errors.err("there is no 'main' block", program.modules.firstOrNull()?.position ?: Position.DUMMY)
|
||||||
|
|
||||||
for(mainBlock in mainBlocks) {
|
for(mainBlock in mainBlocks) {
|
||||||
val startSub = mainBlock.subScope("start") as? Subroutine
|
val startSub = mainBlock.subScope("start") as? Subroutine
|
||||||
@ -97,9 +92,9 @@ internal class AstChecker(private val program: Program,
|
|||||||
fun checkUnsignedLoopDownto0(range: RangeExpr?) {
|
fun checkUnsignedLoopDownto0(range: RangeExpr?) {
|
||||||
if(range==null)
|
if(range==null)
|
||||||
return
|
return
|
||||||
val step = range.step.constValue(program)?.number?.toDouble() ?: 1.0
|
val step = range.step.constValue(program)?.number ?: 1.0
|
||||||
if(step < -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)
|
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)
|
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)
|
||||||
}
|
}
|
||||||
@ -166,7 +161,6 @@ internal class AstChecker(private val program: Program,
|
|||||||
super.visit(forLoop)
|
super.visit(forLoop)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun visit(jump: Jump) {
|
override fun visit(jump: Jump) {
|
||||||
val ident = jump.identifier
|
val ident = jump.identifier
|
||||||
if(ident!=null) {
|
if(ident!=null) {
|
||||||
@ -175,17 +169,20 @@ internal class AstChecker(private val program: Program,
|
|||||||
if(targetStatement is BuiltinFunctionStatementPlaceholder)
|
if(targetStatement is BuiltinFunctionStatementPlaceholder)
|
||||||
errors.err("can't jump to a builtin function", jump.position)
|
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
|
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)
|
errors.err("jump address must be valid integer 0..\$ffff", jump.position)
|
||||||
super.visit(jump)
|
super.visit(jump)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(block: Block) {
|
override fun visit(block: Block) {
|
||||||
val addr = block.address
|
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)
|
errors.err("block memory address must be valid integer 0..\$ffff", block.position)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,8 +193,12 @@ internal class AstChecker(private val program: Program,
|
|||||||
is Label,
|
is Label,
|
||||||
is VarDecl,
|
is VarDecl,
|
||||||
is InlineAssembly,
|
is InlineAssembly,
|
||||||
is INameScope,
|
is IStatementContainer,
|
||||||
is NopStatement -> true
|
is NopStatement -> true
|
||||||
|
is Assignment -> {
|
||||||
|
val target = statement.target.identifier!!.targetStatement(program)
|
||||||
|
target === statement.previousSibling() // an initializer assignment is okay
|
||||||
|
}
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
@ -217,7 +218,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
super.visit(label)
|
super.visit(label)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun hasReturnOrJump(scope: INameScope): Boolean {
|
private fun hasReturnOrJump(scope: IStatementContainer): Boolean {
|
||||||
class Searcher: IAstVisitor
|
class Searcher: IAstVisitor
|
||||||
{
|
{
|
||||||
var count=0
|
var count=0
|
||||||
@ -226,7 +227,8 @@ internal class AstChecker(private val program: Program,
|
|||||||
count++
|
count++
|
||||||
}
|
}
|
||||||
override fun visit(jump: Jump) {
|
override fun visit(jump: Jump) {
|
||||||
count++
|
if(!jump.isGosub)
|
||||||
|
count++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,8 +247,8 @@ internal class AstChecker(private val program: Program,
|
|||||||
if(subroutine.name in BuiltinFunctions)
|
if(subroutine.name in BuiltinFunctions)
|
||||||
err("cannot redefine a built-in function")
|
err("cannot redefine a built-in function")
|
||||||
|
|
||||||
if(subroutine.parameters.size>16)
|
if(subroutine.parameters.size>6 && !subroutine.isAsmSubroutine)
|
||||||
err("subroutines are limited to 16 parameters")
|
errors.warn("subroutine has a large number of parameters, this slows down code execution a lot", subroutine.position)
|
||||||
|
|
||||||
val uniqueNames = subroutine.parameters.asSequence().map { it.name }.toSet()
|
val uniqueNames = subroutine.parameters.asSequence().map { it.name }.toSet()
|
||||||
if(uniqueNames.size!=subroutine.parameters.size)
|
if(uniqueNames.size!=subroutine.parameters.size)
|
||||||
@ -290,7 +292,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
else if(param.second.registerOrPair in arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
|
else if(param.second.registerOrPair in arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
|
||||||
if (param.first.type != DataType.UWORD && param.first.type != DataType.WORD
|
if (param.first.type != DataType.UWORD && param.first.type != DataType.WORD
|
||||||
&& param.first.type != DataType.STR && param.first.type !in ArrayDatatypes && param.first.type != DataType.FLOAT)
|
&& param.first.type != DataType.STR && param.first.type !in ArrayDatatypes && param.first.type != DataType.FLOAT)
|
||||||
err("parameter '${param.first.name}' should be (u)word/address")
|
err("parameter '${param.first.name}' should be (u)word (an address) or str")
|
||||||
}
|
}
|
||||||
else if(param.second.statusflag!=null) {
|
else if(param.second.statusflag!=null) {
|
||||||
if (param.first.type != DataType.UBYTE)
|
if (param.first.type != DataType.UBYTE)
|
||||||
@ -382,10 +384,12 @@ internal class AstChecker(private val program: Program,
|
|||||||
err("can only use Carry as status flag parameter")
|
err("can only use Carry as status flag parameter")
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Pass-by-reference datatypes can not occur as parameters to a subroutine directly
|
// 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).
|
// Instead, their reference (address) should be passed (as an UWORD).
|
||||||
if(subroutine.parameters.any{it.type in PassByReferenceDatatypes }) {
|
for(p in subroutine.parameters) {
|
||||||
err("Pass-by-reference types (str, array) cannot occur as a parameter type directly. Instead, use an uword to receive their address, or access the variable from the outer scope directly.")
|
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.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -414,11 +418,10 @@ internal class AstChecker(private val program: Program,
|
|||||||
val stmt = (assignment.value as FunctionCall).target.targetStatement(program)
|
val stmt = (assignment.value as FunctionCall).target.targetStatement(program)
|
||||||
if (stmt is Subroutine) {
|
if (stmt is Subroutine) {
|
||||||
val idt = assignment.target.inferType(program)
|
val idt = assignment.target.inferType(program)
|
||||||
if(!idt.isKnown) {
|
if(!idt.isKnown)
|
||||||
errors.err("return type mismatch", assignment.value.position)
|
throw FatalAstException("assignment target invalid dt")
|
||||||
}
|
|
||||||
if(stmt.returntypes.isEmpty() || (stmt.returntypes.size == 1 && stmt.returntypes.single() isNotAssignableTo idt.getOr(DataType.BYTE))) {
|
if(stmt.returntypes.isEmpty() || (stmt.returntypes.size == 1 && stmt.returntypes.single() isNotAssignableTo idt.getOr(DataType.BYTE))) {
|
||||||
errors.err("return type mismatch", assignment.value.position)
|
errors.err("return type mismatch: ${stmt.returntypes.single()} expected $idt", assignment.value.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -428,8 +431,15 @@ internal class AstChecker(private val program: Program,
|
|||||||
if(valueDt.isKnown && !(valueDt isAssignableTo targetDt)) {
|
if(valueDt.isKnown && !(valueDt isAssignableTo targetDt)) {
|
||||||
if(targetDt.isIterable)
|
if(targetDt.isIterable)
|
||||||
errors.err("cannot assign value to string or array", assignment.value.position)
|
errors.err("cannot assign value to string or array", assignment.value.position)
|
||||||
else if(!(valueDt istype DataType.STR && targetDt istype DataType.UWORD))
|
else if(!(valueDt istype DataType.STR && targetDt istype DataType.UWORD)) {
|
||||||
errors.err("type of value doesn't match target", assignment.value.position)
|
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) {
|
if(assignment.value is TypecastExpression) {
|
||||||
@ -453,7 +463,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
val targetIdentifier = assignTarget.identifier
|
val targetIdentifier = assignTarget.identifier
|
||||||
if (targetIdentifier != null) {
|
if (targetIdentifier != null) {
|
||||||
val targetName = targetIdentifier.nameInSource
|
val targetName = targetIdentifier.nameInSource
|
||||||
when (val targetSymbol = program.namespace.lookup(targetName, assignment)) {
|
when (val targetSymbol = assignment.definingScope.lookup(targetName)) {
|
||||||
null -> {
|
null -> {
|
||||||
errors.err("undefined symbol: ${targetIdentifier.nameInSource.joinToString(".")}", targetIdentifier.position)
|
errors.err("undefined symbol: ${targetIdentifier.nameInSource.joinToString(".")}", targetIdentifier.position)
|
||||||
return
|
return
|
||||||
@ -489,7 +499,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
if (assignment.value !is FunctionCall)
|
if (assignment.value !is FunctionCall)
|
||||||
errors.err("assignment value is invalid or has no proper datatype", assignment.value.position)
|
errors.err("assignment value is invalid or has no proper datatype", assignment.value.position)
|
||||||
} else {
|
} else {
|
||||||
checkAssignmentCompatible(targetDatatype.getOr(DataType.BYTE), assignTarget,
|
checkAssignmentCompatible(targetDatatype.getOr(DataType.BYTE),
|
||||||
sourceDatatype.getOr(DataType.BYTE), assignment.value, assignment.position)
|
sourceDatatype.getOr(DataType.BYTE), assignment.value, assignment.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -505,10 +515,10 @@ internal class AstChecker(private val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(decl: VarDecl) {
|
override fun visit(decl: VarDecl) {
|
||||||
fun err(msg: String, position: Position?=null) = errors.err(msg, position ?: decl.position)
|
fun err(msg: String) = errors.err(msg, decl.position)
|
||||||
|
|
||||||
// the initializer value can't refer to the variable itself (recursive definition)
|
// 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")
|
err("recursive var declaration")
|
||||||
|
|
||||||
// CONST can only occur on simple types (byte, word, float)
|
// CONST can only occur on simple types (byte, word, float)
|
||||||
@ -586,10 +596,10 @@ internal class AstChecker(private val program: Program,
|
|||||||
val numvalue = decl.value as? NumericLiteralValue
|
val numvalue = decl.value as? NumericLiteralValue
|
||||||
if(numvalue!=null) {
|
if(numvalue!=null) {
|
||||||
if (numvalue.type !in IntegerDatatypes || numvalue.number.toInt() < 0 || numvalue.number.toInt() > 65535) {
|
if (numvalue.type !in IntegerDatatypes || numvalue.number.toInt() < 0 || numvalue.number.toInt() > 65535) {
|
||||||
err("memory address must be valid integer 0..\$ffff", decl.value?.position)
|
err("memory address must be valid integer 0..\$ffff")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err("value of memory mapped variable can only be a fixed number, perhaps you meant to use an address pointer type instead?", decl.value?.position)
|
err("value of memory mapped variable can only be a fixed number, perhaps you meant to use an address pointer type instead?")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -597,7 +607,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
val declValue = decl.value
|
val declValue = decl.value
|
||||||
if(declValue!=null && decl.type==VarDeclType.VAR) {
|
if(declValue!=null && decl.type==VarDeclType.VAR) {
|
||||||
if (declValue.inferType(program) isnot decl.datatype) {
|
if (declValue.inferType(program) isnot decl.datatype) {
|
||||||
err("initialisation value has incompatible type (${declValue.inferType(program)}) for the variable (${decl.datatype})", declValue.position)
|
err("initialisation value has incompatible type (${declValue.inferType(program)}) for the variable (${decl.datatype})")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -626,10 +636,13 @@ internal class AstChecker(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// string assignment is not supported in a vard
|
|
||||||
if(decl.datatype==DataType.STR) {
|
if(decl.datatype==DataType.STR) {
|
||||||
if(decl.value==null)
|
if(decl.value==null) {
|
||||||
err("string var must be initialized with a string literal")
|
// complain about uninitialized str, but only if it's a regular variable
|
||||||
|
val parameter = (decl.parent as? Subroutine)?.parameters?.singleOrNull{ it.name==decl.name }
|
||||||
|
if(parameter==null)
|
||||||
|
err("string var must be initialized with a string literal")
|
||||||
|
}
|
||||||
else if (decl.type==VarDeclType.VAR && decl.value !is StringLiteralValue)
|
else if (decl.type==VarDeclType.VAR && decl.value !is StringLiteralValue)
|
||||||
err("string var can only be initialized with a string literal")
|
err("string var can only be initialized with a string literal")
|
||||||
}
|
}
|
||||||
@ -768,7 +781,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
|
|
||||||
override fun visit(char: CharLiteral) {
|
override fun visit(char: CharLiteral) {
|
||||||
try { // just *try* if it can be encoded, don't actually do it
|
try { // just *try* if it can be encoded, don't actually do it
|
||||||
compTarget.encodeString(char.value.toString(), char.altEncoding)
|
compilerOptions.compTarget.encodeString(char.value.toString(), char.altEncoding)
|
||||||
} catch (cx: CharConversionException) {
|
} catch (cx: CharConversionException) {
|
||||||
errors.err(cx.message ?: "can't encode character", char.position)
|
errors.err(cx.message ?: "can't encode character", char.position)
|
||||||
}
|
}
|
||||||
@ -780,7 +793,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
checkValueTypeAndRangeString(DataType.STR, string)
|
checkValueTypeAndRangeString(DataType.STR, string)
|
||||||
|
|
||||||
try { // just *try* if it can be encoded, don't actually do it
|
try { // just *try* if it can be encoded, don't actually do it
|
||||||
compTarget.encodeString(string.value, string.altEncoding)
|
compilerOptions.compTarget.encodeString(string.value, string.altEncoding)
|
||||||
} catch (cx: CharConversionException) {
|
} catch (cx: CharConversionException) {
|
||||||
errors.err(cx.message ?: "can't encode string", string.position)
|
errors.err(cx.message ?: "can't encode string", string.position)
|
||||||
}
|
}
|
||||||
@ -789,11 +802,10 @@ internal class AstChecker(private val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(expr: PrefixExpression) {
|
override fun visit(expr: PrefixExpression) {
|
||||||
val idt = expr.inferType(program)
|
val dt = expr.expression.inferType(program).getOr(DataType.UNDEFINED)
|
||||||
if(!idt.isKnown)
|
if(dt==DataType.UNDEFINED)
|
||||||
return // any error should be reported elsewhere
|
return // any error should be reported elsewhere
|
||||||
|
|
||||||
val dt = idt.getOr(DataType.UNDEFINED)
|
|
||||||
if(expr.operator=="-") {
|
if(expr.operator=="-") {
|
||||||
if (dt != DataType.BYTE && dt != DataType.WORD && dt != DataType.FLOAT) {
|
if (dt != DataType.BYTE && dt != DataType.WORD && dt != DataType.FLOAT) {
|
||||||
errors.err("can only take negative of a signed number type", expr.position)
|
errors.err("can only take negative of a signed number type", expr.position)
|
||||||
@ -824,7 +836,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
when(expr.operator){
|
when(expr.operator){
|
||||||
"/", "%" -> {
|
"/", "%" -> {
|
||||||
val constvalRight = expr.right.constValue(program)
|
val constvalRight = expr.right.constValue(program)
|
||||||
val divisor = constvalRight?.number?.toDouble()
|
val divisor = constvalRight?.number
|
||||||
if(divisor==0.0)
|
if(divisor==0.0)
|
||||||
errors.err("division by zero", expr.right.position)
|
errors.err("division by zero", expr.right.position)
|
||||||
if(expr.operator=="%") {
|
if(expr.operator=="%") {
|
||||||
@ -964,36 +976,43 @@ internal class AstChecker(private val program: Program,
|
|||||||
|
|
||||||
override fun visit(functionCallStatement: FunctionCallStatement) {
|
override fun visit(functionCallStatement: FunctionCallStatement) {
|
||||||
val targetStatement = checkFunctionOrLabelExists(functionCallStatement.target, functionCallStatement)
|
val targetStatement = checkFunctionOrLabelExists(functionCallStatement.target, functionCallStatement)
|
||||||
if(targetStatement!=null)
|
if(targetStatement!=null) {
|
||||||
checkFunctionCall(targetStatement, functionCallStatement.args, functionCallStatement.position)
|
checkFunctionCall(targetStatement, functionCallStatement.args, functionCallStatement.position)
|
||||||
if (!functionCallStatement.void) {
|
checkUnusedReturnValues(functionCallStatement, targetStatement, program, errors)
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(functionCallStatement.target.nameInSource.last() == "sort") {
|
val funcName = functionCallStatement.target.nameInSource
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(functionCallStatement.target.nameInSource.last() in arrayOf("rol", "ror", "rol2", "ror2", "swap", "sort", "reverse")) {
|
if(funcName.size==1) {
|
||||||
// in-place modification, can't be done on literals
|
// check some builtin function calls
|
||||||
if(functionCallStatement.args.any { it !is IdentifierReference && it !is ArrayIndexedExpression && it !is DirectMemoryRead }) {
|
if(funcName[0] == "sort") {
|
||||||
errors.err("invalid argument to a in-place modifying function", functionCallStatement.args.first().position)
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val error =
|
val error =
|
||||||
@ -1051,7 +1070,12 @@ internal class AstChecker(private val program: Program,
|
|||||||
ident = fcall.args[0] as? IdentifierReference
|
ident = fcall.args[0] as? IdentifierReference
|
||||||
}
|
}
|
||||||
if(ident!=null && ident.nameInSource[0] == "cx16" && ident.nameInSource[1].startsWith("r")) {
|
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 }
|
val same = params.filter { it.value.registerOrPair==reg }
|
||||||
for(s in same) {
|
for(s in same) {
|
||||||
if(s.index!=arg.index) {
|
if(s.index!=arg.index) {
|
||||||
@ -1067,7 +1091,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
override fun visit(postIncrDecr: PostIncrDecr) {
|
override fun visit(postIncrDecr: PostIncrDecr) {
|
||||||
if(postIncrDecr.target.identifier != null) {
|
if(postIncrDecr.target.identifier != null) {
|
||||||
val targetName = postIncrDecr.target.identifier!!.nameInSource
|
val targetName = postIncrDecr.target.identifier!!.nameInSource
|
||||||
val target = program.namespace.lookup(targetName, postIncrDecr)
|
val target = postIncrDecr.definingScope.lookup(targetName)
|
||||||
if(target==null) {
|
if(target==null) {
|
||||||
val symbol = postIncrDecr.target.identifier!!
|
val symbol = postIncrDecr.target.identifier!!
|
||||||
errors.err("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position)
|
errors.err("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position)
|
||||||
@ -1254,7 +1278,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
|
|
||||||
// check if the floating point values are all within range
|
// check if the floating point values are all within range
|
||||||
val doubles = value.value.map {it.constValue(program)?.number!!.toDouble()}.toDoubleArray()
|
val doubles = value.value.map {it.constValue(program)?.number!!.toDouble()}.toDoubleArray()
|
||||||
if(doubles.any { it < compTarget.machine.FLOAT_MAX_NEGATIVE || it > compTarget.machine.FLOAT_MAX_POSITIVE })
|
if(doubles.any { it < compilerOptions.compTarget.machine.FLOAT_MAX_NEGATIVE || it > compilerOptions.compTarget.machine.FLOAT_MAX_POSITIVE })
|
||||||
return err("floating point value overflow")
|
return err("floating point value overflow")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -1271,7 +1295,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
}
|
}
|
||||||
when (targetDt) {
|
when (targetDt) {
|
||||||
DataType.FLOAT -> {
|
DataType.FLOAT -> {
|
||||||
val number=value.number.toDouble()
|
val number=value.number
|
||||||
if (number > 1.7014118345e+38 || number < -1.7014118345e+38)
|
if (number > 1.7014118345e+38 || number < -1.7014118345e+38)
|
||||||
return err("value '$number' out of range for MFLPT format")
|
return err("value '$number' out of range for MFLPT format")
|
||||||
}
|
}
|
||||||
@ -1347,7 +1371,6 @@ internal class AstChecker(private val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun checkAssignmentCompatible(targetDatatype: DataType,
|
private fun checkAssignmentCompatible(targetDatatype: DataType,
|
||||||
target: AssignTarget,
|
|
||||||
sourceDatatype: DataType,
|
sourceDatatype: DataType,
|
||||||
sourceValue: Expression,
|
sourceValue: Expression,
|
||||||
position: Position) : Boolean {
|
position: Position) : Boolean {
|
||||||
@ -1390,3 +1413,19 @@ internal class AstChecker(private val program: Program,
|
|||||||
return false
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -3,82 +3,34 @@ package prog8.compiler.astprocessing
|
|||||||
import prog8.ast.Node
|
import prog8.ast.Node
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.DataType
|
import prog8.ast.base.DataType
|
||||||
import prog8.ast.base.VarDeclType
|
import prog8.ast.expressions.CharLiteral
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.IdentifierReference
|
||||||
import prog8.ast.statements.AssignTarget
|
import prog8.ast.expressions.NumericLiteralValue
|
||||||
import prog8.ast.statements.Directive
|
import prog8.ast.statements.Directive
|
||||||
import prog8.ast.walk.AstWalker
|
import prog8.ast.walk.AstWalker
|
||||||
import prog8.ast.walk.IAstModification
|
import prog8.ast.walk.IAstModification
|
||||||
import prog8.compiler.BeforeAsmGenerationAstChanger
|
import prog8.compiler.BeforeAsmGenerationAstChanger
|
||||||
import prog8.compiler.CompilationOptions
|
import prog8.compilerinterface.CompilationOptions
|
||||||
import prog8.compiler.IErrorReporter
|
import prog8.compilerinterface.ICompilationTarget
|
||||||
import prog8.compiler.IStringEncoding
|
import prog8.compilerinterface.IErrorReporter
|
||||||
import prog8.compiler.target.ICompilationTarget
|
import prog8.compilerinterface.IStringEncoding
|
||||||
import prog8.compiler.target.IMachineDefinition
|
|
||||||
import kotlin.math.abs
|
|
||||||
|
|
||||||
|
|
||||||
fun RangeExpr.size(encoding: IStringEncoding): Int? {
|
internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
||||||
val fromLv = (from as? NumericLiteralValue)
|
val checker = AstChecker(this, errors, compilerOptions)
|
||||||
val toLv = (to as? NumericLiteralValue)
|
|
||||||
if(fromLv==null || toLv==null)
|
|
||||||
return null
|
|
||||||
return toConstantIntegerRange(encoding)?.count()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun RangeExpr.toConstantIntegerRange(encoding: IStringEncoding): IntProgression? {
|
|
||||||
val fromVal: Int
|
|
||||||
val toVal: Int
|
|
||||||
val fromString = from as? StringLiteralValue
|
|
||||||
val toString = to as? StringLiteralValue
|
|
||||||
if(fromString!=null && toString!=null ) {
|
|
||||||
// string range -> int range over character values
|
|
||||||
fromVal = encoding.encodeString(fromString.value, fromString.altEncoding)[0].toInt()
|
|
||||||
toVal = encoding.encodeString(toString.value, fromString.altEncoding)[0].toInt()
|
|
||||||
} else {
|
|
||||||
val fromLv = from as? NumericLiteralValue
|
|
||||||
val toLv = to as? NumericLiteralValue
|
|
||||||
if(fromLv==null || toLv==null)
|
|
||||||
return null // non-constant range
|
|
||||||
// integer range
|
|
||||||
fromVal = fromLv.number.toInt()
|
|
||||||
toVal = toLv.number.toInt()
|
|
||||||
}
|
|
||||||
val stepVal = (step as? NumericLiteralValue)?.number?.toInt() ?: 1
|
|
||||||
return makeRange(fromVal, toVal, stepVal)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression {
|
|
||||||
return when {
|
|
||||||
fromVal <= toVal -> when {
|
|
||||||
stepVal <= 0 -> IntRange.EMPTY
|
|
||||||
stepVal == 1 -> fromVal..toVal
|
|
||||||
else -> fromVal..toVal step stepVal
|
|
||||||
}
|
|
||||||
else -> when {
|
|
||||||
stepVal >= 0 -> IntRange.EMPTY
|
|
||||||
stepVal == -1 -> fromVal downTo toVal
|
|
||||||
else -> fromVal downTo toVal step abs(stepVal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: IErrorReporter, compTarget: ICompilationTarget) {
|
|
||||||
val checker = AstChecker(this, compilerOptions, errors, compTarget)
|
|
||||||
checker.visit(this)
|
checker.visit(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun Program.processAstBeforeAsmGeneration(errors: IErrorReporter, compTarget: ICompilationTarget) {
|
internal fun Program.processAstBeforeAsmGeneration(compilerOptions: CompilationOptions, errors: IErrorReporter) {
|
||||||
val fixer = BeforeAsmGenerationAstChanger(this, errors, compTarget)
|
val fixer = BeforeAsmGenerationAstChanger(this, compilerOptions, errors)
|
||||||
fixer.visit(this)
|
fixer.visit(this)
|
||||||
while(errors.noErrors() && fixer.applyModifications()>0) {
|
while(errors.noErrors() && fixer.applyModifications()>0) {
|
||||||
fixer.visit(this)
|
fixer.visit(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun Program.reorderStatements(errors: IErrorReporter) {
|
internal fun Program.reorderStatements(errors: IErrorReporter, options: CompilationOptions) {
|
||||||
val reorder = StatementReorderer(this, errors)
|
val reorder = StatementReorderer(this, errors, options)
|
||||||
reorder.visit(this)
|
reorder.visit(this)
|
||||||
if(errors.noErrors()) {
|
if(errors.noErrors()) {
|
||||||
reorder.applyModifications()
|
reorder.applyModifications()
|
||||||
@ -88,12 +40,12 @@ internal fun Program.reorderStatements(errors: IErrorReporter) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun Program.charLiteralsToUByteLiterals(errors: IErrorReporter, enc: IStringEncoding) {
|
internal fun Program.charLiteralsToUByteLiterals(enc: IStringEncoding) {
|
||||||
val walker = object : AstWalker() {
|
val walker = object : AstWalker() {
|
||||||
override fun after(char: CharLiteral, parent: Node): Iterable<IAstModification> {
|
override fun after(char: CharLiteral, parent: Node): Iterable<IAstModification> {
|
||||||
return listOf(IAstModification.ReplaceNode(
|
return listOf(IAstModification.ReplaceNode(
|
||||||
char,
|
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
|
parent
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@ -113,9 +65,17 @@ internal fun Program.verifyFunctionArgTypes() {
|
|||||||
fixer.visit(this)
|
fixer.visit(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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, options: CompilationOptions) {
|
||||||
|
|
||||||
val checker2 = AstIdentifiersChecker(this, errors, options.compTarget)
|
val checker2 = AstIdentifiersChecker(errors, options.compTarget)
|
||||||
checker2.visit(this)
|
checker2.visit(this)
|
||||||
|
|
||||||
if(errors.noErrors()) {
|
if(errors.noErrors()) {
|
||||||
@ -135,7 +95,6 @@ internal fun Program.variousCleanups(program: Program, errors: IErrorReporter) {
|
|||||||
process.applyModifications()
|
process.applyModifications()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
internal fun Program.moveMainAndStartToFirst() {
|
internal fun Program.moveMainAndStartToFirst() {
|
||||||
// the module containing the program entrypoint is moved to the first in the sequence.
|
// the module containing the program entrypoint is moved to the first in the sequence.
|
||||||
// the "main" block containing the entrypoint is moved to the top in there,
|
// the "main" block containing the entrypoint is moved to the top in there,
|
||||||
@ -166,46 +125,10 @@ internal fun Program.moveMainAndStartToFirst() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun AssignTarget.isInRegularRAMof(machine: IMachineDefinition): Boolean {
|
internal fun IdentifierReference.isSubroutineParameter(program: Program): Boolean {
|
||||||
val memAddr = memoryAddress
|
val vardecl = this.targetVarDecl(program)
|
||||||
val arrayIdx = arrayindexed
|
if(vardecl!=null && vardecl.autogeneratedDontRemove) {
|
||||||
val ident = identifier
|
return vardecl.definingSubroutine?.parameters?.any { it.name==vardecl.name } == true
|
||||||
when {
|
|
||||||
memAddr != null -> {
|
|
||||||
return when (memAddr.addressExpression) {
|
|
||||||
is NumericLiteralValue -> {
|
|
||||||
machine.isRegularRAMaddress((memAddr.addressExpression as NumericLiteralValue).number.toInt())
|
|
||||||
}
|
|
||||||
is IdentifierReference -> {
|
|
||||||
val program = definingModule.program
|
|
||||||
val decl = (memAddr.addressExpression as IdentifierReference).targetVarDecl(program)
|
|
||||||
if ((decl?.type == VarDeclType.VAR || decl?.type == VarDeclType.CONST) && decl.value is NumericLiteralValue)
|
|
||||||
machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
|
|
||||||
else
|
|
||||||
false
|
|
||||||
}
|
|
||||||
else -> false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
arrayIdx != null -> {
|
|
||||||
val program = definingModule.program
|
|
||||||
val targetStmt = arrayIdx.arrayvar.targetVarDecl(program)
|
|
||||||
return if (targetStmt?.type == VarDeclType.MEMORY) {
|
|
||||||
val addr = targetStmt.value as? NumericLiteralValue
|
|
||||||
if (addr != null)
|
|
||||||
machine.isRegularRAMaddress(addr.number.toInt())
|
|
||||||
else
|
|
||||||
false
|
|
||||||
} else true
|
|
||||||
}
|
|
||||||
ident != null -> {
|
|
||||||
val program = definingModule.program
|
|
||||||
val decl = ident.targetVarDecl(program)!!
|
|
||||||
return if (decl.type == VarDeclType.MEMORY && decl.value is NumericLiteralValue)
|
|
||||||
machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
|
|
||||||
else
|
|
||||||
true
|
|
||||||
}
|
|
||||||
else -> return true
|
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
package prog8.compiler.astprocessing
|
package prog8.compiler.astprocessing
|
||||||
|
|
||||||
import prog8.ast.Program
|
|
||||||
import prog8.ast.base.Position
|
import prog8.ast.base.Position
|
||||||
import prog8.ast.expressions.StringLiteralValue
|
import prog8.ast.expressions.StringLiteralValue
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.ast.walk.IAstVisitor
|
import prog8.ast.walk.IAstVisitor
|
||||||
import prog8.compiler.IErrorReporter
|
import prog8.compilerinterface.BuiltinFunctions
|
||||||
import prog8.compiler.functions.BuiltinFunctions
|
import prog8.compilerinterface.ICompilationTarget
|
||||||
import prog8.compiler.target.ICompilationTarget
|
import prog8.compilerinterface.IErrorReporter
|
||||||
|
|
||||||
internal class AstIdentifiersChecker(private val program: Program, private val errors: IErrorReporter, private val compTarget: ICompilationTarget) : IAstVisitor {
|
internal class AstIdentifiersChecker(private val errors: IErrorReporter, private val compTarget: ICompilationTarget) : IAstVisitor {
|
||||||
private var blocks = mutableMapOf<String, Block>()
|
private var blocks = mutableMapOf<String, Block>()
|
||||||
|
|
||||||
private fun nameError(name: String, position: Position, existing: Statement) {
|
private fun nameError(name: String, position: Position, existing: Statement) {
|
||||||
@ -42,7 +41,7 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
|
|||||||
if(decl.name in compTarget.machine.opcodeNames)
|
if(decl.name in compTarget.machine.opcodeNames)
|
||||||
errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position)
|
errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position)
|
||||||
|
|
||||||
val existing = program.namespace.lookup(listOf(decl.name), decl)
|
val existing = decl.definingScope.lookup(listOf(decl.name))
|
||||||
if (existing != null && existing !== decl)
|
if (existing != null && existing !== decl)
|
||||||
nameError(decl.name, decl.position, existing)
|
nameError(decl.name, decl.position, existing)
|
||||||
|
|
||||||
@ -65,22 +64,19 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
|
|||||||
// if (subroutine.parameters.any { it.name in BuiltinFunctions })
|
// if (subroutine.parameters.any { it.name in BuiltinFunctions })
|
||||||
// checkResult.add(NameError("builtin function name cannot be used as parameter", subroutine.position))
|
// checkResult.add(NameError("builtin function name cannot be used as parameter", subroutine.position))
|
||||||
|
|
||||||
val existing = program.namespace.lookup(listOf(subroutine.name), subroutine)
|
val existing = subroutine.lookup(listOf(subroutine.name))
|
||||||
if (existing != null && existing !== subroutine)
|
if (existing != null && existing !== subroutine)
|
||||||
nameError(subroutine.name, subroutine.position, existing)
|
nameError(subroutine.name, subroutine.position, existing)
|
||||||
|
|
||||||
// check that there are no local variables, labels, or other subs that redefine the subroutine's parameters. Blocks are okay.
|
// check that there are no local symbols (variables, labels, subs) that redefine the subroutine's parameters.
|
||||||
val symbolsInSub = subroutine.allDefinedSymbols
|
val symbolsInSub = subroutine.allDefinedSymbols
|
||||||
val namesInSub = symbolsInSub.map{ it.first }.toSet()
|
val namesInSub = symbolsInSub.map{ it.first }.toSet()
|
||||||
val paramNames = subroutine.parameters.map { it.name }.toSet()
|
val paramNames = subroutine.parameters.map { it.name }.toSet()
|
||||||
val paramsToCheck = paramNames.intersect(namesInSub)
|
val paramsToCheck = paramNames.intersect(namesInSub)
|
||||||
for(name in paramsToCheck) {
|
for(name in paramsToCheck) {
|
||||||
val labelOrVar = subroutine.getLabelOrVariable(name)
|
val symbol = subroutine.searchSymbol(name)
|
||||||
if(labelOrVar!=null && labelOrVar.position != subroutine.position)
|
if(symbol!=null && (symbol as? VarDecl)?.subroutineParameter==null)
|
||||||
nameError(name, labelOrVar.position, subroutine)
|
nameError(name, symbol.position, subroutine)
|
||||||
val sub = subroutine.statements.firstOrNull { it is Subroutine && it.name==name}
|
|
||||||
if(sub!=null)
|
|
||||||
nameError(name, subroutine.position, sub)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) {
|
if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) {
|
||||||
|
85
compiler/src/prog8/compiler/astprocessing/AstPreprocessor.kt
Normal file
85
compiler/src/prog8/compiler/astprocessing/AstPreprocessor.kt
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
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
|
||||||
|
import prog8.ast.statements.VarDecl
|
||||||
|
import prog8.ast.walk.AstWalker
|
||||||
|
import prog8.ast.walk.IAstModification
|
||||||
|
|
||||||
|
|
||||||
|
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> {
|
||||||
|
|
||||||
|
// move vardecls in Anonymous scope up to the containing subroutine
|
||||||
|
// and add initialization assignment in its place if needed
|
||||||
|
val vars = scope.statements.filterIsInstance<VarDecl>()
|
||||||
|
val parentscope = scope.definingScope
|
||||||
|
if(vars.any() && parentscope !== parent) {
|
||||||
|
val movements = mutableListOf<IAstModification>()
|
||||||
|
val replacements = mutableListOf<IAstModification>()
|
||||||
|
|
||||||
|
for(decl in vars) {
|
||||||
|
if(decl.type != VarDeclType.VAR) {
|
||||||
|
movements.add(IAstModification.InsertFirst(decl, parentscope))
|
||||||
|
replacements.add(IAstModification.Remove(decl, scope))
|
||||||
|
} else {
|
||||||
|
if(decl.value!=null && decl.datatype in NumericDatatypes) {
|
||||||
|
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
|
||||||
|
val assign = Assignment(target, decl.value!!, decl.position)
|
||||||
|
replacements.add(IAstModification.ReplaceNode(decl, assign, scope))
|
||||||
|
decl.value = null
|
||||||
|
decl.allowInitializeWithZero = false
|
||||||
|
} else {
|
||||||
|
replacements.add(IAstModification.Remove(decl, scope))
|
||||||
|
}
|
||||||
|
movements.add(IAstModification.InsertFirst(decl, parentscope))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return movements + replacements
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
}
|
@ -20,13 +20,13 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
|
|||||||
val symbolsInSub = subroutine.allDefinedSymbols
|
val symbolsInSub = subroutine.allDefinedSymbols
|
||||||
val namesInSub = symbolsInSub.map{ it.first }.toSet()
|
val namesInSub = symbolsInSub.map{ it.first }.toSet()
|
||||||
if(subroutine.asmAddress==null) {
|
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()
|
val vars = subroutine.statements.filterIsInstance<VarDecl>().map { it.name }.toSet()
|
||||||
if(!vars.containsAll(subroutine.parameters.map{it.name})) {
|
if(!vars.containsAll(subroutine.parameters.map{it.name})) {
|
||||||
return subroutine.parameters
|
return subroutine.parameters
|
||||||
.filter { it.name !in namesInSub }
|
.filter { it.name !in namesInSub }
|
||||||
.map {
|
.map {
|
||||||
val vardecl = ParameterVarDecl(it.name, it.type, subroutine.position)
|
val vardecl = VarDecl.fromParameter(it)
|
||||||
IAstModification.InsertFirst(vardecl, subroutine)
|
IAstModification.InsertFirst(vardecl, subroutine)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -67,7 +67,7 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||||
return replacePointerVarIndexWithMemread(program, arrayIndexedExpression, parent)
|
return replacePointerVarIndexWithMemreadOrMemwrite(program, arrayIndexedExpression, parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun concatString(expr: BinaryExpression): StringLiteralValue? {
|
private fun concatString(expr: BinaryExpression): StringLiteralValue? {
|
||||||
@ -99,12 +99,12 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
internal fun replacePointerVarIndexWithMemread(program: Program, arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
internal fun replacePointerVarIndexWithMemreadOrMemwrite(program: Program, arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||||
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program)
|
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program)
|
||||||
if(arrayVar!=null && arrayVar.datatype == DataType.UWORD) {
|
if(arrayVar!=null && arrayVar.datatype == DataType.UWORD) {
|
||||||
// rewrite pointervar[index] into @(pointervar+index)
|
// rewrite pointervar[index] into @(pointervar+index)
|
||||||
val indexer = arrayIndexedExpression.indexer
|
val indexer = arrayIndexedExpression.indexer
|
||||||
val add = BinaryExpression(arrayIndexedExpression.arrayvar, "+", indexer.indexExpr, arrayIndexedExpression.position)
|
val add = BinaryExpression(arrayIndexedExpression.arrayvar.copy(), "+", indexer.indexExpr, arrayIndexedExpression.position)
|
||||||
return if(parent is AssignTarget) {
|
return if(parent is AssignTarget) {
|
||||||
// we're part of the target of an assignment, we have to actually change the assign target itself
|
// we're part of the target of an assignment, we have to actually change the assign target itself
|
||||||
val memwrite = DirectMemoryWrite(add, arrayIndexedExpression.position)
|
val memwrite = DirectMemoryWrite(add, arrayIndexedExpression.position)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package prog8.compiler.astprocessing
|
package prog8.compiler.astprocessing
|
||||||
|
|
||||||
|
import prog8.ast.IStatementContainer
|
||||||
import prog8.ast.Node
|
import prog8.ast.Node
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.DataType
|
import prog8.ast.base.DataType
|
||||||
@ -40,11 +41,13 @@ internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
|
|||||||
// this array literal is part of an expression, turn it into an identifier reference
|
// this array literal is part of an expression, turn it into an identifier reference
|
||||||
val litval2 = array.cast(arrayDt.getOr(DataType.UNDEFINED))
|
val litval2 = array.cast(arrayDt.getOr(DataType.UNDEFINED))
|
||||||
if(litval2!=null) {
|
if(litval2!=null) {
|
||||||
|
if(array.parent !is IStatementContainer)
|
||||||
|
return noModifications
|
||||||
val vardecl2 = VarDecl.createAuto(litval2)
|
val vardecl2 = VarDecl.createAuto(litval2)
|
||||||
val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position)
|
val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position)
|
||||||
return listOf(
|
return listOf(
|
||||||
IAstModification.ReplaceNode(array, identifier, parent),
|
IAstModification.ReplaceNode(array, identifier, parent),
|
||||||
IAstModification.InsertFirst(vardecl2, array.definingScope)
|
IAstModification.InsertFirst(vardecl2, array.parent as IStatementContainer)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
package prog8.compiler.astprocessing
|
package prog8.compiler.astprocessing
|
||||||
|
|
||||||
import prog8.ast.IFunctionCall
|
import prog8.ast.*
|
||||||
import prog8.ast.Module
|
import prog8.ast.base.*
|
||||||
import prog8.ast.Node
|
|
||||||
import prog8.ast.Program
|
|
||||||
import prog8.ast.base.DataType
|
|
||||||
import prog8.ast.base.FatalAstException
|
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.ast.walk.AstWalker
|
import prog8.ast.walk.AstWalker
|
||||||
import prog8.ast.walk.IAstModification
|
import prog8.ast.walk.IAstModification
|
||||||
import prog8.compiler.IErrorReporter
|
import prog8.compilerinterface.BuiltinFunctions
|
||||||
import prog8.compiler.functions.BuiltinFunctions
|
import prog8.compilerinterface.CompilationOptions
|
||||||
|
import prog8.compilerinterface.ICompilationTarget
|
||||||
|
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.
|
// Reorders the statements in a way the compiler needs.
|
||||||
// - 'main' block must be the very first statement UNLESS it has an address set.
|
// - 'main' block must be the very first statement UNLESS it has an address set.
|
||||||
// - library blocks are put last.
|
// - library blocks are put last.
|
||||||
@ -25,12 +25,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>
|
// - 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.
|
// - sorts the choices in when statement.
|
||||||
// - insert AddressOf (&) expression where required (string params to a UWORD function param etc.).
|
// - 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")
|
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option")
|
||||||
|
|
||||||
override fun after(module: Module, parent: Node): Iterable<IAstModification> {
|
override fun after(module: Module, parent: Node): Iterable<IAstModification> {
|
||||||
val (blocks, other) = module.statements.partition { it is Block }
|
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" }
|
val mainBlock = module.statements.filterIsInstance<Block>().firstOrNull { it.name=="main" }
|
||||||
if(mainBlock!=null && mainBlock.address==null) {
|
if(mainBlock!=null && mainBlock.address==null) {
|
||||||
@ -38,15 +39,59 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
|||||||
module.statements.add(0, mainBlock)
|
module.statements.add(0, mainBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
reorderVardeclsAndDirectives(module.statements)
|
directivesToTheTop(module.statements)
|
||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun reorderVardeclsAndDirectives(statements: MutableList<Statement>) {
|
private val declsProcessedWithInitAssignment = mutableSetOf<VarDecl>()
|
||||||
val varDecls = statements.filterIsInstance<VarDecl>()
|
|
||||||
statements.removeAll(varDecls)
|
|
||||||
statements.addAll(0, varDecls)
|
|
||||||
|
|
||||||
|
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||||
|
if(decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
|
||||||
|
if(decl !in declsProcessedWithInitAssignment) {
|
||||||
|
declsProcessedWithInitAssignment.add(decl)
|
||||||
|
if (decl.value == null) {
|
||||||
|
if (!decl.autogeneratedDontRemove && decl.allowInitializeWithZero) {
|
||||||
|
// A numeric vardecl without an initial value is initialized with zero,
|
||||||
|
// unless there's already an assignment below it, that initializes the value (or a for loop that uses it as loopvar).
|
||||||
|
// This allows you to restart the program and have the same starting values of the variables
|
||||||
|
// So basically consider 'ubyte xx' as a short form for 'ubyte xx; xx=0'
|
||||||
|
decl.value = null
|
||||||
|
if(decl.name.startsWith("retval_interm_") && decl.definingScope.name=="prog8_lib") {
|
||||||
|
// no need to zero out the special internal returnvalue intermediates.
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
val nextStmt = decl.nextSibling()
|
||||||
|
val nextFor = nextStmt as? ForLoop
|
||||||
|
val hasNextForWithThisLoopvar = nextFor?.loopVar?.nameInSource==listOf(decl.name)
|
||||||
|
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)
|
||||||
|
val assignzero = Assignment(AssignTarget(identifier, null, null, decl.position), decl.zeroElementValue(), decl.position)
|
||||||
|
return listOf(IAstModification.InsertAfter(
|
||||||
|
decl, assignzero, parent as IStatementContainer
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Transform the vardecl with initvalue to a plain vardecl + assignment
|
||||||
|
// this allows for other optimizations to kick in.
|
||||||
|
// So basically consider 'ubyte xx=99' as a short form for 'ubyte xx; xx=99'
|
||||||
|
val pos = decl.value!!.position
|
||||||
|
val identifier = IdentifierReference(listOf(decl.name), pos)
|
||||||
|
val assign = Assignment(AssignTarget(identifier, null, null, pos), decl.value!!, pos)
|
||||||
|
decl.value = null
|
||||||
|
return listOf(IAstModification.InsertAfter(
|
||||||
|
decl, assign, parent as IStatementContainer
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun directivesToTheTop(statements: MutableList<Statement>) {
|
||||||
val directives = statements.filterIsInstance<Directive>().filter {it.directive in directivesToMove}
|
val directives = statements.filterIsInstance<Directive>().filter {it.directive in directivesToMove}
|
||||||
statements.removeAll(directives)
|
statements.removeAll(directives)
|
||||||
statements.addAll(0, directives)
|
statements.addAll(0, directives)
|
||||||
@ -61,7 +106,7 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
reorderVardeclsAndDirectives(block.statements)
|
directivesToTheTop(block.statements)
|
||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,11 +127,40 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
|||||||
subs.map { IAstModification.InsertLast(it, subroutine) }
|
subs.map { IAstModification.InsertLast(it, subroutine) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 stringParamsByNames = stringParams.associateBy { it.name }
|
||||||
|
val 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 parameterChanges + varsChanges
|
||||||
|
}
|
||||||
|
|
||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||||
return replacePointerVarIndexWithMemread(program, arrayIndexedExpression, parent)
|
if(parent !is VarDecl) {
|
||||||
|
// don't replace the initializer value in a vardecl - this will be moved to a separate
|
||||||
|
// assignment statement soon in after(VarDecl)
|
||||||
|
return replacePointerVarIndexWithMemreadOrMemwrite(program, arrayIndexedExpression, parent)
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||||
@ -122,8 +196,9 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
|||||||
is Subroutine -> {
|
is Subroutine -> {
|
||||||
val paramType = callee.parameters[argnum].type
|
val paramType = callee.parameters[argnum].type
|
||||||
if(leftDt isAssignableTo paramType) {
|
if(leftDt isAssignableTo paramType) {
|
||||||
val cast = TypecastExpression(expr.left, paramType, true, parent.position)
|
val (replaced, cast) = expr.left.typecastTo(paramType, leftDt.getOr(DataType.UNDEFINED), true)
|
||||||
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
if(replaced)
|
||||||
|
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is BuiltinFunctionStatementPlaceholder -> {
|
is BuiltinFunctionStatementPlaceholder -> {
|
||||||
@ -131,8 +206,9 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
|||||||
val paramTypes = func.parameters[argnum].possibleDatatypes
|
val paramTypes = func.parameters[argnum].possibleDatatypes
|
||||||
for(type in paramTypes) {
|
for(type in paramTypes) {
|
||||||
if(leftDt isAssignableTo type) {
|
if(leftDt isAssignableTo type) {
|
||||||
val cast = TypecastExpression(expr.left, type, true, parent.position)
|
val (replaced, cast) = expr.left.typecastTo(type, leftDt.getOr(DataType.UNDEFINED), true)
|
||||||
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
if(replaced)
|
||||||
|
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -148,7 +224,7 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
|||||||
// generating the wrong results later
|
// generating the wrong results later
|
||||||
|
|
||||||
fun wrapped(expr: Expression): Expression =
|
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 {
|
fun isLogicalExpr(expr: Expression?): Boolean {
|
||||||
if(expr is BinaryExpression && expr.operator in (logicalOperators + comparisonOperators))
|
if(expr is BinaryExpression && expr.operator in (logicalOperators + comparisonOperators))
|
||||||
@ -286,4 +362,110 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
|||||||
)
|
)
|
||||||
return listOf(IAstModification.ReplaceNode(assign, memcopy, assign.parent))
|
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
|
||||||
|
argumentValue = AddressOf(argumentValue as IdentifierReference, 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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,8 @@ import prog8.ast.expressions.*
|
|||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.ast.walk.AstWalker
|
import prog8.ast.walk.AstWalker
|
||||||
import prog8.ast.walk.IAstModification
|
import prog8.ast.walk.IAstModification
|
||||||
import prog8.compiler.IErrorReporter
|
import prog8.compilerinterface.BuiltinFunctions
|
||||||
import prog8.compiler.functions.BuiltinFunctions
|
import prog8.compilerinterface.IErrorReporter
|
||||||
|
|
||||||
|
|
||||||
class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalker() {
|
class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalker() {
|
||||||
@ -46,8 +46,29 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
|
|||||||
val leftDt = expr.left.inferType(program)
|
val leftDt = expr.left.inferType(program)
|
||||||
val rightDt = expr.right.inferType(program)
|
val rightDt = expr.right.inferType(program)
|
||||||
if(leftDt.isKnown && rightDt.isKnown && leftDt!=rightDt) {
|
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
|
// 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) {
|
if(toFix!=null) {
|
||||||
return when {
|
return when {
|
||||||
toFix===expr.left -> listOf(IAstModification.ReplaceNode(
|
toFix===expr.left -> listOf(IAstModification.ReplaceNode(
|
||||||
@ -78,10 +99,10 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
|
|||||||
TypecastExpression(assignment.value, targettype, true, assignment.value.position),
|
TypecastExpression(assignment.value, targettype, true, assignment.value.position),
|
||||||
assignment))
|
assignment))
|
||||||
} else {
|
} else {
|
||||||
fun castLiteral(cvalue: NumericLiteralValue): List<IAstModification.ReplaceNode> {
|
fun castLiteral(cvalue2: NumericLiteralValue): List<IAstModification.ReplaceNode> {
|
||||||
val cast = cvalue.cast(targettype)
|
val cast = cvalue2.cast(targettype)
|
||||||
return if(cast.isValid)
|
return if(cast.isValid)
|
||||||
listOf(IAstModification.ReplaceNode(cvalue, cast.valueOrZero(), cvalue.parent))
|
listOf(IAstModification.ReplaceNode(assignment.value, cast.valueOrZero(), assignment))
|
||||||
else
|
else
|
||||||
emptyList()
|
emptyList()
|
||||||
}
|
}
|
||||||
@ -135,11 +156,13 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
|
|||||||
TypecastExpression(pair.second, requiredType, true, pair.second.position),
|
TypecastExpression(pair.second, requiredType, true, pair.second.position),
|
||||||
call as Node)
|
call as Node)
|
||||||
} else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) {
|
} else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) {
|
||||||
// we allow STR/ARRAY values in place of UWORD parameters. Take their address instead.
|
// We allow STR/ARRAY values in place of UWORD parameters.
|
||||||
if(pair.second is IdentifierReference) {
|
// 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(
|
modifications += IAstModification.ReplaceNode(
|
||||||
call.args[index],
|
call.args[index],
|
||||||
AddressOf(pair.second as IdentifierReference, pair.second.position),
|
AddressOf(identifier, pair.second.position),
|
||||||
call as Node)
|
call as Node)
|
||||||
}
|
}
|
||||||
} else if(pair.second is NumericLiteralValue) {
|
} else if(pair.second is NumericLiteralValue) {
|
||||||
@ -169,6 +192,18 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
|
|||||||
call as Node)
|
call as Node)
|
||||||
break
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package prog8.compiler.astprocessing
|
package prog8.compiler.astprocessing
|
||||||
|
|
||||||
import prog8.ast.IFunctionCall
|
import prog8.ast.IFunctionCall
|
||||||
import prog8.ast.INameScope
|
import prog8.ast.IStatementContainer
|
||||||
import prog8.ast.Node
|
import prog8.ast.Node
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.FatalAstException
|
import prog8.ast.base.FatalAstException
|
||||||
@ -10,23 +10,23 @@ import prog8.ast.expressions.*
|
|||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.ast.walk.AstWalker
|
import prog8.ast.walk.AstWalker
|
||||||
import prog8.ast.walk.IAstModification
|
import prog8.ast.walk.IAstModification
|
||||||
import prog8.compiler.IErrorReporter
|
import prog8.compilerinterface.IErrorReporter
|
||||||
|
|
||||||
|
|
||||||
internal class VariousCleanups(val program: Program, val errors: IErrorReporter): AstWalker() {
|
internal class VariousCleanups(val program: Program, val errors: IErrorReporter): AstWalker() {
|
||||||
|
|
||||||
override fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> {
|
override fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> {
|
||||||
return listOf(IAstModification.Remove(nopStatement, parent as INameScope))
|
return listOf(IAstModification.Remove(nopStatement, parent as IStatementContainer))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||||
return if(parent is INameScope)
|
return if(parent is IStatementContainer)
|
||||||
listOf(ScopeFlatten(scope, parent as INameScope))
|
listOf(ScopeFlatten(scope, parent as IStatementContainer))
|
||||||
else
|
else
|
||||||
noModifications
|
noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
class ScopeFlatten(val scope: AnonymousScope, val into: INameScope) : IAstModification {
|
class ScopeFlatten(val scope: AnonymousScope, val into: IStatementContainer) : IAstModification {
|
||||||
override fun perform() {
|
override fun perform() {
|
||||||
val idx = into.statements.indexOf(scope)
|
val idx = into.statements.indexOf(scope)
|
||||||
if(idx>=0) {
|
if(idx>=0) {
|
||||||
@ -74,6 +74,14 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter)
|
|||||||
if(sourceDt istype typecast.type)
|
if(sourceDt istype typecast.type)
|
||||||
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
|
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
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,6 +94,13 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter)
|
|||||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||||
if(assignment.parent!==parent)
|
if(assignment.parent!==parent)
|
||||||
throw FatalAstException("parent node mismatch at $assignment")
|
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
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,21 +8,21 @@ import prog8.ast.expressions.FunctionCall
|
|||||||
import prog8.ast.expressions.TypecastExpression
|
import prog8.ast.expressions.TypecastExpression
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.ast.walk.IAstVisitor
|
import prog8.ast.walk.IAstVisitor
|
||||||
import prog8.compiler.CompilerException
|
import prog8.compilerinterface.BuiltinFunctions
|
||||||
import prog8.compiler.functions.BuiltinFunctions
|
import prog8.compilerinterface.InternalCompilerException
|
||||||
|
|
||||||
class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
|
class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
|
||||||
|
|
||||||
override fun visit(functionCall: FunctionCall) {
|
override fun visit(functionCall: FunctionCall) {
|
||||||
val error = checkTypes(functionCall as IFunctionCall, program)
|
val error = checkTypes(functionCall as IFunctionCall, program)
|
||||||
if(error!=null)
|
if(error!=null)
|
||||||
throw CompilerException(error)
|
throw InternalCompilerException(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(functionCallStatement: FunctionCallStatement) {
|
override fun visit(functionCallStatement: FunctionCallStatement) {
|
||||||
val error = checkTypes(functionCallStatement as IFunctionCall, program)
|
val error = checkTypes(functionCallStatement as IFunctionCall, program)
|
||||||
if (error!=null)
|
if (error!=null)
|
||||||
throw CompilerException(error)
|
throw InternalCompilerException(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -83,8 +83,13 @@ class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
|
|||||||
val anyCompatible = pair.second.any { argTypeCompatible(pair.first, it) }
|
val anyCompatible = pair.second.any { argTypeCompatible(pair.first, it) }
|
||||||
if (!anyCompatible) {
|
if (!anyCompatible) {
|
||||||
val actual = pair.first.toString()
|
val actual = pair.first.toString()
|
||||||
val expected = pair.second.toString()
|
return if(pair.second.size==1) {
|
||||||
return "argument ${index + 1} type mismatch, was: $actual expected: $expected"
|
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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
package prog8.compiler.target
|
|
||||||
|
|
||||||
import prog8.compiler.CompilationOptions
|
|
||||||
|
|
||||||
internal interface IAssemblyGenerator {
|
|
||||||
fun compileToAssembly(): IAssemblyProgram
|
|
||||||
}
|
|
||||||
|
|
||||||
internal const val generatedLabelPrefix = "_prog8_label_"
|
|
||||||
internal const val subroutineFloatEvalResultVar1 = "_prog8_float_eval_result1"
|
|
||||||
internal const val subroutineFloatEvalResultVar2 = "_prog8_float_eval_result2"
|
|
||||||
|
|
||||||
internal interface IAssemblyProgram {
|
|
||||||
val valid: Boolean
|
|
||||||
val name: String
|
|
||||||
fun assemble(options: CompilationOptions): Int
|
|
||||||
}
|
|
@ -1,98 +0,0 @@
|
|||||||
package prog8.compiler.target
|
|
||||||
|
|
||||||
import com.github.michaelbull.result.fold
|
|
||||||
import prog8.ast.IMemSizer
|
|
||||||
import prog8.ast.Program
|
|
||||||
import prog8.ast.base.*
|
|
||||||
import prog8.ast.expressions.IdentifierReference
|
|
||||||
import prog8.ast.expressions.NumericLiteralValue
|
|
||||||
import prog8.ast.statements.AssignTarget
|
|
||||||
import prog8.compiler.CompilationOptions
|
|
||||||
import prog8.compiler.IErrorReporter
|
|
||||||
import prog8.compiler.IStringEncoding
|
|
||||||
import prog8.compiler.Zeropage
|
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition
|
|
||||||
import prog8.compiler.target.cbm.Petscii
|
|
||||||
import prog8.compiler.target.cpu6502.codegen.AsmGen
|
|
||||||
import prog8.compiler.target.cx16.CX16MachineDefinition
|
|
||||||
import java.io.CharConversionException
|
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
|
|
||||||
interface ICompilationTarget: IStringEncoding, IMemSizer {
|
|
||||||
val name: String
|
|
||||||
val machine: IMachineDefinition
|
|
||||||
override fun encodeString(str: String, altEncoding: Boolean): List<Short>
|
|
||||||
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
internal object C64Target: ICompilationTarget {
|
|
||||||
override val name = "c64"
|
|
||||||
override val machine = C64MachineDefinition
|
|
||||||
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
|
|
||||||
val coded = if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
|
||||||
return coded.fold(
|
|
||||||
failure = { throw it },
|
|
||||||
success = { it }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
|
|
||||||
try {
|
|
||||||
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
|
||||||
} catch (x: CharConversionException) {
|
|
||||||
throw CharConversionException("can't decode string: ${x.message}")
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal object Cx16Target: ICompilationTarget {
|
|
||||||
override val name = "cx16"
|
|
||||||
override val machine = CX16MachineDefinition
|
|
||||||
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
|
|
||||||
val coded= if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
|
||||||
return coded.fold(
|
|
||||||
failure = { throw it },
|
|
||||||
success = { it }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
|
|
||||||
try {
|
|
||||||
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
|
||||||
} catch (x: CharConversionException) {
|
|
||||||
throw CharConversionException("can't decode string: ${x.message}")
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
internal fun asmGeneratorFor(
|
|
||||||
compTarget: ICompilationTarget,
|
|
||||||
program: Program,
|
|
||||||
errors: IErrorReporter,
|
|
||||||
zp: Zeropage,
|
|
||||||
options: CompilationOptions,
|
|
||||||
outputDir: Path
|
|
||||||
): IAssemblyGenerator
|
|
||||||
{
|
|
||||||
// at the moment we only have one code generation backend (for 6502 and 65c02)
|
|
||||||
return AsmGen(program, errors, zp, options, compTarget, outputDir)
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,242 +0,0 @@
|
|||||||
package prog8.compiler.target.cpu6502.codegen
|
|
||||||
|
|
||||||
|
|
||||||
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
|
|
||||||
|
|
||||||
|
|
||||||
fun optimizeAssembly(lines: MutableList<String>): Int {
|
|
||||||
|
|
||||||
var numberOfOptimizations = 0
|
|
||||||
|
|
||||||
var linesByFour = getLinesBy(lines, 4)
|
|
||||||
|
|
||||||
var mods = optimizeUselessStackByteWrites(linesByFour)
|
|
||||||
if(mods.isNotEmpty()) {
|
|
||||||
apply(mods, lines)
|
|
||||||
linesByFour = getLinesBy(lines, 4)
|
|
||||||
numberOfOptimizations++
|
|
||||||
}
|
|
||||||
|
|
||||||
mods = optimizeIncDec(linesByFour)
|
|
||||||
if(mods.isNotEmpty()) {
|
|
||||||
apply(mods, lines)
|
|
||||||
linesByFour = getLinesBy(lines, 4)
|
|
||||||
numberOfOptimizations++
|
|
||||||
}
|
|
||||||
|
|
||||||
mods = optimizeCmpSequence(linesByFour)
|
|
||||||
if(mods.isNotEmpty()) {
|
|
||||||
apply(mods, lines)
|
|
||||||
linesByFour = getLinesBy(lines, 4)
|
|
||||||
numberOfOptimizations++
|
|
||||||
}
|
|
||||||
|
|
||||||
mods = optimizeStoreLoadSame(linesByFour)
|
|
||||||
if(mods.isNotEmpty()) {
|
|
||||||
apply(mods, lines)
|
|
||||||
linesByFour = getLinesBy(lines, 4)
|
|
||||||
numberOfOptimizations++
|
|
||||||
}
|
|
||||||
|
|
||||||
mods= optimizeJsrRts(linesByFour)
|
|
||||||
if(mods.isNotEmpty()) {
|
|
||||||
apply(mods, lines)
|
|
||||||
linesByFour = getLinesBy(lines, 4)
|
|
||||||
numberOfOptimizations++
|
|
||||||
}
|
|
||||||
|
|
||||||
var linesByFourteen = getLinesBy(lines, 14)
|
|
||||||
mods = optimizeSameAssignments(linesByFourteen)
|
|
||||||
if(mods.isNotEmpty()) {
|
|
||||||
apply(mods, lines)
|
|
||||||
linesByFourteen = getLinesBy(lines, 14)
|
|
||||||
numberOfOptimizations++
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO more assembly optimizations
|
|
||||||
|
|
||||||
return numberOfOptimizations
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Modification(val lineIndex: Int, val remove: Boolean, val replacement: String?)
|
|
||||||
|
|
||||||
private fun apply(modifications: List<Modification>, lines: MutableList<String>) {
|
|
||||||
for (modification in modifications.sortedBy { it.lineIndex }.reversed()) {
|
|
||||||
if(modification.remove)
|
|
||||||
lines.removeAt(modification.lineIndex)
|
|
||||||
else
|
|
||||||
lines[modification.lineIndex] = modification.replacement!!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getLinesBy(lines: MutableList<String>, windowSize: Int) =
|
|
||||||
// all lines (that aren't empty or comments) in sliding windows of certain size
|
|
||||||
lines.withIndex().filter { it.value.isNotBlank() && !it.value.trimStart().startsWith(';') }.windowed(windowSize, partialWindows = false)
|
|
||||||
|
|
||||||
private fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
|
||||||
// when statement (on bytes) generates a sequence of:
|
|
||||||
// lda $ce01,x
|
|
||||||
// cmp #$20
|
|
||||||
// beq check_prog8_s72choice_32
|
|
||||||
// lda $ce01,x
|
|
||||||
// cmp #$21
|
|
||||||
// beq check_prog8_s73choice_33
|
|
||||||
// the repeated lda can be removed
|
|
||||||
val mods = mutableListOf<Modification>()
|
|
||||||
for(lines in linesByFour) {
|
|
||||||
if(lines[0].value.trim()=="lda P8ESTACK_LO+1,x" &&
|
|
||||||
lines[1].value.trim().startsWith("cmp ") &&
|
|
||||||
lines[2].value.trim().startsWith("beq ") &&
|
|
||||||
lines[3].value.trim()=="lda P8ESTACK_LO+1,x") {
|
|
||||||
mods.add(Modification(lines[3].index, true, null)) // remove the second lda
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mods
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
|
||||||
// sta on stack, dex, inx, lda from stack -> eliminate this useless stack byte write
|
|
||||||
// this is a lot harder for word values because the instruction sequence varies.
|
|
||||||
val mods = mutableListOf<Modification>()
|
|
||||||
for(lines in linesByFour) {
|
|
||||||
if(lines[0].value.trim()=="sta P8ESTACK_LO,x" &&
|
|
||||||
lines[1].value.trim()=="dex" &&
|
|
||||||
lines[2].value.trim()=="inx" &&
|
|
||||||
lines[3].value.trim()=="lda P8ESTACK_LO,x") {
|
|
||||||
mods.add(Modification(lines[1].index, true, null))
|
|
||||||
mods.add(Modification(lines[2].index, true, null))
|
|
||||||
mods.add(Modification(lines[3].index, true, null))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mods
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>): List<Modification> {
|
|
||||||
|
|
||||||
// optimize sequential assignments of the isSameAs value to various targets (bytes, words, floats)
|
|
||||||
// the float one is the one that requires 2*7=14 lines of code to check...
|
|
||||||
// @todo a better place to do this is in the Compiler instead and transform the Ast, or the AsmGen, and never even create the inefficient asm in the first place...
|
|
||||||
|
|
||||||
val mods = mutableListOf<Modification>()
|
|
||||||
for (pair in linesByFourteen) {
|
|
||||||
val first = pair[0].value.trimStart()
|
|
||||||
val second = pair[1].value.trimStart()
|
|
||||||
val third = pair[2].value.trimStart()
|
|
||||||
val fourth = pair[3].value.trimStart()
|
|
||||||
val fifth = pair[4].value.trimStart()
|
|
||||||
val sixth = pair[5].value.trimStart()
|
|
||||||
val seventh = pair[6].value.trimStart()
|
|
||||||
val eighth = pair[7].value.trimStart()
|
|
||||||
|
|
||||||
if(first.startsWith("lda") && second.startsWith("ldy") && third.startsWith("sta") && fourth.startsWith("sty") &&
|
|
||||||
fifth.startsWith("lda") && sixth.startsWith("ldy") && seventh.startsWith("sta") && eighth.startsWith("sty")) {
|
|
||||||
val firstvalue = first.substring(4)
|
|
||||||
val secondvalue = second.substring(4)
|
|
||||||
val thirdvalue = fifth.substring(4)
|
|
||||||
val fourthvalue = sixth.substring(4)
|
|
||||||
if(firstvalue==thirdvalue && secondvalue==fourthvalue) {
|
|
||||||
// lda/ldy sta/sty twice the isSameAs word --> remove second lda/ldy pair (fifth and sixth lines)
|
|
||||||
mods.add(Modification(pair[4].index, true, null))
|
|
||||||
mods.add(Modification(pair[5].index, true, null))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(first.startsWith("lda") && second.startsWith("sta") && third.startsWith("lda") && fourth.startsWith("sta")) {
|
|
||||||
val firstvalue = first.substring(4)
|
|
||||||
val secondvalue = third.substring(4)
|
|
||||||
if(firstvalue==secondvalue) {
|
|
||||||
// lda value / sta ? / lda isSameAs-value / sta ? -> remove second lda (third line)
|
|
||||||
mods.add(Modification(pair[2].index, true, null))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(first.startsWith("lda") && second.startsWith("ldy") && third.startsWith("sta") && fourth.startsWith("sty") &&
|
|
||||||
fifth.startsWith("lda") && sixth.startsWith("ldy") &&
|
|
||||||
(seventh.startsWith("jsr floats.copy_float") || seventh.startsWith("jsr cx16flt.copy_float"))) {
|
|
||||||
|
|
||||||
val nineth = pair[8].value.trimStart()
|
|
||||||
val tenth = pair[9].value.trimStart()
|
|
||||||
val eleventh = pair[10].value.trimStart()
|
|
||||||
val twelveth = pair[11].value.trimStart()
|
|
||||||
val thirteenth = pair[12].value.trimStart()
|
|
||||||
val fourteenth = pair[13].value.trimStart()
|
|
||||||
|
|
||||||
if(eighth.startsWith("lda") && nineth.startsWith("ldy") && tenth.startsWith("sta") && eleventh.startsWith("sty") &&
|
|
||||||
twelveth.startsWith("lda") && thirteenth.startsWith("ldy") &&
|
|
||||||
(fourteenth.startsWith("jsr floats.copy_float") || fourteenth.startsWith("jsr cx16flt.copy_float"))) {
|
|
||||||
|
|
||||||
if(first.substring(4) == eighth.substring(4) && second.substring(4)==nineth.substring(4)) {
|
|
||||||
// identical float init
|
|
||||||
mods.add(Modification(pair[7].index, true, null))
|
|
||||||
mods.add(Modification(pair[8].index, true, null))
|
|
||||||
mods.add(Modification(pair[9].index, true, null))
|
|
||||||
mods.add(Modification(pair[10].index, true, null))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mods
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
|
||||||
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can OFTEN be eliminated
|
|
||||||
// TODO this is not true if X is not a regular RAM memory address (but instead mapped I/O or ROM)
|
|
||||||
val mods = mutableListOf<Modification>()
|
|
||||||
for (pair in linesByFour) {
|
|
||||||
val first = pair[0].value.trimStart()
|
|
||||||
val second = pair[1].value.trimStart()
|
|
||||||
|
|
||||||
if ((first.startsWith("sta ") && second.startsWith("lda ")) ||
|
|
||||||
(first.startsWith("stx ") && second.startsWith("ldx ")) ||
|
|
||||||
(first.startsWith("sty ") && second.startsWith("ldy ")) ||
|
|
||||||
(first.startsWith("lda ") && second.startsWith("lda ")) ||
|
|
||||||
(first.startsWith("ldy ") && second.startsWith("ldy ")) ||
|
|
||||||
(first.startsWith("ldx ") && second.startsWith("ldx ")) ||
|
|
||||||
(first.startsWith("sta ") && second.startsWith("lda ")) ||
|
|
||||||
(first.startsWith("sty ") && second.startsWith("ldy ")) ||
|
|
||||||
(first.startsWith("stx ") && second.startsWith("ldx "))
|
|
||||||
) {
|
|
||||||
val third = pair[2].value.trimStart()
|
|
||||||
if(!third.startsWith("b")) {
|
|
||||||
// no branch instruction follows, we can potentiall remove the load instruction
|
|
||||||
val firstLoc = first.substring(4).trimStart()
|
|
||||||
val secondLoc = second.substring(4).trimStart()
|
|
||||||
if (firstLoc == secondLoc) {
|
|
||||||
mods.add(Modification(pair[1].index, true, null))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mods
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun optimizeIncDec(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
|
||||||
// sometimes, iny+dey / inx+dex / dey+iny / dex+inx sequences are generated, these can be eliminated.
|
|
||||||
val mods = mutableListOf<Modification>()
|
|
||||||
for (pair in linesByFour) {
|
|
||||||
val first = pair[0].value
|
|
||||||
val second = pair[1].value
|
|
||||||
if ((" iny" in first || "\tiny" in first) && (" dey" in second || "\tdey" in second)
|
|
||||||
|| (" inx" in first || "\tinx" in first) && (" dex" in second || "\tdex" in second)
|
|
||||||
|| (" dey" in first || "\tdey" in first) && (" iny" in second || "\tiny" in second)
|
|
||||||
|| (" dex" in first || "\tdex" in first) && (" inx" in second || "\tinx" in second)) {
|
|
||||||
mods.add(Modification(pair[0].index, true, null))
|
|
||||||
mods.add(Modification(pair[1].index, true, null))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mods
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun optimizeJsrRts(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
|
||||||
// jsr Sub + rts -> jmp Sub
|
|
||||||
val mods = mutableListOf<Modification>()
|
|
||||||
for (pair in linesByFour) {
|
|
||||||
val first = pair[0].value
|
|
||||||
val second = pair[1].value
|
|
||||||
if ((" jsr" in first || "\tjsr" in first ) && (" rts" in second || "\trts" in second)) {
|
|
||||||
mods += Modification(pair[0].index, false, pair[0].value.replace("jsr", "jmp"))
|
|
||||||
mods += Modification(pair[1].index, true, null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mods
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,87 +0,0 @@
|
|||||||
package prog8.optimizer
|
|
||||||
|
|
||||||
import prog8.ast.INameScope
|
|
||||||
import prog8.ast.Node
|
|
||||||
import prog8.ast.Program
|
|
||||||
import prog8.ast.expressions.BinaryExpression
|
|
||||||
import prog8.ast.expressions.augmentAssignmentOperators
|
|
||||||
import prog8.ast.statements.AssignTarget
|
|
||||||
import prog8.ast.statements.Assignment
|
|
||||||
import prog8.ast.walk.AstWalker
|
|
||||||
import prog8.ast.walk.IAstModification
|
|
||||||
import prog8.compiler.astprocessing.isInRegularRAMof
|
|
||||||
import prog8.compiler.target.ICompilationTarget
|
|
||||||
|
|
||||||
|
|
||||||
internal class BinExprSplitter(private val program: Program, 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...:
|
|
||||||
// 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> {
|
|
||||||
|
|
||||||
val binExpr = assignment.value as? BinaryExpression
|
|
||||||
if (binExpr != null) {
|
|
||||||
/*
|
|
||||||
|
|
||||||
Reduce the complexity of a (binary) expression that has to be evaluated on the eval stack,
|
|
||||||
by attempting to splitting it up into individual simple steps.
|
|
||||||
We only consider a binary expression *one* level deep (so the operands must not be a combined expression)
|
|
||||||
|
|
||||||
|
|
||||||
X = BinExpr X = LeftExpr
|
|
||||||
<operator> followed by
|
|
||||||
/ \ IF 'X' not used X = BinExpr
|
|
||||||
/ \ IN expression ==> <operator>
|
|
||||||
/ \ / \
|
|
||||||
LeftExpr. RightExpr. / \
|
|
||||||
X RightExpr.
|
|
||||||
|
|
||||||
|
|
||||||
*/
|
|
||||||
if(binExpr.operator in augmentAssignmentOperators && isSimpleTarget(assignment.target)) {
|
|
||||||
if(assignment.target isSameAs binExpr.left || assignment.target isSameAs binExpr.right)
|
|
||||||
return noModifications
|
|
||||||
|
|
||||||
if(binExpr.right.isSimple && !assignment.isAugmentable) {
|
|
||||||
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)
|
|
||||||
return listOf(
|
|
||||||
IAstModification.ReplaceNode(binExpr, augExpr, assignment),
|
|
||||||
IAstModification.InsertBefore(assignment, firstAssign, assignment.parent as INameScope)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO further unraveling of binary expression trees into flat statements.
|
|
||||||
// however this should probably be done in a more generic way to also service
|
|
||||||
// the expressiontrees that are not used in an assignment statement...
|
|
||||||
}
|
|
||||||
|
|
||||||
return noModifications
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isSimpleTarget(target: AssignTarget) =
|
|
||||||
if (target.identifier!=null || target.memoryAddress!=null)
|
|
||||||
target.isInRegularRAMof(compTarget.machine)
|
|
||||||
else
|
|
||||||
false
|
|
||||||
|
|
||||||
}
|
|
@ -1,142 +0,0 @@
|
|||||||
package prog8.optimizer
|
|
||||||
|
|
||||||
import prog8.ast.*
|
|
||||||
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.statements.*
|
|
||||||
import prog8.ast.walk.AstWalker
|
|
||||||
import prog8.ast.walk.IAstModification
|
|
||||||
import prog8.compiler.IErrorReporter
|
|
||||||
import prog8.compiler.astprocessing.isInRegularRAMof
|
|
||||||
import prog8.compiler.target.ICompilationTarget
|
|
||||||
|
|
||||||
|
|
||||||
internal class UnusedCodeRemover(private val program: Program,
|
|
||||||
private val errors: IErrorReporter,
|
|
||||||
private val compTarget: ICompilationTarget): AstWalker() {
|
|
||||||
|
|
||||||
private val callgraph = CallGraph(program)
|
|
||||||
|
|
||||||
override fun before(module: Module, parent: Node): Iterable<IAstModification> {
|
|
||||||
return if (!module.isLibrary && (module.containsNoCodeNorVars || callgraph.unused(module)))
|
|
||||||
listOf(IAstModification.Remove(module, module.definingScope))
|
|
||||||
else
|
|
||||||
noModifications
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
|
|
||||||
reportUnreachable(breakStmt, parent as INameScope)
|
|
||||||
return emptyList()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun before(jump: Jump, parent: Node): Iterable<IAstModification> {
|
|
||||||
reportUnreachable(jump, parent as INameScope)
|
|
||||||
return emptyList()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> {
|
|
||||||
reportUnreachable(returnStmt, parent as INameScope)
|
|
||||||
return emptyList()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
|
||||||
if(functionCallStatement.target.nameInSource.last() == "exit")
|
|
||||||
reportUnreachable(functionCallStatement, parent as INameScope)
|
|
||||||
return emptyList()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun reportUnreachable(stmt: Statement, parent: INameScope) {
|
|
||||||
when(val next = parent.nextSibling(stmt)) {
|
|
||||||
null, is Label, is Directive, is VarDecl, is InlineAssembly, is Subroutine -> {}
|
|
||||||
else -> errors.warn("unreachable code", next.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
|
||||||
val removeDoubleAssignments = deduplicateAssignments(scope.statements)
|
|
||||||
return removeDoubleAssignments.map { IAstModification.Remove(it, scope) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
|
|
||||||
if("force_output" !in block.options()) {
|
|
||||||
if (block.containsNoCodeNorVars) {
|
|
||||||
if(block.name != internedStringsModuleName)
|
|
||||||
errors.warn("removing unused block '${block.name}'", block.position)
|
|
||||||
return listOf(IAstModification.Remove(block, parent as INameScope))
|
|
||||||
}
|
|
||||||
if(callgraph.unused(block)) {
|
|
||||||
errors.warn("removing unused block '${block.name}'", block.position)
|
|
||||||
return listOf(IAstModification.Remove(block, parent as INameScope))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val removeDoubleAssignments = deduplicateAssignments(block.statements)
|
|
||||||
return removeDoubleAssignments.map { IAstModification.Remove(it, block) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
|
||||||
val forceOutput = "force_output" in subroutine.definingBlock.options()
|
|
||||||
if (subroutine !== program.entrypoint && !forceOutput && !subroutine.inline && !subroutine.isAsmSubroutine) {
|
|
||||||
if(callgraph.unused(subroutine)) {
|
|
||||||
if(subroutine.containsNoCodeNorVars) {
|
|
||||||
if(!subroutine.definingModule.isLibrary)
|
|
||||||
errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position)
|
|
||||||
val removals = mutableListOf(IAstModification.Remove(subroutine, subroutine.definingScope))
|
|
||||||
callgraph.calledBy[subroutine]?.let {
|
|
||||||
for(node in it)
|
|
||||||
removals.add(IAstModification.Remove(node, node.definingScope))
|
|
||||||
}
|
|
||||||
return removals
|
|
||||||
}
|
|
||||||
if(!subroutine.definingModule.isLibrary)
|
|
||||||
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
|
|
||||||
return listOf(IAstModification.Remove(subroutine, subroutine.definingScope))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val removeDoubleAssignments = deduplicateAssignments(subroutine.statements)
|
|
||||||
return removeDoubleAssignments.map { IAstModification.Remove(it, 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)) {
|
|
||||||
errors.warn("removing unused variable '${decl.name}'", decl.position)
|
|
||||||
return listOf(IAstModification.Remove(decl, decl.definingScope))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return noModifications
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun deduplicateAssignments(statements: List<Statement>): List<Assignment> {
|
|
||||||
// removes 'duplicate' assignments that assign the same target directly after another
|
|
||||||
val linesToRemove = mutableListOf<Assignment>()
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return linesToRemove
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,133 +1,104 @@
|
|||||||
package prog8tests
|
package prog8tests
|
||||||
|
|
||||||
import kotlin.test.*
|
|
||||||
import com.github.michaelbull.result.getErrorOrElse
|
import com.github.michaelbull.result.getErrorOrElse
|
||||||
import com.github.michaelbull.result.getOrElse
|
import com.github.michaelbull.result.getOrElse
|
||||||
import org.hamcrest.MatcherAssert.assertThat
|
|
||||||
import org.hamcrest.Matchers.*
|
|
||||||
import org.hamcrest.core.Is
|
|
||||||
import org.junit.jupiter.api.BeforeEach
|
|
||||||
import org.junit.jupiter.api.Nested
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import org.junit.jupiter.api.TestInstance
|
|
||||||
import org.junit.jupiter.api.Disabled
|
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.internedStringsModuleName
|
import prog8.ast.internedStringsModuleName
|
||||||
import prog8.compiler.IErrorReporter
|
|
||||||
import prog8.compiler.ModuleImporter
|
import prog8.compiler.ModuleImporter
|
||||||
|
import prog8.compilerinterface.IErrorReporter
|
||||||
import prog8.parser.ParseError
|
import prog8.parser.ParseError
|
||||||
import prog8.parser.SourceCode
|
import prog8.parser.SourceCode
|
||||||
import prog8tests.helpers.*
|
import prog8tests.ast.helpers.*
|
||||||
|
import prog8tests.helpers.ErrorReporterForTests
|
||||||
|
import prog8tests.helpers.DummyFunctions
|
||||||
|
import prog8tests.helpers.DummyMemsizer
|
||||||
|
import prog8tests.helpers.DummyStringEncoder
|
||||||
import kotlin.io.path.*
|
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
|
||||||
|
|
||||||
|
|
||||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
class TestModuleImporter: FunSpec({
|
||||||
class TestModuleImporter {
|
val count = listOf("1st", "2nd", "3rd", "4th", "5th")
|
||||||
private val count = listOf("1st", "2nd", "3rd", "4th", "5th")
|
|
||||||
|
|
||||||
private lateinit var program: Program
|
lateinit var program: Program
|
||||||
@BeforeEach
|
|
||||||
fun beforeEach() {
|
beforeTest {
|
||||||
program = Program("foo", DummyFunctions, DummyMemsizer)
|
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())
|
return makeImporter(errors, searchIn.asList())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun makeImporter(errors: IErrorReporter? = null, searchIn: Iterable<String>) =
|
context("ImportModule") {
|
||||||
ModuleImporter(program, "blah", errors ?: ErrorReporterForTests(), searchIn.toList())
|
|
||||||
|
|
||||||
@Nested
|
context("WithInvalidPath") {
|
||||||
inner class Constructor {
|
test("testNonexisting") {
|
||||||
|
|
||||||
@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() {
|
|
||||||
val dirRel = assumeDirectory(".", workingDir.relativize(fixturesDir))
|
val dirRel = assumeDirectory(".", workingDir.relativize(fixturesDir))
|
||||||
val importer = makeImporter(null, dirRel.invariantSeparatorsPathString)
|
val importer = makeImporter(null, dirRel.invariantSeparatorsPathString)
|
||||||
val srcPathRel = assumeNotExists(dirRel, "i_do_not_exist")
|
val srcPathRel = assumeNotExists(dirRel, "i_do_not_exist")
|
||||||
val srcPathAbs = srcPathRel.absolute()
|
val srcPathAbs = srcPathRel.absolute()
|
||||||
val error1 = importer.importModule(srcPathRel).getErrorOrElse { fail("should have import error") }
|
val error1 = importer.importModule(srcPathRel).getErrorOrElse { fail("should have import error") }
|
||||||
assertThat(
|
withClue(".file should be normalized") {
|
||||||
".file should be normalized",
|
"${error1.file}" shouldBe "${error1.file.normalize()}"
|
||||||
"${error1.file}", equalTo("${error1.file.normalize()}")
|
}
|
||||||
)
|
withClue(".file should point to specified path") {
|
||||||
assertThat(
|
error1.file.absolutePath shouldBe "${srcPathAbs.normalize()}"
|
||||||
".file should point to specified path",
|
}
|
||||||
error1.file.absolutePath, equalTo("${srcPathAbs.normalize()}")
|
program.modules.size shouldBe 1
|
||||||
)
|
|
||||||
assertThat(program.modules.size, equalTo(1))
|
|
||||||
|
|
||||||
val error2 = importer.importModule(srcPathAbs).getErrorOrElse { fail("should have import error") }
|
val error2 = importer.importModule(srcPathAbs).getErrorOrElse { fail("should have import error") }
|
||||||
assertThat(
|
withClue(".file should be normalized") {
|
||||||
".file should be normalized",
|
"${error2.file}" shouldBe "${error2.file.normalize()}"
|
||||||
"${error2.file}", equalTo("${error2.file.normalize()}")
|
}
|
||||||
)
|
withClue(".file should point to specified path") {
|
||||||
assertThat(
|
error2.file.absolutePath shouldBe "${srcPathAbs.normalize()}"
|
||||||
".file should point to specified path",
|
}
|
||||||
error2.file.absolutePath, equalTo("${srcPathAbs.normalize()}")
|
program.modules.size shouldBe 1
|
||||||
)
|
|
||||||
assertThat(program.modules.size, equalTo(1))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testDirectory") {
|
||||||
fun testDirectory() {
|
|
||||||
val srcPathRel = assumeDirectory(workingDir.relativize(fixturesDir))
|
val srcPathRel = assumeDirectory(workingDir.relativize(fixturesDir))
|
||||||
val srcPathAbs = srcPathRel.absolute()
|
val srcPathAbs = srcPathRel.absolute()
|
||||||
val searchIn = Path(".", "$srcPathRel").invariantSeparatorsPathString
|
val searchIn = Path(".", "$srcPathRel").invariantSeparatorsPathString
|
||||||
val importer = makeImporter(null, searchIn)
|
val importer = makeImporter(null, searchIn)
|
||||||
|
|
||||||
assertFailsWith<AccessDeniedException> { importer.importModule(srcPathRel) }
|
shouldThrow<AccessDeniedException> { importer.importModule(srcPathRel) }
|
||||||
.let {
|
.let {
|
||||||
assertThat(
|
withClue(".file should be normalized") {
|
||||||
".file should be normalized",
|
"${it.file}" shouldBe "${it.file.normalize()}"
|
||||||
"${it.file}", equalTo("${it.file.normalize()}")
|
}
|
||||||
)
|
withClue(".file should point to specified path") {
|
||||||
assertThat(
|
it.file.absolutePath shouldBe "${srcPathAbs.normalize()}"
|
||||||
".file should point to specified path",
|
}
|
||||||
it.file.absolutePath, equalTo("${srcPathAbs.normalize()}")
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
assertThat(program.modules.size, equalTo(1))
|
program.modules.size shouldBe 1
|
||||||
|
|
||||||
assertFailsWith<AccessDeniedException> { importer.importModule(srcPathAbs) }
|
shouldThrow<AccessDeniedException> { importer.importModule(srcPathAbs) }
|
||||||
.let {
|
.let {
|
||||||
assertThat(
|
withClue(".file should be normalized") {
|
||||||
".file should be normalized",
|
"${it.file}" shouldBe "${it.file.normalize()}"
|
||||||
"${it.file}", equalTo("${it.file.normalize()}")
|
}
|
||||||
)
|
withClue(".file should point to specified path") {
|
||||||
assertThat(
|
it.file.absolutePath shouldBe "${srcPathAbs.normalize()}"
|
||||||
".file should point to specified path",
|
}
|
||||||
it.file.absolutePath, equalTo("${srcPathAbs.normalize()}")
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
assertThat(program.modules.size, equalTo(1))
|
program.modules.size shouldBe 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nested
|
context("WithValidPath") {
|
||||||
inner class WithValidPath {
|
|
||||||
|
|
||||||
@Test
|
test("testAbsolute") {
|
||||||
fun testAbsolute() {
|
|
||||||
val searchIn = listOf(
|
val searchIn = listOf(
|
||||||
Path(".").div(workingDir.relativize(fixturesDir)), // we do want a dot "." in front
|
Path(".").div(workingDir.relativize(fixturesDir)), // we do want a dot "." in front
|
||||||
).map { it.invariantSeparatorsPathString }
|
).map { it.invariantSeparatorsPathString }
|
||||||
@ -136,29 +107,29 @@ class TestModuleImporter {
|
|||||||
val path = assumeReadableFile(searchIn[0], fileName)
|
val path = assumeReadableFile(searchIn[0], fileName)
|
||||||
|
|
||||||
val module = importer.importModule(path.absolute()).getOrElse { throw it }
|
val module = importer.importModule(path.absolute()).getOrElse { throw it }
|
||||||
assertThat(program.modules.size, equalTo(2))
|
program.modules.size shouldBe 2
|
||||||
assertContains(program.modules, module)
|
module shouldBeIn program.modules
|
||||||
assertThat(module.program, equalTo(program))
|
module.program shouldBe program
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testRelativeToWorkingDir") {
|
||||||
fun testRelativeToWorkingDir() {
|
|
||||||
val searchIn = listOf(
|
val searchIn = listOf(
|
||||||
Path(".").div(workingDir.relativize(fixturesDir)), // we do want a dot "." in front
|
Path(".").div(workingDir.relativize(fixturesDir)), // we do want a dot "." in front
|
||||||
).map { it.invariantSeparatorsPathString }
|
).map { it.invariantSeparatorsPathString }
|
||||||
val importer = makeImporter(null, searchIn)
|
val importer = makeImporter(null, searchIn)
|
||||||
val fileName = "simple_main.p8"
|
val fileName = "simple_main.p8"
|
||||||
val path = assumeReadableFile(searchIn[0], fileName)
|
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 }
|
val module = importer.importModule(path).getOrElse { throw it }
|
||||||
assertThat(program.modules.size, equalTo(2))
|
program.modules.size shouldBe 2
|
||||||
assertContains(program.modules, module)
|
module shouldBeIn program.modules
|
||||||
assertThat(module.program, equalTo(program))
|
module.program shouldBe program
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testRelativeTo1stDirInSearchList") {
|
||||||
fun testRelativeTo1stDirInSearchList() {
|
|
||||||
val searchIn = Path(".")
|
val searchIn = Path(".")
|
||||||
.div(workingDir.relativize(fixturesDir))
|
.div(workingDir.relativize(fixturesDir))
|
||||||
.invariantSeparatorsPathString
|
.invariantSeparatorsPathString
|
||||||
@ -168,51 +139,32 @@ class TestModuleImporter {
|
|||||||
assumeReadableFile(searchIn, path)
|
assumeReadableFile(searchIn, path)
|
||||||
|
|
||||||
val module = importer.importModule(path).getOrElse { throw it }
|
val module = importer.importModule(path).getOrElse { throw it }
|
||||||
assertThat(program.modules.size, equalTo(2))
|
program.modules.size shouldBe 2
|
||||||
assertContains(program.modules, module)
|
module shouldBeIn program.modules
|
||||||
assertThat(module.program, equalTo(program))
|
module.program shouldBe program
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
context("WithBadFile") {
|
||||||
@Disabled("TODO: relative to 2nd in search list")
|
test("testWithSyntaxError") {
|
||||||
fun testRelativeTo2ndDirInSearchList() {}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Disabled("TODO: ambiguous - 2 or more really different candidates")
|
|
||||||
fun testAmbiguousCandidates() {}
|
|
||||||
|
|
||||||
@Nested
|
|
||||||
inner class WithBadFile {
|
|
||||||
@Test
|
|
||||||
fun testWithSyntaxError() {
|
|
||||||
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
||||||
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
|
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
|
||||||
val srcPath = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
|
val srcPath = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
|
||||||
|
|
||||||
val act = { importer.importModule(srcPath) }
|
val act = { importer.importModule(srcPath) }
|
||||||
|
|
||||||
repeat(2) { n ->
|
repeat(2) { n -> withClue(count[n] + " call") {
|
||||||
assertFailsWith<ParseError>(count[n] + " call") { act() }.let {
|
shouldThrow<ParseError>() { act() }.let {
|
||||||
assertThat(it.position.file, equalTo(SourceCode.relative(srcPath).toString()))
|
it.position.file shouldBe SourceCode.relative(srcPath).toString()
|
||||||
assertThat("line; should be 1-based", it.position.line, equalTo(2))
|
withClue("line; should be 1-based") { it.position.line shouldBe 2 }
|
||||||
assertThat("startCol; should be 0-based", it.position.startCol, equalTo(6))
|
withClue("startCol; should be 0-based") { it.position.startCol shouldBe 6 }
|
||||||
assertThat("endCol; should be 0-based", it.position.endCol, equalTo(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 doTestImportingFileWithSyntaxError(repetitions: Int) {
|
||||||
fun testImportingFileWithSyntaxError_once() {
|
|
||||||
doTestImportingFileWithSyntaxError(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testImportingFileWithSyntaxError_twice() {
|
|
||||||
doTestImportingFileWithSyntaxError(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun doTestImportingFileWithSyntaxError(repetitions: Int) {
|
|
||||||
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
||||||
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
|
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
|
||||||
val importing = assumeReadableFile(fixturesDir, "import_file_with_syntax_error.p8")
|
val importing = assumeReadableFile(fixturesDir, "import_file_with_syntax_error.p8")
|
||||||
@ -220,76 +172,80 @@ class TestModuleImporter {
|
|||||||
|
|
||||||
val act = { importer.importModule(importing) }
|
val act = { importer.importModule(importing) }
|
||||||
|
|
||||||
repeat(repetitions) { n ->
|
repeat(repetitions) { n -> withClue(count[n] + " call") {
|
||||||
assertFailsWith<ParseError>(count[n] + " call") { act() }.let {
|
shouldThrow<ParseError>() { act() }.let {
|
||||||
assertThat(it.position.file, equalTo(SourceCode.relative(imported).toString()))
|
it.position.file shouldBe SourceCode.relative(imported).toString()
|
||||||
assertThat("line; should be 1-based", it.position.line, equalTo(2))
|
withClue("line; should be 1-based") { it.position.line shouldBe 2 }
|
||||||
assertThat("startCol; should be 0-based", it.position.startCol, equalTo(6))
|
withClue("startCol; should be 0-based") { it.position.startCol shouldBe 6 }
|
||||||
assertThat("endCol; should be 0-based", it.position.endCol, equalTo(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
|
context("ImportLibraryModule") {
|
||||||
inner class ImportLibraryModule {
|
context("WithInvalidName") {
|
||||||
@Nested
|
test("testWithNonExistingName") {
|
||||||
inner class WithInvalidName {
|
|
||||||
@Test
|
|
||||||
fun testWithNonExistingName() {
|
|
||||||
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
||||||
val errors = ErrorReporterForTests()
|
val errors = ErrorReporterForTests(false)
|
||||||
val importer = makeImporter(errors, searchIn.invariantSeparatorsPathString)
|
val importer = makeImporter(errors, searchIn.invariantSeparatorsPathString)
|
||||||
val filenameNoExt = assumeNotExists(fixturesDir, "i_do_not_exist").name
|
val filenameNoExt = assumeNotExists(fixturesDir, "i_do_not_exist").name
|
||||||
val filenameWithExt = assumeNotExists(fixturesDir, "i_do_not_exist.p8").name
|
val filenameWithExt = assumeNotExists(fixturesDir, "i_do_not_exist.p8").name
|
||||||
|
|
||||||
repeat(2) { n ->
|
repeat(2) { n ->
|
||||||
val result = importer.importLibraryModule(filenameNoExt)
|
val result = importer.importLibraryModule(filenameNoExt)
|
||||||
assertThat(count[n] + " call / NO .p8 extension", result, Is(nullValue()))
|
withClue(count[n] + " call / NO .p8 extension") { result shouldBe null }
|
||||||
assertFalse(errors.noErrors(), count[n] + " call / NO .p8 extension")
|
withClue(count[n] + " call / NO .p8 extension") { errors.noErrors() shouldBe false }
|
||||||
assertEquals(errors.errors.single(), "no module found with name i_do_not_exist")
|
errors.errors.single() shouldContain "0:0: no module found with name i_do_not_exist"
|
||||||
errors.report()
|
errors.report()
|
||||||
assertThat(program.modules.size, equalTo(1))
|
program.modules.size shouldBe 1
|
||||||
|
|
||||||
val result2 = importer.importLibraryModule(filenameWithExt)
|
val result2 = importer.importLibraryModule(filenameWithExt)
|
||||||
assertThat(count[n] + " call / with .p8 extension", result2, Is(nullValue()))
|
withClue(count[n] + " call / with .p8 extension") { result2 shouldBe null }
|
||||||
assertFalse(importer.errors.noErrors(), count[n] + " call / with .p8 extension")
|
withClue(count[n] + " call / with .p8 extension") { importer.errors.noErrors() shouldBe false }
|
||||||
assertEquals(errors.errors.single(), "no module found with name i_do_not_exist.p8")
|
errors.errors.single() shouldContain "0:0: no module found with name i_do_not_exist.p8"
|
||||||
errors.report()
|
errors.report()
|
||||||
assertThat(program.modules.size, equalTo(1))
|
program.modules.size shouldBe 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nested
|
context("WithValidName") {
|
||||||
inner class WithValidName {
|
context("WithBadFile") {
|
||||||
@Nested
|
test("testWithSyntaxError") {
|
||||||
inner class WithBadFile {
|
|
||||||
@Test
|
|
||||||
fun testWithSyntaxError() {
|
|
||||||
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
||||||
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
|
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
|
||||||
val srcPath = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
|
val srcPath = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
|
||||||
|
|
||||||
repeat(2) { n ->
|
repeat(2) { n -> withClue(count[n] + " call") {
|
||||||
assertFailsWith<ParseError>(count[n] + " call")
|
shouldThrow<ParseError>()
|
||||||
{ importer.importLibraryModule(srcPath.nameWithoutExtension) }.let {
|
{
|
||||||
assertThat(it.position.file, equalTo(SourceCode.relative(srcPath).toString()))
|
importer.importLibraryModule(srcPath.nameWithoutExtension) }.let {
|
||||||
assertThat("line; should be 1-based", it.position.line, equalTo(2))
|
it.position.file shouldBe SourceCode.relative(srcPath).toString()
|
||||||
assertThat("startCol; should be 0-based", it.position.startCol, equalTo(6))
|
withClue("line; should be 1-based") { it.position.line shouldBe 2 }
|
||||||
assertThat("endCol; should be 0-based", it.position.endCol, equalTo(6))
|
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 searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
||||||
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
|
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
|
||||||
val importing = assumeReadableFile(fixturesDir, "import_file_with_syntax_error.p8")
|
val importing = assumeReadableFile(fixturesDir, "import_file_with_syntax_error.p8")
|
||||||
@ -297,29 +253,29 @@ class TestModuleImporter {
|
|||||||
|
|
||||||
val act = { importer.importLibraryModule(importing.nameWithoutExtension) }
|
val act = { importer.importLibraryModule(importing.nameWithoutExtension) }
|
||||||
|
|
||||||
repeat(repetitions) { n ->
|
repeat(repetitions) { n -> withClue(count[n] + " call") {
|
||||||
assertFailsWith<ParseError>(count[n] + " call") { act() }.let {
|
shouldThrow<ParseError>() {
|
||||||
assertThat(it.position.file, equalTo(SourceCode.relative(imported).toString()))
|
act() }.let {
|
||||||
assertThat("line; should be 1-based", it.position.line, equalTo(2))
|
it.position.file shouldBe SourceCode.relative(imported).toString()
|
||||||
assertThat("startCol; should be 0-based", it.position.startCol, equalTo(6))
|
withClue("line; should be 1-based") { it.position.line shouldBe 2 }
|
||||||
assertThat("endCol; should be 0-based", it.position.endCol, equalTo(6))
|
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))
|
withClue("imported module with error in it should not be present") { program.modules.size shouldBe 1 }
|
||||||
assertThat(program.modules[0].name, equalTo(internedStringsModuleName))
|
program.modules[0].name shouldBe internedStringsModuleName
|
||||||
importer.errors.report()
|
importer.errors.report()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testImportingFileWithSyntaxError_once") {
|
||||||
fun testImportingFileWithSyntaxError_once() {
|
|
||||||
doTestImportingFileWithSyntaxError(1)
|
doTestImportingFileWithSyntaxError(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testImportingFileWithSyntaxError_twice") {
|
||||||
fun testImportingFileWithSyntaxError_twice() {
|
|
||||||
doTestImportingFileWithSyntaxError(2)
|
doTestImportingFileWithSyntaxError(2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
58
compiler/test/ProjectConfig.kt
Normal file
58
compiler/test/ProjectConfig.kt
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package prog8tests
|
||||||
|
|
||||||
|
import io.kotest.core.config.AbstractProjectConfig
|
||||||
|
import io.kotest.core.listeners.Listener
|
||||||
|
import io.kotest.core.listeners.TestListener
|
||||||
|
import io.kotest.core.spec.Spec
|
||||||
|
import io.kotest.extensions.system.NoSystemErrListener
|
||||||
|
import io.kotest.extensions.system.NoSystemOutListener
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.io.PrintStream
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
|
object ProjectConfig : AbstractProjectConfig() {
|
||||||
|
override val parallelism = 2 // max(2, Runtime.getRuntime().availableProcessors() / 2)
|
||||||
|
// override fun listeners() = listOf(SystemOutToNullListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
//object SystemOutToNullListener: TestListener {
|
||||||
|
// override suspend fun beforeSpec(spec: Spec) = setup()
|
||||||
|
//
|
||||||
|
// private fun setup() {
|
||||||
|
// System.setOut(object: PrintStream(object: ByteArrayOutputStream(){
|
||||||
|
// override fun write(p0: Int) {
|
||||||
|
// // do nothing
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override fun write(b: ByteArray, off: Int, len: Int) {
|
||||||
|
// // do nothing
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override fun write(b: ByteArray) {
|
||||||
|
// // do nothing
|
||||||
|
// }
|
||||||
|
// }){}
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//object SystemErrToNullListener: TestListener {
|
||||||
|
// override suspend fun beforeSpec(spec: Spec) = setup()
|
||||||
|
//
|
||||||
|
// private fun setup() {
|
||||||
|
// System.setErr(object: PrintStream(object: ByteArrayOutputStream(){
|
||||||
|
// override fun write(p0: Int) {
|
||||||
|
// // do nothing
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override fun write(b: ByteArray, off: Int, len: Int) {
|
||||||
|
// // do nothing
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override fun write(b: ByteArray) {
|
||||||
|
// // do nothing
|
||||||
|
// }
|
||||||
|
// }){}
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//}
|
@ -1,21 +1,19 @@
|
|||||||
package prog8tests
|
package prog8tests
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test
|
import io.kotest.assertions.withClue
|
||||||
import org.junit.jupiter.api.TestInstance
|
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.Block
|
||||||
import prog8.ast.statements.Subroutine
|
import prog8.ast.statements.Subroutine
|
||||||
import prog8.compiler.target.C64Target
|
import prog8.compiler.target.C64Target
|
||||||
import prog8.optimizer.CallGraph
|
import prog8.compilerinterface.CallGraph
|
||||||
import prog8tests.helpers.assertSuccess
|
import prog8tests.helpers.assertSuccess
|
||||||
import prog8tests.helpers.compileText
|
import prog8tests.helpers.compileText
|
||||||
import kotlin.test.assertEquals
|
|
||||||
import kotlin.test.assertFalse
|
|
||||||
import kotlin.test.assertTrue
|
|
||||||
|
|
||||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
class TestCallgraph: FunSpec({
|
||||||
class TestCallgraph {
|
test("testGraphForEmptySubs") {
|
||||||
@Test
|
|
||||||
fun testGraphForEmptySubs() {
|
|
||||||
val sourcecode = """
|
val sourcecode = """
|
||||||
%import string
|
%import string
|
||||||
main {
|
main {
|
||||||
@ -26,34 +24,35 @@ class TestCallgraph {
|
|||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
val result = compileText(C64Target, false, sourcecode).assertSuccess()
|
val result = compileText(C64Target, false, sourcecode).assertSuccess()
|
||||||
val graph = CallGraph(result.programAst)
|
val graph = CallGraph(result.program)
|
||||||
|
|
||||||
assertEquals(1, graph.imports.size)
|
graph.imports.size shouldBe 1
|
||||||
assertEquals(1, graph.importedBy.size)
|
graph.importedBy.size shouldBe 1
|
||||||
val toplevelModule = result.programAst.toplevelModule
|
val toplevelModule = result.program.toplevelModule
|
||||||
val importedModule = graph.imports.getValue(toplevelModule).single()
|
val importedModule = graph.imports.getValue(toplevelModule).single()
|
||||||
assertEquals("string", importedModule.name)
|
importedModule.name shouldBe "string"
|
||||||
val importedBy = graph.importedBy.getValue(importedModule).single()
|
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))
|
graph.unused(toplevelModule) shouldBe false
|
||||||
assertFalse(graph.unused(importedModule))
|
graph.unused(importedModule) shouldBe false
|
||||||
|
|
||||||
val mainBlock = toplevelModule.statements.filterIsInstance<Block>().single()
|
val mainBlock = toplevelModule.statements.filterIsInstance<Block>().single()
|
||||||
for(stmt in mainBlock.statements) {
|
for(stmt in mainBlock.statements) {
|
||||||
val sub = stmt as Subroutine
|
val sub = stmt as Subroutine
|
||||||
assertFalse(sub in graph.calls)
|
graph.calls shouldNotContainKey sub
|
||||||
assertFalse(sub in graph.calledBy)
|
graph.calledBy shouldNotContainKey sub
|
||||||
|
|
||||||
if(sub === result.programAst.entrypoint)
|
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
|
else
|
||||||
assertTrue(graph.unused(sub))
|
graph.unused(sub) shouldBe true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testGraphForEmptyButReferencedSub") {
|
||||||
fun testGraphForEmptyButReferencedSub() {
|
|
||||||
val sourcecode = """
|
val sourcecode = """
|
||||||
%import string
|
%import string
|
||||||
main {
|
main {
|
||||||
@ -66,26 +65,34 @@ class TestCallgraph {
|
|||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
val result = compileText(C64Target, false, sourcecode).assertSuccess()
|
val result = compileText(C64Target, false, sourcecode).assertSuccess()
|
||||||
val graph = CallGraph(result.programAst)
|
val graph = CallGraph(result.program)
|
||||||
|
|
||||||
assertEquals(1, graph.imports.size)
|
graph.imports.size shouldBe 1
|
||||||
assertEquals(1, graph.importedBy.size)
|
graph.importedBy.size shouldBe 1
|
||||||
val toplevelModule = result.programAst.toplevelModule
|
val toplevelModule = result.program.toplevelModule
|
||||||
val importedModule = graph.imports.getValue(toplevelModule).single()
|
val importedModule = graph.imports.getValue(toplevelModule).single()
|
||||||
assertEquals("string", importedModule.name)
|
importedModule.name shouldBe "string"
|
||||||
val importedBy = graph.importedBy.getValue(importedModule).single()
|
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))
|
graph.unused(toplevelModule) shouldBe false
|
||||||
assertFalse(graph.unused(importedModule))
|
graph.unused(importedModule) shouldBe false
|
||||||
|
|
||||||
val mainBlock = toplevelModule.statements.filterIsInstance<Block>().single()
|
val mainBlock = toplevelModule.statements.filterIsInstance<Block>().single()
|
||||||
val startSub = mainBlock.statements.filterIsInstance<Subroutine>().single{it.name=="start"}
|
val startSub = mainBlock.statements.filterIsInstance<Subroutine>().single{it.name=="start"}
|
||||||
val emptySub = mainBlock.statements.filterIsInstance<Subroutine>().single{it.name=="empty"}
|
val emptySub = mainBlock.statements.filterIsInstance<Subroutine>().single{it.name=="empty"}
|
||||||
|
|
||||||
assertTrue(startSub in graph.calls, "start 'calls' (references) empty")
|
withClue("start 'calls' (references) empty") {
|
||||||
assertFalse(emptySub in graph.calls, "empty doesn't call anything")
|
graph.calls shouldContainKey startSub
|
||||||
assertTrue(emptySub in graph.calledBy, "empty gets 'called'")
|
}
|
||||||
assertFalse(startSub in graph.calledBy, "start doesn't get called (except as entrypoint ofc.)")
|
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,17 +1,19 @@
|
|||||||
package prog8tests
|
package prog8tests
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test
|
import io.kotest.assertions.fail
|
||||||
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.types.instanceOf
|
||||||
import prog8.ast.IFunctionCall
|
import prog8.ast.IFunctionCall
|
||||||
import prog8.ast.base.DataType
|
import prog8.ast.base.DataType
|
||||||
import prog8.ast.base.VarDeclType
|
import prog8.ast.base.VarDeclType
|
||||||
import prog8.ast.expressions.IdentifierReference
|
import prog8.ast.expressions.IdentifierReference
|
||||||
import prog8.ast.expressions.NumericLiteralValue
|
import prog8.ast.expressions.NumericLiteralValue
|
||||||
|
import prog8.ast.statements.Assignment
|
||||||
import prog8.compiler.target.Cx16Target
|
import prog8.compiler.target.Cx16Target
|
||||||
import prog8tests.helpers.assertSuccess
|
import prog8tests.helpers.assertSuccess
|
||||||
import prog8tests.helpers.compileText
|
import prog8tests.helpers.compileText
|
||||||
import kotlin.test.assertEquals
|
|
||||||
import kotlin.test.assertIs
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -19,11 +21,9 @@ import kotlin.test.assertIs
|
|||||||
* They are not really unit tests, but rather tests of the whole process,
|
* They are not really unit tests, but rather tests of the whole process,
|
||||||
* from source file loading all the way through to running 64tass.
|
* from source file loading all the way through to running 64tass.
|
||||||
*/
|
*/
|
||||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
class TestCompilerOnCharLit: FunSpec({
|
||||||
class TestCompilerOnCharLit {
|
|
||||||
|
|
||||||
@Test
|
test("testCharLitAsRomsubArg") {
|
||||||
fun testCharLitAsRomsubArg() {
|
|
||||||
val platform = Cx16Target
|
val platform = Cx16Target
|
||||||
val result = compileText(platform, false, """
|
val result = compileText(platform, false, """
|
||||||
main {
|
main {
|
||||||
@ -34,19 +34,19 @@ class TestCompilerOnCharLit {
|
|||||||
}
|
}
|
||||||
""").assertSuccess()
|
""").assertSuccess()
|
||||||
|
|
||||||
val program = result.programAst
|
val program = result.program
|
||||||
val startSub = program.entrypoint
|
val startSub = program.entrypoint
|
||||||
val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0]
|
val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0]
|
||||||
|
|
||||||
assertIs<NumericLiteralValue>(funCall.args[0],
|
withClue("char literal should have been replaced by ubyte literal") {
|
||||||
"char literal should have been replaced by ubyte literal")
|
funCall.args[0] shouldBe instanceOf<NumericLiteralValue>()
|
||||||
|
}
|
||||||
val arg = funCall.args[0] as NumericLiteralValue
|
val arg = funCall.args[0] as NumericLiteralValue
|
||||||
assertEquals(DataType.UBYTE, arg.type)
|
arg.type shouldBe DataType.UBYTE
|
||||||
assertEquals(platform.encodeString("\n", false)[0], arg.number.toShort())
|
arg.number shouldBe platform.encodeString("\n", false)[0].toDouble()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testCharVarAsRomsubArg") {
|
||||||
fun testCharVarAsRomsubArg() {
|
|
||||||
val platform = Cx16Target
|
val platform = Cx16Target
|
||||||
val result = compileText(platform, false, """
|
val result = compileText(platform, false, """
|
||||||
main {
|
main {
|
||||||
@ -58,30 +58,35 @@ class TestCompilerOnCharLit {
|
|||||||
}
|
}
|
||||||
""").assertSuccess()
|
""").assertSuccess()
|
||||||
|
|
||||||
val program = result.programAst
|
val program = result.program
|
||||||
val startSub = program.entrypoint
|
val startSub = program.entrypoint
|
||||||
val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0]
|
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 arg = funCall.args[0] as IdentifierReference
|
||||||
val decl = arg.targetVarDecl(program)!!
|
val decl = arg.targetVarDecl(program)!!
|
||||||
assertEquals(VarDeclType.VAR, decl.type)
|
decl.type shouldBe VarDeclType.VAR
|
||||||
assertEquals(DataType.UBYTE, decl.datatype)
|
decl.datatype shouldBe DataType.UBYTE
|
||||||
|
|
||||||
// TODO: assertIs<CharLiteral>(decl.value,
|
// TODO: assertIs<CharLiteral>(decl.value,
|
||||||
// "char literals should be kept until code gen")
|
// "char literals should be kept until code gen")
|
||||||
// val initializerValue = decl.value as CharLiteral
|
// val initializerValue = decl.value as CharLiteral
|
||||||
// assertEquals('\n', (initializerValue as CharLiteral).value)
|
// assertEquals('\n', (initializerValue as CharLiteral).value)
|
||||||
|
|
||||||
assertIs<NumericLiteralValue>(decl.value,
|
withClue("initializer value should have been moved to separate assignment"){
|
||||||
"char literal should have been replaced by ubyte literal")
|
decl.value shouldBe null
|
||||||
val initializerValue = decl.value as NumericLiteralValue
|
}
|
||||||
assertEquals(DataType.UBYTE, initializerValue.type)
|
val assignInitialValue = decl.nextSibling() as Assignment
|
||||||
assertEquals(platform.encodeString("\n", false)[0], initializerValue.number.toShort())
|
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
|
||||||
|
initializerValue.type shouldBe DataType.UBYTE
|
||||||
|
initializerValue.number shouldBe platform.encodeString("\n", false)[0].toDouble()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testCharConstAsRomsubArg") {
|
||||||
fun testCharConstAsRomsubArg() {
|
|
||||||
val platform = Cx16Target
|
val platform = Cx16Target
|
||||||
val result = compileText(platform, false, """
|
val result = compileText(platform, false, """
|
||||||
main {
|
main {
|
||||||
@ -93,7 +98,7 @@ class TestCompilerOnCharLit {
|
|||||||
}
|
}
|
||||||
""").assertSuccess()
|
""").assertSuccess()
|
||||||
|
|
||||||
val program = result.programAst
|
val program = result.program
|
||||||
val startSub = program.entrypoint
|
val startSub = program.entrypoint
|
||||||
val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0]
|
val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0]
|
||||||
|
|
||||||
@ -101,20 +106,15 @@ class TestCompilerOnCharLit {
|
|||||||
when (val arg = funCall.args[0]) {
|
when (val arg = funCall.args[0]) {
|
||||||
is IdentifierReference -> {
|
is IdentifierReference -> {
|
||||||
val decl = arg.targetVarDecl(program)!!
|
val decl = arg.targetVarDecl(program)!!
|
||||||
assertEquals(VarDeclType.CONST, decl.type)
|
decl.type shouldBe VarDeclType.CONST
|
||||||
assertEquals(DataType.UBYTE, decl.datatype)
|
decl.datatype shouldBe DataType.UBYTE
|
||||||
assertEquals(
|
(decl.value as NumericLiteralValue).number shouldBe platform.encodeString("\n", false)[0]
|
||||||
platform.encodeString("\n", false)[0],
|
|
||||||
(decl.value as NumericLiteralValue).number.toShort())
|
|
||||||
}
|
}
|
||||||
is NumericLiteralValue -> {
|
is NumericLiteralValue -> {
|
||||||
assertEquals(
|
arg.number shouldBe platform.encodeString("\n", false)[0].toDouble()
|
||||||
platform.encodeString("\n", false)[0],
|
|
||||||
arg.number.toShort())
|
|
||||||
}
|
}
|
||||||
else -> assertIs<IdentifierReference>(funCall.args[0]) // make test fail
|
else -> fail("invalid arg type") // funCall.args[0] shouldBe instanceOf<IdentifierReference>() // make test fail
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
})
|
||||||
|
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
package prog8tests
|
package prog8tests
|
||||||
|
|
||||||
import org.junit.jupiter.api.Disabled
|
import io.kotest.core.spec.style.FunSpec
|
||||||
import org.junit.jupiter.api.DynamicTest
|
import prog8.compiler.CompilationResult
|
||||||
import org.junit.jupiter.api.DynamicTest.dynamicTest
|
import prog8.compiler.CompilerArguments
|
||||||
import org.junit.jupiter.api.TestFactory
|
|
||||||
import org.junit.jupiter.api.TestInstance
|
|
||||||
import prog8.compiler.compileProgram
|
import prog8.compiler.compileProgram
|
||||||
import prog8.compiler.target.C64Target
|
import prog8.compiler.target.C64Target
|
||||||
import prog8.compiler.target.Cx16Target
|
import prog8.compiler.target.Cx16Target
|
||||||
import prog8.compiler.target.ICompilationTarget
|
import prog8.compilerinterface.ICompilationTarget
|
||||||
import prog8tests.helpers.*
|
import prog8tests.ast.helpers.*
|
||||||
|
import prog8tests.helpers.assertSuccess
|
||||||
|
import java.nio.file.Path
|
||||||
import kotlin.io.path.absolute
|
import kotlin.io.path.absolute
|
||||||
import kotlin.io.path.exists
|
import kotlin.io.path.exists
|
||||||
|
|
||||||
@ -19,39 +19,99 @@ import kotlin.io.path.exists
|
|||||||
* They are not really unit tests, but rather tests of the whole process,
|
* They are not really unit tests, but rather tests of the whole process,
|
||||||
* from source file loading all the way through to running 64tass.
|
* 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 {
|
private val examplesDir = assumeDirectory(workingDir, "../examples")
|
||||||
val searchIn = mutableListOf(examplesDir)
|
|
||||||
if (platform == Cx16Target) {
|
private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilationTarget): CompilationResult {
|
||||||
searchIn.add(0, assumeDirectory(examplesDir, "cx16"))
|
val args = CompilerArguments(
|
||||||
}
|
filepath,
|
||||||
val filepath = searchIn
|
optimize,
|
||||||
.map { it.resolve("$name.p8") }
|
optimizeFloatExpressions = true,
|
||||||
.map { it.normalize().absolute() }
|
writeAssembly = true,
|
||||||
.map { workingDir.relativize(it) }
|
slowCodegenWarnings = false,
|
||||||
.first { it.exists() }
|
quietAssembler = true,
|
||||||
val displayName = "${examplesDir.relativize(filepath.absolute())}: ${platform.name}, optimize=$optimize"
|
compilationTarget = target.name,
|
||||||
return dynamicTest(displayName) {
|
outputDir = outputDir
|
||||||
compileProgram(
|
)
|
||||||
filepath,
|
return compileProgram(args)
|
||||||
optimize,
|
}
|
||||||
writeAssembly = true,
|
|
||||||
slowCodegenWarnings = false,
|
private fun prepareTestFiles(source: String, optimize: Boolean, target: ICompilationTarget): Pair<String, Path> {
|
||||||
compilationTarget = platform.name,
|
val searchIn = mutableListOf(examplesDir)
|
||||||
sourceDirs = listOf(),
|
if (target == Cx16Target) {
|
||||||
outputDir
|
searchIn.add(0, assumeDirectory(examplesDir, "cx16"))
|
||||||
).assertSuccess("; $displayName")
|
}
|
||||||
|
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
|
class TestCompilerOnExamplesCx16: FunSpec({
|
||||||
// @Disabled("disable to save some time")
|
|
||||||
fun bothCx16AndC64() = mapCombinations(
|
val onlyCx16 = cartesianProduct(
|
||||||
dim1 = listOf(
|
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",
|
"animals",
|
||||||
"balls",
|
"balls",
|
||||||
"cube3d",
|
"cube3d",
|
||||||
@ -74,48 +134,18 @@ class TestCompilerOnExamples {
|
|||||||
"tehtriz",
|
"tehtriz",
|
||||||
"textelite",
|
"textelite",
|
||||||
),
|
),
|
||||||
dim2 = listOf(Cx16Target, C64Target),
|
listOf(false, true)
|
||||||
dim3 = listOf(false, true),
|
|
||||||
combine3 = ::makeDynamicCompilerTest
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@TestFactory
|
bothCx16AndC64.forEach {
|
||||||
// @Disabled("disable to save some time")
|
val (source, optimize) = it
|
||||||
fun onlyC64() = mapCombinations(
|
val (displayNameC64, filepathC64) = prepareTestFiles(source, optimize, C64Target)
|
||||||
dim1 = listOf(
|
val (displayNameCx16, filepathCx16) = prepareTestFiles(source, optimize, Cx16Target)
|
||||||
"balloonflight",
|
test(displayNameC64) {
|
||||||
"bdmusic",
|
compileTheThing(filepathC64, optimize, C64Target).assertSuccess()
|
||||||
"bdmusic-irq",
|
}
|
||||||
"charset",
|
test(displayNameCx16) {
|
||||||
"cube3d-sprites",
|
compileTheThing(filepathCx16, optimize, Cx16Target).assertSuccess()
|
||||||
"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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
@ -1,17 +1,20 @@
|
|||||||
package prog8tests
|
package prog8tests
|
||||||
|
|
||||||
import org.junit.jupiter.api.*
|
import io.kotest.assertions.withClue
|
||||||
import org.junit.jupiter.api.DynamicTest.dynamicTest
|
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.AddressOf
|
||||||
import prog8.ast.expressions.IdentifierReference
|
import prog8.ast.expressions.IdentifierReference
|
||||||
import prog8.ast.expressions.StringLiteralValue
|
import prog8.ast.expressions.StringLiteralValue
|
||||||
import prog8.ast.statements.FunctionCallStatement
|
import prog8.ast.statements.FunctionCallStatement
|
||||||
import prog8.ast.statements.Label
|
import prog8.ast.statements.Label
|
||||||
import prog8.compiler.target.Cx16Target
|
import prog8.compiler.target.Cx16Target
|
||||||
import prog8tests.helpers.*
|
import prog8tests.ast.helpers.*
|
||||||
|
import prog8tests.helpers.assertFailure
|
||||||
|
import prog8tests.helpers.assertSuccess
|
||||||
|
import prog8tests.helpers.compileFile
|
||||||
import kotlin.io.path.name
|
import kotlin.io.path.name
|
||||||
import kotlin.test.assertEquals
|
|
||||||
import kotlin.test.assertNotEquals
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -19,14 +22,11 @@ import kotlin.test.assertNotEquals
|
|||||||
* They are not really unit tests, but rather tests of the whole process,
|
* They are not really unit tests, but rather tests of the whole process,
|
||||||
* from source file loading all the way through to running 64tass.
|
* from source file loading all the way through to running 64tass.
|
||||||
*/
|
*/
|
||||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
class TestCompilerOnImportsAndIncludes: FunSpec({
|
||||||
class TestCompilerOnImportsAndIncludes {
|
|
||||||
|
|
||||||
@Nested
|
context("Import") {
|
||||||
inner class Import {
|
|
||||||
|
|
||||||
@Test
|
test("testImportFromSameFolder") {
|
||||||
fun testImportFromSameFolder() {
|
|
||||||
val filepath = assumeReadableFile(fixturesDir, "importFromSameFolder.p8")
|
val filepath = assumeReadableFile(fixturesDir, "importFromSameFolder.p8")
|
||||||
assumeReadableFile(fixturesDir, "foo_bar.p8")
|
assumeReadableFile(fixturesDir, "foo_bar.p8")
|
||||||
|
|
||||||
@ -34,24 +34,22 @@ class TestCompilerOnImportsAndIncludes {
|
|||||||
val result = compileFile(platform, optimize = false, fixturesDir, filepath.name)
|
val result = compileFile(platform, optimize = false, fixturesDir, filepath.name)
|
||||||
.assertSuccess()
|
.assertSuccess()
|
||||||
|
|
||||||
val program = result.programAst
|
val program = result.program
|
||||||
val startSub = program.entrypoint
|
val startSub = program.entrypoint
|
||||||
val strLits = startSub.statements
|
val strLits = startSub.statements
|
||||||
.filterIsInstance<FunctionCallStatement>()
|
.filterIsInstance<FunctionCallStatement>()
|
||||||
.map { it.args[0] as IdentifierReference }
|
.map { it.args[0] as IdentifierReference }
|
||||||
.map { it.targetVarDecl(program)!!.value as StringLiteralValue }
|
.map { it.targetVarDecl(program)!!.value as StringLiteralValue }
|
||||||
|
|
||||||
assertEquals("main.bar", strLits[0].value)
|
strLits[0].value shouldBe "main.bar"
|
||||||
assertEquals("foo.bar", strLits[1].value)
|
strLits[1].value shouldBe "foo.bar"
|
||||||
assertEquals("main", strLits[0].definingScope.name)
|
strLits[0].definingScope.name shouldBe "main"
|
||||||
assertEquals("foo", strLits[1].definingScope.name)
|
strLits[1].definingScope.name shouldBe "foo"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nested
|
context("AsmInclude") {
|
||||||
inner class AsmInclude {
|
test("testAsmIncludeFromSameFolder") {
|
||||||
@Test
|
|
||||||
fun testAsmIncludeFromSameFolder() {
|
|
||||||
val filepath = assumeReadableFile(fixturesDir, "asmIncludeFromSameFolder.p8")
|
val filepath = assumeReadableFile(fixturesDir, "asmIncludeFromSameFolder.p8")
|
||||||
assumeReadableFile(fixturesDir, "foo_bar.asm")
|
assumeReadableFile(fixturesDir, "foo_bar.asm")
|
||||||
|
|
||||||
@ -59,27 +57,25 @@ class TestCompilerOnImportsAndIncludes {
|
|||||||
val result = compileFile(platform, optimize = false, fixturesDir, filepath.name)
|
val result = compileFile(platform, optimize = false, fixturesDir, filepath.name)
|
||||||
.assertSuccess()
|
.assertSuccess()
|
||||||
|
|
||||||
val program = result.programAst
|
val program = result.program
|
||||||
val startSub = program.entrypoint
|
val startSub = program.entrypoint
|
||||||
val args = startSub.statements
|
val args = startSub.statements
|
||||||
.filterIsInstance<FunctionCallStatement>()
|
.filterIsInstance<FunctionCallStatement>()
|
||||||
.map { it.args[0] }
|
.map { it.args[0] }
|
||||||
|
|
||||||
val str0 = (args[0] as IdentifierReference).targetVarDecl(program)!!.value as StringLiteralValue
|
val str0 = (args[0] as IdentifierReference).targetVarDecl(program)!!.value as StringLiteralValue
|
||||||
assertEquals("main.bar", str0.value)
|
str0.value shouldBe "main.bar"
|
||||||
assertEquals("main", str0.definingScope.name)
|
str0.definingScope.name shouldBe "main"
|
||||||
|
|
||||||
val id1 = (args[1] as AddressOf).identifier
|
val id1 = (args[1] as AddressOf).identifier
|
||||||
val lbl1 = id1.targetStatement(program) as Label
|
val lbl1 = id1.targetStatement(program) as Label
|
||||||
assertEquals("foo_bar", lbl1.name)
|
lbl1.name shouldBe "foo_bar"
|
||||||
assertEquals("start", lbl1.definingScope.name)
|
lbl1.definingScope.name shouldBe "start"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nested
|
context("Asmbinary") {
|
||||||
inner class Asmbinary {
|
test("testAsmbinaryDirectiveWithNonExistingFile") {
|
||||||
@Test
|
|
||||||
fun testAsmbinaryDirectiveWithNonExistingFile() {
|
|
||||||
val p8Path = assumeReadableFile(fixturesDir, "asmBinaryNonExisting.p8")
|
val p8Path = assumeReadableFile(fixturesDir, "asmBinaryNonExisting.p8")
|
||||||
assumeNotExists(fixturesDir, "i_do_not_exist.bin")
|
assumeNotExists(fixturesDir, "i_do_not_exist.bin")
|
||||||
|
|
||||||
@ -87,8 +83,7 @@ class TestCompilerOnImportsAndIncludes {
|
|||||||
.assertFailure()
|
.assertFailure()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testAsmbinaryDirectiveWithNonReadableFile") {
|
||||||
fun testAsmbinaryDirectiveWithNonReadableFile() {
|
|
||||||
val p8Path = assumeReadableFile(fixturesDir, "asmBinaryNonReadable.p8")
|
val p8Path = assumeReadableFile(fixturesDir, "asmBinaryNonReadable.p8")
|
||||||
assumeDirectory(fixturesDir, "subFolder")
|
assumeDirectory(fixturesDir, "subFolder")
|
||||||
|
|
||||||
@ -96,31 +91,30 @@ class TestCompilerOnImportsAndIncludes {
|
|||||||
.assertFailure()
|
.assertFailure()
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestFactory
|
val tests = listOf(
|
||||||
fun asmbinaryDirectiveWithExistingBinFile(): Iterable<DynamicTest> =
|
|
||||||
listOf(
|
|
||||||
Triple("same ", "asmBinaryFromSameFolder.p8", "do_nothing1.bin"),
|
Triple("same ", "asmBinaryFromSameFolder.p8", "do_nothing1.bin"),
|
||||||
Triple("sub", "asmBinaryFromSubFolder.p8", "subFolder/do_nothing2.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 == workinDir
|
|
||||||
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)
|
tests.forEach {
|
||||||
.assertSuccess(
|
val (where, p8Str, binStr) = it
|
||||||
"argument to assembler directive .binary " +
|
test("%asmbinary from ${where}folder") {
|
||||||
"should be relative to the generated .asm file (in output dir), " +
|
val p8Path = assumeReadableFile(fixturesDir, p8Str)
|
||||||
"NOT relative to .p8 neither current working dir"
|
// 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,23 +1,25 @@
|
|||||||
package prog8tests
|
package prog8tests
|
||||||
|
|
||||||
import org.junit.jupiter.api.DynamicTest.dynamicTest
|
import io.kotest.assertions.withClue
|
||||||
import org.junit.jupiter.api.Test
|
import io.kotest.core.spec.style.FunSpec
|
||||||
import org.junit.jupiter.api.TestFactory
|
import io.kotest.matchers.shouldBe
|
||||||
import org.junit.jupiter.api.TestInstance
|
import io.kotest.matchers.string.shouldContain
|
||||||
|
import io.kotest.matchers.types.instanceOf
|
||||||
import prog8.ast.base.DataType
|
import prog8.ast.base.DataType
|
||||||
|
import prog8.ast.base.Position
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.statements.ForLoop
|
import prog8.ast.statements.ForLoop
|
||||||
import prog8.ast.statements.Subroutine
|
|
||||||
import prog8.ast.statements.VarDecl
|
import prog8.ast.statements.VarDecl
|
||||||
import prog8.compiler.astprocessing.size
|
import prog8.compiler.printProgram
|
||||||
import prog8.compiler.astprocessing.toConstantIntegerRange
|
|
||||||
import prog8.compiler.target.C64Target
|
import prog8.compiler.target.C64Target
|
||||||
import prog8.compiler.target.Cx16Target
|
import prog8.compiler.target.Cx16Target
|
||||||
|
import prog8.compilerinterface.size
|
||||||
|
import prog8.compilerinterface.toConstantIntegerRange
|
||||||
|
import prog8tests.ast.helpers.cartesianProduct
|
||||||
|
import prog8tests.helpers.ErrorReporterForTests
|
||||||
import prog8tests.helpers.assertFailure
|
import prog8tests.helpers.assertFailure
|
||||||
import prog8tests.helpers.assertSuccess
|
import prog8tests.helpers.assertSuccess
|
||||||
import prog8tests.helpers.compileText
|
import prog8tests.helpers.compileText
|
||||||
import prog8tests.helpers.mapCombinations
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -25,13 +27,11 @@ import kotlin.test.assertEquals
|
|||||||
* They are not really unit tests, but rather tests of the whole process,
|
* They are not really unit tests, but rather tests of the whole process,
|
||||||
* from source file loading all the way through to running 64tass.
|
* from source file loading all the way through to running 64tass.
|
||||||
*/
|
*/
|
||||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
class TestCompilerOnRanges: FunSpec({
|
||||||
class TestCompilerOnRanges {
|
|
||||||
|
|
||||||
@Test
|
test("testUByteArrayInitializerWithRange_char_to_char") {
|
||||||
fun testUByteArrayInitializerWithRange_char_to_char() {
|
|
||||||
val platform = Cx16Target
|
val platform = Cx16Target
|
||||||
val result = compileText(platform, true, """
|
val result = compileText(platform, false, """
|
||||||
main {
|
main {
|
||||||
sub start() {
|
sub start() {
|
||||||
ubyte[] cs = @'a' to 'z' ; values are computed at compile time
|
ubyte[] cs = @'a' to 'z' ; values are computed at compile time
|
||||||
@ -40,7 +40,7 @@ class TestCompilerOnRanges {
|
|||||||
}
|
}
|
||||||
""").assertSuccess()
|
""").assertSuccess()
|
||||||
|
|
||||||
val program = result.programAst
|
val program = result.program
|
||||||
val startSub = program.entrypoint
|
val startSub = program.entrypoint
|
||||||
val decl = startSub
|
val decl = startSub
|
||||||
.statements.filterIsInstance<VarDecl>()[0]
|
.statements.filterIsInstance<VarDecl>()[0]
|
||||||
@ -52,12 +52,15 @@ class TestCompilerOnRanges {
|
|||||||
val expectedStr = "$expectedStart .. $expectedEnd"
|
val expectedStr = "$expectedStart .. $expectedEnd"
|
||||||
|
|
||||||
val actualStr = "${rhsValues.first()} .. ${rhsValues.last()}"
|
val actualStr = "${rhsValues.first()} .. ${rhsValues.last()}"
|
||||||
assertEquals(expectedStr, actualStr,".first .. .last")
|
withClue(".first .. .last") {
|
||||||
assertEquals(expectedEnd - expectedStart + 1, rhsValues.last() - rhsValues.first() + 1, "rangeExpr.size()")
|
actualStr shouldBe expectedStr
|
||||||
|
}
|
||||||
|
withClue("rangeExpr.size()") {
|
||||||
|
(rhsValues.last() - rhsValues.first() + 1) shouldBe (expectedEnd - expectedStart + 1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testFloatArrayInitializerWithRange_char_to_char") {
|
||||||
fun testFloatArrayInitializerWithRange_char_to_char() {
|
|
||||||
val platform = C64Target
|
val platform = C64Target
|
||||||
val result = compileText(platform, optimize = false, """
|
val result = compileText(platform, optimize = false, """
|
||||||
%option enable_floats
|
%option enable_floats
|
||||||
@ -69,7 +72,7 @@ class TestCompilerOnRanges {
|
|||||||
}
|
}
|
||||||
""").assertSuccess()
|
""").assertSuccess()
|
||||||
|
|
||||||
val program = result.programAst
|
val program = result.program
|
||||||
val startSub = program.entrypoint
|
val startSub = program.entrypoint
|
||||||
val decl = startSub
|
val decl = startSub
|
||||||
.statements.filterIsInstance<VarDecl>()[0]
|
.statements.filterIsInstance<VarDecl>()[0]
|
||||||
@ -81,45 +84,34 @@ class TestCompilerOnRanges {
|
|||||||
val expectedStr = "$expectedStart .. $expectedEnd"
|
val expectedStr = "$expectedStart .. $expectedEnd"
|
||||||
|
|
||||||
val actualStr = "${rhsValues.first()} .. ${rhsValues.last()}"
|
val actualStr = "${rhsValues.first()} .. ${rhsValues.last()}"
|
||||||
assertEquals(expectedStr, actualStr,".first .. .last")
|
withClue(".first .. .last") {
|
||||||
assertEquals(expectedEnd - expectedStart + 1, rhsValues.size, "rangeExpr.size()")
|
actualStr shouldBe expectedStr
|
||||||
|
}
|
||||||
|
withClue("rangeExpr.size()") {
|
||||||
|
rhsValues.size shouldBe (expectedEnd - expectedStart + 1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Subroutine.decl(varName: String): VarDecl {
|
context("floatArrayInitializerWithRange") {
|
||||||
return statements.filterIsInstance<VarDecl>()
|
val combos = cartesianProduct(
|
||||||
.first { it.name == varName }
|
listOf("", "42", "41"), // sizeInDecl
|
||||||
}
|
listOf("%option enable_floats", ""), // optEnableFloats
|
||||||
inline fun <reified T : Expression> VarDecl.rhs() : T {
|
listOf(Cx16Target, C64Target), // platform
|
||||||
return value as T
|
listOf(false, true) // optimize
|
||||||
}
|
)
|
||||||
inline fun <reified T : Expression> ArrayLiteralValue.elements() : List<T> {
|
|
||||||
return value.map { it as T }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <N : Number> assertEndpoints(expFirst: N, expLast: N, actual: Iterable<N>, msg: String = ".first .. .last") {
|
combos.forEach {
|
||||||
val expectedStr = "$expFirst .. $expLast"
|
val (sizeInDecl, optEnableFloats, platform, optimize) = it
|
||||||
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 ->
|
|
||||||
val displayName =
|
val displayName =
|
||||||
"test failed for: " +
|
|
||||||
when (sizeInDecl) {
|
when (sizeInDecl) {
|
||||||
"" -> "no"
|
"" -> "no"
|
||||||
"42" -> "correct"
|
"42" -> "correct"
|
||||||
else -> "wrong"
|
else -> "wrong"
|
||||||
} + " array size given" +
|
} + " array size given" +
|
||||||
", " + (if (optEnableFloats == "") "without" else "with") + " %option enable_floats" +
|
", " + (if (optEnableFloats == "") "without" else "with") + " %option enable_floats" +
|
||||||
", ${platform.name}, optimize: $optimize"
|
", ${platform.name}, optimize: $optimize"
|
||||||
dynamicTest(displayName) {
|
|
||||||
|
test(displayName) {
|
||||||
val result = compileText(platform, optimize, """
|
val result = compileText(platform, optimize, """
|
||||||
$optEnableFloats
|
$optEnableFloats
|
||||||
main {
|
main {
|
||||||
@ -133,12 +125,12 @@ class TestCompilerOnRanges {
|
|||||||
result.assertSuccess()
|
result.assertSuccess()
|
||||||
else
|
else
|
||||||
result.assertFailure()
|
result.assertFailure()
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
@Test
|
test("testForLoopWithRange_char_to_char") {
|
||||||
fun testForLoopWithRange_char_to_char() {
|
|
||||||
val platform = Cx16Target
|
val platform = Cx16Target
|
||||||
val result = compileText(platform, optimize = true, """
|
val result = compileText(platform, optimize = true, """
|
||||||
main {
|
main {
|
||||||
@ -151,7 +143,7 @@ class TestCompilerOnRanges {
|
|||||||
}
|
}
|
||||||
""").assertSuccess()
|
""").assertSuccess()
|
||||||
|
|
||||||
val program = result.programAst
|
val program = result.program
|
||||||
val startSub = program.entrypoint
|
val startSub = program.entrypoint
|
||||||
val iterable = startSub
|
val iterable = startSub
|
||||||
.statements.filterIsInstance<ForLoop>()
|
.statements.filterIsInstance<ForLoop>()
|
||||||
@ -162,14 +154,17 @@ class TestCompilerOnRanges {
|
|||||||
val expectedEnd = platform.encodeString("f", false)[0].toInt()
|
val expectedEnd = platform.encodeString("f", false)[0].toInt()
|
||||||
val expectedStr = "$expectedStart .. $expectedEnd"
|
val expectedStr = "$expectedStart .. $expectedEnd"
|
||||||
|
|
||||||
val intProgression = rangeExpr.toConstantIntegerRange(platform)
|
val intProgression = rangeExpr.toConstantIntegerRange()
|
||||||
val actualStr = "${intProgression?.first} .. ${intProgression?.last}"
|
val actualStr = "${intProgression?.first} .. ${intProgression?.last}"
|
||||||
assertEquals(expectedStr, actualStr,".first .. .last")
|
withClue(".first .. .last") {
|
||||||
assertEquals(expectedEnd - expectedStart + 1, rangeExpr.size(platform), "rangeExpr.size()")
|
actualStr shouldBe expectedStr
|
||||||
|
}
|
||||||
|
withClue("rangeExpr.size()") {
|
||||||
|
rangeExpr.size() shouldBe (expectedEnd - expectedStart + 1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testForLoopWithRange_bool_to_bool") {
|
||||||
fun testForLoopWithRange_bool_to_bool() {
|
|
||||||
val platform = Cx16Target
|
val platform = Cx16Target
|
||||||
val result = compileText(platform, optimize = true, """
|
val result = compileText(platform, optimize = true, """
|
||||||
main {
|
main {
|
||||||
@ -182,21 +177,20 @@ class TestCompilerOnRanges {
|
|||||||
}
|
}
|
||||||
""").assertSuccess()
|
""").assertSuccess()
|
||||||
|
|
||||||
val program = result.programAst
|
val program = result.program
|
||||||
val startSub = program.entrypoint
|
val startSub = program.entrypoint
|
||||||
val rangeExpr = startSub
|
val rangeExpr = startSub
|
||||||
.statements.filterIsInstance<ForLoop>()
|
.statements.filterIsInstance<ForLoop>()
|
||||||
.map { it.iterable }
|
.map { it.iterable }
|
||||||
.filterIsInstance<RangeExpr>()[0]
|
.filterIsInstance<RangeExpr>()[0]
|
||||||
|
|
||||||
assertEquals(2, rangeExpr.size(platform))
|
rangeExpr.size() shouldBe 2
|
||||||
val intProgression = rangeExpr.toConstantIntegerRange(platform)
|
val intProgression = rangeExpr.toConstantIntegerRange()
|
||||||
assertEquals(0, intProgression?.first)
|
intProgression?.first shouldBe 0
|
||||||
assertEquals(1, intProgression?.last)
|
intProgression?.last shouldBe 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testForLoopWithRange_ubyte_to_ubyte") {
|
||||||
fun testForLoopWithRange_ubyte_to_ubyte() {
|
|
||||||
val platform = Cx16Target
|
val platform = Cx16Target
|
||||||
val result = compileText(platform, optimize = true, """
|
val result = compileText(platform, optimize = true, """
|
||||||
main {
|
main {
|
||||||
@ -209,21 +203,21 @@ class TestCompilerOnRanges {
|
|||||||
}
|
}
|
||||||
""").assertSuccess()
|
""").assertSuccess()
|
||||||
|
|
||||||
val program = result.programAst
|
val program = result.program
|
||||||
val startSub = program.entrypoint
|
val startSub = program.entrypoint
|
||||||
val rangeExpr = startSub
|
val rangeExpr = startSub
|
||||||
.statements.filterIsInstance<ForLoop>()
|
.statements.filterIsInstance<ForLoop>()
|
||||||
.map { it.iterable }
|
.map { it.iterable }
|
||||||
.filterIsInstance<RangeExpr>()[0]
|
.filterIsInstance<RangeExpr>()[0]
|
||||||
|
|
||||||
assertEquals(9, rangeExpr.size(platform))
|
rangeExpr.size() shouldBe 9
|
||||||
val intProgression = rangeExpr.toConstantIntegerRange(platform)
|
val intProgression = rangeExpr.toConstantIntegerRange()
|
||||||
assertEquals(1, intProgression?.first)
|
intProgression?.first shouldBe 1
|
||||||
assertEquals(9, intProgression?.last)
|
intProgression?.last shouldBe 9
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testForLoopWithRange_str_downto_str") {
|
||||||
fun testForLoopWithRange_str_downto_str() {
|
val errors = ErrorReporterForTests()
|
||||||
compileText(Cx16Target, true, """
|
compileText(Cx16Target, true, """
|
||||||
main {
|
main {
|
||||||
sub start() {
|
sub start() {
|
||||||
@ -233,12 +227,13 @@ class TestCompilerOnRanges {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
""").assertFailure()
|
""", errors, false).assertFailure()
|
||||||
//TODO("test exact compile error(s)")
|
errors.errors.size shouldBe 2
|
||||||
|
errors.errors[0] shouldContain ".p8:5:29: range expression from value must be integer"
|
||||||
|
errors.errors[1] shouldContain ".p8:5:44: range expression to value must be integer"
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testForLoopWithIterable_str") {
|
||||||
fun testForLoopWithIterable_str() {
|
|
||||||
val result = compileText(Cx16Target, false, """
|
val result = compileText(Cx16Target, false, """
|
||||||
main {
|
main {
|
||||||
sub start() {
|
sub start() {
|
||||||
@ -250,15 +245,61 @@ class TestCompilerOnRanges {
|
|||||||
}
|
}
|
||||||
""").assertSuccess()
|
""").assertSuccess()
|
||||||
|
|
||||||
val program = result.programAst
|
val program = result.program
|
||||||
val startSub = program.entrypoint
|
val startSub = program.entrypoint
|
||||||
val iterable = startSub
|
val iterable = startSub
|
||||||
.statements.filterIsInstance<ForLoop>()
|
.statements.filterIsInstance<ForLoop>()
|
||||||
.map { it.iterable }
|
.map { it.iterable }
|
||||||
.filterIsInstance<IdentifierReference>()[0]
|
.filterIsInstance<IdentifierReference>()[0]
|
||||||
|
|
||||||
assertEquals(DataType.STR, iterable.inferType(program).getOr(DataType.UNDEFINED))
|
iterable.inferType(program).getOr(DataType.UNDEFINED) shouldBe DataType.STR
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
test("testRangeExprNumericSize") {
|
||||||
|
val expr = RangeExpr(
|
||||||
|
NumericLiteralValue.optimalInteger(10, Position.DUMMY),
|
||||||
|
NumericLiteralValue.optimalInteger(20, Position.DUMMY),
|
||||||
|
NumericLiteralValue.optimalInteger(2, Position.DUMMY),
|
||||||
|
Position.DUMMY)
|
||||||
|
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,12 +1,15 @@
|
|||||||
package prog8tests
|
package prog8tests
|
||||||
|
|
||||||
import org.junit.jupiter.api.AfterAll
|
import io.kotest.core.spec.style.FunSpec
|
||||||
import org.junit.jupiter.api.BeforeAll
|
import prog8.compiler.CompilationResult
|
||||||
import org.junit.jupiter.api.Test
|
import prog8.compiler.CompilerArguments
|
||||||
import org.junit.jupiter.api.TestInstance
|
|
||||||
import prog8.compiler.compileProgram
|
import prog8.compiler.compileProgram
|
||||||
import prog8.compiler.target.Cx16Target
|
import prog8.compiler.target.Cx16Target
|
||||||
import prog8tests.helpers.*
|
import prog8tests.ast.helpers.assumeReadableFile
|
||||||
|
import prog8tests.ast.helpers.fixturesDir
|
||||||
|
import prog8tests.ast.helpers.outputDir
|
||||||
|
import prog8tests.ast.helpers.workingDir
|
||||||
|
import prog8tests.helpers.assertSuccess
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import kotlin.io.path.absolute
|
import kotlin.io.path.absolute
|
||||||
import kotlin.io.path.createTempFile
|
import kotlin.io.path.createTempFile
|
||||||
@ -18,13 +21,11 @@ import kotlin.io.path.writeText
|
|||||||
* They are not really unit tests, but rather tests of the whole process,
|
* They are not really unit tests, but rather tests of the whole process,
|
||||||
* from source file loading all the way through to running 64tass.
|
* from source file loading all the way through to running 64tass.
|
||||||
*/
|
*/
|
||||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
class TestCompilerOptionSourcedirs: FunSpec({
|
||||||
class TestCompilerOptionSourcedirs {
|
|
||||||
|
|
||||||
private lateinit var tempFileInWorkingDir: Path
|
lateinit var tempFileInWorkingDir: Path
|
||||||
|
|
||||||
@BeforeAll
|
beforeSpec {
|
||||||
fun setUp() {
|
|
||||||
tempFileInWorkingDir = createTempFile(directory = workingDir, prefix = "tmp_", suffix = ".p8")
|
tempFileInWorkingDir = createTempFile(directory = workingDir, prefix = "tmp_", suffix = ".p8")
|
||||||
.also { it.writeText("""
|
.also { it.writeText("""
|
||||||
main {
|
main {
|
||||||
@ -34,63 +35,60 @@ class TestCompilerOptionSourcedirs {
|
|||||||
""")}
|
""")}
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterAll
|
afterSpec {
|
||||||
fun tearDown() {
|
|
||||||
tempFileInWorkingDir.deleteExisting()
|
tempFileInWorkingDir.deleteExisting()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun compileFile(filePath: Path, sourceDirs: List<String>) =
|
fun compileFile(filePath: Path, sourceDirs: List<String>): CompilationResult {
|
||||||
compileProgram(
|
val args = CompilerArguments(
|
||||||
filepath = filePath,
|
filepath = filePath,
|
||||||
optimize = false,
|
optimize = false,
|
||||||
|
optimizeFloatExpressions = false,
|
||||||
writeAssembly = true,
|
writeAssembly = true,
|
||||||
slowCodegenWarnings = false,
|
slowCodegenWarnings = false,
|
||||||
|
quietAssembler = true,
|
||||||
compilationTarget = Cx16Target.name,
|
compilationTarget = Cx16Target.name,
|
||||||
sourceDirs,
|
sourceDirs,
|
||||||
outputDir
|
outputDir
|
||||||
)
|
)
|
||||||
|
return compileProgram(args)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
test("testAbsoluteFilePathInWorkingDir") {
|
||||||
fun testAbsoluteFilePathInWorkingDir() {
|
|
||||||
val filepath = assumeReadableFile(tempFileInWorkingDir.absolute())
|
val filepath = assumeReadableFile(tempFileInWorkingDir.absolute())
|
||||||
compileFile(filepath, listOf())
|
compileFile(filepath, listOf())
|
||||||
.assertSuccess()
|
.assertSuccess()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testFilePathInWorkingDirRelativeToWorkingDir") {
|
||||||
fun testFilePathInWorkingDirRelativeToWorkingDir() {
|
|
||||||
val filepath = assumeReadableFile(workingDir.relativize(tempFileInWorkingDir.absolute()))
|
val filepath = assumeReadableFile(workingDir.relativize(tempFileInWorkingDir.absolute()))
|
||||||
compileFile(filepath, listOf())
|
compileFile(filepath, listOf())
|
||||||
.assertSuccess()
|
.assertSuccess()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testFilePathInWorkingDirRelativeTo1stInSourcedirs") {
|
||||||
fun testFilePathInWorkingDirRelativeTo1stInSourcedirs() {
|
|
||||||
val filepath = assumeReadableFile(tempFileInWorkingDir)
|
val filepath = assumeReadableFile(tempFileInWorkingDir)
|
||||||
compileFile(filepath.fileName, listOf(workingDir.toString()))
|
compileFile(filepath.fileName, listOf(workingDir.toString()))
|
||||||
.assertSuccess()
|
.assertSuccess()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testAbsoluteFilePathOutsideWorkingDir") {
|
||||||
fun testAbsoluteFilePathOutsideWorkingDir() {
|
|
||||||
val filepath = assumeReadableFile(fixturesDir, "simple_main.p8")
|
val filepath = assumeReadableFile(fixturesDir, "simple_main.p8")
|
||||||
compileFile(filepath.absolute(), listOf())
|
compileFile(filepath.absolute(), listOf())
|
||||||
.assertSuccess()
|
.assertSuccess()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testFilePathOutsideWorkingDirRelativeToWorkingDir") {
|
||||||
fun testFilePathOutsideWorkingDirRelativeToWorkingDir() {
|
|
||||||
val filepath = workingDir.relativize(assumeReadableFile(fixturesDir, "simple_main.p8").absolute())
|
val filepath = workingDir.relativize(assumeReadableFile(fixturesDir, "simple_main.p8").absolute())
|
||||||
compileFile(filepath, listOf())
|
compileFile(filepath, listOf())
|
||||||
.assertSuccess()
|
.assertSuccess()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testFilePathOutsideWorkingDirRelativeTo1stInSourcedirs") {
|
||||||
fun testFilePathOutsideWorkingDirRelativeTo1stInSourcedirs() {
|
|
||||||
val filepath = assumeReadableFile(fixturesDir, "simple_main.p8")
|
val filepath = assumeReadableFile(fixturesDir, "simple_main.p8")
|
||||||
val sourcedirs = listOf("$fixturesDir")
|
val sourcedirs = listOf("$fixturesDir")
|
||||||
compileFile(filepath.fileName, sourcedirs)
|
compileFile(filepath.fileName, sourcedirs)
|
||||||
.assertSuccess()
|
.assertSuccess()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
})
|
||||||
|
@ -1,25 +1,23 @@
|
|||||||
package prog8tests
|
package prog8tests
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test
|
import io.kotest.assertions.withClue
|
||||||
import org.junit.jupiter.api.TestInstance
|
import io.kotest.core.spec.style.FunSpec
|
||||||
|
import io.kotest.matchers.shouldBe
|
||||||
|
import io.kotest.matchers.string.shouldStartWith
|
||||||
import prog8.ast.internedStringsModuleName
|
import prog8.ast.internedStringsModuleName
|
||||||
import prog8.compiler.ErrorReporter
|
|
||||||
import prog8.compiler.ZeropageType
|
|
||||||
import prog8.compiler.determineCompilationOptions
|
import prog8.compiler.determineCompilationOptions
|
||||||
import prog8.compiler.parseImports
|
import prog8.compiler.parseImports
|
||||||
import prog8.compiler.target.C64Target
|
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.assertSuccess
|
||||||
import prog8tests.helpers.compileText
|
import prog8tests.helpers.compileText
|
||||||
import prog8tests.helpers.outputDir
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
import kotlin.test.assertTrue
|
|
||||||
|
|
||||||
|
|
||||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
class TestImportedModulesOrderAndOptions: FunSpec({
|
||||||
class TestImportedModulesOrderAndOptions {
|
|
||||||
|
|
||||||
@Test
|
test("testImportedModuleOrderAndMainModuleCorrect") {
|
||||||
fun testImportedModuleOrderAndMainModuleCorrect() {
|
|
||||||
val result = compileText(C64Target, false, """
|
val result = compileText(C64Target, false, """
|
||||||
%import textio
|
%import textio
|
||||||
%import floats
|
%import floats
|
||||||
@ -30,25 +28,27 @@ main {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
""").assertSuccess()
|
""").assertSuccess()
|
||||||
assertTrue(result.programAst.toplevelModule.name.startsWith("on_the_fly_test"))
|
result.program.toplevelModule.name shouldStartWith "on_the_fly_test"
|
||||||
|
|
||||||
val moduleNames = result.programAst.modules.map { it.name }
|
val moduleNames = result.program.modules.map { it.name }
|
||||||
assertTrue(moduleNames[0].startsWith("on_the_fly_test"), "main module must be first")
|
withClue("main module must be first") {
|
||||||
assertEquals(listOf(
|
moduleNames[0] shouldStartWith "on_the_fly_test"
|
||||||
"prog8_interned_strings",
|
}
|
||||||
"textio",
|
withClue("module order in parse tree") {
|
||||||
"syslib",
|
moduleNames.drop(1) shouldBe listOf(
|
||||||
"conv",
|
"prog8_interned_strings",
|
||||||
"floats",
|
"textio",
|
||||||
"math",
|
"syslib",
|
||||||
"prog8_lib"
|
"conv",
|
||||||
), moduleNames.drop(1), "module order in parse tree")
|
"floats",
|
||||||
|
"math",
|
||||||
assertTrue(result.programAst.toplevelModule.name.startsWith("on_the_fly_test"))
|
"prog8_lib"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
result.program.toplevelModule.name shouldStartWith "on_the_fly_test"
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testCompilationOptionsCorrectFromMain") {
|
||||||
fun testCompilationOptionsCorrectFromMain() {
|
|
||||||
val result = compileText(C64Target, false, """
|
val result = compileText(C64Target, false, """
|
||||||
%import textio
|
%import textio
|
||||||
%import floats
|
%import floats
|
||||||
@ -61,16 +61,15 @@ main {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
""").assertSuccess()
|
""").assertSuccess()
|
||||||
assertTrue(result.programAst.toplevelModule.name.startsWith("on_the_fly_test"))
|
result.program.toplevelModule.name shouldStartWith "on_the_fly_test"
|
||||||
val options = determineCompilationOptions(result.programAst, C64Target)
|
val options = determineCompilationOptions(result.program, C64Target)
|
||||||
assertTrue(options.floats)
|
options.floats shouldBe true
|
||||||
assertEquals(ZeropageType.DONTUSE, options.zeropage)
|
options.zeropage shouldBe ZeropageType.DONTUSE
|
||||||
assertTrue(options.noSysInit)
|
options.noSysInit shouldBe true
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testModuleOrderAndCompilationOptionsCorrectWithJustImports") {
|
||||||
fun testModuleOrderAndCompilationOptionsCorrectWithJustImports() {
|
val errors = ErrorReporterForTests()
|
||||||
val errors = ErrorReporter()
|
|
||||||
val sourceText = """
|
val sourceText = """
|
||||||
%import textio
|
%import textio
|
||||||
%import floats
|
%import floats
|
||||||
@ -88,17 +87,23 @@ main {
|
|||||||
filepath.toFile().writeText(sourceText)
|
filepath.toFile().writeText(sourceText)
|
||||||
val (program, options, importedfiles) = parseImports(filepath, errors, C64Target, emptyList())
|
val (program, options, importedfiles) = parseImports(filepath, errors, C64Target, emptyList())
|
||||||
|
|
||||||
assertEquals(filenameBase, program.toplevelModule.name)
|
program.toplevelModule.name shouldBe filenameBase
|
||||||
assertEquals(1, importedfiles.size, "all imports other than the test source must have been internal resources library files")
|
withClue("all imports other than the test source must have been internal resources library files") {
|
||||||
assertEquals(listOf(
|
importedfiles.size shouldBe 1
|
||||||
internedStringsModuleName,
|
}
|
||||||
filenameBase,
|
withClue("module order in parse tree") {
|
||||||
"textio", "syslib", "conv", "floats", "math", "prog8_lib"
|
program.modules.map { it.name } shouldBe
|
||||||
), program.modules.map {it.name}, "module order in parse tree")
|
listOf(
|
||||||
assertTrue(options.floats)
|
internedStringsModuleName,
|
||||||
assertEquals(ZeropageType.DONTUSE, options.zeropage, "zeropage option must be correctly taken from main module, not from float module import logic")
|
filenameBase,
|
||||||
assertTrue(options.noSysInit)
|
"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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
})
|
||||||
}
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package prog8tests
|
package prog8tests
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test
|
import io.kotest.core.spec.style.FunSpec
|
||||||
import org.junit.jupiter.api.TestInstance
|
import io.kotest.matchers.shouldBe
|
||||||
import prog8.ast.Module
|
import prog8.ast.Module
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.DataType
|
import prog8.ast.base.DataType
|
||||||
@ -12,178 +12,212 @@ import prog8.ast.expressions.IdentifierReference
|
|||||||
import prog8.ast.expressions.NumericLiteralValue
|
import prog8.ast.expressions.NumericLiteralValue
|
||||||
import prog8.ast.expressions.PrefixExpression
|
import prog8.ast.expressions.PrefixExpression
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.compiler.astprocessing.isInRegularRAMof
|
import prog8.compiler.printProgram
|
||||||
import prog8.compiler.target.C64Target
|
import prog8.compiler.target.C64Target
|
||||||
|
import prog8.compilerinterface.isIOAddress
|
||||||
import prog8.parser.SourceCode
|
import prog8.parser.SourceCode
|
||||||
import prog8tests.helpers.DummyFunctions
|
import prog8tests.helpers.DummyFunctions
|
||||||
import prog8tests.helpers.DummyMemsizer
|
import prog8tests.helpers.DummyMemsizer
|
||||||
import kotlin.test.assertFalse
|
import prog8tests.helpers.DummyStringEncoder
|
||||||
import kotlin.test.assertTrue
|
|
||||||
|
|
||||||
|
|
||||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
class TestMemory: FunSpec({
|
||||||
class TestMemory {
|
|
||||||
|
|
||||||
@Test
|
fun wrapWithProgram(statements: List<Statement>): Program {
|
||||||
fun testInValidRamC64_memory_addresses() {
|
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)
|
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)
|
memexpr = NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY)
|
||||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), 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)
|
memexpr = NumericLiteralValue.optimalInteger(0x9fff, Position.DUMMY)
|
||||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), 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)
|
memexpr = NumericLiteralValue.optimalInteger(0xc000, Position.DUMMY)
|
||||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), 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)
|
memexpr = NumericLiteralValue.optimalInteger(0xcfff, Position.DUMMY)
|
||||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), 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
|
memexpr = NumericLiteralValue.optimalInteger(0xeeee, Position.DUMMY)
|
||||||
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)
|
|
||||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), 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))
|
||||||
memexpr = NumericLiteralValue.optimalInteger(0xd000, Position.DUMMY)
|
target.isIOAddress(C64Target.machine) shouldBe false
|
||||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
|
||||||
assertFalse(target.isInRegularRAMof(C64Target.machine))
|
|
||||||
|
|
||||||
memexpr = NumericLiteralValue.optimalInteger(0xffff, Position.DUMMY)
|
memexpr = NumericLiteralValue.optimalInteger(0xffff, Position.DUMMY)
|
||||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), 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
|
test("assign target in mapped IO space C64") {
|
||||||
fun testInValidRamC64_memory_identifiers() {
|
|
||||||
val program = Program("test", DummyFunctions, DummyMemsizer)
|
|
||||||
var target = createTestProgramForMemoryRefViaVar(program, 0x1000, VarDeclType.VAR)
|
|
||||||
|
|
||||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
var memexpr = NumericLiteralValue.optimalInteger(0x0000, Position.DUMMY)
|
||||||
target = createTestProgramForMemoryRefViaVar(program, 0xd020, VarDeclType.VAR)
|
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||||
assertFalse(target.isInRegularRAMof(C64Target.machine))
|
var assign = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||||
target = createTestProgramForMemoryRefViaVar(program, 0x1000, VarDeclType.CONST)
|
wrapWithProgram(listOf(assign))
|
||||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
target.isIOAddress(C64Target.machine) shouldBe true
|
||||||
target = createTestProgramForMemoryRefViaVar(program, 0xd020, VarDeclType.CONST)
|
|
||||||
assertFalse(target.isInRegularRAMof(C64Target.machine))
|
memexpr = NumericLiteralValue.optimalInteger(0x0001, Position.DUMMY)
|
||||||
target = createTestProgramForMemoryRefViaVar(program, 0x1000, VarDeclType.MEMORY)
|
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 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 {
|
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, Position.DUMMY)
|
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 memexpr = IdentifierReference(listOf("address"), Position.DUMMY)
|
||||||
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), 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 assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
wrapWithProgram(listOf(decl, assignment))
|
||||||
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
|
||||||
module.linkIntoProgram(program)
|
|
||||||
return target
|
return target
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("identifier mapped to IO memory on C64") {
|
||||||
fun testInValidRamC64_memory_expression() {
|
var target = createTestProgramForMemoryRefViaVar(0x1000u, VarDeclType.VAR)
|
||||||
val memexpr = PrefixExpression("+", NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY), Position.DUMMY)
|
target.isIOAddress(C64Target.machine) shouldBe false
|
||||||
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
target = createTestProgramForMemoryRefViaVar(0xd020u, VarDeclType.VAR)
|
||||||
assertFalse(target.isInRegularRAMof(C64Target.machine))
|
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
|
test("memory expression mapped to IO memory on C64") {
|
||||||
fun testInValidRamC64_variable() {
|
var memexpr = PrefixExpression("+", NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY), Position.DUMMY)
|
||||||
val decl = VarDecl(VarDeclType.VAR, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, false, false, false, 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 target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
|
||||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), 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 module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||||
val program = Program("test", DummyFunctions, DummyMemsizer)
|
Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||||
.addModule(module)
|
.addModule(module)
|
||||||
module.linkIntoProgram(program)
|
target.isIOAddress(C64Target.machine) shouldBe false
|
||||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("memory mapped variable not in mapped IO ram on C64") {
|
||||||
fun testInValidRamC64_memmap_variable() {
|
val address = 0x1000u
|
||||||
val address = 0x1000
|
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, null, Position.DUMMY)
|
||||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
|
|
||||||
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, 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 assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), 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 module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||||
val program = Program("test", DummyFunctions, DummyMemsizer)
|
Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||||
.addModule(module)
|
.addModule(module)
|
||||||
module.linkIntoProgram(program)
|
target.isIOAddress(C64Target.machine) shouldBe false
|
||||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("memory mapped variable in mapped IO ram on C64") {
|
||||||
fun testNotInValidRamC64_memmap_variable() {
|
val address = 0xd020u
|
||||||
val address = 0xd020
|
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, null, Position.DUMMY)
|
||||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
|
|
||||||
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, 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 assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), 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 module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||||
val program = Program("test", DummyFunctions, DummyMemsizer)
|
Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||||
.addModule(module)
|
.addModule(module)
|
||||||
module.linkIntoProgram(program)
|
target.isIOAddress(C64Target.machine) shouldBe true
|
||||||
assertFalse(target.isInRegularRAMof(C64Target.machine))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("array not in mapped IO ram") {
|
||||||
fun testInValidRamC64_array() {
|
val decl = VarDecl(VarDeclType.VAR, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, false, false, false, null, Position.DUMMY)
|
||||||
val decl = VarDecl(VarDeclType.VAR, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, false, false, false, Position.DUMMY)
|
|
||||||
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), 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 target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
||||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), 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 module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||||
val program = Program("test", DummyFunctions, DummyMemsizer)
|
Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||||
.addModule(module)
|
.addModule(module)
|
||||||
module.linkIntoProgram(program)
|
target.isIOAddress(C64Target.machine) shouldBe false
|
||||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("memory mapped array not in mapped IO ram") {
|
||||||
fun testInValidRamC64_array_memmapped() {
|
val address = 0x1000u
|
||||||
val address = 0x1000
|
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, null, Position.DUMMY)
|
||||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
|
|
||||||
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), 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 target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
||||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), 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 module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||||
val program = Program("test", DummyFunctions, DummyMemsizer)
|
Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||||
.addModule(module)
|
.addModule(module)
|
||||||
module.linkIntoProgram(program)
|
target.isIOAddress(C64Target.machine) shouldBe false
|
||||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("memory mapped array in mapped IO ram") {
|
||||||
fun testNotValidRamC64_array_memmapped() {
|
val address = 0xd800u
|
||||||
val address = 0xe000
|
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, null, Position.DUMMY)
|
||||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
|
|
||||||
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), 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 target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
||||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), 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 module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||||
val program = Program("test", DummyFunctions, DummyMemsizer)
|
Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||||
.addModule(module)
|
.addModule(module)
|
||||||
module.linkIntoProgram(program)
|
target.isIOAddress(C64Target.machine) shouldBe true
|
||||||
assertFalse(target.isInRegularRAMof(C64Target.machine))
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
@ -1,116 +1,110 @@
|
|||||||
package prog8tests
|
package prog8tests
|
||||||
|
|
||||||
import org.hamcrest.MatcherAssert.assertThat
|
import io.kotest.assertions.throwables.shouldThrow
|
||||||
import org.hamcrest.Matchers.closeTo
|
import io.kotest.core.spec.style.FunSpec
|
||||||
import org.hamcrest.Matchers.equalTo
|
import io.kotest.matchers.doubles.plusOrMinus
|
||||||
import org.junit.jupiter.api.Test
|
import io.kotest.matchers.shouldBe
|
||||||
import org.junit.jupiter.api.TestInstance
|
|
||||||
import prog8.ast.toHex
|
import prog8.ast.toHex
|
||||||
import prog8.compiler.CompilerException
|
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_NEGATIVE
|
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_NEGATIVE
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_POSITIVE
|
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_POSITIVE
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.Mflpt5
|
import prog8.compiler.target.c64.C64MachineDefinition.Mflpt5
|
||||||
import kotlin.test.assertEquals
|
import prog8.compilerinterface.InternalCompilerException
|
||||||
import kotlin.test.assertFailsWith
|
|
||||||
|
|
||||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
|
||||||
class TestNumbers {
|
class TestNumbers: FunSpec({
|
||||||
@Test
|
test("testToHex") {
|
||||||
fun testToHex() {
|
0.toHex() shouldBe "0"
|
||||||
assertEquals("0", 0.toHex())
|
1.toHex() shouldBe "1"
|
||||||
assertEquals("1", 1.toHex())
|
1.234.toHex() shouldBe "1"
|
||||||
assertEquals("1", 1.234.toHex())
|
10.toHex() shouldBe "10"
|
||||||
assertEquals("10", 10.toHex())
|
10.99.toHex() shouldBe "10"
|
||||||
assertEquals("10", 10.99.toHex())
|
15.toHex() shouldBe "15"
|
||||||
assertEquals("15", 15.toHex())
|
16.toHex() shouldBe "\$10"
|
||||||
assertEquals("\$10", 16.toHex())
|
255.toHex() shouldBe "\$ff"
|
||||||
assertEquals("\$ff", 255.toHex())
|
256.toHex() shouldBe "\$0100"
|
||||||
assertEquals("\$0100", 256.toHex())
|
20060.toHex() shouldBe "\$4e5c"
|
||||||
assertEquals("\$4e5c", 20060.toHex())
|
50050.toHex() shouldBe "\$c382"
|
||||||
assertEquals("\$c382", 50050.toHex())
|
65535.toHex() shouldBe "\$ffff"
|
||||||
assertEquals("\$ffff", 65535.toHex())
|
65535L.toHex() shouldBe "\$ffff"
|
||||||
assertEquals("\$ffff", 65535L.toHex())
|
0.toHex() shouldBe "0"
|
||||||
assertEquals("0", 0.toHex())
|
(-1).toHex() shouldBe "-1"
|
||||||
assertEquals("-1", (-1).toHex())
|
(-1.234).toHex() shouldBe "-1"
|
||||||
assertEquals("-1", (-1.234).toHex())
|
(-10).toHex() shouldBe "-10"
|
||||||
assertEquals("-10", (-10).toHex())
|
(-10.99).toHex() shouldBe "-10"
|
||||||
assertEquals("-10", (-10.99).toHex())
|
(-15).toHex() shouldBe "-15"
|
||||||
assertEquals("-15", (-15).toHex())
|
(-16).toHex() shouldBe "-\$10"
|
||||||
assertEquals("-\$10", (-16).toHex())
|
(-255).toHex() shouldBe "-\$ff"
|
||||||
assertEquals("-\$ff", (-255).toHex())
|
(-256).toHex() shouldBe "-\$0100"
|
||||||
assertEquals("-\$0100", (-256).toHex())
|
(-20060).toHex() shouldBe "-\$4e5c"
|
||||||
assertEquals("-\$4e5c", (-20060).toHex())
|
(-50050).toHex() shouldBe "-\$c382"
|
||||||
assertEquals("-\$c382", (-50050).toHex())
|
(-65535).toHex() shouldBe "-\$ffff"
|
||||||
assertEquals("-\$ffff", (-65535).toHex())
|
(-65535L).toHex() shouldBe "-\$ffff"
|
||||||
assertEquals("-\$ffff", (-65535L).toHex())
|
shouldThrow<IllegalArgumentException> { 65536.toHex() }
|
||||||
assertFailsWith<IllegalArgumentException> { 65536.toHex() }
|
shouldThrow<IllegalArgumentException> { 65536L.toHex() }
|
||||||
assertFailsWith<IllegalArgumentException> { 65536L.toHex() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testFloatToMflpt5") {
|
||||||
fun testFloatToMflpt5() {
|
Mflpt5.fromNumber(0) shouldBe Mflpt5(0x00u, 0x00u, 0x00u, 0x00u, 0x00u)
|
||||||
assertThat(Mflpt5.fromNumber(0), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)))
|
Mflpt5.fromNumber(3.141592653) shouldBe Mflpt5(0x82u, 0x49u, 0x0Fu, 0xDAu, 0xA1u)
|
||||||
assertThat(Mflpt5.fromNumber(3.141592653), equalTo(Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA1)))
|
Mflpt5.fromNumber(3.141592653589793) shouldBe Mflpt5(0x82u, 0x49u, 0x0Fu, 0xDAu, 0xA2u)
|
||||||
assertThat(Mflpt5.fromNumber(3.141592653589793), equalTo(Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA2)))
|
Mflpt5.fromNumber(32768) shouldBe Mflpt5(0x90u, 0x00u, 0x00u, 0x00u, 0x00u)
|
||||||
assertThat(Mflpt5.fromNumber(32768), equalTo(Mflpt5(0x90, 0x00, 0x00, 0x00, 0x00)))
|
Mflpt5.fromNumber(-32768) shouldBe Mflpt5(0x90u, 0x80u, 0x00u, 0x00u, 0x00u)
|
||||||
assertThat(Mflpt5.fromNumber(-32768), equalTo(Mflpt5(0x90, 0x80, 0x00, 0x00, 0x00)))
|
Mflpt5.fromNumber(1) shouldBe Mflpt5(0x81u, 0x00u, 0x00u, 0x00u, 0x00u)
|
||||||
assertThat(Mflpt5.fromNumber(1), equalTo(Mflpt5(0x81, 0x00, 0x00, 0x00, 0x00)))
|
Mflpt5.fromNumber(0.7071067812) shouldBe Mflpt5(0x80u, 0x35u, 0x04u, 0xF3u, 0x34u)
|
||||||
assertThat(Mflpt5.fromNumber(0.7071067812), equalTo(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x34)))
|
Mflpt5.fromNumber(0.7071067811865476) shouldBe Mflpt5(0x80u, 0x35u, 0x04u, 0xF3u, 0x33u)
|
||||||
assertThat(Mflpt5.fromNumber(0.7071067811865476), equalTo(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x33)))
|
Mflpt5.fromNumber(1.4142135624) shouldBe Mflpt5(0x81u, 0x35u, 0x04u, 0xF3u, 0x34u)
|
||||||
assertThat(Mflpt5.fromNumber(1.4142135624), equalTo(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x34)))
|
Mflpt5.fromNumber(1.4142135623730951) shouldBe Mflpt5(0x81u, 0x35u, 0x04u, 0xF3u, 0x33u)
|
||||||
assertThat(Mflpt5.fromNumber(1.4142135623730951), equalTo(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x33)))
|
Mflpt5.fromNumber(-.5) shouldBe Mflpt5(0x80u, 0x80u, 0x00u, 0x00u, 0x00u)
|
||||||
assertThat(Mflpt5.fromNumber(-.5), equalTo(Mflpt5(0x80, 0x80, 0x00, 0x00, 0x00)))
|
Mflpt5.fromNumber(0.69314718061) shouldBe Mflpt5(0x80u, 0x31u, 0x72u, 0x17u, 0xF8u)
|
||||||
assertThat(Mflpt5.fromNumber(0.69314718061), equalTo(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF8)))
|
Mflpt5.fromNumber(0.6931471805599453) shouldBe Mflpt5(0x80u, 0x31u, 0x72u, 0x17u, 0xF7u)
|
||||||
assertThat(Mflpt5.fromNumber(0.6931471805599453), equalTo(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF7)))
|
Mflpt5.fromNumber(10) shouldBe Mflpt5(0x84u, 0x20u, 0x00u, 0x00u, 0x00u)
|
||||||
assertThat(Mflpt5.fromNumber(10), equalTo(Mflpt5(0x84, 0x20, 0x00, 0x00, 0x00)))
|
Mflpt5.fromNumber(1000000000) shouldBe Mflpt5(0x9Eu, 0x6Eu, 0x6Bu, 0x28u, 0x00u)
|
||||||
assertThat(Mflpt5.fromNumber(1000000000), equalTo(Mflpt5(0x9E, 0x6E, 0x6B, 0x28, 0x00)))
|
Mflpt5.fromNumber(.5) shouldBe Mflpt5(0x80u, 0x00u, 0x00u, 0x00u, 0x00u)
|
||||||
assertThat(Mflpt5.fromNumber(.5), equalTo(Mflpt5(0x80, 0x00, 0x00, 0x00, 0x00)))
|
Mflpt5.fromNumber(1.4426950408889634) shouldBe Mflpt5(0x81u, 0x38u, 0xAAu, 0x3Bu, 0x29u)
|
||||||
assertThat(Mflpt5.fromNumber(1.4426950408889634), equalTo(Mflpt5(0x81, 0x38, 0xAA, 0x3B, 0x29)))
|
Mflpt5.fromNumber(1.5707963267948966) shouldBe Mflpt5(0x81u, 0x49u, 0x0Fu, 0xDAu, 0xA2u)
|
||||||
assertThat(Mflpt5.fromNumber(1.5707963267948966), equalTo(Mflpt5(0x81, 0x49, 0x0F, 0xDA, 0xA2)))
|
Mflpt5.fromNumber(6.283185307179586) shouldBe Mflpt5(0x83u, 0x49u, 0x0Fu, 0xDAu, 0xA2u)
|
||||||
assertThat(Mflpt5.fromNumber(6.283185307179586), equalTo(Mflpt5(0x83, 0x49, 0x0F, 0xDA, 0xA2)))
|
Mflpt5.fromNumber(.25) shouldBe Mflpt5(0x7Fu, 0x00u, 0x00u, 0x00u, 0x00u)
|
||||||
assertThat(Mflpt5.fromNumber(.25), equalTo(Mflpt5(0x7F, 0x00, 0x00, 0x00, 0x00)))
|
Mflpt5.fromNumber(123.45678e22) shouldBe Mflpt5(0xd1u, 0x02u, 0xb7u, 0x06u, 0xfbu)
|
||||||
assertThat(Mflpt5.fromNumber(123.45678e22), equalTo(Mflpt5(0xd1, 0x02, 0xb7, 0x06, 0xfb)))
|
Mflpt5.fromNumber(-123.45678e-22) shouldBe Mflpt5(0x3eu, 0xe9u, 0x34u, 0x09u, 0x1bu)
|
||||||
assertThat(Mflpt5.fromNumber(-123.45678e-22), equalTo(Mflpt5(0x3e, 0xe9, 0x34, 0x09, 0x1b)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testFloatRange") {
|
||||||
fun testFloatRange() {
|
Mflpt5.fromNumber(FLOAT_MAX_POSITIVE) shouldBe Mflpt5(0xffu, 0x7fu, 0xffu, 0xffu, 0xffu)
|
||||||
assertThat(Mflpt5.fromNumber(FLOAT_MAX_POSITIVE), equalTo(Mflpt5(0xff, 0x7f, 0xff, 0xff, 0xff)))
|
Mflpt5.fromNumber(FLOAT_MAX_NEGATIVE) shouldBe Mflpt5(0xffu, 0xffu, 0xffu, 0xffu, 0xffu)
|
||||||
assertThat(Mflpt5.fromNumber(FLOAT_MAX_NEGATIVE), equalTo(Mflpt5(0xff, 0xff, 0xff, 0xff, 0xff)))
|
Mflpt5.fromNumber(1.7e-38) shouldBe Mflpt5(0x03u, 0x39u, 0x1du, 0x15u, 0x63u)
|
||||||
assertThat(Mflpt5.fromNumber(1.7e-38), equalTo(Mflpt5(0x03, 0x39, 0x1d, 0x15, 0x63)))
|
Mflpt5.fromNumber(1.7e-39) shouldBe Mflpt5(0x00u, 0x00u, 0x00u, 0x00u, 0x00u)
|
||||||
assertThat(Mflpt5.fromNumber(1.7e-39), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)))
|
Mflpt5.fromNumber(-1.7e-38) shouldBe Mflpt5(0x03u, 0xb9u, 0x1du, 0x15u, 0x63u)
|
||||||
assertThat(Mflpt5.fromNumber(-1.7e-38), equalTo(Mflpt5(0x03, 0xb9, 0x1d, 0x15, 0x63)))
|
Mflpt5.fromNumber(-1.7e-39) shouldBe Mflpt5(0x00u, 0x00u, 0x00u, 0x00u, 0x00u)
|
||||||
assertThat(Mflpt5.fromNumber(-1.7e-39), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)))
|
shouldThrow<InternalCompilerException> { Mflpt5.fromNumber(1.7014118346e+38) }
|
||||||
assertFailsWith<CompilerException> { Mflpt5.fromNumber(1.7014118346e+38) }
|
shouldThrow<InternalCompilerException> { Mflpt5.fromNumber(-1.7014118346e+38) }
|
||||||
assertFailsWith<CompilerException> { Mflpt5.fromNumber(-1.7014118346e+38) }
|
shouldThrow<InternalCompilerException> { Mflpt5.fromNumber(1.7014118347e+38) }
|
||||||
assertFailsWith<CompilerException> { Mflpt5.fromNumber(1.7014118347e+38) }
|
shouldThrow<InternalCompilerException> { Mflpt5.fromNumber(-1.7014118347e+38) }
|
||||||
assertFailsWith<CompilerException> { Mflpt5.fromNumber(-1.7014118347e+38) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testMflpt5ToFloat") {
|
||||||
fun testMflpt5ToFloat() {
|
|
||||||
val epsilon=0.000000001
|
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))
|
Mflpt5(0x00u, 0x00u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe 0.0
|
||||||
assertThat(Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA2).toDouble(), closeTo(3.141592653589793, epsilon))
|
Mflpt5(0x82u, 0x49u, 0x0Fu, 0xDAu, 0xA1u).toDouble() shouldBe(3.141592653 plusOrMinus epsilon)
|
||||||
assertThat(Mflpt5(0x90, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(32768.0))
|
Mflpt5(0x82u, 0x49u, 0x0Fu, 0xDAu, 0xA2u).toDouble() shouldBe(3.141592653589793 plusOrMinus epsilon)
|
||||||
assertThat(Mflpt5(0x90, 0x80, 0x00, 0x00, 0x00).toDouble(), equalTo(-32768.0))
|
Mflpt5(0x90u, 0x00u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe 32768.0
|
||||||
assertThat(Mflpt5(0x81, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(1.0))
|
Mflpt5(0x90u, 0x80u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe -32768.0
|
||||||
assertThat(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x34).toDouble(), closeTo(0.7071067812, epsilon))
|
Mflpt5(0x81u, 0x00u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe 1.0
|
||||||
assertThat(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x33).toDouble(), closeTo(0.7071067811865476, epsilon))
|
Mflpt5(0x80u, 0x35u, 0x04u, 0xF3u, 0x34u).toDouble() shouldBe(0.7071067812 plusOrMinus epsilon)
|
||||||
assertThat(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x34).toDouble(), closeTo(1.4142135624, epsilon))
|
Mflpt5(0x80u, 0x35u, 0x04u, 0xF3u, 0x33u).toDouble() shouldBe(0.7071067811865476 plusOrMinus epsilon)
|
||||||
assertThat(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x33).toDouble(), closeTo(1.4142135623730951, epsilon))
|
Mflpt5(0x81u, 0x35u, 0x04u, 0xF3u, 0x34u).toDouble() shouldBe(1.4142135624 plusOrMinus epsilon)
|
||||||
assertThat(Mflpt5(0x80, 0x80, 0x00, 0x00, 0x00).toDouble(), equalTo(-.5))
|
Mflpt5(0x81u, 0x35u, 0x04u, 0xF3u, 0x33u).toDouble() shouldBe(1.4142135623730951 plusOrMinus epsilon)
|
||||||
assertThat(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF8).toDouble(), closeTo(0.69314718061, epsilon))
|
Mflpt5(0x80u, 0x80u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe -.5
|
||||||
assertThat(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF7).toDouble(), closeTo(0.6931471805599453, epsilon))
|
Mflpt5(0x80u, 0x31u, 0x72u, 0x17u, 0xF8u).toDouble() shouldBe(0.69314718061 plusOrMinus epsilon)
|
||||||
assertThat(Mflpt5(0x84, 0x20, 0x00, 0x00, 0x00).toDouble(), equalTo(10.0))
|
Mflpt5(0x80u, 0x31u, 0x72u, 0x17u, 0xF7u).toDouble() shouldBe(0.6931471805599453 plusOrMinus epsilon)
|
||||||
assertThat(Mflpt5(0x9E, 0x6E, 0x6B, 0x28, 0x00).toDouble(), equalTo(1000000000.0))
|
Mflpt5(0x84u, 0x20u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe 10.0
|
||||||
assertThat(Mflpt5(0x80, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(.5))
|
Mflpt5(0x9Eu, 0x6Eu, 0x6Bu, 0x28u, 0x00u).toDouble() shouldBe 1000000000.0
|
||||||
assertThat(Mflpt5(0x81, 0x38, 0xAA, 0x3B, 0x29).toDouble(), closeTo(1.4426950408889634, epsilon))
|
Mflpt5(0x80u, 0x00u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe .5
|
||||||
assertThat(Mflpt5(0x81, 0x49, 0x0F, 0xDA, 0xA2).toDouble(), closeTo(1.5707963267948966, epsilon))
|
Mflpt5(0x81u, 0x38u, 0xAAu, 0x3Bu, 0x29u).toDouble() shouldBe(1.4426950408889634 plusOrMinus epsilon)
|
||||||
assertThat(Mflpt5(0x83, 0x49, 0x0F, 0xDA, 0xA2).toDouble(), closeTo(6.283185307179586, epsilon))
|
Mflpt5(0x81u, 0x49u, 0x0Fu, 0xDAu, 0xA2u).toDouble() shouldBe(1.5707963267948966 plusOrMinus epsilon)
|
||||||
assertThat(Mflpt5(0x7F, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(.25))
|
Mflpt5(0x83u, 0x49u, 0x0Fu, 0xDAu, 0xA2u).toDouble() shouldBe(6.283185307179586 plusOrMinus epsilon)
|
||||||
assertThat(Mflpt5(0xd1, 0x02, 0xb7, 0x06, 0xfb).toDouble(), closeTo(123.45678e22, 1.0e15))
|
Mflpt5(0x7Fu, 0x00u, 0x00u, 0x00u, 0x00u).toDouble() shouldBe .25
|
||||||
assertThat(Mflpt5(0x3e, 0xe9, 0x34, 0x09, 0x1b).toDouble(), closeTo(-123.45678e-22, epsilon))
|
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
|
package prog8tests
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test
|
import io.kotest.assertions.throwables.shouldThrow
|
||||||
import org.junit.jupiter.api.TestInstance
|
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.DataType
|
||||||
|
import prog8.ast.base.ExpressionError
|
||||||
import prog8.ast.base.Position
|
import prog8.ast.base.Position
|
||||||
import prog8.ast.expressions.ArrayLiteralValue
|
import prog8.ast.expressions.ArrayLiteralValue
|
||||||
import prog8.ast.expressions.InferredTypes
|
import prog8.ast.expressions.InferredTypes
|
||||||
import prog8.ast.expressions.NumericLiteralValue
|
import prog8.ast.expressions.NumericLiteralValue
|
||||||
import prog8.ast.expressions.StringLiteralValue
|
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: FunSpec({
|
||||||
class TestNumericLiteralValue {
|
|
||||||
|
|
||||||
private fun sameValueAndType(lv1: NumericLiteralValue, lv2: NumericLiteralValue): Boolean {
|
fun sameValueAndType(lv1: NumericLiteralValue, lv2: NumericLiteralValue): Boolean {
|
||||||
return lv1.type==lv2.type && lv1==lv2
|
return lv1.type==lv2.type && lv1==lv2
|
||||||
}
|
}
|
||||||
|
|
||||||
private val dummyPos = Position("test", 0, 0, 0)
|
val dummyPos = Position("test", 0, 0, 0)
|
||||||
|
|
||||||
@Test
|
test("testIdentity") {
|
||||||
fun testIdentity() {
|
val v = NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos)
|
||||||
val v = NumericLiteralValue(DataType.UWORD, 12345, dummyPos)
|
(v==v) shouldBe true
|
||||||
assertEquals(v, v)
|
(v != v) shouldBe false
|
||||||
assertFalse(v != v)
|
(v <= v) shouldBe true
|
||||||
assertTrue(v <= v)
|
(v >= v) shouldBe true
|
||||||
assertTrue(v >= v)
|
(v < v ) shouldBe false
|
||||||
assertFalse(v < v)
|
(v > v ) shouldBe false
|
||||||
assertFalse(v > v)
|
|
||||||
|
|
||||||
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
|
test("test rounding") {
|
||||||
fun testEqualsAndNotEquals() {
|
shouldThrow<ExpressionError> {
|
||||||
assertEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
|
NumericLiteralValue(DataType.BYTE, -2.345, dummyPos)
|
||||||
assertEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UWORD, 100, dummyPos))
|
}.message shouldContain "refused silent rounding"
|
||||||
assertEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos))
|
shouldThrow<ExpressionError> {
|
||||||
assertEquals(NumericLiteralValue(DataType.UWORD, 254, dummyPos), NumericLiteralValue(DataType.UBYTE, 254, dummyPos))
|
NumericLiteralValue(DataType.BYTE, -2.6, dummyPos)
|
||||||
assertEquals(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12345, dummyPos))
|
}.message shouldContain "refused silent rounding"
|
||||||
assertEquals(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.FLOAT, 12345.0, dummyPos))
|
shouldThrow<ExpressionError> {
|
||||||
assertEquals(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
|
NumericLiteralValue(DataType.UWORD, 2222.345, dummyPos)
|
||||||
assertEquals(NumericLiteralValue(DataType.FLOAT, 22239.0, dummyPos), NumericLiteralValue(DataType.UWORD, 22239, dummyPos))
|
}.message shouldContain "refused silent rounding"
|
||||||
assertEquals(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos))
|
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)))
|
test("testEqualsAndNotEquals") {
|
||||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UWORD, 100, dummyPos)))
|
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) == NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe true
|
||||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)))
|
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) == NumericLiteralValue(DataType.UWORD, 100.0, dummyPos)) shouldBe true
|
||||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 254, dummyPos), NumericLiteralValue(DataType.UBYTE, 254, dummyPos)))
|
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) == NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)) shouldBe true
|
||||||
assertTrue(sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12345, dummyPos)))
|
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) == NumericLiteralValue(DataType.UBYTE, 254.0, dummyPos)) shouldBe true
|
||||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.FLOAT, 12345.0, dummyPos)))
|
(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos) == NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos)) shouldBe true
|
||||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 100, dummyPos)))
|
(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos) == NumericLiteralValue(DataType.FLOAT, 12345.0, dummyPos)) shouldBe true
|
||||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 22239.0, dummyPos), NumericLiteralValue(DataType.UWORD, 22239, dummyPos)))
|
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) == NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe true
|
||||||
assertTrue(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos)))
|
(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))
|
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe true
|
||||||
assertNotEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UWORD, 101, dummyPos))
|
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos), NumericLiteralValue(DataType.UWORD, 100.0, dummyPos)) shouldBe false
|
||||||
assertNotEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.FLOAT, 101.0, dummyPos))
|
sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos), NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)) shouldBe false
|
||||||
assertNotEquals(NumericLiteralValue(DataType.UWORD, 245, dummyPos), NumericLiteralValue(DataType.UBYTE, 246, dummyPos))
|
sameValueAndType(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 254.0, dummyPos)) shouldBe false
|
||||||
assertNotEquals(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12346, dummyPos))
|
sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos), NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos)) shouldBe true
|
||||||
assertNotEquals(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.FLOAT, 12346.0, dummyPos))
|
sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos), NumericLiteralValue(DataType.FLOAT, 12345.0, dummyPos)) shouldBe false
|
||||||
assertNotEquals(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UBYTE, 9, dummyPos))
|
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe false
|
||||||
assertNotEquals(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UWORD, 9, dummyPos))
|
sameValueAndType(NumericLiteralValue(DataType.FLOAT, 22239.0, dummyPos), NumericLiteralValue(DataType.UWORD, 22239.0, dummyPos)) shouldBe false
|
||||||
assertNotEquals(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.FLOAT, 9.0, dummyPos))
|
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)))
|
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) != NumericLiteralValue(DataType.UBYTE, 101.0, dummyPos)) shouldBe true
|
||||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UWORD, 101, dummyPos)))
|
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) != NumericLiteralValue(DataType.UWORD, 101.0, dummyPos)) shouldBe true
|
||||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.FLOAT, 101.0, dummyPos)))
|
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) != NumericLiteralValue(DataType.FLOAT, 101.0, dummyPos)) shouldBe true
|
||||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 245, dummyPos), NumericLiteralValue(DataType.UBYTE, 246, dummyPos)))
|
(NumericLiteralValue(DataType.UWORD, 245.0, dummyPos) != NumericLiteralValue(DataType.UBYTE, 246.0, dummyPos)) shouldBe true
|
||||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12346, dummyPos)))
|
(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos) != NumericLiteralValue(DataType.UWORD, 12346.0, dummyPos)) shouldBe true
|
||||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.FLOAT, 12346.0, dummyPos)))
|
(NumericLiteralValue(DataType.UWORD, 12345.0, dummyPos) != NumericLiteralValue(DataType.FLOAT, 12346.0, dummyPos)) shouldBe true
|
||||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UBYTE, 9, dummyPos)))
|
(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos) != NumericLiteralValue(DataType.UBYTE, 9.0, dummyPos)) shouldBe true
|
||||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UWORD, 9, dummyPos)))
|
(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos) != NumericLiteralValue(DataType.UWORD, 9.0, dummyPos)) shouldBe true
|
||||||
assertFalse(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.FLOAT, 9.0, dummyPos)))
|
(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
|
test("testEqualsRef") {
|
||||||
fun testEqualsRef() {
|
(StringLiteralValue("hello", false, dummyPos) == StringLiteralValue("hello", false, dummyPos)) shouldBe true
|
||||||
assertEquals(StringLiteralValue("hello", false, dummyPos), StringLiteralValue("hello", false, dummyPos))
|
(StringLiteralValue("hello", false, dummyPos) != StringLiteralValue("bye", false, dummyPos)) shouldBe true
|
||||||
assertNotEquals(StringLiteralValue("hello", false, dummyPos), StringLiteralValue("bye", false, dummyPos))
|
(StringLiteralValue("hello", true, dummyPos) == StringLiteralValue("hello", true, dummyPos)) shouldBe true
|
||||||
assertEquals(StringLiteralValue("hello", true, dummyPos), StringLiteralValue("hello", true, dummyPos))
|
(StringLiteralValue("hello", true, dummyPos) != StringLiteralValue("bye", true, dummyPos)) shouldBe true
|
||||||
assertNotEquals(StringLiteralValue("hello", true, dummyPos), StringLiteralValue("bye", true, dummyPos))
|
(StringLiteralValue("hello", true, dummyPos) != StringLiteralValue("hello", false, dummyPos)) shouldBe true
|
||||||
assertNotEquals(StringLiteralValue("hello", true, dummyPos), StringLiteralValue("hello", false, dummyPos))
|
|
||||||
|
|
||||||
val lvOne = NumericLiteralValue(DataType.UBYTE, 1, dummyPos)
|
val lvOne = NumericLiteralValue(DataType.UBYTE, 1.0, dummyPos)
|
||||||
val lvTwo = NumericLiteralValue(DataType.UBYTE, 2, dummyPos)
|
val lvTwo = NumericLiteralValue(DataType.UBYTE, 2.0, dummyPos)
|
||||||
val lvThree = NumericLiteralValue(DataType.UBYTE, 3, dummyPos)
|
val lvThree = NumericLiteralValue(DataType.UBYTE, 3.0, dummyPos)
|
||||||
val lvOneR = NumericLiteralValue(DataType.UBYTE, 1, dummyPos)
|
val lvOneR = NumericLiteralValue(DataType.UBYTE, 1.0, dummyPos)
|
||||||
val lvTwoR = NumericLiteralValue(DataType.UBYTE, 2, dummyPos)
|
val lvTwoR = NumericLiteralValue(DataType.UBYTE, 2.0, dummyPos)
|
||||||
val lvThreeR = NumericLiteralValue(DataType.UBYTE, 3, dummyPos)
|
val lvThreeR = NumericLiteralValue(DataType.UBYTE, 3.0, dummyPos)
|
||||||
val lvFour= NumericLiteralValue(DataType.UBYTE, 4, dummyPos)
|
val lvFour= NumericLiteralValue(DataType.UBYTE, 4.0, dummyPos)
|
||||||
val lv1 = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_UB), arrayOf(lvOne, lvTwo, lvThree), 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 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)
|
val lv3 = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_UB), arrayOf(lvOneR, lvTwoR, lvFour), dummyPos)
|
||||||
assertEquals(lv1, lv2)
|
lv1 shouldBe lv2
|
||||||
assertNotEquals(lv1, lv3)
|
lv1 shouldNotBe lv3
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testGreaterThan") {
|
||||||
fun testGreaterThan(){
|
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) > NumericLiteralValue(DataType.UBYTE, 99.0, dummyPos)) shouldBe true
|
||||||
assertTrue(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) > NumericLiteralValue(DataType.UBYTE, 99, dummyPos))
|
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) > NumericLiteralValue(DataType.UWORD, 253.0, dummyPos)) shouldBe true
|
||||||
assertTrue(NumericLiteralValue(DataType.UWORD, 254, dummyPos) > NumericLiteralValue(DataType.UWORD, 253, dummyPos))
|
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) > NumericLiteralValue(DataType.FLOAT, 99.9, dummyPos)) shouldBe true
|
||||||
assertTrue(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) > NumericLiteralValue(DataType.FLOAT, 99.9, dummyPos))
|
|
||||||
|
|
||||||
assertTrue(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) >= NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
|
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) >= NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe true
|
||||||
assertTrue(NumericLiteralValue(DataType.UWORD, 254, dummyPos) >= NumericLiteralValue(DataType.UWORD, 254, dummyPos))
|
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) >= NumericLiteralValue(DataType.UWORD, 254.0, dummyPos)) shouldBe true
|
||||||
assertTrue(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) >= NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos))
|
(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))
|
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) > NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe false
|
||||||
assertFalse(NumericLiteralValue(DataType.UWORD, 254, dummyPos) > NumericLiteralValue(DataType.UWORD, 254, dummyPos))
|
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) > NumericLiteralValue(DataType.UWORD, 254.0, dummyPos)) shouldBe false
|
||||||
assertFalse(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) > NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos))
|
(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))
|
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) >= NumericLiteralValue(DataType.UBYTE, 101.0, dummyPos)) shouldBe false
|
||||||
assertFalse(NumericLiteralValue(DataType.UWORD, 254, dummyPos) >= NumericLiteralValue(DataType.UWORD, 255, dummyPos))
|
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) >= NumericLiteralValue(DataType.UWORD, 255.0, dummyPos)) shouldBe false
|
||||||
assertFalse(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) >= NumericLiteralValue(DataType.FLOAT, 100.1, dummyPos))
|
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) >= NumericLiteralValue(DataType.FLOAT, 100.1, dummyPos)) shouldBe false
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testLessThan") {
|
||||||
fun testLessThan() {
|
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) < NumericLiteralValue(DataType.UBYTE, 101.0, dummyPos)) shouldBe true
|
||||||
assertTrue(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) < NumericLiteralValue(DataType.UBYTE, 101, dummyPos))
|
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) < NumericLiteralValue(DataType.UWORD, 255.0, dummyPos)) shouldBe true
|
||||||
assertTrue(NumericLiteralValue(DataType.UWORD, 254, dummyPos) < NumericLiteralValue(DataType.UWORD, 255, dummyPos))
|
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) < NumericLiteralValue(DataType.FLOAT, 100.1, dummyPos)) shouldBe true
|
||||||
assertTrue(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) < NumericLiteralValue(DataType.FLOAT, 100.1, dummyPos))
|
|
||||||
|
|
||||||
assertTrue(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) <= NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
|
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) <= NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe true
|
||||||
assertTrue(NumericLiteralValue(DataType.UWORD, 254, dummyPos) <= NumericLiteralValue(DataType.UWORD, 254, dummyPos))
|
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) <= NumericLiteralValue(DataType.UWORD, 254.0, dummyPos)) shouldBe true
|
||||||
assertTrue(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) <= NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos))
|
(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))
|
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) < NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos)) shouldBe false
|
||||||
assertFalse(NumericLiteralValue(DataType.UWORD, 254, dummyPos) < NumericLiteralValue(DataType.UWORD, 254, dummyPos))
|
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) < NumericLiteralValue(DataType.UWORD, 254.0, dummyPos)) shouldBe false
|
||||||
assertFalse(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) < NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos))
|
(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))
|
(NumericLiteralValue(DataType.UBYTE, 100.0, dummyPos) <= NumericLiteralValue(DataType.UBYTE, 99.0, dummyPos)) shouldBe false
|
||||||
assertFalse(NumericLiteralValue(DataType.UWORD, 254, dummyPos) <= NumericLiteralValue(DataType.UWORD, 253, dummyPos))
|
(NumericLiteralValue(DataType.UWORD, 254.0, dummyPos) <= NumericLiteralValue(DataType.UWORD, 253.0, dummyPos)) shouldBe false
|
||||||
assertFalse(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) <= NumericLiteralValue(DataType.FLOAT, 99.9, dummyPos))
|
(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) <= NumericLiteralValue(DataType.FLOAT, 99.9, dummyPos)) shouldBe false
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
})
|
||||||
|
|
||||||
|
@ -1,23 +1,34 @@
|
|||||||
package prog8tests
|
package prog8tests
|
||||||
|
|
||||||
import org.hamcrest.CoreMatchers.instanceOf
|
import io.kotest.assertions.fail
|
||||||
import org.hamcrest.MatcherAssert.assertThat
|
import io.kotest.assertions.withClue
|
||||||
import org.junit.jupiter.api.Test
|
import io.kotest.core.spec.style.FunSpec
|
||||||
import org.junit.jupiter.api.TestInstance
|
import io.kotest.matchers.shouldBe
|
||||||
import prog8.ast.statements.Block
|
import io.kotest.matchers.shouldNotBe
|
||||||
import prog8.ast.statements.Return
|
import io.kotest.matchers.string.shouldContain
|
||||||
import prog8.ast.statements.Subroutine
|
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.*
|
||||||
|
import prog8.ast.statements.*
|
||||||
|
import prog8.compiler.BeforeAsmGenerationAstChanger
|
||||||
|
import prog8.compiler.printProgram
|
||||||
import prog8.compiler.target.C64Target
|
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.assertSuccess
|
||||||
import prog8tests.helpers.compileText
|
import prog8tests.helpers.compileText
|
||||||
import kotlin.test.assertEquals
|
import prog8tests.helpers.generateAssembly
|
||||||
import kotlin.test.assertSame
|
|
||||||
import kotlin.test.assertTrue
|
|
||||||
|
|
||||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
class TestOptimization: FunSpec({
|
||||||
class TestOptimization {
|
test("remove empty subroutine except start") {
|
||||||
@Test
|
|
||||||
fun testRemoveEmptySubroutineExceptStart() {
|
|
||||||
val sourcecode = """
|
val sourcecode = """
|
||||||
main {
|
main {
|
||||||
sub start() {
|
sub start() {
|
||||||
@ -28,16 +39,19 @@ class TestOptimization {
|
|||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
val result = compileText(C64Target, true, sourcecode).assertSuccess()
|
val result = compileText(C64Target, true, sourcecode).assertSuccess()
|
||||||
val toplevelModule = result.programAst.toplevelModule
|
val toplevelModule = result.program.toplevelModule
|
||||||
val mainBlock = toplevelModule.statements.single() as Block
|
val mainBlock = toplevelModule.statements.single() as Block
|
||||||
val startSub = mainBlock.statements.single() as Subroutine
|
val startSub = mainBlock.statements.single() as Subroutine
|
||||||
assertSame(result.programAst.entrypoint, startSub)
|
result.program.entrypoint shouldBeSameInstanceAs startSub
|
||||||
assertEquals("start", startSub.name, "only start sub should remain")
|
withClue("only start sub should remain") {
|
||||||
assertTrue(startSub.statements.single() is Return, "compiler has inserted return in empty subroutines")
|
startSub.name shouldBe "start"
|
||||||
|
}
|
||||||
|
withClue("compiler has inserted return in empty subroutines") {
|
||||||
|
startSub.statements.single() shouldBe instanceOf<Return>()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("don't remove empty subroutine if it's referenced") {
|
||||||
fun testDontRemoveEmptySubroutineIfItsReferenced() {
|
|
||||||
val sourcecode = """
|
val sourcecode = """
|
||||||
main {
|
main {
|
||||||
sub start() {
|
sub start() {
|
||||||
@ -50,13 +64,506 @@ class TestOptimization {
|
|||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
val result = compileText(C64Target, true, sourcecode).assertSuccess()
|
val result = compileText(C64Target, true, sourcecode).assertSuccess()
|
||||||
val toplevelModule = result.programAst.toplevelModule
|
val toplevelModule = result.program.toplevelModule
|
||||||
val mainBlock = toplevelModule.statements.single() as Block
|
val mainBlock = toplevelModule.statements.single() as Block
|
||||||
val startSub = mainBlock.statements[0] as Subroutine
|
val startSub = mainBlock.statements[0] as Subroutine
|
||||||
val emptySub = mainBlock.statements[1] as Subroutine
|
val emptySub = mainBlock.statements[1] as Subroutine
|
||||||
assertSame(result.programAst.entrypoint, startSub)
|
result.program.entrypoint shouldBeSameInstanceAs startSub
|
||||||
assertEquals("start", startSub.name)
|
startSub.name shouldBe "start"
|
||||||
assertEquals("empty", emptySub.name)
|
emptySub.name shouldBe "empty"
|
||||||
assertTrue(emptySub.statements.single() is Return, "compiler has inserted return in empty subroutines")
|
withClue("compiler has inserted return in empty subroutines") {
|
||||||
|
emptySub.statements.single() shouldBe instanceOf<Return>()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
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)
|
||||||
|
tc.parent shouldNotBe null
|
||||||
|
number.parent shouldNotBe null
|
||||||
|
tc shouldBeSameInstanceAs number.parent
|
||||||
|
val constvalue = tc.constValue(program)!!
|
||||||
|
constvalue shouldBe instanceOf<NumericLiteralValue>()
|
||||||
|
constvalue.number shouldBe 11.0
|
||||||
|
constvalue.type shouldBe DataType.BYTE
|
||||||
|
constvalue.parent shouldBeSameInstanceAs tc.parent
|
||||||
|
}
|
||||||
|
|
||||||
|
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("constantfolded and silently typecasted for initializervalues") {
|
||||||
|
val sourcecode = """
|
||||||
|
main {
|
||||||
|
sub start() {
|
||||||
|
const ubyte TEST = 10
|
||||||
|
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 mainsub = result.program.entrypoint
|
||||||
|
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
|
||||||
|
val declX2 = mainsub.statements[3] as VarDecl
|
||||||
|
val initX2 = mainsub.statements[4] as Assignment
|
||||||
|
val declY1 = mainsub.statements[5] as VarDecl
|
||||||
|
val initY1 = mainsub.statements[6] as Assignment
|
||||||
|
val declY2 = mainsub.statements[7] as VarDecl
|
||||||
|
val initY2 = mainsub.statements[8] as Assignment
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
@ -1,109 +1,175 @@
|
|||||||
package prog8tests
|
package prog8tests
|
||||||
|
|
||||||
import com.github.michaelbull.result.Ok
|
import com.github.michaelbull.result.Ok
|
||||||
import org.hamcrest.MatcherAssert.assertThat
|
import com.github.michaelbull.result.expectError
|
||||||
import org.hamcrest.Matchers.equalTo
|
import com.github.michaelbull.result.getOrElse
|
||||||
import org.junit.jupiter.api.Test
|
import io.kotest.assertions.withClue
|
||||||
import org.junit.jupiter.api.TestInstance
|
import io.kotest.core.spec.style.FunSpec
|
||||||
import prog8.ast.base.DataType
|
import io.kotest.matchers.shouldBe
|
||||||
import prog8.ast.base.Position
|
|
||||||
import prog8.ast.expressions.NumericLiteralValue
|
|
||||||
import prog8.ast.expressions.StringLiteralValue
|
|
||||||
import prog8.compiler.target.cbm.Petscii
|
import prog8.compiler.target.cbm.Petscii
|
||||||
import kotlin.test.*
|
|
||||||
|
|
||||||
|
|
||||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
class TestPetscii: FunSpec({
|
||||||
class TestPetscii {
|
|
||||||
|
|
||||||
@Test
|
test("testZero") {
|
||||||
fun testZero() {
|
Petscii.encodePetscii("\u0000", true) shouldBe Ok(listOf<UByte>(0u))
|
||||||
assertThat(Petscii.encodePetscii("\u0000", true), equalTo(Ok(listOf<Short>(0))))
|
Petscii.encodePetscii("\u0000", false) shouldBe Ok(listOf<UByte>(0u))
|
||||||
assertThat(Petscii.encodePetscii("\u0000", false), equalTo(Ok(listOf<Short>(0))))
|
Petscii.decodePetscii(listOf(0u), true) shouldBe "\u0000"
|
||||||
assertThat(Petscii.decodePetscii(listOf(0), true), equalTo("\u0000"))
|
Petscii.decodePetscii(listOf(0u), false) shouldBe "\u0000"
|
||||||
assertThat(Petscii.decodePetscii(listOf(0), false), equalTo("\u0000"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testLowercase") {
|
||||||
fun testLowercase() {
|
Petscii.encodePetscii("hello WORLD 123 @!£", true) shouldBe
|
||||||
assertThat(Petscii.encodePetscii("hello WORLD 123 @!£", true), equalTo(
|
Ok(listOf<UByte>(72u, 69u, 76u, 76u, 79u, 32u, 0xd7u, 0xcfu, 0xd2u, 0xccu, 0xc4u, 32u, 49u, 50u, 51u, 32u, 64u, 33u, 0x5cu))
|
||||||
Ok(listOf<Short>(72, 69, 76, 76, 79, 32, 0xd7, 0xcf, 0xd2, 0xcc, 0xc4, 32, 49, 50, 51, 32, 64, 33, 0x5c))))
|
Petscii.encodePetscii("\uf11a", true) shouldBe Ok(listOf<UByte>(0x12u)) // reverse vid
|
||||||
assertThat(Petscii.encodePetscii("\uf11a", true), equalTo(Ok(listOf<Short>(0x12)))) // reverse vid
|
Petscii.encodePetscii("✓", true) shouldBe Ok(listOf<UByte>(0xfau))
|
||||||
assertThat(Petscii.encodePetscii("✓", true), equalTo(Ok(listOf<Short>(0xfa))))
|
withClue("expect lowercase error fallback") {
|
||||||
assertThat("expect lowercase error fallback", Petscii.encodePetscii("π", true), equalTo(Ok(listOf<Short>(255))))
|
Petscii.encodePetscii("π", true) shouldBe Ok(listOf<UByte>(255u))
|
||||||
assertThat("expect lowercase error fallback", Petscii.encodePetscii("♥", true), equalTo(Ok(listOf<Short>(0xd3))))
|
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"
|
||||||
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodePetscii(listOf(-1), true) }
|
|
||||||
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodePetscii(listOf(256), true) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testUppercase") {
|
||||||
fun testUppercase() {
|
Petscii.encodePetscii("HELLO 123 @!£") shouldBe
|
||||||
assertThat(Petscii.encodePetscii("HELLO 123 @!£"), equalTo(
|
Ok(listOf<UByte>(72u, 69u, 76u, 76u, 79u, 32u, 49u, 50u, 51u, 32u, 64u, 33u, 0x5cu))
|
||||||
Ok(listOf<Short>(72, 69, 76, 76, 79, 32, 49, 50, 51, 32, 64, 33, 0x5c))))
|
Petscii.encodePetscii("\uf11a") shouldBe Ok(listOf<UByte>(0x12u)) // reverse vid
|
||||||
assertThat(Petscii.encodePetscii("\uf11a"), equalTo(Ok(listOf<Short>(0x12)))) // reverse vid
|
Petscii.encodePetscii("♥") shouldBe Ok(listOf<UByte>(0xd3u))
|
||||||
assertThat(Petscii.encodePetscii("♥"), equalTo(Ok(listOf<Short>(0xd3))))
|
Petscii.encodePetscii("π") shouldBe Ok(listOf<UByte>(0xffu))
|
||||||
assertThat(Petscii.encodePetscii("π"), equalTo(Ok(listOf<Short>(0xff))))
|
withClue("expecting fallback") {
|
||||||
assertThat("expecting fallback", Petscii.encodePetscii("✓"), equalTo(Ok(listOf<Short>(250))))
|
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£♥π"
|
||||||
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodePetscii(listOf(-1)) }
|
|
||||||
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodePetscii(listOf(256)) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testScreencodeLowercase") {
|
||||||
fun testScreencodeLowercase() {
|
Petscii.encodeScreencode("hello WORLD 123 @!£", true) shouldBe
|
||||||
assertThat(Petscii.encodeScreencode("hello WORLD 123 @!£", true), equalTo(
|
Ok(listOf<UByte>(0x08u, 0x05u, 0x0cu, 0x0cu, 0x0fu, 0x20u, 0x57u, 0x4fu, 0x52u, 0x4cu, 0x44u, 0x20u, 0x31u, 0x32u, 0x33u, 0x20u, 0x00u, 0x21u, 0x1cu))
|
||||||
Ok(listOf<Short>(0x08, 0x05, 0x0c, 0x0c, 0x0f, 0x20, 0x57, 0x4f, 0x52, 0x4c, 0x44, 0x20, 0x31, 0x32, 0x33, 0x20, 0x00, 0x21, 0x1c))
|
Petscii.encodeScreencode("✓", true) shouldBe Ok(listOf<UByte>(0x7au))
|
||||||
))
|
withClue("expect fallback") {
|
||||||
assertThat(Petscii.encodeScreencode("✓", true), equalTo(Ok(listOf<Short>(0x7a))))
|
Petscii.encodeScreencode("♥", true) shouldBe Ok(listOf<UByte>(83u))
|
||||||
assertThat("expect fallback", Petscii.encodeScreencode("♥", true), equalTo(Ok(listOf<Short>(83))))
|
Petscii.encodeScreencode("π", true) shouldBe Ok(listOf<UByte>(94u))
|
||||||
assertThat("expect fallback", Petscii.encodeScreencode("π", true), equalTo(Ok(listOf<Short>(94))))
|
}
|
||||||
|
|
||||||
assertThat(Petscii.decodeScreencode(listOf(0x08, 0x57, 0x1c, 0x7a), true), equalTo("hW£✓"))
|
Petscii.decodeScreencode(listOf(0x08u, 0x57u, 0x1cu, 0x7au), true) shouldBe "hW£✓"
|
||||||
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodeScreencode(listOf(-1), true) }
|
|
||||||
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodeScreencode(listOf(256), true) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testScreencodeUppercase") {
|
||||||
fun testScreencodeUppercase() {
|
Petscii.encodeScreencode("WORLD 123 @!£") shouldBe
|
||||||
assertThat(Petscii.encodeScreencode("WORLD 123 @!£"), equalTo(
|
Ok(listOf<UByte>(0x17u, 0x0fu, 0x12u, 0x0cu, 0x04u, 0x20u, 0x31u, 0x32u, 0x33u, 0x20u, 0x00u, 0x21u, 0x1cu))
|
||||||
Ok(listOf<Short>(0x17, 0x0f, 0x12, 0x0c, 0x04, 0x20, 0x31, 0x32, 0x33, 0x20, 0x00, 0x21, 0x1c))))
|
Petscii.encodeScreencode("♥") shouldBe Ok(listOf<UByte>(0x53u))
|
||||||
assertThat(Petscii.encodeScreencode("♥"), equalTo(Ok(listOf<Short>(0x53))))
|
Petscii.encodeScreencode("π") shouldBe Ok(listOf<UByte>(0x5eu))
|
||||||
assertThat(Petscii.encodeScreencode("π"), equalTo(Ok(listOf<Short>(0x5e))))
|
Petscii.encodeScreencode("HELLO") shouldBe Ok(listOf<UByte>(8u, 5u, 12u, 12u, 15u))
|
||||||
assertThat(Petscii.encodeScreencode("HELLO"), equalTo(Ok(listOf<Short>(8, 5, 12, 12, 15))))
|
withClue("expecting fallback") {
|
||||||
assertThat("expecting fallback", Petscii.encodeScreencode("hello"), equalTo(Ok(listOf<Short>(8, 5, 12, 12, 15))))
|
Petscii.encodeScreencode("hello") shouldBe Ok(listOf<UByte>(8u, 5u, 12u, 12u, 15u))
|
||||||
assertThat("expecting fallback", Petscii.encodeScreencode("✓"), equalTo(Ok(listOf<Short>(122))))
|
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£♥π"
|
||||||
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodeScreencode(listOf(-1)) }
|
|
||||||
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodeScreencode(listOf(256)) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testErrorCases") {
|
||||||
fun testLiteralValueComparisons() {
|
Petscii.encodePetscii("~", true).expectError { "shouldn't be able to encode tilde" }
|
||||||
val ten = NumericLiteralValue(DataType.UWORD, 10, Position.DUMMY)
|
Petscii.encodePetscii("~", false).expectError { "shouldn't be able to encode tilde" }
|
||||||
val nine = NumericLiteralValue(DataType.UBYTE, 9, Position.DUMMY)
|
Petscii.encodeScreencode("~", true).expectError { "shouldn't be able to encode tilde" }
|
||||||
assertEquals(ten, ten)
|
Petscii.encodeScreencode("~", false).expectError { "shouldn't be able to encode tilde" }
|
||||||
assertNotEquals(ten, nine)
|
|
||||||
assertFalse(ten != ten)
|
|
||||||
assertTrue(ten != nine)
|
|
||||||
|
|
||||||
assertTrue(ten > nine)
|
|
||||||
assertTrue(ten >= nine)
|
|
||||||
assertTrue(ten >= ten)
|
|
||||||
assertFalse(ten > ten)
|
|
||||||
|
|
||||||
assertFalse(ten < nine)
|
|
||||||
assertFalse(ten <= nine)
|
|
||||||
assertTrue(ten <= ten)
|
|
||||||
assertFalse(ten < ten)
|
|
||||||
|
|
||||||
val abc = StringLiteralValue("abc", false, Position.DUMMY)
|
|
||||||
val abd = StringLiteralValue("abd", false, Position.DUMMY)
|
|
||||||
assertEquals(abc, abc)
|
|
||||||
assertTrue(abc!=abd)
|
|
||||||
assertFalse(abc!=abc)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
Petscii.encodePetscii("`", false).expectError { "shouldn't have translation for backtick" }
|
||||||
|
Petscii.encodePetscii("`", true).expectError { "shouldn't have translation for backtick" }
|
||||||
|
Petscii.encodePetscii("~", false).expectError { "shouldn't have translation for tilde" }
|
||||||
|
Petscii.encodePetscii("~", true).expectError { "shouldn't have translation for tilde" }
|
||||||
|
|
||||||
|
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("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
|
||||||
|
encodeP('|', false) shouldBe 221u
|
||||||
|
encodeP('|', true) shouldBe 221u
|
||||||
|
encodeS('|', false) shouldBe 93u
|
||||||
|
encodeS('|', true) shouldBe 93u
|
||||||
|
// ... same as '│', 0x7D -> BOX DRAWINGS LIGHT VERTICAL
|
||||||
|
encodeP('│', false) shouldBe 221u
|
||||||
|
encodeP('│', true) shouldBe 221u
|
||||||
|
encodeS('│', false) shouldBe 93u
|
||||||
|
encodeS('│', true) shouldBe 93u
|
||||||
|
|
||||||
|
// underscore
|
||||||
|
encodeP('_', false) shouldBe 228u
|
||||||
|
encodeP('_', true) shouldBe 228u
|
||||||
|
encodeS('_', false) shouldBe 100u
|
||||||
|
encodeS('_', true) shouldBe 100u
|
||||||
|
// ... same as '▁', 0xE4 LOWER ONE EIGHTH BLOCK
|
||||||
|
encodeP('▁', false) shouldBe 228u
|
||||||
|
encodeP('▁', true) shouldBe 228u
|
||||||
|
encodeS('▁', false) shouldBe 100u
|
||||||
|
encodeS('▁', true) shouldBe 100u
|
||||||
|
|
||||||
|
// ─ 0xC0 -> BOX DRAWINGS LIGHT HORIZONTAL
|
||||||
|
encodeP('─', false) shouldBe 192u
|
||||||
|
encodeP('─', true) shouldBe 192u
|
||||||
|
encodeS('─', false) shouldBe 64u
|
||||||
|
encodeS('─', true) shouldBe 64u
|
||||||
|
// │ 0x62 -> BOX DRAWINGS LIGHT VERTICAL
|
||||||
|
encodeP('│', false) shouldBe 221u
|
||||||
|
encodeP('│', true) shouldBe 221u
|
||||||
|
encodeS('│', false) shouldBe 93u
|
||||||
|
encodeS('│', true) shouldBe 93u
|
||||||
|
}
|
||||||
|
|
||||||
|
test("testBoxDrawingCharsDecoding") {
|
||||||
|
// ─ 0xC0 -> BOX DRAWINGS LIGHT HORIZONTAL
|
||||||
|
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
|
||||||
|
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'
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
335
compiler/test/TestScoping.kt
Normal file
335
compiler/test/TestScoping.kt
Normal file
@ -0,0 +1,335 @@
|
|||||||
|
package prog8tests
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
class TestScoping: FunSpec({
|
||||||
|
|
||||||
|
test("modules parent is global namespace") {
|
||||||
|
val src = """
|
||||||
|
main {
|
||||||
|
sub start() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
val result = compileText(C64Target, false, src, writeAssembly = false).assertSuccess()
|
||||||
|
val module = result.program.toplevelModule
|
||||||
|
module.parent shouldBe instanceOf<GlobalNamespace>()
|
||||||
|
module.program shouldBeSameInstanceAs result.program
|
||||||
|
module.parent.parent shouldBe instanceOf<ParentSentinel>()
|
||||||
|
}
|
||||||
|
|
||||||
|
test("anon scope vars moved into subroutine scope") {
|
||||||
|
val src = """
|
||||||
|
main {
|
||||||
|
sub start() {
|
||||||
|
repeat {
|
||||||
|
ubyte xx = 99
|
||||||
|
xx++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
val result = compileText(C64Target, false, src, writeAssembly = false).assertSuccess()
|
||||||
|
val module = result.program.toplevelModule
|
||||||
|
val mainBlock = module.statements.single() as Block
|
||||||
|
val start = mainBlock.statements.single() as Subroutine
|
||||||
|
val repeatbody = start.statements.filterIsInstance<RepeatLoop>().single().body
|
||||||
|
withClue("no vars moved to main block") {
|
||||||
|
mainBlock.statements.any { it is VarDecl } shouldBe false
|
||||||
|
}
|
||||||
|
val subroutineVars = start.statements.filterIsInstance<VarDecl>()
|
||||||
|
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
|
||||||
|
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("labels with anon scopes") {
|
||||||
|
val src = """
|
||||||
|
main {
|
||||||
|
sub start() {
|
||||||
|
uword addr
|
||||||
|
goto labeloutside
|
||||||
|
|
||||||
|
if true {
|
||||||
|
if true {
|
||||||
|
addr = &iflabel
|
||||||
|
addr = &labelinside
|
||||||
|
addr = &labeloutside
|
||||||
|
addr = &main.start.nested.nestedlabel
|
||||||
|
goto labeloutside
|
||||||
|
goto iflabel
|
||||||
|
goto main.start.nested.nestedlabel
|
||||||
|
}
|
||||||
|
iflabel:
|
||||||
|
}
|
||||||
|
|
||||||
|
repeat {
|
||||||
|
addr = &iflabel
|
||||||
|
addr = &labelinside
|
||||||
|
addr = &labeloutside
|
||||||
|
addr = &main.start.nested.nestedlabel
|
||||||
|
goto iflabel
|
||||||
|
goto labelinside
|
||||||
|
goto main.start.nested.nestedlabel
|
||||||
|
labelinside:
|
||||||
|
}
|
||||||
|
|
||||||
|
sub nested () {
|
||||||
|
nestedlabel:
|
||||||
|
addr = &nestedlabel
|
||||||
|
goto nestedlabel
|
||||||
|
goto main.start.nested.nestedlabel
|
||||||
|
}
|
||||||
|
|
||||||
|
labeloutside:
|
||||||
|
addr = &iflabel
|
||||||
|
addr = &labelinside
|
||||||
|
addr = &labeloutside
|
||||||
|
addr = &main.start.nested.nestedlabel
|
||||||
|
goto main.start.nested.nestedlabel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
val result = compileText(C64Target, false, src, writeAssembly = true).assertSuccess()
|
||||||
|
val module = result.program.toplevelModule
|
||||||
|
val mainBlock = module.statements.single() as Block
|
||||||
|
val start = mainBlock.statements.single() as Subroutine
|
||||||
|
val labels = start.statements.filterIsInstance<Label>()
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
})
|
265
compiler/test/TestSubroutines.kt
Normal file
265
compiler/test/TestSubroutines.kt
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
package prog8tests
|
||||||
|
|
||||||
|
import io.kotest.assertions.withClue
|
||||||
|
import io.kotest.core.spec.style.FunSpec
|
||||||
|
import io.kotest.matchers.shouldBe
|
||||||
|
import io.kotest.matchers.shouldNotBe
|
||||||
|
import io.kotest.matchers.string.shouldContain
|
||||||
|
import io.kotest.matchers.types.instanceOf
|
||||||
|
import prog8.ast.base.DataType
|
||||||
|
import prog8.ast.expressions.*
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
class TestSubroutines: FunSpec({
|
||||||
|
|
||||||
|
test("stringParameter") {
|
||||||
|
val text = """
|
||||||
|
main {
|
||||||
|
sub start() {
|
||||||
|
str text = "test"
|
||||||
|
|
||||||
|
asmfunc("text")
|
||||||
|
asmfunc(text)
|
||||||
|
asmfunc($2000)
|
||||||
|
func("text")
|
||||||
|
func(text)
|
||||||
|
func($2000)
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub asmfunc(str thing @AY) {
|
||||||
|
}
|
||||||
|
|
||||||
|
sub func(str thing) {
|
||||||
|
uword t2 = thing as uword
|
||||||
|
asmfunc(thing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
val result = compileText(C64Target, false, text, writeAssembly = false).assertSuccess()
|
||||||
|
val module = result.program.toplevelModule
|
||||||
|
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"}
|
||||||
|
asmfunc.isAsmSubroutine shouldBe true
|
||||||
|
asmfunc.parameters.single().type shouldBe DataType.STR
|
||||||
|
asmfunc.statements.isEmpty() shouldBe true
|
||||||
|
func.isAsmSubroutine shouldBe false
|
||||||
|
withClue("str param for normal subroutine should be changed into 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
|
||||||
|
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
|
||||||
|
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("stringParameterAsmGen") {
|
||||||
|
val text = """
|
||||||
|
main {
|
||||||
|
sub start() {
|
||||||
|
str text = "test"
|
||||||
|
|
||||||
|
asmfunc("text")
|
||||||
|
asmfunc(text)
|
||||||
|
asmfunc($2000)
|
||||||
|
func("text")
|
||||||
|
func(text)
|
||||||
|
func($2000)
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub asmfunc(str thing @AY) {
|
||||||
|
}
|
||||||
|
|
||||||
|
sub func(str thing) {
|
||||||
|
uword t2 = thing as uword
|
||||||
|
asmfunc(thing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
val result = compileText(C64Target, false, text, writeAssembly = true).assertSuccess()
|
||||||
|
val module = result.program.toplevelModule
|
||||||
|
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"}
|
||||||
|
asmfunc.isAsmSubroutine shouldBe true
|
||||||
|
asmfunc.parameters.single().type shouldBe DataType.STR
|
||||||
|
asmfunc.statements.single() shouldBe instanceOf<Return>()
|
||||||
|
func.isAsmSubroutine shouldBe false
|
||||||
|
withClue("asmgen should have changed str to uword type") {
|
||||||
|
func.parameters.single().type shouldBe DataType.UWORD
|
||||||
|
}
|
||||||
|
asmfunc.statements.last() shouldBe instanceOf<Return>()
|
||||||
|
|
||||||
|
func.statements.size shouldBe 5
|
||||||
|
func.statements[4] shouldBe instanceOf<Return>()
|
||||||
|
val paramvar = func.statements[0] as VarDecl
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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("array param not yet allowd (but should perhaps be?)") {
|
||||||
|
// 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?
|
||||||
|
errors.warnings.size shouldBe 0
|
||||||
|
errors.errors.single() shouldContain ".p8:9:16: Non-string pass-by-reference types cannot occur as a parameter type directly"
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO allow this?
|
||||||
|
xtest("arrayParameter") {
|
||||||
|
val text = """
|
||||||
|
main {
|
||||||
|
sub start() {
|
||||||
|
ubyte[] array = [1,2,3]
|
||||||
|
|
||||||
|
asmfunc(array)
|
||||||
|
asmfunc([4,5,6])
|
||||||
|
asmfunc($2000)
|
||||||
|
asmfunc(12.345)
|
||||||
|
func(array)
|
||||||
|
func([4,5,6])
|
||||||
|
func($2000)
|
||||||
|
func(12.345)
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub asmfunc(ubyte[] thing @AY) {
|
||||||
|
}
|
||||||
|
|
||||||
|
sub func(ubyte[22] thing) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
val result = compileText(C64Target, false, text, writeAssembly = false).assertSuccess()
|
||||||
|
val module = result.program.toplevelModule
|
||||||
|
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"}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
test("uword param and normal varindexed as array work as DirectMemoryRead") {
|
||||||
|
val text="""
|
||||||
|
main {
|
||||||
|
sub thing(uword rr) {
|
||||||
|
ubyte @shared xx = rr[1] ; should still work as var initializer that will be rewritten
|
||||||
|
ubyte @shared yy
|
||||||
|
yy = rr[2]
|
||||||
|
uword @shared other
|
||||||
|
ubyte zz = other[3]
|
||||||
|
}
|
||||||
|
|
||||||
|
sub start() {
|
||||||
|
ubyte[] array=[1,2,3]
|
||||||
|
thing(array)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
val result = compileText(C64Target, false, text, writeAssembly = true).assertSuccess()
|
||||||
|
val module = result.program.toplevelModule
|
||||||
|
val block = module.statements.single() as Block
|
||||||
|
val thing = block.statements.filterIsInstance<Subroutine>().single {it.name=="thing"}
|
||||||
|
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
|
||||||
|
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[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
|
||||||
|
(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("uword param and normal varindexed as array work as MemoryWrite") {
|
||||||
|
val text="""
|
||||||
|
main {
|
||||||
|
sub thing(uword rr) {
|
||||||
|
rr[10] = 42
|
||||||
|
}
|
||||||
|
|
||||||
|
sub start() {
|
||||||
|
ubyte[] array=[1,2,3]
|
||||||
|
thing(array)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
val result = compileText(C64Target, false, text, writeAssembly = true).assertSuccess()
|
||||||
|
val module = result.program.toplevelModule
|
||||||
|
val block = module.statements.single() as Block
|
||||||
|
val thing = block.statements.filterIsInstance<Subroutine>().single {it.name=="thing"}
|
||||||
|
block.name shouldBe "main"
|
||||||
|
thing.statements.size shouldBe 3 // "rr, rr assign, return void"
|
||||||
|
val assignRR = thing.statements[1] as Assignment
|
||||||
|
(assignRR.value as NumericLiteralValue).number.toInt() shouldBe 42
|
||||||
|
val memwrite = assignRR.target.memoryAddress
|
||||||
|
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
|
||||||
|
}
|
||||||
|
})
|
@ -1,175 +1,233 @@
|
|||||||
package prog8tests
|
package prog8tests
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test
|
import io.kotest.assertions.throwables.shouldThrow
|
||||||
import org.junit.jupiter.api.TestInstance
|
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.base.DataType
|
||||||
import prog8.compiler.*
|
import prog8.ast.expressions.Expression
|
||||||
|
import prog8.ast.statements.RegisterOrStatusflag
|
||||||
|
import prog8.ast.statements.Subroutine
|
||||||
import prog8.compiler.target.C64Target
|
import prog8.compiler.target.C64Target
|
||||||
import prog8.compiler.target.Cx16Target
|
import prog8.compiler.target.Cx16Target
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
|
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
|
||||||
import prog8.compiler.target.cx16.CX16MachineDefinition.CX16Zeropage
|
import prog8.compiler.target.cx16.CX16MachineDefinition.CX16Zeropage
|
||||||
import kotlin.test.assertEquals
|
import prog8.compilerinterface.*
|
||||||
import kotlin.test.assertFailsWith
|
import prog8tests.helpers.ErrorReporterForTests
|
||||||
import kotlin.test.assertFalse
|
|
||||||
import kotlin.test.assertTrue
|
|
||||||
|
|
||||||
|
|
||||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
class TestAbstractZeropage: FunSpec({
|
||||||
class TestC64Zeropage {
|
|
||||||
|
|
||||||
private val errors = ErrorReporter()
|
class DummyCompilationTarget: ICompilationTarget {
|
||||||
|
override val name: String = "dummy"
|
||||||
|
override val machine: IMachineDefinition
|
||||||
|
get() = throw NotImplementedError("dummy")
|
||||||
|
|
||||||
@Test
|
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
|
||||||
fun testNames() {
|
throw NotImplementedError("dummy")
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun memorySize(dt: DataType): Int {
|
||||||
|
throw NotImplementedError("dummy")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class DummyZeropage(options: CompilationOptions) : Zeropage(options) {
|
||||||
|
override val SCRATCH_B1 = 0x10u
|
||||||
|
override val SCRATCH_REG = 0x11u
|
||||||
|
override val SCRATCH_W1 = 0x20u
|
||||||
|
override val SCRATCH_W2 = 0x30u
|
||||||
|
|
||||||
|
init {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class TestC64Zeropage: FunSpec({
|
||||||
|
|
||||||
|
val errors = ErrorReporterForTests()
|
||||||
|
|
||||||
|
test("testNames") {
|
||||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target))
|
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("", DataType.UBYTE, null, errors)
|
zp.allocate("", DataType.UBYTE, null, errors)
|
||||||
zp.allocate("varname", DataType.UBYTE, null, errors)
|
zp.allocate("varname", DataType.UBYTE, null, errors)
|
||||||
assertFailsWith<AssertionError> {
|
shouldThrow<IllegalArgumentException> {
|
||||||
zp.allocate("varname", DataType.UBYTE, null, errors)
|
zp.allocate("varname", DataType.UBYTE, null, errors)
|
||||||
}
|
}
|
||||||
zp.allocate("varname2", DataType.UBYTE, null, errors)
|
zp.allocate("varname2", DataType.UBYTE, null, errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testZpFloatEnable") {
|
||||||
fun testZpFloatEnable() {
|
|
||||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
|
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
|
||||||
assertFailsWith<CompilerException> {
|
shouldThrow<InternalCompilerException> {
|
||||||
zp.allocate("", DataType.FLOAT, null, errors)
|
zp.allocate("", DataType.FLOAT, null, errors)
|
||||||
}
|
}
|
||||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false, C64Target))
|
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false, C64Target))
|
||||||
assertFailsWith<CompilerException> {
|
shouldThrow<InternalCompilerException> {
|
||||||
zp2.allocate("", DataType.FLOAT, null, errors)
|
zp2.allocate("", DataType.FLOAT, null, errors)
|
||||||
}
|
}
|
||||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, C64Target))
|
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, C64Target))
|
||||||
zp3.allocate("", DataType.FLOAT, null, errors)
|
zp3.allocate("", DataType.FLOAT, null, errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testZpModesWithFloats") {
|
||||||
fun testZpModesWithFloats() {
|
|
||||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
|
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.KERNALSAFE, emptyList(), false, false, C64Target))
|
||||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, 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.FLOATSAFE, emptyList(), false, false, C64Target))
|
||||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, 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))
|
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, C64Target))
|
||||||
assertFailsWith<CompilerException> {
|
shouldThrow<InternalCompilerException> {
|
||||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true, false, C64Target))
|
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true, false, C64Target))
|
||||||
}
|
}
|
||||||
assertFailsWith<CompilerException> {
|
shouldThrow<InternalCompilerException> {
|
||||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, false, C64Target))
|
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, false, C64Target))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testZpDontuse") {
|
||||||
fun testZpDontuse() {
|
|
||||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false, C64Target))
|
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false, C64Target))
|
||||||
println(zp.free)
|
println(zp.free)
|
||||||
assertEquals(0, zp.availableBytes())
|
zp.availableBytes() shouldBe 0
|
||||||
assertFailsWith<CompilerException> {
|
shouldThrow<InternalCompilerException> {
|
||||||
zp.allocate("", DataType.BYTE, null, errors)
|
zp.allocate("", DataType.BYTE, null, errors)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testFreeSpacesBytes") {
|
||||||
fun testFreeSpacesBytes() {
|
|
||||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
|
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))
|
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))
|
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))
|
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)
|
zp4.allocate("test", DataType.UBYTE, null, errors)
|
||||||
assertEquals(237, zp4.availableBytes())
|
zp4.availableBytes() shouldBe 237
|
||||||
zp4.allocate("test2", DataType.UBYTE, null, errors)
|
zp4.allocate("test2", DataType.UBYTE, null, errors)
|
||||||
assertEquals(236, zp4.availableBytes())
|
zp4.availableBytes() shouldBe 236
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testFreeSpacesWords") {
|
||||||
fun testFreeSpacesWords() {
|
|
||||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
|
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))
|
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))
|
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))
|
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)
|
zp4.allocate("test", DataType.UWORD, null, errors)
|
||||||
assertEquals(115, zp4.availableWords())
|
zp4.availableWords() shouldBe 115
|
||||||
zp4.allocate("test2", DataType.UWORD, null, errors)
|
zp4.allocate("test2", DataType.UWORD, null, errors)
|
||||||
assertEquals(114, zp4.availableWords())
|
zp4.availableWords() shouldBe 114
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testReservedSpace") {
|
||||||
fun testReservedSpace() {
|
|
||||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
|
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
|
||||||
assertEquals(238, zp1.availableBytes())
|
zp1.availableBytes() shouldBe 238
|
||||||
assertTrue(50 in zp1.free)
|
50u shouldBeIn zp1.free
|
||||||
assertTrue(100 in zp1.free)
|
100u shouldBeIn zp1.free
|
||||||
assertTrue(49 in zp1.free)
|
49u shouldBeIn zp1.free
|
||||||
assertTrue(101 in zp1.free)
|
101u shouldBeIn zp1.free
|
||||||
assertTrue(200 in zp1.free)
|
200u shouldBeIn zp1.free
|
||||||
assertTrue(255 in zp1.free)
|
255u shouldBeIn zp1.free
|
||||||
assertTrue(199 in zp1.free)
|
199u shouldBeIn zp1.free
|
||||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50 .. 100, 200..255), false, false, C64Target))
|
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50u .. 100u, 200u..255u), false, false, C64Target))
|
||||||
assertEquals(139, zp2.availableBytes())
|
zp2.availableBytes() shouldBe 139
|
||||||
assertFalse(50 in zp2.free)
|
50u shouldNotBeIn zp2.free
|
||||||
assertFalse(100 in zp2.free)
|
100u shouldNotBeIn zp2.free
|
||||||
assertTrue(49 in zp2.free)
|
49u shouldBeIn zp2.free
|
||||||
assertTrue(101 in zp2.free)
|
101u shouldBeIn zp2.free
|
||||||
assertFalse(200 in zp2.free)
|
200u shouldNotBeIn zp2.free
|
||||||
assertFalse(255 in zp2.free)
|
255u shouldNotBeIn zp2.free
|
||||||
assertTrue(199 in zp2.free)
|
199u shouldBeIn zp2.free
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testBasicsafeAllocation") {
|
||||||
fun testBasicsafeAllocation() {
|
|
||||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
|
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
|
||||||
assertEquals(18, zp.availableBytes())
|
zp.availableBytes() shouldBe 18
|
||||||
assertTrue(zp.hasByteAvailable())
|
zp.hasByteAvailable() shouldBe true
|
||||||
assertTrue(zp.hasWordAvailable())
|
zp.hasWordAvailable() shouldBe true
|
||||||
|
|
||||||
assertFailsWith<ZeropageDepletedError> {
|
shouldThrow<ZeropageDepletedError> {
|
||||||
// in regular zp there aren't 5 sequential bytes free
|
// in regular zp there aren't 5 sequential bytes free
|
||||||
zp.allocate("", DataType.FLOAT, null, errors)
|
zp.allocate("", DataType.FLOAT, null, errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i in 0 until zp.availableBytes()) {
|
for (i in 0 until zp.availableBytes()) {
|
||||||
val loc = zp.allocate("", DataType.UBYTE, null, errors)
|
val loc = zp.allocate("", DataType.UBYTE, null, errors)
|
||||||
assertTrue(loc > 0)
|
loc shouldBeGreaterThan 0u
|
||||||
}
|
}
|
||||||
assertEquals(0, zp.availableBytes())
|
zp.availableBytes() shouldBe 0
|
||||||
assertFalse(zp.hasByteAvailable())
|
zp.hasByteAvailable() shouldBe false
|
||||||
assertFalse(zp.hasWordAvailable())
|
zp.hasWordAvailable() shouldBe false
|
||||||
assertFailsWith<ZeropageDepletedError> {
|
shouldThrow<ZeropageDepletedError> {
|
||||||
zp.allocate("", DataType.UBYTE, null, errors)
|
zp.allocate("", DataType.UBYTE, null, errors)
|
||||||
}
|
}
|
||||||
assertFailsWith<ZeropageDepletedError> {
|
shouldThrow<ZeropageDepletedError> {
|
||||||
zp.allocate("", DataType.UWORD, null, errors)
|
zp.allocate("", DataType.UWORD, null, errors)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testFullAllocation") {
|
||||||
fun testFullAllocation() {
|
|
||||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
|
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
|
||||||
assertEquals(238, zp.availableBytes())
|
zp.availableBytes() shouldBe 238
|
||||||
assertTrue(zp.hasByteAvailable())
|
zp.hasByteAvailable() shouldBe true
|
||||||
assertTrue(zp.hasWordAvailable())
|
zp.hasWordAvailable() shouldBe true
|
||||||
val loc = zp.allocate("", DataType.UWORD, null, errors)
|
val loc = zp.allocate("", DataType.UWORD, null, errors)
|
||||||
assertTrue(loc > 3)
|
loc shouldBeGreaterThan 3u
|
||||||
assertFalse(loc in zp.free)
|
loc shouldNotBeIn zp.free
|
||||||
val num = zp.availableBytes() / 2
|
val num = zp.availableBytes() / 2
|
||||||
|
|
||||||
for(i in 0..num-4) {
|
for(i in 0..num-4) {
|
||||||
zp.allocate("", DataType.UWORD, null, errors)
|
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
|
// can't allocate because no more sequential bytes, only fragmented
|
||||||
zp.allocate("", DataType.UWORD, null, errors)
|
zp.allocate("", DataType.UWORD, null, errors)
|
||||||
}
|
}
|
||||||
@ -178,88 +236,85 @@ class TestC64Zeropage {
|
|||||||
zp.allocate("", DataType.UBYTE, null, errors)
|
zp.allocate("", DataType.UBYTE, null, errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
assertEquals(0, zp.availableBytes())
|
zp.availableBytes() shouldBe 0
|
||||||
assertFalse(zp.hasByteAvailable())
|
zp.hasByteAvailable() shouldBe false
|
||||||
assertFalse(zp.hasWordAvailable())
|
zp.hasWordAvailable() shouldBe false
|
||||||
assertFailsWith<ZeropageDepletedError> {
|
shouldThrow<ZeropageDepletedError> {
|
||||||
// no more space
|
// no more space
|
||||||
zp.allocate("", DataType.UBYTE, null, errors)
|
zp.allocate("", DataType.UBYTE, null, errors)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testEfficientAllocation") {
|
||||||
fun testEfficientAllocation() {
|
|
||||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
|
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
|
||||||
assertEquals(18, zp.availableBytes())
|
zp.availableBytes() shouldBe 18
|
||||||
assertEquals(0x04, zp.allocate("", DataType.WORD, null, errors))
|
zp.allocate("", DataType.WORD, null, errors) shouldBe 0x04u
|
||||||
assertEquals(0x06, zp.allocate("", DataType.UBYTE, null, errors))
|
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x06u
|
||||||
assertEquals(0x0a, zp.allocate("", DataType.UBYTE, null, errors))
|
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x0au
|
||||||
assertEquals(0x9b, zp.allocate("", DataType.UWORD, null, errors))
|
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0x9bu
|
||||||
assertEquals(0x9e, zp.allocate("", DataType.UWORD, null, errors))
|
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0x9eu
|
||||||
assertEquals(0xa5, zp.allocate("", DataType.UWORD, null, errors))
|
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0xa5u
|
||||||
assertEquals(0xb0, zp.allocate("", DataType.UWORD, null, errors))
|
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0xb0u
|
||||||
assertEquals(0xbe, zp.allocate("", DataType.UWORD, null, errors))
|
zp.allocate("", DataType.UWORD, null, errors) shouldBe 0xbeu
|
||||||
assertEquals(0x0e, zp.allocate("", DataType.UBYTE, null, errors))
|
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x0eu
|
||||||
assertEquals(0x92, zp.allocate("", DataType.UBYTE, null, errors))
|
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x92u
|
||||||
assertEquals(0x96, zp.allocate("", DataType.UBYTE, null, errors))
|
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x96u
|
||||||
assertEquals(0xf9, zp.allocate("", DataType.UBYTE, null, errors))
|
zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0xf9u
|
||||||
assertEquals(0, zp.availableBytes())
|
zp.availableBytes() shouldBe 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testReservedLocations") {
|
||||||
fun testReservedLocations() {
|
|
||||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target))
|
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: FunSpec({
|
||||||
class TestCx16Zeropage {
|
val errors = ErrorReporterForTests()
|
||||||
private val errors = ErrorReporter()
|
|
||||||
|
|
||||||
@Test
|
test("testReservedLocations") {
|
||||||
fun testReservedLocations() {
|
|
||||||
val zp = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, Cx16Target))
|
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
|
test("testFreeSpacesBytes") {
|
||||||
fun testFreeSpacesBytes() {
|
|
||||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, Cx16Target))
|
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))
|
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))
|
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)
|
zp3.allocate("test", DataType.UBYTE, null, errors)
|
||||||
assertEquals(215, zp3.availableBytes())
|
zp3.availableBytes() shouldBe 215
|
||||||
zp3.allocate("test2", DataType.UBYTE, null, errors)
|
zp3.allocate("test2", DataType.UBYTE, null, errors)
|
||||||
assertEquals(214, zp3.availableBytes())
|
zp3.availableBytes() shouldBe 214
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testFreeSpacesWords") {
|
||||||
fun testFreeSpacesWords() {
|
|
||||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target))
|
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))
|
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))
|
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)
|
zp3.allocate("test", DataType.UWORD, null, errors)
|
||||||
assertEquals(43, zp3.availableWords())
|
zp3.availableWords() shouldBe 43
|
||||||
zp3.allocate("test2", DataType.UWORD, null, errors)
|
zp3.allocate("test2", DataType.UWORD, null, errors)
|
||||||
assertEquals(42, zp3.availableWords())
|
zp3.availableWords() shouldBe 42
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
test("testReservedSpace") {
|
||||||
fun testReservedSpace() {
|
|
||||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target))
|
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target))
|
||||||
assertEquals(216, zp1.availableBytes())
|
zp1.availableBytes() shouldBe 216
|
||||||
assertTrue(0x22 in zp1.free)
|
0x22u shouldBeIn zp1.free
|
||||||
assertTrue(0x80 in zp1.free)
|
0x80u shouldBeIn zp1.free
|
||||||
assertTrue(0xff in zp1.free)
|
0xffu shouldBeIn zp1.free
|
||||||
assertFalse(0x02 in zp1.free)
|
0x02u shouldNotBeIn zp1.free
|
||||||
assertFalse(0x21 in zp1.free)
|
0x21u shouldNotBeIn zp1.free
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
@ -1,21 +1,36 @@
|
|||||||
package prog8tests.helpers
|
package prog8tests.helpers
|
||||||
|
|
||||||
import prog8.ast.IBuiltinFunctions
|
import prog8.ast.IBuiltinFunctions
|
||||||
import prog8.ast.IMemSizer
|
import prog8.ast.base.DataType
|
||||||
import prog8.ast.base.Position
|
import prog8.ast.base.Position
|
||||||
import prog8.ast.expressions.Expression
|
import prog8.ast.expressions.Expression
|
||||||
import prog8.ast.expressions.InferredTypes
|
import prog8.ast.expressions.InferredTypes
|
||||||
import prog8.ast.expressions.NumericLiteralValue
|
import prog8.ast.expressions.NumericLiteralValue
|
||||||
|
import prog8.compilerinterface.IMemSizer
|
||||||
|
import prog8.compilerinterface.IStringEncoding
|
||||||
|
|
||||||
val DummyFunctions = object : IBuiltinFunctions {
|
internal val DummyFunctions = object : IBuiltinFunctions {
|
||||||
override val names: Set<String> = emptySet()
|
override val names: Set<String> = emptySet()
|
||||||
override val purefunctionNames: Set<String> = emptySet()
|
override val purefunctionNames: Set<String> = emptySet()
|
||||||
override fun constValue(
|
override fun constValue(
|
||||||
name: String,
|
name: String,
|
||||||
args: List<Expression>,
|
args: List<Expression>,
|
||||||
position: Position,
|
position: Position,
|
||||||
memsizer: IMemSizer
|
|
||||||
): NumericLiteralValue? = null
|
): NumericLiteralValue? = null
|
||||||
|
|
||||||
override fun returnType(name: String, args: MutableList<Expression>) = InferredTypes.InferredType.unknown()
|
override fun returnType(name: String, args: MutableList<Expression>) = InferredTypes.InferredType.unknown()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal val DummyMemsizer = object : IMemSizer {
|
||||||
|
override fun memorySize(dt: DataType) = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val DummyStringEncoder = object : IStringEncoding {
|
||||||
|
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean): String {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user