mirror of
https://github.com/irmen/prog8.git
synced 2025-06-16 09:23:43 +00:00
Compare commits
422 Commits
v6.3
...
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 | |||
f4d83075be | |||
254592c383 | |||
ee23ac0537 | |||
a48cf0bb24 | |||
dae59238cd | |||
8736da1a21 | |||
09a1de69e7 | |||
63d67bc6cb | |||
7099245204 | |||
4d097d2139 | |||
6485bf9ad9 | |||
b7c5b1bfc7 | |||
2b7546e827 | |||
3549ccf4b3 | |||
e2f5752d9a | |||
1a59019fc8 | |||
7bac7bdc3e | |||
19fe58dbac | |||
0a5b30e21c | |||
664818fd29 | |||
d5214e2505 | |||
d906fcea0e | |||
29c8e8b740 | |||
71fec4c555 | |||
5ee36c897d | |||
4aba0c7405 | |||
ed7479c854 | |||
8d3d5f726a | |||
a9a7068818 | |||
1bde7c7718 | |||
17068130bb | |||
81a91d62cb | |||
2575263438 | |||
7f0e25cb50 | |||
a1e4e9c50f | |||
98eff2701b | |||
8b84f87217 | |||
306a1b7bc2 | |||
481214c46e | |||
a5961cbeab | |||
3bf335e0a0 | |||
68f696d165 | |||
1170aed026 | |||
bf1b2066b6 | |||
4c080afb76 | |||
ee1c43ca91 | |||
1c2e6f9e4c | |||
dd379430d9 | |||
42033ebd35 | |||
a086d6e009 | |||
c70bbdab26 | |||
3d956ef554 | |||
329f491c30 | |||
e93701f50e | |||
e680de05ea | |||
56fec674c5 | |||
54d92a027a | |||
319ac3a641 | |||
0a03c46351 | |||
ae1b62e147 | |||
8d567f6b06 | |||
b1ef09675b | |||
2b7b925090 | |||
e0454e95db | |||
91e421d961 | |||
c853afe769 | |||
1a64cb38d5 | |||
ccebd22856 | |||
a1f3b82333 | |||
3dda29781e | |||
a9d297ee31 | |||
e5ff61f201 | |||
d116eb7655 | |||
bc726c6334 | |||
123473dfc8 | |||
d9eccd4fba | |||
5b890847e5 | |||
64c85b9617 | |||
3e3b0bcd8b | |||
4c1eb1b12a | |||
530d03d284 | |||
619fa9b65e | |||
0032235933 | |||
61d1f1ea87 | |||
238d27acdc | |||
2f62271453 | |||
75d5117a2d | |||
b4700af2f5 | |||
374e2b311d | |||
49036abbaf | |||
38ccbac97c | |||
6b4896b8f5 | |||
d582d1cc42 | |||
9e2b8a2aa9 | |||
6fdc733941 | |||
422b390c48 | |||
67a9d1285c | |||
8e26e38ecc | |||
02e12d8575 | |||
fe2954ce08 | |||
1fe4439395 | |||
2ff04d2abd | |||
3f30d3aa89 | |||
129e17b33a | |||
bf2d8c3f4b | |||
b29f04ce01 | |||
d185ebad48 | |||
605df7c91c | |||
ec60cad8bb | |||
6aa0f5a392 | |||
4cae2c56ec | |||
d840975054 | |||
1b14da6c03 | |||
292640b17a | |||
112a7b09f2 | |||
863ec9ce8a | |||
2eb346a205 | |||
8092355acb | |||
e7ef2ed31b | |||
af4de6d2fc | |||
69f73dd779 | |||
9706b46012 | |||
6d75dd3bb8 | |||
bd295ffc99 | |||
07ce3e3c9d | |||
cbc3e37a89 | |||
3626828ceb | |||
24b77fb5a5 | |||
1505fe686a | |||
0991131fa8 | |||
2e928bd3c2 | |||
ca868ae19e | |||
3e286dd14c | |||
11247d52b1 | |||
1dbc902513 | |||
330e691b78 | |||
6780d4f562 | |||
b30b8b7368 | |||
3df182b8c3 | |||
7f21d89fea | |||
2b267b4ba1 | |||
ef64881528 | |||
9a6bd760bd | |||
00b9766aea | |||
6381d2b6ac | |||
d2ab5f230d | |||
824b41d457 | |||
b5523c7077 | |||
eb3594b18c | |||
852d85d010 | |||
5e0aef04fe | |||
a00c693f93 | |||
c943da1448 | |||
b630fae580 | |||
38e40084f1 | |||
bf23ad78e6 | |||
ded1d19737 | |||
496a3b0d2c | |||
6922333755 | |||
a00c39e9cf | |||
1c1da8e38e | |||
50a306f492 | |||
6995ee2d17 | |||
6c60ea9cac | |||
2431ed811a | |||
6bd205c02a | |||
62ec77e148 | |||
9120e1de88 | |||
60e169bd87 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -26,8 +26,9 @@ parser.out
|
|||||||
parsetab.py
|
parsetab.py
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
.attach_pid*
|
.attach_pid*
|
||||||
|
compiler/lib/
|
||||||
|
|
||||||
.gradle
|
.gradle
|
||||||
/prog8compiler.jar
|
/prog8compiler.jar
|
||||||
sd*.img
|
sd*.img
|
||||||
|
*.d64
|
||||||
|
8
.idea/codeInsightSettings.xml
generated
Normal file
8
.idea/codeInsightSettings.xml
generated
Normal file
@ -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>
|
7
.idea/compiler.xml
generated
Normal file
7
.idea/compiler.xml
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CompilerConfiguration">
|
||||||
|
<option name="BUILD_PROCESS_HEAP_SIZE" value="1200" />
|
||||||
|
<bytecodeTargetLevel target="11" />
|
||||||
|
</component>
|
||||||
|
</project>
|
9
.idea/libraries/antlr_4_9_complete.xml
generated
9
.idea/libraries/antlr_4_9_complete.xml
generated
@ -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>
|
|
19
.idea/libraries/antlr_antlr4.xml
generated
Normal file
19
.idea/libraries/antlr_antlr4.xml
generated
Normal file
@ -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>
|
9
.idea/libraries/antlr_runtime_4_9.xml
generated
9
.idea/libraries/antlr_runtime_4_9.xml
generated
@ -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>
|
|
9
.idea/libraries/dbus_java_3_2_4.xml
generated
9
.idea/libraries/dbus_java_3_2_4.xml
generated
@ -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
.idea/libraries/github_hypfvieh_dbus_java.xml
generated
Normal file
25
.idea/libraries/github_hypfvieh_dbus_java.xml
generated
Normal file
@ -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>
|
10
.idea/libraries/glassfish_javax_json.xml
generated
Normal file
10
.idea/libraries/glassfish_javax_json.xml
generated
Normal file
@ -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>
|
10
.idea/libraries/hamcrest.xml
generated
Normal file
10
.idea/libraries/hamcrest.xml
generated
Normal file
@ -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>
|
10
.idea/libraries/javax_json_api_1_1_4.xml
generated
10
.idea/libraries/javax_json_api_1_1_4.xml
generated
@ -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
.idea/libraries/jetbrains_kotlinx_cli_jvm.xml
generated
Normal file
10
.idea/libraries/jetbrains_kotlinx_cli_jvm.xml
generated
Normal file
@ -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>
|
17
.idea/libraries/junit_jupiter.xml
generated
Normal file
17
.idea/libraries/junit_jupiter.xml
generated
Normal file
@ -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>
|
9
.idea/libraries/kotlinx_cli_jvm.xml
generated
9
.idea/libraries/kotlinx_cli_jvm.xml
generated
@ -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
.idea/libraries/michael_bull_kotlin_result_jvm.xml
generated
Normal file
15
.idea/libraries/michael_bull_kotlin_result_jvm.xml
generated
Normal file
@ -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>
|
10
.idea/libraries/slf4j_api_1_7_30.xml
generated
10
.idea/libraries/slf4j_api_1_7_30.xml
generated
@ -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>
|
|
11
.idea/libraries/slf4j_simple.xml
generated
Normal file
11
.idea/libraries/slf4j_simple.xml
generated
Normal file
@ -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>
|
13
.idea/libraries/takes.xml
generated
Normal file
13
.idea/libraries/takes.xml
generated
Normal file
@ -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>
|
12
.idea/libraries/takes_http.xml
generated
12
.idea/libraries/takes_http.xml
generated
@ -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>
|
|
10
.idea/libraries/unittest_libs.xml
generated
10
.idea/libraries/unittest_libs.xml
generated
@ -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>
|
|
6
.idea/misc.xml
generated
6
.idea/misc.xml
generated
@ -4,7 +4,7 @@
|
|||||||
<option name="perGrammarGenerationSettings">
|
<option name="perGrammarGenerationSettings">
|
||||||
<list>
|
<list>
|
||||||
<PerGrammarGenerationSettings>
|
<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="autoGen" value="true" />
|
||||||
<option name="outputDir" value="$PROJECT_DIR$/parser/src/prog8/parser" />
|
<option name="outputDir" value="$PROJECT_DIR$/parser/src/prog8/parser" />
|
||||||
<option name="libDir" value="" />
|
<option name="libDir" value="" />
|
||||||
@ -16,7 +16,7 @@
|
|||||||
</list>
|
</list>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</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" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
|
11
.travis.yml
11
.travis.yml
@ -1,11 +0,0 @@
|
|||||||
language: java
|
|
||||||
sudo: false
|
|
||||||
# jdk: openjdk8
|
|
||||||
# dist: xenial
|
|
||||||
|
|
||||||
before_install:
|
|
||||||
- chmod +x ./gradlew
|
|
||||||
|
|
||||||
script:
|
|
||||||
- ./gradlew test
|
|
||||||
|
|
32
CompilerDevelopment.md
Normal file
32
CompilerDevelopment.md
Normal file
@ -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
|
||||||
|
|
7
LICENSE
7
LICENSE
@ -1,3 +1,10 @@
|
|||||||
|
|
||||||
|
This sofware license is for Prog8 the compiler + associated libraries.
|
||||||
|
The software generated by running the compiler is excluded from this.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
GNU GENERAL PUBLIC LICENSE
|
GNU GENERAL PUBLIC LICENSE
|
||||||
Version 3, 29 June 2007
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
25
README.md
25
README.md
@ -1,5 +1,3 @@
|
|||||||
[](https://saythanks.io/to/irmen)
|
|
||||||
[](https://travis-ci.org/irmen/prog8)
|
|
||||||
[](https://prog8.readthedocs.io/)
|
[](https://prog8.readthedocs.io/)
|
||||||
|
|
||||||
Prog8 - Structured Programming Language for 8-bit 6502/65c02 microprocessors
|
Prog8 - Structured Programming Language for 8-bit 6502/65c02 microprocessors
|
||||||
@ -7,9 +5,6 @@ Prog8 - Structured Programming Language for 8-bit 6502/65c02 microprocessors
|
|||||||
|
|
||||||
*Written by Irmen de Jong (irmen@razorvine.net)*
|
*Written by Irmen de Jong (irmen@razorvine.net)*
|
||||||
|
|
||||||
*Software license: GNU GPL 3.0, see file LICENSE*
|
|
||||||
|
|
||||||
|
|
||||||
This is a structured programming language for the 8-bit 6502/6510/65c02 microprocessor from the late 1970's and 1980's
|
This is a structured programming language for the 8-bit 6502/6510/65c02 microprocessor from the late 1970's and 1980's
|
||||||
as used in many home computers from that era. It is a medium to low level programming language,
|
as used in many home computers from that era. It is a medium to low level programming language,
|
||||||
which aims to provide many conveniences over raw assembly code (even when using a macro assembler).
|
which aims to provide many conveniences over raw assembly code (even when using a macro assembler).
|
||||||
@ -19,29 +14,35 @@ Documentation
|
|||||||
Full documentation (syntax reference, how to use the language and the compiler, etc.) can be found at:
|
Full documentation (syntax reference, how to use the language and the compiler, etc.) can be found at:
|
||||||
https://prog8.readthedocs.io/
|
https://prog8.readthedocs.io/
|
||||||
|
|
||||||
|
Software license
|
||||||
|
----------------
|
||||||
|
GNU GPL 3.0, see file LICENSE
|
||||||
|
|
||||||
|
- prog8 (the compiler + libraries) is licensed under GNU GPL 3.0
|
||||||
|
- *exception:* the resulting files created by running the compiler are free to use in whatever way desired.
|
||||||
|
|
||||||
|
|
||||||
What does Prog8 provide?
|
What does Prog8 provide?
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
- reduction of source code length over raw assembly
|
- reduction of source code length over raw assembly
|
||||||
|
- fast execution speed due to compilation to native assembly code. It's possible to write certain raster interrupt 'demoscene' effects purely in Prog8.
|
||||||
- modularity, symbol scoping, subroutines
|
- modularity, symbol scoping, subroutines
|
||||||
- various data types other than just bytes (16-bit words, floats, strings)
|
- various data types other than just bytes (16-bit words, floats, strings)
|
||||||
- automatic variable allocations, automatic string and array variables and string sharing
|
- floating point math is supported if the target system provides floating point library routines (C64 and Cx16 both do)
|
||||||
|
- strings can contain escaped characters but also many symbols directly if they have a petscii equivalent, such as "♠♥♣♦π▚●○╳". Characters like ^, _, \, {, } and | are also accepted and converted to the closest petscii equivalents.
|
||||||
|
- automatic static variable allocations, automatic string and array variables and string sharing
|
||||||
- subroutines with input parameters and result values
|
- subroutines with input parameters and result values
|
||||||
- high-level program optimizations
|
- high-level program optimizations
|
||||||
- small program boilerplate/compilersupport overhead
|
- small program boilerplate/compilersupport overhead
|
||||||
- sane variable initialization, programs can be restarted again just fine after exiting to basic
|
- programs can be run multiple times without reloading because of automatic variable (re)initializations.
|
||||||
- conditional branches
|
- conditional branches
|
||||||
- floating point operations (requires the C64 Basic ROM routines for this)
|
|
||||||
- 'when' statement to provide a concise jump table alternative to if/elseif chains
|
- 'when' statement to provide a concise jump table alternative to if/elseif chains
|
||||||
- structs to group together sets of variables and manipulate them at once
|
|
||||||
- many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``sort`` and ``reverse``
|
- many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``sort`` and ``reverse``
|
||||||
- various powerful built-in libraries to do I/O, number conversions, graphics and more
|
- various powerful built-in libraries to do I/O, number conversions, graphics and more
|
||||||
- convenience abstractions for low level aspects such as ZeroPage handling, program startup, explicit memory addresses
|
- convenience abstractions for low level aspects such as ZeroPage handling, program startup, explicit memory addresses
|
||||||
- fast execution speed due to compilation to native assembly code
|
|
||||||
- variables are allocated statically
|
|
||||||
- inline assembly allows you to have full control when every cycle or byte matters
|
- inline assembly allows you to have full control when every cycle or byte matters
|
||||||
- supports the sixteen 'virtual' 16-bit registers R0 .. R15 from the Commander X16, and provides them also on the C64.
|
- supports the sixteen 'virtual' 16-bit registers R0 - R15 from the Commander X16, and provides them also on the C64.
|
||||||
- encode strings and characters into petscii or screencodes as desired (C64/Cx16)
|
- encode strings and characters into petscii or screencodes as desired (C64/Cx16)
|
||||||
|
|
||||||
*Rapid edit-compile-run-debug cycle:*
|
*Rapid edit-compile-run-debug cycle:*
|
||||||
|
3
build.gradle
Normal file
3
build.gradle
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
plugins {
|
||||||
|
id "org.jetbrains.kotlin.jvm" version "1.5.30" apply false
|
||||||
|
}
|
@ -1,14 +1,14 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'java'
|
id 'java'
|
||||||
id 'application'
|
id 'application'
|
||||||
id "org.jetbrains.kotlin.jvm" version "1.4.30"
|
id "org.jetbrains.kotlin.jvm"
|
||||||
id 'org.jetbrains.dokka' version "0.9.18"
|
id 'com.github.johnrengelman.shadow' version '7.1.0'
|
||||||
id 'com.github.johnrengelman.shadow' version '6.1.0'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
targetCompatibility = 11
|
targetCompatibility = 11
|
||||||
sourceCompatibility = 11
|
sourceCompatibility = 11
|
||||||
|
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
@ -21,16 +21,29 @@ dependencies {
|
|||||||
implementation project(':compilerAst')
|
implementation project(':compilerAst')
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.1'
|
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.3'
|
||||||
// implementation 'net.razorvine:ksim65:1.8'
|
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.12"
|
||||||
// implementation "com.github.hypfvieh:dbus-java:3.2.4"
|
|
||||||
|
|
||||||
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
|
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2'
|
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2'
|
||||||
testImplementation 'org.hamcrest:hamcrest-junit:2.0.0.0'
|
testImplementation 'org.hamcrest:hamcrest:2.2'
|
||||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.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 {
|
compileKotlin {
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "11"
|
jvmTarget = "11"
|
||||||
@ -58,7 +71,8 @@ sourceSets {
|
|||||||
}
|
}
|
||||||
test {
|
test {
|
||||||
java {
|
java {
|
||||||
srcDirs = ["${project.projectDir}/test"]
|
srcDir "${project.projectDir}/test"
|
||||||
|
srcDir "${project(':compilerAst').projectDir}/test/helpers"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -67,7 +81,6 @@ startScripts.enabled = true
|
|||||||
|
|
||||||
application {
|
application {
|
||||||
mainClass = 'prog8.CompilerMainKt'
|
mainClass = 'prog8.CompilerMainKt'
|
||||||
mainClassName = 'prog8.CompilerMainKt' // deprecated
|
|
||||||
applicationName = 'p8compile'
|
applicationName = 'p8compile'
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,13 +108,3 @@ test {
|
|||||||
events "skipped", "failed"
|
events "skipped", "failed"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
dokka {
|
|
||||||
outputFormat = 'html'
|
|
||||||
outputDirectory = "$buildDir/kdoc"
|
|
||||||
}
|
|
||||||
|
|
||||||
task wrapper(type: Wrapper) {
|
|
||||||
gradleVersion = '6.7'
|
|
||||||
}
|
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<module type="JAVA_MODULE" version="4">
|
<module type="JAVA_MODULE" version="4">
|
||||||
|
<component name="FacetManager">
|
||||||
|
<facet type="Python" name="Python">
|
||||||
|
<configuration sdkName="Python 3.9" />
|
||||||
|
</facet>
|
||||||
|
</component>
|
||||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
<exclude-output />
|
<exclude-output />
|
||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
@ -8,11 +13,14 @@
|
|||||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="jdk" jdkName="11" jdkType="JavaSDK" />
|
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||||
<orderEntry type="library" name="unittest-libs" level="project" />
|
|
||||||
<orderEntry type="library" name="kotlinx-cli-jvm" level="project" />
|
|
||||||
<orderEntry type="module" module-name="compilerAst" />
|
<orderEntry type="module" module-name="compilerAst" />
|
||||||
|
<orderEntry type="library" name="Python 3.9 interpreter library" level="application" />
|
||||||
|
<orderEntry type="library" name="hamcrest" level="project" />
|
||||||
|
<orderEntry type="library" name="jetbrains.kotlinx.cli.jvm" level="project" />
|
||||||
|
<orderEntry type="library" name="junit.jupiter" level="project" />
|
||||||
|
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</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.
@ -428,7 +428,9 @@ var_fac1_greater_f .proc
|
|||||||
cmp #1
|
cmp #1
|
||||||
beq +
|
beq +
|
||||||
lda #0
|
lda #0
|
||||||
+ rts
|
rts
|
||||||
|
+ lda #1
|
||||||
|
rts
|
||||||
.pend
|
.pend
|
||||||
|
|
||||||
var_fac1_greatereq_f .proc
|
var_fac1_greatereq_f .proc
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
;
|
;
|
||||||
; indent format: TABS, size=8
|
; indent format: TABS, size=8
|
||||||
|
|
||||||
%target c64
|
|
||||||
%option enable_floats
|
%option enable_floats
|
||||||
|
|
||||||
floats {
|
floats {
|
||||||
@ -83,7 +82,7 @@ romsub $bc58 = ABS() ; fac1 = ABS(fac1)
|
|||||||
romsub $bf71 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
|
romsub $bf71 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
|
||||||
romsub $bf74 = SQRA() clobbers(A,X,Y) ; fac1 = SQRT(fac2)
|
romsub $bf74 = SQRA() clobbers(A,X,Y) ; fac1 = SQRT(fac2)
|
||||||
romsub $bfed = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
|
romsub $bfed = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
|
||||||
romsub $bfb4 = NEGOP() clobbers(A) ; switch the sign of fac1
|
romsub $bfb4 = NEGOP() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1)
|
||||||
romsub $e097 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
|
romsub $e097 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
|
||||||
romsub $e264 = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
|
romsub $e264 = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
|
||||||
romsub $e26b = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
|
romsub $e26b = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
|
||||||
@ -195,7 +194,7 @@ sub print_f (float value) {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
%asminclude "library:c64/floats.asm", ""
|
%asminclude "library:c64/floats.asm"
|
||||||
%asminclude "library:c64/floats_funcs.asm", ""
|
%asminclude "library:c64/floats_funcs.asm"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
%target c64
|
|
||||||
%import textio
|
%import textio
|
||||||
|
|
||||||
; bitmap pixel graphics module for the C64
|
; bitmap pixel graphics module for the C64
|
||||||
@ -34,36 +33,33 @@ graphics {
|
|||||||
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
|
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
|
||||||
; Bresenham algorithm.
|
; Bresenham algorithm.
|
||||||
; This code special-cases various quadrant loops to allow simple ++ and -- operations.
|
; This code special-cases various quadrant loops to allow simple ++ and -- operations.
|
||||||
; TODO there are some slight errors at the first/last pixels in certain slopes...??
|
|
||||||
if y1>y2 {
|
if y1>y2 {
|
||||||
; make sure dy is always positive to have only 4 instead of 8 special cases
|
; make sure dy is always positive to have only 4 instead of 8 special cases
|
||||||
swap(x1, x2)
|
swap(x1, x2)
|
||||||
swap(y1, y2)
|
swap(y1, y2)
|
||||||
}
|
}
|
||||||
word @zp dx = x2-x1 as word
|
word @zp dx = (x2 as word)-x1
|
||||||
word @zp dy = y2-y1
|
word @zp dy = (y2 as word)-y1
|
||||||
|
|
||||||
if dx==0 {
|
if dx==0 {
|
||||||
vertical_line(x1, y1, abs(dy)+1 as ubyte)
|
vertical_line(x1, y1, abs(dy) as ubyte +1)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if dy==0 {
|
if dy==0 {
|
||||||
if x1>x2
|
if x1>x2
|
||||||
x1=x2
|
x1=x2
|
||||||
horizontal_line(x1, y1, abs(dx)+1 as uword)
|
horizontal_line(x1, y1, abs(dx) as uword +1)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
; TODO rewrite the rest in optimized assembly
|
|
||||||
|
|
||||||
word @zp d = 0
|
word @zp d = 0
|
||||||
ubyte positive_ix = true
|
ubyte positive_ix = true
|
||||||
if dx < 0 {
|
if dx < 0 {
|
||||||
dx = -dx
|
dx = -dx
|
||||||
positive_ix = false
|
positive_ix = false
|
||||||
}
|
}
|
||||||
dx *= 2
|
word @zp dx2 = dx*2
|
||||||
dy *= 2
|
word @zp dy2 = dy*2
|
||||||
internal_plotx = x1
|
internal_plotx = x1
|
||||||
|
|
||||||
if dx >= dy {
|
if dx >= dy {
|
||||||
@ -73,10 +69,10 @@ graphics {
|
|||||||
if internal_plotx==x2
|
if internal_plotx==x2
|
||||||
return
|
return
|
||||||
internal_plotx++
|
internal_plotx++
|
||||||
d += dy
|
d += dy2
|
||||||
if d > dx {
|
if d > dx {
|
||||||
y1++
|
y1++
|
||||||
d -= dx
|
d -= dx2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -85,10 +81,10 @@ graphics {
|
|||||||
if internal_plotx==x2
|
if internal_plotx==x2
|
||||||
return
|
return
|
||||||
internal_plotx--
|
internal_plotx--
|
||||||
d += dy
|
d += dy2
|
||||||
if d > dx {
|
if d > dx {
|
||||||
y1++
|
y1++
|
||||||
d -= dx
|
d -= dx2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -100,10 +96,10 @@ graphics {
|
|||||||
if y1 == y2
|
if y1 == y2
|
||||||
return
|
return
|
||||||
y1++
|
y1++
|
||||||
d += dx
|
d += dx2
|
||||||
if d > dy {
|
if d > dy {
|
||||||
internal_plotx++
|
internal_plotx++
|
||||||
d -= dy
|
d -= dy2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -112,10 +108,10 @@ graphics {
|
|||||||
if y1 == y2
|
if y1 == y2
|
||||||
return
|
return
|
||||||
y1++
|
y1++
|
||||||
d += dx
|
d += dx2
|
||||||
if d > dy {
|
if d > dy {
|
||||||
internal_plotx--
|
internal_plotx--
|
||||||
d -= dy
|
d -= dy2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,6 @@
|
|||||||
;
|
;
|
||||||
; indent format: TABS, size=8
|
; indent format: TABS, size=8
|
||||||
|
|
||||||
%target c64
|
|
||||||
|
|
||||||
c64 {
|
c64 {
|
||||||
&ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
|
&ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
|
||||||
&ubyte TIME_MID = $a1 ; .. mid byte
|
&ubyte TIME_MID = $a1 ; .. mid byte
|
||||||
@ -202,7 +200,7 @@ romsub $FFAE = UNLSN() clobbers(A) ; command serial
|
|||||||
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
|
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
|
||||||
romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
|
romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
|
||||||
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word
|
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word
|
||||||
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte address @ Y) ; set logical file parameters
|
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte secondary @ Y) ; set logical file parameters
|
||||||
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
|
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
|
||||||
romsub $FFC0 = OPEN() clobbers(X,Y) -> ubyte @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
|
romsub $FFC0 = OPEN() clobbers(X,Y) -> ubyte @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
|
||||||
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
|
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
|
||||||
@ -211,10 +209,10 @@ romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320
|
|||||||
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
|
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
|
||||||
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
|
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
|
||||||
romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326)) output a character
|
romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326)) output a character
|
||||||
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, ubyte @ X, ubyte @ Y ; (via 816 ($330)) load from device
|
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, uword @ XY ; (via 816 ($330)) load from device
|
||||||
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
|
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
|
||||||
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
|
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
|
||||||
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock
|
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (A=lo,X=mid,Y=high)
|
||||||
romsub $FFE1 = STOP() clobbers(X) -> ubyte @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
|
romsub $FFE1 = STOP() clobbers(X) -> ubyte @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
|
||||||
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @Pc, ubyte @ A ; (via 810 ($32A)) get a character
|
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @Pc, ubyte @ A ; (via 810 ($32A)) get a character
|
||||||
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
|
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
|
||||||
@ -246,7 +244,7 @@ asmsub STOP2() -> ubyte @A {
|
|||||||
}
|
}
|
||||||
|
|
||||||
asmsub RDTIM16() -> uword @AY {
|
asmsub RDTIM16() -> uword @AY {
|
||||||
; -- like RDTIM() but only returning the lower 16 bits for convenience
|
; -- like RDTIM() but only returning the lower 16 bits in AY for convenience
|
||||||
%asm {{
|
%asm {{
|
||||||
stx P8ZP_SCRATCH_REG
|
stx P8ZP_SCRATCH_REG
|
||||||
jsr c64.RDTIM
|
jsr c64.RDTIM
|
||||||
@ -478,7 +476,7 @@ sys {
|
|||||||
|
|
||||||
|
|
||||||
asmsub reset_system() {
|
asmsub reset_system() {
|
||||||
; Soft-reset the system back to Basic prompt.
|
; Soft-reset the system back to initial power-on Basic prompt.
|
||||||
%asm {{
|
%asm {{
|
||||||
sei
|
sei
|
||||||
lda #14
|
lda #14
|
||||||
@ -489,6 +487,7 @@ sys {
|
|||||||
|
|
||||||
sub wait(uword jiffies) {
|
sub wait(uword jiffies) {
|
||||||
; --- wait approximately the given number of jiffies (1/60th seconds)
|
; --- wait approximately the given number of jiffies (1/60th seconds)
|
||||||
|
; note: the system irq handler has to be active for this to work as it depends on the system jiffy clock
|
||||||
repeat jiffies {
|
repeat jiffies {
|
||||||
ubyte jiff = lsb(c64.RDTIM16())
|
ubyte jiff = lsb(c64.RDTIM16())
|
||||||
while jiff==lsb(c64.RDTIM16()) {
|
while jiff==lsb(c64.RDTIM16()) {
|
||||||
@ -497,6 +496,27 @@ sys {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
asmsub waitvsync() clobbers(A) {
|
||||||
|
; --- busy wait till the next vsync has occurred (approximately), without depending on custom irq handling.
|
||||||
|
; note: a more accurate way to wait for vsync is to set up a vsync irq handler instead.
|
||||||
|
%asm {{
|
||||||
|
- bit c64.SCROLY
|
||||||
|
bpl -
|
||||||
|
- bit c64.SCROLY
|
||||||
|
bmi -
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline asmsub waitrastborder() {
|
||||||
|
; --- busy wait till the raster position has reached the bottom screen border (approximately)
|
||||||
|
; note: a more accurate way to do this is by using a raster irq handler instead.
|
||||||
|
%asm {{
|
||||||
|
- bit c64.SCROLY
|
||||||
|
bpl -
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
|
asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
|
||||||
%asm {{
|
%asm {{
|
||||||
ldx cx16.r0
|
ldx cx16.r0
|
||||||
@ -679,4 +699,37 @@ cx16 {
|
|||||||
&uword r14 = $cf1c
|
&uword r14 = $cf1c
|
||||||
&uword r15 = $cf1e
|
&uword r15 = $cf1e
|
||||||
|
|
||||||
|
&ubyte r0L = $cf00
|
||||||
|
&ubyte r1L = $cf02
|
||||||
|
&ubyte r2L = $cf04
|
||||||
|
&ubyte r3L = $cf06
|
||||||
|
&ubyte r4L = $cf08
|
||||||
|
&ubyte r5L = $cf0a
|
||||||
|
&ubyte r6L = $cf0c
|
||||||
|
&ubyte r7L = $cf0e
|
||||||
|
&ubyte r8L = $cf10
|
||||||
|
&ubyte r9L = $cf12
|
||||||
|
&ubyte r10L = $cf14
|
||||||
|
&ubyte r11L = $cf16
|
||||||
|
&ubyte r12L = $cf18
|
||||||
|
&ubyte r13L = $cf1a
|
||||||
|
&ubyte r14L = $cf1c
|
||||||
|
&ubyte r15L = $cf1e
|
||||||
|
|
||||||
|
&ubyte r0H = $cf01
|
||||||
|
&ubyte r1H = $cf03
|
||||||
|
&ubyte r2H = $cf05
|
||||||
|
&ubyte r3H = $cf07
|
||||||
|
&ubyte r4H = $cf09
|
||||||
|
&ubyte r5H = $cf0b
|
||||||
|
&ubyte r6H = $cf0d
|
||||||
|
&ubyte r7H = $cf0f
|
||||||
|
&ubyte r8H = $cf11
|
||||||
|
&ubyte r9H = $cf13
|
||||||
|
&ubyte r10H = $cf15
|
||||||
|
&ubyte r11H = $cf17
|
||||||
|
&ubyte r12H = $cf19
|
||||||
|
&ubyte r13H = $cf1b
|
||||||
|
&ubyte r14H = $cf1d
|
||||||
|
&ubyte r15H = $cf1f
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
;
|
;
|
||||||
; indent format: TABS, size=8
|
; indent format: TABS, size=8
|
||||||
|
|
||||||
%target c64
|
|
||||||
%import syslib
|
%import syslib
|
||||||
%import conv
|
%import conv
|
||||||
|
|
||||||
|
@ -4,11 +4,12 @@
|
|||||||
;
|
;
|
||||||
; indent format: TABS, size=8
|
; indent format: TABS, size=8
|
||||||
|
|
||||||
%target cx16
|
|
||||||
%option enable_floats
|
%option enable_floats
|
||||||
|
|
||||||
floats {
|
floats {
|
||||||
; ---- this block contains C-64 floating point related functions ----
|
; ---- this block contains C-64 compatible floating point related functions ----
|
||||||
|
; the addresses are from cx16 V39 emulator and roms! they won't work on older versions.
|
||||||
|
|
||||||
|
|
||||||
const float PI = 3.141592653589793
|
const float PI = 3.141592653589793
|
||||||
const float TWOPI = 6.283185307179586
|
const float TWOPI = 6.283185307179586
|
||||||
@ -43,46 +44,44 @@ romsub $fe1e = NORMAL() clobbers(A,X,Y) ; normalize fac1 (?)
|
|||||||
romsub $fe24 = LOG() clobbers(A,X,Y) ; fac1 = LN(fac1) (natural log)
|
romsub $fe24 = LOG() clobbers(A,X,Y) ; fac1 = LN(fac1) (natural log)
|
||||||
romsub $fe27 = FMULT(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 *= mflpt value from A/Y
|
romsub $fe27 = FMULT(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 *= mflpt value from A/Y
|
||||||
romsub $fe2a = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2
|
romsub $fe2a = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2
|
||||||
romsub $fe33 = CONUPK(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac2
|
romsub $fe30 = CONUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac2
|
||||||
romsub $fe36 = MUL10() clobbers(A,X,Y) ; fac1 *= 10
|
romsub $fe33 = MUL10() clobbers(A,X,Y) ; fac1 *= 10
|
||||||
romsub $fe3c = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive!
|
romsub $fe36 = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive!
|
||||||
romsub $fe3f = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1 (remainder in fac2)
|
romsub $fe39 = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1 (remainder in fac2)
|
||||||
romsub $fe42 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 (remainder in fac2) mind the order of the operands
|
romsub $fe3c = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 (remainder in fac2) mind the order of the operands
|
||||||
|
|
||||||
romsub $fe48 = MOVFM(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac1
|
romsub $fe42 = MOVFM(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac1
|
||||||
romsub $fe4b = MOVMF(uword mflpt @ XY) clobbers(A,Y) ; store fac1 to memory X/Y as 5-byte mflpt
|
romsub $fe45 = MOVMF(uword mflpt @ XY) clobbers(A,X,Y) ; store fac1 to memory X/Y as 5-byte mflpt
|
||||||
romsub $fe4e = MOVFA() clobbers(A,X) ; copy fac2 to fac1
|
romsub $fe48 = MOVFA() clobbers(A,X) ; copy fac2 to fac1
|
||||||
romsub $fe51 = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded)
|
romsub $fe4b = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded)
|
||||||
romsub $fe54 = MOVEF() clobbers(A,X) ; copy fac1 to fac2
|
romsub $fe4e = MOVEF() clobbers(A,X) ; copy fac1 to fac2
|
||||||
romsub $fe5a = SIGN() -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
|
romsub $fe54 = SIGN() clobbers(X,Y) -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
|
||||||
romsub $fe5d = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
|
romsub $fe57 = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
|
||||||
romsub $fe60 = FREADSA(byte value @ A) clobbers(A,X,Y) ; 8 bit signed A -> float in fac1
|
romsub $fe5a = FREADSA(byte value @ A) clobbers(A,X,Y) ; 8 bit signed A -> float in fac1
|
||||||
romsub $fe6c = ABS() ; fac1 = ABS(fac1)
|
romsub $fe66 = ABS() clobbers(A,X,Y) ; fac1 = ABS(fac1)
|
||||||
romsub $fe6f = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
|
romsub $fe69 = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
|
||||||
romsub $fe78 = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to round instead of trunc
|
romsub $fe72 = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to round instead of trunc
|
||||||
romsub $fe7e = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
|
romsub $fe78 = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
|
||||||
romsub $fe81 = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY
|
romsub $fe7b = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY
|
||||||
romsub $fe8a = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
|
romsub $fe81 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
|
||||||
romsub $fe8d = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
|
romsub $fe84 = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
|
||||||
; note: there is no FPWR() on the Cx16
|
romsub $fe8a = NEGOP() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1)
|
||||||
romsub $fe93 = NEGOP() clobbers(A) ; switch the sign of fac1
|
romsub $fe8d = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
|
||||||
romsub $fe96 = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
|
romsub $fe96 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
|
||||||
romsub $fe9f = RND2(byte value @A) clobbers(A,X,Y) ; fac1 = RND(A) float random number generator
|
romsub $fe99 = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
|
||||||
romsub $fea2 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
|
romsub $fe9c = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
|
||||||
romsub $fea5 = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
|
romsub $fe9f = TAN() clobbers(A,X,Y) ; fac1 = TAN(fac1)
|
||||||
romsub $fea8 = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
|
romsub $fea2 = ATN() clobbers(A,X,Y) ; fac1 = ATN(fac1)
|
||||||
romsub $feab = TAN() clobbers(A,X,Y) ; fac1 = TAN(fac1)
|
|
||||||
romsub $feae = ATN() clobbers(A,X,Y) ; fac1 = ATN(fac1)
|
|
||||||
|
|
||||||
|
|
||||||
asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
||||||
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
|
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
|
||||||
%asm {{
|
%asm {{
|
||||||
phx
|
phx
|
||||||
sta P8ZP_SCRATCH_W2
|
sta _tmp
|
||||||
sty P8ZP_SCRATCH_B1
|
sty P8ZP_SCRATCH_B1
|
||||||
tya
|
tya
|
||||||
ldy P8ZP_SCRATCH_W2
|
ldy _tmp
|
||||||
jsr GIVAYF ; load it as signed... correct afterwards
|
jsr GIVAYF ; load it as signed... correct afterwards
|
||||||
lda P8ZP_SCRATCH_B1
|
lda P8ZP_SCRATCH_B1
|
||||||
bpl +
|
bpl +
|
||||||
@ -91,6 +90,7 @@ asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
|||||||
jsr FADD
|
jsr FADD
|
||||||
+ plx
|
+ plx
|
||||||
rts
|
rts
|
||||||
|
_tmp .byte 0
|
||||||
_flt65536 .byte 145,0,0,0,0 ; 65536.0
|
_flt65536 .byte 145,0,0,0,0 ; 65536.0
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
@ -128,6 +128,14 @@ asmsub GETADRAY () clobbers(X) -> uword @ AY {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
asmsub FREADUY (ubyte value @Y) {
|
||||||
|
; -- 8 bit unsigned Y -> float in fac1
|
||||||
|
%asm {{
|
||||||
|
lda #0
|
||||||
|
jmp GIVAYF
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
sub print_f (float value) {
|
sub print_f (float value) {
|
||||||
; ---- prints the floating point value (without a newline).
|
; ---- prints the floating point value (without a newline).
|
||||||
%asm {{
|
%asm {{
|
||||||
@ -149,7 +157,7 @@ sub print_f (float value) {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
%asminclude "library:c64/floats.asm", ""
|
%asminclude "library:c64/floats.asm"
|
||||||
%asminclude "library:c64/floats_funcs.asm", ""
|
%asminclude "library:c64/floats_funcs.asm"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
%target cx16
|
|
||||||
|
|
||||||
; Bitmap pixel graphics routines for the CommanderX16
|
; Bitmap pixel graphics routines for the CommanderX16
|
||||||
; Custom routines to use the full-screen 640x480 and 320x240 screen modes.
|
; Custom routines to use the full-screen 640x480 and 320x240 screen modes.
|
||||||
; (These modes are not supported by the documented GRAPH_xxxx kernal routines)
|
; (These modes are not supported by the documented GRAPH_xxxx kernal routines)
|
||||||
@ -22,6 +20,7 @@
|
|||||||
; mode 6 = bitmap 640 x 480 x 4c
|
; mode 6 = bitmap 640 x 480 x 4c
|
||||||
; higher color dephts in highres are not supported due to lack of VRAM
|
; higher color dephts in highres are not supported due to lack of VRAM
|
||||||
|
|
||||||
|
|
||||||
; TODO can we make a FB vector table and emulation routines for the Cx16s' GRAPH_init() call? to replace the builtin 320x200 fb driver?
|
; TODO can we make a FB vector table and emulation routines for the Cx16s' GRAPH_init() call? to replace the builtin 320x200 fb driver?
|
||||||
|
|
||||||
gfx2 {
|
gfx2 {
|
||||||
@ -274,9 +273,9 @@ _done
|
|||||||
ora colorbits,y
|
ora colorbits,y
|
||||||
sta cx16.VERA_DATA0
|
sta cx16.VERA_DATA0
|
||||||
cpy #%00000011 ; next vera byte?
|
cpy #%00000011 ; next vera byte?
|
||||||
bne +
|
bne ++
|
||||||
inc cx16.VERA_ADDR_L
|
inc cx16.VERA_ADDR_L
|
||||||
bne +
|
bne ++
|
||||||
inc cx16.VERA_ADDR_M
|
inc cx16.VERA_ADDR_M
|
||||||
+ bne +
|
+ bne +
|
||||||
inc cx16.VERA_ADDR_H
|
inc cx16.VERA_ADDR_H
|
||||||
@ -292,92 +291,56 @@ _done
|
|||||||
}
|
}
|
||||||
|
|
||||||
sub vertical_line(uword x, uword y, uword height, ubyte color) {
|
sub vertical_line(uword x, uword y, uword height, ubyte color) {
|
||||||
position(x,y)
|
|
||||||
when active_mode {
|
when active_mode {
|
||||||
1, 5 -> {
|
1, 5 -> {
|
||||||
; monochrome, either resolution
|
; monochrome, lo-res
|
||||||
; note for the 1 bpp modes we can't use vera's auto increment mode because we have to 'or' the pixel data in place.
|
cx16.r15L = gfx2.plot.bits[x as ubyte & 7] ; bitmask
|
||||||
; TODO use TWO vera adress pointers simultaneously one for reading, one for writing, so auto-increment IS possible
|
|
||||||
cx16.VERA_ADDR_H &= %00000111 ; no auto advance
|
|
||||||
cx16.r15 = gfx2.plot.bits[x as ubyte & 7] ; bitmask
|
|
||||||
if active_mode>=5
|
|
||||||
cx16.r14 = 640/8
|
|
||||||
else
|
|
||||||
cx16.r14 = 320/8
|
|
||||||
if color {
|
if color {
|
||||||
if monochrome_dont_stipple_flag {
|
if monochrome_dont_stipple_flag {
|
||||||
|
; draw continuous line.
|
||||||
|
position2(x,y,true)
|
||||||
|
if active_mode==1
|
||||||
|
set_both_strides(11) ; 40 increment = 1 line in 320 px monochrome
|
||||||
|
else
|
||||||
|
set_both_strides(12) ; 80 increment = 1 line in 640 px monochrome
|
||||||
repeat height {
|
repeat height {
|
||||||
%asm {{
|
%asm {{
|
||||||
lda cx16.VERA_DATA0
|
lda cx16.VERA_DATA0
|
||||||
ora cx16.r15
|
ora cx16.r15L
|
||||||
sta cx16.VERA_DATA0
|
sta cx16.VERA_DATA1
|
||||||
lda cx16.VERA_ADDR_L
|
|
||||||
clc
|
|
||||||
adc cx16.r14 ; advance vera ptr to go to the next line
|
|
||||||
sta cx16.VERA_ADDR_L
|
|
||||||
lda cx16.VERA_ADDR_M
|
|
||||||
adc #0
|
|
||||||
sta cx16.VERA_ADDR_M
|
|
||||||
; lda cx16.VERA_ADDR_H ; the bitmap size is small enough to not have to deal with the _H part.
|
|
||||||
; adc #0
|
|
||||||
; sta cx16.VERA_ADDR_H
|
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
; stippling.
|
; draw stippled line.
|
||||||
height = (height+1)/2 ; TODO is the line sometimes 1 pixel too long now because of rounding?
|
if x&1 {
|
||||||
%asm {{
|
y++
|
||||||
lda x
|
height--
|
||||||
eor y
|
}
|
||||||
and #1
|
position2(x,y,true)
|
||||||
bne +
|
if active_mode==1
|
||||||
lda cx16.VERA_ADDR_L
|
set_both_strides(12) ; 80 increment = 2 line in 320 px monochrome
|
||||||
clc
|
else
|
||||||
adc cx16.r14 ; advance vera ptr to go to the next line for correct stipple pattern
|
set_both_strides(13) ; 160 increment = 2 line in 640 px monochrome
|
||||||
sta cx16.VERA_ADDR_L
|
repeat height/2 {
|
||||||
lda cx16.VERA_ADDR_M
|
%asm {{
|
||||||
adc #0
|
lda cx16.VERA_DATA0
|
||||||
sta cx16.VERA_ADDR_M
|
ora cx16.r15L
|
||||||
+
|
sta cx16.VERA_DATA1
|
||||||
asl cx16.r14
|
}}
|
||||||
ldy height
|
}
|
||||||
beq +
|
|
||||||
- lda cx16.VERA_DATA0
|
|
||||||
ora cx16.r15
|
|
||||||
sta cx16.VERA_DATA0
|
|
||||||
lda cx16.VERA_ADDR_L
|
|
||||||
clc
|
|
||||||
adc cx16.r14 ; advance vera data ptr to go to the next-next line
|
|
||||||
sta cx16.VERA_ADDR_L
|
|
||||||
lda cx16.VERA_ADDR_M
|
|
||||||
adc #0
|
|
||||||
sta cx16.VERA_ADDR_M
|
|
||||||
; lda cx16.VERA_ADDR_H ; the bitmap size is small enough to not have to deal with the _H part.
|
|
||||||
; adc #0
|
|
||||||
; sta cx16.VERA_ADDR_H
|
|
||||||
dey
|
|
||||||
bne -
|
|
||||||
+
|
|
||||||
}}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cx16.r15 = ~cx16.r15
|
position2(x,y,true)
|
||||||
|
cx16.r15 = ~cx16.r15 ; erase pixels
|
||||||
|
if active_mode==1
|
||||||
|
set_both_strides(11) ; 40 increment = 1 line in 320 px monochrome
|
||||||
|
else
|
||||||
|
set_both_strides(12) ; 80 increment = 1 line in 640 px monochrome
|
||||||
repeat height {
|
repeat height {
|
||||||
%asm {{
|
%asm {{
|
||||||
lda cx16.VERA_DATA0
|
lda cx16.VERA_DATA0
|
||||||
and cx16.r15
|
and cx16.r15L
|
||||||
sta cx16.VERA_DATA0
|
sta cx16.VERA_DATA1
|
||||||
lda cx16.VERA_ADDR_L
|
|
||||||
clc
|
|
||||||
adc cx16.r14 ; advance vera data ptr to go to the next line
|
|
||||||
sta cx16.VERA_ADDR_L
|
|
||||||
lda cx16.VERA_ADDR_M
|
|
||||||
adc #0
|
|
||||||
sta cx16.VERA_ADDR_M
|
|
||||||
; the bitmap size is small enough to not have to deal with the _H part:
|
|
||||||
; lda cx16.VERA_ADDR_H
|
|
||||||
; adc #0
|
|
||||||
; sta cx16.VERA_ADDR_H
|
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -385,6 +348,7 @@ _done
|
|||||||
4 -> {
|
4 -> {
|
||||||
; lores 256c
|
; lores 256c
|
||||||
; set vera auto-increment to 320 pixel increment (=next line)
|
; set vera auto-increment to 320 pixel increment (=next line)
|
||||||
|
position(x,y)
|
||||||
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | (14<<4)
|
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | (14<<4)
|
||||||
%asm {{
|
%asm {{
|
||||||
ldy height
|
ldy height
|
||||||
@ -398,83 +362,78 @@ _done
|
|||||||
}
|
}
|
||||||
6 -> {
|
6 -> {
|
||||||
; highres 4c
|
; highres 4c
|
||||||
; note for this mode we can't use vera's auto increment mode because we have to 'or' the pixel data in place.
|
; use TWO vera adress pointers simultaneously one for reading, one for writing, so auto-increment is possible
|
||||||
; TODO use TWO vera adress pointers simultaneously one for reading, one for writing, so auto-increment IS possible
|
if height==0
|
||||||
cx16.VERA_ADDR_H &= %00000111 ; no auto advance
|
return
|
||||||
; TODO also mostly usable for lores 4c?
|
position2(x,y,true)
|
||||||
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
|
set_both_strides(13) ; 160 increment = 1 line in 640 px 4c mode
|
||||||
|
|
||||||
; TODO optimize the loop in pure assembly
|
|
||||||
color &= 3
|
color &= 3
|
||||||
color <<= gfx2.plot.shift4c[lsb(x) & 3]
|
color <<= gfx2.plot.shift4c[lsb(x) & 3]
|
||||||
ubyte mask = gfx2.plot.mask4c[lsb(x) & 3]
|
ubyte mask = gfx2.plot.mask4c[lsb(x) & 3]
|
||||||
repeat height {
|
repeat height {
|
||||||
ubyte value = cx16.vpeek(lsb(cx16.r1), cx16.r0) & mask | color
|
|
||||||
cx16.vpoke(lsb(cx16.r1), cx16.r0, value)
|
|
||||||
%asm {{
|
%asm {{
|
||||||
; 24 bits add 160 (640/4)
|
lda cx16.VERA_DATA0
|
||||||
clc
|
and mask
|
||||||
lda cx16.r0
|
ora color
|
||||||
adc #640/4
|
sta cx16.VERA_DATA1
|
||||||
sta cx16.r0
|
|
||||||
lda cx16.r0+1
|
|
||||||
adc #0
|
|
||||||
sta cx16.r0+1
|
|
||||||
bcc +
|
|
||||||
inc cx16.r1
|
|
||||||
+
|
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub set_both_strides(ubyte stride) {
|
||||||
|
stride <<= 4
|
||||||
|
cx16.VERA_CTRL = 0
|
||||||
|
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | stride
|
||||||
|
cx16.VERA_CTRL = 1
|
||||||
|
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | stride
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub line(uword @zp x1, uword @zp y1, uword @zp x2, uword @zp y2, ubyte color) {
|
sub line(uword @zp x1, uword @zp y1, uword @zp x2, uword @zp y2, ubyte color) {
|
||||||
; Bresenham algorithm.
|
; Bresenham algorithm.
|
||||||
; This code special-cases various quadrant loops to allow simple ++ and -- operations.
|
; This code special-cases various quadrant loops to allow simple ++ and -- operations.
|
||||||
; TODO there are some slight errors at the first/last pixels in certain slopes...
|
|
||||||
if y1>y2 {
|
if y1>y2 {
|
||||||
; make sure dy is always positive to have only 4 instead of 8 special cases
|
; make sure dy is always positive to have only 4 instead of 8 special cases
|
||||||
swap(x1, x2)
|
swap(x1, x2)
|
||||||
swap(y1, y2)
|
swap(y1, y2)
|
||||||
}
|
}
|
||||||
word @zp dx = x2-x1 as word
|
word @zp dx = (x2 as word)-x1
|
||||||
word @zp dy = y2-y1 as word
|
word @zp dy = (y2 as word)-y1
|
||||||
|
|
||||||
if dx==0 {
|
if dx==0 {
|
||||||
vertical_line(x1, y1, abs(dy)+1 as uword, color)
|
vertical_line(x1, y1, abs(dy) as uword +1, color)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if dy==0 {
|
if dy==0 {
|
||||||
if x1>x2
|
if x1>x2
|
||||||
x1=x2
|
x1=x2
|
||||||
horizontal_line(x1, y1, abs(dx)+1 as uword, color)
|
horizontal_line(x1, y1, abs(dx) as uword +1, color)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
; TODO rewrite the rest in optimized assembly (or reuse GRAPH_draw_line if we can get the FB replacement vector layer working)
|
|
||||||
word @zp d = 0
|
word @zp d = 0
|
||||||
ubyte positive_ix = true
|
cx16.r13 = true ; 'positive_ix'
|
||||||
if dx < 0 {
|
if dx < 0 {
|
||||||
dx = -dx
|
dx = -dx
|
||||||
positive_ix = false
|
cx16.r13 = false
|
||||||
}
|
}
|
||||||
dx *= 2
|
word @zp dx2 = dx*2
|
||||||
dy *= 2
|
word @zp dy2 = dy*2
|
||||||
cx16.r14 = x1 ; internal plot X
|
cx16.r14 = x1 ; internal plot X
|
||||||
|
|
||||||
if dx >= dy {
|
if dx >= dy {
|
||||||
if positive_ix {
|
if cx16.r13 {
|
||||||
repeat {
|
repeat {
|
||||||
plot(cx16.r14, y1, color)
|
plot(cx16.r14, y1, color)
|
||||||
if cx16.r14==x2
|
if cx16.r14==x2
|
||||||
return
|
return
|
||||||
cx16.r14++
|
cx16.r14++
|
||||||
d += dy
|
d += dy2
|
||||||
if d > dx {
|
if d > dx {
|
||||||
y1++
|
y1++
|
||||||
d -= dx
|
d -= dx2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -483,25 +442,25 @@ _done
|
|||||||
if cx16.r14==x2
|
if cx16.r14==x2
|
||||||
return
|
return
|
||||||
cx16.r14--
|
cx16.r14--
|
||||||
d += dy
|
d += dy2
|
||||||
if d > dx {
|
if d > dx {
|
||||||
y1++
|
y1++
|
||||||
d -= dx
|
d -= dx2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if positive_ix {
|
if cx16.r13 {
|
||||||
repeat {
|
repeat {
|
||||||
plot(cx16.r14, y1, color)
|
plot(cx16.r14, y1, color)
|
||||||
if y1 == y2
|
if y1 == y2
|
||||||
return
|
return
|
||||||
y1++
|
y1++
|
||||||
d += dx
|
d += dx2
|
||||||
if d > dy {
|
if d > dy {
|
||||||
cx16.r14++
|
cx16.r14++
|
||||||
d -= dy
|
d -= dy2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -510,10 +469,10 @@ _done
|
|||||||
if y1 == y2
|
if y1 == y2
|
||||||
return
|
return
|
||||||
y1++
|
y1++
|
||||||
d += dx
|
d += dx2
|
||||||
if d > dy {
|
if d > dy {
|
||||||
cx16.r14--
|
cx16.r14--
|
||||||
d -= dy
|
d -= dy2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -589,8 +548,6 @@ _done
|
|||||||
ubyte[8] bits = [128, 64, 32, 16, 8, 4, 2, 1]
|
ubyte[8] bits = [128, 64, 32, 16, 8, 4, 2, 1]
|
||||||
ubyte[4] mask4c = [%00111111, %11001111, %11110011, %11111100]
|
ubyte[4] mask4c = [%00111111, %11001111, %11110011, %11111100]
|
||||||
ubyte[4] shift4c = [6,4,2,0]
|
ubyte[4] shift4c = [6,4,2,0]
|
||||||
uword addr
|
|
||||||
ubyte value
|
|
||||||
|
|
||||||
when active_mode {
|
when active_mode {
|
||||||
1 -> {
|
1 -> {
|
||||||
@ -602,23 +559,43 @@ _done
|
|||||||
and #1
|
and #1
|
||||||
}}
|
}}
|
||||||
if_nz {
|
if_nz {
|
||||||
addr = x/8 + y*(320/8)
|
cx16.r0L = lsb(x) & 7 ; xbits
|
||||||
value = bits[lsb(x)&7]
|
x /= 8
|
||||||
if color
|
x += y*(320/8)
|
||||||
cx16.vpoke_or(0, addr, value)
|
%asm {{
|
||||||
else {
|
stz cx16.VERA_CTRL
|
||||||
value = ~value
|
stz cx16.VERA_ADDR_H
|
||||||
cx16.vpoke_and(0, addr, value)
|
lda x+1
|
||||||
}
|
sta cx16.VERA_ADDR_M
|
||||||
|
lda x
|
||||||
|
sta cx16.VERA_ADDR_L
|
||||||
|
ldy cx16.r0L ; xbits
|
||||||
|
lda bits,y
|
||||||
|
ldy color
|
||||||
|
beq +
|
||||||
|
tsb cx16.VERA_DATA0
|
||||||
|
bra ++
|
||||||
|
+ trb cx16.VERA_DATA0
|
||||||
|
+
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
; TODO mode 2,3
|
||||||
4 -> {
|
4 -> {
|
||||||
; lores 256c
|
; lores 256c
|
||||||
void addr_mul_24_for_lores_256c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
|
void addr_mul_24_for_lores_256c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
|
||||||
cx16.vpoke(lsb(cx16.r1), cx16.r0, color)
|
%asm {{
|
||||||
; activate vera auto-increment mode so next_pixel() can be used after this
|
stz cx16.VERA_CTRL
|
||||||
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | %00010000
|
lda cx16.r1
|
||||||
color = cx16.VERA_DATA0
|
ora #%00010000 ; enable auto-increment so next_pixel() can be used after this
|
||||||
|
sta cx16.VERA_ADDR_H
|
||||||
|
lda cx16.r0+1
|
||||||
|
sta cx16.VERA_ADDR_M
|
||||||
|
lda cx16.r0
|
||||||
|
sta cx16.VERA_ADDR_L
|
||||||
|
lda color
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
5 -> {
|
5 -> {
|
||||||
; highres monochrome
|
; highres monochrome
|
||||||
@ -629,26 +606,48 @@ _done
|
|||||||
and #1
|
and #1
|
||||||
}}
|
}}
|
||||||
if_nz {
|
if_nz {
|
||||||
addr = x/8 + y*(640/8)
|
cx16.r0L = lsb(x) & 7 ; xbits
|
||||||
value = bits[lsb(x)&7]
|
x /= 8
|
||||||
if color
|
x += y*(640/8)
|
||||||
cx16.vpoke_or(0, addr, value)
|
%asm {{
|
||||||
else {
|
stz cx16.VERA_CTRL
|
||||||
value = ~value
|
stz cx16.VERA_ADDR_H
|
||||||
cx16.vpoke_and(0, addr, value)
|
lda x+1
|
||||||
}
|
sta cx16.VERA_ADDR_M
|
||||||
|
lda x
|
||||||
|
sta cx16.VERA_ADDR_L
|
||||||
|
ldy cx16.r0L ; xbits
|
||||||
|
lda bits,y
|
||||||
|
ldy color
|
||||||
|
beq +
|
||||||
|
tsb cx16.VERA_DATA0
|
||||||
|
bra ++
|
||||||
|
+ trb cx16.VERA_DATA0
|
||||||
|
+
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
6 -> {
|
6 -> {
|
||||||
; highres 4c
|
; highres 4c
|
||||||
; TODO also mostly usable for lores 4c?
|
; TODO also mostly usable for lores 4c?
|
||||||
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
|
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
|
||||||
|
cx16.r2L = lsb(x) & 3 ; xbits
|
||||||
color &= 3
|
color &= 3
|
||||||
color <<= shift4c[lsb(x) & 3]
|
color <<= shift4c[cx16.r2L]
|
||||||
; TODO optimize the vera memory manipulation in pure assembly
|
%asm {{
|
||||||
cx16.VERA_ADDR_H &= %00000111 ; no auto advance
|
stz cx16.VERA_CTRL
|
||||||
value = cx16.vpeek(lsb(cx16.r1), cx16.r0) & mask4c[lsb(x) & 3] | color
|
lda cx16.r1L
|
||||||
cx16.vpoke(lsb(cx16.r1), cx16.r0, value)
|
sta cx16.VERA_ADDR_H
|
||||||
|
lda cx16.r0H
|
||||||
|
sta cx16.VERA_ADDR_M
|
||||||
|
lda cx16.r0L
|
||||||
|
sta cx16.VERA_ADDR_L
|
||||||
|
ldy cx16.r2L ; xbits
|
||||||
|
lda mask4c,y
|
||||||
|
and cx16.VERA_DATA0
|
||||||
|
ora color
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -682,6 +681,20 @@ _done
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub position2(uword @zp x, uword y, ubyte also_port_1) {
|
||||||
|
position(x, y)
|
||||||
|
if also_port_1 {
|
||||||
|
when active_mode {
|
||||||
|
1, 5 -> cx16.vaddr(0, cx16.r0, 1, 1)
|
||||||
|
; TODO modes 2, 3
|
||||||
|
4, 6 -> {
|
||||||
|
ubyte bank = lsb(cx16.r1)
|
||||||
|
cx16.vaddr(bank, cx16.r0, 1, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
inline asmsub next_pixel(ubyte color @A) {
|
inline asmsub next_pixel(ubyte color @A) {
|
||||||
; -- sets the next pixel byte to the graphics chip.
|
; -- sets the next pixel byte to the graphics chip.
|
||||||
; for 8 bpp screens this will plot 1 pixel.
|
; for 8 bpp screens this will plot 1 pixel.
|
||||||
@ -761,13 +774,13 @@ _done
|
|||||||
sub text(uword @zp x, uword y, ubyte color, uword sctextptr) {
|
sub text(uword @zp x, uword y, ubyte color, uword sctextptr) {
|
||||||
; -- Write some text at the given pixel position. The text string must be in screencode encoding (not petscii!).
|
; -- Write some text at the given pixel position. The text string must be in screencode encoding (not petscii!).
|
||||||
; You must also have called text_charset() first to select and prepare the character set to use.
|
; You must also have called text_charset() first to select and prepare the character set to use.
|
||||||
; NOTE: in monochrome (1bpp) screen modes, x position is currently constrained to mulitples of 8 ! TODO allow per-pixel horizontal positioning
|
; NOTE: in monochrome (1bpp) screen modes, x position is currently constrained to multiples of 8 ! TODO allow per-pixel horizontal positioning
|
||||||
uword chardataptr
|
uword chardataptr
|
||||||
when active_mode {
|
when active_mode {
|
||||||
1, 5 -> {
|
1, 5 -> {
|
||||||
; monochrome mode, either resolution
|
; monochrome mode, either resolution
|
||||||
cx16.r2 = 40
|
cx16.r2 = 40
|
||||||
if active_mode>=5
|
if active_mode==5
|
||||||
cx16.r2 = 80
|
cx16.r2 = 80
|
||||||
while @(sctextptr) {
|
while @(sctextptr) {
|
||||||
chardataptr = charset_addr + (@(sctextptr) as uword)*8
|
chardataptr = charset_addr + (@(sctextptr) as uword)*8
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
%target cx16
|
|
||||||
%import syslib
|
%import syslib
|
||||||
%import textio
|
%import textio
|
||||||
|
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
%target cx16
|
|
||||||
|
|
||||||
; Manipulate the Commander X16's display color palette.
|
; Manipulate the Commander X16's display color palette.
|
||||||
; Should you want to restore the default palette, you have to reinitialize the Vera yourself.
|
; Should you want to restore the default palette, you have to reinitialize the Vera yourself.
|
||||||
|
|
||||||
@ -9,7 +7,7 @@ palette {
|
|||||||
ubyte c
|
ubyte c
|
||||||
|
|
||||||
sub set_color(ubyte index, uword color) {
|
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))
|
cx16.vpoke(1, vera_palette_ptr, lsb(color))
|
||||||
vera_palette_ptr++
|
vera_palette_ptr++
|
||||||
cx16.vpoke(1, vera_palette_ptr, msb(color))
|
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() {
|
sub set_grayscale() {
|
||||||
vera_palette_ptr = $fa00
|
vera_palette_ptr = $fa00
|
||||||
repeat 16 {
|
repeat 16 {
|
||||||
|
@ -5,9 +5,6 @@
|
|||||||
;
|
;
|
||||||
; indent format: TABS, size=8
|
; indent format: TABS, size=8
|
||||||
|
|
||||||
%target cx16
|
|
||||||
|
|
||||||
|
|
||||||
c64 {
|
c64 {
|
||||||
|
|
||||||
; ---- kernal routines, these are the same as on the Commodore-64 (hence the same block name) ----
|
; ---- kernal routines, these are the same as on the Commodore-64 (hence the same block name) ----
|
||||||
@ -24,7 +21,7 @@ romsub $FF8D = VECTOR(uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y) ; re
|
|||||||
romsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
|
romsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
|
||||||
romsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
|
romsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
|
||||||
romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
|
romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
|
||||||
romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set top of memory pointer. NOTE: as a Cx16 extension, also returns the number of RAM memory banks in register A ! See MEMTOP2
|
romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set top of memory pointer. NOTE: as a Cx16 extension, also returns the number of RAM memory banks in register A ! See cx16.numbanks()
|
||||||
romsub $FF9C = MEMBOT(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
|
romsub $FF9C = MEMBOT(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
|
||||||
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
|
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
|
||||||
romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
|
romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
|
||||||
@ -35,7 +32,7 @@ romsub $FFAE = UNLSN() clobbers(A) ; command serial
|
|||||||
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
|
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
|
||||||
romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
|
romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
|
||||||
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word
|
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word
|
||||||
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte address @ Y) ; set logical file parameters
|
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte secondary @ Y) ; set logical file parameters
|
||||||
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
|
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
|
||||||
romsub $FFC0 = OPEN() clobbers(X,Y) -> ubyte @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
|
romsub $FFC0 = OPEN() clobbers(X,Y) -> ubyte @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
|
||||||
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
|
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
|
||||||
@ -44,10 +41,10 @@ romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320
|
|||||||
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
|
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
|
||||||
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
|
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
|
||||||
romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326)) output a character
|
romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326)) output a character
|
||||||
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, ubyte @ X, ubyte @ Y ; (via 816 ($330)) load from device
|
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, uword @ XY ; (via 816 ($330)) load from device
|
||||||
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
|
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
|
||||||
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
|
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
|
||||||
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock
|
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (A=lo,X=mid,Y=high)
|
||||||
romsub $FFE1 = STOP() clobbers(X) -> ubyte @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
|
romsub $FFE1 = STOP() clobbers(X) -> ubyte @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
|
||||||
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @Pc, ubyte @ A ; (via 810 ($32A)) get a character
|
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @Pc, ubyte @ A ; (via 810 ($32A)) get a character
|
||||||
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
|
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
|
||||||
@ -74,7 +71,7 @@ asmsub STOP2() -> ubyte @A {
|
|||||||
}
|
}
|
||||||
|
|
||||||
asmsub RDTIM16() -> uword @AY {
|
asmsub RDTIM16() -> uword @AY {
|
||||||
; -- like RDTIM() but only returning the lower 16 bits for convenience
|
; -- like RDTIM() but only returning the lower 16 bits in AY for convenience
|
||||||
%asm {{
|
%asm {{
|
||||||
phx
|
phx
|
||||||
jsr c64.RDTIM
|
jsr c64.RDTIM
|
||||||
@ -87,17 +84,6 @@ asmsub RDTIM16() -> uword @AY {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
asmsub MEMTOP2() -> ubyte @A {
|
|
||||||
; -- uses MEMTOP's cx16 extension to query the number of available RAM banks.
|
|
||||||
%asm {{
|
|
||||||
phx
|
|
||||||
sec
|
|
||||||
jsr c64.MEMTOP
|
|
||||||
plx
|
|
||||||
rts
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cx16 {
|
cx16 {
|
||||||
@ -127,6 +113,41 @@ cx16 {
|
|||||||
&uword r14 = $001e
|
&uword r14 = $001e
|
||||||
&uword r15 = $0020
|
&uword r15 = $0020
|
||||||
|
|
||||||
|
&ubyte r0L = $0002
|
||||||
|
&ubyte r1L = $0004
|
||||||
|
&ubyte r2L = $0006
|
||||||
|
&ubyte r3L = $0008
|
||||||
|
&ubyte r4L = $000a
|
||||||
|
&ubyte r5L = $000c
|
||||||
|
&ubyte r6L = $000e
|
||||||
|
&ubyte r7L = $0010
|
||||||
|
&ubyte r8L = $0012
|
||||||
|
&ubyte r9L = $0014
|
||||||
|
&ubyte r10L = $0016
|
||||||
|
&ubyte r11L = $0018
|
||||||
|
&ubyte r12L = $001a
|
||||||
|
&ubyte r13L = $001c
|
||||||
|
&ubyte r14L = $001e
|
||||||
|
&ubyte r15L = $0020
|
||||||
|
|
||||||
|
&ubyte r0H = $0003
|
||||||
|
&ubyte r1H = $0005
|
||||||
|
&ubyte r2H = $0007
|
||||||
|
&ubyte r3H = $0009
|
||||||
|
&ubyte r4H = $000b
|
||||||
|
&ubyte r5H = $000d
|
||||||
|
&ubyte r6H = $000f
|
||||||
|
&ubyte r7H = $0011
|
||||||
|
&ubyte r8H = $0013
|
||||||
|
&ubyte r9H = $0015
|
||||||
|
&ubyte r10H = $0017
|
||||||
|
&ubyte r11H = $0019
|
||||||
|
&ubyte r12H = $001b
|
||||||
|
&ubyte r13H = $001d
|
||||||
|
&ubyte r14H = $001f
|
||||||
|
&ubyte r15H = $0021
|
||||||
|
|
||||||
|
|
||||||
; VERA registers
|
; VERA registers
|
||||||
|
|
||||||
const uword VERA_BASE = $9F20
|
const uword VERA_BASE = $9F20
|
||||||
@ -172,7 +193,7 @@ cx16 {
|
|||||||
|
|
||||||
; I/O
|
; I/O
|
||||||
|
|
||||||
const uword via1 = $9f60 ;VIA 6522 #1
|
const uword via1 = $9f00 ;VIA 6522 #1
|
||||||
&ubyte d1prb = via1+0
|
&ubyte d1prb = via1+0
|
||||||
&ubyte d1pra = via1+1
|
&ubyte d1pra = via1+1
|
||||||
&ubyte d1ddrb = via1+2
|
&ubyte d1ddrb = via1+2
|
||||||
@ -190,7 +211,7 @@ cx16 {
|
|||||||
&ubyte d1ier = via1+14
|
&ubyte d1ier = via1+14
|
||||||
&ubyte d1ora = via1+15
|
&ubyte d1ora = via1+15
|
||||||
|
|
||||||
const uword via2 = $9f70 ;VIA 6522 #2
|
const uword via2 = $9f10 ;VIA 6522 #2
|
||||||
&ubyte d2prb = via2+0
|
&ubyte d2prb = via2+0
|
||||||
&ubyte d2pra = via2+1
|
&ubyte d2pra = via2+1
|
||||||
&ubyte d2ddrb = via2+2
|
&ubyte d2ddrb = via2+2
|
||||||
@ -208,6 +229,11 @@ cx16 {
|
|||||||
&ubyte d2ier = via2+14
|
&ubyte d2ier = via2+14
|
||||||
&ubyte d2ora = via2+15
|
&ubyte d2ora = via2+15
|
||||||
|
|
||||||
|
&ubyte ym2151adr = $9f40
|
||||||
|
&ubyte ym2151dat = $9f41
|
||||||
|
|
||||||
|
const uword extdev = $9f60
|
||||||
|
|
||||||
|
|
||||||
; ---- Commander X-16 additions on top of C64 kernal routines ----
|
; ---- Commander X-16 additions on top of C64 kernal routines ----
|
||||||
; spelling of the names is taken from the Commander X-16 rom sources
|
; spelling of the names is taken from the Commander X-16 rom sources
|
||||||
@ -294,16 +320,25 @@ romsub $fecc = monitor() clobbers(A,X,Y)
|
|||||||
inline asmsub rombank(ubyte rombank @A) {
|
inline asmsub rombank(ubyte rombank @A) {
|
||||||
; -- set the rom banks
|
; -- set the rom banks
|
||||||
%asm {{
|
%asm {{
|
||||||
sta $01 ; rom bank register (new)
|
sta $01 ; rom bank register (v39+, used to be cx16.d1prb $9f60 in v38)
|
||||||
sta cx16.d1prb ; rom bank register (old)
|
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline asmsub rambank(ubyte rambank @A) {
|
inline asmsub rambank(ubyte rambank @A) {
|
||||||
; -- set the ram bank
|
; -- set the ram bank
|
||||||
%asm {{
|
%asm {{
|
||||||
sta $00 ; ram bank register (new)
|
sta $00 ; ram bank register (v39+, used to be cx16.d1pra $9f61 in v38)
|
||||||
sta cx16.d1pra ; ram bank register (old)
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub numbanks() -> ubyte @A {
|
||||||
|
; -- uses MEMTOP's cx16 extension to query the number of available RAM banks. (each is 8 Kb)
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
sec
|
||||||
|
jsr c64.MEMTOP
|
||||||
|
plx
|
||||||
|
rts
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -350,75 +385,126 @@ asmsub vaddr(ubyte bank @A, uword address @R0, ubyte addrsel @R1, byte autoIncrO
|
|||||||
}
|
}
|
||||||
|
|
||||||
asmsub vpoke(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers(A) {
|
asmsub vpoke(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers(A) {
|
||||||
; -- write a single byte to VERA's video memory
|
; -- write a single byte to VERA's video memory
|
||||||
; note: inefficient when writing multiple sequential bytes!
|
; note: inefficient when writing multiple sequential bytes!
|
||||||
%asm {{
|
%asm {{
|
||||||
stz cx16.VERA_CTRL
|
stz cx16.VERA_CTRL
|
||||||
and #1
|
and #1
|
||||||
sta cx16.VERA_ADDR_H
|
sta cx16.VERA_ADDR_H
|
||||||
lda cx16.r0
|
lda cx16.r0
|
||||||
sta cx16.VERA_ADDR_L
|
sta cx16.VERA_ADDR_L
|
||||||
lda cx16.r0+1
|
lda cx16.r0+1
|
||||||
sta cx16.VERA_ADDR_M
|
sta cx16.VERA_ADDR_M
|
||||||
sty cx16.VERA_DATA0
|
sty cx16.VERA_DATA0
|
||||||
rts
|
rts
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
asmsub vpoke_or(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers (A) {
|
asmsub vpoke_or(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers (A) {
|
||||||
; -- or a single byte to the value already in the VERA's video memory at that location
|
; -- or a single byte to the value already in the VERA's video memory at that location
|
||||||
; note: inefficient when writing multiple sequential bytes!
|
; note: inefficient when writing multiple sequential bytes!
|
||||||
%asm {{
|
%asm {{
|
||||||
stz cx16.VERA_CTRL
|
stz cx16.VERA_CTRL
|
||||||
and #1
|
and #1
|
||||||
sta cx16.VERA_ADDR_H
|
sta cx16.VERA_ADDR_H
|
||||||
lda cx16.r0
|
lda cx16.r0
|
||||||
sta cx16.VERA_ADDR_L
|
sta cx16.VERA_ADDR_L
|
||||||
lda cx16.r0+1
|
lda cx16.r0+1
|
||||||
sta cx16.VERA_ADDR_M
|
sta cx16.VERA_ADDR_M
|
||||||
tya
|
tya
|
||||||
ora cx16.VERA_DATA0
|
ora cx16.VERA_DATA0
|
||||||
sta cx16.VERA_DATA0
|
sta cx16.VERA_DATA0
|
||||||
rts
|
rts
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
asmsub vpoke_and(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers(A) {
|
asmsub vpoke_and(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers(A) {
|
||||||
; -- and a single byte to the value already in the VERA's video memory at that location
|
; -- and a single byte to the value already in the VERA's video memory at that location
|
||||||
; note: inefficient when writing multiple sequential bytes!
|
; note: inefficient when writing multiple sequential bytes!
|
||||||
%asm {{
|
%asm {{
|
||||||
stz cx16.VERA_CTRL
|
stz cx16.VERA_CTRL
|
||||||
and #1
|
and #1
|
||||||
sta cx16.VERA_ADDR_H
|
sta cx16.VERA_ADDR_H
|
||||||
lda cx16.r0
|
lda cx16.r0
|
||||||
sta cx16.VERA_ADDR_L
|
sta cx16.VERA_ADDR_L
|
||||||
lda cx16.r0+1
|
lda cx16.r0+1
|
||||||
sta cx16.VERA_ADDR_M
|
sta cx16.VERA_ADDR_M
|
||||||
tya
|
tya
|
||||||
and cx16.VERA_DATA0
|
and cx16.VERA_DATA0
|
||||||
sta cx16.VERA_DATA0
|
sta cx16.VERA_DATA0
|
||||||
rts
|
rts
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
asmsub vpoke_xor(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers (A) {
|
asmsub vpoke_xor(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers (A) {
|
||||||
; -- xor a single byte to the value already in the VERA's video memory at that location
|
; -- xor a single byte to the value already in the VERA's video memory at that location
|
||||||
; note: inefficient when writing multiple sequential bytes!
|
; note: inefficient when writing multiple sequential bytes!
|
||||||
%asm {{
|
%asm {{
|
||||||
stz cx16.VERA_CTRL
|
stz cx16.VERA_CTRL
|
||||||
and #1
|
and #1
|
||||||
sta cx16.VERA_ADDR_H
|
sta cx16.VERA_ADDR_H
|
||||||
lda cx16.r0
|
lda cx16.r0
|
||||||
sta cx16.VERA_ADDR_L
|
sta cx16.VERA_ADDR_L
|
||||||
lda cx16.r0+1
|
lda cx16.r0+1
|
||||||
sta cx16.VERA_ADDR_M
|
sta cx16.VERA_ADDR_M
|
||||||
tya
|
tya
|
||||||
eor cx16.VERA_DATA0
|
eor cx16.VERA_DATA0
|
||||||
sta cx16.VERA_DATA0
|
sta cx16.VERA_DATA0
|
||||||
rts
|
rts
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
asmsub vload(str name @R0, ubyte device @Y, ubyte bank @A, uword address @R1) -> ubyte @A {
|
||||||
|
; -- like the basic command VLOAD "filename",device,bank,address
|
||||||
|
; loads a file into video memory in the given bank:address, returns success in A
|
||||||
|
; !! NOTE !! the V38 ROMs contain a bug in the LOAD code that makes the load address not work correctly,
|
||||||
|
; it works fine when loading from local filesystem
|
||||||
|
%asm {{
|
||||||
|
; -- load a file into video ram
|
||||||
|
phx
|
||||||
|
pha
|
||||||
|
tya
|
||||||
|
tax
|
||||||
|
lda #1
|
||||||
|
ldy #0
|
||||||
|
jsr c64.SETLFS
|
||||||
|
lda cx16.r0
|
||||||
|
ldy cx16.r0+1
|
||||||
|
jsr prog8_lib.strlen
|
||||||
|
tya
|
||||||
|
ldx cx16.r0
|
||||||
|
ldy cx16.r0+1
|
||||||
|
jsr c64.SETNAM
|
||||||
|
pla
|
||||||
|
clc
|
||||||
|
adc #2
|
||||||
|
ldx cx16.r1
|
||||||
|
ldy cx16.r1+1
|
||||||
|
stz P8ZP_SCRATCH_B1
|
||||||
|
jsr c64.LOAD
|
||||||
|
bcs +
|
||||||
|
inc P8ZP_SCRATCH_B1
|
||||||
|
+ jsr c64.CLRCHN
|
||||||
|
lda #1
|
||||||
|
jsr c64.CLOSE
|
||||||
|
plx
|
||||||
|
lda P8ZP_SCRATCH_B1
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline asmsub joystick_get2(ubyte joynr @A) clobbers(Y) -> uword @AX {
|
||||||
|
; convenience routine to get the joystick state without requiring inline assembly that deals with the multiple return values.
|
||||||
|
; Also disables interrupts to avoid the IRQ race condition mentioned here: https://github.com/commanderx16/x16-rom/issues/203
|
||||||
|
; TODO once that issue is resolved, this routine can be redefined as: romsub $ff56 = joystick_get2(ubyte joynr @A) clobbers(Y) -> uword @AX
|
||||||
|
%asm {{
|
||||||
|
sei
|
||||||
|
jsr cx16.joystick_get
|
||||||
|
cli
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
sub FB_set_pixels_from_buf(uword buffer, uword count) {
|
sub FB_set_pixels_from_buf(uword buffer, uword count) {
|
||||||
%asm {{
|
%asm {{
|
||||||
; -- This is replacement code for the normal FB_set_pixels subroutine in ROM
|
; -- This is replacement code for the normal FB_set_pixels subroutine in ROM
|
||||||
@ -460,11 +546,9 @@ asmsub init_system() {
|
|||||||
%asm {{
|
%asm {{
|
||||||
sei
|
sei
|
||||||
cld
|
cld
|
||||||
;stz $00
|
|
||||||
;stz $01
|
|
||||||
;stz d1prb ; select rom bank 0 (enable kernal)
|
|
||||||
lda #$80
|
lda #$80
|
||||||
sta VERA_CTRL
|
sta VERA_CTRL
|
||||||
|
stz $01 ; select rom bank 0 (enable kernal)
|
||||||
jsr c64.IOINIT
|
jsr c64.IOINIT
|
||||||
jsr c64.RESTOR
|
jsr c64.RESTOR
|
||||||
jsr c64.CINT
|
jsr c64.CINT
|
||||||
@ -664,23 +748,37 @@ sys {
|
|||||||
|
|
||||||
|
|
||||||
asmsub reset_system() {
|
asmsub reset_system() {
|
||||||
; Soft-reset the system back to Basic prompt.
|
; Soft-reset the system back to initial power-on Basic prompt.
|
||||||
%asm {{
|
%asm {{
|
||||||
sei
|
sei
|
||||||
stz $01 ; bank the kernal in (new rom bank register)
|
stz $01 ; bank the kernal in
|
||||||
stz cx16.d1prb ; bank the kernal in (old rom bank register)
|
|
||||||
jmp (cx16.RESET_VEC)
|
jmp (cx16.RESET_VEC)
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub wait(uword jiffies) {
|
asmsub wait(uword jiffies @AY) {
|
||||||
; --- wait approximately the given number of jiffies (1/60th seconds)
|
; --- wait approximately the given number of jiffies (1/60th seconds) (N or N+1)
|
||||||
repeat jiffies {
|
; note: regular system vsync irq handler must be running, and no nother irqs
|
||||||
ubyte jiff = lsb(c64.RDTIM16())
|
%asm {{
|
||||||
while jiff==lsb(c64.RDTIM16()) {
|
- wai ; wait for irq (assume it was vsync)
|
||||||
; wait until 1 jiffy has passed
|
cmp #0
|
||||||
}
|
bne +
|
||||||
}
|
dey
|
||||||
|
+ dec a
|
||||||
|
bne -
|
||||||
|
cpy #0
|
||||||
|
bne -
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {{
|
||||||
|
wai
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
|
inline asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
;
|
;
|
||||||
; indent format: TABS, size=8
|
; indent format: TABS, size=8
|
||||||
|
|
||||||
%target cx16
|
|
||||||
%import syslib
|
%import syslib
|
||||||
%import conv
|
%import conv
|
||||||
|
|
||||||
|
@ -336,6 +336,45 @@ _end rts
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
; ----- iterative file saver functions (uses io channel 14) -----
|
||||||
|
|
||||||
|
sub f_open_w(ubyte drivenumber, uword filenameptr) -> ubyte {
|
||||||
|
; -- open a file for iterative writing with f_write
|
||||||
|
f_close_w()
|
||||||
|
|
||||||
|
c64.SETNAM(string.length(filenameptr), filenameptr)
|
||||||
|
c64.SETLFS(14, drivenumber, 1)
|
||||||
|
void c64.OPEN() ; open 14,8,1,"filename"
|
||||||
|
if_cc {
|
||||||
|
void c64.CHKOUT(14) ; use #14 as input channel
|
||||||
|
return not c64.READST()
|
||||||
|
}
|
||||||
|
f_close_w()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
sub f_write(uword bufferpointer, uword num_bytes) -> ubyte {
|
||||||
|
; -- write the given umber of bytes to the currently open file
|
||||||
|
if num_bytes!=0 {
|
||||||
|
void c64.CHKOUT(14) ; use #14 as input channel again
|
||||||
|
repeat num_bytes {
|
||||||
|
c64.CHROUT(@(bufferpointer))
|
||||||
|
bufferpointer++
|
||||||
|
}
|
||||||
|
return not c64.READST()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
sub f_close_w() {
|
||||||
|
; -- end an iterative file writing session (close channels).
|
||||||
|
c64.CLRCHN()
|
||||||
|
c64.CLOSE(14)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
; ---- other functions ----
|
||||||
|
|
||||||
sub status(ubyte drivenumber) -> uword {
|
sub status(ubyte drivenumber) -> uword {
|
||||||
; -- retrieve the disk drive's current status message
|
; -- retrieve the disk drive's current status message
|
||||||
uword messageptr = &filename
|
uword messageptr = &filename
|
||||||
@ -353,18 +392,22 @@ _end rts
|
|||||||
messageptr++
|
messageptr++
|
||||||
}
|
}
|
||||||
|
|
||||||
io_error:
|
|
||||||
@(messageptr) = 0
|
@(messageptr) = 0
|
||||||
|
done:
|
||||||
c64.CLRCHN() ; restore default i/o devices
|
c64.CLRCHN() ; restore default i/o devices
|
||||||
c64.CLOSE(15)
|
c64.CLOSE(15)
|
||||||
return filename
|
return filename
|
||||||
}
|
|
||||||
|
|
||||||
|
io_error:
|
||||||
|
filename = "?disk error"
|
||||||
|
goto done
|
||||||
|
}
|
||||||
|
|
||||||
sub save(ubyte drivenumber, uword filenameptr, uword address, uword size) -> ubyte {
|
sub save(ubyte drivenumber, uword filenameptr, uword address, uword size) -> ubyte {
|
||||||
c64.SETNAM(string.length(filenameptr), filenameptr)
|
c64.SETNAM(string.length(filenameptr), filenameptr)
|
||||||
c64.SETLFS(1, drivenumber, 0)
|
c64.SETLFS(1, drivenumber, 0)
|
||||||
uword end_address = address + size
|
uword end_address = address + size
|
||||||
|
first_byte = 0 ; result var reuse
|
||||||
|
|
||||||
%asm {{
|
%asm {{
|
||||||
lda address
|
lda address
|
||||||
@ -381,7 +424,6 @@ io_error:
|
|||||||
plp
|
plp
|
||||||
}}
|
}}
|
||||||
|
|
||||||
first_byte = 0 ; result var reuse
|
|
||||||
if_cc
|
if_cc
|
||||||
first_byte = c64.READST()==0
|
first_byte = c64.READST()==0
|
||||||
|
|
||||||
|
@ -244,25 +244,6 @@ randseed .proc
|
|||||||
.pend
|
.pend
|
||||||
|
|
||||||
|
|
||||||
fast_randbyte .proc
|
|
||||||
; -- fast but bad 8-bit pseudo random number generator into A
|
|
||||||
lda _seed
|
|
||||||
beq _eor
|
|
||||||
asl a
|
|
||||||
beq _done ; if the input was $80, skip the EOR
|
|
||||||
bcc _done
|
|
||||||
_eor eor #$1d ; xor with magic value see below for possible values
|
|
||||||
_done sta _seed
|
|
||||||
rts
|
|
||||||
|
|
||||||
_seed .byte $3a
|
|
||||||
|
|
||||||
; possible 'magic' eor bytes are:
|
|
||||||
; $1d, $2b, $2d, $4d, $5f, $63, $65, $69
|
|
||||||
; $71, $87, $8d, $a9, $c3, $cf, $e7, $f5
|
|
||||||
|
|
||||||
.pend
|
|
||||||
|
|
||||||
randbyte .proc
|
randbyte .proc
|
||||||
; -- 8 bit pseudo random number generator into A (by just reusing randword)
|
; -- 8 bit pseudo random number generator into A (by just reusing randword)
|
||||||
jmp randword
|
jmp randword
|
||||||
@ -271,45 +252,37 @@ randbyte .proc
|
|||||||
randword .proc
|
randword .proc
|
||||||
; -- 16 bit pseudo random number generator into AY
|
; -- 16 bit pseudo random number generator into AY
|
||||||
|
|
||||||
magic_eor = $3f1d
|
; rand64k ;Factors of 65535: 3 5 17 257
|
||||||
; possible magic eor words are:
|
lda sr1+1
|
||||||
; $3f1d, $3f81, $3fa5, $3fc5, $4075, $409d, $40cd, $4109
|
asl a
|
||||||
; $413f, $414b, $4153, $4159, $4193, $4199, $41af, $41bb
|
asl a
|
||||||
|
eor sr1+1
|
||||||
|
asl a
|
||||||
|
eor sr1+1
|
||||||
|
asl a
|
||||||
|
asl a
|
||||||
|
eor sr1+1
|
||||||
|
asl a
|
||||||
|
rol sr1 ;shift this left, "random" bit comes from low
|
||||||
|
rol sr1+1
|
||||||
|
; rand32k ;Factors of 32767: 7 31 151 are independent and can be combined
|
||||||
|
lda sr2+1
|
||||||
|
asl a
|
||||||
|
eor sr2+1
|
||||||
|
asl a
|
||||||
|
asl a
|
||||||
|
ror sr2 ;shift this right, random bit comes from high - nicer when eor with sr1
|
||||||
|
rol sr2+1
|
||||||
|
lda sr1+1 ;can be left out
|
||||||
|
eor sr2+1 ;if you dont use
|
||||||
|
tay ;y as suggested
|
||||||
|
lda sr1 ;mix up lowbytes of SR1
|
||||||
|
eor sr2 ;and SR2 to combine both
|
||||||
|
rts
|
||||||
|
|
||||||
lda _seed
|
sr1 .word $a55a
|
||||||
beq _lowZero ; $0000 and $8000 are special values to test for
|
sr2 .word $7653
|
||||||
|
|
||||||
; Do a normal shift
|
|
||||||
asl _seed
|
|
||||||
lda _seed+1
|
|
||||||
rol a
|
|
||||||
bcc _noEor
|
|
||||||
|
|
||||||
_doEor ; high byte is in A
|
|
||||||
eor #>magic_eor
|
|
||||||
sta _seed+1
|
|
||||||
lda _seed
|
|
||||||
eor #<magic_eor
|
|
||||||
sta _seed
|
|
||||||
ldy _seed+1
|
|
||||||
rts
|
|
||||||
|
|
||||||
_lowZero lda _seed+1
|
|
||||||
beq _doEor ; High byte is also zero, so apply the EOR
|
|
||||||
; For speed, you could store 'magic' into 'seed' directly
|
|
||||||
; instead of running the EORs
|
|
||||||
|
|
||||||
; wasn't zero, check for $8000
|
|
||||||
asl a
|
|
||||||
beq _noEor ; if $00 is left after the shift, then it was $80
|
|
||||||
bcs _doEor ; else, do the EOR based on the carry bit as usual
|
|
||||||
|
|
||||||
_noEor sta _seed+1
|
|
||||||
tay
|
|
||||||
lda _seed
|
|
||||||
rts
|
|
||||||
|
|
||||||
_seed .word $2c9e
|
|
||||||
.pend
|
.pend
|
||||||
|
|
||||||
|
|
||||||
@ -801,6 +774,13 @@ stack_mul_word_320 .proc
|
|||||||
rts
|
rts
|
||||||
.pend
|
.pend
|
||||||
|
|
||||||
|
stack_mul_word_640 .proc
|
||||||
|
; stackW = (stackLo * 2 * 320) (stackHi doesn't matter)
|
||||||
|
asl P8ESTACK_LO+1,x
|
||||||
|
jmp stack_mul_word_320
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
; ----------- optimized multiplications (in-place A (byte) and ?? (word)) : ---------
|
; ----------- optimized multiplications (in-place A (byte) and ?? (word)) : ---------
|
||||||
mul_byte_3 .proc
|
mul_byte_3 .proc
|
||||||
; A = A + A*2
|
; A = A + A*2
|
||||||
@ -1291,6 +1271,13 @@ mul_word_320 .proc
|
|||||||
rts
|
rts
|
||||||
.pend
|
.pend
|
||||||
|
|
||||||
|
mul_word_640 .proc
|
||||||
|
; AY = (A * 2 * 320) (msb in Y doesn't matter)
|
||||||
|
asl a
|
||||||
|
jmp mul_word_320
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
; ----------- end optimized multiplications -----------
|
; ----------- end optimized multiplications -----------
|
||||||
|
|
||||||
|
|
||||||
@ -1541,3 +1528,71 @@ _negative lsr a
|
|||||||
rts
|
rts
|
||||||
.pend
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
square .proc
|
||||||
|
; -- calculate square root of signed word in AY, result in AY
|
||||||
|
; routine by Lee Davsion, source: http://6502.org/source/integers/square.htm
|
||||||
|
; using this routine is about twice as fast as doing a regular multiplication.
|
||||||
|
;
|
||||||
|
; Calculates the 16 bit unsigned integer square of the signed 16 bit integer in
|
||||||
|
; Numberl/Numberh. The result is always in the range 0 to 65025 and is held in
|
||||||
|
; Squarel/Squareh
|
||||||
|
;
|
||||||
|
; The maximum input range is only +/-255 and no checking is done to ensure that
|
||||||
|
; this is so.
|
||||||
|
;
|
||||||
|
; This routine is useful if you are trying to draw circles as for any circle
|
||||||
|
;
|
||||||
|
; x^2+y^2=r^2 where x and y are the co-ordinates of any point on the circle and
|
||||||
|
; r is the circle radius
|
||||||
|
|
||||||
|
numberl = P8ZP_SCRATCH_W1 ; number to square low byte
|
||||||
|
numberh = P8ZP_SCRATCH_W1+1 ; number to square high byte
|
||||||
|
squarel = P8ZP_SCRATCH_W2 ; square low byte
|
||||||
|
squareh = P8ZP_SCRATCH_W2+1 ; square high byte
|
||||||
|
tempsq = P8ZP_SCRATCH_B1 ; temp byte for intermediate result
|
||||||
|
|
||||||
|
sta numberl
|
||||||
|
sty numberh
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
|
||||||
|
lda #$00 ; clear a
|
||||||
|
sta squarel ; clear square low byte
|
||||||
|
; (no need to clear the high byte, it gets shifted out)
|
||||||
|
lda numberl ; get number low byte
|
||||||
|
ldx numberh ; get number high byte
|
||||||
|
bpl _nonneg ; if +ve don't negate it
|
||||||
|
; else do a two's complement
|
||||||
|
eor #$ff ; invert
|
||||||
|
sec ; +1
|
||||||
|
adc #$00 ; and add it
|
||||||
|
|
||||||
|
_nonneg:
|
||||||
|
sta tempsq ; save abs(number)
|
||||||
|
ldx #$08 ; set bit count
|
||||||
|
|
||||||
|
_nextr2bit:
|
||||||
|
asl squarel ; low byte *2
|
||||||
|
rol squareh ; high byte *2+carry from low
|
||||||
|
asl a ; shift number byte
|
||||||
|
bcc _nosqadd ; don't do add if c = 0
|
||||||
|
tay ; save a
|
||||||
|
clc ; clear carry for add
|
||||||
|
lda tempsq ; get number
|
||||||
|
adc squarel ; add number^2 low byte
|
||||||
|
sta squarel ; save number^2 low byte
|
||||||
|
lda #$00 ; clear a
|
||||||
|
adc squareh ; add number^2 high byte
|
||||||
|
sta squareh ; save number^2 high byte
|
||||||
|
tya ; get a back
|
||||||
|
|
||||||
|
_nosqadd:
|
||||||
|
dex ; decrement bit count
|
||||||
|
bne _nextr2bit ; go do next bit
|
||||||
|
|
||||||
|
lda squarel
|
||||||
|
ldy squareh
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
|
||||||
|
.pend
|
||||||
|
@ -3,5 +3,5 @@
|
|||||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||||
|
|
||||||
math {
|
math {
|
||||||
%asminclude "library:math.asm", ""
|
%asminclude "library:math.asm"
|
||||||
}
|
}
|
||||||
|
@ -387,14 +387,6 @@ func_sqrt16_into_A .proc
|
|||||||
rts
|
rts
|
||||||
.pend
|
.pend
|
||||||
|
|
||||||
func_fastrnd8_stack .proc
|
|
||||||
; -- put a random ubyte on the estack (using fast but bad RNG)
|
|
||||||
jsr math.fast_randbyte
|
|
||||||
sta P8ESTACK_LO,x
|
|
||||||
dex
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_rnd_stack .proc
|
func_rnd_stack .proc
|
||||||
; -- put a random ubyte on the estack
|
; -- put a random ubyte on the estack
|
||||||
jsr math.randbyte
|
jsr math.randbyte
|
||||||
|
@ -1072,3 +1072,14 @@ sign_extend_AY_byte .proc
|
|||||||
rts
|
rts
|
||||||
.pend
|
.pend
|
||||||
|
|
||||||
|
strlen .proc
|
||||||
|
; -- returns the number of bytes in the string in AY, in Y.
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
ldy #0
|
||||||
|
- lda (P8ZP_SCRATCH_W1),y
|
||||||
|
beq +
|
||||||
|
iny
|
||||||
|
bne -
|
||||||
|
+ rts
|
||||||
|
.pend
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||||
|
|
||||||
prog8_lib {
|
prog8_lib {
|
||||||
%asminclude "library:prog8_lib.asm", ""
|
%asminclude "library:prog8_lib.asm"
|
||||||
%asminclude "library:prog8_funcs.asm", ""
|
%asminclude "library:prog8_funcs.asm"
|
||||||
|
|
||||||
uword @zp retval_interm_uw ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
|
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)
|
word @zp retval_interm_w ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
|
||||||
|
@ -178,7 +178,7 @@ _found sty P8ZP_SCRATCH_B1
|
|||||||
|
|
||||||
asmsub compare(uword string1 @R0, uword string2 @AY) clobbers(Y) -> byte @A {
|
asmsub compare(uword string1 @R0, uword string2 @AY) clobbers(Y) -> byte @A {
|
||||||
; Compares two strings for sorting.
|
; Compares two strings for sorting.
|
||||||
; Returns -1 (255), 0 or 1 depeding on wether string1 sorts before, equal or after string2.
|
; Returns -1 (255), 0 or 1 depending on wether string1 sorts before, equal or after string2.
|
||||||
; Note that you can also directly compare strings and string values with eachother using
|
; Note that you can also directly compare strings and string values with eachother using
|
||||||
; comparison operators ==, < etcetera (it will use strcmp for you under water automatically).
|
; comparison operators ==, < etcetera (it will use strcmp for you under water automatically).
|
||||||
%asm {{
|
%asm {{
|
||||||
@ -190,8 +190,8 @@ _found sty P8ZP_SCRATCH_B1
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
asmsub lower(uword st @AY) {
|
asmsub lower(uword st @AY) -> ubyte @Y {
|
||||||
; Lowercases the petscii string in-place.
|
; Lowercases the petscii string in-place. Returns length of the string.
|
||||||
; (for efficiency, non-letter characters > 128 will also not be left intact,
|
; (for efficiency, non-letter characters > 128 will also not be left intact,
|
||||||
; but regular text doesn't usually contain those characters anyway.)
|
; but regular text doesn't usually contain those characters anyway.)
|
||||||
%asm {{
|
%asm {{
|
||||||
@ -213,8 +213,8 @@ _done rts
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
asmsub upper(uword st @AY) {
|
asmsub upper(uword st @AY) -> ubyte @Y {
|
||||||
; Uppercases the petscii string in-place.
|
; Uppercases the petscii string in-place. Returns length of the string.
|
||||||
%asm {{
|
%asm {{
|
||||||
sta P8ZP_SCRATCH_W1
|
sta P8ZP_SCRATCH_W1
|
||||||
sty P8ZP_SCRATCH_W1+1
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
@ -1 +1 @@
|
|||||||
6.3
|
7.1
|
||||||
|
@ -1,15 +1,13 @@
|
|||||||
package prog8
|
package prog8
|
||||||
|
|
||||||
import kotlinx.cli.ArgParser
|
import kotlinx.cli.*
|
||||||
import kotlinx.cli.ArgType
|
|
||||||
import kotlinx.cli.default
|
|
||||||
import kotlinx.cli.multiple
|
|
||||||
import prog8.ast.base.AstException
|
import prog8.ast.base.AstException
|
||||||
import prog8.compiler.CompilationResult
|
import prog8.compiler.CompilationResult
|
||||||
import prog8.compiler.compileProgram
|
import prog8.compiler.compileProgram
|
||||||
import prog8.compiler.target.C64Target
|
import prog8.compiler.target.C64Target
|
||||||
import prog8.compiler.target.Cx16Target
|
import prog8.compiler.target.Cx16Target
|
||||||
import prog8.parser.ParsingFailedError
|
import prog8.parser.ParsingFailedError
|
||||||
|
import java.io.File
|
||||||
import java.nio.file.FileSystems
|
import java.nio.file.FileSystems
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.nio.file.StandardWatchEventKinds
|
import java.nio.file.StandardWatchEventKinds
|
||||||
@ -18,45 +16,55 @@ import kotlin.system.exitProcess
|
|||||||
|
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
printSoftwareHeader("compiler")
|
|
||||||
|
|
||||||
compileMain(args)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun printSoftwareHeader(what: String) {
|
|
||||||
val buildVersion = object {}.javaClass.getResource("/version.txt").readText().trim()
|
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")
|
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)
|
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 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 outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".")
|
||||||
val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code")
|
val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code")
|
||||||
val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform any optimizations")
|
val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform any optimizations")
|
||||||
val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watches for file changes), greatly increases compilation speed")
|
val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watches for file changes), greatly increases compilation speed")
|
||||||
val slowCodegenWarnings by cli.option(ArgType.Boolean, fullName = "slowwarn", description="show debug warnings about slow/problematic assembly code generation")
|
val slowCodegenWarnings by cli.option(ArgType.Boolean, fullName = "slowwarn", description="show debug warnings about slow/problematic assembly code generation")
|
||||||
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available").default(C64Target.name)
|
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available").default(C64Target.name)
|
||||||
|
val sourceDirs by cli.option(ArgType.String, fullName="srcdirs", description = "list of extra paths to search in for imported modules").multiple().delimiter(File.pathSeparator)
|
||||||
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999)
|
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
cli.parse(args)
|
cli.parse(args)
|
||||||
} catch (e: IllegalStateException) {
|
} catch (e: IllegalStateException) {
|
||||||
System.err.println(e.message)
|
System.err.println(e.message)
|
||||||
exitProcess(1)
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
val outputPath = pathFrom(outputDir)
|
val outputPath = pathFrom(outputDir)
|
||||||
if(!outputPath.toFile().isDirectory) {
|
if(!outputPath.toFile().isDirectory) {
|
||||||
System.err.println("Output path doesn't exist")
|
System.err.println("Output path doesn't exist")
|
||||||
exitProcess(1)
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val faultyOption = moduleFiles.firstOrNull { it.startsWith('-') }
|
||||||
|
if(faultyOption!=null) {
|
||||||
|
System.err.println("Unknown command line option given: $faultyOption")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val srcdirs = sourceDirs.toMutableList()
|
||||||
|
if(srcdirs.firstOrNull()!=".")
|
||||||
|
srcdirs.add(0, ".")
|
||||||
|
|
||||||
if(watchMode==true) {
|
if(watchMode==true) {
|
||||||
val watchservice = FileSystems.getDefault().newWatchService()
|
val watchservice = FileSystems.getDefault().newWatchService()
|
||||||
val allImportedFiles = mutableSetOf<Path>()
|
val allImportedFiles = mutableSetOf<Path>()
|
||||||
@ -66,7 +74,7 @@ private fun compileMain(args: Array<String>) {
|
|||||||
val results = mutableListOf<CompilationResult>()
|
val results = mutableListOf<CompilationResult>()
|
||||||
for(filepathRaw in moduleFiles) {
|
for(filepathRaw in moduleFiles) {
|
||||||
val filepath = pathFrom(filepathRaw).normalize()
|
val filepath = pathFrom(filepathRaw).normalize()
|
||||||
val compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, outputPath)
|
val compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, srcdirs, outputPath)
|
||||||
results.add(compilationResult)
|
results.add(compilationResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,7 +85,7 @@ private fun compileMain(args: Array<String>) {
|
|||||||
for (importedFile in allImportedFiles) {
|
for (importedFile in allImportedFiles) {
|
||||||
print(" ")
|
print(" ")
|
||||||
println(importedFile)
|
println(importedFile)
|
||||||
val watchDir = importedFile.parent ?: Path.of(".")
|
val watchDir = importedFile.parent ?: Path.of("")
|
||||||
watchDir.register(watchservice, StandardWatchEventKinds.ENTRY_MODIFY)
|
watchDir.register(watchservice, StandardWatchEventKinds.ENTRY_MODIFY)
|
||||||
}
|
}
|
||||||
println("[${LocalDateTime.now().withNano(0)}] Waiting for file changes.")
|
println("[${LocalDateTime.now().withNano(0)}] Waiting for file changes.")
|
||||||
@ -103,22 +111,30 @@ private fun compileMain(args: Array<String>) {
|
|||||||
val filepath = pathFrom(filepathRaw).normalize()
|
val filepath = pathFrom(filepathRaw).normalize()
|
||||||
val compilationResult: CompilationResult
|
val compilationResult: CompilationResult
|
||||||
try {
|
try {
|
||||||
compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, outputPath)
|
compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, srcdirs, outputPath)
|
||||||
if(!compilationResult.success)
|
if(!compilationResult.success)
|
||||||
exitProcess(1)
|
return false
|
||||||
} catch (x: ParsingFailedError) {
|
} catch (x: ParsingFailedError) {
|
||||||
exitProcess(1)
|
return false
|
||||||
} catch (x: AstException) {
|
} catch (x: AstException) {
|
||||||
exitProcess(1)
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (startEmulator==true) {
|
if(startEmulator1==true || startEmulator2==true) {
|
||||||
if (compilationResult.programName.isEmpty())
|
if (compilationResult.programName.isEmpty()) {
|
||||||
println("\nCan't start emulator because no program was assembled.")
|
println("\nCan't start emulator because no program was assembled.")
|
||||||
else {
|
return true
|
||||||
compilationResult.compTarget.machine.launchEmulator(compilationResult.programName)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
@ -8,25 +8,27 @@ import prog8.ast.expressions.*
|
|||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.ast.walk.AstWalker
|
import prog8.ast.walk.AstWalker
|
||||||
import prog8.ast.walk.IAstModification
|
import prog8.ast.walk.IAstModification
|
||||||
|
import prog8.ast.walk.IAstVisitor
|
||||||
|
import prog8.compiler.astprocessing.isInRegularRAMof
|
||||||
import prog8.compiler.target.ICompilationTarget
|
import prog8.compiler.target.ICompilationTarget
|
||||||
|
|
||||||
|
|
||||||
internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: IErrorReporter, private val compTarget: ICompilationTarget) : AstWalker() {
|
internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: IErrorReporter, private val compTarget: ICompilationTarget) : AstWalker() {
|
||||||
|
|
||||||
private val noModifications = emptyList<IAstModification>()
|
|
||||||
|
|
||||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||||
subroutineVariables.add(decl.name to decl)
|
subroutineVariables.add(decl.name to decl)
|
||||||
if (decl.value == null && !decl.autogeneratedDontRemove && decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
|
if (decl.value == null && !decl.autogeneratedDontRemove && decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
|
||||||
// a numeric vardecl without an initial value is initialized with zero,
|
// A numeric vardecl without an initial value is initialized with zero,
|
||||||
// unless there's already an assignment below, that initializes the value
|
// unless there's already an assignment below, that initializes the value.
|
||||||
|
// This allows you to restart the program and have the same starting values of the variables
|
||||||
if(decl.allowInitializeWithZero)
|
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)))
|
if (nextAssign != null && nextAssign.target isSameAs IdentifierReference(listOf(decl.name), Position.DUMMY))
|
||||||
decl.value = null
|
decl.value = null
|
||||||
else
|
else {
|
||||||
decl.value = decl.zeroElementValue()
|
decl.value = decl.zeroElementValue()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return noModifications
|
return noModifications
|
||||||
@ -38,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.
|
// But it can only be done if the target variable IS NOT OCCURRING AS AN OPERAND ITSELF.
|
||||||
if(!assignment.isAugmentable
|
if(!assignment.isAugmentable
|
||||||
&& assignment.target.identifier != null
|
&& assignment.target.identifier != null
|
||||||
&& compTarget.isInRegularRAM(assignment.target, program)) {
|
&& assignment.target.isInRegularRAMof(compTarget.machine)) {
|
||||||
val binExpr = assignment.value as? BinaryExpression
|
val binExpr = assignment.value as? BinaryExpression
|
||||||
if (binExpr != null && binExpr.operator !in comparisonOperators) {
|
if (binExpr != null && binExpr.operator !in comparisonOperators) {
|
||||||
if (binExpr.left !is BinaryExpression) {
|
if (binExpr.left !is BinaryExpression) {
|
||||||
@ -50,14 +52,14 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
|
|||||||
// use the other part of the expression to split.
|
// use the other part of the expression to split.
|
||||||
val assignRight = Assignment(assignment.target, binExpr.right, assignment.position)
|
val assignRight = Assignment(assignment.target, binExpr.right, assignment.position)
|
||||||
return listOf(
|
return listOf(
|
||||||
IAstModification.InsertBefore(assignment, assignRight, assignment.definingScope()),
|
IAstModification.InsertBefore(assignment, assignRight, assignment.definingScope),
|
||||||
IAstModification.ReplaceNode(binExpr.right, binExpr.left, binExpr),
|
IAstModification.ReplaceNode(binExpr.right, binExpr.left, binExpr),
|
||||||
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
|
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val assignLeft = Assignment(assignment.target, binExpr.left, assignment.position)
|
val assignLeft = Assignment(assignment.target, binExpr.left, assignment.position)
|
||||||
return listOf(
|
return listOf(
|
||||||
IAstModification.InsertBefore(assignment, assignLeft, assignment.definingScope()),
|
IAstModification.InsertBefore(assignment, assignLeft, assignment.definingScope),
|
||||||
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
|
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -67,30 +69,37 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val subroutineVariables = mutableListOf<Pair<String, VarDecl>>()
|
private val subroutineVariables = mutableListOf<Pair<String, VarDecl>>()
|
||||||
|
private val addedIfConditionVars = mutableSetOf<Pair<Subroutine, String>>()
|
||||||
|
|
||||||
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||||
subroutineVariables.clear()
|
subroutineVariables.clear()
|
||||||
|
addedIfConditionVars.clear()
|
||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||||
val decls = scope.statements.filterIsInstance<VarDecl>()
|
val decls = scope.statements.filterIsInstance<VarDecl>().filter { it.type == VarDeclType.VAR }
|
||||||
subroutineVariables.addAll(decls.map { it.name to it })
|
subroutineVariables.addAll(decls.map { it.name to it })
|
||||||
|
|
||||||
val sub = scope.definingSubroutine()
|
val sub = scope.definingSubroutine
|
||||||
if (sub != null) {
|
if (sub != null) {
|
||||||
// move vardecls of the scope into the upper scope. Make sure the position remains the same!
|
// move any remaining vardecls of the scope into the upper scope. Make sure the position remains the same!
|
||||||
val numericVarsWithValue = decls.filter { it.value != null && it.datatype in NumericDatatypes }
|
val replacements = mutableListOf<IAstModification>()
|
||||||
val replaceVardecls =numericVarsWithValue.map {
|
val movements = mutableListOf<IAstModification.InsertFirst>()
|
||||||
val initValue = it.value!! // assume here that value has always been set by now
|
|
||||||
it.value = null // make sure no value init assignment for this vardecl will be created later (would be superfluous)
|
for(decl in decls) {
|
||||||
val target = AssignTarget(IdentifierReference(listOf(it.name), it.position), null, null, it.position)
|
if(decl.value!=null && decl.datatype in NumericDatatypes) {
|
||||||
val assign = Assignment(target, initValue, it.position)
|
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
|
||||||
initValue.parent = assign
|
val assign = Assignment(target, decl.value!!, decl.position)
|
||||||
IAstModification.ReplaceNode(it, assign, scope)
|
replacements.add(IAstModification.ReplaceNode(decl, assign, scope))
|
||||||
|
decl.value = null
|
||||||
|
decl.allowInitializeWithZero = false
|
||||||
|
} else {
|
||||||
|
replacements.add(IAstModification.Remove(decl, scope))
|
||||||
|
}
|
||||||
|
movements.add(IAstModification.InsertFirst(decl, sub))
|
||||||
}
|
}
|
||||||
val moveVardeclsUp = decls.map { IAstModification.InsertFirst(it, sub) }
|
return replacements + movements
|
||||||
return replaceVardecls + moveVardeclsUp
|
|
||||||
}
|
}
|
||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
@ -111,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.
|
// and if an assembly block doesn't contain a rts/rti, and some other situations.
|
||||||
val mods = mutableListOf<IAstModification>()
|
val mods = mutableListOf<IAstModification>()
|
||||||
val returnStmt = Return(null, subroutine.position)
|
val returnStmt = Return(null, subroutine.position)
|
||||||
if (subroutine.asmAddress == null
|
if (subroutine.asmAddress == null && !subroutine.inline) {
|
||||||
&& !subroutine.inline
|
if(subroutine.statements.isEmpty() ||
|
||||||
&& subroutine.statements.isNotEmpty()
|
(subroutine.amountOfRtsInAsm() == 0
|
||||||
&& subroutine.amountOfRtsInAsm() == 0
|
|
||||||
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return
|
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return
|
||||||
&& subroutine.statements.last() !is Subroutine) {
|
&& subroutine.statements.last() !is Subroutine)) {
|
||||||
mods += IAstModification.InsertLast(returnStmt, subroutine)
|
mods += IAstModification.InsertLast(returnStmt, subroutine)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// precede a subroutine with a return to avoid falling through into the subroutine from code above it
|
// 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 outerStatements = outerScope.statements
|
||||||
val subroutineStmtIdx = outerStatements.indexOf(subroutine)
|
val subroutineStmtIdx = outerStatements.indexOf(subroutine)
|
||||||
if (subroutineStmtIdx > 0
|
if (subroutineStmtIdx > 0
|
||||||
@ -138,7 +147,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
|
|||||||
// see if we can remove superfluous typecasts (outside of expressions)
|
// see if we can remove superfluous typecasts (outside of expressions)
|
||||||
// such as casting byte<->ubyte, word<->uword
|
// such as casting byte<->ubyte, word<->uword
|
||||||
// Also the special typecast of a reference type (str, array) to an UWORD will be changed into address-of.
|
// Also the special typecast of a reference type (str, array) to an UWORD will be changed into address-of.
|
||||||
val sourceDt = typecast.expression.inferType(program).typeOrElse(DataType.STRUCT)
|
val sourceDt = typecast.expression.inferType(program).getOr(DataType.UNDEFINED)
|
||||||
if (typecast.type in ByteDatatypes && sourceDt in ByteDatatypes
|
if (typecast.type in ByteDatatypes && sourceDt in ByteDatatypes
|
||||||
|| typecast.type in WordDatatypes && sourceDt in WordDatatypes) {
|
|| typecast.type in WordDatatypes && sourceDt in WordDatatypes) {
|
||||||
if(typecast.parent !is Expression) {
|
if(typecast.parent !is Expression) {
|
||||||
@ -154,16 +163,6 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
|
|||||||
// The only place for now where we can do this is for:
|
// The only place for now where we can do this is for:
|
||||||
// asmsub register pair parameter.
|
// asmsub register pair parameter.
|
||||||
|
|
||||||
if(typecast.type in WordDatatypes) {
|
|
||||||
val fcall = typecast.parent as? IFunctionCall
|
|
||||||
if (fcall != null) {
|
|
||||||
val sub = fcall.target.targetStatement(program) as? Subroutine
|
|
||||||
if (sub != null && sub.isAsmSubroutine) {
|
|
||||||
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(sourceDt in PassByReferenceDatatypes) {
|
if(sourceDt in PassByReferenceDatatypes) {
|
||||||
if(typecast.type==DataType.UWORD) {
|
if(typecast.type==DataType.UWORD) {
|
||||||
if(typecast.expression is IdentifierReference) {
|
if(typecast.expression is IdentifierReference) {
|
||||||
@ -194,9 +193,55 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
|
|||||||
val booleanExpr = BinaryExpression(ifStatement.condition, "!=", NumericLiteralValue.optimalInteger(0, ifStatement.condition.position), ifStatement.condition.position)
|
val booleanExpr = BinaryExpression(ifStatement.condition, "!=", NumericLiteralValue.optimalInteger(0, ifStatement.condition.position), ifStatement.condition.position)
|
||||||
return listOf(IAstModification.ReplaceNode(ifStatement.condition, booleanExpr, ifStatement))
|
return listOf(IAstModification.ReplaceNode(ifStatement.condition, booleanExpr, ifStatement))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if((binExpr.operator=="==" || binExpr.operator=="!=") &&
|
||||||
|
(binExpr.left as? NumericLiteralValue)?.number==0 &&
|
||||||
|
(binExpr.right as? NumericLiteralValue)?.number!=0)
|
||||||
|
throw CompilerException("if 0==X should have been swapped to if X==0")
|
||||||
|
|
||||||
|
// split the conditional expression into separate variables if the operand(s) is not simple.
|
||||||
|
// DISABLED FOR NOW AS IT GENEREATES LARGER CODE IN THE SIMPLE CASES LIKE IF X {...} or IF NOT X {...}
|
||||||
|
// val modifications = mutableListOf<IAstModification>()
|
||||||
|
// if(!binExpr.left.isSimple) {
|
||||||
|
// val sub = binExpr.definingSubroutine()!!
|
||||||
|
// val (variable, isNew, assignment) = addIfOperandVar(sub, "left", binExpr.left)
|
||||||
|
// if(isNew)
|
||||||
|
// modifications.add(IAstModification.InsertFirst(variable, sub))
|
||||||
|
// modifications.add(IAstModification.InsertBefore(ifStatement, assignment, parent as INameScope))
|
||||||
|
// modifications.add(IAstModification.ReplaceNode(binExpr.left, IdentifierReference(listOf(variable.name), binExpr.position), binExpr))
|
||||||
|
// addedIfConditionVars.add(Pair(sub, variable.name))
|
||||||
|
// }
|
||||||
|
// if(!binExpr.right.isSimple) {
|
||||||
|
// val sub = binExpr.definingSubroutine()!!
|
||||||
|
// val (variable, isNew, assignment) = addIfOperandVar(sub, "right", binExpr.right)
|
||||||
|
// if(isNew)
|
||||||
|
// modifications.add(IAstModification.InsertFirst(variable, sub))
|
||||||
|
// modifications.add(IAstModification.InsertBefore(ifStatement, assignment, parent as INameScope))
|
||||||
|
// modifications.add(IAstModification.ReplaceNode(binExpr.right, IdentifierReference(listOf(variable.name), binExpr.position), binExpr))
|
||||||
|
// addedIfConditionVars.add(Pair(sub, variable.name))
|
||||||
|
// }
|
||||||
|
// return modifications
|
||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// private fun addIfOperandVar(sub: Subroutine, side: String, operand: Expression): Triple<VarDecl, Boolean, Assignment> {
|
||||||
|
// val dt = operand.inferType(program).typeOrElse(DataType.UNDEFINED)
|
||||||
|
// val varname = "prog8_ifvar_${side}_${dt.name.toLowerCase()}"
|
||||||
|
// val tgt = AssignTarget(IdentifierReference(listOf(varname), operand.position), null, null, operand.position)
|
||||||
|
// val assign = Assignment(tgt, operand, operand.position)
|
||||||
|
// if(Pair(sub, varname) in addedIfConditionVars) {
|
||||||
|
// val vardecl = VarDecl(VarDeclType.VAR, dt, ZeropageWish.DONTCARE, null, varname, null, null, false, true, operand.position)
|
||||||
|
// return Triple(vardecl, false, assign)
|
||||||
|
// }
|
||||||
|
// val existing = sub.statements.firstOrNull { it is VarDecl && it.name == varname} as VarDecl?
|
||||||
|
// return if (existing == null) {
|
||||||
|
// val vardecl = VarDecl(VarDeclType.VAR, dt, ZeropageWish.DONTCARE, null, varname, null, null, false, true, operand.position)
|
||||||
|
// Triple(vardecl, true, assign)
|
||||||
|
// } else {
|
||||||
|
// Triple(existing, false, assign)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
override fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
|
override fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
|
||||||
val binExpr = untilLoop.condition as? BinaryExpression
|
val binExpr = untilLoop.condition as? BinaryExpression
|
||||||
if(binExpr==null || binExpr.operator !in comparisonOperators) {
|
if(binExpr==null || binExpr.operator !in comparisonOperators) {
|
||||||
@ -216,4 +261,95 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I
|
|||||||
}
|
}
|
||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||||
|
if(functionCallStatement.target.nameInSource==listOf("cmp")) {
|
||||||
|
// 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).getOr(DataType.UNDEFINED)
|
||||||
|
val dt2 = arg2.inferType(program).getOr(DataType.UNDEFINED)
|
||||||
|
if(dt1 in ByteDatatypes) {
|
||||||
|
if(dt2 in ByteDatatypes)
|
||||||
|
return noModifications
|
||||||
|
val cast1 = TypecastExpression(arg1, if(dt1==DataType.UBYTE) DataType.UWORD else DataType.WORD, true, functionCallStatement.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(arg1, cast1, functionCallStatement))
|
||||||
|
} else {
|
||||||
|
if(dt2 in WordDatatypes)
|
||||||
|
return noModifications
|
||||||
|
val cast2 = TypecastExpression(arg2, if(dt2==DataType.UBYTE) DataType.UWORD else DataType.WORD, true, functionCallStatement.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(arg2, cast2, functionCallStatement))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||||
|
|
||||||
|
val containingStatement = getContainingStatement(arrayIndexedExpression)
|
||||||
|
if(getComplexArrayIndexedExpressions(containingStatement).size > 1) {
|
||||||
|
errors.err("it's not possible to use more than one complex array indexing expression in a single statement; break it up via a temporary variable for instance", containingStatement.position)
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val index = arrayIndexedExpression.indexer.indexExpr
|
||||||
|
if(index !is NumericLiteralValue && index !is IdentifierReference) {
|
||||||
|
// replace complex indexing expression with a temp variable to hold the computed index first
|
||||||
|
return getAutoIndexerVarFor(arrayIndexedExpression)
|
||||||
|
}
|
||||||
|
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getComplexArrayIndexedExpressions(stmt: Statement): List<ArrayIndexedExpression> {
|
||||||
|
|
||||||
|
class Searcher : IAstVisitor {
|
||||||
|
val complexArrayIndexedExpressions = mutableListOf<ArrayIndexedExpression>()
|
||||||
|
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
|
||||||
|
val ix = arrayIndexedExpression.indexer.indexExpr
|
||||||
|
if(ix !is NumericLiteralValue && ix !is IdentifierReference)
|
||||||
|
complexArrayIndexedExpressions.add(arrayIndexedExpression)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(branchStatement: BranchStatement) {}
|
||||||
|
|
||||||
|
override fun visit(forLoop: ForLoop) {}
|
||||||
|
|
||||||
|
override fun visit(ifStatement: IfStatement) {
|
||||||
|
ifStatement.condition.accept(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(untilLoop: UntilLoop) {
|
||||||
|
untilLoop.condition.accept(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val searcher = Searcher()
|
||||||
|
stmt.accept(searcher)
|
||||||
|
return searcher.complexArrayIndexedExpressions
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getContainingStatement(expression: Expression): Statement {
|
||||||
|
var node: Node = expression
|
||||||
|
while(node !is Statement)
|
||||||
|
node = node.parent
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAutoIndexerVarFor(expr: ArrayIndexedExpression): MutableList<IAstModification> {
|
||||||
|
val modifications = mutableListOf<IAstModification>()
|
||||||
|
val statement = expr.containingStatement
|
||||||
|
val dt = expr.indexer.indexExpr.inferType(program)
|
||||||
|
val register = if(dt istype DataType.UBYTE || dt istype DataType.BYTE ) "r9L" else "r9"
|
||||||
|
// replace the indexer with just the variable (simply use a cx16 virtual register r9, that we HOPE is not used for other things in the expression...)
|
||||||
|
// assign the indexing expression to the helper variable, but only if that hasn't been done already
|
||||||
|
val target = AssignTarget(IdentifierReference(listOf("cx16", register), expr.indexer.position), null, null, expr.indexer.position)
|
||||||
|
val assign = Assignment(target, expr.indexer.indexExpr, expr.indexer.position)
|
||||||
|
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
|
package prog8.compiler
|
||||||
|
|
||||||
|
import com.github.michaelbull.result.*
|
||||||
import prog8.ast.AstToSourceCode
|
import prog8.ast.AstToSourceCode
|
||||||
import prog8.ast.IBuiltinFunctions
|
import prog8.ast.IBuiltinFunctions
|
||||||
import prog8.ast.IMemSizer
|
import prog8.ast.IMemSizer
|
||||||
@ -16,13 +17,14 @@ import prog8.compiler.target.Cx16Target
|
|||||||
import prog8.compiler.target.ICompilationTarget
|
import prog8.compiler.target.ICompilationTarget
|
||||||
import prog8.compiler.target.asmGeneratorFor
|
import prog8.compiler.target.asmGeneratorFor
|
||||||
import prog8.optimizer.*
|
import prog8.optimizer.*
|
||||||
import prog8.parser.ModuleImporter
|
import prog8.parser.ParseError
|
||||||
import prog8.parser.ParsingFailedError
|
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.File
|
||||||
import java.io.InputStream
|
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import kotlin.system.exitProcess
|
import kotlin.io.path.Path
|
||||||
|
import kotlin.io.path.nameWithoutExtension
|
||||||
import kotlin.system.measureTimeMillis
|
import kotlin.system.measureTimeMillis
|
||||||
|
|
||||||
|
|
||||||
@ -70,6 +72,7 @@ fun compileProgram(filepath: Path,
|
|||||||
writeAssembly: Boolean,
|
writeAssembly: Boolean,
|
||||||
slowCodegenWarnings: Boolean,
|
slowCodegenWarnings: Boolean,
|
||||||
compilationTarget: String,
|
compilationTarget: String,
|
||||||
|
sourceDirs: List<String>,
|
||||||
outputDir: Path): CompilationResult {
|
outputDir: Path): CompilationResult {
|
||||||
var programName = ""
|
var programName = ""
|
||||||
lateinit var programAst: Program
|
lateinit var programAst: Program
|
||||||
@ -80,38 +83,52 @@ fun compileProgram(filepath: Path,
|
|||||||
when(compilationTarget) {
|
when(compilationTarget) {
|
||||||
C64Target.name -> C64Target
|
C64Target.name -> C64Target
|
||||||
Cx16Target.name -> Cx16Target
|
Cx16Target.name -> Cx16Target
|
||||||
else -> {
|
else -> throw IllegalArgumentException("invalid compilation target")
|
||||||
System.err.println("invalid compilation target")
|
|
||||||
exitProcess(1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val totalTime = measureTimeMillis {
|
val totalTime = measureTimeMillis {
|
||||||
// import main module and everything it needs
|
// import main module and everything it needs
|
||||||
val (ast, compilationOptions, imported) = parseImports(filepath, errors, compTarget)
|
val (ast, compilationOptions, imported) = parseImports(filepath, errors, compTarget, sourceDirs)
|
||||||
compilationOptions.slowCodegenWarnings = slowCodegenWarnings
|
compilationOptions.slowCodegenWarnings = slowCodegenWarnings
|
||||||
compilationOptions.optimize = optimize
|
compilationOptions.optimize = optimize
|
||||||
programAst = ast
|
programAst = ast
|
||||||
importedFiles = imported
|
importedFiles = imported
|
||||||
processAst(programAst, errors, compilationOptions)
|
processAst(programAst, errors, compilationOptions)
|
||||||
if (compilationOptions.optimize)
|
if (compilationOptions.optimize)
|
||||||
optimizeAst(programAst, errors, BuiltinFunctionsFacade(BuiltinFunctions), compTarget)
|
optimizeAst(
|
||||||
|
programAst,
|
||||||
|
errors,
|
||||||
|
BuiltinFunctionsFacade(BuiltinFunctions),
|
||||||
|
compTarget,
|
||||||
|
compilationOptions
|
||||||
|
)
|
||||||
postprocessAst(programAst, errors, compilationOptions)
|
postprocessAst(programAst, errors, compilationOptions)
|
||||||
|
|
||||||
// printAst(programAst)
|
// printAst(programAst)
|
||||||
|
|
||||||
if(writeAssembly)
|
if (writeAssembly) {
|
||||||
programName = writeAssembly(programAst, errors, outputDir, compilationOptions)
|
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.out.flush()
|
||||||
System.err.flush()
|
System.err.flush()
|
||||||
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
|
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
|
||||||
return CompilationResult(true, programAst, programName, compTarget, importedFiles)
|
return CompilationResult(true, programAst, programName, compTarget, importedFiles)
|
||||||
|
} catch (px: ParseError) {
|
||||||
} catch (px: ParsingFailedError) {
|
|
||||||
System.err.print("\u001b[91m") // bright red
|
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
|
System.err.print("\u001b[0m") // reset
|
||||||
} catch (ax: AstException) {
|
} catch (ax: AstException) {
|
||||||
System.err.print("\u001b[91m") // bright red
|
System.err.print("\u001b[91m") // bright red
|
||||||
@ -131,7 +148,7 @@ fun compileProgram(filepath: Path,
|
|||||||
throw x
|
throw x
|
||||||
}
|
}
|
||||||
|
|
||||||
val failedProgram = Program("failed", mutableListOf(), BuiltinFunctionsFacade(BuiltinFunctions), compTarget)
|
val failedProgram = Program("failed", BuiltinFunctionsFacade(BuiltinFunctions), compTarget)
|
||||||
return CompilationResult(false, failedProgram, programName, compTarget, emptyList())
|
return CompilationResult(false, failedProgram, programName, compTarget, emptyList())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,7 +174,7 @@ private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuilt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if(func.known_returntype==null)
|
else if(func.known_returntype==null)
|
||||||
throw IllegalArgumentException("builtin function $name can't be used here because it doesn't return a value")
|
return null // builtin function $name can't be used here because it doesn't return a value
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@ -165,87 +182,107 @@ private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuilt
|
|||||||
builtinFunctionReturnType(name, args, program)
|
builtinFunctionReturnType(name, args, program)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseImports(filepath: Path, errors: IErrorReporter, compTarget: ICompilationTarget): Triple<Program, CompilationOptions, List<Path>> {
|
fun parseImports(filepath: Path,
|
||||||
val compilationTargetName = compTarget.name
|
errors: IErrorReporter,
|
||||||
println("Compiler target: $compilationTargetName. Parsing...")
|
compTarget: ICompilationTarget,
|
||||||
val importer = ModuleImporter()
|
sourceDirs: List<String>): Triple<Program, CompilationOptions, List<Path>> {
|
||||||
|
println("Compiler target: ${compTarget.name}. Parsing...")
|
||||||
val bf = BuiltinFunctionsFacade(BuiltinFunctions)
|
val bf = BuiltinFunctionsFacade(BuiltinFunctions)
|
||||||
val programAst = Program(moduleName(filepath.fileName), mutableListOf(), bf, compTarget)
|
val programAst = Program(filepath.nameWithoutExtension, bf, compTarget)
|
||||||
bf.program = programAst
|
bf.program = programAst
|
||||||
importer.importModule(programAst, filepath, compTarget, compilationTargetName)
|
|
||||||
|
val importer = ModuleImporter(programAst, compTarget.name, errors, sourceDirs)
|
||||||
|
val importedModuleResult = importer.importModule(filepath)
|
||||||
|
importedModuleResult.onFailure { throw it }
|
||||||
errors.report()
|
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)
|
val compilerOptions = determineCompilationOptions(programAst, compTarget)
|
||||||
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
|
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
|
||||||
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
|
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
|
||||||
|
|
||||||
// depending on the machine and compiler options we may have to include some libraries
|
// depending on the machine and compiler options we may have to include some libraries
|
||||||
for(lib in compTarget.machine.importLibs(compilerOptions, compilationTargetName))
|
for(lib in compTarget.machine.importLibs(compilerOptions, compTarget.name))
|
||||||
importer.importLibraryModule(programAst, lib, compTarget, compilationTargetName)
|
importer.importLibraryModule(lib)
|
||||||
|
|
||||||
// always import prog8_lib and math
|
// always import prog8_lib and math
|
||||||
importer.importLibraryModule(programAst, "math", compTarget, compilationTargetName)
|
importer.importLibraryModule("math")
|
||||||
importer.importLibraryModule(programAst, "prog8_lib", compTarget, compilationTargetName)
|
importer.importLibraryModule("prog8_lib")
|
||||||
errors.report()
|
errors.report()
|
||||||
return Triple(programAst, compilerOptions, importedFiles)
|
return Triple(programAst, compilerOptions, importedFiles)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget): CompilationOptions {
|
fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget): CompilationOptions {
|
||||||
val mainModule = program.mainModule
|
val toplevelModule = program.toplevelModule
|
||||||
val outputType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%output" }
|
val outputDirective = (toplevelModule.statements.singleOrNull { it is Directive && it.directive == "%output" } as? Directive)
|
||||||
as? Directive)?.args?.single()?.name?.toUpperCase()
|
val launcherDirective = (toplevelModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" } as? Directive)
|
||||||
val launcherType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" }
|
val outputTypeStr = outputDirective?.args?.single()?.name?.uppercase()
|
||||||
as? Directive)?.args?.single()?.name?.toUpperCase()
|
val launcherTypeStr = launcherDirective?.args?.single()?.name?.uppercase()
|
||||||
val zpoption: String? = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%zeropage" }
|
val zpoption: String? = (toplevelModule.statements.singleOrNull { it is Directive && it.directive == "%zeropage" }
|
||||||
as? Directive)?.args?.single()?.name?.toUpperCase()
|
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()
|
val allOptions = program.modules.flatMap { it.statements }.filter { it is Directive && it.directive == "%option" }
|
||||||
|
.flatMap { (it as Directive).args }.toSet()
|
||||||
val floatsEnabled = allOptions.any { it.name == "enable_floats" }
|
val floatsEnabled = allOptions.any { it.name == "enable_floats" }
|
||||||
val noSysInit = allOptions.any { it.name == "no_sysinit" }
|
val noSysInit = allOptions.any { it.name == "no_sysinit" }
|
||||||
var zpType: ZeropageType =
|
var zpType: ZeropageType =
|
||||||
if (zpoption == null)
|
if (zpoption == null)
|
||||||
if(floatsEnabled) ZeropageType.FLOATSAFE else ZeropageType.KERNALSAFE
|
if (floatsEnabled) ZeropageType.FLOATSAFE else ZeropageType.KERNALSAFE
|
||||||
else
|
else
|
||||||
try {
|
try {
|
||||||
ZeropageType.valueOf(zpoption)
|
ZeropageType.valueOf(zpoption)
|
||||||
} catch (x: IllegalArgumentException) {
|
} catch (x: IllegalArgumentException) {
|
||||||
ZeropageType.KERNALSAFE
|
ZeropageType.KERNALSAFE
|
||||||
// error will be printed by the astchecker
|
// error will be printed by the astchecker
|
||||||
}
|
}
|
||||||
|
|
||||||
if (zpType==ZeropageType.FLOATSAFE && compTarget.name == Cx16Target.name) {
|
if (zpType == ZeropageType.FLOATSAFE && compTarget.name == Cx16Target.name) {
|
||||||
System.err.println("Warning: Cx16 target must use zp option basicsafe instead of floatsafe")
|
System.err.println("Warning: zp option floatsafe changed to basicsafe for cx16 target")
|
||||||
zpType = ZeropageType.BASICSAFE
|
zpType = ZeropageType.BASICSAFE
|
||||||
}
|
}
|
||||||
|
|
||||||
val zpReserved = mainModule.statements
|
val zpReserved = toplevelModule.statements
|
||||||
.asSequence()
|
.asSequence()
|
||||||
.filter { it is Directive && it.directive == "%zpreserved" }
|
.filter { it is Directive && it.directive == "%zpreserved" }
|
||||||
.map { (it as Directive).args }
|
.map { (it as Directive).args }
|
||||||
.map { it[0].int!!..it[1].int!! }
|
.map { it[0].int!!..it[1].int!! }
|
||||||
.toList()
|
.toList()
|
||||||
|
|
||||||
if(outputType!=null && !OutputType.values().any {it.name==outputType}) {
|
val outputType = if (outputTypeStr == null) OutputType.PRG else {
|
||||||
System.err.println("invalid output type $outputType")
|
try {
|
||||||
exitProcess(1)
|
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}) {
|
val launcherType = if (launcherTypeStr == null) LauncherType.BASIC else {
|
||||||
System.err.println("invalid launcher type $launcherType")
|
try {
|
||||||
exitProcess(1)
|
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(
|
return CompilationOptions(
|
||||||
if (outputType == null) OutputType.PRG else OutputType.valueOf(outputType),
|
outputType,
|
||||||
if (launcherType == null) LauncherType.BASIC else LauncherType.valueOf(launcherType),
|
launcherType,
|
||||||
zpType, zpReserved, floatsEnabled, noSysInit,
|
zpType, zpReserved, floatsEnabled, noSysInit,
|
||||||
compTarget
|
compTarget
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
private fun processAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
||||||
// perform initial syntax checks and processings
|
// perform initial syntax checks and processings
|
||||||
println("Processing for target ${compilerOptions.compTarget.name}...")
|
println("Processing for target ${compilerOptions.compTarget.name}...")
|
||||||
programAst.checkIdentifiers(errors, compilerOptions.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()
|
errors.report()
|
||||||
programAst.constantFold(errors, compilerOptions.compTarget)
|
programAst.constantFold(errors, compilerOptions.compTarget)
|
||||||
errors.report()
|
errors.report()
|
||||||
@ -253,51 +290,59 @@ private fun processAst(programAst: Program, errors: IErrorReporter, compilerOpti
|
|||||||
errors.report()
|
errors.report()
|
||||||
programAst.addTypecasts(errors)
|
programAst.addTypecasts(errors)
|
||||||
errors.report()
|
errors.report()
|
||||||
programAst.variousCleanups()
|
programAst.variousCleanups(programAst, errors)
|
||||||
|
errors.report()
|
||||||
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget)
|
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget)
|
||||||
errors.report()
|
errors.report()
|
||||||
programAst.checkIdentifiers(errors, compilerOptions.compTarget)
|
programAst.checkIdentifiers(errors, compilerOptions)
|
||||||
errors.report()
|
errors.report()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun optimizeAst(programAst: Program, errors: IErrorReporter, functions: IBuiltinFunctions, compTarget: ICompilationTarget) {
|
private fun optimizeAst(programAst: Program, errors: IErrorReporter, functions: IBuiltinFunctions, compTarget: ICompilationTarget, options: CompilationOptions) {
|
||||||
// optimize the parse tree
|
// optimize the parse tree
|
||||||
println("Optimizing...")
|
println("Optimizing...")
|
||||||
|
|
||||||
|
val remover = UnusedCodeRemover(programAst, errors, compTarget)
|
||||||
|
remover.visit(programAst)
|
||||||
|
remover.applyModifications()
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
// keep optimizing expressions and statements until no more steps remain
|
// keep optimizing expressions and statements until no more steps remain
|
||||||
val optsDone1 = programAst.simplifyExpressions()
|
val optsDone1 = programAst.simplifyExpressions()
|
||||||
val optsDone2 = programAst.splitBinaryExpressions(compTarget)
|
val optsDone2 = programAst.splitBinaryExpressions(compTarget)
|
||||||
val optsDone3 = programAst.optimizeStatements(errors, functions, compTarget, ::loadAsmIncludeFile)
|
val optsDone3 = programAst.optimizeStatements(errors, functions, compTarget)
|
||||||
programAst.constantFold(errors, compTarget) // because simplified statements and expressions can result in more constants that can be folded away
|
programAst.constantFold(errors, compTarget) // because simplified statements and expressions can result in more constants that can be folded away
|
||||||
errors.report()
|
errors.report()
|
||||||
if (optsDone1 + optsDone2 + optsDone3 == 0)
|
if (optsDone1 + optsDone2 + optsDone3 == 0)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
val remover = UnusedCodeRemover(programAst, errors, compTarget, ::loadAsmIncludeFile)
|
|
||||||
remover.visit(programAst)
|
|
||||||
remover.applyModifications()
|
|
||||||
errors.report()
|
errors.report()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun postprocessAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
private fun postprocessAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
||||||
programAst.addTypecasts(errors)
|
programAst.addTypecasts(errors)
|
||||||
errors.report()
|
errors.report()
|
||||||
programAst.variousCleanups()
|
programAst.variousCleanups(programAst, errors)
|
||||||
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget) // check if final tree is still valid
|
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget) // check if final tree is still valid
|
||||||
errors.report()
|
errors.report()
|
||||||
val callGraph = CallGraph(programAst, ::loadAsmIncludeFile)
|
val callGraph = CallGraph(programAst)
|
||||||
callGraph.checkRecursiveCalls(errors)
|
callGraph.checkRecursiveCalls(errors)
|
||||||
errors.report()
|
errors.report()
|
||||||
programAst.verifyFunctionArgTypes()
|
programAst.verifyFunctionArgTypes()
|
||||||
programAst.moveMainAndStartToFirst()
|
programAst.moveMainAndStartToFirst()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class WriteAssemblyResult {
|
||||||
|
class Ok(val filename: String): WriteAssemblyResult()
|
||||||
|
class Fail(val error: String): WriteAssemblyResult()
|
||||||
|
}
|
||||||
|
|
||||||
private fun writeAssembly(programAst: Program,
|
private fun writeAssembly(programAst: Program,
|
||||||
errors: IErrorReporter,
|
errors: IErrorReporter,
|
||||||
outputDir: Path,
|
outputDir: Path,
|
||||||
compilerOptions: CompilationOptions): String {
|
compilerOptions: CompilationOptions): WriteAssemblyResult {
|
||||||
// asm generation directly from the Ast,
|
// asm generation directly from the Ast
|
||||||
programAst.processAstBeforeAsmGeneration(errors, compilerOptions.compTarget)
|
programAst.processAstBeforeAsmGeneration(errors, compilerOptions.compTarget)
|
||||||
errors.report()
|
errors.report()
|
||||||
|
|
||||||
@ -310,9 +355,19 @@ private fun writeAssembly(programAst: Program,
|
|||||||
compilerOptions.compTarget.machine.zeropage,
|
compilerOptions.compTarget.machine.zeropage,
|
||||||
compilerOptions,
|
compilerOptions,
|
||||||
outputDir).compileToAssembly()
|
outputDir).compileToAssembly()
|
||||||
assembly.assemble(compilerOptions)
|
|
||||||
errors.report()
|
return if(assembly.valid && errors.noErrors()) {
|
||||||
return assembly.name
|
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) {
|
fun printAst(programAst: Program) {
|
||||||
@ -322,21 +377,19 @@ fun printAst(programAst: Program) {
|
|||||||
println()
|
println()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadAsmIncludeFile(filename: String, source: Path): String {
|
internal fun loadAsmIncludeFile(filename: String, source: SourceCode): Result<String, NoSuchFileException> {
|
||||||
return if (filename.startsWith("library:")) {
|
return if (filename.startsWith(libraryFilePrefix)) {
|
||||||
val resource = tryGetEmbeddedResource(filename.substring(8))
|
return runCatching {
|
||||||
?: throw IllegalArgumentException("library file '$filename' not found")
|
val stream = object {}.javaClass.getResourceAsStream("/prog8lib/${filename.substring(libraryFilePrefix.length)}") // TODO handle via SourceCode
|
||||||
resource.bufferedReader().use { it.readText() }
|
stream!!.bufferedReader().use { r -> r.readText() }
|
||||||
|
}.mapError { NoSuchFileException(File(filename)) }
|
||||||
} else {
|
} else {
|
||||||
// first try in the isSameAs folder as where the containing file was imported from
|
// 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)
|
if (sib.toFile().isFile)
|
||||||
sib.toFile().readText()
|
Ok(sib.toFile().readText())
|
||||||
else
|
else
|
||||||
File(filename).readText()
|
Ok(File(filename).readText())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun tryGetEmbeddedResource(name: String): InputStream? {
|
|
||||||
return object{}.javaClass.getResourceAsStream("/prog8lib/$name")
|
|
||||||
}
|
|
||||||
|
@ -7,7 +7,7 @@ import prog8.parser.ParsingFailedError
|
|||||||
interface IErrorReporter {
|
interface IErrorReporter {
|
||||||
fun err(msg: String, position: Position)
|
fun err(msg: String, position: Position)
|
||||||
fun warn(msg: String, position: Position)
|
fun warn(msg: String, position: Position)
|
||||||
fun isEmpty(): Boolean
|
fun noErrors(): Boolean
|
||||||
fun report()
|
fun report()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,5 +53,5 @@ internal class ErrorReporter: IErrorReporter {
|
|||||||
throw ParsingFailedError("There are $numErrors errors and $numWarnings warnings.")
|
throw ParsingFailedError("There are $numErrors errors and $numWarnings warnings.")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isEmpty() = messages.isEmpty()
|
override fun noErrors() = messages.none { it.severity==MessageSeverity.ERROR }
|
||||||
}
|
}
|
||||||
|
6
compiler/src/prog8/compiler/IStringEncoding.kt
Normal file
6
compiler/src/prog8/compiler/IStringEncoding.kt
Normal file
@ -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
|
||||||
|
}
|
159
compiler/src/prog8/compiler/ModuleImporter.kt
Normal file
159
compiler/src/prog8/compiler/ModuleImporter.kt
Normal file
@ -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
|
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 {
|
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"}
|
assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"scopedname can't be allocated twice"}
|
||||||
|
@ -9,12 +9,14 @@ import prog8.ast.statements.*
|
|||||||
import prog8.ast.walk.IAstVisitor
|
import prog8.ast.walk.IAstVisitor
|
||||||
import prog8.compiler.CompilationOptions
|
import prog8.compiler.CompilationOptions
|
||||||
import prog8.compiler.IErrorReporter
|
import prog8.compiler.IErrorReporter
|
||||||
|
import prog8.compiler.ZeropageType
|
||||||
import prog8.compiler.functions.BuiltinFunctions
|
import prog8.compiler.functions.BuiltinFunctions
|
||||||
import prog8.compiler.functions.builtinFunctionReturnType
|
import prog8.compiler.functions.builtinFunctionReturnType
|
||||||
import prog8.compiler.target.C64Target
|
|
||||||
import prog8.compiler.target.Cx16Target
|
|
||||||
import prog8.compiler.target.ICompilationTarget
|
import prog8.compiler.target.ICompilationTarget
|
||||||
|
import java.io.CharConversionException
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.io.path.*
|
||||||
|
|
||||||
internal class AstChecker(private val program: Program,
|
internal class AstChecker(private val program: Program,
|
||||||
private val compilerOptions: CompilationOptions,
|
private val compilerOptions: CompilationOptions,
|
||||||
@ -41,6 +43,11 @@ internal class AstChecker(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(compilerOptions.floats) {
|
||||||
|
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)
|
super.visit(program)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,7 +63,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(returnStmt: Return) {
|
override fun visit(returnStmt: Return) {
|
||||||
val expectedReturnValues = returnStmt.definingSubroutine()?.returntypes ?: emptyList()
|
val expectedReturnValues = returnStmt.definingSubroutine?.returntypes ?: emptyList()
|
||||||
if(expectedReturnValues.size>1) {
|
if(expectedReturnValues.size>1) {
|
||||||
throw FatalAstException("cannot use a return with one value in a subroutine that has multiple return values: $returnStmt")
|
throw FatalAstException("cannot use a return with one value in a subroutine that has multiple return values: $returnStmt")
|
||||||
}
|
}
|
||||||
@ -70,9 +77,9 @@ internal class AstChecker(private val program: Program,
|
|||||||
if(expectedReturnValues.size==1 && returnStmt.value!=null) {
|
if(expectedReturnValues.size==1 && returnStmt.value!=null) {
|
||||||
val valueDt = returnStmt.value!!.inferType(program)
|
val valueDt = returnStmt.value!!.inferType(program)
|
||||||
if(!valueDt.isKnown) {
|
if(!valueDt.isKnown) {
|
||||||
errors.err("return value type mismatch", returnStmt.value!!.position)
|
errors.err("return value type mismatch or unknown symbol", returnStmt.value!!.position)
|
||||||
} else {
|
} else {
|
||||||
if (expectedReturnValues[0] != valueDt.typeOrElse(DataType.STRUCT))
|
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)
|
errors.err("type $valueDt of return value doesn't match subroutine's return type ${expectedReturnValues[0]}", returnStmt.value!!.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -80,13 +87,25 @@ internal class AstChecker(private val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(ifStatement: IfStatement) {
|
override fun visit(ifStatement: IfStatement) {
|
||||||
if(ifStatement.condition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
|
if(!ifStatement.condition.inferType(program).isInteger)
|
||||||
errors.err("condition value should be an integer type", ifStatement.condition.position)
|
errors.err("condition value should be an integer type", ifStatement.condition.position)
|
||||||
super.visit(ifStatement)
|
super.visit(ifStatement)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(forLoop: ForLoop) {
|
override fun visit(forLoop: ForLoop) {
|
||||||
val iterableDt = forLoop.iterable.inferType(program).typeOrElse(DataType.BYTE)
|
|
||||||
|
fun checkUnsignedLoopDownto0(range: RangeExpr?) {
|
||||||
|
if(range==null)
|
||||||
|
return
|
||||||
|
val step = range.step.constValue(program)?.number?.toDouble() ?: 1.0
|
||||||
|
if(step < -1.0) {
|
||||||
|
val limit = range.to.constValue(program)?.number?.toDouble()
|
||||||
|
if(limit==0.0 && range.from.constValue(program)==null)
|
||||||
|
errors.err("for unsigned loop variable it's not possible to count down with step != -1 from a non-const value to exactly zero due to value wrapping", forLoop.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val iterableDt = forLoop.iterable.inferType(program).getOr(DataType.BYTE)
|
||||||
if(iterableDt !in IterableDatatypes && forLoop.iterable !is RangeExpr) {
|
if(iterableDt !in IterableDatatypes && forLoop.iterable !is RangeExpr) {
|
||||||
errors.err("can only loop over an iterable type", forLoop.position)
|
errors.err("can only loop over an iterable type", forLoop.position)
|
||||||
} else {
|
} else {
|
||||||
@ -98,11 +117,15 @@ internal class AstChecker(private val program: Program,
|
|||||||
DataType.UBYTE -> {
|
DataType.UBYTE -> {
|
||||||
if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.ARRAY_UB && iterableDt != DataType.STR)
|
if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.ARRAY_UB && iterableDt != DataType.STR)
|
||||||
errors.err("ubyte loop variable can only loop over unsigned bytes or strings", forLoop.position)
|
errors.err("ubyte loop variable can only loop over unsigned bytes or strings", forLoop.position)
|
||||||
|
|
||||||
|
checkUnsignedLoopDownto0(forLoop.iterable as? RangeExpr)
|
||||||
}
|
}
|
||||||
DataType.UWORD -> {
|
DataType.UWORD -> {
|
||||||
if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.UWORD && iterableDt != DataType.STR &&
|
if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.UWORD && iterableDt != DataType.STR &&
|
||||||
iterableDt != DataType.ARRAY_UB && iterableDt!= DataType.ARRAY_UW)
|
iterableDt != DataType.ARRAY_UB && iterableDt!= DataType.ARRAY_UW)
|
||||||
errors.err("uword loop variable can only loop over unsigned bytes, words or strings", forLoop.position)
|
errors.err("uword loop variable can only loop over unsigned bytes, words or strings", forLoop.position)
|
||||||
|
|
||||||
|
checkUnsignedLoopDownto0(forLoop.iterable as? RangeExpr)
|
||||||
}
|
}
|
||||||
DataType.BYTE -> {
|
DataType.BYTE -> {
|
||||||
if(iterableDt!= DataType.BYTE && iterableDt!= DataType.ARRAY_B)
|
if(iterableDt!= DataType.BYTE && iterableDt!= DataType.ARRAY_B)
|
||||||
@ -114,11 +137,14 @@ internal class AstChecker(private val program: Program,
|
|||||||
errors.err("word loop variable can only loop over bytes or words", forLoop.position)
|
errors.err("word loop variable can only loop over bytes or words", forLoop.position)
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> {
|
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)
|
errors.err("for loop only supports integers", forLoop.position)
|
||||||
}
|
}
|
||||||
else -> errors.err("loop variable must be numeric type", forLoop.position)
|
else -> errors.err("loop variable must be numeric type", forLoop.position)
|
||||||
}
|
}
|
||||||
if(errors.isEmpty()) {
|
if(errors.noErrors()) {
|
||||||
// check loop range values
|
// check loop range values
|
||||||
val range = forLoop.iterable as? RangeExpr
|
val range = forLoop.iterable as? RangeExpr
|
||||||
if(range!=null) {
|
if(range!=null) {
|
||||||
@ -126,11 +152,11 @@ internal class AstChecker(private val program: Program,
|
|||||||
val to = range.to as? NumericLiteralValue
|
val to = range.to as? NumericLiteralValue
|
||||||
if(from != null)
|
if(from != null)
|
||||||
checkValueTypeAndRange(loopvar.datatype, from)
|
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)
|
errors.err("range start value is incompatible with loop variable type", range.position)
|
||||||
if(to != null)
|
if(to != null)
|
||||||
checkValueTypeAndRange(loopvar.datatype, to)
|
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)
|
errors.err("range end value is incompatible with loop variable type", range.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -140,6 +166,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
super.visit(forLoop)
|
super.visit(forLoop)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun visit(jump: Jump) {
|
override fun visit(jump: Jump) {
|
||||||
val ident = jump.identifier
|
val ident = jump.identifier
|
||||||
if(ident!=null) {
|
if(ident!=null) {
|
||||||
@ -190,6 +217,28 @@ internal class AstChecker(private val program: Program,
|
|||||||
super.visit(label)
|
super.visit(label)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun hasReturnOrJump(scope: INameScope): Boolean {
|
||||||
|
class Searcher: IAstVisitor
|
||||||
|
{
|
||||||
|
var count=0
|
||||||
|
|
||||||
|
override fun visit(returnStmt: Return) {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
override fun visit(jump: Jump) {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val s=Searcher()
|
||||||
|
for(stmt in scope.statements) {
|
||||||
|
stmt.accept(s)
|
||||||
|
if(s.count>0)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return s.count > 0
|
||||||
|
}
|
||||||
|
|
||||||
override fun visit(subroutine: Subroutine) {
|
override fun visit(subroutine: Subroutine) {
|
||||||
fun err(msg: String) = errors.err(msg, subroutine.position)
|
fun err(msg: String) = errors.err(msg, subroutine.position)
|
||||||
|
|
||||||
@ -203,13 +252,6 @@ internal class AstChecker(private val program: Program,
|
|||||||
if(uniqueNames.size!=subroutine.parameters.size)
|
if(uniqueNames.size!=subroutine.parameters.size)
|
||||||
err("parameter names must be unique")
|
err("parameter names must be unique")
|
||||||
|
|
||||||
if(subroutine.inline) {
|
|
||||||
if (subroutine.containsDefinedVariables())
|
|
||||||
err("can't inline a subroutine that defines variables")
|
|
||||||
if (!subroutine.isAsmSubroutine && subroutine.parameters.isNotEmpty())
|
|
||||||
err("can't inline a non-asm subroutine that has parameters")
|
|
||||||
}
|
|
||||||
|
|
||||||
super.visit(subroutine)
|
super.visit(subroutine)
|
||||||
|
|
||||||
// user-defined subroutines can only have zero or one return type
|
// user-defined subroutines can only have zero or one return type
|
||||||
@ -219,7 +261,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
|
|
||||||
// subroutine must contain at least one 'return' or 'goto'
|
// subroutine must contain at least one 'return' or 'goto'
|
||||||
// (or if it has an asm block, that must contain a 'rts' or 'jmp' or 'bra')
|
// (or if it has an asm block, that must contain a 'rts' or 'jmp' or 'bra')
|
||||||
if(subroutine.statements.count { it is Return || it is Jump } == 0) {
|
if(!hasReturnOrJump(subroutine)) {
|
||||||
if (subroutine.amountOfRtsInAsm() == 0) {
|
if (subroutine.amountOfRtsInAsm() == 0) {
|
||||||
if (subroutine.returntypes.isNotEmpty()) {
|
if (subroutine.returntypes.isNotEmpty()) {
|
||||||
// for asm subroutines with an address, no statement check is possible.
|
// for asm subroutines with an address, no statement check is possible.
|
||||||
@ -229,10 +271,11 @@ internal class AstChecker(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// scope check
|
if(subroutine.inline && !subroutine.isAsmSubroutine)
|
||||||
if(subroutine.parent !is Block && subroutine.parent !is Subroutine) {
|
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")
|
err("subroutines can only be defined in the scope of a block or within another subroutine")
|
||||||
}
|
|
||||||
|
|
||||||
if(subroutine.isAsmSubroutine) {
|
if(subroutine.isAsmSubroutine) {
|
||||||
if(subroutine.asmParameterRegisters.size != subroutine.parameters.size)
|
if(subroutine.asmParameterRegisters.size != subroutine.parameters.size)
|
||||||
@ -240,11 +283,11 @@ internal class AstChecker(private val program: Program,
|
|||||||
if(subroutine.asmReturnvaluesRegisters.size != subroutine.returntypes.size)
|
if(subroutine.asmReturnvaluesRegisters.size != subroutine.returntypes.size)
|
||||||
err("number of return registers is not the isSameAs as number of return values")
|
err("number of return registers is not the isSameAs as number of return values")
|
||||||
for(param in subroutine.parameters.zip(subroutine.asmParameterRegisters)) {
|
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)
|
if (param.first.type != DataType.UBYTE && param.first.type != DataType.BYTE)
|
||||||
err("parameter '${param.first.name}' should be (u)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
|
if (param.first.type != DataType.UWORD && param.first.type != DataType.WORD
|
||||||
&& param.first.type != DataType.STR && param.first.type !in ArrayDatatypes && param.first.type != DataType.FLOAT)
|
&& param.first.type != DataType.STR && param.first.type !in ArrayDatatypes && param.first.type != DataType.FLOAT)
|
||||||
err("parameter '${param.first.name}' should be (u)word/address")
|
err("parameter '${param.first.name}' should be (u)word/address")
|
||||||
@ -255,11 +298,11 @@ internal class AstChecker(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
subroutine.returntypes.zip(subroutine.asmReturnvaluesRegisters).forEachIndexed { index, pair ->
|
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)
|
if (pair.first != DataType.UBYTE && pair.first != DataType.BYTE)
|
||||||
err("return value #${index + 1} should be (u)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
|
if (pair.first != DataType.UWORD && pair.first != DataType.WORD
|
||||||
&& pair.first != DataType.STR && pair.first !in ArrayDatatypes && pair.first != DataType.FLOAT)
|
&& pair.first != DataType.STR && pair.first !in ArrayDatatypes && pair.first != DataType.FLOAT)
|
||||||
err("return value #${index + 1} should be (u)word/address")
|
err("return value #${index + 1} should be (u)word/address")
|
||||||
@ -348,13 +391,13 @@ internal class AstChecker(private val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(untilLoop: UntilLoop) {
|
override fun visit(untilLoop: UntilLoop) {
|
||||||
if(untilLoop.condition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
|
if(!untilLoop.condition.inferType(program).isInteger)
|
||||||
errors.err("condition value should be an integer type", untilLoop.condition.position)
|
errors.err("condition value should be an integer type", untilLoop.condition.position)
|
||||||
super.visit(untilLoop)
|
super.visit(untilLoop)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(whileLoop: WhileLoop) {
|
override fun visit(whileLoop: WhileLoop) {
|
||||||
if(whileLoop.condition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
|
if(!whileLoop.condition.inferType(program).isInteger)
|
||||||
errors.err("condition value should be an integer type", whileLoop.condition.position)
|
errors.err("condition value should be an integer type", whileLoop.condition.position)
|
||||||
super.visit(whileLoop)
|
super.visit(whileLoop)
|
||||||
}
|
}
|
||||||
@ -374,47 +417,23 @@ internal class AstChecker(private val program: Program,
|
|||||||
if(!idt.isKnown) {
|
if(!idt.isKnown) {
|
||||||
errors.err("return type mismatch", assignment.value.position)
|
errors.err("return type mismatch", assignment.value.position)
|
||||||
}
|
}
|
||||||
if(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)
|
errors.err("return type mismatch", assignment.value.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val targetIdent = assignment.target.identifier
|
|
||||||
if(targetIdent!=null) {
|
|
||||||
val targetVar = targetIdent.targetVarDecl(program)
|
|
||||||
if(targetVar?.struct != null) {
|
|
||||||
val sourceStructLv = assignment.value as? ArrayLiteralValue
|
|
||||||
if (sourceStructLv != null) {
|
|
||||||
if (sourceStructLv.value.size != targetVar.struct?.numberOfElements)
|
|
||||||
errors.err("number of elements doesn't match struct definition", sourceStructLv.position)
|
|
||||||
} else {
|
|
||||||
val sourceIdent = assignment.value as? IdentifierReference
|
|
||||||
if (sourceIdent != null) {
|
|
||||||
val sourceVar = sourceIdent.targetVarDecl(program)
|
|
||||||
if (sourceVar?.struct != null) {
|
|
||||||
if (sourceVar.struct !== targetVar.struct)
|
|
||||||
errors.err("assignment of different struct types", assignment.position)
|
|
||||||
} else if(sourceVar?.isArray==true) {
|
|
||||||
if((sourceVar.value as ArrayLiteralValue).value.size != targetVar.struct?.numberOfElements)
|
|
||||||
errors.err("number of elements doesn't match struct definition", sourceVar.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val targetDt = assignment.target.inferType(program)
|
val targetDt = assignment.target.inferType(program)
|
||||||
val valueDt = assignment.value.inferType(program)
|
val valueDt = assignment.value.inferType(program)
|
||||||
if(valueDt.isKnown && !(valueDt isAssignableTo targetDt)) {
|
if(valueDt.isKnown && !(valueDt isAssignableTo targetDt)) {
|
||||||
if(targetDt.typeOrElse(DataType.STRUCT) in IterableDatatypes)
|
if(targetDt.isIterable)
|
||||||
errors.err("cannot assign value to string or array", assignment.value.position)
|
errors.err("cannot assign value to string or array", assignment.value.position)
|
||||||
else if(!(valueDt.istype(DataType.STR) && targetDt.istype(DataType.UWORD)))
|
else if(!(valueDt istype DataType.STR && targetDt istype DataType.UWORD))
|
||||||
errors.err("value's type doesn't match target", assignment.value.position)
|
errors.err("type of value doesn't match target", assignment.value.position)
|
||||||
}
|
}
|
||||||
|
|
||||||
if(assignment.value is TypecastExpression) {
|
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)
|
errors.err("typecasting a float value in-place makes no sense", assignment.value.position)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -463,15 +482,15 @@ internal class AstChecker(private val program: Program,
|
|||||||
if (targetDatatype.isKnown) {
|
if (targetDatatype.isKnown) {
|
||||||
val constVal = assignment.value.constValue(program)
|
val constVal = assignment.value.constValue(program)
|
||||||
if (constVal != null) {
|
if (constVal != null) {
|
||||||
checkValueTypeAndRange(targetDatatype.typeOrElse(DataType.BYTE), constVal)
|
checkValueTypeAndRange(targetDatatype.getOr(DataType.BYTE), constVal)
|
||||||
} else {
|
} else {
|
||||||
val sourceDatatype = assignment.value.inferType(program)
|
val sourceDatatype = assignment.value.inferType(program)
|
||||||
if (sourceDatatype.isUnknown) {
|
if (sourceDatatype.isUnknown) {
|
||||||
if (assignment.value !is FunctionCall)
|
if (assignment.value !is FunctionCall)
|
||||||
errors.err("assignment value is invalid or has no proper datatype", assignment.value.position)
|
errors.err("assignment value is invalid or has no proper datatype", assignment.value.position)
|
||||||
} else {
|
} else {
|
||||||
checkAssignmentCompatible(targetDatatype.typeOrElse(DataType.BYTE), assignTarget,
|
checkAssignmentCompatible(targetDatatype.getOr(DataType.BYTE), assignTarget,
|
||||||
sourceDatatype.typeOrElse(DataType.BYTE), assignment.value, assignment.position)
|
sourceDatatype.getOr(DataType.BYTE), assignment.value, assignment.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -480,12 +499,8 @@ internal class AstChecker(private val program: Program,
|
|||||||
|
|
||||||
override fun visit(addressOf: AddressOf) {
|
override fun visit(addressOf: AddressOf) {
|
||||||
val variable=addressOf.identifier.targetVarDecl(program)
|
val variable=addressOf.identifier.targetVarDecl(program)
|
||||||
if(variable!=null
|
if(variable!=null && variable.type==VarDeclType.CONST)
|
||||||
&& variable.datatype !in ArrayDatatypes
|
errors.err("invalid pointer-of operand type", addressOf.position)
|
||||||
&& variable.type!=VarDeclType.MEMORY
|
|
||||||
&& variable.struct == null
|
|
||||||
&& variable.datatype != DataType.STR && variable.datatype!=DataType.STRUCT)
|
|
||||||
errors.err("invalid pointer-of operand type", addressOf.position)
|
|
||||||
super.visit(addressOf)
|
super.visit(addressOf)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -493,7 +508,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
fun err(msg: String, position: Position?=null) = errors.err(msg, position ?: decl.position)
|
fun err(msg: String, position: Position?=null) = errors.err(msg, position ?: decl.position)
|
||||||
|
|
||||||
// the initializer value can't refer to the variable itself (recursive definition)
|
// the initializer value can't refer to the variable itself (recursive definition)
|
||||||
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.indexVar?.referencesIdentifier(decl.name) == true)
|
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.indexExpr?.referencesIdentifier(decl.name) == true)
|
||||||
err("recursive var declaration")
|
err("recursive var declaration")
|
||||||
|
|
||||||
// CONST can only occur on simple types (byte, word, float)
|
// CONST can only occur on simple types (byte, word, float)
|
||||||
@ -503,7 +518,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FLOATS enabled?
|
// 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")
|
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))
|
if(decl.datatype == DataType.FLOAT && (decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE || decl.zeropage==ZeropageWish.PREFER_ZEROPAGE))
|
||||||
@ -527,17 +542,6 @@ internal class AstChecker(private val program: Program,
|
|||||||
|
|
||||||
when(decl.type) {
|
when(decl.type) {
|
||||||
VarDeclType.VAR, VarDeclType.CONST -> {
|
VarDeclType.VAR, VarDeclType.CONST -> {
|
||||||
if(decl.datatype==DataType.STRUCT) {
|
|
||||||
if(decl.struct==null)
|
|
||||||
throw FatalAstException("struct vardecl should be linked to its struct $decl")
|
|
||||||
if(decl.zeropage==ZeropageWish.PREFER_ZEROPAGE || decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE)
|
|
||||||
err("struct can not be in zeropage")
|
|
||||||
}
|
|
||||||
if(decl.struct!=null) {
|
|
||||||
if(decl.zeropage==ZeropageWish.PREFER_ZEROPAGE || decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE)
|
|
||||||
err("struct can not be in zeropage")
|
|
||||||
}
|
|
||||||
|
|
||||||
when(decl.value) {
|
when(decl.value) {
|
||||||
null -> {
|
null -> {
|
||||||
// a vardecl without an initial value, don't bother with it
|
// a vardecl without an initial value, don't bother with it
|
||||||
@ -547,30 +551,8 @@ internal class AstChecker(private val program: Program,
|
|||||||
checkValueTypeAndRangeString(decl.datatype, decl.value as StringLiteralValue)
|
checkValueTypeAndRangeString(decl.datatype, decl.value as StringLiteralValue)
|
||||||
}
|
}
|
||||||
is ArrayLiteralValue -> {
|
is ArrayLiteralValue -> {
|
||||||
if(decl.datatype==DataType.STRUCT) {
|
val arraySpec = decl.arraysize ?: ArrayIndex.forArray(decl.value as ArrayLiteralValue)
|
||||||
val struct = decl.struct!!
|
checkValueTypeAndRangeArray(decl.datatype, arraySpec, decl.value as ArrayLiteralValue)
|
||||||
val structLv = decl.value as ArrayLiteralValue
|
|
||||||
if(struct.numberOfElements != structLv.value.size) {
|
|
||||||
errors.err("struct value has incorrect number of elements", structLv.position)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for(value in structLv.value.zip(struct.statements)) {
|
|
||||||
val memberdecl = value.second as VarDecl
|
|
||||||
val constValue = value.first.constValue(program)
|
|
||||||
if(constValue==null) {
|
|
||||||
errors.err("struct literal value for field '${memberdecl.name}' should consist of compile-time constants", value.first.position)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val memberDt = memberdecl.datatype
|
|
||||||
if(!checkValueTypeAndRange(memberDt, constValue)) {
|
|
||||||
errors.err("struct member value's type is not compatible with member field '${memberdecl.name}'", value.first.position)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val arraySpec = decl.arraysize ?: ArrayIndex.forArray(decl.value as ArrayLiteralValue)
|
|
||||||
checkValueTypeAndRangeArray(decl.datatype, decl.struct, arraySpec, decl.value as ArrayLiteralValue)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
is NumericLiteralValue -> {
|
is NumericLiteralValue -> {
|
||||||
checkValueTypeAndRange(decl.datatype, decl.value as NumericLiteralValue)
|
checkValueTypeAndRange(decl.datatype, decl.value as NumericLiteralValue)
|
||||||
@ -585,8 +567,9 @@ internal class AstChecker(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
VarDeclType.MEMORY -> {
|
VarDeclType.MEMORY -> {
|
||||||
if(decl.arraysize!=null) {
|
val arraysize = decl.arraysize
|
||||||
val arraySize = decl.arraysize!!.constIndex() ?: 1
|
if(arraysize!=null) {
|
||||||
|
val arraySize = arraysize.constIndex() ?: 1
|
||||||
when(decl.datatype) {
|
when(decl.datatype) {
|
||||||
DataType.ARRAY_B, DataType.ARRAY_UB ->
|
DataType.ARRAY_B, DataType.ARRAY_UB ->
|
||||||
if(arraySize > 256)
|
if(arraySize > 256)
|
||||||
@ -600,10 +583,9 @@ internal class AstChecker(private val program: Program,
|
|||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val numvalue = decl.value as? NumericLiteralValue
|
||||||
if(decl.value is NumericLiteralValue) {
|
if(numvalue!=null) {
|
||||||
val value = decl.value as NumericLiteralValue
|
if (numvalue.type !in IntegerDatatypes || numvalue.number.toInt() < 0 || numvalue.number.toInt() > 65535) {
|
||||||
if (value.type !in IntegerDatatypes || value.number.toInt() < 0 || value.number.toInt() > 65535) {
|
|
||||||
err("memory address must be valid integer 0..\$ffff", decl.value?.position)
|
err("memory address must be valid integer 0..\$ffff", decl.value?.position)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -614,23 +596,16 @@ internal class AstChecker(private val program: Program,
|
|||||||
|
|
||||||
val declValue = decl.value
|
val declValue = decl.value
|
||||||
if(declValue!=null && decl.type==VarDeclType.VAR) {
|
if(declValue!=null && decl.type==VarDeclType.VAR) {
|
||||||
if(decl.datatype==DataType.STRUCT) {
|
if (declValue.inferType(program) isnot decl.datatype) {
|
||||||
val valueIdt = declValue.inferType(program)
|
|
||||||
if(!valueIdt.isKnown)
|
|
||||||
throw AstException("unknown dt")
|
|
||||||
val valueDt = valueIdt.typeOrElse(DataType.STRUCT)
|
|
||||||
if(valueDt !in ArrayDatatypes)
|
|
||||||
err("initialisation of struct should be with array value", declValue.position)
|
|
||||||
} else if (!declValue.inferType(program).istype(decl.datatype)) {
|
|
||||||
err("initialisation value has incompatible type (${declValue.inferType(program)}) for the variable (${decl.datatype})", declValue.position)
|
err("initialisation value has incompatible type (${declValue.inferType(program)}) for the variable (${decl.datatype})", declValue.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// array length limits and constant lenghts
|
// array length limits and constant lenghts
|
||||||
if(decl.isArray) {
|
if(decl.isArray) {
|
||||||
val length = decl.arraysize!!.constIndex()
|
val length = decl.arraysize?.constIndex()
|
||||||
if(length==null)
|
if(length==null)
|
||||||
err("array length must be a constant")
|
err("array length must be known at compile-time")
|
||||||
else {
|
else {
|
||||||
when (decl.datatype) {
|
when (decl.datatype) {
|
||||||
DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||||
@ -712,20 +687,20 @@ internal class AstChecker(private val program: Program,
|
|||||||
}
|
}
|
||||||
"%breakpoint" -> {
|
"%breakpoint" -> {
|
||||||
if(directive.parent !is INameScope || directive.parent is Module)
|
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())
|
if(directive.args.isNotEmpty())
|
||||||
err("invalid breakpoint directive, expected no arguments")
|
err("invalid breakpoint directive, expected no arguments")
|
||||||
}
|
}
|
||||||
"%asminclude" -> {
|
"%asminclude" -> {
|
||||||
if(directive.parent !is INameScope || directive.parent is Module)
|
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.size!=2 || directive.args[0].str==null || directive.args[1].str==null)
|
if(directive.args.size!=1 || directive.args[0].str==null)
|
||||||
err("invalid asminclude directive, expected arguments: \"filename\", \"scopelabel\"")
|
err("invalid asminclude directive, expected argument: \"filename\"")
|
||||||
checkFileExists(directive, directive.args[0].str!!)
|
checkFileExists(directive, directive.args[0].str!!)
|
||||||
}
|
}
|
||||||
"%asmbinary" -> {
|
"%asmbinary" -> {
|
||||||
if(directive.parent !is INameScope || directive.parent is Module)
|
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 ] ]"
|
val errormsg = "invalid asmbinary directive, expected arguments: \"filename\" [, offset [, length ] ]"
|
||||||
if(directive.args.isEmpty()) err(errormsg)
|
if(directive.args.isEmpty()) err(errormsg)
|
||||||
else if(directive.args.isNotEmpty() && directive.args[0].str==null) err(errormsg)
|
else if(directive.args.isNotEmpty() && directive.args[0].str==null) err(errormsg)
|
||||||
@ -739,37 +714,37 @@ internal class AstChecker(private val program: Program,
|
|||||||
err("this directive may only occur in a block or at module level")
|
err("this directive may only occur in a block or at module level")
|
||||||
if(directive.args.isEmpty())
|
if(directive.args.isEmpty())
|
||||||
err("missing option directive argument(s)")
|
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)")
|
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)
|
else -> throw SyntaxError("invalid directive ${directive.directive}", directive.position)
|
||||||
}
|
}
|
||||||
super.visit(directive)
|
super.visit(directive)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkFileExists(directive: Directive, filename: String) {
|
private fun checkFileExists(directive: Directive, filename: String) {
|
||||||
var definingModule = directive.parent
|
if (File(filename).isFile)
|
||||||
while (definingModule !is Module)
|
return
|
||||||
definingModule = definingModule.parent
|
|
||||||
if (!(filename.startsWith("library:") || definingModule.source.resolveSibling(filename).toFile().isFile || File(filename).isFile))
|
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)
|
errors.err("included file not found: $filename", directive.position)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(array: ArrayLiteralValue) {
|
override fun visit(array: ArrayLiteralValue) {
|
||||||
if(array.type.isKnown) {
|
if(array.type.isKnown) {
|
||||||
if (!compilerOptions.floats && array.type.typeOrElse(DataType.STRUCT) 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)
|
errors.err("floating point used, but that is not enabled via options", array.position)
|
||||||
}
|
}
|
||||||
val arrayspec = ArrayIndex.forArray(array)
|
val arrayspec = ArrayIndex.forArray(array)
|
||||||
checkValueTypeAndRangeArray(array.type.typeOrElse(DataType.STRUCT), null, arrayspec, array)
|
checkValueTypeAndRangeArray(array.type.getOr(DataType.UNDEFINED), arrayspec, array)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isPassByReferenceElement(e: Expression): Boolean {
|
fun isPassByReferenceElement(e: Expression): Boolean {
|
||||||
@ -791,8 +766,25 @@ internal class AstChecker(private val program: Program,
|
|||||||
super.visit(array)
|
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) {
|
override fun visit(string: StringLiteralValue) {
|
||||||
checkValueTypeAndRangeString(DataType.STR, string)
|
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)
|
super.visit(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -801,7 +793,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
if(!idt.isKnown)
|
if(!idt.isKnown)
|
||||||
return // any error should be reported elsewhere
|
return // any error should be reported elsewhere
|
||||||
|
|
||||||
val dt = idt.typeOrElse(DataType.STRUCT)
|
val dt = idt.getOr(DataType.UNDEFINED)
|
||||||
if(expr.operator=="-") {
|
if(expr.operator=="-") {
|
||||||
if (dt != DataType.BYTE && dt != DataType.WORD && dt != DataType.FLOAT) {
|
if (dt != DataType.BYTE && dt != DataType.WORD && dt != DataType.FLOAT) {
|
||||||
errors.err("can only take negative of a signed number type", expr.position)
|
errors.err("can only take negative of a signed number type", expr.position)
|
||||||
@ -826,8 +818,8 @@ internal class AstChecker(private val program: Program,
|
|||||||
if(!leftIDt.isKnown || !rightIDt.isKnown)
|
if(!leftIDt.isKnown || !rightIDt.isKnown)
|
||||||
return // hopefully this error will be detected elsewhere
|
return // hopefully this error will be detected elsewhere
|
||||||
|
|
||||||
val leftDt = leftIDt.typeOrElse(DataType.STRUCT)
|
val leftDt = leftIDt.getOr(DataType.UNDEFINED)
|
||||||
val rightDt = rightIDt.typeOrElse(DataType.STRUCT)
|
val rightDt = rightIDt.getOr(DataType.UNDEFINED)
|
||||||
|
|
||||||
when(expr.operator){
|
when(expr.operator){
|
||||||
"/", "%" -> {
|
"/", "%" -> {
|
||||||
@ -923,7 +915,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
// warn about sgn(unsigned) this is likely a mistake
|
// warn about sgn(unsigned) this is likely a mistake
|
||||||
if(functionCall.target.nameInSource.last()=="sgn") {
|
if(functionCall.target.nameInSource.last()=="sgn") {
|
||||||
val sgnArgType = functionCall.args.first().inferType(program)
|
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)
|
errors.warn("sgn() of unsigned type is always 0 or 1, this is perhaps not what was intended", functionCall.args.first().position)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -953,6 +945,20 @@ internal class AstChecker(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// functions that don't return a value, can't be used in an expression or assignment
|
||||||
|
if(targetStatement is Subroutine) {
|
||||||
|
if(targetStatement.returntypes.isEmpty()) {
|
||||||
|
if(functionCall.parent is Expression || functionCall.parent is Assignment)
|
||||||
|
errors.err("subroutine doesn't return a value", functionCall.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(targetStatement is BuiltinFunctionStatementPlaceholder) {
|
||||||
|
if(builtinFunctionReturnType(targetStatement.name, functionCall.args, program).isUnknown) {
|
||||||
|
if(functionCall.parent is Expression || functionCall.parent is Assignment)
|
||||||
|
errors.err("function doesn't return a value", functionCall.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
super.visit(functionCall)
|
super.visit(functionCall)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -978,12 +984,12 @@ internal class AstChecker(private val program: Program,
|
|||||||
if(functionCallStatement.target.nameInSource.last() == "sort") {
|
if(functionCallStatement.target.nameInSource.last() == "sort") {
|
||||||
// sort is not supported on float arrays
|
// sort is not supported on float arrays
|
||||||
val idref = functionCallStatement.args.singleOrNull() as? IdentifierReference
|
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)
|
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
|
// in-place modification, can't be done on literals
|
||||||
if(functionCallStatement.args.any { it !is IdentifierReference && it !is ArrayIndexedExpression && it !is DirectMemoryRead }) {
|
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)
|
errors.err("invalid argument to a in-place modifying function", functionCallStatement.args.first().position)
|
||||||
@ -1005,7 +1011,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
|
|
||||||
if(target is BuiltinFunctionStatementPlaceholder) {
|
if(target is BuiltinFunctionStatementPlaceholder) {
|
||||||
if(target.name=="swap") {
|
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 dt1 = args[0].inferType(program)
|
||||||
val dt2 = args[1].inferType(program)
|
val dt2 = args[1].inferType(program)
|
||||||
if (dt1 != dt2)
|
if (dt1 != dt2)
|
||||||
@ -1014,14 +1020,14 @@ internal class AstChecker(private val program: Program,
|
|||||||
errors.err("swap requires 2 variables, not constant value(s)", position)
|
errors.err("swap requires 2 variables, not constant value(s)", position)
|
||||||
else if(args[0] isSameAs args[1])
|
else if(args[0] isSameAs args[1])
|
||||||
errors.err("swap should have 2 different args", position)
|
errors.err("swap should have 2 different args", position)
|
||||||
else if(dt1.typeOrElse(DataType.STRUCT) !in NumericDatatypes)
|
else if(!dt1.isNumeric)
|
||||||
errors.err("swap requires args of numerical type", position)
|
errors.err("swap requires args of numerical type", position)
|
||||||
}
|
}
|
||||||
else if(target.name=="all" || target.name=="any") {
|
else if(target.name=="all" || target.name=="any") {
|
||||||
if((args[0] as? AddressOf)?.identifier?.targetVarDecl(program)?.datatype == DataType.STR) {
|
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)
|
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)
|
errors.err("any/all on a string is useless (is always true unless the string is empty)", position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1045,7 +1051,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
ident = fcall.args[0] as? IdentifierReference
|
ident = fcall.args[0] as? IdentifierReference
|
||||||
}
|
}
|
||||||
if(ident!=null && ident.nameInSource[0] == "cx16" && ident.nameInSource[1].startsWith("r")) {
|
if(ident!=null && ident.nameInSource[0] == "cx16" && ident.nameInSource[1].startsWith("r")) {
|
||||||
val reg = RegisterOrPair.valueOf(ident.nameInSource[1].toUpperCase())
|
val reg = RegisterOrPair.valueOf(ident.nameInSource[1].uppercase())
|
||||||
val same = params.filter { it.value.registerOrPair==reg }
|
val same = params.filter { it.value.registerOrPair==reg }
|
||||||
for(s in same) {
|
for(s in same) {
|
||||||
if(s.index!=arg.index) {
|
if(s.index!=arg.index) {
|
||||||
@ -1111,22 +1117,15 @@ internal class AstChecker(private val program: Program,
|
|||||||
errors.err("indexing requires a variable to act upon", arrayIndexedExpression.position)
|
errors.err("indexing requires a variable to act upon", arrayIndexedExpression.position)
|
||||||
|
|
||||||
// check index value 0..255
|
// check index value 0..255
|
||||||
val dtxNum = arrayIndexedExpression.indexer.indexNum?.inferType(program)?.typeOrElse(DataType.STRUCT)
|
val dtxNum = arrayIndexedExpression.indexer.indexExpr.inferType(program)
|
||||||
if(dtxNum!=null && dtxNum != DataType.UBYTE && dtxNum != DataType.BYTE)
|
if(dtxNum isnot DataType.UBYTE && dtxNum isnot DataType.BYTE)
|
||||||
errors.err("array indexing is limited to byte size 0..255", arrayIndexedExpression.position)
|
errors.err("array indexing is limited to byte size 0..255", arrayIndexedExpression.position)
|
||||||
val dtxVar = arrayIndexedExpression.indexer.indexVar?.inferType(program)?.typeOrElse(DataType.STRUCT)
|
|
||||||
if(dtxVar!=null && dtxVar != DataType.UBYTE && dtxVar != DataType.BYTE)
|
|
||||||
errors.err("array indexing is limited to byte size 0..255", arrayIndexedExpression.position)
|
|
||||||
|
|
||||||
if(arrayIndexedExpression.indexer.origExpression!=null)
|
|
||||||
throw FatalAstException("array indexer should have been replaced with a temp var @ ${arrayIndexedExpression.indexer.position}")
|
|
||||||
|
|
||||||
super.visit(arrayIndexedExpression)
|
super.visit(arrayIndexedExpression)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(whenStatement: WhenStatement) {
|
override fun visit(whenStatement: WhenStatement) {
|
||||||
val conditionType = whenStatement.condition.inferType(program).typeOrElse(DataType.STRUCT)
|
if(!whenStatement.condition.inferType(program).isInteger)
|
||||||
if(conditionType !in IntegerDatatypes)
|
|
||||||
errors.err("when condition must be an integer value", whenStatement.position)
|
errors.err("when condition must be an integer value", whenStatement.position)
|
||||||
val tally = mutableSetOf<Int>()
|
val tally = mutableSetOf<Int>()
|
||||||
for((choices, choiceNode) in whenStatement.choiceValues(program)) {
|
for((choices, choiceNode) in whenStatement.choiceValues(program)) {
|
||||||
@ -1157,7 +1156,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
when {
|
when {
|
||||||
constvalue == null -> errors.err("choice value must be a constant", whenChoice.position)
|
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 !in IntegerDatatypes -> errors.err("choice value must be a byte or word", whenChoice.position)
|
||||||
constvalue.type != conditionType.typeOrElse(DataType.STRUCT) -> 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 {
|
} else {
|
||||||
@ -1167,32 +1166,12 @@ internal class AstChecker(private val program: Program,
|
|||||||
super.visit(whenChoice)
|
super.visit(whenChoice)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(structDecl: StructDecl) {
|
|
||||||
// a struct can only contain 1 or more vardecls and can not be nested
|
|
||||||
if(structDecl.statements.isEmpty())
|
|
||||||
errors.err("struct must contain at least one member", structDecl.position)
|
|
||||||
|
|
||||||
for(member in structDecl.statements){
|
|
||||||
val decl = member as? VarDecl
|
|
||||||
if(decl==null)
|
|
||||||
errors.err("struct can only contain variable declarations", structDecl.position)
|
|
||||||
else {
|
|
||||||
if(decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE || decl.zeropage==ZeropageWish.PREFER_ZEROPAGE)
|
|
||||||
errors.err("struct can not contain zeropage members", decl.position)
|
|
||||||
if(decl.datatype !in NumericDatatypes)
|
|
||||||
errors.err("structs can only contain numerical types", decl.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: Statement): Statement? {
|
private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: Statement): Statement? {
|
||||||
val targetStatement = target.targetStatement(program)
|
when (val targetStatement = target.targetStatement(program)) {
|
||||||
if(targetStatement is Label || targetStatement is Subroutine || targetStatement is BuiltinFunctionStatementPlaceholder)
|
is Label, is Subroutine, is BuiltinFunctionStatementPlaceholder -> return targetStatement
|
||||||
return targetStatement
|
null -> errors.err("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", statement.position)
|
||||||
else if(targetStatement==null)
|
else -> errors.err("cannot call that: ${target.nameInSource.joinToString(".")}", statement.position)
|
||||||
errors.err("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", statement.position)
|
}
|
||||||
else
|
|
||||||
errors.err("cannot call that: ${target.nameInSource.joinToString(".")}", statement.position)
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1208,8 +1187,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
else false
|
else false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkValueTypeAndRangeArray(targetDt: DataType, struct: StructDecl?,
|
private fun checkValueTypeAndRangeArray(targetDt: DataType, arrayspec: ArrayIndex, value: ArrayLiteralValue) : Boolean {
|
||||||
arrayspec: ArrayIndex, value: ArrayLiteralValue) : Boolean {
|
|
||||||
fun err(msg: String) : Boolean {
|
fun err(msg: String) : Boolean {
|
||||||
errors.err(msg, value.position)
|
errors.err(msg, value.position)
|
||||||
return false
|
return false
|
||||||
@ -1222,7 +1200,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
DataType.STR -> return err("string value expected")
|
DataType.STR -> return err("string value expected")
|
||||||
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||||
// value may be either a single byte, or a byte arraysize (of all constant values), or a range
|
// 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))
|
if(!checkArrayValues(value, targetDt))
|
||||||
return false
|
return false
|
||||||
val arraySpecSize = arrayspec.constIndex()
|
val arraySpecSize = arrayspec.constIndex()
|
||||||
@ -1241,7 +1219,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
}
|
}
|
||||||
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||||
// value may be either a single word, or a word arraysize, or a range
|
// 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))
|
if(!checkArrayValues(value, targetDt))
|
||||||
return false
|
return false
|
||||||
val arraySpecSize = arrayspec.constIndex()
|
val arraySpecSize = arrayspec.constIndex()
|
||||||
@ -1260,7 +1238,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
}
|
}
|
||||||
DataType.ARRAY_F -> {
|
DataType.ARRAY_F -> {
|
||||||
// value may be either a single float, or a float arraysize
|
// 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))
|
if(!checkArrayValues(value, targetDt))
|
||||||
return false
|
return false
|
||||||
val arraySize = value.value.size
|
val arraySize = value.value.size
|
||||||
@ -1282,22 +1260,6 @@ internal class AstChecker(private val program: Program,
|
|||||||
}
|
}
|
||||||
return err("invalid float array initialization value ${value.type}, expected $targetDt")
|
return err("invalid float array initialization value ${value.type}, expected $targetDt")
|
||||||
}
|
}
|
||||||
DataType.STRUCT -> {
|
|
||||||
if(value.type.typeOrElse(DataType.STRUCT) in ArrayDatatypes) {
|
|
||||||
if(value.value.size != struct!!.numberOfElements)
|
|
||||||
return err("number of values is not the same as the number of members in the struct")
|
|
||||||
for(elt in value.value.zip(struct.statements)) {
|
|
||||||
val vardecl = elt.second as VarDecl
|
|
||||||
val valuetype = elt.first.inferType(program)
|
|
||||||
if (!valuetype.isKnown || valuetype isNotAssignableTo vardecl.datatype) {
|
|
||||||
errors.err("invalid struct member init value type $valuetype, expected ${vardecl.datatype}", elt.first.position)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
else -> return false
|
else -> return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1400,15 +1362,6 @@ internal class AstChecker(private val program: Program,
|
|||||||
DataType.UWORD -> sourceDatatype== DataType.UBYTE || sourceDatatype== DataType.UWORD
|
DataType.UWORD -> sourceDatatype== DataType.UBYTE || sourceDatatype== DataType.UWORD
|
||||||
DataType.FLOAT -> sourceDatatype in NumericDatatypes
|
DataType.FLOAT -> sourceDatatype in NumericDatatypes
|
||||||
DataType.STR -> sourceDatatype== DataType.STR
|
DataType.STR -> sourceDatatype== DataType.STR
|
||||||
DataType.STRUCT -> {
|
|
||||||
if(sourceDatatype==DataType.STRUCT) {
|
|
||||||
val structLv = sourceValue as ArrayLiteralValue
|
|
||||||
val numValues = structLv.value.size
|
|
||||||
val targetstruct = target.identifier!!.targetVarDecl(program)!!.struct!!
|
|
||||||
return targetstruct.numberOfElements == numValues
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
else -> {
|
else -> {
|
||||||
errors.err("cannot assign new value to variable of type $targetDatatype", position)
|
errors.err("cannot assign new value to variable of type $targetDatatype", position)
|
||||||
false
|
false
|
||||||
@ -1422,10 +1375,15 @@ internal class AstChecker(private val program: Program,
|
|||||||
errors.err("cannot assign word to byte, use msb() or lsb()?", position)
|
errors.err("cannot assign word to byte, use msb() or lsb()?", position)
|
||||||
}
|
}
|
||||||
else if(sourceDatatype== DataType.FLOAT && targetDatatype in IntegerDatatypes)
|
else if(sourceDatatype== DataType.FLOAT && targetDatatype in IntegerDatatypes)
|
||||||
errors.err("cannot assign float to ${targetDatatype.name.toLowerCase()}; possible loss of precision. Suggestion: round the value or revert to integer arithmetic", position)
|
errors.err("cannot assign float to ${targetDatatype.name.lowercase()}; possible loss of precision. Suggestion: round the value or revert to integer arithmetic", position)
|
||||||
else {
|
else {
|
||||||
if(targetDatatype!=DataType.UWORD && sourceDatatype !in PassByReferenceDatatypes)
|
if(targetDatatype!=DataType.UWORD && sourceDatatype !in PassByReferenceDatatypes)
|
||||||
errors.err("cannot assign ${sourceDatatype.name.toLowerCase()} to ${targetDatatype.name.toLowerCase()}", position)
|
errors.err(
|
||||||
|
"cannot assign ${sourceDatatype.name.lowercase()} to ${
|
||||||
|
targetDatatype.name.lowercase(
|
||||||
|
Locale.getDefault()
|
||||||
|
)
|
||||||
|
}", position)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,12 +1,67 @@
|
|||||||
package prog8.compiler.astprocessing
|
package prog8.compiler.astprocessing
|
||||||
|
|
||||||
|
import prog8.ast.Node
|
||||||
import prog8.ast.Program
|
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.statements.Directive
|
||||||
|
import prog8.ast.walk.AstWalker
|
||||||
|
import prog8.ast.walk.IAstModification
|
||||||
import prog8.compiler.BeforeAsmGenerationAstChanger
|
import prog8.compiler.BeforeAsmGenerationAstChanger
|
||||||
import prog8.compiler.CompilationOptions
|
import prog8.compiler.CompilationOptions
|
||||||
import prog8.compiler.IErrorReporter
|
import prog8.compiler.IErrorReporter
|
||||||
|
import prog8.compiler.IStringEncoding
|
||||||
import prog8.compiler.target.ICompilationTarget
|
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) {
|
internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: IErrorReporter, compTarget: ICompilationTarget) {
|
||||||
@ -17,13 +72,34 @@ internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: IEr
|
|||||||
internal fun Program.processAstBeforeAsmGeneration(errors: IErrorReporter, compTarget: ICompilationTarget) {
|
internal fun Program.processAstBeforeAsmGeneration(errors: IErrorReporter, compTarget: ICompilationTarget) {
|
||||||
val fixer = BeforeAsmGenerationAstChanger(this, errors, compTarget)
|
val fixer = BeforeAsmGenerationAstChanger(this, errors, compTarget)
|
||||||
fixer.visit(this)
|
fixer.visit(this)
|
||||||
fixer.applyModifications()
|
while(errors.noErrors() && fixer.applyModifications()>0) {
|
||||||
|
fixer.visit(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun Program.reorderStatements(errors: IErrorReporter) {
|
internal fun Program.reorderStatements(errors: IErrorReporter) {
|
||||||
val reorder = StatementReorderer(this, errors)
|
val reorder = StatementReorderer(this, errors)
|
||||||
reorder.visit(this)
|
reorder.visit(this)
|
||||||
reorder.applyModifications()
|
if(errors.noErrors()) {
|
||||||
|
reorder.applyModifications()
|
||||||
|
reorder.visit(this)
|
||||||
|
if(errors.noErrors())
|
||||||
|
reorder.applyModifications()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
internal fun Program.addTypecasts(errors: IErrorReporter) {
|
||||||
@ -37,12 +113,12 @@ internal fun Program.verifyFunctionArgTypes() {
|
|||||||
fixer.visit(this)
|
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)
|
checker2.visit(this)
|
||||||
|
|
||||||
if(errors.isEmpty()) {
|
if(errors.noErrors()) {
|
||||||
val transforms = AstVariousTransforms(this)
|
val transforms = AstVariousTransforms(this)
|
||||||
transforms.visit(this)
|
transforms.visit(this)
|
||||||
transforms.applyModifications()
|
transforms.applyModifications()
|
||||||
@ -50,48 +126,86 @@ internal fun Program.checkIdentifiers(errors: IErrorReporter, compTarget: ICompi
|
|||||||
lit2decl.visit(this)
|
lit2decl.visit(this)
|
||||||
lit2decl.applyModifications()
|
lit2decl.applyModifications()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modules.map { it.name }.toSet().size != modules.size) {
|
|
||||||
throw FatalAstException("modules should all be unique")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun Program.variousCleanups() {
|
internal fun Program.variousCleanups(program: Program, errors: IErrorReporter) {
|
||||||
val process = VariousCleanups()
|
val process = VariousCleanups(program, errors)
|
||||||
process.visit(this)
|
process.visit(this)
|
||||||
process.applyModifications()
|
if(errors.noErrors())
|
||||||
|
process.applyModifications()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
internal fun Program.moveMainAndStartToFirst() {
|
internal fun Program.moveMainAndStartToFirst() {
|
||||||
// the module containing the program entrypoint is moved to the first in the sequence.
|
// the module containing the program entrypoint is moved to the first in the sequence.
|
||||||
// the "main" block containing the entrypoint is moved to the top in there,
|
// the "main" block containing the entrypoint is moved to the top in there,
|
||||||
// and finally the entrypoint subroutine "start" itself is moved to the top in that block.
|
// and finally the entrypoint subroutine "start" itself is moved to the top in that block.
|
||||||
|
|
||||||
val directives = modules[0].statements.filterIsInstance<Directive>()
|
val directives = modules[0].statements.filterIsInstance<Directive>()
|
||||||
val start = this.entrypoint()
|
val start = this.entrypoint
|
||||||
if(start!=null) {
|
val mod = start.definingModule
|
||||||
val mod = start.definingModule()
|
val block = start.definingBlock
|
||||||
val block = start.definingBlock()
|
moveModuleToFront(mod)
|
||||||
if(!modules.remove(mod))
|
mod.remove(block)
|
||||||
throw FatalAstException("module wrong")
|
var afterDirective = mod.statements.indexOfFirst { it !is Directive }
|
||||||
modules.add(0, mod)
|
if(afterDirective<0)
|
||||||
mod.remove(block)
|
mod.statements.add(block)
|
||||||
var afterDirective = mod.statements.indexOfFirst { it !is Directive }
|
else
|
||||||
if(afterDirective<0)
|
mod.statements.add(afterDirective, block)
|
||||||
mod.statements.add(block)
|
block.remove(start)
|
||||||
else
|
afterDirective = block.statements.indexOfFirst { it !is Directive }
|
||||||
mod.statements.add(afterDirective, block)
|
if(afterDirective<0)
|
||||||
block.remove(start)
|
block.statements.add(start)
|
||||||
afterDirective = block.statements.indexOfFirst { it !is Directive }
|
else
|
||||||
if(afterDirective<0)
|
block.statements.add(afterDirective, start)
|
||||||
block.statements.add(start)
|
|
||||||
else
|
|
||||||
block.statements.add(afterDirective, start)
|
|
||||||
|
|
||||||
// overwrite the directives in the module containing the entrypoint
|
// overwrite the directives in the module containing the entrypoint
|
||||||
for(directive in directives) {
|
for(directive in directives) {
|
||||||
modules[0].statements.removeAll { it is Directive && it.directive == directive.directive }
|
modules[0].statements.removeAll { it is Directive && it.directive == directive.directive }
|
||||||
modules[0].statements.add(0, directive)
|
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,12 +1,7 @@
|
|||||||
package prog8.compiler.astprocessing
|
package prog8.compiler.astprocessing
|
||||||
|
|
||||||
import prog8.ast.Module
|
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.DataType
|
|
||||||
import prog8.ast.base.NumericDatatypes
|
|
||||||
import prog8.ast.base.Position
|
import prog8.ast.base.Position
|
||||||
import prog8.ast.expressions.ArrayLiteralValue
|
|
||||||
import prog8.ast.expressions.NumericLiteralValue
|
|
||||||
import prog8.ast.expressions.StringLiteralValue
|
import prog8.ast.expressions.StringLiteralValue
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.ast.walk.IAstVisitor
|
import prog8.ast.walk.IAstVisitor
|
||||||
@ -21,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)
|
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) {
|
override fun visit(block: Block) {
|
||||||
if(block.name in compTarget.machine.opcodeNames)
|
if(block.name in compTarget.machine.opcodeNames)
|
||||||
errors.err("can't use a cpu opcode name as a symbol: '${block.name}'", block.position)
|
errors.err("can't use a cpu opcode name as a symbol: '${block.name}'", block.position)
|
||||||
|
|
||||||
val existing = blocks[block.name]
|
val existing = blocks[block.name]
|
||||||
if(existing!=null)
|
if(existing!=null) {
|
||||||
nameError(block.name, block.position, existing)
|
if(block.isInLibrary)
|
||||||
|
nameError(existing.name, existing.position, block)
|
||||||
|
else
|
||||||
|
nameError(block.name, block.position, existing)
|
||||||
|
}
|
||||||
else
|
else
|
||||||
blocks[block.name] = block
|
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)
|
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) {
|
override fun visit(decl: VarDecl) {
|
||||||
decl.datatypeErrors.forEach { errors.err(it.message, it.position) }
|
decl.datatypeErrors.forEach { errors.err(it.message, it.position) }
|
||||||
|
|
||||||
@ -66,37 +42,14 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
|
|||||||
if(decl.name in compTarget.machine.opcodeNames)
|
if(decl.name in compTarget.machine.opcodeNames)
|
||||||
errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position)
|
errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position)
|
||||||
|
|
||||||
if(decl.datatype==DataType.STRUCT) {
|
|
||||||
if (decl.structHasBeenFlattened)
|
|
||||||
return super.visit(decl) // don't do this multiple times
|
|
||||||
|
|
||||||
if (decl.struct == null) {
|
|
||||||
errors.err("undefined struct type", decl.position)
|
|
||||||
return super.visit(decl)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (decl.struct!!.statements.any { (it as VarDecl).datatype !in NumericDatatypes })
|
|
||||||
return super.visit(decl) // a non-numeric member, not supported. proper error is given by AstChecker later
|
|
||||||
|
|
||||||
if (decl.value is NumericLiteralValue) {
|
|
||||||
errors.err("you cannot initialize a struct using a single value", decl.position)
|
|
||||||
return super.visit(decl)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (decl.value != null && decl.value !is ArrayLiteralValue) {
|
|
||||||
errors.err("initializing a struct requires array literal value", decl.value?.position ?: decl.position)
|
|
||||||
return super.visit(decl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val existing = program.namespace.lookup(listOf(decl.name), decl)
|
val existing = program.namespace.lookup(listOf(decl.name), decl)
|
||||||
if (existing != null && existing !== decl)
|
if (existing != null && existing !== decl)
|
||||||
nameError(decl.name, decl.position, existing)
|
nameError(decl.name, decl.position, existing)
|
||||||
|
|
||||||
if(decl.definingBlock().name==decl.name)
|
if(decl.definingBlock.name==decl.name)
|
||||||
nameError(decl.name, decl.position, decl.definingBlock())
|
nameError(decl.name, decl.position, decl.definingBlock)
|
||||||
if(decl.definingSubroutine()?.name==decl.name)
|
if(decl.definingSubroutine?.name==decl.name)
|
||||||
nameError(decl.name, decl.position, decl.definingSubroutine()!!)
|
nameError(decl.name, decl.position, decl.definingSubroutine!!)
|
||||||
|
|
||||||
super.visit(decl)
|
super.visit(decl)
|
||||||
}
|
}
|
||||||
@ -116,8 +69,8 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
|
|||||||
if (existing != null && existing !== subroutine)
|
if (existing != null && existing !== subroutine)
|
||||||
nameError(subroutine.name, subroutine.position, existing)
|
nameError(subroutine.name, subroutine.position, existing)
|
||||||
|
|
||||||
// check that there are no local variables, labels, or other subs that redefine the subroutine's parameters
|
// 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 symbolsInSub = subroutine.allDefinedSymbols
|
||||||
val namesInSub = symbolsInSub.map{ it.first }.toSet()
|
val namesInSub = symbolsInSub.map{ it.first }.toSet()
|
||||||
val paramNames = subroutine.parameters.map { it.name }.toSet()
|
val paramNames = subroutine.parameters.map { it.name }.toSet()
|
||||||
val paramsToCheck = paramNames.intersect(namesInSub)
|
val paramsToCheck = paramNames.intersect(namesInSub)
|
||||||
@ -128,14 +81,17 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
|
|||||||
val sub = subroutine.statements.firstOrNull { it is Subroutine && it.name==name}
|
val sub = subroutine.statements.firstOrNull { it is Subroutine && it.name==name}
|
||||||
if(sub!=null)
|
if(sub!=null)
|
||||||
nameError(name, subroutine.position, sub)
|
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}) {
|
if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) {
|
||||||
errors.err("asmsub can only contain inline assembly (%asm)", subroutine.position)
|
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)
|
super.visit(subroutine)
|
||||||
@ -149,7 +105,7 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
|
|||||||
// the builtin functions can't be redefined
|
// the builtin functions can't be redefined
|
||||||
errors.err("builtin function cannot be redefined", label.position)
|
errors.err("builtin function cannot be redefined", label.position)
|
||||||
} else {
|
} else {
|
||||||
val existing = label.definingSubroutine()?.getAllLabels(label.name) ?: emptyList()
|
val existing = label.definingSubroutine?.getAllLabels(label.name) ?: emptyList()
|
||||||
for(el in existing) {
|
for(el in existing) {
|
||||||
if(el === label || el.name != label.name)
|
if(el === label || el.name != label.name)
|
||||||
continue
|
continue
|
||||||
@ -169,14 +125,4 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
|
|||||||
|
|
||||||
super.visit(string)
|
super.visit(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(structDecl: StructDecl) {
|
|
||||||
for(member in structDecl.statements){
|
|
||||||
val decl = member as? VarDecl
|
|
||||||
if(decl!=null && decl.datatype !in NumericDatatypes)
|
|
||||||
errors.err("structs can only contain numerical types", decl.position)
|
|
||||||
}
|
|
||||||
|
|
||||||
super.visit(structDecl)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -3,38 +3,21 @@ package prog8.compiler.astprocessing
|
|||||||
import prog8.ast.Node
|
import prog8.ast.Node
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.DataType
|
import prog8.ast.base.DataType
|
||||||
|
import prog8.ast.expressions.ArrayIndexedExpression
|
||||||
import prog8.ast.expressions.BinaryExpression
|
import prog8.ast.expressions.BinaryExpression
|
||||||
|
import prog8.ast.expressions.DirectMemoryRead
|
||||||
import prog8.ast.expressions.StringLiteralValue
|
import prog8.ast.expressions.StringLiteralValue
|
||||||
import prog8.ast.statements.AnonymousScope
|
import prog8.ast.statements.*
|
||||||
import prog8.ast.statements.ParameterVarDecl
|
|
||||||
import prog8.ast.statements.Subroutine
|
|
||||||
import prog8.ast.statements.VarDecl
|
|
||||||
import prog8.ast.walk.AstWalker
|
import prog8.ast.walk.AstWalker
|
||||||
import prog8.ast.walk.IAstModification
|
import prog8.ast.walk.IAstModification
|
||||||
|
|
||||||
|
|
||||||
internal class AstVariousTransforms(private val program: Program) : AstWalker() {
|
internal class AstVariousTransforms(private val program: Program) : AstWalker() {
|
||||||
private val noModifications = emptyList<IAstModification>()
|
|
||||||
|
|
||||||
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
|
||||||
// is it a struct variable? then define all its struct members as mangled names,
|
|
||||||
// and include the original decl as well.
|
|
||||||
if(decl.datatype==DataType.STRUCT && !decl.structHasBeenFlattened) {
|
|
||||||
val decls = decl.flattenStructMembers()
|
|
||||||
decls.add(decl)
|
|
||||||
val result = AnonymousScope(decls, decl.position)
|
|
||||||
return listOf(IAstModification.ReplaceNode(
|
|
||||||
decl, result, parent
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
return noModifications
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||||
// For non-kernal subroutines and non-asm parameters:
|
// For non-kernal subroutines and non-asm parameters:
|
||||||
// inject subroutine params as local variables (if they're not there yet).
|
// 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()
|
val namesInSub = symbolsInSub.map{ it.first }.toSet()
|
||||||
if(subroutine.asmAddress==null) {
|
if(subroutine.asmAddress==null) {
|
||||||
if(subroutine.asmParameterRegisters.isEmpty() && subroutine.parameters.isNotEmpty()) {
|
if(subroutine.asmParameterRegisters.isEmpty() && subroutine.parameters.isNotEmpty()) {
|
||||||
@ -83,6 +66,10 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
|
|||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||||
|
return replacePointerVarIndexWithMemread(program, arrayIndexedExpression, parent)
|
||||||
|
}
|
||||||
|
|
||||||
private fun concatString(expr: BinaryExpression): StringLiteralValue? {
|
private fun concatString(expr: BinaryExpression): StringLiteralValue? {
|
||||||
val rightStrval = expr.right as? StringLiteralValue
|
val rightStrval = expr.right as? StringLiteralValue
|
||||||
val leftStrval = expr.left as? StringLiteralValue
|
val leftStrval = expr.left as? StringLiteralValue
|
||||||
@ -109,3 +96,25 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
internal fun replacePointerVarIndexWithMemread(program: Program, arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||||
|
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program)
|
||||||
|
if(arrayVar!=null && arrayVar.datatype == DataType.UWORD) {
|
||||||
|
// rewrite pointervar[index] into @(pointervar+index)
|
||||||
|
val indexer = arrayIndexedExpression.indexer
|
||||||
|
val add = BinaryExpression(arrayIndexedExpression.arrayvar, "+", indexer.indexExpr, arrayIndexedExpression.position)
|
||||||
|
return if(parent is AssignTarget) {
|
||||||
|
// we're part of the target of an assignment, we have to actually change the assign target itself
|
||||||
|
val memwrite = DirectMemoryWrite(add, arrayIndexedExpression.position)
|
||||||
|
val newtarget = AssignTarget(null, null, memwrite, arrayIndexedExpression.position)
|
||||||
|
listOf(IAstModification.ReplaceNode(parent, newtarget, parent.parent))
|
||||||
|
} else {
|
||||||
|
val memread = DirectMemoryRead(add, arrayIndexedExpression.position)
|
||||||
|
listOf(IAstModification.ReplaceNode(arrayIndexedExpression, memread, parent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
@ -13,11 +13,10 @@ import prog8.ast.walk.IAstModification
|
|||||||
|
|
||||||
|
|
||||||
internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
|
internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
|
||||||
private val noModifications = emptyList<IAstModification>()
|
|
||||||
|
|
||||||
override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> {
|
override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||||
if(string.parent !is VarDecl && string.parent !is WhenChoice) {
|
if(string.parent !is VarDecl && string.parent !is WhenChoice) {
|
||||||
// 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 scopedName = program.internString(string)
|
||||||
val identifier = IdentifierReference(scopedName, string.position)
|
val identifier = IdentifierReference(scopedName, string.position)
|
||||||
return listOf(IAstModification.ReplaceNode(string, identifier, parent))
|
return listOf(IAstModification.ReplaceNode(string, identifier, parent))
|
||||||
@ -30,7 +29,7 @@ internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
|
|||||||
if(vardecl!=null) {
|
if(vardecl!=null) {
|
||||||
// adjust the datatype of the array (to an educated guess)
|
// adjust the datatype of the array (to an educated guess)
|
||||||
val arrayDt = array.type
|
val arrayDt = array.type
|
||||||
if(!arrayDt.istype(vardecl.datatype)) {
|
if(arrayDt isnot vardecl.datatype) {
|
||||||
val cast = array.cast(vardecl.datatype)
|
val cast = array.cast(vardecl.datatype)
|
||||||
if (cast != null && cast !== array)
|
if (cast != null && cast !== array)
|
||||||
return listOf(IAstModification.ReplaceNode(vardecl.value!!, cast, vardecl))
|
return listOf(IAstModification.ReplaceNode(vardecl.value!!, cast, vardecl))
|
||||||
@ -39,13 +38,13 @@ internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
|
|||||||
val arrayDt = array.guessDatatype(program)
|
val arrayDt = array.guessDatatype(program)
|
||||||
if(arrayDt.isKnown) {
|
if(arrayDt.isKnown) {
|
||||||
// this array literal is part of an expression, turn it into an identifier reference
|
// this array literal is part of an expression, turn it into an identifier reference
|
||||||
val litval2 = array.cast(arrayDt.typeOrElse(DataType.STRUCT))
|
val litval2 = array.cast(arrayDt.getOr(DataType.UNDEFINED))
|
||||||
if(litval2!=null) {
|
if(litval2!=null) {
|
||||||
val vardecl2 = VarDecl.createAuto(litval2)
|
val vardecl2 = VarDecl.createAuto(litval2)
|
||||||
val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position)
|
val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position)
|
||||||
return listOf(
|
return listOf(
|
||||||
IAstModification.ReplaceNode(array, identifier, parent),
|
IAstModification.ReplaceNode(array, identifier, parent),
|
||||||
IAstModification.InsertFirst(vardecl2, array.definingScope())
|
IAstModification.InsertFirst(vardecl2, array.definingScope)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
package prog8.compiler.astprocessing
|
package prog8.compiler.astprocessing
|
||||||
|
|
||||||
import prog8.ast.*
|
import prog8.ast.IFunctionCall
|
||||||
import prog8.ast.base.*
|
import prog8.ast.Module
|
||||||
|
import prog8.ast.Node
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.DataType
|
||||||
|
import prog8.ast.base.FatalAstException
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.ast.walk.AstWalker
|
import prog8.ast.walk.AstWalker
|
||||||
@ -18,12 +22,10 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
|||||||
// - in every block and module, most directives and vardecls are moved to the top. (not in subroutines!)
|
// - in every block and module, most directives and vardecls are moved to the top. (not in subroutines!)
|
||||||
// - the 'start' subroutine is moved to the top.
|
// - the 'start' subroutine is moved to the top.
|
||||||
// - (syntax desugaring) a vardecl with a non-const initializer value is split into a regular vardecl and an assignment statement.
|
// - (syntax desugaring) a vardecl with a non-const initializer value is split into a regular vardecl and an assignment statement.
|
||||||
// - (syntax desugaring) struct value assignment is expanded into several struct member assignments.
|
|
||||||
// - in-place assignments are reordered a bit so that they are mostly of the form A = A <operator> <rest>
|
// - in-place assignments are reordered a bit so that they are mostly of the form A = A <operator> <rest>
|
||||||
// - sorts the choices in when statement.
|
// - sorts the choices in when statement.
|
||||||
// - insert AddressOf (&) expression where required (string params to a UWORD function param etc).
|
// - insert AddressOf (&) expression where required (string params to a UWORD function param etc.).
|
||||||
|
|
||||||
private val noModifications = emptyList<IAstModification>()
|
|
||||||
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option")
|
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option")
|
||||||
|
|
||||||
override fun after(module: Module, parent: Node): Iterable<IAstModification> {
|
override fun after(module: Module, parent: Node): Iterable<IAstModification> {
|
||||||
@ -84,44 +86,17 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||||
|
return replacePointerVarIndexWithMemread(program, arrayIndexedExpression, parent)
|
||||||
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program)
|
|
||||||
if(arrayVar!=null && arrayVar.datatype == DataType.UWORD) {
|
|
||||||
// rewrite pointervar[index] into @(pointervar+index)
|
|
||||||
val indexer = arrayIndexedExpression.indexer
|
|
||||||
val index = (indexer.indexNum ?: indexer.indexVar)!!
|
|
||||||
val add = BinaryExpression(arrayIndexedExpression.arrayvar, "+", index, arrayIndexedExpression.position)
|
|
||||||
return if(parent is AssignTarget) {
|
|
||||||
// we're part of the target of an assignment, we have to actually change the assign target itself
|
|
||||||
val memwrite = DirectMemoryWrite(add, arrayIndexedExpression.position)
|
|
||||||
val newtarget = AssignTarget(null, null, memwrite, arrayIndexedExpression.position)
|
|
||||||
listOf(IAstModification.ReplaceNode(parent, newtarget, parent.parent))
|
|
||||||
} else {
|
|
||||||
val memread = DirectMemoryRead(add, arrayIndexedExpression.position)
|
|
||||||
listOf(IAstModification.ReplaceNode(arrayIndexedExpression, memread, parent))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
when (val expr2 = arrayIndexedExpression.indexer.origExpression) {
|
|
||||||
is NumericLiteralValue -> {
|
|
||||||
arrayIndexedExpression.indexer.indexNum = expr2
|
|
||||||
arrayIndexedExpression.indexer.origExpression = null
|
|
||||||
return noModifications
|
|
||||||
}
|
|
||||||
is IdentifierReference -> {
|
|
||||||
arrayIndexedExpression.indexer.indexVar = expr2
|
|
||||||
arrayIndexedExpression.indexer.origExpression = null
|
|
||||||
return noModifications
|
|
||||||
}
|
|
||||||
is Expression -> {
|
|
||||||
// replace complex indexing with a temp variable
|
|
||||||
return getAutoIndexerVarFor(arrayIndexedExpression)
|
|
||||||
}
|
|
||||||
else -> return noModifications
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||||
|
|
||||||
|
// ConstValue <associativeoperator> X --> X <associativeoperator> ConstValue
|
||||||
|
// (this should be done by the ExpressionSimplifier when optimizing is enabled,
|
||||||
|
// but the current assembly code generator for IF statements now also depends on it, so we do it here regardless of optimization.)
|
||||||
|
if (expr.left.constValue(program) != null && expr.operator in associativeOperators && expr.right.constValue(program) == null)
|
||||||
|
return listOf(IAstModification.SwapOperands(expr))
|
||||||
|
|
||||||
// when using a simple bit shift and assigning it to a variable of a different type,
|
// when using a simple bit shift and assigning it to a variable of a different type,
|
||||||
// try to make the bit shifting 'wide enough' to fall into the variable's type.
|
// try to make the bit shifting 'wide enough' to fall into the variable's type.
|
||||||
// with this, for instance, uword x = 1 << 10 will result in 1024 rather than 0 (the ubyte result).
|
// with this, for instance, uword x = 1 << 10 will result in 1024 rather than 0 (the ubyte result).
|
||||||
@ -131,12 +106,12 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
|||||||
is Assignment -> {
|
is Assignment -> {
|
||||||
val targetDt = parent.target.inferType(program)
|
val targetDt = parent.target.inferType(program)
|
||||||
if(leftDt != targetDt) {
|
if(leftDt != targetDt) {
|
||||||
val cast = TypecastExpression(expr.left, targetDt.typeOrElse(DataType.STRUCT), true, parent.position)
|
val cast = TypecastExpression(expr.left, targetDt.getOr(DataType.UNDEFINED), true, parent.position)
|
||||||
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is VarDecl -> {
|
is VarDecl -> {
|
||||||
if(!leftDt.istype(parent.datatype)) {
|
if(leftDt isnot parent.datatype) {
|
||||||
val cast = TypecastExpression(expr.left, parent.datatype, true, parent.position)
|
val cast = TypecastExpression(expr.left, parent.datatype, true, parent.position)
|
||||||
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
|
||||||
}
|
}
|
||||||
@ -202,38 +177,6 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
|||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getAutoIndexerVarFor(expr: ArrayIndexedExpression): MutableList<IAstModification> {
|
|
||||||
val modifications = mutableListOf<IAstModification>()
|
|
||||||
val subroutine = expr.definingSubroutine()!!
|
|
||||||
val statement = expr.containingStatement()
|
|
||||||
val indexerVarPrefix = "prog8_autovar_index_"
|
|
||||||
val repo = subroutine.asmGenInfo.usedAutoArrayIndexerForStatements
|
|
||||||
|
|
||||||
// TODO make this a bit smarter so it can reuse indexer variables. BUT BEWARE of scoping+initialization problems then
|
|
||||||
// add another loop index var to be used for this expression
|
|
||||||
val indexerVarName = "$indexerVarPrefix${expr.indexer.hashCode()}"
|
|
||||||
val indexerVar = AsmGenInfo.ArrayIndexerInfo(indexerVarName, expr.indexer)
|
|
||||||
repo.add(indexerVar)
|
|
||||||
// create the indexer var at block level scope
|
|
||||||
val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.PREFER_ZEROPAGE,
|
|
||||||
null, indexerVarName, null, null, isArray = false, autogeneratedDontRemove = true, position = expr.position)
|
|
||||||
modifications.add(IAstModification.InsertFirst(vardecl, subroutine))
|
|
||||||
|
|
||||||
// replace the indexer with just the variable
|
|
||||||
// assign the indexing expression to the helper variable, but only if that hasn't been done already
|
|
||||||
val indexerExpression = expr.indexer.origExpression!!
|
|
||||||
val target = AssignTarget(IdentifierReference(listOf(indexerVar.name), indexerExpression.position), null, null, indexerExpression.position)
|
|
||||||
val assign = Assignment(target, indexerExpression, indexerExpression.position)
|
|
||||||
modifications.add(IAstModification.InsertBefore(statement, assign, statement.definingScope()))
|
|
||||||
modifications.add(IAstModification.SetExpression( {
|
|
||||||
expr.indexer.indexVar = it as IdentifierReference
|
|
||||||
expr.indexer.indexNum = null
|
|
||||||
expr.indexer.origExpression = null
|
|
||||||
}, target.identifier!!.copy(), expr.indexer))
|
|
||||||
|
|
||||||
return modifications
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
|
override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
|
||||||
val choices = whenStatement.choiceValues(program).sortedBy {
|
val choices = whenStatement.choiceValues(program).sortedBy {
|
||||||
it.first?.first() ?: Int.MAX_VALUE
|
it.first?.first() ?: Int.MAX_VALUE
|
||||||
@ -243,43 +186,13 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
|||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
|
||||||
val declValue = decl.value
|
|
||||||
if(declValue!=null && decl.type== VarDeclType.VAR && decl.datatype in NumericDatatypes) {
|
|
||||||
val declConstValue = declValue.constValue(program)
|
|
||||||
if(declConstValue==null) {
|
|
||||||
// move the vardecl (without value) to the scope and replace this with a regular assignment
|
|
||||||
// Unless we're dealing with a floating point variable because that will actually make things less efficient at the moment (because floats are mostly calcualated via the stack)
|
|
||||||
if(decl.datatype!=DataType.FLOAT) {
|
|
||||||
decl.value = null
|
|
||||||
decl.allowInitializeWithZero = false
|
|
||||||
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
|
|
||||||
val assign = Assignment(target, declValue, decl.position)
|
|
||||||
return listOf(
|
|
||||||
IAstModification.ReplaceNode(decl, assign, parent),
|
|
||||||
IAstModification.InsertFirst(decl, decl.definingScope())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return noModifications
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||||
val valueType = assignment.value.inferType(program)
|
val valueType = assignment.value.inferType(program)
|
||||||
val targetType = assignment.target.inferType(program)
|
val targetType = assignment.target.inferType(program)
|
||||||
|
|
||||||
if(targetType.istype(DataType.STRUCT) && (valueType.istype(DataType.STRUCT) || valueType.typeOrElse(DataType.STRUCT) in ArrayDatatypes )) {
|
if(targetType.isArray && valueType.isArray) {
|
||||||
if (assignment.value is ArrayLiteralValue) {
|
if (assignment.value is ArrayLiteralValue) {
|
||||||
errors.err("cannot assign non-const array value, use separate assignment per field", assignment.position)
|
errors.err("cannot assign array literal here, use separate assignment per element", assignment.position)
|
||||||
} else {
|
|
||||||
return copyStructValue(assignment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(targetType.typeOrElse(DataType.STRUCT) in ArrayDatatypes && valueType.typeOrElse(DataType.STRUCT) in ArrayDatatypes ) {
|
|
||||||
if (assignment.value is ArrayLiteralValue) {
|
|
||||||
errors.err("cannot assign non-const array value, use separate assignment per element", assignment.position)
|
|
||||||
} else {
|
} else {
|
||||||
return copyArrayValue(assignment)
|
return copyArrayValue(assignment)
|
||||||
}
|
}
|
||||||
@ -344,6 +257,10 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
|||||||
if(targetVar.arraysize==null)
|
if(targetVar.arraysize==null)
|
||||||
errors.err("array has no defined size", assign.position)
|
errors.err("array has no defined size", assign.position)
|
||||||
|
|
||||||
|
if(assign.value !is IdentifierReference) {
|
||||||
|
errors.err("invalid array value to assign to other array", assign.value.position)
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
val sourceIdent = assign.value as IdentifierReference
|
val sourceIdent = assign.value as IdentifierReference
|
||||||
val sourceVar = sourceIdent.targetVarDecl(program)!!
|
val sourceVar = sourceIdent.targetVarDecl(program)!!
|
||||||
if(!sourceVar.isArray) {
|
if(!sourceVar.isArray) {
|
||||||
@ -355,8 +272,8 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
|||||||
errors.err("element type mismatch", assign.position)
|
errors.err("element type mismatch", assign.position)
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!errors.isEmpty())
|
if(!errors.noErrors())
|
||||||
return emptyList()
|
return noModifications
|
||||||
|
|
||||||
val memcopy = FunctionCallStatement(IdentifierReference(listOf("sys", "memcopy"), assign.position),
|
val memcopy = FunctionCallStatement(IdentifierReference(listOf("sys", "memcopy"), assign.position),
|
||||||
mutableListOf(
|
mutableListOf(
|
||||||
@ -369,73 +286,4 @@ internal class StatementReorderer(val program: Program, val errors: IErrorReport
|
|||||||
)
|
)
|
||||||
return listOf(IAstModification.ReplaceNode(assign, memcopy, assign.parent))
|
return listOf(IAstModification.ReplaceNode(assign, memcopy, assign.parent))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun copyStructValue(structAssignment: Assignment): List<IAstModification> {
|
|
||||||
val identifier = structAssignment.target.identifier!!
|
|
||||||
val targetVar = identifier.targetVarDecl(program)!!
|
|
||||||
val struct = targetVar.struct!!
|
|
||||||
when (structAssignment.value) {
|
|
||||||
is IdentifierReference -> {
|
|
||||||
val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program)!!
|
|
||||||
val memsize = struct.memsize(program.memsizer)
|
|
||||||
when {
|
|
||||||
sourceVar.struct!=null -> {
|
|
||||||
// struct memberwise copy
|
|
||||||
val sourceStruct = sourceVar.struct!!
|
|
||||||
if(sourceStruct!==targetVar.struct) {
|
|
||||||
errors.err("struct type mismatch", structAssignment.position)
|
|
||||||
return listOf()
|
|
||||||
}
|
|
||||||
if(struct.statements.size!=sourceStruct.statements.size) {
|
|
||||||
errors.err("struct element count mismatch", structAssignment.position)
|
|
||||||
return listOf()
|
|
||||||
}
|
|
||||||
if(memsize!=sourceStruct.memsize(program.memsizer)) {
|
|
||||||
errors.err("memory size mismatch", structAssignment.position)
|
|
||||||
return listOf()
|
|
||||||
}
|
|
||||||
val memcopy = FunctionCallStatement(IdentifierReference(listOf("sys", "memcopy"), structAssignment.position),
|
|
||||||
mutableListOf(
|
|
||||||
AddressOf(structAssignment.value as IdentifierReference, structAssignment.position),
|
|
||||||
AddressOf(identifier, structAssignment.position),
|
|
||||||
NumericLiteralValue.optimalInteger(memsize, structAssignment.position)
|
|
||||||
),
|
|
||||||
true,
|
|
||||||
structAssignment.position
|
|
||||||
)
|
|
||||||
return listOf(IAstModification.ReplaceNode(structAssignment, memcopy, structAssignment.parent))
|
|
||||||
}
|
|
||||||
sourceVar.isArray -> {
|
|
||||||
val array = sourceVar.value as ArrayLiteralValue
|
|
||||||
if(struct.statements.size!=array.value.size) {
|
|
||||||
errors.err("struct element count mismatch", structAssignment.position)
|
|
||||||
return listOf()
|
|
||||||
}
|
|
||||||
if(memsize!=array.memsize(program.memsizer)) {
|
|
||||||
errors.err("memory size mismatch", structAssignment.position)
|
|
||||||
return listOf()
|
|
||||||
}
|
|
||||||
val memcopy = FunctionCallStatement(IdentifierReference(listOf("sys", "memcopy"), structAssignment.position),
|
|
||||||
mutableListOf(
|
|
||||||
AddressOf(structAssignment.value as IdentifierReference, structAssignment.position),
|
|
||||||
AddressOf(identifier, structAssignment.position),
|
|
||||||
NumericLiteralValue.optimalInteger(memsize, structAssignment.position)
|
|
||||||
),
|
|
||||||
true,
|
|
||||||
structAssignment.position
|
|
||||||
)
|
|
||||||
return listOf(IAstModification.ReplaceNode(structAssignment, memcopy, structAssignment.parent))
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
throw FatalAstException("can only assign arrays or structs to structs")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is ArrayLiteralValue -> {
|
|
||||||
throw IllegalArgumentException("not going to do a structLv assignment here")
|
|
||||||
}
|
|
||||||
else -> throw FatalAstException("strange struct value")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -18,16 +18,18 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
|
|||||||
* (this includes function call arguments)
|
* (this includes function call arguments)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
private val noModifications = emptyList<IAstModification>()
|
|
||||||
|
|
||||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||||
val declValue = decl.value
|
val declValue = decl.value
|
||||||
if(decl.type==VarDeclType.VAR && declValue!=null && decl.struct==null) {
|
if(decl.type==VarDeclType.VAR && declValue!=null) {
|
||||||
val valueDt = declValue.inferType(program)
|
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
|
// don't add a typecast on an array initializer value
|
||||||
if(valueDt.typeOrElse(DataType.STRUCT) in IntegerDatatypes && 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
|
||||||
|
if(valueDt isNotAssignableTo decl.datatype)
|
||||||
return noModifications
|
return noModifications
|
||||||
|
|
||||||
return listOf(IAstModification.ReplaceNode(
|
return listOf(IAstModification.ReplaceNode(
|
||||||
@ -45,7 +47,7 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
|
|||||||
val rightDt = expr.right.inferType(program)
|
val rightDt = expr.right.inferType(program)
|
||||||
if(leftDt.isKnown && rightDt.isKnown && leftDt!=rightDt) {
|
if(leftDt.isKnown && rightDt.isKnown && leftDt!=rightDt) {
|
||||||
// determine common datatype and add typecast as required to make left and right equal types
|
// determine common datatype and add typecast as required to make left and right equal types
|
||||||
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.typeOrElse(DataType.STRUCT), rightDt.typeOrElse(DataType.STRUCT), expr.left, expr.right)
|
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.getOr(DataType.UNDEFINED), rightDt.getOr(DataType.UNDEFINED), expr.left, expr.right)
|
||||||
if(toFix!=null) {
|
if(toFix!=null) {
|
||||||
return when {
|
return when {
|
||||||
toFix===expr.left -> listOf(IAstModification.ReplaceNode(
|
toFix===expr.left -> listOf(IAstModification.ReplaceNode(
|
||||||
@ -64,8 +66,8 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
|
|||||||
val valueItype = assignment.value.inferType(program)
|
val valueItype = assignment.value.inferType(program)
|
||||||
val targetItype = assignment.target.inferType(program)
|
val targetItype = assignment.target.inferType(program)
|
||||||
if(targetItype.isKnown && valueItype.isKnown) {
|
if(targetItype.isKnown && valueItype.isKnown) {
|
||||||
val targettype = targetItype.typeOrElse(DataType.STRUCT)
|
val targettype = targetItype.getOr(DataType.UNDEFINED)
|
||||||
val valuetype = valueItype.typeOrElse(DataType.STRUCT)
|
val valuetype = valueItype.getOr(DataType.UNDEFINED)
|
||||||
if (valuetype != targettype) {
|
if (valuetype != targettype) {
|
||||||
if (valuetype isAssignableTo targettype) {
|
if (valuetype isAssignableTo targettype) {
|
||||||
if(valuetype in IterableDatatypes && targettype==DataType.UWORD)
|
if(valuetype in IterableDatatypes && targettype==DataType.UWORD)
|
||||||
@ -124,7 +126,7 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
|
|||||||
sub.parameters.zip(call.args).forEachIndexed { index, pair ->
|
sub.parameters.zip(call.args).forEachIndexed { index, pair ->
|
||||||
val argItype = pair.second.inferType(program)
|
val argItype = pair.second.inferType(program)
|
||||||
if(argItype.isKnown) {
|
if(argItype.isKnown) {
|
||||||
val argtype = argItype.typeOrElse(DataType.STRUCT)
|
val argtype = argItype.getOr(DataType.UNDEFINED)
|
||||||
val requiredType = pair.first.type
|
val requiredType = pair.first.type
|
||||||
if (requiredType != argtype) {
|
if (requiredType != argtype) {
|
||||||
if (argtype isAssignableTo requiredType) {
|
if (argtype isAssignableTo requiredType) {
|
||||||
@ -157,7 +159,7 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
|
|||||||
func.parameters.zip(call.args).forEachIndexed { index, pair ->
|
func.parameters.zip(call.args).forEachIndexed { index, pair ->
|
||||||
val argItype = pair.second.inferType(program)
|
val argItype = pair.second.inferType(program)
|
||||||
if (argItype.isKnown) {
|
if (argItype.isKnown) {
|
||||||
val argtype = argItype.typeOrElse(DataType.STRUCT)
|
val argtype = argItype.getOr(DataType.UNDEFINED)
|
||||||
if (pair.first.possibleDatatypes.all { argtype != it }) {
|
if (pair.first.possibleDatatypes.all { argtype != it }) {
|
||||||
for (possibleType in pair.first.possibleDatatypes) {
|
for (possibleType in pair.first.possibleDatatypes) {
|
||||||
if (argtype isAssignableTo possibleType) {
|
if (argtype isAssignableTo possibleType) {
|
||||||
@ -180,7 +182,7 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
|
|||||||
|
|
||||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||||
// warn about any implicit type casts to Float, because that may not be intended
|
// 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)
|
errors.warn("integer implicitly converted to float. Suggestion: use float literals, add an explicit cast, or revert to integer arithmetic", typecast.position)
|
||||||
}
|
}
|
||||||
return noModifications
|
return noModifications
|
||||||
@ -189,7 +191,7 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
|
|||||||
override fun after(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
|
override fun after(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
|
||||||
// make sure the memory address is an uword
|
// make sure the memory address is an uword
|
||||||
val dt = memread.addressExpression.inferType(program)
|
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()
|
val typecast = (memread.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)?.valueOrZero()
|
||||||
?: TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position)
|
?: TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position)
|
||||||
return listOf(IAstModification.ReplaceNode(memread.addressExpression, typecast, memread))
|
return listOf(IAstModification.ReplaceNode(memread.addressExpression, typecast, memread))
|
||||||
@ -200,7 +202,7 @@ class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalk
|
|||||||
override fun after(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> {
|
override fun after(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> {
|
||||||
// make sure the memory address is an uword
|
// make sure the memory address is an uword
|
||||||
val dt = memwrite.addressExpression.inferType(program)
|
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()
|
val typecast = (memwrite.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)?.valueOrZero()
|
||||||
?: TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position)
|
?: TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position)
|
||||||
return listOf(IAstModification.ReplaceNode(memwrite.addressExpression, typecast, memwrite))
|
return listOf(IAstModification.ReplaceNode(memwrite.addressExpression, typecast, memwrite))
|
||||||
@ -212,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
|
// add a typecast to the return type if it doesn't match the subroutine's signature
|
||||||
val returnValue = returnStmt.value
|
val returnValue = returnStmt.value
|
||||||
if(returnValue!=null) {
|
if(returnValue!=null) {
|
||||||
val subroutine = returnStmt.definingSubroutine()!!
|
val subroutine = returnStmt.definingSubroutine!!
|
||||||
if(subroutine.returntypes.size==1) {
|
if(subroutine.returntypes.size==1) {
|
||||||
val subReturnType = subroutine.returntypes.first()
|
val subReturnType = subroutine.returntypes.first()
|
||||||
if (returnValue.inferType(program).istype(subReturnType))
|
if (returnValue.inferType(program) istype subReturnType)
|
||||||
return noModifications
|
return noModifications
|
||||||
if (returnValue is NumericLiteralValue) {
|
if (returnValue is NumericLiteralValue) {
|
||||||
val cast = returnValue.cast(subroutine.returntypes.single())
|
val cast = returnValue.cast(subroutine.returntypes.single())
|
||||||
|
@ -3,18 +3,17 @@ package prog8.compiler.astprocessing
|
|||||||
import prog8.ast.IFunctionCall
|
import prog8.ast.IFunctionCall
|
||||||
import prog8.ast.INameScope
|
import prog8.ast.INameScope
|
||||||
import prog8.ast.Node
|
import prog8.ast.Node
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.FatalAstException
|
||||||
import prog8.ast.base.Position
|
import prog8.ast.base.Position
|
||||||
import prog8.ast.expressions.DirectMemoryRead
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.expressions.FunctionCall
|
|
||||||
import prog8.ast.expressions.NumericLiteralValue
|
|
||||||
import prog8.ast.expressions.TypecastExpression
|
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.ast.walk.AstWalker
|
import prog8.ast.walk.AstWalker
|
||||||
import prog8.ast.walk.IAstModification
|
import prog8.ast.walk.IAstModification
|
||||||
|
import prog8.compiler.IErrorReporter
|
||||||
|
|
||||||
|
|
||||||
internal class VariousCleanups: AstWalker() {
|
internal class VariousCleanups(val program: Program, val errors: IErrorReporter): AstWalker() {
|
||||||
private val noModifications = emptyList<IAstModification>()
|
|
||||||
|
|
||||||
override fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> {
|
override fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> {
|
||||||
return listOf(IAstModification.Remove(nopStatement, parent as INameScope))
|
return listOf(IAstModification.Remove(nopStatement, parent as INameScope))
|
||||||
@ -32,21 +31,12 @@ internal class VariousCleanups: AstWalker() {
|
|||||||
val idx = into.statements.indexOf(scope)
|
val idx = into.statements.indexOf(scope)
|
||||||
if(idx>=0) {
|
if(idx>=0) {
|
||||||
into.statements.addAll(idx+1, scope.statements)
|
into.statements.addAll(idx+1, scope.statements)
|
||||||
|
scope.statements.forEach { it.parent = into as Node }
|
||||||
into.statements.remove(scope)
|
into.statements.remove(scope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
|
||||||
if(typecast.expression is NumericLiteralValue) {
|
|
||||||
val value = (typecast.expression as NumericLiteralValue).cast(typecast.type)
|
|
||||||
if(value.isValid)
|
|
||||||
return listOf(IAstModification.ReplaceNode(typecast, value.valueOrZero(), parent))
|
|
||||||
}
|
|
||||||
|
|
||||||
return noModifications
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
override fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||||
return before(functionCallStatement as IFunctionCall, parent, functionCallStatement.position)
|
return before(functionCallStatement as IFunctionCall, parent, functionCallStatement.position)
|
||||||
}
|
}
|
||||||
@ -70,4 +60,62 @@ internal class VariousCleanups: AstWalker() {
|
|||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||||
|
if(typecast.parent!==parent)
|
||||||
|
throw FatalAstException("parent node mismatch at $typecast")
|
||||||
|
|
||||||
|
if(typecast.expression is NumericLiteralValue) {
|
||||||
|
val value = (typecast.expression as NumericLiteralValue).cast(typecast.type)
|
||||||
|
if(value.isValid)
|
||||||
|
return listOf(IAstModification.ReplaceNode(typecast, value.valueOrZero(), parent))
|
||||||
|
}
|
||||||
|
|
||||||
|
val sourceDt = typecast.expression.inferType(program)
|
||||||
|
if(sourceDt istype typecast.type)
|
||||||
|
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
|
||||||
|
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||||
|
if(subroutine.parent!==parent)
|
||||||
|
throw FatalAstException("parent node mismatch at $subroutine")
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||||
|
if(assignment.parent!==parent)
|
||||||
|
throw FatalAstException("parent node mismatch at $assignment")
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(assignTarget: AssignTarget, parent: Node): Iterable<IAstModification> {
|
||||||
|
if(assignTarget.parent!==parent)
|
||||||
|
throw FatalAstException("parent node mismatch at $assignTarget")
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||||
|
if(decl.parent!==parent)
|
||||||
|
throw FatalAstException("parent node mismatch at $decl")
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||||
|
if(scope.parent!==parent)
|
||||||
|
throw FatalAstException("parent node mismatch at $scope")
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> {
|
||||||
|
if(returnStmt.parent!==parent)
|
||||||
|
throw FatalAstException("parent node mismatch at $returnStmt")
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
|
||||||
|
if(identifier.parent!==parent)
|
||||||
|
throw FatalAstException("parent node mismatch at $identifier")
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
|
|||||||
val firstUnknownDt = argITypes.indexOfFirst { it.isUnknown }
|
val firstUnknownDt = argITypes.indexOfFirst { it.isUnknown }
|
||||||
if(firstUnknownDt>=0)
|
if(firstUnknownDt>=0)
|
||||||
return "argument ${firstUnknownDt+1} invalid argument type"
|
return "argument ${firstUnknownDt+1} invalid argument type"
|
||||||
val argtypes = argITypes.map { it.typeOrElse(DataType.STRUCT) }
|
val argtypes = argITypes.map { it.getOr(DataType.UNDEFINED) }
|
||||||
val target = call.target.targetStatement(program)
|
val target = call.target.targetStatement(program)
|
||||||
if (target is Subroutine) {
|
if (target is Subroutine) {
|
||||||
if(call.args.size != target.parameters.size)
|
if(call.args.size != target.parameters.size)
|
||||||
|
@ -4,13 +4,12 @@ import prog8.ast.IMemSizer
|
|||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.statements.StructDecl
|
|
||||||
import prog8.ast.statements.VarDecl
|
import prog8.ast.statements.VarDecl
|
||||||
import prog8.compiler.CompilerException
|
import prog8.compiler.CompilerException
|
||||||
import kotlin.math.*
|
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
|
typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer) -> NumericLiteralValue
|
||||||
@ -91,58 +90,59 @@ class FSignature(val name: String,
|
|||||||
@Suppress("UNUSED_ANONYMOUS_PARAMETER")
|
@Suppress("UNUSED_ANONYMOUS_PARAMETER")
|
||||||
private val functionSignatures: List<FSignature> = listOf(
|
private val functionSignatures: List<FSignature> = listOf(
|
||||||
// this set of function have no return value and operate in-place:
|
// 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("rol" , false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||||
FSignature("ror" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
FSignature("ror" , false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||||
FSignature("rol2" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
FSignature("rol2" , false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||||
FSignature("ror2" , false, listOf(FParam("item", setOf(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("sort" , false, listOf(FParam("array", ArrayDatatypes)), null),
|
||||||
FSignature("reverse" , 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),
|
||||||
// these few have a return value depending on the argument(s):
|
// these few have a return value depending on the argument(s):
|
||||||
FSignature("max" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinMax) }, // type depends on args
|
FSignature("max" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinMax) }, // type depends on args
|
||||||
FSignature("min" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinMin) }, // type depends on args
|
FSignature("min" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinMin) }, // type depends on args
|
||||||
FSignature("sum" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args
|
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("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("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),
|
||||||
FSignature("offsetof" , true, listOf(FParam("object", DataType.values().toSet())), DataType.UBYTE, ::builtinOffsetof),
|
|
||||||
// normal functions follow:
|
// normal functions follow:
|
||||||
FSignature("sgn" , true, listOf(FParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
|
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("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", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
|
FSignature("sin8" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
|
||||||
FSignature("sin8u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
|
FSignature("sin8u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
|
||||||
FSignature("sin16" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ),
|
FSignature("sin16" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ),
|
||||||
FSignature("sin16u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ),
|
FSignature("sin16u" , true, listOf(FParam("angle8", arrayOf(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("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", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ),
|
FSignature("cos8" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ),
|
||||||
FSignature("cos8u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ),
|
FSignature("cos8u" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ),
|
||||||
FSignature("cos16" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ),
|
FSignature("cos16" , true, listOf(FParam("angle8", arrayOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ),
|
||||||
FSignature("cos16u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ),
|
FSignature("cos16u" , true, listOf(FParam("angle8", arrayOf(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("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", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::atan) },
|
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", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::log) },
|
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", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, ::log2) },
|
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", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } },
|
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", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::sqrt) },
|
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", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::toRadians) },
|
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", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::toDegrees) },
|
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", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
|
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", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
|
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", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
|
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("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("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("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", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 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", setOf(DataType.UBYTE)), FParam("lsb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
|
FSignature("mkword" , true, listOf(FParam("msb", arrayOf(DataType.UBYTE)), FParam("lsb", arrayOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
|
||||||
FSignature("peek" , true, listOf(FParam("address", setOf(DataType.UWORD))), DataType.UBYTE),
|
FSignature("peek" , true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UBYTE),
|
||||||
FSignature("peekw" , true, listOf(FParam("address", setOf(DataType.UWORD))), DataType.UWORD),
|
FSignature("peekw" , true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UWORD),
|
||||||
FSignature("poke" , false, listOf(FParam("address", setOf(DataType.UWORD)), FParam("value", setOf(DataType.UBYTE))), null),
|
FSignature("poke" , false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), null),
|
||||||
FSignature("pokew" , false, listOf(FParam("address", setOf(DataType.UWORD)), FParam("value", setOf(DataType.UWORD))), null),
|
FSignature("pokew" , false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UWORD))), null),
|
||||||
FSignature("fastrnd8" , false, emptyList(), DataType.UBYTE),
|
|
||||||
FSignature("rnd" , false, emptyList(), DataType.UBYTE),
|
FSignature("rnd" , false, emptyList(), DataType.UBYTE),
|
||||||
FSignature("rndw" , false, emptyList(), DataType.UWORD),
|
FSignature("rndw" , false, emptyList(), DataType.UWORD),
|
||||||
FSignature("rndf" , false, emptyList(), DataType.FLOAT),
|
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("swap" , false, listOf(FParam("first", NumericDatatypes), FParam("second", NumericDatatypes)), 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),
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -153,7 +153,7 @@ fun builtinMax(array: List<Number>): Number = array.maxByOrNull { it.toDouble()
|
|||||||
|
|
||||||
fun builtinMin(array: List<Number>): Number = array.minByOrNull { it.toDouble() }!!
|
fun builtinMin(array: List<Number>): Number = array.minByOrNull { it.toDouble() }!!
|
||||||
|
|
||||||
fun builtinSum(array: List<Number>): Number = array.sumByDouble { it.toDouble() }
|
fun builtinSum(array: List<Number>): Number = array.sumOf { it.toDouble() }
|
||||||
|
|
||||||
fun builtinAny(array: List<Number>): Number = if(array.any { it.toDouble()!=0.0 }) 1 else 0
|
fun builtinAny(array: List<Number>): Number = if(array.any { it.toDouble()!=0.0 }) 1 else 0
|
||||||
|
|
||||||
@ -164,7 +164,7 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
|
|||||||
|
|
||||||
fun datatypeFromIterableArg(arglist: Expression): DataType {
|
fun datatypeFromIterableArg(arglist: Expression): DataType {
|
||||||
if(arglist is ArrayLiteralValue) {
|
if(arglist is ArrayLiteralValue) {
|
||||||
val dt = arglist.value.map {it.inferType(program).typeOrElse(DataType.STRUCT)}.toSet()
|
val dt = arglist.value.map {it.inferType(program).getOr(DataType.UNDEFINED)}.toSet()
|
||||||
if(dt.any { it !in NumericDatatypes }) {
|
if(dt.any { it !in NumericDatatypes }) {
|
||||||
throw FatalAstException("fuction $function only accepts array of numeric values")
|
throw FatalAstException("fuction $function only accepts array of numeric values")
|
||||||
}
|
}
|
||||||
@ -178,9 +178,9 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
|
|||||||
val idt = arglist.inferType(program)
|
val idt = arglist.inferType(program)
|
||||||
if(!idt.isKnown)
|
if(!idt.isKnown)
|
||||||
throw FatalAstException("couldn't determine type of iterable $arglist")
|
throw FatalAstException("couldn't determine type of iterable $arglist")
|
||||||
return when(val dt = idt.typeOrElse(DataType.STRUCT)) {
|
return when(val dt = idt.getOr(DataType.UNDEFINED)) {
|
||||||
DataType.STR, in NumericDatatypes -> dt
|
DataType.STR, in NumericDatatypes -> dt
|
||||||
in ArrayDatatypes -> ArrayElementTypes.getValue(dt)
|
in ArrayDatatypes -> ArrayToElementTypes.getValue(dt)
|
||||||
else -> throw FatalAstException("function '$function' requires one argument which is an iterable")
|
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) {
|
return when (function) {
|
||||||
"abs" -> {
|
"abs" -> {
|
||||||
val dt = args.single().inferType(program)
|
val dt = args.single().inferType(program)
|
||||||
return if(dt.typeOrElse(DataType.STRUCT) in NumericDatatypes)
|
return if(dt.isNumeric)
|
||||||
dt
|
dt
|
||||||
else
|
else
|
||||||
InferredTypes.InferredType.unknown()
|
InferredTypes.InferredType.unknown()
|
||||||
@ -204,7 +204,7 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
|
|||||||
when(val dt = datatypeFromIterableArg(args.single())) {
|
when(val dt = datatypeFromIterableArg(args.single())) {
|
||||||
DataType.STR -> InferredTypes.knownFor(DataType.UBYTE)
|
DataType.STR -> InferredTypes.knownFor(DataType.UBYTE)
|
||||||
in NumericDatatypes -> InferredTypes.knownFor(dt)
|
in NumericDatatypes -> InferredTypes.knownFor(dt)
|
||||||
in ArrayDatatypes -> InferredTypes.knownFor(ArrayElementTypes.getValue(dt))
|
in ArrayDatatypes -> InferredTypes.knownFor(ArrayToElementTypes.getValue(dt))
|
||||||
else -> InferredTypes.unknown()
|
else -> InferredTypes.unknown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -287,28 +287,6 @@ private fun builtinAbs(args: List<Expression>, position: Position, program: Prog
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun builtinOffsetof(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
|
|
||||||
// 1 arg, type = anything, result type = ubyte
|
|
||||||
if(args.size!=1)
|
|
||||||
throw SyntaxError("offsetof requires one argument", position)
|
|
||||||
val idref = args[0] as? IdentifierReference
|
|
||||||
?: throw SyntaxError("offsetof argument should be an identifier", position)
|
|
||||||
|
|
||||||
val vardecl = idref.targetVarDecl(program)!!
|
|
||||||
val struct = vardecl.struct
|
|
||||||
if (struct == null || vardecl.datatype == DataType.STRUCT)
|
|
||||||
throw SyntaxError("offsetof can only be used on struct members", position)
|
|
||||||
|
|
||||||
val membername = idref.nameInSource.last()
|
|
||||||
var offset = 0
|
|
||||||
for(member in struct.statements) {
|
|
||||||
if((member as VarDecl).name == membername)
|
|
||||||
return NumericLiteralValue(DataType.UBYTE, offset, position)
|
|
||||||
offset += memsizer.memorySize(member.datatype)
|
|
||||||
}
|
|
||||||
throw SyntaxError("undefined struct member", position)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun builtinSizeof(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
|
private fun builtinSizeof(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
|
||||||
// 1 arg, type = anything, result type = ubyte
|
// 1 arg, type = anything, result type = ubyte
|
||||||
if(args.size!=1)
|
if(args.size!=1)
|
||||||
@ -321,23 +299,14 @@ private fun builtinSizeof(args: List<Expression>, position: Position, program: P
|
|||||||
val target = (args[0] as IdentifierReference).targetStatement(program)
|
val target = (args[0] as IdentifierReference).targetStatement(program)
|
||||||
?: throw CannotEvaluateException("sizeof", "no target")
|
?: throw CannotEvaluateException("sizeof", "no target")
|
||||||
|
|
||||||
fun structSize(target: StructDecl) = NumericLiteralValue(DataType.UBYTE, target.memsize(memsizer), position)
|
|
||||||
|
|
||||||
return when {
|
return when {
|
||||||
dt.typeOrElse(DataType.STRUCT) in ArrayDatatypes -> {
|
dt.isArray -> {
|
||||||
val length = (target as VarDecl).arraysize!!.constIndex() ?: throw CannotEvaluateException("sizeof", "unknown array size")
|
val length = (target as VarDecl).arraysize!!.constIndex() ?: throw CannotEvaluateException("sizeof", "unknown array size")
|
||||||
val elementDt = ArrayElementTypes.getValue(dt.typeOrElse(DataType.STRUCT))
|
val elementDt = ArrayToElementTypes.getValue(dt.getOr(DataType.UNDEFINED))
|
||||||
numericLiteral(memsizer.memorySize(elementDt) * length, position)
|
numericLiteral(memsizer.memorySize(elementDt) * length, position)
|
||||||
}
|
}
|
||||||
dt.istype(DataType.STRUCT) -> {
|
dt istype DataType.STR -> throw SyntaxError("sizeof str is undefined, did you mean len?", position)
|
||||||
when (target) {
|
else -> NumericLiteralValue(DataType.UBYTE, memsizer.memorySize(dt.getOr(DataType.UNDEFINED)), position)
|
||||||
is VarDecl -> structSize(target.struct!!)
|
|
||||||
is StructDecl -> structSize(target)
|
|
||||||
else -> throw CompilerException("weird struct type $target")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dt.istype(DataType.STR) -> throw SyntaxError("sizeof str is undefined, did you mean len?", position)
|
|
||||||
else -> NumericLiteralValue(DataType.UBYTE, memsizer.memorySize(dt.typeOrElse(DataType.STRUCT)), position)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw SyntaxError("sizeof invalid argument type", position)
|
throw SyntaxError("sizeof invalid argument type", position)
|
||||||
@ -346,7 +315,7 @@ private fun builtinSizeof(args: List<Expression>, position: Position, program: P
|
|||||||
|
|
||||||
@Suppress("UNUSED_PARAMETER")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
private fun builtinLen(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
|
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)
|
if(args.size!=1)
|
||||||
throw SyntaxError("len requires one argument", position)
|
throw SyntaxError("len requires one argument", position)
|
||||||
|
|
||||||
@ -372,7 +341,6 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
|
|||||||
val refLv = target.value as? StringLiteralValue ?: throw CannotEvaluateException("len", "stringsize unknown")
|
val refLv = target.value as? StringLiteralValue ?: throw CannotEvaluateException("len", "stringsize unknown")
|
||||||
NumericLiteralValue.optimalInteger(refLv.value.length, args[0].position)
|
NumericLiteralValue.optimalInteger(refLv.value.length, args[0].position)
|
||||||
}
|
}
|
||||||
DataType.STRUCT -> throw SyntaxError("cannot use len on struct, did you mean sizeof?", args[0].position)
|
|
||||||
in NumericDatatypes -> throw SyntaxError("cannot use len on numeric value, did you mean sizeof?", args[0].position)
|
in NumericDatatypes -> throw SyntaxError("cannot use len on numeric value, did you mean sizeof?", args[0].position)
|
||||||
else -> throw CompilerException("weird datatype")
|
else -> throw CompilerException("weird datatype")
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ internal const val subroutineFloatEvalResultVar1 = "_prog8_float_eval_result1"
|
|||||||
internal const val subroutineFloatEvalResultVar2 = "_prog8_float_eval_result2"
|
internal const val subroutineFloatEvalResultVar2 = "_prog8_float_eval_result2"
|
||||||
|
|
||||||
internal interface IAssemblyProgram {
|
internal interface IAssemblyProgram {
|
||||||
|
val valid: Boolean
|
||||||
val name: String
|
val name: String
|
||||||
fun assemble(options: CompilationOptions)
|
fun assemble(options: CompilationOptions): Int
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package prog8.compiler.target
|
package prog8.compiler.target
|
||||||
|
|
||||||
|
import com.github.michaelbull.result.fold
|
||||||
import prog8.ast.IMemSizer
|
import prog8.ast.IMemSizer
|
||||||
import prog8.ast.IStringEncoding
|
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
import prog8.ast.expressions.IdentifierReference
|
import prog8.ast.expressions.IdentifierReference
|
||||||
@ -9,11 +9,13 @@ import prog8.ast.expressions.NumericLiteralValue
|
|||||||
import prog8.ast.statements.AssignTarget
|
import prog8.ast.statements.AssignTarget
|
||||||
import prog8.compiler.CompilationOptions
|
import prog8.compiler.CompilationOptions
|
||||||
import prog8.compiler.IErrorReporter
|
import prog8.compiler.IErrorReporter
|
||||||
|
import prog8.compiler.IStringEncoding
|
||||||
import prog8.compiler.Zeropage
|
import prog8.compiler.Zeropage
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition
|
import prog8.compiler.target.c64.C64MachineDefinition
|
||||||
import prog8.compiler.target.c64.Petscii
|
import prog8.compiler.target.cbm.Petscii
|
||||||
import prog8.compiler.target.cpu6502.codegen.AsmGen
|
import prog8.compiler.target.cpu6502.codegen.AsmGen
|
||||||
import prog8.compiler.target.cx16.CX16MachineDefinition
|
import prog8.compiler.target.cx16.CX16MachineDefinition
|
||||||
|
import java.io.CharConversionException
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
|
||||||
@ -22,57 +24,25 @@ interface ICompilationTarget: IStringEncoding, IMemSizer {
|
|||||||
val machine: IMachineDefinition
|
val machine: IMachineDefinition
|
||||||
override fun encodeString(str: String, altEncoding: Boolean): List<Short>
|
override fun encodeString(str: String, altEncoding: Boolean): List<Short>
|
||||||
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
|
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 {
|
internal object C64Target: ICompilationTarget {
|
||||||
override val name = "c64"
|
override val name = "c64"
|
||||||
override val machine = C64MachineDefinition
|
override val machine = C64MachineDefinition
|
||||||
override fun encodeString(str: String, altEncoding: Boolean) =
|
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
|
||||||
if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
val coded = if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||||
|
return coded.fold(
|
||||||
|
failure = { throw it },
|
||||||
|
success = { it }
|
||||||
|
)
|
||||||
|
}
|
||||||
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
|
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
|
||||||
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
try {
|
||||||
|
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||||
|
} catch (x: CharConversionException) {
|
||||||
|
throw CharConversionException("can't decode string: ${x.message}")
|
||||||
|
}
|
||||||
|
|
||||||
override fun memorySize(dt: DataType): Int {
|
override fun memorySize(dt: DataType): Int {
|
||||||
return when(dt) {
|
return when(dt) {
|
||||||
@ -88,10 +58,19 @@ internal object C64Target: ICompilationTarget {
|
|||||||
internal object Cx16Target: ICompilationTarget {
|
internal object Cx16Target: ICompilationTarget {
|
||||||
override val name = "cx16"
|
override val name = "cx16"
|
||||||
override val machine = CX16MachineDefinition
|
override val machine = CX16MachineDefinition
|
||||||
override fun encodeString(str: String, altEncoding: Boolean) =
|
override fun encodeString(str: String, altEncoding: Boolean): List<Short> {
|
||||||
if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
val coded= if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||||
|
return coded.fold(
|
||||||
|
failure = { throw it },
|
||||||
|
success = { it }
|
||||||
|
)
|
||||||
|
}
|
||||||
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
|
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
|
||||||
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
try {
|
||||||
|
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||||
|
} catch (x: CharConversionException) {
|
||||||
|
throw CharConversionException("can't decode string: ${x.message}")
|
||||||
|
}
|
||||||
|
|
||||||
override fun memorySize(dt: DataType): Int {
|
override fun memorySize(dt: DataType): Int {
|
||||||
return when(dt) {
|
return when(dt) {
|
||||||
|
@ -2,6 +2,7 @@ package prog8.compiler.target
|
|||||||
|
|
||||||
import prog8.compiler.CompilationOptions
|
import prog8.compiler.CompilationOptions
|
||||||
import prog8.compiler.Zeropage
|
import prog8.compiler.Zeropage
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
|
||||||
interface IMachineFloat {
|
interface IMachineFloat {
|
||||||
@ -32,6 +33,6 @@ interface IMachineDefinition {
|
|||||||
fun getFloat(num: Number): IMachineFloat
|
fun getFloat(num: Number): IMachineFloat
|
||||||
|
|
||||||
fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String>
|
fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String>
|
||||||
fun launchEmulator(programName: String)
|
fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path)
|
||||||
fun isRegularRAMaddress(address: Int): Boolean
|
fun isRegularRAMaddress(address: Int): Boolean
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,9 @@ import prog8.compiler.*
|
|||||||
import prog8.compiler.target.CpuType
|
import prog8.compiler.target.CpuType
|
||||||
import prog8.compiler.target.IMachineDefinition
|
import prog8.compiler.target.IMachineDefinition
|
||||||
import prog8.compiler.target.IMachineFloat
|
import prog8.compiler.target.IMachineFloat
|
||||||
|
import prog8.compiler.target.cbm.viceMonListPostfix
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import java.nio.file.Path
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
|
|
||||||
@ -35,11 +37,16 @@ internal object C64MachineDefinition: IMachineDefinition {
|
|||||||
emptyList()
|
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")) {
|
for(emulator in listOf("x64sc", "x64")) {
|
||||||
println("\nStarting C-64 emulator $emulator...")
|
println("\nStarting C-64 emulator $emulator...")
|
||||||
val cmdline = listOf(emulator, "-silent", "-moncommands", "$programName.vice-mon-list",
|
val cmdline = listOf(emulator, "-silent", "-moncommands", "${programNameWithPath}.$viceMonListPostfix",
|
||||||
"-autostartprgmode", "1", "-autostart-warp", "-autostart", programName + ".prg")
|
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg")
|
||||||
val processb = ProcessBuilder(cmdline).inheritIO()
|
val processb = ProcessBuilder(cmdline).inheritIO()
|
||||||
val process: Process
|
val process: Process
|
||||||
try {
|
try {
|
||||||
@ -79,7 +86,7 @@ internal object C64MachineDefinition: IMachineDefinition {
|
|||||||
|
|
||||||
|
|
||||||
init {
|
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'")
|
throw CompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
|
||||||
|
|
||||||
if (options.zeropage == ZeropageType.FULL) {
|
if (options.zeropage == ZeropageType.FULL) {
|
||||||
@ -107,8 +114,9 @@ internal object C64MachineDefinition: IMachineDefinition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (options.zeropage == ZeropageType.FLOATSAFE) {
|
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(
|
free.removeAll(listOf(
|
||||||
|
0x22, 0x23, 0x24, 0x25,
|
||||||
0x10, 0x11, 0x12, 0x26, 0x27, 0x28, 0x29, 0x2a,
|
0x10, 0x11, 0x12, 0x26, 0x27, 0x28, 0x29, 0x2a,
|
||||||
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
|
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
|
||||||
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,19 +1,26 @@
|
|||||||
package prog8.compiler.target.c64
|
package prog8.compiler.target.cbm
|
||||||
|
|
||||||
import prog8.compiler.CompilationOptions
|
import prog8.compiler.CompilationOptions
|
||||||
import prog8.compiler.OutputType
|
import prog8.compiler.OutputType
|
||||||
import prog8.compiler.target.IAssemblyProgram
|
import prog8.compiler.target.IAssemblyProgram
|
||||||
import prog8.compiler.target.generatedLabelPrefix
|
import prog8.compiler.target.generatedLabelPrefix
|
||||||
import java.nio.file.Path
|
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 assemblyFile = outputDir.resolve("$name.asm")
|
||||||
private val prgFile = outputDir.resolve("$name.prg")
|
private val prgFile = outputDir.resolve("$name.prg")
|
||||||
private val binFile = outputDir.resolve("$name.bin")
|
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)
|
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps (default = do this silently)
|
||||||
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
|
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
|
||||||
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
|
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
|
||||||
@ -35,13 +42,11 @@ class AssemblyProgram(override val name: String, outputDir: Path, private val co
|
|||||||
|
|
||||||
val proc = ProcessBuilder(command).inheritIO().start()
|
val proc = ProcessBuilder(command).inheritIO().start()
|
||||||
val result = proc.waitFor()
|
val result = proc.waitFor()
|
||||||
if (result != 0) {
|
if (result == 0) {
|
||||||
System.err.println("assembler failed with returncode $result")
|
removeGeneratedLabelsFromMonlist()
|
||||||
exitProcess(result)
|
generateBreakpointList()
|
||||||
}
|
}
|
||||||
|
return result
|
||||||
removeGeneratedLabelsFromMonlist()
|
|
||||||
generateBreakpointList()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun removeGeneratedLabelsFromMonlist() {
|
private fun removeGeneratedLabelsFromMonlist() {
|
1184
compiler/src/prog8/compiler/target/cbm/Petscii.kt
Normal file
1184
compiler/src/prog8/compiler/target/cbm/Petscii.kt
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,6 @@
|
|||||||
package prog8.compiler.target.cpu6502.codegen
|
package prog8.compiler.target.cpu6502.codegen
|
||||||
|
|
||||||
|
import com.github.michaelbull.result.*
|
||||||
import prog8.ast.*
|
import prog8.ast.*
|
||||||
import prog8.ast.antlr.escape
|
import prog8.ast.antlr.escape
|
||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
@ -9,16 +10,17 @@ import prog8.compiler.*
|
|||||||
import prog8.compiler.functions.BuiltinFunctions
|
import prog8.compiler.functions.BuiltinFunctions
|
||||||
import prog8.compiler.functions.FSignature
|
import prog8.compiler.functions.FSignature
|
||||||
import prog8.compiler.target.*
|
import prog8.compiler.target.*
|
||||||
import prog8.compiler.target.c64.AssemblyProgram
|
import prog8.compiler.target.cbm.AssemblyProgram
|
||||||
import prog8.compiler.target.c64.Petscii
|
|
||||||
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignment
|
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignment
|
||||||
import prog8.compiler.target.cpu6502.codegen.assignment.AssignmentAsmGen
|
import prog8.compiler.target.cpu6502.codegen.assignment.AssignmentAsmGen
|
||||||
import java.io.CharConversionException
|
import prog8.optimizer.CallGraph
|
||||||
|
import prog8.parser.SourceCode
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.nio.file.Paths
|
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import kotlin.io.path.Path
|
||||||
|
import kotlin.io.path.absolute
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
|
|
||||||
@ -31,7 +33,8 @@ internal class AsmGen(private val program: Program,
|
|||||||
|
|
||||||
// for expressions and augmented assignments:
|
// for expressions and augmented assignments:
|
||||||
val optimizedByteMultiplications = setOf(3,5,6,7,9,10,11,12,13,14,15,20,25,40,50,80,100)
|
val optimizedByteMultiplications = setOf(3,5,6,7,9,10,11,12,13,14,15,20,25,40,50,80,100)
|
||||||
val optimizedWordMultiplications = setOf(3,5,6,7,9,10,12,15,20,25,40,50,80,100,320)
|
val optimizedWordMultiplications = setOf(3,5,6,7,9,10,12,15,20,25,40,50,80,100,320,640)
|
||||||
|
private val callGraph = CallGraph(program)
|
||||||
|
|
||||||
private val assemblyLines = mutableListOf<String>()
|
private val assemblyLines = mutableListOf<String>()
|
||||||
private val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
|
private val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
|
||||||
@ -55,10 +58,10 @@ internal class AsmGen(private val program: Program,
|
|||||||
println("Generating assembly code... ")
|
println("Generating assembly code... ")
|
||||||
|
|
||||||
header()
|
header()
|
||||||
val allBlocks = program.allBlocks()
|
val allBlocks = program.allBlocks
|
||||||
if(allBlocks.first().name != "main")
|
if(allBlocks.first().name != "main")
|
||||||
throw AssemblyError("first block should be 'main'")
|
throw AssemblyError("first block should be 'main'")
|
||||||
for(b in program.allBlocks())
|
for(b in program.allBlocks)
|
||||||
block2asm(b)
|
block2asm(b)
|
||||||
|
|
||||||
for(removal in removals.toList()) {
|
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
|
internal fun isTargetCpu(cpu: CpuType) = compTarget.machine.cpu == cpu
|
||||||
@ -128,7 +135,7 @@ internal class AsmGen(private val program: Program,
|
|||||||
out("* = ${program.actualLoadAddress.toHex()}")
|
out("* = ${program.actualLoadAddress.toHex()}")
|
||||||
val year = LocalDate.now().year
|
val year = LocalDate.now().year
|
||||||
out(" .word (+), $year")
|
out(" .word (+), $year")
|
||||||
out(" .null $9e, format(' %d ', _prog8_entrypoint), $3a, $8f, ' prog8 by idj'")
|
out(" .null $9e, format(' %d ', _prog8_entrypoint), $3a, $8f, ' prog8'")
|
||||||
out("+\t.word 0")
|
out("+\t.word 0")
|
||||||
out("_prog8_entrypoint\t; assembly code starts here\n")
|
out("_prog8_entrypoint\t; assembly code starts here\n")
|
||||||
if(!options.noSysInit)
|
if(!options.noSysInit)
|
||||||
@ -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("""
|
out("""
|
||||||
; zeropage is clobbered so we need to reset the machine at exit
|
; zeropage is clobbered so we need to reset the machine at exit
|
||||||
lda #>sys.reset_system
|
lda #>sys.reset_system
|
||||||
@ -157,7 +164,16 @@ internal class AsmGen(private val program: Program,
|
|||||||
pha""")
|
pha""")
|
||||||
}
|
}
|
||||||
|
|
||||||
jmp("main.start")
|
// make sure that on the cx16 and c64, basic rom is banked in again when we exit the program
|
||||||
|
when(compTarget.name) {
|
||||||
|
Cx16Target.name -> {
|
||||||
|
if(options.floats)
|
||||||
|
out(" lda #4 | sta $01") // to use floats, make sure Basic rom is banked in
|
||||||
|
out(" jsr main.start | lda #4 | sta $01 | rts")
|
||||||
|
}
|
||||||
|
C64Target.name -> out(" jsr main.start | lda #31 | sta $01 | rts")
|
||||||
|
else -> jmp("main.start")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun slaballocations() {
|
private fun slaballocations() {
|
||||||
@ -220,7 +236,7 @@ internal class AsmGen(private val program: Program,
|
|||||||
|
|
||||||
private fun assignInitialValueToVar(decl: VarDecl, variableName: List<String>) {
|
private fun assignInitialValueToVar(decl: VarDecl, variableName: List<String>) {
|
||||||
val asmName = asmVariableName(variableName)
|
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
|
private var generatedLabelSequenceNumber: Int = 0
|
||||||
@ -246,15 +262,6 @@ internal class AsmGen(private val program: Program,
|
|||||||
} else assemblyLines.add(fragment)
|
} else assemblyLines.add(fragment)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun encode(str: String, altEncoding: Boolean): List<Short> {
|
|
||||||
try {
|
|
||||||
val bytes = if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
|
||||||
return bytes.plus(0)
|
|
||||||
} catch(x: CharConversionException) {
|
|
||||||
throw AssemblyError("There was a problem converting a string to the target machine's char encoding: ${x.message}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun zeropagevars2asm(statements: List<Statement>) {
|
private fun zeropagevars2asm(statements: List<Statement>) {
|
||||||
out("; vars allocated on zeropage")
|
out("; vars allocated on zeropage")
|
||||||
val variables = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.VAR }
|
val variables = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.VAR }
|
||||||
@ -290,10 +297,9 @@ internal class AsmGen(private val program: Program,
|
|||||||
DataType.UWORD -> out("$name\t.word 0")
|
DataType.UWORD -> out("$name\t.word 0")
|
||||||
DataType.WORD -> out("$name\t.sint 0")
|
DataType.WORD -> out("$name\t.sint 0")
|
||||||
DataType.FLOAT -> out("$name\t.byte 0,0,0,0,0 ; float")
|
DataType.FLOAT -> out("$name\t.byte 0,0,0,0,0 ; float")
|
||||||
DataType.STRUCT -> {} // is flattened
|
|
||||||
DataType.STR -> {
|
DataType.STR -> {
|
||||||
val str = decl.value as StringLiteralValue
|
val str = decl.value as StringLiteralValue
|
||||||
outputStringvar(decl, encode(str.value, str.altEncoding))
|
outputStringvar(decl, compTarget.encodeString(str.value, str.altEncoding).plus(0))
|
||||||
}
|
}
|
||||||
DataType.ARRAY_UB -> {
|
DataType.ARRAY_UB -> {
|
||||||
val data = makeArrayFillDataUnsigned(decl)
|
val data = makeArrayFillDataUnsigned(decl)
|
||||||
@ -352,6 +358,9 @@ internal class AsmGen(private val program: Program,
|
|||||||
for (f in array.zip(floatFills))
|
for (f in array.zip(floatFills))
|
||||||
out(" .byte ${f.second} ; float ${f.first}")
|
out(" .byte ${f.second} ; float ${f.first}")
|
||||||
}
|
}
|
||||||
|
else -> {
|
||||||
|
throw AssemblyError("weird dt")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -379,38 +388,28 @@ internal class AsmGen(private val program: Program,
|
|||||||
out("\n; non-zeropage variables")
|
out("\n; non-zeropage variables")
|
||||||
val vars = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.VAR }
|
val vars = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.VAR }
|
||||||
|
|
||||||
// first output the flattened struct member variables *in order*
|
val encodedstringVars = vars
|
||||||
// after that, the other variables sorted by their datatype
|
|
||||||
|
|
||||||
val (structMembers, normalVars) = vars.partition { it.struct!=null }
|
|
||||||
structMembers.forEach { vardecl2asm(it) }
|
|
||||||
|
|
||||||
// special treatment for string types: merge strings that are identical
|
|
||||||
val encodedstringVars = normalVars
|
|
||||||
.filter {it.datatype == DataType.STR }
|
.filter {it.datatype == DataType.STR }
|
||||||
.map {
|
.map {
|
||||||
val str = it.value as StringLiteralValue
|
val str = it.value as StringLiteralValue
|
||||||
it to encode(str.value, str.altEncoding)
|
it to compTarget.encodeString(str.value, str.altEncoding).plus(0)
|
||||||
}
|
}
|
||||||
.groupBy({it.second}, {it.first})
|
for((decl, variables) in encodedstringVars) {
|
||||||
for((encoded, variables) in encodedstringVars) {
|
outputStringvar(decl, variables)
|
||||||
variables.dropLast(1).forEach { out(it.name) }
|
|
||||||
val lastvar = variables.last()
|
|
||||||
outputStringvar(lastvar, encoded)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// non-string variables
|
// non-string variables
|
||||||
normalVars.filter{ it.datatype != DataType.STR }.sortedBy { it.datatype }.forEach {
|
vars.filter{ it.datatype != DataType.STR }.sortedBy { it.datatype }.forEach {
|
||||||
if(it.makeScopedName(it.name) !in allocatedZeropageVariables)
|
if(it.makeScopedName(it.name) !in allocatedZeropageVariables)
|
||||||
vardecl2asm(it)
|
vardecl2asm(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun outputStringvar(lastvar: VarDecl, encoded: List<Short>) {
|
private fun outputStringvar(strdecl: VarDecl, bytes: List<Short>) {
|
||||||
val sv = lastvar.value as StringLiteralValue
|
val sv = strdecl.value as StringLiteralValue
|
||||||
val altEncoding = if(sv.altEncoding) "@" else ""
|
val altEncoding = if(sv.altEncoding) "@" else ""
|
||||||
out("${lastvar.name}\t; ${lastvar.datatype} $altEncoding\"${escape(sv.value).replace("\u0000", "<NULL>")}\"")
|
out("${strdecl.name}\t; ${strdecl.datatype} $altEncoding\"${escape(sv.value).replace("\u0000", "<NULL>")}\"")
|
||||||
val outputBytes = encoded.map { "$" + it.toString(16).padStart(2, '0') }
|
val outputBytes = bytes.map { "$" + it.toString(16).padStart(2, '0') }
|
||||||
for (chunk in outputBytes.chunked(16))
|
for (chunk in outputBytes.chunked(16))
|
||||||
out(" .byte " + chunk.joinToString())
|
out(" .byte " + chunk.joinToString())
|
||||||
}
|
}
|
||||||
@ -437,10 +436,10 @@ internal class AsmGen(private val program: Program,
|
|||||||
"$" + it.number.toInt().toString(16).padStart(4, '0')
|
"$" + it.number.toInt().toString(16).padStart(4, '0')
|
||||||
}
|
}
|
||||||
is AddressOf -> {
|
is AddressOf -> {
|
||||||
it.identifier.firstStructVarName(program) ?: asmSymbolName(it.identifier)
|
asmSymbolName(it.identifier)
|
||||||
}
|
}
|
||||||
is IdentifierReference -> {
|
is IdentifierReference -> {
|
||||||
it.firstStructVarName(program) ?: asmSymbolName(it)
|
asmSymbolName(it)
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("weird array elt dt")
|
else -> throw AssemblyError("weird array elt dt")
|
||||||
}
|
}
|
||||||
@ -502,69 +501,107 @@ internal class AsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal fun asmSymbolName(identifier: IdentifierReference): String {
|
internal fun asmSymbolName(identifier: IdentifierReference): String {
|
||||||
return if(identifier.memberOfStruct(program)!=null) {
|
if(identifier.nameInSource.size==2 && identifier.nameInSource[0]=="prog8_slabs")
|
||||||
val name = identifier.targetVarDecl(program)!!.name
|
return identifier.nameInSource.joinToString(".")
|
||||||
fixNameSymbols(name)
|
|
||||||
|
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 {
|
} else {
|
||||||
fixNameSymbols(identifier.nameInSource.joinToString("."))
|
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 =
|
internal fun asmSymbolName(regs: RegisterOrPair): String =
|
||||||
if(regs in Cx16VirtualRegisters)
|
if (regs in Cx16VirtualRegisters)
|
||||||
"cx16." + regs.toString().toLowerCase()
|
"cx16." + regs.toString().lowercase()
|
||||||
else
|
else
|
||||||
throw AssemblyError("no symbol name for register $regs")
|
throw AssemblyError("no symbol name for register $regs")
|
||||||
|
|
||||||
internal fun asmVariableName(identifier: IdentifierReference): String {
|
|
||||||
return if(identifier.memberOfStruct(program)!=null) {
|
|
||||||
val name = identifier.targetVarDecl(program)!!.name
|
|
||||||
fixNameSymbols(name)
|
|
||||||
} else {
|
|
||||||
fixNameSymbols(identifier.nameInSource.joinToString("."))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun asmSymbolName(name: String) = fixNameSymbols(name)
|
internal fun asmSymbolName(name: String) = fixNameSymbols(name)
|
||||||
internal fun asmVariableName(name: String) = fixNameSymbols(name)
|
internal fun asmVariableName(name: String) = fixNameSymbols(name)
|
||||||
internal fun asmSymbolName(name: Iterable<String>) = fixNameSymbols(name.joinToString("."))
|
internal fun asmSymbolName(name: Iterable<String>) = fixNameSymbols(name.joinToString("."))
|
||||||
internal fun asmVariableName(name: Iterable<String>) = fixNameSymbols(name.joinToString("."))
|
internal fun asmVariableName(name: Iterable<String>) = fixNameSymbols(name.joinToString("."))
|
||||||
|
|
||||||
|
|
||||||
internal fun loadByteFromPointerIntoA(pointervar: IdentifierReference): Pair<Boolean, String> {
|
internal fun loadByteFromPointerIntoA(pointervar: IdentifierReference): String {
|
||||||
// returns if the pointer is already on the ZP itself or not (in the latter case SCRATCH_W1 is used as intermediary)
|
// returns the source name of the zero page pointervar if it's already in the ZP,
|
||||||
val sourceName = asmVariableName(pointervar)
|
// otherwise returns "P8ZP_SCRATCH_W1" which is the intermediary
|
||||||
val vardecl = pointervar.targetVarDecl(program)!!
|
when (val target = pointervar.targetStatement(program)) {
|
||||||
val scopedName = vardecl.makeScopedName(vardecl.name)
|
is Label -> {
|
||||||
if (isTargetCpu(CpuType.CPU65c02)) {
|
val sourceName = asmSymbolName(pointervar)
|
||||||
return if (isZpVar(scopedName)) {
|
out(" lda $sourceName")
|
||||||
// pointervar is already in the zero page, no need to copy
|
return sourceName
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
} else {
|
is VarDecl -> {
|
||||||
return if (isZpVar(scopedName)) {
|
val sourceName = asmVariableName(pointervar)
|
||||||
// pointervar is already in the zero page, no need to copy
|
val scopedName = target.makeScopedName(target.name)
|
||||||
out(" ldy #0 | lda ($sourceName),y")
|
if (isTargetCpu(CpuType.CPU65c02)) {
|
||||||
Pair(true, sourceName)
|
return if (isZpVar(scopedName)) {
|
||||||
} else {
|
// pointervar is already in the zero page, no need to copy
|
||||||
out("""
|
out(" lda ($sourceName)")
|
||||||
lda $sourceName
|
sourceName
|
||||||
ldy $sourceName+1
|
} else {
|
||||||
sta P8ZP_SCRATCH_W1
|
out("""
|
||||||
sty P8ZP_SCRATCH_W1+1
|
lda $sourceName
|
||||||
ldy #0
|
ldy $sourceName+1
|
||||||
lda (P8ZP_SCRATCH_W1),y""")
|
sta P8ZP_SCRATCH_W1
|
||||||
Pair(false, sourceName)
|
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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -671,7 +708,7 @@ internal class AsmGen(private val program: Program,
|
|||||||
when(stmt) {
|
when(stmt) {
|
||||||
is ParameterVarDecl -> { /* subroutine parameter vardecls don't get any special treatment here */ }
|
is ParameterVarDecl -> { /* subroutine parameter vardecls don't get any special treatment here */ }
|
||||||
is VarDecl -> translate(stmt)
|
is VarDecl -> translate(stmt)
|
||||||
is StructDecl, is NopStatement -> {}
|
is NopStatement -> {}
|
||||||
is Directive -> translate(stmt)
|
is Directive -> translate(stmt)
|
||||||
is Return -> translate(stmt)
|
is Return -> translate(stmt)
|
||||||
is Subroutine -> translateSubroutine(stmt)
|
is Subroutine -> translateSubroutine(stmt)
|
||||||
@ -708,48 +745,58 @@ internal class AsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun loadScaledArrayIndexIntoRegister(expr: ArrayIndexedExpression,
|
internal fun loadScaledArrayIndexIntoRegister(
|
||||||
elementDt: DataType,
|
expr: ArrayIndexedExpression,
|
||||||
register: CpuRegister,
|
elementDt: DataType,
|
||||||
addOneExtra: Boolean=false) {
|
register: CpuRegister,
|
||||||
val reg = register.toString().toLowerCase()
|
addOneExtra: Boolean = false
|
||||||
|
) {
|
||||||
|
val reg = register.toString().lowercase()
|
||||||
val indexnum = expr.indexer.constIndex()
|
val indexnum = expr.indexer.constIndex()
|
||||||
if(indexnum!=null) {
|
if (indexnum != null) {
|
||||||
val indexValue = indexnum * compTarget.memorySize(elementDt) + if(addOneExtra) 1 else 0
|
val indexValue = indexnum * compTarget.memorySize(elementDt) + if (addOneExtra) 1 else 0
|
||||||
out(" ld$reg #$indexValue")
|
out(" ld$reg #$indexValue")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val indexName = asmVariableName(expr.indexer.indexVar!!)
|
val indexVar = expr.indexer.indexExpr as? IdentifierReference
|
||||||
if(addOneExtra) {
|
?: throw AssemblyError("array indexer should have been replaced with a temp var @ ${expr.indexer.position}")
|
||||||
|
|
||||||
|
val indexName = asmVariableName(indexVar)
|
||||||
|
if (addOneExtra) {
|
||||||
// add 1 to the result
|
// add 1 to the result
|
||||||
when(elementDt) {
|
when (elementDt) {
|
||||||
in ByteDatatypes -> {
|
in ByteDatatypes -> {
|
||||||
out(" ldy $indexName | iny")
|
out(" ldy $indexName | iny")
|
||||||
when(register) {
|
when (register) {
|
||||||
CpuRegister.A -> out(" tya")
|
CpuRegister.A -> out(" tya")
|
||||||
CpuRegister.X -> out(" tyx")
|
CpuRegister.X -> out(" tyx")
|
||||||
CpuRegister.Y -> {}
|
CpuRegister.Y -> {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
in WordDatatypes -> {
|
in WordDatatypes -> {
|
||||||
out(" lda $indexName | sec | rol a")
|
out(" lda $indexName | sec | rol a")
|
||||||
when(register) {
|
when (register) {
|
||||||
CpuRegister.A -> {}
|
CpuRegister.A -> {
|
||||||
|
}
|
||||||
CpuRegister.X -> out(" tax")
|
CpuRegister.X -> out(" tax")
|
||||||
CpuRegister.Y -> out(" tay")
|
CpuRegister.Y -> out(" tay")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> {
|
DataType.FLOAT -> {
|
||||||
require(compTarget.memorySize(DataType.FLOAT)==5)
|
require(compTarget.memorySize(DataType.FLOAT) == 5)
|
||||||
out("""
|
out(
|
||||||
|
"""
|
||||||
lda $indexName
|
lda $indexName
|
||||||
asl a
|
asl a
|
||||||
asl a
|
asl a
|
||||||
sec
|
sec
|
||||||
adc $indexName""")
|
adc $indexName"""
|
||||||
when(register) {
|
)
|
||||||
CpuRegister.A -> {}
|
when (register) {
|
||||||
|
CpuRegister.A -> {
|
||||||
|
}
|
||||||
CpuRegister.X -> out(" tax")
|
CpuRegister.X -> out(" tax")
|
||||||
CpuRegister.Y -> out(" tay")
|
CpuRegister.Y -> out(" tay")
|
||||||
}
|
}
|
||||||
@ -757,26 +804,30 @@ internal class AsmGen(private val program: Program,
|
|||||||
else -> throw AssemblyError("weird dt")
|
else -> throw AssemblyError("weird dt")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
when(elementDt) {
|
when (elementDt) {
|
||||||
in ByteDatatypes -> out(" ld$reg $indexName")
|
in ByteDatatypes -> out(" ld$reg $indexName")
|
||||||
in WordDatatypes -> {
|
in WordDatatypes -> {
|
||||||
out(" lda $indexName | asl a")
|
out(" lda $indexName | asl a")
|
||||||
when(register) {
|
when (register) {
|
||||||
CpuRegister.A -> {}
|
CpuRegister.A -> {
|
||||||
|
}
|
||||||
CpuRegister.X -> out(" tax")
|
CpuRegister.X -> out(" tax")
|
||||||
CpuRegister.Y -> out(" tay")
|
CpuRegister.Y -> out(" tay")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> {
|
DataType.FLOAT -> {
|
||||||
require(compTarget.memorySize(DataType.FLOAT)==5)
|
require(compTarget.memorySize(DataType.FLOAT) == 5)
|
||||||
out("""
|
out(
|
||||||
|
"""
|
||||||
lda $indexName
|
lda $indexName
|
||||||
asl a
|
asl a
|
||||||
asl a
|
asl a
|
||||||
clc
|
clc
|
||||||
adc $indexName""")
|
adc $indexName"""
|
||||||
when(register) {
|
)
|
||||||
CpuRegister.A -> {}
|
when (register) {
|
||||||
|
CpuRegister.A -> {
|
||||||
|
}
|
||||||
CpuRegister.X -> out(" tax")
|
CpuRegister.X -> out(" tax")
|
||||||
CpuRegister.Y -> out(" tay")
|
CpuRegister.Y -> out(" tay")
|
||||||
}
|
}
|
||||||
@ -818,11 +869,20 @@ internal class AsmGen(private val program: Program,
|
|||||||
|
|
||||||
|
|
||||||
private fun translateSubroutine(sub: Subroutine) {
|
private fun translateSubroutine(sub: Subroutine) {
|
||||||
|
var onlyVariables = false
|
||||||
|
|
||||||
if(sub.inline) {
|
if(sub.inline) {
|
||||||
if(options.optimize)
|
if(options.optimize) {
|
||||||
return // inline subroutines don't exist anymore on their own
|
if(sub.isAsmSubroutine || callGraph.unused(sub))
|
||||||
|
return
|
||||||
|
|
||||||
|
// from an inlined subroutine only the local variables are generated,
|
||||||
|
// all other code statements are omitted in the subroutine itself
|
||||||
|
// (they've been inlined at the call site, remember?)
|
||||||
|
onlyVariables = true
|
||||||
|
}
|
||||||
else if(sub.amountOfRtsInAsm()==0) {
|
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))
|
sub.statements.add(Return(null, Position.DUMMY))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -837,7 +897,7 @@ internal class AsmGen(private val program: Program,
|
|||||||
|
|
||||||
// asmsub with most likely just an inline asm in it
|
// asmsub with most likely just an inline asm in it
|
||||||
out("${sub.name}\t.proc")
|
out("${sub.name}\t.proc")
|
||||||
sub.statements.forEach{ translate(it) }
|
sub.statements.forEach { translate(it) }
|
||||||
out(" .pend\n")
|
out(" .pend\n")
|
||||||
} else {
|
} else {
|
||||||
// regular subroutine
|
// regular subroutine
|
||||||
@ -846,10 +906,10 @@ internal class AsmGen(private val program: Program,
|
|||||||
memdefs2asm(sub.statements)
|
memdefs2asm(sub.statements)
|
||||||
|
|
||||||
// the main.start subroutine is the program's entrypoint and should perform some initialization logic
|
// 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("; program startup initialization")
|
||||||
out(" cld")
|
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})
|
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")
|
out(" jsr ${it.name}.prog8_init_vars")
|
||||||
}
|
}
|
||||||
@ -861,8 +921,10 @@ internal class AsmGen(private val program: Program,
|
|||||||
clc""")
|
clc""")
|
||||||
}
|
}
|
||||||
|
|
||||||
out("; statements")
|
if(!onlyVariables) {
|
||||||
sub.statements.forEach{ translate(it) }
|
out("; statements")
|
||||||
|
sub.statements.forEach { translate(it) }
|
||||||
|
}
|
||||||
|
|
||||||
for(removal in removals.toList()) {
|
for(removal in removals.toList()) {
|
||||||
if(removal.second==sub) {
|
if(removal.second==sub) {
|
||||||
@ -872,7 +934,15 @@ internal class AsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
out("; variables")
|
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)
|
if(sub.asmGenInfo.usedRegsaveA)
|
||||||
out("_prog8_regsaveA .byte 0")
|
out("_prog8_regsaveA .byte 0")
|
||||||
if(sub.asmGenInfo.usedRegsaveX)
|
if(sub.asmGenInfo.usedRegsaveX)
|
||||||
@ -880,9 +950,9 @@ internal class AsmGen(private val program: Program,
|
|||||||
if(sub.asmGenInfo.usedRegsaveY)
|
if(sub.asmGenInfo.usedRegsaveY)
|
||||||
out("_prog8_regsaveY .byte 0")
|
out("_prog8_regsaveY .byte 0")
|
||||||
if(sub.asmGenInfo.usedFloatEvalResultVar1)
|
if(sub.asmGenInfo.usedFloatEvalResultVar1)
|
||||||
out("$subroutineFloatEvalResultVar1 .byte 0,0,0,0,0")
|
out("$subroutineFloatEvalResultVar1 .byte 0,0,0,0,0")
|
||||||
if(sub.asmGenInfo.usedFloatEvalResultVar2)
|
if(sub.asmGenInfo.usedFloatEvalResultVar2)
|
||||||
out("$subroutineFloatEvalResultVar2 .byte 0,0,0,0,0")
|
out("$subroutineFloatEvalResultVar2 .byte 0,0,0,0,0")
|
||||||
vardecls2asm(sub.statements)
|
vardecls2asm(sub.statements)
|
||||||
out(" .pend\n")
|
out(" .pend\n")
|
||||||
}
|
}
|
||||||
@ -917,7 +987,11 @@ internal class AsmGen(private val program: Program,
|
|||||||
checkBooleanExpression(stmt.condition) // we require the condition to be of the form 'x <comparison> <value>'
|
checkBooleanExpression(stmt.condition) // we require the condition to be of the form 'x <comparison> <value>'
|
||||||
val booleanCondition = stmt.condition as BinaryExpression
|
val booleanCondition = stmt.condition as BinaryExpression
|
||||||
|
|
||||||
if (stmt.elsepart.containsNoCodeNorVars()) {
|
// DISABLED FOR NOW:
|
||||||
|
// if(!booleanCondition.left.isSimple || !booleanCondition.right.isSimple)
|
||||||
|
// throw AssemblyError("both operands for if comparison expression should have been simplified")
|
||||||
|
|
||||||
|
if (stmt.elsepart.containsNoCodeNorVars) {
|
||||||
// empty else
|
// empty else
|
||||||
val endLabel = makeLabel("if_end")
|
val endLabel = makeLabel("if_end")
|
||||||
expressionsAsmGen.translateComparisonExpressionWithJumpIfFalse(booleanCondition, endLabel)
|
expressionsAsmGen.translateComparisonExpressionWithJumpIfFalse(booleanCondition, endLabel)
|
||||||
@ -963,11 +1037,11 @@ internal class AsmGen(private val program: Program,
|
|||||||
iterations == 0 -> {}
|
iterations == 0 -> {}
|
||||||
iterations <= 256 -> {
|
iterations <= 256 -> {
|
||||||
out(" lda #${iterations and 255}")
|
out(" lda #${iterations and 255}")
|
||||||
repeatByteCountInA(iterations, repeatLabel, endLabel, stmt.body)
|
repeatByteCountInA(iterations, repeatLabel, endLabel, stmt)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
out(" lda #<${iterations} | ldy #>${iterations}")
|
out(" lda #<${iterations} | ldy #>${iterations}")
|
||||||
repeatWordCountInAY(iterations, repeatLabel, endLabel, stmt.body)
|
repeatWordCountInAY(iterations, repeatLabel, endLabel, stmt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -977,11 +1051,11 @@ internal class AsmGen(private val program: Program,
|
|||||||
when(vardecl.datatype) {
|
when(vardecl.datatype) {
|
||||||
DataType.UBYTE, DataType.BYTE -> {
|
DataType.UBYTE, DataType.BYTE -> {
|
||||||
assignVariableToRegister(name, RegisterOrPair.A)
|
assignVariableToRegister(name, RegisterOrPair.A)
|
||||||
repeatByteCountInA(null, repeatLabel, endLabel, stmt.body)
|
repeatByteCountInA(null, repeatLabel, endLabel, stmt)
|
||||||
}
|
}
|
||||||
DataType.UWORD, DataType.WORD -> {
|
DataType.UWORD, DataType.WORD -> {
|
||||||
assignVariableToRegister(name, RegisterOrPair.AY)
|
assignVariableToRegister(name, RegisterOrPair.AY)
|
||||||
repeatWordCountInAY(null, repeatLabel, endLabel, stmt.body)
|
repeatWordCountInAY(null, repeatLabel, endLabel, stmt)
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("invalid loop variable datatype $vardecl")
|
else -> throw AssemblyError("invalid loop variable datatype $vardecl")
|
||||||
}
|
}
|
||||||
@ -990,14 +1064,14 @@ internal class AsmGen(private val program: Program,
|
|||||||
val dt = stmt.iterations!!.inferType(program)
|
val dt = stmt.iterations!!.inferType(program)
|
||||||
if(!dt.isKnown)
|
if(!dt.isKnown)
|
||||||
throw AssemblyError("unknown dt")
|
throw AssemblyError("unknown dt")
|
||||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
when (dt.getOr(DataType.UNDEFINED)) {
|
||||||
in ByteDatatypes -> {
|
in ByteDatatypes -> {
|
||||||
assignExpressionToRegister(stmt.iterations!!, RegisterOrPair.A)
|
assignExpressionToRegister(stmt.iterations!!, RegisterOrPair.A)
|
||||||
repeatByteCountInA(null, repeatLabel, endLabel, stmt.body)
|
repeatByteCountInA(null, repeatLabel, endLabel, stmt)
|
||||||
}
|
}
|
||||||
in WordDatatypes -> {
|
in WordDatatypes -> {
|
||||||
assignExpressionToRegister(stmt.iterations!!, RegisterOrPair.AY)
|
assignExpressionToRegister(stmt.iterations!!, RegisterOrPair.AY)
|
||||||
repeatWordCountInAY(null, repeatLabel, endLabel, stmt.body)
|
repeatWordCountInAY(null, repeatLabel, endLabel, stmt)
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("invalid loop expression datatype $dt")
|
else -> throw AssemblyError("invalid loop expression datatype $dt")
|
||||||
}
|
}
|
||||||
@ -1007,13 +1081,13 @@ internal class AsmGen(private val program: Program,
|
|||||||
loopEndLabels.pop()
|
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!
|
// note: A/Y must have been loaded with the number of iterations!
|
||||||
if(constIterations==0)
|
if(constIterations==0)
|
||||||
return
|
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("""
|
out("""
|
||||||
sta $counterVar
|
sta $counterVar
|
||||||
sty $counterVar+1
|
sty $counterVar+1
|
||||||
@ -1026,44 +1100,67 @@ $repeatLabel lda $counterVar
|
|||||||
dec $counterVar+1
|
dec $counterVar+1
|
||||||
+ dec $counterVar
|
+ dec $counterVar
|
||||||
""")
|
""")
|
||||||
translate(body)
|
translate(stmt.body)
|
||||||
jmp(repeatLabel)
|
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)
|
out(endLabel)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun repeatByteCountInA(constIterations: Int?, repeatLabel: String, endLabel: String, body: AnonymousScope) {
|
private fun repeatByteCountInA(constIterations: Int?, repeatLabel: String, endLabel: String, stmt: RepeatLoop) {
|
||||||
// note: A must have been loaded with the number of iterations!
|
// note: A must be loaded with the number of iterations!
|
||||||
if(constIterations==0)
|
if(constIterations==0)
|
||||||
return
|
return
|
||||||
|
|
||||||
if(constIterations==null)
|
if(constIterations==null)
|
||||||
out(" beq $endLabel ; skip loop if zero iters")
|
out(" beq $endLabel ; skip loop if zero iters")
|
||||||
val counterVar = makeLabel("repeatcounter")
|
val counterVar = createRepeatCounterVar(DataType.UBYTE, constIterations, stmt)
|
||||||
out(" sta $counterVar")
|
out(" sta $counterVar")
|
||||||
out(repeatLabel)
|
out(repeatLabel)
|
||||||
translate(body)
|
translate(stmt.body)
|
||||||
out("""
|
out(" dec $counterVar | bne $repeatLabel")
|
||||||
dec $counterVar
|
if(constIterations==null)
|
||||||
bne $repeatLabel
|
out(endLabel)
|
||||||
beq $endLabel""")
|
}
|
||||||
|
|
||||||
if(constIterations!=null && constIterations>=16 && zeropage.available() > 0) {
|
private fun createRepeatCounterVar(dt: DataType, constIterations: Int?, stmt: RepeatLoop): String {
|
||||||
// allocate count var on ZP TODO can be shared with countervars from other subroutines
|
val asmInfo = stmt.definingSubroutine!!.asmGenInfo
|
||||||
val zpAddr = zeropage.allocate(counterVar, DataType.UBYTE, body.position, errors)
|
var parent = stmt.parent
|
||||||
out("$counterVar = $zpAddr ; auto zp UBYTE")
|
while(parent !is ParentSentinel) {
|
||||||
} else {
|
if(parent is RepeatLoop)
|
||||||
out("$counterVar .byte 0")
|
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) {
|
private fun translate(stmt: WhileLoop) {
|
||||||
@ -1099,7 +1196,7 @@ $repeatLabel lda $counterVar
|
|||||||
val conditionDt = stmt.condition.inferType(program)
|
val conditionDt = stmt.condition.inferType(program)
|
||||||
if(!conditionDt.isKnown)
|
if(!conditionDt.isKnown)
|
||||||
throw AssemblyError("unknown condition dt")
|
throw AssemblyError("unknown condition dt")
|
||||||
if(conditionDt.typeOrElse(DataType.BYTE) in ByteDatatypes)
|
if(conditionDt.getOr(DataType.BYTE) in ByteDatatypes)
|
||||||
assignExpressionToRegister(stmt.condition, RegisterOrPair.A)
|
assignExpressionToRegister(stmt.condition, RegisterOrPair.A)
|
||||||
else
|
else
|
||||||
assignExpressionToRegister(stmt.condition, RegisterOrPair.AY)
|
assignExpressionToRegister(stmt.condition, RegisterOrPair.AY)
|
||||||
@ -1114,7 +1211,7 @@ $repeatLabel lda $counterVar
|
|||||||
choiceBlocks.add(choiceLabel to choice.statements)
|
choiceBlocks.add(choiceLabel to choice.statements)
|
||||||
for (cv in choice.values!!) {
|
for (cv in choice.values!!) {
|
||||||
val value = (cv as NumericLiteralValue).number.toInt()
|
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")
|
out(" cmp #${value.toHex()} | beq $choiceLabel")
|
||||||
} else {
|
} else {
|
||||||
out("""
|
out("""
|
||||||
@ -1138,7 +1235,8 @@ $repeatLabel lda $counterVar
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun translate(stmt: Label) {
|
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) {
|
private fun translate(scope: AnonymousScope) {
|
||||||
@ -1147,7 +1245,7 @@ $repeatLabel lda $counterVar
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun translate(stmt: BranchStatement) {
|
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")
|
throw AssemblyError("only else part contains code, shoud have been switched already")
|
||||||
|
|
||||||
val jump = stmt.truepart.statements.first() as? Jump
|
val jump = stmt.truepart.statements.first() as? Jump
|
||||||
@ -1159,7 +1257,7 @@ $repeatLabel lda $counterVar
|
|||||||
} else {
|
} else {
|
||||||
val truePartIsJustBreak = stmt.truepart.statements.firstOrNull() is Break
|
val truePartIsJustBreak = stmt.truepart.statements.firstOrNull() is Break
|
||||||
val elsePartIsJustBreak = stmt.elsepart.statements.firstOrNull() is Break
|
val elsePartIsJustBreak = stmt.elsepart.statements.firstOrNull() is Break
|
||||||
if(stmt.elsepart.containsNoCodeNorVars()) {
|
if(stmt.elsepart.containsNoCodeNorVars) {
|
||||||
if(truePartIsJustBreak) {
|
if(truePartIsJustBreak) {
|
||||||
// branch with just a break (jump out of loop)
|
// branch with just a break (jump out of loop)
|
||||||
val instruction = branchInstruction(stmt.condition, false)
|
val instruction = branchInstruction(stmt.condition, false)
|
||||||
@ -1203,8 +1301,8 @@ $repeatLabel lda $counterVar
|
|||||||
if(stmt.value!=null && stmt.type==VarDeclType.VAR && stmt.datatype in NumericDatatypes) {
|
if(stmt.value!=null && stmt.type==VarDeclType.VAR && stmt.datatype in NumericDatatypes) {
|
||||||
// generate an assignment statement to (re)initialize the variable's value.
|
// 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 the vardecl is not in a subroutine however, we have to initialize it globally.
|
||||||
if(stmt.definingSubroutine()==null) {
|
if(stmt.definingSubroutine ==null) {
|
||||||
val block = stmt.definingBlock()
|
val block = stmt.definingBlock
|
||||||
var inits = blockLevelVarInits[block]
|
var inits = blockLevelVarInits[block]
|
||||||
if(inits==null) {
|
if(inits==null) {
|
||||||
inits = mutableSetOf()
|
inits = mutableSetOf()
|
||||||
@ -1220,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) {
|
private fun translate(stmt: Directive) {
|
||||||
when(stmt.directive) {
|
when(stmt.directive) {
|
||||||
"%asminclude" -> {
|
"%asminclude" -> {
|
||||||
val sourcecode = loadAsmIncludeFile(stmt.args[0].str!!, stmt.definingModule().source)
|
// TODO: handle %asminclude with SourceCode
|
||||||
val scopeprefix = stmt.args[1].str ?: ""
|
val includedName = stmt.args[0].str!!
|
||||||
if(scopeprefix.isNotBlank())
|
if(stmt.definingModule.source is SourceCode.Generated)
|
||||||
out("$scopeprefix\t.proc")
|
TODO("%asminclude inside non-library, non-filesystem module")
|
||||||
assemblyLines.add(sourcecode.trimEnd().trimStart('\n'))
|
loadAsmIncludeFile(includedName, stmt.definingModule.source).fold(
|
||||||
if(scopeprefix.isNotBlank())
|
success = { assemblyLines.add(it.trimEnd().trimStart('\n')) },
|
||||||
out(" .pend\n")
|
failure = { errors.err(it.toString(), stmt.position) }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
"%asmbinary" -> {
|
"%asmbinary" -> {
|
||||||
|
val includedName = stmt.args[0].str!!
|
||||||
val offset = if(stmt.args.size>1) ", ${stmt.args[1].int}" else ""
|
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 length = if(stmt.args.size>2) ", ${stmt.args[2].int}" else ""
|
||||||
val includedSourcePath = stmt.definingModule().source.resolveSibling(stmt.args[0].str)
|
if(stmt.definingModule.source is SourceCode.Generated)
|
||||||
val relPath = Paths.get("").relativize(includedSourcePath)
|
TODO("%asmbinary inside non-library, non-filesystem module")
|
||||||
out(" .binary \"./$relPath\" $offset $length")
|
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" -> {
|
"%breakpoint" -> {
|
||||||
val label = "_prog8_breakpoint_${breakpointLabels.size+1}"
|
val label = "_prog8_breakpoint_${breakpointLabels.size+1}"
|
||||||
@ -1255,23 +1365,16 @@ $label nop""")
|
|||||||
val label = jump.generatedLabel
|
val label = jump.generatedLabel
|
||||||
val addr = jump.address
|
val addr = jump.address
|
||||||
return when {
|
return when {
|
||||||
ident!=null -> {
|
ident!=null -> asmSymbolName(ident)
|
||||||
val target = ident.targetStatement(program)
|
|
||||||
val asmName = asmSymbolName(ident)
|
|
||||||
if(target is Label)
|
|
||||||
"_$asmName" // prefix with underscore to jump to local label
|
|
||||||
else
|
|
||||||
asmName
|
|
||||||
}
|
|
||||||
label!=null -> label
|
label!=null -> label
|
||||||
addr!=null -> addr.toHex()
|
addr!=null -> addr.toHex()
|
||||||
else -> "????"
|
else -> "????"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translate(ret: Return) {
|
internal fun translate(ret: Return, withRts: Boolean=true) {
|
||||||
ret.value?.let { returnvalue ->
|
ret.value?.let { returnvalue ->
|
||||||
val sub = ret.definingSubroutine()!!
|
val sub = ret.definingSubroutine!!
|
||||||
val returnType = sub.returntypes.single()
|
val returnType = sub.returntypes.single()
|
||||||
val returnReg = sub.asmReturnvaluesRegisters.single()
|
val returnReg = sub.asmReturnvaluesRegisters.single()
|
||||||
if(returnReg.registerOrPair==null)
|
if(returnReg.registerOrPair==null)
|
||||||
@ -1288,7 +1391,9 @@ $label nop""")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
out(" rts")
|
|
||||||
|
if(withRts)
|
||||||
|
out(" rts")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translate(asm: InlineAssembly) {
|
private fun translate(asm: InlineAssembly) {
|
||||||
@ -1358,22 +1463,22 @@ $label nop""")
|
|||||||
if(pointerOffsetExpr is BinaryExpression && pointerOffsetExpr.operator=="+") {
|
if(pointerOffsetExpr is BinaryExpression && pointerOffsetExpr.operator=="+") {
|
||||||
val leftDt = pointerOffsetExpr.left.inferType(program)
|
val leftDt = pointerOffsetExpr.left.inferType(program)
|
||||||
val rightDt = 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)
|
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)
|
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
|
// could be that the index was a constant numeric byte but converted to word, check that
|
||||||
val constIdx = pointerOffsetExpr.right.constValue(program)
|
val constIdx = pointerOffsetExpr.right.constValue(program)
|
||||||
if(constIdx!=null && constIdx.number.toInt()>=0 && constIdx.number.toInt()<=255) {
|
if(constIdx!=null && constIdx.number.toInt()>=0 && constIdx.number.toInt()<=255) {
|
||||||
return Pair(pointerOffsetExpr.left, NumericLiteralValue(DataType.UBYTE, constIdx.number, constIdx.position))
|
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
|
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)
|
return Pair(pointerOffsetExpr.left, rightTc.expression)
|
||||||
val leftTc = pointerOffsetExpr.left as? TypecastExpression
|
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)
|
return Pair(pointerOffsetExpr.right, leftTc.expression)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1386,7 +1491,7 @@ $label nop""")
|
|||||||
|
|
||||||
fun evalBytevalueWillClobberA(expr: Expression): Boolean {
|
fun evalBytevalueWillClobberA(expr: Expression): Boolean {
|
||||||
val dt = expr.inferType(program)
|
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 true
|
||||||
return when(expr) {
|
return when(expr) {
|
||||||
is IdentifierReference -> false
|
is IdentifierReference -> false
|
||||||
@ -1402,38 +1507,48 @@ $label nop""")
|
|||||||
val ptrAndIndex = pointerViaIndexRegisterPossible(expr)
|
val ptrAndIndex = pointerViaIndexRegisterPossible(expr)
|
||||||
if(ptrAndIndex!=null) {
|
if(ptrAndIndex!=null) {
|
||||||
val pointervar = ptrAndIndex.first as? IdentifierReference
|
val pointervar = ptrAndIndex.first as? IdentifierReference
|
||||||
if(write) {
|
when(pointervar?.targetStatement(program)) {
|
||||||
if(pointervar!=null && isZpVar(pointervar)) {
|
is Label -> {
|
||||||
val saveA = evalBytevalueWillClobberA(ptrAndIndex.second)
|
|
||||||
if(saveA)
|
|
||||||
out(" pha")
|
|
||||||
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
|
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
|
||||||
if(saveA)
|
out(" lda ${asmSymbolName(pointervar)},y")
|
||||||
out(" pla")
|
return true
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
} else {
|
is VarDecl, null -> {
|
||||||
if(pointervar!=null && isZpVar(pointervar)) {
|
if(write) {
|
||||||
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
|
if(pointervar!=null && isZpVar(pointervar)) {
|
||||||
out(" lda (${asmSymbolName(pointervar)}),y")
|
val saveA = evalBytevalueWillClobberA(ptrAndIndex.second)
|
||||||
} else {
|
if(saveA)
|
||||||
// copy the pointer var to zp first
|
out(" pha")
|
||||||
assignExpressionToVariable(ptrAndIndex.first, asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
|
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
|
||||||
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
|
if(saveA)
|
||||||
out(" lda (P8ZP_SCRATCH_W2),y")
|
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
|
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)
|
lines.withIndex().filter { it.value.isNotBlank() && !it.value.trimStart().startsWith(';') }.windowed(windowSize, partialWindows = false)
|
||||||
|
|
||||||
private fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
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
|
// lda $ce01,x
|
||||||
// cmp #$20
|
// cmp #$20
|
||||||
// beq check_prog8_s72choice_32
|
// beq check_prog8_s72choice_32
|
||||||
|
@ -13,12 +13,8 @@ import prog8.ast.toHex
|
|||||||
import prog8.compiler.AssemblyError
|
import prog8.compiler.AssemblyError
|
||||||
import prog8.compiler.functions.FSignature
|
import prog8.compiler.functions.FSignature
|
||||||
import prog8.compiler.target.CpuType
|
import prog8.compiler.target.CpuType
|
||||||
|
import prog8.compiler.target.Cx16Target
|
||||||
import prog8.compiler.target.cpu6502.codegen.assignment.*
|
import prog8.compiler.target.cpu6502.codegen.assignment.*
|
||||||
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignSource
|
|
||||||
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignTarget
|
|
||||||
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignment
|
|
||||||
import prog8.compiler.target.cpu6502.codegen.assignment.SourceStorageKind
|
|
||||||
import prog8.compiler.target.cpu6502.codegen.assignment.TargetStorageKind
|
|
||||||
import prog8.compiler.target.subroutineFloatEvalResultVar2
|
import prog8.compiler.target.subroutineFloatEvalResultVar2
|
||||||
|
|
||||||
internal class BuiltinFunctionsAsmGen(private val program: Program, private val asmgen: AsmGen, private val assignAsmGen: AssignmentAsmGen) {
|
internal class BuiltinFunctionsAsmGen(private val program: Program, private val asmgen: AsmGen, private val assignAsmGen: AssignmentAsmGen) {
|
||||||
@ -38,7 +34,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
if(discardResult && resultToStack)
|
if(discardResult && resultToStack)
|
||||||
throw AssemblyError("cannot both discard the result AND put it onto stack")
|
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) {
|
when (func.name) {
|
||||||
"msb" -> funcMsb(fcall, resultToStack, resultRegister)
|
"msb" -> funcMsb(fcall, resultToStack, resultRegister)
|
||||||
@ -56,7 +52,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
"ln", "log2", "sqrt", "rad",
|
"ln", "log2", "sqrt", "rad",
|
||||||
"deg", "round", "floor", "ceil",
|
"deg", "round", "floor", "ceil",
|
||||||
"rndf" -> funcVariousFloatFuncs(fcall, func, resultToStack, resultRegister, sscope)
|
"rndf" -> funcVariousFloatFuncs(fcall, func, resultToStack, resultRegister, sscope)
|
||||||
"fastrnd8", "rnd", "rndw" -> funcRnd(func, resultToStack, resultRegister, sscope)
|
"rnd", "rndw" -> funcRnd(func, resultToStack, resultRegister, sscope)
|
||||||
"sqrt16" -> funcSqrt16(fcall, func, resultToStack, resultRegister, sscope)
|
"sqrt16" -> funcSqrt16(fcall, func, resultToStack, resultRegister, sscope)
|
||||||
"rol" -> funcRol(fcall)
|
"rol" -> funcRol(fcall)
|
||||||
"rol2" -> funcRol2(fcall)
|
"rol2" -> funcRol2(fcall)
|
||||||
@ -69,7 +65,181 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
"peek" -> throw AssemblyError("peek() should have been replaced by @()")
|
"peek" -> throw AssemblyError("peek() should have been replaced by @()")
|
||||||
"pokew" -> funcPokeW(fcall)
|
"pokew" -> funcPokeW(fcall)
|
||||||
"poke" -> throw AssemblyError("poke() should have been replaced by @()")
|
"poke" -> throw AssemblyError("poke() should have been replaced by @()")
|
||||||
else -> TODO("missing asmgen for builtin func ${func.name}")
|
"cmp" -> funcCmp(fcall)
|
||||||
|
"callfar" -> funcCallFar(fcall)
|
||||||
|
"callrom" -> funcCallRom(fcall)
|
||||||
|
else -> throw AssemblyError("missing asmgen for builtin func ${func.name}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcCallFar(fcall: IFunctionCall) {
|
||||||
|
if(asmgen.options.compTarget !is Cx16Target)
|
||||||
|
throw AssemblyError("callfar only works on cx16 target at this time")
|
||||||
|
|
||||||
|
val bank = fcall.args[0].constValue(program)?.number?.toInt()
|
||||||
|
val address = fcall.args[1].constValue(program)?.number?.toInt()
|
||||||
|
if(bank==null || address==null)
|
||||||
|
throw AssemblyError("callfar (jsrfar) requires constant arguments")
|
||||||
|
|
||||||
|
if(address !in 0xa000..0xbfff)
|
||||||
|
throw AssemblyError("callfar done on address outside of cx16 banked ram")
|
||||||
|
if(bank==0)
|
||||||
|
throw AssemblyError("callfar done on bank 0 which is reserved for the kernal")
|
||||||
|
|
||||||
|
val argAddrArg = fcall.args[2]
|
||||||
|
if(argAddrArg.constValue(program)?.number == 0) {
|
||||||
|
asmgen.out("""
|
||||||
|
jsr cx16.jsrfar
|
||||||
|
.word ${address.toHex()}
|
||||||
|
.byte ${bank.toHex()}""")
|
||||||
|
} else {
|
||||||
|
when(argAddrArg) {
|
||||||
|
is AddressOf -> {
|
||||||
|
if(argAddrArg.identifier.targetVarDecl(program)?.datatype != DataType.UBYTE)
|
||||||
|
throw AssemblyError("callfar done with 'arg' pointer to variable that's not UBYTE")
|
||||||
|
asmgen.out("""
|
||||||
|
lda ${asmgen.asmVariableName(argAddrArg.identifier)}
|
||||||
|
jsr cx16.jsrfar
|
||||||
|
.word ${address.toHex()}
|
||||||
|
.byte ${bank.toHex()}
|
||||||
|
sta ${asmgen.asmVariableName(argAddrArg.identifier)}""")
|
||||||
|
}
|
||||||
|
is NumericLiteralValue -> {
|
||||||
|
asmgen.out("""
|
||||||
|
lda ${argAddrArg.number.toHex()}
|
||||||
|
jsr cx16.jsrfar
|
||||||
|
.word ${address.toHex()}
|
||||||
|
.byte ${bank.toHex()}
|
||||||
|
sta ${argAddrArg.number.toHex()}""")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("callfar only accepts pointer-of a (ubyte) variable or constant memory address for the 'arg' parameter")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcCallRom(fcall: IFunctionCall) {
|
||||||
|
if(asmgen.options.compTarget !is Cx16Target)
|
||||||
|
throw AssemblyError("callrom only works on cx16 target at this time")
|
||||||
|
|
||||||
|
val bank = fcall.args[0].constValue(program)?.number?.toInt()
|
||||||
|
val address = fcall.args[1].constValue(program)?.number?.toInt()
|
||||||
|
if(bank==null || address==null)
|
||||||
|
throw AssemblyError("callrom requires constant arguments")
|
||||||
|
|
||||||
|
if(address !in 0xc000..0xffff)
|
||||||
|
throw AssemblyError("callrom done on address outside of cx16 banked rom")
|
||||||
|
if(bank>=32)
|
||||||
|
throw AssemblyError("callrom bank must be <32")
|
||||||
|
|
||||||
|
val argAddrArg = fcall.args[2]
|
||||||
|
if(argAddrArg.constValue(program)?.number == 0) {
|
||||||
|
asmgen.out("""
|
||||||
|
lda $01
|
||||||
|
pha
|
||||||
|
lda #${bank}
|
||||||
|
sta $01
|
||||||
|
jsr ${address.toHex()}
|
||||||
|
pla
|
||||||
|
sta $01""")
|
||||||
|
} else {
|
||||||
|
when(argAddrArg) {
|
||||||
|
is AddressOf -> {
|
||||||
|
if(argAddrArg.identifier.targetVarDecl(program)?.datatype != DataType.UBYTE)
|
||||||
|
throw AssemblyError("callrom done with 'arg' pointer to variable that's not UBYTE")
|
||||||
|
asmgen.out("""
|
||||||
|
lda $01
|
||||||
|
pha
|
||||||
|
lda #${bank}
|
||||||
|
sta $01
|
||||||
|
lda ${asmgen.asmVariableName(argAddrArg.identifier)}
|
||||||
|
jsr ${address.toHex()}
|
||||||
|
sta ${asmgen.asmVariableName(argAddrArg.identifier)}
|
||||||
|
pla
|
||||||
|
sta $01""")
|
||||||
|
}
|
||||||
|
is NumericLiteralValue -> {
|
||||||
|
asmgen.out("""
|
||||||
|
lda $01
|
||||||
|
pha
|
||||||
|
lda #${bank}
|
||||||
|
sta $01
|
||||||
|
lda ${argAddrArg.number.toHex()}
|
||||||
|
jsr ${address.toHex()}
|
||||||
|
sta ${argAddrArg.number.toHex()}
|
||||||
|
pla
|
||||||
|
sta $01""")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("callrom only accepts pointer-of a (ubyte) variable or constant memory address for the 'arg' parameter")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcCmp(fcall: IFunctionCall) {
|
||||||
|
val arg1 = fcall.args[0]
|
||||||
|
val arg2 = fcall.args[1]
|
||||||
|
val dt1 = arg1.inferType(program).getOr(DataType.UNDEFINED)
|
||||||
|
val dt2 = arg2.inferType(program).getOr(DataType.UNDEFINED)
|
||||||
|
if(dt1 in ByteDatatypes) {
|
||||||
|
if(dt2 in ByteDatatypes) {
|
||||||
|
when (arg2) {
|
||||||
|
is IdentifierReference -> {
|
||||||
|
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
||||||
|
asmgen.out(" cmp ${asmgen.asmVariableName(arg2)}")
|
||||||
|
}
|
||||||
|
is NumericLiteralValue -> {
|
||||||
|
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
||||||
|
asmgen.out(" cmp #${arg2.number}")
|
||||||
|
}
|
||||||
|
is DirectMemoryRead -> {
|
||||||
|
if(arg2.addressExpression is NumericLiteralValue) {
|
||||||
|
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.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
||||||
|
asmgen.out(" cmp P8ZP_SCRATCH_B1")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
|
||||||
|
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
||||||
|
asmgen.out(" cmp P8ZP_SCRATCH_B1")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
throw AssemblyError("args for cmp() should have same dt")
|
||||||
|
} else {
|
||||||
|
// dt1 is a word
|
||||||
|
if(dt2 in WordDatatypes) {
|
||||||
|
when (arg2) {
|
||||||
|
is IdentifierReference -> {
|
||||||
|
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
|
||||||
|
asmgen.out("""
|
||||||
|
cpy ${asmgen.asmVariableName(arg2)}+1
|
||||||
|
bne +
|
||||||
|
cmp ${asmgen.asmVariableName(arg2)}
|
||||||
|
+""")
|
||||||
|
}
|
||||||
|
is NumericLiteralValue -> {
|
||||||
|
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
|
||||||
|
asmgen.out("""
|
||||||
|
cpy #>${arg2.number}
|
||||||
|
bne +
|
||||||
|
cmp #<${arg2.number}
|
||||||
|
+""")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_W1", DataType.UWORD, (fcall as Node).definingSubroutine)
|
||||||
|
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
|
||||||
|
asmgen.out("""
|
||||||
|
cpy P8ZP_SCRATCH_W1+1
|
||||||
|
bne +
|
||||||
|
cmp P8ZP_SCRATCH_W1
|
||||||
|
+""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
throw AssemblyError("args for cmp() should have same dt")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,7 +368,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
private fun funcRor2(fcall: IFunctionCall) {
|
private fun funcRor2(fcall: IFunctionCall) {
|
||||||
val what = fcall.args.single()
|
val what = fcall.args.single()
|
||||||
val dt = what.inferType(program)
|
val dt = what.inferType(program)
|
||||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
when (dt.getOr(DataType.UNDEFINED)) {
|
||||||
DataType.UBYTE -> {
|
DataType.UBYTE -> {
|
||||||
when (what) {
|
when (what) {
|
||||||
is ArrayIndexedExpression -> {
|
is ArrayIndexedExpression -> {
|
||||||
@ -241,7 +411,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
private fun funcRor(fcall: IFunctionCall) {
|
private fun funcRor(fcall: IFunctionCall) {
|
||||||
val what = fcall.args.single()
|
val what = fcall.args.single()
|
||||||
val dt = what.inferType(program)
|
val dt = what.inferType(program)
|
||||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
when (dt.getOr(DataType.UNDEFINED)) {
|
||||||
DataType.UBYTE -> {
|
DataType.UBYTE -> {
|
||||||
when (what) {
|
when (what) {
|
||||||
is ArrayIndexedExpression -> {
|
is ArrayIndexedExpression -> {
|
||||||
@ -256,7 +426,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.addressExpression)
|
val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.addressExpression)
|
||||||
if(ptrAndIndex!=null) {
|
if(ptrAndIndex!=null) {
|
||||||
asmgen.assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.X)
|
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.assignExpressionToRegister(ptrAndIndex.first, RegisterOrPair.AY)
|
||||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
@ -299,7 +469,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
private fun funcRol2(fcall: IFunctionCall) {
|
private fun funcRol2(fcall: IFunctionCall) {
|
||||||
val what = fcall.args.single()
|
val what = fcall.args.single()
|
||||||
val dt = what.inferType(program)
|
val dt = what.inferType(program)
|
||||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
when (dt.getOr(DataType.UNDEFINED)) {
|
||||||
DataType.UBYTE -> {
|
DataType.UBYTE -> {
|
||||||
when (what) {
|
when (what) {
|
||||||
is ArrayIndexedExpression -> {
|
is ArrayIndexedExpression -> {
|
||||||
@ -342,7 +512,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
private fun funcRol(fcall: IFunctionCall) {
|
private fun funcRol(fcall: IFunctionCall) {
|
||||||
val what = fcall.args.single()
|
val what = fcall.args.single()
|
||||||
val dt = what.inferType(program)
|
val dt = what.inferType(program)
|
||||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
when (dt.getOr(DataType.UNDEFINED)) {
|
||||||
DataType.UBYTE -> {
|
DataType.UBYTE -> {
|
||||||
when (what) {
|
when (what) {
|
||||||
is ArrayIndexedExpression -> {
|
is ArrayIndexedExpression -> {
|
||||||
@ -357,7 +527,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.addressExpression)
|
val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.addressExpression)
|
||||||
if(ptrAndIndex!=null) {
|
if(ptrAndIndex!=null) {
|
||||||
asmgen.assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.X)
|
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.assignExpressionToRegister(ptrAndIndex.first, RegisterOrPair.AY)
|
||||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
@ -399,8 +569,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
|
|
||||||
private fun translateRolRorArrayArgs(arrayvar: IdentifierReference, indexer: ArrayIndex, operation: String, dt: Char) {
|
private fun translateRolRorArrayArgs(arrayvar: IdentifierReference, indexer: ArrayIndex, operation: String, dt: Char) {
|
||||||
asmgen.assignExpressionToVariable(AddressOf(arrayvar, arrayvar.position), "prog8_lib.${operation}_array_u${dt}._arg_target", DataType.UWORD, null)
|
asmgen.assignExpressionToVariable(AddressOf(arrayvar, arrayvar.position), "prog8_lib.${operation}_array_u${dt}._arg_target", DataType.UWORD, null)
|
||||||
val indexerExpr = if(indexer.indexVar!=null) indexer.indexVar!! else indexer.indexNum!!
|
asmgen.assignExpressionToVariable(indexer.indexExpr, "prog8_lib.${operation}_array_u${dt}._arg_index", DataType.UBYTE, null)
|
||||||
asmgen.assignExpressionToVariable(indexerExpr, "prog8_lib.${operation}_array_u${dt}._arg_index", DataType.UBYTE, null)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun funcVariousFloatFuncs(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
|
private fun funcVariousFloatFuncs(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
|
||||||
@ -417,7 +586,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
translateArguments(fcall.args, func, scope)
|
translateArguments(fcall.args, func, scope)
|
||||||
val dt = fcall.args.single().inferType(program)
|
val dt = fcall.args.single().inferType(program)
|
||||||
if(resultToStack) {
|
if(resultToStack) {
|
||||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
when (dt.getOr(DataType.UNDEFINED)) {
|
||||||
DataType.UBYTE -> asmgen.out(" jsr prog8_lib.func_sign_ub_stack")
|
DataType.UBYTE -> asmgen.out(" jsr prog8_lib.func_sign_ub_stack")
|
||||||
DataType.BYTE -> asmgen.out(" jsr prog8_lib.func_sign_b_stack")
|
DataType.BYTE -> asmgen.out(" jsr prog8_lib.func_sign_b_stack")
|
||||||
DataType.UWORD -> asmgen.out(" jsr prog8_lib.func_sign_uw_stack")
|
DataType.UWORD -> asmgen.out(" jsr prog8_lib.func_sign_uw_stack")
|
||||||
@ -426,7 +595,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
else -> throw AssemblyError("weird type $dt")
|
else -> throw AssemblyError("weird type $dt")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
when (dt.getOr(DataType.UNDEFINED)) {
|
||||||
DataType.UBYTE -> asmgen.out(" jsr prog8_lib.func_sign_ub_into_A")
|
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.BYTE -> asmgen.out(" jsr prog8_lib.func_sign_b_into_A")
|
||||||
DataType.UWORD -> asmgen.out(" jsr prog8_lib.func_sign_uw_into_A")
|
DataType.UWORD -> asmgen.out(" jsr prog8_lib.func_sign_uw_into_A")
|
||||||
@ -442,14 +611,14 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
outputAddressAndLenghtOfArray(fcall.args[0])
|
outputAddressAndLenghtOfArray(fcall.args[0])
|
||||||
val dt = fcall.args.single().inferType(program)
|
val dt = fcall.args.single().inferType(program)
|
||||||
if(resultToStack) {
|
if(resultToStack) {
|
||||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
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_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_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")
|
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${function.name}_f_stack")
|
||||||
else -> throw AssemblyError("weird type $dt")
|
else -> throw AssemblyError("weird type $dt")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
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_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_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")
|
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${function.name}_f_into_A")
|
||||||
@ -463,7 +632,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
outputAddressAndLenghtOfArray(fcall.args[0])
|
outputAddressAndLenghtOfArray(fcall.args[0])
|
||||||
val dt = fcall.args.single().inferType(program)
|
val dt = fcall.args.single().inferType(program)
|
||||||
if(resultToStack) {
|
if(resultToStack) {
|
||||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
when (dt.getOr(DataType.UNDEFINED)) {
|
||||||
DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${function.name}_ub_stack")
|
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_B -> asmgen.out(" jsr prog8_lib.func_${function.name}_b_stack")
|
||||||
DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_${function.name}_uw_stack")
|
DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_${function.name}_uw_stack")
|
||||||
@ -472,7 +641,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
else -> throw AssemblyError("weird type $dt")
|
else -> throw AssemblyError("weird type $dt")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
when (dt.getOr(DataType.UNDEFINED)) {
|
||||||
DataType.ARRAY_UB, DataType.STR -> {
|
DataType.ARRAY_UB, DataType.STR -> {
|
||||||
asmgen.out(" jsr prog8_lib.func_${function.name}_ub_into_A")
|
asmgen.out(" jsr prog8_lib.func_${function.name}_ub_into_A")
|
||||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
|
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
|
||||||
@ -502,7 +671,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
outputAddressAndLenghtOfArray(fcall.args[0])
|
outputAddressAndLenghtOfArray(fcall.args[0])
|
||||||
val dt = fcall.args.single().inferType(program)
|
val dt = fcall.args.single().inferType(program)
|
||||||
if(resultToStack) {
|
if(resultToStack) {
|
||||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
when (dt.getOr(DataType.UNDEFINED)) {
|
||||||
DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_sum_ub_stack")
|
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_B -> asmgen.out(" jsr prog8_lib.func_sum_b_stack")
|
||||||
DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_sum_uw_stack")
|
DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_sum_uw_stack")
|
||||||
@ -511,7 +680,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
else -> throw AssemblyError("weird type $dt")
|
else -> throw AssemblyError("weird type $dt")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
when (dt.getOr(DataType.UNDEFINED)) {
|
||||||
DataType.ARRAY_UB, DataType.STR -> {
|
DataType.ARRAY_UB, DataType.STR -> {
|
||||||
asmgen.out(" jsr prog8_lib.func_sum_ub_into_AY")
|
asmgen.out(" jsr prog8_lib.func_sum_ub_into_AY")
|
||||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
|
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
|
||||||
@ -546,11 +715,11 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
val firstName = asmgen.asmVariableName(first)
|
val firstName = asmgen.asmVariableName(first)
|
||||||
val secondName = asmgen.asmVariableName(second)
|
val secondName = asmgen.asmVariableName(second)
|
||||||
val dt = first.inferType(program)
|
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")
|
asmgen.out(" ldy $firstName | lda $secondName | sta $firstName | sty $secondName")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if(dt.istype(DataType.WORD) || dt.istype(DataType.UWORD)) {
|
if(dt istype DataType.WORD || dt istype DataType.UWORD) {
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
ldy $firstName
|
ldy $firstName
|
||||||
lda $secondName
|
lda $secondName
|
||||||
@ -563,7 +732,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
""")
|
""")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if(dt.istype(DataType.FLOAT)) {
|
if(dt istype DataType.FLOAT) {
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda #<$firstName
|
lda #<$firstName
|
||||||
sta P8ZP_SCRATCH_W1
|
sta P8ZP_SCRATCH_W1
|
||||||
@ -581,7 +750,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
|
|
||||||
// optimized simple case: swap two memory locations
|
// optimized simple case: swap two memory locations
|
||||||
if(first is DirectMemoryRead && second is DirectMemoryRead) {
|
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 addr1 = (first.addressExpression as? NumericLiteralValue)?.number?.toHex()
|
||||||
val addr2 = (second.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
|
val name1 = if(first.addressExpression is IdentifierReference) asmgen.asmVariableName(first.addressExpression as IdentifierReference) else null
|
||||||
@ -604,6 +772,50 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
asmgen.out(" ldy $name1 | lda $name2 | sta $name1 | sty $name2")
|
asmgen.out(" ldy $name1 | lda $name2 | sta $name1 | sty $name2")
|
||||||
return
|
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}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -613,12 +825,12 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
val elementIDt = first.inferType(program)
|
val elementIDt = first.inferType(program)
|
||||||
if(!elementIDt.isKnown)
|
if(!elementIDt.isKnown)
|
||||||
throw AssemblyError("unknown dt")
|
throw AssemblyError("unknown dt")
|
||||||
val elementDt = elementIDt.typeOrElse(DataType.STRUCT)
|
val elementDt = elementIDt.getOr(DataType.UNDEFINED)
|
||||||
|
|
||||||
val firstNum = first.indexer.indexNum
|
val firstNum = first.indexer.indexExpr as? NumericLiteralValue
|
||||||
val firstVar = first.indexer.indexVar
|
val firstVar = first.indexer.indexExpr as? IdentifierReference
|
||||||
val secondNum = second.indexer.indexNum
|
val secondNum = second.indexer.indexExpr as? NumericLiteralValue
|
||||||
val secondVar = second.indexer.indexVar
|
val secondVar = second.indexer.indexExpr as? IdentifierReference
|
||||||
|
|
||||||
if(firstNum!=null && secondNum!=null) {
|
if(firstNum!=null && secondNum!=null) {
|
||||||
swapArrayValues(elementDt, arrayVarName1, firstNum, arrayVarName2, secondNum)
|
swapArrayValues(elementDt, arrayVarName1, firstNum, arrayVarName2, secondNum)
|
||||||
@ -639,15 +851,14 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
|
|
||||||
fun targetFromExpr(expr: Expression, datatype: DataType): AsmAssignTarget {
|
fun targetFromExpr(expr: Expression, datatype: DataType): AsmAssignTarget {
|
||||||
return when (expr) {
|
return when (expr) {
|
||||||
is IdentifierReference -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, datatype, expr.definingSubroutine(), variableAsmName = asmgen.asmVariableName(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 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 DirectMemoryRead -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, datatype, expr.definingSubroutine, memory = DirectMemoryWrite(expr.addressExpression, expr.position))
|
||||||
else -> throw AssemblyError("invalid expression object $expr")
|
else -> throw AssemblyError("invalid expression object $expr")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val datatype = first.inferType(program).typeOrElse(DataType.STRUCT)
|
when(val datatype: DataType = first.inferType(program).getOr(DataType.UNDEFINED)) {
|
||||||
when(datatype) {
|
|
||||||
in ByteDatatypes, in WordDatatypes -> {
|
in ByteDatatypes, in WordDatatypes -> {
|
||||||
asmgen.assignExpressionToVariable(first, "P8ZP_SCRATCH_W1", datatype, null)
|
asmgen.assignExpressionToVariable(first, "P8ZP_SCRATCH_W1", datatype, null)
|
||||||
asmgen.assignExpressionToVariable(second, "P8ZP_SCRATCH_W2", datatype, null)
|
asmgen.assignExpressionToVariable(second, "P8ZP_SCRATCH_W2", datatype, null)
|
||||||
@ -719,7 +930,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
sta P8ZP_SCRATCH_W2
|
sta P8ZP_SCRATCH_W2
|
||||||
lda #>(${arrayVarName2}+$index2)
|
lda #>(${arrayVarName2}+$index2)
|
||||||
sta P8ZP_SCRATCH_W2+1
|
sta P8ZP_SCRATCH_W2+1
|
||||||
jsr floats.swap_floats
|
jsr floats.func_swap_f
|
||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("invalid aray elt type")
|
else -> throw AssemblyError("invalid aray elt type")
|
||||||
@ -792,7 +1003,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
sta P8ZP_SCRATCH_W2
|
sta P8ZP_SCRATCH_W2
|
||||||
bcc +
|
bcc +
|
||||||
inc P8ZP_SCRATCH_W2+1
|
inc P8ZP_SCRATCH_W2+1
|
||||||
+ jsr floats.swap_floats
|
+ jsr floats.func_swap_f
|
||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("invalid aray elt type")
|
else -> throw AssemblyError("invalid aray elt type")
|
||||||
@ -850,7 +1061,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
sta P8ZP_SCRATCH_W1
|
sta P8ZP_SCRATCH_W1
|
||||||
bcc +
|
bcc +
|
||||||
inc P8ZP_SCRATCH_W1+1
|
inc P8ZP_SCRATCH_W1+1
|
||||||
+ jsr floats.swap_floats
|
+ jsr floats.func_swap_f
|
||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("invalid aray elt type")
|
else -> throw AssemblyError("invalid aray elt type")
|
||||||
@ -908,7 +1119,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
sta P8ZP_SCRATCH_W2
|
sta P8ZP_SCRATCH_W2
|
||||||
lda #>(${arrayVarName2}+$index2)
|
lda #>(${arrayVarName2}+$index2)
|
||||||
sta P8ZP_SCRATCH_W2+1
|
sta P8ZP_SCRATCH_W2+1
|
||||||
jsr floats.swap_floats
|
jsr floats.func_swap_f
|
||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("invalid aray elt type")
|
else -> throw AssemblyError("invalid aray elt type")
|
||||||
@ -917,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?) {
|
private fun funcAbs(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
|
||||||
translateArguments(fcall.args, func, scope)
|
translateArguments(fcall.args, func, scope)
|
||||||
val dt = fcall.args.single().inferType(program).typeOrElse(DataType.STRUCT)
|
val dt = fcall.args.single().inferType(program).getOr(DataType.UNDEFINED)
|
||||||
if(resultToStack) {
|
if(resultToStack) {
|
||||||
when (dt) {
|
when (dt) {
|
||||||
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.abs_b_stack")
|
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.abs_b_stack")
|
||||||
@ -946,14 +1157,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
|
|
||||||
private fun funcRnd(func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
|
private fun funcRnd(func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
|
||||||
when(func.name) {
|
when(func.name) {
|
||||||
"fastrnd8" -> {
|
|
||||||
if(resultToStack)
|
|
||||||
asmgen.out(" jsr prog8_lib.func_fastrnd8_stack")
|
|
||||||
else {
|
|
||||||
asmgen.out(" jsr math.fast_randbyte")
|
|
||||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"rnd" -> {
|
"rnd" -> {
|
||||||
if(resultToStack)
|
if(resultToStack)
|
||||||
asmgen.out(" jsr prog8_lib.func_rnd_stack")
|
asmgen.out(" jsr prog8_lib.func_rnd_stack")
|
||||||
@ -986,7 +1189,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
val varname = asmgen.asmVariableName(addrExpr)
|
val varname = asmgen.asmVariableName(addrExpr)
|
||||||
if(asmgen.isZpVar(addrExpr)) {
|
if(asmgen.isZpVar(addrExpr)) {
|
||||||
// pointervar is already in the zero page, no need to copy
|
// 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)
|
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AX)
|
||||||
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
@ -1008,18 +1211,21 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
}
|
}
|
||||||
is BinaryExpression -> {
|
is BinaryExpression -> {
|
||||||
if(addrExpr.operator=="+" && addrExpr.left is IdentifierReference && addrExpr.right is NumericLiteralValue) {
|
if(addrExpr.operator=="+" && addrExpr.left is IdentifierReference && addrExpr.right is NumericLiteralValue) {
|
||||||
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine()!!)
|
|
||||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AX)
|
|
||||||
val varname = asmgen.asmVariableName(addrExpr.left as IdentifierReference)
|
val varname = asmgen.asmVariableName(addrExpr.left as IdentifierReference)
|
||||||
val index = (addrExpr.right as NumericLiteralValue).number.toHex()
|
if(asmgen.isZpVar(addrExpr.left as IdentifierReference)) {
|
||||||
asmgen.out("""
|
// pointervar is already in the zero page, no need to copy
|
||||||
ldy #$index
|
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine!!)
|
||||||
sta ($varname),y
|
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AX)
|
||||||
txa
|
val index = (addrExpr.right as NumericLiteralValue).number.toHex()
|
||||||
iny
|
asmgen.out("""
|
||||||
sta ($varname),y""")
|
ldy #$index
|
||||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
sta ($varname),y
|
||||||
return
|
txa
|
||||||
|
iny
|
||||||
|
sta ($varname),y""")
|
||||||
|
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1063,15 +1269,21 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
is BinaryExpression -> {
|
is BinaryExpression -> {
|
||||||
if(addrExpr.operator=="+" && addrExpr.left is IdentifierReference && addrExpr.right is NumericLiteralValue) {
|
if(addrExpr.operator=="+" && addrExpr.left is IdentifierReference && addrExpr.right is NumericLiteralValue) {
|
||||||
val varname = asmgen.asmVariableName(addrExpr.left as IdentifierReference)
|
val varname = asmgen.asmVariableName(addrExpr.left as IdentifierReference)
|
||||||
val index = (addrExpr.right as NumericLiteralValue).number.toHex()
|
if(asmgen.isZpVar(addrExpr.left as IdentifierReference)) {
|
||||||
asmgen.out("""
|
// pointervar is already in the zero page, no need to copy
|
||||||
ldy #$index
|
val index = (addrExpr.right as NumericLiteralValue).number.toHex()
|
||||||
lda ($varname),y
|
asmgen.out("""
|
||||||
pha
|
ldy #$index
|
||||||
iny
|
lda ($varname),y
|
||||||
lda ($varname),y
|
pha
|
||||||
tay
|
iny
|
||||||
pla""")
|
lda ($varname),y
|
||||||
|
tay
|
||||||
|
pla""")
|
||||||
|
} else {
|
||||||
|
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
|
||||||
|
asmgen.out(" jsr prog8_lib.func_peekw")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
|
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
|
||||||
asmgen.out(" jsr prog8_lib.func_peekw")
|
asmgen.out(" jsr prog8_lib.func_peekw")
|
||||||
@ -1090,7 +1302,10 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
RegisterOrPair.AY -> {}
|
RegisterOrPair.AY -> {}
|
||||||
RegisterOrPair.AX -> asmgen.out(" sty P8ZP_SCRATCH_REG | ldx P8ZP_SCRATCH_REG")
|
RegisterOrPair.AX -> asmgen.out(" sty P8ZP_SCRATCH_REG | ldx P8ZP_SCRATCH_REG")
|
||||||
RegisterOrPair.XY -> asmgen.out(" tax")
|
RegisterOrPair.XY -> asmgen.out(" tax")
|
||||||
in Cx16VirtualRegisters -> asmgen.out(" sta cx16.${resultRegister.toString().toLowerCase()} | sty cx16.${resultRegister.toString().toLowerCase()}+1")
|
in Cx16VirtualRegisters -> asmgen.out(
|
||||||
|
" sta cx16.${
|
||||||
|
resultRegister.toString().lowercase()
|
||||||
|
} | sty cx16.${resultRegister.toString().lowercase()}+1")
|
||||||
else -> throw AssemblyError("invalid reg")
|
else -> throw AssemblyError("invalid reg")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1140,9 +1355,9 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
}
|
}
|
||||||
in Cx16VirtualRegisters -> {
|
in Cx16VirtualRegisters -> {
|
||||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb
|
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb
|
||||||
asmgen.out(" sta cx16.${reg.toString().toLowerCase()}")
|
asmgen.out(" sta cx16.${reg.toString().lowercase()}")
|
||||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // msb
|
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // msb
|
||||||
asmgen.out(" sta cx16.${reg.toString().toLowerCase()}+1")
|
asmgen.out(" sta cx16.${reg.toString().lowercase()}+1")
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("invalid mkword target reg")
|
else -> throw AssemblyError("invalid mkword target reg")
|
||||||
}
|
}
|
||||||
@ -1151,7 +1366,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
|
|
||||||
private fun funcMsb(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
|
private fun funcMsb(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
|
||||||
val arg = fcall.args.single()
|
val arg = fcall.args.single()
|
||||||
if (arg.inferType(program).typeOrElse(DataType.STRUCT) !in WordDatatypes)
|
if (!arg.inferType(program).isWords)
|
||||||
throw AssemblyError("msb required word argument")
|
throw AssemblyError("msb required word argument")
|
||||||
if (arg is NumericLiteralValue)
|
if (arg is NumericLiteralValue)
|
||||||
throw AssemblyError("msb(const) should have been const-folded away")
|
throw AssemblyError("msb(const) should have been const-folded away")
|
||||||
@ -1195,7 +1410,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
|
|
||||||
private fun funcLsb(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
|
private fun funcLsb(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
|
||||||
val arg = fcall.args.single()
|
val arg = fcall.args.single()
|
||||||
if (arg.inferType(program).typeOrElse(DataType.STRUCT) !in WordDatatypes)
|
if (!arg.inferType(program).isWords)
|
||||||
throw AssemblyError("lsb required word argument")
|
throw AssemblyError("lsb required word argument")
|
||||||
if (arg is NumericLiteralValue)
|
if (arg is NumericLiteralValue)
|
||||||
throw AssemblyError("lsb(const) should have been const-folded away")
|
throw AssemblyError("lsb(const) should have been const-folded away")
|
||||||
@ -1259,7 +1474,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun translateArguments(args: MutableList<Expression>, signature: FSignature, scope: Subroutine?) {
|
private fun translateArguments(args: MutableList<Expression>, signature: FSignature, scope: Subroutine?) {
|
||||||
val callConv = signature.callConvention(args.map { it.inferType(program).typeOrElse(DataType.STRUCT) })
|
val callConv = signature.callConvention(args.map { it.inferType(program).getOr(DataType.UNDEFINED) })
|
||||||
|
|
||||||
fun getSourceForFloat(value: Expression): AsmAssignSource {
|
fun getSourceForFloat(value: Expression): AsmAssignSource {
|
||||||
return when (value) {
|
return when (value) {
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,7 @@
|
|||||||
package prog8.compiler.target.cpu6502.codegen
|
package prog8.compiler.target.cpu6502.codegen
|
||||||
|
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.ArrayElementTypes
|
import prog8.ast.base.ArrayToElementTypes
|
||||||
import prog8.ast.base.DataType
|
import prog8.ast.base.DataType
|
||||||
import prog8.ast.base.RegisterOrPair
|
import prog8.ast.base.RegisterOrPair
|
||||||
import prog8.ast.expressions.IdentifierReference
|
import prog8.ast.expressions.IdentifierReference
|
||||||
@ -9,6 +9,7 @@ import prog8.ast.expressions.RangeExpr
|
|||||||
import prog8.ast.statements.ForLoop
|
import prog8.ast.statements.ForLoop
|
||||||
import prog8.ast.toHex
|
import prog8.ast.toHex
|
||||||
import prog8.compiler.AssemblyError
|
import prog8.compiler.AssemblyError
|
||||||
|
import prog8.compiler.astprocessing.toConstantIntegerRange
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||||
@ -19,15 +20,15 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
|
|||||||
throw AssemblyError("unknown dt")
|
throw AssemblyError("unknown dt")
|
||||||
when(stmt.iterable) {
|
when(stmt.iterable) {
|
||||||
is RangeExpr -> {
|
is RangeExpr -> {
|
||||||
val range = (stmt.iterable as RangeExpr).toConstantIntegerRange()
|
val range = (stmt.iterable as RangeExpr).toConstantIntegerRange(asmgen.options.compTarget)
|
||||||
if(range==null) {
|
if(range==null) {
|
||||||
translateForOverNonconstRange(stmt, iterableDt.typeOrElse(DataType.STRUCT), stmt.iterable as RangeExpr)
|
translateForOverNonconstRange(stmt, iterableDt.getOr(DataType.UNDEFINED), stmt.iterable as RangeExpr)
|
||||||
} else {
|
} else {
|
||||||
translateForOverConstRange(stmt, iterableDt.typeOrElse(DataType.STRUCT), range)
|
translateForOverConstRange(stmt, iterableDt.getOr(DataType.UNDEFINED), range)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is IdentifierReference -> {
|
is IdentifierReference -> {
|
||||||
translateForOverIterableVar(stmt, iterableDt.typeOrElse(DataType.STRUCT), 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")
|
else -> throw AssemblyError("can't iterate over ${stmt.iterable.javaClass} - should have been replaced by a variable")
|
||||||
}
|
}
|
||||||
@ -40,6 +41,13 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
|
|||||||
val modifiedLabel2 = asmgen.makeLabel("for_modifiedb")
|
val modifiedLabel2 = asmgen.makeLabel("for_modifiedb")
|
||||||
asmgen.loopEndLabels.push(endLabel)
|
asmgen.loopEndLabels.push(endLabel)
|
||||||
val stepsize=range.step.constValue(program)!!.number.toInt()
|
val stepsize=range.step.constValue(program)!!.number.toInt()
|
||||||
|
|
||||||
|
if(stepsize < -1) {
|
||||||
|
val limit = range.to.constValue(program)?.number?.toDouble()
|
||||||
|
if(limit==0.0)
|
||||||
|
throw AssemblyError("for unsigned loop variable it's not possible to count down with step != -1 from a non-const value to exactly zero due to value wrapping")
|
||||||
|
}
|
||||||
|
|
||||||
when(iterableDt) {
|
when(iterableDt) {
|
||||||
DataType.ARRAY_B, DataType.ARRAY_UB -> {
|
DataType.ARRAY_B, DataType.ARRAY_UB -> {
|
||||||
if (stepsize==1 || stepsize==-1) {
|
if (stepsize==1 || stepsize==-1) {
|
||||||
@ -49,8 +57,8 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
|
|||||||
val incdec = if(stepsize==1) "inc" else "dec"
|
val incdec = if(stepsize==1) "inc" else "dec"
|
||||||
// loop over byte range via loopvar
|
// loop over byte range via loopvar
|
||||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
val varname = asmgen.asmVariableName(stmt.loopVar)
|
||||||
asmgen.assignExpressionToVariable(range.from, varname, ArrayElementTypes.getValue(iterableDt), null)
|
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt), null)
|
||||||
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayElementTypes.getValue(iterableDt), null)
|
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayToElementTypes.getValue(iterableDt), null)
|
||||||
asmgen.out(loopLabel)
|
asmgen.out(loopLabel)
|
||||||
asmgen.translate(stmt.body)
|
asmgen.translate(stmt.body)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
@ -67,8 +75,8 @@ $modifiedLabel cmp #0 ; modified
|
|||||||
|
|
||||||
// loop over byte range via loopvar
|
// loop over byte range via loopvar
|
||||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
val varname = asmgen.asmVariableName(stmt.loopVar)
|
||||||
asmgen.assignExpressionToVariable(range.from, varname, ArrayElementTypes.getValue(iterableDt), null)
|
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt), null)
|
||||||
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayElementTypes.getValue(iterableDt), null)
|
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayToElementTypes.getValue(iterableDt), null)
|
||||||
asmgen.out(loopLabel)
|
asmgen.out(loopLabel)
|
||||||
asmgen.translate(stmt.body)
|
asmgen.translate(stmt.body)
|
||||||
if(stepsize>0) {
|
if(stepsize>0) {
|
||||||
@ -281,7 +289,7 @@ $loopLabel sty $indexVar
|
|||||||
bne $loopLabel
|
bne $loopLabel
|
||||||
beq $endLabel""")
|
beq $endLabel""")
|
||||||
}
|
}
|
||||||
if(length>=16 && asmgen.zeropage.available() > 0) {
|
if(length>=16 && asmgen.zeropage.hasByteAvailable()) {
|
||||||
// allocate index var on ZP
|
// allocate index var on ZP
|
||||||
val zpAddr = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, stmt.position, asmgen.errors)
|
val zpAddr = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, stmt.position, asmgen.errors)
|
||||||
asmgen.out("""$indexVar = $zpAddr ; auto zp UBYTE""")
|
asmgen.out("""$indexVar = $zpAddr ; auto zp UBYTE""")
|
||||||
@ -320,7 +328,7 @@ $loopLabel sty $indexVar
|
|||||||
bne $loopLabel
|
bne $loopLabel
|
||||||
beq $endLabel""")
|
beq $endLabel""")
|
||||||
}
|
}
|
||||||
if(length>=16 && asmgen.zeropage.available() > 0) {
|
if(length>=16 && asmgen.zeropage.hasByteAvailable()) {
|
||||||
// allocate index var on ZP
|
// allocate index var on ZP
|
||||||
val zpAddr = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, stmt.position, asmgen.errors)
|
val zpAddr = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, stmt.position, asmgen.errors)
|
||||||
asmgen.out("""$indexVar = $zpAddr ; auto zp UBYTE""")
|
asmgen.out("""$indexVar = $zpAddr ; auto zp UBYTE""")
|
||||||
@ -581,5 +589,5 @@ $loopLabel""")
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun assignLoopvar(stmt: ForLoop, range: RangeExpr) =
|
private fun assignLoopvar(stmt: ForLoop, range: RangeExpr) =
|
||||||
asmgen.assignExpressionToVariable(range.from, asmgen.asmVariableName(stmt.loopVar), stmt.loopVarDt(program).typeOrElse(DataType.STRUCT), 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.Program
|
||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.InlineAssembly
|
||||||
|
import prog8.ast.statements.RegisterOrStatusflag
|
||||||
|
import prog8.ast.statements.Subroutine
|
||||||
|
import prog8.ast.statements.SubroutineParameter
|
||||||
import prog8.compiler.AssemblyError
|
import prog8.compiler.AssemblyError
|
||||||
import prog8.compiler.target.CpuType
|
import prog8.compiler.target.CpuType
|
||||||
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignSource
|
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}")
|
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
|
||||||
if(sub.shouldSaveX()) {
|
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 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)
|
if(regSaveOnStack)
|
||||||
asmgen.saveRegisterStack(CpuRegister.X, keepAonEntry)
|
asmgen.saveRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnEntry)
|
||||||
else
|
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}")
|
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
|
||||||
if(sub.shouldSaveX()) {
|
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 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)
|
if(regSaveOnStack)
|
||||||
asmgen.restoreRegisterStack(CpuRegister.X, keepAonReturn)
|
asmgen.restoreRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnReturn)
|
||||||
else
|
else
|
||||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun translateFunctionCall(stmt: IFunctionCall) {
|
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 the code to deal with the result values!
|
||||||
// NOTE: does NOT output code to save/restore the X register for this call! Every caller should deal with this in their own way!!
|
// NOTE: does NOT output code to save/restore the X register for this call! Every caller should deal with this in their own way!!
|
||||||
// (you can use subroutine.shouldSaveX() and saveX()/restoreX() routines as a help for this)
|
// (you can use subroutine.shouldSaveX() and saveX()/restoreX() routines as a help for this)
|
||||||
@ -111,17 +111,22 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(sub.inline && asmgen.options.optimize) {
|
if(!sub.inline || !asmgen.options.optimize) {
|
||||||
if(sub.containsDefinedVariables())
|
|
||||||
throw AssemblyError("can't inline sub with vars")
|
|
||||||
if(!sub.isAsmSubroutine && sub.parameters.isNotEmpty())
|
|
||||||
throw AssemblyError("can't inline a non-asm subroutine with parameters")
|
|
||||||
asmgen.out(" \t; inlined routine follows: ${sub.name} from ${sub.position}")
|
|
||||||
val statements = sub.statements.filter { it !is ParameterVarDecl && it !is Directive }
|
|
||||||
statements.forEach { asmgen.translate(it) }
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
asmgen.out(" jsr $subName")
|
asmgen.out(" jsr $subName")
|
||||||
|
} else {
|
||||||
|
// inline the subroutine.
|
||||||
|
// we do this by copying the subroutine's statements at the call site.
|
||||||
|
// NOTE: *if* there is a return statement, it will be the only one, and the very last statement of the subroutine
|
||||||
|
// (this condition has been enforced by an ast check earlier)
|
||||||
|
|
||||||
|
// note: for now, this is only reliably supported for asmsubs.
|
||||||
|
if(!sub.isAsmSubroutine)
|
||||||
|
throw AssemblyError("can only reliably inline asmsub routines at this time")
|
||||||
|
|
||||||
|
asmgen.out(" \t; inlined routine follows: ${sub.name}")
|
||||||
|
val assembly = sub.statements.single() as InlineAssembly
|
||||||
|
asmgen.translate(assembly)
|
||||||
|
asmgen.out(" \t; inlined routine end: ${sub.name}")
|
||||||
}
|
}
|
||||||
|
|
||||||
// remember: dealing with the X register and/or dealing with return values is the responsibility of the caller
|
// remember: dealing with the X register and/or dealing with return values is the responsibility of the caller
|
||||||
@ -152,11 +157,11 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
|||||||
argForCarry = argi
|
argForCarry = argi
|
||||||
}
|
}
|
||||||
argi.value.second.statusflag != null -> throw AssemblyError("can only use Carry as status flag parameter")
|
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)
|
require(argForXregister==null)
|
||||||
argForXregister = argi
|
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)
|
require(argForAregister == null)
|
||||||
argForAregister = argi
|
argForAregister = argi
|
||||||
}
|
}
|
||||||
@ -168,21 +173,31 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
|||||||
when (sub.parameters[argi.index].type) {
|
when (sub.parameters[argi.index].type) {
|
||||||
in ByteDatatypes -> {
|
in ByteDatatypes -> {
|
||||||
// only load the lsb of the virtual register
|
// only load the lsb of the virtual register
|
||||||
asmgen.out("""
|
asmgen.out(
|
||||||
|
"""
|
||||||
lda P8ESTACK_LO$plusIdxStr,x
|
lda P8ESTACK_LO$plusIdxStr,x
|
||||||
sta cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}
|
sta cx16.${argi.value.second.registerOrPair.toString().lowercase()}
|
||||||
""")
|
""")
|
||||||
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||||
asmgen.out(" stz cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}+1")
|
asmgen.out(
|
||||||
|
" stz cx16.${
|
||||||
|
argi.value.second.registerOrPair.toString().lowercase()
|
||||||
|
}+1")
|
||||||
else
|
else
|
||||||
asmgen.out(" lda #0 | sta cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}+1")
|
asmgen.out(
|
||||||
|
" lda #0 | sta cx16.${
|
||||||
|
argi.value.second.registerOrPair.toString().lowercase()
|
||||||
|
}+1")
|
||||||
}
|
}
|
||||||
in WordDatatypes ->
|
in WordDatatypes, in IterableDatatypes ->
|
||||||
asmgen.out("""
|
asmgen.out(
|
||||||
|
"""
|
||||||
lda P8ESTACK_LO$plusIdxStr,x
|
lda P8ESTACK_LO$plusIdxStr,x
|
||||||
sta cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}
|
sta cx16.${argi.value.second.registerOrPair.toString().lowercase()}
|
||||||
lda P8ESTACK_HI$plusIdxStr,x
|
lda P8ESTACK_HI$plusIdxStr,x
|
||||||
sta cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}+1
|
sta cx16.${
|
||||||
|
argi.value.second.registerOrPair.toString().lowercase()
|
||||||
|
}+1
|
||||||
""")
|
""")
|
||||||
else -> throw AssemblyError("weird dt")
|
else -> throw AssemblyError("weird dt")
|
||||||
}
|
}
|
||||||
@ -237,7 +252,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
|||||||
val valueIDt = value.inferType(program)
|
val valueIDt = value.inferType(program)
|
||||||
if(!valueIDt.isKnown)
|
if(!valueIDt.isKnown)
|
||||||
throw AssemblyError("unknown dt")
|
throw AssemblyError("unknown dt")
|
||||||
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
|
val valueDt = valueIDt.getOr(DataType.UNDEFINED)
|
||||||
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
|
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
|
||||||
throw AssemblyError("argument type incompatible")
|
throw AssemblyError("argument type incompatible")
|
||||||
|
|
||||||
@ -250,7 +265,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
|||||||
val valueIDt = value.inferType(program)
|
val valueIDt = value.inferType(program)
|
||||||
if(!valueIDt.isKnown)
|
if(!valueIDt.isKnown)
|
||||||
throw AssemblyError("unknown dt")
|
throw AssemblyError("unknown dt")
|
||||||
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
|
val valueDt = valueIDt.getOr(DataType.UNDEFINED)
|
||||||
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
|
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
|
||||||
throw AssemblyError("argument type incompatible")
|
throw AssemblyError("argument type incompatible")
|
||||||
|
|
||||||
@ -302,10 +317,9 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
|||||||
register!!
|
register!!
|
||||||
if(requiredDt largerThan valueDt) {
|
if(requiredDt largerThan valueDt) {
|
||||||
// we need to sign extend the source, do this via temporary word variable
|
// we need to sign extend the source, do this via temporary word variable
|
||||||
val scratchVar = asmgen.asmVariableName("P8ZP_SCRATCH_W1")
|
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_W1", DataType.UBYTE, sub)
|
||||||
asmgen.assignExpressionToVariable(value, scratchVar, DataType.UBYTE, sub)
|
asmgen.signExtendVariableLsb("P8ZP_SCRATCH_W1", valueDt)
|
||||||
asmgen.signExtendVariableLsb(scratchVar, valueDt)
|
asmgen.assignVariableToRegister("P8ZP_SCRATCH_W1", register)
|
||||||
asmgen.assignVariableToRegister(scratchVar, register)
|
|
||||||
} else {
|
} else {
|
||||||
val target: AsmAssignTarget =
|
val target: AsmAssignTarget =
|
||||||
if(parameter.value.type in ByteDatatypes && (register==RegisterOrPair.AX || register == RegisterOrPair.AY || register==RegisterOrPair.XY || register in Cx16VirtualRegisters))
|
if(parameter.value.type in ByteDatatypes && (register==RegisterOrPair.AX || register == RegisterOrPair.AY || register==RegisterOrPair.XY || register in Cx16VirtualRegisters))
|
||||||
|
@ -15,11 +15,11 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
|||||||
val targetIdent = stmt.target.identifier
|
val targetIdent = stmt.target.identifier
|
||||||
val targetMemory = stmt.target.memoryAddress
|
val targetMemory = stmt.target.memoryAddress
|
||||||
val targetArrayIdx = stmt.target.arrayindexed
|
val targetArrayIdx = stmt.target.arrayindexed
|
||||||
val scope = stmt.definingSubroutine()
|
val scope = stmt.definingSubroutine
|
||||||
when {
|
when {
|
||||||
targetIdent!=null -> {
|
targetIdent!=null -> {
|
||||||
val what = asmgen.asmVariableName(targetIdent)
|
val what = asmgen.asmVariableName(targetIdent)
|
||||||
when (stmt.target.inferType(program).typeOrElse(DataType.STRUCT)) {
|
when (stmt.target.inferType(program).getOr(DataType.UNDEFINED)) {
|
||||||
in ByteDatatypes -> asmgen.out(if (incr) " inc $what" else " dec $what")
|
in ByteDatatypes -> asmgen.out(if (incr) " inc $what" else " dec $what")
|
||||||
in WordDatatypes -> {
|
in WordDatatypes -> {
|
||||||
if(incr)
|
if(incr)
|
||||||
@ -65,9 +65,10 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
|||||||
}
|
}
|
||||||
targetArrayIdx!=null -> {
|
targetArrayIdx!=null -> {
|
||||||
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.arrayvar)
|
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.arrayvar)
|
||||||
val elementDt = targetArrayIdx.inferType(program).typeOrElse(DataType.STRUCT)
|
val elementDt = targetArrayIdx.inferType(program).getOr(DataType.UNDEFINED)
|
||||||
if(targetArrayIdx.indexer.indexNum!=null) {
|
val constIndex = targetArrayIdx.indexer.constIndex()
|
||||||
val indexValue = targetArrayIdx.indexer.constIndex()!! * program.memsizer.memorySize(elementDt)
|
if(constIndex!=null) {
|
||||||
|
val indexValue = constIndex * program.memsizer.memorySize(elementDt)
|
||||||
when(elementDt) {
|
when(elementDt) {
|
||||||
in ByteDatatypes -> asmgen.out(if (incr) " inc $asmArrayvarname+$indexValue" else " dec $asmArrayvarname+$indexValue")
|
in ByteDatatypes -> asmgen.out(if (incr) " inc $asmArrayvarname+$indexValue" else " dec $asmArrayvarname+$indexValue")
|
||||||
in WordDatatypes -> {
|
in WordDatatypes -> {
|
||||||
|
@ -59,11 +59,11 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
|
|||||||
val idt = inferType(program)
|
val idt = inferType(program)
|
||||||
if(!idt.isKnown)
|
if(!idt.isKnown)
|
||||||
throw AssemblyError("unknown dt")
|
throw AssemblyError("unknown dt")
|
||||||
val dt = idt.typeOrElse(DataType.STRUCT)
|
val dt = idt.getOr(DataType.UNDEFINED)
|
||||||
when {
|
when {
|
||||||
identifier != null -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, assign.definingSubroutine(), variableAsmName = asmgen.asmVariableName(identifier!!), 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)
|
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)
|
memoryAddress != null -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, dt, assign.definingSubroutine, memory = memoryAddress, origAstTarget = this)
|
||||||
else -> throw AssemblyError("weird target")
|
else -> throw AssemblyError("weird target")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -120,13 +120,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
|||||||
asmgen.asmVariableName(array.arrayvar)
|
asmgen.asmVariableName(array.arrayvar)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromAstSource(indexer: ArrayIndex, program: Program, asmgen: AsmGen): AsmAssignSource {
|
fun fromAstSource(indexer: ArrayIndex, program: Program, asmgen: AsmGen): AsmAssignSource = fromAstSource(indexer.indexExpr, program, asmgen)
|
||||||
return when {
|
|
||||||
indexer.indexNum!=null -> fromAstSource(indexer.indexNum!!, program, asmgen)
|
|
||||||
indexer.indexVar!=null -> fromAstSource(indexer.indexVar!!, program, asmgen)
|
|
||||||
else -> throw AssemblyError("weird indexer")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun fromAstSource(value: Expression, program: Program, asmgen: AsmGen): AsmAssignSource {
|
fun fromAstSource(value: Expression, program: Program, asmgen: AsmGen): AsmAssignSource {
|
||||||
val cv = value.constValue(program)
|
val cv = value.constValue(program)
|
||||||
@ -138,12 +132,12 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
|||||||
is StringLiteralValue -> throw AssemblyError("string literal value should not occur anymore for asm generation")
|
is StringLiteralValue -> throw AssemblyError("string literal value should not occur anymore for asm generation")
|
||||||
is ArrayLiteralValue -> throw AssemblyError("array literal value should not occur anymore for asm generation")
|
is ArrayLiteralValue -> throw AssemblyError("array literal value should not occur anymore for asm generation")
|
||||||
is IdentifierReference -> {
|
is IdentifierReference -> {
|
||||||
val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
|
val dt = value.inferType(program).getOr(DataType.UNDEFINED)
|
||||||
val varName=asmgen.asmVariableName(value)
|
val varName=asmgen.asmVariableName(value)
|
||||||
// special case: "cx16.r[0-15]" are 16-bits virtual registers of the commander X16 system
|
// special case: "cx16.r[0-15]" are 16-bits virtual registers of the commander X16 system
|
||||||
if(dt==DataType.UWORD && varName.toLowerCase().startsWith("cx16.r")) {
|
if(dt == DataType.UWORD && varName.lowercase().startsWith("cx16.r")) {
|
||||||
val regStr = varName.toLowerCase().substring(5)
|
val regStr = varName.lowercase().substring(5)
|
||||||
val reg = RegisterOrPair.valueOf(regStr.toUpperCase())
|
val reg = RegisterOrPair.valueOf(regStr.uppercase())
|
||||||
AsmAssignSource(SourceStorageKind.REGISTER, program, asmgen, dt, register = reg)
|
AsmAssignSource(SourceStorageKind.REGISTER, program, asmgen, dt, register = reg)
|
||||||
} else {
|
} else {
|
||||||
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, dt, variableAsmName = varName)
|
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, dt, variableAsmName = varName)
|
||||||
@ -153,7 +147,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
|||||||
AsmAssignSource(SourceStorageKind.MEMORY, program, asmgen, DataType.UBYTE, memory = value)
|
AsmAssignSource(SourceStorageKind.MEMORY, program, asmgen, DataType.UBYTE, memory = value)
|
||||||
}
|
}
|
||||||
is ArrayIndexedExpression -> {
|
is ArrayIndexedExpression -> {
|
||||||
val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
|
val dt = value.inferType(program).getOr(DataType.UNDEFINED)
|
||||||
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, dt, array = value)
|
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, dt, array = value)
|
||||||
}
|
}
|
||||||
is FunctionCall -> {
|
is FunctionCall -> {
|
||||||
@ -168,7 +162,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
|||||||
val returnType = value.inferType(program)
|
val returnType = value.inferType(program)
|
||||||
if(!returnType.isKnown)
|
if(!returnType.isKnown)
|
||||||
throw AssemblyError("unknown dt")
|
throw AssemblyError("unknown dt")
|
||||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType.typeOrElse(DataType.STRUCT), expression = value)
|
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType.getOr(DataType.UNDEFINED), expression = value)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
throw AssemblyError("weird call")
|
throw AssemblyError("weird call")
|
||||||
@ -179,7 +173,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
|||||||
val dt = value.inferType(program)
|
val dt = value.inferType(program)
|
||||||
if(!dt.isKnown)
|
if(!dt.isKnown)
|
||||||
throw AssemblyError("unknown dt")
|
throw AssemblyError("unknown dt")
|
||||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, dt.typeOrElse(DataType.STRUCT), expression = value)
|
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, dt.getOr(DataType.UNDEFINED), expression = value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -211,8 +205,8 @@ internal class AsmAssignment(val source: AsmAssignSource,
|
|||||||
val position: Position) {
|
val position: Position) {
|
||||||
|
|
||||||
init {
|
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.STRUCT) { "must not be placeholder datatype" }
|
require(source.datatype != DataType.UNDEFINED) { "must not be placeholder/undefined datatype" }
|
||||||
require(memsizer.memorySize(source.datatype) <= memsizer.memorySize(target.datatype)) {
|
require(memsizer.memorySize(source.datatype) <= memsizer.memorySize(target.datatype)) {
|
||||||
"source storage size must be less or equal to target datatype storage size"
|
"source storage size must be less or equal to target datatype storage size"
|
||||||
}
|
}
|
||||||
|
@ -65,9 +65,10 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
val value = assign.source.array!!
|
val value = assign.source.array!!
|
||||||
val elementDt = assign.source.datatype
|
val elementDt = assign.source.datatype
|
||||||
val arrayVarName = asmgen.asmVariableName(value.arrayvar)
|
val arrayVarName = asmgen.asmVariableName(value.arrayvar)
|
||||||
if (value.indexer.indexNum!=null) {
|
val constIndex = value.indexer.constIndex()
|
||||||
|
if (constIndex!=null) {
|
||||||
// constant array index value
|
// constant array index value
|
||||||
val indexValue = value.indexer.constIndex()!! * program.memsizer.memorySize(elementDt)
|
val indexValue = constIndex * program.memsizer.memorySize(elementDt)
|
||||||
when (elementDt) {
|
when (elementDt) {
|
||||||
in ByteDatatypes -> {
|
in ByteDatatypes -> {
|
||||||
asmgen.out(" lda $arrayVarName+$indexValue")
|
asmgen.out(" lda $arrayVarName+$indexValue")
|
||||||
@ -114,7 +115,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
}
|
}
|
||||||
SourceStorageKind.MEMORY -> {
|
SourceStorageKind.MEMORY -> {
|
||||||
fun assignViaExprEval(expression: Expression) {
|
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))
|
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||||
asmgen.out(" lda (P8ZP_SCRATCH_W2)")
|
asmgen.out(" lda (P8ZP_SCRATCH_W2)")
|
||||||
else
|
else
|
||||||
@ -144,7 +145,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
SourceStorageKind.EXPRESSION -> {
|
SourceStorageKind.EXPRESSION -> {
|
||||||
when(val value = assign.source.expression!!) {
|
when(val value = assign.source.expression!!) {
|
||||||
is AddressOf -> {
|
is AddressOf -> {
|
||||||
val sourceName = value.identifier.firstStructVarName(program) ?: asmgen.asmVariableName(value.identifier)
|
val sourceName = asmgen.asmSymbolName(value.identifier)
|
||||||
assignAddressOf(assign.target, sourceName)
|
assignAddressOf(assign.target, sourceName)
|
||||||
}
|
}
|
||||||
is NumericLiteralValue -> throw AssemblyError("source kind should have been literalnumber")
|
is NumericLiteralValue -> throw AssemblyError("source kind should have been literalnumber")
|
||||||
@ -216,7 +217,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
val returntype = builtinFunctionReturnType(sub.name, value.args, program)
|
val returntype = builtinFunctionReturnType(sub.name, value.args, program)
|
||||||
if(!returntype.isKnown)
|
if(!returntype.isKnown)
|
||||||
throw AssemblyError("unknown dt")
|
throw AssemblyError("unknown dt")
|
||||||
when(returntype.typeOrElse(DataType.STRUCT)) {
|
when(returntype.getOr(DataType.UNDEFINED)) {
|
||||||
in ByteDatatypes -> assignRegisterByte(assign.target, CpuRegister.A) // function's byte result is in A
|
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
|
in WordDatatypes -> assignRegisterpairWord(assign.target, RegisterOrPair.AY) // function's word result is in AY
|
||||||
DataType.STR -> {
|
DataType.STR -> {
|
||||||
@ -298,7 +299,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
val valueIDt = value.inferType(program)
|
val valueIDt = value.inferType(program)
|
||||||
if(!valueIDt.isKnown)
|
if(!valueIDt.isKnown)
|
||||||
throw AssemblyError("unknown dt")
|
throw AssemblyError("unknown dt")
|
||||||
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
|
val valueDt = valueIDt.getOr(DataType.UNDEFINED)
|
||||||
if(valueDt==targetDt)
|
if(valueDt==targetDt)
|
||||||
throw AssemblyError("type cast to identical dt should have been removed")
|
throw AssemblyError("type cast to identical dt should have been removed")
|
||||||
|
|
||||||
@ -319,7 +320,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
if(targetDt in WordDatatypes) {
|
if(targetDt in WordDatatypes) {
|
||||||
|
|
||||||
fun assignViaExprEval(addressExpression: Expression) {
|
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))
|
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||||
asmgen.out(" lda (P8ZP_SCRATCH_W2)")
|
asmgen.out(" lda (P8ZP_SCRATCH_W2)")
|
||||||
else
|
else
|
||||||
@ -358,7 +359,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
|
|
||||||
// special case optimizations
|
// special case optimizations
|
||||||
if(target.kind== TargetStorageKind.VARIABLE) {
|
if(target.kind== TargetStorageKind.VARIABLE) {
|
||||||
if(value is IdentifierReference && valueDt != DataType.STRUCT)
|
if(value is IdentifierReference && valueDt != DataType.UNDEFINED)
|
||||||
return assignTypeCastedIdentifier(target.asmVarname, targetDt, asmgen.asmVariableName(value), valueDt)
|
return assignTypeCastedIdentifier(target.asmVarname, targetDt, asmgen.asmVariableName(value), valueDt)
|
||||||
|
|
||||||
when (valueDt) {
|
when (valueDt) {
|
||||||
@ -397,7 +398,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
RegisterOrPair.A,
|
RegisterOrPair.A,
|
||||||
RegisterOrPair.X,
|
RegisterOrPair.X,
|
||||||
RegisterOrPair.Y -> {
|
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)
|
return assignExpressionToRegister(value, target.register)
|
||||||
}
|
}
|
||||||
RegisterOrPair.AX,
|
RegisterOrPair.AX,
|
||||||
@ -430,8 +431,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
}
|
}
|
||||||
|
|
||||||
// give up, do it via eval stack
|
// give up, do it via eval stack
|
||||||
// TODO optimize typecasts for more special cases?
|
|
||||||
// note: cannot use assignTypeCastedValue because that is ourselves :P
|
// note: cannot use assignTypeCastedValue because that is ourselves :P
|
||||||
|
// TODO optimize typecasts for more special cases?
|
||||||
if(this.asmgen.options.slowCodegenWarnings)
|
if(this.asmgen.options.slowCodegenWarnings)
|
||||||
println("warning: slow stack evaluation used for typecast: $value into $targetDt (target=${target.kind} at ${value.position}")
|
println("warning: slow stack evaluation used for typecast: $value into $targetDt (target=${target.kind} at ${value.position}")
|
||||||
asmgen.translateExpression(origTypeCastExpression) // this performs the actual type cast in translateExpression(Typecast)
|
asmgen.translateExpression(origTypeCastExpression) // this performs the actual type cast in translateExpression(Typecast)
|
||||||
@ -570,11 +571,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
else -> throw AssemblyError("weird type")
|
else -> throw AssemblyError("weird type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.STR -> {
|
DataType.STR -> throw AssemblyError("cannot typecast a string value")
|
||||||
if (targetDt != DataType.UWORD && targetDt == DataType.STR)
|
|
||||||
throw AssemblyError("cannot typecast a string into another incompatitble type")
|
|
||||||
TODO("assign typecasted string into target var")
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird type")
|
else -> throw AssemblyError("weird type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -590,13 +587,19 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
DataType.UBYTE -> {
|
DataType.UBYTE -> {
|
||||||
when(targetDt) {
|
when(targetDt) {
|
||||||
DataType.UBYTE, DataType.BYTE -> {
|
DataType.UBYTE, DataType.BYTE -> {
|
||||||
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName")
|
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName")
|
||||||
}
|
}
|
||||||
DataType.UWORD, DataType.WORD -> {
|
DataType.UWORD, DataType.WORD -> {
|
||||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||||
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName | stz $targetAsmVarName+1")
|
asmgen.out(
|
||||||
|
" st${
|
||||||
|
regs.toString().lowercase()
|
||||||
|
} $targetAsmVarName | stz $targetAsmVarName+1")
|
||||||
else
|
else
|
||||||
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
|
asmgen.out(
|
||||||
|
" st${
|
||||||
|
regs.toString().lowercase()
|
||||||
|
} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> {
|
DataType.FLOAT -> {
|
||||||
when(regs) {
|
when(regs) {
|
||||||
@ -618,13 +621,19 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
DataType.BYTE -> {
|
DataType.BYTE -> {
|
||||||
when(targetDt) {
|
when(targetDt) {
|
||||||
DataType.UBYTE, DataType.BYTE -> {
|
DataType.UBYTE, DataType.BYTE -> {
|
||||||
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName")
|
asmgen.out(" st${regs.toString().lowercase()} $targetAsmVarName")
|
||||||
}
|
}
|
||||||
DataType.UWORD -> {
|
DataType.UWORD -> {
|
||||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||||
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName | stz $targetAsmVarName+1")
|
asmgen.out(
|
||||||
|
" st${
|
||||||
|
regs.toString().lowercase()
|
||||||
|
} $targetAsmVarName | stz $targetAsmVarName+1")
|
||||||
else
|
else
|
||||||
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
|
asmgen.out(
|
||||||
|
" st${
|
||||||
|
regs.toString().lowercase()
|
||||||
|
} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
|
||||||
}
|
}
|
||||||
DataType.WORD -> {
|
DataType.WORD -> {
|
||||||
when(regs) {
|
when(regs) {
|
||||||
@ -656,7 +665,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
DataType.UWORD -> {
|
DataType.UWORD -> {
|
||||||
when(targetDt) {
|
when(targetDt) {
|
||||||
DataType.BYTE, DataType.UBYTE -> {
|
DataType.BYTE, DataType.UBYTE -> {
|
||||||
asmgen.out(" st${regs.toString().toLowerCase().first()} $targetAsmVarName")
|
asmgen.out(" st${regs.toString().lowercase().first()} $targetAsmVarName")
|
||||||
}
|
}
|
||||||
DataType.WORD, DataType.UWORD -> {
|
DataType.WORD, DataType.UWORD -> {
|
||||||
when(regs) {
|
when(regs) {
|
||||||
@ -684,7 +693,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
DataType.WORD -> {
|
DataType.WORD -> {
|
||||||
when(targetDt) {
|
when(targetDt) {
|
||||||
DataType.BYTE, DataType.UBYTE -> {
|
DataType.BYTE, DataType.UBYTE -> {
|
||||||
asmgen.out(" st${regs.toString().toLowerCase().first()} $targetAsmVarName")
|
asmgen.out(" st${regs.toString().lowercase().first()} $targetAsmVarName")
|
||||||
}
|
}
|
||||||
DataType.WORD, DataType.UWORD -> {
|
DataType.WORD, DataType.UWORD -> {
|
||||||
when(regs) {
|
when(regs) {
|
||||||
@ -709,11 +718,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
else -> throw AssemblyError("weird type")
|
else -> throw AssemblyError("weird type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.STR -> {
|
DataType.STR -> throw AssemblyError("cannot typecast a string value")
|
||||||
if (targetDt != DataType.UWORD && targetDt == DataType.STR)
|
|
||||||
throw AssemblyError("cannot typecast a string into another incompatitble type")
|
|
||||||
TODO("assign typecasted string into target var")
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird type")
|
else -> throw AssemblyError("weird type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -829,12 +834,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
RegisterOrPair.AX -> asmgen.out(" inx | txy | ldx #0 | lda P8ESTACK_LO,y")
|
RegisterOrPair.AX -> asmgen.out(" inx | txy | ldx #0 | lda P8ESTACK_LO,y")
|
||||||
RegisterOrPair.AY -> asmgen.out(" inx | ldy #0 | lda P8ESTACK_LO,x")
|
RegisterOrPair.AY -> asmgen.out(" inx | ldy #0 | lda P8ESTACK_LO,x")
|
||||||
in Cx16VirtualRegisters -> {
|
in Cx16VirtualRegisters -> {
|
||||||
asmgen.out("""
|
asmgen.out(
|
||||||
|
"""
|
||||||
inx
|
inx
|
||||||
lda P8ESTACK_LO,x
|
lda P8ESTACK_LO,x
|
||||||
sta cx16.${target.register.toString().toLowerCase()}
|
sta cx16.${target.register.toString().lowercase()}
|
||||||
lda #0
|
lda #0
|
||||||
sta cx16.${target.register.toString().toLowerCase()}+1
|
sta cx16.${target.register.toString().lowercase()}+1
|
||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("can't assign byte from stack to register pair XY")
|
else -> throw AssemblyError("can't assign byte from stack to register pair XY")
|
||||||
@ -846,12 +852,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
RegisterOrPair.AY-> asmgen.out(" inx | ldy P8ESTACK_HI,x | lda P8ESTACK_LO,x")
|
RegisterOrPair.AY-> asmgen.out(" inx | ldy P8ESTACK_HI,x | lda P8ESTACK_LO,x")
|
||||||
RegisterOrPair.XY-> throw AssemblyError("can't load X from stack here - use intermediary var? ${target.origAstTarget?.position}")
|
RegisterOrPair.XY-> throw AssemblyError("can't load X from stack here - use intermediary var? ${target.origAstTarget?.position}")
|
||||||
in Cx16VirtualRegisters -> {
|
in Cx16VirtualRegisters -> {
|
||||||
asmgen.out("""
|
asmgen.out(
|
||||||
|
"""
|
||||||
inx
|
inx
|
||||||
lda P8ESTACK_LO,x
|
lda P8ESTACK_LO,x
|
||||||
sta cx16.${target.register.toString().toLowerCase()}
|
sta cx16.${target.register.toString().lowercase()}
|
||||||
lda P8ESTACK_HI,x
|
lda P8ESTACK_HI,x
|
||||||
sta cx16.${target.register.toString().toLowerCase()}+1
|
sta cx16.${target.register.toString().lowercase()}+1
|
||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("can't assign word to single byte register")
|
else -> throw AssemblyError("can't assign word to single byte register")
|
||||||
@ -895,11 +902,12 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
RegisterOrPair.AY -> asmgen.out(" ldy #>$sourceName | lda #<$sourceName")
|
RegisterOrPair.AY -> asmgen.out(" ldy #>$sourceName | lda #<$sourceName")
|
||||||
RegisterOrPair.XY -> asmgen.out(" ldy #>$sourceName | ldx #<$sourceName")
|
RegisterOrPair.XY -> asmgen.out(" ldy #>$sourceName | ldx #<$sourceName")
|
||||||
in Cx16VirtualRegisters -> {
|
in Cx16VirtualRegisters -> {
|
||||||
asmgen.out("""
|
asmgen.out(
|
||||||
|
"""
|
||||||
lda #<$sourceName
|
lda #<$sourceName
|
||||||
sta cx16.${target.register.toString().toLowerCase()}
|
sta cx16.${target.register.toString().lowercase()}
|
||||||
lda #>$sourceName
|
lda #>$sourceName
|
||||||
sta cx16.${target.register.toString().toLowerCase()}+1
|
sta cx16.${target.register.toString().lowercase()}+1
|
||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("can't load address in a single 8-bit register")
|
else -> throw AssemblyError("can't load address in a single 8-bit register")
|
||||||
@ -1036,11 +1044,12 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
RegisterOrPair.AY -> asmgen.out(" ldy $sourceName+1 | lda $sourceName")
|
RegisterOrPair.AY -> asmgen.out(" ldy $sourceName+1 | lda $sourceName")
|
||||||
RegisterOrPair.XY -> asmgen.out(" ldy $sourceName+1 | ldx $sourceName")
|
RegisterOrPair.XY -> asmgen.out(" ldy $sourceName+1 | ldx $sourceName")
|
||||||
in Cx16VirtualRegisters -> {
|
in Cx16VirtualRegisters -> {
|
||||||
asmgen.out("""
|
asmgen.out(
|
||||||
|
"""
|
||||||
lda $sourceName
|
lda $sourceName
|
||||||
sta cx16.${target.register.toString().toLowerCase()}
|
sta cx16.${target.register.toString().lowercase()}
|
||||||
lda $sourceName+1
|
lda $sourceName+1
|
||||||
sta cx16.${target.register.toString().toLowerCase()}+1
|
sta cx16.${target.register.toString().lowercase()}+1
|
||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("can't load word in a single 8-bit register")
|
else -> throw AssemblyError("can't load word in a single 8-bit register")
|
||||||
@ -1074,11 +1083,11 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
ldy #>${target.asmVarname}
|
ldy #>${target.asmVarname}
|
||||||
sta P8ZP_SCRATCH_W1
|
sta P8ZP_SCRATCH_W1
|
||||||
sty P8ZP_SCRATCH_W1+1""")
|
sty P8ZP_SCRATCH_W1+1""")
|
||||||
if(target.array!!.indexer.indexNum!=null) {
|
val constIndex = target.array!!.indexer.constIndex()
|
||||||
val index = target.array.indexer.constIndex()!!
|
if(constIndex!=null) {
|
||||||
asmgen.out(" lda #$index")
|
asmgen.out(" lda #$constIndex")
|
||||||
} else {
|
} else {
|
||||||
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexVar!!)
|
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexExpr as IdentifierReference)
|
||||||
asmgen.out(" lda $asmvarname")
|
asmgen.out(" lda $asmvarname")
|
||||||
}
|
}
|
||||||
asmgen.out(" jsr floats.set_array_float_from_fac1")
|
asmgen.out(" jsr floats.set_array_float_from_fac1")
|
||||||
@ -1110,11 +1119,11 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
ldy #>${target.asmVarname}
|
ldy #>${target.asmVarname}
|
||||||
sta P8ZP_SCRATCH_W2
|
sta P8ZP_SCRATCH_W2
|
||||||
sty P8ZP_SCRATCH_W2+1""")
|
sty P8ZP_SCRATCH_W2+1""")
|
||||||
if(target.array!!.indexer.indexNum!=null) {
|
val constIndex = target.array!!.indexer.constIndex()
|
||||||
val index = target.array.indexer.constIndex()!!
|
if(constIndex!=null) {
|
||||||
asmgen.out(" lda #$index")
|
asmgen.out(" lda #$constIndex")
|
||||||
} else {
|
} else {
|
||||||
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexVar!!)
|
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexExpr as IdentifierReference)
|
||||||
asmgen.out(" lda $asmvarname")
|
asmgen.out(" lda $asmvarname")
|
||||||
}
|
}
|
||||||
asmgen.out(" jsr floats.set_array_float")
|
asmgen.out(" jsr floats.set_array_float")
|
||||||
@ -1157,11 +1166,11 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
ldy #>${target.asmVarname}
|
ldy #>${target.asmVarname}
|
||||||
sta P8ZP_SCRATCH_W2
|
sta P8ZP_SCRATCH_W2
|
||||||
sty P8ZP_SCRATCH_W2+1""")
|
sty P8ZP_SCRATCH_W2+1""")
|
||||||
if(target.array!!.indexer.indexNum!=null) {
|
val constIndex = target.array!!.indexer.constIndex()
|
||||||
val index = target.array.indexer.constIndex()!!
|
if(constIndex!=null) {
|
||||||
asmgen.out(" lda #$index")
|
asmgen.out(" lda #$constIndex")
|
||||||
} else {
|
} else {
|
||||||
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexVar!!)
|
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexExpr as IdentifierReference)
|
||||||
asmgen.out(" lda $asmvarname")
|
asmgen.out(" lda $asmvarname")
|
||||||
}
|
}
|
||||||
asmgen.out(" jsr floats.set_array_float")
|
asmgen.out(" jsr floats.set_array_float")
|
||||||
@ -1210,11 +1219,12 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
RegisterOrPair.XY -> asmgen.out(" ldy #0 | ldx $sourceName")
|
RegisterOrPair.XY -> asmgen.out(" ldy #0 | ldx $sourceName")
|
||||||
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected typecasted byte to float")
|
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected typecasted byte to float")
|
||||||
in Cx16VirtualRegisters -> {
|
in Cx16VirtualRegisters -> {
|
||||||
asmgen.out("""
|
asmgen.out(
|
||||||
|
"""
|
||||||
lda $sourceName
|
lda $sourceName
|
||||||
sta cx16.${target.register.toString().toLowerCase()}
|
sta cx16.${target.register.toString().lowercase()}
|
||||||
lda #0
|
lda #0
|
||||||
sta cx16.${target.register.toString().toLowerCase()}+1
|
sta cx16.${target.register.toString().lowercase()}+1
|
||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("weird register")
|
else -> throw AssemblyError("weird register")
|
||||||
@ -1243,11 +1253,20 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
TargetStorageKind.ARRAY -> {
|
TargetStorageKind.ARRAY -> {
|
||||||
// TODO optimize slow stack evaluation for this case, see assignVariableUByteIntoWord
|
if (wordtarget.constArrayIndexValue!=null) {
|
||||||
if(this.asmgen.options.slowCodegenWarnings)
|
val scaledIdx = wordtarget.constArrayIndexValue!! * 2
|
||||||
println("warning: slow stack evaluation used for sign-extend byte typecast at ${bytevar.position}")
|
asmgen.out(" lda $sourceName")
|
||||||
asmgen.translateExpression(wordtarget.origAssign.source.expression!!)
|
asmgen.signExtendAYlsb(DataType.BYTE)
|
||||||
assignStackValue(wordtarget)
|
asmgen.out(" sta ${wordtarget.asmVarname}+$scaledIdx | sty ${wordtarget.asmVarname}+$scaledIdx+1")
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
asmgen.saveRegisterLocal(CpuRegister.X, wordtarget.scope!!)
|
||||||
|
asmgen.loadScaledArrayIndexIntoRegister(wordtarget.array!!, wordtarget.datatype, CpuRegister.X)
|
||||||
|
asmgen.out(" lda $sourceName")
|
||||||
|
asmgen.signExtendAYlsb(DataType.BYTE)
|
||||||
|
asmgen.out(" sta ${wordtarget.asmVarname},x | inx | tya | sta ${wordtarget.asmVarname},x")
|
||||||
|
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
TargetStorageKind.REGISTER -> {
|
TargetStorageKind.REGISTER -> {
|
||||||
when(wordtarget.register!!) {
|
when(wordtarget.register!!) {
|
||||||
@ -1340,12 +1359,22 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal fun assignRegisterByte(target: AsmAssignTarget, register: CpuRegister) {
|
internal fun assignRegisterByte(target: AsmAssignTarget, register: CpuRegister) {
|
||||||
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
|
||||||
require(target.datatype in ByteDatatypes)
|
// these will be correctly typecasted from a byte to a word value
|
||||||
|
if(target.register !in Cx16VirtualRegisters &&
|
||||||
|
target.register!=RegisterOrPair.AX && target.register!=RegisterOrPair.AY && target.register!=RegisterOrPair.XY) {
|
||||||
|
if(target.kind==TargetStorageKind.VARIABLE) {
|
||||||
|
val parts = target.asmVarname.split('.')
|
||||||
|
if (parts.size != 2 || parts[0] != "cx16")
|
||||||
|
require(target.datatype in ByteDatatypes)
|
||||||
|
} else {
|
||||||
|
require(target.datatype in ByteDatatypes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
when(target.kind) {
|
when(target.kind) {
|
||||||
TargetStorageKind.VARIABLE -> {
|
TargetStorageKind.VARIABLE -> {
|
||||||
asmgen.out(" st${register.name.toLowerCase()} ${target.asmVarname}")
|
asmgen.out(" st${register.name.lowercase()} ${target.asmVarname}")
|
||||||
}
|
}
|
||||||
TargetStorageKind.MEMORY -> {
|
TargetStorageKind.MEMORY -> {
|
||||||
when(register) {
|
when(register) {
|
||||||
@ -1369,7 +1398,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
CpuRegister.X -> asmgen.out(" txa")
|
CpuRegister.X -> asmgen.out(" txa")
|
||||||
CpuRegister.Y -> asmgen.out(" tya")
|
CpuRegister.Y -> asmgen.out(" tya")
|
||||||
}
|
}
|
||||||
asmgen.out(" ldy ${asmgen.asmVariableName(target.array!!.indexer.indexVar!!)} | sta ${target.asmVarname},y")
|
val indexVar = target.array!!.indexer.indexExpr as IdentifierReference
|
||||||
|
asmgen.out(" ldy ${asmgen.asmVariableName(indexVar)} | sta ${target.asmVarname},y")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TargetStorageKind.REGISTER -> {
|
TargetStorageKind.REGISTER -> {
|
||||||
@ -1384,7 +1414,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected type cast to float")
|
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected type cast to float")
|
||||||
in Cx16VirtualRegisters -> {
|
in Cx16VirtualRegisters -> {
|
||||||
// only assign a single byte to the virtual register's Lsb
|
// only assign a single byte to the virtual register's Lsb
|
||||||
asmgen.out(" sta cx16.${target.register.toString().toLowerCase()}")
|
asmgen.out(" sta cx16.${target.register.toString().lowercase()}")
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("weird register")
|
else -> throw AssemblyError("weird register")
|
||||||
}
|
}
|
||||||
@ -1398,7 +1428,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected type cast to float")
|
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected type cast to float")
|
||||||
in Cx16VirtualRegisters -> {
|
in Cx16VirtualRegisters -> {
|
||||||
// only assign a single byte to the virtual register's Lsb
|
// only assign a single byte to the virtual register's Lsb
|
||||||
asmgen.out(" stx cx16.${target.register.toString().toLowerCase()}")
|
asmgen.out(" stx cx16.${target.register.toString().lowercase()}")
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("weird register")
|
else -> throw AssemblyError("weird register")
|
||||||
}
|
}
|
||||||
@ -1412,7 +1442,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected type cast to float")
|
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected type cast to float")
|
||||||
in Cx16VirtualRegisters -> {
|
in Cx16VirtualRegisters -> {
|
||||||
// only assign a single byte to the virtual register's Lsb
|
// only assign a single byte to the virtual register's Lsb
|
||||||
asmgen.out(" sty cx16.${target.register.toString().toLowerCase()}")
|
asmgen.out(" sty cx16.${target.register.toString().lowercase()}")
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("weird register")
|
else -> throw AssemblyError("weird register")
|
||||||
}
|
}
|
||||||
@ -1502,9 +1532,10 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
RegisterOrPair.AX -> { }
|
RegisterOrPair.AX -> { }
|
||||||
RegisterOrPair.XY -> { asmgen.out(" stx P8ZP_SCRATCH_REG | ldy P8ZP_SCRATCH_REG | tax") }
|
RegisterOrPair.XY -> { asmgen.out(" stx P8ZP_SCRATCH_REG | ldy P8ZP_SCRATCH_REG | tax") }
|
||||||
in Cx16VirtualRegisters -> {
|
in Cx16VirtualRegisters -> {
|
||||||
asmgen.out("""
|
asmgen.out(
|
||||||
sta cx16.${target.register.toString().toLowerCase()}
|
"""
|
||||||
stx cx16.${target.register.toString().toLowerCase()}+1
|
sta cx16.${target.register.toString().lowercase()}
|
||||||
|
stx cx16.${target.register.toString().lowercase()}+1
|
||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("expected reg pair or cx16 virtual 16-bit register")
|
else -> throw AssemblyError("expected reg pair or cx16 virtual 16-bit register")
|
||||||
@ -1514,9 +1545,10 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
RegisterOrPair.AX -> { asmgen.out(" sty P8ZP_SCRATCH_REG | ldx P8ZP_SCRATCH_REG") }
|
RegisterOrPair.AX -> { asmgen.out(" sty P8ZP_SCRATCH_REG | ldx P8ZP_SCRATCH_REG") }
|
||||||
RegisterOrPair.XY -> { asmgen.out(" tax") }
|
RegisterOrPair.XY -> { asmgen.out(" tax") }
|
||||||
in Cx16VirtualRegisters -> {
|
in Cx16VirtualRegisters -> {
|
||||||
asmgen.out("""
|
asmgen.out(
|
||||||
sta cx16.${target.register.toString().toLowerCase()}
|
"""
|
||||||
sty cx16.${target.register.toString().toLowerCase()}+1
|
sta cx16.${target.register.toString().lowercase()}
|
||||||
|
sty cx16.${target.register.toString().lowercase()}+1
|
||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("expected reg pair or cx16 virtual 16-bit register")
|
else -> throw AssemblyError("expected reg pair or cx16 virtual 16-bit register")
|
||||||
@ -1526,9 +1558,10 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
RegisterOrPair.AX -> { asmgen.out(" txa | sty P8ZP_SCRATCH_REG | ldx P8ZP_SCRATCH_REG") }
|
RegisterOrPair.AX -> { asmgen.out(" txa | sty P8ZP_SCRATCH_REG | ldx P8ZP_SCRATCH_REG") }
|
||||||
RegisterOrPair.XY -> { }
|
RegisterOrPair.XY -> { }
|
||||||
in Cx16VirtualRegisters -> {
|
in Cx16VirtualRegisters -> {
|
||||||
asmgen.out("""
|
asmgen.out(
|
||||||
stx cx16.${target.register.toString().toLowerCase()}
|
"""
|
||||||
sty cx16.${target.register.toString().toLowerCase()}+1
|
stx cx16.${target.register.toString().lowercase()}
|
||||||
|
sty cx16.${target.register.toString().lowercase()}+1
|
||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("expected reg pair or cx16 virtual 16-bit register")
|
else -> throw AssemblyError("expected reg pair or cx16 virtual 16-bit register")
|
||||||
@ -1595,7 +1628,10 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
RegisterOrPair.AY -> asmgen.out(" lda #0 | tay")
|
RegisterOrPair.AY -> asmgen.out(" lda #0 | tay")
|
||||||
RegisterOrPair.XY -> asmgen.out(" ldx #0 | ldy #0")
|
RegisterOrPair.XY -> asmgen.out(" ldx #0 | ldy #0")
|
||||||
in Cx16VirtualRegisters -> {
|
in Cx16VirtualRegisters -> {
|
||||||
asmgen.out(" stz cx16.${target.register.toString().toLowerCase()} | stz cx16.${target.register.toString().toLowerCase()}+1")
|
asmgen.out(
|
||||||
|
" stz cx16.${
|
||||||
|
target.register.toString().lowercase()
|
||||||
|
} | stz cx16.${target.register.toString().lowercase()}+1")
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("invalid register for word value")
|
else -> throw AssemblyError("invalid register for word value")
|
||||||
}
|
}
|
||||||
@ -1645,11 +1681,12 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
RegisterOrPair.AY -> asmgen.out(" ldy #>${word.toHex()} | lda #<${word.toHex()}")
|
RegisterOrPair.AY -> asmgen.out(" ldy #>${word.toHex()} | lda #<${word.toHex()}")
|
||||||
RegisterOrPair.XY -> asmgen.out(" ldy #>${word.toHex()} | ldx #<${word.toHex()}")
|
RegisterOrPair.XY -> asmgen.out(" ldy #>${word.toHex()} | ldx #<${word.toHex()}")
|
||||||
in Cx16VirtualRegisters -> {
|
in Cx16VirtualRegisters -> {
|
||||||
asmgen.out("""
|
asmgen.out(
|
||||||
|
"""
|
||||||
lda #<${word.toHex()}
|
lda #<${word.toHex()}
|
||||||
sta cx16.${target.register.toString().toLowerCase()}
|
sta cx16.${target.register.toString().lowercase()}
|
||||||
lda #>${word.toHex()}
|
lda #>${word.toHex()}
|
||||||
sta cx16.${target.register.toString().toLowerCase()}+1
|
sta cx16.${target.register.toString().lowercase()}+1
|
||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("invalid register for word value")
|
else -> throw AssemblyError("invalid register for word value")
|
||||||
@ -1696,7 +1733,10 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
RegisterOrPair.XY -> asmgen.out(" ldx #0 | ldy #0")
|
RegisterOrPair.XY -> asmgen.out(" ldx #0 | ldy #0")
|
||||||
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected typecasted byte to float")
|
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected typecasted byte to float")
|
||||||
in Cx16VirtualRegisters -> {
|
in Cx16VirtualRegisters -> {
|
||||||
asmgen.out(" stz cx16.${target.register.toString().toLowerCase()} | stz cx16.${target.register.toString().toLowerCase()}+1")
|
asmgen.out(
|
||||||
|
" stz cx16.${
|
||||||
|
target.register.toString().lowercase()
|
||||||
|
} | stz cx16.${target.register.toString().lowercase()}+1")
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("weird register")
|
else -> throw AssemblyError("weird register")
|
||||||
}
|
}
|
||||||
@ -1736,11 +1776,17 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
RegisterOrPair.XY -> asmgen.out(" ldy #0 | ldx #${byte.toHex()}")
|
RegisterOrPair.XY -> asmgen.out(" ldy #0 | ldx #${byte.toHex()}")
|
||||||
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected typecasted byte to float")
|
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected typecasted byte to float")
|
||||||
in Cx16VirtualRegisters -> {
|
in Cx16VirtualRegisters -> {
|
||||||
asmgen.out(" lda #${byte.toHex()} | sta cx16.${target.register.toString().toLowerCase()}")
|
asmgen.out(
|
||||||
|
" lda #${byte.toHex()} | sta cx16.${
|
||||||
|
target.register.toString().lowercase()
|
||||||
|
}")
|
||||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||||
asmgen.out(" stz cx16.${target.register.toString().toLowerCase()}+1\n")
|
asmgen.out(" stz cx16.${target.register.toString().lowercase()}+1\n")
|
||||||
else
|
else
|
||||||
asmgen.out(" lda #0 | sta cx16.${target.register.toString().toLowerCase()}+1\n")
|
asmgen.out(
|
||||||
|
" lda #0 | sta cx16.${
|
||||||
|
target.register.toString().lowercase()
|
||||||
|
}+1\n")
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("weird register")
|
else -> throw AssemblyError("weird register")
|
||||||
}
|
}
|
||||||
@ -1777,8 +1823,9 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
TargetStorageKind.ARRAY -> {
|
TargetStorageKind.ARRAY -> {
|
||||||
if (target.array!!.indexer.indexNum!=null) {
|
val constIndex = target.array!!.indexer.constIndex()
|
||||||
val indexValue = target.array.indexer.constIndex()!! * program.memsizer.memorySize(DataType.FLOAT)
|
if (constIndex!=null) {
|
||||||
|
val indexValue = constIndex * program.memsizer.memorySize(DataType.FLOAT)
|
||||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
stz ${target.asmVarname}+$indexValue
|
stz ${target.asmVarname}+$indexValue
|
||||||
@ -1797,7 +1844,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
sta ${target.asmVarname}+$indexValue+4
|
sta ${target.asmVarname}+$indexValue+4
|
||||||
""")
|
""")
|
||||||
} else {
|
} else {
|
||||||
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexVar!!)
|
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexExpr as IdentifierReference)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda #<${target.asmVarname}
|
lda #<${target.asmVarname}
|
||||||
sta P8ZP_SCRATCH_W1
|
sta P8ZP_SCRATCH_W1
|
||||||
@ -1842,8 +1889,9 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
}
|
}
|
||||||
TargetStorageKind.ARRAY -> {
|
TargetStorageKind.ARRAY -> {
|
||||||
val arrayVarName = target.asmVarname
|
val arrayVarName = target.asmVarname
|
||||||
if (target.array!!.indexer.indexNum!=null) {
|
val constIndex = target.array!!.indexer.constIndex()
|
||||||
val indexValue = target.array.indexer.constIndex()!! * program.memsizer.memorySize(DataType.FLOAT)
|
if (constIndex!=null) {
|
||||||
|
val indexValue = constIndex * program.memsizer.memorySize(DataType.FLOAT)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda $constFloat
|
lda $constFloat
|
||||||
sta $arrayVarName+$indexValue
|
sta $arrayVarName+$indexValue
|
||||||
@ -1857,7 +1905,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
sta $arrayVarName+$indexValue+4
|
sta $arrayVarName+$indexValue+4
|
||||||
""")
|
""")
|
||||||
} else {
|
} else {
|
||||||
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexVar!!)
|
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexExpr as IdentifierReference)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda #<${constFloat}
|
lda #<${constFloat}
|
||||||
sta P8ZP_SCRATCH_W1
|
sta P8ZP_SCRATCH_W1
|
||||||
@ -1914,11 +1962,12 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
RegisterOrPair.XY -> asmgen.out(" ldy #0 | ldy ${address.toHex()}")
|
RegisterOrPair.XY -> asmgen.out(" ldy #0 | ldy ${address.toHex()}")
|
||||||
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected typecasted byte to float")
|
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected typecasted byte to float")
|
||||||
in Cx16VirtualRegisters -> {
|
in Cx16VirtualRegisters -> {
|
||||||
asmgen.out("""
|
asmgen.out(
|
||||||
|
"""
|
||||||
lda ${address.toHex()}
|
lda ${address.toHex()}
|
||||||
sta cx16.${target.register.toString().toLowerCase()}
|
sta cx16.${target.register.toString().lowercase()}
|
||||||
lda #0
|
lda #0
|
||||||
sta cx16.${target.register.toString().toLowerCase()}+1
|
sta cx16.${target.register.toString().lowercase()}+1
|
||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("weird register")
|
else -> throw AssemblyError("weird register")
|
||||||
@ -1954,10 +2003,11 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
RegisterOrPair.XY -> asmgen.out(" tax | ldy #0")
|
RegisterOrPair.XY -> asmgen.out(" tax | ldy #0")
|
||||||
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected typecasted byte to float")
|
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected typecasted byte to float")
|
||||||
in Cx16VirtualRegisters -> {
|
in Cx16VirtualRegisters -> {
|
||||||
asmgen.out("""
|
asmgen.out(
|
||||||
sta cx16.${target.register.toString().toLowerCase()}
|
"""
|
||||||
|
sta cx16.${target.register.toString().lowercase()}
|
||||||
lda #0
|
lda #0
|
||||||
sta cx16.${target.register.toString().toLowerCase()}+1
|
sta cx16.${target.register.toString().lowercase()}+1
|
||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("weird register")
|
else -> throw AssemblyError("weird register")
|
||||||
@ -2041,7 +2091,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
fun storeViaExprEval() {
|
fun storeViaExprEval() {
|
||||||
when(addressExpr) {
|
when(addressExpr) {
|
||||||
is NumericLiteralValue, is IdentifierReference -> {
|
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))
|
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||||
asmgen.out(" sta (P8ZP_SCRATCH_W2)")
|
asmgen.out(" sta (P8ZP_SCRATCH_W2)")
|
||||||
else
|
else
|
||||||
@ -2050,7 +2100,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
|||||||
else -> {
|
else -> {
|
||||||
// same as above but we need to save the A register
|
// same as above but we need to save the A register
|
||||||
asmgen.out(" pha")
|
asmgen.out(" pha")
|
||||||
assignExpressionToVariable(addressExpr, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
|
assignExpressionToVariable(addressExpr, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
|
||||||
asmgen.out(" pla")
|
asmgen.out(" pla")
|
||||||
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||||
asmgen.out(" sta (P8ZP_SCRATCH_W2)")
|
asmgen.out(" sta (P8ZP_SCRATCH_W2)")
|
||||||
|
@ -25,7 +25,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
val itype = value.inferType(program)
|
val itype = value.inferType(program)
|
||||||
if(!itype.isKnown)
|
if(!itype.isKnown)
|
||||||
throw AssemblyError("unknown dt")
|
throw AssemblyError("unknown dt")
|
||||||
val type = itype.typeOrElse(DataType.STRUCT)
|
val type = itype.getOr(DataType.UNDEFINED)
|
||||||
when (value.operator) {
|
when (value.operator) {
|
||||||
"+" -> {}
|
"+" -> {}
|
||||||
"-" -> inplaceNegate(assign.target, type)
|
"-" -> inplaceNegate(assign.target, type)
|
||||||
@ -110,7 +110,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
val ident = value as? IdentifierReference
|
val ident = value as? IdentifierReference
|
||||||
val memread = value as? DirectMemoryRead
|
val memread = value as? DirectMemoryRead
|
||||||
|
|
||||||
when(target.kind) {
|
when (target.kind) {
|
||||||
TargetStorageKind.VARIABLE -> {
|
TargetStorageKind.VARIABLE -> {
|
||||||
when (target.datatype) {
|
when (target.datatype) {
|
||||||
in ByteDatatypes -> {
|
in ByteDatatypes -> {
|
||||||
@ -160,7 +160,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
when {
|
when {
|
||||||
valueLv != null -> inplaceModification_byte_litval_to_variable(addr.toHex(), DataType.UBYTE, operator, valueLv.toInt())
|
valueLv != null -> inplaceModification_byte_litval_to_variable(addr.toHex(), DataType.UBYTE, operator, valueLv.toInt())
|
||||||
ident != null -> inplaceModification_byte_variable_to_variable(addr.toHex(), DataType.UBYTE, operator, ident)
|
ident != null -> inplaceModification_byte_variable_to_variable(addr.toHex(), DataType.UBYTE, operator, ident)
|
||||||
// TODO more specialized code for types such as memory read etc. -> inplaceModification_byte_memread_to_variable()
|
memread != null -> inplaceModification_byte_memread_to_variable(addr.toHex(), DataType.UBYTE, operator, value)
|
||||||
value is TypecastExpression -> {
|
value is TypecastExpression -> {
|
||||||
if (tryRemoveRedundantCast(value, target, operator)) return
|
if (tryRemoveRedundantCast(value, target, operator)) return
|
||||||
inplaceModification_byte_value_to_variable(addr.toHex(), DataType.UBYTE, operator, value)
|
inplaceModification_byte_value_to_variable(addr.toHex(), DataType.UBYTE, operator, value)
|
||||||
@ -199,10 +199,12 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
TargetStorageKind.ARRAY -> {
|
TargetStorageKind.ARRAY -> {
|
||||||
with(target.array!!.indexer) {
|
with(target.array!!.indexer) {
|
||||||
|
val indexNum = indexExpr as? NumericLiteralValue
|
||||||
|
val indexVar = indexExpr as? IdentifierReference
|
||||||
when {
|
when {
|
||||||
indexNum!=null -> {
|
indexNum!=null -> {
|
||||||
val targetVarName = "${target.asmVarname} + ${indexNum!!.number.toInt()*program.memsizer.memorySize(target.datatype)}"
|
val targetVarName = "${target.asmVarname} + ${indexNum.number.toInt()*program.memsizer.memorySize(target.datatype)}"
|
||||||
when(target.datatype) {
|
when (target.datatype) {
|
||||||
in ByteDatatypes -> {
|
in ByteDatatypes -> {
|
||||||
when {
|
when {
|
||||||
valueLv != null -> inplaceModification_byte_litval_to_variable(targetVarName, target.datatype, operator, valueLv.toInt())
|
valueLv != null -> inplaceModification_byte_litval_to_variable(targetVarName, target.datatype, operator, valueLv.toInt())
|
||||||
@ -242,7 +244,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
indexVar!=null -> {
|
indexVar!=null -> {
|
||||||
when(target.datatype) {
|
when (target.datatype) {
|
||||||
in ByteDatatypes -> {
|
in ByteDatatypes -> {
|
||||||
val tgt = AsmAssignTarget.fromRegisters(RegisterOrPair.A, null, program, asmgen)
|
val tgt = AsmAssignTarget.fromRegisters(RegisterOrPair.A, null, program, asmgen)
|
||||||
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
|
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
|
||||||
@ -268,8 +270,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TargetStorageKind.REGISTER -> TODO("reg in-place modification")
|
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg in-place modification")
|
||||||
TargetStorageKind.STACK -> TODO("stack in-place modification")
|
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack in-place modification")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,7 +280,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
val childIDt = value.expression.inferType(program)
|
val childIDt = value.expression.inferType(program)
|
||||||
if(!childIDt.isKnown)
|
if(!childIDt.isKnown)
|
||||||
throw AssemblyError("unknown dt")
|
throw AssemblyError("unknown dt")
|
||||||
val childDt = childIDt.typeOrElse(DataType.STRUCT)
|
val childDt = childIDt.getOr(DataType.UNDEFINED)
|
||||||
if (value.type!=DataType.FLOAT && (value.type.equalsSize(childDt) || value.type.largerThan(childDt))) {
|
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.
|
// 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.)
|
// (works for integer types, not for float.)
|
||||||
@ -291,7 +293,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
|
|
||||||
private fun inplaceModification_byte_value_to_pointer(pointervar: IdentifierReference, operator: String, value: Expression) {
|
private fun inplaceModification_byte_value_to_pointer(pointervar: IdentifierReference, operator: String, value: Expression) {
|
||||||
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
|
||||||
when (operator) {
|
when (operator) {
|
||||||
// note: ** (power) operator requires floats.
|
// note: ** (power) operator requires floats.
|
||||||
"+" -> asmgen.out(" clc | adc P8ZP_SCRATCH_B1")
|
"+" -> asmgen.out(" clc | adc P8ZP_SCRATCH_B1")
|
||||||
@ -320,18 +321,15 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
"&", "and" -> asmgen.out(" and P8ZP_SCRATCH_B1")
|
"&", "and" -> asmgen.out(" and P8ZP_SCRATCH_B1")
|
||||||
"|", "or" -> asmgen.out(" ora P8ZP_SCRATCH_B1")
|
"|", "or" -> asmgen.out(" ora P8ZP_SCRATCH_B1")
|
||||||
"^", "xor" -> asmgen.out(" eor P8ZP_SCRATCH_B1")
|
"^", "xor" -> asmgen.out(" eor P8ZP_SCRATCH_B1")
|
||||||
in comparisonOperators -> TODO("in-place modification for $operator")
|
|
||||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||||
}
|
}
|
||||||
if(ptrOnZp)
|
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||||
asmgen.out(" sta ($sourceName),y")
|
asmgen.out(" sta ($sourceName),y")
|
||||||
else
|
|
||||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun inplaceModification_byte_variable_to_pointer(pointervar: IdentifierReference, operator: String, value: IdentifierReference) {
|
private fun inplaceModification_byte_variable_to_pointer(pointervar: IdentifierReference, operator: String, value: IdentifierReference) {
|
||||||
val otherName = asmgen.asmVariableName(value)
|
val otherName = asmgen.asmVariableName(value)
|
||||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||||
|
|
||||||
when (operator) {
|
when (operator) {
|
||||||
// note: ** (power) operator requires floats.
|
// note: ** (power) operator requires floats.
|
||||||
@ -361,110 +359,75 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
"&", "and" -> asmgen.out(" and $otherName")
|
"&", "and" -> asmgen.out(" and $otherName")
|
||||||
"|", "or" -> asmgen.out(" ora $otherName")
|
"|", "or" -> asmgen.out(" ora $otherName")
|
||||||
"^", "xor" -> asmgen.out(" eor $otherName")
|
"^", "xor" -> asmgen.out(" eor $otherName")
|
||||||
in comparisonOperators -> TODO("in-place modification for $operator")
|
|
||||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||||
}
|
}
|
||||||
if(ptrOnZp)
|
asmgen.out(" sta ($sourceName),y")
|
||||||
asmgen.out(" sta ($sourceName),y")
|
|
||||||
else
|
|
||||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun inplaceModification_byte_litval_to_pointer(pointervar: IdentifierReference, operator: String, value: Int) {
|
private fun inplaceModification_byte_litval_to_pointer(pointervar: IdentifierReference, operator: String, value: Int) {
|
||||||
when (operator) {
|
when (operator) {
|
||||||
// note: ** (power) operator requires floats.
|
// note: ** (power) operator requires floats.
|
||||||
"+" -> {
|
"+" -> {
|
||||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||||
asmgen.out(" clc | adc #$value")
|
asmgen.out(" clc | adc #$value")
|
||||||
if(ptrOnZp)
|
asmgen.out(" sta ($sourceName),y")
|
||||||
asmgen.out(" sta ($sourceName),y")
|
|
||||||
else
|
|
||||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
|
||||||
}
|
}
|
||||||
"-" -> {
|
"-" -> {
|
||||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||||
asmgen.out(" sec | sbc #$value")
|
asmgen.out(" sec | sbc #$value")
|
||||||
if(ptrOnZp)
|
asmgen.out(" sta ($sourceName),y")
|
||||||
asmgen.out(" sta ($sourceName),y")
|
|
||||||
else
|
|
||||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
|
||||||
}
|
}
|
||||||
"*" -> {
|
"*" -> {
|
||||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||||
if(value in asmgen.optimizedByteMultiplications)
|
if(value in asmgen.optimizedByteMultiplications)
|
||||||
asmgen.out(" jsr math.mul_byte_${value}")
|
asmgen.out(" jsr math.mul_byte_${value}")
|
||||||
else
|
else
|
||||||
asmgen.out(" ldy #$value | jsr math.multiply_bytes | ldy #0")
|
asmgen.out(" ldy #$value | jsr math.multiply_bytes | ldy #0")
|
||||||
if(ptrOnZp)
|
asmgen.out(" sta ($sourceName),y")
|
||||||
asmgen.out(" sta ($sourceName),y")
|
|
||||||
else
|
|
||||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
|
||||||
}
|
}
|
||||||
"/" -> {
|
"/" -> {
|
||||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||||
if(value==0)
|
if(value==0)
|
||||||
throw AssemblyError("division by zero")
|
throw AssemblyError("division by zero")
|
||||||
asmgen.out(" ldy #$value | jsr math.divmod_ub_asm | tya | ldy #0")
|
asmgen.out(" ldy #$value | jsr math.divmod_ub_asm | tya | ldy #0")
|
||||||
if(ptrOnZp)
|
asmgen.out(" sta ($sourceName),y")
|
||||||
asmgen.out(" sta ($sourceName),y")
|
|
||||||
else
|
|
||||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
|
||||||
}
|
}
|
||||||
"%" -> {
|
"%" -> {
|
||||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||||
if(value==0)
|
if(value==0)
|
||||||
throw AssemblyError("division by zero")
|
throw AssemblyError("division by zero")
|
||||||
asmgen.out(" ldy #$value | jsr math.divmod_ub_asm | ldy #0")
|
asmgen.out(" ldy #$value | jsr math.divmod_ub_asm | ldy #0")
|
||||||
if(ptrOnZp)
|
asmgen.out(" sta ($sourceName),y")
|
||||||
asmgen.out(" sta ($sourceName),y")
|
|
||||||
else
|
|
||||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
|
||||||
}
|
}
|
||||||
"<<" -> {
|
"<<" -> {
|
||||||
if (value > 0) {
|
if (value > 0) {
|
||||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||||
repeat(value) { asmgen.out(" asl a") }
|
repeat(value) { asmgen.out(" asl a") }
|
||||||
if(ptrOnZp)
|
asmgen.out(" sta ($sourceName),y")
|
||||||
asmgen.out(" sta ($sourceName),y")
|
|
||||||
else
|
|
||||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
">>" -> {
|
">>" -> {
|
||||||
if (value > 0) {
|
if (value > 0) {
|
||||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||||
repeat(value) { asmgen.out(" lsr a") }
|
repeat(value) { asmgen.out(" lsr a") }
|
||||||
if(ptrOnZp)
|
asmgen.out(" sta ($sourceName),y")
|
||||||
asmgen.out(" sta ($sourceName),y")
|
|
||||||
else
|
|
||||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"&", "and" -> {
|
"&", "and" -> {
|
||||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||||
asmgen.out(" and #$value")
|
asmgen.out(" and #$value")
|
||||||
if(ptrOnZp)
|
asmgen.out(" sta ($sourceName),y")
|
||||||
asmgen.out(" sta ($sourceName),y")
|
|
||||||
else
|
|
||||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
|
||||||
}
|
}
|
||||||
"|", "or" -> {
|
"|", "or" -> {
|
||||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||||
asmgen.out(" ora #$value")
|
asmgen.out(" ora #$value")
|
||||||
if(ptrOnZp)
|
asmgen.out(" sta ($sourceName),y")
|
||||||
asmgen.out(" sta ($sourceName),y")
|
|
||||||
else
|
|
||||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
|
||||||
}
|
}
|
||||||
"^", "xor" -> {
|
"^", "xor" -> {
|
||||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||||
asmgen.out(" eor #$value")
|
asmgen.out(" eor #$value")
|
||||||
if(ptrOnZp)
|
asmgen.out(" sta ($sourceName),y")
|
||||||
asmgen.out(" sta ($sourceName),y")
|
|
||||||
else
|
|
||||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
|
||||||
}
|
}
|
||||||
in comparisonOperators -> TODO("in-place modification for $operator")
|
|
||||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -540,7 +503,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
|
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
|
||||||
asmgen.out(" eor $name | sta $name")
|
asmgen.out(" eor $name | sta $name")
|
||||||
}
|
}
|
||||||
in comparisonOperators -> TODO("in-place modification for $operator")
|
|
||||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -598,7 +560,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
"&", "and" -> asmgen.out(" lda $name | and $otherName | sta $name")
|
"&", "and" -> asmgen.out(" lda $name | and $otherName | sta $name")
|
||||||
"|", "or" -> asmgen.out(" lda $name | ora $otherName | sta $name")
|
"|", "or" -> asmgen.out(" lda $name | ora $otherName | sta $name")
|
||||||
"^", "xor" -> asmgen.out(" lda $name | eor $otherName | sta $name")
|
"^", "xor" -> asmgen.out(" lda $name | eor $otherName | sta $name")
|
||||||
in comparisonOperators -> TODO("in-place modification for $operator")
|
|
||||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -670,13 +631,12 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
"&", "and" -> asmgen.out(" lda $name | and #$value | sta $name")
|
"&", "and" -> asmgen.out(" lda $name | and #$value | sta $name")
|
||||||
"|", "or" -> asmgen.out(" lda $name | ora #$value | sta $name")
|
"|", "or" -> asmgen.out(" lda $name | ora #$value | sta $name")
|
||||||
"^", "xor" -> asmgen.out(" lda $name | eor #$value | sta $name")
|
"^", "xor" -> asmgen.out(" lda $name | eor #$value | sta $name")
|
||||||
in comparisonOperators -> TODO("in-place modification for $operator")
|
|
||||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun inplaceModification_byte_memread_to_variable(name: String, dt: DataType, operator: String, memread: DirectMemoryRead) {
|
private fun inplaceModification_byte_memread_to_variable(name: String, dt: DataType, operator: String, memread: DirectMemoryRead) {
|
||||||
when(operator) {
|
when (operator) {
|
||||||
"+" -> {
|
"+" -> {
|
||||||
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
@ -692,8 +652,20 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
sec
|
sec
|
||||||
sbc P8ZP_SCRATCH_B1
|
sbc P8ZP_SCRATCH_B1
|
||||||
sta $name""")
|
sta $name""")
|
||||||
// TODO: tuned code for more operators
|
|
||||||
}
|
}
|
||||||
|
"|", "or" -> {
|
||||||
|
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
||||||
|
asmgen.out(" ora $name | sta $name")
|
||||||
|
}
|
||||||
|
"&", "and" -> {
|
||||||
|
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
||||||
|
asmgen.out(" and $name | sta $name")
|
||||||
|
}
|
||||||
|
"^", "xor" -> {
|
||||||
|
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
||||||
|
asmgen.out(" eor $name | sta $name")
|
||||||
|
}
|
||||||
|
// TODO: tuned code for more operators
|
||||||
else -> {
|
else -> {
|
||||||
inplaceModification_byte_value_to_variable(name, dt, operator, memread)
|
inplaceModification_byte_value_to_variable(name, dt, operator, memread)
|
||||||
}
|
}
|
||||||
@ -701,7 +673,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun inplaceModification_word_memread_to_variable(name: String, dt: DataType, operator: String, memread: DirectMemoryRead) {
|
private fun inplaceModification_word_memread_to_variable(name: String, dt: DataType, operator: String, memread: DirectMemoryRead) {
|
||||||
when(operator) {
|
when (operator) {
|
||||||
"+" -> {
|
"+" -> {
|
||||||
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
@ -723,8 +695,26 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
bcc +
|
bcc +
|
||||||
dec $name+1
|
dec $name+1
|
||||||
+""")
|
+""")
|
||||||
// TODO: tuned code for more operators
|
|
||||||
}
|
}
|
||||||
|
"|", "or" -> {
|
||||||
|
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
||||||
|
asmgen.out(" ora $name | sta $name")
|
||||||
|
}
|
||||||
|
"&", "and" -> {
|
||||||
|
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
||||||
|
asmgen.out(" and $name | sta $name")
|
||||||
|
if(dt in WordDatatypes) {
|
||||||
|
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||||
|
asmgen.out(" stz $name+1")
|
||||||
|
else
|
||||||
|
asmgen.out(" lda #0 | sta $name+1")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"^", "xor" -> {
|
||||||
|
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
||||||
|
asmgen.out(" eor $name | sta $name")
|
||||||
|
}
|
||||||
|
// TODO: tuned code for more operators
|
||||||
else -> {
|
else -> {
|
||||||
inplaceModification_word_value_to_variable(name, dt, operator, memread)
|
inplaceModification_word_value_to_variable(name, dt, operator, memread)
|
||||||
}
|
}
|
||||||
@ -978,15 +968,13 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
else -> asmgen.out(" lda $name | eor #<$value | sta $name | lda $name+1 | eor #>$value | sta $name+1")
|
else -> asmgen.out(" lda $name | eor #<$value | sta $name | lda $name+1 | eor #>$value | sta $name+1")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
in comparisonOperators -> TODO("in-place modification for $operator")
|
|
||||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun inplaceModification_word_variable_to_variable(name: String, dt: DataType, operator: String, ident: IdentifierReference) {
|
private fun inplaceModification_word_variable_to_variable(name: String, dt: DataType, operator: String, ident: IdentifierReference) {
|
||||||
val otherName = asmgen.asmVariableName(ident)
|
val otherName = asmgen.asmVariableName(ident)
|
||||||
val valueDt = ident.targetVarDecl(program)!!.datatype
|
when (val valueDt = ident.targetVarDecl(program)!!.datatype) {
|
||||||
when (valueDt) {
|
|
||||||
in ByteDatatypes -> {
|
in ByteDatatypes -> {
|
||||||
// the other variable is a BYTE type so optimize for that
|
// the other variable is a BYTE type so optimize for that
|
||||||
when (operator) {
|
when (operator) {
|
||||||
@ -1054,8 +1042,49 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
lda math.multiply_words.result+1
|
lda math.multiply_words.result+1
|
||||||
sta $name+1""")
|
sta $name+1""")
|
||||||
}
|
}
|
||||||
"/" -> TODO("div (u)wordvar/bytevar")
|
"/" -> {
|
||||||
"%" -> TODO("(u)word remainder bytevar")
|
if(dt==DataType.UWORD) {
|
||||||
|
asmgen.out("""
|
||||||
|
lda $name
|
||||||
|
ldy $name+1
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
lda $otherName
|
||||||
|
ldy #0
|
||||||
|
jsr math.divmod_uw_asm
|
||||||
|
sta $name
|
||||||
|
sty $name+1
|
||||||
|
""")
|
||||||
|
} else {
|
||||||
|
asmgen.out("""
|
||||||
|
lda $name
|
||||||
|
ldy $name+1
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
lda $otherName
|
||||||
|
ldy #0
|
||||||
|
jsr math.divmod_w_asm
|
||||||
|
sta $name
|
||||||
|
sty $name+1
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"%" -> {
|
||||||
|
if(valueDt!=DataType.UBYTE || dt!=DataType.UWORD)
|
||||||
|
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
|
||||||
|
asmgen.out("""
|
||||||
|
lda $name
|
||||||
|
ldy $name+1
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
lda $otherName
|
||||||
|
ldy #0
|
||||||
|
jsr math.divmod_uw_asm
|
||||||
|
lda P8ZP_SCRATCH_W2
|
||||||
|
sta $name
|
||||||
|
lda P8ZP_SCRATCH_W2+1
|
||||||
|
sta $name+1
|
||||||
|
""") }
|
||||||
"<<" -> {
|
"<<" -> {
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
ldy $otherName
|
ldy $otherName
|
||||||
@ -1100,7 +1129,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
"|", "or" -> asmgen.out(" lda $otherName | ora $name | sta $name")
|
"|", "or" -> asmgen.out(" lda $otherName | ora $name | sta $name")
|
||||||
"^", "xor" -> asmgen.out(" lda $otherName | eor $name | sta $name")
|
"^", "xor" -> asmgen.out(" lda $otherName | eor $name | sta $name")
|
||||||
in comparisonOperators -> TODO("in-place modification for $operator")
|
|
||||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1174,7 +1202,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
"&", "and" -> asmgen.out(" lda $name | and $otherName | sta $name | lda $name+1 | and $otherName+1 | sta $name+1")
|
"&", "and" -> asmgen.out(" lda $name | and $otherName | sta $name | lda $name+1 | and $otherName+1 | sta $name+1")
|
||||||
"|", "or" -> asmgen.out(" lda $name | ora $otherName | sta $name | lda $name+1 | ora $otherName+1 | sta $name+1")
|
"|", "or" -> asmgen.out(" lda $name | ora $otherName | sta $name | lda $name+1 | ora $otherName+1 | sta $name+1")
|
||||||
"^", "xor" -> asmgen.out(" lda $name | eor $otherName | sta $name | lda $name+1 | eor $otherName+1 | sta $name+1")
|
"^", "xor" -> asmgen.out(" lda $name | eor $otherName | sta $name | lda $name+1 | eor $otherName+1 | sta $name+1")
|
||||||
in comparisonOperators -> TODO("in-place modification for $operator")
|
|
||||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1191,7 +1218,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
val valueiDt = value.inferType(program)
|
val valueiDt = value.inferType(program)
|
||||||
if(!valueiDt.isKnown)
|
if(!valueiDt.isKnown)
|
||||||
throw AssemblyError("unknown dt")
|
throw AssemblyError("unknown dt")
|
||||||
val valueDt = valueiDt.typeOrElse(DataType.STRUCT)
|
val valueDt = valueiDt.getOr(DataType.UNDEFINED)
|
||||||
|
|
||||||
fun multiplyVarByWordInAY() {
|
fun multiplyVarByWordInAY() {
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
@ -1240,7 +1267,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
|
|
||||||
when(valueDt) {
|
when (valueDt) {
|
||||||
in ByteDatatypes -> {
|
in ByteDatatypes -> {
|
||||||
// the other variable is a BYTE type so optimize for that
|
// the other variable is a BYTE type so optimize for that
|
||||||
when (operator) {
|
when (operator) {
|
||||||
@ -1297,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
|
// 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.assignExpressionToRegister(value, RegisterOrPair.A)
|
||||||
asmgen.signExtendAYlsb(valueDt)
|
asmgen.signExtendAYlsb(valueDt)
|
||||||
multiplyVarByWordInAY()
|
multiplyVarByWordInAY()
|
||||||
}
|
}
|
||||||
"/" -> {
|
"/" -> {
|
||||||
// stack contains (u) byte value, sign extend that and proceed with regular 16 bit operation
|
// 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.assignExpressionToRegister(value, RegisterOrPair.A)
|
||||||
asmgen.signExtendAYlsb(valueDt)
|
asmgen.signExtendAYlsb(valueDt)
|
||||||
divideVarByWordInAY()
|
divideVarByWordInAY()
|
||||||
}
|
}
|
||||||
"%" -> {
|
"%" -> {
|
||||||
// stack contains (u) byte value, sign extend that and proceed with regular 16 bit operation
|
// 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.assignExpressionToRegister(value, RegisterOrPair.A)
|
||||||
asmgen.signExtendAYlsb(valueDt)
|
asmgen.signExtendAYlsb(valueDt)
|
||||||
remainderVarByWordInAY()
|
remainderVarByWordInAY()
|
||||||
@ -1365,7 +1392,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
|
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
|
||||||
asmgen.out(" eor $name | sta $name")
|
asmgen.out(" eor $name | sta $name")
|
||||||
}
|
}
|
||||||
in comparisonOperators -> TODO("in-place modification for $operator")
|
|
||||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1406,7 +1432,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.AY)
|
asmgen.assignExpressionToRegister(value, RegisterOrPair.AY)
|
||||||
asmgen.out(" eor $name | sta $name | tya | eor $name+1 | sta $name+1")
|
asmgen.out(" eor $name | sta $name | tya | eor $name+1 | sta $name+1")
|
||||||
}
|
}
|
||||||
in comparisonOperators -> TODO("in-place modification for $operator")
|
|
||||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1454,7 +1479,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
jsr floats.FDIV
|
jsr floats.FDIV
|
||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
in comparisonOperators -> TODO("in-place float modification for $operator")
|
|
||||||
else -> throw AssemblyError("invalid operator for in-place float modification $operator")
|
else -> throw AssemblyError("invalid operator for in-place float modification $operator")
|
||||||
}
|
}
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
@ -1535,7 +1559,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
jsr floats.FDIV
|
jsr floats.FDIV
|
||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
in comparisonOperators -> TODO("in-place float modification for $operator")
|
|
||||||
else -> throw AssemblyError("invalid operator for in-place float modification $operator")
|
else -> throw AssemblyError("invalid operator for in-place float modification $operator")
|
||||||
}
|
}
|
||||||
// store Fac1 back into memory
|
// store Fac1 back into memory
|
||||||
@ -1620,7 +1643,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
jsr floats.FDIV
|
jsr floats.FDIV
|
||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
in comparisonOperators -> TODO("in-place float modification for $operator")
|
|
||||||
else -> throw AssemblyError("invalid operator for in-place float modification $operator")
|
else -> throw AssemblyError("invalid operator for in-place float modification $operator")
|
||||||
}
|
}
|
||||||
// store Fac1 back into memory
|
// store Fac1 back into memory
|
||||||
@ -1638,11 +1660,11 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
if (innerCastDt == null) {
|
if (innerCastDt == null) {
|
||||||
// simple typecast where the value is the target
|
// simple typecast where the value is the target
|
||||||
when (target.datatype) {
|
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 -> {
|
DataType.UWORD, DataType.WORD -> {
|
||||||
when (outerCastDt) {
|
when (outerCastDt) {
|
||||||
DataType.UBYTE, DataType.BYTE -> {
|
DataType.UBYTE, DataType.BYTE -> {
|
||||||
when(target.kind) {
|
when (target.kind) {
|
||||||
TargetStorageKind.VARIABLE -> {
|
TargetStorageKind.VARIABLE -> {
|
||||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||||
asmgen.out(" stz ${target.asmVarname}+1")
|
asmgen.out(" stz ${target.asmVarname}+1")
|
||||||
@ -1686,7 +1708,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
private fun inplaceBooleanNot(target: AsmAssignTarget, dt: DataType) {
|
private fun inplaceBooleanNot(target: AsmAssignTarget, dt: DataType) {
|
||||||
when (dt) {
|
when (dt) {
|
||||||
DataType.UBYTE -> {
|
DataType.UBYTE -> {
|
||||||
when(target.kind) {
|
when (target.kind) {
|
||||||
TargetStorageKind.VARIABLE -> {
|
TargetStorageKind.VARIABLE -> {
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda ${target.asmVarname}
|
lda ${target.asmVarname}
|
||||||
@ -1708,18 +1730,15 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
sta $addr""")
|
sta $addr""")
|
||||||
}
|
}
|
||||||
is IdentifierReference -> {
|
is IdentifierReference -> {
|
||||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(mem.addressExpression as IdentifierReference)
|
val sourceName = asmgen.loadByteFromPointerIntoA(mem.addressExpression as IdentifierReference)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
beq +
|
beq +
|
||||||
lda #1
|
lda #1
|
||||||
+ eor #1""")
|
+ eor #1""")
|
||||||
if(ptrOnZp)
|
asmgen.out(" sta ($sourceName),y")
|
||||||
asmgen.out(" sta ($sourceName),y")
|
|
||||||
else
|
|
||||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
|
||||||
}
|
}
|
||||||
else -> {
|
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("""
|
asmgen.out("""
|
||||||
ldy #0
|
ldy #0
|
||||||
lda (P8ZP_SCRATCH_W2),y
|
lda (P8ZP_SCRATCH_W2),y
|
||||||
@ -1730,13 +1749,13 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TargetStorageKind.ARRAY -> TODO("in-place not of ubyte array")
|
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place not of ubyte array")
|
||||||
TargetStorageKind.REGISTER -> TODO("reg not")
|
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg not")
|
||||||
TargetStorageKind.STACK -> TODO("stack not")
|
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack not")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.UWORD -> {
|
DataType.UWORD -> {
|
||||||
when(target.kind) {
|
when (target.kind) {
|
||||||
TargetStorageKind.VARIABLE -> {
|
TargetStorageKind.VARIABLE -> {
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda ${target.asmVarname}
|
lda ${target.asmVarname}
|
||||||
@ -1749,9 +1768,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
sta ${target.asmVarname}+1""")
|
sta ${target.asmVarname}+1""")
|
||||||
}
|
}
|
||||||
TargetStorageKind.MEMORY -> throw AssemblyError("no asm gen for uword-memory not")
|
TargetStorageKind.MEMORY -> throw AssemblyError("no asm gen for uword-memory not")
|
||||||
TargetStorageKind.ARRAY -> TODO("in-place not of uword array")
|
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place not of uword array")
|
||||||
TargetStorageKind.REGISTER -> TODO("reg not")
|
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg not")
|
||||||
TargetStorageKind.STACK -> TODO("stack not")
|
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack not")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("boolean-not of invalid type")
|
else -> throw AssemblyError("boolean-not of invalid type")
|
||||||
@ -1761,7 +1780,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
private fun inplaceInvert(target: AsmAssignTarget, dt: DataType) {
|
private fun inplaceInvert(target: AsmAssignTarget, dt: DataType) {
|
||||||
when (dt) {
|
when (dt) {
|
||||||
DataType.UBYTE -> {
|
DataType.UBYTE -> {
|
||||||
when(target.kind) {
|
when (target.kind) {
|
||||||
TargetStorageKind.VARIABLE -> {
|
TargetStorageKind.VARIABLE -> {
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda ${target.asmVarname}
|
lda ${target.asmVarname}
|
||||||
@ -1779,15 +1798,12 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
sta $addr""")
|
sta $addr""")
|
||||||
}
|
}
|
||||||
is IdentifierReference -> {
|
is IdentifierReference -> {
|
||||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(memory.addressExpression as IdentifierReference)
|
val sourceName = asmgen.loadByteFromPointerIntoA(memory.addressExpression as IdentifierReference)
|
||||||
asmgen.out(" eor #255")
|
asmgen.out(" eor #255")
|
||||||
if(ptrOnZp)
|
asmgen.out(" sta ($sourceName),y")
|
||||||
asmgen.out(" sta ($sourceName),y")
|
|
||||||
else
|
|
||||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
|
||||||
}
|
}
|
||||||
else -> {
|
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("""
|
asmgen.out("""
|
||||||
ldy #0
|
ldy #0
|
||||||
lda (P8ZP_SCRATCH_W2),y
|
lda (P8ZP_SCRATCH_W2),y
|
||||||
@ -1796,13 +1812,13 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TargetStorageKind.ARRAY -> TODO("in-place invert ubyte array")
|
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place invert ubyte array")
|
||||||
TargetStorageKind.REGISTER -> TODO("reg invert")
|
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg invert")
|
||||||
TargetStorageKind.STACK -> TODO("stack invert")
|
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack invert")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.UWORD -> {
|
DataType.UWORD -> {
|
||||||
when(target.kind) {
|
when (target.kind) {
|
||||||
TargetStorageKind.VARIABLE -> {
|
TargetStorageKind.VARIABLE -> {
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda ${target.asmVarname}
|
lda ${target.asmVarname}
|
||||||
@ -1813,9 +1829,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
sta ${target.asmVarname}+1""")
|
sta ${target.asmVarname}+1""")
|
||||||
}
|
}
|
||||||
TargetStorageKind.MEMORY -> throw AssemblyError("no asm gen for uword-memory invert")
|
TargetStorageKind.MEMORY -> throw AssemblyError("no asm gen for uword-memory invert")
|
||||||
TargetStorageKind.ARRAY -> TODO("in-place invert uword array")
|
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place invert uword array")
|
||||||
TargetStorageKind.REGISTER -> TODO("reg invert")
|
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg invert")
|
||||||
TargetStorageKind.STACK -> TODO("stack invert")
|
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack invert")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("invert of invalid type")
|
else -> throw AssemblyError("invert of invalid type")
|
||||||
@ -1834,13 +1850,13 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
sta ${target.asmVarname}""")
|
sta ${target.asmVarname}""")
|
||||||
}
|
}
|
||||||
TargetStorageKind.MEMORY -> throw AssemblyError("can't in-place negate memory ubyte")
|
TargetStorageKind.MEMORY -> throw AssemblyError("can't in-place negate memory ubyte")
|
||||||
TargetStorageKind.ARRAY -> TODO("in-place negate byte array")
|
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place negate byte array")
|
||||||
TargetStorageKind.REGISTER -> TODO("reg negate")
|
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg negate")
|
||||||
TargetStorageKind.STACK -> TODO("stack negate")
|
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack negate")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.WORD -> {
|
DataType.WORD -> {
|
||||||
when(target.kind) {
|
when (target.kind) {
|
||||||
TargetStorageKind.VARIABLE -> {
|
TargetStorageKind.VARIABLE -> {
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda #0
|
lda #0
|
||||||
@ -1851,14 +1867,14 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
sbc ${target.asmVarname}+1
|
sbc ${target.asmVarname}+1
|
||||||
sta ${target.asmVarname}+1""")
|
sta ${target.asmVarname}+1""")
|
||||||
}
|
}
|
||||||
TargetStorageKind.ARRAY -> TODO("in-place negate word array")
|
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place negate word array")
|
||||||
TargetStorageKind.MEMORY -> throw AssemblyError("no asm gen for word memory negate")
|
TargetStorageKind.MEMORY -> throw AssemblyError("no asm gen for word memory negate")
|
||||||
TargetStorageKind.REGISTER -> TODO("reg negate")
|
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg negate")
|
||||||
TargetStorageKind.STACK -> TODO("stack negate")
|
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack negate")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> {
|
DataType.FLOAT -> {
|
||||||
when(target.kind) {
|
when (target.kind) {
|
||||||
TargetStorageKind.VARIABLE -> {
|
TargetStorageKind.VARIABLE -> {
|
||||||
// simply flip the sign bit in the float
|
// simply flip the sign bit in the float
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
@ -1867,8 +1883,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
|||||||
sta ${target.asmVarname}+1
|
sta ${target.asmVarname}+1
|
||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
TargetStorageKind.ARRAY -> TODO("in-place negate float array")
|
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place negate float array")
|
||||||
TargetStorageKind.STACK -> TODO("stack float negate")
|
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack float negate")
|
||||||
else -> throw AssemblyError("weird target kind for float")
|
else -> throw AssemblyError("weird target kind for float")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,9 @@ import prog8.compiler.*
|
|||||||
import prog8.compiler.target.CpuType
|
import prog8.compiler.target.CpuType
|
||||||
import prog8.compiler.target.IMachineDefinition
|
import prog8.compiler.target.IMachineDefinition
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition
|
import prog8.compiler.target.c64.C64MachineDefinition
|
||||||
|
import prog8.compiler.target.cbm.viceMonListPostfix
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
internal object CX16MachineDefinition: IMachineDefinition {
|
internal object CX16MachineDefinition: IMachineDefinition {
|
||||||
|
|
||||||
@ -32,10 +34,28 @@ internal object CX16MachineDefinition: IMachineDefinition {
|
|||||||
emptyList()
|
emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun launchEmulator(programName: String) {
|
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
|
||||||
for(emulator in listOf("x16emu")) {
|
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...")
|
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 processb = ProcessBuilder(cmdline).inheritIO()
|
||||||
val process: Process
|
val process: Process
|
||||||
try {
|
try {
|
||||||
@ -77,7 +97,7 @@ internal object CX16MachineDefinition: IMachineDefinition {
|
|||||||
|
|
||||||
|
|
||||||
init {
|
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'")
|
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.
|
// the addresses 0x02 to 0x21 (inclusive) are taken for sixteen virtual 16-bit api registers.
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
package prog8.optimizer
|
package prog8.optimizer
|
||||||
|
|
||||||
|
import prog8.ast.INameScope
|
||||||
import prog8.ast.Node
|
import prog8.ast.Node
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.BinaryExpression
|
||||||
|
import prog8.ast.expressions.augmentAssignmentOperators
|
||||||
import prog8.ast.statements.AssignTarget
|
import prog8.ast.statements.AssignTarget
|
||||||
import prog8.ast.statements.Assignment
|
import prog8.ast.statements.Assignment
|
||||||
import prog8.ast.walk.AstWalker
|
import prog8.ast.walk.AstWalker
|
||||||
import prog8.ast.walk.IAstModification
|
import prog8.ast.walk.IAstModification
|
||||||
|
import prog8.compiler.astprocessing.isInRegularRAMof
|
||||||
import prog8.compiler.target.ICompilationTarget
|
import prog8.compiler.target.ICompilationTarget
|
||||||
|
|
||||||
|
|
||||||
internal class BinExprSplitter(private val program: Program, private val compTarget: ICompilationTarget) : AstWalker() {
|
internal class BinExprSplitter(private val program: Program, private val compTarget: ICompilationTarget) : AstWalker() {
|
||||||
private val noModifications = emptyList<IAstModification>()
|
|
||||||
|
|
||||||
// override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
// override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||||
// TODO somehow if we do this, the resulting code for some programs (cube3d.p8) gets hundreds of bytes larger...:
|
// TODO somehow if we do this, the resulting code for some programs (cube3d.p8) gets hundreds of bytes larger...:
|
||||||
@ -53,17 +55,18 @@ 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)
|
if(assignment.target isSameAs binExpr.left || assignment.target isSameAs binExpr.right)
|
||||||
return noModifications
|
return noModifications
|
||||||
|
|
||||||
if(isSimpleExpression(binExpr.right) && !assignment.isAugmentable) {
|
if(binExpr.right.isSimple && !assignment.isAugmentable) {
|
||||||
val firstAssign = Assignment(assignment.target, binExpr.left, binExpr.left.position)
|
val firstAssign = Assignment(assignment.target.copy(), binExpr.left, binExpr.left.position)
|
||||||
val targetExpr = assignment.target.toExpression()
|
val targetExpr = assignment.target.toExpression()
|
||||||
val augExpr = BinaryExpression(targetExpr, binExpr.operator, binExpr.right, binExpr.right.position)
|
val augExpr = BinaryExpression(targetExpr, binExpr.operator, binExpr.right, binExpr.right.position)
|
||||||
return listOf(
|
return listOf(
|
||||||
IAstModification.InsertBefore(assignment, firstAssign, assignment.definingScope()),
|
IAstModification.ReplaceNode(binExpr, augExpr, assignment),
|
||||||
IAstModification.ReplaceNode(assignment.value, augExpr, assignment))
|
IAstModification.InsertBefore(assignment, firstAssign, assignment.parent as INameScope)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,12 +78,9 @@ X = BinExpr X = LeftExpr
|
|||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isSimpleExpression(expr: Expression) =
|
private fun isSimpleTarget(target: AssignTarget) =
|
||||||
expr is IdentifierReference || expr is NumericLiteralValue || expr is AddressOf || expr is DirectMemoryRead || expr is StringLiteralValue || expr is ArrayLiteralValue || expr is RangeExpr
|
|
||||||
|
|
||||||
private fun isSimpleTarget(target: AssignTarget, program: Program) =
|
|
||||||
if (target.identifier!=null || target.memoryAddress!=null)
|
if (target.identifier!=null || target.memoryAddress!=null)
|
||||||
compTarget.isInRegularRAM(target, program)
|
target.isInRegularRAMof(compTarget.machine)
|
||||||
else
|
else
|
||||||
false
|
false
|
||||||
|
|
||||||
|
@ -1,139 +1,71 @@
|
|||||||
package prog8.optimizer
|
package prog8.optimizer
|
||||||
|
|
||||||
import prog8.ast.INameScope
|
|
||||||
import prog8.ast.Module
|
import prog8.ast.Module
|
||||||
import prog8.ast.Node
|
import prog8.ast.Node
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.DataType
|
|
||||||
import prog8.ast.base.ParentSentinel
|
|
||||||
import prog8.ast.base.Position
|
import prog8.ast.base.Position
|
||||||
|
import prog8.ast.base.VarDeclType
|
||||||
import prog8.ast.expressions.AddressOf
|
import prog8.ast.expressions.AddressOf
|
||||||
import prog8.ast.expressions.FunctionCall
|
import prog8.ast.expressions.FunctionCall
|
||||||
import prog8.ast.expressions.IdentifierReference
|
import prog8.ast.expressions.IdentifierReference
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.ast.walk.IAstVisitor
|
import prog8.ast.walk.IAstVisitor
|
||||||
import prog8.compiler.IErrorReporter
|
import prog8.compiler.IErrorReporter
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
private val alwaysKeepSubroutines = setOf(
|
|
||||||
Pair("main", "start")
|
|
||||||
)
|
|
||||||
|
|
||||||
private val asmJumpRx = Regex("""[\-+a-zA-Z0-9_ \t]+(jmp|jsr|bra)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
|
|
||||||
private val asmRefRx = Regex("""[\-+a-zA-Z0-9_ \t]+(...)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
|
|
||||||
|
|
||||||
|
|
||||||
class CallGraph(private val program: Program, private val asmFileLoader: (filename: String, source: Path)->String) : IAstVisitor {
|
class CallGraph(private val program: Program) : IAstVisitor {
|
||||||
|
|
||||||
val imports = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
|
val imports = mutableMapOf<Module, Set<Module>>().withDefault { setOf() }
|
||||||
val importedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
|
val importedBy = mutableMapOf<Module, Set<Module>>().withDefault { setOf() }
|
||||||
val calls = mutableMapOf<Subroutine, List<Subroutine>>().withDefault { mutableListOf() }
|
val calls = mutableMapOf<Subroutine, Set<Subroutine>>().withDefault { setOf() }
|
||||||
val calledBy = mutableMapOf<Subroutine, List<Node>>().withDefault { mutableListOf() }
|
val calledBy = mutableMapOf<Subroutine, Set<Node>>().withDefault { setOf() }
|
||||||
|
private val allIdentifiersAndTargets = mutableMapOf<Pair<IdentifierReference, Position>, Statement>()
|
||||||
// TODO add dataflow graph: what statements use what variables - can be used to eliminate unused vars
|
private val allAssemblyNodes = mutableListOf<InlineAssembly>()
|
||||||
val usedSymbols = mutableSetOf<Statement>()
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
visit(program)
|
visit(program)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun forAllSubroutines(scope: INameScope, sub: (s: Subroutine) -> Unit) {
|
private val usedSubroutines: Set<Subroutine> by lazy {
|
||||||
fun findSubs(scope: INameScope) {
|
calledBy.keys + program.entrypoint
|
||||||
scope.statements.forEach {
|
}
|
||||||
if (it is Subroutine)
|
|
||||||
sub(it)
|
private val usedBlocks: Set<Block> by lazy {
|
||||||
if (it is INameScope)
|
val blocksFromSubroutines = usedSubroutines.map { it.definingBlock }
|
||||||
findSubs(it)
|
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
|
||||||
|
used.add(target)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
findSubs(scope)
|
|
||||||
|
used + blocksFromLibraries + program.entrypoint.definingBlock
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(program: Program) {
|
private val usedModules: Set<Module> by lazy {
|
||||||
super.visit(program)
|
usedBlocks.map { it.definingModule }.toSet()
|
||||||
|
|
||||||
program.modules.forEach {
|
|
||||||
it.importedBy.clear()
|
|
||||||
it.imports.clear()
|
|
||||||
|
|
||||||
it.importedBy.addAll(importedBy.getValue(it))
|
|
||||||
it.imports.addAll(imports.getValue(it))
|
|
||||||
}
|
|
||||||
|
|
||||||
val rootmodule = program.modules.first()
|
|
||||||
rootmodule.importedBy.add(rootmodule) // don't discard root module
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(block: Block) {
|
|
||||||
if (block.definingModule().isLibraryModule) {
|
|
||||||
// make sure the block is not removed
|
|
||||||
addNodeAndParentScopes(block)
|
|
||||||
}
|
|
||||||
|
|
||||||
super.visit(block)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(directive: Directive) {
|
override fun visit(directive: Directive) {
|
||||||
val thisModule = directive.definingModule()
|
val thisModule = directive.definingModule
|
||||||
if (directive.directive == "%import") {
|
if (directive.directive == "%import") {
|
||||||
val importedModule: Module = program.modules.single { it.name == directive.args[0].name }
|
val importedModule: Module = program.modules.single { it.name == directive.args[0].name }
|
||||||
imports[thisModule] = imports.getValue(thisModule).plus(importedModule)
|
imports[thisModule] = imports.getValue(thisModule) + importedModule
|
||||||
importedBy[importedModule] = importedBy.getValue(importedModule).plus(thisModule)
|
importedBy[importedModule] = importedBy.getValue(importedModule) + thisModule
|
||||||
} else if (directive.directive == "%asminclude") {
|
|
||||||
val asm = asmFileLoader(directive.args[0].str!!, thisModule.source)
|
|
||||||
val scope = directive.definingSubroutine()
|
|
||||||
if(scope!=null) {
|
|
||||||
scanAssemblyCode(asm, directive, scope)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
super.visit(directive)
|
super.visit(directive)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(identifier: IdentifierReference) {
|
|
||||||
// track symbol usage
|
|
||||||
val target = identifier.targetStatement(program)
|
|
||||||
if (target != null) {
|
|
||||||
addNodeAndParentScopes(target)
|
|
||||||
}
|
|
||||||
super.visit(identifier)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addNodeAndParentScopes(stmt: Statement) {
|
|
||||||
usedSymbols.add(stmt)
|
|
||||||
var node: Node = stmt
|
|
||||||
do {
|
|
||||||
if (node is INameScope && node is Statement) {
|
|
||||||
usedSymbols.add(node)
|
|
||||||
}
|
|
||||||
node = node.parent
|
|
||||||
} while (node !is Module && node !is ParentSentinel)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(subroutine: Subroutine) {
|
|
||||||
if (Pair(subroutine.definingScope().name, subroutine.name) in alwaysKeepSubroutines
|
|
||||||
|| subroutine.definingModule().isLibraryModule) {
|
|
||||||
// make sure the entrypoint is mentioned in the used symbols
|
|
||||||
addNodeAndParentScopes(subroutine)
|
|
||||||
}
|
|
||||||
super.visit(subroutine)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(decl: VarDecl) {
|
|
||||||
if (decl.autogeneratedDontRemove || decl.datatype==DataType.STRUCT)
|
|
||||||
addNodeAndParentScopes(decl)
|
|
||||||
else if(decl.parent is Block && decl.definingModule().isLibraryModule)
|
|
||||||
addNodeAndParentScopes(decl)
|
|
||||||
|
|
||||||
super.visit(decl)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(functionCall: FunctionCall) {
|
override fun visit(functionCall: FunctionCall) {
|
||||||
val otherSub = functionCall.target.targetSubroutine(program)
|
val otherSub = functionCall.target.targetSubroutine(program)
|
||||||
if (otherSub != null) {
|
if (otherSub != null) {
|
||||||
functionCall.definingSubroutine()?.let { thisSub ->
|
functionCall.definingSubroutine?.let { thisSub ->
|
||||||
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
|
calls[thisSub] = calls.getValue(thisSub) + otherSub
|
||||||
calledBy[otherSub] = calledBy.getValue(otherSub).plus(functionCall)
|
calledBy[otherSub] = calledBy.getValue(otherSub) + functionCall
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
super.visit(functionCall)
|
super.visit(functionCall)
|
||||||
@ -142,9 +74,9 @@ class CallGraph(private val program: Program, private val asmFileLoader: (filena
|
|||||||
override fun visit(functionCallStatement: FunctionCallStatement) {
|
override fun visit(functionCallStatement: FunctionCallStatement) {
|
||||||
val otherSub = functionCallStatement.target.targetSubroutine(program)
|
val otherSub = functionCallStatement.target.targetSubroutine(program)
|
||||||
if (otherSub != null) {
|
if (otherSub != null) {
|
||||||
functionCallStatement.definingSubroutine()?.let { thisSub ->
|
functionCallStatement.definingSubroutine?.let { thisSub ->
|
||||||
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
|
calls[thisSub] = calls.getValue(thisSub) + otherSub
|
||||||
calledBy[otherSub] = calledBy.getValue(otherSub).plus(functionCallStatement)
|
calledBy[otherSub] = calledBy.getValue(otherSub) + functionCallStatement
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
super.visit(functionCallStatement)
|
super.visit(functionCallStatement)
|
||||||
@ -153,9 +85,9 @@ class CallGraph(private val program: Program, private val asmFileLoader: (filena
|
|||||||
override fun visit(addressOf: AddressOf) {
|
override fun visit(addressOf: AddressOf) {
|
||||||
val otherSub = addressOf.identifier.targetSubroutine(program)
|
val otherSub = addressOf.identifier.targetSubroutine(program)
|
||||||
if(otherSub!=null) {
|
if(otherSub!=null) {
|
||||||
addressOf.definingSubroutine()?.let { thisSub ->
|
addressOf.definingSubroutine?.let { thisSub ->
|
||||||
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
|
calls[thisSub] = calls.getValue(thisSub) + otherSub
|
||||||
calledBy[otherSub] = calledBy.getValue(otherSub).plus(thisSub)
|
calledBy[otherSub] = calledBy.getValue(otherSub) + thisSub
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
super.visit(addressOf)
|
super.visit(addressOf)
|
||||||
@ -164,64 +96,20 @@ class CallGraph(private val program: Program, private val asmFileLoader: (filena
|
|||||||
override fun visit(jump: Jump) {
|
override fun visit(jump: Jump) {
|
||||||
val otherSub = jump.identifier?.targetSubroutine(program)
|
val otherSub = jump.identifier?.targetSubroutine(program)
|
||||||
if (otherSub != null) {
|
if (otherSub != null) {
|
||||||
jump.definingSubroutine()?.let { thisSub ->
|
jump.definingSubroutine?.let { thisSub ->
|
||||||
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
|
calls[thisSub] = calls.getValue(thisSub) + otherSub
|
||||||
calledBy[otherSub] = calledBy.getValue(otherSub).plus(jump)
|
calledBy[otherSub] = calledBy.getValue(otherSub) + jump
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
super.visit(jump)
|
super.visit(jump)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(structDecl: StructDecl) {
|
override fun visit(identifier: IdentifierReference) {
|
||||||
usedSymbols.add(structDecl)
|
allIdentifiersAndTargets[Pair(identifier, identifier.position)] = identifier.targetStatement(program)!!
|
||||||
usedSymbols.addAll(structDecl.statements)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(inlineAssembly: InlineAssembly) {
|
override fun visit(inlineAssembly: InlineAssembly) {
|
||||||
// parse inline asm for subroutine calls (jmp, jsr, bra)
|
allAssemblyNodes.add(inlineAssembly)
|
||||||
val scope = inlineAssembly.definingSubroutine()
|
|
||||||
scanAssemblyCode(inlineAssembly.assembly, inlineAssembly, scope)
|
|
||||||
super.visit(inlineAssembly)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun scanAssemblyCode(asm: String, context: Statement, scope: Subroutine?) {
|
|
||||||
asm.lines().forEach { line ->
|
|
||||||
val matches = asmJumpRx.matchEntire(line)
|
|
||||||
if (matches != null) {
|
|
||||||
val jumptarget = matches.groups[2]?.value
|
|
||||||
if (jumptarget != null && (jumptarget[0].isLetter() || jumptarget[0] == '_')) {
|
|
||||||
val node = program.namespace.lookup(jumptarget.split('.'), context)
|
|
||||||
if (node is Subroutine) {
|
|
||||||
if(scope!=null)
|
|
||||||
calls[scope] = calls.getValue(scope).plus(node)
|
|
||||||
calledBy[node] = calledBy.getValue(node).plus(context)
|
|
||||||
} else if (jumptarget.contains('.')) {
|
|
||||||
// maybe only the first part already refers to a subroutine
|
|
||||||
val node2 = program.namespace.lookup(listOf(jumptarget.substringBefore('.')), context)
|
|
||||||
if (node2 is Subroutine) {
|
|
||||||
if(scope!=null)
|
|
||||||
calls[scope] = calls.getValue(scope).plus(node2)
|
|
||||||
calledBy[node2] = calledBy.getValue(node2).plus(context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val matches2 = asmRefRx.matchEntire(line)
|
|
||||||
if (matches2 != null) {
|
|
||||||
val target = matches2.groups[2]?.value
|
|
||||||
if (target != null && (target[0].isLetter() || target[0] == '_')) {
|
|
||||||
if (target.contains('.')) {
|
|
||||||
val node = program.namespace.lookup(listOf(target.substringBefore('.')), context)
|
|
||||||
if (node is Subroutine) {
|
|
||||||
if(scope!=null)
|
|
||||||
calls[scope] = calls.getValue(scope).plus(node)
|
|
||||||
calledBy[node] = calledBy.getValue(node).plus(context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkRecursiveCalls(errors: IErrorReporter) {
|
fun checkRecursiveCalls(errors: IErrorReporter) {
|
||||||
@ -274,4 +162,39 @@ class CallGraph(private val program: Program, private val asmFileLoader: (filena
|
|||||||
recStack[sub] = false
|
recStack[sub] = false
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun unused(module: Module) = module !in usedModules
|
||||||
|
|
||||||
|
fun unused(sub: Subroutine): Boolean {
|
||||||
|
return sub !in usedSubroutines && !nameInAssemblyCode(sub.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unused(block: Block): Boolean {
|
||||||
|
return block !in usedBlocks && !nameInAssemblyCode(block.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unused(decl: VarDecl): Boolean {
|
||||||
|
if(decl.type!=VarDeclType.VAR || decl.autogeneratedDontRemove || decl.sharedWithAsm)
|
||||||
|
return false
|
||||||
|
|
||||||
|
if(decl.definingBlock !in usedBlocks)
|
||||||
|
return false
|
||||||
|
|
||||||
|
val allReferencedVardecls = allIdentifiersAndTargets.filter { it.value is VarDecl }.map { it.value }.toSet()
|
||||||
|
return decl !in allReferencedVardecls // Don't check assembly just for occurrences of variables, if they're not used in prog8 itself, just kill them
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun nameInAssemblyCode(name: String) = allAssemblyNodes.any { it.assembly.contains(name) }
|
||||||
|
|
||||||
|
inline fun unused(label: Label) = false // just always output labels
|
||||||
|
|
||||||
|
fun unused(stmt: ISymbolStatement): Boolean {
|
||||||
|
return when(stmt) {
|
||||||
|
is Subroutine -> unused(stmt)
|
||||||
|
is Block -> unused(stmt)
|
||||||
|
is VarDecl -> unused(stmt)
|
||||||
|
is Label -> false // just always output labels
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,12 +9,10 @@ import prog8.ast.statements.ForLoop
|
|||||||
import prog8.ast.statements.VarDecl
|
import prog8.ast.statements.VarDecl
|
||||||
import prog8.ast.walk.AstWalker
|
import prog8.ast.walk.AstWalker
|
||||||
import prog8.ast.walk.IAstModification
|
import prog8.ast.walk.IAstModification
|
||||||
import prog8.compiler.target.ICompilationTarget
|
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
|
|
||||||
|
|
||||||
internal class ConstantFoldingOptimizer(private val program: Program, private val compTarget: ICompilationTarget) : AstWalker() {
|
internal class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||||
private val noModifications = emptyList<IAstModification>()
|
|
||||||
|
|
||||||
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
|
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
|
||||||
// @( &thing ) --> thing
|
// @( &thing ) --> thing
|
||||||
@ -107,7 +105,7 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
|||||||
// optimize various simple cases of ** :
|
// optimize various simple cases of ** :
|
||||||
// optimize away 1 ** x into just 1 and 0 ** x into just 0
|
// optimize away 1 ** x into just 1 and 0 ** x into just 0
|
||||||
// optimize 2 ** x into (1<<x) if both operands are integer.
|
// optimize 2 ** x into (1<<x) if both operands are integer.
|
||||||
val leftDt = leftconst.inferType(program).typeOrElse(DataType.STRUCT)
|
val leftDt = leftconst.inferType(program).getOr(DataType.UNDEFINED)
|
||||||
when (leftconst.number.toDouble()) {
|
when (leftconst.number.toDouble()) {
|
||||||
0.0 -> {
|
0.0 -> {
|
||||||
val value = NumericLiteralValue(leftDt, 0, expr.position)
|
val value = NumericLiteralValue(leftDt, 0, expr.position)
|
||||||
@ -122,11 +120,11 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
|||||||
val value = NumericLiteralValue(leftDt, 2.0.pow(rightconst.number.toDouble()), expr.position)
|
val value = NumericLiteralValue(leftDt, 2.0.pow(rightconst.number.toDouble()), expr.position)
|
||||||
modifications += IAstModification.ReplaceNode(expr, value, parent)
|
modifications += IAstModification.ReplaceNode(expr, value, parent)
|
||||||
} else {
|
} else {
|
||||||
val rightDt = expr.right.inferType(program).typeOrElse(DataType.STRUCT)
|
val rightDt = expr.right.inferType(program).getOr(DataType.UNDEFINED)
|
||||||
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
|
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
|
||||||
val targetDt =
|
val targetDt =
|
||||||
when (parent) {
|
when (parent) {
|
||||||
is Assignment -> parent.target.inferType(program).typeOrElse(DataType.STRUCT)
|
is Assignment -> parent.target.inferType(program).getOr(DataType.UNDEFINED)
|
||||||
is VarDecl -> parent.datatype
|
is VarDecl -> parent.datatype
|
||||||
else -> leftDt
|
else -> leftDt
|
||||||
}
|
}
|
||||||
@ -139,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 {
|
val subExpr: BinaryExpression? = when {
|
||||||
leftconst != null -> expr.right as? BinaryExpression
|
leftconst != null -> expr.right as? BinaryExpression
|
||||||
rightconst != null -> expr.left as? BinaryExpression
|
rightconst != null -> expr.left as? BinaryExpression
|
||||||
@ -188,7 +186,7 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
|||||||
} else {
|
} else {
|
||||||
val arrayDt = array.guessDatatype(program)
|
val arrayDt = array.guessDatatype(program)
|
||||||
if (arrayDt.isKnown) {
|
if (arrayDt.isKnown) {
|
||||||
val newArray = array.cast(arrayDt.typeOrElse(DataType.STRUCT))
|
val newArray = array.cast(arrayDt.getOr(DataType.UNDEFINED))
|
||||||
if (newArray != null && newArray != array)
|
if (newArray != null && newArray != array)
|
||||||
return listOf(IAstModification.ReplaceNode(array, newArray, parent))
|
return listOf(IAstModification.ReplaceNode(array, newArray, parent))
|
||||||
}
|
}
|
||||||
@ -224,7 +222,7 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
|||||||
range.step
|
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.
|
// adjust the datatype of a range expression in for loops to the loop variable.
|
||||||
@ -279,7 +277,7 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
|||||||
val numval = decl.value as? NumericLiteralValue
|
val numval = decl.value as? NumericLiteralValue
|
||||||
if(decl.type== VarDeclType.CONST && numval!=null) {
|
if(decl.type== VarDeclType.CONST && numval!=null) {
|
||||||
val valueDt = numval.inferType(program)
|
val valueDt = numval.inferType(program)
|
||||||
if(!valueDt.istype(decl.datatype)) {
|
if(valueDt isnot decl.datatype) {
|
||||||
val cast = numval.cast(decl.datatype)
|
val cast = numval.cast(decl.datatype)
|
||||||
if(cast.isValid)
|
if(cast.isValid)
|
||||||
return listOf(IAstModification.ReplaceNode(numval, cast.valueOrZero(), decl))
|
return listOf(IAstModification.ReplaceNode(numval, cast.valueOrZero(), decl))
|
||||||
|
@ -1,27 +1,27 @@
|
|||||||
package prog8.optimizer
|
package prog8.optimizer
|
||||||
|
|
||||||
|
import prog8.ast.INameScope
|
||||||
import prog8.ast.Node
|
import prog8.ast.Node
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.statements.ArrayIndex
|
import prog8.ast.statements.*
|
||||||
import prog8.ast.statements.AssignTarget
|
|
||||||
import prog8.ast.statements.ForLoop
|
|
||||||
import prog8.ast.statements.VarDecl
|
|
||||||
import prog8.ast.walk.AstWalker
|
import prog8.ast.walk.AstWalker
|
||||||
import prog8.ast.walk.IAstModification
|
import prog8.ast.walk.IAstModification
|
||||||
import prog8.compiler.IErrorReporter
|
import prog8.compiler.IErrorReporter
|
||||||
|
import prog8.compiler.astprocessing.size
|
||||||
|
import prog8.compiler.astprocessing.toConstantIntegerRange
|
||||||
import prog8.compiler.target.ICompilationTarget
|
import prog8.compiler.target.ICompilationTarget
|
||||||
|
|
||||||
// Fix up the literal value's type to match that of the vardecl
|
// Fix up the literal value's type to match that of the vardecl
|
||||||
|
// (also check range literal operands types before they get expanded into arrays for instance)
|
||||||
internal class VarConstantValueTypeAdjuster(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
|
internal class VarConstantValueTypeAdjuster(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
|
||||||
private val noModifications = emptyList<IAstModification>()
|
|
||||||
|
|
||||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||||
try {
|
try {
|
||||||
val declConstValue = decl.value?.constValue(program)
|
val declConstValue = decl.value?.constValue(program)
|
||||||
if(declConstValue!=null && (decl.type==VarDeclType.VAR || decl.type==VarDeclType.CONST)
|
if(declConstValue!=null && (decl.type==VarDeclType.VAR || decl.type==VarDeclType.CONST)
|
||||||
&& !declConstValue.inferType(program).istype(decl.datatype)) {
|
&& declConstValue.inferType(program) isnot decl.datatype) {
|
||||||
// cast the numeric literal to the appropriate datatype of the variable
|
// cast the numeric literal to the appropriate datatype of the variable
|
||||||
val cast = declConstValue.cast(decl.datatype)
|
val cast = declConstValue.cast(decl.datatype)
|
||||||
if(cast.isValid)
|
if(cast.isValid)
|
||||||
@ -31,6 +31,57 @@ internal class VarConstantValueTypeAdjuster(private val program: Program, privat
|
|||||||
errors.err(x.message, x.position)
|
errors.err(x.message, x.position)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// move vardecl to the containing subroutine and add initialization assignment in its place if needed
|
||||||
|
if(decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
|
||||||
|
val subroutine = decl.definingSubroutine as? INameScope
|
||||||
|
if(subroutine!=null && subroutine!==parent) {
|
||||||
|
val declValue = decl.value
|
||||||
|
decl.value = null
|
||||||
|
decl.allowInitializeWithZero = false
|
||||||
|
return if (declValue == null) {
|
||||||
|
listOf(
|
||||||
|
IAstModification.Remove(decl, parent as INameScope),
|
||||||
|
IAstModification.InsertFirst(decl, subroutine)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
|
||||||
|
val assign = Assignment(target, declValue, decl.position)
|
||||||
|
listOf(
|
||||||
|
IAstModification.ReplaceNode(decl, assign, parent),
|
||||||
|
IAstModification.InsertFirst(decl, subroutine)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
return noModifications
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -40,7 +91,6 @@ internal class VarConstantValueTypeAdjuster(private val program: Program, privat
|
|||||||
// and the array var initializer values and sizes.
|
// and the array var initializer values and sizes.
|
||||||
// This is needed because further constant optimizations depend on those.
|
// This is needed because further constant optimizations depend on those.
|
||||||
internal class ConstantIdentifierReplacer(private val program: Program, private val errors: IErrorReporter, private val compTarget: ICompilationTarget) : AstWalker() {
|
internal class ConstantIdentifierReplacer(private val program: Program, private val errors: IErrorReporter, private val compTarget: ICompilationTarget) : AstWalker() {
|
||||||
private val noModifications = emptyList<IAstModification>()
|
|
||||||
|
|
||||||
override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
|
override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
|
||||||
// replace identifiers that refer to const value, with the value itself
|
// replace identifiers that refer to const value, with the value itself
|
||||||
@ -75,7 +125,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
|||||||
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||||
// the initializer value can't refer to the variable itself (recursive definition)
|
// the initializer value can't refer to the variable itself (recursive definition)
|
||||||
// TODO: use call graph for this?
|
// TODO: use call graph for this?
|
||||||
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.indexVar?.referencesIdentifier(decl.name) == true) {
|
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.indexExpr?.referencesIdentifier(decl.name) == true) {
|
||||||
errors.err("recursive var declaration", decl.position)
|
errors.err("recursive var declaration", decl.position)
|
||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
@ -93,19 +143,6 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
|||||||
decl
|
decl
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
} else if(arraysize.constIndex()==null) {
|
|
||||||
// see if we can calculate the size from other fields
|
|
||||||
try {
|
|
||||||
val cval = arraysize.indexVar?.constValue(program) ?: arraysize.origExpression?.constValue(program)
|
|
||||||
if (cval != null) {
|
|
||||||
arraysize.indexVar = null
|
|
||||||
arraysize.origExpression = null
|
|
||||||
arraysize.indexNum = cval
|
|
||||||
}
|
|
||||||
} catch (x: UndefinedSymbolError) {
|
|
||||||
errors.err(x.message, x.position)
|
|
||||||
return noModifications
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,16 +156,15 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||||
val numericLv = decl.value as? NumericLiteralValue
|
|
||||||
val rangeExpr = decl.value as? RangeExpr
|
val rangeExpr = decl.value as? RangeExpr
|
||||||
if(rangeExpr!=null) {
|
if(rangeExpr!=null) {
|
||||||
// convert the initializer range expression to an actual array
|
// convert the initializer range expression to an actual array
|
||||||
val declArraySize = decl.arraysize?.constIndex()
|
val declArraySize = decl.arraysize?.constIndex()
|
||||||
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
if(declArraySize!=null && declArraySize!=rangeExpr.size(compTarget))
|
||||||
errors.err("range expression size doesn't match declared array size", decl.value?.position!!)
|
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) {
|
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) {
|
val newValue = if(eltType in ByteDatatypes) {
|
||||||
ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
|
ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
|
||||||
constRange.map { NumericLiteralValue(eltType, it.toShort(), decl.value!!.position) }.toTypedArray(),
|
constRange.map { NumericLiteralValue(eltType, it.toShort(), decl.value!!.position) }.toTypedArray(),
|
||||||
@ -141,6 +177,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
|||||||
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val numericLv = decl.value as? NumericLiteralValue
|
||||||
if(numericLv!=null && numericLv.type== DataType.FLOAT)
|
if(numericLv!=null && numericLv.type== DataType.FLOAT)
|
||||||
errors.err("arraysize requires only integers here", numericLv.position)
|
errors.err("arraysize requires only integers here", numericLv.position)
|
||||||
val size = decl.arraysize?.constIndex() ?: return noModifications
|
val size = decl.arraysize?.constIndex() ?: return noModifications
|
||||||
@ -167,21 +204,19 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
|||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
// create the array itself, filled with the fillvalue.
|
// create the array itself, filled with the fillvalue.
|
||||||
val array = Array(size) {fillvalue}.map { NumericLiteralValue(ArrayElementTypes.getValue(decl.datatype), it, numericLv.position) }.toTypedArray<Expression>()
|
val array = Array(size) {fillvalue}.map { NumericLiteralValue(ArrayToElementTypes.getValue(decl.datatype), it, numericLv.position) }.toTypedArray<Expression>()
|
||||||
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
|
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
|
||||||
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
|
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.ARRAY_F -> {
|
DataType.ARRAY_F -> {
|
||||||
val size = decl.arraysize?.constIndex() ?: return noModifications
|
|
||||||
val litval = decl.value as? NumericLiteralValue
|
|
||||||
val rangeExpr = decl.value as? RangeExpr
|
val rangeExpr = decl.value as? RangeExpr
|
||||||
if(rangeExpr!=null) {
|
if(rangeExpr!=null) {
|
||||||
// convert the initializer range expression to an actual array of floats
|
// convert the initializer range expression to an actual array of floats
|
||||||
val declArraySize = decl.arraysize?.constIndex()
|
val declArraySize = decl.arraysize?.constIndex()
|
||||||
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
if(declArraySize!=null && declArraySize!=rangeExpr.size(compTarget))
|
||||||
errors.err("range expression size doesn't match declared array size", decl.value?.position!!)
|
errors.err("range expression size (${rangeExpr.size(compTarget)}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
|
||||||
val constRange = rangeExpr.toConstantIntegerRange()
|
val constRange = rangeExpr.toConstantIntegerRange(compTarget)
|
||||||
if(constRange!=null) {
|
if(constRange!=null) {
|
||||||
val newValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F),
|
val newValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F),
|
||||||
constRange.map { NumericLiteralValue(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
constRange.map { NumericLiteralValue(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||||
@ -189,22 +224,24 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
|||||||
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
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.
|
// 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)
|
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 {
|
else {
|
||||||
// create the array itself, filled with the fillvalue.
|
// create the array itself, filled with the fillvalue.
|
||||||
val array = Array(size) {fillvalue}.map { NumericLiteralValue(DataType.FLOAT, it, litval.position) }.toTypedArray<Expression>()
|
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 = litval.position)
|
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = numericLv.position)
|
||||||
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
|
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
// nothing to do for this type
|
// nothing to do for this type
|
||||||
// this includes strings and structs
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,6 @@ import kotlin.math.pow
|
|||||||
internal class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
internal class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||||
private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet()
|
private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet()
|
||||||
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
|
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
|
||||||
private val noModifications = emptyList<IAstModification>()
|
|
||||||
|
|
||||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||||
val mods = mutableListOf<IAstModification>()
|
val mods = mutableListOf<IAstModification>()
|
||||||
@ -46,7 +45,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
|||||||
mods += IAstModification.ReplaceNode(typecast.expression, subTypecast.expression, typecast)
|
mods += IAstModification.ReplaceNode(typecast.expression, subTypecast.expression, typecast)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (typecast.expression.inferType(program).istype(typecast.type)) {
|
if (typecast.expression.inferType(program) istype typecast.type) {
|
||||||
// remove duplicate cast
|
// remove duplicate cast
|
||||||
mods += IAstModification.ReplaceNode(typecast, typecast.expression, parent)
|
mods += IAstModification.ReplaceNode(typecast, typecast.expression, parent)
|
||||||
}
|
}
|
||||||
@ -135,8 +134,8 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
val leftDt = leftIDt.typeOrElse(DataType.STRUCT)
|
val leftDt = leftIDt.getOr(DataType.UNDEFINED)
|
||||||
val rightDt = rightIDt.typeOrElse(DataType.STRUCT)
|
val rightDt = rightIDt.getOr(DataType.UNDEFINED)
|
||||||
|
|
||||||
if (expr.operator == "+" || expr.operator == "-"
|
if (expr.operator == "+" || expr.operator == "-"
|
||||||
&& leftVal == null && rightVal == null
|
&& leftVal == null && rightVal == null
|
||||||
@ -290,13 +289,13 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
|||||||
val arg = functionCall.args[0]
|
val arg = functionCall.args[0]
|
||||||
if(arg is TypecastExpression) {
|
if(arg is TypecastExpression) {
|
||||||
val valueDt = arg.expression.inferType(program)
|
val valueDt = arg.expression.inferType(program)
|
||||||
if (valueDt.istype(DataType.BYTE) || valueDt.istype(DataType.UBYTE)) {
|
if (valueDt istype DataType.BYTE || valueDt istype DataType.UBYTE) {
|
||||||
// useless lsb() of byte value that was casted to word
|
// useless lsb() of byte value that was typecasted to word
|
||||||
return listOf(IAstModification.ReplaceNode(functionCall, arg.expression, parent))
|
return listOf(IAstModification.ReplaceNode(functionCall, arg.expression, parent))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val argDt = arg.inferType(program)
|
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
|
// useless lsb() of byte value
|
||||||
return listOf(IAstModification.ReplaceNode(functionCall, arg, parent))
|
return listOf(IAstModification.ReplaceNode(functionCall, arg, parent))
|
||||||
}
|
}
|
||||||
@ -306,20 +305,20 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
|||||||
val arg = functionCall.args[0]
|
val arg = functionCall.args[0]
|
||||||
if(arg is TypecastExpression) {
|
if(arg is TypecastExpression) {
|
||||||
val valueDt = arg.expression.inferType(program)
|
val valueDt = arg.expression.inferType(program)
|
||||||
if (valueDt.istype(DataType.BYTE) || valueDt.istype(DataType.UBYTE)) {
|
if (valueDt istype DataType.BYTE || valueDt istype DataType.UBYTE) {
|
||||||
// useless msb() of byte value that was casted to word, replace with 0
|
// useless msb() of byte value that was typecasted to word, replace with 0
|
||||||
return listOf(IAstModification.ReplaceNode(
|
return listOf(IAstModification.ReplaceNode(
|
||||||
functionCall,
|
functionCall,
|
||||||
NumericLiteralValue(valueDt.typeOrElse(DataType.UBYTE), 0, arg.expression.position),
|
NumericLiteralValue(valueDt.getOr(DataType.UBYTE), 0, arg.expression.position),
|
||||||
parent))
|
parent))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val argDt = arg.inferType(program)
|
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
|
// useless msb() of byte value, replace with 0
|
||||||
return listOf(IAstModification.ReplaceNode(
|
return listOf(IAstModification.ReplaceNode(
|
||||||
functionCall,
|
functionCall,
|
||||||
NumericLiteralValue(argDt.typeOrElse(DataType.UBYTE), 0, arg.position),
|
NumericLiteralValue(argDt.getOr(DataType.UBYTE), 0, arg.position),
|
||||||
parent))
|
parent))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -490,10 +489,10 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
|||||||
val idt = expr.inferType(program)
|
val idt = expr.inferType(program)
|
||||||
if(!idt.isKnown)
|
if(!idt.isKnown)
|
||||||
throw FatalAstException("unknown dt")
|
throw FatalAstException("unknown dt")
|
||||||
return NumericLiteralValue(idt.typeOrElse(DataType.STRUCT), 0, expr.position)
|
return NumericLiteralValue(idt.getOr(DataType.UNDEFINED), 0, expr.position)
|
||||||
} else if (cv == 2.0) {
|
} else if (cv in powersOfTwo) {
|
||||||
expr.operator = "&"
|
expr.operator = "&"
|
||||||
expr.right = NumericLiteralValue.optimalInteger(1, expr.position)
|
expr.right = NumericLiteralValue.optimalInteger(cv!!.toInt()-1, expr.position)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -514,7 +513,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
|||||||
val leftIDt = expr.left.inferType(program)
|
val leftIDt = expr.left.inferType(program)
|
||||||
if (!leftIDt.isKnown)
|
if (!leftIDt.isKnown)
|
||||||
return null
|
return null
|
||||||
val leftDt = leftIDt.typeOrElse(DataType.STRUCT)
|
val leftDt = leftIDt.getOr(DataType.UNDEFINED)
|
||||||
when (cv) {
|
when (cv) {
|
||||||
-1.0 -> {
|
-1.0 -> {
|
||||||
// '/' -> -left
|
// '/' -> -left
|
||||||
@ -591,14 +590,14 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
|||||||
return expr2.left
|
return expr2.left
|
||||||
}
|
}
|
||||||
in powersOfTwo -> {
|
in powersOfTwo -> {
|
||||||
if (leftValue.inferType(program).typeOrElse(DataType.STRUCT) in IntegerDatatypes) {
|
if (leftValue.inferType(program).isInteger) {
|
||||||
// times a power of two => shift left
|
// times a power of two => shift left
|
||||||
val numshifts = log2(cv).toInt()
|
val numshifts = log2(cv).toInt()
|
||||||
return BinaryExpression(expr2.left, "<<", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
|
return BinaryExpression(expr2.left, "<<", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
in negativePowersOfTwo -> {
|
in negativePowersOfTwo -> {
|
||||||
if (leftValue.inferType(program).typeOrElse(DataType.STRUCT) in IntegerDatatypes) {
|
if (leftValue.inferType(program).isInteger) {
|
||||||
// times a negative power of two => negate, then shift left
|
// times a negative power of two => negate, then shift left
|
||||||
val numshifts = log2(-cv).toInt()
|
val numshifts = log2(-cv).toInt()
|
||||||
return BinaryExpression(PrefixExpression("-", expr2.left, expr.position), "<<", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
|
return BinaryExpression(PrefixExpression("-", expr2.left, expr.position), "<<", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
|
||||||
@ -622,7 +621,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
|||||||
val targetIDt = expr.left.inferType(program)
|
val targetIDt = expr.left.inferType(program)
|
||||||
if(!targetIDt.isKnown)
|
if(!targetIDt.isKnown)
|
||||||
throw FatalAstException("unknown dt")
|
throw FatalAstException("unknown dt")
|
||||||
when (val targetDt = targetIDt.typeOrElse(DataType.STRUCT)) {
|
when (val targetDt = targetIDt.getOr(DataType.UNDEFINED)) {
|
||||||
DataType.UBYTE, DataType.BYTE -> {
|
DataType.UBYTE, DataType.BYTE -> {
|
||||||
if (amount >= 8) {
|
if (amount >= 8) {
|
||||||
return NumericLiteralValue(targetDt, 0, expr.position)
|
return NumericLiteralValue(targetDt, 0, expr.position)
|
||||||
@ -657,7 +656,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
|||||||
val idt = expr.left.inferType(program)
|
val idt = expr.left.inferType(program)
|
||||||
if(!idt.isKnown)
|
if(!idt.isKnown)
|
||||||
throw FatalAstException("unknown dt")
|
throw FatalAstException("unknown dt")
|
||||||
when (idt.typeOrElse(DataType.STRUCT)) {
|
when (idt.getOr(DataType.UNDEFINED)) {
|
||||||
DataType.UBYTE -> {
|
DataType.UBYTE -> {
|
||||||
if (amount >= 8) {
|
if (amount >= 8) {
|
||||||
return NumericLiteralValue.optimalInteger(0, expr.position)
|
return NumericLiteralValue.optimalInteger(0, expr.position)
|
||||||
|
@ -4,31 +4,30 @@ import prog8.ast.IBuiltinFunctions
|
|||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.compiler.IErrorReporter
|
import prog8.compiler.IErrorReporter
|
||||||
import prog8.compiler.target.ICompilationTarget
|
import prog8.compiler.target.ICompilationTarget
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
|
|
||||||
internal fun Program.constantFold(errors: IErrorReporter, compTarget: ICompilationTarget) {
|
internal fun Program.constantFold(errors: IErrorReporter, compTarget: ICompilationTarget) {
|
||||||
val valuetypefixer = VarConstantValueTypeAdjuster(this, errors)
|
val valuetypefixer = VarConstantValueTypeAdjuster(this, errors)
|
||||||
valuetypefixer.visit(this)
|
valuetypefixer.visit(this)
|
||||||
if(errors.isEmpty()) {
|
if(errors.noErrors()) {
|
||||||
valuetypefixer.applyModifications()
|
valuetypefixer.applyModifications()
|
||||||
|
|
||||||
val replacer = ConstantIdentifierReplacer(this, errors, compTarget)
|
val replacer = ConstantIdentifierReplacer(this, errors, compTarget)
|
||||||
replacer.visit(this)
|
replacer.visit(this)
|
||||||
if (errors.isEmpty()) {
|
if (errors.noErrors()) {
|
||||||
replacer.applyModifications()
|
replacer.applyModifications()
|
||||||
|
|
||||||
valuetypefixer.visit(this)
|
valuetypefixer.visit(this)
|
||||||
if(errors.isEmpty()) {
|
if(errors.noErrors()) {
|
||||||
valuetypefixer.applyModifications()
|
valuetypefixer.applyModifications()
|
||||||
|
|
||||||
val optimizer = ConstantFoldingOptimizer(this, compTarget)
|
val optimizer = ConstantFoldingOptimizer(this)
|
||||||
optimizer.visit(this)
|
optimizer.visit(this)
|
||||||
while (errors.isEmpty() && optimizer.applyModifications() > 0) {
|
while (errors.noErrors() && optimizer.applyModifications() > 0) {
|
||||||
optimizer.visit(this)
|
optimizer.visit(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errors.isEmpty()) {
|
if (errors.noErrors()) {
|
||||||
replacer.visit(this)
|
replacer.visit(this)
|
||||||
replacer.applyModifications()
|
replacer.applyModifications()
|
||||||
}
|
}
|
||||||
@ -36,16 +35,15 @@ internal fun Program.constantFold(errors: IErrorReporter, compTarget: ICompilati
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(errors.isEmpty())
|
if(errors.noErrors())
|
||||||
modules.forEach { it.linkParents(namespace) } // re-link in final configuration
|
modules.forEach { it.linkParents(namespace) } // re-link in final configuration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
internal fun Program.optimizeStatements(errors: IErrorReporter,
|
internal fun Program.optimizeStatements(errors: IErrorReporter,
|
||||||
functions: IBuiltinFunctions,
|
functions: IBuiltinFunctions,
|
||||||
compTarget: ICompilationTarget,
|
compTarget: ICompilationTarget): Int {
|
||||||
asmFileLoader: (filename: String, source: Path)->String): Int {
|
val optimizer = StatementOptimizer(this, errors, functions, compTarget)
|
||||||
val optimizer = StatementOptimizer(this, errors, functions, compTarget, asmFileLoader)
|
|
||||||
optimizer.visit(this)
|
optimizer.visit(this)
|
||||||
val optimizationCount = optimizer.applyModifications()
|
val optimizationCount = optimizer.applyModifications()
|
||||||
|
|
||||||
|
@ -11,81 +11,80 @@ import prog8.ast.walk.AstWalker
|
|||||||
import prog8.ast.walk.IAstModification
|
import prog8.ast.walk.IAstModification
|
||||||
import prog8.ast.walk.IAstVisitor
|
import prog8.ast.walk.IAstVisitor
|
||||||
import prog8.compiler.IErrorReporter
|
import prog8.compiler.IErrorReporter
|
||||||
|
import prog8.compiler.astprocessing.size
|
||||||
import prog8.compiler.target.ICompilationTarget
|
import prog8.compiler.target.ICompilationTarget
|
||||||
import java.nio.file.Path
|
|
||||||
import kotlin.math.floor
|
import kotlin.math.floor
|
||||||
|
|
||||||
|
internal const val retvarName = "prog8_retval"
|
||||||
|
|
||||||
|
|
||||||
internal class StatementOptimizer(private val program: Program,
|
internal class StatementOptimizer(private val program: Program,
|
||||||
private val errors: IErrorReporter,
|
private val errors: IErrorReporter,
|
||||||
private val functions: IBuiltinFunctions,
|
private val functions: IBuiltinFunctions,
|
||||||
private val compTarget: ICompilationTarget,
|
private val compTarget: ICompilationTarget) : AstWalker() {
|
||||||
asmFileLoader: (filename: String, source: Path)->String
|
|
||||||
) : AstWalker() {
|
|
||||||
|
|
||||||
private val noModifications = emptyList<IAstModification>()
|
private val subsThatNeedReturnVariable = mutableSetOf<Triple<INameScope, DataType, Position>>()
|
||||||
private val callgraph = CallGraph(program, asmFileLoader)
|
|
||||||
|
|
||||||
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
|
|
||||||
if("force_output" !in block.options()) {
|
|
||||||
if (block.containsNoCodeNorVars()) {
|
|
||||||
if(block.name != program.internedStringsModuleName)
|
|
||||||
errors.warn("removing empty block '${block.name}'", block.position)
|
|
||||||
return listOf(IAstModification.Remove(block, parent as INameScope))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (block !in callgraph.usedSymbols) {
|
|
||||||
errors.warn("removing unused block '${block.name}'", block.position)
|
|
||||||
return listOf(IAstModification.Remove(block, parent as INameScope))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return noModifications
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||||
val forceOutput = "force_output" in subroutine.definingBlock().options()
|
for(returnvar in subsThatNeedReturnVariable) {
|
||||||
if(subroutine.asmAddress==null && !forceOutput) {
|
val decl = VarDecl(VarDeclType.VAR, returnvar.second, ZeropageWish.DONTCARE, null, retvarName, null,
|
||||||
if(subroutine.containsNoCodeNorVars() && !subroutine.inline) {
|
isArray = false,
|
||||||
errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position)
|
autogeneratedDontRemove = true,
|
||||||
val removals = callgraph.calledBy.getValue(subroutine).map {
|
sharedWithAsm = false,
|
||||||
IAstModification.Remove(it, it.definingScope())
|
position = returnvar.third
|
||||||
}.toMutableList()
|
)
|
||||||
removals += IAstModification.Remove(subroutine, subroutine.definingScope())
|
returnvar.first.statements.add(0, decl)
|
||||||
return removals
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
subsThatNeedReturnVariable.clear()
|
||||||
if(subroutine !in callgraph.usedSymbols && !forceOutput) {
|
|
||||||
if(!subroutine.isAsmSubroutine)
|
|
||||||
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
|
|
||||||
return listOf(IAstModification.Remove(subroutine, subroutine.definingScope()))
|
|
||||||
}
|
|
||||||
|
|
||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||||
val forceOutput = "force_output" in decl.definingBlock().options()
|
// if the first instruction in the called subroutine is a return statement with a simple value,
|
||||||
if(decl !in callgraph.usedSymbols && !forceOutput) {
|
// remove the jump altogeter and inline the returnvalue directly.
|
||||||
if(decl.type == VarDeclType.VAR)
|
val subroutine = functionCall.target.targetSubroutine(program)
|
||||||
errors.warn("removing unused variable '${decl.name}'", decl.position)
|
if(subroutine!=null) {
|
||||||
|
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
||||||
return listOf(IAstModification.Remove(decl, decl.definingScope()))
|
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
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun scopePrefix(variable: IdentifierReference, subroutine: Subroutine): IdentifierReference {
|
||||||
|
val scoped = subroutine.makeScopedName(variable.nameInSource.last())
|
||||||
|
return IdentifierReference(scoped.split('.'), variable.position)
|
||||||
|
}
|
||||||
|
|
||||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||||
if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in functions.names) {
|
if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in functions.names) {
|
||||||
val functionName = functionCallStatement.target.nameInSource[0]
|
val functionName = functionCallStatement.target.nameInSource[0]
|
||||||
if (functionName in functions.purefunctionNames) {
|
if (functionName in functions.purefunctionNames) {
|
||||||
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
|
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
|
||||||
return listOf(IAstModification.Remove(functionCallStatement, functionCallStatement.definingScope()))
|
return listOf(IAstModification.Remove(functionCallStatement, functionCallStatement.definingScope))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// printing a literal string of just 2 or 1 characters is replaced by directly outputting those characters
|
// 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")) {
|
if(functionCallStatement.target.nameInSource==listOf("txt", "print")) {
|
||||||
val arg = functionCallStatement.args.single()
|
val arg = functionCallStatement.args.single()
|
||||||
val stringVar: IdentifierReference? = if(arg is AddressOf) {
|
val stringVar: IdentifierReference? = if(arg is AddressOf) {
|
||||||
@ -93,30 +92,29 @@ internal class StatementOptimizer(private val program: Program,
|
|||||||
} else {
|
} else {
|
||||||
arg as? IdentifierReference
|
arg as? IdentifierReference
|
||||||
}
|
}
|
||||||
if(stringVar!=null) {
|
if(stringVar!=null && stringVar.wasStringLiteral(program)) {
|
||||||
val vardecl = stringVar.targetVarDecl(program)!!
|
val string = stringVar.targetVarDecl(program)?.value as? StringLiteralValue
|
||||||
val string = vardecl.value as? StringLiteralValue
|
|
||||||
if(string!=null) {
|
if(string!=null) {
|
||||||
val pos = functionCallStatement.position
|
val pos = functionCallStatement.position
|
||||||
if (string.value.length == 1) {
|
if (string.value.length == 1) {
|
||||||
val firstCharEncoded = compTarget.encodeString(string.value, string.altEncoding)[0]
|
val firstCharEncoded = compTarget.encodeString(string.value, string.altEncoding)[0]
|
||||||
val chrout = FunctionCallStatement(
|
val chrout = FunctionCallStatement(
|
||||||
IdentifierReference(listOf("txt", "chrout"), pos),
|
IdentifierReference(listOf("txt", "chrout"), pos),
|
||||||
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstCharEncoded.toInt(), pos)),
|
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstCharEncoded.toInt(), pos)),
|
||||||
functionCallStatement.void, pos
|
functionCallStatement.void, pos
|
||||||
)
|
)
|
||||||
return listOf(IAstModification.ReplaceNode(functionCallStatement, chrout, parent))
|
return listOf(IAstModification.ReplaceNode(functionCallStatement, chrout, parent))
|
||||||
} else if (string.value.length == 2) {
|
} else if (string.value.length == 2) {
|
||||||
val firstTwoCharsEncoded = compTarget.encodeString(string.value.take(2), string.altEncoding)
|
val firstTwoCharsEncoded = compTarget.encodeString(string.value.take(2), string.altEncoding)
|
||||||
val chrout1 = FunctionCallStatement(
|
val chrout1 = FunctionCallStatement(
|
||||||
IdentifierReference(listOf("txt", "chrout"), pos),
|
IdentifierReference(listOf("txt", "chrout"), pos),
|
||||||
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toInt(), pos)),
|
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toInt(), pos)),
|
||||||
functionCallStatement.void, pos
|
functionCallStatement.void, pos
|
||||||
)
|
)
|
||||||
val chrout2 = FunctionCallStatement(
|
val chrout2 = FunctionCallStatement(
|
||||||
IdentifierReference(listOf("txt", "chrout"), pos),
|
IdentifierReference(listOf("txt", "chrout"), pos),
|
||||||
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[1].toInt(), pos)),
|
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[1].toInt(), pos)),
|
||||||
functionCallStatement.void, pos
|
functionCallStatement.void, pos
|
||||||
)
|
)
|
||||||
return listOf(
|
return listOf(
|
||||||
IAstModification.InsertBefore(functionCallStatement, chrout1, parent as INameScope),
|
IAstModification.InsertBefore(functionCallStatement, chrout1, parent as INameScope),
|
||||||
@ -132,33 +130,33 @@ internal class StatementOptimizer(private val program: Program,
|
|||||||
if(subroutine!=null) {
|
if(subroutine!=null) {
|
||||||
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
||||||
if(first is Return)
|
if(first is Return)
|
||||||
return listOf(IAstModification.Remove(functionCallStatement, functionCallStatement.definingScope()))
|
return listOf(IAstModification.Remove(functionCallStatement, functionCallStatement.definingScope))
|
||||||
}
|
}
|
||||||
|
|
||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
// override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||||
// if the first instruction in the called subroutine is a return statement with constant value, replace with the constant value
|
// // 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)
|
// val subroutine = functionCall.target.targetSubroutine(program)
|
||||||
if(subroutine!=null) {
|
// if(subroutine!=null) {
|
||||||
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
// val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
||||||
if(first is Return && first.value!=null) {
|
// if(first is Return && first.value!=null) {
|
||||||
val constval = first.value?.constValue(program)
|
// val constval = first.value?.constValue(program)
|
||||||
if(constval!=null)
|
// if(constval!=null)
|
||||||
return listOf(IAstModification.ReplaceNode(functionCall, constval, parent))
|
// return listOf(IAstModification.ReplaceNode(functionCall, constval, parent))
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
return noModifications
|
// return noModifications
|
||||||
}
|
// }
|
||||||
|
|
||||||
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
|
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
|
||||||
// remove empty if statements
|
// remove empty if statements
|
||||||
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsNoCodeNorVars())
|
if(ifStatement.truepart.containsNoCodeNorVars && ifStatement.elsepart.containsNoCodeNorVars)
|
||||||
return listOf(IAstModification.Remove(ifStatement, ifStatement.definingScope()))
|
return listOf(IAstModification.Remove(ifStatement, ifStatement.definingScope))
|
||||||
|
|
||||||
// empty true part? switch with the else part
|
// empty true part? switch with the else part
|
||||||
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsCodeOrVars()) {
|
if(ifStatement.truepart.containsNoCodeNorVars && ifStatement.elsepart.containsCodeOrVars) {
|
||||||
val invertedCondition = PrefixExpression("not", ifStatement.condition, ifStatement.condition.position)
|
val invertedCondition = PrefixExpression("not", ifStatement.condition, ifStatement.condition.position)
|
||||||
val emptyscope = AnonymousScope(mutableListOf(), ifStatement.elsepart.position)
|
val emptyscope = AnonymousScope(mutableListOf(), ifStatement.elsepart.position)
|
||||||
val truepart = AnonymousScope(ifStatement.elsepart.statements, ifStatement.truepart.position)
|
val truepart = AnonymousScope(ifStatement.elsepart.statements, ifStatement.truepart.position)
|
||||||
@ -186,20 +184,20 @@ internal class StatementOptimizer(private val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
|
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
|
||||||
if(forLoop.body.containsNoCodeNorVars()) {
|
if(forLoop.body.containsNoCodeNorVars) {
|
||||||
errors.warn("removing empty for loop", forLoop.position)
|
errors.warn("removing empty for loop", forLoop.position)
|
||||||
return listOf(IAstModification.Remove(forLoop, forLoop.definingScope()))
|
return listOf(IAstModification.Remove(forLoop, forLoop.definingScope))
|
||||||
} else if(forLoop.body.statements.size==1) {
|
} else if(forLoop.body.statements.size==1) {
|
||||||
val loopvar = forLoop.body.statements[0] as? VarDecl
|
val loopvar = forLoop.body.statements[0] as? VarDecl
|
||||||
if(loopvar!=null && loopvar.name==forLoop.loopVar.nameInSource.singleOrNull()) {
|
if(loopvar!=null && loopvar.name==forLoop.loopVar.nameInSource.singleOrNull()) {
|
||||||
// remove empty for loop (only loopvar decl in it)
|
// remove empty for loop (only loopvar decl in it)
|
||||||
return listOf(IAstModification.Remove(forLoop, forLoop.definingScope()))
|
return listOf(IAstModification.Remove(forLoop, forLoop.definingScope))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val range = forLoop.iterable as? RangeExpr
|
val range = forLoop.iterable as? RangeExpr
|
||||||
if(range!=null) {
|
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
|
// for loop over a (constant) range of just a single value-- optimize the loop away
|
||||||
// loopvar/reg = range value , follow by block
|
// loopvar/reg = range value , follow by block
|
||||||
val scope = AnonymousScope(mutableListOf(), forLoop.position)
|
val scope = AnonymousScope(mutableListOf(), forLoop.position)
|
||||||
@ -270,7 +268,7 @@ internal class StatementOptimizer(private val program: Program,
|
|||||||
} else {
|
} else {
|
||||||
// always false -> remove the while statement altogether
|
// always false -> remove the while statement altogether
|
||||||
errors.warn("condition is always false", whileLoop.condition.position)
|
errors.warn("condition is always false", whileLoop.condition.position)
|
||||||
listOf(IAstModification.Remove(whileLoop, whileLoop.definingScope()))
|
listOf(IAstModification.Remove(whileLoop, whileLoop.definingScope))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return noModifications
|
return noModifications
|
||||||
@ -279,14 +277,14 @@ internal class StatementOptimizer(private val program: Program,
|
|||||||
override fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> {
|
override fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> {
|
||||||
val iter = repeatLoop.iterations
|
val iter = repeatLoop.iterations
|
||||||
if(iter!=null) {
|
if(iter!=null) {
|
||||||
if(repeatLoop.body.containsNoCodeNorVars()) {
|
if(repeatLoop.body.containsNoCodeNorVars) {
|
||||||
errors.warn("empty loop removed", repeatLoop.position)
|
errors.warn("empty loop removed", repeatLoop.position)
|
||||||
return listOf(IAstModification.Remove(repeatLoop, repeatLoop.definingScope()))
|
return listOf(IAstModification.Remove(repeatLoop, repeatLoop.definingScope))
|
||||||
}
|
}
|
||||||
val iterations = iter.constValue(program)?.number?.toInt()
|
val iterations = iter.constValue(program)?.number?.toInt()
|
||||||
if (iterations == 0) {
|
if (iterations == 0) {
|
||||||
errors.warn("iterations is always 0, removed loop", iter.position)
|
errors.warn("iterations is always 0, removed loop", iter.position)
|
||||||
return listOf(IAstModification.Remove(repeatLoop, repeatLoop.definingScope()))
|
return listOf(IAstModification.Remove(repeatLoop, repeatLoop.definingScope))
|
||||||
}
|
}
|
||||||
if (iterations == 1) {
|
if (iterations == 1) {
|
||||||
errors.warn("iterations is always 1", iter.position)
|
errors.warn("iterations is always 1", iter.position)
|
||||||
@ -298,10 +296,10 @@ internal class StatementOptimizer(private val program: Program,
|
|||||||
|
|
||||||
override fun after(jump: Jump, parent: Node): Iterable<IAstModification> {
|
override fun after(jump: Jump, parent: Node): Iterable<IAstModification> {
|
||||||
// if the jump is to the next statement, remove the jump
|
// if the jump is to the next statement, remove the jump
|
||||||
val scope = jump.definingScope()
|
val scope = jump.definingScope
|
||||||
val label = jump.identifier?.targetStatement(program)
|
val label = jump.identifier?.targetStatement(program)
|
||||||
if(label!=null && scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1)
|
if(label!=null && scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1)
|
||||||
return listOf(IAstModification.Remove(jump, jump.definingScope()))
|
return listOf(IAstModification.Remove(jump, jump.definingScope))
|
||||||
|
|
||||||
return noModifications
|
return noModifications
|
||||||
}
|
}
|
||||||
@ -334,7 +332,7 @@ internal class StatementOptimizer(private val program: Program,
|
|||||||
)
|
)
|
||||||
return listOf(
|
return listOf(
|
||||||
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
|
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
|
||||||
IAstModification.InsertAfter(assignment, addConstant, assignment.definingScope()))
|
IAstModification.InsertAfter(assignment, addConstant, assignment.definingScope))
|
||||||
} else if (op2 == "-") {
|
} else if (op2 == "-") {
|
||||||
// A = A +/- B - N
|
// A = A +/- B - N
|
||||||
val expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position)
|
val expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position)
|
||||||
@ -345,7 +343,7 @@ internal class StatementOptimizer(private val program: Program,
|
|||||||
)
|
)
|
||||||
return listOf(
|
return listOf(
|
||||||
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
|
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
|
||||||
IAstModification.InsertAfter(assignment, subConstant, assignment.definingScope()))
|
IAstModification.InsertAfter(assignment, subConstant, assignment.definingScope))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -367,7 +365,7 @@ internal class StatementOptimizer(private val program: Program,
|
|||||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||||
if(assignment.target isSameAs assignment.value) {
|
if(assignment.target isSameAs assignment.value) {
|
||||||
// remove assignment to self
|
// remove assignment to self
|
||||||
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
return listOf(IAstModification.Remove(assignment, assignment.definingScope))
|
||||||
}
|
}
|
||||||
|
|
||||||
val targetIDt = assignment.target.inferType(program)
|
val targetIDt = assignment.target.inferType(program)
|
||||||
@ -375,7 +373,7 @@ internal class StatementOptimizer(private val program: Program,
|
|||||||
throw FatalAstException("can't infer type of assignment target")
|
throw FatalAstException("can't infer type of assignment target")
|
||||||
|
|
||||||
// optimize binary expressions a bit
|
// optimize binary expressions a bit
|
||||||
val targetDt = targetIDt.typeOrElse(DataType.STRUCT)
|
val targetDt = targetIDt.getOr(DataType.UNDEFINED)
|
||||||
val bexpr=assignment.value as? BinaryExpression
|
val bexpr=assignment.value as? BinaryExpression
|
||||||
if(bexpr!=null) {
|
if(bexpr!=null) {
|
||||||
val rightCv = bexpr.right.constValue(program)?.number?.toDouble()
|
val rightCv = bexpr.right.constValue(program)?.number?.toDouble()
|
||||||
@ -387,13 +385,13 @@ internal class StatementOptimizer(private val program: Program,
|
|||||||
when (bexpr.operator) {
|
when (bexpr.operator) {
|
||||||
"+" -> {
|
"+" -> {
|
||||||
if (rightCv == 0.0) {
|
if (rightCv == 0.0) {
|
||||||
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
return listOf(IAstModification.Remove(assignment, assignment.definingScope))
|
||||||
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
|
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
|
||||||
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..4.0) {
|
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..4.0) {
|
||||||
// replace by several INCs if it's not a memory address (inc on a memory mapped register doesn't work very well)
|
// replace by several INCs if it's not a memory address (inc on a memory mapped register doesn't work very well)
|
||||||
val incs = AnonymousScope(mutableListOf(), assignment.position)
|
val incs = AnonymousScope(mutableListOf(), assignment.position)
|
||||||
repeat(rightCv.toInt()) {
|
repeat(rightCv.toInt()) {
|
||||||
incs.statements.add(PostIncrDecr(assignment.target, "++", assignment.position))
|
incs.statements.add(PostIncrDecr(assignment.target.copy(), "++", assignment.position))
|
||||||
}
|
}
|
||||||
return listOf(IAstModification.ReplaceNode(assignment, incs, parent))
|
return listOf(IAstModification.ReplaceNode(assignment, incs, parent))
|
||||||
}
|
}
|
||||||
@ -401,30 +399,30 @@ internal class StatementOptimizer(private val program: Program,
|
|||||||
}
|
}
|
||||||
"-" -> {
|
"-" -> {
|
||||||
if (rightCv == 0.0) {
|
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) {
|
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
|
||||||
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..4.0) {
|
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..4.0) {
|
||||||
// replace by several DECs if it's not a memory address (dec on a memory mapped register doesn't work very well)
|
// replace by several DECs if it's not a memory address (dec on a memory mapped register doesn't work very well)
|
||||||
val decs = AnonymousScope(mutableListOf(), assignment.position)
|
val decs = AnonymousScope(mutableListOf(), assignment.position)
|
||||||
repeat(rightCv.toInt()) {
|
repeat(rightCv.toInt()) {
|
||||||
decs.statements.add(PostIncrDecr(assignment.target, "--", assignment.position))
|
decs.statements.add(PostIncrDecr(assignment.target.copy(), "--", assignment.position))
|
||||||
}
|
}
|
||||||
return listOf(IAstModification.ReplaceNode(assignment, decs, parent))
|
return listOf(IAstModification.ReplaceNode(assignment, decs, parent))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"*" -> 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 == 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()))
|
"^" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope))
|
||||||
"<<" -> {
|
"<<" -> {
|
||||||
if (rightCv == 0.0)
|
if (rightCv == 0.0)
|
||||||
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
return listOf(IAstModification.Remove(assignment, assignment.definingScope))
|
||||||
}
|
}
|
||||||
">>" -> {
|
">>" -> {
|
||||||
if (rightCv == 0.0)
|
if (rightCv == 0.0)
|
||||||
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
return listOf(IAstModification.Remove(assignment, assignment.definingScope))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -435,21 +433,17 @@ internal class StatementOptimizer(private val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> {
|
override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> {
|
||||||
fun returnViaIntermediary(value: Expression): Iterable<IAstModification>? {
|
fun returnViaIntermediaryVar(value: Expression): Iterable<IAstModification>? {
|
||||||
val returnDt = returnStmt.definingSubroutine()!!.returntypes.single()
|
val subr = returnStmt.definingSubroutine!!
|
||||||
|
val returnDt = subr.returntypes.single()
|
||||||
if (returnDt in IntegerDatatypes) {
|
if (returnDt in IntegerDatatypes) {
|
||||||
// first assign to intermediary, then return that register
|
// first assign to intermediary variable, then return that
|
||||||
val returnValueIntermediary =
|
subsThatNeedReturnVariable.add(Triple(subr, returnDt, returnStmt.position))
|
||||||
when(returnDt) {
|
val returnValueIntermediary1 = IdentifierReference(listOf(retvarName), returnStmt.position)
|
||||||
DataType.UBYTE -> IdentifierReference(listOf("prog8_lib", "retval_interm_ub"), returnStmt.position)
|
val returnValueIntermediary2 = IdentifierReference(listOf(retvarName), returnStmt.position)
|
||||||
DataType.BYTE -> IdentifierReference(listOf("prog8_lib", "retval_interm_b"), returnStmt.position)
|
val tgt = AssignTarget(returnValueIntermediary1, null, null, returnStmt.position)
|
||||||
DataType.UWORD -> IdentifierReference(listOf("prog8_lib", "retval_interm_uw"), returnStmt.position)
|
|
||||||
DataType.WORD -> IdentifierReference(listOf("prog8_lib", "retval_interm_w"), returnStmt.position)
|
|
||||||
else -> throw FatalAstException("weird return dt")
|
|
||||||
}
|
|
||||||
val tgt = AssignTarget(returnValueIntermediary, null, null, returnStmt.position)
|
|
||||||
val assign = Assignment(tgt, value, returnStmt.position)
|
val assign = Assignment(tgt, value, returnStmt.position)
|
||||||
val returnReplacement = Return(returnValueIntermediary, returnStmt.position)
|
val returnReplacement = Return(returnValueIntermediary2, returnStmt.position)
|
||||||
return listOf(
|
return listOf(
|
||||||
IAstModification.InsertBefore(returnStmt, assign, parent as INameScope),
|
IAstModification.InsertBefore(returnStmt, assign, parent as INameScope),
|
||||||
IAstModification.ReplaceNode(returnStmt, returnReplacement, parent)
|
IAstModification.ReplaceNode(returnStmt, returnReplacement, parent)
|
||||||
@ -460,12 +454,12 @@ internal class StatementOptimizer(private val program: Program,
|
|||||||
|
|
||||||
when(returnStmt.value) {
|
when(returnStmt.value) {
|
||||||
is PrefixExpression -> {
|
is PrefixExpression -> {
|
||||||
val mod = returnViaIntermediary(returnStmt.value!!)
|
val mod = returnViaIntermediaryVar(returnStmt.value!!)
|
||||||
if(mod!=null)
|
if(mod!=null)
|
||||||
return mod
|
return mod
|
||||||
}
|
}
|
||||||
is BinaryExpression -> {
|
is BinaryExpression -> {
|
||||||
val mod = returnViaIntermediary(returnStmt.value!!)
|
val mod = returnViaIntermediaryVar(returnStmt.value!!)
|
||||||
if(mod!=null)
|
if(mod!=null)
|
||||||
return mod
|
return mod
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
package prog8.optimizer
|
package prog8.optimizer
|
||||||
|
|
||||||
import prog8.ast.INameScope
|
import prog8.ast.*
|
||||||
import prog8.ast.Node
|
import prog8.ast.base.VarDeclType
|
||||||
import prog8.ast.Program
|
|
||||||
import prog8.ast.expressions.BinaryExpression
|
import prog8.ast.expressions.BinaryExpression
|
||||||
import prog8.ast.expressions.FunctionCall
|
import prog8.ast.expressions.FunctionCall
|
||||||
import prog8.ast.expressions.PrefixExpression
|
import prog8.ast.expressions.PrefixExpression
|
||||||
@ -11,45 +10,23 @@ import prog8.ast.statements.*
|
|||||||
import prog8.ast.walk.AstWalker
|
import prog8.ast.walk.AstWalker
|
||||||
import prog8.ast.walk.IAstModification
|
import prog8.ast.walk.IAstModification
|
||||||
import prog8.compiler.IErrorReporter
|
import prog8.compiler.IErrorReporter
|
||||||
|
import prog8.compiler.astprocessing.isInRegularRAMof
|
||||||
import prog8.compiler.target.ICompilationTarget
|
import prog8.compiler.target.ICompilationTarget
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
|
|
||||||
internal class UnusedCodeRemover(private val program: Program,
|
internal class UnusedCodeRemover(private val program: Program,
|
||||||
private val errors: IErrorReporter,
|
private val errors: IErrorReporter,
|
||||||
private val compTarget: ICompilationTarget,
|
private val compTarget: ICompilationTarget): AstWalker() {
|
||||||
private val asmFileLoader: (filename: String, source: Path)->String): AstWalker() {
|
|
||||||
|
|
||||||
override fun before(program: Program, parent: Node): Iterable<IAstModification> {
|
private val callgraph = CallGraph(program)
|
||||||
val callgraph = CallGraph(program, asmFileLoader)
|
|
||||||
val removals = mutableListOf<IAstModification>()
|
|
||||||
|
|
||||||
// remove all subroutines that aren't called, or are empty
|
override fun before(module: Module, parent: Node): Iterable<IAstModification> {
|
||||||
val entrypoint = program.entrypoint()
|
return if (!module.isLibrary && (module.containsNoCodeNorVars || callgraph.unused(module)))
|
||||||
program.modules.forEach {
|
listOf(IAstModification.Remove(module, module.definingScope))
|
||||||
callgraph.forAllSubroutines(it) { sub ->
|
else
|
||||||
val forceOutput = "force_output" in sub.definingBlock().options()
|
noModifications
|
||||||
if (sub !== entrypoint && !forceOutput && !sub.isAsmSubroutine && (callgraph.calledBy[sub].isNullOrEmpty() || sub.containsNoCodeNorVars())) {
|
|
||||||
removals.add(IAstModification.Remove(sub, sub.definingScope()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
program.modules.flatMap { it.statements }.filterIsInstance<Block>().forEach { block ->
|
|
||||||
if (block.containsNoCodeNorVars() && "force_output" !in block.options())
|
|
||||||
removals.add(IAstModification.Remove(block, block.definingScope()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove modules that are not imported, or are empty (unless it's a library modules)
|
|
||||||
program.modules.forEach {
|
|
||||||
if (!it.isLibraryModule && (it.importedBy.isEmpty() || it.containsNoCodeNorVars()))
|
|
||||||
removals.add(IAstModification.Remove(it, it.definingScope()))
|
|
||||||
}
|
|
||||||
|
|
||||||
return removals
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
|
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
|
||||||
reportUnreachable(breakStmt, parent as INameScope)
|
reportUnreachable(breakStmt, parent as INameScope)
|
||||||
return emptyList()
|
return emptyList()
|
||||||
@ -73,7 +50,7 @@ internal class UnusedCodeRemover(private val program: Program,
|
|||||||
|
|
||||||
private fun reportUnreachable(stmt: Statement, parent: INameScope) {
|
private fun reportUnreachable(stmt: Statement, parent: INameScope) {
|
||||||
when(val next = parent.nextSibling(stmt)) {
|
when(val next = parent.nextSibling(stmt)) {
|
||||||
null, is Label, is Directive, is VarDecl, is InlineAssembly, is Subroutine, is StructDecl -> {}
|
null, is Label, is Directive, is VarDecl, is InlineAssembly, is Subroutine -> {}
|
||||||
else -> errors.warn("unreachable code", next.position)
|
else -> errors.warn("unreachable code", next.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -84,15 +61,60 @@ internal class UnusedCodeRemover(private val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
|
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
|
||||||
|
if("force_output" !in block.options()) {
|
||||||
|
if (block.containsNoCodeNorVars) {
|
||||||
|
if(block.name != internedStringsModuleName)
|
||||||
|
errors.warn("removing unused block '${block.name}'", block.position)
|
||||||
|
return listOf(IAstModification.Remove(block, parent as INameScope))
|
||||||
|
}
|
||||||
|
if(callgraph.unused(block)) {
|
||||||
|
errors.warn("removing unused block '${block.name}'", block.position)
|
||||||
|
return listOf(IAstModification.Remove(block, parent as INameScope))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val removeDoubleAssignments = deduplicateAssignments(block.statements)
|
val removeDoubleAssignments = deduplicateAssignments(block.statements)
|
||||||
return removeDoubleAssignments.map { IAstModification.Remove(it, block) }
|
return removeDoubleAssignments.map { IAstModification.Remove(it, block) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||||
|
val forceOutput = "force_output" in subroutine.definingBlock.options()
|
||||||
|
if (subroutine !== program.entrypoint && !forceOutput && !subroutine.inline && !subroutine.isAsmSubroutine) {
|
||||||
|
if(callgraph.unused(subroutine)) {
|
||||||
|
if(subroutine.containsNoCodeNorVars) {
|
||||||
|
if(!subroutine.definingModule.isLibrary)
|
||||||
|
errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position)
|
||||||
|
val removals = mutableListOf(IAstModification.Remove(subroutine, subroutine.definingScope))
|
||||||
|
callgraph.calledBy[subroutine]?.let {
|
||||||
|
for(node in it)
|
||||||
|
removals.add(IAstModification.Remove(node, node.definingScope))
|
||||||
|
}
|
||||||
|
return removals
|
||||||
|
}
|
||||||
|
if(!subroutine.definingModule.isLibrary)
|
||||||
|
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
|
||||||
|
return listOf(IAstModification.Remove(subroutine, subroutine.definingScope))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val removeDoubleAssignments = deduplicateAssignments(subroutine.statements)
|
val removeDoubleAssignments = deduplicateAssignments(subroutine.statements)
|
||||||
return removeDoubleAssignments.map { IAstModification.Remove(it, subroutine) }
|
return removeDoubleAssignments.map { IAstModification.Remove(it, subroutine) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||||
|
if(decl.type==VarDeclType.VAR) {
|
||||||
|
val forceOutput = "force_output" in decl.definingBlock.options()
|
||||||
|
if (!forceOutput && !decl.autogeneratedDontRemove && !decl.sharedWithAsm && !decl.definingBlock.isInLibrary) {
|
||||||
|
if (callgraph.unused(decl)) {
|
||||||
|
errors.warn("removing unused variable '${decl.name}'", decl.position)
|
||||||
|
return listOf(IAstModification.Remove(decl, decl.definingScope))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
private fun deduplicateAssignments(statements: List<Statement>): List<Assignment> {
|
private fun deduplicateAssignments(statements: List<Statement>): List<Assignment> {
|
||||||
// removes 'duplicate' assignments that assign the same target directly after another
|
// removes 'duplicate' assignments that assign the same target directly after another
|
||||||
val linesToRemove = mutableListOf<Assignment>()
|
val linesToRemove = mutableListOf<Assignment>()
|
||||||
@ -101,7 +123,7 @@ internal class UnusedCodeRemover(private val program: Program,
|
|||||||
val assign1 = stmtPairs[0] as? Assignment
|
val assign1 = stmtPairs[0] as? Assignment
|
||||||
val assign2 = stmtPairs[1] as? Assignment
|
val assign2 = stmtPairs[1] as? Assignment
|
||||||
if (assign1 != null && assign2 != null && !assign2.isAugmentable) {
|
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())))
|
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!
|
// only remove the second assignment if its value is a simple expression!
|
||||||
when(assign2.value) {
|
when(assign2.value) {
|
||||||
|
146
compiler/test/AsmgenTests.kt
Normal file
146
compiler/test/AsmgenTests.kt
Normal file
@ -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"))
|
||||||
|
}
|
||||||
|
}
|
325
compiler/test/ModuleImporterTests.kt
Normal file
325
compiler/test/ModuleImporterTests.kt
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
91
compiler/test/TestCallgraph.kt
Normal file
91
compiler/test/TestCallgraph.kt
Normal file
@ -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.)")
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user