mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 20:02:27 +00:00
Compare commits
253
Commits
v7.0-beta
...
version7.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f8fe75ab2 | ||
|
|
44143f481a | ||
|
|
440abf4998 | ||
|
|
3c10427e04 | ||
|
|
85956b5828 | ||
|
|
41e40cad03 | ||
|
|
df2d5c6585 | ||
|
|
f696fce187 | ||
|
|
82d3d81bb2 | ||
|
|
f0cff661df | ||
|
|
82d20dea39 | ||
|
|
804bb06859 | ||
|
|
5afa7e53f8 | ||
|
|
7f15b7b716 | ||
|
|
552e0c2248 | ||
|
|
e5b9e1f5e7 | ||
|
|
502bf90007 | ||
|
|
40bf117497 | ||
|
|
4011dce31b | ||
|
|
9e881e32f8 | ||
|
|
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 | ||
|
|
aa10997171 | ||
|
|
7880ac1909 | ||
|
|
f53848b4b9 | ||
|
|
82f2f38680 | ||
|
|
dcc2549574 | ||
|
|
cfe4753913 | ||
|
|
fcb1a7e4d4 | ||
|
|
ce76a7dfa5 | ||
|
|
7c1de81861 | ||
|
|
eddad20acc | ||
|
|
7daad57862 | ||
|
|
1468049fe9 | ||
|
|
3b91e59a79 | ||
|
|
3496a30528 | ||
|
|
32bad5df15 | ||
|
|
3f58eca1be | ||
|
|
2350843d1d | ||
|
|
a2588a178c | ||
|
|
e5292696c4 | ||
|
|
34b2a65ccb | ||
|
|
3aa3659bc7 | ||
|
|
b8117394c0 | ||
|
|
fd2bbd2b59 | ||
|
|
127c470746 | ||
|
|
f2844bdf1a | ||
|
|
c5bfef4264 | ||
|
|
73863acc12 | ||
|
|
19e99204b9 | ||
|
|
13f5b94c3e | ||
|
|
3a2498401d | ||
|
|
53b20ba625 | ||
|
|
e7f6f0950f | ||
|
|
9fbe1b38a5 | ||
|
|
078485134d | ||
|
|
67b1766e32 | ||
|
|
d4b69ac79c | ||
|
|
e61a2d7083 | ||
|
|
c03f6604af | ||
|
|
572bb38ddb | ||
|
|
42c5c0cb9f | ||
|
|
e145d2255e | ||
|
|
442fa07dd4 | ||
|
|
31ae9e1243 | ||
|
|
d7f83f8df2 | ||
|
|
29e2d4e0c8 | ||
|
|
2732d2c844 | ||
|
|
c4a037b277 | ||
|
|
0e614ad6fc | ||
|
|
ca1a8cd617 | ||
|
|
ba96a637be | ||
|
|
c2cac772e3 | ||
|
|
6b7216f4ec | ||
|
|
e4fb5946dd | ||
|
|
ca61248861 | ||
|
|
68d7b4649e | ||
|
|
0416aacbbd | ||
|
|
bc731e6f8e | ||
|
|
ae5d7705bb | ||
|
|
b9bd541532 | ||
|
|
83639c2535 | ||
|
|
25d80f4df1 | ||
|
|
74f918d911 | ||
|
|
a20efa56eb |
No files matched your search
+2
-1
@@ -26,8 +26,9 @@ parser.out
|
||||
parsetab.py
|
||||
.pytest_cache/
|
||||
.attach_pid*
|
||||
compiler/lib/
|
||||
|
||||
.gradle
|
||||
/prog8compiler.jar
|
||||
sd*.img
|
||||
|
||||
*.d64
|
||||
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
-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-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.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.0" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/hypfvieh/dbus-java/3.3.0/dbus-java-3.3.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-unixsocket/0.38.5/jnr-unixsocket-0.38.5.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-ffi/2.2.1/jnr-ffi-2.2.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/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.0/asm-9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-commons/9.0/asm-commons-9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-analysis/9.0/asm-analysis-9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-tree/9.0/asm-tree-9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-util/9.0/asm-util-9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-a64asm/1.0.0/jnr-a64asm-1.0.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-x86asm/1.0.2/jnr-x86asm-1.0.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-constants/0.10.1/jnr-constants-0.10.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-enxio/0.32.3/jnr-enxio-0.32.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-posix/3.1.4/jnr-posix-3.1.4.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.1.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>
|
||||
-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
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.jvm" version "1.5.30" apply false
|
||||
}
|
||||
+24
-15
@@ -1,13 +1,14 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'application'
|
||||
id "org.jetbrains.kotlin.jvm" version "1.5.0"
|
||||
id 'com.github.johnrengelman.shadow' version '6.1.0'
|
||||
id "org.jetbrains.kotlin.jvm"
|
||||
id 'com.github.johnrengelman.shadow' version '7.1.0'
|
||||
}
|
||||
|
||||
targetCompatibility = 11
|
||||
sourceCompatibility = 11
|
||||
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
@@ -20,16 +21,29 @@ dependencies {
|
||||
implementation project(':compilerAst')
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.2'
|
||||
// implementation 'net.razorvine:ksim65:1.8'
|
||||
// implementation "com.github.hypfvieh:dbus-java:3.2.4"
|
||||
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 {
|
||||
exclude group: 'com.ibm.icu', module: 'icu4j'
|
||||
exclude group: "org.antlr", module: "antlr4"
|
||||
}
|
||||
|
||||
configurations {
|
||||
// strange antlr plugin issue, see https://github.com/gradle/gradle/issues/820
|
||||
// this avoids linking in the complete antlr binary jar
|
||||
compile {
|
||||
extendsFrom = extendsFrom.findAll { it != configurations.antlr }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
compileKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
@@ -57,7 +71,8 @@ sourceSets {
|
||||
}
|
||||
test {
|
||||
java {
|
||||
srcDirs = ["${project.projectDir}/test"]
|
||||
srcDir "${project.projectDir}/test"
|
||||
srcDir "${project(':compilerAst').projectDir}/test/helpers"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,7 +81,6 @@ startScripts.enabled = true
|
||||
|
||||
application {
|
||||
mainClass = 'prog8.CompilerMainKt'
|
||||
mainClassName = 'prog8.CompilerMainKt' // deprecated
|
||||
applicationName = 'p8compile'
|
||||
}
|
||||
|
||||
@@ -94,8 +108,3 @@ test {
|
||||
events "skipped", "failed"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
task wrapper(type: Wrapper) {
|
||||
gradleVersion = '6.7'
|
||||
}
|
||||
@@ -13,12 +13,14 @@
|
||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="11" jdkType="JavaSDK" />
|
||||
<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" />
|
||||
</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 {
|
||||
@@ -195,7 +194,7 @@ sub print_f (float value) {
|
||||
}}
|
||||
}
|
||||
|
||||
%asminclude "library:c64/floats.asm", ""
|
||||
%asminclude "library:c64/floats_funcs.asm", ""
|
||||
%asminclude "library:c64/floats.asm"
|
||||
%asminclude "library:c64/floats_funcs.asm"
|
||||
|
||||
}
|
||||
@@ -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,7 +4,6 @@
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
%target cx16
|
||||
%option enable_floats
|
||||
|
||||
floats {
|
||||
@@ -158,7 +157,7 @@ sub print_f (float value) {
|
||||
}}
|
||||
}
|
||||
|
||||
%asminclude "library:c64/floats.asm", ""
|
||||
%asminclude "library:c64/floats_funcs.asm", ""
|
||||
%asminclude "library:c64/floats.asm"
|
||||
%asminclude "library:c64/floats_funcs.asm"
|
||||
|
||||
}
|
||||
@@ -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,6 +68,14 @@ palette {
|
||||
}
|
||||
}
|
||||
|
||||
sub set_all_black() {
|
||||
set_monochrome($000, $000)
|
||||
}
|
||||
|
||||
sub set_all_white() {
|
||||
set_monochrome($fff, $fff)
|
||||
}
|
||||
|
||||
sub set_grayscale() {
|
||||
vera_palette_ptr = $fa00
|
||||
repeat 16 {
|
||||
|
||||
@@ -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) ----
|
||||
@@ -759,28 +756,28 @@ sys {
|
||||
}}
|
||||
}
|
||||
|
||||
sub wait(uword jiffies) {
|
||||
; --- wait approximately the given number of jiffies (1/60th seconds)
|
||||
repeat jiffies {
|
||||
ubyte jiff = lsb(c64.RDTIM16())
|
||||
while jiff==lsb(c64.RDTIM16()) {
|
||||
; wait until 1 jiffy has passed
|
||||
}
|
||||
}
|
||||
asmsub wait(uword jiffies @AY) {
|
||||
; --- wait approximately the given number of jiffies (1/60th seconds) (N or N+1)
|
||||
; note: regular system vsync irq handler must be running, and no nother irqs
|
||||
%asm {{
|
||||
- wai ; wait for irq (assume it was vsync)
|
||||
cmp #0
|
||||
bne +
|
||||
dey
|
||||
+ dec a
|
||||
bne -
|
||||
cpy #0
|
||||
bne -
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub waitvsync() clobbers(A, X, Y) {
|
||||
; --- busy wait till the next vsync has occurred (approximately), without depending on custom irq handling.
|
||||
; note: system vsync irq handler has to be active for this routine to work.
|
||||
; note 2: a more accurate way to wait for vsync is to set up a vsync irq handler instead.
|
||||
inline asmsub waitvsync() {
|
||||
; --- suspend execution until the next vsync has occurred, without depending on custom irq handling.
|
||||
; note: system vsync irq handler has to be active for this routine to work (and no other irqs-- which is the default).
|
||||
; note: a more accurate way to wait for vsync is to set up a vsync irq handler instead.
|
||||
%asm {{
|
||||
jsr c64.RDTIM
|
||||
sta _mod + 1
|
||||
inc _mod + 1
|
||||
_loop jsr c64.RDTIM
|
||||
_mod cmp #255 ; modified
|
||||
bne _loop
|
||||
rts
|
||||
wai
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
%target cx16
|
||||
%import syslib
|
||||
%import conv
|
||||
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
|
||||
math {
|
||||
%asminclude "library:math.asm", ""
|
||||
%asminclude "library:math.asm"
|
||||
}
|
||||
@@ -3,8 +3,8 @@
|
||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
|
||||
prog8_lib {
|
||||
%asminclude "library:prog8_lib.asm", ""
|
||||
%asminclude "library:prog8_funcs.asm", ""
|
||||
%asminclude "library:prog8_lib.asm"
|
||||
%asminclude "library:prog8_funcs.asm"
|
||||
|
||||
uword @zp retval_interm_uw ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
|
||||
word @zp retval_interm_w ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
|
||||
|
||||
@@ -1 +1 @@
|
||||
7.0-BETA
|
||||
7.1
|
||||
@@ -16,49 +16,54 @@ 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 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 compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available").default(C64Target.name)
|
||||
val sourceDirs by cli.option(ArgType.String, fullName="srcdirs", description = "list of extra paths to search in for imported modules").multiple().delimiter(File.pathSeparator)
|
||||
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999)
|
||||
val libDirs by cli.option(ArgType.String, fullName="libdirs", description = "list of extra paths to search in for imported modules").multiple().delimiter(File.pathSeparator)
|
||||
|
||||
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 libdirs = libDirs.toMutableList()
|
||||
if(libdirs.firstOrNull()!=".")
|
||||
libdirs.add(0, ".")
|
||||
val faultyOption = moduleFiles.firstOrNull { it.startsWith('-') }
|
||||
if(faultyOption!=null) {
|
||||
System.err.println("Unknown command line option given: $faultyOption")
|
||||
return false
|
||||
}
|
||||
|
||||
val srcdirs = sourceDirs.toMutableList()
|
||||
if(srcdirs.firstOrNull()!=".")
|
||||
srcdirs.add(0, ".")
|
||||
|
||||
if(watchMode==true) {
|
||||
val watchservice = FileSystems.getDefault().newWatchService()
|
||||
@@ -69,7 +74,7 @@ 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, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, srcdirs, outputPath)
|
||||
results.add(compilationResult)
|
||||
}
|
||||
|
||||
@@ -80,7 +85,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.")
|
||||
@@ -106,22 +111,30 @@ 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, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, srcdirs, outputPath)
|
||||
if(!compilationResult.success)
|
||||
exitProcess(1)
|
||||
return false
|
||||
} 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
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.compiler.astprocessing.isInRegularRAMof
|
||||
import prog8.compiler.target.ICompilationTarget
|
||||
|
||||
|
||||
@@ -22,7 +23,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
|
||||
// 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
|
||||
val nextAssign = decl.definingScope.nextSibling(decl) as? Assignment
|
||||
if (nextAssign != null && nextAssign.target isSameAs IdentifierReference(listOf(decl.name), Position.DUMMY))
|
||||
decl.value = null
|
||||
else {
|
||||
@@ -39,7 +40,7 @@ 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(compTarget.machine)) {
|
||||
val binExpr = assignment.value as? BinaryExpression
|
||||
if (binExpr != null && binExpr.operator !in comparisonOperators) {
|
||||
if (binExpr.left !is BinaryExpression) {
|
||||
@@ -51,14 +52,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, assignment.definingScope),
|
||||
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, assignment.definingScope),
|
||||
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
|
||||
}
|
||||
}
|
||||
@@ -80,7 +81,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
|
||||
val decls = scope.statements.filterIsInstance<VarDecl>().filter { it.type == VarDeclType.VAR }
|
||||
subroutineVariables.addAll(decls.map { it.name to it })
|
||||
|
||||
val sub = scope.definingSubroutine()
|
||||
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>()
|
||||
@@ -119,17 +120,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
|
||||
@@ -146,7 +147,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
|
||||
// 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)
|
||||
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) {
|
||||
@@ -266,8 +267,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 +340,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.definingScope))
|
||||
modifications.add(IAstModification.ReplaceNode(expr.indexer.indexExpr, target.identifier!!.copy(), expr.indexer))
|
||||
return modifications
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package prog8.compiler
|
||||
|
||||
import com.github.michaelbull.result.*
|
||||
import prog8.ast.AstToSourceCode
|
||||
import prog8.ast.IBuiltinFunctions
|
||||
import prog8.ast.IMemSizer
|
||||
@@ -16,13 +17,14 @@ import prog8.compiler.target.Cx16Target
|
||||
import prog8.compiler.target.ICompilationTarget
|
||||
import prog8.compiler.target.asmGeneratorFor
|
||||
import prog8.optimizer.*
|
||||
import prog8.parser.ModuleImporter
|
||||
import prog8.parser.ParseError
|
||||
import prog8.parser.ParsingFailedError
|
||||
import prog8.parser.moduleName
|
||||
import prog8.parser.SourceCode
|
||||
import prog8.parser.SourceCode.Companion.libraryFilePrefix
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.nio.file.Path
|
||||
import kotlin.system.exitProcess
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.nameWithoutExtension
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
|
||||
@@ -70,7 +72,7 @@ fun compileProgram(filepath: Path,
|
||||
writeAssembly: Boolean,
|
||||
slowCodegenWarnings: Boolean,
|
||||
compilationTarget: String,
|
||||
libdirs: List<String>,
|
||||
sourceDirs: List<String>,
|
||||
outputDir: Path): CompilationResult {
|
||||
var programName = ""
|
||||
lateinit var programAst: Program
|
||||
@@ -81,38 +83,52 @@ fun compileProgram(filepath: Path,
|
||||
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)
|
||||
val (ast, compilationOptions, imported) = parseImports(filepath, errors, compTarget, sourceDirs)
|
||||
compilationOptions.slowCodegenWarnings = slowCodegenWarnings
|
||||
compilationOptions.optimize = optimize
|
||||
programAst = ast
|
||||
importedFiles = imported
|
||||
processAst(programAst, errors, compilationOptions)
|
||||
if (compilationOptions.optimize)
|
||||
optimizeAst(programAst, errors, BuiltinFunctionsFacade(BuiltinFunctions), compTarget, compilationOptions)
|
||||
optimizeAst(
|
||||
programAst,
|
||||
errors,
|
||||
BuiltinFunctionsFacade(BuiltinFunctions),
|
||||
compTarget,
|
||||
compilationOptions
|
||||
)
|
||||
postprocessAst(programAst, errors, compilationOptions)
|
||||
|
||||
// printAst(programAst)
|
||||
|
||||
if(writeAssembly)
|
||||
programName = writeAssembly(programAst, errors, outputDir, compilationOptions)
|
||||
if (writeAssembly) {
|
||||
val result = writeAssembly(programAst, errors, outputDir, compilationOptions)
|
||||
when (result) {
|
||||
is WriteAssemblyResult.Ok -> programName = result.filename
|
||||
is WriteAssemblyResult.Fail -> {
|
||||
System.err.println(result.error)
|
||||
return CompilationResult(false, programAst, 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) {
|
||||
} 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 (pfx: ParsingFailedError) {
|
||||
System.err.print("\u001b[91m") // bright red
|
||||
System.err.println(pfx.message)
|
||||
System.err.print("\u001b[0m") // reset
|
||||
} catch (ax: AstException) {
|
||||
System.err.print("\u001b[91m") // bright red
|
||||
@@ -132,7 +148,7 @@ fun compileProgram(filepath: Path,
|
||||
throw x
|
||||
}
|
||||
|
||||
val failedProgram = Program("failed", mutableListOf(), BuiltinFunctionsFacade(BuiltinFunctions), compTarget)
|
||||
val failedProgram = Program("failed", BuiltinFunctionsFacade(BuiltinFunctions), compTarget)
|
||||
return CompilationResult(false, failedProgram, programName, compTarget, emptyList())
|
||||
}
|
||||
|
||||
@@ -166,24 +182,29 @@ 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)
|
||||
val programAst = Program(filepath.nameWithoutExtension, bf, compTarget)
|
||||
bf.program = programAst
|
||||
|
||||
val importer = ModuleImporter(programAst, compTarget, compilationTargetName, libdirs)
|
||||
importer.importModule(filepath)
|
||||
val importer = ModuleImporter(programAst, 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 importedFiles = programAst.modules.map { it.source }
|
||||
.filter { it.isFromFilesystem }
|
||||
.map { Path(it.origin) }
|
||||
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.")
|
||||
|
||||
// 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
|
||||
@@ -193,13 +214,13 @@ private fun parseImports(filepath: Path, errors: IErrorReporter, compTarget: ICo
|
||||
return Triple(programAst, 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()
|
||||
@@ -221,25 +242,33 @@ 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
|
||||
)
|
||||
@@ -248,7 +277,12 @@ private fun determineCompilationOptions(program: Program, compTarget: ICompilati
|
||||
private fun processAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
||||
// perform initial syntax checks and processings
|
||||
println("Processing for target ${compilerOptions.compTarget.name}...")
|
||||
programAst.checkIdentifiers(errors, compilerOptions.compTarget)
|
||||
programAst.checkIdentifiers(errors, compilerOptions)
|
||||
errors.report()
|
||||
// TODO: turning char literals into UBYTEs via an encoding should really happen in code gen - but for that we'd need DataType.CHAR
|
||||
// NOTE: we will then lose the opportunity to do constant-folding on any expression containing a char literal, but how often will those occur?
|
||||
// Also they might be optimized away eventually in codegen or by the assembler even
|
||||
programAst.charLiteralsToUByteLiterals(errors, compilerOptions.compTarget)
|
||||
errors.report()
|
||||
programAst.constantFold(errors, compilerOptions.compTarget)
|
||||
errors.report()
|
||||
@@ -260,7 +294,7 @@ private fun processAst(programAst: Program, errors: IErrorReporter, compilerOpti
|
||||
errors.report()
|
||||
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget)
|
||||
errors.report()
|
||||
programAst.checkIdentifiers(errors, compilerOptions.compTarget)
|
||||
programAst.checkIdentifiers(errors, compilerOptions)
|
||||
errors.report()
|
||||
}
|
||||
|
||||
@@ -283,17 +317,6 @@ private fun optimizeAst(programAst: Program, errors: IErrorReporter, functions:
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -310,10 +333,15 @@ private fun postprocessAst(programAst: Program, errors: IErrorReporter, compiler
|
||||
programAst.moveMainAndStartToFirst()
|
||||
}
|
||||
|
||||
private sealed class WriteAssemblyResult {
|
||||
class Ok(val filename: String): WriteAssemblyResult()
|
||||
class Fail(val error: String): WriteAssemblyResult()
|
||||
}
|
||||
|
||||
private fun writeAssembly(programAst: Program,
|
||||
errors: IErrorReporter,
|
||||
outputDir: Path,
|
||||
compilerOptions: CompilationOptions): String {
|
||||
compilerOptions: CompilationOptions): WriteAssemblyResult {
|
||||
// asm generation directly from the Ast
|
||||
programAst.processAstBeforeAsmGeneration(errors, compilerOptions.compTarget)
|
||||
errors.report()
|
||||
@@ -327,9 +355,19 @@ private fun writeAssembly(programAst: Program,
|
||||
compilerOptions.compTarget.machine.zeropage,
|
||||
compilerOptions,
|
||||
outputDir).compileToAssembly()
|
||||
assembly.assemble(compilerOptions)
|
||||
errors.report()
|
||||
return assembly.name
|
||||
|
||||
return if(assembly.valid && errors.noErrors()) {
|
||||
val assemblerReturnStatus = assembly.assemble(compilerOptions)
|
||||
if(assemblerReturnStatus!=0)
|
||||
WriteAssemblyResult.Fail("assembler step failed with return code $assemblerReturnStatus")
|
||||
else {
|
||||
errors.report()
|
||||
WriteAssemblyResult.Ok(assembly.name)
|
||||
}
|
||||
} else {
|
||||
errors.report()
|
||||
WriteAssemblyResult.Fail("compiler failed with errors")
|
||||
}
|
||||
}
|
||||
|
||||
fun printAst(programAst: Program) {
|
||||
@@ -339,21 +377,19 @@ fun printAst(programAst: Program) {
|
||||
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() }
|
||||
internal fun loadAsmIncludeFile(filename: String, source: SourceCode): Result<String, NoSuchFileException> {
|
||||
return if (filename.startsWith(libraryFilePrefix)) {
|
||||
return runCatching {
|
||||
val stream = object {}.javaClass.getResourceAsStream("/prog8lib/${filename.substring(libraryFilePrefix.length)}") // TODO handle via SourceCode
|
||||
stream!!.bufferedReader().use { r -> r.readText() }
|
||||
}.mapError { NoSuchFileException(File(filename)) }
|
||||
} else {
|
||||
// first try in the isSameAs folder as where the containing file was imported from
|
||||
val sib = source.resolveSibling(filename)
|
||||
val sib = Path(source.origin).resolveSibling(filename)
|
||||
|
||||
if (sib.toFile().isFile)
|
||||
sib.toFile().readText()
|
||||
Ok(sib.toFile().readText())
|
||||
else
|
||||
File(filename).readText()
|
||||
Ok(File(filename).readText())
|
||||
}
|
||||
}
|
||||
|
||||
internal fun tryGetEmbeddedResource(name: String): InputStream? {
|
||||
return object{}.javaClass.getResourceAsStream("/prog8lib/$name")
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package prog8.compiler
|
||||
|
||||
interface IStringEncoding {
|
||||
fun encodeString(str: String, altEncoding: Boolean): List<Short>
|
||||
fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
|
||||
}
|
||||
@@ -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.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", 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) +
|
||||
// TODO: won't work until Prog8Parser is fixed s.t. it fully initializes the modules it returns. // hm, what won't work?)
|
||||
listOf(Path(importingModule.position.file).parent ?: Path("")) +
|
||||
listOf(Path(".", "prog8lib"))
|
||||
}
|
||||
|
||||
locations.forEach {
|
||||
try {
|
||||
return Ok(SourceCode.File(it.resolve(fileName)))
|
||||
} catch (e: NoSuchFileException) {
|
||||
}
|
||||
}
|
||||
|
||||
return Err(NoSuchFileException(File("name")))
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,29 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
||||
|
||||
val allowedDatatypes = NumericDatatypes
|
||||
|
||||
fun available() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size
|
||||
fun availableBytes() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size
|
||||
fun hasByteAvailable() = if(options.zeropage==ZeropageType.DONTUSE) false else free.isNotEmpty()
|
||||
fun availableWords(): Int {
|
||||
if(options.zeropage==ZeropageType.DONTUSE)
|
||||
return 0
|
||||
|
||||
val words = free.windowed(2).filter { it[0] == it[1]-1 }
|
||||
var nonOverlappingWordsCount = 0
|
||||
var prevMsbLoc = -1
|
||||
for(w in words) {
|
||||
if(w[0]!=prevMsbLoc) {
|
||||
nonOverlappingWordsCount++
|
||||
prevMsbLoc = w[1]
|
||||
}
|
||||
}
|
||||
return nonOverlappingWordsCount
|
||||
}
|
||||
fun hasWordAvailable(): Boolean {
|
||||
if(options.zeropage==ZeropageType.DONTUSE)
|
||||
return false
|
||||
|
||||
return free.windowed(2).any { it[0] == it[1] - 1 }
|
||||
}
|
||||
|
||||
fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: IErrorReporter): Int {
|
||||
assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"scopedname can't be allocated twice"}
|
||||
|
||||
@@ -12,11 +12,11 @@ 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 java.io.CharConversionException
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import kotlin.io.path.*
|
||||
|
||||
internal class AstChecker(private val program: Program,
|
||||
private val compilerOptions: CompilationOptions,
|
||||
@@ -44,8 +44,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)
|
||||
@@ -63,7 +63,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")
|
||||
}
|
||||
@@ -79,7 +79,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)
|
||||
}
|
||||
}
|
||||
@@ -87,7 +87,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)
|
||||
}
|
||||
@@ -105,7 +105,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 {
|
||||
@@ -137,6 +137,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)
|
||||
@@ -149,11 +152,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)
|
||||
}
|
||||
}
|
||||
@@ -268,10 +271,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)
|
||||
@@ -279,11 +283,11 @@ 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")
|
||||
@@ -294,11 +298,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")
|
||||
@@ -387,13 +391,13 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -413,7 +417,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)
|
||||
}
|
||||
}
|
||||
@@ -422,14 +426,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)
|
||||
}
|
||||
|
||||
@@ -478,15 +482,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -514,7 +518,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))
|
||||
@@ -592,7 +596,7 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
val declValue = decl.value
|
||||
if(declValue!=null && decl.type==VarDeclType.VAR) {
|
||||
if (!declValue.inferType(program).istype(decl.datatype)) {
|
||||
if (declValue.inferType(program) isnot decl.datatype) {
|
||||
err("initialisation value has incompatible type (${declValue.inferType(program)}) for the variable (${decl.datatype})", declValue.position)
|
||||
}
|
||||
}
|
||||
@@ -683,20 +687,20 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
"%breakpoint" -> {
|
||||
if(directive.parent !is INameScope || directive.parent is Module)
|
||||
err("this directive may only occur in a block")
|
||||
err("this directive can't be used here")
|
||||
if(directive.args.isNotEmpty())
|
||||
err("invalid breakpoint directive, expected no arguments")
|
||||
}
|
||||
"%asminclude" -> {
|
||||
if(directive.parent !is INameScope || directive.parent is Module)
|
||||
err("this directive may only occur in a block")
|
||||
if(directive.args.size!=2 || directive.args[0].str==null || directive.args[1].str==null)
|
||||
err("invalid asminclude directive, expected arguments: \"filename\", \"scopelabel\"")
|
||||
err("this directive can't be used here")
|
||||
if(directive.args.size!=1 || directive.args[0].str==null)
|
||||
err("invalid asminclude directive, expected argument: \"filename\"")
|
||||
checkFileExists(directive, directive.args[0].str!!)
|
||||
}
|
||||
"%asmbinary" -> {
|
||||
if(directive.parent !is INameScope || directive.parent is Module)
|
||||
err("this directive may only occur in a block")
|
||||
err("this directive can't be used here")
|
||||
val errormsg = "invalid asmbinary directive, expected arguments: \"filename\" [, offset [, length ] ]"
|
||||
if(directive.args.isEmpty()) err(errormsg)
|
||||
else if(directive.args.isNotEmpty() && directive.args[0].str==null) err(errormsg)
|
||||
@@ -710,37 +714,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 {
|
||||
@@ -762,8 +766,25 @@ 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
|
||||
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 { // just *try* if it can be encoded, don't actually do it
|
||||
compTarget.encodeString(string.value, string.altEncoding)
|
||||
} catch (cx: CharConversionException) {
|
||||
errors.err(cx.message ?: "can't encode string", string.position)
|
||||
}
|
||||
|
||||
super.visit(string)
|
||||
}
|
||||
|
||||
@@ -772,7 +793,7 @@ internal class AstChecker(private val program: Program,
|
||||
if(!idt.isKnown)
|
||||
return // any error should be reported elsewhere
|
||||
|
||||
val dt = idt.typeOrElse(DataType.UNDEFINED)
|
||||
val dt = idt.getOr(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)
|
||||
@@ -797,8 +818,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){
|
||||
"/", "%" -> {
|
||||
@@ -894,7 +915,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)
|
||||
}
|
||||
|
||||
@@ -963,12 +984,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)
|
||||
@@ -990,7 +1011,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)
|
||||
@@ -999,14 +1020,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)
|
||||
}
|
||||
}
|
||||
@@ -1097,14 +1118,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)) {
|
||||
@@ -1135,7 +1156,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 {
|
||||
@@ -1146,13 +1167,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
|
||||
}
|
||||
|
||||
@@ -1181,7 +1200,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()
|
||||
@@ -1200,7 +1219,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()
|
||||
@@ -1219,7 +1238,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
|
||||
|
||||
@@ -1,12 +1,67 @@
|
||||
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.base.VarDeclType
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.AssignTarget
|
||||
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.IStringEncoding
|
||||
import prog8.compiler.target.ICompilationTarget
|
||||
import prog8.compiler.target.IMachineDefinition
|
||||
import kotlin.math.abs
|
||||
|
||||
|
||||
fun RangeExpr.size(encoding: IStringEncoding): Int? {
|
||||
val fromLv = (from as? NumericLiteralValue)
|
||||
val toLv = (to as? NumericLiteralValue)
|
||||
if(fromLv==null || toLv==null)
|
||||
return null
|
||||
return toConstantIntegerRange(encoding)?.count()
|
||||
}
|
||||
|
||||
fun RangeExpr.toConstantIntegerRange(encoding: IStringEncoding): IntProgression? {
|
||||
val fromVal: Int
|
||||
val toVal: Int
|
||||
val fromString = from as? StringLiteralValue
|
||||
val toString = to as? StringLiteralValue
|
||||
if(fromString!=null && toString!=null ) {
|
||||
// string range -> int range over character values
|
||||
fromVal = encoding.encodeString(fromString.value, fromString.altEncoding)[0].toInt()
|
||||
toVal = encoding.encodeString(toString.value, fromString.altEncoding)[0].toInt()
|
||||
} else {
|
||||
val fromLv = from as? NumericLiteralValue
|
||||
val toLv = to as? NumericLiteralValue
|
||||
if(fromLv==null || toLv==null)
|
||||
return null // non-constant range
|
||||
// integer range
|
||||
fromVal = fromLv.number.toInt()
|
||||
toVal = toLv.number.toInt()
|
||||
}
|
||||
val stepVal = (step as? NumericLiteralValue)?.number?.toInt() ?: 1
|
||||
return makeRange(fromVal, toVal, stepVal)
|
||||
}
|
||||
|
||||
private fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression {
|
||||
return when {
|
||||
fromVal <= toVal -> when {
|
||||
stepVal <= 0 -> IntRange.EMPTY
|
||||
stepVal == 1 -> fromVal..toVal
|
||||
else -> fromVal..toVal step stepVal
|
||||
}
|
||||
else -> when {
|
||||
stepVal >= 0 -> IntRange.EMPTY
|
||||
stepVal == -1 -> fromVal downTo toVal
|
||||
else -> fromVal downTo toVal step abs(stepVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: IErrorReporter, compTarget: ICompilationTarget) {
|
||||
@@ -33,6 +88,20 @@ internal fun Program.reorderStatements(errors: IErrorReporter) {
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Program.charLiteralsToUByteLiterals(errors: IErrorReporter, 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 +113,9 @@ internal fun Program.verifyFunctionArgTypes() {
|
||||
fixer.visit(this)
|
||||
}
|
||||
|
||||
internal fun Program.checkIdentifiers(errors: IErrorReporter, compTarget: ICompilationTarget) {
|
||||
internal fun Program.checkIdentifiers(errors: IErrorReporter, options: CompilationOptions) {
|
||||
|
||||
val checker2 = AstIdentifiersChecker(this, errors, compTarget)
|
||||
val checker2 = AstIdentifiersChecker(this, errors, options.compTarget)
|
||||
checker2.visit(this)
|
||||
|
||||
if(errors.noErrors()) {
|
||||
@@ -57,10 +126,6 @@ internal fun Program.checkIdentifiers(errors: IErrorReporter, compTarget: ICompi
|
||||
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) {
|
||||
@@ -77,12 +142,10 @@ internal fun Program.moveMainAndStartToFirst() {
|
||||
// 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 +165,47 @@ internal fun Program.moveMainAndStartToFirst() {
|
||||
modules[0].statements.add(0, directive)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun AssignTarget.isInRegularRAMof(machine: IMachineDefinition): Boolean {
|
||||
val memAddr = memoryAddress
|
||||
val arrayIdx = arrayindexed
|
||||
val ident = identifier
|
||||
when {
|
||||
memAddr != null -> {
|
||||
return when (memAddr.addressExpression) {
|
||||
is NumericLiteralValue -> {
|
||||
machine.isRegularRAMaddress((memAddr.addressExpression as NumericLiteralValue).number.toInt())
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val program = definingModule.program
|
||||
val decl = (memAddr.addressExpression as IdentifierReference).targetVarDecl(program)
|
||||
if ((decl?.type == VarDeclType.VAR || decl?.type == VarDeclType.CONST) && decl.value is NumericLiteralValue)
|
||||
machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
|
||||
else
|
||||
false
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
arrayIdx != null -> {
|
||||
val program = definingModule.program
|
||||
val targetStmt = arrayIdx.arrayvar.targetVarDecl(program)
|
||||
return if (targetStmt?.type == VarDeclType.MEMORY) {
|
||||
val addr = targetStmt.value as? NumericLiteralValue
|
||||
if (addr != null)
|
||||
machine.isRegularRAMaddress(addr.number.toInt())
|
||||
else
|
||||
false
|
||||
} else true
|
||||
}
|
||||
ident != null -> {
|
||||
val program = definingModule.program
|
||||
val decl = ident.targetVarDecl(program)!!
|
||||
return if (decl.type == VarDeclType.MEMORY && decl.value is NumericLiteralValue)
|
||||
machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
|
||||
else
|
||||
true
|
||||
}
|
||||
else -> return true
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.StringLiteralValue
|
||||
@@ -17,42 +16,23 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
|
||||
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) }
|
||||
|
||||
@@ -66,10 +46,10 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
|
||||
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)
|
||||
}
|
||||
@@ -89,8 +69,8 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
|
||||
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
|
||||
val symbolsInSub = subroutine.allDefinedSymbols()
|
||||
// check that there are no local variables, labels, or other subs that redefine the subroutine's parameters. Blocks are okay.
|
||||
val symbolsInSub = subroutine.allDefinedSymbols
|
||||
val namesInSub = symbolsInSub.map{ it.first }.toSet()
|
||||
val paramNames = subroutine.parameters.map { it.name }.toSet()
|
||||
val paramsToCheck = paramNames.intersect(namesInSub)
|
||||
@@ -101,14 +81,17 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
|
||||
val sub = subroutine.statements.firstOrNull { it is Subroutine && it.name==name}
|
||||
if(sub!=null)
|
||||
nameError(name, subroutine.position, sub)
|
||||
val block = program.allBlocks().firstOrNull { it.name==name }
|
||||
if(block!=null)
|
||||
nameError(name, subroutine.position, block)
|
||||
}
|
||||
|
||||
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) {
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
super.visit(subroutine)
|
||||
@@ -122,7 +105,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
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -16,7 +16,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 +29,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 +38,13 @@ 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) {
|
||||
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.definingScope)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,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")
|
||||
|
||||
@@ -93,7 +93,7 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
||||
|
||||
// ConstValue <associativeoperator> X --> X <associativeoperator> ConstValue
|
||||
// (this should be done by the ExpressionSimplifier when optimizing is enabled,
|
||||
// but the current assembly code generator for IF statements now also depends on it so we do it here regardless of optimization.)
|
||||
// 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 +106,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 +190,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 {
|
||||
|
||||
@@ -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)
|
||||
@@ -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) {
|
||||
@@ -159,7 +159,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 +182,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 +191,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 +202,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 +214,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())
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -9,7 +9,7 @@ import prog8.compiler.CompilerException
|
||||
import kotlin.math.*
|
||||
|
||||
|
||||
class FParam(val name: String, val possibleDatatypes: Set<DataType>)
|
||||
class FParam(val name: String, val possibleDatatypes: Array<DataType>)
|
||||
|
||||
|
||||
typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer) -> NumericLiteralValue
|
||||
@@ -90,10 +90,10 @@ class FSignature(val name: String,
|
||||
@Suppress("UNUSED_ANONYMOUS_PARAMETER")
|
||||
private val functionSignatures: List<FSignature> = listOf(
|
||||
// this set of function have no return value and operate in-place:
|
||||
FSignature("rol" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
FSignature("ror" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
FSignature("rol2" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
FSignature("ror2" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
FSignature("rol" , false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
FSignature("ror" , false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
FSignature("rol2" , false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
FSignature("ror2" , false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
FSignature("sort" , false, listOf(FParam("array", ArrayDatatypes)), null),
|
||||
FSignature("reverse" , false, listOf(FParam("array", ArrayDatatypes)), null),
|
||||
FSignature("cmp" , false, listOf(FParam("value1", IntegerDatatypes), FParam("value2", NumericDatatypes)), null),
|
||||
@@ -103,46 +103,46 @@ private val functionSignatures: List<FSignature> = listOf(
|
||||
FSignature("sum" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args
|
||||
FSignature("abs" , true, listOf(FParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
|
||||
FSignature("len" , true, listOf(FParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
|
||||
FSignature("sizeof" , true, listOf(FParam("object", DataType.values().toSet())), DataType.UBYTE, ::builtinSizeof),
|
||||
FSignature("sizeof" , true, listOf(FParam("object", DataType.values())), DataType.UBYTE, ::builtinSizeof),
|
||||
// normal functions follow:
|
||||
FSignature("sgn" , true, listOf(FParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
|
||||
FSignature("sin" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::sin) },
|
||||
FSignature("sin8" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
|
||||
FSignature("sin8u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
|
||||
FSignature("sin16" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ),
|
||||
FSignature("sin16u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ),
|
||||
FSignature("cos" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::cos) },
|
||||
FSignature("cos8" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ),
|
||||
FSignature("cos8u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ),
|
||||
FSignature("cos16" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ),
|
||||
FSignature("cos16u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ),
|
||||
FSignature("tan" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::tan) },
|
||||
FSignature("atan" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::atan) },
|
||||
FSignature("ln" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::log) },
|
||||
FSignature("log2" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, ::log2) },
|
||||
FSignature("sqrt16" , true, listOf(FParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } },
|
||||
FSignature("sqrt" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::sqrt) },
|
||||
FSignature("rad" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::toRadians) },
|
||||
FSignature("deg" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::toDegrees) },
|
||||
FSignature("round" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
|
||||
FSignature("floor" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
|
||||
FSignature("ceil" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
|
||||
FSignature("sin" , true, listOf(FParam("rads", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::sin) },
|
||||
FSignature("sin8" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
|
||||
FSignature("sin8u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
|
||||
FSignature("sin16" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ),
|
||||
FSignature("sin16u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ),
|
||||
FSignature("cos" , true, listOf(FParam("rads", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::cos) },
|
||||
FSignature("cos8" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ),
|
||||
FSignature("cos8u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ),
|
||||
FSignature("cos16" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ),
|
||||
FSignature("cos16u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ),
|
||||
FSignature("tan" , true, listOf(FParam("rads", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::tan) },
|
||||
FSignature("atan" , true, listOf(FParam("rads", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::atan) },
|
||||
FSignature("ln" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::log) },
|
||||
FSignature("log2" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, ::log2) },
|
||||
FSignature("sqrt16" , true, listOf(FParam("value", arrayOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } },
|
||||
FSignature("sqrt" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::sqrt) },
|
||||
FSignature("rad" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::toRadians) },
|
||||
FSignature("deg" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::toDegrees) },
|
||||
FSignature("round" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
|
||||
FSignature("floor" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
|
||||
FSignature("ceil" , true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
|
||||
FSignature("any" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinAny) },
|
||||
FSignature("all" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinAll) },
|
||||
FSignature("lsb" , true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 } },
|
||||
FSignature("msb" , true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255} },
|
||||
FSignature("mkword" , true, listOf(FParam("msb", setOf(DataType.UBYTE)), FParam("lsb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
|
||||
FSignature("peek" , true, listOf(FParam("address", setOf(DataType.UWORD))), DataType.UBYTE),
|
||||
FSignature("peekw" , true, listOf(FParam("address", setOf(DataType.UWORD))), DataType.UWORD),
|
||||
FSignature("poke" , false, listOf(FParam("address", setOf(DataType.UWORD)), FParam("value", setOf(DataType.UBYTE))), null),
|
||||
FSignature("pokew" , false, listOf(FParam("address", setOf(DataType.UWORD)), FParam("value", setOf(DataType.UWORD))), null),
|
||||
FSignature("lsb" , true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 } },
|
||||
FSignature("msb" , true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255} },
|
||||
FSignature("mkword" , true, listOf(FParam("msb", arrayOf(DataType.UBYTE)), FParam("lsb", arrayOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
|
||||
FSignature("peek" , true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UBYTE),
|
||||
FSignature("peekw" , true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UWORD),
|
||||
FSignature("poke" , false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), null),
|
||||
FSignature("pokew" , false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UWORD))), null),
|
||||
FSignature("rnd" , false, emptyList(), DataType.UBYTE),
|
||||
FSignature("rndw" , false, emptyList(), DataType.UWORD),
|
||||
FSignature("rndf" , false, emptyList(), DataType.FLOAT),
|
||||
FSignature("memory" , true, listOf(FParam("name", setOf(DataType.STR)), FParam("size", setOf(DataType.UWORD))), DataType.UWORD),
|
||||
FSignature("memory" , true, listOf(FParam("name", arrayOf(DataType.STR)), FParam("size", arrayOf(DataType.UWORD))), DataType.UWORD),
|
||||
FSignature("swap" , false, listOf(FParam("first", NumericDatatypes), FParam("second", NumericDatatypes)), null),
|
||||
FSignature("callfar" , false, listOf(FParam("bank", setOf(DataType.UBYTE)), FParam("address", setOf(DataType.UWORD)), FParam("arg", setOf(DataType.UWORD))), null),
|
||||
FSignature("callrom" , false, listOf(FParam("bank", setOf(DataType.UBYTE)), FParam("address", setOf(DataType.UWORD)), FParam("arg", setOf(DataType.UWORD))), null),
|
||||
FSignature("callfar" , false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("arg", arrayOf(DataType.UWORD))), null),
|
||||
FSignature("callrom" , false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("arg", arrayOf(DataType.UWORD))), null),
|
||||
|
||||
)
|
||||
|
||||
@@ -164,7 +164,7 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
|
||||
|
||||
fun datatypeFromIterableArg(arglist: Expression): DataType {
|
||||
if(arglist is ArrayLiteralValue) {
|
||||
val dt = arglist.value.map {it.inferType(program).typeOrElse(DataType.UNDEFINED)}.toSet()
|
||||
val dt = arglist.value.map {it.inferType(program).getOr(DataType.UNDEFINED)}.toSet()
|
||||
if(dt.any { it !in NumericDatatypes }) {
|
||||
throw FatalAstException("fuction $function only accepts array of numeric values")
|
||||
}
|
||||
@@ -178,7 +178,7 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
|
||||
val idt = arglist.inferType(program)
|
||||
if(!idt.isKnown)
|
||||
throw FatalAstException("couldn't determine type of iterable $arglist")
|
||||
return when(val dt = idt.typeOrElse(DataType.UNDEFINED)) {
|
||||
return when(val dt = idt.getOr(DataType.UNDEFINED)) {
|
||||
DataType.STR, in NumericDatatypes -> dt
|
||||
in ArrayDatatypes -> ArrayToElementTypes.getValue(dt)
|
||||
else -> throw FatalAstException("function '$function' requires one argument which is an iterable")
|
||||
@@ -195,7 +195,7 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
|
||||
return when (function) {
|
||||
"abs" -> {
|
||||
val dt = args.single().inferType(program)
|
||||
return if(dt.isNumeric())
|
||||
return if(dt.isNumeric)
|
||||
dt
|
||||
else
|
||||
InferredTypes.InferredType.unknown()
|
||||
@@ -300,13 +300,13 @@ private fun builtinSizeof(args: List<Expression>, position: Position, program: P
|
||||
?: throw CannotEvaluateException("sizeof", "no target")
|
||||
|
||||
return when {
|
||||
dt.isArray() -> {
|
||||
dt.isArray -> {
|
||||
val length = (target as VarDecl).arraysize!!.constIndex() ?: throw CannotEvaluateException("sizeof", "unknown array size")
|
||||
val elementDt = ArrayToElementTypes.getValue(dt.typeOrElse(DataType.UNDEFINED))
|
||||
val elementDt = ArrayToElementTypes.getValue(dt.getOr(DataType.UNDEFINED))
|
||||
numericLiteral(memsizer.memorySize(elementDt) * length, position)
|
||||
}
|
||||
dt.istype(DataType.STR) -> throw SyntaxError("sizeof str is undefined, did you mean len?", position)
|
||||
else -> NumericLiteralValue(DataType.UBYTE, memsizer.memorySize(dt.typeOrElse(DataType.UNDEFINED)), position)
|
||||
dt istype DataType.STR -> throw SyntaxError("sizeof str is undefined, did you mean len?", position)
|
||||
else -> NumericLiteralValue(DataType.UBYTE, memsizer.memorySize(dt.getOr(DataType.UNDEFINED)), position)
|
||||
}
|
||||
} else {
|
||||
throw SyntaxError("sizeof invalid argument type", position)
|
||||
@@ -315,7 +315,7 @@ private fun builtinSizeof(args: List<Expression>, position: Position, program: P
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
private fun builtinLen(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
|
||||
// note: in some cases the length is > 255 and then we have to return a UWORD type instead of a UBYTE.
|
||||
// note: in some cases the length is > 255, and then we have to return a UWORD type instead of a UBYTE.
|
||||
if(args.size!=1)
|
||||
throw SyntaxError("len requires one argument", position)
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ internal const val subroutineFloatEvalResultVar1 = "_prog8_float_eval_result1"
|
||||
internal const val subroutineFloatEvalResultVar2 = "_prog8_float_eval_result2"
|
||||
|
||||
internal interface IAssemblyProgram {
|
||||
val valid: Boolean
|
||||
val name: String
|
||||
fun assemble(options: CompilationOptions)
|
||||
fun assemble(options: CompilationOptions): Int
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
package prog8.compiler.target
|
||||
|
||||
import com.github.michaelbull.result.fold
|
||||
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.AssemblyError
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.compiler.IErrorReporter
|
||||
import prog8.compiler.IStringEncoding
|
||||
import prog8.compiler.Zeropage
|
||||
import prog8.compiler.target.c64.C64MachineDefinition
|
||||
import prog8.compiler.target.cbm.Petscii
|
||||
@@ -24,64 +24,24 @@ interface ICompilationTarget: IStringEncoding, IMemSizer {
|
||||
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 AssemblyError("There was a problem converting a string to the target machine's char encoding: ${x.message}")
|
||||
}
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
|
||||
val coded = if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||
return coded.fold(
|
||||
failure = { throw it },
|
||||
success = { it }
|
||||
)
|
||||
}
|
||||
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
|
||||
try {
|
||||
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||
} catch (x: CharConversionException) {
|
||||
throw AssemblyError("There was a problem decoding to a string: ${x.message}")
|
||||
throw CharConversionException("can't decode string: ${x.message}")
|
||||
}
|
||||
|
||||
override fun memorySize(dt: DataType): Int {
|
||||
@@ -98,17 +58,18 @@ internal object C64Target: ICompilationTarget {
|
||||
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 AssemblyError("There was a problem converting a string to the target machine's char encoding: ${x.message}")
|
||||
}
|
||||
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
|
||||
val coded= if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||
return coded.fold(
|
||||
failure = { throw it },
|
||||
success = { it }
|
||||
)
|
||||
}
|
||||
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
|
||||
try {
|
||||
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||
} catch (x: CharConversionException) {
|
||||
throw AssemblyError("There was a problem decoding to a string: ${x.message}")
|
||||
throw CharConversionException("can't decode string: ${x.message}")
|
||||
}
|
||||
|
||||
override fun memorySize(dt: DataType): Int {
|
||||
|
||||
@@ -2,6 +2,7 @@ package prog8.compiler.target
|
||||
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.compiler.Zeropage
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
interface IMachineFloat {
|
||||
@@ -32,6 +33,6 @@ interface IMachineDefinition {
|
||||
fun getFloat(num: Number): IMachineFloat
|
||||
|
||||
fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String>
|
||||
fun launchEmulator(programName: String)
|
||||
fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path)
|
||||
fun isRegularRAMaddress(address: Int): Boolean
|
||||
}
|
||||
@@ -4,7 +4,9 @@ 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 java.io.IOException
|
||||
import java.nio.file.Path
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.math.pow
|
||||
|
||||
@@ -35,11 +37,16 @@ internal object C64MachineDefinition: IMachineDefinition {
|
||||
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 {
|
||||
@@ -79,7 +86,7 @@ internal object C64MachineDefinition: IMachineDefinition {
|
||||
|
||||
|
||||
init {
|
||||
if (options.floats && options.zeropage !in setOf(ZeropageType.FLOATSAFE, ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
|
||||
if (options.floats && options.zeropage !in arrayOf(ZeropageType.FLOATSAFE, ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
|
||||
throw CompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
|
||||
|
||||
if (options.zeropage == ZeropageType.FULL) {
|
||||
@@ -107,7 +114,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,
|
||||
|
||||
@@ -5,15 +5,22 @@ import prog8.compiler.OutputType
|
||||
import prog8.compiler.target.IAssemblyProgram
|
||||
import prog8.compiler.target.generatedLabelPrefix
|
||||
import java.nio.file.Path
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
class AssemblyProgram(override val name: String, outputDir: Path, private val compTarget: String) : IAssemblyProgram {
|
||||
|
||||
internal const val viceMonListPostfix = "vice-mon-list"
|
||||
|
||||
class AssemblyProgram(
|
||||
override val valid: Boolean,
|
||||
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(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",
|
||||
@@ -35,13 +42,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() {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,5 +1,6 @@
|
||||
package prog8.compiler.target.cpu6502.codegen
|
||||
|
||||
import com.github.michaelbull.result.*
|
||||
import prog8.ast.*
|
||||
import prog8.ast.antlr.escape
|
||||
import prog8.ast.base.*
|
||||
@@ -13,11 +14,13 @@ import prog8.compiler.target.cbm.AssemblyProgram
|
||||
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignment
|
||||
import prog8.compiler.target.cpu6502.codegen.assignment.AssignmentAsmGen
|
||||
import prog8.optimizer.CallGraph
|
||||
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.io.path.absolute
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
|
||||
@@ -55,10 +58,10 @@ 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)
|
||||
block2asm(b)
|
||||
|
||||
for(removal in removals.toList()) {
|
||||
@@ -86,7 +89,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 +155,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
|
||||
@@ -229,7 +236,7 @@ internal class AsmGen(private val program: Program,
|
||||
|
||||
private fun assignInitialValueToVar(decl: VarDecl, variableName: List<String>) {
|
||||
val asmName = asmVariableName(variableName)
|
||||
assignmentAsmGen.assignExpressionToVariable(decl.value!!, asmName, decl.datatype, decl.definingSubroutine())
|
||||
assignmentAsmGen.assignExpressionToVariable(decl.value!!, asmName, decl.datatype, decl.definingSubroutine)
|
||||
}
|
||||
|
||||
private var generatedLabelSequenceNumber: Int = 0
|
||||
@@ -381,18 +388,14 @@ internal class AsmGen(private val program: Program,
|
||||
out("\n; non-zeropage variables")
|
||||
val vars = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.VAR }
|
||||
|
||||
// special treatment for string types: merge strings that are identical
|
||||
val encodedstringVars = vars
|
||||
.filter {it.datatype == DataType.STR }
|
||||
.map {
|
||||
val str = it.value as StringLiteralValue
|
||||
it to compTarget.encodeString(str.value, str.altEncoding).plus(0)
|
||||
}
|
||||
.groupBy({it.second}, {it.first})
|
||||
for((encoded, variables) in encodedstringVars) {
|
||||
variables.dropLast(1).forEach { out(it.name) }
|
||||
val lastvar = variables.last()
|
||||
outputStringvar(lastvar, encoded)
|
||||
for((decl, variables) in encodedstringVars) {
|
||||
outputStringvar(decl, variables)
|
||||
}
|
||||
|
||||
// non-string variables
|
||||
@@ -402,11 +405,11 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun outputStringvar(lastvar: VarDecl, encoded: List<Short>) {
|
||||
val sv = lastvar.value as StringLiteralValue
|
||||
private fun outputStringvar(strdecl: VarDecl, bytes: List<Short>) {
|
||||
val sv = strdecl.value as StringLiteralValue
|
||||
val altEncoding = if(sv.altEncoding) "@" else ""
|
||||
out("${lastvar.name}\t; ${lastvar.datatype} $altEncoding\"${escape(sv.value).replace("\u0000", "<NULL>")}\"")
|
||||
val outputBytes = encoded.map { "$" + it.toString(16).padStart(2, '0') }
|
||||
out("${strdecl.name}\t; ${strdecl.datatype} $altEncoding\"${escape(sv.value).replace("\u0000", "<NULL>")}\"")
|
||||
val outputBytes = bytes.map { "$" + it.toString(16).padStart(2, '0') }
|
||||
for (chunk in outputBytes.chunked(16))
|
||||
out(" .byte " + chunk.joinToString())
|
||||
}
|
||||
@@ -497,58 +500,108 @@ internal class AsmGen(private val program: Program,
|
||||
return newName
|
||||
}
|
||||
|
||||
internal fun asmSymbolName(identifier: IdentifierReference) =
|
||||
internal fun asmSymbolName(identifier: IdentifierReference): String {
|
||||
if(identifier.nameInSource.size==2 && identifier.nameInSource[0]=="prog8_slabs")
|
||||
return identifier.nameInSource.joinToString(".")
|
||||
|
||||
val tgt2 = identifier.targetStatement(program)
|
||||
if(tgt2==null && (identifier.nameInSource[0].startsWith("_prog8") || identifier.nameInSource[0].startsWith("prog8")))
|
||||
return identifier.nameInSource.joinToString(".")
|
||||
|
||||
val target = identifier.targetStatement(program)!!
|
||||
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")
|
||||
fixNameSymbols(scopedName.joinToString("."))
|
||||
}
|
||||
else fixNameSymbols(identifier.nameInSource.joinToString("."))
|
||||
}
|
||||
}
|
||||
|
||||
internal fun asmVariableName(identifier: IdentifierReference) =
|
||||
fixNameSymbols(identifier.nameInSource.joinToString("."))
|
||||
|
||||
private fun getScopedSymbolNameForTarget(actualName: String, target: Statement): MutableList<String> {
|
||||
val scopedName = mutableListOf(actualName)
|
||||
var node: Node = target
|
||||
while (node !is Block) {
|
||||
node = node.parent
|
||||
if(node is INameScope) {
|
||||
scopedName.add(0, node.name)
|
||||
}
|
||||
}
|
||||
return scopedName
|
||||
}
|
||||
|
||||
internal fun asmSymbolName(regs: RegisterOrPair): String =
|
||||
if (regs in Cx16VirtualRegisters)
|
||||
"cx16." + regs.toString().lowercase()
|
||||
else
|
||||
throw AssemblyError("no symbol name for register $regs")
|
||||
|
||||
internal fun asmVariableName(identifier: IdentifierReference) =
|
||||
fixNameSymbols(identifier.nameInSource.joinToString("."))
|
||||
|
||||
internal fun asmSymbolName(name: String) = fixNameSymbols(name)
|
||||
internal fun asmVariableName(name: String) = fixNameSymbols(name)
|
||||
internal fun asmSymbolName(name: Iterable<String>) = fixNameSymbols(name.joinToString("."))
|
||||
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 sourceName = asmVariableName(pointervar)
|
||||
val vardecl = pointervar.targetVarDecl(program)!!
|
||||
val scopedName = vardecl.makeScopedName(vardecl.name)
|
||||
if (isTargetCpu(CpuType.CPU65c02)) {
|
||||
return if (isZpVar(scopedName)) {
|
||||
// pointervar is already in the zero page, no need to copy
|
||||
out(" lda ($sourceName)")
|
||||
Pair(true, sourceName)
|
||||
} else {
|
||||
out("""
|
||||
lda $sourceName
|
||||
ldy $sourceName+1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda (P8ZP_SCRATCH_W1)""")
|
||||
Pair(false, sourceName)
|
||||
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 sourceName
|
||||
}
|
||||
} 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)
|
||||
} else {
|
||||
out("""
|
||||
lda $sourceName
|
||||
ldy $sourceName+1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
lda (P8ZP_SCRATCH_W1),y""")
|
||||
Pair(false, sourceName)
|
||||
is VarDecl -> {
|
||||
val sourceName = asmVariableName(pointervar)
|
||||
val scopedName = target.makeScopedName(target.name)
|
||||
if (isTargetCpu(CpuType.CPU65c02)) {
|
||||
return if (isZpVar(scopedName)) {
|
||||
// pointervar is already in the zero page, no need to copy
|
||||
out(" lda ($sourceName)")
|
||||
sourceName
|
||||
} else {
|
||||
out("""
|
||||
lda $sourceName
|
||||
ldy $sourceName+1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda (P8ZP_SCRATCH_W1)""")
|
||||
"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")
|
||||
sourceName
|
||||
} else {
|
||||
out("""
|
||||
lda $sourceName
|
||||
ldy $sourceName+1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
lda (P8ZP_SCRATCH_W1),y""")
|
||||
"P8ZP_SCRATCH_W1"
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("invalid pointervar")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -820,7 +873,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,
|
||||
@@ -829,7 +882,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))
|
||||
}
|
||||
}
|
||||
@@ -853,10 +906,10 @@ 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 {
|
||||
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")
|
||||
}
|
||||
@@ -881,7 +934,15 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
|
||||
out("; variables")
|
||||
out("; register saves")
|
||||
for((dt, name, addr) in sub.asmGenInfo.extraVars) {
|
||||
if(addr!=null)
|
||||
out("$name = $addr")
|
||||
else when(dt) {
|
||||
DataType.UBYTE -> out("$name .byte 0")
|
||||
DataType.UWORD -> out("$name .word 0")
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
}
|
||||
if(sub.asmGenInfo.usedRegsaveA)
|
||||
out("_prog8_regsaveA .byte 0")
|
||||
if(sub.asmGenInfo.usedRegsaveX)
|
||||
@@ -889,9 +950,9 @@ internal class AsmGen(private val program: Program,
|
||||
if(sub.asmGenInfo.usedRegsaveY)
|
||||
out("_prog8_regsaveY .byte 0")
|
||||
if(sub.asmGenInfo.usedFloatEvalResultVar1)
|
||||
out("$subroutineFloatEvalResultVar1 .byte 0,0,0,0,0")
|
||||
out("$subroutineFloatEvalResultVar1 .byte 0,0,0,0,0")
|
||||
if(sub.asmGenInfo.usedFloatEvalResultVar2)
|
||||
out("$subroutineFloatEvalResultVar2 .byte 0,0,0,0,0")
|
||||
out("$subroutineFloatEvalResultVar2 .byte 0,0,0,0,0")
|
||||
vardecls2asm(sub.statements)
|
||||
out(" .pend\n")
|
||||
}
|
||||
@@ -930,7 +991,7 @@ internal class AsmGen(private val program: Program,
|
||||
// if(!booleanCondition.left.isSimple || !booleanCondition.right.isSimple)
|
||||
// throw AssemblyError("both operands for if comparison expression should have been simplified")
|
||||
|
||||
if (stmt.elsepart.containsNoCodeNorVars()) {
|
||||
if (stmt.elsepart.containsNoCodeNorVars) {
|
||||
// empty else
|
||||
val endLabel = makeLabel("if_end")
|
||||
expressionsAsmGen.translateComparisonExpressionWithJumpIfFalse(booleanCondition, endLabel)
|
||||
@@ -976,11 +1037,11 @@ internal class AsmGen(private val program: Program,
|
||||
iterations == 0 -> {}
|
||||
iterations <= 256 -> {
|
||||
out(" lda #${iterations and 255}")
|
||||
repeatByteCountInA(iterations, repeatLabel, endLabel, stmt.body)
|
||||
repeatByteCountInA(iterations, repeatLabel, endLabel, stmt)
|
||||
}
|
||||
else -> {
|
||||
out(" lda #<${iterations} | ldy #>${iterations}")
|
||||
repeatWordCountInAY(iterations, repeatLabel, endLabel, stmt.body)
|
||||
repeatWordCountInAY(iterations, repeatLabel, endLabel, stmt)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -990,11 +1051,11 @@ internal class AsmGen(private val program: Program,
|
||||
when(vardecl.datatype) {
|
||||
DataType.UBYTE, DataType.BYTE -> {
|
||||
assignVariableToRegister(name, RegisterOrPair.A)
|
||||
repeatByteCountInA(null, repeatLabel, endLabel, stmt.body)
|
||||
repeatByteCountInA(null, repeatLabel, endLabel, stmt)
|
||||
}
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
assignVariableToRegister(name, RegisterOrPair.AY)
|
||||
repeatWordCountInAY(null, repeatLabel, endLabel, stmt.body)
|
||||
repeatWordCountInAY(null, repeatLabel, endLabel, stmt)
|
||||
}
|
||||
else -> throw AssemblyError("invalid loop variable datatype $vardecl")
|
||||
}
|
||||
@@ -1003,14 +1064,14 @@ 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.body)
|
||||
repeatByteCountInA(null, repeatLabel, endLabel, stmt)
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
assignExpressionToRegister(stmt.iterations!!, RegisterOrPair.AY)
|
||||
repeatWordCountInAY(null, repeatLabel, endLabel, stmt.body)
|
||||
repeatWordCountInAY(null, repeatLabel, endLabel, stmt)
|
||||
}
|
||||
else -> throw AssemblyError("invalid loop expression datatype $dt")
|
||||
}
|
||||
@@ -1020,13 +1081,13 @@ internal class AsmGen(private val program: Program,
|
||||
loopEndLabels.pop()
|
||||
}
|
||||
|
||||
private fun repeatWordCountInAY(constIterations: Int?, repeatLabel: String, endLabel: String, body: AnonymousScope) {
|
||||
private fun repeatWordCountInAY(constIterations: Int?, repeatLabel: String, endLabel: String, stmt: RepeatLoop) {
|
||||
// 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 = makeLabel("repeatcounter")
|
||||
val counterVar: String = createRepeatCounterVar(DataType.UWORD, constIterations, stmt)
|
||||
out("""
|
||||
sta $counterVar
|
||||
sty $counterVar+1
|
||||
@@ -1039,44 +1100,67 @@ $repeatLabel lda $counterVar
|
||||
dec $counterVar+1
|
||||
+ dec $counterVar
|
||||
""")
|
||||
translate(body)
|
||||
translate(stmt.body)
|
||||
jmp(repeatLabel)
|
||||
|
||||
if(constIterations!=null && constIterations>=16 && zeropage.available() > 1) {
|
||||
// allocate count var on ZP TODO can be shared with countervars from other subroutines
|
||||
val zpAddr = zeropage.allocate(counterVar, DataType.UWORD, body.position, errors)
|
||||
out("$counterVar = $zpAddr ; auto zp UWORD")
|
||||
} else {
|
||||
out("$counterVar .word 0")
|
||||
}
|
||||
|
||||
out(endLabel)
|
||||
}
|
||||
|
||||
private fun repeatByteCountInA(constIterations: Int?, repeatLabel: String, endLabel: String, body: AnonymousScope) {
|
||||
// note: A must have been loaded with the number of iterations!
|
||||
private fun repeatByteCountInA(constIterations: Int?, repeatLabel: String, endLabel: String, stmt: RepeatLoop) {
|
||||
// note: A must be loaded with the number of iterations!
|
||||
if(constIterations==0)
|
||||
return
|
||||
|
||||
if(constIterations==null)
|
||||
out(" beq $endLabel ; skip loop if zero iters")
|
||||
val counterVar = makeLabel("repeatcounter")
|
||||
val counterVar = createRepeatCounterVar(DataType.UBYTE, constIterations, stmt)
|
||||
out(" sta $counterVar")
|
||||
out(repeatLabel)
|
||||
translate(body)
|
||||
out("""
|
||||
dec $counterVar
|
||||
bne $repeatLabel
|
||||
beq $endLabel""")
|
||||
translate(stmt.body)
|
||||
out(" dec $counterVar | bne $repeatLabel")
|
||||
if(constIterations==null)
|
||||
out(endLabel)
|
||||
}
|
||||
|
||||
if(constIterations!=null && constIterations>=16 && zeropage.available() > 0) {
|
||||
// allocate count var on ZP TODO can be shared with countervars from other subroutines
|
||||
val zpAddr = zeropage.allocate(counterVar, DataType.UBYTE, body.position, errors)
|
||||
out("$counterVar = $zpAddr ; auto zp UBYTE")
|
||||
} else {
|
||||
out("$counterVar .byte 0")
|
||||
private fun createRepeatCounterVar(dt: DataType, constIterations: Int?, stmt: RepeatLoop): String {
|
||||
val asmInfo = stmt.definingSubroutine!!.asmGenInfo
|
||||
var parent = stmt.parent
|
||||
while(parent !is ParentSentinel) {
|
||||
if(parent is RepeatLoop)
|
||||
break
|
||||
parent = parent.parent
|
||||
}
|
||||
val isNested = parent is RepeatLoop
|
||||
|
||||
if(!isNested) {
|
||||
// we can re-use a counter var from the subroutine if it already has one for that datatype
|
||||
val existingVar = asmInfo.extraVars.firstOrNull { it.first==dt }
|
||||
if(existingVar!=null)
|
||||
return existingVar.second
|
||||
}
|
||||
|
||||
out(endLabel)
|
||||
val counterVar = makeLabel("repeatcounter")
|
||||
when(dt) {
|
||||
DataType.UBYTE -> {
|
||||
if(constIterations!=null && constIterations>=16 && zeropage.hasByteAvailable()) {
|
||||
// allocate count var on ZP
|
||||
val zpAddr = zeropage.allocate(counterVar, DataType.UBYTE, stmt.position, errors)
|
||||
asmInfo.extraVars.add(Triple(DataType.UBYTE, counterVar, zpAddr))
|
||||
} else {
|
||||
asmInfo.extraVars.add(Triple(DataType.UBYTE, counterVar, null))
|
||||
}
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
if(constIterations!=null && constIterations>=16 && zeropage.hasWordAvailable()) {
|
||||
// allocate count var on ZP
|
||||
val zpAddr = zeropage.allocate(counterVar, DataType.UWORD, stmt.position, errors)
|
||||
asmInfo.extraVars.add(Triple(DataType.UWORD, counterVar, zpAddr))
|
||||
} else {
|
||||
asmInfo.extraVars.add(Triple(DataType.UWORD, counterVar, null))
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("invalidt dt")
|
||||
}
|
||||
return counterVar
|
||||
}
|
||||
|
||||
private fun translate(stmt: WhileLoop) {
|
||||
@@ -1112,7 +1196,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)
|
||||
@@ -1127,7 +1211,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("""
|
||||
@@ -1151,7 +1235,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) {
|
||||
@@ -1160,7 +1245,7 @@ $repeatLabel lda $counterVar
|
||||
}
|
||||
|
||||
private fun translate(stmt: BranchStatement) {
|
||||
if(stmt.truepart.containsNoCodeNorVars() && stmt.elsepart.containsCodeOrVars())
|
||||
if(stmt.truepart.containsNoCodeNorVars && stmt.elsepart.containsCodeOrVars)
|
||||
throw AssemblyError("only else part contains code, shoud have been switched already")
|
||||
|
||||
val jump = stmt.truepart.statements.first() as? Jump
|
||||
@@ -1172,7 +1257,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.containsNoCodeNorVars) {
|
||||
if(truePartIsJustBreak) {
|
||||
// branch with just a break (jump out of loop)
|
||||
val instruction = branchInstruction(stmt.condition, false)
|
||||
@@ -1216,8 +1301,8 @@ $repeatLabel lda $counterVar
|
||||
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()
|
||||
if(stmt.definingSubroutine ==null) {
|
||||
val block = stmt.definingBlock
|
||||
var inits = blockLevelVarInits[block]
|
||||
if(inits==null) {
|
||||
inits = mutableSetOf()
|
||||
@@ -1233,23 +1318,35 @@ $repeatLabel lda $counterVar
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: %asminclude and %asmbinary should be done earlier than code gen (-> put content into AST) ... (describe why?)
|
||||
*/
|
||||
private fun translate(stmt: Directive) {
|
||||
when(stmt.directive) {
|
||||
"%asminclude" -> {
|
||||
val sourcecode = loadAsmIncludeFile(stmt.args[0].str!!, stmt.definingModule().source)
|
||||
val scopeprefix = stmt.args[1].str ?: ""
|
||||
if(scopeprefix.isNotBlank())
|
||||
out("$scopeprefix\t.proc")
|
||||
assemblyLines.add(sourcecode.trimEnd().trimStart('\n'))
|
||||
if(scopeprefix.isNotBlank())
|
||||
out(" .pend\n")
|
||||
// TODO: handle %asminclude with SourceCode
|
||||
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}"
|
||||
@@ -1268,14 +1365,7 @@ $label nop""")
|
||||
val label = jump.generatedLabel
|
||||
val addr = jump.address
|
||||
return when {
|
||||
ident!=null -> {
|
||||
val target = ident.targetStatement(program)
|
||||
val asmName = asmSymbolName(ident)
|
||||
if(target is Label)
|
||||
"_$asmName" // prefix with underscore to jump to local label
|
||||
else
|
||||
asmName
|
||||
}
|
||||
ident!=null -> asmSymbolName(ident)
|
||||
label!=null -> label
|
||||
addr!=null -> addr.toHex()
|
||||
else -> "????"
|
||||
@@ -1284,7 +1374,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)
|
||||
@@ -1373,22 +1463,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)
|
||||
}
|
||||
|
||||
@@ -1401,7 +1491,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
|
||||
@@ -1417,38 +1507,48 @@ $label nop""")
|
||||
val ptrAndIndex = pointerViaIndexRegisterPossible(expr)
|
||||
if(ptrAndIndex!=null) {
|
||||
val pointervar = ptrAndIndex.first as? IdentifierReference
|
||||
if(write) {
|
||||
if(pointervar!=null && isZpVar(pointervar)) {
|
||||
val saveA = evalBytevalueWillClobberA(ptrAndIndex.second)
|
||||
if(saveA)
|
||||
out(" pha")
|
||||
when(pointervar?.targetStatement(program)) {
|
||||
is Label -> {
|
||||
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
|
||||
if(saveA)
|
||||
out(" pla")
|
||||
out(" sta (${asmSymbolName(pointervar)}),y")
|
||||
} else {
|
||||
// copy the pointer var to zp first
|
||||
val saveA = evalBytevalueWillClobberA(ptrAndIndex.first) || evalBytevalueWillClobberA(ptrAndIndex.second)
|
||||
if(saveA)
|
||||
out(" pha")
|
||||
assignExpressionToVariable(ptrAndIndex.first, asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
|
||||
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
|
||||
if(saveA)
|
||||
out(" pla")
|
||||
out(" sta (P8ZP_SCRATCH_W2),y")
|
||||
out(" lda ${asmSymbolName(pointervar)},y")
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if(pointervar!=null && isZpVar(pointervar)) {
|
||||
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
|
||||
out(" lda (${asmSymbolName(pointervar)}),y")
|
||||
} else {
|
||||
// copy the pointer var to zp first
|
||||
assignExpressionToVariable(ptrAndIndex.first, asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
|
||||
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
|
||||
out(" lda (P8ZP_SCRATCH_W2),y")
|
||||
is VarDecl, null -> {
|
||||
if(write) {
|
||||
if(pointervar!=null && isZpVar(pointervar)) {
|
||||
val saveA = evalBytevalueWillClobberA(ptrAndIndex.second)
|
||||
if(saveA)
|
||||
out(" pha")
|
||||
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
|
||||
if(saveA)
|
||||
out(" pla")
|
||||
out(" sta (${asmSymbolName(pointervar)}),y")
|
||||
} else {
|
||||
// copy the pointer var to zp first
|
||||
val saveA = evalBytevalueWillClobberA(ptrAndIndex.first) || evalBytevalueWillClobberA(ptrAndIndex.second)
|
||||
if(saveA)
|
||||
out(" pha")
|
||||
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
|
||||
if(saveA)
|
||||
out(" pla")
|
||||
out(" sta (P8ZP_SCRATCH_W2),y")
|
||||
}
|
||||
} else {
|
||||
if(pointervar!=null && isZpVar(pointervar)) {
|
||||
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
|
||||
out(" lda (${asmSymbolName(pointervar)}),y")
|
||||
} else {
|
||||
// copy the pointer var to zp first
|
||||
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
|
||||
out(" lda (P8ZP_SCRATCH_W2),y")
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
else -> throw AssemblyError("invalid pointervar")
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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("""
|
||||
@@ -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")
|
||||
@@ -611,14 +611,14 @@ 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")
|
||||
@@ -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,7 +641,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.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)
|
||||
@@ -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,7 +680,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.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)
|
||||
@@ -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+1), @(ptr+2))
|
||||
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,50 @@ 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)
|
||||
) {
|
||||
val pointerVar = firstExpr.left as IdentifierReference
|
||||
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 +825,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 +851,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)
|
||||
@@ -888,7 +930,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 +1003,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 +1061,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 +1119,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 +1128,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")
|
||||
@@ -1147,7 +1189,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 +1214,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 +1366,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 +1410,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 +1474,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) {
|
||||
|
||||
@@ -57,7 +57,7 @@ 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)
|
||||
val dt = idt.getOr(DataType.UNDEFINED)
|
||||
when (operator) {
|
||||
"==" -> {
|
||||
// if the left operand is an expression, and the right is 0, we can just evaluate that expression,
|
||||
@@ -197,7 +197,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 +242,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 +287,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 +332,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 +1339,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 +1424,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)
|
||||
@@ -1626,7 +1626,7 @@ 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)) {
|
||||
when(typecast.expression.inferType(program).getOr(DataType.UNDEFINED)) {
|
||||
DataType.UBYTE -> {
|
||||
when(typecast.type) {
|
||||
DataType.UBYTE, DataType.BYTE -> {}
|
||||
@@ -1696,7 +1696,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
internal fun translateDirectMemReadExpression(expr: DirectMemoryRead, pushResultOnEstack: Boolean) {
|
||||
|
||||
fun assignViaExprEval() {
|
||||
asmgen.assignExpressionToVariable(expr.addressExpression, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
|
||||
asmgen.assignExpressionToVariable(expr.addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
||||
if (pushResultOnEstack) {
|
||||
asmgen.out(" lda (P8ZP_SCRATCH_W2) | dex | sta P8ESTACK_LO+1,x")
|
||||
@@ -1757,7 +1757,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")
|
||||
}
|
||||
@@ -1781,8 +1781,8 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
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) {
|
||||
@@ -2108,7 +2108,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
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 +2146,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) {
|
||||
|
||||
@@ -9,6 +9,7 @@ import prog8.ast.expressions.RangeExpr
|
||||
import prog8.ast.statements.ForLoop
|
||||
import prog8.ast.toHex
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.astprocessing.toConstantIntegerRange
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||
@@ -19,15 +20,15 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
|
||||
throw AssemblyError("unknown dt")
|
||||
when(stmt.iterable) {
|
||||
is RangeExpr -> {
|
||||
val range = (stmt.iterable as RangeExpr).toConstantIntegerRange()
|
||||
val range = (stmt.iterable as RangeExpr).toConstantIntegerRange(asmgen.options.compTarget)
|
||||
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")
|
||||
}
|
||||
@@ -288,7 +289,7 @@ $loopLabel sty $indexVar
|
||||
bne $loopLabel
|
||||
beq $endLabel""")
|
||||
}
|
||||
if(length>=16 && asmgen.zeropage.available() > 0) {
|
||||
if(length>=16 && asmgen.zeropage.hasByteAvailable()) {
|
||||
// allocate index var on ZP
|
||||
val zpAddr = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, stmt.position, asmgen.errors)
|
||||
asmgen.out("""$indexVar = $zpAddr ; auto zp UBYTE""")
|
||||
@@ -327,7 +328,7 @@ $loopLabel sty $indexVar
|
||||
bne $loopLabel
|
||||
beq $endLabel""")
|
||||
}
|
||||
if(length>=16 && asmgen.zeropage.available() > 0) {
|
||||
if(length>=16 && asmgen.zeropage.hasByteAvailable()) {
|
||||
// allocate index var on ZP
|
||||
val zpAddr = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, stmt.position, asmgen.errors)
|
||||
asmgen.out("""$indexVar = $zpAddr ; auto zp UBYTE""")
|
||||
@@ -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)
|
||||
}
|
||||
@@ -5,7 +5,10 @@ import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.statements.InlineAssembly
|
||||
import prog8.ast.statements.RegisterOrStatusflag
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.statements.SubroutineParameter
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.CpuType
|
||||
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignSource
|
||||
@@ -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)
|
||||
@@ -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}")
|
||||
}
|
||||
|
||||
@@ -159,11 +157,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 +252,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 +265,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")
|
||||
|
||||
@@ -319,10 +317,9 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
register!!
|
||||
if(requiredDt largerThan valueDt) {
|
||||
// we need to sign extend the source, do this via temporary word variable
|
||||
val scratchVar = asmgen.asmVariableName("P8ZP_SCRATCH_W1")
|
||||
asmgen.assignExpressionToVariable(value, scratchVar, DataType.UBYTE, sub)
|
||||
asmgen.signExtendVariableLsb(scratchVar, valueDt)
|
||||
asmgen.assignVariableToRegister(scratchVar, register)
|
||||
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_W1", DataType.UBYTE, sub)
|
||||
asmgen.signExtendVariableLsb("P8ZP_SCRATCH_W1", valueDt)
|
||||
asmgen.assignVariableToRegister("P8ZP_SCRATCH_W1", register)
|
||||
} else {
|
||||
val target: AsmAssignTarget =
|
||||
if(parameter.value.type in ByteDatatypes && (register==RegisterOrPair.AX || register == RegisterOrPair.AY || register==RegisterOrPair.XY || register in Cx16VirtualRegisters))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -59,11 +59,11 @@ 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")
|
||||
}
|
||||
}
|
||||
@@ -132,7 +132,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 +147,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 +162,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 +173,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 +205,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"
|
||||
|
||||
@@ -115,7 +115,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
SourceStorageKind.MEMORY -> {
|
||||
fun assignViaExprEval(expression: Expression) {
|
||||
assignExpressionToVariable(expression, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, assign.target.scope)
|
||||
assignExpressionToVariable(expression, "P8ZP_SCRATCH_W2", DataType.UWORD, assign.target.scope)
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" lda (P8ZP_SCRATCH_W2)")
|
||||
else
|
||||
@@ -145,7 +145,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
when(val value = assign.source.expression!!) {
|
||||
is AddressOf -> {
|
||||
val sourceName = asmgen.asmVariableName(value.identifier)
|
||||
val sourceName = asmgen.asmSymbolName(value.identifier)
|
||||
assignAddressOf(assign.target, sourceName)
|
||||
}
|
||||
is NumericLiteralValue -> throw AssemblyError("source kind should have been literalnumber")
|
||||
@@ -217,7 +217,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 -> {
|
||||
@@ -299,7 +299,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")
|
||||
|
||||
@@ -320,7 +320,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
if(targetDt in WordDatatypes) {
|
||||
|
||||
fun assignViaExprEval(addressExpression: Expression) {
|
||||
asmgen.assignExpressionToVariable(addressExpression, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
|
||||
asmgen.assignExpressionToVariable(addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" lda (P8ZP_SCRATCH_W2)")
|
||||
else
|
||||
@@ -398,7 +398,7 @@ 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
|
||||
// 'cast' an ubyte value to a byte register; no cast needed at all
|
||||
return assignExpressionToRegister(value, target.register)
|
||||
}
|
||||
RegisterOrPair.AX,
|
||||
@@ -1359,8 +1359,10 @@ 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) {
|
||||
// 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")
|
||||
@@ -2089,7 +2091,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
fun storeViaExprEval() {
|
||||
when(addressExpr) {
|
||||
is NumericLiteralValue, is IdentifierReference -> {
|
||||
assignExpressionToVariable(addressExpr, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
|
||||
assignExpressionToVariable(addressExpr, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W2)")
|
||||
else
|
||||
@@ -2098,7 +2100,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
else -> {
|
||||
// same as above but we need to save the A register
|
||||
asmgen.out(" pha")
|
||||
assignExpressionToVariable(addressExpr, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
|
||||
assignExpressionToVariable(addressExpr, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||
asmgen.out(" pla")
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W2)")
|
||||
|
||||
+43
-86
@@ -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)
|
||||
@@ -280,7 +280,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 +293,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 +323,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 +361,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 +974,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 +1218,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("""
|
||||
@@ -1361,21 +1324,21 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
"*" -> {
|
||||
// stack contains (u) byte value, sign extend that and proceed with regular 16 bit operation
|
||||
// TODO use an optimized word * byte multiplication routine
|
||||
// TODO use an optimized word * byte multiplication routine?
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
|
||||
asmgen.signExtendAYlsb(valueDt)
|
||||
multiplyVarByWordInAY()
|
||||
}
|
||||
"/" -> {
|
||||
// stack contains (u) byte value, sign extend that and proceed with regular 16 bit operation
|
||||
// TODO use an optimized word / byte divmod routine
|
||||
// TODO use an optimized word / byte divmod routine?
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
|
||||
asmgen.signExtendAYlsb(valueDt)
|
||||
divideVarByWordInAY()
|
||||
}
|
||||
"%" -> {
|
||||
// stack contains (u) byte value, sign extend that and proceed with regular 16 bit operation
|
||||
// TODO use an optimized word / byte divmod routine
|
||||
// TODO use an optimized word / byte divmod routine?
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
|
||||
asmgen.signExtendAYlsb(valueDt)
|
||||
remainderVarByWordInAY()
|
||||
@@ -1697,7 +1660,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 -> {
|
||||
@@ -1767,18 +1730,15 @@ 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, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, target.scope)
|
||||
asmgen.assignExpressionToVariable(mem.addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD, target.scope)
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
@@ -1838,15 +1798,12 @@ 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, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, target.scope)
|
||||
asmgen.assignExpressionToVariable(memory.addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD, target.scope)
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
|
||||
@@ -4,7 +4,9 @@ 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 java.io.IOException
|
||||
import java.nio.file.Path
|
||||
|
||||
internal object CX16MachineDefinition: IMachineDefinition {
|
||||
|
||||
@@ -32,10 +34,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 {
|
||||
@@ -77,7 +97,7 @@ internal object CX16MachineDefinition: IMachineDefinition {
|
||||
|
||||
|
||||
init {
|
||||
if (options.floats && options.zeropage !in setOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
|
||||
if (options.floats && options.zeropage !in arrayOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
|
||||
throw CompilerException("when floats are enabled, zero page type should be 'basicsafe' or 'dontuse'")
|
||||
|
||||
// the addresses 0x02 to 0x21 (inclusive) are taken for sixteen virtual 16-bit api registers.
|
||||
|
||||
@@ -9,6 +9,7 @@ import prog8.ast.statements.AssignTarget
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compiler.astprocessing.isInRegularRAMof
|
||||
import prog8.compiler.target.ICompilationTarget
|
||||
|
||||
|
||||
@@ -54,7 +55,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
|
||||
|
||||
@@ -77,9 +78,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
|
||||
|
||||
|
||||
@@ -27,30 +27,30 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
}
|
||||
|
||||
private val usedSubroutines: Set<Subroutine> by lazy {
|
||||
calledBy.keys + program.entrypoint()
|
||||
calledBy.keys + program.entrypoint
|
||||
}
|
||||
|
||||
private val usedBlocks: Set<Block> by lazy {
|
||||
val blocksFromSubroutines = usedSubroutines.map { it.definingBlock() }
|
||||
val blocksFromLibraries = program.allBlocks().filter { it.isInLibrary }
|
||||
val blocksFromSubroutines = usedSubroutines.map { it.definingBlock }
|
||||
val blocksFromLibraries = program.allBlocks.filter { it.isInLibrary }
|
||||
val used = mutableSetOf<Block>()
|
||||
|
||||
allIdentifiersAndTargets.forEach {
|
||||
if(it.key.first.definingBlock() in blocksFromSubroutines) {
|
||||
val target = it.value.definingBlock()
|
||||
if(it.key.first.definingBlock in blocksFromSubroutines) {
|
||||
val target = it.value.definingBlock
|
||||
used.add(target)
|
||||
}
|
||||
}
|
||||
|
||||
used + blocksFromLibraries + program.entrypoint().definingBlock()
|
||||
used + blocksFromLibraries + program.entrypoint.definingBlock
|
||||
}
|
||||
|
||||
private val usedModules: Set<Module> by lazy {
|
||||
usedBlocks.map { it.definingModule() }.toSet()
|
||||
usedBlocks.map { it.definingModule }.toSet()
|
||||
}
|
||||
|
||||
override fun visit(directive: Directive) {
|
||||
val thisModule = directive.definingModule()
|
||||
val thisModule = directive.definingModule
|
||||
if (directive.directive == "%import") {
|
||||
val importedModule: Module = program.modules.single { it.name == directive.args[0].name }
|
||||
imports[thisModule] = imports.getValue(thisModule) + importedModule
|
||||
@@ -63,7 +63,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
override fun visit(functionCall: FunctionCall) {
|
||||
val otherSub = functionCall.target.targetSubroutine(program)
|
||||
if (otherSub != null) {
|
||||
functionCall.definingSubroutine()?.let { thisSub ->
|
||||
functionCall.definingSubroutine?.let { thisSub ->
|
||||
calls[thisSub] = calls.getValue(thisSub) + otherSub
|
||||
calledBy[otherSub] = calledBy.getValue(otherSub) + functionCall
|
||||
}
|
||||
@@ -74,7 +74,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
override fun visit(functionCallStatement: FunctionCallStatement) {
|
||||
val otherSub = functionCallStatement.target.targetSubroutine(program)
|
||||
if (otherSub != null) {
|
||||
functionCallStatement.definingSubroutine()?.let { thisSub ->
|
||||
functionCallStatement.definingSubroutine?.let { thisSub ->
|
||||
calls[thisSub] = calls.getValue(thisSub) + otherSub
|
||||
calledBy[otherSub] = calledBy.getValue(otherSub) + functionCallStatement
|
||||
}
|
||||
@@ -85,7 +85,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
override fun visit(addressOf: AddressOf) {
|
||||
val otherSub = addressOf.identifier.targetSubroutine(program)
|
||||
if(otherSub!=null) {
|
||||
addressOf.definingSubroutine()?.let { thisSub ->
|
||||
addressOf.definingSubroutine?.let { thisSub ->
|
||||
calls[thisSub] = calls.getValue(thisSub) + otherSub
|
||||
calledBy[otherSub] = calledBy.getValue(otherSub) + thisSub
|
||||
}
|
||||
@@ -96,7 +96,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
override fun visit(jump: Jump) {
|
||||
val otherSub = jump.identifier?.targetSubroutine(program)
|
||||
if (otherSub != null) {
|
||||
jump.definingSubroutine()?.let { thisSub ->
|
||||
jump.definingSubroutine?.let { thisSub ->
|
||||
calls[thisSub] = calls.getValue(thisSub) + otherSub
|
||||
calledBy[otherSub] = calledBy.getValue(otherSub) + jump
|
||||
}
|
||||
@@ -174,10 +174,10 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
}
|
||||
|
||||
fun unused(decl: VarDecl): Boolean {
|
||||
if(decl.type!=VarDeclType.VAR || decl.autogeneratedDontRemove)
|
||||
if(decl.type!=VarDeclType.VAR || decl.autogeneratedDontRemove || decl.sharedWithAsm)
|
||||
return false
|
||||
|
||||
if(decl.definingBlock() !in usedBlocks)
|
||||
if(decl.definingBlock !in usedBlocks)
|
||||
return false
|
||||
|
||||
val allReferencedVardecls = allIdentifiersAndTargets.filter { it.value is VarDecl }.map { it.value }.toSet()
|
||||
|
||||
@@ -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() {
|
||||
internal 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))
|
||||
|
||||
@@ -9,16 +9,19 @@ import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compiler.IErrorReporter
|
||||
import prog8.compiler.astprocessing.size
|
||||
import prog8.compiler.astprocessing.toConstantIntegerRange
|
||||
import prog8.compiler.target.ICompilationTarget
|
||||
|
||||
// Fix up the literal value's type to match that of the vardecl
|
||||
// (also check range literal operands types before they get expanded into arrays for instance)
|
||||
internal class VarConstantValueTypeAdjuster(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
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)
|
||||
@@ -30,7 +33,7 @@ internal class VarConstantValueTypeAdjuster(private val program: Program, privat
|
||||
|
||||
// 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
|
||||
val subroutine = decl.definingSubroutine as? INameScope
|
||||
if(subroutine!=null && subroutine!==parent) {
|
||||
val declValue = decl.value
|
||||
decl.value = null
|
||||
@@ -52,6 +55,35 @@ internal class VarConstantValueTypeAdjuster(private val program: Program, privat
|
||||
}
|
||||
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,16 +156,15 @@ 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
|
||||
val declArraySize = decl.arraysize?.constIndex()
|
||||
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
||||
if(declArraySize!=null && declArraySize!=rangeExpr.size(compTarget))
|
||||
errors.err("range expression size doesn't match declared array size", decl.value?.position!!)
|
||||
val constRange = rangeExpr.toConstantIntegerRange()
|
||||
val constRange = rangeExpr.toConstantIntegerRange(compTarget)
|
||||
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 +177,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,15 +210,13 @@ 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!!)
|
||||
val constRange = rangeExpr.toConstantIntegerRange()
|
||||
if(declArraySize!=null && declArraySize!=rangeExpr.size(compTarget))
|
||||
errors.err("range expression size (${rangeExpr.size(compTarget)}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
|
||||
val constRange = rangeExpr.toConstantIntegerRange(compTarget)
|
||||
if(constRange!=null) {
|
||||
val newValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F),
|
||||
constRange.map { NumericLiteralValue(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||
@@ -194,15 +224,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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,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 +134,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 +289,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 +305,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 +489,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 +513,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 +590,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 +621,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 +656,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)
|
||||
|
||||
@@ -21,7 +21,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)
|
||||
|
||||
@@ -11,6 +11,7 @@ import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.compiler.IErrorReporter
|
||||
import prog8.compiler.astprocessing.size
|
||||
import prog8.compiler.target.ICompilationTarget
|
||||
import kotlin.math.floor
|
||||
|
||||
@@ -27,19 +28,100 @@ internal class StatementOptimizer(private val program: Program,
|
||||
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
for(returnvar in subsThatNeedReturnVariable) {
|
||||
val decl = VarDecl(VarDeclType.VAR, returnvar.second, ZeropageWish.DONTCARE, null, retvarName, null, false, true, returnvar.third)
|
||||
val decl = VarDecl(VarDeclType.VAR, returnvar.second, ZeropageWish.DONTCARE, null, retvarName, null,
|
||||
isArray = false,
|
||||
autogeneratedDontRemove = true,
|
||||
sharedWithAsm = false,
|
||||
position = returnvar.third
|
||||
)
|
||||
returnvar.first.statements.add(0, decl)
|
||||
}
|
||||
subsThatNeedReturnVariable.clear()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||
// if the first instruction in the called subroutine is a return statement with a simple value,
|
||||
// remove the jump altogeter and inline the returnvalue directly.
|
||||
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, functionCallStatement.definingScope))
|
||||
}
|
||||
}
|
||||
|
||||
// printing a literal string of just 2 or 1 characters is replaced by directly outputting those characters
|
||||
// only do this optimization if the arg is a known-constant string literal instead of a user defined variable.
|
||||
if(functionCallStatement.target.nameInSource==listOf("txt", "print")) {
|
||||
val arg = functionCallStatement.args.single()
|
||||
val stringVar: IdentifierReference? = if(arg is AddressOf) {
|
||||
arg.identifier
|
||||
} else {
|
||||
arg as? IdentifierReference
|
||||
}
|
||||
if(stringVar!=null && stringVar.wasStringLiteral(program)) {
|
||||
val string = stringVar.targetVarDecl(program)?.value as? StringLiteralValue
|
||||
if(string!=null) {
|
||||
val pos = functionCallStatement.position
|
||||
if (string.value.length == 1) {
|
||||
val firstCharEncoded = compTarget.encodeString(string.value, string.altEncoding)[0]
|
||||
val chrout = FunctionCallStatement(
|
||||
IdentifierReference(listOf("txt", "chrout"), pos),
|
||||
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstCharEncoded.toInt(), pos)),
|
||||
functionCallStatement.void, pos
|
||||
)
|
||||
return listOf(IAstModification.ReplaceNode(functionCallStatement, chrout, parent))
|
||||
} else if (string.value.length == 2) {
|
||||
val firstTwoCharsEncoded = compTarget.encodeString(string.value.take(2), string.altEncoding)
|
||||
val chrout1 = FunctionCallStatement(
|
||||
IdentifierReference(listOf("txt", "chrout"), pos),
|
||||
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toInt(), pos)),
|
||||
functionCallStatement.void, pos
|
||||
)
|
||||
val chrout2 = FunctionCallStatement(
|
||||
IdentifierReference(listOf("txt", "chrout"), pos),
|
||||
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[1].toInt(), pos)),
|
||||
functionCallStatement.void, pos
|
||||
)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(functionCallStatement, chrout1, parent as INameScope),
|
||||
IAstModification.ReplaceNode(functionCallStatement, chrout2, parent)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,33 +130,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, functionCallStatement.definingScope))
|
||||
}
|
||||
|
||||
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.containsNoCodeNorVars && ifStatement.elsepart.containsNoCodeNorVars)
|
||||
return listOf(IAstModification.Remove(ifStatement, ifStatement.definingScope))
|
||||
|
||||
// empty true part? switch with the else part
|
||||
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsCodeOrVars()) {
|
||||
if(ifStatement.truepart.containsNoCodeNorVars && ifStatement.elsepart.containsCodeOrVars) {
|
||||
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)
|
||||
@@ -102,20 +184,20 @@ internal class StatementOptimizer(private val program: Program,
|
||||
}
|
||||
|
||||
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
|
||||
if(forLoop.body.containsNoCodeNorVars()) {
|
||||
if(forLoop.body.containsNoCodeNorVars) {
|
||||
errors.warn("removing empty for loop", forLoop.position)
|
||||
return listOf(IAstModification.Remove(forLoop, forLoop.definingScope()))
|
||||
return listOf(IAstModification.Remove(forLoop, forLoop.definingScope))
|
||||
} 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, forLoop.definingScope))
|
||||
}
|
||||
}
|
||||
|
||||
val range = forLoop.iterable as? RangeExpr
|
||||
if(range!=null) {
|
||||
if(range.size()==1) {
|
||||
if (range.size(compTarget) == 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)
|
||||
@@ -186,7 +268,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, whileLoop.definingScope))
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
@@ -195,14 +277,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.containsNoCodeNorVars) {
|
||||
errors.warn("empty loop removed", repeatLoop.position)
|
||||
return listOf(IAstModification.Remove(repeatLoop, repeatLoop.definingScope()))
|
||||
return listOf(IAstModification.Remove(repeatLoop, repeatLoop.definingScope))
|
||||
}
|
||||
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, repeatLoop.definingScope))
|
||||
}
|
||||
if (iterations == 1) {
|
||||
errors.warn("iterations is always 1", iter.position)
|
||||
@@ -214,10 +296,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.definingScope
|
||||
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, jump.definingScope))
|
||||
|
||||
return noModifications
|
||||
}
|
||||
@@ -250,7 +332,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, assignment.definingScope))
|
||||
} else if (op2 == "-") {
|
||||
// A = A +/- B - N
|
||||
val expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position)
|
||||
@@ -261,7 +343,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, assignment.definingScope))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -283,7 +365,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, assignment.definingScope))
|
||||
}
|
||||
|
||||
val targetIDt = assignment.target.inferType(program)
|
||||
@@ -291,7 +373,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()
|
||||
@@ -303,7 +385,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, assignment.definingScope))
|
||||
} 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)
|
||||
@@ -317,7 +399,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
}
|
||||
"-" -> {
|
||||
if (rightCv == 0.0) {
|
||||
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
||||
return listOf(IAstModification.Remove(assignment, assignment.definingScope))
|
||||
} 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)
|
||||
@@ -329,18 +411,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, 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 == 0.0)
|
||||
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
||||
return listOf(IAstModification.Remove(assignment, assignment.definingScope))
|
||||
}
|
||||
">>" -> {
|
||||
if (rightCv == 0.0)
|
||||
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
||||
return listOf(IAstModification.Remove(assignment, assignment.definingScope))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,7 +434,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
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.FunctionCall
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.compiler.IErrorReporter
|
||||
|
||||
|
||||
internal class SubroutineInliner(private val program: Program, val errors: IErrorReporter, private val compilerOptions: CompilationOptions): AstWalker() {
|
||||
private var callsToInlinedSubroutines = mutableListOf<Pair<IFunctionCall, Node>>()
|
||||
|
||||
fun fixCallsToInlinedSubroutines() {
|
||||
for((call, parent) in callsToInlinedSubroutines) {
|
||||
val sub = call.target.targetSubroutine(program)!!
|
||||
val intermediateReturnValueVar = sub.statements.filterIsInstance<VarDecl>().singleOrNull { it.name.endsWith(retvarName) }
|
||||
if(intermediateReturnValueVar!=null) {
|
||||
val scope = parent.definingScope()
|
||||
if(!scope.statements.filterIsInstance<VarDecl>().any { it.name==intermediateReturnValueVar.name}) {
|
||||
val decl = intermediateReturnValueVar.copy()
|
||||
scope.statements.add(0, decl)
|
||||
decl.linkParents(scope as Node)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
return if(compilerOptions.optimize && subroutine.inline && !subroutine.isAsmSubroutine)
|
||||
annotateInlinedSubroutineIdentifiers(subroutine)
|
||||
else
|
||||
noModifications
|
||||
}
|
||||
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
return after(functionCallStatement as IFunctionCall, parent, functionCallStatement.position)
|
||||
}
|
||||
|
||||
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||
return after(functionCall as IFunctionCall, parent, functionCall.position)
|
||||
}
|
||||
|
||||
private fun after(functionCall: IFunctionCall, parent: Node, position: Position): Iterable<IAstModification> {
|
||||
val sub = functionCall.target.targetSubroutine(program)
|
||||
if(sub != null && compilerOptions.optimize && sub.inline && !sub.isAsmSubroutine)
|
||||
callsToInlinedSubroutines.add(Pair(functionCall, parent))
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun annotateInlinedSubroutineIdentifiers(sub: Subroutine): List<IAstModification> {
|
||||
// this adds name prefixes to the identifiers used in the subroutine,
|
||||
// so that the statements can be inlined (=copied) in the call site and still reference
|
||||
// the correct symbols as seen from the scope of the subroutine.
|
||||
|
||||
class Annotator: AstWalker() {
|
||||
var numReturns=0
|
||||
|
||||
override fun before(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
|
||||
val stmt = identifier.targetStatement(program)!!
|
||||
if(stmt is BuiltinFunctionStatementPlaceholder)
|
||||
return noModifications
|
||||
|
||||
val prefixed = stmt.makeScopedName(identifier.nameInSource.last()).split('.')
|
||||
val withPrefix = IdentifierReference(prefixed, identifier.position)
|
||||
return listOf(IAstModification.ReplaceNode(identifier, withPrefix, parent))
|
||||
}
|
||||
|
||||
override fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> {
|
||||
numReturns++
|
||||
if(parent !== sub || sub.indexOfChild(returnStmt)<sub.statements.size-1)
|
||||
errors.err("return statement must be the very last statement in the inlined subroutine", sub.position)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
fun theModifications(): List<IAstModification> {
|
||||
return this.modifications.map { it.first }.toList()
|
||||
}
|
||||
}
|
||||
|
||||
val annotator = Annotator()
|
||||
sub.accept(annotator, sub.parent)
|
||||
if(annotator.numReturns>1) {
|
||||
errors.err("inlined subroutine can only have one return statement", sub.position)
|
||||
return noModifications
|
||||
}
|
||||
return annotator.theModifications()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.*
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.expressions.BinaryExpression
|
||||
import prog8.ast.expressions.FunctionCall
|
||||
@@ -13,6 +10,7 @@ import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compiler.IErrorReporter
|
||||
import prog8.compiler.astprocessing.isInRegularRAMof
|
||||
import prog8.compiler.target.ICompilationTarget
|
||||
|
||||
|
||||
@@ -23,8 +21,8 @@ internal class UnusedCodeRemover(private val program: Program,
|
||||
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, module.definingScope))
|
||||
else
|
||||
noModifications
|
||||
}
|
||||
@@ -64,8 +62,8 @@ 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.name != program.internedStringsModuleName)
|
||||
if (block.containsNoCodeNorVars) {
|
||||
if(block.name != internedStringsModuleName)
|
||||
errors.warn("removing unused block '${block.name}'", block.position)
|
||||
return listOf(IAstModification.Remove(block, parent as INameScope))
|
||||
}
|
||||
@@ -80,22 +78,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, subroutine.definingScope))
|
||||
callgraph.calledBy[subroutine]?.let {
|
||||
for(node in it)
|
||||
removals.add(IAstModification.Remove(node, node.definingScope))
|
||||
}
|
||||
return removals
|
||||
}
|
||||
return removals
|
||||
if(!subroutine.definingModule.isLibrary)
|
||||
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
|
||||
return listOf(IAstModification.Remove(subroutine, subroutine.definingScope))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,11 +103,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.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, decl.definingScope))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -125,7 +123,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,146 @@
|
||||
package prog8tests
|
||||
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.Matchers.equalTo
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.AddressOf
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.*
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.c64.C64MachineDefinition
|
||||
import prog8.compiler.target.cpu6502.codegen.AsmGen
|
||||
import prog8.parser.SourceCode
|
||||
import prog8tests.helpers.DummyFunctions
|
||||
import prog8tests.helpers.DummyMemsizer
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestAsmGen6502 {
|
||||
|
||||
private fun createTestProgram(): Program {
|
||||
/*
|
||||
main {
|
||||
|
||||
label_outside:
|
||||
uword var_outside
|
||||
|
||||
sub start () {
|
||||
uword localvar = 1234
|
||||
uword tgt
|
||||
|
||||
locallabel:
|
||||
tgt = localvar
|
||||
tgt = &locallabel
|
||||
tgt = &var_outside
|
||||
tgt = &label_outside
|
||||
tgt = &main.start.localvar
|
||||
tgt = &main.start.locallabel
|
||||
tgt = &main.var_outside
|
||||
tgt = &main.label_outside
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
val varInSub = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "localvar", NumericLiteralValue.optimalInteger(1234, Position.DUMMY), false, false, false, Position.DUMMY)
|
||||
val var2InSub = VarDecl(VarDeclType.VAR, DataType.UWORD, ZeropageWish.DONTCARE, null, "tgt", null, false, false, false, Position.DUMMY)
|
||||
val labelInSub = Label("locallabel", Position.DUMMY)
|
||||
|
||||
val tgt = AssignTarget(IdentifierReference(listOf("tgt"), Position.DUMMY), null, null, Position.DUMMY)
|
||||
val assign1 = Assignment(tgt, IdentifierReference(listOf("localvar"), Position.DUMMY), Position.DUMMY)
|
||||
val assign2 = Assignment(tgt, AddressOf(IdentifierReference(listOf("locallabel"), Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val assign3 = Assignment(tgt, AddressOf(IdentifierReference(listOf("var_outside"), Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val assign4 = Assignment(tgt, AddressOf(IdentifierReference(listOf("label_outside"), Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val assign5 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","start","localvar"), Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val assign6 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","start","locallabel"), Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val assign7 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","var_outside"), Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val assign8 = Assignment(tgt, AddressOf(IdentifierReference(listOf("main","label_outside"), Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
|
||||
val statements = mutableListOf(varInSub, var2InSub, labelInSub, assign1, assign2, assign3, assign4, assign5, assign6, assign7, assign8)
|
||||
val subroutine = Subroutine("start", emptyList(), 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(mutableListOf(block), Position.DUMMY, SourceCode.Generated("test"))
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer)
|
||||
.addModule(module)
|
||||
module.linkIntoProgram(program)
|
||||
return program
|
||||
}
|
||||
|
||||
private fun createTestAsmGen(program: Program): AsmGen {
|
||||
val errors = ErrorReporter()
|
||||
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(""))
|
||||
return asmgen
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSymbolNameFromStrings() {
|
||||
val program = createTestProgram()
|
||||
val asmgen = createTestAsmGen(program)
|
||||
|
||||
assertThat(asmgen.asmSymbolName("name"), equalTo("name"))
|
||||
assertThat(asmgen.asmSymbolName("<name>"), equalTo("prog8_name"))
|
||||
assertThat(asmgen.asmSymbolName(RegisterOrPair.R15), equalTo("cx16.r15"))
|
||||
assertThat(asmgen.asmSymbolName(listOf("a", "b", "name")), equalTo("a.b.name"))
|
||||
assertThat(asmgen.asmVariableName("name"), equalTo("name"))
|
||||
assertThat(asmgen.asmVariableName("<name>"), equalTo("prog8_name"))
|
||||
assertThat(asmgen.asmVariableName(listOf("a", "b", "name")), equalTo("a.b.name"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSymbolNameFromVarIdentifier() {
|
||||
val program = createTestProgram()
|
||||
val asmgen = createTestAsmGen(program)
|
||||
val sub = program.entrypoint
|
||||
|
||||
// local variable
|
||||
val localvarIdent = sub.statements.filterIsInstance<Assignment>().first { it.value is IdentifierReference }.value as IdentifierReference
|
||||
assertThat(asmgen.asmSymbolName(localvarIdent), equalTo("localvar"))
|
||||
assertThat(asmgen.asmVariableName(localvarIdent), equalTo("localvar"))
|
||||
val localvarIdentScoped = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("main", "start", "localvar") }.value as AddressOf).identifier
|
||||
assertThat(asmgen.asmSymbolName(localvarIdentScoped), equalTo("main.start.localvar"))
|
||||
assertThat(asmgen.asmVariableName(localvarIdentScoped), equalTo("main.start.localvar"))
|
||||
|
||||
// variable from outer scope (note that for Variables, no scoping prefix symbols are required,
|
||||
// because they're not outputted as locally scoped symbols for the assembler
|
||||
val scopedVarIdent = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("var_outside") }.value as AddressOf).identifier
|
||||
assertThat(asmgen.asmSymbolName(scopedVarIdent), equalTo("main.var_outside"))
|
||||
assertThat(asmgen.asmVariableName(scopedVarIdent), equalTo("var_outside"))
|
||||
val scopedVarIdentScoped = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("main", "var_outside") }.value as AddressOf).identifier
|
||||
assertThat(asmgen.asmSymbolName(scopedVarIdentScoped), equalTo("main.var_outside"))
|
||||
assertThat(asmgen.asmVariableName(scopedVarIdentScoped), equalTo("main.var_outside"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSymbolNameFromLabelIdentifier() {
|
||||
val program = createTestProgram()
|
||||
val asmgen = createTestAsmGen(program)
|
||||
val sub = program.entrypoint
|
||||
|
||||
// local label
|
||||
val localLabelIdent = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("locallabel") }.value as AddressOf).identifier
|
||||
assertThat(asmgen.asmSymbolName(localLabelIdent), equalTo("_locallabel"))
|
||||
assertThat("as a variable it uses different naming rules (no underscore prefix)", asmgen.asmVariableName(localLabelIdent), equalTo("locallabel"))
|
||||
val localLabelIdentScoped = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("main","start","locallabel") }.value as AddressOf).identifier
|
||||
assertThat(asmgen.asmSymbolName(localLabelIdentScoped), equalTo("main.start._locallabel"))
|
||||
assertThat("as a variable it uses different naming rules (no underscore prefix)", asmgen.asmVariableName(localLabelIdentScoped), equalTo("main.start.locallabel"))
|
||||
|
||||
// label from outer scope needs sope prefixes because it is outputted as a locally scoped symbol for the assembler
|
||||
val scopedLabelIdent = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("label_outside") }.value as AddressOf).identifier
|
||||
assertThat(asmgen.asmSymbolName(scopedLabelIdent), equalTo("main._label_outside"))
|
||||
assertThat("as a variable it uses different naming rules (no underscore prefix)", asmgen.asmVariableName(scopedLabelIdent), equalTo("label_outside"))
|
||||
val scopedLabelIdentScoped = (sub.statements.filterIsInstance<Assignment>().first { (it.value as? AddressOf)?.identifier?.nameInSource==listOf("main","label_outside") }.value as AddressOf).identifier
|
||||
assertThat(asmgen.asmSymbolName(scopedLabelIdentScoped), equalTo("main._label_outside"))
|
||||
assertThat("as a variable it uses different naming rules (no underscore prefix)", asmgen.asmVariableName(scopedLabelIdentScoped), equalTo("main.label_outside"))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,325 @@
|
||||
package prog8tests
|
||||
|
||||
import kotlin.test.*
|
||||
import com.github.michaelbull.result.getErrorOrElse
|
||||
import com.github.michaelbull.result.getOrElse
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.Matchers.*
|
||||
import org.hamcrest.core.Is
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.api.Disabled
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.internedStringsModuleName
|
||||
import prog8.compiler.IErrorReporter
|
||||
import prog8.compiler.ModuleImporter
|
||||
import prog8.parser.ParseError
|
||||
import prog8.parser.SourceCode
|
||||
import prog8tests.helpers.*
|
||||
import kotlin.io.path.*
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestModuleImporter {
|
||||
private val count = listOf("1st", "2nd", "3rd", "4th", "5th")
|
||||
|
||||
private lateinit var program: Program
|
||||
@BeforeEach
|
||||
fun beforeEach() {
|
||||
program = Program("foo", DummyFunctions, DummyMemsizer)
|
||||
}
|
||||
|
||||
private fun makeImporter(errors: IErrorReporter?, vararg searchIn: String): ModuleImporter {
|
||||
return makeImporter(errors, searchIn.asList())
|
||||
}
|
||||
|
||||
private fun makeImporter(errors: IErrorReporter? = null, searchIn: Iterable<String>) =
|
||||
ModuleImporter(program, "blah", errors ?: ErrorReporterForTests(), searchIn.toList())
|
||||
|
||||
@Nested
|
||||
inner class Constructor {
|
||||
|
||||
@Test
|
||||
@Disabled("TODO: invalid entries in search list")
|
||||
fun testInvalidEntriesInSearchList() {}
|
||||
|
||||
@Test
|
||||
@Disabled("TODO: literal duplicates in search list")
|
||||
fun testLiteralDuplicatesInSearchList() {}
|
||||
|
||||
@Test
|
||||
@Disabled("TODO: factual duplicates in search list")
|
||||
fun testFactualDuplicatesInSearchList() {}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class ImportModule {
|
||||
|
||||
@Nested
|
||||
inner class WithInvalidPath {
|
||||
@Test
|
||||
fun testNonexisting() {
|
||||
val dirRel = assumeDirectory(".", workingDir.relativize(fixturesDir))
|
||||
val importer = makeImporter(null, dirRel.invariantSeparatorsPathString)
|
||||
val srcPathRel = assumeNotExists(dirRel, "i_do_not_exist")
|
||||
val srcPathAbs = srcPathRel.absolute()
|
||||
val error1 = importer.importModule(srcPathRel).getErrorOrElse { fail("should have import error") }
|
||||
assertThat(
|
||||
".file should be normalized",
|
||||
"${error1.file}", equalTo("${error1.file.normalize()}")
|
||||
)
|
||||
assertThat(
|
||||
".file should point to specified path",
|
||||
error1.file.absolutePath, equalTo("${srcPathAbs.normalize()}")
|
||||
)
|
||||
assertThat(program.modules.size, equalTo(1))
|
||||
|
||||
val error2 = importer.importModule(srcPathAbs).getErrorOrElse { fail("should have import error") }
|
||||
assertThat(
|
||||
".file should be normalized",
|
||||
"${error2.file}", equalTo("${error2.file.normalize()}")
|
||||
)
|
||||
assertThat(
|
||||
".file should point to specified path",
|
||||
error2.file.absolutePath, equalTo("${srcPathAbs.normalize()}")
|
||||
)
|
||||
assertThat(program.modules.size, equalTo(1))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDirectory() {
|
||||
val srcPathRel = assumeDirectory(workingDir.relativize(fixturesDir))
|
||||
val srcPathAbs = srcPathRel.absolute()
|
||||
val searchIn = Path(".", "$srcPathRel").invariantSeparatorsPathString
|
||||
val importer = makeImporter(null, searchIn)
|
||||
|
||||
assertFailsWith<AccessDeniedException> { importer.importModule(srcPathRel) }
|
||||
.let {
|
||||
assertThat(
|
||||
".file should be normalized",
|
||||
"${it.file}", equalTo("${it.file.normalize()}")
|
||||
)
|
||||
assertThat(
|
||||
".file should point to specified path",
|
||||
it.file.absolutePath, equalTo("${srcPathAbs.normalize()}")
|
||||
)
|
||||
}
|
||||
assertThat(program.modules.size, equalTo(1))
|
||||
|
||||
assertFailsWith<AccessDeniedException> { importer.importModule(srcPathAbs) }
|
||||
.let {
|
||||
assertThat(
|
||||
".file should be normalized",
|
||||
"${it.file}", equalTo("${it.file.normalize()}")
|
||||
)
|
||||
assertThat(
|
||||
".file should point to specified path",
|
||||
it.file.absolutePath, equalTo("${srcPathAbs.normalize()}")
|
||||
)
|
||||
}
|
||||
assertThat(program.modules.size, equalTo(1))
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class WithValidPath {
|
||||
|
||||
@Test
|
||||
fun testAbsolute() {
|
||||
val searchIn = listOf(
|
||||
Path(".").div(workingDir.relativize(fixturesDir)), // we do want a dot "." in front
|
||||
).map { it.invariantSeparatorsPathString }
|
||||
val importer = makeImporter(null, searchIn)
|
||||
val fileName = "simple_main.p8"
|
||||
val path = assumeReadableFile(searchIn[0], fileName)
|
||||
|
||||
val module = importer.importModule(path.absolute()).getOrElse { throw it }
|
||||
assertThat(program.modules.size, equalTo(2))
|
||||
assertContains(program.modules, module)
|
||||
assertThat(module.program, equalTo(program))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRelativeToWorkingDir() {
|
||||
val searchIn = listOf(
|
||||
Path(".").div(workingDir.relativize(fixturesDir)), // we do want a dot "." in front
|
||||
).map { it.invariantSeparatorsPathString }
|
||||
val importer = makeImporter(null, searchIn)
|
||||
val fileName = "simple_main.p8"
|
||||
val path = assumeReadableFile(searchIn[0], fileName)
|
||||
assertThat("sanity check: path should NOT be absolute", path.isAbsolute, equalTo(false))
|
||||
|
||||
val module = importer.importModule(path).getOrElse { throw it }
|
||||
assertThat(program.modules.size, equalTo(2))
|
||||
assertContains(program.modules, module)
|
||||
assertThat(module.program, equalTo(program))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRelativeTo1stDirInSearchList() {
|
||||
val searchIn = Path(".")
|
||||
.div(workingDir.relativize(fixturesDir))
|
||||
.invariantSeparatorsPathString
|
||||
val importer = makeImporter(null, searchIn)
|
||||
val fileName = "simple_main.p8"
|
||||
val path = Path(".", fileName)
|
||||
assumeReadableFile(searchIn, path)
|
||||
|
||||
val module = importer.importModule(path).getOrElse { throw it }
|
||||
assertThat(program.modules.size, equalTo(2))
|
||||
assertContains(program.modules, module)
|
||||
assertThat(module.program, equalTo(program))
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("TODO: relative to 2nd in search list")
|
||||
fun testRelativeTo2ndDirInSearchList() {}
|
||||
|
||||
@Test
|
||||
@Disabled("TODO: ambiguous - 2 or more really different candidates")
|
||||
fun testAmbiguousCandidates() {}
|
||||
|
||||
@Nested
|
||||
inner class WithBadFile {
|
||||
@Test
|
||||
fun testWithSyntaxError() {
|
||||
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
||||
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
|
||||
val srcPath = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
|
||||
|
||||
val act = { importer.importModule(srcPath) }
|
||||
|
||||
repeat(2) { n ->
|
||||
assertFailsWith<ParseError>(count[n] + " call") { act() }.let {
|
||||
assertThat(it.position.file, equalTo(SourceCode.relative(srcPath).toString()))
|
||||
assertThat("line; should be 1-based", it.position.line, equalTo(2))
|
||||
assertThat("startCol; should be 0-based", it.position.startCol, equalTo(6))
|
||||
assertThat("endCol; should be 0-based", it.position.endCol, equalTo(6))
|
||||
}
|
||||
assertThat(program.modules.size, equalTo(1))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testImportingFileWithSyntaxError_once() {
|
||||
doTestImportingFileWithSyntaxError(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testImportingFileWithSyntaxError_twice() {
|
||||
doTestImportingFileWithSyntaxError(2)
|
||||
}
|
||||
|
||||
private fun doTestImportingFileWithSyntaxError(repetitions: Int) {
|
||||
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
||||
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
|
||||
val importing = assumeReadableFile(fixturesDir, "import_file_with_syntax_error.p8")
|
||||
val imported = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
|
||||
|
||||
val act = { importer.importModule(importing) }
|
||||
|
||||
repeat(repetitions) { n ->
|
||||
assertFailsWith<ParseError>(count[n] + " call") { act() }.let {
|
||||
assertThat(it.position.file, equalTo(SourceCode.relative(imported).toString()))
|
||||
assertThat("line; should be 1-based", it.position.line, equalTo(2))
|
||||
assertThat("startCol; should be 0-based", it.position.startCol, equalTo(6))
|
||||
assertThat("endCol; should be 0-based", it.position.endCol, equalTo(6))
|
||||
}
|
||||
assertThat("imported module with error in it should not be present", program.modules.size, equalTo(1))
|
||||
assertThat(program.modules[0].name, equalTo(internedStringsModuleName))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class ImportLibraryModule {
|
||||
@Nested
|
||||
inner class WithInvalidName {
|
||||
@Test
|
||||
fun testWithNonExistingName() {
|
||||
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
||||
val errors = ErrorReporterForTests()
|
||||
val importer = makeImporter(errors, searchIn.invariantSeparatorsPathString)
|
||||
val filenameNoExt = assumeNotExists(fixturesDir, "i_do_not_exist").name
|
||||
val filenameWithExt = assumeNotExists(fixturesDir, "i_do_not_exist.p8").name
|
||||
|
||||
repeat(2) { n ->
|
||||
val result = importer.importLibraryModule(filenameNoExt)
|
||||
assertThat(count[n] + " call / NO .p8 extension", result, Is(nullValue()))
|
||||
assertFalse(errors.noErrors(), count[n] + " call / NO .p8 extension")
|
||||
assertEquals(errors.errors.single(), "no module found with name i_do_not_exist")
|
||||
errors.report()
|
||||
assertThat(program.modules.size, equalTo(1))
|
||||
|
||||
val result2 = importer.importLibraryModule(filenameWithExt)
|
||||
assertThat(count[n] + " call / with .p8 extension", result2, Is(nullValue()))
|
||||
assertFalse(importer.errors.noErrors(), count[n] + " call / with .p8 extension")
|
||||
assertEquals(errors.errors.single(), "no module found with name i_do_not_exist.p8")
|
||||
errors.report()
|
||||
assertThat(program.modules.size, equalTo(1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class WithValidName {
|
||||
@Nested
|
||||
inner class WithBadFile {
|
||||
@Test
|
||||
fun testWithSyntaxError() {
|
||||
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
||||
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
|
||||
val srcPath = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
|
||||
|
||||
repeat(2) { n ->
|
||||
assertFailsWith<ParseError>(count[n] + " call")
|
||||
{ importer.importLibraryModule(srcPath.nameWithoutExtension) }.let {
|
||||
assertThat(it.position.file, equalTo(SourceCode.relative(srcPath).toString()))
|
||||
assertThat("line; should be 1-based", it.position.line, equalTo(2))
|
||||
assertThat("startCol; should be 0-based", it.position.startCol, equalTo(6))
|
||||
assertThat("endCol; should be 0-based", it.position.endCol, equalTo(6))
|
||||
}
|
||||
assertThat(program.modules.size, equalTo(1))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun doTestImportingFileWithSyntaxError(repetitions: Int) {
|
||||
val searchIn = assumeDirectory("./", workingDir.relativize(fixturesDir))
|
||||
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
|
||||
val importing = assumeReadableFile(fixturesDir, "import_file_with_syntax_error.p8")
|
||||
val imported = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
|
||||
|
||||
val act = { importer.importLibraryModule(importing.nameWithoutExtension) }
|
||||
|
||||
repeat(repetitions) { n ->
|
||||
assertFailsWith<ParseError>(count[n] + " call") { act() }.let {
|
||||
assertThat(it.position.file, equalTo(SourceCode.relative(imported).toString()))
|
||||
assertThat("line; should be 1-based", it.position.line, equalTo(2))
|
||||
assertThat("startCol; should be 0-based", it.position.startCol, equalTo(6))
|
||||
assertThat("endCol; should be 0-based", it.position.endCol, equalTo(6))
|
||||
}
|
||||
assertThat("imported module with error in it should not be present", program.modules.size, equalTo(1))
|
||||
assertThat(program.modules[0].name, equalTo(internedStringsModuleName))
|
||||
importer.errors.report()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testImportingFileWithSyntaxError_once() {
|
||||
doTestImportingFileWithSyntaxError(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testImportingFileWithSyntaxError_twice() {
|
||||
doTestImportingFileWithSyntaxError(2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import prog8.ast.statements.Block
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.optimizer.CallGraph
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import prog8tests.helpers.compileText
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestCallgraph {
|
||||
@Test
|
||||
fun testGraphForEmptySubs() {
|
||||
val sourcecode = """
|
||||
%import string
|
||||
main {
|
||||
sub start() {
|
||||
}
|
||||
sub empty() {
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, false, sourcecode).assertSuccess()
|
||||
val graph = CallGraph(result.programAst)
|
||||
|
||||
assertEquals(1, graph.imports.size)
|
||||
assertEquals(1, graph.importedBy.size)
|
||||
val toplevelModule = result.programAst.toplevelModule
|
||||
val importedModule = graph.imports.getValue(toplevelModule).single()
|
||||
assertEquals("string", importedModule.name)
|
||||
val importedBy = graph.importedBy.getValue(importedModule).single()
|
||||
assertTrue(importedBy.name.startsWith("on_the_fly_test"))
|
||||
|
||||
assertFalse(graph.unused(toplevelModule))
|
||||
assertFalse(graph.unused(importedModule))
|
||||
|
||||
val mainBlock = toplevelModule.statements.filterIsInstance<Block>().single()
|
||||
for(stmt in mainBlock.statements) {
|
||||
val sub = stmt as Subroutine
|
||||
assertFalse(sub in graph.calls)
|
||||
assertFalse(sub in graph.calledBy)
|
||||
|
||||
if(sub === result.programAst.entrypoint)
|
||||
assertFalse(graph.unused(sub), "start() should always be marked as used to avoid having it removed")
|
||||
else
|
||||
assertTrue(graph.unused(sub))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGraphForEmptyButReferencedSub() {
|
||||
val sourcecode = """
|
||||
%import string
|
||||
main {
|
||||
sub start() {
|
||||
uword xx = &empty
|
||||
xx++
|
||||
}
|
||||
sub empty() {
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target, false, sourcecode).assertSuccess()
|
||||
val graph = CallGraph(result.programAst)
|
||||
|
||||
assertEquals(1, graph.imports.size)
|
||||
assertEquals(1, graph.importedBy.size)
|
||||
val toplevelModule = result.programAst.toplevelModule
|
||||
val importedModule = graph.imports.getValue(toplevelModule).single()
|
||||
assertEquals("string", importedModule.name)
|
||||
val importedBy = graph.importedBy.getValue(importedModule).single()
|
||||
assertTrue(importedBy.name.startsWith("on_the_fly_test"))
|
||||
|
||||
assertFalse(graph.unused(toplevelModule))
|
||||
assertFalse(graph.unused(importedModule))
|
||||
|
||||
val mainBlock = toplevelModule.statements.filterIsInstance<Block>().single()
|
||||
val startSub = mainBlock.statements.filterIsInstance<Subroutine>().single{it.name=="start"}
|
||||
val emptySub = mainBlock.statements.filterIsInstance<Subroutine>().single{it.name=="empty"}
|
||||
|
||||
assertTrue(startSub in graph.calls, "start 'calls' (references) empty")
|
||||
assertFalse(emptySub in graph.calls, "empty doesn't call anything")
|
||||
assertTrue(emptySub in graph.calledBy, "empty gets 'called'")
|
||||
assertFalse(startSub in graph.calledBy, "start doesn't get called (except as entrypoint ofc.)")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import prog8tests.helpers.compileText
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertIs
|
||||
|
||||
|
||||
/**
|
||||
* ATTENTION: this is just kludge!
|
||||
* They are not really unit tests, but rather tests of the whole process,
|
||||
* from source file loading all the way through to running 64tass.
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestCompilerOnCharLit {
|
||||
|
||||
@Test
|
||||
fun testCharLitAsRomsubArg() {
|
||||
val platform = Cx16Target
|
||||
val result = compileText(platform, false, """
|
||||
main {
|
||||
romsub ${"$"}FFD2 = chrout(ubyte ch @ A)
|
||||
sub start() {
|
||||
chrout('\n')
|
||||
}
|
||||
}
|
||||
""").assertSuccess()
|
||||
|
||||
val program = result.programAst
|
||||
val startSub = program.entrypoint
|
||||
val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0]
|
||||
|
||||
assertIs<NumericLiteralValue>(funCall.args[0],
|
||||
"char literal should have been replaced by ubyte literal")
|
||||
val arg = funCall.args[0] as NumericLiteralValue
|
||||
assertEquals(DataType.UBYTE, arg.type)
|
||||
assertEquals(platform.encodeString("\n", false)[0], arg.number.toShort())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCharVarAsRomsubArg() {
|
||||
val platform = Cx16Target
|
||||
val result = compileText(platform, false, """
|
||||
main {
|
||||
romsub ${"$"}FFD2 = chrout(ubyte ch @ A)
|
||||
sub start() {
|
||||
ubyte ch = '\n'
|
||||
chrout(ch)
|
||||
}
|
||||
}
|
||||
""").assertSuccess()
|
||||
|
||||
val program = result.programAst
|
||||
val startSub = program.entrypoint
|
||||
val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0]
|
||||
|
||||
assertIs<IdentifierReference>(funCall.args[0])
|
||||
val arg = funCall.args[0] as IdentifierReference
|
||||
val decl = arg.targetVarDecl(program)!!
|
||||
assertEquals(VarDeclType.VAR, decl.type)
|
||||
assertEquals(DataType.UBYTE, decl.datatype)
|
||||
|
||||
// TODO: assertIs<CharLiteral>(decl.value,
|
||||
// "char literals should be kept until code gen")
|
||||
// val initializerValue = decl.value as CharLiteral
|
||||
// assertEquals('\n', (initializerValue as CharLiteral).value)
|
||||
|
||||
assertIs<NumericLiteralValue>(decl.value,
|
||||
"char literal should have been replaced by ubyte literal")
|
||||
val initializerValue = decl.value as NumericLiteralValue
|
||||
assertEquals(DataType.UBYTE, initializerValue.type)
|
||||
assertEquals(platform.encodeString("\n", false)[0], initializerValue.number.toShort())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCharConstAsRomsubArg() {
|
||||
val platform = Cx16Target
|
||||
val result = compileText(platform, false, """
|
||||
main {
|
||||
romsub ${"$"}FFD2 = chrout(ubyte ch @ A)
|
||||
sub start() {
|
||||
const ubyte ch = '\n'
|
||||
chrout(ch)
|
||||
}
|
||||
}
|
||||
""").assertSuccess()
|
||||
|
||||
val program = result.programAst
|
||||
val startSub = program.entrypoint
|
||||
val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0]
|
||||
|
||||
// Now, both is ok for the arg: a) still the IdRef or b) replaced by numeric literal
|
||||
when (val arg = funCall.args[0]) {
|
||||
is IdentifierReference -> {
|
||||
val decl = arg.targetVarDecl(program)!!
|
||||
assertEquals(VarDeclType.CONST, decl.type)
|
||||
assertEquals(DataType.UBYTE, decl.datatype)
|
||||
assertEquals(
|
||||
platform.encodeString("\n", false)[0],
|
||||
(decl.value as NumericLiteralValue).number.toShort())
|
||||
}
|
||||
is NumericLiteralValue -> {
|
||||
assertEquals(
|
||||
platform.encodeString("\n", false)[0],
|
||||
arg.number.toShort())
|
||||
}
|
||||
else -> assertIs<IdentifierReference>(funCall.args[0]) // make test fail
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.Disabled
|
||||
import org.junit.jupiter.api.DynamicTest
|
||||
import org.junit.jupiter.api.DynamicTest.dynamicTest
|
||||
import org.junit.jupiter.api.TestFactory
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import prog8.compiler.compileProgram
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8.compiler.target.ICompilationTarget
|
||||
import prog8tests.helpers.*
|
||||
import kotlin.io.path.absolute
|
||||
import kotlin.io.path.exists
|
||||
|
||||
|
||||
/**
|
||||
* ATTENTION: this is just kludge!
|
||||
* They are not really unit tests, but rather tests of the whole process,
|
||||
* from source file loading all the way through to running 64tass.
|
||||
*/
|
||||
// @Disabled("disable to save some time")
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestCompilerOnExamples {
|
||||
private val examplesDir = assumeDirectory(workingDir, "../examples")
|
||||
|
||||
private fun makeDynamicCompilerTest(name: String, platform: ICompilationTarget, optimize: Boolean) : DynamicTest {
|
||||
val searchIn = mutableListOf(examplesDir)
|
||||
if (platform == Cx16Target) {
|
||||
searchIn.add(0, assumeDirectory(examplesDir, "cx16"))
|
||||
}
|
||||
val filepath = searchIn
|
||||
.map { it.resolve("$name.p8") }
|
||||
.map { it.normalize().absolute() }
|
||||
.map { workingDir.relativize(it) }
|
||||
.first { it.exists() }
|
||||
val displayName = "${examplesDir.relativize(filepath.absolute())}: ${platform.name}, optimize=$optimize"
|
||||
return dynamicTest(displayName) {
|
||||
compileProgram(
|
||||
filepath,
|
||||
optimize,
|
||||
writeAssembly = true,
|
||||
slowCodegenWarnings = false,
|
||||
compilationTarget = platform.name,
|
||||
sourceDirs = listOf(),
|
||||
outputDir
|
||||
).assertSuccess("; $displayName")
|
||||
}
|
||||
}
|
||||
|
||||
@TestFactory
|
||||
// @Disabled("disable to save some time")
|
||||
fun bothCx16AndC64() = mapCombinations(
|
||||
dim1 = listOf(
|
||||
"animals",
|
||||
"balls",
|
||||
"cube3d",
|
||||
"cube3d-float",
|
||||
"cube3d-gfx",
|
||||
"cxlogo",
|
||||
"dirlist",
|
||||
"fibonacci",
|
||||
"line-circle-gfx",
|
||||
"line-circle-txt",
|
||||
"mandelbrot",
|
||||
"mandelbrot-gfx",
|
||||
"numbergame",
|
||||
"primes",
|
||||
"rasterbars",
|
||||
"screencodes",
|
||||
"sorting",
|
||||
"swirl",
|
||||
"swirl-float",
|
||||
"tehtriz",
|
||||
"textelite",
|
||||
),
|
||||
dim2 = listOf(Cx16Target, C64Target),
|
||||
dim3 = listOf(false, true),
|
||||
combine3 = ::makeDynamicCompilerTest
|
||||
)
|
||||
|
||||
@TestFactory
|
||||
// @Disabled("disable to save some time")
|
||||
fun onlyC64() = mapCombinations(
|
||||
dim1 = listOf(
|
||||
"balloonflight",
|
||||
"bdmusic",
|
||||
"bdmusic-irq",
|
||||
"charset",
|
||||
"cube3d-sprites",
|
||||
"plasma",
|
||||
"sprites",
|
||||
"turtle-gfx",
|
||||
"wizzine",
|
||||
),
|
||||
dim2 = listOf(C64Target),
|
||||
dim3 = listOf(false, true),
|
||||
combine3 = ::makeDynamicCompilerTest
|
||||
)
|
||||
|
||||
@TestFactory
|
||||
// @Disabled("disable to save some time")
|
||||
fun onlyCx16() = mapCombinations(
|
||||
dim1 = listOf(
|
||||
"vtui/testvtui",
|
||||
"amiga",
|
||||
"bobs",
|
||||
"cobramk3-gfx",
|
||||
"colorbars",
|
||||
"datetime",
|
||||
"highresbitmap",
|
||||
"kefrenbars",
|
||||
"mandelbrot-gfx-colors",
|
||||
"multipalette",
|
||||
"testgfx2",
|
||||
),
|
||||
dim2 = listOf(Cx16Target),
|
||||
dim3 = listOf(false, true),
|
||||
combine3 = ::makeDynamicCompilerTest
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.*
|
||||
import org.junit.jupiter.api.DynamicTest.dynamicTest
|
||||
import prog8.ast.expressions.AddressOf
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.StringLiteralValue
|
||||
import prog8.ast.statements.FunctionCallStatement
|
||||
import prog8.ast.statements.Label
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8tests.helpers.*
|
||||
import kotlin.io.path.name
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotEquals
|
||||
|
||||
|
||||
/**
|
||||
* ATTENTION: this is just kludge!
|
||||
* They are not really unit tests, but rather tests of the whole process,
|
||||
* from source file loading all the way through to running 64tass.
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestCompilerOnImportsAndIncludes {
|
||||
|
||||
@Nested
|
||||
inner class Import {
|
||||
|
||||
@Test
|
||||
fun testImportFromSameFolder() {
|
||||
val filepath = assumeReadableFile(fixturesDir, "importFromSameFolder.p8")
|
||||
assumeReadableFile(fixturesDir, "foo_bar.p8")
|
||||
|
||||
val platform = Cx16Target
|
||||
val result = compileFile(platform, optimize = false, fixturesDir, filepath.name)
|
||||
.assertSuccess()
|
||||
|
||||
val program = result.programAst
|
||||
val startSub = program.entrypoint
|
||||
val strLits = startSub.statements
|
||||
.filterIsInstance<FunctionCallStatement>()
|
||||
.map { it.args[0] as IdentifierReference }
|
||||
.map { it.targetVarDecl(program)!!.value as StringLiteralValue }
|
||||
|
||||
assertEquals("main.bar", strLits[0].value)
|
||||
assertEquals("foo.bar", strLits[1].value)
|
||||
assertEquals("main", strLits[0].definingScope.name)
|
||||
assertEquals("foo", strLits[1].definingScope.name)
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class AsmInclude {
|
||||
@Test
|
||||
fun testAsmIncludeFromSameFolder() {
|
||||
val filepath = assumeReadableFile(fixturesDir, "asmIncludeFromSameFolder.p8")
|
||||
assumeReadableFile(fixturesDir, "foo_bar.asm")
|
||||
|
||||
val platform = Cx16Target
|
||||
val result = compileFile(platform, optimize = false, fixturesDir, filepath.name)
|
||||
.assertSuccess()
|
||||
|
||||
val program = result.programAst
|
||||
val startSub = program.entrypoint
|
||||
val args = startSub.statements
|
||||
.filterIsInstance<FunctionCallStatement>()
|
||||
.map { it.args[0] }
|
||||
|
||||
val str0 = (args[0] as IdentifierReference).targetVarDecl(program)!!.value as StringLiteralValue
|
||||
assertEquals("main.bar", str0.value)
|
||||
assertEquals("main", str0.definingScope.name)
|
||||
|
||||
val id1 = (args[1] as AddressOf).identifier
|
||||
val lbl1 = id1.targetStatement(program) as Label
|
||||
assertEquals("foo_bar", lbl1.name)
|
||||
assertEquals("start", lbl1.definingScope.name)
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class Asmbinary {
|
||||
@Test
|
||||
fun testAsmbinaryDirectiveWithNonExistingFile() {
|
||||
val p8Path = assumeReadableFile(fixturesDir, "asmBinaryNonExisting.p8")
|
||||
assumeNotExists(fixturesDir, "i_do_not_exist.bin")
|
||||
|
||||
compileFile(Cx16Target, false, p8Path.parent, p8Path.name, outputDir)
|
||||
.assertFailure()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAsmbinaryDirectiveWithNonReadableFile() {
|
||||
val p8Path = assumeReadableFile(fixturesDir, "asmBinaryNonReadable.p8")
|
||||
assumeDirectory(fixturesDir, "subFolder")
|
||||
|
||||
compileFile(Cx16Target, false, p8Path.parent, p8Path.name, outputDir)
|
||||
.assertFailure()
|
||||
}
|
||||
|
||||
@TestFactory
|
||||
fun asmbinaryDirectiveWithExistingBinFile(): Iterable<DynamicTest> =
|
||||
listOf(
|
||||
Triple("same ", "asmBinaryFromSameFolder.p8", "do_nothing1.bin"),
|
||||
Triple("sub", "asmBinaryFromSubFolder.p8", "subFolder/do_nothing2.bin"),
|
||||
).map {
|
||||
val (where, p8Str, binStr) = it
|
||||
dynamicTest("%asmbinary from ${where}folder") {
|
||||
val p8Path = assumeReadableFile(fixturesDir, p8Str)
|
||||
val binPath = assumeReadableFile(fixturesDir, binStr)
|
||||
assertNotEquals( // the bug we're testing for (#54) was hidden if outputDir == workinDir
|
||||
workingDir.normalize().toAbsolutePath(),
|
||||
outputDir.normalize().toAbsolutePath(),
|
||||
"sanity check: workingDir and outputDir should not be the same folder"
|
||||
)
|
||||
|
||||
compileFile(Cx16Target, false, p8Path.parent, p8Path.name, outputDir)
|
||||
.assertSuccess(
|
||||
"argument to assembler directive .binary " +
|
||||
"should be relative to the generated .asm file (in output dir), " +
|
||||
"NOT relative to .p8 neither current working dir"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.DynamicTest.dynamicTest
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestFactory
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.ForLoop
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.compiler.astprocessing.size
|
||||
import prog8.compiler.astprocessing.toConstantIntegerRange
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8tests.helpers.assertFailure
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import prog8tests.helpers.compileText
|
||||
import prog8tests.helpers.mapCombinations
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
|
||||
/**
|
||||
* ATTENTION: this is just kludge!
|
||||
* They are not really unit tests, but rather tests of the whole process,
|
||||
* from source file loading all the way through to running 64tass.
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestCompilerOnRanges {
|
||||
|
||||
@Test
|
||||
fun testUByteArrayInitializerWithRange_char_to_char() {
|
||||
val platform = Cx16Target
|
||||
val result = compileText(platform, true, """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte[] cs = @'a' to 'z' ; values are computed at compile time
|
||||
cs[0] = 23 ; keep optimizer from removing it
|
||||
}
|
||||
}
|
||||
""").assertSuccess()
|
||||
|
||||
val program = result.programAst
|
||||
val startSub = program.entrypoint
|
||||
val decl = startSub
|
||||
.statements.filterIsInstance<VarDecl>()[0]
|
||||
val rhsValues = (decl.value as ArrayLiteralValue)
|
||||
.value // Array<Expression>
|
||||
.map { (it as NumericLiteralValue).number.toInt() }
|
||||
val expectedStart = platform.encodeString("a", true)[0].toInt()
|
||||
val expectedEnd = platform.encodeString("z", false)[0].toInt()
|
||||
val expectedStr = "$expectedStart .. $expectedEnd"
|
||||
|
||||
val actualStr = "${rhsValues.first()} .. ${rhsValues.last()}"
|
||||
assertEquals(expectedStr, actualStr,".first .. .last")
|
||||
assertEquals(expectedEnd - expectedStart + 1, rhsValues.last() - rhsValues.first() + 1, "rangeExpr.size()")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFloatArrayInitializerWithRange_char_to_char() {
|
||||
val platform = C64Target
|
||||
val result = compileText(platform, optimize = false, """
|
||||
%option enable_floats
|
||||
main {
|
||||
sub start() {
|
||||
float[] cs = 'a' to 'z' ; values are computed at compile time
|
||||
cs[0] = 23 ; keep optimizer from removing it
|
||||
}
|
||||
}
|
||||
""").assertSuccess()
|
||||
|
||||
val program = result.programAst
|
||||
val startSub = program.entrypoint
|
||||
val decl = startSub
|
||||
.statements.filterIsInstance<VarDecl>()[0]
|
||||
val rhsValues = (decl.value as ArrayLiteralValue)
|
||||
.value // Array<Expression>
|
||||
.map { (it as NumericLiteralValue).number.toInt() }
|
||||
val expectedStart = platform.encodeString("a", false)[0].toInt()
|
||||
val expectedEnd = platform.encodeString("z", false)[0].toInt()
|
||||
val expectedStr = "$expectedStart .. $expectedEnd"
|
||||
|
||||
val actualStr = "${rhsValues.first()} .. ${rhsValues.last()}"
|
||||
assertEquals(expectedStr, actualStr,".first .. .last")
|
||||
assertEquals(expectedEnd - expectedStart + 1, rhsValues.size, "rangeExpr.size()")
|
||||
}
|
||||
|
||||
fun Subroutine.decl(varName: String): VarDecl {
|
||||
return statements.filterIsInstance<VarDecl>()
|
||||
.first { it.name == varName }
|
||||
}
|
||||
inline fun <reified T : Expression> VarDecl.rhs() : T {
|
||||
return value as T
|
||||
}
|
||||
inline fun <reified T : Expression> ArrayLiteralValue.elements() : List<T> {
|
||||
return value.map { it as T }
|
||||
}
|
||||
|
||||
fun <N : Number> assertEndpoints(expFirst: N, expLast: N, actual: Iterable<N>, msg: String = ".first .. .last") {
|
||||
val expectedStr = "$expFirst .. $expLast"
|
||||
val actualStr = "${actual.first()} .. ${actual.last()}"
|
||||
assertEquals(expectedStr, actualStr,".first .. .last")
|
||||
}
|
||||
|
||||
|
||||
@TestFactory
|
||||
fun floatArrayInitializerWithRange() = mapCombinations(
|
||||
dim1 = listOf("", "42", "41"), // sizeInDecl
|
||||
dim2 = listOf("%option enable_floats", ""), // optEnableFloats
|
||||
dim3 = listOf(Cx16Target, C64Target), // platform
|
||||
dim4 = listOf(false, true), // optimize
|
||||
combine4 = { sizeInDecl, optEnableFloats, platform, optimize ->
|
||||
val displayName =
|
||||
"test failed for: " +
|
||||
when (sizeInDecl) {
|
||||
"" -> "no"
|
||||
"42" -> "correct"
|
||||
else -> "wrong"
|
||||
} + " array size given" +
|
||||
", " + (if (optEnableFloats == "") "without" else "with") + " %option enable_floats" +
|
||||
", ${platform.name}, optimize: $optimize"
|
||||
dynamicTest(displayName) {
|
||||
val result = compileText(platform, optimize, """
|
||||
$optEnableFloats
|
||||
main {
|
||||
sub start() {
|
||||
float[$sizeInDecl] cs = 1 to 42 ; values are computed at compile time
|
||||
cs[0] = 23 ; keep optimizer from removing it
|
||||
}
|
||||
}
|
||||
""")
|
||||
if (optEnableFloats != "" && (sizeInDecl=="" || sizeInDecl=="42"))
|
||||
result.assertSuccess()
|
||||
else
|
||||
result.assertFailure()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@Test
|
||||
fun testForLoopWithRange_char_to_char() {
|
||||
val platform = Cx16Target
|
||||
val result = compileText(platform, optimize = true, """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte i
|
||||
for i in @'a' to 'f' {
|
||||
i += i ; keep optimizer from removing it
|
||||
}
|
||||
}
|
||||
}
|
||||
""").assertSuccess()
|
||||
|
||||
val program = result.programAst
|
||||
val startSub = program.entrypoint
|
||||
val iterable = startSub
|
||||
.statements.filterIsInstance<ForLoop>()
|
||||
.map { it.iterable }[0]
|
||||
val rangeExpr = iterable as RangeExpr
|
||||
|
||||
val expectedStart = platform.encodeString("a", true)[0].toInt()
|
||||
val expectedEnd = platform.encodeString("f", false)[0].toInt()
|
||||
val expectedStr = "$expectedStart .. $expectedEnd"
|
||||
|
||||
val intProgression = rangeExpr.toConstantIntegerRange(platform)
|
||||
val actualStr = "${intProgression?.first} .. ${intProgression?.last}"
|
||||
assertEquals(expectedStr, actualStr,".first .. .last")
|
||||
assertEquals(expectedEnd - expectedStart + 1, rangeExpr.size(platform), "rangeExpr.size()")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testForLoopWithRange_bool_to_bool() {
|
||||
val platform = Cx16Target
|
||||
val result = compileText(platform, optimize = true, """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte i
|
||||
for i in false to true {
|
||||
i += i ; keep optimizer from removing it
|
||||
}
|
||||
}
|
||||
}
|
||||
""").assertSuccess()
|
||||
|
||||
val program = result.programAst
|
||||
val startSub = program.entrypoint
|
||||
val rangeExpr = startSub
|
||||
.statements.filterIsInstance<ForLoop>()
|
||||
.map { it.iterable }
|
||||
.filterIsInstance<RangeExpr>()[0]
|
||||
|
||||
assertEquals(2, rangeExpr.size(platform))
|
||||
val intProgression = rangeExpr.toConstantIntegerRange(platform)
|
||||
assertEquals(0, intProgression?.first)
|
||||
assertEquals(1, intProgression?.last)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testForLoopWithRange_ubyte_to_ubyte() {
|
||||
val platform = Cx16Target
|
||||
val result = compileText(platform, optimize = true, """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte i
|
||||
for i in 1 to 9 {
|
||||
i += i ; keep optimizer from removing it
|
||||
}
|
||||
}
|
||||
}
|
||||
""").assertSuccess()
|
||||
|
||||
val program = result.programAst
|
||||
val startSub = program.entrypoint
|
||||
val rangeExpr = startSub
|
||||
.statements.filterIsInstance<ForLoop>()
|
||||
.map { it.iterable }
|
||||
.filterIsInstance<RangeExpr>()[0]
|
||||
|
||||
assertEquals(9, rangeExpr.size(platform))
|
||||
val intProgression = rangeExpr.toConstantIntegerRange(platform)
|
||||
assertEquals(1, intProgression?.first)
|
||||
assertEquals(9, intProgression?.last)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testForLoopWithRange_str_downto_str() {
|
||||
compileText(Cx16Target, true, """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte i
|
||||
for i in "start" downto "end" {
|
||||
i += i ; keep optimizer from removing it
|
||||
}
|
||||
}
|
||||
}
|
||||
""").assertFailure()
|
||||
//TODO("test exact compile error(s)")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testForLoopWithIterable_str() {
|
||||
val result = compileText(Cx16Target, false, """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte i
|
||||
for i in "something" {
|
||||
i += i ; keep optimizer from removing it
|
||||
}
|
||||
}
|
||||
}
|
||||
""").assertSuccess()
|
||||
|
||||
val program = result.programAst
|
||||
val startSub = program.entrypoint
|
||||
val iterable = startSub
|
||||
.statements.filterIsInstance<ForLoop>()
|
||||
.map { it.iterable }
|
||||
.filterIsInstance<IdentifierReference>()[0]
|
||||
|
||||
assertEquals(DataType.STR, iterable.inferType(program).getOr(DataType.UNDEFINED))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.AfterAll
|
||||
import org.junit.jupiter.api.BeforeAll
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import prog8.compiler.compileProgram
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8tests.helpers.*
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.absolute
|
||||
import kotlin.io.path.createTempFile
|
||||
import kotlin.io.path.deleteExisting
|
||||
import kotlin.io.path.writeText
|
||||
|
||||
/**
|
||||
* ATTENTION: this is just kludge!
|
||||
* They are not really unit tests, but rather tests of the whole process,
|
||||
* from source file loading all the way through to running 64tass.
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestCompilerOptionSourcedirs {
|
||||
|
||||
private lateinit var tempFileInWorkingDir: Path
|
||||
|
||||
@BeforeAll
|
||||
fun setUp() {
|
||||
tempFileInWorkingDir = createTempFile(directory = workingDir, prefix = "tmp_", suffix = ".p8")
|
||||
.also { it.writeText("""
|
||||
main {
|
||||
sub start() {
|
||||
}
|
||||
}
|
||||
""")}
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
fun tearDown() {
|
||||
tempFileInWorkingDir.deleteExisting()
|
||||
}
|
||||
|
||||
private fun compileFile(filePath: Path, sourceDirs: List<String>) =
|
||||
compileProgram(
|
||||
filepath = filePath,
|
||||
optimize = false,
|
||||
writeAssembly = true,
|
||||
slowCodegenWarnings = false,
|
||||
compilationTarget = Cx16Target.name,
|
||||
sourceDirs,
|
||||
outputDir
|
||||
)
|
||||
|
||||
@Test
|
||||
fun testAbsoluteFilePathInWorkingDir() {
|
||||
val filepath = assumeReadableFile(tempFileInWorkingDir.absolute())
|
||||
compileFile(filepath, listOf())
|
||||
.assertSuccess()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFilePathInWorkingDirRelativeToWorkingDir() {
|
||||
val filepath = assumeReadableFile(workingDir.relativize(tempFileInWorkingDir.absolute()))
|
||||
compileFile(filepath, listOf())
|
||||
.assertSuccess()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFilePathInWorkingDirRelativeTo1stInSourcedirs() {
|
||||
val filepath = assumeReadableFile(tempFileInWorkingDir)
|
||||
compileFile(filepath.fileName, listOf(workingDir.toString()))
|
||||
.assertSuccess()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAbsoluteFilePathOutsideWorkingDir() {
|
||||
val filepath = assumeReadableFile(fixturesDir, "simple_main.p8")
|
||||
compileFile(filepath.absolute(), listOf())
|
||||
.assertSuccess()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFilePathOutsideWorkingDirRelativeToWorkingDir() {
|
||||
val filepath = workingDir.relativize(assumeReadableFile(fixturesDir, "simple_main.p8").absolute())
|
||||
compileFile(filepath, listOf())
|
||||
.assertSuccess()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFilePathOutsideWorkingDirRelativeTo1stInSourcedirs() {
|
||||
val filepath = assumeReadableFile(fixturesDir, "simple_main.p8")
|
||||
val sourcedirs = listOf("$fixturesDir")
|
||||
compileFile(filepath.fileName, sourcedirs)
|
||||
.assertSuccess()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import prog8.ast.internedStringsModuleName
|
||||
import prog8.compiler.ErrorReporter
|
||||
import prog8.compiler.ZeropageType
|
||||
import prog8.compiler.determineCompilationOptions
|
||||
import prog8.compiler.parseImports
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8tests.helpers.assertSuccess
|
||||
import prog8tests.helpers.compileText
|
||||
import prog8tests.helpers.outputDir
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestImportedModulesOrderAndOptions {
|
||||
|
||||
@Test
|
||||
fun testImportedModuleOrderAndMainModuleCorrect() {
|
||||
val result = compileText(C64Target, false, """
|
||||
%import textio
|
||||
%import floats
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
; nothing
|
||||
}
|
||||
}
|
||||
""").assertSuccess()
|
||||
assertTrue(result.programAst.toplevelModule.name.startsWith("on_the_fly_test"))
|
||||
|
||||
val moduleNames = result.programAst.modules.map { it.name }
|
||||
assertTrue(moduleNames[0].startsWith("on_the_fly_test"), "main module must be first")
|
||||
assertEquals(listOf(
|
||||
"prog8_interned_strings",
|
||||
"textio",
|
||||
"syslib",
|
||||
"conv",
|
||||
"floats",
|
||||
"math",
|
||||
"prog8_lib"
|
||||
), moduleNames.drop(1), "module order in parse tree")
|
||||
|
||||
assertTrue(result.programAst.toplevelModule.name.startsWith("on_the_fly_test"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCompilationOptionsCorrectFromMain() {
|
||||
val result = compileText(C64Target, false, """
|
||||
%import textio
|
||||
%import floats
|
||||
%zeropage dontuse
|
||||
%option no_sysinit
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
; nothing
|
||||
}
|
||||
}
|
||||
""").assertSuccess()
|
||||
assertTrue(result.programAst.toplevelModule.name.startsWith("on_the_fly_test"))
|
||||
val options = determineCompilationOptions(result.programAst, C64Target)
|
||||
assertTrue(options.floats)
|
||||
assertEquals(ZeropageType.DONTUSE, options.zeropage)
|
||||
assertTrue(options.noSysInit)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testModuleOrderAndCompilationOptionsCorrectWithJustImports() {
|
||||
val errors = ErrorReporter()
|
||||
val sourceText = """
|
||||
%import textio
|
||||
%import floats
|
||||
%option no_sysinit
|
||||
%zeropage dontuse
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
; nothing
|
||||
}
|
||||
}
|
||||
"""
|
||||
val filenameBase = "on_the_fly_test_" + sourceText.hashCode().toUInt().toString(16)
|
||||
val filepath = outputDir.resolve("$filenameBase.p8")
|
||||
filepath.toFile().writeText(sourceText)
|
||||
val (program, options, importedfiles) = parseImports(filepath, errors, C64Target, emptyList())
|
||||
|
||||
assertEquals(filenameBase, program.toplevelModule.name)
|
||||
assertEquals(1, importedfiles.size, "all imports other than the test source must have been internal resources library files")
|
||||
assertEquals(listOf(
|
||||
internedStringsModuleName,
|
||||
filenameBase,
|
||||
"textio", "syslib", "conv", "floats", "math", "prog8_lib"
|
||||
), program.modules.map {it.name}, "module order in parse tree")
|
||||
assertTrue(options.floats)
|
||||
assertEquals(ZeropageType.DONTUSE, options.zeropage, "zeropage option must be correctly taken from main module, not from float module import logic")
|
||||
assertTrue(options.noSysInit)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
package prog8tests
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.expressions.ArrayIndexedExpression
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.expressions.PrefixExpression
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.astprocessing.isInRegularRAMof
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.parser.SourceCode
|
||||
import prog8tests.helpers.DummyFunctions
|
||||
import prog8tests.helpers.DummyMemsizer
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestMemory {
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_memory_addresses() {
|
||||
|
||||
var memexpr = NumericLiteralValue.optimalInteger(0x0000, Position.DUMMY)
|
||||
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0x9fff, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xc000, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xcfff, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotInValidRamC64_memory_addresses() {
|
||||
|
||||
var memexpr = NumericLiteralValue.optimalInteger(0xa000, Position.DUMMY)
|
||||
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assertFalse(target.isInRegularRAMof(C64Target.machine))
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xafff, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assertFalse(target.isInRegularRAMof(C64Target.machine))
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xd000, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assertFalse(target.isInRegularRAMof(C64Target.machine))
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xffff, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assertFalse(target.isInRegularRAMof(C64Target.machine))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_memory_identifiers() {
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer)
|
||||
var target = createTestProgramForMemoryRefViaVar(program, 0x1000, VarDeclType.VAR)
|
||||
|
||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
||||
target = createTestProgramForMemoryRefViaVar(program, 0xd020, VarDeclType.VAR)
|
||||
assertFalse(target.isInRegularRAMof(C64Target.machine))
|
||||
target = createTestProgramForMemoryRefViaVar(program, 0x1000, VarDeclType.CONST)
|
||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
||||
target = createTestProgramForMemoryRefViaVar(program, 0xd020, VarDeclType.CONST)
|
||||
assertFalse(target.isInRegularRAMof(C64Target.machine))
|
||||
target = createTestProgramForMemoryRefViaVar(program, 0x1000, VarDeclType.MEMORY)
|
||||
assertFalse(target.isInRegularRAMof(C64Target.machine))
|
||||
}
|
||||
|
||||
private fun createTestProgramForMemoryRefViaVar(program: Program, address: Int, vartype: VarDeclType): AssignTarget {
|
||||
val decl = VarDecl(vartype, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
|
||||
val memexpr = IdentifierReference(listOf("address"), Position.DUMMY)
|
||||
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||
module.linkIntoProgram(program)
|
||||
return target
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_memory_expression() {
|
||||
val memexpr = PrefixExpression("+", NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY), Position.DUMMY)
|
||||
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
assertFalse(target.isInRegularRAMof(C64Target.machine))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_variable() {
|
||||
val decl = VarDecl(VarDeclType.VAR, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, false, false, false, Position.DUMMY)
|
||||
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer)
|
||||
.addModule(module)
|
||||
module.linkIntoProgram(program)
|
||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_memmap_variable() {
|
||||
val address = 0x1000
|
||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
|
||||
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer)
|
||||
.addModule(module)
|
||||
module.linkIntoProgram(program)
|
||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotInValidRamC64_memmap_variable() {
|
||||
val address = 0xd020
|
||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
|
||||
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer)
|
||||
.addModule(module)
|
||||
module.linkIntoProgram(program)
|
||||
assertFalse(target.isInRegularRAMof(C64Target.machine))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_array() {
|
||||
val decl = VarDecl(VarDeclType.VAR, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, false, false, false, Position.DUMMY)
|
||||
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer)
|
||||
.addModule(module)
|
||||
module.linkIntoProgram(program)
|
||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_array_memmapped() {
|
||||
val address = 0x1000
|
||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
|
||||
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer)
|
||||
.addModule(module)
|
||||
module.linkIntoProgram(program)
|
||||
assertTrue(target.isInRegularRAMof(C64Target.machine))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotValidRamC64_array_memmapped() {
|
||||
val address = 0xe000
|
||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, false, Position.DUMMY)
|
||||
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
val module = Module(mutableListOf(subroutine), Position.DUMMY, SourceCode.Generated("test"))
|
||||
val program = Program("test", DummyFunctions, DummyMemsizer)
|
||||
.addModule(module)
|
||||
module.linkIntoProgram(program)
|
||||
assertFalse(target.isInRegularRAMof(C64Target.machine))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package prog8tests
|
||||
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.Matchers.closeTo
|
||||
import org.hamcrest.Matchers.equalTo
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import prog8.ast.toHex
|
||||
import prog8.compiler.CompilerException
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_NEGATIVE
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_POSITIVE
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.Mflpt5
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestNumbers {
|
||||
@Test
|
||||
fun testToHex() {
|
||||
assertEquals("0", 0.toHex())
|
||||
assertEquals("1", 1.toHex())
|
||||
assertEquals("1", 1.234.toHex())
|
||||
assertEquals("10", 10.toHex())
|
||||
assertEquals("10", 10.99.toHex())
|
||||
assertEquals("15", 15.toHex())
|
||||
assertEquals("\$10", 16.toHex())
|
||||
assertEquals("\$ff", 255.toHex())
|
||||
assertEquals("\$0100", 256.toHex())
|
||||
assertEquals("\$4e5c", 20060.toHex())
|
||||
assertEquals("\$c382", 50050.toHex())
|
||||
assertEquals("\$ffff", 65535.toHex())
|
||||
assertEquals("\$ffff", 65535L.toHex())
|
||||
assertEquals("0", 0.toHex())
|
||||
assertEquals("-1", (-1).toHex())
|
||||
assertEquals("-1", (-1.234).toHex())
|
||||
assertEquals("-10", (-10).toHex())
|
||||
assertEquals("-10", (-10.99).toHex())
|
||||
assertEquals("-15", (-15).toHex())
|
||||
assertEquals("-\$10", (-16).toHex())
|
||||
assertEquals("-\$ff", (-255).toHex())
|
||||
assertEquals("-\$0100", (-256).toHex())
|
||||
assertEquals("-\$4e5c", (-20060).toHex())
|
||||
assertEquals("-\$c382", (-50050).toHex())
|
||||
assertEquals("-\$ffff", (-65535).toHex())
|
||||
assertEquals("-\$ffff", (-65535L).toHex())
|
||||
assertFailsWith<IllegalArgumentException> { 65536.toHex() }
|
||||
assertFailsWith<IllegalArgumentException> { 65536L.toHex() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFloatToMflpt5() {
|
||||
assertThat(Mflpt5.fromNumber(0), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)))
|
||||
assertThat(Mflpt5.fromNumber(3.141592653), equalTo(Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA1)))
|
||||
assertThat(Mflpt5.fromNumber(3.141592653589793), equalTo(Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA2)))
|
||||
assertThat(Mflpt5.fromNumber(32768), equalTo(Mflpt5(0x90, 0x00, 0x00, 0x00, 0x00)))
|
||||
assertThat(Mflpt5.fromNumber(-32768), equalTo(Mflpt5(0x90, 0x80, 0x00, 0x00, 0x00)))
|
||||
assertThat(Mflpt5.fromNumber(1), equalTo(Mflpt5(0x81, 0x00, 0x00, 0x00, 0x00)))
|
||||
assertThat(Mflpt5.fromNumber(0.7071067812), equalTo(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x34)))
|
||||
assertThat(Mflpt5.fromNumber(0.7071067811865476), equalTo(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x33)))
|
||||
assertThat(Mflpt5.fromNumber(1.4142135624), equalTo(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x34)))
|
||||
assertThat(Mflpt5.fromNumber(1.4142135623730951), equalTo(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x33)))
|
||||
assertThat(Mflpt5.fromNumber(-.5), equalTo(Mflpt5(0x80, 0x80, 0x00, 0x00, 0x00)))
|
||||
assertThat(Mflpt5.fromNumber(0.69314718061), equalTo(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF8)))
|
||||
assertThat(Mflpt5.fromNumber(0.6931471805599453), equalTo(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF7)))
|
||||
assertThat(Mflpt5.fromNumber(10), equalTo(Mflpt5(0x84, 0x20, 0x00, 0x00, 0x00)))
|
||||
assertThat(Mflpt5.fromNumber(1000000000), equalTo(Mflpt5(0x9E, 0x6E, 0x6B, 0x28, 0x00)))
|
||||
assertThat(Mflpt5.fromNumber(.5), equalTo(Mflpt5(0x80, 0x00, 0x00, 0x00, 0x00)))
|
||||
assertThat(Mflpt5.fromNumber(1.4426950408889634), equalTo(Mflpt5(0x81, 0x38, 0xAA, 0x3B, 0x29)))
|
||||
assertThat(Mflpt5.fromNumber(1.5707963267948966), equalTo(Mflpt5(0x81, 0x49, 0x0F, 0xDA, 0xA2)))
|
||||
assertThat(Mflpt5.fromNumber(6.283185307179586), equalTo(Mflpt5(0x83, 0x49, 0x0F, 0xDA, 0xA2)))
|
||||
assertThat(Mflpt5.fromNumber(.25), equalTo(Mflpt5(0x7F, 0x00, 0x00, 0x00, 0x00)))
|
||||
assertThat(Mflpt5.fromNumber(123.45678e22), equalTo(Mflpt5(0xd1, 0x02, 0xb7, 0x06, 0xfb)))
|
||||
assertThat(Mflpt5.fromNumber(-123.45678e-22), equalTo(Mflpt5(0x3e, 0xe9, 0x34, 0x09, 0x1b)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFloatRange() {
|
||||
assertThat(Mflpt5.fromNumber(FLOAT_MAX_POSITIVE), equalTo(Mflpt5(0xff, 0x7f, 0xff, 0xff, 0xff)))
|
||||
assertThat(Mflpt5.fromNumber(FLOAT_MAX_NEGATIVE), equalTo(Mflpt5(0xff, 0xff, 0xff, 0xff, 0xff)))
|
||||
assertThat(Mflpt5.fromNumber(1.7e-38), equalTo(Mflpt5(0x03, 0x39, 0x1d, 0x15, 0x63)))
|
||||
assertThat(Mflpt5.fromNumber(1.7e-39), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)))
|
||||
assertThat(Mflpt5.fromNumber(-1.7e-38), equalTo(Mflpt5(0x03, 0xb9, 0x1d, 0x15, 0x63)))
|
||||
assertThat(Mflpt5.fromNumber(-1.7e-39), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)))
|
||||
assertFailsWith<CompilerException> { Mflpt5.fromNumber(1.7014118346e+38) }
|
||||
assertFailsWith<CompilerException> { Mflpt5.fromNumber(-1.7014118346e+38) }
|
||||
assertFailsWith<CompilerException> { Mflpt5.fromNumber(1.7014118347e+38) }
|
||||
assertFailsWith<CompilerException> { Mflpt5.fromNumber(-1.7014118347e+38) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMflpt5ToFloat() {
|
||||
val epsilon=0.000000001
|
||||
assertThat(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(0.0))
|
||||
assertThat(Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA1).toDouble(), closeTo(3.141592653, epsilon))
|
||||
assertThat(Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA2).toDouble(), closeTo(3.141592653589793, epsilon))
|
||||
assertThat(Mflpt5(0x90, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(32768.0))
|
||||
assertThat(Mflpt5(0x90, 0x80, 0x00, 0x00, 0x00).toDouble(), equalTo(-32768.0))
|
||||
assertThat(Mflpt5(0x81, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(1.0))
|
||||
assertThat(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x34).toDouble(), closeTo(0.7071067812, epsilon))
|
||||
assertThat(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x33).toDouble(), closeTo(0.7071067811865476, epsilon))
|
||||
assertThat(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x34).toDouble(), closeTo(1.4142135624, epsilon))
|
||||
assertThat(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x33).toDouble(), closeTo(1.4142135623730951, epsilon))
|
||||
assertThat(Mflpt5(0x80, 0x80, 0x00, 0x00, 0x00).toDouble(), equalTo(-.5))
|
||||
assertThat(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF8).toDouble(), closeTo(0.69314718061, epsilon))
|
||||
assertThat(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF7).toDouble(), closeTo(0.6931471805599453, epsilon))
|
||||
assertThat(Mflpt5(0x84, 0x20, 0x00, 0x00, 0x00).toDouble(), equalTo(10.0))
|
||||
assertThat(Mflpt5(0x9E, 0x6E, 0x6B, 0x28, 0x00).toDouble(), equalTo(1000000000.0))
|
||||
assertThat(Mflpt5(0x80, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(.5))
|
||||
assertThat(Mflpt5(0x81, 0x38, 0xAA, 0x3B, 0x29).toDouble(), closeTo(1.4426950408889634, epsilon))
|
||||
assertThat(Mflpt5(0x81, 0x49, 0x0F, 0xDA, 0xA2).toDouble(), closeTo(1.5707963267948966, epsilon))
|
||||
assertThat(Mflpt5(0x83, 0x49, 0x0F, 0xDA, 0xA2).toDouble(), closeTo(6.283185307179586, epsilon))
|
||||
assertThat(Mflpt5(0x7F, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(.25))
|
||||
assertThat(Mflpt5(0xd1, 0x02, 0xb7, 0x06, 0xfb).toDouble(), closeTo(123.45678e22, 1.0e15))
|
||||
assertThat(Mflpt5(0x3e, 0xe9, 0x34, 0x09, 0x1b).toDouble(), closeTo(-123.45678e-22, epsilon))
|
||||
}
|
||||
}
|
||||
Loaded 100 of 221 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user