mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 05:02:58 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
7f8fe75ab2 | ||
|
|
2815a14bb5 | ||
|
|
f4dfa60790 | ||
|
|
35e88dd529 | ||
|
|
4d5094a517 | ||
|
|
dd5abae721 | ||
|
|
8f2fb20934 | ||
|
|
44143f481a | ||
|
|
440abf4998 | ||
|
|
3c10427e04 | ||
|
|
74555a32ed | ||
|
|
85956b5828 | ||
|
|
41e40cad03 | ||
|
|
df2d5c6585 | ||
|
|
1a111b706e | ||
|
|
f696fce187 | ||
|
|
82d3d81bb2 | ||
|
|
4668932bac | ||
|
|
e6c41eac93 | ||
|
|
f0cff661df | ||
|
|
82d20dea39 | ||
|
|
804bb06859 | ||
|
|
5afa7e53f8 | ||
|
|
7f15b7b716 | ||
|
|
552e0c2248 | ||
|
|
e5b9e1f5e7 | ||
|
|
502bf90007 | ||
|
|
40bf117497 | ||
|
|
4011dce31b | ||
|
|
9e881e32f8 | ||
|
|
14aad2358f | ||
|
|
637a8899c5 | ||
|
|
cf0e395921 | ||
|
|
6ef438ce50 | ||
|
|
46e4b977a4 | ||
|
|
9626c5dead | ||
|
|
aea364e43d | ||
|
|
06defd0cb0 | ||
|
|
0f80897c50 | ||
|
|
57bb1c2c0d | ||
|
|
7b35b414e8 | ||
|
|
761aac7a23 | ||
|
|
15a02d7664 | ||
|
|
16ed68c1ec | ||
|
|
e63cf660c6 | ||
|
|
aaff484306 | ||
|
|
3281d9a215 | ||
|
|
0fcd61e00f | ||
|
|
c4523ea470 | ||
|
|
0447b3e4cc | ||
|
|
4d27c2901b | ||
|
|
855e18b31c | ||
|
|
d790878af6 | ||
|
|
85b244df2f | ||
|
|
6070afa6b6 | ||
|
|
975594703d | ||
|
|
6b8c3ef614 | ||
|
|
9b22f05381 | ||
|
|
ca3a990f9e | ||
|
|
557f4f689f | ||
|
|
66574d058a | ||
|
|
1c7c67060d | ||
|
|
9827ee97ad | ||
|
|
71a9a84211 | ||
|
|
367a2a4cee | ||
|
|
4f7465ba44 | ||
|
|
f891fc698c | ||
|
|
36bec62c9a | ||
|
|
dd5a2c8315 | ||
|
|
56bff46481 | ||
|
|
b83a0adb19 | ||
|
|
92ffefe656 | ||
|
|
51b2e41879 | ||
|
|
ef43bc9208 | ||
|
|
33733a4001 | ||
|
|
e5a1b37981 | ||
|
|
30aa72dc8e | ||
|
|
2c2d474059 | ||
|
|
c55ac0450f | ||
|
|
2d26b9c994 | ||
|
|
f38fe092ee | ||
|
|
7a33eb163b | ||
|
|
5db0408b9f | ||
|
|
3557d38ce0 | ||
|
|
7de4e9e66a | ||
|
|
73838ccb8b | ||
|
|
0509de76d5 | ||
|
|
f4b3d19059 | ||
|
|
f37fb82d53 | ||
|
|
dbe98f3fa5 | ||
|
|
371d4768e6 | ||
|
|
562d8386ec | ||
|
|
1625e4eb85 | ||
|
|
2365a076ac | ||
|
|
9898791771 | ||
|
|
a1c658274d | ||
|
|
be9998b48b | ||
|
|
e8f308f654 | ||
|
|
07132a2c42 | ||
|
|
9c4582e283 | ||
|
|
0204002d9b | ||
|
|
b3107cfad0 | ||
|
|
91ae68c07e | ||
|
|
06b3bf27b5 | ||
|
|
fbef63e150 | ||
|
|
bb8ee9bb3e | ||
|
|
25677a4126 | ||
|
|
3aeca0a770 | ||
|
|
4bd4733e52 | ||
|
|
9acec4d952 | ||
|
|
8388adcd1d | ||
|
|
5988ba76b5 | ||
|
|
1a06e7a16e | ||
|
|
7241cef7a5 | ||
|
|
5145296486 | ||
|
|
2cbf2d2226 | ||
|
|
754664aefa | ||
|
|
af99173cd7 | ||
|
|
fd1f30f92b | ||
|
|
d9ab2f8b90 | ||
|
|
bd6c60cf8a | ||
|
|
f0c150d93b | ||
|
|
c2986eaf47 | ||
|
|
ef0c4797bb | ||
|
|
ac02a99934 | ||
|
|
fb67d1155f | ||
|
|
eb46852bb9 | ||
|
|
007d8d2811 | ||
|
|
ebe04fc114 | ||
|
|
d7dd7f70c0 | ||
|
|
f2cb89a128 | ||
|
|
b8fade23de | ||
|
|
3b97a17648 | ||
|
|
0d06e3ff22 | ||
|
|
c914f7bbcf | ||
|
|
1b451180c1 | ||
|
|
ed061b362b | ||
|
|
e1026584c8 | ||
|
|
4c615e4fac | ||
|
|
7c9d48833b | ||
|
|
b60b195aec | ||
|
|
db76c8d7f4 | ||
|
|
de92740e87 | ||
|
|
522bf91c30 | ||
|
|
48d3abc1fe | ||
|
|
3f6f25e06f | ||
|
|
34ba07ee3b | ||
|
|
ac37319d20 | ||
|
|
b2c6274f74 | ||
|
|
402884b5ce | ||
|
|
23c99002c0 | ||
|
|
ee115b3337 | ||
|
|
82f5a141ed | ||
|
|
0567168ea9 | ||
|
|
c80a15846d | ||
|
|
5e194536a8 | ||
|
|
43c5ab8ecc | ||
|
|
cd295228ef | ||
|
|
6c42221620 | ||
|
|
0d73a7cd07 | ||
|
|
39d5b7edb0 | ||
|
|
6fa50a699f | ||
|
|
ddaef3e5d5 | ||
|
|
c3e9d4a9f8 | ||
|
|
7530fb67c8 | ||
|
|
19bb56df47 | ||
|
|
b0073ac933 | ||
|
|
137a89da15 | ||
|
|
44da7a302f | ||
|
|
4096aae8d4 | ||
|
|
d3e026d82a | ||
|
|
fa5ecd6495 | ||
|
|
af209ad50e | ||
|
|
7b89228fa7 | ||
|
|
d31a88206c | ||
|
|
cd4ed8765b | ||
|
|
b6f780d70d | ||
|
|
b071a58ca7 | ||
|
|
ce554f7718 | ||
|
|
99b1cec2e1 | ||
|
|
46911a8905 | ||
|
|
4eb61529f6 | ||
|
|
81abf29bec | ||
|
|
85897ef8cd | ||
|
|
b824c0b125 | ||
|
|
6367c6d116 | ||
|
|
a7736d88a9 | ||
|
|
049dbf5a78 | ||
|
|
4ac92caeb5 | ||
|
|
7af3da2a97 | ||
|
|
95a62fcdd1 | ||
|
|
7872d20554 | ||
|
|
a598eb7e98 | ||
|
|
c786acc39b | ||
|
|
07d8052a57 | ||
|
|
db9edb477e | ||
|
|
9bd3a6758a | ||
|
|
2cb1560bbd | ||
|
|
006059438f | ||
|
|
84ea3b9788 | ||
|
|
b667abde10 |
No files matched your search
@@ -26,6 +26,7 @@ parser.out
|
||||
parsetab.py
|
||||
.pytest_cache/
|
||||
.attach_pid*
|
||||
compiler/lib/
|
||||
|
||||
.gradle
|
||||
/prog8compiler.jar
|
||||
|
||||
Generated
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JavaProjectCodeInsightSettings">
|
||||
<excluded-names>
|
||||
<name>kotlin.Result</name>
|
||||
</excluded-names>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+2
-1
@@ -2,5 +2,6 @@
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<option name="BUILD_PROCESS_HEAP_SIZE" value="1200" />
|
||||
<bytecodeTargetLevel target="11" />
|
||||
</component>
|
||||
</project>
|
||||
</project>
|
||||
Generated
-6
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Kotlin2JvmCompilerArguments">
|
||||
<option name="jvmTarget" value="11" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
-9
@@ -1,9 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="antlr-4.9-complete">
|
||||
<CLASSES>
|
||||
<root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-4.9.2-complete.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
Generated
+19
@@ -0,0 +1,19 @@
|
||||
<component name="libraryTable">
|
||||
<library name="antlr.antlr4" type="repository">
|
||||
<properties maven-id="org.antlr:antlr4:4.9.2">
|
||||
<exclude>
|
||||
<dependency maven-id="com.ibm.icu:icu4j" />
|
||||
</exclude>
|
||||
</properties>
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.9.2/antlr4-4.9.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.9.2/antlr4-runtime-4.9.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr-runtime/3.5.2/antlr-runtime-3.5.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/ST4/4.3/ST4-4.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/abego/treelayout/org.abego.treelayout.core/1.0.3/org.abego.treelayout.core-1.0.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/javax.json/1.0.4/javax.json-1.0.4.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
Generated
-9
@@ -1,9 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="antlr-runtime-4.9">
|
||||
<CLASSES>
|
||||
<root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-runtime-4.9.2.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
Generated
-9
@@ -1,9 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="dbus-java-3.2.4">
|
||||
<CLASSES>
|
||||
<root url="jar://$PROJECT_DIR$/dbusCompilerService/lib/dbus-java-3.2.4.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
<component name="libraryTable">
|
||||
<library name="github.hypfvieh.dbus.java" type="repository">
|
||||
<properties maven-id="com.github.hypfvieh:dbus-java:3.3.1" />
|
||||
<CLASSES>
|
||||
<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.6/jnr-unixsocket-0.38.6.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-native.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.1/asm-commons-9.1.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.1/asm-tree-9.1.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-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-enxio/0.32.4/jnr-enxio-0.32.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!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
Generated
+10
@@ -0,0 +1,10 @@
|
||||
<component name="libraryTable">
|
||||
<library name="glassfish.javax.json" type="repository">
|
||||
<properties include-transitive-deps="false" maven-id="org.glassfish:javax.json:1.1.4" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/javax.json/1.1.4/javax.json-1.1.4.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
Generated
+10
@@ -0,0 +1,10 @@
|
||||
<component name="libraryTable">
|
||||
<library name="hamcrest" type="repository">
|
||||
<properties maven-id="org.hamcrest:hamcrest:2.2" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/hamcrest/hamcrest/2.2/hamcrest-2.2.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
Generated
-10
@@ -1,10 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="javax.json-api-1.1.4">
|
||||
<CLASSES>
|
||||
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/javax.json-api-1.1.4.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/javax.json-1.1.4.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<component name="libraryTable">
|
||||
<library name="jetbrains.kotlinx.cli.jvm" type="repository">
|
||||
<properties include-transitive-deps="false" maven-id="org.jetbrains.kotlinx:kotlinx-cli-jvm:0.3.3" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-cli-jvm/0.3.3/kotlinx-cli-jvm-0.3.3.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
Generated
+17
@@ -0,0 +1,17 @@
|
||||
<component name="libraryTable">
|
||||
<library name="junit.jupiter" type="repository">
|
||||
<properties maven-id="org.junit.jupiter:junit-jupiter:5.7.2" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter/5.7.2/junit-jupiter-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.7.2/junit-jupiter-api-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.0/apiguardian-api-1.1.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.7.2/junit-platform-commons-1.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-params/5.7.2/junit-jupiter-params-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-engine/5.7.2/junit-jupiter-engine-5.7.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.7.2/junit-platform-engine-1.7.2.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
Generated
-9
@@ -1,9 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="kotlinx-cli-jvm">
|
||||
<CLASSES>
|
||||
<root url="jar://$PROJECT_DIR$/compiler/lib/kotlinx-cli-jvm-0.3.2.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
<component name="libraryTable">
|
||||
<library name="michael.bull.kotlin.result.jvm" type="repository">
|
||||
<properties maven-id="com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.12" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/1.1.12/kotlin-result-jvm-1.1.12.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.5.10/kotlin-stdlib-jdk8-1.5.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.5.10/kotlin-stdlib-1.5.10.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.10/kotlin-stdlib-jdk7-1.5.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.5.10/kotlin-stdlib-common-1.5.10.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
Generated
-10
@@ -1,10 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="slf4j-api-1.7.30">
|
||||
<CLASSES>
|
||||
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/slf4j-api-1.7.30.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/slf4j-simple-1.7.30.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
Generated
+11
@@ -0,0 +1,11 @@
|
||||
<component name="libraryTable">
|
||||
<library name="slf4j.simple" type="repository">
|
||||
<properties maven-id="org.slf4j:slf4j-simple:1.7.30" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-simple/1.7.30/slf4j-simple-1.7.30.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
Generated
+13
@@ -0,0 +1,13 @@
|
||||
<component name="libraryTable">
|
||||
<library name="takes" type="repository">
|
||||
<properties maven-id="org.takes:takes:1.19" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/takes/takes/1.19/takes-1.19.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/cactoos/cactoos/0.42/cactoos-0.42.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/apache/commons/commons-text/1.4/commons-text-1.4.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/apache/commons/commons-lang3/3.7/commons-lang3-3.7.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
Generated
-12
@@ -1,12 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="takes-http">
|
||||
<CLASSES>
|
||||
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/cactoos-0.42.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/commons-lang3-3.7.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/commons-text-1.4.jar!/" />
|
||||
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/takes-1.19.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
Generated
-10
@@ -1,10 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="unittest-libs">
|
||||
<CLASSES>
|
||||
<root url="file://$PROJECT_DIR$/compiler/lib" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
<jarDirectory url="file://$PROJECT_DIR$/compiler/lib" recursive="false" />
|
||||
</library>
|
||||
</component>
|
||||
Generated
+3
-3
@@ -4,7 +4,7 @@
|
||||
<option name="perGrammarGenerationSettings">
|
||||
<list>
|
||||
<PerGrammarGenerationSettings>
|
||||
<option name="fileName" value="$PROJECT_DIR$/parser/antlr/prog8.g4" />
|
||||
<option name="fileName" value="$PROJECT_DIR$/parser/antlr/Prog8ANTLR.g4" />
|
||||
<option name="autoGen" value="true" />
|
||||
<option name="outputDir" value="$PROJECT_DIR$/parser/src/prog8/parser" />
|
||||
<option name="libDir" value="" />
|
||||
@@ -16,7 +16,7 @@
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="false" 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" />
|
||||
</component>
|
||||
</project>
|
||||
</project>
|
||||
Generated
+3
@@ -2,8 +2,11 @@
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<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$/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$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />
|
||||
|
||||
@@ -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
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
language: java
|
||||
sudo: false
|
||||
# jdk: openjdk8
|
||||
# dist: xenial
|
||||
|
||||
before_install:
|
||||
- chmod +x ./gradlew
|
||||
|
||||
script:
|
||||
- ./gradlew test
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
#### Just a few remarks upfront:
|
||||
* There is the (gradle/IDEA) module `parser`: that's the parser generated by ANTLR4, in Java. The only file to be edited here is the grammar, `prog8.g4`.
|
||||
* Then we have the module `compilerAst` - in Kotlin - which uses `parser` and adds AST nodes. Here we put our additions to the generated thing, *including any tests of the parsing stage*.
|
||||
- the name is a bit misleading, as this module isn't (or, resp. shouldn't be; see below) about *compiling*, only the parsing stage
|
||||
- also, the tree that comes out isn't much of an *abstraction*, but rather still more or less a parse tree (this might very well change).
|
||||
- **However, let's not *yet* rename the module.** We'll find a good name during refactoring.
|
||||
|
||||
#### Problems with `compilerAst`:
|
||||
* `ModuleImporter.kt`, doing (Prog8-) module resolution. That's not the parser's job.
|
||||
* `ParsingFailedError` (in `ModuleParsing.kt`): this exception (it is actually *not* a `java.lang.Error`...) is thrown in a number of places, where other exceptions would make more sense. For example: not finding a file should just yield a `NoSuchFileException`, not this one. The other problem with it is that it does not provide any additional information about the source of parsing error, in particular a `Position`.
|
||||
* During parsing, character literals are turned into UBYTEs (since there is no basic type e.g. CHAR). That's bad because it depends on a specific character encoding (`IStringEncoding` in `compilerAst/src/prog8/ast/AstToplevel.kt`) of/for some target platform. Note that *strings* are indeed encoded later, in the `compiler` module.
|
||||
* The same argument applies to `IMemSizer`, and - not entirely sure about that - `IBuiltinFunctions`.
|
||||
|
||||
#### Steps to take, in conceptual (!) order:
|
||||
1. introduce an abstraction `SourceCode` that encapsulates the origin and actual loading of Prog8 source code
|
||||
- from the local file system (use case: user programs)
|
||||
- from resources (prog8lib)
|
||||
- from plain strings (for testing)
|
||||
2. add subclass `ParseError : ParsingFailedError` which adds information about the *source of parsing error* (`SourceCode` and `Position`). We cannot just replace `ParsingFailedError` right away because it is so widely used (even in the `compiler` module). Therefore we'll just subclass for the time being, add more and more tests requiring the new one to be thrown (or, resp., NOT to be thrown), and gradually transition.
|
||||
3. introduce a minimal interface to the outside, input: `SourceCode`, output: a tree with a `Module` node as the root
|
||||
- this will be the Kotlin singleton `Prog8Parser` with the main method `parseModule`
|
||||
- plus, optionally, method's for registering/unregistering a listener with the parser
|
||||
- the *only* exception ever thrown / reported to listeners (TBD) will be `ParseError`
|
||||
- anything related to the lexer, error strategies, character/token streams is hidden from the outside
|
||||
- to make a clear distinction between the *generated* parser (and lexer) vs. `Prog8Parser`, and to discourage directly using the generated stuff, we'll rename the existing `prog8Parser`/`prog8Lexer` to `Prog8ANTLRParser` and `Prog8ANTLRLexer` and move them to package `prog8.parser.generated`
|
||||
4. introduce AST node `CharLiteral` and keep them until after identifier resolution and type checking; insert there an AST transformation step that turns them in UBYTE constants (literals)
|
||||
5. remove uses of `IStringEncoding` from module `compilerAst` - none should be necessary anymore
|
||||
6. move `IStringEncoding` to module `compiler`
|
||||
7. same with `ModuleImporter`, then rewrite that (addressing #46)
|
||||
8. refactor AST nodes and grammar: less generated parse tree nodes (`XyzContext`), less intermediary stuff (private classes in `Antrl2Kotlin.kt`), more compact code. Also: nicer names such as simply `StringLiteral` instead of `StringLiteralValue`
|
||||
9. re-think `IStringEncoding` to address #38
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
[](https://saythanks.io/to/irmen)
|
||||
[](https://travis-ci.org/irmen/prog8)
|
||||
[](https://prog8.readthedocs.io/)
|
||||
|
||||
Prog8 - Structured Programming Language for 8-bit 6502/65c02 microprocessors
|
||||
|
||||
+8
-1
@@ -1,3 +1,10 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.jvm" version "1.5.20" apply false
|
||||
id "org.jetbrains.kotlin.jvm" version "$kotlinVersion" apply false
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
|
||||
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"
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.12"
|
||||
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2'
|
||||
testImplementation 'org.hamcrest:hamcrest:2.2'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2'
|
||||
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDirs = ["${project.projectDir}/src"]
|
||||
}
|
||||
resources {
|
||||
srcDirs = ["${project.projectDir}/res"]
|
||||
}
|
||||
}
|
||||
test {
|
||||
java {
|
||||
srcDirs = ["${project.projectDir}/test"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
test {
|
||||
// Enable JUnit 5 (Gradle 4.6+).
|
||||
useJUnitPlatform()
|
||||
|
||||
// Always run tests, even when nothing changed.
|
||||
dependsOn 'cleanTest'
|
||||
|
||||
// Show test results.
|
||||
testLogging {
|
||||
events "skipped", "failed"
|
||||
}
|
||||
}
|
||||
@@ -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" scope="TEST" name="hamcrest" level="project" />
|
||||
<orderEntry type="library" name="junit.jupiter" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -0,0 +1,3 @@
|
||||
package prog8.compiler.target
|
||||
|
||||
class AssemblyError(msg: String) : RuntimeException(msg)
|
||||
@@ -0,0 +1,35 @@
|
||||
package prog8.compiler.target
|
||||
|
||||
import com.github.michaelbull.result.fold
|
||||
import prog8.ast.base.ByteDatatypes
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.PassByReferenceDatatypes
|
||||
import prog8.ast.base.WordDatatypes
|
||||
import prog8.compiler.target.c64.C64MachineDefinition
|
||||
import prog8.compiler.target.cbm.Petscii
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
|
||||
|
||||
object C64Target: ICompilationTarget {
|
||||
override val name = "c64"
|
||||
override val machine = C64MachineDefinition
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
|
||||
val coded = if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||
return coded.fold(
|
||||
failure = { throw it },
|
||||
success = { it }
|
||||
)
|
||||
}
|
||||
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
|
||||
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||
|
||||
override fun memorySize(dt: DataType): Int {
|
||||
return when(dt) {
|
||||
in ByteDatatypes -> 1
|
||||
in WordDatatypes -> 2
|
||||
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
|
||||
in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE
|
||||
else -> -9999999
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package prog8.compiler.target
|
||||
|
||||
import com.github.michaelbull.result.fold
|
||||
import prog8.ast.base.ByteDatatypes
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.PassByReferenceDatatypes
|
||||
import prog8.ast.base.WordDatatypes
|
||||
import prog8.compiler.target.cbm.Petscii
|
||||
import prog8.compiler.target.cx16.CX16MachineDefinition
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
|
||||
|
||||
object Cx16Target: ICompilationTarget {
|
||||
override val name = "cx16"
|
||||
override val machine = CX16MachineDefinition
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
|
||||
val coded= if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||
return coded.fold(
|
||||
failure = { throw it },
|
||||
success = { it }
|
||||
)
|
||||
}
|
||||
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
|
||||
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||
|
||||
override fun memorySize(dt: DataType): Int {
|
||||
return when(dt) {
|
||||
in ByteDatatypes -> 1
|
||||
in WordDatatypes -> 2
|
||||
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
|
||||
in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE
|
||||
else -> -9999999
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+23
-24
@@ -1,14 +1,13 @@
|
||||
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.compilerinterface.*
|
||||
import java.io.IOException
|
||||
import java.nio.file.Path
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.math.pow
|
||||
|
||||
internal object C64MachineDefinition: IMachineDefinition {
|
||||
object C64MachineDefinition: IMachineDefinition {
|
||||
|
||||
override val cpu = CpuType.CPU6502
|
||||
|
||||
@@ -28,18 +27,23 @@ internal object C64MachineDefinition: IMachineDefinition {
|
||||
|
||||
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)
|
||||
listOf("syslib")
|
||||
else
|
||||
emptyList()
|
||||
}
|
||||
|
||||
override fun launchEmulator(programName: String) {
|
||||
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
|
||||
if(selectedEmulator!=1) {
|
||||
System.err.println("The c64 target only supports the main emulator (Vice).")
|
||||
return
|
||||
}
|
||||
|
||||
for(emulator in listOf("x64sc", "x64")) {
|
||||
println("\nStarting C-64 emulator $emulator...")
|
||||
val cmdline = listOf(emulator, "-silent", "-moncommands", "$programName.vice-mon-list",
|
||||
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "$programName.prg")
|
||||
val cmdline = listOf(emulator, "-silent", "-moncommands", "${programNameWithPath}.$viceMonListPostfix",
|
||||
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg")
|
||||
val processb = ProcessBuilder(cmdline).inheritIO()
|
||||
val process: Process
|
||||
try {
|
||||
@@ -70,7 +74,7 @@ internal object C64MachineDefinition: IMachineDefinition {
|
||||
"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_REG = 0x03 // temp storage for a register, must be B1+1
|
||||
@@ -79,13 +83,12 @@ internal object C64MachineDefinition: IMachineDefinition {
|
||||
|
||||
|
||||
init {
|
||||
if (options.floats && options.zeropage !in setOf(ZeropageType.FLOATSAFE, ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
|
||||
throw CompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
|
||||
if (options.floats && options.zeropage !in arrayOf(ZeropageType.FLOATSAFE, ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
|
||||
throw InternalCompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
|
||||
|
||||
if (options.zeropage == ZeropageType.FULL) {
|
||||
free.addAll(0x04..0xf9)
|
||||
free.add(0xff)
|
||||
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
|
||||
free.removeAll(listOf(0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6)) // these are updated by IRQ
|
||||
} else {
|
||||
if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
|
||||
@@ -107,7 +110,7 @@ internal object C64MachineDefinition: IMachineDefinition {
|
||||
}
|
||||
|
||||
if (options.zeropage == ZeropageType.FLOATSAFE) {
|
||||
// remove the zero page locations used for floating point operations from the free list
|
||||
// remove the zeropage locations used for floating point operations from the free list
|
||||
free.removeAll(listOf(
|
||||
0x22, 0x23, 0x24, 0x25,
|
||||
0x10, 0x11, 0x12, 0x26, 0x27, 0x28, 0x29, 0x2a,
|
||||
@@ -118,7 +121,7 @@ internal object C64MachineDefinition: IMachineDefinition {
|
||||
))
|
||||
}
|
||||
|
||||
if(options.zeropage!=ZeropageType.DONTUSE) {
|
||||
if(options.zeropage!= ZeropageType.DONTUSE) {
|
||||
// add the free Zp addresses
|
||||
// these are valid for the C-64 but allow BASIC to keep running fully *as long as you don't use tape I/O*
|
||||
free.addAll(listOf(0x04, 0x05, 0x06, 0x0a, 0x0e,
|
||||
@@ -129,17 +132,13 @@ internal object C64MachineDefinition: IMachineDefinition {
|
||||
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)
|
||||
reserve(reserved)
|
||||
removeReservedFromFreePool()
|
||||
}
|
||||
}
|
||||
|
||||
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: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short):
|
||||
IMachineFloat {
|
||||
|
||||
companion object {
|
||||
val zero = Mflpt5(0, 0, 0, 0, 0)
|
||||
@@ -150,7 +149,7 @@ internal object C64MachineDefinition: IMachineDefinition {
|
||||
|
||||
val flt = num.toDouble()
|
||||
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)
|
||||
return zero
|
||||
|
||||
@@ -171,7 +170,7 @@ internal object C64MachineDefinition: IMachineDefinition {
|
||||
|
||||
return when {
|
||||
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
|
||||
else -> {
|
||||
val mantLong = mantissa.toLong()
|
||||
+44
-14
@@ -1,24 +1,41 @@
|
||||
package prog8.compiler.target.cbm
|
||||
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.compiler.OutputType
|
||||
import prog8.compiler.target.IAssemblyProgram
|
||||
import prog8.compiler.target.generatedLabelPrefix
|
||||
import com.github.michaelbull.result.Ok
|
||||
import com.github.michaelbull.result.Result
|
||||
import com.github.michaelbull.result.mapError
|
||||
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 kotlin.system.exitProcess
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.isRegularFile
|
||||
|
||||
|
||||
internal const val viceMonListPostfix = "vice-mon-list"
|
||||
|
||||
class AssemblyProgram(
|
||||
override val valid: Boolean,
|
||||
override val name: String,
|
||||
outputDir: Path,
|
||||
private val compTarget: String) : IAssemblyProgram {
|
||||
|
||||
class AssemblyProgram(override val name: String, outputDir: Path, private val compTarget: String) : IAssemblyProgram {
|
||||
private val assemblyFile = outputDir.resolve("$name.asm")
|
||||
private val prgFile = outputDir.resolve("$name.prg")
|
||||
private val binFile = outputDir.resolve("$name.bin")
|
||||
private val viceMonListFile = outputDir.resolve("$name.vice-mon-list")
|
||||
private val viceMonListFile = outputDir.resolve("$name.$viceMonListPostfix")
|
||||
|
||||
override fun assemble(options: CompilationOptions) {
|
||||
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)
|
||||
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
|
||||
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
|
||||
"--dump-labels", "--vice-labels", "-l", viceMonListFile.toString(), "--no-monitor")
|
||||
|
||||
if(quiet)
|
||||
command.add("--quiet")
|
||||
|
||||
val outFile = when (options.output) {
|
||||
OutputType.PRG -> {
|
||||
command.add("--cbm-prg")
|
||||
@@ -35,13 +52,11 @@ class AssemblyProgram(override val name: String, outputDir: Path, private val co
|
||||
|
||||
val proc = ProcessBuilder(command).inheritIO().start()
|
||||
val result = proc.waitFor()
|
||||
if (result != 0) {
|
||||
System.err.println("assembler failed with returncode $result")
|
||||
exitProcess(result)
|
||||
if (result == 0) {
|
||||
removeGeneratedLabelsFromMonlist()
|
||||
generateBreakpointList()
|
||||
}
|
||||
|
||||
removeGeneratedLabelsFromMonlist()
|
||||
generateBreakpointList()
|
||||
return result
|
||||
}
|
||||
|
||||
private fun removeGeneratedLabelsFromMonlist() {
|
||||
@@ -71,3 +86,18 @@ class AssemblyProgram(override val name: String, outputDir: Path, private val co
|
||||
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())
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
+142
-104
@@ -1,27 +1,32 @@
|
||||
package prog8.compiler.target.cpu6502.codegen
|
||||
|
||||
import com.github.michaelbull.result.fold
|
||||
import prog8.ast.*
|
||||
import prog8.ast.antlr.escape
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.*
|
||||
import prog8.compiler.functions.BuiltinFunctions
|
||||
import prog8.compiler.functions.FSignature
|
||||
import prog8.compiler.target.*
|
||||
import prog8.compiler.target.AssemblyError
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8.compiler.target.cbm.AssemblyProgram
|
||||
import prog8.compiler.target.cbm.loadAsmIncludeFile
|
||||
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignSource
|
||||
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignTarget
|
||||
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignment
|
||||
import prog8.compiler.target.cpu6502.codegen.assignment.AssignmentAsmGen
|
||||
import prog8.optimizer.CallGraph
|
||||
import prog8.compiler.target.cpu6502.codegen.assignment.SourceStorageKind
|
||||
import prog8.compilerinterface.*
|
||||
import prog8.parser.SourceCode
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import java.util.*
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
|
||||
internal class AsmGen(private val program: Program,
|
||||
class AsmGen(private val program: Program,
|
||||
val errors: IErrorReporter,
|
||||
val zeropage: Zeropage,
|
||||
val options: CompilationOptions,
|
||||
@@ -44,9 +49,9 @@ internal class AsmGen(private val program: Program,
|
||||
private val assignmentAsmGen = AssignmentAsmGen(program, this, expressionsAsmGen)
|
||||
private val builtinFunctionsAsmGen = BuiltinFunctionsAsmGen(program, this, assignmentAsmGen)
|
||||
internal val loopEndLabels = ArrayDeque<String>()
|
||||
private val blockLevelVarInits = mutableMapOf<Block, MutableSet<VarDecl>>()
|
||||
internal val slabs = mutableMapOf<String, Int>()
|
||||
internal val removals = mutableListOf<Pair<Statement, INameScope>>()
|
||||
internal val removals = mutableListOf<Pair<Statement, IStatementContainer>>()
|
||||
private val blockVariableInitializers = program.allBlocks.associateWith { it.statements.filterIsInstance<Assignment>() }
|
||||
|
||||
override fun compileToAssembly(): IAssemblyProgram {
|
||||
assemblyLines.clear()
|
||||
@@ -55,11 +60,14 @@ internal class AsmGen(private val program: Program,
|
||||
println("Generating assembly code... ")
|
||||
|
||||
header()
|
||||
val allBlocks = program.allBlocks()
|
||||
val allBlocks = program.allBlocks
|
||||
if(allBlocks.first().name != "main")
|
||||
throw AssemblyError("first block should be 'main'")
|
||||
for(b in program.allBlocks())
|
||||
|
||||
for(b in program.allBlocks) {
|
||||
b.statements.removeAll(blockVariableInitializers.getValue(b)) // no longer output the initialization assignments as regular statements in the block!
|
||||
block2asm(b)
|
||||
}
|
||||
|
||||
for(removal in removals.toList()) {
|
||||
removal.second.remove(removal.first)
|
||||
@@ -86,7 +94,11 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
return AssemblyProgram(program.name, outputDir, compTarget.name)
|
||||
return if(errors.noErrors())
|
||||
AssemblyProgram(true, program.name, outputDir, compTarget.name)
|
||||
else {
|
||||
AssemblyProgram(false, "<error>", outputDir, compTarget.name)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun isTargetCpu(cpu: CpuType) = compTarget.machine.cpu == cpu
|
||||
@@ -148,7 +160,7 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
if(options.zeropage !in setOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE)) {
|
||||
if(options.zeropage !in arrayOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE)) {
|
||||
out("""
|
||||
; zeropage is clobbered so we need to reset the machine at exit
|
||||
lda #>sys.reset_system
|
||||
@@ -212,26 +224,17 @@ internal class AsmGen(private val program: Program,
|
||||
stmts.forEach { translate(it) }
|
||||
subroutine.forEach { translateSubroutine(it as Subroutine) }
|
||||
|
||||
// if any global vars need to be initialized, generate a subroutine that does this
|
||||
// it will be called from program init.
|
||||
if(block in blockLevelVarInits) {
|
||||
// generate subroutine to initialize block-level variables
|
||||
val initializers = blockVariableInitializers.getValue(block)
|
||||
if(initializers.isNotEmpty()) {
|
||||
out("prog8_init_vars\t.proc\n")
|
||||
blockLevelVarInits.getValue(block).forEach { decl ->
|
||||
val scopedFullName = decl.makeScopedName(decl.name).split('.')
|
||||
require(scopedFullName.first()==block.name)
|
||||
assignInitialValueToVar(decl, scopedFullName.drop(1))
|
||||
}
|
||||
initializers.forEach { assign -> translate(assign) }
|
||||
out(" rts\n .pend")
|
||||
}
|
||||
|
||||
out(if("force_output" in block.options()) "\n\t.bend\n" else "\n\t.pend\n")
|
||||
}
|
||||
|
||||
private fun assignInitialValueToVar(decl: VarDecl, variableName: List<String>) {
|
||||
val asmName = asmVariableName(variableName)
|
||||
assignmentAsmGen.assignExpressionToVariable(decl.value!!, asmName, decl.datatype, decl.definingSubroutine())
|
||||
}
|
||||
|
||||
private var generatedLabelSequenceNumber: Int = 0
|
||||
|
||||
internal fun makeLabel(postfix: String): String {
|
||||
@@ -268,7 +271,6 @@ internal class AsmGen(private val program: Program,
|
||||
&& variable.datatype != DataType.FLOAT
|
||||
&& options.zeropage != ZeropageType.DONTUSE) {
|
||||
try {
|
||||
val errors = ErrorReporter()
|
||||
val address = zeropage.allocate(fullName, variable.datatype, null, errors)
|
||||
errors.report()
|
||||
out("${variable.name} = $address\t; auto zp ${variable.datatype}")
|
||||
@@ -493,7 +495,7 @@ internal class AsmGen(private val program: Program,
|
||||
return newName
|
||||
}
|
||||
|
||||
internal fun asmSymbolName(identifier: IdentifierReference): String {
|
||||
fun asmSymbolName(identifier: IdentifierReference): String {
|
||||
if(identifier.nameInSource.size==2 && identifier.nameInSource[0]=="prog8_slabs")
|
||||
return identifier.nameInSource.joinToString(".")
|
||||
|
||||
@@ -502,17 +504,19 @@ internal class AsmGen(private val program: Program,
|
||||
return identifier.nameInSource.joinToString(".")
|
||||
|
||||
val target = identifier.targetStatement(program)!!
|
||||
val targetScope = target.definingSubroutine()
|
||||
val identScope = identifier.definingSubroutine()
|
||||
val targetScope = target.definingSubroutine
|
||||
val identScope = identifier.definingSubroutine
|
||||
return if(targetScope !== identScope) {
|
||||
val scopedName = getScopedSymbolNameForTarget(identifier.nameInSource.last(), target)
|
||||
if(target is Label) {
|
||||
// make labels locally scoped in the asm. Is slightly problematic, see github issue #62
|
||||
val last = scopedName.removeLast()
|
||||
scopedName.add("_$last")
|
||||
}
|
||||
fixNameSymbols(scopedName.joinToString("."))
|
||||
} else {
|
||||
if(target is Label) {
|
||||
// make labels locally scoped in the asm. Is slightly problematic, see github issue #62
|
||||
val scopedName = identifier.nameInSource.toMutableList()
|
||||
val last = scopedName.removeLast()
|
||||
scopedName.add("_$last")
|
||||
@@ -522,7 +526,7 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
internal fun asmVariableName(identifier: IdentifierReference) =
|
||||
fun asmVariableName(identifier: IdentifierReference) =
|
||||
fixNameSymbols(identifier.nameInSource.joinToString("."))
|
||||
|
||||
private fun getScopedSymbolNameForTarget(actualName: String, target: Statement): MutableList<String> {
|
||||
@@ -549,14 +553,14 @@ internal class AsmGen(private val program: Program,
|
||||
internal fun asmVariableName(name: Iterable<String>) = fixNameSymbols(name.joinToString("."))
|
||||
|
||||
|
||||
internal fun loadByteFromPointerIntoA(pointervar: IdentifierReference): Pair<Boolean, String> {
|
||||
// returns if the pointer is already on the ZP itself or not (in the latter case SCRATCH_W1 is used as intermediary)
|
||||
val target = pointervar.targetStatement(program)
|
||||
when (target) {
|
||||
internal fun loadByteFromPointerIntoA(pointervar: IdentifierReference): String {
|
||||
// returns the source name of the zero page pointervar if it's already in the ZP,
|
||||
// otherwise returns "P8ZP_SCRATCH_W1" which is the intermediary
|
||||
when (val target = pointervar.targetStatement(program)) {
|
||||
is Label -> {
|
||||
val sourceName = asmSymbolName(pointervar)
|
||||
out(" lda $sourceName")
|
||||
return Pair(true, sourceName)
|
||||
return sourceName
|
||||
}
|
||||
is VarDecl -> {
|
||||
val sourceName = asmVariableName(pointervar)
|
||||
@@ -565,7 +569,7 @@ internal class AsmGen(private val program: Program,
|
||||
return if (isZpVar(scopedName)) {
|
||||
// pointervar is already in the zero page, no need to copy
|
||||
out(" lda ($sourceName)")
|
||||
Pair(true, sourceName)
|
||||
sourceName
|
||||
} else {
|
||||
out("""
|
||||
lda $sourceName
|
||||
@@ -573,13 +577,13 @@ internal class AsmGen(private val program: Program,
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda (P8ZP_SCRATCH_W1)""")
|
||||
Pair(false, sourceName)
|
||||
"P8ZP_SCRATCH_W1"
|
||||
}
|
||||
} else {
|
||||
return if (isZpVar(scopedName)) {
|
||||
// pointervar is already in the zero page, no need to copy
|
||||
out(" ldy #0 | lda ($sourceName),y")
|
||||
Pair(true, sourceName)
|
||||
sourceName
|
||||
} else {
|
||||
out("""
|
||||
lda $sourceName
|
||||
@@ -588,7 +592,7 @@ internal class AsmGen(private val program: Program,
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
lda (P8ZP_SCRATCH_W1),y""")
|
||||
Pair(false, sourceName)
|
||||
"P8ZP_SCRATCH_W1"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -828,12 +832,10 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("avoid calling this as it generates slow evalstack based code")
|
||||
internal fun translateExpression(expression: Expression) =
|
||||
expressionsAsmGen.translateExpression(expression)
|
||||
|
||||
internal fun translateExpression(indexer: ArrayIndex) =
|
||||
expressionsAsmGen.translateExpression(indexer)
|
||||
|
||||
internal fun translateBuiltinFunctionCallExpression(functionCall: FunctionCall, signature: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?) =
|
||||
builtinFunctionsAsmGen.translateFunctioncallExpression(functionCall, signature, resultToStack, resultRegister)
|
||||
|
||||
@@ -849,14 +851,53 @@ internal class AsmGen(private val program: Program,
|
||||
internal fun translateNormalAssignment(assign: AsmAssignment) =
|
||||
assignmentAsmGen.translateNormalAssignment(assign)
|
||||
|
||||
internal fun assignExpressionToRegister(expr: Expression, register: RegisterOrPair) =
|
||||
assignmentAsmGen.assignExpressionToRegister(expr, register)
|
||||
internal fun assignExpressionToRegister(expr: Expression, register: RegisterOrPair, signed: Boolean=false) =
|
||||
assignmentAsmGen.assignExpressionToRegister(expr, register, signed)
|
||||
|
||||
internal fun assignExpressionToVariable(expr: Expression, asmVarName: String, dt: DataType, scope: Subroutine?) =
|
||||
assignmentAsmGen.assignExpressionToVariable(expr, asmVarName, dt, scope)
|
||||
|
||||
internal fun assignVariableToRegister(asmVarName: String, register: RegisterOrPair) =
|
||||
assignmentAsmGen.assignVariableToRegister(asmVarName, register)
|
||||
internal fun assignVariableToRegister(asmVarName: String, register: RegisterOrPair, signed: Boolean=false) =
|
||||
assignmentAsmGen.assignVariableToRegister(asmVarName, register, signed)
|
||||
|
||||
internal fun assignRegister(reg: RegisterOrPair, target: AsmAssignTarget) {
|
||||
when(reg) {
|
||||
RegisterOrPair.A,
|
||||
RegisterOrPair.X,
|
||||
RegisterOrPair.Y -> assignmentAsmGen.assignRegisterByte(target, reg.asCpuRegister())
|
||||
RegisterOrPair.AX,
|
||||
RegisterOrPair.AY,
|
||||
RegisterOrPair.XY,
|
||||
in Cx16VirtualRegisters -> assignmentAsmGen.assignRegisterpairWord(target, reg)
|
||||
RegisterOrPair.FAC1 -> assignmentAsmGen.assignFAC1float(target)
|
||||
RegisterOrPair.FAC2 -> TODO("no support yet to assign FAC2 directly to something")
|
||||
else -> throw AssemblyError("invalid register")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignExpressionTo(value: Expression, target: AsmAssignTarget) {
|
||||
// don't use asmgen.translateExpression() to avoid evalstack
|
||||
when (target.datatype) {
|
||||
in ByteDatatypes -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.A)
|
||||
assignRegister(RegisterOrPair.A, target)
|
||||
}
|
||||
in WordDatatypes, in PassByReferenceDatatypes -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.AY)
|
||||
translateNormalAssignment(
|
||||
AsmAssignment(
|
||||
AsmAssignSource(SourceStorageKind.REGISTER, program, this, target.datatype, register=RegisterOrPair.AY),
|
||||
target, false, program.memsizer, value.position
|
||||
)
|
||||
)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.FAC1)
|
||||
assignRegister(RegisterOrPair.FAC1, target)
|
||||
}
|
||||
else -> throw AssemblyError("weird dt ${target.datatype}")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun translateSubroutine(sub: Subroutine) {
|
||||
@@ -864,7 +905,7 @@ internal class AsmGen(private val program: Program,
|
||||
|
||||
if(sub.inline) {
|
||||
if(options.optimize) {
|
||||
if(sub.isAsmSubroutine ||callGraph.unused(sub))
|
||||
if(sub.isAsmSubroutine || callGraph.unused(sub))
|
||||
return
|
||||
|
||||
// from an inlined subroutine only the local variables are generated,
|
||||
@@ -873,7 +914,7 @@ internal class AsmGen(private val program: Program,
|
||||
onlyVariables = true
|
||||
}
|
||||
else if(sub.amountOfRtsInAsm()==0) {
|
||||
// make sure the NOT INLINED subroutine actually does an rts at the end
|
||||
// make sure the NOT INLINED subroutine actually does a rts at the end
|
||||
sub.statements.add(Return(null, Position.DUMMY))
|
||||
}
|
||||
}
|
||||
@@ -897,12 +938,12 @@ internal class AsmGen(private val program: Program,
|
||||
memdefs2asm(sub.statements)
|
||||
|
||||
// the main.start subroutine is the program's entrypoint and should perform some initialization logic
|
||||
if(sub.name=="start" && sub.definingBlock().name=="main") {
|
||||
if(sub.name=="start" && sub.definingBlock.name=="main") {
|
||||
out("; program startup initialization")
|
||||
out(" cld")
|
||||
program.allBlocks().forEach {
|
||||
if(it.statements.filterIsInstance<VarDecl>().any { vd->vd.value!=null && vd.type==VarDeclType.VAR && vd.datatype in NumericDatatypes})
|
||||
out(" jsr ${it.name}.prog8_init_vars")
|
||||
blockVariableInitializers.forEach {
|
||||
if(it.value.isNotEmpty())
|
||||
out(" jsr ${it.key.name}.prog8_init_vars")
|
||||
}
|
||||
out("""
|
||||
tsx
|
||||
@@ -975,15 +1016,14 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
|
||||
private fun translate(stmt: IfStatement) {
|
||||
checkBooleanExpression(stmt.condition) // we require the condition to be of the form 'x <comparison> <value>'
|
||||
requireComparisonExpression(stmt.condition) // IfStatement: condition must be of form 'x <comparison> <value>'
|
||||
val booleanCondition = stmt.condition as BinaryExpression
|
||||
|
||||
// DISABLED FOR NOW:
|
||||
// if(!booleanCondition.left.isSimple || !booleanCondition.right.isSimple)
|
||||
// throw AssemblyError("both operands for if comparison expression should have been simplified")
|
||||
|
||||
if (stmt.elsepart.containsNoCodeNorVars()) {
|
||||
// empty else
|
||||
if (stmt.elsepart.isEmpty()) {
|
||||
val endLabel = makeLabel("if_end")
|
||||
expressionsAsmGen.translateComparisonExpressionWithJumpIfFalse(booleanCondition, endLabel)
|
||||
translate(stmt.truepart)
|
||||
@@ -1002,9 +1042,9 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkBooleanExpression(condition: Expression) {
|
||||
private fun requireComparisonExpression(condition: Expression) {
|
||||
if(condition !is BinaryExpression || condition.operator !in comparisonOperators)
|
||||
throw AssemblyError("expected boolean expression $condition")
|
||||
throw AssemblyError("expected boolean comparison expression $condition")
|
||||
}
|
||||
|
||||
private fun translate(stmt: RepeatLoop) {
|
||||
@@ -1055,7 +1095,7 @@ internal class AsmGen(private val program: Program,
|
||||
val dt = stmt.iterations!!.inferType(program)
|
||||
if(!dt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
when (dt.typeOrElse(DataType.UNDEFINED)) {
|
||||
when (dt.getOr(DataType.UNDEFINED)) {
|
||||
in ByteDatatypes -> {
|
||||
assignExpressionToRegister(stmt.iterations!!, RegisterOrPair.A)
|
||||
repeatByteCountInA(null, repeatLabel, endLabel, stmt)
|
||||
@@ -1076,7 +1116,7 @@ internal class AsmGen(private val program: Program,
|
||||
// note: A/Y must have been loaded with the number of iterations!
|
||||
if(constIterations==0)
|
||||
return
|
||||
// no need to explicitly test for 0 iterations as this is done in the count down logic below
|
||||
// no need to explicitly test for 0 iterations as this is done in the countdown logic below
|
||||
|
||||
val counterVar: String = createRepeatCounterVar(DataType.UWORD, constIterations, stmt)
|
||||
out("""
|
||||
@@ -1097,7 +1137,7 @@ $repeatLabel lda $counterVar
|
||||
}
|
||||
|
||||
private fun repeatByteCountInA(constIterations: Int?, repeatLabel: String, endLabel: String, stmt: RepeatLoop) {
|
||||
// note: A must have been loaded with the number of iterations!
|
||||
// note: A must be loaded with the number of iterations!
|
||||
if(constIterations==0)
|
||||
return
|
||||
|
||||
@@ -1113,7 +1153,7 @@ $repeatLabel lda $counterVar
|
||||
}
|
||||
|
||||
private fun createRepeatCounterVar(dt: DataType, constIterations: Int?, stmt: RepeatLoop): String {
|
||||
val asmInfo = stmt.definingSubroutine()!!.asmGenInfo
|
||||
val asmInfo = stmt.definingSubroutine!!.asmGenInfo
|
||||
var parent = stmt.parent
|
||||
while(parent !is ParentSentinel) {
|
||||
if(parent is RepeatLoop)
|
||||
@@ -1155,7 +1195,7 @@ $repeatLabel lda $counterVar
|
||||
}
|
||||
|
||||
private fun translate(stmt: WhileLoop) {
|
||||
checkBooleanExpression(stmt.condition) // we require the condition to be of the form 'x <comparison> <value>'
|
||||
requireComparisonExpression(stmt.condition) // WhileLoop: condition must be of form 'x <comparison> <value>'
|
||||
val booleanCondition = stmt.condition as BinaryExpression
|
||||
val whileLabel = makeLabel("while")
|
||||
val endLabel = makeLabel("whileend")
|
||||
@@ -1169,7 +1209,7 @@ $repeatLabel lda $counterVar
|
||||
}
|
||||
|
||||
private fun translate(stmt: UntilLoop) {
|
||||
checkBooleanExpression(stmt.condition) // we require the condition to be of the form 'x <comparison> <value>'
|
||||
requireComparisonExpression(stmt.condition) // UntilLoop: condition must be of form 'x <comparison> <value>'
|
||||
val booleanCondition = stmt.condition as BinaryExpression
|
||||
val repeatLabel = makeLabel("repeat")
|
||||
val endLabel = makeLabel("repeatend")
|
||||
@@ -1187,7 +1227,7 @@ $repeatLabel lda $counterVar
|
||||
val conditionDt = stmt.condition.inferType(program)
|
||||
if(!conditionDt.isKnown)
|
||||
throw AssemblyError("unknown condition dt")
|
||||
if(conditionDt.typeOrElse(DataType.BYTE) in ByteDatatypes)
|
||||
if(conditionDt.getOr(DataType.BYTE) in ByteDatatypes)
|
||||
assignExpressionToRegister(stmt.condition, RegisterOrPair.A)
|
||||
else
|
||||
assignExpressionToRegister(stmt.condition, RegisterOrPair.AY)
|
||||
@@ -1202,7 +1242,7 @@ $repeatLabel lda $counterVar
|
||||
choiceBlocks.add(choiceLabel to choice.statements)
|
||||
for (cv in choice.values!!) {
|
||||
val value = (cv as NumericLiteralValue).number.toInt()
|
||||
if(conditionDt.typeOrElse(DataType.BYTE) in ByteDatatypes) {
|
||||
if(conditionDt.getOr(DataType.BYTE) in ByteDatatypes) {
|
||||
out(" cmp #${value.toHex()} | beq $choiceLabel")
|
||||
} else {
|
||||
out("""
|
||||
@@ -1226,7 +1266,8 @@ $repeatLabel lda $counterVar
|
||||
}
|
||||
|
||||
private fun translate(stmt: Label) {
|
||||
out("_${stmt.name}") // underscore prefix to make sure it's a local label
|
||||
// underscore prefix to make sure it's a local label. Is slightly problematic, see github issue #62
|
||||
out("_${stmt.name}")
|
||||
}
|
||||
|
||||
private fun translate(scope: AnonymousScope) {
|
||||
@@ -1235,7 +1276,7 @@ $repeatLabel lda $counterVar
|
||||
}
|
||||
|
||||
private fun translate(stmt: BranchStatement) {
|
||||
if(stmt.truepart.containsNoCodeNorVars() && stmt.elsepart.containsCodeOrVars())
|
||||
if(stmt.truepart.isEmpty() && stmt.elsepart.isNotEmpty())
|
||||
throw AssemblyError("only else part contains code, shoud have been switched already")
|
||||
|
||||
val jump = stmt.truepart.statements.first() as? Jump
|
||||
@@ -1247,7 +1288,7 @@ $repeatLabel lda $counterVar
|
||||
} else {
|
||||
val truePartIsJustBreak = stmt.truepart.statements.firstOrNull() is Break
|
||||
val elsePartIsJustBreak = stmt.elsepart.statements.firstOrNull() is Break
|
||||
if(stmt.elsepart.containsNoCodeNorVars()) {
|
||||
if(stmt.elsepart.isEmpty()) {
|
||||
if(truePartIsJustBreak) {
|
||||
// branch with just a break (jump out of loop)
|
||||
val instruction = branchInstruction(stmt.condition, false)
|
||||
@@ -1287,39 +1328,37 @@ $repeatLabel lda $counterVar
|
||||
}
|
||||
}
|
||||
|
||||
private fun translate(stmt: VarDecl) {
|
||||
if(stmt.value!=null && stmt.type==VarDeclType.VAR && stmt.datatype in NumericDatatypes) {
|
||||
// generate an assignment statement to (re)initialize the variable's value.
|
||||
// if the vardecl is not in a subroutine however, we have to initialize it globally.
|
||||
if(stmt.definingSubroutine()==null) {
|
||||
val block = stmt.definingBlock()
|
||||
var inits = blockLevelVarInits[block]
|
||||
if(inits==null) {
|
||||
inits = mutableSetOf()
|
||||
blockLevelVarInits[block] = inits
|
||||
}
|
||||
inits.add(stmt)
|
||||
} else {
|
||||
val next = (stmt.parent as INameScope).nextSibling(stmt)
|
||||
if (next !is ForLoop || next.loopVar.nameInSource.single() != stmt.name) {
|
||||
assignInitialValueToVar(stmt, listOf(stmt.name))
|
||||
}
|
||||
}
|
||||
}
|
||||
private fun translate(decl: VarDecl) {
|
||||
if(decl.type==VarDeclType.VAR && decl.value != null && decl.datatype in NumericDatatypes)
|
||||
throw AssemblyError("vardecls for variables, with initial numerical value, should have been rewritten as plain vardecl + assignment $decl")
|
||||
// at this time, nothing has to be done here anymore code-wise
|
||||
}
|
||||
|
||||
private fun translate(stmt: Directive) {
|
||||
when(stmt.directive) {
|
||||
"%asminclude" -> {
|
||||
val sourcecode = loadAsmIncludeFile(stmt.args[0].str!!, stmt.definingModule().source)
|
||||
assemblyLines.add(sourcecode.trimEnd().trimStart('\n'))
|
||||
val includedName = stmt.args[0].str!!
|
||||
if(stmt.definingModule.source is SourceCode.Generated)
|
||||
TODO("%asminclude inside non-library, non-filesystem module")
|
||||
loadAsmIncludeFile(includedName, stmt.definingModule.source).fold(
|
||||
success = { assemblyLines.add(it.trimEnd().trimStart('\n')) },
|
||||
failure = { errors.err(it.toString(), stmt.position) }
|
||||
)
|
||||
}
|
||||
"%asmbinary" -> {
|
||||
val includedName = stmt.args[0].str!!
|
||||
val offset = if(stmt.args.size>1) ", ${stmt.args[1].int}" else ""
|
||||
val length = if(stmt.args.size>2) ", ${stmt.args[2].int}" else ""
|
||||
val includedSourcePath = stmt.definingModule().source.resolveSibling(stmt.args[0].str)
|
||||
val relPath = Paths.get("").relativize(includedSourcePath)
|
||||
out(" .binary \"$relPath\" $offset $length")
|
||||
if(stmt.definingModule.source is SourceCode.Generated)
|
||||
TODO("%asmbinary inside non-library, non-filesystem module")
|
||||
val sourcePath = Path(stmt.definingModule.source.origin)
|
||||
val includedPath = sourcePath.resolveSibling(includedName)
|
||||
val pathForAssembler = outputDir // #54: 64tass needs the path *relative to the .asm file*
|
||||
.toAbsolutePath()
|
||||
.relativize(includedPath.toAbsolutePath())
|
||||
.normalize() // avoid assembler warnings (-Wportable; only some, not all)
|
||||
.toString().replace('\\', '/')
|
||||
out(" .binary \"$pathForAssembler\" $offset $length")
|
||||
}
|
||||
"%breakpoint" -> {
|
||||
val label = "_prog8_breakpoint_${breakpointLabels.size+1}"
|
||||
@@ -1347,7 +1386,7 @@ $label nop""")
|
||||
|
||||
internal fun translate(ret: Return, withRts: Boolean=true) {
|
||||
ret.value?.let { returnvalue ->
|
||||
val sub = ret.definingSubroutine()!!
|
||||
val sub = ret.definingSubroutine!!
|
||||
val returnType = sub.returntypes.single()
|
||||
val returnReg = sub.asmReturnvaluesRegisters.single()
|
||||
if(returnReg.registerOrPair==null)
|
||||
@@ -1360,7 +1399,7 @@ $label nop""")
|
||||
else -> {
|
||||
// all else take its address and assign that also to AY register pair
|
||||
val addrofValue = AddressOf(returnvalue as IdentifierReference, returnvalue.position)
|
||||
assignmentAsmGen.assignExpressionToRegister(addrofValue, returnReg.registerOrPair!!)
|
||||
assignmentAsmGen.assignExpressionToRegister(addrofValue, returnReg.registerOrPair!!, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1436,22 +1475,22 @@ $label nop""")
|
||||
if(pointerOffsetExpr is BinaryExpression && pointerOffsetExpr.operator=="+") {
|
||||
val leftDt = pointerOffsetExpr.left.inferType(program)
|
||||
val rightDt = pointerOffsetExpr.left.inferType(program)
|
||||
if(leftDt.istype(DataType.UWORD) && rightDt.istype(DataType.UBYTE))
|
||||
if(leftDt istype DataType.UWORD && rightDt istype DataType.UBYTE)
|
||||
return Pair(pointerOffsetExpr.left, pointerOffsetExpr.right)
|
||||
if(leftDt.istype(DataType.UBYTE) && rightDt.istype(DataType.UWORD))
|
||||
if(leftDt istype DataType.UBYTE && rightDt istype DataType.UWORD)
|
||||
return Pair(pointerOffsetExpr.right, pointerOffsetExpr.left)
|
||||
if(leftDt.istype(DataType.UWORD) && rightDt.istype(DataType.UWORD)) {
|
||||
if(leftDt istype DataType.UWORD && rightDt istype DataType.UWORD) {
|
||||
// could be that the index was a constant numeric byte but converted to word, check that
|
||||
val constIdx = pointerOffsetExpr.right.constValue(program)
|
||||
if(constIdx!=null && constIdx.number.toInt()>=0 && constIdx.number.toInt()<=255) {
|
||||
return Pair(pointerOffsetExpr.left, NumericLiteralValue(DataType.UBYTE, constIdx.number, constIdx.position))
|
||||
}
|
||||
// could be that the index was type casted into uword, check that
|
||||
// could be that the index was typecasted into uword, check that
|
||||
val rightTc = pointerOffsetExpr.right as? TypecastExpression
|
||||
if(rightTc!=null && rightTc.expression.inferType(program).istype(DataType.UBYTE))
|
||||
if(rightTc!=null && rightTc.expression.inferType(program) istype DataType.UBYTE)
|
||||
return Pair(pointerOffsetExpr.left, rightTc.expression)
|
||||
val leftTc = pointerOffsetExpr.left as? TypecastExpression
|
||||
if(leftTc!=null && leftTc.expression.inferType(program).istype(DataType.UBYTE))
|
||||
if(leftTc!=null && leftTc.expression.inferType(program) istype DataType.UBYTE)
|
||||
return Pair(pointerOffsetExpr.right, leftTc.expression)
|
||||
}
|
||||
|
||||
@@ -1464,7 +1503,7 @@ $label nop""")
|
||||
|
||||
fun evalBytevalueWillClobberA(expr: Expression): Boolean {
|
||||
val dt = expr.inferType(program)
|
||||
if(!dt.istype(DataType.UBYTE) && !dt.istype(DataType.BYTE))
|
||||
if(dt isnot DataType.UBYTE && dt isnot DataType.BYTE)
|
||||
return true
|
||||
return when(expr) {
|
||||
is IdentifierReference -> false
|
||||
@@ -1480,8 +1519,7 @@ $label nop""")
|
||||
val ptrAndIndex = pointerViaIndexRegisterPossible(expr)
|
||||
if(ptrAndIndex!=null) {
|
||||
val pointervar = ptrAndIndex.first as? IdentifierReference
|
||||
val target = pointervar?.targetStatement(program)
|
||||
when(target) {
|
||||
when(pointervar?.targetStatement(program)) {
|
||||
is Label -> {
|
||||
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
|
||||
out(" lda ${asmSymbolName(pointervar)},y")
|
||||
+2
-2
@@ -74,7 +74,7 @@ private fun getLinesBy(lines: MutableList<String>, windowSize: Int) =
|
||||
lines.withIndex().filter { it.value.isNotBlank() && !it.value.trimStart().startsWith(';') }.windowed(windowSize, partialWindows = false)
|
||||
|
||||
private fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||
// the when statement (on bytes) generates a sequence of:
|
||||
// when statement (on bytes) generates a sequence of:
|
||||
// lda $ce01,x
|
||||
// cmp #$20
|
||||
// beq check_prog8_s72choice_32
|
||||
@@ -180,7 +180,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
||||
|
||||
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)
|
||||
// TODO this is not true if X is not a regular RAM memory address (but instead mapped I/O or ROM) but how does this code know?
|
||||
val mods = mutableListOf<Modification>()
|
||||
for (pair in linesByFour) {
|
||||
val first = pair[0].value.trimStart()
|
||||
+119
-76
@@ -10,12 +10,12 @@ import prog8.ast.statements.DirectMemoryWrite
|
||||
import prog8.ast.statements.FunctionCallStatement
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.toHex
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.functions.FSignature
|
||||
import prog8.compiler.target.CpuType
|
||||
import prog8.compiler.target.AssemblyError
|
||||
import prog8.compiler.target.Cx16Target
|
||||
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) {
|
||||
|
||||
@@ -34,7 +34,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
if(discardResult && resultToStack)
|
||||
throw AssemblyError("cannot both discard the result AND put it onto stack")
|
||||
|
||||
val sscope = (fcall as Node).definingSubroutine()
|
||||
val sscope = (fcall as Node).definingSubroutine
|
||||
|
||||
when (func.name) {
|
||||
"msb" -> funcMsb(fcall, resultToStack, resultRegister)
|
||||
@@ -177,8 +177,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
private fun funcCmp(fcall: IFunctionCall) {
|
||||
val arg1 = fcall.args[0]
|
||||
val arg2 = fcall.args[1]
|
||||
val dt1 = arg1.inferType(program).typeOrElse(DataType.UNDEFINED)
|
||||
val dt2 = arg2.inferType(program).typeOrElse(DataType.UNDEFINED)
|
||||
val dt1 = arg1.inferType(program).getOr(DataType.UNDEFINED)
|
||||
val dt2 = arg2.inferType(program).getOr(DataType.UNDEFINED)
|
||||
if(dt1 in ByteDatatypes) {
|
||||
if(dt2 in ByteDatatypes) {
|
||||
when (arg2) {
|
||||
@@ -195,13 +195,13 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
||||
asmgen.out(" cmp ${arg2.addressExpression.constValue(program)!!.number.toHex()}")
|
||||
} else {
|
||||
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine())
|
||||
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
||||
asmgen.out(" cmp P8ZP_SCRATCH_B1")
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine())
|
||||
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
||||
asmgen.out(" cmp P8ZP_SCRATCH_B1")
|
||||
}
|
||||
@@ -229,7 +229,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
+""")
|
||||
}
|
||||
else -> {
|
||||
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_W1", DataType.UWORD, (fcall as Node).definingSubroutine())
|
||||
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_W1", DataType.UWORD, (fcall as Node).definingSubroutine)
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
|
||||
asmgen.out("""
|
||||
cpy P8ZP_SCRATCH_W1+1
|
||||
@@ -261,7 +261,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
if(resultToStack)
|
||||
AsmAssignTarget(TargetStorageKind.STACK, program, asmgen, DataType.UWORD, null)
|
||||
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)
|
||||
asmgen.translateNormalAssignment(assign)
|
||||
asmgen.slabs[name] = size
|
||||
@@ -273,7 +273,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
asmgen.out(" jsr prog8_lib.func_sqrt16_stack")
|
||||
else {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,11 +285,11 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
when(func.name) {
|
||||
"sin8", "sin8u", "cos8", "cos8u" -> {
|
||||
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" -> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -368,7 +368,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
private fun funcRor2(fcall: IFunctionCall) {
|
||||
val what = fcall.args.single()
|
||||
val dt = what.inferType(program)
|
||||
when (dt.typeOrElse(DataType.UNDEFINED)) {
|
||||
when (dt.getOr(DataType.UNDEFINED)) {
|
||||
DataType.UBYTE -> {
|
||||
when (what) {
|
||||
is ArrayIndexedExpression -> {
|
||||
@@ -411,7 +411,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
private fun funcRor(fcall: IFunctionCall) {
|
||||
val what = fcall.args.single()
|
||||
val dt = what.inferType(program)
|
||||
when (dt.typeOrElse(DataType.UNDEFINED)) {
|
||||
when (dt.getOr(DataType.UNDEFINED)) {
|
||||
DataType.UBYTE -> {
|
||||
when (what) {
|
||||
is ArrayIndexedExpression -> {
|
||||
@@ -426,7 +426,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.addressExpression)
|
||||
if(ptrAndIndex!=null) {
|
||||
asmgen.assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.X)
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as FunctionCallStatement).definingSubroutine()!!)
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as FunctionCallStatement).definingSubroutine!!)
|
||||
asmgen.assignExpressionToRegister(ptrAndIndex.first, RegisterOrPair.AY)
|
||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||
asmgen.out("""
|
||||
@@ -469,7 +469,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
private fun funcRol2(fcall: IFunctionCall) {
|
||||
val what = fcall.args.single()
|
||||
val dt = what.inferType(program)
|
||||
when (dt.typeOrElse(DataType.UNDEFINED)) {
|
||||
when (dt.getOr(DataType.UNDEFINED)) {
|
||||
DataType.UBYTE -> {
|
||||
when (what) {
|
||||
is ArrayIndexedExpression -> {
|
||||
@@ -512,7 +512,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
private fun funcRol(fcall: IFunctionCall) {
|
||||
val what = fcall.args.single()
|
||||
val dt = what.inferType(program)
|
||||
when (dt.typeOrElse(DataType.UNDEFINED)) {
|
||||
when (dt.getOr(DataType.UNDEFINED)) {
|
||||
DataType.UBYTE -> {
|
||||
when (what) {
|
||||
is ArrayIndexedExpression -> {
|
||||
@@ -527,7 +527,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.addressExpression)
|
||||
if(ptrAndIndex!=null) {
|
||||
asmgen.assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.X)
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as FunctionCallStatement).definingSubroutine()!!)
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as FunctionCallStatement).definingSubroutine!!)
|
||||
asmgen.assignExpressionToRegister(ptrAndIndex.first, RegisterOrPair.AY)
|
||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||
asmgen.out("""
|
||||
@@ -578,7 +578,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
asmgen.out(" jsr floats.func_${func.name}_stack")
|
||||
else {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -586,7 +586,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
translateArguments(fcall.args, func, scope)
|
||||
val dt = fcall.args.single().inferType(program)
|
||||
if(resultToStack) {
|
||||
when (dt.typeOrElse(DataType.UNDEFINED)) {
|
||||
when (dt.getOr(DataType.UNDEFINED)) {
|
||||
DataType.UBYTE -> asmgen.out(" jsr prog8_lib.func_sign_ub_stack")
|
||||
DataType.BYTE -> asmgen.out(" jsr prog8_lib.func_sign_b_stack")
|
||||
DataType.UWORD -> asmgen.out(" jsr prog8_lib.func_sign_uw_stack")
|
||||
@@ -595,7 +595,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
}
|
||||
} else {
|
||||
when (dt.typeOrElse(DataType.UNDEFINED)) {
|
||||
when (dt.getOr(DataType.UNDEFINED)) {
|
||||
DataType.UBYTE -> asmgen.out(" jsr prog8_lib.func_sign_ub_into_A")
|
||||
DataType.BYTE -> asmgen.out(" jsr prog8_lib.func_sign_b_into_A")
|
||||
DataType.UWORD -> asmgen.out(" jsr prog8_lib.func_sign_uw_into_A")
|
||||
@@ -603,7 +603,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.func_sign_f_into_A")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -611,20 +611,20 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
outputAddressAndLenghtOfArray(fcall.args[0])
|
||||
val dt = fcall.args.single().inferType(program)
|
||||
if(resultToStack) {
|
||||
when (dt.typeOrElse(DataType.UNDEFINED)) {
|
||||
when (dt.getOr(DataType.UNDEFINED)) {
|
||||
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${function.name}_b_stack")
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${function.name}_w_stack")
|
||||
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${function.name}_f_stack")
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
}
|
||||
} else {
|
||||
when (dt.typeOrElse(DataType.UNDEFINED)) {
|
||||
when (dt.getOr(DataType.UNDEFINED)) {
|
||||
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${function.name}_b_into_A")
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${function.name}_w_into_A")
|
||||
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${function.name}_f_into_A")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -632,7 +632,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
outputAddressAndLenghtOfArray(fcall.args[0])
|
||||
val dt = fcall.args.single().inferType(program)
|
||||
if(resultToStack) {
|
||||
when (dt.typeOrElse(DataType.UNDEFINED)) {
|
||||
when (dt.getOr(DataType.UNDEFINED)) {
|
||||
DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${function.name}_ub_stack")
|
||||
DataType.ARRAY_B -> asmgen.out(" jsr prog8_lib.func_${function.name}_b_stack")
|
||||
DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_${function.name}_uw_stack")
|
||||
@@ -641,26 +641,26 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
}
|
||||
} else {
|
||||
when (dt.typeOrElse(DataType.UNDEFINED)) {
|
||||
when (dt.getOr(DataType.UNDEFINED)) {
|
||||
DataType.ARRAY_UB, DataType.STR -> {
|
||||
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 -> {
|
||||
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 -> {
|
||||
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 -> {
|
||||
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 -> {
|
||||
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")
|
||||
}
|
||||
@@ -671,7 +671,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
outputAddressAndLenghtOfArray(fcall.args[0])
|
||||
val dt = fcall.args.single().inferType(program)
|
||||
if(resultToStack) {
|
||||
when (dt.typeOrElse(DataType.UNDEFINED)) {
|
||||
when (dt.getOr(DataType.UNDEFINED)) {
|
||||
DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_sum_ub_stack")
|
||||
DataType.ARRAY_B -> asmgen.out(" jsr prog8_lib.func_sum_b_stack")
|
||||
DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_sum_uw_stack")
|
||||
@@ -680,26 +680,26 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
}
|
||||
} else {
|
||||
when (dt.typeOrElse(DataType.UNDEFINED)) {
|
||||
when (dt.getOr(DataType.UNDEFINED)) {
|
||||
DataType.ARRAY_UB, DataType.STR -> {
|
||||
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 -> {
|
||||
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 -> {
|
||||
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 -> {
|
||||
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 -> {
|
||||
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")
|
||||
}
|
||||
@@ -715,11 +715,11 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
val firstName = asmgen.asmVariableName(first)
|
||||
val secondName = asmgen.asmVariableName(second)
|
||||
val dt = first.inferType(program)
|
||||
if(dt.istype(DataType.BYTE) || dt.istype(DataType.UBYTE)) {
|
||||
if(dt istype DataType.BYTE || dt istype DataType.UBYTE) {
|
||||
asmgen.out(" ldy $firstName | lda $secondName | sta $firstName | sty $secondName")
|
||||
return
|
||||
}
|
||||
if(dt.istype(DataType.WORD) || dt.istype(DataType.UWORD)) {
|
||||
if(dt istype DataType.WORD || dt istype DataType.UWORD) {
|
||||
asmgen.out("""
|
||||
ldy $firstName
|
||||
lda $secondName
|
||||
@@ -732,7 +732,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
""")
|
||||
return
|
||||
}
|
||||
if(dt.istype(DataType.FLOAT)) {
|
||||
if(dt istype DataType.FLOAT) {
|
||||
asmgen.out("""
|
||||
lda #<$firstName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
@@ -750,7 +750,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
|
||||
// optimized simple case: swap two memory locations
|
||||
if(first is DirectMemoryRead && second is DirectMemoryRead) {
|
||||
// TODO optimize swap of two memread values with index, using the same pointer expression/variable, like swap(@(ptr+i1), @(ptr+i2))
|
||||
val addr1 = (first.addressExpression as? NumericLiteralValue)?.number?.toHex()
|
||||
val addr2 = (second.addressExpression as? NumericLiteralValue)?.number?.toHex()
|
||||
val name1 = if(first.addressExpression is IdentifierReference) asmgen.asmVariableName(first.addressExpression as IdentifierReference) else null
|
||||
@@ -773,6 +772,49 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
asmgen.out(" ldy $name1 | lda $name2 | sta $name1 | sty $name2")
|
||||
return
|
||||
}
|
||||
addr1==null && addr2==null && name1==null && name2==null -> {
|
||||
val firstExpr = first.addressExpression as? BinaryExpression
|
||||
val secondExpr = second.addressExpression as? BinaryExpression
|
||||
if(firstExpr!=null && secondExpr!=null) {
|
||||
val pointerVariable = firstExpr.left as? IdentifierReference
|
||||
val firstOffset = firstExpr.right
|
||||
val secondOffset = secondExpr.right
|
||||
if(pointerVariable != null
|
||||
&& pointerVariable isSameAs secondExpr.left
|
||||
&& firstExpr.operator == "+" && secondExpr.operator == "+"
|
||||
&& (firstOffset is NumericLiteralValue || firstOffset is IdentifierReference || firstOffset is TypecastExpression)
|
||||
&& (secondOffset is NumericLiteralValue || secondOffset is IdentifierReference || secondOffset is TypecastExpression)
|
||||
) {
|
||||
if(firstOffset is NumericLiteralValue && secondOffset is NumericLiteralValue) {
|
||||
if(firstOffset!=secondOffset) {
|
||||
swapArrayValues(
|
||||
DataType.UBYTE,
|
||||
asmgen.asmVariableName(pointerVariable), firstOffset,
|
||||
asmgen.asmVariableName(pointerVariable), secondOffset
|
||||
)
|
||||
return
|
||||
}
|
||||
} else if(firstOffset is TypecastExpression && secondOffset is TypecastExpression) {
|
||||
if(firstOffset.type in WordDatatypes && secondOffset.type in WordDatatypes) {
|
||||
val firstOffsetVar = firstOffset.expression as? IdentifierReference
|
||||
val secondOffsetVar = secondOffset.expression as? IdentifierReference
|
||||
if(firstOffsetVar!=null && secondOffsetVar!=null) {
|
||||
if(firstOffsetVar!=secondOffsetVar) {
|
||||
swapArrayValues(
|
||||
DataType.UBYTE,
|
||||
asmgen.asmVariableName(pointerVariable), firstOffsetVar,
|
||||
asmgen.asmVariableName(pointerVariable), secondOffsetVar
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if(firstOffset is IdentifierReference || secondOffset is IdentifierReference) {
|
||||
throw AssemblyError("expected a typecast-to-word for index variable at ${firstOffset.position} and/or ${secondOffset.position}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -782,7 +824,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
val elementIDt = first.inferType(program)
|
||||
if(!elementIDt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
val elementDt = elementIDt.typeOrElse(DataType.UNDEFINED)
|
||||
val elementDt = elementIDt.getOr(DataType.UNDEFINED)
|
||||
|
||||
val firstNum = first.indexer.indexExpr as? NumericLiteralValue
|
||||
val firstVar = first.indexer.indexExpr as? IdentifierReference
|
||||
@@ -808,15 +850,14 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
|
||||
fun targetFromExpr(expr: Expression, datatype: DataType): AsmAssignTarget {
|
||||
return when (expr) {
|
||||
is IdentifierReference -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, datatype, expr.definingSubroutine(), variableAsmName = asmgen.asmVariableName(expr))
|
||||
is ArrayIndexedExpression -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, datatype, expr.definingSubroutine(), array = expr)
|
||||
is DirectMemoryRead -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, datatype, expr.definingSubroutine(), memory = DirectMemoryWrite(expr.addressExpression, expr.position))
|
||||
is IdentifierReference -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, datatype, expr.definingSubroutine, variableAsmName = asmgen.asmVariableName(expr))
|
||||
is ArrayIndexedExpression -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, datatype, expr.definingSubroutine, array = expr)
|
||||
is DirectMemoryRead -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, datatype, expr.definingSubroutine, memory = DirectMemoryWrite(expr.addressExpression, expr.position))
|
||||
else -> throw AssemblyError("invalid expression object $expr")
|
||||
}
|
||||
}
|
||||
|
||||
val datatype = first.inferType(program).typeOrElse(DataType.UNDEFINED)
|
||||
when(datatype) {
|
||||
when(val datatype: DataType = first.inferType(program).getOr(DataType.UNDEFINED)) {
|
||||
in ByteDatatypes, in WordDatatypes -> {
|
||||
asmgen.assignExpressionToVariable(first, "P8ZP_SCRATCH_W1", datatype, null)
|
||||
asmgen.assignExpressionToVariable(second, "P8ZP_SCRATCH_W2", datatype, null)
|
||||
@@ -834,21 +875,23 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
asmgen.translateNormalAssignment(assignSecond)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
// via evaluation stack
|
||||
asmgen.translateExpression(first)
|
||||
asmgen.translateExpression(second)
|
||||
val assignFirst = AsmAssignment(
|
||||
AsmAssignSource(SourceStorageKind.STACK, program, asmgen, DataType.FLOAT),
|
||||
// via temp variable and FAC1
|
||||
asmgen.assignExpressionTo(first, AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.FLOAT, first.definingSubroutine, "floats.tempvar_swap_float"))
|
||||
asmgen.assignExpressionTo(second, AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.FLOAT, null, register=RegisterOrPair.FAC1))
|
||||
asmgen.translateNormalAssignment(
|
||||
AsmAssignment(
|
||||
AsmAssignSource(SourceStorageKind.REGISTER, program, asmgen, datatype, register = RegisterOrPair.FAC1),
|
||||
targetFromExpr(first, datatype),
|
||||
false, program.memsizer, first.position
|
||||
)
|
||||
)
|
||||
val assignSecond = AsmAssignment(
|
||||
AsmAssignSource(SourceStorageKind.STACK, program, asmgen, DataType.FLOAT),
|
||||
asmgen.translateNormalAssignment(
|
||||
AsmAssignment(
|
||||
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, datatype, "floats.tempvar_swap_float"),
|
||||
targetFromExpr(second, datatype),
|
||||
false, program.memsizer, second.position
|
||||
)
|
||||
)
|
||||
asmgen.translateNormalAssignment(assignFirst)
|
||||
asmgen.translateNormalAssignment(assignSecond)
|
||||
}
|
||||
else -> throw AssemblyError("weird swap dt")
|
||||
}
|
||||
@@ -888,7 +931,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda #>(${arrayVarName2}+$index2)
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
jsr floats.swap_floats
|
||||
jsr floats.func_swap_f
|
||||
""")
|
||||
}
|
||||
else -> throw AssemblyError("invalid aray elt type")
|
||||
@@ -961,7 +1004,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
sta P8ZP_SCRATCH_W2
|
||||
bcc +
|
||||
inc P8ZP_SCRATCH_W2+1
|
||||
+ jsr floats.swap_floats
|
||||
+ jsr floats.func_swap_f
|
||||
""")
|
||||
}
|
||||
else -> throw AssemblyError("invalid aray elt type")
|
||||
@@ -1019,7 +1062,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
sta P8ZP_SCRATCH_W1
|
||||
bcc +
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
+ jsr floats.swap_floats
|
||||
+ jsr floats.func_swap_f
|
||||
""")
|
||||
}
|
||||
else -> throw AssemblyError("invalid aray elt type")
|
||||
@@ -1077,7 +1120,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda #>(${arrayVarName2}+$index2)
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
jsr floats.swap_floats
|
||||
jsr floats.func_swap_f
|
||||
""")
|
||||
}
|
||||
else -> throw AssemblyError("invalid aray elt type")
|
||||
@@ -1086,7 +1129,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
|
||||
private fun funcAbs(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
|
||||
translateArguments(fcall.args, func, scope)
|
||||
val dt = fcall.args.single().inferType(program).typeOrElse(DataType.UNDEFINED)
|
||||
val dt = fcall.args.single().inferType(program).getOr(DataType.UNDEFINED)
|
||||
if(resultToStack) {
|
||||
when (dt) {
|
||||
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.abs_b_stack")
|
||||
@@ -1098,15 +1141,15 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
when (dt) {
|
||||
in ByteDatatypes -> {
|
||||
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 -> {
|
||||
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 -> {
|
||||
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")
|
||||
}
|
||||
@@ -1120,7 +1163,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
asmgen.out(" jsr prog8_lib.func_rnd_stack")
|
||||
else {
|
||||
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" -> {
|
||||
@@ -1128,7 +1171,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
asmgen.out(" jsr prog8_lib.func_rndw_stack")
|
||||
else {
|
||||
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")
|
||||
@@ -1147,7 +1190,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
val varname = asmgen.asmVariableName(addrExpr)
|
||||
if(asmgen.isZpVar(addrExpr)) {
|
||||
// pointervar is already in the zero page, no need to copy
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine()!!)
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine!!)
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AX)
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
||||
asmgen.out("""
|
||||
@@ -1172,7 +1215,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
val varname = asmgen.asmVariableName(addrExpr.left as IdentifierReference)
|
||||
if(asmgen.isZpVar(addrExpr.left as IdentifierReference)) {
|
||||
// pointervar is already in the zero page, no need to copy
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine()!!)
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine!!)
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AX)
|
||||
val index = (addrExpr.right as NumericLiteralValue).number.toHex()
|
||||
asmgen.out("""
|
||||
@@ -1324,7 +1367,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
|
||||
private fun funcMsb(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
|
||||
val arg = fcall.args.single()
|
||||
if (!arg.inferType(program).isWords())
|
||||
if (!arg.inferType(program).isWords)
|
||||
throw AssemblyError("msb required word argument")
|
||||
if (arg is NumericLiteralValue)
|
||||
throw AssemblyError("msb(const) should have been const-folded away")
|
||||
@@ -1368,7 +1411,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
|
||||
private fun funcLsb(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
|
||||
val arg = fcall.args.single()
|
||||
if (!arg.inferType(program).isWords())
|
||||
if (!arg.inferType(program).isWords)
|
||||
throw AssemblyError("lsb required word argument")
|
||||
if (arg is NumericLiteralValue)
|
||||
throw AssemblyError("lsb(const) should have been const-folded away")
|
||||
@@ -1432,7 +1475,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
}
|
||||
|
||||
private fun translateArguments(args: MutableList<Expression>, signature: FSignature, scope: Subroutine?) {
|
||||
val callConv = signature.callConvention(args.map { it.inferType(program).typeOrElse(DataType.UNDEFINED) })
|
||||
val callConv = signature.callConvention(args.map { it.inferType(program).getOr(DataType.UNDEFINED) })
|
||||
|
||||
fun getSourceForFloat(value: Expression): AsmAssignSource {
|
||||
return when (value) {
|
||||
@@ -1491,7 +1534,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
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)
|
||||
asmgen.translateNormalAssignment(assign)
|
||||
}
|
||||
+156
-85
@@ -3,19 +3,27 @@ package prog8.compiler.target.cpu6502.codegen
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.ArrayIndex
|
||||
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.toHex
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.functions.BuiltinFunctions
|
||||
import prog8.compiler.target.CpuType
|
||||
import prog8.compiler.target.subroutineFloatEvalResultVar1
|
||||
import prog8.compiler.target.AssemblyError
|
||||
import prog8.compilerinterface.BuiltinFunctions
|
||||
import prog8.compilerinterface.CpuType
|
||||
import prog8.compilerinterface.subroutineFloatEvalResultVar1
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
internal class ExpressionsAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||
|
||||
internal fun translateExpression(expression: Expression) {
|
||||
@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)
|
||||
}
|
||||
|
||||
private fun translateExpressionInternal(expression: Expression) {
|
||||
|
||||
when(expression) {
|
||||
is PrefixExpression -> translateExpression(expression)
|
||||
is BinaryExpression -> translateExpression(expression)
|
||||
@@ -28,17 +36,21 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun translateComparisonExpressionWithJumpIfFalse(expr: BinaryExpression, jumpIfFalseLabel: String) {
|
||||
// This is a helper routine called from while, do-util, and if expressions to generate optimized conditional branching code.
|
||||
// This is a helper routine called from while, do-util, and if expressions to generate optimized conditional branching code.
|
||||
// First, if it is of the form: <constvalue> <comparison> X , then flip the expression so the constant is always the right operand.
|
||||
|
||||
var left = expr.left
|
||||
var right = expr.right
|
||||
var operator = expr.operator
|
||||
var leftConstVal = left.constValue(program)
|
||||
var rightConstVal = right.constValue(program)
|
||||
|
||||
// make sure the constant value is on the right of the comparison expression
|
||||
if(leftConstVal!=null) {
|
||||
val tmp = left
|
||||
left = right
|
||||
@@ -54,32 +66,113 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
}
|
||||
}
|
||||
|
||||
val idt = left.inferType(program)
|
||||
if(!idt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
val dt = idt.typeOrElse(DataType.UNDEFINED)
|
||||
if (rightConstVal!=null && rightConstVal.number.toDouble() == 0.0)
|
||||
jumpIfZeroOrNot(left, operator, jumpIfFalseLabel)
|
||||
else
|
||||
jumpIfComparison(left, operator, right, jumpIfFalseLabel, leftConstVal, rightConstVal)
|
||||
}
|
||||
|
||||
private fun jumpIfZeroOrNot(
|
||||
left: Expression,
|
||||
operator: String,
|
||||
jumpIfFalseLabel: String
|
||||
) {
|
||||
when(val dt = left.inferType(program).getOr(DataType.UNDEFINED)) {
|
||||
DataType.UBYTE, DataType.UWORD -> {
|
||||
if(operator=="<") {
|
||||
asmgen.out(" jmp $jumpIfFalseLabel")
|
||||
return
|
||||
} else if(operator==">=") {
|
||||
return
|
||||
}
|
||||
if(dt==DataType.UBYTE) {
|
||||
asmgen.assignExpressionToRegister(left, RegisterOrPair.A, false)
|
||||
if (left is FunctionCall && !left.isSimple)
|
||||
asmgen.out(" cmp #0")
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY, false)
|
||||
asmgen.out(" sty P8ZP_SCRATCH_B1 | ora P8ZP_SCRATCH_B1")
|
||||
}
|
||||
when (operator) {
|
||||
"==" -> asmgen.out(" bne $jumpIfFalseLabel")
|
||||
"!=" -> asmgen.out(" beq $jumpIfFalseLabel")
|
||||
">" -> asmgen.out(" beq $jumpIfFalseLabel")
|
||||
"<=" -> asmgen.out(" bne $jumpIfFalseLabel")
|
||||
else -> throw AssemblyError("invalid comparison operator $operator")
|
||||
}
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
asmgen.assignExpressionToRegister(left, RegisterOrPair.A, true)
|
||||
if (left is FunctionCall && !left.isSimple)
|
||||
asmgen.out(" cmp #0")
|
||||
when (operator) {
|
||||
"==" -> asmgen.out(" bne $jumpIfFalseLabel")
|
||||
"!=" -> asmgen.out(" beq $jumpIfFalseLabel")
|
||||
">" -> asmgen.out(" beq $jumpIfFalseLabel | bmi $jumpIfFalseLabel")
|
||||
"<" -> asmgen.out(" bpl $jumpIfFalseLabel")
|
||||
">=" -> asmgen.out(" bmi $jumpIfFalseLabel")
|
||||
"<=" -> asmgen.out("""
|
||||
beq +
|
||||
bpl $jumpIfFalseLabel
|
||||
+ """)
|
||||
else -> throw AssemblyError("invalid comparison operator $operator")
|
||||
}
|
||||
}
|
||||
DataType.WORD -> {
|
||||
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY, true)
|
||||
when (operator) {
|
||||
"==" -> asmgen.out(" bne $jumpIfFalseLabel | cpy #0 | bne $jumpIfFalseLabel")
|
||||
"!=" -> asmgen.out(" sty P8ZP_SCRATCH_B1 | ora P8ZP_SCRATCH_B1 | beq $jumpIfFalseLabel")
|
||||
">" -> asmgen.out("""
|
||||
cpy #0
|
||||
bmi $jumpIfFalseLabel
|
||||
bne +
|
||||
cmp #0
|
||||
beq $jumpIfFalseLabel
|
||||
+ """)
|
||||
"<" -> asmgen.out(" cpy #0 | bpl $jumpIfFalseLabel")
|
||||
">=" -> asmgen.out(" cpy #0 | bmi $jumpIfFalseLabel")
|
||||
"<=" -> asmgen.out("""
|
||||
cpy #0
|
||||
bmi +
|
||||
bne $jumpIfFalseLabel
|
||||
cmp #0
|
||||
bne $jumpIfFalseLabel
|
||||
+ """)
|
||||
else -> throw AssemblyError("invalid comparison operator $operator")
|
||||
}
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.assignExpressionToRegister(left, RegisterOrPair.FAC1)
|
||||
asmgen.out(" jsr floats.SIGN") // SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
|
||||
when (operator) {
|
||||
"==" -> asmgen.out(" bne $jumpIfFalseLabel")
|
||||
"!=" -> asmgen.out(" beq $jumpIfFalseLabel")
|
||||
">" -> asmgen.out(" bmi $jumpIfFalseLabel | beq $jumpIfFalseLabel")
|
||||
"<" -> asmgen.out(" bpl $jumpIfFalseLabel")
|
||||
">=" -> asmgen.out(" bmi $jumpIfFalseLabel")
|
||||
"<=" -> asmgen.out(" cmp #1 | beq $jumpIfFalseLabel")
|
||||
else -> throw AssemblyError("invalid comparison operator $operator")
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
throw AssemblyError("invalid dt")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun jumpIfComparison(
|
||||
left: Expression,
|
||||
operator: String,
|
||||
right: Expression,
|
||||
jumpIfFalseLabel: String,
|
||||
leftConstVal: NumericLiteralValue?,
|
||||
rightConstVal: NumericLiteralValue?
|
||||
) {
|
||||
val dt = left.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
|
||||
|
||||
when (operator) {
|
||||
"==" -> {
|
||||
// if the left operand is an expression, and the right is 0, we can just evaluate that expression,
|
||||
// and use the result value directly to determine the boolean result. Shortcut only for integers.
|
||||
if(rightConstVal?.number?.toDouble() == 0.0) {
|
||||
if(dt in ByteDatatypes) {
|
||||
asmgen.assignExpressionToRegister(left, RegisterOrPair.A)
|
||||
if(left is FunctionCall && !left.isSimple)
|
||||
asmgen.out(" cmp #0")
|
||||
asmgen.out(" bne $jumpIfFalseLabel")
|
||||
return
|
||||
}
|
||||
else if(dt in WordDatatypes) {
|
||||
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY)
|
||||
asmgen.out("""
|
||||
sty P8ZP_SCRATCH_B1
|
||||
ora P8ZP_SCRATCH_B1
|
||||
bne $jumpIfFalseLabel""")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
when (dt) {
|
||||
in ByteDatatypes -> translateByteEqualsJump(left, right, leftConstVal, rightConstVal, jumpIfFalseLabel)
|
||||
in WordDatatypes -> translateWordEqualsJump(left, right, leftConstVal, rightConstVal, jumpIfFalseLabel)
|
||||
@@ -89,26 +182,6 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
}
|
||||
}
|
||||
"!=" -> {
|
||||
// if the left operand is an expression, and the right is 0, we can just evaluate that expression,
|
||||
// and use the result value directly to determine the boolean result. Shortcut only for integers.
|
||||
if(rightConstVal?.number?.toDouble() == 0.0) {
|
||||
if(dt in ByteDatatypes) {
|
||||
asmgen.assignExpressionToRegister(left, RegisterOrPair.A)
|
||||
if(left is FunctionCall && !left.isSimple)
|
||||
asmgen.out(" cmp #0")
|
||||
asmgen.out(" beq $jumpIfFalseLabel")
|
||||
return
|
||||
}
|
||||
else if(dt in WordDatatypes) {
|
||||
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY)
|
||||
asmgen.out("""
|
||||
sty P8ZP_SCRATCH_B1
|
||||
ora P8ZP_SCRATCH_B1
|
||||
beq $jumpIfFalseLabel""")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
when (dt) {
|
||||
in ByteDatatypes -> translateByteNotEqualsJump(left, right, leftConstVal, rightConstVal, jumpIfFalseLabel)
|
||||
in WordDatatypes -> translateWordNotEqualsJump(left, right, leftConstVal, rightConstVal, jumpIfFalseLabel)
|
||||
@@ -197,7 +270,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
jsr floats.vars_less_f
|
||||
beq $jumpIfFalseLabel""")
|
||||
} else {
|
||||
val subroutine = left.definingSubroutine()!!
|
||||
val subroutine = left.definingSubroutine!!
|
||||
subroutine.asmGenInfo.usedFloatEvalResultVar1 = true
|
||||
asmgen.assignExpressionToVariable(right, subroutineFloatEvalResultVar1, DataType.FLOAT, subroutine)
|
||||
asmgen.assignExpressionToRegister(left, RegisterOrPair.FAC1)
|
||||
@@ -242,7 +315,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
jsr floats.vars_lesseq_f
|
||||
beq $jumpIfFalseLabel""")
|
||||
} else {
|
||||
val subroutine = left.definingSubroutine()!!
|
||||
val subroutine = left.definingSubroutine!!
|
||||
subroutine.asmGenInfo.usedFloatEvalResultVar1 = true
|
||||
asmgen.assignExpressionToVariable(right, subroutineFloatEvalResultVar1, DataType.FLOAT, subroutine)
|
||||
asmgen.assignExpressionToRegister(left, RegisterOrPair.FAC1)
|
||||
@@ -287,7 +360,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
jsr floats.vars_less_f
|
||||
beq $jumpIfFalseLabel""")
|
||||
} else {
|
||||
val subroutine = left.definingSubroutine()!!
|
||||
val subroutine = left.definingSubroutine!!
|
||||
subroutine.asmGenInfo.usedFloatEvalResultVar1 = true
|
||||
asmgen.assignExpressionToVariable(right, subroutineFloatEvalResultVar1, DataType.FLOAT, subroutine)
|
||||
asmgen.assignExpressionToRegister(left, RegisterOrPair.FAC1)
|
||||
@@ -332,7 +405,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
jsr floats.vars_lesseq_f
|
||||
beq $jumpIfFalseLabel""")
|
||||
} else {
|
||||
val subroutine = left.definingSubroutine()!!
|
||||
val subroutine = left.definingSubroutine!!
|
||||
subroutine.asmGenInfo.usedFloatEvalResultVar1 = true
|
||||
asmgen.assignExpressionToVariable(right, subroutineFloatEvalResultVar1, DataType.FLOAT, subroutine)
|
||||
asmgen.assignExpressionToRegister(left, RegisterOrPair.FAC1)
|
||||
@@ -1339,7 +1412,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
jsr floats.vars_equal_f
|
||||
beq $jumpIfFalseLabel""")
|
||||
} else {
|
||||
val subroutine = left.definingSubroutine()!!
|
||||
val subroutine = left.definingSubroutine!!
|
||||
subroutine.asmGenInfo.usedFloatEvalResultVar1 = true
|
||||
asmgen.assignExpressionToVariable(right, subroutineFloatEvalResultVar1, DataType.FLOAT, subroutine)
|
||||
asmgen.assignExpressionToRegister(left, RegisterOrPair.FAC1)
|
||||
@@ -1424,7 +1497,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
jsr floats.vars_equal_f
|
||||
bne $jumpIfFalseLabel""")
|
||||
} else {
|
||||
val subroutine = left.definingSubroutine()!!
|
||||
val subroutine = left.definingSubroutine!!
|
||||
subroutine.asmGenInfo.usedFloatEvalResultVar1 = true
|
||||
asmgen.assignExpressionToVariable(right, subroutineFloatEvalResultVar1, DataType.FLOAT, subroutine)
|
||||
asmgen.assignExpressionToRegister(left, RegisterOrPair.FAC1)
|
||||
@@ -1625,8 +1698,8 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
}
|
||||
|
||||
private fun translateExpression(typecast: TypecastExpression) {
|
||||
translateExpression(typecast.expression)
|
||||
when(typecast.expression.inferType(program).typeOrElse(DataType.UNDEFINED)) {
|
||||
translateExpressionInternal(typecast.expression)
|
||||
when(typecast.expression.inferType(program).getOr(DataType.UNDEFINED)) {
|
||||
DataType.UBYTE -> {
|
||||
when(typecast.type) {
|
||||
DataType.UBYTE, DataType.BYTE -> {}
|
||||
@@ -1757,7 +1830,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
|
||||
private fun translateExpression(expr: IdentifierReference) {
|
||||
val varname = asmgen.asmVariableName(expr)
|
||||
when(expr.inferType(program).typeOrElse(DataType.UNDEFINED)) {
|
||||
when(expr.inferType(program).getOr(DataType.UNDEFINED)) {
|
||||
DataType.UBYTE, DataType.BYTE -> {
|
||||
asmgen.out(" lda $varname | sta P8ESTACK_LO,x | dex")
|
||||
}
|
||||
@@ -1775,23 +1848,23 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
}
|
||||
|
||||
private fun translateExpression(expr: BinaryExpression) {
|
||||
// TODO needs to use optimized assembly generation like the assignment instructions. But avoid code duplication.... rewrite all expressions into assignment form?
|
||||
// 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.typeOrElse(DataType.UNDEFINED)
|
||||
val rightDt = rightIDt.typeOrElse(DataType.UNDEFINED)
|
||||
val leftDt = leftIDt.getOr(DataType.UNDEFINED)
|
||||
val rightDt = rightIDt.getOr(DataType.UNDEFINED)
|
||||
// see if we can apply some optimized routines
|
||||
// TODO avoid using evaluation on stack everywhere
|
||||
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) {
|
||||
translateExpression(expr.right)
|
||||
translateExpressionInternal(expr.right)
|
||||
if(rightDt in ByteDatatypes) {
|
||||
val incdec = if(leftVal<0) "dec" else "inc"
|
||||
repeat(leftVal.absoluteValue) {
|
||||
@@ -1821,7 +1894,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
}
|
||||
else if (rightVal!=null && rightVal in -4..4)
|
||||
{
|
||||
translateExpression(expr.left)
|
||||
translateExpressionInternal(expr.left)
|
||||
if(leftDt in ByteDatatypes) {
|
||||
val incdec = if(rightVal<0) "dec" else "inc"
|
||||
repeat(rightVal.absoluteValue) {
|
||||
@@ -1856,7 +1929,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
val rightVal = expr.right.constValue(program)?.number?.toInt()
|
||||
if (rightVal!=null && rightVal in -4..4)
|
||||
{
|
||||
translateExpression(expr.left)
|
||||
translateExpressionInternal(expr.left)
|
||||
if(leftDt in ByteDatatypes) {
|
||||
val incdec = if(rightVal<0) "inc" else "dec"
|
||||
repeat(rightVal.absoluteValue) {
|
||||
@@ -1889,7 +1962,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
">>" -> {
|
||||
val amount = expr.right.constValue(program)?.number?.toInt()
|
||||
if(amount!=null) {
|
||||
translateExpression(expr.left)
|
||||
translateExpressionInternal(expr.left)
|
||||
when (leftDt) {
|
||||
DataType.UBYTE -> {
|
||||
if (amount <= 2)
|
||||
@@ -1960,7 +2033,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
"<<" -> {
|
||||
val amount = expr.right.constValue(program)?.number?.toInt()
|
||||
if(amount!=null) {
|
||||
translateExpression(expr.left)
|
||||
translateExpressionInternal(expr.left)
|
||||
if (leftDt in ByteDatatypes) {
|
||||
if (amount <= 2)
|
||||
repeat(amount) { asmgen.out(" asl P8ESTACK_LO+1,x") }
|
||||
@@ -1997,7 +2070,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
val amount = value.number.toInt()
|
||||
if(amount==2) {
|
||||
// optimize x*2 common case
|
||||
translateExpression(expr.left)
|
||||
translateExpressionInternal(expr.left)
|
||||
if(leftDt in ByteDatatypes) {
|
||||
asmgen.out(" asl P8ESTACK_LO+1,x")
|
||||
} else {
|
||||
@@ -2008,38 +2081,38 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
when(rightDt) {
|
||||
DataType.UBYTE -> {
|
||||
if(amount in asmgen.optimizedByteMultiplications) {
|
||||
translateExpression(expr.left)
|
||||
translateExpressionInternal(expr.left)
|
||||
asmgen.out(" jsr math.stack_mul_byte_$amount")
|
||||
return
|
||||
}
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
if(amount in asmgen.optimizedByteMultiplications) {
|
||||
translateExpression(expr.left)
|
||||
translateExpressionInternal(expr.left)
|
||||
asmgen.out(" jsr math.stack_mul_byte_$amount")
|
||||
return
|
||||
}
|
||||
if(amount.absoluteValue in asmgen.optimizedByteMultiplications) {
|
||||
translateExpression(expr.left)
|
||||
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) {
|
||||
translateExpression(expr.left)
|
||||
translateExpressionInternal(expr.left)
|
||||
asmgen.out(" jsr math.stack_mul_word_$amount")
|
||||
return
|
||||
}
|
||||
}
|
||||
DataType.WORD -> {
|
||||
if(amount in asmgen.optimizedWordMultiplications) {
|
||||
translateExpression(expr.left)
|
||||
translateExpressionInternal(expr.left)
|
||||
asmgen.out(" jsr math.stack_mul_word_$amount")
|
||||
return
|
||||
}
|
||||
if(amount.absoluteValue in asmgen.optimizedWordMultiplications) {
|
||||
translateExpression(expr.left)
|
||||
translateExpressionInternal(expr.left)
|
||||
asmgen.out(" jsr prog8_lib.neg_w | jsr math.stack_mul_word_${amount.absoluteValue}")
|
||||
return
|
||||
}
|
||||
@@ -2053,7 +2126,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
|
||||
val rightVal = expr.right.constValue(program)?.number?.toInt()
|
||||
if(rightVal!=null && rightVal==2) {
|
||||
translateExpression(expr.left)
|
||||
translateExpressionInternal(expr.left)
|
||||
when(leftDt) {
|
||||
DataType.UBYTE -> asmgen.out(" lsr P8ESTACK_LO+1,x")
|
||||
DataType.BYTE -> asmgen.out(" asl P8ESTACK_LO+1,x | ror P8ESTACK_LO+1,x")
|
||||
@@ -2075,9 +2148,9 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
translateCompareStrings(expr.left, expr.operator, expr.right)
|
||||
}
|
||||
else {
|
||||
// the general, non-optimized cases TODO optimize more cases....
|
||||
translateExpression(expr.left)
|
||||
translateExpression(expr.right)
|
||||
// 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)
|
||||
@@ -2104,11 +2177,11 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
}
|
||||
|
||||
private fun translateExpression(expr: PrefixExpression) {
|
||||
translateExpression(expr.expression)
|
||||
translateExpressionInternal(expr.expression)
|
||||
val itype = expr.inferType(program)
|
||||
if(!itype.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
val type = itype.typeOrElse(DataType.UNDEFINED)
|
||||
val type = itype.getOr(DataType.UNDEFINED)
|
||||
when(expr.operator) {
|
||||
"+" -> {}
|
||||
"-" -> {
|
||||
@@ -2146,7 +2219,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
val elementIDt = arrayExpr.inferType(program)
|
||||
if(!elementIDt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
val elementDt = elementIDt.typeOrElse(DataType.UNDEFINED)
|
||||
val elementDt = elementIDt.getOr(DataType.UNDEFINED)
|
||||
val arrayVarName = asmgen.asmVariableName(arrayExpr.arrayvar)
|
||||
val constIndexNum = arrayExpr.indexer.constIndex()
|
||||
if(constIndexNum!=null) {
|
||||
@@ -2189,8 +2262,6 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
}
|
||||
}
|
||||
|
||||
fun translateExpression(indexer: ArrayIndex) = asmgen.translateExpression(indexer.indexExpr)
|
||||
|
||||
private fun translateBinaryOperatorBytes(operator: String, types: DataType) {
|
||||
when(operator) {
|
||||
"**" -> throw AssemblyError("** operator requires floats")
|
||||
+6
-5
@@ -8,7 +8,8 @@ import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.RangeExpr
|
||||
import prog8.ast.statements.ForLoop
|
||||
import prog8.ast.toHex
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.AssemblyError
|
||||
import prog8.compilerinterface.toConstantIntegerRange
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||
@@ -21,13 +22,13 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
|
||||
is RangeExpr -> {
|
||||
val range = (stmt.iterable as RangeExpr).toConstantIntegerRange()
|
||||
if(range==null) {
|
||||
translateForOverNonconstRange(stmt, iterableDt.typeOrElse(DataType.UNDEFINED), stmt.iterable as RangeExpr)
|
||||
translateForOverNonconstRange(stmt, iterableDt.getOr(DataType.UNDEFINED), stmt.iterable as RangeExpr)
|
||||
} else {
|
||||
translateForOverConstRange(stmt, iterableDt.typeOrElse(DataType.UNDEFINED), range)
|
||||
translateForOverConstRange(stmt, iterableDt.getOr(DataType.UNDEFINED), range)
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
translateForOverIterableVar(stmt, iterableDt.typeOrElse(DataType.UNDEFINED), stmt.iterable as IdentifierReference)
|
||||
translateForOverIterableVar(stmt, iterableDt.getOr(DataType.UNDEFINED), stmt.iterable as IdentifierReference)
|
||||
}
|
||||
else -> throw AssemblyError("can't iterate over ${stmt.iterable.javaClass} - should have been replaced by a variable")
|
||||
}
|
||||
@@ -588,5 +589,5 @@ $loopLabel""")
|
||||
}
|
||||
|
||||
private fun assignLoopvar(stmt: ForLoop, range: RangeExpr) =
|
||||
asmgen.assignExpressionToVariable(range.from, asmgen.asmVariableName(stmt.loopVar), stmt.loopVarDt(program).typeOrElse(DataType.UNDEFINED), stmt.definingSubroutine())
|
||||
asmgen.assignExpressionToVariable(range.from, asmgen.asmVariableName(stmt.loopVar), stmt.loopVarDt(program).getOr(DataType.UNDEFINED), stmt.definingSubroutine)
|
||||
}
|
||||
+34
-25
@@ -5,13 +5,16 @@ import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.CpuType
|
||||
import prog8.ast.statements.InlineAssembly
|
||||
import prog8.ast.statements.RegisterOrStatusflag
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.statements.SubroutineParameter
|
||||
import prog8.compiler.target.AssemblyError
|
||||
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignSource
|
||||
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignTarget
|
||||
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignment
|
||||
import prog8.compiler.target.cpu6502.codegen.assignment.TargetStorageKind
|
||||
import prog8.compilerinterface.CpuType
|
||||
|
||||
|
||||
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||
@@ -27,11 +30,10 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
|
||||
if(sub.shouldSaveX()) {
|
||||
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
|
||||
val (keepAonEntry: Boolean, keepAonReturn: Boolean) = sub.shouldKeepA()
|
||||
if(regSaveOnStack)
|
||||
asmgen.saveRegisterStack(CpuRegister.X, keepAonEntry)
|
||||
asmgen.saveRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnEntry)
|
||||
else
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, (stmt as Node).definingSubroutine()!!)
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, (stmt as Node).definingSubroutine!!)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,17 +41,15 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
|
||||
if(sub.shouldSaveX()) {
|
||||
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
|
||||
val (keepAonEntry: Boolean, keepAonReturn: Boolean) = sub.shouldKeepA()
|
||||
|
||||
if(regSaveOnStack)
|
||||
asmgen.restoreRegisterStack(CpuRegister.X, keepAonReturn)
|
||||
asmgen.restoreRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnReturn)
|
||||
else
|
||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun translateFunctionCall(stmt: IFunctionCall) {
|
||||
// Output only the code to setup 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 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)
|
||||
@@ -64,7 +64,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
argumentViaVariable(sub, arg.first, arg.second)
|
||||
}
|
||||
} else {
|
||||
// via registers
|
||||
require(sub.isAsmSubroutine)
|
||||
if(sub.parameters.size==1) {
|
||||
// just a single parameter, no risk of clobbering registers
|
||||
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single()), stmt.args[0])
|
||||
@@ -118,16 +118,14 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
// 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")
|
||||
|
||||
asmgen.out(" \t; inlined routine follows: ${sub.name}")
|
||||
val statements = sub.statements.filter { it !is ParameterVarDecl && it !is Directive }
|
||||
statements.forEach {
|
||||
if(it is Return) {
|
||||
asmgen.translate(it, false) // don't use RTS for the inlined return statement
|
||||
} else {
|
||||
if(!sub.inline || it !is VarDecl)
|
||||
asmgen.translate(it)
|
||||
}
|
||||
}
|
||||
val assembly = sub.statements.single() as InlineAssembly
|
||||
asmgen.translate(assembly)
|
||||
asmgen.out(" \t; inlined routine end: ${sub.name}")
|
||||
}
|
||||
|
||||
@@ -137,14 +135,25 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
private fun registerArgsViaStackEvaluation(stmt: IFunctionCall, sub: Subroutine) {
|
||||
// this is called when one or more of the arguments are 'complex' and
|
||||
// cannot be assigned to a register easily or risk clobbering other registers.
|
||||
// TODO find another way to prepare the arguments, without using the eval stack
|
||||
|
||||
if(sub.parameters.isEmpty())
|
||||
return
|
||||
|
||||
|
||||
// 1. load all arguments reversed onto the stack: first arg goes last (is on top).
|
||||
|
||||
for (arg in stmt.args.reversed())
|
||||
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 argForXregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
|
||||
var argForAregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
|
||||
@@ -159,11 +168,11 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
argForCarry = argi
|
||||
}
|
||||
argi.value.second.statusflag != null -> throw AssemblyError("can only use Carry as status flag parameter")
|
||||
argi.value.second.registerOrPair in setOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) -> {
|
||||
argi.value.second.registerOrPair in arrayOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) -> {
|
||||
require(argForXregister==null)
|
||||
argForXregister = argi
|
||||
}
|
||||
argi.value.second.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.AY) -> {
|
||||
argi.value.second.registerOrPair in arrayOf(RegisterOrPair.A, RegisterOrPair.AY) -> {
|
||||
require(argForAregister == null)
|
||||
argForAregister = argi
|
||||
}
|
||||
@@ -254,7 +263,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
val valueIDt = value.inferType(program)
|
||||
if(!valueIDt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
val valueDt = valueIDt.typeOrElse(DataType.UNDEFINED)
|
||||
val valueDt = valueIDt.getOr(DataType.UNDEFINED)
|
||||
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
|
||||
throw AssemblyError("argument type incompatible")
|
||||
|
||||
@@ -267,7 +276,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
val valueIDt = value.inferType(program)
|
||||
if(!valueIDt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
val valueDt = valueIDt.typeOrElse(DataType.UNDEFINED)
|
||||
val valueDt = valueIDt.getOr(DataType.UNDEFINED)
|
||||
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
|
||||
throw AssemblyError("argument type incompatible")
|
||||
|
||||
@@ -327,7 +336,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
if(parameter.value.type in ByteDatatypes && (register==RegisterOrPair.AX || register == RegisterOrPair.AY || register==RegisterOrPair.XY || register in Cx16VirtualRegisters))
|
||||
AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, parameter.value.type, sub, register = register)
|
||||
else
|
||||
AsmAssignTarget.fromRegisters(register, sub, program, asmgen)
|
||||
AsmAssignTarget.fromRegisters(register, false, sub, program, asmgen)
|
||||
val src = if(valueDt in PassByReferenceDatatypes) {
|
||||
if(value is IdentifierReference) {
|
||||
val addr = AddressOf(value, Position.DUMMY)
|
||||
+4
-4
@@ -6,7 +6,7 @@ import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.statements.PostIncrDecr
|
||||
import prog8.ast.toHex
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.AssemblyError
|
||||
|
||||
|
||||
internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||
@@ -15,11 +15,11 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
||||
val targetIdent = stmt.target.identifier
|
||||
val targetMemory = stmt.target.memoryAddress
|
||||
val targetArrayIdx = stmt.target.arrayindexed
|
||||
val scope = stmt.definingSubroutine()
|
||||
val scope = stmt.definingSubroutine
|
||||
when {
|
||||
targetIdent!=null -> {
|
||||
val what = asmgen.asmVariableName(targetIdent)
|
||||
when (stmt.target.inferType(program).typeOrElse(DataType.UNDEFINED)) {
|
||||
when (stmt.target.inferType(program).getOr(DataType.UNDEFINED)) {
|
||||
in ByteDatatypes -> asmgen.out(if (incr) " inc $what" else " dec $what")
|
||||
in WordDatatypes -> {
|
||||
if(incr)
|
||||
@@ -65,7 +65,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
||||
}
|
||||
targetArrayIdx!=null -> {
|
||||
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.arrayvar)
|
||||
val elementDt = targetArrayIdx.inferType(program).typeOrElse(DataType.UNDEFINED)
|
||||
val elementDt = targetArrayIdx.inferType(program).getOr(DataType.UNDEFINED)
|
||||
val constIndex = targetArrayIdx.indexer.constIndex()
|
||||
if(constIndex!=null) {
|
||||
val indexValue = constIndex * program.memsizer.memorySize(elementDt)
|
||||
+18
-17
@@ -1,11 +1,11 @@
|
||||
package prog8.compiler.target.cpu6502.codegen.assignment
|
||||
|
||||
import prog8.ast.IMemSizer
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compilerinterface.IMemSizer
|
||||
import prog8.compiler.target.AssemblyError
|
||||
import prog8.compiler.target.cpu6502.codegen.AsmGen
|
||||
|
||||
|
||||
@@ -41,11 +41,12 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
|
||||
{
|
||||
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0}
|
||||
val constArrayIndexValue by lazy { array?.indexer?.constIndex() }
|
||||
val asmVarname: String
|
||||
get() = if(array==null)
|
||||
val asmVarname: String by lazy {
|
||||
if (array == null)
|
||||
variableAsmName!!
|
||||
else
|
||||
asmgen.asmVariableName(array.arrayvar)
|
||||
}
|
||||
|
||||
lateinit var origAssign: AsmAssignment
|
||||
|
||||
@@ -59,23 +60,23 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
|
||||
val idt = inferType(program)
|
||||
if(!idt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
val dt = idt.typeOrElse(DataType.UNDEFINED)
|
||||
val dt = idt.getOr(DataType.UNDEFINED)
|
||||
when {
|
||||
identifier != null -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, assign.definingSubroutine(), variableAsmName = asmgen.asmVariableName(identifier!!), origAstTarget = this)
|
||||
arrayindexed != null -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, dt, assign.definingSubroutine(), array = arrayindexed, origAstTarget = this)
|
||||
memoryAddress != null -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, dt, assign.definingSubroutine(), memory = memoryAddress, origAstTarget = this)
|
||||
identifier != null -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, assign.definingSubroutine, variableAsmName = asmgen.asmVariableName(identifier!!), origAstTarget = this)
|
||||
arrayindexed != null -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, dt, assign.definingSubroutine, array = arrayindexed, origAstTarget = this)
|
||||
memoryAddress != null -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, dt, assign.definingSubroutine, memory = memoryAddress, origAstTarget = this)
|
||||
else -> throw AssemblyError("weird target")
|
||||
}
|
||||
}
|
||||
|
||||
fun 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) {
|
||||
RegisterOrPair.A,
|
||||
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.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.FAC2 -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.FLOAT, scope, register = registers)
|
||||
RegisterOrPair.R0,
|
||||
@@ -93,7 +94,7 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
|
||||
RegisterOrPair.R12,
|
||||
RegisterOrPair.R13,
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,7 +133,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||
is StringLiteralValue -> throw AssemblyError("string literal value should not occur anymore for asm generation")
|
||||
is ArrayLiteralValue -> throw AssemblyError("array literal value should not occur anymore for asm generation")
|
||||
is IdentifierReference -> {
|
||||
val dt = value.inferType(program).typeOrElse(DataType.UNDEFINED)
|
||||
val dt = value.inferType(program).getOr(DataType.UNDEFINED)
|
||||
val varName=asmgen.asmVariableName(value)
|
||||
// special case: "cx16.r[0-15]" are 16-bits virtual registers of the commander X16 system
|
||||
if(dt == DataType.UWORD && varName.lowercase().startsWith("cx16.r")) {
|
||||
@@ -147,7 +148,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||
AsmAssignSource(SourceStorageKind.MEMORY, program, asmgen, DataType.UBYTE, memory = value)
|
||||
}
|
||||
is ArrayIndexedExpression -> {
|
||||
val dt = value.inferType(program).typeOrElse(DataType.UNDEFINED)
|
||||
val dt = value.inferType(program).getOr(DataType.UNDEFINED)
|
||||
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, dt, array = value)
|
||||
}
|
||||
is FunctionCall -> {
|
||||
@@ -162,7 +163,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||
val returnType = value.inferType(program)
|
||||
if(!returnType.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType.typeOrElse(DataType.UNDEFINED), expression = value)
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType.getOr(DataType.UNDEFINED), expression = value)
|
||||
}
|
||||
else -> {
|
||||
throw AssemblyError("weird call")
|
||||
@@ -173,7 +174,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||
val dt = value.inferType(program)
|
||||
if(!dt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, dt.typeOrElse(DataType.UNDEFINED), expression = value)
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, dt.getOr(DataType.UNDEFINED), expression = value)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -205,7 +206,7 @@ internal class AsmAssignment(val source: AsmAssignSource,
|
||||
val position: Position) {
|
||||
|
||||
init {
|
||||
if(target.register !in setOf(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(memsizer.memorySize(source.datatype) <= memsizer.memorySize(target.datatype)) {
|
||||
"source storage size must be less or equal to target datatype storage size"
|
||||
+65
-51
@@ -5,12 +5,12 @@ import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.toHex
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.functions.BuiltinFunctions
|
||||
import prog8.compiler.functions.builtinFunctionReturnType
|
||||
import prog8.compiler.target.CpuType
|
||||
import prog8.compiler.target.AssemblyError
|
||||
import prog8.compiler.target.cpu6502.codegen.AsmGen
|
||||
import prog8.compiler.target.cpu6502.codegen.ExpressionsAsmGen
|
||||
import prog8.compilerinterface.BuiltinFunctions
|
||||
import prog8.compilerinterface.CpuType
|
||||
import prog8.compilerinterface.builtinFunctionReturnType
|
||||
|
||||
|
||||
internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen,
|
||||
@@ -33,6 +33,11 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
|
||||
fun translateNormalAssignment(assign: AsmAssignment) {
|
||||
if(assign.isAugmentable) {
|
||||
augmentableAsmGen.translate(assign)
|
||||
return
|
||||
}
|
||||
|
||||
when(assign.source.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> {
|
||||
// simple case: assign a constant number
|
||||
@@ -171,13 +176,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||
// copy the actual string result into the target string variable
|
||||
asmgen.out("""
|
||||
pha
|
||||
lda #<${assign.target.asmVarname}
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda #>${assign.target.asmVarname}
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
pla
|
||||
jsr prog8_lib.strcpy""")
|
||||
pha
|
||||
lda #<${assign.target.asmVarname}
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda #>${assign.target.asmVarname}
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
pla
|
||||
jsr prog8_lib.strcpy""")
|
||||
}
|
||||
else -> throw AssemblyError("weird target dt")
|
||||
}
|
||||
@@ -217,7 +222,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
val returntype = builtinFunctionReturnType(sub.name, value.args, program)
|
||||
if(!returntype.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
when(returntype.typeOrElse(DataType.UNDEFINED)) {
|
||||
when(returntype.getOr(DataType.UNDEFINED)) {
|
||||
in ByteDatatypes -> assignRegisterByte(assign.target, CpuRegister.A) // function's byte result is in A
|
||||
in WordDatatypes -> assignRegisterpairWord(assign.target, RegisterOrPair.AY) // function's word result is in AY
|
||||
DataType.STR -> {
|
||||
@@ -249,30 +254,40 @@ 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
|
||||
))
|
||||
when(value.operator) {
|
||||
"+" -> {}
|
||||
"-" -> augmentableAsmGen.inplaceNegate(assign.target, assign.target.datatype)
|
||||
"~" -> augmentableAsmGen.inplaceInvert(assign.target, assign.target.datatype)
|
||||
"not" -> augmentableAsmGen.inplaceBooleanNot(assign.target, assign.target.datatype)
|
||||
else -> throw AssemblyError("invalid prefix operator")
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// 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...)
|
||||
if (value.parent is Return) {
|
||||
if (this.asmgen.options.slowCodegenWarnings)
|
||||
println("warning: slow stack evaluation used for return: $value target=${assign.target.kind} at ${value.position}")
|
||||
}
|
||||
exprAsmgen.translateExpression(value)
|
||||
// TODO FIX THIS... by using a temp var? so that it becomes augmentable assignment expression?
|
||||
asmgen.translateExpression(value)
|
||||
if (assign.target.datatype in WordDatatypes && assign.source.datatype in ByteDatatypes)
|
||||
asmgen.signExtendStackLsb(assign.source.datatype)
|
||||
assignStackValue(assign.target)
|
||||
if(assign.target.kind!=TargetStorageKind.STACK || assign.target.datatype != assign.source.datatype)
|
||||
assignStackValue(assign.target)
|
||||
}
|
||||
}
|
||||
}
|
||||
SourceStorageKind.REGISTER -> {
|
||||
when(assign.source.datatype) {
|
||||
DataType.UBYTE -> assignRegisterByte(assign.target, assign.source.register!!.asCpuRegister())
|
||||
DataType.UWORD -> assignRegisterpairWord(assign.target, assign.source.register!!)
|
||||
else -> throw AssemblyError("invalid register dt")
|
||||
}
|
||||
asmgen.assignRegister(assign.source.register!!, assign.target)
|
||||
}
|
||||
SourceStorageKind.STACK -> {
|
||||
assignStackValue(assign.target)
|
||||
if(assign.target.kind!=TargetStorageKind.STACK || assign.target.datatype != assign.source.datatype)
|
||||
assignStackValue(assign.target)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -299,7 +314,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
val valueIDt = value.inferType(program)
|
||||
if(!valueIDt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
val valueDt = valueIDt.typeOrElse(DataType.UNDEFINED)
|
||||
val valueDt = valueIDt.getOr(DataType.UNDEFINED)
|
||||
if(valueDt==targetDt)
|
||||
throw AssemblyError("type cast to identical dt should have been removed")
|
||||
|
||||
@@ -364,16 +379,16 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
|
||||
when (valueDt) {
|
||||
in ByteDatatypes -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.A)
|
||||
assignExpressionToRegister(value, RegisterOrPair.A, valueDt==DataType.BYTE)
|
||||
assignTypeCastedRegisters(target.asmVarname, targetDt, RegisterOrPair.A, valueDt)
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.AY)
|
||||
assignExpressionToRegister(value, RegisterOrPair.AY, valueDt==DataType.WORD)
|
||||
assignTypeCastedRegisters(target.asmVarname, targetDt, RegisterOrPair.AY, valueDt)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.FAC1)
|
||||
assignTypecastedFloatFAC1(target.asmVarname, targetDt)
|
||||
assignExpressionToRegister(value, RegisterOrPair.FAC1, true)
|
||||
assignTypeCastedFloatFAC1(target.asmVarname, targetDt)
|
||||
}
|
||||
in PassByReferenceDatatypes -> {
|
||||
// str/array value cast (most likely to UWORD, take address-of)
|
||||
@@ -398,15 +413,15 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
RegisterOrPair.A,
|
||||
RegisterOrPair.X,
|
||||
RegisterOrPair.Y -> {
|
||||
// 'cast' a ubyte value to a byte register; no cast needed at all
|
||||
return assignExpressionToRegister(value, target.register)
|
||||
// 'cast' an ubyte value to a byte register; no cast needed at all
|
||||
return assignExpressionToRegister(value, target.register, false)
|
||||
}
|
||||
RegisterOrPair.AX,
|
||||
RegisterOrPair.AY,
|
||||
RegisterOrPair.XY,
|
||||
in Cx16VirtualRegisters -> {
|
||||
// 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 -> {}
|
||||
}
|
||||
@@ -424,19 +439,15 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
RegisterOrPair.XY,
|
||||
in Cx16VirtualRegisters -> {
|
||||
// 'cast' uword into a 16 bits register, just assign it
|
||||
return assignExpressionToRegister(value, target.register!!)
|
||||
return assignExpressionToRegister(value, target.register!!, false)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
// give up, do it via eval stack
|
||||
// No more special optmized cases yet. Do the rest via more complex evaluation
|
||||
// note: cannot use assignTypeCastedValue because that is ourselves :P
|
||||
// TODO optimize typecasts for more special cases?
|
||||
if(this.asmgen.options.slowCodegenWarnings)
|
||||
println("warning: slow stack evaluation used for typecast: $value into $targetDt (target=${target.kind} at ${value.position}")
|
||||
asmgen.translateExpression(origTypeCastExpression) // this performs the actual type cast in translateExpression(Typecast)
|
||||
assignStackValue(target)
|
||||
asmgen.assignExpressionTo(origTypeCastExpression, target)
|
||||
}
|
||||
|
||||
private fun assignCastViaLsbFunc(value: Expression, target: AsmAssignTarget) {
|
||||
@@ -447,7 +458,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
translateNormalAssignment(assign)
|
||||
}
|
||||
|
||||
private fun assignTypecastedFloatFAC1(targetAsmVarName: String, targetDt: DataType) {
|
||||
private fun assignTypeCastedFloatFAC1(targetAsmVarName: String, targetDt: DataType) {
|
||||
|
||||
if(targetDt==DataType.FLOAT)
|
||||
throw AssemblyError("typecast to identical type")
|
||||
@@ -954,7 +965,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
lda #<$sourceName
|
||||
ldy #>$sourceName+1
|
||||
sta P8ESTACK_LO,x
|
||||
sty P8ESTACK_HI,x
|
||||
tya
|
||||
sta P8ESTACK_HI,x
|
||||
dex""")
|
||||
}
|
||||
else -> throw AssemblyError("string-assign to weird target")
|
||||
@@ -1359,9 +1371,11 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
|
||||
internal fun assignRegisterByte(target: AsmAssignTarget, register: CpuRegister) {
|
||||
// we make an exception in the type check for assigning something to a cx16 virtual register
|
||||
if(target.register !in Cx16VirtualRegisters) {
|
||||
if(target.kind==TargetStorageKind.VARIABLE) {
|
||||
// we make an exception in the type check for assigning something to a cx16 virtual register, or a register pair
|
||||
// these will be correctly typecasted from a byte to a word value
|
||||
if(target.register !in Cx16VirtualRegisters &&
|
||||
target.register!=RegisterOrPair.AX && target.register!=RegisterOrPair.AY && target.register!=RegisterOrPair.XY) {
|
||||
if(target.kind== TargetStorageKind.VARIABLE) {
|
||||
val parts = target.asmVarname.split('.')
|
||||
if (parts.size != 2 || parts[0] != "cx16")
|
||||
require(target.datatype in ByteDatatypes)
|
||||
@@ -1457,7 +1471,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
|
||||
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)
|
||||
throw AssemblyError("float value should be from FAC1 not from registerpair memory pointer")
|
||||
|
||||
@@ -1584,7 +1598,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
TargetStorageKind.STACK -> {
|
||||
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")
|
||||
in Cx16VirtualRegisters -> {
|
||||
val srcReg = asmgen.asmSymbolName(regs)
|
||||
@@ -2155,9 +2169,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 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)
|
||||
translateNormalAssignment(assign)
|
||||
}
|
||||
@@ -2169,8 +2183,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
translateNormalAssignment(assign)
|
||||
}
|
||||
|
||||
internal fun assignVariableToRegister(asmVarName: String, register: RegisterOrPair) {
|
||||
val tgt = AsmAssignTarget.fromRegisters(register, null, program, asmgen)
|
||||
internal fun assignVariableToRegister(asmVarName: String, register: RegisterOrPair, signed: Boolean) {
|
||||
val tgt = AsmAssignTarget.fromRegisters(register, signed, null, program, asmgen)
|
||||
val src = AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, tgt.datatype, variableAsmName = asmVarName)
|
||||
val assign = AsmAssignment(src, tgt, false, program.memsizer, Position.DUMMY)
|
||||
translateNormalAssignment(assign)
|
||||
+223
-117
@@ -5,10 +5,10 @@ import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.toHex
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.CpuType
|
||||
import prog8.compiler.target.AssemblyError
|
||||
import prog8.compiler.target.cpu6502.codegen.AsmGen
|
||||
import prog8.compiler.target.cpu6502.codegen.ExpressionsAsmGen
|
||||
import prog8.compilerinterface.CpuType
|
||||
|
||||
internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
private val assignmentAsmGen: AssignmentAsmGen,
|
||||
@@ -25,7 +25,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
val itype = value.inferType(program)
|
||||
if(!itype.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
val type = itype.typeOrElse(DataType.UNDEFINED)
|
||||
val type = itype.getOr(DataType.UNDEFINED)
|
||||
when (value.operator) {
|
||||
"+" -> {}
|
||||
"-" -> inplaceNegate(assign.target, type)
|
||||
@@ -181,8 +181,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
asmgen.translateExpression(memory.addressExpression)
|
||||
asmgen.out(" jsr prog8_lib.read_byte_from_address_on_stack | sta P8ZP_SCRATCH_B1")
|
||||
// TODO OTHER EVALUATION HERE, don't use the estack
|
||||
asmgen.assignExpressionTo(memory.addressExpression, AsmAssignTarget(TargetStorageKind.STACK, program, asmgen, DataType.UWORD, memory.definingSubroutine))
|
||||
asmgen.out(" jsr prog8_lib.read_byte_from_address_on_stack | sta P8ZP_SCRATCH_B1") // TODO don't use estack to transfer the address to read from
|
||||
when {
|
||||
valueLv != null -> inplaceModification_byte_litval_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, valueLv.toInt())
|
||||
ident != null -> inplaceModification_byte_variable_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, ident)
|
||||
@@ -193,7 +194,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
else -> inplaceModification_byte_value_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, value)
|
||||
}
|
||||
asmgen.out(" lda P8ZP_SCRATCH_B1 | jsr prog8_lib.write_byte_to_address_on_stack | inx")
|
||||
asmgen.out(" lda P8ZP_SCRATCH_B1 | jsr prog8_lib.write_byte_to_address_on_stack | inx") // TODO don't use estack to transfer the address to read from
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -246,19 +247,37 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
indexVar!=null -> {
|
||||
when (target.datatype) {
|
||||
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)
|
||||
assignmentAsmGen.translateNormalAssignment(assign)
|
||||
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A)
|
||||
}
|
||||
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)
|
||||
assignmentAsmGen.translateNormalAssignment(assign)
|
||||
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
}
|
||||
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)
|
||||
assignmentAsmGen.translateNormalAssignment(assign)
|
||||
assignmentAsmGen.assignFAC1float(target)
|
||||
@@ -280,7 +299,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
val childIDt = value.expression.inferType(program)
|
||||
if(!childIDt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
val childDt = childIDt.typeOrElse(DataType.UNDEFINED)
|
||||
val childDt = childIDt.getOr(DataType.UNDEFINED)
|
||||
if (value.type!=DataType.FLOAT && (value.type.equalsSize(childDt) || value.type.largerThan(childDt))) {
|
||||
// this typecast is redundant here; the rest of the code knows how to deal with the uncasted value.
|
||||
// (works for integer types, not for float.)
|
||||
@@ -293,7 +312,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
|
||||
private fun inplaceModification_byte_value_to_pointer(pointervar: IdentifierReference, operator: String, value: Expression) {
|
||||
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
when (operator) {
|
||||
// note: ** (power) operator requires floats.
|
||||
"+" -> asmgen.out(" clc | adc P8ZP_SCRATCH_B1")
|
||||
@@ -324,15 +342,13 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
"^", "xor" -> asmgen.out(" eor P8ZP_SCRATCH_B1")
|
||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||
}
|
||||
if(ptrOnZp)
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
else
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
}
|
||||
|
||||
private fun inplaceModification_byte_variable_to_pointer(pointervar: IdentifierReference, operator: String, value: IdentifierReference) {
|
||||
val otherName = asmgen.asmVariableName(value)
|
||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
|
||||
when (operator) {
|
||||
// note: ** (power) operator requires floats.
|
||||
@@ -364,105 +380,72 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
"^", "xor" -> asmgen.out(" eor $otherName")
|
||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||
}
|
||||
if(ptrOnZp)
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
else
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
}
|
||||
|
||||
private fun inplaceModification_byte_litval_to_pointer(pointervar: IdentifierReference, operator: String, value: Int) {
|
||||
when (operator) {
|
||||
// note: ** (power) operator requires floats.
|
||||
"+" -> {
|
||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" clc | adc #$value")
|
||||
if(ptrOnZp)
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
else
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" clc | adc #$value")
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
}
|
||||
"-" -> {
|
||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" sec | sbc #$value")
|
||||
if(ptrOnZp)
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
else
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" sec | sbc #$value")
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
}
|
||||
"*" -> {
|
||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
if(value in asmgen.optimizedByteMultiplications)
|
||||
asmgen.out(" jsr math.mul_byte_${value}")
|
||||
else
|
||||
asmgen.out(" ldy #$value | jsr math.multiply_bytes | ldy #0")
|
||||
if(ptrOnZp)
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
else
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
}
|
||||
"/" -> {
|
||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
if(value==0)
|
||||
throw AssemblyError("division by zero")
|
||||
asmgen.out(" ldy #$value | jsr math.divmod_ub_asm | tya | ldy #0")
|
||||
if(ptrOnZp)
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
else
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
}
|
||||
"%" -> {
|
||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
if(value==0)
|
||||
throw AssemblyError("division by zero")
|
||||
asmgen.out(" ldy #$value | jsr math.divmod_ub_asm | ldy #0")
|
||||
if(ptrOnZp)
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
else
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
}
|
||||
"<<" -> {
|
||||
if (value > 0) {
|
||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
repeat(value) { asmgen.out(" asl a") }
|
||||
if(ptrOnZp)
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
else
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
}
|
||||
}
|
||||
">>" -> {
|
||||
if (value > 0) {
|
||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
repeat(value) { asmgen.out(" lsr a") }
|
||||
if(ptrOnZp)
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
else
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
}
|
||||
}
|
||||
"&", "and" -> {
|
||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" and #$value")
|
||||
if(ptrOnZp)
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
else
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" and #$value")
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
}
|
||||
"|", "or" -> {
|
||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" ora #$value")
|
||||
if(ptrOnZp)
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
else
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" ora #$value")
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
}
|
||||
"^", "xor" -> {
|
||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" eor #$value")
|
||||
if(ptrOnZp)
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
else
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" eor #$value")
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
}
|
||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||
}
|
||||
@@ -1010,8 +993,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
|
||||
private fun inplaceModification_word_variable_to_variable(name: String, dt: DataType, operator: String, ident: IdentifierReference) {
|
||||
val otherName = asmgen.asmVariableName(ident)
|
||||
val valueDt = ident.targetVarDecl(program)!!.datatype
|
||||
when (valueDt) {
|
||||
when (val valueDt = ident.targetVarDecl(program)!!.datatype) {
|
||||
in ByteDatatypes -> {
|
||||
// the other variable is a BYTE type so optimize for that
|
||||
when (operator) {
|
||||
@@ -1255,7 +1237,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
val valueiDt = value.inferType(program)
|
||||
if(!valueiDt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
val valueDt = valueiDt.typeOrElse(DataType.UNDEFINED)
|
||||
val valueDt = valueiDt.getOr(DataType.UNDEFINED)
|
||||
|
||||
fun multiplyVarByWordInAY() {
|
||||
asmgen.out("""
|
||||
@@ -1697,7 +1679,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
if (innerCastDt == null) {
|
||||
// simple typecast where the value is the target
|
||||
when (target.datatype) {
|
||||
DataType.UBYTE, DataType.BYTE -> { /* byte target can't be casted to anything else at all */ }
|
||||
DataType.UBYTE, DataType.BYTE -> { /* byte target can't be typecasted to anything else at all */ }
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
when (outerCastDt) {
|
||||
DataType.UBYTE, DataType.BYTE -> {
|
||||
@@ -1742,7 +1724,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplaceBooleanNot(target: AsmAssignTarget, dt: DataType) {
|
||||
internal fun inplaceBooleanNot(target: AsmAssignTarget, dt: DataType) {
|
||||
when (dt) {
|
||||
DataType.UBYTE -> {
|
||||
when (target.kind) {
|
||||
@@ -1767,15 +1749,12 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
sta $addr""")
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(mem.addressExpression as IdentifierReference)
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(mem.addressExpression as IdentifierReference)
|
||||
asmgen.out("""
|
||||
beq +
|
||||
lda #1
|
||||
+ eor #1""")
|
||||
if(ptrOnZp)
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
else
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
}
|
||||
else -> {
|
||||
asmgen.assignExpressionToVariable(mem.addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD, target.scope)
|
||||
@@ -1789,9 +1768,30 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
}
|
||||
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place not of ubyte array")
|
||||
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg not")
|
||||
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack not")
|
||||
TargetStorageKind.REGISTER -> {
|
||||
when(target.register!!) {
|
||||
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("missing codegen for byte stack not")
|
||||
else -> throw AssemblyError("missing codegen for in-place not of ubyte ${target.kind}")
|
||||
}
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
@@ -1807,17 +1807,56 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
lsr a
|
||||
sta ${target.asmVarname}+1""")
|
||||
}
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("no asm gen for uword-memory not")
|
||||
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place not of uword array")
|
||||
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg not")
|
||||
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack not")
|
||||
TargetStorageKind.REGISTER -> {
|
||||
when(target.register!!) {
|
||||
RegisterOrPair.AX -> {
|
||||
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 -> TODO()
|
||||
else -> throw AssemblyError("invalid reg dt for word not")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.MEMORY -> TODO("no asm gen for uword-memory not")
|
||||
TargetStorageKind.STACK -> TODO("missing codegen for word stack not")
|
||||
else -> throw AssemblyError("missing codegen for in-place not of uword for ${target.kind}")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("boolean-not of invalid type")
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplaceInvert(target: AsmAssignTarget, dt: DataType) {
|
||||
internal fun inplaceInvert(target: AsmAssignTarget, dt: DataType) {
|
||||
when (dt) {
|
||||
DataType.UBYTE -> {
|
||||
when (target.kind) {
|
||||
@@ -1838,12 +1877,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
sta $addr""")
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(memory.addressExpression as IdentifierReference)
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(memory.addressExpression as IdentifierReference)
|
||||
asmgen.out(" eor #255")
|
||||
if(ptrOnZp)
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
else
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
}
|
||||
else -> {
|
||||
asmgen.assignExpressionToVariable(memory.addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD, target.scope)
|
||||
@@ -1855,9 +1891,16 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
}
|
||||
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place invert ubyte array")
|
||||
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg invert")
|
||||
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack invert")
|
||||
TargetStorageKind.REGISTER -> {
|
||||
when(target.register!!) {
|
||||
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("missing codegen for byte stack invert")
|
||||
else -> throw AssemblyError("missing codegen for in-place invert ubyte for ${target.kind}")
|
||||
}
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
@@ -1871,17 +1914,27 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
eor #255
|
||||
sta ${target.asmVarname}+1""")
|
||||
}
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("no asm gen for uword-memory invert")
|
||||
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place invert uword array")
|
||||
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg invert")
|
||||
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack invert")
|
||||
TargetStorageKind.REGISTER -> {
|
||||
when(target.register!!) {
|
||||
RegisterOrPair.AX -> asmgen.out(" pha | txa | eor #255 | tax | pla | eor #255")
|
||||
RegisterOrPair.AY -> asmgen.out(" pha | tya | eor #255 | tay | pla | eor #255")
|
||||
RegisterOrPair.XY -> asmgen.out(" txa | eor #255 | tax | tya | eor #255 | tay")
|
||||
in Cx16VirtualRegisters -> {
|
||||
TODO("codegen for cx16 word register invert")
|
||||
}
|
||||
else -> throw AssemblyError("invalid reg dt for word invert")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.MEMORY -> TODO("no asm gen for uword-memory invert")
|
||||
TargetStorageKind.STACK -> TODO("missing codegen for word stack invert")
|
||||
else -> throw AssemblyError("missing codegen for in-place invert uword for ${target.kind}")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("invert of invalid type")
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplaceNegate(target: AsmAssignTarget, dt: DataType) {
|
||||
internal fun inplaceNegate(target: AsmAssignTarget, dt: DataType) {
|
||||
when (dt) {
|
||||
DataType.BYTE -> {
|
||||
when (target.kind) {
|
||||
@@ -1892,10 +1945,17 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
sbc ${target.asmVarname}
|
||||
sta ${target.asmVarname}""")
|
||||
}
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("can't in-place negate memory ubyte")
|
||||
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place negate byte array")
|
||||
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg negate")
|
||||
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack negate")
|
||||
TargetStorageKind.REGISTER -> {
|
||||
when(target.register!!) {
|
||||
RegisterOrPair.A -> asmgen.out(" sta P8ZP_SCRATCH_B1 | lda #0 | sec | sbc P8ZP_SCRATCH_B1")
|
||||
RegisterOrPair.X -> asmgen.out(" stx P8ZP_SCRATCH_B1 | lda #0 | sec | sbc P8ZP_SCRATCH_B1 | tax")
|
||||
RegisterOrPair.Y -> asmgen.out(" sty P8ZP_SCRATCH_B1 | lda #0 | sec | sbc P8ZP_SCRATCH_B1 | tay")
|
||||
else -> throw AssemblyError("invalid reg dt for byte negate")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.MEMORY -> TODO("can't in-place negate memory ubyte")
|
||||
TargetStorageKind.STACK -> TODO("missing codegen for byte stack negate")
|
||||
else -> throw AssemblyError("missing codegen for in-place negate byte array")
|
||||
}
|
||||
}
|
||||
DataType.WORD -> {
|
||||
@@ -1910,10 +1970,55 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
sbc ${target.asmVarname}+1
|
||||
sta ${target.asmVarname}+1""")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place negate word array")
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("no asm gen for word memory negate")
|
||||
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg negate")
|
||||
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack negate")
|
||||
TargetStorageKind.REGISTER -> {
|
||||
when(target.register!!) { //P8ZP_SCRATCH_REG
|
||||
RegisterOrPair.AX -> {
|
||||
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 -> {
|
||||
TODO("codegen for cx16 word register negate")
|
||||
}
|
||||
else -> throw AssemblyError("invalid reg dt for word neg")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.MEMORY -> TODO("no asm gen for word memory negate")
|
||||
TargetStorageKind.STACK -> TODO("missing codegen for word stack negate")
|
||||
else -> throw AssemblyError("missing codegen for in-place negate word array")
|
||||
}
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
@@ -1926,9 +2031,10 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
sta ${target.asmVarname}+1
|
||||
""")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place negate float array")
|
||||
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack float negate")
|
||||
else -> throw AssemblyError("weird target kind for float")
|
||||
TargetStorageKind.REGISTER -> TODO("missing codegen for float reg negate")
|
||||
TargetStorageKind.MEMORY -> TODO("missing codegen for float memory negate")
|
||||
TargetStorageKind.STACK -> TODO("missing codegen for stack float negate")
|
||||
else -> throw AssemblyError("weird target kind for inplace negate float ${target.kind}")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("negate of invalid type")
|
||||
+31
-21
@@ -1,12 +1,13 @@
|
||||
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.cbm.viceMonListPostfix
|
||||
import prog8.compilerinterface.*
|
||||
import java.io.IOException
|
||||
import java.nio.file.Path
|
||||
|
||||
internal object CX16MachineDefinition: IMachineDefinition {
|
||||
|
||||
object CX16MachineDefinition: IMachineDefinition {
|
||||
|
||||
override val cpu = CpuType.CPU65c02
|
||||
|
||||
@@ -32,10 +33,28 @@ internal object CX16MachineDefinition: IMachineDefinition {
|
||||
emptyList()
|
||||
}
|
||||
|
||||
override fun launchEmulator(programName: String) {
|
||||
for(emulator in listOf("x16emu")) {
|
||||
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
|
||||
val emulatorName: String
|
||||
val extraArgs: List<String>
|
||||
|
||||
when(selectedEmulator) {
|
||||
1 -> {
|
||||
emulatorName = "x16emu"
|
||||
extraArgs = emptyList()
|
||||
}
|
||||
2 -> {
|
||||
emulatorName = "box16"
|
||||
extraArgs = listOf("-sym", "${programNameWithPath}.$viceMonListPostfix")
|
||||
}
|
||||
else -> {
|
||||
System.err.println("Cx16 target only supports x16emu and box16 emulators.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for(emulator in listOf(emulatorName)) {
|
||||
println("\nStarting Commander X16 emulator $emulator...")
|
||||
val cmdline = listOf(emulator, "-scale", "2", "-run", "-prg", "$programName.prg")
|
||||
val cmdline = listOf(emulator, "-scale", "2", "-run", "-prg", "${programNameWithPath}.prg") + extraArgs
|
||||
val processb = ProcessBuilder(cmdline).inheritIO()
|
||||
val process: Process
|
||||
try {
|
||||
@@ -68,7 +87,7 @@ internal object CX16MachineDefinition: IMachineDefinition {
|
||||
"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_REG = 0x7b // temp storage for a register, must be B1+1
|
||||
@@ -77,38 +96,29 @@ internal object CX16MachineDefinition: IMachineDefinition {
|
||||
|
||||
|
||||
init {
|
||||
if (options.floats && options.zeropage !in setOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
|
||||
throw CompilerException("when floats are enabled, zero page type should be 'basicsafe' or 'dontuse'")
|
||||
if (options.floats && options.zeropage !in arrayOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
|
||||
throw InternalCompilerException("when floats are enabled, zero page type should be 'basicsafe' or 'dontuse'")
|
||||
|
||||
// the addresses 0x02 to 0x21 (inclusive) are taken for sixteen virtual 16-bit api registers.
|
||||
|
||||
when (options.zeropage) {
|
||||
ZeropageType.FULL -> {
|
||||
free.addAll(0x22..0xff)
|
||||
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
|
||||
}
|
||||
ZeropageType.KERNALSAFE -> {
|
||||
free.addAll(0x22..0x7f)
|
||||
free.addAll(0xa9..0xff)
|
||||
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
|
||||
}
|
||||
ZeropageType.BASICSAFE -> {
|
||||
free.addAll(0x22..0x7f)
|
||||
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
|
||||
}
|
||||
ZeropageType.DONTUSE -> {
|
||||
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)
|
||||
require(SCRATCH_REG !in free)
|
||||
require(SCRATCH_W1 !in free)
|
||||
require(SCRATCH_W2 !in free)
|
||||
|
||||
for (reserved in options.zpReserved)
|
||||
reserve(reserved)
|
||||
removeReservedFromFreePool()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +1,33 @@
|
||||
package prog8tests
|
||||
package prog8tests.asmgen
|
||||
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.Matchers.equalTo
|
||||
import org.junit.jupiter.api.Disabled
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import prog8.ast.IBuiltinFunctions
|
||||
import prog8.ast.IMemSizer
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.base.RegisterOrPair
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.expressions.AddressOf
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.*
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.c64.C64MachineDefinition
|
||||
import prog8.compiler.target.cpu6502.codegen.AsmGen
|
||||
import prog8.compilerinterface.*
|
||||
import prog8.parser.SourceCode
|
||||
import prog8tests.asmgen.helpers.DummyFunctions
|
||||
import prog8tests.asmgen.helpers.DummyMemsizer
|
||||
import prog8tests.asmgen.helpers.DummyStringEncoder
|
||||
import prog8tests.asmgen.helpers.ErrorReporterForTests
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestAsmGen6502 {
|
||||
private class DummyFunctions: IBuiltinFunctions {
|
||||
override val names: Set<String> = emptySet()
|
||||
override val purefunctionNames: Set<String> = emptySet()
|
||||
override fun constValue(name: String, args: List<Expression>, position: Position, memsizer: IMemSizer): NumericLiteralValue? = null
|
||||
override fun returnType(name: String, args: MutableList<Expression>) = InferredTypes.InferredType.unknown()
|
||||
}
|
||||
|
||||
private class DummyMemsizer: IMemSizer {
|
||||
override fun memorySize(dt: DataType): Int = 0
|
||||
}
|
||||
|
||||
private fun createTestProgram(): Program {
|
||||
/*
|
||||
@@ -70,23 +68,23 @@ locallabel:
|
||||
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 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 varInBlock = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "var_outside", null, false, false, false, Position.DUMMY)
|
||||
val block = Block("main", null, mutableListOf(labelInBlock, varInBlock, subroutine), false, Position.DUMMY)
|
||||
|
||||
val module = Module("test", mutableListOf(block), Position.DUMMY, false, Path.of(""))
|
||||
module.linkParents(ParentSentinel)
|
||||
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
|
||||
module.program = program
|
||||
val module = Module(mutableListOf(block), Position.DUMMY, SourceCode.Generated("test"))
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
.addModule(module)
|
||||
module.linkIntoProgram(program)
|
||||
return program
|
||||
}
|
||||
|
||||
private fun createTestAsmGen(program: Program): AsmGen {
|
||||
val errors = ErrorReporter()
|
||||
val errors = ErrorReporterForTests()
|
||||
val options = CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, true, C64Target)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -108,7 +106,7 @@ locallabel:
|
||||
fun testSymbolNameFromVarIdentifier() {
|
||||
val program = createTestProgram()
|
||||
val asmgen = createTestAsmGen(program)
|
||||
val sub = program.entrypoint()
|
||||
val sub = program.entrypoint
|
||||
|
||||
// local variable
|
||||
val localvarIdent = sub.statements.filterIsInstance<Assignment>().first { it.value is IdentifierReference }.value as IdentifierReference
|
||||
@@ -132,7 +130,7 @@ locallabel:
|
||||
fun testSymbolNameFromLabelIdentifier() {
|
||||
val program = createTestProgram()
|
||||
val asmgen = createTestAsmGen(program)
|
||||
val sub = program.entrypoint()
|
||||
val sub = program.entrypoint
|
||||
|
||||
// local label
|
||||
val localLabelIdent = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("locallabel") }.value as AddressOf).identifier
|
||||
@@ -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): Int = 0
|
||||
}
|
||||
|
||||
internal val DummyStringEncoder = object : IStringEncoding {
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
|
||||
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"
|
||||
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2'
|
||||
testImplementation 'org.hamcrest:hamcrest:2.2'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2'
|
||||
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDirs = ["${project.projectDir}/src"]
|
||||
}
|
||||
resources {
|
||||
srcDirs = ["${project.projectDir}/res"]
|
||||
}
|
||||
}
|
||||
test {
|
||||
java {
|
||||
srcDirs = ["${project.projectDir}/test"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
// Enable JUnit 5 (Gradle 4.6+).
|
||||
useJUnitPlatform()
|
||||
|
||||
// Always run tests, even when nothing changed.
|
||||
dependsOn 'cleanTest'
|
||||
|
||||
// Show test results.
|
||||
testLogging {
|
||||
events "skipped", "failed"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?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" />
|
||||
<orderEntry type="library" scope="TEST" name="hamcrest" level="project" />
|
||||
<orderEntry type="library" name="junit.jupiter" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
+16
-8
@@ -1,21 +1,24 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.IStatementContainer
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
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.target.ICompilationTarget
|
||||
import prog8.compilerinterface.CompilationOptions
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
import prog8.compilerinterface.isInRegularRAMof
|
||||
|
||||
|
||||
internal class BinExprSplitter(private val program: Program, private val compTarget: ICompilationTarget) : AstWalker() {
|
||||
class BinExprSplitter(private val program: Program, private val options: CompilationOptions, private val compTarget: ICompilationTarget) : AstWalker() {
|
||||
|
||||
// override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
// TODO somehow if we do this, the resulting code for some programs (cube3d.p8) gets hundreds of bytes larger...:
|
||||
// TODO somehow if we do this, the resulting code for some programs (cube3d.p8) gets hundreds of bytes larger...: [ IS THIS STILL TRUE AFTER ALL CHANGES? ]
|
||||
// if(decl.type==VarDeclType.VAR ) {
|
||||
// val binExpr = decl.value as? BinaryExpression
|
||||
// if (binExpr != null && binExpr.operator in augmentAssignmentOperators) {
|
||||
@@ -37,6 +40,11 @@ internal class BinExprSplitter(private val program: Program, private val compTar
|
||||
|
||||
val binExpr = assignment.value as? BinaryExpression
|
||||
if (binExpr != null) {
|
||||
|
||||
if(binExpr.inferType(program).istype(DataType.FLOAT) && !options.optimizeFloatExpressions)
|
||||
return noModifications
|
||||
|
||||
|
||||
/*
|
||||
|
||||
Reduce the complexity of a (binary) expression that has to be evaluated on the eval stack,
|
||||
@@ -54,7 +62,7 @@ X = BinExpr X = LeftExpr
|
||||
|
||||
|
||||
*/
|
||||
if(binExpr.operator in augmentAssignmentOperators && isSimpleTarget(assignment.target, program)) {
|
||||
if(binExpr.operator in augmentAssignmentOperators && isSimpleTarget(assignment.target)) {
|
||||
if(assignment.target isSameAs binExpr.left || assignment.target isSameAs binExpr.right)
|
||||
return noModifications
|
||||
|
||||
@@ -64,7 +72,7 @@ X = BinExpr X = LeftExpr
|
||||
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)
|
||||
IAstModification.InsertBefore(assignment, firstAssign, assignment.parent as IStatementContainer)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -77,9 +85,9 @@ X = BinExpr X = LeftExpr
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun isSimpleTarget(target: AssignTarget, program: Program) =
|
||||
private fun isSimpleTarget(target: AssignTarget) =
|
||||
if (target.identifier!=null || target.memoryAddress!=null)
|
||||
compTarget.isInRegularRAM(target, program)
|
||||
target.isInRegularRAMof(compTarget.machine)
|
||||
else
|
||||
false
|
||||
|
||||
File renamed without changes.
+8
-9
@@ -9,11 +9,10 @@ import prog8.ast.statements.ForLoop
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compiler.target.ICompilationTarget
|
||||
import kotlin.math.pow
|
||||
|
||||
|
||||
internal class ConstantFoldingOptimizer(private val program: Program, private val compTarget: ICompilationTarget) : AstWalker() {
|
||||
class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
|
||||
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
|
||||
// @( &thing ) --> thing
|
||||
@@ -106,7 +105,7 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
// optimize various simple cases of ** :
|
||||
// optimize away 1 ** x into just 1 and 0 ** x into just 0
|
||||
// optimize 2 ** x into (1<<x) if both operands are integer.
|
||||
val leftDt = leftconst.inferType(program).typeOrElse(DataType.UNDEFINED)
|
||||
val leftDt = leftconst.inferType(program).getOr(DataType.UNDEFINED)
|
||||
when (leftconst.number.toDouble()) {
|
||||
0.0 -> {
|
||||
val value = NumericLiteralValue(leftDt, 0, expr.position)
|
||||
@@ -121,11 +120,11 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
val value = NumericLiteralValue(leftDt, 2.0.pow(rightconst.number.toDouble()), expr.position)
|
||||
modifications += IAstModification.ReplaceNode(expr, value, parent)
|
||||
} else {
|
||||
val rightDt = expr.right.inferType(program).typeOrElse(DataType.UNDEFINED)
|
||||
val rightDt = expr.right.inferType(program).getOr(DataType.UNDEFINED)
|
||||
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
|
||||
val targetDt =
|
||||
when (parent) {
|
||||
is Assignment -> parent.target.inferType(program).typeOrElse(DataType.UNDEFINED)
|
||||
is Assignment -> parent.target.inferType(program).getOr(DataType.UNDEFINED)
|
||||
is VarDecl -> parent.datatype
|
||||
else -> leftDt
|
||||
}
|
||||
@@ -138,7 +137,7 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
}
|
||||
}
|
||||
|
||||
if(expr.inferType(program).istype(DataType.FLOAT)) {
|
||||
if(expr.inferType(program) istype DataType.FLOAT) {
|
||||
val subExpr: BinaryExpression? = when {
|
||||
leftconst != null -> expr.right as? BinaryExpression
|
||||
rightconst != null -> expr.left as? BinaryExpression
|
||||
@@ -187,7 +186,7 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
} else {
|
||||
val arrayDt = array.guessDatatype(program)
|
||||
if (arrayDt.isKnown) {
|
||||
val newArray = array.cast(arrayDt.typeOrElse(DataType.UNDEFINED))
|
||||
val newArray = array.cast(arrayDt.getOr(DataType.UNDEFINED))
|
||||
if (newArray != null && newArray != array)
|
||||
return listOf(IAstModification.ReplaceNode(array, newArray, parent))
|
||||
}
|
||||
@@ -223,7 +222,7 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
range.step
|
||||
}
|
||||
|
||||
return RangeExpr(fromCast.valueOrZero(), toCast.valueOrZero(), newStep, compTarget, range.position)
|
||||
return RangeExpr(fromCast.valueOrZero(), toCast.valueOrZero(), newStep, range.position)
|
||||
}
|
||||
|
||||
// adjust the datatype of a range expression in for loops to the loop variable.
|
||||
@@ -278,7 +277,7 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
val numval = decl.value as? NumericLiteralValue
|
||||
if(decl.type== VarDeclType.CONST && numval!=null) {
|
||||
val valueDt = numval.inferType(program)
|
||||
if(!valueDt.istype(decl.datatype)) {
|
||||
if(valueDt isnot decl.datatype) {
|
||||
val cast = numval.cast(decl.datatype)
|
||||
if(cast.isValid)
|
||||
return listOf(IAstModification.ReplaceNode(numval, cast.valueOrZero(), decl))
|
||||
+50
-36
@@ -1,6 +1,5 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
@@ -8,17 +7,24 @@ import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compiler.IErrorReporter
|
||||
import prog8.compiler.target.ICompilationTarget
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
import prog8.compilerinterface.size
|
||||
import prog8.compilerinterface.toConstantIntegerRange
|
||||
|
||||
// Fix up the literal value's type to match that of the vardecl
|
||||
internal class VarConstantValueTypeAdjuster(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
|
||||
// (also check range literal operands types before they get expanded into arrays for instance)
|
||||
class VarConstantValueTypeAdjuster(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
|
||||
if(decl.parent is AnonymousScope)
|
||||
throw FatalAstException("vardecl may no longer occur in anonymousscope")
|
||||
|
||||
try {
|
||||
val declConstValue = decl.value?.constValue(program)
|
||||
if(declConstValue!=null && (decl.type==VarDeclType.VAR || decl.type==VarDeclType.CONST)
|
||||
&& !declConstValue.inferType(program).istype(decl.datatype)) {
|
||||
&& declConstValue.inferType(program) isnot decl.datatype) {
|
||||
// cast the numeric literal to the appropriate datatype of the variable
|
||||
val cast = declConstValue.cast(decl.datatype)
|
||||
if(cast.isValid)
|
||||
@@ -28,28 +34,35 @@ internal class VarConstantValueTypeAdjuster(private val program: Program, privat
|
||||
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
|
||||
}
|
||||
|
||||
override fun after(range: RangeExpr, parent: Node): Iterable<IAstModification> {
|
||||
val from = range.from.constValue(program)?.number?.toDouble()
|
||||
val to = range.to.constValue(program)?.number?.toDouble()
|
||||
val step = range.step.constValue(program)?.number?.toDouble()
|
||||
|
||||
if(from==null) {
|
||||
if(!range.from.inferType(program).isInteger)
|
||||
errors.err("range expression from value must be integer", range.from.position)
|
||||
} else if(from-from.toInt()>0) {
|
||||
errors.err("range expression from value must be integer", range.from.position)
|
||||
}
|
||||
|
||||
if(to==null) {
|
||||
if(!range.to.inferType(program).isInteger)
|
||||
errors.err("range expression to value must be integer", range.to.position)
|
||||
} else if(to-to.toInt()>0) {
|
||||
errors.err("range expression to value must be integer", range.to.position)
|
||||
}
|
||||
|
||||
if(step==null) {
|
||||
if(!range.step.inferType(program).isInteger)
|
||||
errors.err("range expression step value must be integer", range.step.position)
|
||||
} else if(step-step.toInt()>0) {
|
||||
errors.err("range expression step value must be integer", range.step.position)
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
@@ -124,7 +137,6 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
val numericLv = decl.value as? NumericLiteralValue
|
||||
val rangeExpr = decl.value as? RangeExpr
|
||||
if(rangeExpr!=null) {
|
||||
// convert the initializer range expression to an actual array
|
||||
@@ -133,7 +145,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
errors.err("range expression size doesn't match declared array size", decl.value?.position!!)
|
||||
val constRange = rangeExpr.toConstantIntegerRange()
|
||||
if(constRange!=null) {
|
||||
val eltType = rangeExpr.inferType(program).typeOrElse(DataType.UBYTE)
|
||||
val eltType = rangeExpr.inferType(program).getOr(DataType.UBYTE)
|
||||
val newValue = if(eltType in ByteDatatypes) {
|
||||
ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
|
||||
constRange.map { NumericLiteralValue(eltType, it.toShort(), decl.value!!.position) }.toTypedArray(),
|
||||
@@ -146,6 +158,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||
}
|
||||
}
|
||||
val numericLv = decl.value as? NumericLiteralValue
|
||||
if(numericLv!=null && numericLv.type== DataType.FLOAT)
|
||||
errors.err("arraysize requires only integers here", numericLv.position)
|
||||
val size = decl.arraysize?.constIndex() ?: return noModifications
|
||||
@@ -178,14 +191,12 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
val size = decl.arraysize?.constIndex() ?: return noModifications
|
||||
val litval = decl.value as? NumericLiteralValue
|
||||
val rangeExpr = decl.value as? RangeExpr
|
||||
if(rangeExpr!=null) {
|
||||
// convert the initializer range expression to an actual array of floats
|
||||
val declArraySize = decl.arraysize?.constIndex()
|
||||
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
||||
errors.err("range expression size doesn't match declared array size", decl.value?.position!!)
|
||||
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
|
||||
val constRange = rangeExpr.toConstantIntegerRange()
|
||||
if(constRange!=null) {
|
||||
val newValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F),
|
||||
@@ -194,15 +205,18 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||
}
|
||||
}
|
||||
if(rangeExpr==null && litval!=null) {
|
||||
|
||||
val numericLv = decl.value as? NumericLiteralValue
|
||||
val size = decl.arraysize?.constIndex() ?: return noModifications
|
||||
if(rangeExpr==null && numericLv!=null) {
|
||||
// arraysize initializer is a single int, and we know the size.
|
||||
val fillvalue = litval.number.toDouble()
|
||||
val fillvalue = numericLv.number.toDouble()
|
||||
if (fillvalue < compTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > compTarget.machine.FLOAT_MAX_POSITIVE)
|
||||
errors.err("float value overflow", litval.position)
|
||||
errors.err("float value overflow", numericLv.position)
|
||||
else {
|
||||
// create the array itself, filled with the fillvalue.
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteralValue(DataType.FLOAT, it, litval.position) }.toTypedArray<Expression>()
|
||||
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = litval.position)
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteralValue(DataType.FLOAT, it, numericLv.position) }.toTypedArray<Expression>()
|
||||
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = numericLv.position)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
|
||||
}
|
||||
}
|
||||
+30
-19
@@ -15,14 +15,25 @@ import kotlin.math.log2
|
||||
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
|
||||
|
||||
*(&X) => X
|
||||
X % 1 => 0
|
||||
X / 1 => X
|
||||
X ^ -1 => ~x
|
||||
X >= 1 => X > 0
|
||||
X < 1 => X <= 0
|
||||
X + С1 == C2 => X == C2 - C1
|
||||
((X + C1) + C2) => (X + (C1 + C2))
|
||||
((X + C1) + (Y + C2)) => ((X + Y) + (C1 + C2))
|
||||
|
||||
|
||||
*/
|
||||
|
||||
|
||||
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 negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
|
||||
|
||||
@@ -45,7 +56,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
mods += IAstModification.ReplaceNode(typecast.expression, subTypecast.expression, typecast)
|
||||
}
|
||||
} else {
|
||||
if (typecast.expression.inferType(program).istype(typecast.type)) {
|
||||
if (typecast.expression.inferType(program) istype typecast.type) {
|
||||
// remove duplicate cast
|
||||
mods += IAstModification.ReplaceNode(typecast, typecast.expression, parent)
|
||||
}
|
||||
@@ -134,8 +145,8 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
))
|
||||
}
|
||||
|
||||
val leftDt = leftIDt.typeOrElse(DataType.UNDEFINED)
|
||||
val rightDt = rightIDt.typeOrElse(DataType.UNDEFINED)
|
||||
val leftDt = leftIDt.getOr(DataType.UNDEFINED)
|
||||
val rightDt = rightIDt.getOr(DataType.UNDEFINED)
|
||||
|
||||
if (expr.operator == "+" || expr.operator == "-"
|
||||
&& leftVal == null && rightVal == null
|
||||
@@ -289,13 +300,13 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
val arg = functionCall.args[0]
|
||||
if(arg is TypecastExpression) {
|
||||
val valueDt = arg.expression.inferType(program)
|
||||
if (valueDt.istype(DataType.BYTE) || valueDt.istype(DataType.UBYTE)) {
|
||||
// useless lsb() of byte value that was casted to word
|
||||
if (valueDt istype DataType.BYTE || valueDt istype DataType.UBYTE) {
|
||||
// useless lsb() of byte value that was typecasted to word
|
||||
return listOf(IAstModification.ReplaceNode(functionCall, arg.expression, parent))
|
||||
}
|
||||
} else {
|
||||
val argDt = arg.inferType(program)
|
||||
if (argDt.istype(DataType.BYTE) || argDt.istype(DataType.UBYTE)) {
|
||||
if (argDt istype DataType.BYTE || argDt istype DataType.UBYTE) {
|
||||
// useless lsb() of byte value
|
||||
return listOf(IAstModification.ReplaceNode(functionCall, arg, parent))
|
||||
}
|
||||
@@ -305,20 +316,20 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
val arg = functionCall.args[0]
|
||||
if(arg is TypecastExpression) {
|
||||
val valueDt = arg.expression.inferType(program)
|
||||
if (valueDt.istype(DataType.BYTE) || valueDt.istype(DataType.UBYTE)) {
|
||||
// useless msb() of byte value that was casted to word, replace with 0
|
||||
if (valueDt istype DataType.BYTE || valueDt istype DataType.UBYTE) {
|
||||
// useless msb() of byte value that was typecasted to word, replace with 0
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
functionCall,
|
||||
NumericLiteralValue(valueDt.typeOrElse(DataType.UBYTE), 0, arg.expression.position),
|
||||
NumericLiteralValue(valueDt.getOr(DataType.UBYTE), 0, arg.expression.position),
|
||||
parent))
|
||||
}
|
||||
} else {
|
||||
val argDt = arg.inferType(program)
|
||||
if (argDt.istype(DataType.BYTE) || argDt.istype(DataType.UBYTE)) {
|
||||
if (argDt istype DataType.BYTE || argDt istype DataType.UBYTE) {
|
||||
// useless msb() of byte value, replace with 0
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
functionCall,
|
||||
NumericLiteralValue(argDt.typeOrElse(DataType.UBYTE), 0, arg.position),
|
||||
NumericLiteralValue(argDt.getOr(DataType.UBYTE), 0, arg.position),
|
||||
parent))
|
||||
}
|
||||
}
|
||||
@@ -489,7 +500,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
val idt = expr.inferType(program)
|
||||
if(!idt.isKnown)
|
||||
throw FatalAstException("unknown dt")
|
||||
return NumericLiteralValue(idt.typeOrElse(DataType.UNDEFINED), 0, expr.position)
|
||||
return NumericLiteralValue(idt.getOr(DataType.UNDEFINED), 0, expr.position)
|
||||
} else if (cv in powersOfTwo) {
|
||||
expr.operator = "&"
|
||||
expr.right = NumericLiteralValue.optimalInteger(cv!!.toInt()-1, expr.position)
|
||||
@@ -513,7 +524,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
val leftIDt = expr.left.inferType(program)
|
||||
if (!leftIDt.isKnown)
|
||||
return null
|
||||
val leftDt = leftIDt.typeOrElse(DataType.UNDEFINED)
|
||||
val leftDt = leftIDt.getOr(DataType.UNDEFINED)
|
||||
when (cv) {
|
||||
-1.0 -> {
|
||||
// '/' -> -left
|
||||
@@ -590,14 +601,14 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
return expr2.left
|
||||
}
|
||||
in powersOfTwo -> {
|
||||
if (leftValue.inferType(program).isInteger()) {
|
||||
if (leftValue.inferType(program).isInteger) {
|
||||
// times a power of two => shift left
|
||||
val numshifts = log2(cv).toInt()
|
||||
return BinaryExpression(expr2.left, "<<", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
|
||||
}
|
||||
}
|
||||
in negativePowersOfTwo -> {
|
||||
if (leftValue.inferType(program).isInteger()) {
|
||||
if (leftValue.inferType(program).isInteger) {
|
||||
// times a negative power of two => negate, then shift left
|
||||
val numshifts = log2(-cv).toInt()
|
||||
return BinaryExpression(PrefixExpression("-", expr2.left, expr.position), "<<", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
|
||||
@@ -621,7 +632,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
val targetIDt = expr.left.inferType(program)
|
||||
if(!targetIDt.isKnown)
|
||||
throw FatalAstException("unknown dt")
|
||||
when (val targetDt = targetIDt.typeOrElse(DataType.UNDEFINED)) {
|
||||
when (val targetDt = targetIDt.getOr(DataType.UNDEFINED)) {
|
||||
DataType.UBYTE, DataType.BYTE -> {
|
||||
if (amount >= 8) {
|
||||
return NumericLiteralValue(targetDt, 0, expr.position)
|
||||
@@ -656,7 +667,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
val idt = expr.left.inferType(program)
|
||||
if(!idt.isKnown)
|
||||
throw FatalAstException("unknown dt")
|
||||
when (idt.typeOrElse(DataType.UNDEFINED)) {
|
||||
when (idt.getOr(DataType.UNDEFINED)) {
|
||||
DataType.UBYTE -> {
|
||||
if (amount >= 8) {
|
||||
return NumericLiteralValue.optimalInteger(0, expr.position)
|
||||
+12
-10
@@ -2,11 +2,12 @@ package prog8.optimizer
|
||||
|
||||
import prog8.ast.IBuiltinFunctions
|
||||
import prog8.ast.Program
|
||||
import prog8.compiler.IErrorReporter
|
||||
import prog8.compiler.target.ICompilationTarget
|
||||
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)
|
||||
valuetypefixer.visit(this)
|
||||
if(errors.noErrors()) {
|
||||
@@ -21,7 +22,7 @@ internal fun Program.constantFold(errors: IErrorReporter, compTarget: ICompilati
|
||||
if(errors.noErrors()) {
|
||||
valuetypefixer.applyModifications()
|
||||
|
||||
val optimizer = ConstantFoldingOptimizer(this, compTarget)
|
||||
val optimizer = ConstantFoldingOptimizer(this)
|
||||
optimizer.visit(this)
|
||||
while (errors.noErrors() && optimizer.applyModifications() > 0) {
|
||||
optimizer.visit(this)
|
||||
@@ -40,9 +41,10 @@ internal fun Program.constantFold(errors: IErrorReporter, compTarget: ICompilati
|
||||
}
|
||||
|
||||
|
||||
internal fun Program.optimizeStatements(errors: IErrorReporter,
|
||||
functions: IBuiltinFunctions,
|
||||
compTarget: ICompilationTarget): Int {
|
||||
fun Program.optimizeStatements(errors: IErrorReporter,
|
||||
functions: IBuiltinFunctions,
|
||||
compTarget: ICompilationTarget
|
||||
): Int {
|
||||
val optimizer = StatementOptimizer(this, errors, functions, compTarget)
|
||||
optimizer.visit(this)
|
||||
val optimizationCount = optimizer.applyModifications()
|
||||
@@ -52,14 +54,14 @@ internal fun Program.optimizeStatements(errors: IErrorReporter,
|
||||
return optimizationCount
|
||||
}
|
||||
|
||||
internal fun Program.simplifyExpressions() : Int {
|
||||
fun Program.simplifyExpressions() : Int {
|
||||
val opti = ExpressionSimplifier(this)
|
||||
opti.visit(this)
|
||||
return opti.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.splitBinaryExpressions(compTarget: ICompilationTarget) : Int {
|
||||
val opti = BinExprSplitter(this, compTarget)
|
||||
fun Program.splitBinaryExpressions(options: CompilationOptions, compTarget: ICompilationTarget) : Int {
|
||||
val opti = BinExprSplitter(this, options, compTarget)
|
||||
opti.visit(this)
|
||||
return opti.applyModifications()
|
||||
}
|
||||
+87
-51
@@ -1,7 +1,7 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.IBuiltinFunctions
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.IStatementContainer
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
@@ -10,19 +10,21 @@ import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.compiler.IErrorReporter
|
||||
import prog8.compiler.target.ICompilationTarget
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
import prog8.compilerinterface.size
|
||||
import kotlin.math.floor
|
||||
|
||||
internal const val retvarName = "prog8_retval"
|
||||
|
||||
|
||||
internal class StatementOptimizer(private val program: Program,
|
||||
class StatementOptimizer(private val program: Program,
|
||||
private val errors: IErrorReporter,
|
||||
private val functions: IBuiltinFunctions,
|
||||
private val compTarget: ICompilationTarget) : AstWalker() {
|
||||
private val compTarget: ICompilationTarget
|
||||
) : AstWalker() {
|
||||
|
||||
private val subsThatNeedReturnVariable = mutableSetOf<Triple<INameScope, DataType, Position>>()
|
||||
private val subsThatNeedReturnVariable = mutableSetOf<Triple<IStatementContainer, DataType, Position>>()
|
||||
|
||||
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
@@ -39,12 +41,46 @@ internal class StatementOptimizer(private val program: Program,
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||
// if the first instruction in the called subroutine is a return statement with a simple value,
|
||||
// remove the jump altogeter and inline the returnvalue directly.
|
||||
val subroutine = functionCall.target.targetSubroutine(program)
|
||||
if(subroutine!=null) {
|
||||
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
||||
if(first is Return && first.value?.isSimple==true) {
|
||||
val copy = when(val orig = first.value!!) {
|
||||
is AddressOf -> {
|
||||
val scoped = scopePrefix(orig.identifier, subroutine)
|
||||
AddressOf(scoped, orig.position)
|
||||
}
|
||||
is DirectMemoryRead -> {
|
||||
when(val expr = orig.addressExpression) {
|
||||
is NumericLiteralValue -> DirectMemoryRead(expr.copy(), orig.position)
|
||||
else -> return noModifications
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> scopePrefix(orig, subroutine)
|
||||
is NumericLiteralValue -> orig.copy()
|
||||
is StringLiteralValue -> orig.copy()
|
||||
else -> return noModifications
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(functionCall, copy, parent))
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun scopePrefix(variable: IdentifierReference, subroutine: Subroutine): IdentifierReference {
|
||||
val scoped = subroutine.makeScopedName(variable.nameInSource.last())
|
||||
return IdentifierReference(scoped.split('.'), variable.position)
|
||||
}
|
||||
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in functions.names) {
|
||||
val functionName = functionCallStatement.target.nameInSource[0]
|
||||
if (functionName in functions.purefunctionNames) {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +118,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
functionCallStatement.void, pos
|
||||
)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(functionCallStatement, chrout1, parent as INameScope),
|
||||
IAstModification.InsertBefore(functionCallStatement, chrout1, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(functionCallStatement, chrout2, parent)
|
||||
)
|
||||
}
|
||||
@@ -95,33 +131,33 @@ internal class StatementOptimizer(private val program: Program,
|
||||
if(subroutine!=null) {
|
||||
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
||||
if(first is Return)
|
||||
return listOf(IAstModification.Remove(functionCallStatement, functionCallStatement.definingScope()))
|
||||
return listOf(IAstModification.Remove(functionCallStatement, parent as IStatementContainer))
|
||||
}
|
||||
|
||||
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 before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||
// // if the first instruction in the called subroutine is a return statement with constant value, replace with the constant value
|
||||
// val subroutine = functionCall.target.targetSubroutine(program)
|
||||
// if(subroutine!=null) {
|
||||
// val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
||||
// if(first is Return && first.value!=null) {
|
||||
// val constval = first.value?.constValue(program)
|
||||
// if(constval!=null)
|
||||
// return listOf(IAstModification.ReplaceNode(functionCall, constval, parent))
|
||||
// }
|
||||
// }
|
||||
// return noModifications
|
||||
// }
|
||||
|
||||
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
|
||||
// remove empty if statements
|
||||
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsNoCodeNorVars())
|
||||
return listOf(IAstModification.Remove(ifStatement, ifStatement.definingScope()))
|
||||
if(ifStatement.truepart.isEmpty() && ifStatement.elsepart.isEmpty())
|
||||
return listOf(IAstModification.Remove(ifStatement, parent as IStatementContainer))
|
||||
|
||||
// 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 emptyscope = AnonymousScope(mutableListOf(), ifStatement.elsepart.position)
|
||||
val truepart = AnonymousScope(ifStatement.elsepart.statements, ifStatement.truepart.position)
|
||||
@@ -149,20 +185,20 @@ internal class StatementOptimizer(private val program: Program,
|
||||
}
|
||||
|
||||
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)
|
||||
return listOf(IAstModification.Remove(forLoop, forLoop.definingScope()))
|
||||
return listOf(IAstModification.Remove(forLoop, parent as IStatementContainer))
|
||||
} else if(forLoop.body.statements.size==1) {
|
||||
val loopvar = forLoop.body.statements[0] as? VarDecl
|
||||
if(loopvar!=null && loopvar.name==forLoop.loopVar.nameInSource.singleOrNull()) {
|
||||
// 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
|
||||
if(range!=null) {
|
||||
if(range.size()==1) {
|
||||
if (range.size() == 1) {
|
||||
// for loop over a (constant) range of just a single value-- optimize the loop away
|
||||
// loopvar/reg = range value , follow by block
|
||||
val scope = AnonymousScope(mutableListOf(), forLoop.position)
|
||||
@@ -233,7 +269,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
} else {
|
||||
// always false -> remove the while statement altogether
|
||||
errors.warn("condition is always false", whileLoop.condition.position)
|
||||
listOf(IAstModification.Remove(whileLoop, whileLoop.definingScope()))
|
||||
listOf(IAstModification.Remove(whileLoop, parent as IStatementContainer))
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
@@ -242,14 +278,14 @@ internal class StatementOptimizer(private val program: Program,
|
||||
override fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> {
|
||||
val iter = repeatLoop.iterations
|
||||
if(iter!=null) {
|
||||
if(repeatLoop.body.containsNoCodeNorVars()) {
|
||||
if(repeatLoop.body.isEmpty()) {
|
||||
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()
|
||||
if (iterations == 0) {
|
||||
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) {
|
||||
errors.warn("iterations is always 1", iter.position)
|
||||
@@ -261,10 +297,10 @@ internal class StatementOptimizer(private val program: Program,
|
||||
|
||||
override fun after(jump: Jump, parent: Node): Iterable<IAstModification> {
|
||||
// if the jump is to the next statement, remove the jump
|
||||
val scope = jump.definingScope()
|
||||
val scope = jump.parent as IStatementContainer
|
||||
val label = jump.identifier?.targetStatement(program)
|
||||
if(label!=null && scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1)
|
||||
return listOf(IAstModification.Remove(jump, jump.definingScope()))
|
||||
return listOf(IAstModification.Remove(jump, scope))
|
||||
|
||||
return noModifications
|
||||
}
|
||||
@@ -297,7 +333,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
|
||||
IAstModification.InsertAfter(assignment, addConstant, assignment.definingScope()))
|
||||
IAstModification.InsertAfter(assignment, addConstant, parent as IStatementContainer))
|
||||
} else if (op2 == "-") {
|
||||
// A = A +/- B - N
|
||||
val expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position)
|
||||
@@ -308,7 +344,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
|
||||
IAstModification.InsertAfter(assignment, subConstant, assignment.definingScope()))
|
||||
IAstModification.InsertAfter(assignment, subConstant, parent as IStatementContainer))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -330,7 +366,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
if(assignment.target isSameAs assignment.value) {
|
||||
// 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)
|
||||
@@ -338,7 +374,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
throw FatalAstException("can't infer type of assignment target")
|
||||
|
||||
// optimize binary expressions a bit
|
||||
val targetDt = targetIDt.typeOrElse(DataType.UNDEFINED)
|
||||
val targetDt = targetIDt.getOr(DataType.UNDEFINED)
|
||||
val bexpr=assignment.value as? BinaryExpression
|
||||
if(bexpr!=null) {
|
||||
val rightCv = bexpr.right.constValue(program)?.number?.toDouble()
|
||||
@@ -350,7 +386,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
when (bexpr.operator) {
|
||||
"+" -> {
|
||||
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) {
|
||||
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)
|
||||
@@ -364,7 +400,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
}
|
||||
"-" -> {
|
||||
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) {
|
||||
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)
|
||||
@@ -376,18 +412,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, assignment.definingScope()))
|
||||
"**" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
||||
"|" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
||||
"^" -> if (rightCv == 0.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, parent as IStatementContainer))
|
||||
"**" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
|
||||
"|" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
|
||||
"^" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
|
||||
"<<" -> {
|
||||
if (rightCv == 0.0)
|
||||
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
||||
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
|
||||
}
|
||||
">>" -> {
|
||||
if (rightCv == 0.0)
|
||||
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
||||
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,7 +435,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
|
||||
override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> {
|
||||
fun returnViaIntermediaryVar(value: Expression): Iterable<IAstModification>? {
|
||||
val subr = returnStmt.definingSubroutine()!!
|
||||
val subr = returnStmt.definingSubroutine!!
|
||||
val returnDt = subr.returntypes.single()
|
||||
if (returnDt in IntegerDatatypes) {
|
||||
// first assign to intermediary variable, then return that
|
||||
@@ -410,7 +446,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
val assign = Assignment(tgt, value, returnStmt.position)
|
||||
val returnReplacement = Return(returnValueIntermediary2, returnStmt.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(returnStmt, assign, parent as INameScope),
|
||||
IAstModification.InsertBefore(returnStmt, assign, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(returnStmt, returnReplacement, parent)
|
||||
)
|
||||
}
|
||||
@@ -434,7 +470,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
return super.after(returnStmt, parent)
|
||||
}
|
||||
|
||||
private fun hasBreak(scope: INameScope): Boolean {
|
||||
private fun hasBreak(scope: IStatementContainer): Boolean {
|
||||
|
||||
class Searcher: IAstVisitor
|
||||
{
|
||||
+37
-34
@@ -9,46 +9,49 @@ 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.target.ICompilationTarget
|
||||
import prog8.compilerinterface.CallGraph
|
||||
import prog8.compilerinterface.ICompilationTarget
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
import prog8.compilerinterface.isInRegularRAMof
|
||||
|
||||
|
||||
internal class UnusedCodeRemover(private val program: Program,
|
||||
private val errors: IErrorReporter,
|
||||
private val compTarget: ICompilationTarget): AstWalker() {
|
||||
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.isLibraryModule && (module.containsNoCodeNorVars() || callgraph.unused(module)))
|
||||
listOf(IAstModification.Remove(module, module.definingScope()))
|
||||
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, parent as INameScope)
|
||||
reportUnreachable(breakStmt, parent as IStatementContainer)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun before(jump: Jump, parent: Node): Iterable<IAstModification> {
|
||||
reportUnreachable(jump, parent as INameScope)
|
||||
reportUnreachable(jump, parent as IStatementContainer)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> {
|
||||
reportUnreachable(returnStmt, parent as INameScope)
|
||||
reportUnreachable(returnStmt, parent as IStatementContainer)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
if(functionCallStatement.target.nameInSource.last() == "exit")
|
||||
reportUnreachable(functionCallStatement, parent as INameScope)
|
||||
reportUnreachable(functionCallStatement, parent as IStatementContainer)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
private fun reportUnreachable(stmt: Statement, parent: INameScope) {
|
||||
when(val next = parent.nextSibling(stmt)) {
|
||||
private fun reportUnreachable(stmt: Statement, parent: IStatementContainer) {
|
||||
when(val next = stmt.nextSibling()) {
|
||||
null, is Label, is Directive, is VarDecl, is InlineAssembly, is Subroutine -> {}
|
||||
else -> errors.warn("unreachable code", next.position)
|
||||
}
|
||||
@@ -61,14 +64,14 @@ internal class UnusedCodeRemover(private val program: Program,
|
||||
|
||||
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
|
||||
if("force_output" !in block.options()) {
|
||||
if (block.containsNoCodeNorVars()) {
|
||||
if (block.containsNoCodeNorVars) {
|
||||
if(block.name != internedStringsModuleName)
|
||||
errors.warn("removing unused block '${block.name}'", block.position)
|
||||
return listOf(IAstModification.Remove(block, parent as INameScope))
|
||||
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 INameScope))
|
||||
return listOf(IAstModification.Remove(block, parent as IStatementContainer))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,22 +80,22 @@ internal class UnusedCodeRemover(private val program: Program,
|
||||
}
|
||||
|
||||
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) {
|
||||
val forceOutput = "force_output" in subroutine.definingBlock.options()
|
||||
if (subroutine !== program.entrypoint && !forceOutput && !subroutine.inline && !subroutine.isAsmSubroutine) {
|
||||
if(callgraph.unused(subroutine)) {
|
||||
if(!subroutine.definingModule().isLibraryModule)
|
||||
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
|
||||
return listOf(IAstModification.Remove(subroutine, subroutine.definingScope()))
|
||||
}
|
||||
if(subroutine.containsNoCodeNorVars()) {
|
||||
if(!subroutine.definingModule().isLibraryModule)
|
||||
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()))
|
||||
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
|
||||
}
|
||||
return removals
|
||||
if(!subroutine.definingModule.isLibrary)
|
||||
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
|
||||
return listOf(IAstModification.Remove(subroutine, parent as IStatementContainer))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,11 +105,11 @@ internal class UnusedCodeRemover(private val program: Program,
|
||||
|
||||
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 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 listOf(IAstModification.Remove(decl, parent as IStatementContainer))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,7 +125,7 @@ internal class UnusedCodeRemover(private val program: Program,
|
||||
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) && compTarget.isInRegularRAM(assign1.target, program)) {
|
||||
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) {
|
||||
@@ -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.
|
||||
+17
-35
@@ -2,31 +2,31 @@ plugins {
|
||||
id 'java'
|
||||
id 'application'
|
||||
id "org.jetbrains.kotlin.jvm"
|
||||
id 'com.github.johnrengelman.shadow' version '7.0.0'
|
||||
id 'com.github.johnrengelman.shadow' version '7.1.0'
|
||||
}
|
||||
|
||||
targetCompatibility = 11
|
||||
sourceCompatibility = 11
|
||||
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
maven { url "https://kotlin.bintray.com/kotlinx" }
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(javaVersion)
|
||||
}
|
||||
}
|
||||
|
||||
def prog8version = rootProject.file('compiler/res/version.txt').text.trim()
|
||||
|
||||
dependencies {
|
||||
implementation project(':compilerInterfaces')
|
||||
implementation project(':codeOptimizers')
|
||||
implementation project(':compilerAst')
|
||||
implementation project(':codeGeneration')
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.2'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.3'
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.12"
|
||||
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2'
|
||||
testImplementation 'org.hamcrest:hamcrest-junit:2.0.0.0'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.2'
|
||||
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 {
|
||||
@@ -43,22 +43,6 @@ configurations {
|
||||
}
|
||||
|
||||
|
||||
compileKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
useIR = true
|
||||
// verbose = true
|
||||
// freeCompilerArgs += "-XXLanguage:+NewInference"
|
||||
}
|
||||
}
|
||||
|
||||
compileTestKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
useIR = true
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
@@ -70,7 +54,8 @@ sourceSets {
|
||||
}
|
||||
test {
|
||||
java {
|
||||
srcDirs = ["${project.projectDir}/test"]
|
||||
srcDir "${project.projectDir}/test"
|
||||
srcDir "${project(':compilerAst').projectDir}/test/helpers"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,11 +67,6 @@ application {
|
||||
applicationName = 'p8compile'
|
||||
}
|
||||
|
||||
artifacts {
|
||||
archives shadowJar
|
||||
}
|
||||
|
||||
|
||||
shadowJar {
|
||||
archiveBaseName = 'prog8compiler'
|
||||
archiveVersion = prog8version
|
||||
@@ -106,3 +86,5 @@ test {
|
||||
events "skipped", "failed"
|
||||
}
|
||||
}
|
||||
|
||||
build.finalizedBy installDist, installShadowDist
|
||||
@@ -16,9 +16,14 @@
|
||||
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="library" name="unittest-libs" level="project" />
|
||||
<orderEntry type="library" name="kotlinx-cli-jvm" level="project" />
|
||||
<orderEntry type="module" module-name="compilerAst" />
|
||||
<orderEntry type="library" name="Python 3.9 interpreter library" level="application" />
|
||||
<orderEntry type="library" name="hamcrest" level="project" />
|
||||
<orderEntry type="library" name="jetbrains.kotlinx.cli.jvm" level="project" />
|
||||
<orderEntry type="library" name="junit.jupiter" level="project" />
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
<orderEntry type="module" module-name="codeOptimizers" />
|
||||
<orderEntry type="module" module-name="compilerInterfaces" />
|
||||
<orderEntry type="module" module-name="codeGeneration" />
|
||||
</component>
|
||||
</module>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -4,7 +4,6 @@
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
%target c64
|
||||
%option enable_floats
|
||||
|
||||
floats {
|
||||
@@ -13,6 +12,7 @@ floats {
|
||||
const float PI = 3.141592653589793
|
||||
const float TWOPI = 6.283185307179586
|
||||
|
||||
ubyte[5] tempvar_swap_float ; used for some swap() operations
|
||||
|
||||
; ---- C64 basic and kernal ROM float constants and functions ----
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
%target c64
|
||||
%import textio
|
||||
|
||||
; bitmap pixel graphics module for the C64
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
%target c64
|
||||
|
||||
c64 {
|
||||
&ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
|
||||
&ubyte TIME_MID = $a1 ; .. mid byte
|
||||
@@ -502,11 +500,9 @@ sys {
|
||||
; --- busy wait till the next vsync has occurred (approximately), without depending on custom irq handling.
|
||||
; note: a more accurate way to wait for vsync is to set up a vsync irq handler instead.
|
||||
%asm {{
|
||||
- lda c64.RASTER
|
||||
beq -
|
||||
- lda c64.RASTER
|
||||
bne -
|
||||
bit c64.SCROLY
|
||||
- bit c64.SCROLY
|
||||
bpl -
|
||||
- bit c64.SCROLY
|
||||
bmi -
|
||||
rts
|
||||
}}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
%target c64
|
||||
%import syslib
|
||||
%import conv
|
||||
|
||||
|
||||
@@ -4,17 +4,17 @@
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
%target cx16
|
||||
%option enable_floats
|
||||
|
||||
floats {
|
||||
; ---- 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.
|
||||
|
||||
|
||||
const float PI = 3.141592653589793
|
||||
const float TWOPI = 6.283185307179586
|
||||
|
||||
ubyte[5] tempvar_swap_float ; used for some swap() operations
|
||||
|
||||
|
||||
; ---- ROM float functions ----
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
%target cx16
|
||||
|
||||
; Bitmap pixel graphics routines for the CommanderX16
|
||||
; Custom routines to use the full-screen 640x480 and 320x240 screen modes.
|
||||
; (These modes are not supported by the documented GRAPH_xxxx kernal routines)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
%target cx16
|
||||
%import syslib
|
||||
%import textio
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
%target cx16
|
||||
|
||||
; Manipulate the Commander X16's display color palette.
|
||||
; Should you want to restore the default palette, you have to reinitialize the Vera yourself.
|
||||
|
||||
@@ -9,7 +7,7 @@ palette {
|
||||
ubyte c
|
||||
|
||||
sub set_color(ubyte index, uword color) {
|
||||
vera_palette_ptr = $fa00+index*2
|
||||
vera_palette_ptr = $fa00+(index as uword * 2)
|
||||
cx16.vpoke(1, vera_palette_ptr, lsb(color))
|
||||
vera_palette_ptr++
|
||||
cx16.vpoke(1, vera_palette_ptr, msb(color))
|
||||
@@ -70,11 +68,11 @@ palette {
|
||||
}
|
||||
}
|
||||
|
||||
inline sub set_all_black() {
|
||||
sub set_all_black() {
|
||||
set_monochrome($000, $000)
|
||||
}
|
||||
|
||||
inline sub set_all_white() {
|
||||
sub set_all_white() {
|
||||
set_monochrome($fff, $fff)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,6 @@
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
%target cx16
|
||||
|
||||
|
||||
c64 {
|
||||
|
||||
; ---- kernal routines, these are the same as on the Commodore-64 (hence the same block name) ----
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
%target cx16
|
||||
%import syslib
|
||||
%import conv
|
||||
|
||||
|
||||
@@ -6,10 +6,12 @@ prog8_lib {
|
||||
%asminclude "library:prog8_lib.asm"
|
||||
%asminclude "library:prog8_funcs.asm"
|
||||
|
||||
; TODO these retval variables are no longer used???
|
||||
uword @zp retval_interm_uw ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
|
||||
word @zp retval_interm_w ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
|
||||
ubyte @zp retval_interm_ub ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
|
||||
byte @zp retval_interm_b ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
|
||||
; NOTE: these variables are checked in the StatementReorderer (in fun after(decl: VarDecl)), for these exact names!
|
||||
|
||||
asmsub pattern_match(str string @AY, str pattern @R0) clobbers(Y) -> ubyte @A {
|
||||
%asm {{
|
||||
|
||||
@@ -1 +1 @@
|
||||
7.0
|
||||
7.2
|
||||
@@ -6,7 +6,6 @@ import prog8.compiler.CompilationResult
|
||||
import prog8.compiler.compileProgram
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8.parser.ParsingFailedError
|
||||
import java.io.File
|
||||
import java.nio.file.FileSystems
|
||||
import java.nio.file.Path
|
||||
@@ -16,55 +15,56 @@ import kotlin.system.exitProcess
|
||||
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
printSoftwareHeader("compiler")
|
||||
|
||||
compileMain(args)
|
||||
}
|
||||
|
||||
internal fun printSoftwareHeader(what: String) {
|
||||
val buildVersion = object {}.javaClass.getResource("/version.txt").readText().trim()
|
||||
println("\nProg8 $what v$buildVersion by Irmen de Jong (irmen@razorvine.net)")
|
||||
println("\nProg8 compiler v$buildVersion by Irmen de Jong (irmen@razorvine.net)")
|
||||
println("This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html\n")
|
||||
|
||||
val succes = compileMain(args)
|
||||
if(!succes)
|
||||
exitProcess(1)
|
||||
}
|
||||
|
||||
|
||||
fun pathFrom(stringPath: String, vararg rest: String): Path = FileSystems.getDefault().getPath(stringPath, *rest)
|
||||
|
||||
|
||||
private fun compileMain(args: Array<String>) {
|
||||
private fun compileMain(args: Array<String>): Boolean {
|
||||
val cli = ArgParser("prog8compiler", prefixStyle = ArgParser.OptionPrefixStyle.JVM)
|
||||
val startEmulator by cli.option(ArgType.Boolean, fullName = "emu", description = "auto-start emulator after successful compilation")
|
||||
val startEmulator1 by cli.option(ArgType.Boolean, fullName = "emu", description = "auto-start emulator after successful compilation")
|
||||
val startEmulator2 by cli.option(ArgType.Boolean, fullName = "emu2", description = "auto-start alternative emulator after successful compilation")
|
||||
val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".")
|
||||
val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code")
|
||||
val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform any optimizations")
|
||||
val optimizeFloatExpressions by cli.option(ArgType.Boolean, fullName = "optfloatx", description = "optimize float expressions (warning: can increase program size)")
|
||||
val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watches for file changes), greatly increases compilation speed")
|
||||
val slowCodegenWarnings by cli.option(ArgType.Boolean, fullName = "slowwarn", description="show debug warnings about slow/problematic assembly code generation")
|
||||
val quietAssembler by cli.option(ArgType.Boolean, fullName = "quietasm", description = "don't print assembler output results")
|
||||
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available").default(C64Target.name)
|
||||
val libDirs by cli.option(ArgType.String, fullName="libdirs", 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)
|
||||
|
||||
try {
|
||||
cli.parse(args)
|
||||
} catch (e: IllegalStateException) {
|
||||
System.err.println(e.message)
|
||||
exitProcess(1)
|
||||
return false
|
||||
}
|
||||
|
||||
val outputPath = pathFrom(outputDir)
|
||||
if(!outputPath.toFile().isDirectory) {
|
||||
System.err.println("Output path doesn't exist")
|
||||
exitProcess(1)
|
||||
return false
|
||||
}
|
||||
|
||||
val faultyOption = moduleFiles.firstOrNull { it.startsWith('-') }
|
||||
if(faultyOption!=null) {
|
||||
System.err.println("Unknown command line option given: $faultyOption")
|
||||
exitProcess(1)
|
||||
return false
|
||||
}
|
||||
|
||||
val libdirs = libDirs.toMutableList()
|
||||
if(libdirs.firstOrNull()!=".")
|
||||
libdirs.add(0, ".")
|
||||
val srcdirs = sourceDirs.toMutableList()
|
||||
if(srcdirs.firstOrNull()!=".")
|
||||
srcdirs.add(0, ".")
|
||||
|
||||
if(watchMode==true) {
|
||||
val watchservice = FileSystems.getDefault().newWatchService()
|
||||
@@ -75,7 +75,10 @@ private fun compileMain(args: Array<String>) {
|
||||
val results = mutableListOf<CompilationResult>()
|
||||
for(filepathRaw in moduleFiles) {
|
||||
val filepath = pathFrom(filepathRaw).normalize()
|
||||
val compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, libdirs, outputPath)
|
||||
val compilationResult = compileProgram(filepath,
|
||||
dontOptimize!=true, optimizeFloatExpressions==true,
|
||||
dontWriteAssembly!=true, slowCodegenWarnings==true, quietAssembler==true,
|
||||
compilationTarget, srcdirs, outputPath)
|
||||
results.add(compilationResult)
|
||||
}
|
||||
|
||||
@@ -86,7 +89,7 @@ private fun compileMain(args: Array<String>) {
|
||||
for (importedFile in allImportedFiles) {
|
||||
print(" ")
|
||||
println(importedFile)
|
||||
val watchDir = importedFile.parent ?: Path.of(".")
|
||||
val watchDir = importedFile.parent ?: Path.of("")
|
||||
watchDir.register(watchservice, StandardWatchEventKinds.ENTRY_MODIFY)
|
||||
}
|
||||
println("[${LocalDateTime.now().withNano(0)}] Waiting for file changes.")
|
||||
@@ -112,22 +115,31 @@ private fun compileMain(args: Array<String>) {
|
||||
val filepath = pathFrom(filepathRaw).normalize()
|
||||
val compilationResult: CompilationResult
|
||||
try {
|
||||
compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, libdirs, outputPath)
|
||||
compilationResult = compileProgram(filepath,
|
||||
dontOptimize!=true, optimizeFloatExpressions==true,
|
||||
dontWriteAssembly!=true, slowCodegenWarnings==true, quietAssembler==true,
|
||||
compilationTarget, srcdirs, outputPath)
|
||||
if(!compilationResult.success)
|
||||
exitProcess(1)
|
||||
} catch (x: ParsingFailedError) {
|
||||
exitProcess(1)
|
||||
return false
|
||||
} catch (x: AstException) {
|
||||
exitProcess(1)
|
||||
return false
|
||||
}
|
||||
|
||||
if (startEmulator==true) {
|
||||
if (compilationResult.programName.isEmpty())
|
||||
if(startEmulator1==true || startEmulator2==true) {
|
||||
if (compilationResult.programName.isEmpty()) {
|
||||
println("\nCan't start emulator because no program was assembled.")
|
||||
else {
|
||||
compilationResult.compTarget.machine.launchEmulator(compilationResult.programName)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
val programNameInPath = outputPath.resolve(compilationResult.programName)
|
||||
|
||||
if (startEmulator1==true)
|
||||
compilationResult.compTarget.machine.launchEmulator(1, programNameInPath)
|
||||
else if (startEmulator2==true)
|
||||
compilationResult.compTarget.machine.launchEmulator(2, programNameInPath)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
package prog8.compiler
|
||||
|
||||
internal class AssemblyError(msg: String) : RuntimeException(msg)
|
||||
@@ -1,6 +1,7 @@
|
||||
package prog8.compiler
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.IStatementContainer
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
@@ -9,27 +10,18 @@ import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.compiler.target.ICompilationTarget
|
||||
import prog8.compiler.astprocessing.isSubroutineParameter
|
||||
import prog8.compilerinterface.*
|
||||
|
||||
|
||||
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() {
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
if(decl.type==VarDeclType.VAR && decl.value != null && decl.datatype in NumericDatatypes)
|
||||
throw FatalAstException("vardecls for variables, with initial numerical value, should have been rewritten as plain vardecl + assignment $decl")
|
||||
|
||||
subroutineVariables.add(decl.name to decl)
|
||||
if (decl.value == null && !decl.autogeneratedDontRemove && decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
|
||||
// A numeric vardecl without an initial value is initialized with zero,
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -39,8 +31,12 @@ 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.
|
||||
if(!assignment.isAugmentable
|
||||
&& assignment.target.identifier != null
|
||||
&& compTarget.isInRegularRAM(assignment.target, program)) {
|
||||
&& assignment.target.isInRegularRAMof(options.compTarget.machine)) {
|
||||
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.left !is BinaryExpression) {
|
||||
if (binExpr.right.referencesIdentifier(*assignment.target.identifier!!.nameInSource.toTypedArray())) {
|
||||
@@ -51,14 +47,14 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
|
||||
// use the other part of the expression to split.
|
||||
val assignRight = Assignment(assignment.target, binExpr.right, assignment.position)
|
||||
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.left, assignment.target.toExpression(), binExpr))
|
||||
}
|
||||
} else {
|
||||
val assignLeft = Assignment(assignment.target, binExpr.left, assignment.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(assignment, assignLeft, assignment.definingScope()),
|
||||
IAstModification.InsertBefore(assignment, assignLeft, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
|
||||
}
|
||||
}
|
||||
@@ -73,33 +69,35 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
|
||||
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
subroutineVariables.clear()
|
||||
addedIfConditionVars.clear()
|
||||
|
||||
if(!subroutine.isAsmSubroutine) {
|
||||
// change 'str' parameters into 'uword' (just treat it as an address)
|
||||
val stringParams = subroutine.parameters.filter { it.type==DataType.STR }
|
||||
val parameterChanges = stringParams.map {
|
||||
val uwordParam = SubroutineParameter(it.name, DataType.UWORD, it.position)
|
||||
IAstModification.ReplaceNode(it, uwordParam, subroutine)
|
||||
}
|
||||
|
||||
val stringParamNames = stringParams.map { it.name }.toSet()
|
||||
val varsChanges = subroutine.statements
|
||||
.filterIsInstance<VarDecl>()
|
||||
.filter { it.autogeneratedDontRemove && it.name in stringParamNames }
|
||||
.map {
|
||||
val newvar = VarDecl(it.type, DataType.UWORD, it.zeropage, null, it.name, null, false, true, it.sharedWithAsm, it.position)
|
||||
IAstModification.ReplaceNode(it, newvar, subroutine)
|
||||
}
|
||||
|
||||
return parameterChanges + varsChanges
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
if(scope.statements.any { it is VarDecl || it is IStatementContainer })
|
||||
throw FatalAstException("anonymousscope may no longer contain any vardecls or subscopes")
|
||||
|
||||
val decls = scope.statements.filterIsInstance<VarDecl>().filter { it.type == VarDeclType.VAR }
|
||||
subroutineVariables.addAll(decls.map { it.name to it })
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -119,17 +117,17 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
|
||||
// and if an assembly block doesn't contain a rts/rti, and some other situations.
|
||||
val mods = mutableListOf<IAstModification>()
|
||||
val returnStmt = Return(null, subroutine.position)
|
||||
if (subroutine.asmAddress == null
|
||||
&& !subroutine.inline
|
||||
&& subroutine.statements.isNotEmpty()
|
||||
&& subroutine.amountOfRtsInAsm() == 0
|
||||
if (subroutine.asmAddress == null && !subroutine.inline) {
|
||||
if(subroutine.statements.isEmpty() ||
|
||||
(subroutine.amountOfRtsInAsm() == 0
|
||||
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return
|
||||
&& subroutine.statements.last() !is Subroutine) {
|
||||
mods += IAstModification.InsertLast(returnStmt, subroutine)
|
||||
&& subroutine.statements.last() !is Subroutine)) {
|
||||
mods += IAstModification.InsertLast(returnStmt, subroutine)
|
||||
}
|
||||
}
|
||||
|
||||
// precede a subroutine with a return to avoid falling through into the subroutine from code above it
|
||||
val outerScope = subroutine.definingScope()
|
||||
val outerScope = subroutine.definingScope
|
||||
val outerStatements = outerScope.statements
|
||||
val subroutineStmtIdx = outerStatements.indexOf(subroutine)
|
||||
if (subroutineStmtIdx > 0
|
||||
@@ -145,8 +143,9 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
|
||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||
// see if we can remove superfluous typecasts (outside of expressions)
|
||||
// such as casting byte<->ubyte, word<->uword
|
||||
// Also the special typecast of a reference type (str, array) to an UWORD will be changed into address-of.
|
||||
val sourceDt = typecast.expression.inferType(program).typeOrElse(DataType.UNDEFINED)
|
||||
// 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)
|
||||
if (typecast.type in ByteDatatypes && sourceDt in ByteDatatypes
|
||||
|| typecast.type in WordDatatypes && sourceDt in WordDatatypes) {
|
||||
if(typecast.parent !is Expression) {
|
||||
@@ -154,22 +153,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(typecast.type==DataType.UWORD) {
|
||||
if(typecast.expression is IdentifierReference) {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
val identifier = typecast.expression as? IdentifierReference
|
||||
if(identifier!=null) {
|
||||
return if(identifier.isSubroutineParameter(program)) {
|
||||
listOf(IAstModification.ReplaceNode(
|
||||
typecast,
|
||||
AddressOf(typecast.expression as IdentifierReference, typecast.position),
|
||||
typecast.expression,
|
||||
parent
|
||||
))
|
||||
))
|
||||
} else {
|
||||
listOf(IAstModification.ReplaceNode(
|
||||
typecast,
|
||||
AddressOf(identifier, typecast.position),
|
||||
parent
|
||||
))
|
||||
}
|
||||
} else if(typecast.expression is IFunctionCall) {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
typecast,
|
||||
@@ -185,7 +185,15 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@Suppress("DuplicatedCode")
|
||||
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
|
||||
if(binExpr==null || binExpr.operator !in comparisonOperators) {
|
||||
// if x -> if x!=0, if x+5 -> if x+5 != 0
|
||||
@@ -196,52 +204,20 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
|
||||
if((binExpr.operator=="==" || binExpr.operator=="!=") &&
|
||||
(binExpr.left as? NumericLiteralValue)?.number==0 &&
|
||||
(binExpr.right as? NumericLiteralValue)?.number!=0)
|
||||
throw CompilerException("if 0==X should have been swapped to if X==0")
|
||||
throw InternalCompilerException("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.
|
||||
// DISABLED FOR NOW AS IT GENEREATES LARGER CODE IN THE SIMPLE CASES LIKE IF X {...} or IF NOT X {...}
|
||||
// val modifications = mutableListOf<IAstModification>()
|
||||
// if(!binExpr.left.isSimple) {
|
||||
// val sub = binExpr.definingSubroutine()!!
|
||||
// val (variable, isNew, assignment) = addIfOperandVar(sub, "left", binExpr.left)
|
||||
// if(isNew)
|
||||
// modifications.add(IAstModification.InsertFirst(variable, sub))
|
||||
// modifications.add(IAstModification.InsertBefore(ifStatement, assignment, parent as INameScope))
|
||||
// modifications.add(IAstModification.ReplaceNode(binExpr.left, IdentifierReference(listOf(variable.name), binExpr.position), binExpr))
|
||||
// addedIfConditionVars.add(Pair(sub, variable.name))
|
||||
// }
|
||||
// if(!binExpr.right.isSimple) {
|
||||
// val sub = binExpr.definingSubroutine()!!
|
||||
// val (variable, isNew, assignment) = addIfOperandVar(sub, "right", binExpr.right)
|
||||
// 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> {
|
||||
// val dt = operand.inferType(program).typeOrElse(DataType.UNDEFINED)
|
||||
// val varname = "prog8_ifvar_${side}_${dt.name.toLowerCase()}"
|
||||
// val tgt = AssignTarget(IdentifierReference(listOf(varname), operand.position), null, null, operand.position)
|
||||
// val assign = Assignment(tgt, operand, operand.position)
|
||||
// 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)
|
||||
// }
|
||||
// }
|
||||
|
||||
@Suppress("DuplicatedCode")
|
||||
override fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
|
||||
val prefixExpr = untilLoop.condition as? PrefixExpression
|
||||
if(prefixExpr!=null && prefixExpr.operator=="not") {
|
||||
// until not x -> until x==0
|
||||
val booleanExpr = BinaryExpression(prefixExpr.expression, "==", NumericLiteralValue.optimalInteger(0, untilLoop.condition.position), untilLoop.condition.position)
|
||||
return listOf(IAstModification.ReplaceNode(untilLoop.condition, booleanExpr, untilLoop))
|
||||
}
|
||||
|
||||
val binExpr = untilLoop.condition as? BinaryExpression
|
||||
if(binExpr==null || binExpr.operator !in comparisonOperators) {
|
||||
// until x -> until x!=0, until x+5 -> until x+5 != 0
|
||||
@@ -251,7 +227,15 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@Suppress("DuplicatedCode")
|
||||
override fun after(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
|
||||
val prefixExpr = whileLoop.condition as? PrefixExpression
|
||||
if(prefixExpr!=null && prefixExpr.operator=="not") {
|
||||
// while not x -> while x==0
|
||||
val booleanExpr = BinaryExpression(prefixExpr.expression, "==", NumericLiteralValue.optimalInteger(0, whileLoop.condition.position), whileLoop.condition.position)
|
||||
return listOf(IAstModification.ReplaceNode(whileLoop.condition, booleanExpr, whileLoop))
|
||||
}
|
||||
|
||||
val binExpr = whileLoop.condition as? BinaryExpression
|
||||
if(binExpr==null || binExpr.operator !in comparisonOperators) {
|
||||
// while x -> while x!=0, while x+5 -> while x+5 != 0
|
||||
@@ -266,8 +250,8 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
|
||||
// if the datatype of the arguments of cmp() are different, cast the byte one to word.
|
||||
val arg1 = functionCallStatement.args[0]
|
||||
val arg2 = functionCallStatement.args[1]
|
||||
val dt1 = arg1.inferType(program).typeOrElse(DataType.UNDEFINED)
|
||||
val dt2 = arg2.inferType(program).typeOrElse(DataType.UNDEFINED)
|
||||
val dt1 = arg1.inferType(program).getOr(DataType.UNDEFINED)
|
||||
val dt2 = arg2.inferType(program).getOr(DataType.UNDEFINED)
|
||||
if(dt1 in ByteDatatypes) {
|
||||
if(dt2 in ByteDatatypes)
|
||||
return noModifications
|
||||
@@ -339,14 +323,14 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
|
||||
|
||||
private fun getAutoIndexerVarFor(expr: ArrayIndexedExpression): MutableList<IAstModification> {
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
val statement = expr.containingStatement()
|
||||
val statement = expr.containingStatement
|
||||
val dt = expr.indexer.indexExpr.inferType(program)
|
||||
val register = if(dt.istype(DataType.UBYTE) || dt.istype(DataType.BYTE)) "r9L" else "r9"
|
||||
val register = if(dt istype DataType.UBYTE || dt istype DataType.BYTE ) "r9L" else "r9"
|
||||
// replace the indexer with just the variable (simply use a cx16 virtual register r9, that we HOPE is not used for other things in the expression...)
|
||||
// assign the indexing expression to the helper variable, but only if that hasn't been done already
|
||||
val target = AssignTarget(IdentifierReference(listOf("cx16", register), expr.indexer.position), null, null, expr.indexer.position)
|
||||
val 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))
|
||||
return modifications
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package prog8.compiler
|
||||
|
||||
import prog8.ast.AstToSourceCode
|
||||
import com.github.michaelbull.result.*
|
||||
import prog8.ast.AstToSourceTextConverter
|
||||
import prog8.ast.IBuiltinFunctions
|
||||
import prog8.ast.IMemSizer
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.AstException
|
||||
import prog8.ast.base.Position
|
||||
@@ -10,109 +10,100 @@ import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.statements.Directive
|
||||
import prog8.compiler.astprocessing.*
|
||||
import prog8.compiler.functions.*
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8.compiler.target.ICompilationTarget
|
||||
import prog8.compiler.target.asmGeneratorFor
|
||||
import prog8.compiler.target.cpu6502.codegen.AsmGen
|
||||
import prog8.compilerinterface.*
|
||||
import prog8.optimizer.*
|
||||
import prog8.parser.ModuleImporter
|
||||
import prog8.parser.ParsingFailedError
|
||||
import prog8.parser.moduleName
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import prog8.parser.ParseError
|
||||
import java.nio.file.Path
|
||||
import kotlin.system.exitProcess
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.nameWithoutExtension
|
||||
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,
|
||||
val programAst: Program,
|
||||
val program: Program,
|
||||
val programName: String,
|
||||
val compTarget: ICompilationTarget,
|
||||
val importedFiles: List<Path>)
|
||||
|
||||
|
||||
// TODO refactor the gigantic list of parameters
|
||||
fun compileProgram(filepath: Path,
|
||||
optimize: Boolean,
|
||||
optimizeFloatExpressions: Boolean,
|
||||
writeAssembly: Boolean,
|
||||
slowCodegenWarnings: Boolean,
|
||||
quietAssembler: Boolean,
|
||||
compilationTarget: String,
|
||||
libdirs: List<String>,
|
||||
outputDir: Path): CompilationResult {
|
||||
sourceDirs: List<String>,
|
||||
outputDir: Path,
|
||||
errors: IErrorReporter = ErrorReporter()): CompilationResult {
|
||||
var programName = ""
|
||||
lateinit var programAst: Program
|
||||
lateinit var program: Program
|
||||
lateinit var importedFiles: List<Path>
|
||||
val errors = ErrorReporter()
|
||||
|
||||
val compTarget =
|
||||
when(compilationTarget) {
|
||||
C64Target.name -> C64Target
|
||||
Cx16Target.name -> Cx16Target
|
||||
else -> {
|
||||
System.err.println("invalid compilation target")
|
||||
exitProcess(1)
|
||||
}
|
||||
else -> throw IllegalArgumentException("invalid compilation target")
|
||||
}
|
||||
|
||||
try {
|
||||
val totalTime = measureTimeMillis {
|
||||
// import main module and everything it needs
|
||||
val (ast, compilationOptions, imported) = parseImports(filepath, errors, compTarget, libdirs)
|
||||
compilationOptions.slowCodegenWarnings = slowCodegenWarnings
|
||||
compilationOptions.optimize = optimize
|
||||
programAst = ast
|
||||
val (programresult, compilationOptions, imported) = parseImports(filepath, errors, compTarget, sourceDirs)
|
||||
with(compilationOptions) {
|
||||
this.slowCodegenWarnings = slowCodegenWarnings
|
||||
this.optimize = optimize
|
||||
this.optimizeFloatExpressions = optimizeFloatExpressions
|
||||
}
|
||||
program = programresult
|
||||
importedFiles = imported
|
||||
processAst(programAst, errors, compilationOptions)
|
||||
processAst(program, errors, compilationOptions)
|
||||
if (compilationOptions.optimize)
|
||||
optimizeAst(programAst, errors, BuiltinFunctionsFacade(BuiltinFunctions), compTarget, compilationOptions)
|
||||
postprocessAst(programAst, errors, compilationOptions)
|
||||
optimizeAst(
|
||||
program,
|
||||
compilationOptions,
|
||||
errors,
|
||||
BuiltinFunctionsFacade(BuiltinFunctions),
|
||||
compTarget
|
||||
)
|
||||
postprocessAst(program, errors, compilationOptions)
|
||||
|
||||
// printAst(programAst)
|
||||
// println("*********** AST BEFORE ASSEMBLYGEN *************")
|
||||
// printAst(program)
|
||||
|
||||
if(writeAssembly)
|
||||
programName = writeAssembly(programAst, errors, outputDir, compilationOptions)
|
||||
if (writeAssembly) {
|
||||
val result = writeAssembly(program, errors, outputDir, quietAssembler, compilationOptions)
|
||||
when (result) {
|
||||
is WriteAssemblyResult.Ok -> programName = result.filename
|
||||
is WriteAssemblyResult.Fail -> {
|
||||
System.err.println(result.error)
|
||||
return CompilationResult(false, program, programName, compTarget, importedFiles)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
System.out.flush()
|
||||
System.err.flush()
|
||||
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
|
||||
return CompilationResult(true, programAst, programName, compTarget, importedFiles)
|
||||
|
||||
} catch (px: ParsingFailedError) {
|
||||
return CompilationResult(true, program, programName, compTarget, importedFiles)
|
||||
} catch (px: ParseError) {
|
||||
System.err.print("\u001b[91m") // bright red
|
||||
System.err.println(px.message)
|
||||
System.err.println("${px.position.toClickableStr()} parse error: ${px.message}".trim())
|
||||
System.err.print("\u001b[0m") // reset
|
||||
} 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.println("File not found: ${nsf.message}")
|
||||
System.err.print("\u001b[0m") // reset
|
||||
} catch (ax: AstException) {
|
||||
System.err.print("\u001b[91m") // bright red
|
||||
@@ -132,7 +123,7 @@ fun compileProgram(filepath: Path,
|
||||
throw x
|
||||
}
|
||||
|
||||
val failedProgram = Program("failed", mutableListOf(), BuiltinFunctionsFacade(BuiltinFunctions), compTarget)
|
||||
val failedProgram = Program("failed", BuiltinFunctionsFacade(BuiltinFunctions), compTarget, compTarget)
|
||||
return CompilationResult(false, failedProgram, programName, compTarget, emptyList())
|
||||
}
|
||||
|
||||
@@ -142,13 +133,13 @@ private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuilt
|
||||
override val names = functions.keys
|
||||
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]
|
||||
if(func!=null) {
|
||||
val exprfunc = func.constExpressionFunc
|
||||
if(exprfunc!=null) {
|
||||
return try {
|
||||
exprfunc(args, position, program, memsizer)
|
||||
exprfunc(args, position, program)
|
||||
} catch(x: NotConstArgumentException) {
|
||||
// const-evaluating the builtin function call failed.
|
||||
null
|
||||
@@ -166,43 +157,46 @@ private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuilt
|
||||
builtinFunctionReturnType(name, args, program)
|
||||
}
|
||||
|
||||
private fun parseImports(filepath: Path,
|
||||
errors: IErrorReporter,
|
||||
compTarget: ICompilationTarget,
|
||||
libdirs: List<String>): Triple<Program, CompilationOptions, List<Path>> {
|
||||
val compilationTargetName = compTarget.name
|
||||
println("Compiler target: $compilationTargetName. Parsing...")
|
||||
fun parseImports(filepath: Path,
|
||||
errors: IErrorReporter,
|
||||
compTarget: ICompilationTarget,
|
||||
sourceDirs: List<String>): Triple<Program, CompilationOptions, List<Path>> {
|
||||
println("Compiler target: ${compTarget.name}. Parsing...")
|
||||
val bf = BuiltinFunctionsFacade(BuiltinFunctions)
|
||||
val programAst = Program(moduleName(filepath.fileName), mutableListOf(), bf, compTarget)
|
||||
bf.program = programAst
|
||||
val program = Program(filepath.nameWithoutExtension, bf, compTarget, compTarget)
|
||||
bf.program = program
|
||||
|
||||
val importer = ModuleImporter(programAst, compTarget, compilationTargetName, libdirs)
|
||||
importer.importModule(filepath)
|
||||
val importer = ModuleImporter(program, compTarget.name, errors, sourceDirs)
|
||||
val importedModuleResult = importer.importModule(filepath)
|
||||
importedModuleResult.onFailure { throw it }
|
||||
errors.report()
|
||||
|
||||
val importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map { it.source }
|
||||
val compilerOptions = determineCompilationOptions(programAst, compTarget)
|
||||
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
|
||||
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
|
||||
|
||||
val importedFiles = program.modules.map { it.source }
|
||||
.filter { it.isFromFilesystem }
|
||||
.map { Path(it.origin) }
|
||||
val compilerOptions = determineCompilationOptions(program, compTarget)
|
||||
// depending on the machine and compiler options we may have to include some libraries
|
||||
for(lib in compTarget.machine.importLibs(compilerOptions, compilationTargetName))
|
||||
for(lib in compTarget.machine.importLibs(compilerOptions, compTarget.name))
|
||||
importer.importLibraryModule(lib)
|
||||
|
||||
// always import prog8_lib and math
|
||||
importer.importLibraryModule("math")
|
||||
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()
|
||||
return Triple(programAst, compilerOptions, importedFiles)
|
||||
|
||||
return Triple(program, compilerOptions, importedFiles)
|
||||
}
|
||||
|
||||
private fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget): CompilationOptions {
|
||||
val mainModule = program.mainModule
|
||||
val outputType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%output" }
|
||||
as? Directive)?.args?.single()?.name?.uppercase()
|
||||
val launcherType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" }
|
||||
as? Directive)?.args?.single()?.name?.uppercase()
|
||||
val zpoption: String? = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%zeropage" }
|
||||
fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget): CompilationOptions {
|
||||
val toplevelModule = program.toplevelModule
|
||||
val outputDirective = (toplevelModule.statements.singleOrNull { it is Directive && it.directive == "%output" } as? Directive)
|
||||
val launcherDirective = (toplevelModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" } as? Directive)
|
||||
val outputTypeStr = outputDirective?.args?.single()?.name?.uppercase()
|
||||
val launcherTypeStr = launcherDirective?.args?.single()?.name?.uppercase()
|
||||
val zpoption: String? = (toplevelModule.statements.singleOrNull { it is Directive && it.directive == "%zeropage" }
|
||||
as? Directive)?.args?.single()?.name?.uppercase()
|
||||
val allOptions = program.modules.flatMap { it.statements }.filter { it is Directive && it.directive == "%option" }
|
||||
.flatMap { (it as Directive).args }.toSet()
|
||||
@@ -224,139 +218,152 @@ private fun determineCompilationOptions(program: Program, compTarget: ICompilati
|
||||
zpType = ZeropageType.BASICSAFE
|
||||
}
|
||||
|
||||
val zpReserved = mainModule.statements
|
||||
val zpReserved = toplevelModule.statements
|
||||
.asSequence()
|
||||
.filter { it is Directive && it.directive == "%zpreserved" }
|
||||
.map { (it as Directive).args }
|
||||
.map { it[0].int!!..it[1].int!! }
|
||||
.toList()
|
||||
|
||||
if (outputType != null && !OutputType.values().any { it.name == outputType }) {
|
||||
System.err.println("invalid output type $outputType")
|
||||
exitProcess(1)
|
||||
val outputType = if (outputTypeStr == null) OutputType.PRG else {
|
||||
try {
|
||||
OutputType.valueOf(outputTypeStr)
|
||||
} catch (x: IllegalArgumentException) {
|
||||
// set default value; actual check and error handling of invalid option is handled in the AstChecker later
|
||||
OutputType.PRG
|
||||
}
|
||||
}
|
||||
if (launcherType != null && !LauncherType.values().any { it.name == launcherType }) {
|
||||
System.err.println("invalid launcher type $launcherType")
|
||||
exitProcess(1)
|
||||
val launcherType = if (launcherTypeStr == null) LauncherType.BASIC else {
|
||||
try {
|
||||
LauncherType.valueOf(launcherTypeStr)
|
||||
} catch (x: IllegalArgumentException) {
|
||||
// set default value; actual check and error handling of invalid option is handled in the AstChecker later
|
||||
LauncherType.BASIC
|
||||
}
|
||||
}
|
||||
|
||||
return CompilationOptions(
|
||||
if (outputType == null) OutputType.PRG else OutputType.valueOf(outputType),
|
||||
if (launcherType == null) LauncherType.BASIC else LauncherType.valueOf(launcherType),
|
||||
outputType,
|
||||
launcherType,
|
||||
zpType, zpReserved, floatsEnabled, noSysInit,
|
||||
compTarget
|
||||
)
|
||||
}
|
||||
|
||||
private fun processAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
||||
private fun processAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
||||
// perform initial syntax checks and processings
|
||||
println("Processing for target ${compilerOptions.compTarget.name}...")
|
||||
programAst.checkIdentifiers(errors, compilerOptions)
|
||||
program.preprocessAst()
|
||||
program.checkIdentifiers(errors, compilerOptions)
|
||||
errors.report()
|
||||
programAst.constantFold(errors, compilerOptions.compTarget)
|
||||
// TODO: turning char literals into UBYTEs via an encoding should really happen in code gen - but for that we'd need DataType.CHAR
|
||||
// ...but what do we gain from this? We can leave it as it is now: where a char literal is no more than syntactic sugar for an UBYTE value.
|
||||
// By introduction a CHAR dt, we will also lose the opportunity to do constant-folding on any expression containing a char literal.
|
||||
program.charLiteralsToUByteLiterals(compilerOptions.compTarget)
|
||||
program.constantFold(errors, compilerOptions.compTarget)
|
||||
errors.report()
|
||||
programAst.reorderStatements(errors)
|
||||
program.reorderStatements(errors)
|
||||
errors.report()
|
||||
programAst.addTypecasts(errors)
|
||||
program.addTypecasts(errors)
|
||||
errors.report()
|
||||
programAst.variousCleanups(programAst, errors)
|
||||
program.variousCleanups(program, errors)
|
||||
errors.report()
|
||||
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget)
|
||||
program.checkValid(errors, compilerOptions)
|
||||
errors.report()
|
||||
programAst.checkIdentifiers(errors, compilerOptions)
|
||||
program.checkIdentifiers(errors, compilerOptions)
|
||||
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
|
||||
println("Optimizing...")
|
||||
|
||||
val remover = UnusedCodeRemover(programAst, errors, compTarget)
|
||||
remover.visit(programAst)
|
||||
val remover = UnusedCodeRemover(program, errors, compTarget)
|
||||
remover.visit(program)
|
||||
remover.applyModifications()
|
||||
|
||||
while (true) {
|
||||
// keep optimizing expressions and statements until no more steps remain
|
||||
val optsDone1 = programAst.simplifyExpressions()
|
||||
val optsDone2 = programAst.splitBinaryExpressions(compTarget)
|
||||
val optsDone3 = programAst.optimizeStatements(errors, functions, compTarget)
|
||||
programAst.constantFold(errors, compTarget) // because simplified statements and expressions can result in more constants that can be folded away
|
||||
val optsDone1 = program.simplifyExpressions()
|
||||
val optsDone2 = program.splitBinaryExpressions(compilerOptions, compTarget)
|
||||
val optsDone3 = program.optimizeStatements(errors, functions, compTarget)
|
||||
program.constantFold(errors, compTarget) // because simplified statements and expressions can result in more constants that can be folded away
|
||||
errors.report()
|
||||
if (optsDone1 + optsDone2 + optsDone3 == 0)
|
||||
break
|
||||
}
|
||||
|
||||
val inliner = SubroutineInliner(programAst, errors, options)
|
||||
inliner.visit(programAst)
|
||||
errors.report()
|
||||
if(errors.noErrors()) {
|
||||
inliner.applyModifications()
|
||||
inliner.fixCallsToInlinedSubroutines()
|
||||
val remover2 = UnusedCodeRemover(programAst, errors, compTarget)
|
||||
remover2.visit(programAst)
|
||||
remover2.applyModifications()
|
||||
}
|
||||
|
||||
errors.report()
|
||||
}
|
||||
|
||||
private fun postprocessAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
||||
programAst.addTypecasts(errors)
|
||||
private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
||||
program.addTypecasts(errors)
|
||||
errors.report()
|
||||
programAst.variousCleanups(programAst, errors)
|
||||
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget) // check if final tree is still valid
|
||||
program.variousCleanups(program, errors)
|
||||
program.checkValid(errors, compilerOptions) // check if final tree is still valid
|
||||
errors.report()
|
||||
val callGraph = CallGraph(programAst)
|
||||
val callGraph = CallGraph(program)
|
||||
callGraph.checkRecursiveCalls(errors)
|
||||
errors.report()
|
||||
programAst.verifyFunctionArgTypes()
|
||||
programAst.moveMainAndStartToFirst()
|
||||
program.verifyFunctionArgTypes()
|
||||
program.moveMainAndStartToFirst()
|
||||
}
|
||||
|
||||
private fun writeAssembly(programAst: Program,
|
||||
private sealed class WriteAssemblyResult {
|
||||
class Ok(val filename: String): WriteAssemblyResult()
|
||||
class Fail(val error: String): WriteAssemblyResult()
|
||||
}
|
||||
|
||||
private fun writeAssembly(program: Program,
|
||||
errors: IErrorReporter,
|
||||
outputDir: Path,
|
||||
compilerOptions: CompilationOptions): String {
|
||||
quietAssembler: Boolean,
|
||||
compilerOptions: CompilationOptions
|
||||
): WriteAssemblyResult {
|
||||
// asm generation directly from the Ast
|
||||
programAst.processAstBeforeAsmGeneration(errors, compilerOptions.compTarget)
|
||||
program.processAstBeforeAsmGeneration(compilerOptions, errors)
|
||||
errors.report()
|
||||
|
||||
// printAst(programAst)
|
||||
// println("*********** AST RIGHT BEFORE ASM GENERATION *************")
|
||||
// printAst(program)
|
||||
|
||||
compilerOptions.compTarget.machine.initializeZeropage(compilerOptions)
|
||||
val assembly = asmGeneratorFor(compilerOptions.compTarget,
|
||||
programAst,
|
||||
program,
|
||||
errors,
|
||||
compilerOptions.compTarget.machine.zeropage,
|
||||
compilerOptions,
|
||||
outputDir).compileToAssembly()
|
||||
assembly.assemble(compilerOptions)
|
||||
errors.report()
|
||||
return assembly.name
|
||||
}
|
||||
|
||||
fun printAst(programAst: Program) {
|
||||
println()
|
||||
val printer = AstToSourceCode(::print, programAst)
|
||||
printer.visit(programAst)
|
||||
println()
|
||||
}
|
||||
|
||||
fun loadAsmIncludeFile(filename: String, source: Path): String {
|
||||
return if (filename.startsWith("library:")) {
|
||||
val resource = tryGetEmbeddedResource(filename.substring(8))
|
||||
?: throw IllegalArgumentException("library file '$filename' not found")
|
||||
resource.bufferedReader().use { it.readText() }
|
||||
return if(assembly.valid && errors.noErrors()) {
|
||||
val assemblerReturnStatus = assembly.assemble(quietAssembler, compilerOptions)
|
||||
if(assemblerReturnStatus!=0)
|
||||
WriteAssemblyResult.Fail("assembler step failed with return code $assemblerReturnStatus")
|
||||
else {
|
||||
WriteAssemblyResult.Ok(assembly.name)
|
||||
}
|
||||
} else {
|
||||
// first try in the isSameAs folder as where the containing file was imported from
|
||||
val sib = source.resolveSibling(filename)
|
||||
if (sib.toFile().isFile)
|
||||
sib.toFile().readText()
|
||||
else
|
||||
File(filename).readText()
|
||||
WriteAssemblyResult.Fail("compiler failed with errors")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun tryGetEmbeddedResource(name: String): InputStream? {
|
||||
return object{}.javaClass.getResourceAsStream("/prog8lib/$name")
|
||||
fun printAst(program: Program) {
|
||||
println()
|
||||
val printer = AstToSourceTextConverter(::print, program)
|
||||
printer.visit(program)
|
||||
println()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
+12
-16
@@ -1,16 +1,7 @@
|
||||
package prog8.compiler
|
||||
|
||||
import prog8.ast.base.Position
|
||||
import prog8.parser.ParsingFailedError
|
||||
|
||||
|
||||
interface IErrorReporter {
|
||||
fun err(msg: String, position: Position)
|
||||
fun warn(msg: String, position: Position)
|
||||
fun noErrors(): Boolean
|
||||
fun report()
|
||||
}
|
||||
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
|
||||
internal class ErrorReporter: IErrorReporter {
|
||||
private enum class MessageSeverity {
|
||||
@@ -33,24 +24,29 @@ internal class ErrorReporter: IErrorReporter {
|
||||
var numErrors = 0
|
||||
var numWarnings = 0
|
||||
messages.forEach {
|
||||
val printer = when(it.severity) {
|
||||
MessageSeverity.WARNING -> System.out
|
||||
MessageSeverity.ERROR -> System.err
|
||||
}
|
||||
when(it.severity) {
|
||||
MessageSeverity.ERROR -> System.err.print("\u001b[91m") // bright red
|
||||
MessageSeverity.WARNING -> System.err.print("\u001b[93m") // bright yellow
|
||||
MessageSeverity.ERROR -> printer.print("\u001b[91m") // bright red
|
||||
MessageSeverity.WARNING -> printer.print("\u001b[93m") // bright yellow
|
||||
}
|
||||
val msg = "${it.position.toClickableStr()} ${it.severity} ${it.message}".trim()
|
||||
if(msg !in alreadyReportedMessages) {
|
||||
System.err.println(msg)
|
||||
printer.println(msg)
|
||||
alreadyReportedMessages.add(msg)
|
||||
when(it.severity) {
|
||||
MessageSeverity.WARNING -> numWarnings++
|
||||
MessageSeverity.ERROR -> numErrors++
|
||||
}
|
||||
}
|
||||
System.err.print("\u001b[0m") // reset color
|
||||
printer.print("\u001b[0m") // reset color
|
||||
}
|
||||
System.out.flush()
|
||||
System.err.flush()
|
||||
messages.clear()
|
||||
if(numErrors>0)
|
||||
throw ParsingFailedError("There are $numErrors errors and $numWarnings warnings.")
|
||||
finalizeNumErrors(numErrors, numWarnings)
|
||||
}
|
||||
|
||||
override fun noErrors() = messages.none { it.severity==MessageSeverity.ERROR }
|
||||
@@ -0,0 +1,159 @@
|
||||
package prog8.compiler
|
||||
|
||||
import com.github.michaelbull.result.*
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.base.SyntaxError
|
||||
import prog8.ast.statements.Directive
|
||||
import prog8.ast.statements.DirectiveArg
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
import prog8.parser.Prog8Parser
|
||||
import prog8.parser.SourceCode
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.*
|
||||
|
||||
|
||||
class ModuleImporter(private val program: Program,
|
||||
private val compilationTargetName: String,
|
||||
val errors: IErrorReporter,
|
||||
sourceDirs: List<String>) {
|
||||
|
||||
private val sourcePaths: List<Path> = sourceDirs.map { Path(it) }
|
||||
|
||||
fun importModule(filePath: Path): Result<Module, NoSuchFileException> {
|
||||
val currentDir = Path("").absolute()
|
||||
val searchIn = listOf(currentDir) + sourcePaths
|
||||
val candidates = searchIn
|
||||
.map { it.absolute().div(filePath).normalize().absolute() }
|
||||
.filter { it.exists() }
|
||||
.map { currentDir.relativize(it) }
|
||||
.map { if (it.isAbsolute) it else Path(".", "$it") }
|
||||
|
||||
val srcPath = when (candidates.size) {
|
||||
0 -> return Err(NoSuchFileException(
|
||||
file = filePath.normalize().toFile(),
|
||||
reason = "Searched in $searchIn"))
|
||||
1 -> candidates.first()
|
||||
else -> candidates.first() // when more candiates, pick the one from the first location
|
||||
}
|
||||
|
||||
val logMsg = "importing '${filePath.nameWithoutExtension}' (from file $srcPath)"
|
||||
println(logMsg)
|
||||
|
||||
val module = importModule(SourceCode.File(srcPath))
|
||||
return if(module==null)
|
||||
Err(NoSuchFileException(srcPath.toFile()))
|
||||
else
|
||||
Ok(module)
|
||||
}
|
||||
|
||||
fun importLibraryModule(name: String): Module? {
|
||||
val import = Directive("%import", listOf(
|
||||
DirectiveArg("", name, 42, position = Position("<<<implicit-import>>>", 0, 0, 0))
|
||||
), Position("<<<implicit-import>>>", 0, 0, 0))
|
||||
return executeImportDirective(import, null)
|
||||
}
|
||||
|
||||
private fun importModule(src: SourceCode) : Module? {
|
||||
val moduleAst = Prog8Parser.parseModule(src)
|
||||
program.addModule(moduleAst)
|
||||
|
||||
// accept additional imports
|
||||
try {
|
||||
val lines = moduleAst.statements.toMutableList()
|
||||
lines.asSequence()
|
||||
.mapIndexed { i, it -> i to it }
|
||||
.filter { (it.second as? Directive)?.directive == "%import" }
|
||||
.forEach { executeImportDirective(it.second as Directive, moduleAst) }
|
||||
moduleAst.statements = lines
|
||||
return moduleAst
|
||||
} catch (x: Exception) {
|
||||
// in case of error, make sure the module we're importing is no longer in the Ast
|
||||
program.removeModule(moduleAst)
|
||||
throw x
|
||||
}
|
||||
}
|
||||
|
||||
private fun executeImportDirective(import: Directive, importingModule: Module?): Module? {
|
||||
if(import.directive!="%import" || import.args.size!=1)
|
||||
throw SyntaxError("invalid import directive", import.position)
|
||||
if(!import.args[0].str.isNullOrEmpty() || import.args[0].name==null)
|
||||
throw SyntaxError("%import requires unquoted module name", import.position)
|
||||
val moduleName = import.args[0].name!!
|
||||
if("$moduleName.p8" == import.position.file)
|
||||
throw SyntaxError("cannot import self", import.position)
|
||||
|
||||
val existing = program.modules.singleOrNull { it.name == moduleName }
|
||||
if (existing!=null)
|
||||
return existing
|
||||
|
||||
// try internal library first
|
||||
val moduleResourceSrc = getModuleFromResource("$moduleName.p8", compilationTargetName)
|
||||
val importedModule =
|
||||
moduleResourceSrc.fold(
|
||||
success = {
|
||||
println("importing '$moduleName' (from internal ${it.origin})")
|
||||
importModule(it)
|
||||
},
|
||||
failure = {
|
||||
// try filesystem next
|
||||
val moduleSrc = getModuleFromFile(moduleName, importingModule)
|
||||
moduleSrc.fold(
|
||||
success = {
|
||||
println("importing '$moduleName' (from file ${it.origin})")
|
||||
importModule(it)
|
||||
},
|
||||
failure = {
|
||||
errors.err("no module found with name $moduleName. Searched in: $sourcePaths (and internal libraries)", import.position)
|
||||
return null
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
if(importedModule!=null)
|
||||
removeDirectivesFromImportedModule(importedModule)
|
||||
return importedModule
|
||||
}
|
||||
|
||||
private fun removeDirectivesFromImportedModule(importedModule: Module) {
|
||||
// Most global directives don't apply for imported modules, so remove them
|
||||
val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address")
|
||||
var directives = importedModule.statements.filterIsInstance<Directive>()
|
||||
importedModule.statements.removeAll(directives)
|
||||
directives = directives.filter{ it.directive !in moduleLevelDirectives }
|
||||
importedModule.statements.addAll(0, directives)
|
||||
}
|
||||
|
||||
private fun getModuleFromResource(name: String, compilationTargetName: String): Result<SourceCode, NoSuchFileException> {
|
||||
val result =
|
||||
runCatching { SourceCode.Resource("/prog8lib/$compilationTargetName/$name") }
|
||||
.orElse { runCatching { SourceCode.Resource("/prog8lib/$name") } }
|
||||
|
||||
return result.mapError { NoSuchFileException(File(name)) }
|
||||
}
|
||||
|
||||
private fun getModuleFromFile(name: String, importingModule: Module?): Result<SourceCode, NoSuchFileException> {
|
||||
val fileName = "$name.p8"
|
||||
val locations =
|
||||
if (importingModule == null) { // <=> imported from library module
|
||||
sourcePaths
|
||||
} else {
|
||||
val dropCurDir = if(sourcePaths.isNotEmpty() && sourcePaths[0].name == ".") 1 else 0
|
||||
sourcePaths.drop(dropCurDir) +
|
||||
listOf(Path(importingModule.position.file).parent ?: Path("")) +
|
||||
listOf(Path(".", "prog8lib"))
|
||||
}
|
||||
|
||||
locations.forEach {
|
||||
try {
|
||||
return Ok(SourceCode.File(it.resolve(fileName)))
|
||||
} catch (e: NoSuchFileException) {
|
||||
}
|
||||
}
|
||||
|
||||
return Err(NoSuchFileException(File("name")))
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,22 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.IStatementContainer
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.compiler.IErrorReporter
|
||||
import prog8.compiler.ZeropageType
|
||||
import prog8.compiler.functions.BuiltinFunctions
|
||||
import prog8.compiler.functions.builtinFunctionReturnType
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8.compiler.target.ICompilationTarget
|
||||
import prog8.compilerinterface.*
|
||||
import java.io.CharConversionException
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import kotlin.io.path.Path
|
||||
|
||||
internal class AstChecker(private val program: Program,
|
||||
private val compilerOptions: CompilationOptions,
|
||||
private val errors: IErrorReporter,
|
||||
private val compTarget: ICompilationTarget
|
||||
private val compilerOptions: CompilationOptions
|
||||
) : IAstVisitor {
|
||||
|
||||
override fun visit(program: Program) {
|
||||
@@ -32,7 +26,7 @@ internal class AstChecker(private val program: Program,
|
||||
if(mainBlocks.size>1)
|
||||
errors.err("more than one 'main' block", mainBlocks[0].position)
|
||||
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) {
|
||||
val startSub = mainBlock.subScope("start") as? Subroutine
|
||||
@@ -45,8 +39,8 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
if(compilerOptions.floats) {
|
||||
if (compilerOptions.zeropage !in setOf(ZeropageType.FLOATSAFE, ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
|
||||
errors.err("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'", program.mainModule.position)
|
||||
if (compilerOptions.zeropage !in arrayOf(ZeropageType.FLOATSAFE, ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
|
||||
errors.err("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'", program.toplevelModule.position)
|
||||
}
|
||||
|
||||
super.visit(program)
|
||||
@@ -64,7 +58,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(returnStmt: Return) {
|
||||
val expectedReturnValues = returnStmt.definingSubroutine()?.returntypes ?: emptyList()
|
||||
val expectedReturnValues = returnStmt.definingSubroutine?.returntypes ?: emptyList()
|
||||
if(expectedReturnValues.size>1) {
|
||||
throw FatalAstException("cannot use a return with one value in a subroutine that has multiple return values: $returnStmt")
|
||||
}
|
||||
@@ -80,7 +74,7 @@ internal class AstChecker(private val program: Program,
|
||||
if(!valueDt.isKnown) {
|
||||
errors.err("return value type mismatch or unknown symbol", returnStmt.value!!.position)
|
||||
} else {
|
||||
if (expectedReturnValues[0] != valueDt.typeOrElse(DataType.UNDEFINED))
|
||||
if (expectedReturnValues[0] != valueDt.getOr(DataType.UNDEFINED))
|
||||
errors.err("type $valueDt of return value doesn't match subroutine's return type ${expectedReturnValues[0]}", returnStmt.value!!.position)
|
||||
}
|
||||
}
|
||||
@@ -88,7 +82,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(ifStatement: IfStatement) {
|
||||
if(!ifStatement.condition.inferType(program).isInteger())
|
||||
if(!ifStatement.condition.inferType(program).isInteger)
|
||||
errors.err("condition value should be an integer type", ifStatement.condition.position)
|
||||
super.visit(ifStatement)
|
||||
}
|
||||
@@ -106,7 +100,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
val iterableDt = forLoop.iterable.inferType(program).typeOrElse(DataType.BYTE)
|
||||
val iterableDt = forLoop.iterable.inferType(program).getOr(DataType.BYTE)
|
||||
if(iterableDt !in IterableDatatypes && forLoop.iterable !is RangeExpr) {
|
||||
errors.err("can only loop over an iterable type", forLoop.position)
|
||||
} else {
|
||||
@@ -138,6 +132,9 @@ internal class AstChecker(private val program: Program,
|
||||
errors.err("word loop variable can only loop over bytes or words", forLoop.position)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
// Looping over float variables is very inefficient because the loopvar is going to
|
||||
// get copied over with new values all the time. We don't support this for now.
|
||||
// Loop with an integer index variable if you really need to... or write different code.
|
||||
errors.err("for loop only supports integers", forLoop.position)
|
||||
}
|
||||
else -> errors.err("loop variable must be numeric type", forLoop.position)
|
||||
@@ -150,11 +147,11 @@ internal class AstChecker(private val program: Program,
|
||||
val to = range.to as? NumericLiteralValue
|
||||
if(from != null)
|
||||
checkValueTypeAndRange(loopvar.datatype, from)
|
||||
else if(!range.from.inferType(program).istype(loopvar.datatype))
|
||||
else if(range.from.inferType(program) isnot loopvar.datatype)
|
||||
errors.err("range start value is incompatible with loop variable type", range.position)
|
||||
if(to != null)
|
||||
checkValueTypeAndRange(loopvar.datatype, to)
|
||||
else if(!range.to.inferType(program).istype(loopvar.datatype))
|
||||
else if(range.to.inferType(program) isnot loopvar.datatype)
|
||||
errors.err("range end value is incompatible with loop variable type", range.position)
|
||||
}
|
||||
}
|
||||
@@ -194,8 +191,12 @@ internal class AstChecker(private val program: Program,
|
||||
is Label,
|
||||
is VarDecl,
|
||||
is InlineAssembly,
|
||||
is INameScope,
|
||||
is IStatementContainer,
|
||||
is NopStatement -> true
|
||||
is Assignment -> {
|
||||
val target = statement.target.identifier!!.targetStatement(program)
|
||||
target === statement.previousSibling() // an initializer assignment is okay
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
if (!ok) {
|
||||
@@ -215,7 +216,7 @@ internal class AstChecker(private val program: Program,
|
||||
super.visit(label)
|
||||
}
|
||||
|
||||
private fun hasReturnOrJump(scope: INameScope): Boolean {
|
||||
private fun hasReturnOrJump(scope: IStatementContainer): Boolean {
|
||||
class Searcher: IAstVisitor
|
||||
{
|
||||
var count=0
|
||||
@@ -243,8 +244,8 @@ internal class AstChecker(private val program: Program,
|
||||
if(subroutine.name in BuiltinFunctions)
|
||||
err("cannot redefine a built-in function")
|
||||
|
||||
if(subroutine.parameters.size>16)
|
||||
err("subroutines are limited to 16 parameters")
|
||||
if(subroutine.parameters.size>6 && !subroutine.isAsmSubroutine)
|
||||
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()
|
||||
if(uniqueNames.size!=subroutine.parameters.size)
|
||||
@@ -269,10 +270,11 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
// scope check
|
||||
if(subroutine.parent !is Block && subroutine.parent !is Subroutine) {
|
||||
if(subroutine.inline && !subroutine.isAsmSubroutine)
|
||||
err("subroutine inlining is currently only supported on asmsub routines")
|
||||
|
||||
if(subroutine.parent !is Block && subroutine.parent !is Subroutine)
|
||||
err("subroutines can only be defined in the scope of a block or within another subroutine")
|
||||
}
|
||||
|
||||
if(subroutine.isAsmSubroutine) {
|
||||
if(subroutine.asmParameterRegisters.size != subroutine.parameters.size)
|
||||
@@ -280,14 +282,14 @@ internal class AstChecker(private val program: Program,
|
||||
if(subroutine.asmReturnvaluesRegisters.size != subroutine.returntypes.size)
|
||||
err("number of return registers is not the isSameAs as number of return values")
|
||||
for(param in subroutine.parameters.zip(subroutine.asmParameterRegisters)) {
|
||||
if(param.second.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y)) {
|
||||
if(param.second.registerOrPair in arrayOf(RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y)) {
|
||||
if (param.first.type != DataType.UBYTE && param.first.type != DataType.BYTE)
|
||||
err("parameter '${param.first.name}' should be (u)byte")
|
||||
}
|
||||
else if(param.second.registerOrPair in setOf(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
|
||||
&& 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) {
|
||||
if (param.first.type != DataType.UBYTE)
|
||||
@@ -295,11 +297,11 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
subroutine.returntypes.zip(subroutine.asmReturnvaluesRegisters).forEachIndexed { index, pair ->
|
||||
if(pair.second.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y)) {
|
||||
if(pair.second.registerOrPair in arrayOf(RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y)) {
|
||||
if (pair.first != DataType.UBYTE && pair.first != DataType.BYTE)
|
||||
err("return value #${index + 1} should be (u)byte")
|
||||
}
|
||||
else if(pair.second.registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
|
||||
else if(pair.second.registerOrPair in arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
|
||||
if (pair.first != DataType.UWORD && pair.first != DataType.WORD
|
||||
&& pair.first != DataType.STR && pair.first !in ArrayDatatypes && pair.first != DataType.FLOAT)
|
||||
err("return value #${index + 1} should be (u)word/address")
|
||||
@@ -379,22 +381,24 @@ internal class AstChecker(private val program: Program,
|
||||
err("can only use Carry as status flag parameter")
|
||||
|
||||
} 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).
|
||||
if(subroutine.parameters.any{it.type in PassByReferenceDatatypes }) {
|
||||
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.")
|
||||
for(p in subroutine.parameters) {
|
||||
if(p.type in PassByReferenceDatatypes && p.type != DataType.STR) {
|
||||
err("Non-string pass-by-reference types cannot occur as a parameter type directly. Instead, use an uword to receive their address, or access the variable from the outer scope directly.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(untilLoop: UntilLoop) {
|
||||
if(!untilLoop.condition.inferType(program).isInteger())
|
||||
if(!untilLoop.condition.inferType(program).isInteger)
|
||||
errors.err("condition value should be an integer type", untilLoop.condition.position)
|
||||
super.visit(untilLoop)
|
||||
}
|
||||
|
||||
override fun visit(whileLoop: WhileLoop) {
|
||||
if(!whileLoop.condition.inferType(program).isInteger())
|
||||
if(!whileLoop.condition.inferType(program).isInteger)
|
||||
errors.err("condition value should be an integer type", whileLoop.condition.position)
|
||||
super.visit(whileLoop)
|
||||
}
|
||||
@@ -414,7 +418,7 @@ internal class AstChecker(private val program: Program,
|
||||
if(!idt.isKnown) {
|
||||
errors.err("return type mismatch", assignment.value.position)
|
||||
}
|
||||
if(stmt.returntypes.isEmpty() || (stmt.returntypes.size == 1 && stmt.returntypes.single() isNotAssignableTo idt.typeOrElse(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)
|
||||
}
|
||||
}
|
||||
@@ -423,14 +427,14 @@ internal class AstChecker(private val program: Program,
|
||||
val targetDt = assignment.target.inferType(program)
|
||||
val valueDt = assignment.value.inferType(program)
|
||||
if(valueDt.isKnown && !(valueDt isAssignableTo targetDt)) {
|
||||
if(targetDt.isIterable())
|
||||
if(targetDt.isIterable)
|
||||
errors.err("cannot assign value to string or array", assignment.value.position)
|
||||
else if(!(valueDt.istype(DataType.STR) && targetDt.istype(DataType.UWORD)))
|
||||
else if(!(valueDt istype DataType.STR && targetDt istype DataType.UWORD))
|
||||
errors.err("type of value doesn't match target", assignment.value.position)
|
||||
}
|
||||
|
||||
if(assignment.value is TypecastExpression) {
|
||||
if(assignment.isAugmentable && targetDt.istype(DataType.FLOAT))
|
||||
if(assignment.isAugmentable && targetDt istype DataType.FLOAT)
|
||||
errors.err("typecasting a float value in-place makes no sense", assignment.value.position)
|
||||
}
|
||||
|
||||
@@ -450,7 +454,7 @@ internal class AstChecker(private val program: Program,
|
||||
val targetIdentifier = assignTarget.identifier
|
||||
if (targetIdentifier != null) {
|
||||
val targetName = targetIdentifier.nameInSource
|
||||
when (val targetSymbol = program.namespace.lookup(targetName, assignment)) {
|
||||
when (val targetSymbol = assignment.definingScope.lookup(targetName)) {
|
||||
null -> {
|
||||
errors.err("undefined symbol: ${targetIdentifier.nameInSource.joinToString(".")}", targetIdentifier.position)
|
||||
return
|
||||
@@ -479,15 +483,15 @@ internal class AstChecker(private val program: Program,
|
||||
if (targetDatatype.isKnown) {
|
||||
val constVal = assignment.value.constValue(program)
|
||||
if (constVal != null) {
|
||||
checkValueTypeAndRange(targetDatatype.typeOrElse(DataType.BYTE), constVal)
|
||||
checkValueTypeAndRange(targetDatatype.getOr(DataType.BYTE), constVal)
|
||||
} else {
|
||||
val sourceDatatype = assignment.value.inferType(program)
|
||||
if (sourceDatatype.isUnknown) {
|
||||
if (assignment.value !is FunctionCall)
|
||||
errors.err("assignment value is invalid or has no proper datatype", assignment.value.position)
|
||||
} else {
|
||||
checkAssignmentCompatible(targetDatatype.typeOrElse(DataType.BYTE), assignTarget,
|
||||
sourceDatatype.typeOrElse(DataType.BYTE), assignment.value, assignment.position)
|
||||
checkAssignmentCompatible(targetDatatype.getOr(DataType.BYTE), assignTarget,
|
||||
sourceDatatype.getOr(DataType.BYTE), assignment.value, assignment.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -502,7 +506,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
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)
|
||||
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.indexExpr?.referencesIdentifier(decl.name) == true)
|
||||
@@ -515,7 +519,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
// FLOATS enabled?
|
||||
if(!compilerOptions.floats && decl.datatype in setOf(DataType.FLOAT, DataType.ARRAY_F) && decl.type!= VarDeclType.MEMORY)
|
||||
if(!compilerOptions.floats && decl.datatype.oneOf(DataType.FLOAT, DataType.ARRAY_F) && decl.type!= VarDeclType.MEMORY)
|
||||
err("floating point used, but that is not enabled via options")
|
||||
|
||||
if(decl.datatype == DataType.FLOAT && (decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE || decl.zeropage==ZeropageWish.PREFER_ZEROPAGE))
|
||||
@@ -583,18 +587,18 @@ internal class AstChecker(private val program: Program,
|
||||
val numvalue = decl.value as? NumericLiteralValue
|
||||
if(numvalue!=null) {
|
||||
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 {
|
||||
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?")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val declValue = decl.value
|
||||
if(declValue!=null && decl.type==VarDeclType.VAR) {
|
||||
if (!declValue.inferType(program).istype(decl.datatype)) {
|
||||
err("initialisation value has incompatible type (${declValue.inferType(program)}) for the variable (${decl.datatype})", declValue.position)
|
||||
if (declValue.inferType(program) isnot decl.datatype) {
|
||||
err("initialisation value has incompatible type (${declValue.inferType(program)}) for the variable (${decl.datatype})")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -623,10 +627,13 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
// string assignment is not supported in a vard
|
||||
if(decl.datatype==DataType.STR) {
|
||||
if(decl.value==null)
|
||||
err("string var must be initialized with a string literal")
|
||||
if(decl.value==null) {
|
||||
// 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)
|
||||
err("string var can only be initialized with a string literal")
|
||||
}
|
||||
@@ -711,37 +718,37 @@ internal class AstChecker(private val program: Program,
|
||||
err("this directive may only occur in a block or at module level")
|
||||
if(directive.args.isEmpty())
|
||||
err("missing option directive argument(s)")
|
||||
else if(directive.args.map{it.name in setOf("enable_floats", "force_output", "no_sysinit", "align_word", "align_page")}.any { !it })
|
||||
else if(directive.args.map{it.name in arrayOf("enable_floats", "force_output", "no_sysinit", "align_word", "align_page")}.any { !it })
|
||||
err("invalid option directive argument(s)")
|
||||
}
|
||||
"%target" -> {
|
||||
if(directive.parent !is Block && directive.parent !is Module)
|
||||
err("this directive may only occur in a block or at module level")
|
||||
if(directive.args.size != 1)
|
||||
err("directive requires one argument")
|
||||
if(directive.args.single().name !in setOf(C64Target.name, Cx16Target.name))
|
||||
err("invalid compilation target")
|
||||
}
|
||||
else -> throw SyntaxError("invalid directive ${directive.directive}", directive.position)
|
||||
}
|
||||
super.visit(directive)
|
||||
}
|
||||
|
||||
private fun checkFileExists(directive: Directive, filename: String) {
|
||||
var definingModule = directive.parent
|
||||
while (definingModule !is Module)
|
||||
definingModule = definingModule.parent
|
||||
if (!(filename.startsWith("library:") || definingModule.source.resolveSibling(filename).toFile().isFile || File(filename).isFile))
|
||||
if (File(filename).isFile)
|
||||
return
|
||||
|
||||
val definingModule = directive.definingModule
|
||||
if (definingModule.isLibrary || !definingModule.source.isFromFilesystem)
|
||||
return
|
||||
|
||||
val s = definingModule.source.origin
|
||||
val sourceFileCandidate = Path(s).resolveSibling(filename).toFile()
|
||||
if (sourceFileCandidate.isFile)
|
||||
return
|
||||
else
|
||||
errors.err("included file not found: $filename", directive.position)
|
||||
}
|
||||
|
||||
override fun visit(array: ArrayLiteralValue) {
|
||||
if(array.type.isKnown) {
|
||||
if (!compilerOptions.floats && array.type.typeOrElse(DataType.UNDEFINED) in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
|
||||
if (!compilerOptions.floats && array.type.oneOf(DataType.FLOAT, DataType.ARRAY_F)) {
|
||||
errors.err("floating point used, but that is not enabled via options", array.position)
|
||||
}
|
||||
val arrayspec = ArrayIndex.forArray(array)
|
||||
checkValueTypeAndRangeArray(array.type.typeOrElse(DataType.UNDEFINED), arrayspec, array)
|
||||
checkValueTypeAndRangeArray(array.type.getOr(DataType.UNDEFINED), arrayspec, array)
|
||||
}
|
||||
|
||||
fun isPassByReferenceElement(e: Expression): Boolean {
|
||||
@@ -763,11 +770,21 @@ internal class AstChecker(private val program: Program,
|
||||
super.visit(array)
|
||||
}
|
||||
|
||||
override fun visit(char: CharLiteral) {
|
||||
try { // just *try* if it can be encoded, don't actually do it
|
||||
compilerOptions.compTarget.encodeString(char.value.toString(), char.altEncoding)
|
||||
} catch (cx: CharConversionException) {
|
||||
errors.err(cx.message ?: "can't encode character", char.position)
|
||||
}
|
||||
|
||||
super.visit(char)
|
||||
}
|
||||
|
||||
override fun visit(string: StringLiteralValue) {
|
||||
checkValueTypeAndRangeString(DataType.STR, string)
|
||||
|
||||
try {
|
||||
compTarget.encodeString(string.value, string.altEncoding)
|
||||
try { // just *try* if it can be encoded, don't actually do it
|
||||
compilerOptions.compTarget.encodeString(string.value, string.altEncoding)
|
||||
} catch (cx: CharConversionException) {
|
||||
errors.err(cx.message ?: "can't encode string", string.position)
|
||||
}
|
||||
@@ -776,11 +793,10 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(expr: PrefixExpression) {
|
||||
val idt = expr.inferType(program)
|
||||
if(!idt.isKnown)
|
||||
val dt = expr.expression.inferType(program).getOr(DataType.UNDEFINED)
|
||||
if(dt==DataType.UNDEFINED)
|
||||
return // any error should be reported elsewhere
|
||||
|
||||
val dt = idt.typeOrElse(DataType.UNDEFINED)
|
||||
if(expr.operator=="-") {
|
||||
if (dt != DataType.BYTE && dt != DataType.WORD && dt != DataType.FLOAT) {
|
||||
errors.err("can only take negative of a signed number type", expr.position)
|
||||
@@ -805,8 +821,8 @@ internal class AstChecker(private val program: Program,
|
||||
if(!leftIDt.isKnown || !rightIDt.isKnown)
|
||||
return // hopefully this error will be detected elsewhere
|
||||
|
||||
val leftDt = leftIDt.typeOrElse(DataType.UNDEFINED)
|
||||
val rightDt = rightIDt.typeOrElse(DataType.UNDEFINED)
|
||||
val leftDt = leftIDt.getOr(DataType.UNDEFINED)
|
||||
val rightDt = rightIDt.getOr(DataType.UNDEFINED)
|
||||
|
||||
when(expr.operator){
|
||||
"/", "%" -> {
|
||||
@@ -902,7 +918,7 @@ internal class AstChecker(private val program: Program,
|
||||
// warn about sgn(unsigned) this is likely a mistake
|
||||
if(functionCall.target.nameInSource.last()=="sgn") {
|
||||
val sgnArgType = functionCall.args.first().inferType(program)
|
||||
if(sgnArgType.istype(DataType.UBYTE) || sgnArgType.istype(DataType.UWORD))
|
||||
if(sgnArgType istype DataType.UBYTE || sgnArgType istype DataType.UWORD)
|
||||
errors.warn("sgn() of unsigned type is always 0 or 1, this is perhaps not what was intended", functionCall.args.first().position)
|
||||
}
|
||||
|
||||
@@ -971,12 +987,12 @@ internal class AstChecker(private val program: Program,
|
||||
if(functionCallStatement.target.nameInSource.last() == "sort") {
|
||||
// sort is not supported on float arrays
|
||||
val idref = functionCallStatement.args.singleOrNull() as? IdentifierReference
|
||||
if(idref!=null && idref.inferType(program).istype(DataType.ARRAY_F)) {
|
||||
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 setOf("rol", "ror", "rol2", "ror2", "swap", "sort", "reverse")) {
|
||||
if(functionCallStatement.target.nameInSource.last() in arrayOf("rol", "ror", "rol2", "ror2", "swap", "sort", "reverse")) {
|
||||
// in-place modification, can't be done on literals
|
||||
if(functionCallStatement.args.any { it !is IdentifierReference && it !is ArrayIndexedExpression && it !is DirectMemoryRead }) {
|
||||
errors.err("invalid argument to a in-place modifying function", functionCallStatement.args.first().position)
|
||||
@@ -998,7 +1014,7 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
if(target is BuiltinFunctionStatementPlaceholder) {
|
||||
if(target.name=="swap") {
|
||||
// swap() is a bit weird because this one is translated into a operations directly, instead of being a function call
|
||||
// swap() is a bit weird because this one is translated into an operations directly, instead of being a function call
|
||||
val dt1 = args[0].inferType(program)
|
||||
val dt2 = args[1].inferType(program)
|
||||
if (dt1 != dt2)
|
||||
@@ -1007,14 +1023,14 @@ internal class AstChecker(private val program: Program,
|
||||
errors.err("swap requires 2 variables, not constant value(s)", position)
|
||||
else if(args[0] isSameAs args[1])
|
||||
errors.err("swap should have 2 different args", position)
|
||||
else if(!dt1.isNumeric())
|
||||
else if(!dt1.isNumeric)
|
||||
errors.err("swap requires args of numerical type", position)
|
||||
}
|
||||
else if(target.name=="all" || target.name=="any") {
|
||||
if((args[0] as? AddressOf)?.identifier?.targetVarDecl(program)?.datatype == DataType.STR) {
|
||||
errors.err("any/all on a string is useless (is always true unless the string is empty)", position)
|
||||
}
|
||||
if(args[0].inferType(program).typeOrElse(DataType.STR) == DataType.STR) {
|
||||
if(args[0].inferType(program).getOr(DataType.STR) == DataType.STR) {
|
||||
errors.err("any/all on a string is useless (is always true unless the string is empty)", position)
|
||||
}
|
||||
}
|
||||
@@ -1054,7 +1070,7 @@ internal class AstChecker(private val program: Program,
|
||||
override fun visit(postIncrDecr: PostIncrDecr) {
|
||||
if(postIncrDecr.target.identifier != null) {
|
||||
val targetName = postIncrDecr.target.identifier!!.nameInSource
|
||||
val target = program.namespace.lookup(targetName, postIncrDecr)
|
||||
val target = postIncrDecr.definingScope.lookup(targetName)
|
||||
if(target==null) {
|
||||
val symbol = postIncrDecr.target.identifier!!
|
||||
errors.err("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position)
|
||||
@@ -1105,14 +1121,14 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
// check index value 0..255
|
||||
val dtxNum = arrayIndexedExpression.indexer.indexExpr.inferType(program)
|
||||
if(!dtxNum.istype(DataType.UBYTE) && !dtxNum.istype(DataType.BYTE))
|
||||
if(dtxNum isnot DataType.UBYTE && dtxNum isnot DataType.BYTE)
|
||||
errors.err("array indexing is limited to byte size 0..255", arrayIndexedExpression.position)
|
||||
|
||||
super.visit(arrayIndexedExpression)
|
||||
}
|
||||
|
||||
override fun visit(whenStatement: WhenStatement) {
|
||||
if(!whenStatement.condition.inferType(program).isInteger())
|
||||
if(!whenStatement.condition.inferType(program).isInteger)
|
||||
errors.err("when condition must be an integer value", whenStatement.position)
|
||||
val tally = mutableSetOf<Int>()
|
||||
for((choices, choiceNode) in whenStatement.choiceValues(program)) {
|
||||
@@ -1143,7 +1159,7 @@ internal class AstChecker(private val program: Program,
|
||||
when {
|
||||
constvalue == null -> errors.err("choice value must be a constant", whenChoice.position)
|
||||
constvalue.type !in IntegerDatatypes -> errors.err("choice value must be a byte or word", whenChoice.position)
|
||||
constvalue.type != conditionType.typeOrElse(DataType.UNDEFINED) -> errors.err("choice value datatype differs from condition value", whenChoice.position)
|
||||
conditionType isnot constvalue.type -> errors.err("choice value datatype differs from condition value", whenChoice.position)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -1154,13 +1170,11 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: Statement): Statement? {
|
||||
val targetStatement = target.targetStatement(program)
|
||||
if(targetStatement is Label || targetStatement is Subroutine || targetStatement is BuiltinFunctionStatementPlaceholder)
|
||||
return targetStatement
|
||||
else if(targetStatement==null)
|
||||
errors.err("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", statement.position)
|
||||
else
|
||||
errors.err("cannot call that: ${target.nameInSource.joinToString(".")}", statement.position)
|
||||
when (val targetStatement = target.targetStatement(program)) {
|
||||
is Label, is Subroutine, is BuiltinFunctionStatementPlaceholder -> return targetStatement
|
||||
null -> errors.err("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", statement.position)
|
||||
else -> errors.err("cannot call that: ${target.nameInSource.joinToString(".")}", statement.position)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -1189,7 +1203,7 @@ internal class AstChecker(private val program: Program,
|
||||
DataType.STR -> return err("string value expected")
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||
// value may be either a single byte, or a byte arraysize (of all constant values), or a range
|
||||
if(value.type.istype(targetDt)) {
|
||||
if(value.type istype targetDt) {
|
||||
if(!checkArrayValues(value, targetDt))
|
||||
return false
|
||||
val arraySpecSize = arrayspec.constIndex()
|
||||
@@ -1208,7 +1222,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
// value may be either a single word, or a word arraysize, or a range
|
||||
if(value.type.istype(targetDt)) {
|
||||
if(value.type istype targetDt) {
|
||||
if(!checkArrayValues(value, targetDt))
|
||||
return false
|
||||
val arraySpecSize = arrayspec.constIndex()
|
||||
@@ -1227,7 +1241,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
// value may be either a single float, or a float arraysize
|
||||
if(value.type.istype(targetDt)) {
|
||||
if(value.type istype targetDt) {
|
||||
if(!checkArrayValues(value, targetDt))
|
||||
return false
|
||||
val arraySize = value.value.size
|
||||
@@ -1243,7 +1257,7 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
// check if the floating point values are all within range
|
||||
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 true
|
||||
}
|
||||
|
||||
@@ -1,21 +1,27 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.expressions.CharLiteral
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.statements.Directive
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compiler.BeforeAsmGenerationAstChanger
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.compiler.IErrorReporter
|
||||
import prog8.compiler.target.ICompilationTarget
|
||||
import prog8.compilerinterface.CompilationOptions
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
import prog8.compilerinterface.IStringEncoding
|
||||
|
||||
|
||||
internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: IErrorReporter, compTarget: ICompilationTarget) {
|
||||
val checker = AstChecker(this, compilerOptions, errors, compTarget)
|
||||
internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
||||
val checker = AstChecker(this, errors, compilerOptions)
|
||||
checker.visit(this)
|
||||
}
|
||||
|
||||
internal fun Program.processAstBeforeAsmGeneration(errors: IErrorReporter, compTarget: ICompilationTarget) {
|
||||
val fixer = BeforeAsmGenerationAstChanger(this, errors, compTarget)
|
||||
internal fun Program.processAstBeforeAsmGeneration(compilerOptions: CompilationOptions, errors: IErrorReporter) {
|
||||
val fixer = BeforeAsmGenerationAstChanger(this, compilerOptions, errors)
|
||||
fixer.visit(this)
|
||||
while(errors.noErrors() && fixer.applyModifications()>0) {
|
||||
fixer.visit(this)
|
||||
@@ -33,6 +39,20 @@ internal fun Program.reorderStatements(errors: IErrorReporter) {
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Program.charLiteralsToUByteLiterals(enc: IStringEncoding) {
|
||||
val walker = object : AstWalker() {
|
||||
override fun after(char: CharLiteral, parent: Node): Iterable<IAstModification> {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
char,
|
||||
NumericLiteralValue(DataType.UBYTE, enc.encodeString(char.value.toString(), char.altEncoding)[0].toInt(), char.position),
|
||||
parent
|
||||
))
|
||||
}
|
||||
}
|
||||
walker.visit(this)
|
||||
walker.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.addTypecasts(errors: IErrorReporter) {
|
||||
val caster = TypecastsAdder(this, errors)
|
||||
caster.visit(this)
|
||||
@@ -44,9 +64,17 @@ internal fun Program.verifyFunctionArgTypes() {
|
||||
fixer.visit(this)
|
||||
}
|
||||
|
||||
internal fun Program.preprocessAst() {
|
||||
val transforms = AstPreprocessor()
|
||||
transforms.visit(this)
|
||||
var mods = transforms.applyModifications()
|
||||
while(mods>0)
|
||||
mods = transforms.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.checkIdentifiers(errors: IErrorReporter, options: CompilationOptions) {
|
||||
|
||||
val checker2 = AstIdentifiersChecker(this, errors, options.compTarget)
|
||||
val checker2 = AstIdentifiersChecker(errors, options.compTarget)
|
||||
checker2.visit(this)
|
||||
|
||||
if(errors.noErrors()) {
|
||||
@@ -57,10 +85,6 @@ internal fun Program.checkIdentifiers(errors: IErrorReporter, options: Compilati
|
||||
lit2decl.visit(this)
|
||||
lit2decl.applyModifications()
|
||||
}
|
||||
|
||||
if (modules.map { it.name }.toSet().size != modules.size) {
|
||||
throw FatalAstException("modules should all be unique")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Program.variousCleanups(program: Program, errors: IErrorReporter) {
|
||||
@@ -70,19 +94,16 @@ internal fun Program.variousCleanups(program: Program, errors: IErrorReporter) {
|
||||
process.applyModifications()
|
||||
}
|
||||
|
||||
|
||||
internal fun Program.moveMainAndStartToFirst() {
|
||||
// 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,
|
||||
// and finally the entrypoint subroutine "start" itself is moved to the top in that block.
|
||||
|
||||
val directives = modules[0].statements.filterIsInstance<Directive>()
|
||||
val start = this.entrypoint()
|
||||
val mod = start.definingModule()
|
||||
val block = start.definingBlock()
|
||||
if(!modules.remove(mod))
|
||||
throw FatalAstException("module wrong")
|
||||
modules.add(0, mod)
|
||||
val start = this.entrypoint
|
||||
val mod = start.definingModule
|
||||
val block = start.definingBlock
|
||||
moveModuleToFront(mod)
|
||||
mod.remove(block)
|
||||
var afterDirective = mod.statements.indexOfFirst { it !is Directive }
|
||||
if(afterDirective<0)
|
||||
@@ -102,3 +123,11 @@ internal fun Program.moveMainAndStartToFirst() {
|
||||
modules[0].statements.add(0, directive)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun IdentifierReference.isSubroutineParameter(program: Program): Boolean {
|
||||
val vardecl = this.targetVarDecl(program)
|
||||
if(vardecl!=null && vardecl.autogeneratedDontRemove) {
|
||||
return vardecl.definingSubroutine?.parameters?.any { it.name==vardecl.name } == true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,58 +1,37 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.StringLiteralValue
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.compiler.IErrorReporter
|
||||
import prog8.compiler.functions.BuiltinFunctions
|
||||
import prog8.compiler.target.ICompilationTarget
|
||||
import prog8.compilerinterface.BuiltinFunctions
|
||||
import prog8.compilerinterface.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 fun nameError(name: String, position: Position, existing: Statement) {
|
||||
errors.err("name conflict '$name', also defined in ${existing.position.file} line ${existing.position.line}", position)
|
||||
}
|
||||
|
||||
override fun visit(module: Module) {
|
||||
blocks.clear() // blocks may be redefined within a different module
|
||||
|
||||
super.visit(module)
|
||||
}
|
||||
|
||||
override fun visit(block: Block) {
|
||||
if(block.name in compTarget.machine.opcodeNames)
|
||||
errors.err("can't use a cpu opcode name as a symbol: '${block.name}'", block.position)
|
||||
|
||||
val existing = blocks[block.name]
|
||||
if(existing!=null)
|
||||
nameError(block.name, block.position, existing)
|
||||
if(existing!=null) {
|
||||
if(block.isInLibrary)
|
||||
nameError(existing.name, existing.position, block)
|
||||
else
|
||||
nameError(block.name, block.position, existing)
|
||||
}
|
||||
else
|
||||
blocks[block.name] = block
|
||||
|
||||
if(!block.isInLibrary) {
|
||||
val libraries = program.modules.filter { it.isLibraryModule }
|
||||
val libraryBlockNames = libraries.flatMap { it.statements.filterIsInstance<Block>().map { b -> b.name } }
|
||||
if(block.name in libraryBlockNames)
|
||||
errors.err("block is already defined in an included library module", block.position)
|
||||
}
|
||||
|
||||
super.visit(block)
|
||||
}
|
||||
|
||||
override fun visit(directive: Directive) {
|
||||
if(directive.directive=="%target") {
|
||||
val compatibleTarget = directive.args.single().name
|
||||
if (compatibleTarget != compTarget.name)
|
||||
errors.err("module's compilation target ($compatibleTarget) differs from active target (${compTarget.name})", directive.position)
|
||||
}
|
||||
|
||||
super.visit(directive)
|
||||
}
|
||||
|
||||
override fun visit(decl: VarDecl) {
|
||||
decl.datatypeErrors.forEach { errors.err(it.message, it.position) }
|
||||
|
||||
@@ -62,14 +41,14 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
|
||||
if(decl.name in compTarget.machine.opcodeNames)
|
||||
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)
|
||||
nameError(decl.name, decl.position, existing)
|
||||
|
||||
if(decl.definingBlock().name==decl.name)
|
||||
nameError(decl.name, decl.position, decl.definingBlock())
|
||||
if(decl.definingSubroutine()?.name==decl.name)
|
||||
nameError(decl.name, decl.position, decl.definingSubroutine()!!)
|
||||
if(decl.definingBlock.name==decl.name)
|
||||
nameError(decl.name, decl.position, decl.definingBlock)
|
||||
if(decl.definingSubroutine?.name==decl.name)
|
||||
nameError(decl.name, decl.position, decl.definingSubroutine!!)
|
||||
|
||||
super.visit(decl)
|
||||
}
|
||||
@@ -85,32 +64,29 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
|
||||
// if (subroutine.parameters.any { it.name in BuiltinFunctions })
|
||||
// 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)
|
||||
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.
|
||||
val symbolsInSub = subroutine.allDefinedSymbols()
|
||||
// check that there are no local symbols (variables, labels, subs) that redefine the subroutine's parameters.
|
||||
val symbolsInSub = subroutine.allDefinedSymbols
|
||||
val namesInSub = symbolsInSub.map{ it.first }.toSet()
|
||||
val paramNames = subroutine.parameters.map { it.name }.toSet()
|
||||
val paramsToCheck = paramNames.intersect(namesInSub)
|
||||
for(name in paramsToCheck) {
|
||||
val labelOrVar = subroutine.getLabelOrVariable(name)
|
||||
if(labelOrVar!=null && labelOrVar.position != subroutine.position)
|
||||
nameError(name, labelOrVar.position, subroutine)
|
||||
val sub = subroutine.statements.firstOrNull { it is Subroutine && it.name==name}
|
||||
if(sub!=null)
|
||||
nameError(name, subroutine.position, sub)
|
||||
val symbol = subroutine.searchSymbol(name)
|
||||
if(symbol!=null && symbol.position != subroutine.position)
|
||||
nameError(name, symbol.position, subroutine)
|
||||
}
|
||||
|
||||
if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) {
|
||||
errors.err("asmsub can only contain inline assembly (%asm)", subroutine.position)
|
||||
}
|
||||
|
||||
if(subroutine.name == subroutine.definingBlock().name) {
|
||||
if(subroutine.name == subroutine.definingBlock.name) {
|
||||
// subroutines cannot have the same name as their enclosing block,
|
||||
// because this causes symbol scoping issues in the resulting assembly source
|
||||
nameError(subroutine.name, subroutine.position, subroutine.definingBlock())
|
||||
nameError(subroutine.name, subroutine.position, subroutine.definingBlock)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +101,7 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
|
||||
// the builtin functions can't be redefined
|
||||
errors.err("builtin function cannot be redefined", label.position)
|
||||
} else {
|
||||
val existing = label.definingSubroutine()?.getAllLabels(label.name) ?: emptyList()
|
||||
val existing = label.definingSubroutine?.getAllLabels(label.name) ?: emptyList()
|
||||
for(el in existing) {
|
||||
if(el === label || el.name != label.name)
|
||||
continue
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.base.NumericDatatypes
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.statements.AnonymousScope
|
||||
import prog8.ast.statements.AssignTarget
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
|
||||
|
||||
class AstPreprocessor : AstWalker() {
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
// For non-kernal subroutines and non-asm parameters:
|
||||
// inject subroutine params as local variables (if they're not there yet).
|
||||
val symbolsInSub = subroutine.allDefinedSymbols()
|
||||
val symbolsInSub = subroutine.allDefinedSymbols
|
||||
val namesInSub = symbolsInSub.map{ it.first }.toSet()
|
||||
if(subroutine.asmAddress==null) {
|
||||
if(subroutine.asmParameterRegisters.isEmpty() && subroutine.parameters.isNotEmpty()) {
|
||||
@@ -67,7 +67,7 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
|
||||
}
|
||||
|
||||
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? {
|
||||
@@ -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)
|
||||
if(arrayVar!=null && arrayVar.datatype == DataType.UWORD) {
|
||||
// rewrite pointervar[index] into @(pointervar+index)
|
||||
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) {
|
||||
// we're part of the target of an assignment, we have to actually change the assign target itself
|
||||
val memwrite = DirectMemoryWrite(add, arrayIndexedExpression.position)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.IStatementContainer
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
@@ -16,7 +17,7 @@ internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
|
||||
|
||||
override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||
if(string.parent !is VarDecl && string.parent !is WhenChoice) {
|
||||
// replace the literal string by a identifier reference to the interned string
|
||||
// replace the literal string by an identifier reference to the interned string
|
||||
val scopedName = program.internString(string)
|
||||
val identifier = IdentifierReference(scopedName, string.position)
|
||||
return listOf(IAstModification.ReplaceNode(string, identifier, parent))
|
||||
@@ -29,7 +30,7 @@ internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
|
||||
if(vardecl!=null) {
|
||||
// adjust the datatype of the array (to an educated guess)
|
||||
val arrayDt = array.type
|
||||
if(!arrayDt.istype(vardecl.datatype)) {
|
||||
if(arrayDt isnot vardecl.datatype) {
|
||||
val cast = array.cast(vardecl.datatype)
|
||||
if (cast != null && cast !== array)
|
||||
return listOf(IAstModification.ReplaceNode(vardecl.value!!, cast, vardecl))
|
||||
@@ -38,13 +39,15 @@ internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
|
||||
val arrayDt = array.guessDatatype(program)
|
||||
if(arrayDt.isKnown) {
|
||||
// this array literal is part of an expression, turn it into an identifier reference
|
||||
val litval2 = array.cast(arrayDt.typeOrElse(DataType.UNDEFINED))
|
||||
val litval2 = array.cast(arrayDt.getOr(DataType.UNDEFINED))
|
||||
if(litval2!=null) {
|
||||
if(array.parent !is IStatementContainer)
|
||||
return noModifications
|
||||
val vardecl2 = VarDecl.createAuto(litval2)
|
||||
val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(array, identifier, parent),
|
||||
IAstModification.InsertFirst(vardecl2, array.definingScope())
|
||||
IAstModification.InsertFirst(vardecl2, array.parent as IStatementContainer)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.*
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compiler.IErrorReporter
|
||||
import prog8.compiler.functions.BuiltinFunctions
|
||||
import prog8.compilerinterface.BuiltinFunctions
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
|
||||
|
||||
internal class StatementReorderer(val program: Program, val errors: IErrorReporter) : AstWalker() {
|
||||
@@ -24,7 +20,7 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
||||
// - (syntax desugaring) a vardecl with a non-const initializer value is split into a regular vardecl and an assignment statement.
|
||||
// - in-place assignments are reordered a bit so that they are mostly of the form A = A <operator> <rest>
|
||||
// - sorts the choices in when statement.
|
||||
// - insert AddressOf (&) expression where required (string params to a UWORD function param etc).
|
||||
// - insert AddressOf (&) expression where required (string params to a UWORD function param etc.).
|
||||
|
||||
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option")
|
||||
|
||||
@@ -38,15 +34,62 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
||||
module.statements.add(0, mainBlock)
|
||||
}
|
||||
|
||||
reorderVardeclsAndDirectives(module.statements)
|
||||
directivesToTheTop(module.statements)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun reorderVardeclsAndDirectives(statements: MutableList<Statement>) {
|
||||
val varDecls = statements.filterIsInstance<VarDecl>()
|
||||
statements.removeAll(varDecls)
|
||||
statements.addAll(0, varDecls)
|
||||
private val declsProcessedWithInitAssignment = mutableSetOf<VarDecl>()
|
||||
|
||||
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.
|
||||
// TODO these variables are no longer used???
|
||||
return noModifications
|
||||
}
|
||||
val nextStmt = decl.nextSibling()
|
||||
val nextAssign = nextStmt as? Assignment
|
||||
val nextFor = nextStmt as? ForLoop
|
||||
val hasNextForWithThisLoopvar = nextFor?.loopVar?.nameInSource==listOf(decl.name)
|
||||
val hasNextAssignment = nextAssign!=null && nextAssign.target isSameAs IdentifierReference(listOf(decl.name), Position.DUMMY)
|
||||
if (!hasNextAssignment && !hasNextForWithThisLoopvar) {
|
||||
// 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}
|
||||
statements.removeAll(directives)
|
||||
statements.addAll(0, directives)
|
||||
@@ -61,7 +104,7 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
||||
)
|
||||
}
|
||||
|
||||
reorderVardeclsAndDirectives(block.statements)
|
||||
directivesToTheTop(block.statements)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@@ -86,14 +129,19 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
||||
}
|
||||
|
||||
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> {
|
||||
|
||||
// ConstValue <associativeoperator> X --> X <associativeoperator> ConstValue
|
||||
// (this should be done by the ExpressionSimplifier when optimizing is enabled,
|
||||
// but the current assembly code generator for IF statements now also depends on it so we do it here regardless of optimization.)
|
||||
// but the current assembly code generator for IF statements now also depends on it, so we do it here regardless of optimization.)
|
||||
if (expr.left.constValue(program) != null && expr.operator in associativeOperators && expr.right.constValue(program) == null)
|
||||
return listOf(IAstModification.SwapOperands(expr))
|
||||
|
||||
@@ -106,12 +154,12 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
||||
is Assignment -> {
|
||||
val targetDt = parent.target.inferType(program)
|
||||
if(leftDt != targetDt) {
|
||||
val cast = TypecastExpression(expr.left, targetDt.typeOrElse(DataType.UNDEFINED), true, parent.position)
|
||||
val cast = TypecastExpression(expr.left, targetDt.getOr(DataType.UNDEFINED), true, parent.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
||||
}
|
||||
}
|
||||
is VarDecl -> {
|
||||
if(!leftDt.istype(parent.datatype)) {
|
||||
if(leftDt isnot parent.datatype) {
|
||||
val cast = TypecastExpression(expr.left, parent.datatype, true, parent.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
||||
}
|
||||
@@ -190,7 +238,7 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
||||
val valueType = assignment.value.inferType(program)
|
||||
val targetType = assignment.target.inferType(program)
|
||||
|
||||
if(targetType.isArray() && valueType.isArray() ) {
|
||||
if(targetType.isArray && valueType.isArray) {
|
||||
if (assignment.value is ArrayLiteralValue) {
|
||||
errors.err("cannot assign array literal here, use separate assignment per element", assignment.position)
|
||||
} else {
|
||||
|
||||
@@ -8,8 +8,8 @@ import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compiler.IErrorReporter
|
||||
import prog8.compiler.functions.BuiltinFunctions
|
||||
import prog8.compilerinterface.BuiltinFunctions
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
|
||||
|
||||
class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalker() {
|
||||
@@ -22,10 +22,10 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
|
||||
val declValue = decl.value
|
||||
if(decl.type==VarDeclType.VAR && declValue!=null) {
|
||||
val valueDt = declValue.inferType(program)
|
||||
if(!valueDt.istype(decl.datatype)) {
|
||||
if(valueDt isnot decl.datatype) {
|
||||
|
||||
// don't add a typecast on an array initializer value
|
||||
if(valueDt.isInteger() && decl.datatype in ArrayDatatypes)
|
||||
if(valueDt.isInteger && decl.datatype in ArrayDatatypes)
|
||||
return noModifications
|
||||
|
||||
// don't add a typecast if the initializer value is inherently not assignable
|
||||
@@ -47,7 +47,7 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
|
||||
val rightDt = expr.right.inferType(program)
|
||||
if(leftDt.isKnown && rightDt.isKnown && leftDt!=rightDt) {
|
||||
// determine common datatype and add typecast as required to make left and right equal types
|
||||
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.typeOrElse(DataType.UNDEFINED), rightDt.typeOrElse(DataType.UNDEFINED), expr.left, expr.right)
|
||||
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.getOr(DataType.UNDEFINED), rightDt.getOr(DataType.UNDEFINED), expr.left, expr.right)
|
||||
if(toFix!=null) {
|
||||
return when {
|
||||
toFix===expr.left -> listOf(IAstModification.ReplaceNode(
|
||||
@@ -66,8 +66,8 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
|
||||
val valueItype = assignment.value.inferType(program)
|
||||
val targetItype = assignment.target.inferType(program)
|
||||
if(targetItype.isKnown && valueItype.isKnown) {
|
||||
val targettype = targetItype.typeOrElse(DataType.UNDEFINED)
|
||||
val valuetype = valueItype.typeOrElse(DataType.UNDEFINED)
|
||||
val targettype = targetItype.getOr(DataType.UNDEFINED)
|
||||
val valuetype = valueItype.getOr(DataType.UNDEFINED)
|
||||
if (valuetype != targettype) {
|
||||
if (valuetype isAssignableTo targettype) {
|
||||
if(valuetype in IterableDatatypes && targettype==DataType.UWORD)
|
||||
@@ -78,10 +78,10 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
|
||||
TypecastExpression(assignment.value, targettype, true, assignment.value.position),
|
||||
assignment))
|
||||
} else {
|
||||
fun castLiteral(cvalue: NumericLiteralValue): List<IAstModification.ReplaceNode> {
|
||||
val cast = cvalue.cast(targettype)
|
||||
fun castLiteral(cvalue2: NumericLiteralValue): List<IAstModification.ReplaceNode> {
|
||||
val cast = cvalue2.cast(targettype)
|
||||
return if(cast.isValid)
|
||||
listOf(IAstModification.ReplaceNode(cvalue, cast.valueOrZero(), cvalue.parent))
|
||||
listOf(IAstModification.ReplaceNode(assignment.value, cast.valueOrZero(), assignment))
|
||||
else
|
||||
emptyList()
|
||||
}
|
||||
@@ -126,7 +126,7 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
|
||||
sub.parameters.zip(call.args).forEachIndexed { index, pair ->
|
||||
val argItype = pair.second.inferType(program)
|
||||
if(argItype.isKnown) {
|
||||
val argtype = argItype.typeOrElse(DataType.UNDEFINED)
|
||||
val argtype = argItype.getOr(DataType.UNDEFINED)
|
||||
val requiredType = pair.first.type
|
||||
if (requiredType != argtype) {
|
||||
if (argtype isAssignableTo requiredType) {
|
||||
@@ -135,11 +135,13 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
|
||||
TypecastExpression(pair.second, requiredType, true, pair.second.position),
|
||||
call as Node)
|
||||
} else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) {
|
||||
// we allow STR/ARRAY values in place of UWORD parameters. Take their address instead.
|
||||
if(pair.second is IdentifierReference) {
|
||||
// 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(pair.second as IdentifierReference, pair.second.position),
|
||||
AddressOf(identifier, pair.second.position),
|
||||
call as Node)
|
||||
}
|
||||
} else if(pair.second is NumericLiteralValue) {
|
||||
@@ -159,7 +161,7 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
|
||||
func.parameters.zip(call.args).forEachIndexed { index, pair ->
|
||||
val argItype = pair.second.inferType(program)
|
||||
if (argItype.isKnown) {
|
||||
val argtype = argItype.typeOrElse(DataType.UNDEFINED)
|
||||
val argtype = argItype.getOr(DataType.UNDEFINED)
|
||||
if (pair.first.possibleDatatypes.all { argtype != it }) {
|
||||
for (possibleType in pair.first.possibleDatatypes) {
|
||||
if (argtype isAssignableTo possibleType) {
|
||||
@@ -182,7 +184,7 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
|
||||
|
||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||
// warn about any implicit type casts to Float, because that may not be intended
|
||||
if(typecast.implicit && typecast.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
|
||||
if(typecast.implicit && typecast.type.oneOf(DataType.FLOAT, DataType.ARRAY_F)) {
|
||||
errors.warn("integer implicitly converted to float. Suggestion: use float literals, add an explicit cast, or revert to integer arithmetic", typecast.position)
|
||||
}
|
||||
return noModifications
|
||||
@@ -191,7 +193,7 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
|
||||
override fun after(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
|
||||
// make sure the memory address is an uword
|
||||
val dt = memread.addressExpression.inferType(program)
|
||||
if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) {
|
||||
if(dt.isKnown && dt.getOr(DataType.UWORD)!=DataType.UWORD) {
|
||||
val typecast = (memread.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)?.valueOrZero()
|
||||
?: TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position)
|
||||
return listOf(IAstModification.ReplaceNode(memread.addressExpression, typecast, memread))
|
||||
@@ -202,7 +204,7 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
|
||||
override fun after(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> {
|
||||
// make sure the memory address is an uword
|
||||
val dt = memwrite.addressExpression.inferType(program)
|
||||
if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) {
|
||||
if(dt.isKnown && dt.getOr(DataType.UWORD)!=DataType.UWORD) {
|
||||
val typecast = (memwrite.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)?.valueOrZero()
|
||||
?: TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position)
|
||||
return listOf(IAstModification.ReplaceNode(memwrite.addressExpression, typecast, memwrite))
|
||||
@@ -214,10 +216,10 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
|
||||
// add a typecast to the return type if it doesn't match the subroutine's signature
|
||||
val returnValue = returnStmt.value
|
||||
if(returnValue!=null) {
|
||||
val subroutine = returnStmt.definingSubroutine()!!
|
||||
val subroutine = returnStmt.definingSubroutine!!
|
||||
if(subroutine.returntypes.size==1) {
|
||||
val subReturnType = subroutine.returntypes.first()
|
||||
if (returnValue.inferType(program).istype(subReturnType))
|
||||
if (returnValue.inferType(program) istype subReturnType)
|
||||
return noModifications
|
||||
if (returnValue is NumericLiteralValue) {
|
||||
val cast = returnValue.cast(subroutine.returntypes.single())
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.IStatementContainer
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.FatalAstException
|
||||
@@ -10,23 +10,23 @@ import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compiler.IErrorReporter
|
||||
import prog8.compilerinterface.IErrorReporter
|
||||
|
||||
|
||||
internal class VariousCleanups(val program: Program, val errors: IErrorReporter): AstWalker() {
|
||||
|
||||
override fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> {
|
||||
return listOf(IAstModification.Remove(nopStatement, parent as INameScope))
|
||||
return listOf(IAstModification.Remove(nopStatement, parent as IStatementContainer))
|
||||
}
|
||||
|
||||
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
return if(parent is INameScope)
|
||||
listOf(ScopeFlatten(scope, parent as INameScope))
|
||||
return if(parent is IStatementContainer)
|
||||
listOf(ScopeFlatten(scope, parent as IStatementContainer))
|
||||
else
|
||||
noModifications
|
||||
}
|
||||
|
||||
class ScopeFlatten(val scope: AnonymousScope, val into: INameScope) : IAstModification {
|
||||
class ScopeFlatten(val scope: AnonymousScope, val into: IStatementContainer) : IAstModification {
|
||||
override fun perform() {
|
||||
val idx = into.statements.indexOf(scope)
|
||||
if(idx>=0) {
|
||||
@@ -71,7 +71,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter)
|
||||
}
|
||||
|
||||
val sourceDt = typecast.expression.inferType(program)
|
||||
if(sourceDt.istype(typecast.type))
|
||||
if(sourceDt istype typecast.type)
|
||||
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
|
||||
|
||||
return noModifications
|
||||
|
||||
@@ -8,21 +8,21 @@ import prog8.ast.expressions.FunctionCall
|
||||
import prog8.ast.expressions.TypecastExpression
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.compiler.CompilerException
|
||||
import prog8.compiler.functions.BuiltinFunctions
|
||||
import prog8.compilerinterface.BuiltinFunctions
|
||||
import prog8.compilerinterface.InternalCompilerException
|
||||
|
||||
class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
|
||||
|
||||
override fun visit(functionCall: FunctionCall) {
|
||||
val error = checkTypes(functionCall as IFunctionCall, program)
|
||||
if(error!=null)
|
||||
throw CompilerException(error)
|
||||
throw InternalCompilerException(error)
|
||||
}
|
||||
|
||||
override fun visit(functionCallStatement: FunctionCallStatement) {
|
||||
val error = checkTypes(functionCallStatement as IFunctionCall, program)
|
||||
if (error!=null)
|
||||
throw CompilerException(error)
|
||||
throw InternalCompilerException(error)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -44,7 +44,7 @@ class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
|
||||
val firstUnknownDt = argITypes.indexOfFirst { it.isUnknown }
|
||||
if(firstUnknownDt>=0)
|
||||
return "argument ${firstUnknownDt+1} invalid argument type"
|
||||
val argtypes = argITypes.map { it.typeOrElse(DataType.UNDEFINED) }
|
||||
val argtypes = argITypes.map { it.getOr(DataType.UNDEFINED) }
|
||||
val target = call.target.targetStatement(program)
|
||||
if (target is Subroutine) {
|
||||
if(call.args.size != target.parameters.size)
|
||||
|
||||
@@ -1,16 +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 name: String
|
||||
fun assemble(options: CompilationOptions)
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
package prog8.compiler.target
|
||||
|
||||
import prog8.ast.IMemSizer
|
||||
import prog8.ast.IStringEncoding
|
||||
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.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
|
||||
|
||||
fun isInRegularRAM(target: AssignTarget, program: Program): Boolean {
|
||||
val memAddr = target.memoryAddress
|
||||
val arrayIdx = target.arrayindexed
|
||||
val ident = target.identifier
|
||||
when {
|
||||
memAddr != null -> {
|
||||
return when (memAddr.addressExpression) {
|
||||
is NumericLiteralValue -> {
|
||||
machine.isRegularRAMaddress((memAddr.addressExpression as NumericLiteralValue).number.toInt())
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
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 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 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal object C64Target: ICompilationTarget {
|
||||
override val name = "c64"
|
||||
override val machine = C64MachineDefinition
|
||||
override fun encodeString(str: String, altEncoding: Boolean) =
|
||||
try {
|
||||
if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||
} catch (x: CharConversionException) {
|
||||
throw CharConversionException("can't convert string to target machine's char encoding: ${x.message}")
|
||||
}
|
||||
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) =
|
||||
try {
|
||||
if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||
} catch (x: CharConversionException) {
|
||||
throw CharConversionException("can't convert string to target machine's char encoding: ${x.message}")
|
||||
}
|
||||
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)
|
||||
}
|
||||
Loaded 100 of 252 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user