mirror of
https://github.com/irmen/prog8.git
synced 2025-06-15 02:23:36 +00:00
Compare commits
660 Commits
Author | SHA1 | Date | |
---|---|---|---|
8132edbb08 | |||
d29ce78c86 | |||
94bc9d7a69 | |||
e8faec0932 | |||
69ca4fe304 | |||
cd99fe46fd | |||
4825b4dc68 | |||
8d0607ef58 | |||
225295a7d8 | |||
4cd74daf53 | |||
6eb9118197 | |||
d0bd2f522c | |||
661c757236 | |||
aaa20093ef | |||
1eecdd6fa3 | |||
800b5b2a43 | |||
9d17421c66 | |||
0edd50e956 | |||
288d4f08b3 | |||
526e4b8bdc | |||
e0c5ccc16b | |||
ebc2c614d7 | |||
29f5a85158 | |||
8af2380a47 | |||
431f2a2088 | |||
e05ea887f6 | |||
95c0425151 | |||
47cbc7b1f9 | |||
e7b75d591c | |||
99f7d469f4 | |||
8a6ef17fbf | |||
5f337a0bd9 | |||
87862f772a | |||
3ab641aa21 | |||
3efa8da8e0 | |||
3e28ed4fe4 | |||
44949460ed | |||
83cc19ad6f | |||
66bb98c479 | |||
ff3f985658 | |||
2ba6c9ccbe | |||
3eaf111e7d | |||
30da26b9a9 | |||
e35ad0cc8f | |||
1a36302cf1 | |||
82a28bb555 | |||
c1ce0be451 | |||
c0a5f8fef0 | |||
702cf304d0 | |||
4dee8b6048 | |||
ec665e0cc1 | |||
aec3b82476 | |||
e83796b5b9 | |||
8eb69d6eda | |||
74b5124a42 | |||
b9706a180b | |||
8aeb8a9bb7 | |||
8f2e166a22 | |||
fdd91170dc | |||
c40ddb061b | |||
353d6cfc55 | |||
f37564c49c | |||
157484d94b | |||
7626c9fff7 | |||
1f55f9fc49 | |||
2554bc7ef8 | |||
7cb4100419 | |||
2d3b7eb878 | |||
4d01a78731 | |||
a03e36828a | |||
260fb65b06 | |||
9fb8526136 | |||
26fc5ff5e2 | |||
5060f0bb19 | |||
beaf6d449b | |||
4d68b508a2 | |||
cd825e386d | |||
095c8b2309 | |||
8b6eb74c58 | |||
aba437e5a2 | |||
efe3ed499b | |||
5595564a1f | |||
439761cb67 | |||
bee6c65293 | |||
10145b946b | |||
ebf4b50059 | |||
07cce3b3fc | |||
f2c19afd95 | |||
d159e70e1c | |||
ac693a2541 | |||
1e988116ce | |||
ec9e722927 | |||
4cd5e8c378 | |||
b759d5e06a | |||
1469033c1e | |||
c15fd75df7 | |||
73524e01a6 | |||
9e54e11113 | |||
01ac5f29db | |||
67a2241e32 | |||
72b6dc3de7 | |||
6f5b645995 | |||
458ad1de57 | |||
216f48b7c1 | |||
b2d1757e5a | |||
6e53eb9d5c | |||
e5ee5be9c5 | |||
bd237b2b95 | |||
d31cf766eb | |||
56d530ff04 | |||
0bbb2240f2 | |||
1c8e4dba73 | |||
4a9956c4a4 | |||
59c0e6ae32 | |||
94c30fc21e | |||
8bb3b3be20 | |||
85e3c2c5a2 | |||
4be381c597 | |||
6ff5470cf1 | |||
151dcfdef9 | |||
c282b4cb9f | |||
c426f4626c | |||
0e3c92626e | |||
5099525e24 | |||
e22b4cbb67 | |||
2b48828179 | |||
3e181362dd | |||
71fd98e39e | |||
71cd8b6d51 | |||
ad75fcbf7e | |||
f8b04a6357 | |||
d8fcbb78d3 | |||
8408bf3789 | |||
3e1185658e | |||
d778cdcd61 | |||
90b303fc03 | |||
eb86b1270d | |||
a1f0cc878b | |||
f2e2720b15 | |||
ec8cfe1591 | |||
22eac159e5 | |||
956b0c3fa7 | |||
a6427e0949 | |||
22031f39b0 | |||
c4673d3a67 | |||
e83e021541 | |||
c1f2ecd413 | |||
46fbe01df9 | |||
8647a8290e | |||
bac51f4b31 | |||
582aab180a | |||
5fb714fcb2 | |||
3994de77d0 | |||
24c8d1f1f4 | |||
110f877dcc | |||
9cd3a9f8e8 | |||
1464050bf5 | |||
95e9e1b550 | |||
bda1c1c1eb | |||
d020a7974a | |||
a51fad3aab | |||
3cd32778bb | |||
8d67056f84 | |||
e986973b5e | |||
448c934cba | |||
96ef7ba55d | |||
4372de1e7e | |||
af0fb88adf | |||
066233eee8 | |||
b6f85d10b0 | |||
6f75413c09 | |||
d45fe4ce74 | |||
e828c013e6 | |||
988459f744 | |||
7c701bdf3f | |||
446fc35d5c | |||
bec9cc7047 | |||
961380acb6 | |||
84c0685a60 | |||
629222f103 | |||
8c448e5bc2 | |||
b5fa6c2d0a | |||
680b2df08a | |||
09bd47f98b | |||
7f69f9ce4f | |||
4179b4e543 | |||
66364554c4 | |||
43f2448789 | |||
130cee1e70 | |||
b976360248 | |||
225bfc4164 | |||
d7ceda4d82 | |||
14d091e60a | |||
2809668ef4 | |||
bafb86e00b | |||
f5db31b8ff | |||
e1d0dbed0c | |||
1d1fe364d0 | |||
2b9316c4ff | |||
c50cbbb526 | |||
b93d9ecd7e | |||
96243db88b | |||
4daf75a8cc | |||
8c63d7cf5b | |||
6f78a32e64 | |||
af6731c9c8 | |||
25cf0d2b94 | |||
9389791d91 | |||
aa8191d0a1 | |||
0d5c78e875 | |||
e8679ae03b | |||
d1d224b7fc | |||
df995f7bc9 | |||
af39502450 | |||
ffa38955d6 | |||
8d82fb6d8f | |||
306770331a | |||
d3f433c8cf | |||
cf49cbd1f8 | |||
8a99e75299 | |||
2dbf849c82 | |||
ba3dce0b4c | |||
ca9588380a | |||
ae2619602d | |||
de06353194 | |||
3ff3f5e1cc | |||
4b747859b3 | |||
2201765366 | |||
dfa1d5e398 | |||
ce9a90f626 | |||
2deb18beb2 | |||
0f7454059c | |||
f9ba09ac4d | |||
4e74873eae | |||
f0cd03d14f | |||
f2b069c562 | |||
bc89306dc1 | |||
bf4da1655b | |||
d819aa270f | |||
e6d945f835 | |||
4fe408f1fd | |||
c376e42092 | |||
63a653cdf0 | |||
5d900800f2 | |||
def06dbc0b | |||
9b66a597bb | |||
f1ee3b4e60 | |||
6395e39d63 | |||
2a6d9d7e31 | |||
32a7cd31da | |||
dd4a56cb5f | |||
d110d1cb5f | |||
48858019b7 | |||
aff6b1fca5 | |||
d260182ef3 | |||
e39a38b0d9 | |||
82d7179c92 | |||
f42746ba06 | |||
1f69deaccd | |||
ea8b7ab193 | |||
9938959026 | |||
d5e5485d2e | |||
97b9c8f320 | |||
35aebbc209 | |||
81f7419f70 | |||
2f951bd54d | |||
18f5963b09 | |||
836509c1d1 | |||
949d536e42 | |||
f69b17e165 | |||
49a0584c54 | |||
e21aa2c8f0 | |||
40071b1431 | |||
02e29e6990 | |||
e19de0901e | |||
137d506e42 | |||
90c4a26d52 | |||
f378a8997b | |||
1377bed988 | |||
8f9f947c42 | |||
37f6c2858f | |||
13d7f239ab | |||
a6f3c84e28 | |||
fe4e0e9835 | |||
809917f13b | |||
2b35498370 | |||
f45eabdd9e | |||
438f3ee8d2 | |||
4bea31f051 | |||
5eae7a2b93 | |||
364ef3e55c | |||
e61818f194 | |||
0f9ce319d4 | |||
5d90871789 | |||
88a9e09918 | |||
c50ecf6055 | |||
a18de75da9 | |||
e112dfd910 | |||
9154d8bd37 | |||
0b55372b3b | |||
3ad7fb010f | |||
3f64d1bb5a | |||
a6f564ad88 | |||
d97da3bb7b | |||
a77d3c92ad | |||
6d17e5307c | |||
c2205e473a | |||
4ffb194847 | |||
744cd6ec42 | |||
f08fc18ab5 | |||
462af76770 | |||
9cec554f7c | |||
08b25e610d | |||
e896d5a1a6 | |||
b939562062 | |||
256781bba5 | |||
19705196d6 | |||
3ce692bb10 | |||
78bdbde3ae | |||
8d8c066447 | |||
5da9379c37 | |||
032d20ff37 | |||
d19b17cbfe | |||
4a4f8ff5db | |||
60a9209a14 | |||
0f9e167df3 | |||
2e2b8c498e | |||
144199730f | |||
4bb4eab3b2 | |||
cf9151f669 | |||
aef4598cec | |||
3ada0fdf84 | |||
a5d97b326e | |||
2640015fb1 | |||
6cd42ddafe | |||
1f17c22132 | |||
5c62f612cc | |||
b9ca1c2e2c | |||
93b2ff2e52 | |||
3991d23a69 | |||
1be139759c | |||
d0674ad688 | |||
ffb47458ff | |||
84ec1be8a4 | |||
f4dafec645 | |||
97ce72521d | |||
d2f0e74879 | |||
d9e3895c45 | |||
5075901830 | |||
f1193bb5a0 | |||
d3dc279105 | |||
acc942f690 | |||
e947067dcf | |||
bd9ebf4603 | |||
f41192a52a | |||
ff54d6abd7 | |||
f40bcc219f | |||
679965410a | |||
c6e13ae2a3 | |||
20cdcc673b | |||
89f46222d9 | |||
b27cbfac5e | |||
31c946aeeb | |||
bfc8a26381 | |||
9d98746501 | |||
63b03ba70c | |||
70bab76b36 | |||
15d24d4308 | |||
9ec62eb045 | |||
12f841e30d | |||
335599ed22 | |||
0b717f9e76 | |||
e941f6ecca | |||
ef7744dbda | |||
c83a61c460 | |||
335684caf7 | |||
8d6220ce51 | |||
39ea5c5f99 | |||
b03597ac13 | |||
58f323c087 | |||
513a68584c | |||
88d5c68b32 | |||
14f9382cf9 | |||
cffb582568 | |||
e1812ce16c | |||
7a3163f59a | |||
6f3b2749b0 | |||
c144d4e501 | |||
edfd9d55ba | |||
774897260e | |||
65ba91411d | |||
9cbb8e1a64 | |||
53e9ad5088 | |||
cf6ea63fa6 | |||
1de0ebb7bc | |||
77c1376d6d | |||
353f1954a5 | |||
8bf3406cf8 | |||
936bf9a05c | |||
4487499663 | |||
3976cc26a2 | |||
e6ff87ecd0 | |||
c0887b5f08 | |||
f14dda4eca | |||
bd7f75c130 | |||
fbe3ce008b | |||
7ac6c8f2d1 | |||
fdfbb7bdf0 | |||
1c16bbb742 | |||
9735527062 | |||
402827497e | |||
f81aa0d867 | |||
d32a970101 | |||
cd651aa416 | |||
8a3189123a | |||
b37231d0f5 | |||
3c55719bf1 | |||
af8279a9b9 | |||
c38508c262 | |||
b0e8738ab8 | |||
cae480768e | |||
a70276c190 | |||
0c461ffe2e | |||
237511f2d6 | |||
cdcb652033 | |||
71e678b382 | |||
3050156325 | |||
4bfdbad2e4 | |||
06137ecdc4 | |||
d89f5b0df8 | |||
b6e2b36692 | |||
a6d789cfbc | |||
c07907e7bd | |||
7d8496c874 | |||
164ac56db1 | |||
fdddb8ca64 | |||
a9d4b8b0fa | |||
ec7b9f54c2 | |||
307558a7e7 | |||
febf423eab | |||
a999c23014 | |||
69f1ade595 | |||
b166576e54 | |||
ee2ba5f398 | |||
cb9825484d | |||
76cda82e23 | |||
37b61d9e6b | |||
52f0222a6d | |||
75ccac2f2c | |||
5c771a91f7 | |||
a242ad10e6 | |||
b5086b6a8f | |||
3e47dad12a | |||
235610f40c | |||
6b59559c65 | |||
23e954f716 | |||
983c899cad | |||
c2f9385965 | |||
ceb2c9e4f8 | |||
68a7f9c665 | |||
ffd8d9c7c1 | |||
c66fc8630c | |||
9ca1c66f2b | |||
33647a29d0 | |||
02b12cc762 | |||
3280993e2a | |||
3723c22054 | |||
0a2c4ea0c4 | |||
58a83c0439 | |||
d665489054 | |||
9200992024 | |||
6408cc46a8 | |||
961bcdb7ae | |||
edee70cf31 | |||
1978a9815a | |||
f5e6db9d66 | |||
a94bc40ab0 | |||
534b5ced8f | |||
5ebd9b54e4 | |||
cc4e272526 | |||
295e199bfa | |||
df3371b0f0 | |||
e4fe1d2b8d | |||
b8b9244ffa | |||
3be3989e1c | |||
ed54cf680a | |||
95e76058d3 | |||
a6bee6a860 | |||
d22780ee44 | |||
f8b0b9575d | |||
4274fd168e | |||
be7f5957f3 | |||
f2e5d987a9 | |||
f01173d8db | |||
15e8e0bf6d | |||
2c59cbdece | |||
b73da4ed02 | |||
267adb4612 | |||
05c73fa8bc | |||
bfe9f442e6 | |||
0deadb694b | |||
bed34378be | |||
5927cf2d43 | |||
fffe36e358 | |||
fac2a2d7cb | |||
0af5582ca7 | |||
582d31263c | |||
4108a528e1 | |||
ab7d7c2907 | |||
152888ee93 | |||
22f8f4f359 | |||
5f3a9e189a | |||
b734dc44fd | |||
fab224f509 | |||
2f05ebb966 | |||
a335ba519a | |||
8805693ed2 | |||
f2bb238e9b | |||
131fe670a4 | |||
11e9539416 | |||
3881ebe429 | |||
29d1b8802e | |||
bcc75732e9 | |||
50a85ee6b0 | |||
2c7424fd43 | |||
7426587c38 | |||
1f39749a5e | |||
ca63051c71 | |||
6dd44aaf0d | |||
f89457ba68 | |||
efef205fcf | |||
0c561d8528 | |||
8bfa2c4c02 | |||
f0d4c3aba9 | |||
3a99115070 | |||
7232134931 | |||
954e911eb3 | |||
63c073c93f | |||
78feef9d59 | |||
4fbdd6d570 | |||
4929c198ba | |||
9409f17372 | |||
43781c02d0 | |||
824f06e17f | |||
21dbc6da97 | |||
270ea54ff7 | |||
771ac7aba7 | |||
97d36243f2 | |||
511b47bac4 | |||
f265199fbe | |||
a191ec71a4 | |||
82dce2dd53 | |||
29ac160811 | |||
5e50ea14f8 | |||
40e6091506 | |||
0ee4d420b1 | |||
66acce9e8e | |||
6c23ae14ab | |||
6f000d0d26 | |||
9d7eb3be5a | |||
835555171e | |||
68ce4a1bf0 | |||
a995867deb | |||
6bd99d63b4 | |||
baf5d3041a | |||
a326ffa00a | |||
d28dd92b47 | |||
1de328b2e8 | |||
51bb902162 | |||
4fd14f1366 | |||
91d9559f79 | |||
3245a9b157 | |||
2b28493bba | |||
1382728bd2 | |||
0422ad080a | |||
64d682bfde | |||
b182f7e693 | |||
e6be428589 | |||
85c7f8314b | |||
796d07a7f8 | |||
2af86a10b2 | |||
7fbe486dff | |||
87e5a9859a | |||
b036e5ed72 | |||
5f1ec80ae0 | |||
fbecedaf41 | |||
aa36acd65a | |||
8d1a4588d3 | |||
66d2af4453 | |||
ef6c731bb3 | |||
98a638a2f3 | |||
96d8a7f0d7 | |||
3162b10392 | |||
e2358de27c | |||
7facb4f372 | |||
ee90fed489 | |||
4796c56c35 | |||
e2cb031386 | |||
a0bc97b90c | |||
fd240899bd | |||
885b22df40 | |||
11de3db25f | |||
14a13da7ec | |||
875a71c786 | |||
0ff5b79353 | |||
8c4d276810 | |||
3dd38c0ac8 | |||
b8816a0e2f | |||
a01a9e76f9 | |||
357d704aec | |||
868df1865c | |||
654d74da1e | |||
59939c727a | |||
fbcf190324 | |||
b9922a90cc | |||
66e0b07428 | |||
01e617ae8f | |||
52769decd4 | |||
165eec4054 | |||
8c2e602cc7 | |||
b68f141568 | |||
b5d1e8653d | |||
f6d4c90dea | |||
b5b24636ae | |||
9dedbbf47c | |||
c493c3e5c6 | |||
61d4ca1d24 | |||
2cf9af4a6e | |||
bdcd10512f | |||
fec8db6a75 | |||
b400010426 | |||
28109a39ac | |||
651f0ec445 | |||
e61d3df380 | |||
15710207b2 | |||
adfddddac6 | |||
e46982f652 | |||
900c2aea23 | |||
42f8e98cab | |||
bed0e33b4f | |||
8d6542905d | |||
39798a1a4f | |||
befe4b8e9f | |||
772e48105e | |||
9afe451b8d | |||
89d469e77e | |||
59a43889a5 | |||
7caa0daffc | |||
5e854c2cf8 | |||
9edc92ec29 | |||
1d178080a3 | |||
aa94300bdd | |||
2d768c3f28 | |||
b79af624ae | |||
38208a7c9e | |||
8eff51904e | |||
c717f4573d | |||
984d251a6d | |||
8c3b43f3ed | |||
0f1485f30b |
13
.gitignore
vendored
13
.gitignore
vendored
@ -1,8 +1,8 @@
|
|||||||
.idea/workspace.xml
|
.idea/workspace.xml
|
||||||
.idea/discord.xml
|
.idea/discord.xml
|
||||||
/build/
|
build/
|
||||||
/dist/
|
dist/
|
||||||
/output/
|
output/
|
||||||
.*cache/
|
.*cache/
|
||||||
*.directory
|
*.directory
|
||||||
*.prg
|
*.prg
|
||||||
@ -12,9 +12,9 @@
|
|||||||
*.vice-mon-list
|
*.vice-mon-list
|
||||||
docs/build
|
docs/build
|
||||||
out/
|
out/
|
||||||
**/*.interp
|
parser/**/*.interp
|
||||||
**/*.tokens
|
parser/**/*.tokens
|
||||||
|
parser/**/*.java
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
*.egg
|
*.egg
|
||||||
*.egg-info
|
*.egg-info
|
||||||
@ -28,5 +28,4 @@ parsetab.py
|
|||||||
.attach_pid*
|
.attach_pid*
|
||||||
|
|
||||||
.gradle
|
.gradle
|
||||||
build/
|
|
||||||
/prog8compiler.jar
|
/prog8compiler.jar
|
||||||
|
10
.idea/inspectionProfiles/Project_Default.xml
generated
10
.idea/inspectionProfiles/Project_Default.xml
generated
@ -1,6 +1,16 @@
|
|||||||
<component name="InspectionProjectProfileManager">
|
<component name="InspectionProjectProfileManager">
|
||||||
<profile version="1.0">
|
<profile version="1.0">
|
||||||
<option name="myName" value="Project Default" />
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||||
|
<Languages>
|
||||||
|
<language minSize="100" isEnabled="false" name="JavaScript" />
|
||||||
|
<language isEnabled="false" name="Groovy" />
|
||||||
|
<language isEnabled="false" name="Style Sheets" />
|
||||||
|
<language minSize="70" name="Kotlin" />
|
||||||
|
<language isEnabled="false" name="TypeScript" />
|
||||||
|
<language isEnabled="false" name="ActionScript" />
|
||||||
|
</Languages>
|
||||||
|
</inspection_tool>
|
||||||
<inspection_tool class="SpellCheckingInspection" enabled="true" level="TYPO" enabled_by_default="true">
|
<inspection_tool class="SpellCheckingInspection" enabled="true" level="TYPO" enabled_by_default="true">
|
||||||
<option name="processCode" value="false" />
|
<option name="processCode" value="false" />
|
||||||
<option name="processLiterals" value="true" />
|
<option name="processLiterals" value="true" />
|
||||||
|
9
.idea/libraries/antlr_4_8_complete.xml
generated
Normal file
9
.idea/libraries/antlr_4_8_complete.xml
generated
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<component name="libraryTable">
|
||||||
|
<library name="antlr-4.8-complete">
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-4.8-complete.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</component>
|
9
.idea/libraries/antlr_runtime_4_8.xml
generated
Normal file
9
.idea/libraries/antlr_runtime_4_8.xml
generated
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<component name="libraryTable">
|
||||||
|
<library name="antlr-runtime-4.8">
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-runtime-4.8.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</component>
|
9
.idea/libraries/kotlinx_cli_jvm_0_1_0_dev_5.xml
generated
Normal file
9
.idea/libraries/kotlinx_cli_jvm_0_1_0_dev_5.xml
generated
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<component name="libraryTable">
|
||||||
|
<library name="kotlinx-cli-jvm-0.1.0-dev-5">
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$PROJECT_DIR$/compiler/lib/kotlinx-cli-jvm-0.1.0-dev-5.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</component>
|
29
.idea/markdown-navigator-enh.xml
generated
Normal file
29
.idea/markdown-navigator-enh.xml
generated
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="MarkdownEnhProjectSettings">
|
||||||
|
<AnnotatorSettings targetHasSpaces="true" linkCaseMismatch="true" wikiCaseMismatch="true" wikiLinkHasDashes="true" notUnderWikiHome="true" targetNotWikiPageExt="true" notUnderSourceWikiHome="true" targetNameHasAnchor="true" targetPathHasAnchor="true" wikiLinkHasSlash="true" wikiLinkHasSubdir="true" wikiLinkHasOnlyAnchor="true" linkTargetsWikiHasExt="true" linkTargetsWikiHasBadExt="true" notUnderSameRepo="true" targetNotUnderVcs="false" linkNeedsExt="true" linkHasBadExt="true" linkTargetNeedsExt="true" linkTargetHasBadExt="true" wikiLinkNotInWiki="true" imageTargetNotInRaw="true" repoRelativeAcrossVcsRoots="true" multipleWikiTargetsMatch="true" unresolvedLinkReference="true" linkIsIgnored="true" anchorIsIgnored="true" anchorIsUnresolved="true" anchorLineReferenceIsUnresolved="true" anchorLineReferenceFormat="true" anchorHasDuplicates="true" abbreviationDuplicates="true" abbreviationNotUsed="true" attributeIdDuplicateDefinition="true" attributeIdNotUsed="true" footnoteDuplicateDefinition="true" footnoteUnresolved="true" footnoteDuplicates="true" footnoteNotUsed="true" macroDuplicateDefinition="true" macroUnresolved="true" macroDuplicates="true" macroNotUsed="true" referenceDuplicateDefinition="true" referenceUnresolved="true" referenceDuplicates="true" referenceNotUsed="true" referenceUnresolvedNumericId="true" enumRefDuplicateDefinition="true" enumRefUnresolved="true" enumRefDuplicates="true" enumRefNotUsed="true" enumRefLinkUnresolved="true" enumRefLinkDuplicates="true" simTocUpdateNeeded="true" simTocTitleSpaceNeeded="true" />
|
||||||
|
<HtmlExportSettings updateOnSave="false" parentDir="" targetDir="" cssDir="css" scriptDir="js" plainHtml="false" imageDir="" copyLinkedImages="false" imagePathType="0" targetPathType="2" targetExt="" useTargetExt="false" noCssNoScripts="false" useElementStyleAttribute="false" linkToExportedHtml="true" exportOnSettingsChange="true" regenerateOnProjectOpen="false" linkFormatType="HTTP_ABSOLUTE" />
|
||||||
|
<LinkMapSettings>
|
||||||
|
<textMaps />
|
||||||
|
</LinkMapSettings>
|
||||||
|
</component>
|
||||||
|
<component name="MarkdownNavigatorHistory">
|
||||||
|
<PasteImageHistory checkeredTransparentBackground="false" filename="image" directory="" onPasteImageTargetRef="3" onPasteLinkText="0" onPasteImageElement="1" onPasteLinkElement="1" onPasteReferenceElement="2" cornerRadius="20" borderColor="0" transparentColor="16777215" borderWidth="1" trimTop="0" trimBottom="0" trimLeft="0" trimRight="0" transparent="false" roundCorners="false" showPreview="true" bordered="false" scaled="false" cropped="false" hideInapplicableOperations="false" preserveLinkFormat="false" scale="50" scalingInterpolation="1" transparentTolerance="0" saveAsDefaultOnOK="false" linkFormat="0" addHighlights="false" showHighlightCoordinates="true" showHighlights="false" mouseSelectionAddsHighlight="false" outerFilled="false" outerFillColor="0" outerFillTransparent="true" outerFillAlpha="30">
|
||||||
|
<highlightList />
|
||||||
|
<directories />
|
||||||
|
<filenames />
|
||||||
|
</PasteImageHistory>
|
||||||
|
<CopyImageHistory checkeredTransparentBackground="false" filename="image" directory="" onPasteImageTargetRef="3" onPasteLinkText="0" onPasteImageElement="1" onPasteLinkElement="1" onPasteReferenceElement="2" cornerRadius="20" borderColor="0" transparentColor="16777215" borderWidth="1" trimTop="0" trimBottom="0" trimLeft="0" trimRight="0" transparent="false" roundCorners="false" showPreview="true" bordered="false" scaled="false" cropped="false" hideInapplicableOperations="false" preserveLinkFormat="false" scale="50" scalingInterpolation="1" transparentTolerance="0" saveAsDefaultOnOK="false" linkFormat="0" addHighlights="false" showHighlightCoordinates="true" showHighlights="false" mouseSelectionAddsHighlight="false" outerFilled="false" outerFillColor="0" outerFillTransparent="true" outerFillAlpha="30">
|
||||||
|
<highlightList />
|
||||||
|
<directories />
|
||||||
|
<filenames />
|
||||||
|
</CopyImageHistory>
|
||||||
|
<PasteLinkHistory onPasteImageTargetRef="3" onPasteTargetRef="1" onPasteLinkText="0" onPasteImageElement="1" onPasteLinkElement="1" onPasteWikiElement="2" onPasteReferenceElement="2" hideInapplicableOperations="false" preserveLinkFormat="false" useHeadingForLinkText="false" linkFormat="0" saveAsDefaultOnOK="false" />
|
||||||
|
<TableToJsonHistory>
|
||||||
|
<entries />
|
||||||
|
</TableToJsonHistory>
|
||||||
|
<TableSortHistory>
|
||||||
|
<entries />
|
||||||
|
</TableSortHistory>
|
||||||
|
</component>
|
||||||
|
</project>
|
57
.idea/markdown-navigator.xml
generated
Normal file
57
.idea/markdown-navigator.xml
generated
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="FlexmarkProjectSettings">
|
||||||
|
<FlexmarkHtmlSettings flexmarkSpecExampleRendering="0" flexmarkSpecExampleRenderHtml="false">
|
||||||
|
<flexmarkSectionLanguages>
|
||||||
|
<option name="1" value="Markdown" />
|
||||||
|
<option name="2" value="HTML" />
|
||||||
|
<option name="3" value="flexmark-ast:1" />
|
||||||
|
</flexmarkSectionLanguages>
|
||||||
|
</FlexmarkHtmlSettings>
|
||||||
|
</component>
|
||||||
|
<component name="MarkdownProjectSettings">
|
||||||
|
<PreviewSettings splitEditorLayout="SPLIT" splitEditorPreview="PREVIEW" useGrayscaleRendering="false" zoomFactor="1.0" maxImageWidth="0" synchronizePreviewPosition="true" highlightPreviewType="LINE" highlightFadeOut="5" highlightOnTyping="true" synchronizeSourcePosition="true" verticallyAlignSourceAndPreviewSyncPosition="true" showSearchHighlightsInPreview="true" showSelectionInPreview="true" lastLayoutSetsDefault="false">
|
||||||
|
<PanelProvider>
|
||||||
|
<provider providerId="com.vladsch.md.nav.editor.javafx.html.panel" providerName="JavaFX WebView" />
|
||||||
|
</PanelProvider>
|
||||||
|
</PreviewSettings>
|
||||||
|
<ParserSettings gitHubSyntaxChange="false" correctedInvalidSettings="false" emojiShortcuts="1" emojiImages="0">
|
||||||
|
<PegdownExtensions>
|
||||||
|
<option name="ANCHORLINKS" value="true" />
|
||||||
|
<option name="ATXHEADERSPACE" value="true" />
|
||||||
|
<option name="FENCED_CODE_BLOCKS" value="true" />
|
||||||
|
<option name="INTELLIJ_DUMMY_IDENTIFIER" value="true" />
|
||||||
|
<option name="RELAXEDHRULES" value="true" />
|
||||||
|
<option name="STRIKETHROUGH" value="true" />
|
||||||
|
<option name="TABLES" value="true" />
|
||||||
|
<option name="TASKLISTITEMS" value="true" />
|
||||||
|
</PegdownExtensions>
|
||||||
|
<ParserOptions>
|
||||||
|
<option name="COMMONMARK_LISTS" value="true" />
|
||||||
|
<option name="EMOJI_SHORTCUTS" value="true" />
|
||||||
|
<option name="GFM_TABLE_RENDERING" value="true" />
|
||||||
|
<option name="PRODUCTION_SPEC_PARSER" value="true" />
|
||||||
|
<option name="SIM_TOC_BLANK_LINE_SPACER" value="true" />
|
||||||
|
</ParserOptions>
|
||||||
|
</ParserSettings>
|
||||||
|
<HtmlSettings headerTopEnabled="false" headerBottomEnabled="false" bodyTopEnabled="false" bodyBottomEnabled="false" addPageHeader="false" imageUriSerials="false" addDocTypeHtml="true" noParaTags="false" plantUmlConversion="0">
|
||||||
|
<GeneratorProvider>
|
||||||
|
<provider providerId="com.vladsch.md.nav.editor.javafx.html.generator" providerName="JavaFx HTML Generator" />
|
||||||
|
</GeneratorProvider>
|
||||||
|
<headerTop />
|
||||||
|
<headerBottom />
|
||||||
|
<bodyTop />
|
||||||
|
<bodyBottom />
|
||||||
|
</HtmlSettings>
|
||||||
|
<CssSettings previewScheme="UI_SCHEME" cssUri="" isCssUriEnabled="false" isCssUriSerial="true" isCssTextEnabled="false" isDynamicPageWidth="true">
|
||||||
|
<StylesheetProvider>
|
||||||
|
<provider providerId="com.vladsch.md.nav.editor.javafx.html.css" providerName="Default JavaFx Stylesheet" />
|
||||||
|
</StylesheetProvider>
|
||||||
|
<ScriptProviders>
|
||||||
|
<provider providerId="com.vladsch.md.nav.editor.hljs.html.script" providerName="HighlightJS Script" />
|
||||||
|
</ScriptProviders>
|
||||||
|
<cssText />
|
||||||
|
<cssUriHistory />
|
||||||
|
</CssSettings>
|
||||||
|
</component>
|
||||||
|
</project>
|
16
.idea/misc.xml
generated
16
.idea/misc.xml
generated
@ -1,5 +1,21 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
|
<component name="ANTLRGenerationPreferences">
|
||||||
|
<option name="perGrammarGenerationSettings">
|
||||||
|
<list>
|
||||||
|
<PerGrammarGenerationSettings>
|
||||||
|
<option name="fileName" value="$PROJECT_DIR$/parser/antlr/prog8.g4" />
|
||||||
|
<option name="autoGen" value="true" />
|
||||||
|
<option name="outputDir" value="$PROJECT_DIR$/parser/src/prog8/parser" />
|
||||||
|
<option name="libDir" value="" />
|
||||||
|
<option name="encoding" value="" />
|
||||||
|
<option name="pkg" value="" />
|
||||||
|
<option name="language" value="" />
|
||||||
|
<option name="generateListener" value="false" />
|
||||||
|
</PerGrammarGenerationSettings>
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="false" project-jdk-name="Kotlin SDK" project-jdk-type="KotlinSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="false" project-jdk-name="Kotlin SDK" project-jdk-type="KotlinSDK">
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
|
1
.idea/modules.xml
generated
1
.idea/modules.xml
generated
@ -2,7 +2,6 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectModuleManager">
|
<component name="ProjectModuleManager">
|
||||||
<modules>
|
<modules>
|
||||||
<module fileurl="file://$PROJECT_DIR$/DeprecatedStackVm/DeprecatedStackVm.iml" filepath="$PROJECT_DIR$/DeprecatedStackVm/DeprecatedStackVm.iml" />
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" />
|
<module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
|
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />
|
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />
|
||||||
|
2
.idea/vcs.xml
generated
2
.idea/vcs.xml
generated
@ -3,4 +3,4 @@
|
|||||||
<component name="VcsDirectoryMappings">
|
<component name="VcsDirectoryMappings">
|
||||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
|
@ -4,8 +4,8 @@ sudo: false
|
|||||||
# dist: xenial
|
# dist: xenial
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- chmod +x gradlew
|
- chmod +x ./gradlew
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- gradle test
|
- ./gradlew test
|
||||||
|
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module type="JAVA_MODULE" version="4">
|
|
||||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
|
||||||
<exclude-output />
|
|
||||||
<content url="file://$MODULE_DIR$" />
|
|
||||||
<orderEntry type="inheritedJdk" />
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
|
||||||
</component>
|
|
||||||
</module>
|
|
File diff suppressed because it is too large
Load Diff
@ -1,51 +0,0 @@
|
|||||||
package compiler.intermediate
|
|
||||||
|
|
||||||
import prog8.vm.RuntimeValue
|
|
||||||
import prog8.vm.stackvm.Syscall
|
|
||||||
|
|
||||||
open class Instruction(val opcode: Opcode,
|
|
||||||
val arg: RuntimeValue? = null,
|
|
||||||
val arg2: RuntimeValue? = null,
|
|
||||||
val callLabel: String? = null,
|
|
||||||
val callLabel2: String? = null)
|
|
||||||
{
|
|
||||||
var branchAddress: Int? = null
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
val argStr = arg?.toString() ?: ""
|
|
||||||
val result =
|
|
||||||
when {
|
|
||||||
opcode== Opcode.LINE -> "_line $callLabel"
|
|
||||||
opcode== Opcode.INLINE_ASSEMBLY -> {
|
|
||||||
// inline assembly is not written out (it can't be processed as intermediate language)
|
|
||||||
// instead, it is converted into a system call that can be intercepted by the vm
|
|
||||||
if(callLabel!=null)
|
|
||||||
"syscall SYSASM.$callLabel\n return"
|
|
||||||
else
|
|
||||||
"inline_assembly"
|
|
||||||
}
|
|
||||||
opcode== Opcode.INCLUDE_FILE -> {
|
|
||||||
"include_file \"$callLabel\" $arg $arg2"
|
|
||||||
}
|
|
||||||
opcode== Opcode.SYSCALL -> {
|
|
||||||
val syscall = Syscall.values().find { it.callNr==arg!!.numericValue() }
|
|
||||||
"syscall $syscall"
|
|
||||||
}
|
|
||||||
opcode in opcodesWithVarArgument -> {
|
|
||||||
// opcodes that manipulate a variable
|
|
||||||
"${opcode.name.toLowerCase()} ${callLabel?:""} ${callLabel2?:""}".trimEnd()
|
|
||||||
}
|
|
||||||
callLabel==null -> "${opcode.name.toLowerCase()} $argStr"
|
|
||||||
else -> "${opcode.name.toLowerCase()} $callLabel $argStr"
|
|
||||||
}
|
|
||||||
.trimEnd()
|
|
||||||
|
|
||||||
return " $result"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class LabelInstr(val name: String, val asmProc: Boolean) : Instruction(Opcode.NOP, null, null) {
|
|
||||||
override fun toString(): String {
|
|
||||||
return "\n$name:"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,548 +0,0 @@
|
|||||||
package compiler.intermediate
|
|
||||||
|
|
||||||
import prog8.ast.antlr.escape
|
|
||||||
import prog8.ast.base.*
|
|
||||||
import prog8.ast.expressions.NumericLiteralValue
|
|
||||||
import prog8.ast.expressions.ReferenceLiteralValue
|
|
||||||
import prog8.ast.statements.StructDecl
|
|
||||||
import prog8.ast.statements.VarDecl
|
|
||||||
import prog8.ast.statements.ZeropageWish
|
|
||||||
import prog8.compiler.CompilerException
|
|
||||||
import prog8.compiler.HeapValues
|
|
||||||
import prog8.compiler.Zeropage
|
|
||||||
import prog8.compiler.ZeropageDepletedError
|
|
||||||
import prog8.vm.RuntimeValue
|
|
||||||
import java.io.PrintStream
|
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
|
|
||||||
class IntermediateProgram(val name: String, var loadAddress: Int, val heap: HeapValues, val source: Path) {
|
|
||||||
|
|
||||||
class VariableParameters (val zp: ZeropageWish, val memberOfStruct: StructDecl?)
|
|
||||||
class Variable(val scopedname: String, val value: RuntimeValue, val params: VariableParameters)
|
|
||||||
|
|
||||||
class ProgramBlock(val name: String,
|
|
||||||
var address: Int?,
|
|
||||||
val instructions: MutableList<Instruction> = mutableListOf(),
|
|
||||||
val variables: MutableList<Variable> = mutableListOf(),
|
|
||||||
val memoryPointers: MutableMap<String, Pair<Int, DataType>> = mutableMapOf(),
|
|
||||||
val labels: MutableMap<String, Instruction> = mutableMapOf(), // names are fully scoped
|
|
||||||
val force_output: Boolean)
|
|
||||||
|
|
||||||
val allocatedZeropageVariables = mutableMapOf<String, Pair<Int, DataType>>()
|
|
||||||
val blocks = mutableListOf<ProgramBlock>()
|
|
||||||
val memory = mutableMapOf<Int, List<RuntimeValue>>()
|
|
||||||
private lateinit var currentBlock: ProgramBlock
|
|
||||||
|
|
||||||
fun allocateZeropage(zeropage: Zeropage) { // TODO not used anymore???
|
|
||||||
// allocates all @zp marked variables on the zeropage (for all blocks, as long as there is space in the ZP)
|
|
||||||
var notAllocated = 0
|
|
||||||
for(block in blocks) {
|
|
||||||
val zpVariables = block.variables.filter { it.params.zp==ZeropageWish.REQUIRE_ZEROPAGE || it.params.zp==ZeropageWish.PREFER_ZEROPAGE }
|
|
||||||
if (zpVariables.isNotEmpty()) {
|
|
||||||
for (variable in zpVariables) {
|
|
||||||
if(variable.params.zp==ZeropageWish.NOT_IN_ZEROPAGE || variable.params.memberOfStruct!=null)
|
|
||||||
throw CompilerException("zp conflict")
|
|
||||||
try {
|
|
||||||
val address = zeropage.allocate(variable.scopedname, variable.value.type, null)
|
|
||||||
allocatedZeropageVariables[variable.scopedname] = Pair(address, variable.value.type)
|
|
||||||
} catch (x: ZeropageDepletedError) {
|
|
||||||
printWarning(x.toString() + " variable ${variable.scopedname} type ${variable.value.type}")
|
|
||||||
notAllocated++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(notAllocated>0)
|
|
||||||
printWarning("$notAllocated variables marked for Zeropage could not be allocated there")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun optimize() {
|
|
||||||
println("Optimizing stackVM code...")
|
|
||||||
// remove nops (that are not a label)
|
|
||||||
for (blk in blocks) {
|
|
||||||
blk.instructions.removeIf { it.opcode== Opcode.NOP && it !is LabelInstr }
|
|
||||||
}
|
|
||||||
|
|
||||||
optimizeDataConversionAndUselessDiscards()
|
|
||||||
optimizeVariableCopying()
|
|
||||||
optimizeMultipleSequentialLineInstrs()
|
|
||||||
optimizeCallReturnIntoJump()
|
|
||||||
optimizeConditionalBranches()
|
|
||||||
// todo: add more optimizations to intermediate code!
|
|
||||||
|
|
||||||
optimizeRemoveNops() // must be done as the last step
|
|
||||||
optimizeMultipleSequentialLineInstrs() // once more
|
|
||||||
optimizeRemoveNops() // once more
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun optimizeConditionalBranches() {
|
|
||||||
// conditional branches that consume the value on the stack
|
|
||||||
// sometimes these are just constant values, so we can statically determine the branch
|
|
||||||
// or, they are preceded by a NOT instruction so we can simply remove that and flip the branch condition
|
|
||||||
val pushvalue = setOf(Opcode.PUSH_BYTE, Opcode.PUSH_WORD)
|
|
||||||
val notvalue = setOf(Opcode.NOT_BYTE, Opcode.NOT_WORD)
|
|
||||||
val branchOpcodes = setOf(Opcode.JZ, Opcode.JNZ, Opcode.JZW, Opcode.JNZW)
|
|
||||||
for(blk in blocks) {
|
|
||||||
val instructionsToReplace = mutableMapOf<Int, Instruction>()
|
|
||||||
blk.instructions.asSequence().withIndex().filter {it.value.opcode!= Opcode.LINE }.windowed(2).toList().forEach {
|
|
||||||
if (it[1].value.opcode in branchOpcodes) {
|
|
||||||
if (it[0].value.opcode in pushvalue) {
|
|
||||||
val value = it[0].value.arg!!.asBoolean
|
|
||||||
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
|
|
||||||
val replacement: Instruction =
|
|
||||||
if (value) {
|
|
||||||
when (it[1].value.opcode) {
|
|
||||||
Opcode.JNZ -> Instruction(Opcode.JUMP, callLabel = it[1].value.callLabel)
|
|
||||||
Opcode.JNZW -> Instruction(Opcode.JUMP, callLabel = it[1].value.callLabel)
|
|
||||||
else -> Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
when (it[1].value.opcode) {
|
|
||||||
Opcode.JZ -> Instruction(Opcode.JUMP, callLabel = it[1].value.callLabel)
|
|
||||||
Opcode.JZW -> Instruction(Opcode.JUMP, callLabel = it[1].value.callLabel)
|
|
||||||
else -> Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
instructionsToReplace[it[1].index] = replacement
|
|
||||||
}
|
|
||||||
else if (it[0].value.opcode in notvalue) {
|
|
||||||
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
|
|
||||||
val replacement: Instruction =
|
|
||||||
when (it[1].value.opcode) {
|
|
||||||
Opcode.JZ -> Instruction(Opcode.JNZ, callLabel = it[1].value.callLabel)
|
|
||||||
Opcode.JZW -> Instruction(Opcode.JNZW, callLabel = it[1].value.callLabel)
|
|
||||||
Opcode.JNZ -> Instruction(Opcode.JZ, callLabel = it[1].value.callLabel)
|
|
||||||
Opcode.JNZW -> Instruction(Opcode.JZW, callLabel = it[1].value.callLabel)
|
|
||||||
else -> Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
instructionsToReplace[it[1].index] = replacement
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (rins in instructionsToReplace) {
|
|
||||||
blk.instructions[rins.key] = rins.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun optimizeRemoveNops() {
|
|
||||||
// remove nops (that are not a label)
|
|
||||||
for (blk in blocks)
|
|
||||||
blk.instructions.removeIf { it.opcode== Opcode.NOP && it !is LabelInstr }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun optimizeCallReturnIntoJump() {
|
|
||||||
// replaces call X followed by return, by jump X
|
|
||||||
for(blk in blocks) {
|
|
||||||
val instructionsToReplace = mutableMapOf<Int, Instruction>()
|
|
||||||
|
|
||||||
blk.instructions.asSequence().withIndex().filter {it.value.opcode!= Opcode.LINE }.windowed(2).toList().forEach {
|
|
||||||
if(it[0].value.opcode== Opcode.CALL && it[1].value.opcode== Opcode.RETURN) {
|
|
||||||
instructionsToReplace[it[1].index] = Instruction(Opcode.JUMP, callLabel = it[0].value.callLabel)
|
|
||||||
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (rins in instructionsToReplace) {
|
|
||||||
blk.instructions[rins.key] = rins.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun optimizeMultipleSequentialLineInstrs() {
|
|
||||||
for(blk in blocks) {
|
|
||||||
val instructionsToReplace = mutableMapOf<Int, Instruction>()
|
|
||||||
|
|
||||||
blk.instructions.asSequence().withIndex().windowed(2).toList().forEach {
|
|
||||||
if (it[0].value.opcode == Opcode.LINE && it[1].value.opcode == Opcode.LINE)
|
|
||||||
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (rins in instructionsToReplace) {
|
|
||||||
blk.instructions[rins.key] = rins.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun optimizeVariableCopying() {
|
|
||||||
for(blk in blocks) {
|
|
||||||
|
|
||||||
val instructionsToReplace = mutableMapOf<Int, Instruction>()
|
|
||||||
|
|
||||||
blk.instructions.asSequence().withIndex().windowed(2).toList().forEach {
|
|
||||||
when (it[0].value.opcode) {
|
|
||||||
Opcode.PUSH_VAR_BYTE ->
|
|
||||||
if (it[1].value.opcode == Opcode.POP_VAR_BYTE) {
|
|
||||||
if (it[0].value.callLabel == it[1].value.callLabel) {
|
|
||||||
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
|
|
||||||
instructionsToReplace[it[1].index] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Opcode.PUSH_VAR_WORD ->
|
|
||||||
if (it[1].value.opcode == Opcode.POP_VAR_WORD) {
|
|
||||||
if (it[0].value.callLabel == it[1].value.callLabel) {
|
|
||||||
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
|
|
||||||
instructionsToReplace[it[1].index] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Opcode.PUSH_VAR_FLOAT ->
|
|
||||||
if (it[1].value.opcode == Opcode.POP_VAR_FLOAT) {
|
|
||||||
if (it[0].value.callLabel == it[1].value.callLabel) {
|
|
||||||
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
|
|
||||||
instructionsToReplace[it[1].index] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Opcode.PUSH_MEM_B, Opcode.PUSH_MEM_UB ->
|
|
||||||
if(it[1].value.opcode == Opcode.POP_MEM_BYTE) {
|
|
||||||
if(it[0].value.arg == it[1].value.arg) {
|
|
||||||
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
|
|
||||||
instructionsToReplace[it[1].index] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Opcode.PUSH_MEM_W, Opcode.PUSH_MEM_UW ->
|
|
||||||
if(it[1].value.opcode == Opcode.POP_MEM_WORD) {
|
|
||||||
if(it[0].value.arg == it[1].value.arg) {
|
|
||||||
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
|
|
||||||
instructionsToReplace[it[1].index] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Opcode.PUSH_MEM_FLOAT ->
|
|
||||||
if(it[1].value.opcode == Opcode.POP_MEM_FLOAT) {
|
|
||||||
if(it[0].value.arg == it[1].value.arg) {
|
|
||||||
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
|
|
||||||
instructionsToReplace[it[1].index] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (rins in instructionsToReplace) {
|
|
||||||
blk.instructions[rins.key] = rins.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun optimizeDataConversionAndUselessDiscards() {
|
|
||||||
// - push value followed by a data type conversion -> push the value in the correct type and remove the conversion
|
|
||||||
// - push something followed by a discard -> remove both
|
|
||||||
val instructionsToReplace = mutableMapOf<Int, Instruction>()
|
|
||||||
|
|
||||||
fun optimizeDiscardAfterPush(index0: Int, index1: Int, ins1: Instruction) {
|
|
||||||
if (ins1.opcode == Opcode.DISCARD_FLOAT || ins1.opcode == Opcode.DISCARD_WORD || ins1.opcode == Opcode.DISCARD_BYTE) {
|
|
||||||
instructionsToReplace[index0] = Instruction(Opcode.NOP)
|
|
||||||
instructionsToReplace[index1] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun optimizeFloatConversion(index0: Int, index1: Int, ins1: Instruction) {
|
|
||||||
when (ins1.opcode) {
|
|
||||||
Opcode.DISCARD_FLOAT -> {
|
|
||||||
instructionsToReplace[index0] = Instruction(Opcode.NOP)
|
|
||||||
instructionsToReplace[index1] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
Opcode.DISCARD_BYTE, Opcode.DISCARD_WORD -> throw CompilerException("invalid discard type following a float")
|
|
||||||
else -> throw CompilerException("invalid conversion opcode ${ins1.opcode} following a float")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun optimizeWordConversion(index0: Int, ins0: Instruction, index1: Int, ins1: Instruction) {
|
|
||||||
when (ins1.opcode) {
|
|
||||||
Opcode.CAST_UW_TO_B, Opcode.CAST_W_TO_B -> {
|
|
||||||
val ins = Instruction(Opcode.PUSH_BYTE, ins0.arg!!.cast(DataType.BYTE))
|
|
||||||
instructionsToReplace[index0] = ins
|
|
||||||
instructionsToReplace[index1] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
Opcode.CAST_W_TO_UB, Opcode.CAST_UW_TO_UB -> {
|
|
||||||
val ins = Instruction(Opcode.PUSH_BYTE, RuntimeValue(DataType.UBYTE, ins0.arg!!.integerValue() and 255))
|
|
||||||
instructionsToReplace[index0] = ins
|
|
||||||
instructionsToReplace[index1] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
Opcode.MSB -> {
|
|
||||||
val ins = Instruction(Opcode.PUSH_BYTE, RuntimeValue(DataType.UBYTE, ins0.arg!!.integerValue() ushr 8 and 255))
|
|
||||||
instructionsToReplace[index0] = ins
|
|
||||||
instructionsToReplace[index1] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
Opcode.CAST_W_TO_F, Opcode.CAST_UW_TO_F -> {
|
|
||||||
val ins = Instruction(Opcode.PUSH_FLOAT, RuntimeValue(DataType.FLOAT, ins0.arg!!.integerValue().toDouble()))
|
|
||||||
instructionsToReplace[index0] = ins
|
|
||||||
instructionsToReplace[index1] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
Opcode.CAST_UW_TO_W -> {
|
|
||||||
val cv = ins0.arg!!.cast(DataType.WORD)
|
|
||||||
instructionsToReplace[index0] = Instruction(Opcode.PUSH_WORD, cv)
|
|
||||||
instructionsToReplace[index1] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
Opcode.CAST_W_TO_UW -> {
|
|
||||||
val cv = ins0.arg!!.cast(DataType.UWORD)
|
|
||||||
instructionsToReplace[index0] = Instruction(Opcode.PUSH_WORD, cv)
|
|
||||||
instructionsToReplace[index1] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
Opcode.DISCARD_WORD -> {
|
|
||||||
instructionsToReplace[index0] = Instruction(Opcode.NOP)
|
|
||||||
instructionsToReplace[index1] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
Opcode.DISCARD_BYTE, Opcode.DISCARD_FLOAT -> throw CompilerException("invalid discard type following a byte")
|
|
||||||
else -> throw CompilerException("invalid conversion opcode ${ins1.opcode} following a word")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun optimizeByteConversion(index0: Int, ins0: Instruction, index1: Int, ins1: Instruction) {
|
|
||||||
when (ins1.opcode) {
|
|
||||||
Opcode.CAST_B_TO_UB, Opcode.CAST_UB_TO_B,
|
|
||||||
Opcode.CAST_W_TO_B, Opcode.CAST_W_TO_UB,
|
|
||||||
Opcode.CAST_UW_TO_B, Opcode.CAST_UW_TO_UB -> instructionsToReplace[index1] = Instruction(Opcode.NOP)
|
|
||||||
Opcode.MSB -> throw CompilerException("msb of a byte")
|
|
||||||
Opcode.CAST_UB_TO_UW -> {
|
|
||||||
val ins = Instruction(Opcode.PUSH_WORD, RuntimeValue(DataType.UWORD, ins0.arg!!.integerValue()))
|
|
||||||
instructionsToReplace[index0] = ins
|
|
||||||
instructionsToReplace[index1] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
Opcode.CAST_B_TO_W -> {
|
|
||||||
val ins = Instruction(Opcode.PUSH_WORD, RuntimeValue(DataType.WORD, ins0.arg!!.integerValue()))
|
|
||||||
instructionsToReplace[index0] = ins
|
|
||||||
instructionsToReplace[index1] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
Opcode.CAST_B_TO_UW -> {
|
|
||||||
val ins = Instruction(Opcode.PUSH_WORD, ins0.arg!!.cast(DataType.UWORD))
|
|
||||||
instructionsToReplace[index0] = ins
|
|
||||||
instructionsToReplace[index1] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
Opcode.CAST_UB_TO_W -> {
|
|
||||||
val ins = Instruction(Opcode.PUSH_WORD, ins0.arg!!.cast(DataType.WORD))
|
|
||||||
instructionsToReplace[index0] = ins
|
|
||||||
instructionsToReplace[index1] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
Opcode.CAST_B_TO_F, Opcode.CAST_UB_TO_F -> {
|
|
||||||
val ins = Instruction(Opcode.PUSH_FLOAT, RuntimeValue(DataType.FLOAT, ins0.arg!!.integerValue().toDouble()))
|
|
||||||
instructionsToReplace[index0] = ins
|
|
||||||
instructionsToReplace[index1] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
Opcode.CAST_W_TO_F, Opcode.CAST_UW_TO_F -> throw CompilerException("invalid conversion following a byte")
|
|
||||||
Opcode.DISCARD_BYTE -> {
|
|
||||||
instructionsToReplace[index0] = Instruction(Opcode.NOP)
|
|
||||||
instructionsToReplace[index1] = Instruction(Opcode.NOP)
|
|
||||||
}
|
|
||||||
Opcode.DISCARD_WORD, Opcode.DISCARD_FLOAT -> throw CompilerException("invalid discard type following a byte")
|
|
||||||
Opcode.MKWORD -> {}
|
|
||||||
else -> throw CompilerException("invalid conversion opcode ${ins1.opcode}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for(blk in blocks) {
|
|
||||||
instructionsToReplace.clear()
|
|
||||||
|
|
||||||
val typeConversionOpcodes = setOf(
|
|
||||||
Opcode.MSB,
|
|
||||||
Opcode.MKWORD,
|
|
||||||
Opcode.CAST_UB_TO_B,
|
|
||||||
Opcode.CAST_UB_TO_UW,
|
|
||||||
Opcode.CAST_UB_TO_W,
|
|
||||||
Opcode.CAST_UB_TO_F,
|
|
||||||
Opcode.CAST_B_TO_UB,
|
|
||||||
Opcode.CAST_B_TO_UW,
|
|
||||||
Opcode.CAST_B_TO_W,
|
|
||||||
Opcode.CAST_B_TO_F,
|
|
||||||
Opcode.CAST_UW_TO_UB,
|
|
||||||
Opcode.CAST_UW_TO_B,
|
|
||||||
Opcode.CAST_UW_TO_W,
|
|
||||||
Opcode.CAST_UW_TO_F,
|
|
||||||
Opcode.CAST_W_TO_UB,
|
|
||||||
Opcode.CAST_W_TO_B,
|
|
||||||
Opcode.CAST_W_TO_UW,
|
|
||||||
Opcode.CAST_W_TO_F,
|
|
||||||
Opcode.CAST_F_TO_UB,
|
|
||||||
Opcode.CAST_F_TO_B,
|
|
||||||
Opcode.CAST_F_TO_UW,
|
|
||||||
Opcode.CAST_F_TO_W,
|
|
||||||
Opcode.DISCARD_BYTE,
|
|
||||||
Opcode.DISCARD_WORD,
|
|
||||||
Opcode.DISCARD_FLOAT
|
|
||||||
)
|
|
||||||
blk.instructions.asSequence().withIndex().windowed(2).toList().forEach {
|
|
||||||
if (it[1].value.opcode in typeConversionOpcodes) {
|
|
||||||
when (it[0].value.opcode) {
|
|
||||||
Opcode.PUSH_BYTE -> optimizeByteConversion(it[0].index, it[0].value, it[1].index, it[1].value)
|
|
||||||
Opcode.PUSH_WORD -> optimizeWordConversion(it[0].index, it[0].value, it[1].index, it[1].value)
|
|
||||||
Opcode.PUSH_FLOAT -> optimizeFloatConversion(it[0].index, it[1].index, it[1].value)
|
|
||||||
Opcode.PUSH_VAR_FLOAT,
|
|
||||||
Opcode.PUSH_VAR_WORD,
|
|
||||||
Opcode.PUSH_VAR_BYTE,
|
|
||||||
Opcode.PUSH_MEM_B, Opcode.PUSH_MEM_UB,
|
|
||||||
Opcode.PUSH_MEM_W, Opcode.PUSH_MEM_UW,
|
|
||||||
Opcode.PUSH_MEM_FLOAT -> optimizeDiscardAfterPush(it[0].index, it[1].index, it[1].value)
|
|
||||||
else -> {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (rins in instructionsToReplace) {
|
|
||||||
blk.instructions[rins.key] = rins.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun variable(scopedname: String, decl: VarDecl) {
|
|
||||||
when(decl.type) {
|
|
||||||
VarDeclType.VAR -> {
|
|
||||||
// var decls that are defined inside of a StructDecl are skipped in the output
|
|
||||||
// because every occurrence of the members will have a separate mangled vardecl for that occurrence
|
|
||||||
if(decl.parent is StructDecl)
|
|
||||||
return
|
|
||||||
|
|
||||||
val valueparams = VariableParameters(decl.zeropage, decl.struct)
|
|
||||||
val value = when(decl.datatype) {
|
|
||||||
in NumericDatatypes -> {
|
|
||||||
RuntimeValue(decl.datatype, (decl.value as NumericLiteralValue).number)
|
|
||||||
}
|
|
||||||
in StringDatatypes -> {
|
|
||||||
val litval = (decl.value as ReferenceLiteralValue)
|
|
||||||
if(litval.heapId==null)
|
|
||||||
throw CompilerException("string should already be in the heap")
|
|
||||||
RuntimeValue(decl.datatype, heapId = litval.heapId)
|
|
||||||
}
|
|
||||||
in ArrayDatatypes -> {
|
|
||||||
val litval = (decl.value as? ReferenceLiteralValue)
|
|
||||||
if(litval!=null && litval.heapId==null)
|
|
||||||
throw CompilerException("array should already be in the heap")
|
|
||||||
if(litval!=null){
|
|
||||||
RuntimeValue(decl.datatype, heapId = litval.heapId)
|
|
||||||
} else {
|
|
||||||
throw CompilerException("initialization value expected")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.STRUCT -> {
|
|
||||||
// struct variables have been flattened already
|
|
||||||
return
|
|
||||||
}
|
|
||||||
else -> throw CompilerException("weird datatype")
|
|
||||||
}
|
|
||||||
currentBlock.variables.add(Variable(scopedname, value, valueparams))
|
|
||||||
}
|
|
||||||
VarDeclType.MEMORY -> {
|
|
||||||
// note that constants are all folded away, but assembly code may still refer to them
|
|
||||||
val lv = decl.value as NumericLiteralValue
|
|
||||||
if(lv.type!= DataType.UWORD && lv.type!= DataType.UBYTE)
|
|
||||||
throw CompilerException("expected integer memory address $lv")
|
|
||||||
currentBlock.memoryPointers[scopedname] = Pair(lv.number.toInt(), decl.datatype)
|
|
||||||
}
|
|
||||||
VarDeclType.CONST -> {
|
|
||||||
// note that constants are all folded away, but assembly code may still refer to them (if their integers)
|
|
||||||
// floating point constants are not generated at all!!
|
|
||||||
val lv = decl.value as NumericLiteralValue
|
|
||||||
if(lv.type in IntegerDatatypes)
|
|
||||||
currentBlock.memoryPointers[scopedname] = Pair(lv.number.toInt(), decl.datatype)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun instr(opcode: Opcode, arg: RuntimeValue? = null, arg2: RuntimeValue? = null, callLabel: String? = null, callLabel2: String? = null) {
|
|
||||||
currentBlock.instructions.add(Instruction(opcode, arg, arg2, callLabel, callLabel2))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun label(labelname: String, asmProc: Boolean=false) {
|
|
||||||
val instr = LabelInstr(labelname, asmProc)
|
|
||||||
currentBlock.instructions.add(instr)
|
|
||||||
currentBlock.labels[labelname] = instr
|
|
||||||
}
|
|
||||||
|
|
||||||
fun line(position: Position) {
|
|
||||||
currentBlock.instructions.add(Instruction(Opcode.LINE, callLabel = "${position.line} ${position.file}"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun removeLastInstruction() {
|
|
||||||
currentBlock.instructions.removeAt(currentBlock.instructions.lastIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun memoryPointer(name: String, address: Int, datatype: DataType) {
|
|
||||||
currentBlock.memoryPointers[name] = Pair(address, datatype)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun newBlock(name: String, address: Int?, options: Set<String>) {
|
|
||||||
currentBlock = ProgramBlock(name, address, force_output = "force_output" in options)
|
|
||||||
blocks.add(currentBlock)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun writeCode(out: PrintStream, embeddedLabels: Boolean=true) {
|
|
||||||
out.println("; stackVM program code for '$name'")
|
|
||||||
writeMemory(out)
|
|
||||||
writeHeap(out)
|
|
||||||
for(blk in blocks) {
|
|
||||||
writeBlock(out, blk, embeddedLabels)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun writeHeap(out: PrintStream) {
|
|
||||||
out.println("%heap")
|
|
||||||
heap.allEntries().forEach {
|
|
||||||
out.print("${it.key} ${it.value.type.name.toLowerCase()} ")
|
|
||||||
when {
|
|
||||||
it.value.str!=null ->
|
|
||||||
out.println("\"${escape(it.value.str!!)}\"")
|
|
||||||
it.value.array!=null -> {
|
|
||||||
// this array can contain both normal integers, and pointer values
|
|
||||||
val arrayvalues = it.value.array!!.map { av ->
|
|
||||||
when {
|
|
||||||
av.integer!=null -> av.integer.toString()
|
|
||||||
av.addressOf!=null -> {
|
|
||||||
if(av.addressOf.scopedname==null)
|
|
||||||
throw CompilerException("AddressOf scopedname should have been set")
|
|
||||||
else
|
|
||||||
"&${av.addressOf.scopedname}"
|
|
||||||
}
|
|
||||||
else -> throw CompilerException("weird array value")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out.println(arrayvalues)
|
|
||||||
}
|
|
||||||
it.value.doubleArray!=null ->
|
|
||||||
out.println(it.value.doubleArray!!.toList())
|
|
||||||
else -> throw CompilerException("invalid heap entry $it")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out.println("%end_heap")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun writeBlock(out: PrintStream, blk: ProgramBlock, embeddedLabels: Boolean) {
|
|
||||||
out.println("\n%block ${blk.name} ${blk.address?.toString(16) ?: ""}")
|
|
||||||
|
|
||||||
out.println("%variables")
|
|
||||||
for (variable in blk.variables) {
|
|
||||||
if(variable.params.zp==ZeropageWish.REQUIRE_ZEROPAGE)
|
|
||||||
throw CompilerException("zp conflict")
|
|
||||||
val valuestr = variable.value.toString()
|
|
||||||
val struct = if(variable.params.memberOfStruct==null) "" else "struct=${variable.params.memberOfStruct.name}"
|
|
||||||
out.println("${variable.scopedname} ${variable.value.type.name.toLowerCase()} $valuestr zp=${variable.params.zp} s=$struct")
|
|
||||||
}
|
|
||||||
out.println("%end_variables")
|
|
||||||
out.println("%memorypointers")
|
|
||||||
for (iconst in blk.memoryPointers) {
|
|
||||||
out.println("${iconst.key} ${iconst.value.second.name.toLowerCase()} uw:${iconst.value.first.toString(16)}")
|
|
||||||
}
|
|
||||||
out.println("%end_memorypointers")
|
|
||||||
out.println("%instructions")
|
|
||||||
val labels = blk.labels.entries.associateBy({ it.value }) { it.key }
|
|
||||||
for (instr in blk.instructions) {
|
|
||||||
if (!embeddedLabels) {
|
|
||||||
val label = labels[instr]
|
|
||||||
if (label != null)
|
|
||||||
out.println("$label:")
|
|
||||||
} else {
|
|
||||||
out.println(instr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out.println("%end_instructions")
|
|
||||||
|
|
||||||
out.println("%end_block")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun writeMemory(out: PrintStream) {
|
|
||||||
out.println("%memory")
|
|
||||||
if (memory.isNotEmpty())
|
|
||||||
TODO("add support for writing/reading initial memory values")
|
|
||||||
out.println("%end_memory")
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,291 +0,0 @@
|
|||||||
package compiler.intermediate
|
|
||||||
|
|
||||||
enum class Opcode {
|
|
||||||
|
|
||||||
// pushing values on the (evaluation) stack
|
|
||||||
PUSH_BYTE, // push byte value
|
|
||||||
PUSH_WORD, // push word value (or 'address' of string / array)
|
|
||||||
PUSH_FLOAT, // push float value
|
|
||||||
PUSH_MEM_B, // push byte value from memory to stack
|
|
||||||
PUSH_MEM_UB, // push unsigned byte value from memory to stack
|
|
||||||
PUSH_MEM_W, // push word value from memory to stack
|
|
||||||
PUSH_MEM_UW, // push unsigned word value from memory to stack
|
|
||||||
PUSH_MEM_FLOAT, // push float value from memory to stack
|
|
||||||
PUSH_MEMREAD, // push memory value from address that's on the stack
|
|
||||||
PUSH_VAR_BYTE, // push byte variable (ubyte, byte)
|
|
||||||
PUSH_VAR_WORD, // push word variable (uword, word)
|
|
||||||
PUSH_VAR_FLOAT, // push float variable
|
|
||||||
PUSH_REGAX_WORD, // push registers A/X as a 16-bit word
|
|
||||||
PUSH_REGAY_WORD, // push registers A/Y as a 16-bit word
|
|
||||||
PUSH_REGXY_WORD, // push registers X/Y as a 16-bit word
|
|
||||||
PUSH_ADDR_HEAPVAR, // push the address of the variable that's on the heap (string or array)
|
|
||||||
DUP_B, // duplicate the top byte on the stack
|
|
||||||
DUP_W, // duplicate the top word on the stack
|
|
||||||
|
|
||||||
// popping values off the (evaluation) stack, possibly storing them in another location
|
|
||||||
DISCARD_BYTE, // discard top byte value
|
|
||||||
DISCARD_WORD, // discard top word value
|
|
||||||
DISCARD_FLOAT, // discard top float value
|
|
||||||
POP_MEM_BYTE, // pop (u)byte value into destination memory address
|
|
||||||
POP_MEM_WORD, // pop (u)word value into destination memory address
|
|
||||||
POP_MEM_FLOAT, // pop float value into destination memory address
|
|
||||||
POP_MEMWRITE, // pop address and byte stack and write the byte to the memory address
|
|
||||||
POP_VAR_BYTE, // pop (u)byte value into variable
|
|
||||||
POP_VAR_WORD, // pop (u)word value into variable
|
|
||||||
POP_VAR_FLOAT, // pop float value into variable
|
|
||||||
POP_REGAX_WORD, // pop uword from stack into A/X registers
|
|
||||||
POP_REGAY_WORD, // pop uword from stack into A/Y registers
|
|
||||||
POP_REGXY_WORD, // pop uword from stack into X/Y registers
|
|
||||||
|
|
||||||
// numeric arithmetic
|
|
||||||
ADD_UB,
|
|
||||||
ADD_B,
|
|
||||||
ADD_UW,
|
|
||||||
ADD_W,
|
|
||||||
ADD_F,
|
|
||||||
SUB_UB,
|
|
||||||
SUB_B,
|
|
||||||
SUB_UW,
|
|
||||||
SUB_W,
|
|
||||||
SUB_F,
|
|
||||||
MUL_UB,
|
|
||||||
MUL_B,
|
|
||||||
MUL_UW,
|
|
||||||
MUL_W,
|
|
||||||
MUL_F,
|
|
||||||
IDIV_UB,
|
|
||||||
IDIV_B,
|
|
||||||
IDIV_UW,
|
|
||||||
IDIV_W,
|
|
||||||
DIV_F,
|
|
||||||
REMAINDER_UB, // signed remainder is undefined/unimplemented
|
|
||||||
REMAINDER_UW, // signed remainder is undefined/unimplemented
|
|
||||||
POW_F,
|
|
||||||
NEG_B,
|
|
||||||
NEG_W,
|
|
||||||
NEG_F,
|
|
||||||
ABS_B,
|
|
||||||
ABS_W,
|
|
||||||
ABS_F,
|
|
||||||
|
|
||||||
// bit shifts and bitwise arithmetic
|
|
||||||
SHIFTEDL_BYTE, // shifts stack value rather than in-place mem/var
|
|
||||||
SHIFTEDL_WORD, // shifts stack value rather than in-place mem/var
|
|
||||||
SHIFTEDR_UBYTE, // shifts stack value rather than in-place mem/var
|
|
||||||
SHIFTEDR_SBYTE, // shifts stack value rather than in-place mem/var
|
|
||||||
SHIFTEDR_UWORD, // shifts stack value rather than in-place mem/var
|
|
||||||
SHIFTEDR_SWORD, // shifts stack value rather than in-place mem/var
|
|
||||||
SHL_BYTE,
|
|
||||||
SHL_WORD,
|
|
||||||
SHL_MEM_BYTE,
|
|
||||||
SHL_MEM_WORD,
|
|
||||||
SHL_VAR_BYTE,
|
|
||||||
SHL_VAR_WORD,
|
|
||||||
SHR_UBYTE,
|
|
||||||
SHR_SBYTE,
|
|
||||||
SHR_UWORD,
|
|
||||||
SHR_SWORD,
|
|
||||||
SHR_MEM_UBYTE,
|
|
||||||
SHR_MEM_SBYTE,
|
|
||||||
SHR_MEM_UWORD,
|
|
||||||
SHR_MEM_SWORD,
|
|
||||||
SHR_VAR_UBYTE,
|
|
||||||
SHR_VAR_SBYTE,
|
|
||||||
SHR_VAR_UWORD,
|
|
||||||
SHR_VAR_SWORD,
|
|
||||||
ROL_BYTE,
|
|
||||||
ROL_WORD,
|
|
||||||
ROL_MEM_BYTE,
|
|
||||||
ROL_MEM_WORD,
|
|
||||||
ROL_VAR_BYTE,
|
|
||||||
ROL_VAR_WORD,
|
|
||||||
ROR_BYTE,
|
|
||||||
ROR_WORD,
|
|
||||||
ROR_MEM_BYTE,
|
|
||||||
ROR_MEM_WORD,
|
|
||||||
ROR_VAR_BYTE,
|
|
||||||
ROR_VAR_WORD,
|
|
||||||
ROL2_BYTE,
|
|
||||||
ROL2_WORD,
|
|
||||||
ROL2_MEM_BYTE,
|
|
||||||
ROL2_MEM_WORD,
|
|
||||||
ROL2_VAR_BYTE,
|
|
||||||
ROL2_VAR_WORD,
|
|
||||||
ROR2_BYTE,
|
|
||||||
ROR2_WORD,
|
|
||||||
ROR2_MEM_BYTE,
|
|
||||||
ROR2_MEM_WORD,
|
|
||||||
ROR2_VAR_BYTE,
|
|
||||||
ROR2_VAR_WORD,
|
|
||||||
BITAND_BYTE,
|
|
||||||
BITAND_WORD,
|
|
||||||
BITOR_BYTE,
|
|
||||||
BITOR_WORD,
|
|
||||||
BITXOR_BYTE,
|
|
||||||
BITXOR_WORD,
|
|
||||||
INV_BYTE,
|
|
||||||
INV_WORD,
|
|
||||||
|
|
||||||
// numeric type conversions
|
|
||||||
MSB, // note: lsb is equivalent to CAST_UW_TO_UB or CAST_W_TO_UB
|
|
||||||
MKWORD, // create a word from lsb + msb
|
|
||||||
CAST_UB_TO_B,
|
|
||||||
CAST_UB_TO_UW,
|
|
||||||
CAST_UB_TO_W,
|
|
||||||
CAST_UB_TO_F,
|
|
||||||
CAST_B_TO_UB,
|
|
||||||
CAST_B_TO_UW,
|
|
||||||
CAST_B_TO_W,
|
|
||||||
CAST_B_TO_F,
|
|
||||||
CAST_W_TO_UB,
|
|
||||||
CAST_W_TO_B,
|
|
||||||
CAST_W_TO_UW,
|
|
||||||
CAST_W_TO_F,
|
|
||||||
CAST_UW_TO_UB,
|
|
||||||
CAST_UW_TO_B,
|
|
||||||
CAST_UW_TO_W,
|
|
||||||
CAST_UW_TO_F,
|
|
||||||
CAST_F_TO_UB,
|
|
||||||
CAST_F_TO_B,
|
|
||||||
CAST_F_TO_UW,
|
|
||||||
CAST_F_TO_W,
|
|
||||||
|
|
||||||
// logical operations
|
|
||||||
AND_BYTE,
|
|
||||||
AND_WORD,
|
|
||||||
OR_BYTE,
|
|
||||||
OR_WORD,
|
|
||||||
XOR_BYTE,
|
|
||||||
XOR_WORD,
|
|
||||||
NOT_BYTE,
|
|
||||||
NOT_WORD,
|
|
||||||
|
|
||||||
// increment, decrement
|
|
||||||
INC_VAR_B,
|
|
||||||
INC_VAR_UB,
|
|
||||||
INC_VAR_W,
|
|
||||||
INC_VAR_UW,
|
|
||||||
INC_VAR_F,
|
|
||||||
DEC_VAR_B,
|
|
||||||
DEC_VAR_UB,
|
|
||||||
DEC_VAR_W,
|
|
||||||
DEC_VAR_UW,
|
|
||||||
DEC_VAR_F,
|
|
||||||
INC_MEMORY, // increment direct address
|
|
||||||
DEC_MEMORY, // decrement direct address
|
|
||||||
POP_INC_MEMORY, // increment address from stack
|
|
||||||
POP_DEC_MEMORY, // decrement address from address
|
|
||||||
|
|
||||||
// comparisons
|
|
||||||
LESS_B,
|
|
||||||
LESS_UB,
|
|
||||||
LESS_W,
|
|
||||||
LESS_UW,
|
|
||||||
LESS_F,
|
|
||||||
GREATER_B,
|
|
||||||
GREATER_UB,
|
|
||||||
GREATER_W,
|
|
||||||
GREATER_UW,
|
|
||||||
GREATER_F,
|
|
||||||
LESSEQ_B,
|
|
||||||
LESSEQ_UB,
|
|
||||||
LESSEQ_W,
|
|
||||||
LESSEQ_UW,
|
|
||||||
LESSEQ_F,
|
|
||||||
GREATEREQ_B,
|
|
||||||
GREATEREQ_UB,
|
|
||||||
GREATEREQ_W,
|
|
||||||
GREATEREQ_UW,
|
|
||||||
GREATEREQ_F,
|
|
||||||
EQUAL_BYTE,
|
|
||||||
EQUAL_WORD,
|
|
||||||
EQUAL_F,
|
|
||||||
NOTEQUAL_BYTE,
|
|
||||||
NOTEQUAL_WORD,
|
|
||||||
NOTEQUAL_F,
|
|
||||||
CMP_B, // sets processor status flags based on comparison, instead of pushing a result value
|
|
||||||
CMP_UB, // sets processor status flags based on comparison, instead of pushing a result value
|
|
||||||
CMP_W, // sets processor status flags based on comparison, instead of pushing a result value
|
|
||||||
CMP_UW, // sets processor status flags based on comparison, instead of pushing a result value
|
|
||||||
|
|
||||||
// array access and simple manipulations
|
|
||||||
READ_INDEXED_VAR_BYTE,
|
|
||||||
READ_INDEXED_VAR_WORD,
|
|
||||||
READ_INDEXED_VAR_FLOAT,
|
|
||||||
WRITE_INDEXED_VAR_BYTE,
|
|
||||||
WRITE_INDEXED_VAR_WORD,
|
|
||||||
WRITE_INDEXED_VAR_FLOAT,
|
|
||||||
INC_INDEXED_VAR_B,
|
|
||||||
INC_INDEXED_VAR_UB,
|
|
||||||
INC_INDEXED_VAR_W,
|
|
||||||
INC_INDEXED_VAR_UW,
|
|
||||||
INC_INDEXED_VAR_FLOAT,
|
|
||||||
DEC_INDEXED_VAR_B,
|
|
||||||
DEC_INDEXED_VAR_UB,
|
|
||||||
DEC_INDEXED_VAR_W,
|
|
||||||
DEC_INDEXED_VAR_UW,
|
|
||||||
DEC_INDEXED_VAR_FLOAT,
|
|
||||||
|
|
||||||
// branching, without consuming a value from the stack
|
|
||||||
JUMP,
|
|
||||||
BCS, // branch if carry set
|
|
||||||
BCC, // branch if carry clear
|
|
||||||
BZ, // branch if zero flag
|
|
||||||
BNZ, // branch if not zero flag
|
|
||||||
BNEG, // branch if negative flag
|
|
||||||
BPOS, // branch if not negative flag
|
|
||||||
BVS, // branch if overflow flag
|
|
||||||
BVC, // branch if not overflow flag
|
|
||||||
// branching, based on value on the stack (which is consumed)
|
|
||||||
JZ, // branch if value is zero (byte)
|
|
||||||
JNZ, // branch if value is not zero (byte)
|
|
||||||
JZW, // branch if value is zero (word)
|
|
||||||
JNZW, // branch if value is not zero (word)
|
|
||||||
|
|
||||||
// subroutines
|
|
||||||
CALL,
|
|
||||||
RETURN,
|
|
||||||
SYSCALL,
|
|
||||||
START_PROCDEF,
|
|
||||||
END_PROCDEF,
|
|
||||||
|
|
||||||
// misc
|
|
||||||
SEC, // set carry status flag NOTE: is mostly fake, carry flag is not affected by any numeric operations
|
|
||||||
CLC, // clear carry status flag NOTE: is mostly fake, carry flag is not affected by any numeric operations
|
|
||||||
SEI, // set irq-disable status flag
|
|
||||||
CLI, // clear irq-disable status flag
|
|
||||||
CARRY_TO_A, // load var/register A with carry status bit
|
|
||||||
RSAVE, // save all internal registers and status flags
|
|
||||||
RSAVEX, // save just X (the evaluation stack pointer)
|
|
||||||
RRESTORE, // restore all internal registers and status flags
|
|
||||||
RRESTOREX, // restore just X (the evaluation stack pointer)
|
|
||||||
|
|
||||||
NOP, // do nothing
|
|
||||||
BREAKPOINT, // breakpoint
|
|
||||||
TERMINATE, // end the program
|
|
||||||
LINE, // track source file line number
|
|
||||||
INLINE_ASSEMBLY, // container to hold inline raw assembly code
|
|
||||||
INCLUDE_FILE // directive to include a file at this position in the memory of the program
|
|
||||||
}
|
|
||||||
|
|
||||||
val opcodesWithVarArgument = setOf(
|
|
||||||
Opcode.INC_VAR_B, Opcode.INC_VAR_W, Opcode.DEC_VAR_B, Opcode.DEC_VAR_W,
|
|
||||||
Opcode.INC_VAR_UB, Opcode.INC_VAR_UW, Opcode.DEC_VAR_UB, Opcode.DEC_VAR_UW,
|
|
||||||
Opcode.SHR_VAR_SBYTE, Opcode.SHR_VAR_UBYTE, Opcode.SHR_VAR_SWORD, Opcode.SHR_VAR_UWORD,
|
|
||||||
Opcode.SHL_VAR_BYTE, Opcode.SHL_VAR_WORD,
|
|
||||||
Opcode.ROL_VAR_BYTE, Opcode.ROL_VAR_WORD, Opcode.ROR_VAR_BYTE, Opcode.ROR_VAR_WORD,
|
|
||||||
Opcode.ROL2_VAR_BYTE, Opcode.ROL2_VAR_WORD, Opcode.ROR2_VAR_BYTE, Opcode.ROR2_VAR_WORD,
|
|
||||||
Opcode.POP_VAR_BYTE, Opcode.POP_VAR_WORD, Opcode.POP_VAR_FLOAT,
|
|
||||||
Opcode.PUSH_VAR_BYTE, Opcode.PUSH_VAR_WORD, Opcode.PUSH_VAR_FLOAT, Opcode.PUSH_ADDR_HEAPVAR,
|
|
||||||
Opcode.READ_INDEXED_VAR_BYTE, Opcode.READ_INDEXED_VAR_WORD, Opcode.READ_INDEXED_VAR_FLOAT,
|
|
||||||
Opcode.WRITE_INDEXED_VAR_BYTE, Opcode.WRITE_INDEXED_VAR_WORD, Opcode.WRITE_INDEXED_VAR_FLOAT,
|
|
||||||
Opcode.INC_INDEXED_VAR_UB, Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UW,
|
|
||||||
Opcode.INC_INDEXED_VAR_W, Opcode.INC_INDEXED_VAR_FLOAT,
|
|
||||||
Opcode.DEC_INDEXED_VAR_UB, Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UW,
|
|
||||||
Opcode.DEC_INDEXED_VAR_W, Opcode.DEC_INDEXED_VAR_FLOAT
|
|
||||||
)
|
|
||||||
|
|
||||||
val branchOpcodes = setOf(
|
|
||||||
Opcode.BCS, Opcode.BCC, Opcode.BZ, Opcode.BNZ,
|
|
||||||
Opcode.BNEG, Opcode.BPOS, Opcode.BVS, Opcode.BVC
|
|
||||||
)
|
|
@ -1,761 +0,0 @@
|
|||||||
package compiler.target.c64.codegen
|
|
||||||
|
|
||||||
// note: to put stuff on the stack, we use Absolute,X addressing mode which is 3 bytes / 4 cycles
|
|
||||||
// possible space optimization is to use zeropage (indirect),Y which is 2 bytes, but 5 cycles
|
|
||||||
|
|
||||||
import prog8.ast.antlr.escape
|
|
||||||
import prog8.ast.base.DataType
|
|
||||||
import prog8.ast.base.initvarsSubName
|
|
||||||
import prog8.ast.statements.ZeropageWish
|
|
||||||
import prog8.compiler.*
|
|
||||||
import prog8.compiler.intermediate.Instruction
|
|
||||||
import prog8.compiler.intermediate.IntermediateProgram
|
|
||||||
import prog8.compiler.intermediate.LabelInstr
|
|
||||||
import prog8.compiler.intermediate.Opcode
|
|
||||||
import prog8.compiler.target.c64.AssemblyProgram
|
|
||||||
import prog8.compiler.target.c64.MachineDefinition
|
|
||||||
import prog8.compiler.target.c64.Petscii
|
|
||||||
import prog8.vm.RuntimeValue
|
|
||||||
import java.io.File
|
|
||||||
import java.util.*
|
|
||||||
import kotlin.math.abs
|
|
||||||
|
|
||||||
|
|
||||||
class AssemblyError(msg: String) : RuntimeException(msg)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
internal fun intVal(valueInstr: Instruction) = valueInstr.arg!!.integerValue()
|
|
||||||
internal fun hexVal(valueInstr: Instruction) = valueInstr.arg!!.integerValue().toHex()
|
|
||||||
internal fun hexValPlusOne(valueInstr: Instruction) = (valueInstr.arg!!.integerValue()+1).toHex()
|
|
||||||
internal fun getFloatConst(value: RuntimeValue): String =
|
|
||||||
globalFloatConsts[value.numericValue().toDouble()]
|
|
||||||
?: throw AssemblyError("should have a global float const for number $value")
|
|
||||||
|
|
||||||
internal val globalFloatConsts = mutableMapOf<Double, String>()
|
|
||||||
|
|
||||||
internal fun signExtendA(into: String) =
|
|
||||||
"""
|
|
||||||
ora #$7f
|
|
||||||
bmi +
|
|
||||||
lda #0
|
|
||||||
+ sta $into
|
|
||||||
"""
|
|
||||||
|
|
||||||
class AsmGen(private val options: CompilationOptions, private val program: IntermediateProgram,
|
|
||||||
private val heap: HeapValues, private val zeropage: Zeropage) {
|
|
||||||
private val assemblyLines = mutableListOf<String>()
|
|
||||||
private lateinit var block: IntermediateProgram.ProgramBlock
|
|
||||||
|
|
||||||
init {
|
|
||||||
// Convert invalid label names (such as "<anon-1>") to something that's allowed.
|
|
||||||
val newblocks = mutableListOf<IntermediateProgram.ProgramBlock>()
|
|
||||||
for(block in program.blocks) {
|
|
||||||
val newvars = block.variables.map { IntermediateProgram.Variable(symname(it.scopedname, block), it.value, it.params) }.toMutableList()
|
|
||||||
val newlabels = block.labels.map { symname(it.key, block) to it.value}.toMap().toMutableMap()
|
|
||||||
val newinstructions = block.instructions.asSequence().map {
|
|
||||||
when {
|
|
||||||
it is LabelInstr -> LabelInstr(symname(it.name, block), it.asmProc)
|
|
||||||
it.opcode == Opcode.INLINE_ASSEMBLY -> it
|
|
||||||
else ->
|
|
||||||
Instruction(it.opcode, it.arg, it.arg2,
|
|
||||||
callLabel = if (it.callLabel != null) symname(it.callLabel, block) else null,
|
|
||||||
callLabel2 = if (it.callLabel2 != null) symname(it.callLabel2, block) else null)
|
|
||||||
}
|
|
||||||
}.toMutableList()
|
|
||||||
val newMempointers = block.memoryPointers.map { symname(it.key, block) to it.value }.toMap().toMutableMap()
|
|
||||||
val newblock = IntermediateProgram.ProgramBlock(
|
|
||||||
block.name,
|
|
||||||
block.address,
|
|
||||||
newinstructions,
|
|
||||||
newvars,
|
|
||||||
newMempointers,
|
|
||||||
newlabels,
|
|
||||||
force_output = block.force_output)
|
|
||||||
newblocks.add(newblock)
|
|
||||||
}
|
|
||||||
program.blocks.clear()
|
|
||||||
program.blocks.addAll(newblocks)
|
|
||||||
|
|
||||||
val newAllocatedZp = program.allocatedZeropageVariables.map { symname(it.key, null) to it.value}
|
|
||||||
program.allocatedZeropageVariables.clear()
|
|
||||||
program.allocatedZeropageVariables.putAll(newAllocatedZp)
|
|
||||||
|
|
||||||
// make a list of all const floats that are used
|
|
||||||
for(block in program.blocks) {
|
|
||||||
for(ins in block.instructions.filter{it.arg?.type== DataType.FLOAT}) {
|
|
||||||
val float = ins.arg!!.numericValue().toDouble()
|
|
||||||
if(float !in globalFloatConsts)
|
|
||||||
globalFloatConsts[float] = "prog8_const_float_${globalFloatConsts.size}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun compileToAssembly(optimize: Boolean): AssemblyProgram {
|
|
||||||
println("Generating assembly code from intermediate code... ")
|
|
||||||
|
|
||||||
assemblyLines.clear()
|
|
||||||
header()
|
|
||||||
for(b in program.blocks)
|
|
||||||
block2asm(b)
|
|
||||||
|
|
||||||
if(optimize) {
|
|
||||||
var optimizationsDone = 1
|
|
||||||
while (optimizationsDone > 0) {
|
|
||||||
optimizationsDone = optimizeAssembly(assemblyLines)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
File("${program.name}.asm").printWriter().use {
|
|
||||||
for (line in assemblyLines) { it.println(line) }
|
|
||||||
}
|
|
||||||
|
|
||||||
return AssemblyProgram(program.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun out(str: String, splitlines: Boolean=true) {
|
|
||||||
if(splitlines) {
|
|
||||||
for (line in str.split('\n')) {
|
|
||||||
val trimmed = if (line.startsWith(' ')) "\t" + line.trim() else line.trim()
|
|
||||||
// trimmed = trimmed.replace(Regex("^\\+\\s+"), "+\t") // sanitize local label indentation
|
|
||||||
assemblyLines.add(trimmed)
|
|
||||||
}
|
|
||||||
} else assemblyLines.add(str)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// convert a fully scoped name (defined in the given block) to a valid assembly symbol name
|
|
||||||
private fun symname(scoped: String, block: IntermediateProgram.ProgramBlock?): String {
|
|
||||||
if(' ' in scoped)
|
|
||||||
return scoped
|
|
||||||
val blockLocal: Boolean
|
|
||||||
var name = if (block!=null && scoped.startsWith("${block.name}.")) {
|
|
||||||
blockLocal = true
|
|
||||||
scoped.substring(block.name.length+1)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
blockLocal = false
|
|
||||||
scoped
|
|
||||||
}
|
|
||||||
name = name.replace("<", "prog8_").replace(">", "") // take care of the autogenerated invalid (anon) label names
|
|
||||||
if(name=="-")
|
|
||||||
return "-"
|
|
||||||
if(blockLocal)
|
|
||||||
name = name.replace(".", "_")
|
|
||||||
else {
|
|
||||||
val parts = name.split(".", limit=2)
|
|
||||||
if(parts.size>1)
|
|
||||||
name = "${parts[0]}.${parts[1].replace(".", "_")}"
|
|
||||||
}
|
|
||||||
return name.replace("-", "")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun makeFloatFill(flt: MachineDefinition.Mflpt5): String {
|
|
||||||
val b0 = "$"+flt.b0.toString(16).padStart(2, '0')
|
|
||||||
val b1 = "$"+flt.b1.toString(16).padStart(2, '0')
|
|
||||||
val b2 = "$"+flt.b2.toString(16).padStart(2, '0')
|
|
||||||
val b3 = "$"+flt.b3.toString(16).padStart(2, '0')
|
|
||||||
val b4 = "$"+flt.b4.toString(16).padStart(2, '0')
|
|
||||||
return "$b0, $b1, $b2, $b3, $b4"
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun header() {
|
|
||||||
val ourName = this.javaClass.name
|
|
||||||
out("; 6502 assembly code for '${program.name}'")
|
|
||||||
out("; generated by $ourName on ${Date()}")
|
|
||||||
out("; assembler syntax is for the 64tasm cross-assembler")
|
|
||||||
out("; output options: output=${options.output} launcher=${options.launcher} zp=${options.zeropage}")
|
|
||||||
out("\n.cpu '6502'\n.enc 'none'\n")
|
|
||||||
|
|
||||||
if(program.loadAddress==0) // fix load address
|
|
||||||
program.loadAddress = if(options.launcher==LauncherType.BASIC)
|
|
||||||
MachineDefinition.BASIC_LOAD_ADDRESS else MachineDefinition.RAW_LOAD_ADDRESS
|
|
||||||
|
|
||||||
when {
|
|
||||||
options.launcher == LauncherType.BASIC -> {
|
|
||||||
if (program.loadAddress != 0x0801)
|
|
||||||
throw AssemblyError("BASIC output must have load address $0801")
|
|
||||||
out("; ---- basic program with sys call ----")
|
|
||||||
out("* = ${program.loadAddress.toHex()}")
|
|
||||||
val year = Calendar.getInstance().get(Calendar.YEAR)
|
|
||||||
out(" .word (+), $year")
|
|
||||||
out(" .null $9e, format(' %d ', _prog8_entrypoint), $3a, $8f, ' prog8 by idj'")
|
|
||||||
out("+\t.word 0")
|
|
||||||
out("_prog8_entrypoint\t; assembly code starts here\n")
|
|
||||||
out(" jsr prog8_lib.init_system")
|
|
||||||
}
|
|
||||||
options.output == OutputType.PRG -> {
|
|
||||||
out("; ---- program without basic sys call ----")
|
|
||||||
out("* = ${program.loadAddress.toHex()}\n")
|
|
||||||
out(" jsr prog8_lib.init_system")
|
|
||||||
}
|
|
||||||
options.output == OutputType.RAW -> {
|
|
||||||
out("; ---- raw assembler program ----")
|
|
||||||
out("* = ${program.loadAddress.toHex()}\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(zeropage.exitProgramStrategy!=Zeropage.ExitProgramStrategy.CLEAN_EXIT) {
|
|
||||||
// disable shift-commodore charset switching and run/stop key
|
|
||||||
out(" lda #$80")
|
|
||||||
out(" lda #$80")
|
|
||||||
out(" sta 657\t; disable charset switching")
|
|
||||||
out(" lda #239")
|
|
||||||
out(" sta 808\t; disable run/stop key")
|
|
||||||
}
|
|
||||||
|
|
||||||
out(" ldx #\$ff\t; init estack pointer")
|
|
||||||
out(" ; initialize the variables in each block")
|
|
||||||
for(block in program.blocks) {
|
|
||||||
val initVarsLabel = block.instructions.firstOrNull { it is LabelInstr && it.name== initvarsSubName } as? LabelInstr
|
|
||||||
if(initVarsLabel!=null)
|
|
||||||
out(" jsr ${block.name}.${initVarsLabel.name}")
|
|
||||||
}
|
|
||||||
out(" clc")
|
|
||||||
when(zeropage.exitProgramStrategy) {
|
|
||||||
Zeropage.ExitProgramStrategy.CLEAN_EXIT -> {
|
|
||||||
out(" jmp main.start\t; jump to program entrypoint")
|
|
||||||
}
|
|
||||||
Zeropage.ExitProgramStrategy.SYSTEM_RESET -> {
|
|
||||||
out(" jsr main.start\t; call program entrypoint")
|
|
||||||
out(" jmp (c64.RESET_VEC)\t; cold reset")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out("")
|
|
||||||
|
|
||||||
// the global list of all floating point constants for the whole program
|
|
||||||
for(flt in globalFloatConsts) {
|
|
||||||
val floatFill = makeFloatFill(MachineDefinition.Mflpt5.fromNumber(flt.key))
|
|
||||||
out("${flt.value}\t.byte $floatFill ; float ${flt.key}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun block2asm(blk: IntermediateProgram.ProgramBlock) {
|
|
||||||
block = blk
|
|
||||||
out("\n\n; ---- block: '${block.name}' ----")
|
|
||||||
if(!blk.force_output)
|
|
||||||
out("${block.name}\t.proc\n")
|
|
||||||
if(block.address!=null) {
|
|
||||||
out(".cerror * > ${block.address?.toHex()}, 'block address overlaps by ', *-${block.address?.toHex()},' bytes'")
|
|
||||||
out("* = ${block.address?.toHex()}")
|
|
||||||
}
|
|
||||||
|
|
||||||
// deal with zeropage variables
|
|
||||||
for(variable in blk.variables) {
|
|
||||||
val sym = symname(blk.name+"."+variable.scopedname, null)
|
|
||||||
val zpVar = program.allocatedZeropageVariables[sym]
|
|
||||||
if(zpVar==null) {
|
|
||||||
// This var is not on the ZP yet. Attempt to move it there (if it's not a float, those take up too much space)
|
|
||||||
if(variable.params.zp != ZeropageWish.NOT_IN_ZEROPAGE &&
|
|
||||||
variable.value.type in zeropage.allowedDatatypes
|
|
||||||
&& variable.value.type != DataType.FLOAT) {
|
|
||||||
try {
|
|
||||||
val address = zeropage.allocate(sym, variable.value.type, null)
|
|
||||||
out("${variable.scopedname} = $address\t; auto zp ${variable.value.type}")
|
|
||||||
// make sure we add the var to the set of zpvars for this block
|
|
||||||
program.allocatedZeropageVariables[sym] = Pair(address, variable.value.type)
|
|
||||||
} catch (x: ZeropageDepletedError) {
|
|
||||||
// leave it as it is.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// it was already allocated on the zp
|
|
||||||
out("${variable.scopedname} = ${zpVar.first}\t; zp ${zpVar.second}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
out("\n; memdefs and kernel subroutines")
|
|
||||||
memdefs2asm(block)
|
|
||||||
out("\n; non-zeropage variables")
|
|
||||||
vardecls2asm(block)
|
|
||||||
out("")
|
|
||||||
|
|
||||||
val instructionPatternWindowSize = 8 // increase once patterns occur longer than this.
|
|
||||||
var processed = 0
|
|
||||||
|
|
||||||
for (ins in block.instructions.windowed(instructionPatternWindowSize, partialWindows = true)) {
|
|
||||||
if (processed == 0) {
|
|
||||||
processed = instr2asm(ins)
|
|
||||||
if (processed == 0) {
|
|
||||||
// the instructions are not recognised yet and can't be translated into assembly
|
|
||||||
throw CompilerException("no asm translation found for instruction pattern: $ins")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
processed--
|
|
||||||
}
|
|
||||||
if(!blk.force_output)
|
|
||||||
out("\n\t.pend\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun memdefs2asm(block: IntermediateProgram.ProgramBlock) {
|
|
||||||
for(m in block.memoryPointers) {
|
|
||||||
out(" ${m.key} = ${m.value.first.toHex()}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun vardecls2asm(block: IntermediateProgram.ProgramBlock) {
|
|
||||||
val uniqueNames = block.variables.map { it.scopedname }.toSet()
|
|
||||||
if (uniqueNames.size != block.variables.size)
|
|
||||||
throw AssemblyError("not all variables have unique names")
|
|
||||||
|
|
||||||
// these are the non-zeropage variables.
|
|
||||||
// first get all the flattened struct members, they MUST remain in order
|
|
||||||
out("; flattened struct members")
|
|
||||||
val (structMembers, normalVars) = block.variables.partition { it.params.memberOfStruct!=null }
|
|
||||||
structMembers.forEach { vardecl2asm(it.scopedname, it.value, it.params) }
|
|
||||||
|
|
||||||
// sort the other variables by type
|
|
||||||
out("; other variables sorted by type")
|
|
||||||
val sortedVars = normalVars.sortedBy { it.value.type }
|
|
||||||
for (variable in sortedVars) {
|
|
||||||
val sym = symname(block.name + "." + variable.scopedname, null)
|
|
||||||
if(sym in program.allocatedZeropageVariables)
|
|
||||||
continue // skip the ones that already belong in the zero page
|
|
||||||
vardecl2asm(variable.scopedname, variable.value, variable.params)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun vardecl2asm(varname: String, value: RuntimeValue, parameters: IntermediateProgram.VariableParameters) {
|
|
||||||
when (value.type) {
|
|
||||||
DataType.UBYTE -> out("$varname\t.byte 0")
|
|
||||||
DataType.BYTE -> out("$varname\t.char 0")
|
|
||||||
DataType.UWORD -> out("$varname\t.word 0")
|
|
||||||
DataType.WORD -> out("$varname\t.sint 0")
|
|
||||||
DataType.FLOAT -> out("$varname\t.byte 0,0,0,0,0 ; float")
|
|
||||||
DataType.STR, DataType.STR_S -> {
|
|
||||||
val rawStr = heap.get(value.heapId!!).str!!
|
|
||||||
val bytes = encodeStr(rawStr, value.type).map { "$" + it.toString(16).padStart(2, '0') }
|
|
||||||
out("$varname\t; ${value.type} \"${escape(rawStr).replace("\u0000", "<NULL>")}\"")
|
|
||||||
for (chunk in bytes.chunked(16))
|
|
||||||
out(" .byte " + chunk.joinToString())
|
|
||||||
}
|
|
||||||
DataType.ARRAY_UB -> {
|
|
||||||
// unsigned integer byte arraysize
|
|
||||||
val data = makeArrayFillDataUnsigned(value)
|
|
||||||
if (data.size <= 16)
|
|
||||||
out("$varname\t.byte ${data.joinToString()}")
|
|
||||||
else {
|
|
||||||
out(varname)
|
|
||||||
for (chunk in data.chunked(16))
|
|
||||||
out(" .byte " + chunk.joinToString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.ARRAY_B -> {
|
|
||||||
// signed integer byte arraysize
|
|
||||||
val data = makeArrayFillDataSigned(value)
|
|
||||||
if (data.size <= 16)
|
|
||||||
out("$varname\t.char ${data.joinToString()}")
|
|
||||||
else {
|
|
||||||
out(varname)
|
|
||||||
for (chunk in data.chunked(16))
|
|
||||||
out(" .char " + chunk.joinToString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.ARRAY_UW -> {
|
|
||||||
// unsigned word arraysize
|
|
||||||
val data = makeArrayFillDataUnsigned(value)
|
|
||||||
if (data.size <= 16)
|
|
||||||
out("$varname\t.word ${data.joinToString()}")
|
|
||||||
else {
|
|
||||||
out(varname)
|
|
||||||
for (chunk in data.chunked(16))
|
|
||||||
out(" .word " + chunk.joinToString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.ARRAY_W -> {
|
|
||||||
// signed word arraysize
|
|
||||||
val data = makeArrayFillDataSigned(value)
|
|
||||||
if (data.size <= 16)
|
|
||||||
out("$varname\t.sint ${data.joinToString()}")
|
|
||||||
else {
|
|
||||||
out(varname)
|
|
||||||
for (chunk in data.chunked(16))
|
|
||||||
out(" .sint " + chunk.joinToString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.ARRAY_F -> {
|
|
||||||
// float arraysize
|
|
||||||
val array = heap.get(value.heapId!!).doubleArray!!
|
|
||||||
val floatFills = array.map { makeFloatFill(MachineDefinition.Mflpt5.fromNumber(it)) }
|
|
||||||
out(varname)
|
|
||||||
for (f in array.zip(floatFills))
|
|
||||||
out(" .byte ${f.second} ; float ${f.first}")
|
|
||||||
}
|
|
||||||
DataType.STRUCT -> throw AssemblyError("vars of type STRUCT should have been removed because flattened")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private fun encodeStr(str: String, dt: DataType): List<Short> {
|
|
||||||
return when(dt) {
|
|
||||||
DataType.STR -> {
|
|
||||||
val bytes = Petscii.encodePetscii(str, true)
|
|
||||||
bytes.plus(0)
|
|
||||||
}
|
|
||||||
DataType.STR_S -> {
|
|
||||||
val bytes = Petscii.encodeScreencode(str, true)
|
|
||||||
bytes.plus(0)
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("invalid str type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun makeArrayFillDataUnsigned(value: RuntimeValue): List<String> {
|
|
||||||
val array = heap.get(value.heapId!!).array!!
|
|
||||||
return when {
|
|
||||||
value.type== DataType.ARRAY_UB ->
|
|
||||||
// byte array can never contain pointer-to types, so treat values as all integers
|
|
||||||
array.map { "$"+it.integer!!.toString(16).padStart(2, '0') }
|
|
||||||
value.type== DataType.ARRAY_UW -> array.map {
|
|
||||||
when {
|
|
||||||
it.integer!=null -> "$"+it.integer.toString(16).padStart(2, '0')
|
|
||||||
it.addressOf!=null -> symname(it.addressOf.scopedname!!, block)
|
|
||||||
else -> throw AssemblyError("weird type in array")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("invalid arraysize type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun makeArrayFillDataSigned(value: RuntimeValue): List<String> {
|
|
||||||
val array = heap.get(value.heapId!!).array!!
|
|
||||||
// note: array of signed value can never contain pointer-to type, so simply accept values as being all integers
|
|
||||||
return if (value.type == DataType.ARRAY_B || value.type == DataType.ARRAY_W) {
|
|
||||||
array.map {
|
|
||||||
if(it.integer!!>=0)
|
|
||||||
"$"+it.integer.toString(16).padStart(2, '0')
|
|
||||||
else
|
|
||||||
"-$"+abs(it.integer).toString(16).padStart(2, '0')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else throw AssemblyError("invalid arraysize type")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun instr2asm(ins: List<Instruction>): Int {
|
|
||||||
// find best patterns (matching the most of the lines, then with the smallest weight)
|
|
||||||
val fragments = findPatterns(ins).sortedByDescending { it.segmentSize }
|
|
||||||
if(fragments.isEmpty()) {
|
|
||||||
// we didn't find any matching patterns (complex multi-instruction fragments), try simple ones
|
|
||||||
val firstIns = ins[0]
|
|
||||||
val singleAsm = simpleInstr2Asm(firstIns, block)
|
|
||||||
if(singleAsm != null) {
|
|
||||||
outputAsmFragment(singleAsm)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
val best = fragments[0]
|
|
||||||
outputAsmFragment(best.asm)
|
|
||||||
return best.segmentSize
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun outputAsmFragment(singleAsm: String) {
|
|
||||||
if (singleAsm.isNotEmpty()) {
|
|
||||||
if(singleAsm.startsWith("@inline@"))
|
|
||||||
out(singleAsm.substring(8), false)
|
|
||||||
else {
|
|
||||||
val withNewlines = singleAsm.replace('|', '\n')
|
|
||||||
out(withNewlines)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun findPatterns(segment: List<Instruction>): List<AsmFragment> {
|
|
||||||
val opcodes = segment.map { it.opcode }
|
|
||||||
val result = mutableListOf<AsmFragment>()
|
|
||||||
|
|
||||||
// check for operations that modify a single value, by putting it on the stack (and popping it afterwards)
|
|
||||||
if((opcodes[0]==Opcode.PUSH_VAR_BYTE && opcodes[2]==Opcode.POP_VAR_BYTE) ||
|
|
||||||
(opcodes[0]==Opcode.PUSH_VAR_WORD && opcodes[2]==Opcode.POP_VAR_WORD) ||
|
|
||||||
(opcodes[0]==Opcode.PUSH_VAR_FLOAT && opcodes[2]==Opcode.POP_VAR_FLOAT)) {
|
|
||||||
if (segment[0].callLabel == segment[2].callLabel) {
|
|
||||||
val fragment = sameVarOperation(segment[0].callLabel!!, segment[1])
|
|
||||||
if (fragment != null) {
|
|
||||||
fragment.segmentSize = 3
|
|
||||||
result.add(fragment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if((opcodes[0]==Opcode.PUSH_BYTE && opcodes[1] in setOf(Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UB,
|
|
||||||
Opcode.INC_INDEXED_VAR_UW, Opcode.INC_INDEXED_VAR_W, Opcode.INC_INDEXED_VAR_FLOAT,
|
|
||||||
Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UB, Opcode.DEC_INDEXED_VAR_W,
|
|
||||||
Opcode.DEC_INDEXED_VAR_UW, Opcode.DEC_INDEXED_VAR_FLOAT))) {
|
|
||||||
val fragment = sameConstantIndexedVarOperation(segment[1].callLabel!!, segment[0].arg!!.integerValue(), segment[1])
|
|
||||||
if(fragment!=null) {
|
|
||||||
fragment.segmentSize=2
|
|
||||||
result.add(fragment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if((opcodes[0]==Opcode.PUSH_VAR_BYTE && opcodes[1] in setOf(Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UB,
|
|
||||||
Opcode.INC_INDEXED_VAR_UW, Opcode.INC_INDEXED_VAR_W, Opcode.INC_INDEXED_VAR_FLOAT,
|
|
||||||
Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UB, Opcode.DEC_INDEXED_VAR_W,
|
|
||||||
Opcode.DEC_INDEXED_VAR_UW, Opcode.DEC_INDEXED_VAR_FLOAT))) {
|
|
||||||
val fragment = sameIndexedVarOperation(segment[1].callLabel!!, segment[0].callLabel!!, segment[1])
|
|
||||||
if(fragment!=null) {
|
|
||||||
fragment.segmentSize=2
|
|
||||||
result.add(fragment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if((opcodes[0]==Opcode.PUSH_MEM_UB && opcodes[2]==Opcode.POP_MEM_BYTE) ||
|
|
||||||
(opcodes[0]==Opcode.PUSH_MEM_B && opcodes[2]==Opcode.POP_MEM_BYTE) ||
|
|
||||||
(opcodes[0]==Opcode.PUSH_MEM_UW && opcodes[2]==Opcode.POP_MEM_WORD) ||
|
|
||||||
(opcodes[0]==Opcode.PUSH_MEM_W && opcodes[2]==Opcode.POP_MEM_WORD) ||
|
|
||||||
(opcodes[0]==Opcode.PUSH_MEM_FLOAT && opcodes[2]==Opcode.POP_MEM_FLOAT)) {
|
|
||||||
if(segment[0].arg==segment[2].arg) {
|
|
||||||
val fragment = sameMemOperation(segment[0].arg!!.integerValue(), segment[1])
|
|
||||||
if(fragment!=null) {
|
|
||||||
fragment.segmentSize = 3
|
|
||||||
result.add(fragment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if((opcodes[0]==Opcode.PUSH_BYTE && opcodes[1]==Opcode.READ_INDEXED_VAR_BYTE &&
|
|
||||||
opcodes[3]==Opcode.PUSH_BYTE && opcodes[4]==Opcode.WRITE_INDEXED_VAR_BYTE) ||
|
|
||||||
(opcodes[0]==Opcode.PUSH_BYTE && opcodes[1]==Opcode.READ_INDEXED_VAR_WORD &&
|
|
||||||
opcodes[3]==Opcode.PUSH_BYTE && opcodes[4]==Opcode.WRITE_INDEXED_VAR_WORD)) {
|
|
||||||
if(segment[0].arg==segment[3].arg && segment[1].callLabel==segment[4].callLabel) {
|
|
||||||
val fragment = sameConstantIndexedVarOperation(segment[1].callLabel!!, segment[0].arg!!.integerValue(), segment[2])
|
|
||||||
if(fragment!=null){
|
|
||||||
fragment.segmentSize = 5
|
|
||||||
result.add(fragment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if((opcodes[0]==Opcode.PUSH_VAR_BYTE && opcodes[1]==Opcode.READ_INDEXED_VAR_BYTE &&
|
|
||||||
opcodes[3]==Opcode.PUSH_VAR_BYTE && opcodes[4]==Opcode.WRITE_INDEXED_VAR_BYTE) ||
|
|
||||||
(opcodes[0]==Opcode.PUSH_VAR_BYTE && opcodes[1]==Opcode.READ_INDEXED_VAR_WORD &&
|
|
||||||
opcodes[3]==Opcode.PUSH_VAR_BYTE && opcodes[4]==Opcode.WRITE_INDEXED_VAR_WORD)) {
|
|
||||||
if(segment[0].callLabel==segment[3].callLabel && segment[1].callLabel==segment[4].callLabel) {
|
|
||||||
val fragment = sameIndexedVarOperation(segment[1].callLabel!!, segment[0].callLabel!!, segment[2])
|
|
||||||
if(fragment!=null){
|
|
||||||
fragment.segmentSize = 5
|
|
||||||
result.add(fragment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add any matching patterns from the big list
|
|
||||||
for(pattern in Patterns.patterns) {
|
|
||||||
if(pattern.sequence.size > segment.size || (pattern.altSequence!=null && pattern.altSequence.size > segment.size))
|
|
||||||
continue // don't accept patterns that don't fit
|
|
||||||
val opcodesList = opcodes.subList(0, pattern.sequence.size)
|
|
||||||
if(pattern.sequence == opcodesList) {
|
|
||||||
val asm = pattern.asm(segment)
|
|
||||||
if(asm!=null)
|
|
||||||
result.add(AsmFragment(asm, pattern.sequence.size))
|
|
||||||
} else if(pattern.altSequence!=null) {
|
|
||||||
val opcodesListAlt = opcodes.subList(0, pattern.altSequence.size)
|
|
||||||
if(pattern.altSequence == opcodesListAlt) {
|
|
||||||
val asm = pattern.asm(segment)
|
|
||||||
if (asm != null)
|
|
||||||
result.add(AsmFragment(asm, pattern.sequence.size))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun sameConstantIndexedVarOperation(variable: String, index: Int, ins: Instruction): AsmFragment? {
|
|
||||||
// an in place operation that consists of a push-value / op / push-index-value / pop-into-indexed-var
|
|
||||||
return when(ins.opcode) {
|
|
||||||
Opcode.SHL_BYTE -> AsmFragment(" asl $variable+$index", 8)
|
|
||||||
Opcode.SHR_UBYTE -> AsmFragment(" lsr $variable+$index", 8)
|
|
||||||
Opcode.SHR_SBYTE -> AsmFragment(" lda $variable+$index | asl a | ror $variable+$index")
|
|
||||||
Opcode.SHL_WORD -> AsmFragment(" asl $variable+${index * 2 + 1} | rol $variable+${index * 2}", 8)
|
|
||||||
Opcode.SHR_UWORD -> AsmFragment(" lsr $variable+${index * 2 + 1} | ror $variable+${index * 2}", 8)
|
|
||||||
Opcode.SHR_SWORD -> AsmFragment(" lda $variable+${index * 2 + 1} | asl a | ror $variable+${index * 2 + 1} | ror $variable+${index * 2}", 8)
|
|
||||||
Opcode.ROL_BYTE -> AsmFragment(" rol $variable+$index", 8)
|
|
||||||
Opcode.ROR_BYTE -> AsmFragment(" ror $variable+$index", 8)
|
|
||||||
Opcode.ROL_WORD -> AsmFragment(" rol $variable+${index * 2 + 1} | rol $variable+${index * 2}", 8)
|
|
||||||
Opcode.ROR_WORD -> AsmFragment(" ror $variable+${index * 2 + 1} | ror $variable+${index * 2}", 8)
|
|
||||||
Opcode.ROL2_BYTE -> AsmFragment(" lda $variable+$index | cmp #\$80 | rol $variable+$index", 8)
|
|
||||||
Opcode.ROR2_BYTE -> AsmFragment(" lda $variable+$index | lsr a | bcc + | ora #\$80 |+ | sta $variable+$index", 10)
|
|
||||||
Opcode.ROL2_WORD -> AsmFragment(" asl $variable+${index * 2 + 1} | rol $variable+${index * 2} | bcc + | inc $variable+${index * 2 + 1} |+", 20)
|
|
||||||
Opcode.ROR2_WORD -> AsmFragment(" lsr $variable+${index * 2 + 1} | ror $variable+${index * 2} | bcc + | lda $variable+${index * 2 + 1} | ora #\$80 | sta $variable+${index * 2 + 1} |+", 30)
|
|
||||||
Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UB -> AsmFragment(" inc $variable+$index", 2)
|
|
||||||
Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UB -> AsmFragment(" dec $variable+$index", 5)
|
|
||||||
Opcode.INC_INDEXED_VAR_W, Opcode.INC_INDEXED_VAR_UW -> AsmFragment(" inc $variable+${index * 2} | bne + | inc $variable+${index * 2 + 1} |+")
|
|
||||||
Opcode.DEC_INDEXED_VAR_W, Opcode.DEC_INDEXED_VAR_UW -> AsmFragment(" lda $variable+${index * 2} | bne + | dec $variable+${index * 2 + 1} |+ | dec $variable+${index * 2}")
|
|
||||||
Opcode.INC_INDEXED_VAR_FLOAT -> AsmFragment(
|
|
||||||
"""
|
|
||||||
lda #<($variable+${index * MachineDefinition.Mflpt5.MemorySize})
|
|
||||||
ldy #>($variable+${index * MachineDefinition.Mflpt5.MemorySize})
|
|
||||||
jsr c64flt.inc_var_f
|
|
||||||
""")
|
|
||||||
Opcode.DEC_INDEXED_VAR_FLOAT -> AsmFragment(
|
|
||||||
"""
|
|
||||||
lda #<($variable+${index * MachineDefinition.Mflpt5.MemorySize})
|
|
||||||
ldy #>($variable+${index * MachineDefinition.Mflpt5.MemorySize})
|
|
||||||
jsr c64flt.dec_var_f
|
|
||||||
""")
|
|
||||||
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun sameIndexedVarOperation(variable: String, indexVar: String, ins: Instruction): AsmFragment? {
|
|
||||||
// an in place operation that consists of a push-value / op / push-index-var / pop-into-indexed-var
|
|
||||||
val saveX = " stx ${MachineDefinition.C64Zeropage.SCRATCH_B1} |"
|
|
||||||
val restoreX = " | ldx ${MachineDefinition.C64Zeropage.SCRATCH_B1}"
|
|
||||||
val loadXWord: String
|
|
||||||
val loadX: String
|
|
||||||
|
|
||||||
when(indexVar) {
|
|
||||||
"X" -> {
|
|
||||||
loadX = ""
|
|
||||||
loadXWord = " txa | asl a | tax |"
|
|
||||||
}
|
|
||||||
"Y" -> {
|
|
||||||
loadX = " tya | tax |"
|
|
||||||
loadXWord = " tya | asl a | tax |"
|
|
||||||
}
|
|
||||||
"A" -> {
|
|
||||||
loadX = " tax |"
|
|
||||||
loadXWord = " asl a | tax |"
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
// the indexvar is a real variable, not a register
|
|
||||||
loadX = " ldx $indexVar |"
|
|
||||||
loadXWord = " lda $indexVar | asl a | tax |"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return when (ins.opcode) {
|
|
||||||
Opcode.SHL_BYTE -> AsmFragment(" txa | $loadX asl $variable,x | tax", 10)
|
|
||||||
Opcode.SHR_UBYTE -> AsmFragment(" txa | $loadX lsr $variable,x | tax", 10)
|
|
||||||
Opcode.SHR_SBYTE -> AsmFragment("$saveX $loadX lda $variable,x | asl a | ror $variable,x $restoreX", 10)
|
|
||||||
Opcode.SHL_WORD -> AsmFragment("$saveX $loadXWord asl $variable,x | rol $variable+1,x $restoreX", 10)
|
|
||||||
Opcode.SHR_UWORD -> AsmFragment("$saveX $loadXWord lsr $variable+1,x | ror $variable,x $restoreX", 10)
|
|
||||||
Opcode.SHR_SWORD -> AsmFragment("$saveX $loadXWord lda $variable+1,x | asl a | ror $variable+1,x | ror $variable,x $restoreX", 10)
|
|
||||||
Opcode.ROL_BYTE -> AsmFragment(" txa | $loadX rol $variable,x | tax", 10)
|
|
||||||
Opcode.ROR_BYTE -> AsmFragment(" txa | $loadX ror $variable,x | tax", 10)
|
|
||||||
Opcode.ROL_WORD -> AsmFragment("$saveX $loadXWord rol $variable,x | rol $variable+1,x $restoreX", 10)
|
|
||||||
Opcode.ROR_WORD -> AsmFragment("$saveX $loadXWord ror $variable+1,x | ror $variable,x $restoreX", 10)
|
|
||||||
Opcode.ROL2_BYTE -> AsmFragment("$saveX $loadX lda $variable,x | cmp #\$80 | rol $variable,x $restoreX", 10)
|
|
||||||
Opcode.ROR2_BYTE -> AsmFragment("$saveX $loadX lda $variable,x | lsr a | bcc + | ora #\$80 |+ | sta $variable,x $restoreX", 10)
|
|
||||||
Opcode.ROL2_WORD -> AsmFragment(" txa | $loadXWord asl $variable,x | rol $variable+1,x | bcc + | inc $variable,x |+ | tax", 30)
|
|
||||||
Opcode.ROR2_WORD -> AsmFragment("$saveX $loadXWord lsr $variable+1,x | ror $variable,x | bcc + | lda $variable+1,x | ora #\$80 | sta $variable+1,x |+ $restoreX", 30)
|
|
||||||
Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UB -> AsmFragment(" txa | $loadX inc $variable,x | tax", 10)
|
|
||||||
Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UB -> AsmFragment(" txa | $loadX dec $variable,x | tax", 10)
|
|
||||||
Opcode.INC_INDEXED_VAR_W, Opcode.INC_INDEXED_VAR_UW -> AsmFragment("$saveX $loadXWord inc $variable,x | bne + | inc $variable+1,x |+ $restoreX", 10)
|
|
||||||
Opcode.DEC_INDEXED_VAR_W, Opcode.DEC_INDEXED_VAR_UW -> AsmFragment("$saveX $loadXWord lda $variable,x | bne + | dec $variable+1,x |+ | dec $variable,x $restoreX", 10)
|
|
||||||
Opcode.INC_INDEXED_VAR_FLOAT -> AsmFragment(" lda #<$variable | ldy #>$variable | $saveX $loadX jsr c64flt.inc_indexed_var_f $restoreX")
|
|
||||||
Opcode.DEC_INDEXED_VAR_FLOAT -> AsmFragment(" lda #<$variable | ldy #>$variable | $saveX $loadX jsr c64flt.dec_indexed_var_f $restoreX")
|
|
||||||
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun sameMemOperation(address: Int, ins: Instruction): AsmFragment? {
|
|
||||||
// an in place operation that consists of push-mem / op / pop-mem
|
|
||||||
val addr = address.toHex()
|
|
||||||
val addrHi = (address+1).toHex()
|
|
||||||
return when(ins.opcode) {
|
|
||||||
Opcode.SHL_BYTE -> AsmFragment(" asl $addr", 10)
|
|
||||||
Opcode.SHR_UBYTE -> AsmFragment(" lsr $addr", 10)
|
|
||||||
Opcode.SHR_SBYTE -> AsmFragment(" lda $addr | asl a | ror $addr", 10)
|
|
||||||
Opcode.SHL_WORD -> AsmFragment(" asl $addr | rol $addrHi", 10)
|
|
||||||
Opcode.SHR_UWORD -> AsmFragment(" lsr $addrHi | ror $addr", 10)
|
|
||||||
Opcode.SHR_SWORD -> AsmFragment(" lda $addrHi | asl a | ror $addrHi | ror $addr", 10)
|
|
||||||
Opcode.ROL_BYTE -> AsmFragment(" rol $addr", 10)
|
|
||||||
Opcode.ROR_BYTE -> AsmFragment(" ror $addr", 10)
|
|
||||||
Opcode.ROL_WORD -> AsmFragment(" rol $addr | rol $addrHi", 10)
|
|
||||||
Opcode.ROR_WORD -> AsmFragment(" ror $addrHi | ror $addr", 10)
|
|
||||||
Opcode.ROL2_BYTE -> AsmFragment(" lda $addr | cmp #\$80 | rol $addr", 10)
|
|
||||||
Opcode.ROR2_BYTE -> AsmFragment(" lda $addr | lsr a | bcc + | ora #\$80 |+ | sta $addr", 10)
|
|
||||||
Opcode.ROL2_WORD -> AsmFragment(" lda $addr | cmp #\$80 | rol $addr | rol $addrHi", 10)
|
|
||||||
Opcode.ROR2_WORD -> AsmFragment(" lsr $addrHi | ror $addr | bcc + | lda $addrHi | ora #$80 | sta $addrHi |+", 20)
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun sameVarOperation(variable: String, ins: Instruction): AsmFragment? {
|
|
||||||
// an in place operation that consists of a push-var / op / pop-var
|
|
||||||
return when(ins.opcode) {
|
|
||||||
Opcode.SHL_BYTE -> {
|
|
||||||
when (variable) {
|
|
||||||
"A" -> AsmFragment(" asl a", 10)
|
|
||||||
"X" -> AsmFragment(" txa | asl a | tax", 10)
|
|
||||||
"Y" -> AsmFragment(" tya | asl a | tay", 10)
|
|
||||||
else -> AsmFragment(" asl $variable", 10)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Opcode.SHR_UBYTE -> {
|
|
||||||
when (variable) {
|
|
||||||
"A" -> AsmFragment(" lsr a", 10)
|
|
||||||
"X" -> AsmFragment(" txa | lsr a | tax", 10)
|
|
||||||
"Y" -> AsmFragment(" tya | lsr a | tay", 10)
|
|
||||||
else -> AsmFragment(" lsr $variable", 10)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Opcode.SHR_SBYTE -> {
|
|
||||||
// arithmetic shift right (keep sign bit)
|
|
||||||
when (variable) {
|
|
||||||
"A" -> AsmFragment(" cmp #$80 | ror a", 10)
|
|
||||||
"X" -> AsmFragment(" txa | cmp #$80 | ror a | tax", 10)
|
|
||||||
"Y" -> AsmFragment(" tya | cmp #$80 | ror a | tay", 10)
|
|
||||||
else -> AsmFragment(" lda $variable | asl a | ror $variable", 10)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Opcode.SHL_WORD -> {
|
|
||||||
AsmFragment(" asl $variable | rol $variable+1", 10)
|
|
||||||
}
|
|
||||||
Opcode.SHR_UWORD -> {
|
|
||||||
AsmFragment(" lsr $variable+1 | ror $variable", 10)
|
|
||||||
}
|
|
||||||
Opcode.SHR_SWORD -> {
|
|
||||||
// arithmetic shift right (keep sign bit)
|
|
||||||
AsmFragment(" lda $variable+1 | asl a | ror $variable+1 | ror $variable", 10)
|
|
||||||
}
|
|
||||||
Opcode.ROL_BYTE -> {
|
|
||||||
when (variable) {
|
|
||||||
"A" -> AsmFragment(" rol a", 10)
|
|
||||||
"X" -> AsmFragment(" txa | rol a | tax", 10)
|
|
||||||
"Y" -> AsmFragment(" tya | rol a | tay", 10)
|
|
||||||
else -> AsmFragment(" rol $variable", 10)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Opcode.ROR_BYTE -> {
|
|
||||||
when (variable) {
|
|
||||||
"A" -> AsmFragment(" ror a", 10)
|
|
||||||
"X" -> AsmFragment(" txa | ror a | tax", 10)
|
|
||||||
"Y" -> AsmFragment(" tya | ror a | tay", 10)
|
|
||||||
else -> AsmFragment(" ror $variable", 10)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Opcode.ROL_WORD -> {
|
|
||||||
AsmFragment(" rol $variable | rol $variable+1", 10)
|
|
||||||
}
|
|
||||||
Opcode.ROR_WORD -> {
|
|
||||||
AsmFragment(" ror $variable+1 | ror $variable", 10)
|
|
||||||
}
|
|
||||||
Opcode.ROL2_BYTE -> { // 8-bit rol
|
|
||||||
when (variable) {
|
|
||||||
"A" -> AsmFragment(" cmp #\$80 | rol a", 10)
|
|
||||||
"X" -> AsmFragment(" txa | cmp #\$80 | rol a | tax", 10)
|
|
||||||
"Y" -> AsmFragment(" tya | cmp #\$80 | rol a | tay", 10)
|
|
||||||
else -> AsmFragment(" lda $variable | cmp #\$80 | rol $variable", 10)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Opcode.ROR2_BYTE -> { // 8-bit ror
|
|
||||||
when (variable) {
|
|
||||||
"A" -> AsmFragment(" lsr a | bcc + | ora #\$80 |+", 10)
|
|
||||||
"X" -> AsmFragment(" txa | lsr a | bcc + | ora #\$80 |+ | tax", 10)
|
|
||||||
"Y" -> AsmFragment(" tya | lsr a | bcc + | ora #\$80 |+ | tay", 10)
|
|
||||||
else -> AsmFragment(" lda $variable | lsr a | bcc + | ora #\$80 |+ | sta $variable", 10)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Opcode.ROL2_WORD -> {
|
|
||||||
AsmFragment(" lda $variable | cmp #\$80 | rol $variable | rol $variable+1", 10)
|
|
||||||
}
|
|
||||||
Opcode.ROR2_WORD -> {
|
|
||||||
AsmFragment(" lsr $variable+1 | ror $variable | bcc + | lda $variable+1 | ora #\$80 | sta $variable+1 |+", 30)
|
|
||||||
}
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class AsmFragment(val asm: String, var segmentSize: Int=0)
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,559 +0,0 @@
|
|||||||
package compiler.target.c64.codegen
|
|
||||||
|
|
||||||
import prog8.compiler.CompilerException
|
|
||||||
import prog8.compiler.intermediate.Instruction
|
|
||||||
import prog8.compiler.intermediate.IntermediateProgram
|
|
||||||
import prog8.compiler.intermediate.LabelInstr
|
|
||||||
import prog8.compiler.intermediate.Opcode
|
|
||||||
import prog8.compiler.target.c64.MachineDefinition.C64Zeropage
|
|
||||||
import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_HEX
|
|
||||||
import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_PLUS1_HEX
|
|
||||||
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_HEX
|
|
||||||
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS1_HEX
|
|
||||||
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS2_HEX
|
|
||||||
import prog8.compiler.toHex
|
|
||||||
import prog8.vm.stackvm.Syscall
|
|
||||||
import prog8.vm.stackvm.syscallsForStackVm
|
|
||||||
|
|
||||||
|
|
||||||
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
|
|
||||||
|
|
||||||
|
|
||||||
private var breakpointCounter = 0
|
|
||||||
|
|
||||||
internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.ProgramBlock): String? {
|
|
||||||
// a label 'instruction' is simply translated into a asm label
|
|
||||||
if(ins is LabelInstr) {
|
|
||||||
val labelresult =
|
|
||||||
if(ins.name.startsWith("${block.name}."))
|
|
||||||
ins.name.substring(block.name.length+1)
|
|
||||||
else
|
|
||||||
ins.name
|
|
||||||
return if(ins.asmProc) labelresult+"\t\t.proc" else labelresult
|
|
||||||
}
|
|
||||||
|
|
||||||
// simple opcodes that are translated directly into one or a few asm instructions
|
|
||||||
return when(ins.opcode) {
|
|
||||||
Opcode.LINE -> " ;\tsrc line: ${ins.callLabel}"
|
|
||||||
Opcode.NOP -> " nop" // shouldn't be present anymore though
|
|
||||||
Opcode.START_PROCDEF -> "" // is done as part of a label
|
|
||||||
Opcode.END_PROCDEF -> " .pend"
|
|
||||||
Opcode.TERMINATE -> " brk"
|
|
||||||
Opcode.SEC -> " sec"
|
|
||||||
Opcode.CLC -> " clc"
|
|
||||||
Opcode.SEI -> " sei"
|
|
||||||
Opcode.CLI -> " cli"
|
|
||||||
Opcode.CARRY_TO_A -> " lda #0 | adc #0"
|
|
||||||
Opcode.JUMP -> {
|
|
||||||
if(ins.callLabel!=null)
|
|
||||||
" jmp ${ins.callLabel}"
|
|
||||||
else
|
|
||||||
" jmp ${hexVal(ins)}"
|
|
||||||
}
|
|
||||||
Opcode.CALL -> {
|
|
||||||
if(ins.callLabel!=null)
|
|
||||||
" jsr ${ins.callLabel}"
|
|
||||||
else
|
|
||||||
" jsr ${hexVal(ins)}"
|
|
||||||
}
|
|
||||||
Opcode.RETURN -> " rts"
|
|
||||||
Opcode.RSAVE -> {
|
|
||||||
// save cpu status flag and all registers A, X, Y.
|
|
||||||
// see http://6502.org/tutorials/register_preservation.html
|
|
||||||
" php | sta ${C64Zeropage.SCRATCH_REG} | pha | txa | pha | tya | pha | lda ${C64Zeropage.SCRATCH_REG}"
|
|
||||||
}
|
|
||||||
Opcode.RRESTORE -> {
|
|
||||||
// restore all registers and cpu status flag
|
|
||||||
" pla | tay | pla | tax | pla | plp"
|
|
||||||
}
|
|
||||||
Opcode.RSAVEX -> " sta ${C64Zeropage.SCRATCH_REG} | txa | pha | lda ${C64Zeropage.SCRATCH_REG}"
|
|
||||||
Opcode.RRESTOREX -> " sta ${C64Zeropage.SCRATCH_REG} | pla | tax | lda ${C64Zeropage.SCRATCH_REG}"
|
|
||||||
Opcode.DISCARD_BYTE -> " inx"
|
|
||||||
Opcode.DISCARD_WORD -> " inx"
|
|
||||||
Opcode.DISCARD_FLOAT -> " inx | inx | inx"
|
|
||||||
Opcode.DUP_B -> {
|
|
||||||
" lda $ESTACK_LO_PLUS1_HEX,x | sta $ESTACK_LO_HEX,x | dex | ;DUP_B "
|
|
||||||
}
|
|
||||||
Opcode.DUP_W -> {
|
|
||||||
" lda $ESTACK_LO_PLUS1_HEX,x | sta $ESTACK_LO_HEX,x | lda $ESTACK_HI_PLUS1_HEX,x | sta $ESTACK_HI_HEX,x | dex "
|
|
||||||
}
|
|
||||||
|
|
||||||
Opcode.CMP_B, Opcode.CMP_UB -> {
|
|
||||||
" inx | lda $ESTACK_LO_HEX,x | cmp #${ins.arg!!.integerValue().toHex()} | ;CMP_B "
|
|
||||||
}
|
|
||||||
|
|
||||||
Opcode.CMP_W, Opcode.CMP_UW -> {
|
|
||||||
"""
|
|
||||||
inx
|
|
||||||
lda $ESTACK_HI_HEX,x
|
|
||||||
cmp #>${ins.arg!!.integerValue().toHex()}
|
|
||||||
bne +
|
|
||||||
lda $ESTACK_LO_HEX,x
|
|
||||||
cmp #<${ins.arg.integerValue().toHex()}
|
|
||||||
; bne + not necessary?
|
|
||||||
; lda #0 not necessary?
|
|
||||||
+
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
|
|
||||||
Opcode.INLINE_ASSEMBLY -> "@inline@" + (ins.callLabel2 ?: "") // All of the inline assembly is stored in the calllabel2 property. the '@inline@' is a special marker to accept it.
|
|
||||||
Opcode.INCLUDE_FILE -> {
|
|
||||||
val offset = if(ins.arg==null) "" else ", ${ins.arg.integerValue()}"
|
|
||||||
val length = if(ins.arg2==null) "" else ", ${ins.arg2.integerValue()}"
|
|
||||||
" .binary \"${ins.callLabel}\" $offset $length"
|
|
||||||
}
|
|
||||||
Opcode.SYSCALL -> {
|
|
||||||
if (ins.arg!!.numericValue() in syscallsForStackVm.map { it.callNr })
|
|
||||||
throw CompilerException("cannot translate vm syscalls to real assembly calls - use *real* subroutine calls instead. Syscall ${ins.arg.numericValue()}")
|
|
||||||
val call = Syscall.values().find { it.callNr==ins.arg.numericValue() }
|
|
||||||
when(call) {
|
|
||||||
Syscall.FUNC_SIN,
|
|
||||||
Syscall.FUNC_COS,
|
|
||||||
Syscall.FUNC_ABS,
|
|
||||||
Syscall.FUNC_TAN,
|
|
||||||
Syscall.FUNC_ATAN,
|
|
||||||
Syscall.FUNC_LN,
|
|
||||||
Syscall.FUNC_LOG2,
|
|
||||||
Syscall.FUNC_SQRT,
|
|
||||||
Syscall.FUNC_RAD,
|
|
||||||
Syscall.FUNC_DEG,
|
|
||||||
Syscall.FUNC_ROUND,
|
|
||||||
Syscall.FUNC_FLOOR,
|
|
||||||
Syscall.FUNC_CEIL,
|
|
||||||
Syscall.FUNC_RNDF,
|
|
||||||
Syscall.FUNC_ANY_F,
|
|
||||||
Syscall.FUNC_ALL_F,
|
|
||||||
Syscall.FUNC_MAX_F,
|
|
||||||
Syscall.FUNC_MIN_F,
|
|
||||||
Syscall.FUNC_SUM_F -> " jsr c64flt.${call.name.toLowerCase()}"
|
|
||||||
null -> ""
|
|
||||||
else -> " jsr prog8_lib.${call.name.toLowerCase()}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Opcode.BREAKPOINT -> {
|
|
||||||
breakpointCounter++
|
|
||||||
"_prog8_breakpoint_$breakpointCounter\tnop"
|
|
||||||
}
|
|
||||||
|
|
||||||
Opcode.PUSH_BYTE -> {
|
|
||||||
" lda #${hexVal(ins)} | sta $ESTACK_LO_HEX,x | dex"
|
|
||||||
}
|
|
||||||
Opcode.PUSH_WORD -> {
|
|
||||||
val value = hexVal(ins)
|
|
||||||
" lda #<$value | sta $ESTACK_LO_HEX,x | lda #>$value | sta $ESTACK_HI_HEX,x | dex"
|
|
||||||
}
|
|
||||||
Opcode.PUSH_FLOAT -> {
|
|
||||||
val floatConst = getFloatConst(ins.arg!!)
|
|
||||||
" lda #<$floatConst | ldy #>$floatConst | jsr c64flt.push_float"
|
|
||||||
}
|
|
||||||
Opcode.PUSH_VAR_BYTE -> {
|
|
||||||
when(ins.callLabel) {
|
|
||||||
"X" -> throw CompilerException("makes no sense to push X, it's used as a stack pointer itself. You should probably not use the X register (or only in trivial assignments)")
|
|
||||||
"A" -> " sta $ESTACK_LO_HEX,x | dex"
|
|
||||||
"Y" -> " tya | sta $ESTACK_LO_HEX,x | dex"
|
|
||||||
else -> " lda ${ins.callLabel} | sta $ESTACK_LO_HEX,x | dex"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Opcode.PUSH_VAR_WORD -> {
|
|
||||||
" lda ${ins.callLabel} | sta $ESTACK_LO_HEX,x | lda ${ins.callLabel}+1 | sta $ESTACK_HI_HEX,x | dex"
|
|
||||||
}
|
|
||||||
Opcode.PUSH_VAR_FLOAT -> " lda #<${ins.callLabel} | ldy #>${ins.callLabel}| jsr c64flt.push_float"
|
|
||||||
Opcode.PUSH_MEM_B, Opcode.PUSH_MEM_UB -> {
|
|
||||||
"""
|
|
||||||
lda ${hexVal(ins)}
|
|
||||||
sta $ESTACK_LO_HEX,x
|
|
||||||
dex
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
Opcode.PUSH_MEM_W, Opcode.PUSH_MEM_UW -> {
|
|
||||||
"""
|
|
||||||
lda ${hexVal(ins)}
|
|
||||||
sta $ESTACK_LO_HEX,x
|
|
||||||
lda ${hexValPlusOne(ins)}
|
|
||||||
sta $ESTACK_HI_HEX,x
|
|
||||||
dex
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
Opcode.PUSH_MEM_FLOAT -> {
|
|
||||||
" lda #<${hexVal(ins)} | ldy #>${hexVal(ins)}| jsr c64flt.push_float"
|
|
||||||
}
|
|
||||||
Opcode.PUSH_MEMREAD -> {
|
|
||||||
"""
|
|
||||||
lda $ESTACK_LO_PLUS1_HEX,x
|
|
||||||
sta (+) +1
|
|
||||||
lda $ESTACK_HI_PLUS1_HEX,x
|
|
||||||
sta (+) +2
|
|
||||||
+ lda 65535 ; modified
|
|
||||||
sta $ESTACK_LO_PLUS1_HEX,x
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
|
|
||||||
Opcode.PUSH_REGAY_WORD -> {
|
|
||||||
" sta $ESTACK_LO_HEX,x | tya | sta $ESTACK_HI_HEX,x | dex "
|
|
||||||
}
|
|
||||||
Opcode.PUSH_ADDR_HEAPVAR -> {
|
|
||||||
" lda #<${ins.callLabel} | sta $ESTACK_LO_HEX,x | lda #>${ins.callLabel} | sta $ESTACK_HI_HEX,x | dex"
|
|
||||||
}
|
|
||||||
Opcode.POP_REGAX_WORD -> throw AssemblyError("cannot load X register from stack because it's used as the stack pointer itself")
|
|
||||||
Opcode.POP_REGXY_WORD -> throw AssemblyError("cannot load X register from stack because it's used as the stack pointer itself")
|
|
||||||
Opcode.POP_REGAY_WORD -> {
|
|
||||||
" inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x "
|
|
||||||
}
|
|
||||||
|
|
||||||
Opcode.READ_INDEXED_VAR_BYTE -> {
|
|
||||||
"""
|
|
||||||
ldy $ESTACK_LO_PLUS1_HEX,x
|
|
||||||
lda ${ins.callLabel},y
|
|
||||||
sta $ESTACK_LO_PLUS1_HEX,x
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
Opcode.READ_INDEXED_VAR_WORD -> {
|
|
||||||
"""
|
|
||||||
lda $ESTACK_LO_PLUS1_HEX,x
|
|
||||||
asl a
|
|
||||||
tay
|
|
||||||
lda ${ins.callLabel},y
|
|
||||||
sta $ESTACK_LO_PLUS1_HEX,x
|
|
||||||
lda ${ins.callLabel}+1,y
|
|
||||||
sta $ESTACK_HI_PLUS1_HEX,x
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
Opcode.READ_INDEXED_VAR_FLOAT -> {
|
|
||||||
"""
|
|
||||||
lda #<${ins.callLabel}
|
|
||||||
ldy #>${ins.callLabel}
|
|
||||||
jsr c64flt.push_float_from_indexed_var
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
Opcode.WRITE_INDEXED_VAR_BYTE -> {
|
|
||||||
"""
|
|
||||||
inx
|
|
||||||
ldy $ESTACK_LO_HEX,x
|
|
||||||
inx
|
|
||||||
lda $ESTACK_LO_HEX,x
|
|
||||||
sta ${ins.callLabel},y
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
Opcode.WRITE_INDEXED_VAR_WORD -> {
|
|
||||||
"""
|
|
||||||
inx
|
|
||||||
lda $ESTACK_LO_HEX,x
|
|
||||||
asl a
|
|
||||||
tay
|
|
||||||
inx
|
|
||||||
lda $ESTACK_LO_HEX,x
|
|
||||||
sta ${ins.callLabel},y
|
|
||||||
lda $ESTACK_HI_HEX,x
|
|
||||||
sta ${ins.callLabel}+1,y
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
Opcode.WRITE_INDEXED_VAR_FLOAT -> {
|
|
||||||
"""
|
|
||||||
lda #<${ins.callLabel}
|
|
||||||
ldy #>${ins.callLabel}
|
|
||||||
jsr c64flt.pop_float_to_indexed_var
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
Opcode.POP_MEM_BYTE -> {
|
|
||||||
"""
|
|
||||||
inx
|
|
||||||
lda $ESTACK_LO_HEX,x
|
|
||||||
sta ${hexVal(ins)}
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
Opcode.POP_MEM_WORD -> {
|
|
||||||
"""
|
|
||||||
inx
|
|
||||||
lda $ESTACK_LO_HEX,x
|
|
||||||
sta ${hexVal(ins)}
|
|
||||||
lda $ESTACK_HI_HEX,x
|
|
||||||
sta ${hexValPlusOne(ins)}
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
Opcode.POP_MEM_FLOAT -> {
|
|
||||||
" lda ${hexVal(ins)} | ldy ${hexValPlusOne(ins)} | jsr c64flt.pop_float"
|
|
||||||
}
|
|
||||||
Opcode.POP_MEMWRITE -> {
|
|
||||||
"""
|
|
||||||
inx
|
|
||||||
lda $ESTACK_LO_HEX,x
|
|
||||||
sta (+) +1
|
|
||||||
lda $ESTACK_HI_HEX,x
|
|
||||||
sta (+) +2
|
|
||||||
inx
|
|
||||||
lda $ESTACK_LO_HEX,x
|
|
||||||
+ sta 65535 ; modified
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
|
|
||||||
Opcode.POP_VAR_BYTE -> {
|
|
||||||
when (ins.callLabel) {
|
|
||||||
"X" -> throw CompilerException("makes no sense to pop X, it's used as a stack pointer itself")
|
|
||||||
"A" -> " inx | lda $ESTACK_LO_HEX,x"
|
|
||||||
"Y" -> " inx | ldy $ESTACK_LO_HEX,x"
|
|
||||||
else -> " inx | lda $ESTACK_LO_HEX,x | sta ${ins.callLabel}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Opcode.POP_VAR_WORD -> {
|
|
||||||
" inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x | sta ${ins.callLabel} | sty ${ins.callLabel}+1"
|
|
||||||
}
|
|
||||||
Opcode.POP_VAR_FLOAT -> {
|
|
||||||
" lda #<${ins.callLabel} | ldy #>${ins.callLabel} | jsr c64flt.pop_float"
|
|
||||||
}
|
|
||||||
|
|
||||||
Opcode.INC_VAR_UB, Opcode.INC_VAR_B -> {
|
|
||||||
when (ins.callLabel) {
|
|
||||||
"A" -> " clc | adc #1"
|
|
||||||
"X" -> " inx"
|
|
||||||
"Y" -> " iny"
|
|
||||||
else -> " inc ${ins.callLabel}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Opcode.INC_VAR_UW, Opcode.INC_VAR_W -> {
|
|
||||||
" inc ${ins.callLabel} | bne + | inc ${ins.callLabel}+1 |+"
|
|
||||||
}
|
|
||||||
Opcode.INC_VAR_F -> {
|
|
||||||
"""
|
|
||||||
lda #<${ins.callLabel}
|
|
||||||
ldy #>${ins.callLabel}
|
|
||||||
jsr c64flt.inc_var_f
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
Opcode.POP_INC_MEMORY -> {
|
|
||||||
"""
|
|
||||||
inx
|
|
||||||
lda $ESTACK_LO_HEX,x
|
|
||||||
sta (+) +1
|
|
||||||
lda $ESTACK_HI_HEX,x
|
|
||||||
sta (+) +2
|
|
||||||
+ inc 65535 ; modified
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
Opcode.POP_DEC_MEMORY -> {
|
|
||||||
"""
|
|
||||||
inx
|
|
||||||
lda $ESTACK_LO_HEX,x
|
|
||||||
sta (+) +1
|
|
||||||
lda $ESTACK_HI_HEX,x
|
|
||||||
sta (+) +2
|
|
||||||
+ dec 65535 ; modified
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
Opcode.DEC_VAR_UB, Opcode.DEC_VAR_B -> {
|
|
||||||
when (ins.callLabel) {
|
|
||||||
"A" -> " sec | sbc #1"
|
|
||||||
"X" -> " dex"
|
|
||||||
"Y" -> " dey"
|
|
||||||
else -> " dec ${ins.callLabel}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Opcode.DEC_VAR_UW, Opcode.DEC_VAR_W -> {
|
|
||||||
" lda ${ins.callLabel} | bne + | dec ${ins.callLabel}+1 |+ | dec ${ins.callLabel}"
|
|
||||||
}
|
|
||||||
Opcode.DEC_VAR_F -> {
|
|
||||||
"""
|
|
||||||
lda #<${ins.callLabel}
|
|
||||||
ldy #>${ins.callLabel}
|
|
||||||
jsr c64flt.dec_var_f
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
Opcode.INC_MEMORY -> " inc ${hexVal(ins)}"
|
|
||||||
Opcode.DEC_MEMORY -> " dec ${hexVal(ins)}"
|
|
||||||
Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UB -> " inx | txa | pha | lda $ESTACK_LO_HEX,x | tax | inc ${ins.callLabel},x | pla | tax"
|
|
||||||
Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UB -> " inx | txa | pha | lda $ESTACK_LO_HEX,x | tax | dec ${ins.callLabel},x | pla | tax"
|
|
||||||
|
|
||||||
Opcode.NEG_B -> " jsr prog8_lib.neg_b"
|
|
||||||
Opcode.NEG_W -> " jsr prog8_lib.neg_w"
|
|
||||||
Opcode.NEG_F -> " jsr c64flt.neg_f"
|
|
||||||
Opcode.ABS_B -> " jsr prog8_lib.abs_b"
|
|
||||||
Opcode.ABS_W -> " jsr prog8_lib.abs_w"
|
|
||||||
Opcode.ABS_F -> " jsr c64flt.abs_f"
|
|
||||||
Opcode.POW_F -> " jsr c64flt.pow_f"
|
|
||||||
Opcode.INV_BYTE -> {
|
|
||||||
"""
|
|
||||||
lda $ESTACK_LO_PLUS1_HEX,x
|
|
||||||
eor #255
|
|
||||||
sta $ESTACK_LO_PLUS1_HEX,x
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
Opcode.INV_WORD -> " jsr prog8_lib.inv_word"
|
|
||||||
Opcode.NOT_BYTE -> " jsr prog8_lib.not_byte"
|
|
||||||
Opcode.NOT_WORD -> " jsr prog8_lib.not_word"
|
|
||||||
Opcode.BCS -> {
|
|
||||||
val label = ins.callLabel ?: hexVal(ins)
|
|
||||||
" bcs $label"
|
|
||||||
}
|
|
||||||
Opcode.BCC -> {
|
|
||||||
val label = ins.callLabel ?: hexVal(ins)
|
|
||||||
" bcc $label"
|
|
||||||
}
|
|
||||||
Opcode.BNEG -> {
|
|
||||||
val label = ins.callLabel ?: hexVal(ins)
|
|
||||||
" bmi $label"
|
|
||||||
}
|
|
||||||
Opcode.BPOS -> {
|
|
||||||
val label = ins.callLabel ?: hexVal(ins)
|
|
||||||
" bpl $label"
|
|
||||||
}
|
|
||||||
Opcode.BVC -> {
|
|
||||||
val label = ins.callLabel ?: hexVal(ins)
|
|
||||||
" bvc $label"
|
|
||||||
}
|
|
||||||
Opcode.BVS -> {
|
|
||||||
val label = ins.callLabel ?: hexVal(ins)
|
|
||||||
" bvs $label"
|
|
||||||
}
|
|
||||||
Opcode.BZ -> {
|
|
||||||
val label = ins.callLabel ?: hexVal(ins)
|
|
||||||
" beq $label"
|
|
||||||
}
|
|
||||||
Opcode.BNZ -> {
|
|
||||||
val label = ins.callLabel ?: hexVal(ins)
|
|
||||||
" bne $label"
|
|
||||||
}
|
|
||||||
Opcode.JZ -> {
|
|
||||||
val label = ins.callLabel ?: hexVal(ins)
|
|
||||||
"""
|
|
||||||
inx
|
|
||||||
lda $ESTACK_LO_HEX,x
|
|
||||||
beq $label
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
Opcode.JZW -> {
|
|
||||||
val label = ins.callLabel ?: hexVal(ins)
|
|
||||||
"""
|
|
||||||
inx
|
|
||||||
lda $ESTACK_LO_HEX,x
|
|
||||||
beq $label
|
|
||||||
lda $ESTACK_HI_HEX,x
|
|
||||||
beq $label
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
Opcode.JNZ -> {
|
|
||||||
val label = ins.callLabel ?: hexVal(ins)
|
|
||||||
"""
|
|
||||||
inx
|
|
||||||
lda $ESTACK_LO_HEX,x
|
|
||||||
bne $label
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
Opcode.JNZW -> {
|
|
||||||
val label = ins.callLabel ?: hexVal(ins)
|
|
||||||
"""
|
|
||||||
inx
|
|
||||||
lda $ESTACK_LO_HEX,x
|
|
||||||
bne $label
|
|
||||||
lda $ESTACK_HI_HEX,x
|
|
||||||
bne $label
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
Opcode.CAST_B_TO_UB -> "" // is a no-op, just carry on with the byte as-is
|
|
||||||
Opcode.CAST_UB_TO_B -> "" // is a no-op, just carry on with the byte as-is
|
|
||||||
Opcode.CAST_W_TO_UW -> "" // is a no-op, just carry on with the word as-is
|
|
||||||
Opcode.CAST_UW_TO_W -> "" // is a no-op, just carry on with the word as-is
|
|
||||||
Opcode.CAST_W_TO_UB -> "" // is a no-op, just carry on with the lsb of the word as-is
|
|
||||||
Opcode.CAST_W_TO_B -> "" // is a no-op, just carry on with the lsb of the word as-is
|
|
||||||
Opcode.CAST_UW_TO_UB -> "" // is a no-op, just carry on with the lsb of the uword as-is
|
|
||||||
Opcode.CAST_UW_TO_B -> "" // is a no-op, just carry on with the lsb of the uword as-is
|
|
||||||
Opcode.CAST_UB_TO_F -> " jsr c64flt.stack_ub2float"
|
|
||||||
Opcode.CAST_B_TO_F -> " jsr c64flt.stack_b2float"
|
|
||||||
Opcode.CAST_UW_TO_F -> " jsr c64flt.stack_uw2float"
|
|
||||||
Opcode.CAST_W_TO_F -> " jsr c64flt.stack_w2float"
|
|
||||||
Opcode.CAST_F_TO_UB -> " jsr c64flt.stack_float2ub"
|
|
||||||
Opcode.CAST_F_TO_B -> " jsr c64flt.stack_float2b"
|
|
||||||
Opcode.CAST_F_TO_UW -> " jsr c64flt.stack_float2uw"
|
|
||||||
Opcode.CAST_F_TO_W -> " jsr c64flt.stack_float2w"
|
|
||||||
Opcode.CAST_UB_TO_UW, Opcode.CAST_UB_TO_W -> " lda #0 | sta $ESTACK_HI_PLUS1_HEX,x" // clear the msb
|
|
||||||
Opcode.CAST_B_TO_UW, Opcode.CAST_B_TO_W -> " lda $ESTACK_LO_PLUS1_HEX,x | ${signExtendA("$ESTACK_HI_PLUS1_HEX,x")}" // sign extend the lsb
|
|
||||||
Opcode.MSB -> " lda $ESTACK_HI_PLUS1_HEX,x | sta $ESTACK_LO_PLUS1_HEX,x"
|
|
||||||
Opcode.MKWORD -> " inx | lda $ESTACK_LO_HEX,x | sta $ESTACK_HI_PLUS1_HEX,x "
|
|
||||||
|
|
||||||
Opcode.ADD_UB, Opcode.ADD_B -> { // TODO inline better (pattern with more opcodes)
|
|
||||||
"""
|
|
||||||
lda $ESTACK_LO_PLUS2_HEX,x
|
|
||||||
clc
|
|
||||||
adc $ESTACK_LO_PLUS1_HEX,x
|
|
||||||
inx
|
|
||||||
sta $ESTACK_LO_PLUS1_HEX,x
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
Opcode.SUB_UB, Opcode.SUB_B -> { // TODO inline better (pattern with more opcodes)
|
|
||||||
"""
|
|
||||||
lda $ESTACK_LO_PLUS2_HEX,x
|
|
||||||
sec
|
|
||||||
sbc $ESTACK_LO_PLUS1_HEX,x
|
|
||||||
inx
|
|
||||||
sta $ESTACK_LO_PLUS1_HEX,x
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
Opcode.ADD_W, Opcode.ADD_UW -> " jsr prog8_lib.add_w"
|
|
||||||
Opcode.SUB_W, Opcode.SUB_UW -> " jsr prog8_lib.sub_w"
|
|
||||||
Opcode.MUL_B, Opcode.MUL_UB -> " jsr prog8_lib.mul_byte"
|
|
||||||
Opcode.MUL_W, Opcode.MUL_UW -> " jsr prog8_lib.mul_word"
|
|
||||||
Opcode.MUL_F -> " jsr c64flt.mul_f"
|
|
||||||
Opcode.ADD_F -> " jsr c64flt.add_f"
|
|
||||||
Opcode.SUB_F -> " jsr c64flt.sub_f"
|
|
||||||
Opcode.DIV_F -> " jsr c64flt.div_f"
|
|
||||||
Opcode.IDIV_UB -> " jsr prog8_lib.idiv_ub"
|
|
||||||
Opcode.IDIV_B -> " jsr prog8_lib.idiv_b"
|
|
||||||
Opcode.IDIV_W -> " jsr prog8_lib.idiv_w"
|
|
||||||
Opcode.IDIV_UW -> " jsr prog8_lib.idiv_uw"
|
|
||||||
|
|
||||||
Opcode.AND_BYTE -> " jsr prog8_lib.and_b"
|
|
||||||
Opcode.OR_BYTE -> " jsr prog8_lib.or_b"
|
|
||||||
Opcode.XOR_BYTE -> " jsr prog8_lib.xor_b"
|
|
||||||
Opcode.AND_WORD -> " jsr prog8_lib.and_w"
|
|
||||||
Opcode.OR_WORD -> " jsr prog8_lib.or_w"
|
|
||||||
Opcode.XOR_WORD -> " jsr prog8_lib.xor_w"
|
|
||||||
|
|
||||||
Opcode.BITAND_BYTE -> " jsr prog8_lib.bitand_b"
|
|
||||||
Opcode.BITOR_BYTE -> " jsr prog8_lib.bitor_b"
|
|
||||||
Opcode.BITXOR_BYTE -> " jsr prog8_lib.bitxor_b"
|
|
||||||
Opcode.BITAND_WORD -> " jsr prog8_lib.bitand_w"
|
|
||||||
Opcode.BITOR_WORD -> " jsr prog8_lib.bitor_w"
|
|
||||||
Opcode.BITXOR_WORD -> " jsr prog8_lib.bitxor_w"
|
|
||||||
|
|
||||||
Opcode.REMAINDER_UB -> " jsr prog8_lib.remainder_ub"
|
|
||||||
Opcode.REMAINDER_UW -> " jsr prog8_lib.remainder_uw"
|
|
||||||
|
|
||||||
Opcode.GREATER_B -> " jsr prog8_lib.greater_b"
|
|
||||||
Opcode.GREATER_UB -> " jsr prog8_lib.greater_ub"
|
|
||||||
Opcode.GREATER_W -> " jsr prog8_lib.greater_w"
|
|
||||||
Opcode.GREATER_UW -> " jsr prog8_lib.greater_uw"
|
|
||||||
Opcode.GREATER_F -> " jsr c64flt.greater_f"
|
|
||||||
|
|
||||||
Opcode.GREATEREQ_B -> " jsr prog8_lib.greatereq_b"
|
|
||||||
Opcode.GREATEREQ_UB -> " jsr prog8_lib.greatereq_ub"
|
|
||||||
Opcode.GREATEREQ_W -> " jsr prog8_lib.greatereq_w"
|
|
||||||
Opcode.GREATEREQ_UW -> " jsr prog8_lib.greatereq_uw"
|
|
||||||
Opcode.GREATEREQ_F -> " jsr c64flt.greatereq_f"
|
|
||||||
|
|
||||||
Opcode.EQUAL_BYTE -> " jsr prog8_lib.equal_b"
|
|
||||||
Opcode.EQUAL_WORD -> " jsr prog8_lib.equal_w"
|
|
||||||
Opcode.EQUAL_F -> " jsr c64flt.equal_f"
|
|
||||||
Opcode.NOTEQUAL_BYTE -> " jsr prog8_lib.notequal_b"
|
|
||||||
Opcode.NOTEQUAL_WORD -> " jsr prog8_lib.notequal_w"
|
|
||||||
Opcode.NOTEQUAL_F -> " jsr c64flt.notequal_f"
|
|
||||||
|
|
||||||
Opcode.LESS_UB -> " jsr prog8_lib.less_ub"
|
|
||||||
Opcode.LESS_B -> " jsr prog8_lib.less_b"
|
|
||||||
Opcode.LESS_UW -> " jsr prog8_lib.less_uw"
|
|
||||||
Opcode.LESS_W -> " jsr prog8_lib.less_w"
|
|
||||||
Opcode.LESS_F -> " jsr c64flt.less_f"
|
|
||||||
|
|
||||||
Opcode.LESSEQ_UB -> " jsr prog8_lib.lesseq_ub"
|
|
||||||
Opcode.LESSEQ_B -> " jsr prog8_lib.lesseq_b"
|
|
||||||
Opcode.LESSEQ_UW -> " jsr prog8_lib.lesseq_uw"
|
|
||||||
Opcode.LESSEQ_W -> " jsr prog8_lib.lesseq_w"
|
|
||||||
Opcode.LESSEQ_F -> " jsr c64flt.lesseq_f"
|
|
||||||
|
|
||||||
Opcode.SHIFTEDL_BYTE -> " asl $ESTACK_LO_PLUS1_HEX,x"
|
|
||||||
Opcode.SHIFTEDL_WORD -> " asl $ESTACK_LO_PLUS1_HEX,x | rol $ESTACK_HI_PLUS1_HEX,x"
|
|
||||||
Opcode.SHIFTEDR_SBYTE -> " lda $ESTACK_LO_PLUS1_HEX,x | asl a | ror $ESTACK_LO_PLUS1_HEX,x"
|
|
||||||
Opcode.SHIFTEDR_UBYTE -> " lsr $ESTACK_LO_PLUS1_HEX,x"
|
|
||||||
Opcode.SHIFTEDR_SWORD -> " lda $ESTACK_HI_PLUS1_HEX,x | asl a | ror $ESTACK_HI_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,x"
|
|
||||||
Opcode.SHIFTEDR_UWORD -> " lsr $ESTACK_HI_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,x"
|
|
||||||
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
package prog8.vm.stackvm
|
|
||||||
|
|
||||||
import prog8.printSoftwareHeader
|
|
||||||
import prog8.vm.astvm.ScreenDialog
|
|
||||||
import java.awt.EventQueue
|
|
||||||
import javax.swing.Timer
|
|
||||||
import kotlin.system.exitProcess
|
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
|
||||||
stackVmMain(args)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun stackVmMain(args: Array<String>) {
|
|
||||||
printSoftwareHeader("StackVM")
|
|
||||||
|
|
||||||
if(args.size != 1) {
|
|
||||||
System.err.println("requires one argument: name of stackvm sourcecode file")
|
|
||||||
exitProcess(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
val program = Program.load(args.first())
|
|
||||||
val vm = StackVm(traceOutputFile = null)
|
|
||||||
val dialog = ScreenDialog("StackVM")
|
|
||||||
vm.load(program, dialog.canvas)
|
|
||||||
EventQueue.invokeLater {
|
|
||||||
dialog.pack()
|
|
||||||
dialog.isVisible = true
|
|
||||||
dialog.start()
|
|
||||||
|
|
||||||
val programTimer = Timer(10) { a ->
|
|
||||||
try {
|
|
||||||
vm.step()
|
|
||||||
} catch(bp: VmBreakpointException) {
|
|
||||||
println("Breakpoint: execution halted. Press enter to resume.")
|
|
||||||
readLine()
|
|
||||||
} catch (tx: VmTerminationException) {
|
|
||||||
println("Execution halted: ${tx.message}")
|
|
||||||
(a.source as Timer).stop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val irqTimer = Timer(1000/60) { a -> vm.irq(a.`when`) }
|
|
||||||
|
|
||||||
programTimer.start()
|
|
||||||
irqTimer.start()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,302 +0,0 @@
|
|||||||
package prog8.vm.stackvm
|
|
||||||
|
|
||||||
import prog8.ast.antlr.unescape
|
|
||||||
import prog8.ast.base.*
|
|
||||||
import prog8.ast.expressions.AddressOf
|
|
||||||
import prog8.ast.expressions.IdentifierReference
|
|
||||||
import prog8.compiler.HeapValues
|
|
||||||
import prog8.compiler.IntegerOrAddressOf
|
|
||||||
import prog8.compiler.intermediate.Instruction
|
|
||||||
import prog8.compiler.intermediate.LabelInstr
|
|
||||||
import prog8.compiler.intermediate.Opcode
|
|
||||||
import prog8.compiler.intermediate.opcodesWithVarArgument
|
|
||||||
import prog8.vm.RuntimeValue
|
|
||||||
import java.io.File
|
|
||||||
import java.util.*
|
|
||||||
import java.util.regex.Pattern
|
|
||||||
|
|
||||||
class Program (val name: String,
|
|
||||||
val program: MutableList<Instruction>,
|
|
||||||
val variables: Map<String, RuntimeValue>,
|
|
||||||
val memoryPointers: Map<String, Pair<Int, DataType>>,
|
|
||||||
val labels: Map<String, Int>,
|
|
||||||
val memory: Map<Int, List<RuntimeValue>>,
|
|
||||||
val heap: HeapValues)
|
|
||||||
{
|
|
||||||
init {
|
|
||||||
// add end of program marker and some sentinel instructions, to correctly connect all others
|
|
||||||
program.add(LabelInstr("____program_end", false))
|
|
||||||
program.add(Instruction(Opcode.TERMINATE))
|
|
||||||
program.add(Instruction(Opcode.NOP))
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun load(filename: String): Program {
|
|
||||||
val lines = File(filename).readLines().withIndex().iterator()
|
|
||||||
val memory = mutableMapOf<Int, List<RuntimeValue>>()
|
|
||||||
val heap = HeapValues()
|
|
||||||
val program = mutableListOf<Instruction>()
|
|
||||||
val variables = mutableMapOf<String, RuntimeValue>()
|
|
||||||
val memoryPointers = mutableMapOf<String, Pair<Int, DataType>>()
|
|
||||||
val labels = mutableMapOf<String, Int>()
|
|
||||||
|
|
||||||
while(lines.hasNext()) {
|
|
||||||
val (lineNr, line) = lines.next()
|
|
||||||
if(line.startsWith(';') || line.isEmpty())
|
|
||||||
continue
|
|
||||||
else if(line=="%memory")
|
|
||||||
loadMemory(lines, memory)
|
|
||||||
else if(line=="%heap")
|
|
||||||
loadHeap(lines, heap)
|
|
||||||
else if(line.startsWith("%block "))
|
|
||||||
loadBlock(lines, heap, program, variables, memoryPointers, labels)
|
|
||||||
else throw VmExecutionException("syntax error at line ${lineNr + 1}")
|
|
||||||
}
|
|
||||||
return Program(filename, program, variables, memoryPointers, labels, memory, heap)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadBlock(lines: Iterator<IndexedValue<String>>,
|
|
||||||
heap: HeapValues,
|
|
||||||
program: MutableList<Instruction>,
|
|
||||||
variables: MutableMap<String, RuntimeValue>,
|
|
||||||
memoryPointers: MutableMap<String, Pair<Int, DataType>>,
|
|
||||||
labels: MutableMap<String, Int>)
|
|
||||||
{
|
|
||||||
while(true) {
|
|
||||||
val (_, line) = lines.next()
|
|
||||||
if(line.isEmpty())
|
|
||||||
continue
|
|
||||||
else if(line=="%end_block")
|
|
||||||
return
|
|
||||||
else if(line=="%variables")
|
|
||||||
loadVars(lines, variables)
|
|
||||||
else if(line=="%memorypointers")
|
|
||||||
loadMemoryPointers(lines, memoryPointers, heap)
|
|
||||||
else if(line=="%instructions") {
|
|
||||||
val (blockInstructions, blockLabels) = loadInstructions(lines, heap)
|
|
||||||
val baseIndex = program.size
|
|
||||||
program.addAll(blockInstructions)
|
|
||||||
val labelsWithIndex = blockLabels.mapValues { baseIndex+blockInstructions.indexOf(it.value) }
|
|
||||||
labels.putAll(labelsWithIndex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadHeap(lines: Iterator<IndexedValue<String>>, heap: HeapValues) {
|
|
||||||
val splitpattern = Pattern.compile("\\s+")
|
|
||||||
val heapvalues = mutableListOf<Triple<Int, DataType, String>>()
|
|
||||||
while(true) {
|
|
||||||
val (_, line) = lines.next()
|
|
||||||
if (line == "%end_heap")
|
|
||||||
break
|
|
||||||
val parts = line.split(splitpattern, limit=3)
|
|
||||||
val value = Triple(parts[0].toInt(), DataType.valueOf(parts[1].toUpperCase()), parts[2])
|
|
||||||
heapvalues.add(value)
|
|
||||||
}
|
|
||||||
heapvalues.sortedBy { it.first }.forEach {
|
|
||||||
when(it.second) {
|
|
||||||
DataType.STR, DataType.STR_S -> heap.addString(it.second, unescape(it.third.substring(1, it.third.length - 1), Position("<stackvmsource>", 0, 0, 0)))
|
|
||||||
DataType.ARRAY_UB, DataType.ARRAY_B,
|
|
||||||
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
|
||||||
val numbers = it.third.substring(1, it.third.length-1).split(',')
|
|
||||||
val intarray = numbers.map{number->
|
|
||||||
val num=number.trim()
|
|
||||||
if(num.startsWith("&")) {
|
|
||||||
// it's AddressOf
|
|
||||||
val scopedname = num.substring(1)
|
|
||||||
val iref = IdentifierReference(scopedname.split('.'), Position("<intermediate>", 0, 0, 0))
|
|
||||||
val addrOf = AddressOf(iref, Position("<intermediate>", 0, 0, 0))
|
|
||||||
addrOf.scopedname=scopedname
|
|
||||||
IntegerOrAddressOf(null, addrOf)
|
|
||||||
} else {
|
|
||||||
IntegerOrAddressOf(num.toInt(), null)
|
|
||||||
}
|
|
||||||
}.toTypedArray()
|
|
||||||
heap.addIntegerArray(it.second, intarray)
|
|
||||||
}
|
|
||||||
DataType.ARRAY_F -> {
|
|
||||||
val numbers = it.third.substring(1, it.third.length-1).split(',')
|
|
||||||
val doublearray = numbers.map{number->number.trim().toDouble()}.toDoubleArray()
|
|
||||||
heap.addDoublesArray(doublearray)
|
|
||||||
}
|
|
||||||
in NumericDatatypes -> throw VmExecutionException("invalid heap value type ${it.second}")
|
|
||||||
else -> throw VmExecutionException("weird datatype")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadInstructions(lines: Iterator<IndexedValue<String>>, heap: HeapValues): Pair<MutableList<Instruction>, Map<String, Instruction>> {
|
|
||||||
val instructions = mutableListOf<Instruction>()
|
|
||||||
val labels = mutableMapOf<String, Instruction>()
|
|
||||||
val splitpattern = Pattern.compile("\\s+")
|
|
||||||
val nextInstructionLabels = Stack<String>() // more than one label can occur on the isSameAs line
|
|
||||||
|
|
||||||
while(true) {
|
|
||||||
val (lineNr, line) = lines.next()
|
|
||||||
if(line.isEmpty())
|
|
||||||
continue
|
|
||||||
if(line=="%end_instructions")
|
|
||||||
return Pair(instructions, labels)
|
|
||||||
if(!line.startsWith(' ') && line.endsWith(':')) {
|
|
||||||
nextInstructionLabels.push(line.substring(0, line.length-1))
|
|
||||||
} else if(line.startsWith(' ')) {
|
|
||||||
val parts = line.trimStart().split(splitpattern, limit = 2)
|
|
||||||
val opcodeStr = parts[0].toUpperCase()
|
|
||||||
val opcode= Opcode.valueOf(if(opcodeStr.startsWith('_')) opcodeStr.substring(1) else opcodeStr)
|
|
||||||
val args = if(parts.size==2) parts[1] else null
|
|
||||||
val instruction = when(opcode) {
|
|
||||||
Opcode.LINE -> Instruction(opcode, null, callLabel = args)
|
|
||||||
Opcode.JUMP, Opcode.CALL, Opcode.BNEG, Opcode.BPOS,
|
|
||||||
Opcode.BZ, Opcode.BNZ, Opcode.BCS, Opcode.BCC,
|
|
||||||
Opcode.JZ, Opcode.JNZ, Opcode.JZW, Opcode.JNZW -> {
|
|
||||||
if(args!!.startsWith('$')) {
|
|
||||||
Instruction(opcode, RuntimeValue(DataType.UWORD, args.substring(1).toInt(16)))
|
|
||||||
} else {
|
|
||||||
Instruction(opcode, callLabel = args)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
in opcodesWithVarArgument -> {
|
|
||||||
val withoutQuotes =
|
|
||||||
if(args!!.startsWith('"') && args.endsWith('"'))
|
|
||||||
args.substring(1, args.length-1) else args
|
|
||||||
|
|
||||||
Instruction(opcode, callLabel = withoutQuotes)
|
|
||||||
}
|
|
||||||
Opcode.SYSCALL -> {
|
|
||||||
if(args!! in syscallNames) {
|
|
||||||
val call = Syscall.valueOf(args)
|
|
||||||
Instruction(opcode, RuntimeValue(DataType.UBYTE, call.callNr))
|
|
||||||
} else {
|
|
||||||
val args2 = args.replace('.', '_')
|
|
||||||
if(args2 in syscallNames) {
|
|
||||||
val call = Syscall.valueOf(args2)
|
|
||||||
Instruction(opcode, RuntimeValue(DataType.UBYTE, call.callNr))
|
|
||||||
} else {
|
|
||||||
// the syscall is not yet implemented. emit a stub.
|
|
||||||
Instruction(Opcode.SYSCALL, RuntimeValue(DataType.UBYTE, Syscall.SYSCALLSTUB.callNr), callLabel = args2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Opcode.INCLUDE_FILE -> {
|
|
||||||
val argparts = args!!.split(' ')
|
|
||||||
val filename = argparts[0]
|
|
||||||
val offset = if(argparts.size>=2 && argparts[1]!="null") getArgValue(argparts[1], heap) else null
|
|
||||||
val length = if(argparts.size>=3 && argparts[2]!="null") getArgValue(argparts[2], heap) else null
|
|
||||||
Instruction(opcode, offset, length, filename)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
Instruction(opcode, getArgValue(args, heap))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
instructions.add(instruction)
|
|
||||||
while(nextInstructionLabels.isNotEmpty()) {
|
|
||||||
val label = nextInstructionLabels.pop()
|
|
||||||
labels[label] = instruction
|
|
||||||
}
|
|
||||||
} else throw VmExecutionException("syntax error at line ${lineNr + 1}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getArgValue(args: String?, heap: HeapValues): RuntimeValue? {
|
|
||||||
if(args==null)
|
|
||||||
return null
|
|
||||||
if(args[0]=='"' && args[args.length-1]=='"') {
|
|
||||||
throw VmExecutionException("encountered a string arg value, but all strings should already have been moved into the heap")
|
|
||||||
}
|
|
||||||
val (type, valueStr) = args.split(':')
|
|
||||||
return when(type) {
|
|
||||||
"b" -> RuntimeValue(DataType.BYTE, valueStr.toShort(16))
|
|
||||||
"ub" -> RuntimeValue(DataType.UBYTE, valueStr.toShort(16))
|
|
||||||
"w" -> RuntimeValue(DataType.WORD, valueStr.toInt(16))
|
|
||||||
"uw" -> RuntimeValue(DataType.UWORD, valueStr.toInt(16))
|
|
||||||
"f" -> RuntimeValue(DataType.FLOAT, valueStr.toDouble())
|
|
||||||
"heap" -> {
|
|
||||||
val heapId = valueStr.toInt()
|
|
||||||
RuntimeValue(heap.get(heapId).type, heapId = heapId)
|
|
||||||
}
|
|
||||||
else -> throw VmExecutionException("invalid datatype $type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadVars(lines: Iterator<IndexedValue<String>>,
|
|
||||||
vars: MutableMap<String, RuntimeValue>) {
|
|
||||||
val splitpattern = Pattern.compile("\\s+")
|
|
||||||
while(true) {
|
|
||||||
val (_, line) = lines.next()
|
|
||||||
if(line=="%end_variables")
|
|
||||||
return
|
|
||||||
val (name, typeStr, valueStr) = line.split(splitpattern, limit = 3)
|
|
||||||
if(valueStr[0] !='"' && ':' !in valueStr)
|
|
||||||
throw VmExecutionException("missing value type character")
|
|
||||||
val value = when(val type = DataType.valueOf(typeStr.toUpperCase())) {
|
|
||||||
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, valueStr.substring(3).substringBefore(' ').toShort(16))// TODO process ZP and struct info?
|
|
||||||
DataType.BYTE -> RuntimeValue(DataType.BYTE, valueStr.substring(2).substringBefore(' ').toShort(16))// TODO process ZP and struct info?
|
|
||||||
DataType.UWORD -> RuntimeValue(DataType.UWORD, valueStr.substring(3).substringBefore(' ').toInt(16))// TODO process ZP and struct info?
|
|
||||||
DataType.WORD -> RuntimeValue(DataType.WORD, valueStr.substring(2).substringBefore(' ').toInt(16))// TODO process ZP and struct info?
|
|
||||||
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, valueStr.substring(2).substringBefore(' ').toDouble())// TODO process ZP and struct info?
|
|
||||||
in StringDatatypes -> {
|
|
||||||
if(valueStr.startsWith('"') && valueStr.endsWith('"'))
|
|
||||||
throw VmExecutionException("encountered a var with a string value, but all string values should already have been moved into the heap")
|
|
||||||
else if(!valueStr.startsWith("heap:"))
|
|
||||||
throw VmExecutionException("invalid string value, should be a heap reference")
|
|
||||||
else {
|
|
||||||
val heapId = valueStr.substring(5).substringBefore(' ').toInt() // TODO process ZP and struct info?
|
|
||||||
RuntimeValue(type, heapId = heapId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
in ArrayDatatypes -> {
|
|
||||||
if(!valueStr.startsWith("heap:"))
|
|
||||||
throw VmExecutionException("invalid array value, should be a heap reference")
|
|
||||||
else {
|
|
||||||
val heapId = valueStr.substring(5).substringBefore(' ').toInt() // TODO process ZP and struct info?
|
|
||||||
RuntimeValue(type, heapId = heapId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> throw VmExecutionException("weird datatype")
|
|
||||||
}
|
|
||||||
vars[name] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadMemoryPointers(lines: Iterator<IndexedValue<String>>,
|
|
||||||
pointers: MutableMap<String, Pair<Int, DataType>>,
|
|
||||||
heap: HeapValues) {
|
|
||||||
val splitpattern = Pattern.compile("\\s+")
|
|
||||||
while(true) {
|
|
||||||
val (_, line) = lines.next()
|
|
||||||
if(line=="%end_memorypointers")
|
|
||||||
return
|
|
||||||
val (name, typeStr, valueStr) = line.split(splitpattern, limit = 3)
|
|
||||||
if(valueStr[0] !='"' && ':' !in valueStr)
|
|
||||||
throw VmExecutionException("missing value type character")
|
|
||||||
val type = DataType.valueOf(typeStr.toUpperCase())
|
|
||||||
val value = getArgValue(valueStr, heap)!!.integerValue()
|
|
||||||
pointers[name] = Pair(value, type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadMemory(lines: Iterator<IndexedValue<String>>, memory: MutableMap<Int, List<RuntimeValue>>): Map<Int, List<RuntimeValue>> {
|
|
||||||
while(true) {
|
|
||||||
val (lineNr, line) = lines.next()
|
|
||||||
if(line=="%end_memory")
|
|
||||||
return memory
|
|
||||||
val address = line.substringBefore(' ').toInt(16)
|
|
||||||
val rest = line.substringAfter(' ').trim()
|
|
||||||
if(rest.startsWith('"')) {
|
|
||||||
TODO("memory init with char/string")
|
|
||||||
} else {
|
|
||||||
val valueStrings = rest.split(' ')
|
|
||||||
val values = mutableListOf<RuntimeValue>()
|
|
||||||
valueStrings.forEach {
|
|
||||||
when(it.length) {
|
|
||||||
2 -> values.add(RuntimeValue(DataType.UBYTE, it.toShort(16)))
|
|
||||||
4 -> values.add(RuntimeValue(DataType.UWORD, it.toInt(16)))
|
|
||||||
else -> throw VmExecutionException("invalid value at line $lineNr+1")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
memory[address] = values
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
99
README.md
99
README.md
@ -1,23 +1,33 @@
|
|||||||
[](https://saythanks.io/to/irmen)
|
[](https://saythanks.io/to/irmen)
|
||||||
[](https://travis-ci.org/irmen/prog8)
|
[](https://travis-ci.org/irmen/prog8)
|
||||||
|
[](https://prog8.readthedocs.io/)
|
||||||
|
|
||||||
Prog8 - Structured Programming Language for 8-bit 6502/6510 microprocessors
|
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*
|
*Software license: GNU GPL 3.0, see file LICENSE*
|
||||||
|
|
||||||
|
|
||||||
This is a structured programming language for the 8-bit 6502/6510 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).
|
||||||
|
|
||||||
- reduction of source code length
|
Documentation
|
||||||
|
-------------
|
||||||
|
Full documentation (syntax reference, how to use the language and the compiler, etc.) can be found at:
|
||||||
|
https://prog8.readthedocs.io/
|
||||||
|
|
||||||
|
|
||||||
|
What use Prog8 provide?
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
- reduction of source code length over raw assembly
|
||||||
- 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
|
- automatic variable allocations, automatic string and array variables and string sharing
|
||||||
- subroutines with a input- and output parameter signature
|
- subroutines with an input- and output parameter signature
|
||||||
- constant folding in expressions
|
- constant folding in expressions
|
||||||
- conditional branches
|
- conditional branches
|
||||||
- '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
|
||||||
@ -28,26 +38,24 @@ which aims to provide many conveniences over raw assembly code (even when using
|
|||||||
- 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
|
||||||
- many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``memset``, ``memcopy``, ``sort`` and ``reverse``
|
- many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``memset``, ``memcopy``, ``sort`` and ``reverse``
|
||||||
|
|
||||||
Rapid edit-compile-run-debug cycle:
|
*Rapid edit-compile-run-debug cycle:*
|
||||||
|
|
||||||
- use modern PC to work on
|
- use a modern PC to do the work on
|
||||||
- quick compilation times (seconds)
|
- very quick compilation times
|
||||||
- option to automatically run the program in the Vice emulator
|
- can automatically run the program in the Vice emulator after succesful compilation
|
||||||
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them
|
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them
|
||||||
- source code labels automatically loaded in Vice emulator so it can show them in disassembly
|
- source code labels automatically loaded in Vice emulator so it can show them in disassembly
|
||||||
- virtual machine that can execute compiled code directy on the host system,
|
|
||||||
without having to actually convert it to assembly to run on a real 6502
|
|
||||||
|
|
||||||
It is mainly targeted at the Commodore-64 machine at this time.
|
*Two supported compiler targets* (contributions to improve these or to add support for other machines are welcome!):
|
||||||
Contributions to add support for other 8-bit (or other?!) machines are welcome.
|
|
||||||
|
|
||||||
Documentation/manual
|
- "c64": Commodore-64 (6510 CPU = almost a 6502) premium support.
|
||||||
--------------------
|
- "cx16": [CommanderX16](https://www.commanderx16.com) (65c02 CPU) experimental support.
|
||||||
See https://prog8.readthedocs.io/
|
- If you only use standard kernel and prog8 library routines, it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
|
||||||
|
|
||||||
|
|
||||||
Required tools
|
|
||||||
--------------
|
Additional required tools
|
||||||
|
-------------------------
|
||||||
|
|
||||||
[64tass](https://sourceforge.net/projects/tass64/) - cross assembler. Install this on your shell path.
|
[64tass](https://sourceforge.net/projects/tass64/) - cross assembler. Install this on your shell path.
|
||||||
A recent .exe version of this tool for Windows can be obtained from my [clone](https://github.com/irmen/64tass/releases) of this project.
|
A recent .exe version of this tool for Windows can be obtained from my [clone](https://github.com/irmen/64tass/releases) of this project.
|
||||||
@ -57,8 +65,9 @@ A **Java runtime (jre or jdk), version 8 or newer** is required to run a prepac
|
|||||||
If you want to build it from source, you'll need a Java SDK + Kotlin 1.3.x SDK (or for instance,
|
If you want to build it from source, you'll need a Java SDK + Kotlin 1.3.x SDK (or for instance,
|
||||||
IntelliJ IDEA with the Kotlin plugin).
|
IntelliJ IDEA with the Kotlin plugin).
|
||||||
|
|
||||||
It's handy to have a C-64 emulator or a real C-64 to run the programs on. The compiler assumes the presence
|
It's handy to have an emulator (or a real machine perhaps!) to run the programs on. The compiler assumes the presence
|
||||||
of the [Vice emulator](http://vice-emu.sourceforge.net/)
|
of the [Vice emulator](http://vice-emu.sourceforge.net/) for the C64 target,
|
||||||
|
and the [x16emu emulator](https://github.com/commanderx16/x16-emulator) for the CommanderX16 target.
|
||||||
|
|
||||||
|
|
||||||
Example code
|
Example code
|
||||||
@ -66,44 +75,45 @@ Example code
|
|||||||
|
|
||||||
This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
||||||
|
|
||||||
%import c64utils
|
%import textio
|
||||||
%zeropage basicsafe
|
%zeropage basicsafe
|
||||||
|
|
||||||
main {
|
main {
|
||||||
|
|
||||||
ubyte[256] sieve
|
ubyte[256] sieve
|
||||||
ubyte candidate_prime = 2
|
ubyte candidate_prime = 2 ; is increased in the loop
|
||||||
|
|
||||||
sub start() {
|
sub start() {
|
||||||
memset(sieve, 256, false)
|
; clear the sieve, to reset starting situation on subsequent runs
|
||||||
|
memset(sieve, 256, false)
|
||||||
c64scr.print("prime numbers up to 255:\n\n")
|
; calculate primes
|
||||||
|
txt.print("prime numbers up to 255:\n\n")
|
||||||
ubyte amount=0
|
ubyte amount=0
|
||||||
while true {
|
repeat {
|
||||||
ubyte prime = find_next_prime()
|
ubyte prime = find_next_prime()
|
||||||
if prime==0
|
if prime==0
|
||||||
break
|
break
|
||||||
c64scr.print_ub(prime)
|
txt.print_ub(prime)
|
||||||
c64scr.print(", ")
|
txt.print(", ")
|
||||||
amount++
|
amount++
|
||||||
}
|
}
|
||||||
c64.CHROUT('\n')
|
txt.chrout('\n')
|
||||||
c64scr.print("number of primes (expected 54): ")
|
txt.print("number of primes (expected 54): ")
|
||||||
c64scr.print_ub(amount)
|
txt.print_ub(amount)
|
||||||
c64.CHROUT('\n')
|
txt.chrout('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
sub find_next_prime() -> ubyte {
|
sub find_next_prime() -> ubyte {
|
||||||
|
|
||||||
while sieve[candidate_prime] {
|
while sieve[candidate_prime] {
|
||||||
candidate_prime++
|
candidate_prime++
|
||||||
if candidate_prime==0
|
if candidate_prime==0
|
||||||
return 0
|
return 0 ; we wrapped; no more primes available in the sieve
|
||||||
}
|
}
|
||||||
|
|
||||||
|
; found next one, mark the multiples and return it.
|
||||||
sieve[candidate_prime] = true
|
sieve[candidate_prime] = true
|
||||||
uword multiple = candidate_prime
|
uword multiple = candidate_prime
|
||||||
|
|
||||||
while multiple < len(sieve) {
|
while multiple < len(sieve) {
|
||||||
sieve[lsb(multiple)] = true
|
sieve[lsb(multiple)] = true
|
||||||
multiple += candidate_prime
|
multiple += candidate_prime
|
||||||
@ -113,11 +123,11 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
when compiled an ran on a C-64 you'll get:
|
when compiled an ran on a C-64 you'll get:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
One of the included examples (wizzine.p8) animates a bunch of sprite balloons and looks like this:
|
One of the included examples (wizzine.p8) animates a bunch of sprite balloons and looks like this:
|
||||||
|
|
||||||

|

|
||||||
@ -129,3 +139,8 @@ Another example (cube3d-sprites.p8) draws the vertices of a rotating 3d cube:
|
|||||||
If you want to play a video game, a fully working Tetris clone is included in the examples:
|
If you want to play a video game, a fully working Tetris clone is included in the examples:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
The CommanderX16 compiler target is quite capable already too, here's a well known space ship
|
||||||
|
animated in 3D with hidden line removal, in the CommanderX16 emulator:
|
||||||
|
|
||||||
|

|
||||||
|
@ -1,48 +1,52 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.10"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
// id "org.jetbrains.kotlin.jvm" version $kotlinVersion
|
// id "org.jetbrains.kotlin.jvm" version "1.4.10"
|
||||||
id 'application'
|
id 'application'
|
||||||
id 'org.jetbrains.dokka' version "0.9.18"
|
id 'org.jetbrains.dokka' version "0.9.18"
|
||||||
id 'com.github.johnrengelman.shadow' version '5.1.0'
|
id 'com.github.johnrengelman.shadow' version '5.2.0'
|
||||||
id 'java'
|
id 'java'
|
||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: "kotlin"
|
apply plugin: "kotlin"
|
||||||
apply plugin: "java"
|
apply plugin: "java"
|
||||||
|
|
||||||
|
targetCompatibility = 1.8
|
||||||
|
sourceCompatibility = 1.8
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
jcenter()
|
jcenter()
|
||||||
|
maven { url "https://dl.bintray.com/orangy/maven/" }
|
||||||
}
|
}
|
||||||
|
|
||||||
def prog8version = rootProject.file('compiler/res/version.txt').text.trim()
|
def prog8version = rootProject.file('compiler/res/version.txt').text.trim()
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':parser')
|
implementation project(':parser')
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||||
// implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
|
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||||
// runtime "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
|
implementation 'org.antlr:antlr4-runtime:4.8'
|
||||||
runtime 'org.antlr:antlr4-runtime:4.7.2'
|
implementation 'org.jetbrains.kotlinx:kotlinx-cli-jvm:0.1.0-dev-5'
|
||||||
runtime project(':parser')
|
// implementation 'net.razorvine:ksim65:1.6'
|
||||||
|
// implementation "com.github.hypfvieh:dbus-java:3.2.0"
|
||||||
|
implementation project(':parser')
|
||||||
|
|
||||||
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5:$kotlinVersion"
|
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.3.2'
|
||||||
testImplementation 'org.hamcrest:hamcrest-junit:2.0.0.0'
|
testImplementation 'org.hamcrest:hamcrest-junit:2.0.0.0'
|
||||||
|
|
||||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.2'
|
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.2'
|
||||||
}
|
}
|
||||||
|
|
||||||
compileKotlin {
|
compileKotlin {
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "1.8"
|
jvmTarget = "1.8"
|
||||||
verbose = true
|
// verbose = true
|
||||||
// freeCompilerArgs += "-XXLanguage:+NewInference"
|
// freeCompilerArgs += "-XXLanguage:+NewInference"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -82,8 +86,8 @@ artifacts {
|
|||||||
|
|
||||||
|
|
||||||
shadowJar {
|
shadowJar {
|
||||||
baseName = 'prog8compiler'
|
archiveBaseName = 'prog8compiler'
|
||||||
version = prog8version
|
archiveVersion = prog8version
|
||||||
// minimize()
|
// minimize()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,7 +101,7 @@ test {
|
|||||||
|
|
||||||
// Show test results.
|
// Show test results.
|
||||||
testLogging {
|
testLogging {
|
||||||
events "passed", "skipped", "failed"
|
events "skipped", "failed"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,3 +110,7 @@ dokka {
|
|||||||
outputFormat = 'html'
|
outputFormat = 'html'
|
||||||
outputDirectory = "$buildDir/kdoc"
|
outputDirectory = "$buildDir/kdoc"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task wrapper(type: Wrapper) {
|
||||||
|
gradleVersion = '6.1.1'
|
||||||
|
}
|
||||||
|
@ -8,11 +8,12 @@
|
|||||||
<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="openjdk-11" jdkType="JavaSDK" />
|
<orderEntry type="jdk" jdkName="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="antlr-runtime-4.7.2" level="project" />
|
|
||||||
<orderEntry type="module" module-name="parser" />
|
<orderEntry type="module" module-name="parser" />
|
||||||
<orderEntry type="library" name="unittest-libs" level="project" />
|
<orderEntry type="library" name="unittest-libs" level="project" />
|
||||||
|
<orderEntry type="library" name="kotlinx-cli-jvm-0.1.0-dev-5" level="project" />
|
||||||
|
<orderEntry type="library" name="antlr-runtime-4.8" level="project" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
BIN
compiler/lib/dbus-java-3.2.0.jar
Normal file
BIN
compiler/lib/dbus-java-3.2.0.jar
Normal file
Binary file not shown.
BIN
compiler/lib/kotlinx-cli-jvm-0.1.0-dev-5.jar
Normal file
BIN
compiler/lib/kotlinx-cli-jvm-0.1.0-dev-5.jar
Normal file
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 8.9 KiB |
Binary file not shown.
Before Width: | Height: | Size: 9.0 KiB |
486
compiler/res/prog8lib/c64/floats.asm
Normal file
486
compiler/res/prog8lib/c64/floats.asm
Normal file
@ -0,0 +1,486 @@
|
|||||||
|
; --- low level floating point assembly routines for the C64
|
||||||
|
|
||||||
|
FL_ONE_const .byte 129 ; 1.0
|
||||||
|
FL_ZERO_const .byte 0,0,0,0,0 ; 0.0
|
||||||
|
|
||||||
|
floats_store_reg .byte 0 ; temp storage
|
||||||
|
|
||||||
|
|
||||||
|
ub2float .proc
|
||||||
|
; -- convert ubyte in SCRATCH_ZPB1 to float at address A/Y
|
||||||
|
; clobbers A, Y
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
sta P8ZP_SCRATCH_W2
|
||||||
|
sty P8ZP_SCRATCH_W2+1
|
||||||
|
ldy P8ZP_SCRATCH_B1
|
||||||
|
lda #0
|
||||||
|
jsr GIVAYF
|
||||||
|
_fac_to_mem ldx P8ZP_SCRATCH_W2
|
||||||
|
ldy P8ZP_SCRATCH_W2+1
|
||||||
|
jsr MOVMF
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
b2float .proc
|
||||||
|
; -- convert byte in SCRATCH_ZPB1 to float at address A/Y
|
||||||
|
; clobbers A, Y
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
sta P8ZP_SCRATCH_W2
|
||||||
|
sty P8ZP_SCRATCH_W2+1
|
||||||
|
lda P8ZP_SCRATCH_B1
|
||||||
|
jsr FREADSA
|
||||||
|
jmp ub2float._fac_to_mem
|
||||||
|
.pend
|
||||||
|
|
||||||
|
uw2float .proc
|
||||||
|
; -- convert uword in SCRATCH_ZPWORD1 to float at address A/Y
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
sta P8ZP_SCRATCH_W2
|
||||||
|
sty P8ZP_SCRATCH_W2+1
|
||||||
|
lda P8ZP_SCRATCH_W1
|
||||||
|
ldy P8ZP_SCRATCH_W1+1
|
||||||
|
jsr GIVUAYFAY
|
||||||
|
jmp ub2float._fac_to_mem
|
||||||
|
.pend
|
||||||
|
|
||||||
|
w2float .proc
|
||||||
|
; -- convert word in SCRATCH_ZPWORD1 to float at address A/Y
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
sta P8ZP_SCRATCH_W2
|
||||||
|
sty P8ZP_SCRATCH_W2+1
|
||||||
|
ldy P8ZP_SCRATCH_W1
|
||||||
|
lda P8ZP_SCRATCH_W1+1
|
||||||
|
jsr GIVAYF
|
||||||
|
jmp ub2float._fac_to_mem
|
||||||
|
.pend
|
||||||
|
|
||||||
|
stack_b2float .proc
|
||||||
|
; -- b2float operating on the stack
|
||||||
|
inx
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr FREADSA
|
||||||
|
jmp push_fac1._internal
|
||||||
|
.pend
|
||||||
|
|
||||||
|
stack_w2float .proc
|
||||||
|
; -- w2float operating on the stack
|
||||||
|
inx
|
||||||
|
ldy P8ESTACK_LO,x
|
||||||
|
lda P8ESTACK_HI,x
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr GIVAYF
|
||||||
|
jmp push_fac1._internal
|
||||||
|
.pend
|
||||||
|
|
||||||
|
stack_ub2float .proc
|
||||||
|
; -- ub2float operating on the stack
|
||||||
|
inx
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
tay
|
||||||
|
lda #0
|
||||||
|
jsr GIVAYF
|
||||||
|
jmp push_fac1._internal
|
||||||
|
.pend
|
||||||
|
|
||||||
|
stack_uw2float .proc
|
||||||
|
; -- uw2float operating on the stack
|
||||||
|
inx
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
ldy P8ESTACK_HI,x
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr GIVUAYFAY
|
||||||
|
jmp push_fac1._internal
|
||||||
|
.pend
|
||||||
|
|
||||||
|
stack_float2w .proc ; also used for float2b
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr pop_float_fac1
|
||||||
|
jsr AYINT
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
lda $64
|
||||||
|
sta P8ESTACK_HI,x
|
||||||
|
lda $65
|
||||||
|
sta P8ESTACK_LO,x
|
||||||
|
dex
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
stack_float2uw .proc ; also used for float2ub
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr pop_float_fac1
|
||||||
|
jsr GETADR
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
sta P8ESTACK_HI,x
|
||||||
|
tya
|
||||||
|
sta P8ESTACK_LO,x
|
||||||
|
dex
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
push_float .proc
|
||||||
|
; ---- push mflpt5 in A/Y onto stack
|
||||||
|
; (taking 3 stack positions = 6 bytes of which 1 is padding)
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
ldy #0
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
sta P8ESTACK_LO,x
|
||||||
|
iny
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
sta P8ESTACK_HI,x
|
||||||
|
dex
|
||||||
|
iny
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
sta P8ESTACK_LO,x
|
||||||
|
iny
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
sta P8ESTACK_HI,x
|
||||||
|
dex
|
||||||
|
iny
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
sta P8ESTACK_LO,x
|
||||||
|
dex
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
pop_float .proc
|
||||||
|
; ---- pops mflpt5 from stack to memory A/Y
|
||||||
|
; (frees 3 stack positions = 6 bytes of which 1 is padding)
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
ldy #4
|
||||||
|
inx
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
dey
|
||||||
|
inx
|
||||||
|
lda P8ESTACK_HI,x
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
dey
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
dey
|
||||||
|
inx
|
||||||
|
lda P8ESTACK_HI,x
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
dey
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
pop_float_fac1 .proc
|
||||||
|
; -- pops float from stack into FAC1
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr pop_float
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jmp MOVFM
|
||||||
|
.pend
|
||||||
|
|
||||||
|
copy_float .proc
|
||||||
|
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
|
||||||
|
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
|
||||||
|
sta _target+1
|
||||||
|
sty _target+2
|
||||||
|
ldy #4
|
||||||
|
_loop lda (P8ZP_SCRATCH_W1),y
|
||||||
|
_target sta $ffff,y ; modified
|
||||||
|
dey
|
||||||
|
bpl _loop
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
inc_var_f .proc
|
||||||
|
; -- add 1 to float pointed to by A/Y
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr MOVFM
|
||||||
|
lda #<FL_ONE_const
|
||||||
|
ldy #>FL_ONE_const
|
||||||
|
jsr FADD
|
||||||
|
ldx P8ZP_SCRATCH_W1
|
||||||
|
ldy P8ZP_SCRATCH_W1+1
|
||||||
|
jsr MOVMF
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
dec_var_f .proc
|
||||||
|
; -- subtract 1 from float pointed to by A/Y
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
lda #<FL_ONE_const
|
||||||
|
ldy #>FL_ONE_const
|
||||||
|
jsr MOVFM
|
||||||
|
lda P8ZP_SCRATCH_W1
|
||||||
|
ldy P8ZP_SCRATCH_W1+1
|
||||||
|
jsr FSUB
|
||||||
|
ldx P8ZP_SCRATCH_W1
|
||||||
|
ldy P8ZP_SCRATCH_W1+1
|
||||||
|
jsr MOVMF
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
pop_2_floats_f2_in_fac1 .proc
|
||||||
|
; -- pop 2 floats from stack, load the second one in FAC1 as well
|
||||||
|
lda #<fmath_float2
|
||||||
|
ldy #>fmath_float2
|
||||||
|
jsr pop_float
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr pop_float
|
||||||
|
lda #<fmath_float2
|
||||||
|
ldy #>fmath_float2
|
||||||
|
jmp MOVFM
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
fmath_float1 .byte 0,0,0,0,0 ; storage for a mflpt5 value
|
||||||
|
fmath_float2 .byte 0,0,0,0,0 ; storage for a mflpt5 value
|
||||||
|
|
||||||
|
|
||||||
|
push_fac1 .proc
|
||||||
|
; -- push the float in FAC1 onto the stack
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
_internal ldx #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr MOVMF
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
jmp push_float
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
pow_f .proc
|
||||||
|
; -- push f1 ** f2 on stack
|
||||||
|
lda #<fmath_float2
|
||||||
|
ldy #>fmath_float2
|
||||||
|
jsr pop_float
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr pop_float
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr CONUPK ; fac2 = float1
|
||||||
|
lda #<fmath_float2
|
||||||
|
ldy #>fmath_float2
|
||||||
|
jsr FPWR
|
||||||
|
jmp push_fac1._internal
|
||||||
|
.pend
|
||||||
|
|
||||||
|
div_f .proc
|
||||||
|
; -- push f1/f2 on stack
|
||||||
|
jsr pop_2_floats_f2_in_fac1
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr FDIV
|
||||||
|
jmp push_fac1._internal
|
||||||
|
.pend
|
||||||
|
|
||||||
|
add_f .proc
|
||||||
|
; -- push f1+f2 on stack
|
||||||
|
jsr pop_2_floats_f2_in_fac1
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr FADD
|
||||||
|
jmp push_fac1._internal
|
||||||
|
.pend
|
||||||
|
|
||||||
|
sub_f .proc
|
||||||
|
; -- push f1-f2 on stack
|
||||||
|
jsr pop_2_floats_f2_in_fac1
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr FSUB
|
||||||
|
jmp push_fac1._internal
|
||||||
|
.pend
|
||||||
|
|
||||||
|
mul_f .proc
|
||||||
|
; -- push f1*f2 on stack
|
||||||
|
jsr pop_2_floats_f2_in_fac1
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr FMULT
|
||||||
|
jmp push_fac1._internal
|
||||||
|
.pend
|
||||||
|
|
||||||
|
neg_f .proc
|
||||||
|
; -- toggle the sign bit on the stack
|
||||||
|
lda P8ESTACK_HI+3,x
|
||||||
|
eor #$80
|
||||||
|
sta P8ESTACK_HI+3,x
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
equal_f .proc
|
||||||
|
; -- are the two mflpt5 numbers on the stack identical?
|
||||||
|
inx
|
||||||
|
inx
|
||||||
|
inx
|
||||||
|
inx
|
||||||
|
lda P8ESTACK_LO-3,x
|
||||||
|
cmp P8ESTACK_LO,x
|
||||||
|
bne _equals_false
|
||||||
|
lda P8ESTACK_LO-2,x
|
||||||
|
cmp P8ESTACK_LO+1,x
|
||||||
|
bne _equals_false
|
||||||
|
lda P8ESTACK_LO-1,x
|
||||||
|
cmp P8ESTACK_LO+2,x
|
||||||
|
bne _equals_false
|
||||||
|
lda P8ESTACK_HI-2,x
|
||||||
|
cmp P8ESTACK_HI+1,x
|
||||||
|
bne _equals_false
|
||||||
|
lda P8ESTACK_HI-1,x
|
||||||
|
cmp P8ESTACK_HI+2,x
|
||||||
|
bne _equals_false
|
||||||
|
_equals_true lda #1
|
||||||
|
_equals_store inx
|
||||||
|
sta P8ESTACK_LO+1,x
|
||||||
|
rts
|
||||||
|
_equals_false lda #0
|
||||||
|
beq _equals_store
|
||||||
|
.pend
|
||||||
|
|
||||||
|
notequal_f .proc
|
||||||
|
; -- are the two mflpt5 numbers on the stack different?
|
||||||
|
jsr equal_f
|
||||||
|
eor #1 ; invert the result
|
||||||
|
sta P8ESTACK_LO+1,x
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
less_f .proc
|
||||||
|
; -- is f1 < f2?
|
||||||
|
jsr compare_floats
|
||||||
|
cmp #255
|
||||||
|
beq compare_floats._return_true
|
||||||
|
bne compare_floats._return_false
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
lesseq_f .proc
|
||||||
|
; -- is f1 <= f2?
|
||||||
|
jsr compare_floats
|
||||||
|
cmp #255
|
||||||
|
beq compare_floats._return_true
|
||||||
|
cmp #0
|
||||||
|
beq compare_floats._return_true
|
||||||
|
bne compare_floats._return_false
|
||||||
|
.pend
|
||||||
|
|
||||||
|
greater_f .proc
|
||||||
|
; -- is f1 > f2?
|
||||||
|
jsr compare_floats
|
||||||
|
cmp #1
|
||||||
|
beq compare_floats._return_true
|
||||||
|
bne compare_floats._return_false
|
||||||
|
.pend
|
||||||
|
|
||||||
|
greatereq_f .proc
|
||||||
|
; -- is f1 >= f2?
|
||||||
|
jsr compare_floats
|
||||||
|
cmp #1
|
||||||
|
beq compare_floats._return_true
|
||||||
|
cmp #0
|
||||||
|
beq compare_floats._return_true
|
||||||
|
bne compare_floats._return_false
|
||||||
|
.pend
|
||||||
|
|
||||||
|
compare_floats .proc
|
||||||
|
lda #<fmath_float2
|
||||||
|
ldy #>fmath_float2
|
||||||
|
jsr pop_float
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr pop_float
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr MOVFM ; fac1 = flt1
|
||||||
|
lda #<fmath_float2
|
||||||
|
ldy #>fmath_float2
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr FCOMP ; A = flt1 compared with flt2 (0=equal, 1=flt1>flt2, 255=flt1<flt2)
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
_return_false lda #0
|
||||||
|
_return_result sta P8ESTACK_LO,x
|
||||||
|
dex
|
||||||
|
rts
|
||||||
|
_return_true lda #1
|
||||||
|
bne _return_result
|
||||||
|
.pend
|
||||||
|
|
||||||
|
set_array_float_from_fac1 .proc
|
||||||
|
; -- set the float in FAC1 in the array (index in A, array in P8ZP_SCRATCH_W1)
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
asl a
|
||||||
|
asl a
|
||||||
|
clc
|
||||||
|
adc P8ZP_SCRATCH_B1
|
||||||
|
ldy P8ZP_SCRATCH_W1+1
|
||||||
|
clc
|
||||||
|
adc P8ZP_SCRATCH_W1
|
||||||
|
bcc +
|
||||||
|
iny
|
||||||
|
+ stx floats_store_reg
|
||||||
|
tax
|
||||||
|
jsr MOVMF
|
||||||
|
ldx floats_store_reg
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
set_0_array_float .proc
|
||||||
|
; -- set a float in an array to zero (index in A, array in P8ZP_SCRATCH_W1)
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
asl a
|
||||||
|
asl a
|
||||||
|
clc
|
||||||
|
adc P8ZP_SCRATCH_B1
|
||||||
|
tay
|
||||||
|
lda #0
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
iny
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
iny
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
iny
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
iny
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
set_array_float .proc
|
||||||
|
; -- set a float in an array to a value (index in A, float in P8ZP_SCRATCH_W1, array in P8ZP_SCRATCH_W2)
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
asl a
|
||||||
|
asl a
|
||||||
|
clc
|
||||||
|
adc P8ZP_SCRATCH_B1
|
||||||
|
adc P8ZP_SCRATCH_W2
|
||||||
|
ldy P8ZP_SCRATCH_W2+1
|
||||||
|
bcc +
|
||||||
|
iny
|
||||||
|
+ jmp copy_float
|
||||||
|
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
|
||||||
|
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
218
compiler/res/prog8lib/c64/floats.p8
Normal file
218
compiler/res/prog8lib/c64/floats.p8
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
; Prog8 definitions for floating point handling on the Commodore-64
|
||||||
|
;
|
||||||
|
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||||
|
;
|
||||||
|
; indent format: TABS, size=8
|
||||||
|
|
||||||
|
%target c64
|
||||||
|
%option enable_floats
|
||||||
|
|
||||||
|
floats {
|
||||||
|
; ---- this block contains C-64 floating point related functions ----
|
||||||
|
|
||||||
|
const float PI = 3.141592653589793
|
||||||
|
const float TWOPI = 6.283185307179586
|
||||||
|
|
||||||
|
|
||||||
|
; ---- C64 basic and kernal ROM float constants and functions ----
|
||||||
|
|
||||||
|
; note: the fac1 and fac2 are working registers and take 6 bytes each,
|
||||||
|
; floats in memory (and rom) are stored in 5-byte MFLPT packed format.
|
||||||
|
|
||||||
|
; constants in five-byte "mflpt" format in the BASIC ROM
|
||||||
|
&float FL_PIVAL = $aea8 ; 3.1415926...
|
||||||
|
&float FL_N32768 = $b1a5 ; -32768
|
||||||
|
&float FL_FONE = $b9bc ; 1
|
||||||
|
&float FL_SQRHLF = $b9d6 ; SQR(2) / 2
|
||||||
|
&float FL_SQRTWO = $b9db ; SQR(2)
|
||||||
|
&float FL_NEGHLF = $b9e0 ; -.5
|
||||||
|
&float FL_LOG2 = $b9e5 ; LOG(2)
|
||||||
|
&float FL_TENC = $baf9 ; 10
|
||||||
|
&float FL_NZMIL = $bdbd ; 1e9 (1 billion)
|
||||||
|
&float FL_FHALF = $bf11 ; .5
|
||||||
|
&float FL_LOGEB2 = $bfbf ; 1 / LOG(2)
|
||||||
|
&float FL_PIHALF = $e2e0 ; PI / 2
|
||||||
|
&float FL_TWOPI = $e2e5 ; 2 * PI
|
||||||
|
&float FL_FR4 = $e2ea ; .25
|
||||||
|
; oddly enough, 0.0 isn't available in the kernel.
|
||||||
|
|
||||||
|
|
||||||
|
; note: fac1/2 might get clobbered even if not mentioned in the function's name.
|
||||||
|
; note: for subtraction and division, the left operand is in fac2, the right operand in fac1.
|
||||||
|
|
||||||
|
romsub $bba2 = MOVFM(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac1
|
||||||
|
romsub $bba6 = FREADMEM() clobbers(A,Y) ; load mflpt value from memory in $22/$23 into fac1
|
||||||
|
romsub $ba8c = CONUPK(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac2
|
||||||
|
romsub $ba90 = FAREADMEM() clobbers(A,Y) ; load mflpt value from memory in $22/$23 into fac2
|
||||||
|
romsub $bbfc = MOVFA() clobbers(A,X) ; copy fac2 to fac1
|
||||||
|
romsub $bc0c = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded)
|
||||||
|
romsub $bc0f = MOVEF() clobbers(A,X) ; copy fac1 to fac2
|
||||||
|
romsub $bbd4 = MOVMF(uword mflpt @ XY) clobbers(A,Y) ; store fac1 to memory X/Y as 5-byte mflpt
|
||||||
|
|
||||||
|
; fac1-> signed word in Y/A (might throw ILLEGAL QUANTITY)
|
||||||
|
; (tip: use floats.FTOSWRDAY to get A/Y output; lo/hi switched to normal little endian order)
|
||||||
|
romsub $b1aa = FTOSWORDYA() clobbers(X) -> ubyte @ Y, ubyte @ A ; note: calls AYINT.
|
||||||
|
|
||||||
|
; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
|
||||||
|
; (tip: use floats.GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
|
||||||
|
romsub $b7f7 = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
|
||||||
|
|
||||||
|
romsub $bc9b = QINT() clobbers(A,X,Y) ; fac1 -> 4-byte signed integer in 98-101 ($62-$65), with the MSB FIRST.
|
||||||
|
romsub $b1bf = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 100-101 ($64-$65) MSB FIRST. (might throw ILLEGAL QUANTITY)
|
||||||
|
|
||||||
|
; GIVAYF: signed word in Y/A (note different lsb/msb order) -> float in fac1
|
||||||
|
; (tip: use floats.GIVAYFAY to use A/Y input; lo/hi switched to normal order)
|
||||||
|
; there is also floats.GIVUAYFAY - unsigned word in A/Y (lo/hi) to fac1
|
||||||
|
; there is also floats.FREADS32 that reads from 98-101 ($62-$65) MSB FIRST
|
||||||
|
; there is also floats.FREADUS32 that reads from 98-101 ($62-$65) MSB FIRST
|
||||||
|
; there is also floats.FREADS24AXY that reads signed int24 into fac1 from A/X/Y (lo/mid/hi bytes)
|
||||||
|
romsub $b391 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
|
||||||
|
|
||||||
|
romsub $b3a2 = FREADUY(ubyte value @ Y) clobbers(A,X,Y) ; 8 bit unsigned Y -> float in fac1
|
||||||
|
romsub $bc3c = FREADSA(byte value @ A) clobbers(A,X,Y) ; 8 bit signed A -> float in fac1
|
||||||
|
romsub $b7b5 = FREADSTR(ubyte length @ A) clobbers(A,X,Y) ; str -> fac1, $22/23 must point to string, A=string length
|
||||||
|
romsub $aabc = FPRINTLN() clobbers(A,X,Y) ; print string of fac1, on one line (= with newline) destroys fac1. (consider FOUT + STROUT as well)
|
||||||
|
romsub $bddd = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY ($0100)
|
||||||
|
|
||||||
|
romsub $b849 = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for rounding- call this before INT
|
||||||
|
romsub $bae2 = MUL10() clobbers(A,X,Y) ; fac1 *= 10
|
||||||
|
romsub $bafe = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive!
|
||||||
|
romsub $bc5b = 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 $b86a = FADDT() clobbers(A,X,Y) ; fac1 += fac2
|
||||||
|
romsub $b867 = FADD(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 += mflpt value from A/Y
|
||||||
|
romsub $b853 = FSUBT() clobbers(A,X,Y) ; fac1 = fac2-fac1 mind the order of the operands
|
||||||
|
romsub $b850 = FSUB(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt from A/Y - fac1
|
||||||
|
romsub $ba2b = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2
|
||||||
|
romsub $ba28 = FMULT(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 *= mflpt value from A/Y
|
||||||
|
romsub $bb12 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 (remainder in fac2) mind the order of the operands
|
||||||
|
romsub $bb0f = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1 (remainder in fac2)
|
||||||
|
romsub $bf7b = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
|
||||||
|
romsub $bf78 = FPWR(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = fac2 ** mflpt from A/Y
|
||||||
|
romsub $bd7e = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
|
||||||
|
|
||||||
|
romsub $aed4 = NOTOP() clobbers(A,X,Y) ; fac1 = NOT(fac1)
|
||||||
|
romsub $bccc = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to round instead of trunc
|
||||||
|
romsub $b9ea = LOG() clobbers(A,X,Y) ; fac1 = LN(fac1) (natural log)
|
||||||
|
romsub $bc39 = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
|
||||||
|
romsub $bc2b = SIGN() -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
|
||||||
|
romsub $bc58 = ABS() ; fac1 = ABS(fac1)
|
||||||
|
romsub $bf71 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
|
||||||
|
romsub $bf74 = SQRA() clobbers(A,X,Y) ; fac1 = SQRT(fac2)
|
||||||
|
romsub $bfed = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
|
||||||
|
romsub $bfb4 = NEGOP() clobbers(A) ; switch the sign of fac1
|
||||||
|
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 $e26b = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
|
||||||
|
romsub $e2b4 = TAN() clobbers(A,X,Y) ; fac1 = TAN(fac1)
|
||||||
|
romsub $e30e = ATN() clobbers(A,X,Y) ; fac1 = ATN(fac1)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
asmsub FREADS32() clobbers(A,X,Y) {
|
||||||
|
; ---- fac1 = signed int32 from $62-$65 big endian (MSB FIRST)
|
||||||
|
%asm {{
|
||||||
|
lda $62
|
||||||
|
eor #$ff
|
||||||
|
asl a
|
||||||
|
lda #0
|
||||||
|
ldx #$a0
|
||||||
|
jmp $bc4f ; internal BASIC routine
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub FREADUS32 () clobbers(A,X,Y) {
|
||||||
|
; ---- fac1 = uint32 from $62-$65 big endian (MSB FIRST)
|
||||||
|
%asm {{
|
||||||
|
sec
|
||||||
|
lda #0
|
||||||
|
ldx #$a0
|
||||||
|
jmp $bc4f ; internal BASIC routine
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub FREADS24AXY (ubyte lo @ A, ubyte mid @ X, ubyte hi @ Y) clobbers(A,X,Y) {
|
||||||
|
; ---- fac1 = signed int24 (A/X/Y contain lo/mid/hi bytes)
|
||||||
|
; note: there is no FREADU24AXY (unsigned), use FREADUS32 instead.
|
||||||
|
%asm {{
|
||||||
|
sty $62
|
||||||
|
stx $63
|
||||||
|
sta $64
|
||||||
|
lda $62
|
||||||
|
eor #$FF
|
||||||
|
asl a
|
||||||
|
lda #0
|
||||||
|
sta $65
|
||||||
|
ldx #$98
|
||||||
|
jmp $bc4f ; internal BASIC routine
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
||||||
|
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
|
||||||
|
%asm {{
|
||||||
|
sty $62
|
||||||
|
sta $63
|
||||||
|
ldx #$90
|
||||||
|
sec
|
||||||
|
jmp $bc49 ; internal BASIC routine
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
||||||
|
; ---- signed 16 bit word in A/Y (lo/hi) to float in fac1
|
||||||
|
%asm {{
|
||||||
|
sta P8ZP_SCRATCH_REG
|
||||||
|
tya
|
||||||
|
ldy P8ZP_SCRATCH_REG
|
||||||
|
jmp GIVAYF ; this uses the inverse order, Y/A
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub FTOSWRDAY () clobbers(X) -> uword @ AY {
|
||||||
|
; ---- fac1 to signed word in A/Y
|
||||||
|
%asm {{
|
||||||
|
jsr FTOSWORDYA ; note the inverse Y/A order
|
||||||
|
sta P8ZP_SCRATCH_REG
|
||||||
|
tya
|
||||||
|
ldy P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub GETADRAY () clobbers(X) -> uword @ AY {
|
||||||
|
; ---- fac1 to unsigned word in A/Y
|
||||||
|
%asm {{
|
||||||
|
jsr GETADR ; this uses the inverse order, Y/A
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
tya
|
||||||
|
ldy P8ZP_SCRATCH_B1
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub print_f (float value) {
|
||||||
|
; ---- prints the floating point value (without a newline).
|
||||||
|
%asm {{
|
||||||
|
stx floats_store_reg
|
||||||
|
lda #<value
|
||||||
|
ldy #>value
|
||||||
|
jsr MOVFM ; load float into fac1
|
||||||
|
jsr FOUT ; fac1 to string in A/Y
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
ldy #0
|
||||||
|
- lda (P8ZP_SCRATCH_W1),y
|
||||||
|
beq +
|
||||||
|
jsr c64.CHROUT
|
||||||
|
iny
|
||||||
|
bne -
|
||||||
|
+ ldx floats_store_reg
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
%asminclude "library:c64/floats.asm", ""
|
||||||
|
%asminclude "library:c64/floats_funcs.asm", ""
|
||||||
|
|
||||||
|
}
|
437
compiler/res/prog8lib/c64/floats_funcs.asm
Normal file
437
compiler/res/prog8lib/c64/floats_funcs.asm
Normal file
@ -0,0 +1,437 @@
|
|||||||
|
; --- floating point builtin functions
|
||||||
|
|
||||||
|
|
||||||
|
abs_f_stack .proc
|
||||||
|
; -- push abs(AY) on stack
|
||||||
|
jsr floats.MOVFM
|
||||||
|
jsr floats.ABS
|
||||||
|
jmp push_fac1
|
||||||
|
.pend
|
||||||
|
|
||||||
|
abs_f_fac1 .proc
|
||||||
|
; -- FAC1 = abs(AY)
|
||||||
|
jsr floats.MOVFM
|
||||||
|
jmp floats.ABS
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_atan_stack .proc
|
||||||
|
jsr func_atan_fac1
|
||||||
|
jmp push_fac1
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_atan_fac1 .proc
|
||||||
|
jsr MOVFM
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr ATN
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_ceil_stack .proc
|
||||||
|
jsr func_ceil_fac1
|
||||||
|
jmp push_fac1
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_ceil_fac1 .proc
|
||||||
|
; -- ceil: tr = int(f); if tr==f -> return else return tr+1
|
||||||
|
jsr MOVFM
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
ldx #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr MOVMF
|
||||||
|
jsr INT
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr FCOMP
|
||||||
|
cmp #0
|
||||||
|
beq +
|
||||||
|
lda #<FL_ONE_const
|
||||||
|
ldy #>FL_ONE_const
|
||||||
|
jsr FADD
|
||||||
|
+ ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_floor_stack .proc
|
||||||
|
jsr func_floor_fac1
|
||||||
|
jmp push_fac1
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_floor_fac1 .proc
|
||||||
|
jsr MOVFM
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr INT
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_round_stack .proc
|
||||||
|
jsr func_round_fac1
|
||||||
|
jmp push_fac1
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_round_fac1 .proc
|
||||||
|
jsr MOVFM
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr FADDH
|
||||||
|
jsr INT
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_sin_stack .proc
|
||||||
|
jsr func_sin_fac1
|
||||||
|
jmp push_fac1
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_sin_fac1 .proc
|
||||||
|
jsr MOVFM
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr SIN
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_cos_stack .proc
|
||||||
|
jsr func_cos_fac1
|
||||||
|
jmp push_fac1
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_cos_fac1 .proc
|
||||||
|
jsr MOVFM
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr COS
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_tan_stack .proc
|
||||||
|
jsr func_tan_fac1
|
||||||
|
jmp push_fac1
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_tan_fac1 .proc
|
||||||
|
jsr MOVFM
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr TAN
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_rad_stack .proc
|
||||||
|
jsr func_rad_fac1
|
||||||
|
jmp push_fac1
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_rad_fac1 .proc
|
||||||
|
; -- convert degrees to radians (d * pi / 180)
|
||||||
|
jsr MOVFM
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
lda #<_pi_div_180
|
||||||
|
ldy #>_pi_div_180
|
||||||
|
jsr FMULT
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
_pi_div_180 .byte 123, 14, 250, 53, 18 ; pi / 180
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_deg_stack .proc
|
||||||
|
jsr func_deg_fac1
|
||||||
|
jmp push_fac1
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_deg_fac1 .proc
|
||||||
|
; -- convert radians to degrees (d * (1/ pi * 180))
|
||||||
|
jsr MOVFM
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
lda #<_one_over_pi_div_180
|
||||||
|
ldy #>_one_over_pi_div_180
|
||||||
|
jsr FMULT
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
_one_over_pi_div_180 .byte 134, 101, 46, 224, 211 ; 1 / (pi * 180)
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_ln_stack .proc
|
||||||
|
jsr func_ln_fac1
|
||||||
|
jmp push_fac1
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_ln_fac1 .proc
|
||||||
|
jsr MOVFM
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr LOG
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_log2_stack .proc
|
||||||
|
jsr func_log2_fac1
|
||||||
|
jmp push_fac1
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_log2_fac1 .proc
|
||||||
|
jsr MOVFM
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr LOG
|
||||||
|
jsr MOVEF
|
||||||
|
lda #<FL_LOG2
|
||||||
|
ldy #>FL_LOG2
|
||||||
|
jsr MOVFM
|
||||||
|
jsr FDIVT
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_sign_f_stack .proc
|
||||||
|
jsr func_sign_f_into_A
|
||||||
|
sta P8ESTACK_LO,x
|
||||||
|
dex
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_sign_f_into_A .proc
|
||||||
|
jsr MOVFM
|
||||||
|
jmp SIGN
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_sqrt_stack .proc
|
||||||
|
jsr func_sqrt_fac1
|
||||||
|
jmp push_fac1
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_sqrt_fac1 .proc
|
||||||
|
jsr MOVFM
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr SQR
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_rndf_stack .proc
|
||||||
|
jsr func_rndf_fac1
|
||||||
|
jmp push_fac1
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_rndf_fac1 .proc
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
lda #1
|
||||||
|
jsr FREADSA
|
||||||
|
jsr RND ; rng into fac1
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_swap_f .proc
|
||||||
|
; -- swap floats pointed to by SCRATCH_ZPWORD1, SCRATCH_ZPWORD2
|
||||||
|
ldy #4
|
||||||
|
- lda (P8ZP_SCRATCH_W1),y
|
||||||
|
pha
|
||||||
|
lda (P8ZP_SCRATCH_W2),y
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
pla
|
||||||
|
sta (P8ZP_SCRATCH_W2),y
|
||||||
|
dey
|
||||||
|
bpl -
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_reverse_f .proc
|
||||||
|
; --- reverse an array of floats (array in P8ZP_SCRATCH_W1, num elements in A)
|
||||||
|
_left_index = P8ZP_SCRATCH_W2
|
||||||
|
_right_index = P8ZP_SCRATCH_W2+1
|
||||||
|
_loop_count = P8ZP_SCRATCH_REG
|
||||||
|
pha
|
||||||
|
jsr a_times_5
|
||||||
|
sec
|
||||||
|
sbc #5
|
||||||
|
sta _right_index
|
||||||
|
lda #0
|
||||||
|
sta _left_index
|
||||||
|
pla
|
||||||
|
lsr a
|
||||||
|
sta _loop_count
|
||||||
|
_loop ; push the left indexed float on the stack
|
||||||
|
ldy _left_index
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
pha
|
||||||
|
iny
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
pha
|
||||||
|
iny
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
pha
|
||||||
|
iny
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
pha
|
||||||
|
iny
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
pha
|
||||||
|
; copy right index float to left index float
|
||||||
|
ldy _right_index
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
ldy _left_index
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
inc _left_index
|
||||||
|
inc _right_index
|
||||||
|
ldy _right_index
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
ldy _left_index
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
inc _left_index
|
||||||
|
inc _right_index
|
||||||
|
ldy _right_index
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
ldy _left_index
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
inc _left_index
|
||||||
|
inc _right_index
|
||||||
|
ldy _right_index
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
ldy _left_index
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
inc _left_index
|
||||||
|
inc _right_index
|
||||||
|
ldy _right_index
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
ldy _left_index
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
; pop the float off the stack into the right index float
|
||||||
|
ldy _right_index
|
||||||
|
pla
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
dey
|
||||||
|
pla
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
dey
|
||||||
|
pla
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
dey
|
||||||
|
pla
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
dey
|
||||||
|
pla
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
inc _left_index
|
||||||
|
lda _right_index
|
||||||
|
sec
|
||||||
|
sbc #9
|
||||||
|
sta _right_index
|
||||||
|
dec _loop_count
|
||||||
|
bne _loop
|
||||||
|
rts
|
||||||
|
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
a_times_5 .proc
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
asl a
|
||||||
|
asl a
|
||||||
|
clc
|
||||||
|
adc P8ZP_SCRATCH_B1
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_any_f_into_A .proc
|
||||||
|
jsr a_times_5
|
||||||
|
jmp prog8_lib.func_any_b_into_A
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_all_f_into_A .proc
|
||||||
|
jsr a_times_5
|
||||||
|
jmp prog8_lib.func_all_b_into_A
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_any_f_stack .proc
|
||||||
|
jsr a_times_5
|
||||||
|
jmp prog8_lib.func_any_b_stack
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_all_f_stack .proc
|
||||||
|
jsr a_times_5
|
||||||
|
jmp prog8_lib.func_all_b_stack
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_max_f_stack .proc
|
||||||
|
jsr func_max_f_fac1
|
||||||
|
jmp push_fac1
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_max_f_fac1 .proc
|
||||||
|
; -- max(array) -> fac1, array in P8ZP_SCRATCH_W1, num elts in A
|
||||||
|
_loop_count = P8ZP_SCRATCH_REG
|
||||||
|
stx floats_store_reg
|
||||||
|
sta _loop_count
|
||||||
|
lda #255
|
||||||
|
sta _minmax_cmp+1 ; modifying
|
||||||
|
lda #<_largest_neg_float
|
||||||
|
ldy #>_largest_neg_float
|
||||||
|
_minmax_entry jsr MOVFM
|
||||||
|
- lda P8ZP_SCRATCH_W1
|
||||||
|
ldy P8ZP_SCRATCH_W1+1
|
||||||
|
jsr FCOMP
|
||||||
|
_minmax_cmp cmp #255 ; modified
|
||||||
|
bne +
|
||||||
|
lda P8ZP_SCRATCH_W1
|
||||||
|
ldy P8ZP_SCRATCH_W1+1
|
||||||
|
jsr MOVFM
|
||||||
|
+ lda P8ZP_SCRATCH_W1
|
||||||
|
clc
|
||||||
|
adc #5
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
bcc +
|
||||||
|
inc P8ZP_SCRATCH_W1+1
|
||||||
|
+ dec _loop_count
|
||||||
|
bne -
|
||||||
|
ldx floats_store_reg
|
||||||
|
rts
|
||||||
|
_largest_neg_float .byte 255,255,255,255,255 ; largest negative float -1.7014118345e+38
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_min_f_stack .proc
|
||||||
|
jsr func_min_f_fac1
|
||||||
|
jmp push_fac1
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_min_f_fac1 .proc
|
||||||
|
; -- min(array) -> fac1, array in P8ZP_SCRATCH_W1, num elts in A
|
||||||
|
sta func_max_f_fac1._loop_count
|
||||||
|
lda #1
|
||||||
|
sta func_max_f_fac1._minmax_cmp+1
|
||||||
|
lda #<_largest_pos_float
|
||||||
|
ldy #>_largest_pos_float
|
||||||
|
jmp func_max_f_fac1._minmax_entry
|
||||||
|
_largest_pos_float .byte 255,127,255,255,255 ; largest positive float
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
func_sum_f_stack .proc
|
||||||
|
jsr func_sum_f_fac1
|
||||||
|
jmp push_fac1
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_sum_f_fac1 .proc
|
||||||
|
; -- sum(array) -> fac1, array in P8ZP_SCRATCH_W1, num elts in A
|
||||||
|
_loop_count = P8ZP_SCRATCH_REG
|
||||||
|
stx floats_store_reg
|
||||||
|
sta _loop_count
|
||||||
|
lda #<FL_ZERO_const
|
||||||
|
ldy #>FL_ZERO_const
|
||||||
|
jsr MOVFM
|
||||||
|
- lda P8ZP_SCRATCH_W1
|
||||||
|
ldy P8ZP_SCRATCH_W1+1
|
||||||
|
jsr FADD
|
||||||
|
lda P8ZP_SCRATCH_W1
|
||||||
|
clc
|
||||||
|
adc #5
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
bcc +
|
||||||
|
inc P8ZP_SCRATCH_W1+1
|
||||||
|
+ dec _loop_count
|
||||||
|
bne -
|
||||||
|
ldx floats_store_reg
|
||||||
|
rts
|
||||||
|
.pend
|
244
compiler/res/prog8lib/c64/graphics.p8
Normal file
244
compiler/res/prog8lib/c64/graphics.p8
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
%target c64
|
||||||
|
%import textio
|
||||||
|
|
||||||
|
; bitmap pixel graphics module for the C64
|
||||||
|
; only black/white monchrome 320x200 for now
|
||||||
|
; assumes bitmap screen memory is $2000-$3fff
|
||||||
|
|
||||||
|
graphics {
|
||||||
|
const uword BITMAP_ADDRESS = $2000
|
||||||
|
const uword WIDTH = 320
|
||||||
|
const ubyte HEIGHT = 200
|
||||||
|
|
||||||
|
sub enable_bitmap_mode() {
|
||||||
|
; enable bitmap screen, erase it and set colors to black/white.
|
||||||
|
c64.SCROLY |= %00100000
|
||||||
|
c64.VMCSB = (c64.VMCSB & %11110000) | %00001000 ; $2000-$3fff
|
||||||
|
clear_screen(1, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
sub clear_screen(ubyte pixelcolor, ubyte bgcolor) {
|
||||||
|
memset(BITMAP_ADDRESS, 320*200/8, 0)
|
||||||
|
txt.fill_screen(pixelcolor << 4 | bgcolor, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
|
||||||
|
; Bresenham algorithm.
|
||||||
|
; This code special cases various quadrant loops to allow simple ++ and -- operations.
|
||||||
|
; TODO rewrite this in optimized assembly
|
||||||
|
if y1>y2 {
|
||||||
|
; make sure dy is always positive to avoid 8 instead of just 4 special cases
|
||||||
|
swap(x1, x2)
|
||||||
|
swap(y1, y2)
|
||||||
|
}
|
||||||
|
word @zp d = 0
|
||||||
|
ubyte positive_ix = true
|
||||||
|
word @zp dx = x2-x1 as word
|
||||||
|
word @zp dy = y2-y1
|
||||||
|
if dx < 0 {
|
||||||
|
dx = -dx
|
||||||
|
positive_ix = false
|
||||||
|
}
|
||||||
|
dx *= 2
|
||||||
|
dy *= 2
|
||||||
|
internal_plotx = x1
|
||||||
|
|
||||||
|
if dx >= dy {
|
||||||
|
if positive_ix {
|
||||||
|
repeat {
|
||||||
|
internal_plot(y1)
|
||||||
|
if internal_plotx==x2
|
||||||
|
return
|
||||||
|
internal_plotx++
|
||||||
|
d += dy
|
||||||
|
if d > dx {
|
||||||
|
y1++
|
||||||
|
d -= dx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
repeat {
|
||||||
|
internal_plot(y1)
|
||||||
|
if internal_plotx==x2
|
||||||
|
return
|
||||||
|
internal_plotx--
|
||||||
|
d += dy
|
||||||
|
if d > dx {
|
||||||
|
y1++
|
||||||
|
d -= dx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if positive_ix {
|
||||||
|
repeat {
|
||||||
|
internal_plot(y1)
|
||||||
|
if y1 == y2
|
||||||
|
return
|
||||||
|
y1++
|
||||||
|
d += dx
|
||||||
|
if d > dy {
|
||||||
|
internal_plotx++
|
||||||
|
d -= dy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
repeat {
|
||||||
|
internal_plot(y1)
|
||||||
|
if y1 == y2
|
||||||
|
return
|
||||||
|
y1++
|
||||||
|
d += dx
|
||||||
|
if d > dy {
|
||||||
|
internal_plotx--
|
||||||
|
d -= dy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub circle(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||||
|
; Midpoint algorithm
|
||||||
|
ubyte @zp ploty
|
||||||
|
ubyte @zp xx = radius
|
||||||
|
ubyte @zp yy = 0
|
||||||
|
byte @zp decisionOver2 = 1-xx as byte
|
||||||
|
|
||||||
|
while xx>=yy {
|
||||||
|
internal_plotx = xcenter + xx
|
||||||
|
ploty = ycenter + yy
|
||||||
|
internal_plot(ploty)
|
||||||
|
internal_plotx = xcenter - xx
|
||||||
|
internal_plot(ploty)
|
||||||
|
internal_plotx = xcenter + xx
|
||||||
|
ploty = ycenter - yy
|
||||||
|
internal_plot(ploty)
|
||||||
|
internal_plotx = xcenter - xx
|
||||||
|
internal_plot(ploty)
|
||||||
|
internal_plotx = xcenter + yy
|
||||||
|
ploty = ycenter + xx
|
||||||
|
internal_plot(ploty)
|
||||||
|
internal_plotx = xcenter - yy
|
||||||
|
internal_plot(ploty)
|
||||||
|
internal_plotx = xcenter + yy
|
||||||
|
ploty = ycenter - xx
|
||||||
|
internal_plot(ploty)
|
||||||
|
internal_plotx = xcenter - yy
|
||||||
|
internal_plot(ploty)
|
||||||
|
yy++
|
||||||
|
if decisionOver2<=0
|
||||||
|
decisionOver2 += 2*yy+1
|
||||||
|
else {
|
||||||
|
xx--
|
||||||
|
decisionOver2 += 2*(yy-xx)+1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||||
|
; Midpoint algorithm, filled
|
||||||
|
ubyte xx = radius
|
||||||
|
ubyte yy = 0
|
||||||
|
byte decisionOver2 = 1-xx as byte
|
||||||
|
|
||||||
|
while xx>=yy {
|
||||||
|
ubyte ycenter_plus_yy = ycenter + yy
|
||||||
|
ubyte ycenter_min_yy = ycenter - yy
|
||||||
|
ubyte ycenter_plus_xx = ycenter + xx
|
||||||
|
ubyte ycenter_min_xx = ycenter - xx
|
||||||
|
|
||||||
|
for internal_plotx in xcenter to xcenter+xx {
|
||||||
|
internal_plot(ycenter_plus_yy)
|
||||||
|
internal_plot(ycenter_min_yy)
|
||||||
|
}
|
||||||
|
for internal_plotx in xcenter-xx to xcenter-1 {
|
||||||
|
internal_plot(ycenter_plus_yy)
|
||||||
|
internal_plot(ycenter_min_yy)
|
||||||
|
}
|
||||||
|
for internal_plotx in xcenter to xcenter+yy {
|
||||||
|
internal_plot(ycenter_plus_xx)
|
||||||
|
internal_plot(ycenter_min_xx)
|
||||||
|
}
|
||||||
|
for internal_plotx in xcenter-yy to xcenter {
|
||||||
|
internal_plot(ycenter_plus_xx)
|
||||||
|
internal_plot(ycenter_min_xx)
|
||||||
|
}
|
||||||
|
yy++
|
||||||
|
if decisionOver2<=0
|
||||||
|
decisionOver2 += 2*yy+1
|
||||||
|
else {
|
||||||
|
xx--
|
||||||
|
decisionOver2 += 2*(yy-xx)+1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
; here is the non-asm code for the plot routine below:
|
||||||
|
; sub plot_nonasm(uword px, ubyte py) {
|
||||||
|
; ubyte[] ormask = [128, 64, 32, 16, 8, 4, 2, 1]
|
||||||
|
; uword addr = BITMAP_ADDRESS + 320*(py>>3) + (py & 7) + (px & %0000000111111000)
|
||||||
|
; @(addr) |= ormask[lsb(px) & 7]
|
||||||
|
; }
|
||||||
|
|
||||||
|
asmsub plot(uword plotx @XY, ubyte ploty @A) clobbers (A, X, Y) {
|
||||||
|
%asm {{
|
||||||
|
stx internal_plotx
|
||||||
|
sty internal_plotx+1
|
||||||
|
jmp internal_plot
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
; for efficiency of internal algorithms here is the internal plot routine
|
||||||
|
; that takes the plotx coordinate in a separate variable instead of the XY register pair:
|
||||||
|
|
||||||
|
uword internal_plotx ; 0..319 ; separate 'parameter' for internal_plot()
|
||||||
|
|
||||||
|
asmsub internal_plot(ubyte ploty @A) clobbers (A, X, Y) { ; internal_plotx is 16 bits 0 to 319... doesn't fit in a register
|
||||||
|
%asm {{
|
||||||
|
tay
|
||||||
|
lda internal_plotx+1
|
||||||
|
sta P8ZP_SCRATCH_W2+1
|
||||||
|
lsr a ; 0
|
||||||
|
sta P8ZP_SCRATCH_W2
|
||||||
|
lda internal_plotx
|
||||||
|
pha
|
||||||
|
and #7
|
||||||
|
tax
|
||||||
|
|
||||||
|
lda _y_lookup_lo,y
|
||||||
|
clc
|
||||||
|
adc P8ZP_SCRATCH_W2
|
||||||
|
sta P8ZP_SCRATCH_W2
|
||||||
|
lda _y_lookup_hi,y
|
||||||
|
adc P8ZP_SCRATCH_W2+1
|
||||||
|
sta P8ZP_SCRATCH_W2+1
|
||||||
|
|
||||||
|
pla ; internal_plotx
|
||||||
|
and #%11111000
|
||||||
|
tay
|
||||||
|
lda (P8ZP_SCRATCH_W2),y
|
||||||
|
ora _ormask,x
|
||||||
|
sta (P8ZP_SCRATCH_W2),y
|
||||||
|
rts
|
||||||
|
|
||||||
|
_ormask .byte 128, 64, 32, 16, 8, 4, 2, 1
|
||||||
|
|
||||||
|
; note: this can be even faster if we also have a 256 byte x-lookup table, but hey.
|
||||||
|
; see http://codebase64.org/doku.php?id=base:various_techniques_to_calculate_adresses_fast_common_screen_formats_for_pixel_graphics
|
||||||
|
; the y lookup tables encodes this formula: BITMAP_ADDRESS + 320*(py>>3) + (py & 7) (y from 0..199)
|
||||||
|
; We use the 64tass syntax for range expressions to calculate this table on assembly time.
|
||||||
|
|
||||||
|
_plot_y_values := $2000 + 320*(range(200)>>3) + (range(200) & 7)
|
||||||
|
|
||||||
|
_y_lookup_lo .byte <_plot_y_values
|
||||||
|
_y_lookup_hi .byte >_plot_y_values
|
||||||
|
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
448
compiler/res/prog8lib/c64/syslib.p8
Normal file
448
compiler/res/prog8lib/c64/syslib.p8
Normal file
@ -0,0 +1,448 @@
|
|||||||
|
; Prog8 definitions for the Commodore-64
|
||||||
|
; Including memory registers, I/O registers, Basic and Kernal subroutines.
|
||||||
|
;
|
||||||
|
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||||
|
;
|
||||||
|
; indent format: TABS, size=8
|
||||||
|
|
||||||
|
%target c64
|
||||||
|
|
||||||
|
c64 {
|
||||||
|
&ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
|
||||||
|
&ubyte TIME_MID = $a1 ; .. mid byte
|
||||||
|
&ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
|
||||||
|
&ubyte STATUS = $90 ; kernel status variable for I/O
|
||||||
|
&ubyte STKEY = $91 ; various keyboard statuses (updated by IRQ)
|
||||||
|
&ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
|
||||||
|
|
||||||
|
&ubyte COLOR = $0286 ; cursor color
|
||||||
|
&ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address)
|
||||||
|
&uword CINV = $0314 ; IRQ vector
|
||||||
|
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
|
||||||
|
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
|
||||||
|
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
|
||||||
|
|
||||||
|
; the default addresses for the character screen chars and colors
|
||||||
|
const uword Screen = $0400 ; to have this as an array[40*25] the compiler would have to support array size > 255
|
||||||
|
const uword Colors = $d800 ; to have this as an array[40*25] the compiler would have to support array size > 255
|
||||||
|
|
||||||
|
; the default locations of the 8 sprite pointers (store address of sprite / 64)
|
||||||
|
&ubyte SPRPTR0 = 2040
|
||||||
|
&ubyte SPRPTR1 = 2041
|
||||||
|
&ubyte SPRPTR2 = 2042
|
||||||
|
&ubyte SPRPTR3 = 2043
|
||||||
|
&ubyte SPRPTR4 = 2044
|
||||||
|
&ubyte SPRPTR5 = 2045
|
||||||
|
&ubyte SPRPTR6 = 2046
|
||||||
|
&ubyte SPRPTR7 = 2047
|
||||||
|
&ubyte[8] SPRPTR = 2040 ; the 8 sprite pointers as an array.
|
||||||
|
|
||||||
|
|
||||||
|
; ---- VIC-II 6567/6569/856x registers ----
|
||||||
|
|
||||||
|
&ubyte SP0X = $d000
|
||||||
|
&ubyte SP0Y = $d001
|
||||||
|
&ubyte SP1X = $d002
|
||||||
|
&ubyte SP1Y = $d003
|
||||||
|
&ubyte SP2X = $d004
|
||||||
|
&ubyte SP2Y = $d005
|
||||||
|
&ubyte SP3X = $d006
|
||||||
|
&ubyte SP3Y = $d007
|
||||||
|
&ubyte SP4X = $d008
|
||||||
|
&ubyte SP4Y = $d009
|
||||||
|
&ubyte SP5X = $d00a
|
||||||
|
&ubyte SP5Y = $d00b
|
||||||
|
&ubyte SP6X = $d00c
|
||||||
|
&ubyte SP6Y = $d00d
|
||||||
|
&ubyte SP7X = $d00e
|
||||||
|
&ubyte SP7Y = $d00f
|
||||||
|
&ubyte[16] SPXY = $d000 ; the 8 sprite X and Y registers as an array.
|
||||||
|
&uword[8] SPXYW = $d000 ; the 8 sprite X and Y registers as a combined xy word array.
|
||||||
|
|
||||||
|
&ubyte MSIGX = $d010
|
||||||
|
&ubyte SCROLY = $d011
|
||||||
|
&ubyte RASTER = $d012
|
||||||
|
&ubyte LPENX = $d013
|
||||||
|
&ubyte LPENY = $d014
|
||||||
|
&ubyte SPENA = $d015
|
||||||
|
&ubyte SCROLX = $d016
|
||||||
|
&ubyte YXPAND = $d017
|
||||||
|
&ubyte VMCSB = $d018
|
||||||
|
&ubyte VICIRQ = $d019
|
||||||
|
&ubyte IREQMASK = $d01a
|
||||||
|
&ubyte SPBGPR = $d01b
|
||||||
|
&ubyte SPMC = $d01c
|
||||||
|
&ubyte XXPAND = $d01d
|
||||||
|
&ubyte SPSPCL = $d01e
|
||||||
|
&ubyte SPBGCL = $d01f
|
||||||
|
|
||||||
|
&ubyte EXTCOL = $d020 ; border color
|
||||||
|
&ubyte BGCOL0 = $d021 ; screen color
|
||||||
|
&ubyte BGCOL1 = $d022
|
||||||
|
&ubyte BGCOL2 = $d023
|
||||||
|
&ubyte BGCOL4 = $d024
|
||||||
|
&ubyte SPMC0 = $d025
|
||||||
|
&ubyte SPMC1 = $d026
|
||||||
|
&ubyte SP0COL = $d027
|
||||||
|
&ubyte SP1COL = $d028
|
||||||
|
&ubyte SP2COL = $d029
|
||||||
|
&ubyte SP3COL = $d02a
|
||||||
|
&ubyte SP4COL = $d02b
|
||||||
|
&ubyte SP5COL = $d02c
|
||||||
|
&ubyte SP6COL = $d02d
|
||||||
|
&ubyte SP7COL = $d02e
|
||||||
|
&ubyte[8] SPCOL = $d027
|
||||||
|
|
||||||
|
|
||||||
|
; ---- end of VIC-II registers ----
|
||||||
|
|
||||||
|
; ---- CIA 6526 1 & 2 registers ----
|
||||||
|
|
||||||
|
&ubyte CIA1PRA = $DC00 ; CIA 1 DRA, keyboard column drive (and joystick control port #2)
|
||||||
|
&ubyte CIA1PRB = $DC01 ; CIA 1 DRB, keyboard row port (and joystick control port #1)
|
||||||
|
&ubyte CIA1DDRA = $DC02 ; CIA 1 DDRA, keyboard column
|
||||||
|
&ubyte CIA1DDRB = $DC03 ; CIA 1 DDRB, keyboard row
|
||||||
|
&ubyte CIA1TAL = $DC04 ; CIA 1 timer A low byte
|
||||||
|
&ubyte CIA1TAH = $DC05 ; CIA 1 timer A high byte
|
||||||
|
&ubyte CIA1TBL = $DC06 ; CIA 1 timer B low byte
|
||||||
|
&ubyte CIA1TBH = $DC07 ; CIA 1 timer B high byte
|
||||||
|
&ubyte CIA1TOD10 = $DC08 ; time of day, 1/10 sec.
|
||||||
|
&ubyte CIA1TODSEC = $DC09 ; time of day, seconds
|
||||||
|
&ubyte CIA1TODMMIN = $DC0A ; time of day, minutes
|
||||||
|
&ubyte CIA1TODHR = $DC0B ; time of day, hours
|
||||||
|
&ubyte CIA1SDR = $DC0C ; Serial Data Register
|
||||||
|
&ubyte CIA1ICR = $DC0D
|
||||||
|
&ubyte CIA1CRA = $DC0E
|
||||||
|
&ubyte CIA1CRB = $DC0F
|
||||||
|
|
||||||
|
&ubyte CIA2PRA = $DD00 ; CIA 2 DRA, serial port and video address
|
||||||
|
&ubyte CIA2PRB = $DD01 ; CIA 2 DRB, RS232 port / USERPORT
|
||||||
|
&ubyte CIA2DDRA = $DD02 ; CIA 2 DDRA, serial port and video address
|
||||||
|
&ubyte CIA2DDRB = $DD03 ; CIA 2 DDRB, RS232 port / USERPORT
|
||||||
|
&ubyte CIA2TAL = $DD04 ; CIA 2 timer A low byte
|
||||||
|
&ubyte CIA2TAH = $DD05 ; CIA 2 timer A high byte
|
||||||
|
&ubyte CIA2TBL = $DD06 ; CIA 2 timer B low byte
|
||||||
|
&ubyte CIA2TBH = $DD07 ; CIA 2 timer B high byte
|
||||||
|
&ubyte CIA2TOD10 = $DD08 ; time of day, 1/10 sec.
|
||||||
|
&ubyte CIA2TODSEC = $DD09 ; time of day, seconds
|
||||||
|
&ubyte CIA2TODMIN = $DD0A ; time of day, minutes
|
||||||
|
&ubyte CIA2TODHR = $DD0B ; time of day, hours
|
||||||
|
&ubyte CIA2SDR = $DD0C ; Serial Data Register
|
||||||
|
&ubyte CIA2ICR = $DD0D
|
||||||
|
&ubyte CIA2CRA = $DD0E
|
||||||
|
&ubyte CIA2CRB = $DD0F
|
||||||
|
|
||||||
|
; ---- end of CIA registers ----
|
||||||
|
|
||||||
|
; ---- SID 6581/8580 registers ----
|
||||||
|
|
||||||
|
&ubyte FREQLO1 = $D400 ; channel 1 freq lo
|
||||||
|
&ubyte FREQHI1 = $D401 ; channel 1 freq hi
|
||||||
|
&uword FREQ1 = $D400 ; channel 1 freq (word)
|
||||||
|
&ubyte PWLO1 = $D402 ; channel 1 pulse width lo (7-0)
|
||||||
|
&ubyte PWHI1 = $D403 ; channel 1 pulse width hi (11-8)
|
||||||
|
&uword PW1 = $D402 ; channel 1 pulse width (word)
|
||||||
|
&ubyte CR1 = $D404 ; channel 1 voice control register
|
||||||
|
&ubyte AD1 = $D405 ; channel 1 attack & decay
|
||||||
|
&ubyte SR1 = $D406 ; channel 1 sustain & release
|
||||||
|
&ubyte FREQLO2 = $D407 ; channel 2 freq lo
|
||||||
|
&ubyte FREQHI2 = $D408 ; channel 2 freq hi
|
||||||
|
&uword FREQ2 = $D407 ; channel 2 freq (word)
|
||||||
|
&ubyte PWLO2 = $D409 ; channel 2 pulse width lo (7-0)
|
||||||
|
&ubyte PWHI2 = $D40A ; channel 2 pulse width hi (11-8)
|
||||||
|
&uword PW2 = $D409 ; channel 2 pulse width (word)
|
||||||
|
&ubyte CR2 = $D40B ; channel 2 voice control register
|
||||||
|
&ubyte AD2 = $D40C ; channel 2 attack & decay
|
||||||
|
&ubyte SR2 = $D40D ; channel 2 sustain & release
|
||||||
|
&ubyte FREQLO3 = $D40E ; channel 3 freq lo
|
||||||
|
&ubyte FREQHI3 = $D40F ; channel 3 freq hi
|
||||||
|
&uword FREQ3 = $D40E ; channel 3 freq (word)
|
||||||
|
&ubyte PWLO3 = $D410 ; channel 3 pulse width lo (7-0)
|
||||||
|
&ubyte PWHI3 = $D411 ; channel 3 pulse width hi (11-8)
|
||||||
|
&uword PW3 = $D410 ; channel 3 pulse width (word)
|
||||||
|
&ubyte CR3 = $D412 ; channel 3 voice control register
|
||||||
|
&ubyte AD3 = $D413 ; channel 3 attack & decay
|
||||||
|
&ubyte SR3 = $D414 ; channel 3 sustain & release
|
||||||
|
&ubyte FCLO = $D415 ; filter cutoff lo (2-0)
|
||||||
|
&ubyte FCHI = $D416 ; filter cutoff hi (10-3)
|
||||||
|
&uword FC = $D415 ; filter cutoff (word)
|
||||||
|
&ubyte RESFILT = $D417 ; filter resonance and routing
|
||||||
|
&ubyte MVOL = $D418 ; filter mode and main volume control
|
||||||
|
&ubyte POTX = $D419 ; potentiometer X
|
||||||
|
&ubyte POTY = $D41A ; potentiometer Y
|
||||||
|
&ubyte OSC3 = $D41B ; channel 3 oscillator value read
|
||||||
|
&ubyte ENV3 = $D41C ; channel 3 envelope value read
|
||||||
|
|
||||||
|
; ---- end of SID registers ----
|
||||||
|
|
||||||
|
|
||||||
|
; ---- C64 ROM kernal routines ----
|
||||||
|
|
||||||
|
romsub $AB1E = STROUT(uword strptr @ AY) clobbers(A, X, Y) ; print null-terminated string (use c64scr.print instead)
|
||||||
|
romsub $E544 = CLEARSCR() clobbers(A,X,Y) ; clear the screen
|
||||||
|
romsub $E566 = HOMECRSR() clobbers(A,X,Y) ; cursor to top left of screen
|
||||||
|
romsub $EA31 = IRQDFRT() clobbers(A,X,Y) ; default IRQ routine
|
||||||
|
romsub $EA81 = IRQDFEND() clobbers(A,X,Y) ; default IRQ end/cleanup
|
||||||
|
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
|
||||||
|
romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ)
|
||||||
|
romsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, tape buffer, screen
|
||||||
|
romsub $FF8A = RESTOR() clobbers(A,X,Y) ; restore default I/O vectors
|
||||||
|
romsub $FF8D = VECTOR(uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y) ; read/set I/O vector table
|
||||||
|
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 $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
|
||||||
|
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 $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
|
||||||
|
romsub $FFA5 = ACPTR() -> ubyte @ A ; (alias: IECIN) input byte from serial bus
|
||||||
|
romsub $FFA8 = CIOUT(ubyte databyte @ A) ; (alias: IECOUT) output byte to serial bus
|
||||||
|
romsub $FFAB = UNTLK() clobbers(A) ; command serial bus device to UNTALK
|
||||||
|
romsub $FFAE = UNLSN() clobbers(A) ; command serial bus device to UNLISTEN
|
||||||
|
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 $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 $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 $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
|
||||||
|
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> ubyte @Pc ; (via 798 ($31E)) define an input channel
|
||||||
|
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
|
||||||
|
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 $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 $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 $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock
|
||||||
|
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 $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
|
||||||
|
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
|
||||||
|
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of screen rows and columns
|
||||||
|
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
|
||||||
|
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
|
||||||
|
|
||||||
|
; ---- end of C64 ROM kernal routines ----
|
||||||
|
|
||||||
|
|
||||||
|
; ---- C64 specific system utility routines: ----
|
||||||
|
|
||||||
|
asmsub init_system() {
|
||||||
|
; Initializes the machine to a sane starting state.
|
||||||
|
; Called automatically by the loader program logic.
|
||||||
|
; This means that the BASIC, KERNAL and CHARGEN ROMs are banked in,
|
||||||
|
; the VIC, SID and CIA chips are reset, screen is cleared, and the default IRQ is set.
|
||||||
|
; Also a different color scheme is chosen to identify ourselves a little.
|
||||||
|
; Uppercase charset is activated, and all three registers set to 0, status flags cleared.
|
||||||
|
%asm {{
|
||||||
|
sei
|
||||||
|
cld
|
||||||
|
lda #%00101111
|
||||||
|
sta $00
|
||||||
|
lda #%00100111
|
||||||
|
sta $01
|
||||||
|
jsr c64.IOINIT
|
||||||
|
jsr c64.RESTOR
|
||||||
|
jsr c64.CINT
|
||||||
|
lda #6
|
||||||
|
sta c64.EXTCOL
|
||||||
|
lda #7
|
||||||
|
sta c64.COLOR
|
||||||
|
lda #0
|
||||||
|
sta c64.BGCOL0
|
||||||
|
jsr disable_runstop_and_charsetswitch
|
||||||
|
clc
|
||||||
|
clv
|
||||||
|
cli
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub reset_system() {
|
||||||
|
; Soft-reset the system back to Basic prompt.
|
||||||
|
%asm {{
|
||||||
|
sei
|
||||||
|
lda #14
|
||||||
|
sta $01 ; bank the kernal in
|
||||||
|
jmp (c64.RESET_VEC)
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub disable_runstop_and_charsetswitch() {
|
||||||
|
%asm {{
|
||||||
|
lda #$80
|
||||||
|
sta 657 ; disable charset switching
|
||||||
|
lda #239
|
||||||
|
sta 808 ; disable run/stop key
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub set_irqvec_excl() clobbers(A) {
|
||||||
|
%asm {{
|
||||||
|
sei
|
||||||
|
lda #<_irq_handler
|
||||||
|
sta c64.CINV
|
||||||
|
lda #>_irq_handler
|
||||||
|
sta c64.CINV+1
|
||||||
|
cli
|
||||||
|
rts
|
||||||
|
_irq_handler jsr set_irqvec._irq_handler_init
|
||||||
|
jsr irq.irq
|
||||||
|
jsr set_irqvec._irq_handler_end
|
||||||
|
lda #$ff
|
||||||
|
sta c64.VICIRQ ; acknowledge raster irq
|
||||||
|
lda c64.CIA1ICR ; acknowledge CIA1 interrupt
|
||||||
|
jmp c64.IRQDFEND ; end irq processing - don't call kernel
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub set_irqvec() clobbers(A) {
|
||||||
|
%asm {{
|
||||||
|
sei
|
||||||
|
lda #<_irq_handler
|
||||||
|
sta c64.CINV
|
||||||
|
lda #>_irq_handler
|
||||||
|
sta c64.CINV+1
|
||||||
|
cli
|
||||||
|
rts
|
||||||
|
_irq_handler jsr _irq_handler_init
|
||||||
|
jsr irq.irq
|
||||||
|
jsr _irq_handler_end
|
||||||
|
jmp c64.IRQDFRT ; continue with normal kernel irq routine
|
||||||
|
|
||||||
|
_irq_handler_init
|
||||||
|
; save all zp scratch registers and the X register as these might be clobbered by the irq routine
|
||||||
|
stx IRQ_X_REG
|
||||||
|
lda P8ZP_SCRATCH_B1
|
||||||
|
sta IRQ_SCRATCH_ZPB1
|
||||||
|
lda P8ZP_SCRATCH_REG
|
||||||
|
sta IRQ_SCRATCH_ZPREG
|
||||||
|
lda P8ZP_SCRATCH_W1
|
||||||
|
sta IRQ_SCRATCH_ZPWORD1
|
||||||
|
lda P8ZP_SCRATCH_W1+1
|
||||||
|
sta IRQ_SCRATCH_ZPWORD1+1
|
||||||
|
lda P8ZP_SCRATCH_W2
|
||||||
|
sta IRQ_SCRATCH_ZPWORD2
|
||||||
|
lda P8ZP_SCRATCH_W2+1
|
||||||
|
sta IRQ_SCRATCH_ZPWORD2+1
|
||||||
|
; stack protector; make sure we don't clobber the top of the evaluation stack
|
||||||
|
dex
|
||||||
|
dex
|
||||||
|
dex
|
||||||
|
dex
|
||||||
|
dex
|
||||||
|
dex
|
||||||
|
cld
|
||||||
|
rts
|
||||||
|
|
||||||
|
_irq_handler_end
|
||||||
|
; restore all zp scratch registers and the X register
|
||||||
|
lda IRQ_SCRATCH_ZPB1
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
lda IRQ_SCRATCH_ZPREG
|
||||||
|
sta P8ZP_SCRATCH_REG
|
||||||
|
lda IRQ_SCRATCH_ZPWORD1
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
lda IRQ_SCRATCH_ZPWORD1+1
|
||||||
|
sta P8ZP_SCRATCH_W1+1
|
||||||
|
lda IRQ_SCRATCH_ZPWORD2
|
||||||
|
sta P8ZP_SCRATCH_W2
|
||||||
|
lda IRQ_SCRATCH_ZPWORD2+1
|
||||||
|
sta P8ZP_SCRATCH_W2+1
|
||||||
|
ldx IRQ_X_REG
|
||||||
|
rts
|
||||||
|
|
||||||
|
IRQ_X_REG .byte 0
|
||||||
|
IRQ_SCRATCH_ZPB1 .byte 0
|
||||||
|
IRQ_SCRATCH_ZPREG .byte 0
|
||||||
|
IRQ_SCRATCH_ZPWORD1 .word 0
|
||||||
|
IRQ_SCRATCH_ZPWORD2 .word 0
|
||||||
|
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub restore_irqvec() {
|
||||||
|
%asm {{
|
||||||
|
sei
|
||||||
|
lda #<c64.IRQDFRT
|
||||||
|
sta c64.CINV
|
||||||
|
lda #>c64.IRQDFRT
|
||||||
|
sta c64.CINV+1
|
||||||
|
lda #0
|
||||||
|
sta c64.IREQMASK ; disable raster irq
|
||||||
|
lda #%10000001
|
||||||
|
sta c64.CIA1ICR ; restore CIA1 irq
|
||||||
|
cli
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub set_rasterirq(uword rasterpos @ AY) clobbers(A) {
|
||||||
|
%asm {{
|
||||||
|
sei
|
||||||
|
jsr _setup_raster_irq
|
||||||
|
lda #<_raster_irq_handler
|
||||||
|
sta c64.CINV
|
||||||
|
lda #>_raster_irq_handler
|
||||||
|
sta c64.CINV+1
|
||||||
|
cli
|
||||||
|
rts
|
||||||
|
|
||||||
|
_raster_irq_handler
|
||||||
|
jsr set_irqvec._irq_handler_init
|
||||||
|
jsr irq.irq
|
||||||
|
jsr set_irqvec._irq_handler_end
|
||||||
|
lda #$ff
|
||||||
|
sta c64.VICIRQ ; acknowledge raster irq
|
||||||
|
jmp c64.IRQDFRT
|
||||||
|
|
||||||
|
_setup_raster_irq
|
||||||
|
pha
|
||||||
|
lda #%01111111
|
||||||
|
sta c64.CIA1ICR ; "switch off" interrupts signals from cia-1
|
||||||
|
sta c64.CIA2ICR ; "switch off" interrupts signals from cia-2
|
||||||
|
and c64.SCROLY
|
||||||
|
sta c64.SCROLY ; clear most significant bit of raster position
|
||||||
|
lda c64.CIA1ICR ; ack previous irq
|
||||||
|
lda c64.CIA2ICR ; ack previous irq
|
||||||
|
pla
|
||||||
|
sta c64.RASTER ; set the raster line number where interrupt should occur
|
||||||
|
cpy #0
|
||||||
|
beq +
|
||||||
|
lda c64.SCROLY
|
||||||
|
ora #%10000000
|
||||||
|
sta c64.SCROLY ; set most significant bit of raster position
|
||||||
|
+ lda #%00000001
|
||||||
|
sta c64.IREQMASK ;enable raster interrupt signals from vic
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub set_rasterirq_excl(uword rasterpos @ AY) clobbers(A) {
|
||||||
|
%asm {{
|
||||||
|
sei
|
||||||
|
jsr set_rasterirq._setup_raster_irq
|
||||||
|
lda #<_raster_irq_handler
|
||||||
|
sta c64.CINV
|
||||||
|
lda #>_raster_irq_handler
|
||||||
|
sta c64.CINV+1
|
||||||
|
cli
|
||||||
|
rts
|
||||||
|
|
||||||
|
_raster_irq_handler
|
||||||
|
jsr set_irqvec._irq_handler_init
|
||||||
|
jsr irq.irq
|
||||||
|
jsr set_irqvec._irq_handler_end
|
||||||
|
lda #$ff
|
||||||
|
sta c64.VICIRQ ; acknowledge raster irq
|
||||||
|
jmp c64.IRQDFEND ; end irq processing - don't call kernel
|
||||||
|
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
; ---- end of C64 specific system utility routines ----
|
||||||
|
|
||||||
|
|
||||||
|
}
|
587
compiler/res/prog8lib/c64/textio.p8
Normal file
587
compiler/res/prog8lib/c64/textio.p8
Normal file
@ -0,0 +1,587 @@
|
|||||||
|
; Prog8 definitions for the Text I/O and Screen routines for the Commodore-64
|
||||||
|
;
|
||||||
|
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||||
|
;
|
||||||
|
; indent format: TABS, size=8
|
||||||
|
|
||||||
|
%target c64
|
||||||
|
%import syslib
|
||||||
|
%import conv
|
||||||
|
|
||||||
|
|
||||||
|
txt {
|
||||||
|
|
||||||
|
const ubyte DEFAULT_WIDTH = 40
|
||||||
|
const ubyte DEFAULT_HEIGHT = 25
|
||||||
|
|
||||||
|
|
||||||
|
sub clear_screen() {
|
||||||
|
clear_screenchars(' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
|
||||||
|
; ---- fill the character screen with the given fill character and character color.
|
||||||
|
; (assumes screen and color matrix are at their default addresses)
|
||||||
|
|
||||||
|
%asm {{
|
||||||
|
pha
|
||||||
|
tya
|
||||||
|
jsr clear_screencolors
|
||||||
|
pla
|
||||||
|
jsr clear_screenchars
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
|
||||||
|
; ---- clear the character screen with the given fill character (leaves colors)
|
||||||
|
; (assumes screen matrix is at the default address)
|
||||||
|
%asm {{
|
||||||
|
ldy #0
|
||||||
|
_loop sta c64.Screen,y
|
||||||
|
sta c64.Screen+$0100,y
|
||||||
|
sta c64.Screen+$0200,y
|
||||||
|
sta c64.Screen+$02e8,y
|
||||||
|
iny
|
||||||
|
bne _loop
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub clear_screencolors (ubyte color @ A) clobbers(Y) {
|
||||||
|
; ---- clear the character screen colors with the given color (leaves characters).
|
||||||
|
; (assumes color matrix is at the default address)
|
||||||
|
%asm {{
|
||||||
|
ldy #0
|
||||||
|
_loop sta c64.Colors,y
|
||||||
|
sta c64.Colors+$0100,y
|
||||||
|
sta c64.Colors+$0200,y
|
||||||
|
sta c64.Colors+$02e8,y
|
||||||
|
iny
|
||||||
|
bne _loop
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub color (ubyte txtcol) {
|
||||||
|
c64.COLOR = txtcol
|
||||||
|
}
|
||||||
|
|
||||||
|
sub lowercase() {
|
||||||
|
c64.VMCSB |= 2
|
||||||
|
}
|
||||||
|
|
||||||
|
sub uppercase() {
|
||||||
|
c64.VMCSB &= ~2
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub scroll_left (ubyte alsocolors @ Pc) clobbers(A, Y) {
|
||||||
|
; ---- scroll the whole screen 1 character to the left
|
||||||
|
; contents of the rightmost column are unchanged, you should clear/refill this yourself
|
||||||
|
; Carry flag determines if screen color data must be scrolled too
|
||||||
|
|
||||||
|
%asm {{
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
bcs +
|
||||||
|
jmp _scroll_screen
|
||||||
|
|
||||||
|
+ ; scroll the color memory
|
||||||
|
ldx #0
|
||||||
|
ldy #38
|
||||||
|
-
|
||||||
|
.for row=0, row<=24, row+=1
|
||||||
|
lda c64.Colors + 40*row + 1,x
|
||||||
|
sta c64.Colors + 40*row,x
|
||||||
|
.next
|
||||||
|
inx
|
||||||
|
dey
|
||||||
|
bpl -
|
||||||
|
|
||||||
|
_scroll_screen ; scroll the screen memory
|
||||||
|
ldx #0
|
||||||
|
ldy #38
|
||||||
|
-
|
||||||
|
.for row=0, row<=24, row+=1
|
||||||
|
lda c64.Screen + 40*row + 1,x
|
||||||
|
sta c64.Screen + 40*row,x
|
||||||
|
.next
|
||||||
|
inx
|
||||||
|
dey
|
||||||
|
bpl -
|
||||||
|
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub scroll_right (ubyte alsocolors @ Pc) clobbers(A) {
|
||||||
|
; ---- scroll the whole screen 1 character to the right
|
||||||
|
; contents of the leftmost column are unchanged, you should clear/refill this yourself
|
||||||
|
; Carry flag determines if screen color data must be scrolled too
|
||||||
|
%asm {{
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
bcs +
|
||||||
|
jmp _scroll_screen
|
||||||
|
|
||||||
|
+ ; scroll the color memory
|
||||||
|
ldx #38
|
||||||
|
-
|
||||||
|
.for row=0, row<=24, row+=1
|
||||||
|
lda c64.Colors + 40*row + 0,x
|
||||||
|
sta c64.Colors + 40*row + 1,x
|
||||||
|
.next
|
||||||
|
dex
|
||||||
|
bpl -
|
||||||
|
|
||||||
|
_scroll_screen ; scroll the screen memory
|
||||||
|
ldx #38
|
||||||
|
-
|
||||||
|
.for row=0, row<=24, row+=1
|
||||||
|
lda c64.Screen + 40*row + 0,x
|
||||||
|
sta c64.Screen + 40*row + 1,x
|
||||||
|
.next
|
||||||
|
dex
|
||||||
|
bpl -
|
||||||
|
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub scroll_up (ubyte alsocolors @ Pc) clobbers(A) {
|
||||||
|
; ---- scroll the whole screen 1 character up
|
||||||
|
; contents of the bottom row are unchanged, you should refill/clear this yourself
|
||||||
|
; Carry flag determines if screen color data must be scrolled too
|
||||||
|
%asm {{
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
bcs +
|
||||||
|
jmp _scroll_screen
|
||||||
|
|
||||||
|
+ ; scroll the color memory
|
||||||
|
ldx #39
|
||||||
|
-
|
||||||
|
.for row=1, row<=24, row+=1
|
||||||
|
lda c64.Colors + 40*row,x
|
||||||
|
sta c64.Colors + 40*(row-1),x
|
||||||
|
.next
|
||||||
|
dex
|
||||||
|
bpl -
|
||||||
|
|
||||||
|
_scroll_screen ; scroll the screen memory
|
||||||
|
ldx #39
|
||||||
|
-
|
||||||
|
.for row=1, row<=24, row+=1
|
||||||
|
lda c64.Screen + 40*row,x
|
||||||
|
sta c64.Screen + 40*(row-1),x
|
||||||
|
.next
|
||||||
|
dex
|
||||||
|
bpl -
|
||||||
|
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub scroll_down (ubyte alsocolors @ Pc) clobbers(A) {
|
||||||
|
; ---- scroll the whole screen 1 character down
|
||||||
|
; contents of the top row are unchanged, you should refill/clear this yourself
|
||||||
|
; Carry flag determines if screen color data must be scrolled too
|
||||||
|
%asm {{
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
bcs +
|
||||||
|
jmp _scroll_screen
|
||||||
|
|
||||||
|
+ ; scroll the color memory
|
||||||
|
ldx #39
|
||||||
|
-
|
||||||
|
.for row=23, row>=0, row-=1
|
||||||
|
lda c64.Colors + 40*row,x
|
||||||
|
sta c64.Colors + 40*(row+1),x
|
||||||
|
.next
|
||||||
|
dex
|
||||||
|
bpl -
|
||||||
|
|
||||||
|
_scroll_screen ; scroll the screen memory
|
||||||
|
ldx #39
|
||||||
|
-
|
||||||
|
.for row=23, row>=0, row-=1
|
||||||
|
lda c64.Screen + 40*row,x
|
||||||
|
sta c64.Screen + 40*(row+1),x
|
||||||
|
.next
|
||||||
|
dex
|
||||||
|
bpl -
|
||||||
|
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
romsub $FFD2 = chrout(ubyte char @ A) ; for consistency. You can also use c64.CHROUT directly ofcourse.
|
||||||
|
|
||||||
|
asmsub print (str text @ AY) clobbers(A,Y) {
|
||||||
|
; ---- print null terminated string from A/Y
|
||||||
|
; note: the compiler contains an optimization that will replace
|
||||||
|
; a call to this subroutine with a string argument of just one char,
|
||||||
|
; by just one call to c64.CHROUT of that single char.
|
||||||
|
%asm {{
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
sty P8ZP_SCRATCH_REG
|
||||||
|
ldy #0
|
||||||
|
- lda (P8ZP_SCRATCH_B1),y
|
||||||
|
beq +
|
||||||
|
jsr c64.CHROUT
|
||||||
|
iny
|
||||||
|
bne -
|
||||||
|
+ rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_ub0 (ubyte value @ A) clobbers(A,Y) {
|
||||||
|
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
|
||||||
|
%asm {{
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr conv.ubyte2decimal
|
||||||
|
pha
|
||||||
|
tya
|
||||||
|
jsr c64.CHROUT
|
||||||
|
pla
|
||||||
|
jsr c64.CHROUT
|
||||||
|
txa
|
||||||
|
jsr c64.CHROUT
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_ub (ubyte value @ A) clobbers(A,Y) {
|
||||||
|
; ---- print the ubyte in A in decimal form, without left padding 0s
|
||||||
|
%asm {{
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr conv.ubyte2decimal
|
||||||
|
_print_byte_digits
|
||||||
|
pha
|
||||||
|
cpy #'0'
|
||||||
|
beq +
|
||||||
|
tya
|
||||||
|
jsr c64.CHROUT
|
||||||
|
pla
|
||||||
|
jsr c64.CHROUT
|
||||||
|
jmp _ones
|
||||||
|
+ pla
|
||||||
|
cmp #'0'
|
||||||
|
beq _ones
|
||||||
|
jsr c64.CHROUT
|
||||||
|
_ones txa
|
||||||
|
jsr c64.CHROUT
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_b (byte value @ A) clobbers(A,Y) {
|
||||||
|
; ---- print the byte in A in decimal form, without left padding 0s
|
||||||
|
%asm {{
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
pha
|
||||||
|
cmp #0
|
||||||
|
bpl +
|
||||||
|
lda #'-'
|
||||||
|
jsr c64.CHROUT
|
||||||
|
+ pla
|
||||||
|
jsr conv.byte2decimal
|
||||||
|
jmp print_ub._print_byte_digits
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_ubhex (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||||
|
; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well)
|
||||||
|
%asm {{
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
bcc +
|
||||||
|
pha
|
||||||
|
lda #'$'
|
||||||
|
jsr c64.CHROUT
|
||||||
|
pla
|
||||||
|
+ jsr conv.ubyte2hex
|
||||||
|
jsr c64.CHROUT
|
||||||
|
tya
|
||||||
|
jsr c64.CHROUT
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_ubbin (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||||
|
; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well)
|
||||||
|
%asm {{
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
bcc +
|
||||||
|
lda #'%'
|
||||||
|
jsr c64.CHROUT
|
||||||
|
+ ldy #8
|
||||||
|
- lda #'0'
|
||||||
|
asl P8ZP_SCRATCH_B1
|
||||||
|
bcc +
|
||||||
|
lda #'1'
|
||||||
|
+ jsr c64.CHROUT
|
||||||
|
dey
|
||||||
|
bne -
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_uwbin (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||||
|
; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
|
||||||
|
%asm {{
|
||||||
|
pha
|
||||||
|
tya
|
||||||
|
jsr print_ubbin
|
||||||
|
pla
|
||||||
|
clc
|
||||||
|
jmp print_ubbin
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_uwhex (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||||
|
; ---- print the uword in A/Y in hexadecimal form (4 digits)
|
||||||
|
; (if Carry is set, a radix prefix '$' is printed as well)
|
||||||
|
%asm {{
|
||||||
|
pha
|
||||||
|
tya
|
||||||
|
jsr print_ubhex
|
||||||
|
pla
|
||||||
|
clc
|
||||||
|
jmp print_ubhex
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_uw0 (uword value @ AY) clobbers(A,Y) {
|
||||||
|
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
|
||||||
|
%asm {{
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr conv.uword2decimal
|
||||||
|
ldy #0
|
||||||
|
- lda conv.uword2decimal.decTenThousands,y
|
||||||
|
beq +
|
||||||
|
jsr c64.CHROUT
|
||||||
|
iny
|
||||||
|
bne -
|
||||||
|
+ ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_uw (uword value @ AY) clobbers(A,Y) {
|
||||||
|
; ---- print the uword in A/Y in decimal form, without left padding 0s
|
||||||
|
%asm {{
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
jsr conv.uword2decimal
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
ldy #0
|
||||||
|
- lda conv.uword2decimal.decTenThousands,y
|
||||||
|
beq _allzero
|
||||||
|
cmp #'0'
|
||||||
|
bne _gotdigit
|
||||||
|
iny
|
||||||
|
bne -
|
||||||
|
|
||||||
|
_gotdigit
|
||||||
|
jsr c64.CHROUT
|
||||||
|
iny
|
||||||
|
lda conv.uword2decimal.decTenThousands,y
|
||||||
|
bne _gotdigit
|
||||||
|
rts
|
||||||
|
_allzero
|
||||||
|
lda #'0'
|
||||||
|
jmp c64.CHROUT
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_w (word value @ AY) clobbers(A,Y) {
|
||||||
|
; ---- print the (signed) word in A/Y in decimal form, without left padding 0's
|
||||||
|
%asm {{
|
||||||
|
cpy #0
|
||||||
|
bpl +
|
||||||
|
pha
|
||||||
|
lda #'-'
|
||||||
|
jsr c64.CHROUT
|
||||||
|
tya
|
||||||
|
eor #255
|
||||||
|
tay
|
||||||
|
pla
|
||||||
|
eor #255
|
||||||
|
clc
|
||||||
|
adc #1
|
||||||
|
bcc +
|
||||||
|
iny
|
||||||
|
+ jmp print_uw
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
|
||||||
|
; ---- Input a string (max. 80 chars) from the keyboard. Returns length in Y. (string is terminated with a 0 byte as well)
|
||||||
|
; It assumes the keyboard is selected as I/O channel!
|
||||||
|
|
||||||
|
%asm {{
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
ldy #0 ; char counter = 0
|
||||||
|
- jsr c64.CHRIN
|
||||||
|
cmp #$0d ; return (ascii 13) pressed?
|
||||||
|
beq + ; yes, end.
|
||||||
|
sta (P8ZP_SCRATCH_W1),y ; else store char in buffer
|
||||||
|
iny
|
||||||
|
bne -
|
||||||
|
+ lda #0
|
||||||
|
sta (P8ZP_SCRATCH_W1),y ; finish string with 0 byte
|
||||||
|
rts
|
||||||
|
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A, Y) {
|
||||||
|
; ---- sets the character in the screen matrix at the given position
|
||||||
|
%asm {{
|
||||||
|
pha
|
||||||
|
tya
|
||||||
|
asl a
|
||||||
|
tay
|
||||||
|
lda _screenrows+1,y
|
||||||
|
sta _mod+2
|
||||||
|
txa
|
||||||
|
clc
|
||||||
|
adc _screenrows,y
|
||||||
|
sta _mod+1
|
||||||
|
bcc +
|
||||||
|
inc _mod+2
|
||||||
|
+ pla
|
||||||
|
_mod sta $ffff ; modified
|
||||||
|
rts
|
||||||
|
|
||||||
|
_screenrows .word $0400 + range(0, 1000, 40)
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub getchr (ubyte col @A, ubyte row @Y) clobbers(Y) -> ubyte @ A {
|
||||||
|
; ---- get the character in the screen matrix at the given location
|
||||||
|
%asm {{
|
||||||
|
pha
|
||||||
|
tya
|
||||||
|
asl a
|
||||||
|
tay
|
||||||
|
lda setchr._screenrows+1,y
|
||||||
|
sta _mod+2
|
||||||
|
pla
|
||||||
|
clc
|
||||||
|
adc setchr._screenrows,y
|
||||||
|
sta _mod+1
|
||||||
|
bcc _mod
|
||||||
|
inc _mod+2
|
||||||
|
_mod lda $ffff ; modified
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub setclr (ubyte col @X, ubyte row @Y, ubyte color @A) clobbers(A, Y) {
|
||||||
|
; ---- set the color in A on the screen matrix at the given position
|
||||||
|
%asm {{
|
||||||
|
pha
|
||||||
|
tya
|
||||||
|
asl a
|
||||||
|
tay
|
||||||
|
lda _colorrows+1,y
|
||||||
|
sta _mod+2
|
||||||
|
txa
|
||||||
|
clc
|
||||||
|
adc _colorrows,y
|
||||||
|
sta _mod+1
|
||||||
|
bcc +
|
||||||
|
inc _mod+2
|
||||||
|
+ pla
|
||||||
|
_mod sta $ffff ; modified
|
||||||
|
rts
|
||||||
|
|
||||||
|
_colorrows .word $d800 + range(0, 1000, 40)
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub getclr (ubyte col @A, ubyte row @Y) clobbers(Y) -> ubyte @ A {
|
||||||
|
; ---- get the color in the screen color matrix at the given location
|
||||||
|
%asm {{
|
||||||
|
pha
|
||||||
|
tya
|
||||||
|
asl a
|
||||||
|
tay
|
||||||
|
lda setclr._colorrows+1,y
|
||||||
|
sta _mod+2
|
||||||
|
pla
|
||||||
|
clc
|
||||||
|
adc setclr._colorrows,y
|
||||||
|
sta _mod+1
|
||||||
|
bcc _mod
|
||||||
|
inc _mod+2
|
||||||
|
_mod lda $ffff ; modified
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
|
||||||
|
; ---- set char+color at the given position on the screen
|
||||||
|
%asm {{
|
||||||
|
lda row
|
||||||
|
asl a
|
||||||
|
tay
|
||||||
|
lda setchr._screenrows+1,y
|
||||||
|
sta _charmod+2
|
||||||
|
adc #$d4
|
||||||
|
sta _colormod+2
|
||||||
|
lda setchr._screenrows,y
|
||||||
|
clc
|
||||||
|
adc column
|
||||||
|
sta _charmod+1
|
||||||
|
sta _colormod+1
|
||||||
|
bcc +
|
||||||
|
inc _charmod+2
|
||||||
|
inc _colormod+2
|
||||||
|
+ lda char
|
||||||
|
_charmod sta $ffff ; modified
|
||||||
|
lda charcolor
|
||||||
|
_colormod sta $ffff ; modified
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub plot (ubyte col @ Y, ubyte row @ A) clobbers(A) {
|
||||||
|
; ---- safe wrapper around PLOT kernel routine, to save the X register.
|
||||||
|
%asm {{
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
tax
|
||||||
|
clc
|
||||||
|
jsr c64.PLOT
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub width() clobbers(X,Y) -> ubyte @A {
|
||||||
|
; -- returns the text screen width (number of columns)
|
||||||
|
%asm {{
|
||||||
|
jsr c64.SCREEN
|
||||||
|
txa
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub height() clobbers(X, Y) -> ubyte @A {
|
||||||
|
; -- returns the text screen height (number of rows)
|
||||||
|
%asm {{
|
||||||
|
jsr c64.SCREEN
|
||||||
|
tya
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,969 +0,0 @@
|
|||||||
; Prog8 definitions for floating point handling on the Commodore-64
|
|
||||||
;
|
|
||||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
|
||||||
;
|
|
||||||
; indent format: TABS, size=8
|
|
||||||
|
|
||||||
%option enable_floats
|
|
||||||
|
|
||||||
|
|
||||||
c64flt {
|
|
||||||
; ---- this block contains C-64 floating point related functions ----
|
|
||||||
|
|
||||||
const float PI = 3.141592653589793
|
|
||||||
const float TWOPI = 6.283185307179586
|
|
||||||
|
|
||||||
|
|
||||||
; ---- C64 basic and kernal ROM float constants and functions ----
|
|
||||||
|
|
||||||
; note: the fac1 and fac2 are working registers and take 6 bytes each,
|
|
||||||
; floats in memory (and rom) are stored in 5-byte MFLPT packed format.
|
|
||||||
|
|
||||||
; constants in five-byte "mflpt" format in the BASIC ROM
|
|
||||||
&float FL_PIVAL = $aea8 ; 3.1415926...
|
|
||||||
&float FL_N32768 = $b1a5 ; -32768
|
|
||||||
&float FL_FONE = $b9bc ; 1
|
|
||||||
&float FL_SQRHLF = $b9d6 ; SQR(2) / 2
|
|
||||||
&float FL_SQRTWO = $b9db ; SQR(2)
|
|
||||||
&float FL_NEGHLF = $b9e0 ; -.5
|
|
||||||
&float FL_LOG2 = $b9e5 ; LOG(2)
|
|
||||||
&float FL_TENC = $baf9 ; 10
|
|
||||||
&float FL_NZMIL = $bdbd ; 1e9 (1 billion)
|
|
||||||
&float FL_FHALF = $bf11 ; .5
|
|
||||||
&float FL_LOGEB2 = $bfbf ; 1 / LOG(2)
|
|
||||||
&float FL_PIHALF = $e2e0 ; PI / 2
|
|
||||||
&float FL_TWOPI = $e2e5 ; 2 * PI
|
|
||||||
&float FL_FR4 = $e2ea ; .25
|
|
||||||
float FL_ZERO = 0.0 ; oddly enough 0.0 isn't available in the kernel
|
|
||||||
|
|
||||||
|
|
||||||
; note: fac1/2 might get clobbered even if not mentioned in the function's name.
|
|
||||||
; note: for subtraction and division, the left operand is in fac2, the right operand in fac1.
|
|
||||||
|
|
||||||
; checked functions below:
|
|
||||||
asmsub MOVFM (uword mflpt @ AY) clobbers(A,Y) = $bba2 ; load mflpt value from memory in A/Y into fac1
|
|
||||||
asmsub FREADMEM () clobbers(A,Y) = $bba6 ; load mflpt value from memory in $22/$23 into fac1
|
|
||||||
asmsub CONUPK (uword mflpt @ AY) clobbers(A,Y) = $ba8c ; load mflpt value from memory in A/Y into fac2
|
|
||||||
asmsub FAREADMEM () clobbers(A,Y) = $ba90 ; load mflpt value from memory in $22/$23 into fac2
|
|
||||||
asmsub MOVFA () clobbers(A,X) = $bbfc ; copy fac2 to fac1
|
|
||||||
asmsub MOVAF () clobbers(A,X) = $bc0c ; copy fac1 to fac2 (rounded)
|
|
||||||
asmsub MOVEF () clobbers(A,X) = $bc0f ; copy fac1 to fac2
|
|
||||||
asmsub MOVMF (uword mflpt @ XY) clobbers(A,Y) = $bbd4 ; store fac1 to memory X/Y as 5-byte mflpt
|
|
||||||
|
|
||||||
; fac1-> signed word in Y/A (might throw ILLEGAL QUANTITY)
|
|
||||||
; (tip: use c64flt.FTOSWRDAY to get A/Y output; lo/hi switched to normal little endian order)
|
|
||||||
asmsub FTOSWORDYA () clobbers(X) -> ubyte @ Y, ubyte @ A = $b1aa ; note: calls AYINT.
|
|
||||||
|
|
||||||
; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
|
|
||||||
; (tip: use c64flt.GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
|
|
||||||
asmsub GETADR () clobbers(X) -> ubyte @ Y, ubyte @ A = $b7f7
|
|
||||||
|
|
||||||
asmsub QINT () clobbers(A,X,Y) = $bc9b ; fac1 -> 4-byte signed integer in 98-101 ($62-$65), with the MSB FIRST.
|
|
||||||
asmsub AYINT () clobbers(A,X,Y) = $b1bf ; fac1-> signed word in 100-101 ($64-$65) MSB FIRST. (might throw ILLEGAL QUANTITY)
|
|
||||||
|
|
||||||
; GIVAYF: signed word in Y/A (note different lsb/msb order) -> float in fac1
|
|
||||||
; (tip: use c64flt.GIVAYFAY to use A/Y input; lo/hi switched to normal order)
|
|
||||||
; there is also c64flt.GIVUAYFAY - unsigned word in A/Y (lo/hi) to fac1
|
|
||||||
; there is also c64flt.FREADS32 that reads from 98-101 ($62-$65) MSB FIRST
|
|
||||||
; there is also c64flt.FREADUS32 that reads from 98-101 ($62-$65) MSB FIRST
|
|
||||||
; there is also c64flt.FREADS24AXY that reads signed int24 into fac1 from A/X/Y (lo/mid/hi bytes)
|
|
||||||
asmsub GIVAYF (ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y) = $b391
|
|
||||||
|
|
||||||
asmsub FREADUY (ubyte value @ Y) clobbers(A,X,Y) = $b3a2 ; 8 bit unsigned Y -> float in fac1
|
|
||||||
asmsub FREADSA (byte value @ A) clobbers(A,X,Y) = $bc3c ; 8 bit signed A -> float in fac1
|
|
||||||
asmsub FREADSTR (ubyte length @ A) clobbers(A,X,Y) = $b7b5 ; str -> fac1, $22/23 must point to string, A=string length
|
|
||||||
asmsub FPRINTLN () clobbers(A,X,Y) = $aabc ; print string of fac1, on one line (= with newline) destroys fac1. (consider FOUT + STROUT as well)
|
|
||||||
asmsub FOUT () clobbers(X) -> uword @ AY = $bddd ; fac1 -> string, address returned in AY ($0100)
|
|
||||||
|
|
||||||
asmsub FADDH () clobbers(A,X,Y) = $b849 ; fac1 += 0.5, for rounding- call this before INT
|
|
||||||
asmsub MUL10 () clobbers(A,X,Y) = $bae2 ; fac1 *= 10
|
|
||||||
asmsub DIV10 () clobbers(A,X,Y) = $bafe ; fac1 /= 10 , CAUTION: result is always positive!
|
|
||||||
asmsub FCOMP (uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A = $bc5b ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
|
|
||||||
|
|
||||||
asmsub FADDT () clobbers(A,X,Y) = $b86a ; fac1 += fac2
|
|
||||||
asmsub FADD (uword mflpt @ AY) clobbers(A,X,Y) = $b867 ; fac1 += mflpt value from A/Y
|
|
||||||
asmsub FSUBT () clobbers(A,X,Y) = $b853 ; fac1 = fac2-fac1 mind the order of the operands
|
|
||||||
asmsub FSUB (uword mflpt @ AY) clobbers(A,X,Y) = $b850 ; fac1 = mflpt from A/Y - fac1
|
|
||||||
asmsub FMULTT () clobbers(A,X,Y) = $ba2b ; fac1 *= fac2
|
|
||||||
asmsub FMULT (uword mflpt @ AY) clobbers(A,X,Y) = $ba28 ; fac1 *= mflpt value from A/Y
|
|
||||||
asmsub FDIVT () clobbers(A,X,Y) = $bb12 ; fac1 = fac2/fac1 (remainder in fac2) mind the order of the operands
|
|
||||||
asmsub FDIV (uword mflpt @ AY) clobbers(A,X,Y) = $bb0f ; fac1 = mflpt in A/Y / fac1 (remainder in fac2)
|
|
||||||
asmsub FPWRT () clobbers(A,X,Y) = $bf7b ; fac1 = fac2 ** fac1
|
|
||||||
asmsub FPWR (uword mflpt @ AY) clobbers(A,X,Y) = $bf78 ; fac1 = fac2 ** mflpt from A/Y
|
|
||||||
|
|
||||||
asmsub NOTOP () clobbers(A,X,Y) = $aed4 ; fac1 = NOT(fac1)
|
|
||||||
asmsub INT () clobbers(A,X,Y) = $bccc ; INT() truncates, use FADDH first to round instead of trunc
|
|
||||||
asmsub LOG () clobbers(A,X,Y) = $b9ea ; fac1 = LN(fac1) (natural log)
|
|
||||||
asmsub SGN () clobbers(A,X,Y) = $bc39 ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
|
|
||||||
asmsub SIGN () -> ubyte @ A = $bc2b ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
|
|
||||||
asmsub ABS () = $bc58 ; fac1 = ABS(fac1)
|
|
||||||
asmsub SQR () clobbers(A,X,Y) = $bf71 ; fac1 = SQRT(fac1)
|
|
||||||
asmsub SQRA () clobbers(A,X,Y) = $bf74 ; fac1 = SQRT(fac2)
|
|
||||||
asmsub EXP () clobbers(A,X,Y) = $bfed ; fac1 = EXP(fac1) (e ** fac1)
|
|
||||||
asmsub NEGOP () clobbers(A) = $bfb4 ; switch the sign of fac1
|
|
||||||
asmsub RND () clobbers(A,X,Y) = $e097 ; fac1 = RND(fac1) float random number generator
|
|
||||||
asmsub COS () clobbers(A,X,Y) = $e264 ; fac1 = COS(fac1)
|
|
||||||
asmsub SIN () clobbers(A,X,Y) = $e26b ; fac1 = SIN(fac1)
|
|
||||||
asmsub TAN () clobbers(A,X,Y) = $e2b4 ; fac1 = TAN(fac1)
|
|
||||||
asmsub ATN () clobbers(A,X,Y) = $e30e ; fac1 = ATN(fac1)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
asmsub FREADS32 () clobbers(A,X,Y) {
|
|
||||||
; ---- fac1 = signed int32 from $62-$65 big endian (MSB FIRST)
|
|
||||||
%asm {{
|
|
||||||
lda $62
|
|
||||||
eor #$ff
|
|
||||||
asl a
|
|
||||||
lda #0
|
|
||||||
ldx #$a0
|
|
||||||
jmp $bc4f ; internal BASIC routine
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
asmsub FREADUS32 () clobbers(A,X,Y) {
|
|
||||||
; ---- fac1 = uint32 from $62-$65 big endian (MSB FIRST)
|
|
||||||
%asm {{
|
|
||||||
sec
|
|
||||||
lda #0
|
|
||||||
ldx #$a0
|
|
||||||
jmp $bc4f ; internal BASIC routine
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
asmsub FREADS24AXY (ubyte lo @ A, ubyte mid @ X, ubyte hi @ Y) clobbers(A,X,Y) {
|
|
||||||
; ---- fac1 = signed int24 (A/X/Y contain lo/mid/hi bytes)
|
|
||||||
; note: there is no FREADU24AXY (unsigned), use FREADUS32 instead.
|
|
||||||
%asm {{
|
|
||||||
sty $62
|
|
||||||
stx $63
|
|
||||||
sta $64
|
|
||||||
lda $62
|
|
||||||
eor #$FF
|
|
||||||
asl a
|
|
||||||
lda #0
|
|
||||||
sta $65
|
|
||||||
ldx #$98
|
|
||||||
jmp $bc4f ; internal BASIC routine
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
|
||||||
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
|
|
||||||
%asm {{
|
|
||||||
sty $62
|
|
||||||
sta $63
|
|
||||||
ldx #$90
|
|
||||||
sec
|
|
||||||
jmp $bc49 ; internal BASIC routine
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
|
||||||
; ---- signed 16 bit word in A/Y (lo/hi) to float in fac1
|
|
||||||
%asm {{
|
|
||||||
sta c64.SCRATCH_ZPREG
|
|
||||||
tya
|
|
||||||
ldy c64.SCRATCH_ZPREG
|
|
||||||
jmp GIVAYF ; this uses the inverse order, Y/A
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
asmsub FTOSWRDAY () clobbers(X) -> uword @ AY {
|
|
||||||
; ---- fac1 to signed word in A/Y
|
|
||||||
%asm {{
|
|
||||||
jsr FTOSWORDYA ; note the inverse Y/A order
|
|
||||||
sta c64.SCRATCH_ZPREG
|
|
||||||
tya
|
|
||||||
ldy c64.SCRATCH_ZPREG
|
|
||||||
rts
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
asmsub GETADRAY () clobbers(X) -> uword @ AY {
|
|
||||||
; ---- fac1 to unsigned word in A/Y
|
|
||||||
%asm {{
|
|
||||||
jsr GETADR ; this uses the inverse order, Y/A
|
|
||||||
sta c64.SCRATCH_ZPB1
|
|
||||||
tya
|
|
||||||
ldy c64.SCRATCH_ZPB1
|
|
||||||
rts
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
sub print_f (float value) {
|
|
||||||
; ---- prints the floating point value (without a newline) using basic rom routines.
|
|
||||||
%asm {{
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
lda #<value
|
|
||||||
ldy #>value
|
|
||||||
jsr MOVFM ; load float into fac1
|
|
||||||
jsr FOUT ; fac1 to string in A/Y
|
|
||||||
jsr c64.STROUT ; print string in A/Y
|
|
||||||
ldx c64.SCRATCH_ZPREGX
|
|
||||||
rts
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
sub print_fln (float value) {
|
|
||||||
; ---- prints the floating point value (with a newline at the end) using basic rom routines
|
|
||||||
%asm {{
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
lda #<print_fln_value
|
|
||||||
ldy #>print_fln_value
|
|
||||||
jsr MOVFM ; load float into fac1
|
|
||||||
jsr FPRINTLN ; print fac1 with newline
|
|
||||||
ldx c64.SCRATCH_ZPREGX
|
|
||||||
rts
|
|
||||||
}}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
; --- low level floating point assembly routines
|
|
||||||
%asm {{
|
|
||||||
ub2float .proc
|
|
||||||
; -- convert ubyte in SCRATCH_ZPB1 to float at address A/Y
|
|
||||||
; clobbers A, Y
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
sta c64.SCRATCH_ZPWORD2
|
|
||||||
sty c64.SCRATCH_ZPWORD2+1
|
|
||||||
ldy c64.SCRATCH_ZPB1
|
|
||||||
jsr FREADUY
|
|
||||||
_fac_to_mem ldx c64.SCRATCH_ZPWORD2
|
|
||||||
ldy c64.SCRATCH_ZPWORD2+1
|
|
||||||
jsr MOVMF
|
|
||||||
ldx c64.SCRATCH_ZPREGX
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
b2float .proc
|
|
||||||
; -- convert byte in SCRATCH_ZPB1 to float at address A/Y
|
|
||||||
; clobbers A, Y
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
sta c64.SCRATCH_ZPWORD2
|
|
||||||
sty c64.SCRATCH_ZPWORD2+1
|
|
||||||
lda c64.SCRATCH_ZPB1
|
|
||||||
jsr FREADSA
|
|
||||||
jmp ub2float._fac_to_mem
|
|
||||||
.pend
|
|
||||||
|
|
||||||
uw2float .proc
|
|
||||||
; -- convert uword in SCRATCH_ZPWORD1 to float at address A/Y
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
sta c64.SCRATCH_ZPWORD2
|
|
||||||
sty c64.SCRATCH_ZPWORD2+1
|
|
||||||
lda c64.SCRATCH_ZPWORD1
|
|
||||||
ldy c64.SCRATCH_ZPWORD1+1
|
|
||||||
jsr GIVUAYFAY
|
|
||||||
jmp ub2float._fac_to_mem
|
|
||||||
.pend
|
|
||||||
|
|
||||||
w2float .proc
|
|
||||||
; -- convert word in SCRATCH_ZPWORD1 to float at address A/Y
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
sta c64.SCRATCH_ZPWORD2
|
|
||||||
sty c64.SCRATCH_ZPWORD2+1
|
|
||||||
ldy c64.SCRATCH_ZPWORD1
|
|
||||||
lda c64.SCRATCH_ZPWORD1+1
|
|
||||||
jsr GIVAYF
|
|
||||||
jmp ub2float._fac_to_mem
|
|
||||||
.pend
|
|
||||||
|
|
||||||
stack_b2float .proc
|
|
||||||
; -- b2float operating on the stack
|
|
||||||
inx
|
|
||||||
lda c64.ESTACK_LO,x
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr FREADSA
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
stack_w2float .proc
|
|
||||||
; -- w2float operating on the stack
|
|
||||||
inx
|
|
||||||
ldy c64.ESTACK_LO,x
|
|
||||||
lda c64.ESTACK_HI,x
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr GIVAYF
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
stack_ub2float .proc
|
|
||||||
; -- ub2float operating on the stack
|
|
||||||
inx
|
|
||||||
lda c64.ESTACK_LO,x
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
tay
|
|
||||||
jsr FREADUY
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
stack_uw2float .proc
|
|
||||||
; -- uw2float operating on the stack
|
|
||||||
inx
|
|
||||||
lda c64.ESTACK_LO,x
|
|
||||||
ldy c64.ESTACK_HI,x
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr GIVUAYFAY
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
stack_float2w .proc ; also used for float2b
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr AYINT
|
|
||||||
ldx c64.SCRATCH_ZPREGX
|
|
||||||
lda $64
|
|
||||||
sta c64.ESTACK_HI,x
|
|
||||||
lda $65
|
|
||||||
sta c64.ESTACK_LO,x
|
|
||||||
dex
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
stack_float2uw .proc ; also used for float2ub
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr GETADR
|
|
||||||
ldx c64.SCRATCH_ZPREGX
|
|
||||||
sta c64.ESTACK_HI,x
|
|
||||||
tya
|
|
||||||
sta c64.ESTACK_LO,x
|
|
||||||
dex
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
push_float .proc
|
|
||||||
; ---- push mflpt5 in A/Y onto stack
|
|
||||||
; (taking 3 stack positions = 6 bytes of which 1 is padding)
|
|
||||||
sta c64.SCRATCH_ZPWORD1
|
|
||||||
sty c64.SCRATCH_ZPWORD1+1
|
|
||||||
ldy #0
|
|
||||||
lda (c64.SCRATCH_ZPWORD1),y
|
|
||||||
sta c64.ESTACK_LO,x
|
|
||||||
iny
|
|
||||||
lda (c64.SCRATCH_ZPWORD1),y
|
|
||||||
sta c64.ESTACK_HI,x
|
|
||||||
dex
|
|
||||||
iny
|
|
||||||
lda (c64.SCRATCH_ZPWORD1),y
|
|
||||||
sta c64.ESTACK_LO,x
|
|
||||||
iny
|
|
||||||
lda (c64.SCRATCH_ZPWORD1),y
|
|
||||||
sta c64.ESTACK_HI,x
|
|
||||||
dex
|
|
||||||
iny
|
|
||||||
lda (c64.SCRATCH_ZPWORD1),y
|
|
||||||
sta c64.ESTACK_LO,x
|
|
||||||
dex
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_rndf .proc
|
|
||||||
; -- put a random floating point value on the stack
|
|
||||||
stx c64.SCRATCH_ZPREG
|
|
||||||
lda #1
|
|
||||||
jsr FREADSA
|
|
||||||
jsr RND ; rng into fac1
|
|
||||||
ldx #<_rndf_rnum5
|
|
||||||
ldy #>_rndf_rnum5
|
|
||||||
jsr MOVMF ; fac1 to mem X/Y
|
|
||||||
ldx c64.SCRATCH_ZPREG
|
|
||||||
lda #<_rndf_rnum5
|
|
||||||
ldy #>_rndf_rnum5
|
|
||||||
jmp push_float
|
|
||||||
_rndf_rnum5 .byte 0,0,0,0,0
|
|
||||||
.pend
|
|
||||||
|
|
||||||
push_float_from_indexed_var .proc
|
|
||||||
; -- push the float from the array at A/Y with index on stack, onto the stack.
|
|
||||||
sta c64.SCRATCH_ZPWORD1
|
|
||||||
sty c64.SCRATCH_ZPWORD1+1
|
|
||||||
jsr prog8_lib.pop_index_times_5
|
|
||||||
jsr prog8_lib.add_a_to_zpword
|
|
||||||
lda c64.SCRATCH_ZPWORD1
|
|
||||||
ldy c64.SCRATCH_ZPWORD1+1
|
|
||||||
jmp push_float
|
|
||||||
.pend
|
|
||||||
|
|
||||||
pop_float .proc
|
|
||||||
; ---- pops mflpt5 from stack to memory A/Y
|
|
||||||
; (frees 3 stack positions = 6 bytes of which 1 is padding)
|
|
||||||
sta c64.SCRATCH_ZPWORD1
|
|
||||||
sty c64.SCRATCH_ZPWORD1+1
|
|
||||||
ldy #4
|
|
||||||
inx
|
|
||||||
lda c64.ESTACK_LO,x
|
|
||||||
sta (c64.SCRATCH_ZPWORD1),y
|
|
||||||
dey
|
|
||||||
inx
|
|
||||||
lda c64.ESTACK_HI,x
|
|
||||||
sta (c64.SCRATCH_ZPWORD1),y
|
|
||||||
dey
|
|
||||||
lda c64.ESTACK_LO,x
|
|
||||||
sta (c64.SCRATCH_ZPWORD1),y
|
|
||||||
dey
|
|
||||||
inx
|
|
||||||
lda c64.ESTACK_HI,x
|
|
||||||
sta (c64.SCRATCH_ZPWORD1),y
|
|
||||||
dey
|
|
||||||
lda c64.ESTACK_LO,x
|
|
||||||
sta (c64.SCRATCH_ZPWORD1),y
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
pop_float_fac1 .proc
|
|
||||||
; -- pops float from stack into FAC1
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr pop_float
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jmp MOVFM
|
|
||||||
.pend
|
|
||||||
|
|
||||||
pop_float_to_indexed_var .proc
|
|
||||||
; -- pop the float on the stack, to the memory in the array at A/Y indexed by the byte on stack
|
|
||||||
sta c64.SCRATCH_ZPWORD1
|
|
||||||
sty c64.SCRATCH_ZPWORD1+1
|
|
||||||
jsr prog8_lib.pop_index_times_5
|
|
||||||
jsr prog8_lib.add_a_to_zpword
|
|
||||||
lda c64.SCRATCH_ZPWORD1
|
|
||||||
ldy c64.SCRATCH_ZPWORD1+1
|
|
||||||
jmp pop_float
|
|
||||||
.pend
|
|
||||||
|
|
||||||
copy_float .proc
|
|
||||||
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
|
|
||||||
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
|
|
||||||
sta c64.SCRATCH_ZPWORD2
|
|
||||||
sty c64.SCRATCH_ZPWORD2+1
|
|
||||||
ldy #0
|
|
||||||
lda (c64.SCRATCH_ZPWORD1),y
|
|
||||||
sta (c64.SCRATCH_ZPWORD2),y
|
|
||||||
iny
|
|
||||||
lda (c64.SCRATCH_ZPWORD1),y
|
|
||||||
sta (c64.SCRATCH_ZPWORD2),y
|
|
||||||
iny
|
|
||||||
lda (c64.SCRATCH_ZPWORD1),y
|
|
||||||
sta (c64.SCRATCH_ZPWORD2),y
|
|
||||||
iny
|
|
||||||
lda (c64.SCRATCH_ZPWORD1),y
|
|
||||||
sta (c64.SCRATCH_ZPWORD2),y
|
|
||||||
iny
|
|
||||||
lda (c64.SCRATCH_ZPWORD1),y
|
|
||||||
sta (c64.SCRATCH_ZPWORD2),y
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
inc_var_f .proc
|
|
||||||
; -- add 1 to float pointed to by A/Y
|
|
||||||
sta c64.SCRATCH_ZPWORD1
|
|
||||||
sty c64.SCRATCH_ZPWORD1+1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr MOVFM
|
|
||||||
lda #<FL_FONE
|
|
||||||
ldy #>FL_FONE
|
|
||||||
jsr FADD
|
|
||||||
ldx c64.SCRATCH_ZPWORD1
|
|
||||||
ldy c64.SCRATCH_ZPWORD1+1
|
|
||||||
jsr MOVMF
|
|
||||||
ldx c64.SCRATCH_ZPREGX
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
dec_var_f .proc
|
|
||||||
; -- subtract 1 from float pointed to by A/Y
|
|
||||||
sta c64.SCRATCH_ZPWORD1
|
|
||||||
sty c64.SCRATCH_ZPWORD1+1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
lda #<FL_FONE
|
|
||||||
ldy #>FL_FONE
|
|
||||||
jsr MOVFM
|
|
||||||
lda c64.SCRATCH_ZPWORD1
|
|
||||||
ldy c64.SCRATCH_ZPWORD1+1
|
|
||||||
jsr FSUB
|
|
||||||
ldx c64.SCRATCH_ZPWORD1
|
|
||||||
ldy c64.SCRATCH_ZPWORD1+1
|
|
||||||
jsr MOVMF
|
|
||||||
ldx c64.SCRATCH_ZPREGX
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
inc_indexed_var_f .proc
|
|
||||||
; -- add 1 to float in array pointed to by A/Y, at index X
|
|
||||||
pha
|
|
||||||
txa
|
|
||||||
sta c64.SCRATCH_ZPB1
|
|
||||||
asl a
|
|
||||||
asl a
|
|
||||||
clc
|
|
||||||
adc c64.SCRATCH_ZPB1
|
|
||||||
sta c64.SCRATCH_ZPB1
|
|
||||||
pla
|
|
||||||
clc
|
|
||||||
adc c64.SCRATCH_ZPB1
|
|
||||||
bcc +
|
|
||||||
iny
|
|
||||||
+ jmp inc_var_f
|
|
||||||
.pend
|
|
||||||
|
|
||||||
dec_indexed_var_f .proc
|
|
||||||
; -- subtract 1 to float in array pointed to by A/Y, at index X
|
|
||||||
pha
|
|
||||||
txa
|
|
||||||
sta c64.SCRATCH_ZPB1
|
|
||||||
asl a
|
|
||||||
asl a
|
|
||||||
clc
|
|
||||||
adc c64.SCRATCH_ZPB1
|
|
||||||
sta c64.SCRATCH_ZPB1
|
|
||||||
pla
|
|
||||||
clc
|
|
||||||
adc c64.SCRATCH_ZPB1
|
|
||||||
bcc +
|
|
||||||
iny
|
|
||||||
+ jmp dec_var_f
|
|
||||||
.pend
|
|
||||||
|
|
||||||
|
|
||||||
pop_2_floats_f2_in_fac1 .proc
|
|
||||||
; -- pop 2 floats from stack, load the second one in FAC1 as well
|
|
||||||
lda #<fmath_float2
|
|
||||||
ldy #>fmath_float2
|
|
||||||
jsr pop_float
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr pop_float
|
|
||||||
lda #<fmath_float2
|
|
||||||
ldy #>fmath_float2
|
|
||||||
jmp MOVFM
|
|
||||||
.pend
|
|
||||||
|
|
||||||
|
|
||||||
fmath_float1 .byte 0,0,0,0,0 ; storage for a mflpt5 value
|
|
||||||
fmath_float2 .byte 0,0,0,0,0 ; storage for a mflpt5 value
|
|
||||||
|
|
||||||
push_fac1_as_result .proc
|
|
||||||
; -- push the float in FAC1 onto the stack, and return from calculation
|
|
||||||
ldx #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr MOVMF
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
ldx c64.SCRATCH_ZPREGX
|
|
||||||
jmp push_float
|
|
||||||
.pend
|
|
||||||
|
|
||||||
pow_f .proc
|
|
||||||
; -- push f1 ** f2 on stack
|
|
||||||
lda #<fmath_float2
|
|
||||||
ldy #>fmath_float2
|
|
||||||
jsr pop_float
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr pop_float
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr CONUPK ; fac2 = float1
|
|
||||||
lda #<fmath_float2
|
|
||||||
ldy #>fmath_float2
|
|
||||||
jsr FPWR
|
|
||||||
ldx c64.SCRATCH_ZPREGX
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
div_f .proc
|
|
||||||
; -- push f1/f2 on stack
|
|
||||||
jsr pop_2_floats_f2_in_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr FDIV
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
add_f .proc
|
|
||||||
; -- push f1+f2 on stack
|
|
||||||
jsr pop_2_floats_f2_in_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr FADD
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
sub_f .proc
|
|
||||||
; -- push f1-f2 on stack
|
|
||||||
jsr pop_2_floats_f2_in_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr FSUB
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
mul_f .proc
|
|
||||||
; -- push f1*f2 on stack
|
|
||||||
jsr pop_2_floats_f2_in_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr FMULT
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
neg_f .proc
|
|
||||||
; -- push -flt back on stack
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr NEGOP
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
abs_f .proc
|
|
||||||
; -- push abs(float) on stack (as float)
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr ABS
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
equal_f .proc
|
|
||||||
; -- are the two mflpt5 numbers on the stack identical?
|
|
||||||
inx
|
|
||||||
inx
|
|
||||||
inx
|
|
||||||
inx
|
|
||||||
lda c64.ESTACK_LO-3,x
|
|
||||||
cmp c64.ESTACK_LO,x
|
|
||||||
bne _equals_false
|
|
||||||
lda c64.ESTACK_LO-2,x
|
|
||||||
cmp c64.ESTACK_LO+1,x
|
|
||||||
bne _equals_false
|
|
||||||
lda c64.ESTACK_LO-1,x
|
|
||||||
cmp c64.ESTACK_LO+2,x
|
|
||||||
bne _equals_false
|
|
||||||
lda c64.ESTACK_HI-2,x
|
|
||||||
cmp c64.ESTACK_HI+1,x
|
|
||||||
bne _equals_false
|
|
||||||
lda c64.ESTACK_HI-1,x
|
|
||||||
cmp c64.ESTACK_HI+2,x
|
|
||||||
bne _equals_false
|
|
||||||
_equals_true lda #1
|
|
||||||
_equals_store inx
|
|
||||||
sta c64.ESTACK_LO+1,x
|
|
||||||
rts
|
|
||||||
_equals_false lda #0
|
|
||||||
beq _equals_store
|
|
||||||
.pend
|
|
||||||
|
|
||||||
notequal_f .proc
|
|
||||||
; -- are the two mflpt5 numbers on the stack different?
|
|
||||||
jsr equal_f
|
|
||||||
eor #1 ; invert the result
|
|
||||||
sta c64.ESTACK_LO+1,x
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
less_f .proc
|
|
||||||
; -- is f1 < f2?
|
|
||||||
jsr compare_floats
|
|
||||||
cmp #255
|
|
||||||
beq compare_floats._return_true
|
|
||||||
bne compare_floats._return_false
|
|
||||||
.pend
|
|
||||||
|
|
||||||
|
|
||||||
lesseq_f .proc
|
|
||||||
; -- is f1 <= f2?
|
|
||||||
jsr compare_floats
|
|
||||||
cmp #255
|
|
||||||
beq compare_floats._return_true
|
|
||||||
cmp #0
|
|
||||||
beq compare_floats._return_true
|
|
||||||
bne compare_floats._return_false
|
|
||||||
.pend
|
|
||||||
|
|
||||||
greater_f .proc
|
|
||||||
; -- is f1 > f2?
|
|
||||||
jsr compare_floats
|
|
||||||
cmp #1
|
|
||||||
beq compare_floats._return_true
|
|
||||||
bne compare_floats._return_false
|
|
||||||
.pend
|
|
||||||
|
|
||||||
greatereq_f .proc
|
|
||||||
; -- is f1 >= f2?
|
|
||||||
jsr compare_floats
|
|
||||||
cmp #1
|
|
||||||
beq compare_floats._return_true
|
|
||||||
cmp #0
|
|
||||||
beq compare_floats._return_true
|
|
||||||
bne compare_floats._return_false
|
|
||||||
.pend
|
|
||||||
|
|
||||||
compare_floats .proc
|
|
||||||
lda #<fmath_float2
|
|
||||||
ldy #>fmath_float2
|
|
||||||
jsr pop_float
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr pop_float
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr MOVFM ; fac1 = flt1
|
|
||||||
lda #<fmath_float2
|
|
||||||
ldy #>fmath_float2
|
|
||||||
stx c64.SCRATCH_ZPREG
|
|
||||||
jsr FCOMP ; A = flt1 compared with flt2 (0=equal, 1=flt1>flt2, 255=flt1<flt2)
|
|
||||||
ldx c64.SCRATCH_ZPREG
|
|
||||||
rts
|
|
||||||
_return_false lda #0
|
|
||||||
_return_result sta c64.ESTACK_LO,x
|
|
||||||
dex
|
|
||||||
rts
|
|
||||||
_return_true lda #1
|
|
||||||
bne _return_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_sin .proc
|
|
||||||
; -- push sin(f) back onto stack
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr SIN
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_cos .proc
|
|
||||||
; -- push cos(f) back onto stack
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr COS
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_tan .proc
|
|
||||||
; -- push tan(f) back onto stack
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr TAN
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_atan .proc
|
|
||||||
; -- push atan(f) back onto stack
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr ATN
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_ln .proc
|
|
||||||
; -- push ln(f) back onto stack
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr LOG
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_log2 .proc
|
|
||||||
; -- push log base 2, ln(f)/ln(2), back onto stack
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr LOG
|
|
||||||
jsr MOVEF
|
|
||||||
lda #<c64.FL_LOG2
|
|
||||||
ldy #>c64.FL_LOG2
|
|
||||||
jsr MOVFM
|
|
||||||
jsr FDIVT
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_sqrt .proc
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr SQR
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_rad .proc
|
|
||||||
; -- convert degrees to radians (d * pi / 180)
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
lda #<_pi_div_180
|
|
||||||
ldy #>_pi_div_180
|
|
||||||
jsr FMULT
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
_pi_div_180 .byte 123, 14, 250, 53, 18 ; pi / 180
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_deg .proc
|
|
||||||
; -- convert radians to degrees (d * (1/ pi * 180))
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
lda #<_one_over_pi_div_180
|
|
||||||
ldy #>_one_over_pi_div_180
|
|
||||||
jsr FMULT
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
_one_over_pi_div_180 .byte 134, 101, 46, 224, 211 ; 1 / (pi * 180)
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_round .proc
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr FADDH
|
|
||||||
jsr INT
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_floor .proc
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr INT
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_ceil .proc
|
|
||||||
; -- ceil: tr = int(f); if tr==f -> return else return tr+1
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
ldx #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr MOVMF
|
|
||||||
jsr INT
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr FCOMP
|
|
||||||
cmp #0
|
|
||||||
beq +
|
|
||||||
lda #<FL_FONE
|
|
||||||
ldy #>FL_FONE
|
|
||||||
jsr FADD
|
|
||||||
+ jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_any_f .proc
|
|
||||||
inx
|
|
||||||
lda c64.ESTACK_LO,x ; array size
|
|
||||||
sta c64.SCRATCH_ZPB1
|
|
||||||
asl a
|
|
||||||
asl a
|
|
||||||
clc
|
|
||||||
adc c64.SCRATCH_ZPB1 ; times 5 because of float
|
|
||||||
jmp prog8_lib.func_any_b._entry
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_all_f .proc
|
|
||||||
inx
|
|
||||||
jsr prog8_lib.peek_address
|
|
||||||
lda c64.ESTACK_LO,x ; array size
|
|
||||||
sta c64.SCRATCH_ZPB1
|
|
||||||
asl a
|
|
||||||
asl a
|
|
||||||
clc
|
|
||||||
adc c64.SCRATCH_ZPB1 ; times 5 because of float
|
|
||||||
tay
|
|
||||||
dey
|
|
||||||
- lda (c64.SCRATCH_ZPWORD1),y
|
|
||||||
clc
|
|
||||||
dey
|
|
||||||
adc (c64.SCRATCH_ZPWORD1),y
|
|
||||||
dey
|
|
||||||
adc (c64.SCRATCH_ZPWORD1),y
|
|
||||||
dey
|
|
||||||
adc (c64.SCRATCH_ZPWORD1),y
|
|
||||||
dey
|
|
||||||
adc (c64.SCRATCH_ZPWORD1),y
|
|
||||||
dey
|
|
||||||
cmp #0
|
|
||||||
beq +
|
|
||||||
cpy #255
|
|
||||||
bne -
|
|
||||||
lda #1
|
|
||||||
sta c64.ESTACK_LO+1,x
|
|
||||||
rts
|
|
||||||
+ sta c64.ESTACK_LO+1,x
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_max_f .proc
|
|
||||||
lda #255
|
|
||||||
sta _minmax_cmp+1
|
|
||||||
lda #<_largest_neg_float
|
|
||||||
ldy #>_largest_neg_float
|
|
||||||
_minmax_entry jsr MOVFM
|
|
||||||
jsr prog8_lib.pop_array_and_lengthmin1Y
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
- sty c64.SCRATCH_ZPREG
|
|
||||||
lda c64.SCRATCH_ZPWORD1
|
|
||||||
ldy c64.SCRATCH_ZPWORD1+1
|
|
||||||
jsr FCOMP
|
|
||||||
_minmax_cmp cmp #255 ; modified
|
|
||||||
bne +
|
|
||||||
lda c64.SCRATCH_ZPWORD1
|
|
||||||
ldy c64.SCRATCH_ZPWORD1+1
|
|
||||||
jsr MOVFM
|
|
||||||
+ lda c64.SCRATCH_ZPWORD1
|
|
||||||
clc
|
|
||||||
adc #5
|
|
||||||
sta c64.SCRATCH_ZPWORD1
|
|
||||||
bcc +
|
|
||||||
inc c64.SCRATCH_ZPWORD1+1
|
|
||||||
+ ldy c64.SCRATCH_ZPREG
|
|
||||||
dey
|
|
||||||
cpy #255
|
|
||||||
bne -
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
_largest_neg_float .byte 255,255,255,255,255 ; largest negative float -1.7014118345e+38
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_min_f .proc
|
|
||||||
lda #1
|
|
||||||
sta func_max_f._minmax_cmp+1
|
|
||||||
lda #<_largest_pos_float
|
|
||||||
ldy #>_largest_pos_float
|
|
||||||
jmp func_max_f._minmax_entry
|
|
||||||
_largest_pos_float .byte 255,127,255,255,255 ; largest positive float
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_sum_f .proc
|
|
||||||
lda #<FL_ZERO
|
|
||||||
ldy #>FL_ZERO
|
|
||||||
jsr MOVFM
|
|
||||||
jsr prog8_lib.pop_array_and_lengthmin1Y
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
- sty c64.SCRATCH_ZPREG
|
|
||||||
lda c64.SCRATCH_ZPWORD1
|
|
||||||
ldy c64.SCRATCH_ZPWORD1+1
|
|
||||||
jsr FADD
|
|
||||||
ldy c64.SCRATCH_ZPREG
|
|
||||||
dey
|
|
||||||
cpy #255
|
|
||||||
beq +
|
|
||||||
lda c64.SCRATCH_ZPWORD1
|
|
||||||
clc
|
|
||||||
adc #5
|
|
||||||
sta c64.SCRATCH_ZPWORD1
|
|
||||||
bcc -
|
|
||||||
inc c64.SCRATCH_ZPWORD1+1
|
|
||||||
bne -
|
|
||||||
+ jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
sign_f .proc
|
|
||||||
jsr pop_float_fac1
|
|
||||||
jsr SIGN
|
|
||||||
sta c64.ESTACK_LO,x
|
|
||||||
dex
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
|
|
||||||
}}
|
|
||||||
|
|
||||||
} ; ------ end of block c64flt
|
|
@ -1,243 +0,0 @@
|
|||||||
; Prog8 definitions for the Commodore-64
|
|
||||||
; Including memory registers, I/O registers, Basic and Kernal subroutines.
|
|
||||||
;
|
|
||||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
|
||||||
;
|
|
||||||
; indent format: TABS, size=8
|
|
||||||
|
|
||||||
|
|
||||||
c64 {
|
|
||||||
const uword ESTACK_LO = $ce00 ; evaluation stack (lsb)
|
|
||||||
const uword ESTACK_HI = $cf00 ; evaluation stack (msb)
|
|
||||||
&ubyte SCRATCH_ZPB1 = $02 ; scratch byte 1 in ZP
|
|
||||||
&ubyte SCRATCH_ZPREG = $03 ; scratch register in ZP
|
|
||||||
&ubyte SCRATCH_ZPREGX = $fa ; temp storage for X register (stack pointer)
|
|
||||||
&uword SCRATCH_ZPWORD1 = $fb ; scratch word in ZP ($fb/$fc)
|
|
||||||
&uword SCRATCH_ZPWORD2 = $fd ; scratch word in ZP ($fd/$fe)
|
|
||||||
|
|
||||||
|
|
||||||
&ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
|
|
||||||
&ubyte TIME_MID = $a1 ; .. mid byte
|
|
||||||
&ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
|
|
||||||
&ubyte STKEY = $91 ; various keyboard statuses (updated by IRQ)
|
|
||||||
&ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
|
|
||||||
|
|
||||||
&ubyte COLOR = $0286 ; cursor color
|
|
||||||
&ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address)
|
|
||||||
&uword CINV = $0314 ; IRQ vector
|
|
||||||
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
|
|
||||||
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
|
|
||||||
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
|
|
||||||
|
|
||||||
; the default addresses for the character screen chars and colors
|
|
||||||
const uword Screen = $0400 ; to have this as an array[40*25] the compiler would have to support array size > 255
|
|
||||||
const uword Colors = $d800 ; to have this as an array[40*25] the compiler would have to support array size > 255
|
|
||||||
|
|
||||||
; the default locations of the 8 sprite pointers (store address of sprite / 64)
|
|
||||||
&ubyte SPRPTR0 = 2040
|
|
||||||
&ubyte SPRPTR1 = 2041
|
|
||||||
&ubyte SPRPTR2 = 2042
|
|
||||||
&ubyte SPRPTR3 = 2043
|
|
||||||
&ubyte SPRPTR4 = 2044
|
|
||||||
&ubyte SPRPTR5 = 2045
|
|
||||||
&ubyte SPRPTR6 = 2046
|
|
||||||
&ubyte SPRPTR7 = 2047
|
|
||||||
&ubyte[8] SPRPTR = 2040 ; the 8 sprite pointers as an array.
|
|
||||||
|
|
||||||
|
|
||||||
; ---- VIC-II 6567/6569/856x registers ----
|
|
||||||
|
|
||||||
&ubyte SP0X = $d000
|
|
||||||
&ubyte SP0Y = $d001
|
|
||||||
&ubyte SP1X = $d002
|
|
||||||
&ubyte SP1Y = $d003
|
|
||||||
&ubyte SP2X = $d004
|
|
||||||
&ubyte SP2Y = $d005
|
|
||||||
&ubyte SP3X = $d006
|
|
||||||
&ubyte SP3Y = $d007
|
|
||||||
&ubyte SP4X = $d008
|
|
||||||
&ubyte SP4Y = $d009
|
|
||||||
&ubyte SP5X = $d00a
|
|
||||||
&ubyte SP5Y = $d00b
|
|
||||||
&ubyte SP6X = $d00c
|
|
||||||
&ubyte SP6Y = $d00d
|
|
||||||
&ubyte SP7X = $d00e
|
|
||||||
&ubyte SP7Y = $d00f
|
|
||||||
&ubyte[16] SPXY = $d000 ; the 8 sprite X and Y registers as an array.
|
|
||||||
&uword[8] SPXYW = $d000 ; the 8 sprite X and Y registers as a combined xy word array.
|
|
||||||
|
|
||||||
&ubyte MSIGX = $d010
|
|
||||||
&ubyte SCROLY = $d011
|
|
||||||
&ubyte RASTER = $d012
|
|
||||||
&ubyte LPENX = $d013
|
|
||||||
&ubyte LPENY = $d014
|
|
||||||
&ubyte SPENA = $d015
|
|
||||||
&ubyte SCROLX = $d016
|
|
||||||
&ubyte YXPAND = $d017
|
|
||||||
&ubyte VMCSB = $d018
|
|
||||||
&ubyte VICIRQ = $d019
|
|
||||||
&ubyte IREQMASK = $d01a
|
|
||||||
&ubyte SPBGPR = $d01b
|
|
||||||
&ubyte SPMC = $d01c
|
|
||||||
&ubyte XXPAND = $d01d
|
|
||||||
&ubyte SPSPCL = $d01e
|
|
||||||
&ubyte SPBGCL = $d01f
|
|
||||||
|
|
||||||
&ubyte EXTCOL = $d020 ; border color
|
|
||||||
&ubyte BGCOL0 = $d021 ; screen color
|
|
||||||
&ubyte BGCOL1 = $d022
|
|
||||||
&ubyte BGCOL2 = $d023
|
|
||||||
&ubyte BGCOL4 = $d024
|
|
||||||
&ubyte SPMC0 = $d025
|
|
||||||
&ubyte SPMC1 = $d026
|
|
||||||
&ubyte SP0COL = $d027
|
|
||||||
&ubyte SP1COL = $d028
|
|
||||||
&ubyte SP2COL = $d029
|
|
||||||
&ubyte SP3COL = $d02a
|
|
||||||
&ubyte SP4COL = $d02b
|
|
||||||
&ubyte SP5COL = $d02c
|
|
||||||
&ubyte SP6COL = $d02d
|
|
||||||
&ubyte SP7COL = $d02e
|
|
||||||
&ubyte[8] SPCOL = $d027
|
|
||||||
|
|
||||||
|
|
||||||
; ---- end of VIC-II registers ----
|
|
||||||
|
|
||||||
; ---- CIA 6526 1 & 2 registers ----
|
|
||||||
|
|
||||||
&ubyte CIA1PRA = $DC00 ; CIA 1 DRA, keyboard column drive (and joystick control port #2)
|
|
||||||
&ubyte CIA1PRB = $DC01 ; CIA 1 DRB, keyboard row port (and joystick control port #1)
|
|
||||||
&ubyte CIA1DDRA = $DC02 ; CIA 1 DDRA, keyboard column
|
|
||||||
&ubyte CIA1DDRB = $DC03 ; CIA 1 DDRB, keyboard row
|
|
||||||
&ubyte CIA1TAL = $DC04 ; CIA 1 timer A low byte
|
|
||||||
&ubyte CIA1TAH = $DC05 ; CIA 1 timer A high byte
|
|
||||||
&ubyte CIA1TBL = $DC06 ; CIA 1 timer B low byte
|
|
||||||
&ubyte CIA1TBH = $DC07 ; CIA 1 timer B high byte
|
|
||||||
&ubyte CIA1TOD10 = $DC08 ; time of day, 1/10 sec.
|
|
||||||
&ubyte CIA1TODSEC = $DC09 ; time of day, seconds
|
|
||||||
&ubyte CIA1TODMMIN = $DC0A ; time of day, minutes
|
|
||||||
&ubyte CIA1TODHR = $DC0B ; time of day, hours
|
|
||||||
&ubyte CIA1SDR = $DC0C ; Serial Data Register
|
|
||||||
&ubyte CIA1ICR = $DC0D
|
|
||||||
&ubyte CIA1CRA = $DC0E
|
|
||||||
&ubyte CIA1CRB = $DC0F
|
|
||||||
|
|
||||||
&ubyte CIA2PRA = $DD00 ; CIA 2 DRA, serial port and video address
|
|
||||||
&ubyte CIA2PRB = $DD01 ; CIA 2 DRB, RS232 port / USERPORT
|
|
||||||
&ubyte CIA2DDRA = $DD02 ; CIA 2 DDRA, serial port and video address
|
|
||||||
&ubyte CIA2DDRB = $DD03 ; CIA 2 DDRB, RS232 port / USERPORT
|
|
||||||
&ubyte CIA2TAL = $DD04 ; CIA 2 timer A low byte
|
|
||||||
&ubyte CIA2TAH = $DD05 ; CIA 2 timer A high byte
|
|
||||||
&ubyte CIA2TBL = $DD06 ; CIA 2 timer B low byte
|
|
||||||
&ubyte CIA2TBH = $DD07 ; CIA 2 timer B high byte
|
|
||||||
&ubyte CIA2TOD10 = $DD08 ; time of day, 1/10 sec.
|
|
||||||
&ubyte CIA2TODSEC = $DD09 ; time of day, seconds
|
|
||||||
&ubyte CIA2TODMIN = $DD0A ; time of day, minutes
|
|
||||||
&ubyte CIA2TODHR = $DD0B ; time of day, hours
|
|
||||||
&ubyte CIA2SDR = $DD0C ; Serial Data Register
|
|
||||||
&ubyte CIA2ICR = $DD0D
|
|
||||||
&ubyte CIA2CRA = $DD0E
|
|
||||||
&ubyte CIA2CRB = $DD0F
|
|
||||||
|
|
||||||
; ---- end of CIA registers ----
|
|
||||||
|
|
||||||
; ---- SID 6581/8580 registers ----
|
|
||||||
|
|
||||||
&ubyte FREQLO1 = $D400 ; channel 1 freq lo
|
|
||||||
&ubyte FREQHI1 = $D401 ; channel 1 freq hi
|
|
||||||
&uword FREQ1 = $D400 ; channel 1 freq (word)
|
|
||||||
&ubyte PWLO1 = $D402 ; channel 1 pulse width lo (7-0)
|
|
||||||
&ubyte PWHI1 = $D403 ; channel 1 pulse width hi (11-8)
|
|
||||||
&uword PW1 = $D402 ; channel 1 pulse width (word)
|
|
||||||
&ubyte CR1 = $D404 ; channel 1 voice control register
|
|
||||||
&ubyte AD1 = $D405 ; channel 1 attack & decay
|
|
||||||
&ubyte SR1 = $D406 ; channel 1 sustain & release
|
|
||||||
&ubyte FREQLO2 = $D407 ; channel 2 freq lo
|
|
||||||
&ubyte FREQHI2 = $D408 ; channel 2 freq hi
|
|
||||||
&uword FREQ2 = $D407 ; channel 2 freq (word)
|
|
||||||
&ubyte PWLO2 = $D409 ; channel 2 pulse width lo (7-0)
|
|
||||||
&ubyte PWHI2 = $D40A ; channel 2 pulse width hi (11-8)
|
|
||||||
&uword PW2 = $D409 ; channel 2 pulse width (word)
|
|
||||||
&ubyte CR2 = $D40B ; channel 2 voice control register
|
|
||||||
&ubyte AD2 = $D40C ; channel 2 attack & decay
|
|
||||||
&ubyte SR2 = $D40D ; channel 2 sustain & release
|
|
||||||
&ubyte FREQLO3 = $D40E ; channel 3 freq lo
|
|
||||||
&ubyte FREQHI3 = $D40F ; channel 3 freq hi
|
|
||||||
&uword FREQ3 = $D40E ; channel 3 freq (word)
|
|
||||||
&ubyte PWLO3 = $D410 ; channel 3 pulse width lo (7-0)
|
|
||||||
&ubyte PWHI3 = $D411 ; channel 3 pulse width hi (11-8)
|
|
||||||
&uword PW3 = $D410 ; channel 3 pulse width (word)
|
|
||||||
&ubyte CR3 = $D412 ; channel 3 voice control register
|
|
||||||
&ubyte AD3 = $D413 ; channel 3 attack & decay
|
|
||||||
&ubyte SR3 = $D414 ; channel 3 sustain & release
|
|
||||||
&ubyte FCLO = $D415 ; filter cutoff lo (2-0)
|
|
||||||
&ubyte FCHI = $D416 ; filter cutoff hi (10-3)
|
|
||||||
&uword FC = $D415 ; filter cutoff (word)
|
|
||||||
&ubyte RESFILT = $D417 ; filter resonance and routing
|
|
||||||
&ubyte MVOL = $D418 ; filter mode and main volume control
|
|
||||||
&ubyte POTX = $D419 ; potentiometer X
|
|
||||||
&ubyte POTY = $D41A ; potentiometer Y
|
|
||||||
&ubyte OSC3 = $D41B ; channel 3 oscillator value read
|
|
||||||
&ubyte ENV3 = $D41C ; channel 3 envelope value read
|
|
||||||
|
|
||||||
; ---- end of SID registers ----
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
; ---- C64 basic routines ----
|
|
||||||
|
|
||||||
asmsub CLEARSCR () clobbers(A,X,Y) = $E544 ; clear the screen
|
|
||||||
asmsub HOMECRSR () clobbers(A,X,Y) = $E566 ; cursor to top left of screen
|
|
||||||
|
|
||||||
|
|
||||||
; ---- end of C64 basic routines ----
|
|
||||||
|
|
||||||
|
|
||||||
; ---- C64 kernal routines ----
|
|
||||||
|
|
||||||
asmsub STROUT (uword strptr @ AY) clobbers(A, X, Y) = $AB1E ; print null-terminated string (use c64scr.print instead)
|
|
||||||
asmsub IRQDFRT () clobbers(A,X,Y) = $EA31 ; default IRQ routine
|
|
||||||
asmsub IRQDFEND () clobbers(A,X,Y) = $EA81 ; default IRQ end/cleanup
|
|
||||||
asmsub CINT () clobbers(A,X,Y) = $FF81 ; (alias: SCINIT) initialize screen editor and video chip
|
|
||||||
asmsub IOINIT () clobbers(A, X) = $FF84 ; initialize I/O devices (CIA, SID, IRQ)
|
|
||||||
asmsub RAMTAS () clobbers(A,X,Y) = $FF87 ; initialize RAM, tape buffer, screen
|
|
||||||
asmsub RESTOR () clobbers(A,X,Y) = $FF8A ; restore default I/O vectors
|
|
||||||
asmsub VECTOR (uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y) = $FF8D ; read/set I/O vector table
|
|
||||||
asmsub SETMSG (ubyte value @ A) = $FF90 ; set Kernal message control flag
|
|
||||||
asmsub SECOND (ubyte address @ A) clobbers(A) = $FF93 ; (alias: LSTNSA) send secondary address after LISTEN
|
|
||||||
asmsub TKSA (ubyte address @ A) clobbers(A) = $FF96 ; (alias: TALKSA) send secondary address after TALK
|
|
||||||
asmsub MEMTOP (uword address @ XY, ubyte dir @ Pc) -> uword @ XY = $FF99 ; read/set top of memory pointer
|
|
||||||
asmsub MEMBOT (uword address @ XY, ubyte dir @ Pc) -> uword @ XY = $FF9C ; read/set bottom of memory pointer
|
|
||||||
asmsub SCNKEY () clobbers(A,X,Y) = $FF9F ; scan the keyboard
|
|
||||||
asmsub SETTMO (ubyte timeout @ A) = $FFA2 ; set time-out flag for IEEE bus
|
|
||||||
asmsub ACPTR () -> ubyte @ A = $FFA5 ; (alias: IECIN) input byte from serial bus
|
|
||||||
asmsub CIOUT (ubyte databyte @ A) = $FFA8 ; (alias: IECOUT) output byte to serial bus
|
|
||||||
asmsub UNTLK () clobbers(A) = $FFAB ; command serial bus device to UNTALK
|
|
||||||
asmsub UNLSN () clobbers(A) = $FFAE ; command serial bus device to UNLISTEN
|
|
||||||
asmsub LISTEN (ubyte device @ A) clobbers(A) = $FFB1 ; command serial bus device to LISTEN
|
|
||||||
asmsub TALK (ubyte device @ A) clobbers(A) = $FFB4 ; command serial bus device to TALK
|
|
||||||
asmsub READST () -> ubyte @ A = $FFB7 ; read I/O status word
|
|
||||||
asmsub SETLFS (ubyte logical @ A, ubyte device @ X, ubyte address @ Y) = $FFBA ; set logical file parameters
|
|
||||||
asmsub SETNAM (ubyte namelen @ A, str filename @ XY) = $FFBD ; set filename parameters
|
|
||||||
asmsub OPEN () clobbers(A,X,Y) = $FFC0 ; (via 794 ($31A)) open a logical file
|
|
||||||
asmsub CLOSE (ubyte logical @ A) clobbers(A,X,Y) = $FFC3 ; (via 796 ($31C)) close a logical file
|
|
||||||
asmsub CHKIN (ubyte logical @ X) clobbers(A,X) = $FFC6 ; (via 798 ($31E)) define an input channel
|
|
||||||
asmsub CHKOUT (ubyte logical @ X) clobbers(A,X) = $FFC9 ; (via 800 ($320)) define an output channel
|
|
||||||
asmsub CLRCHN () clobbers(A,X) = $FFCC ; (via 802 ($322)) restore default devices
|
|
||||||
asmsub CHRIN () clobbers(Y) -> ubyte @ A = $FFCF ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
|
|
||||||
asmsub CHROUT (ubyte char @ A) = $FFD2 ; (via 806 ($326)) output a character
|
|
||||||
asmsub LOAD (ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, ubyte @ X, ubyte @ Y = $FFD5 ; (via 816 ($330)) load from device
|
|
||||||
asmsub SAVE (ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A = $FFD8 ; (via 818 ($332)) save to a device
|
|
||||||
asmsub SETTIM (ubyte low @ A, ubyte middle @ X, ubyte high @ Y) = $FFDB ; set the software clock
|
|
||||||
asmsub RDTIM () -> ubyte @ A, ubyte @ X, ubyte @ Y = $FFDE ; read the software clock
|
|
||||||
asmsub STOP () clobbers(A,X) -> ubyte @ Pz, ubyte @ Pc = $FFE1 ; (via 808 ($328)) check the STOP key
|
|
||||||
asmsub GETIN () clobbers(X,Y) -> ubyte @ A = $FFE4 ; (via 810 ($32A)) get a character
|
|
||||||
asmsub CLALL () clobbers(A,X) = $FFE7 ; (via 812 ($32C)) close all files
|
|
||||||
asmsub UDTIM () clobbers(A,X) = $FFEA ; update the software clock
|
|
||||||
asmsub SCREEN () -> ubyte @ X, ubyte @ Y = $FFED ; read number of screen rows and columns
|
|
||||||
asmsub PLOT (ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y = $FFF0 ; read/set position of cursor on screen. Use c64scr.plot for a 'safe' wrapper that preserves X.
|
|
||||||
asmsub IOBASE () -> uword @ XY = $FFF3 ; read base address of I/O devices
|
|
||||||
|
|
||||||
; ---- end of C64 kernal routines ----
|
|
||||||
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
455
compiler/res/prog8lib/conv.p8
Normal file
455
compiler/res/prog8lib/conv.p8
Normal file
@ -0,0 +1,455 @@
|
|||||||
|
; Prog8 definitions for number conversions routines.
|
||||||
|
;
|
||||||
|
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||||
|
;
|
||||||
|
; indent format: TABS, size=8
|
||||||
|
|
||||||
|
|
||||||
|
conv {
|
||||||
|
|
||||||
|
; ----- number conversions to decimal strings
|
||||||
|
|
||||||
|
asmsub ubyte2decimal (ubyte value @ A) -> ubyte @ Y, ubyte @ A, ubyte @ X {
|
||||||
|
; ---- A to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
|
||||||
|
%asm {{
|
||||||
|
ldy #uword2decimal.ASCII_0_OFFSET
|
||||||
|
bne uword2decimal.hex_try200
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub uword2decimal (uword value @ AY) -> ubyte @Y, ubyte @A, ubyte @X {
|
||||||
|
; ---- convert 16 bit uword in A/Y to decimal
|
||||||
|
; output in uword2decimal.decTenThousands, decThousands, decHundreds, decTens, decOnes
|
||||||
|
; (these are terminated by a zero byte so they can be easily printed)
|
||||||
|
; also returns Y = 100's, A = 10's, X = 1's
|
||||||
|
|
||||||
|
%asm {{
|
||||||
|
|
||||||
|
;Convert 16 bit Hex to Decimal (0-65535) Rev 2
|
||||||
|
;By Omegamatrix Further optimizations by tepples
|
||||||
|
; routine from http://forums.nesdev.com/viewtopic.php?f=2&t=11341&start=15
|
||||||
|
|
||||||
|
;HexToDec99
|
||||||
|
; start in A
|
||||||
|
; end with A = 10's, decOnes (also in X)
|
||||||
|
|
||||||
|
;HexToDec255
|
||||||
|
; start in A
|
||||||
|
; end with Y = 100's, A = 10's, decOnes (also in X)
|
||||||
|
|
||||||
|
;HexToDec999
|
||||||
|
; start with A = high byte, Y = low byte
|
||||||
|
; end with Y = 100's, A = 10's, decOnes (also in X)
|
||||||
|
; requires 1 extra temp register on top of decOnes, could combine
|
||||||
|
; these two if HexToDec65535 was eliminated...
|
||||||
|
|
||||||
|
;HexToDec65535
|
||||||
|
; start with A/Y (low/high) as 16 bit value
|
||||||
|
; end with decTenThousand, decThousand, Y = 100's, A = 10's, decOnes (also in X)
|
||||||
|
; (irmen: I store Y and A in decHundreds and decTens too, so all of it can be easily printed)
|
||||||
|
|
||||||
|
|
||||||
|
ASCII_0_OFFSET = $30
|
||||||
|
temp = P8ZP_SCRATCH_B1 ; byte in zeropage
|
||||||
|
hexHigh = P8ZP_SCRATCH_W1 ; byte in zeropage
|
||||||
|
hexLow = P8ZP_SCRATCH_W1+1 ; byte in zeropage
|
||||||
|
|
||||||
|
|
||||||
|
HexToDec65535; SUBROUTINE
|
||||||
|
sty hexHigh ;3 @9
|
||||||
|
sta hexLow ;3 @12
|
||||||
|
tya
|
||||||
|
tax ;2 @14
|
||||||
|
lsr a ;2 @16
|
||||||
|
lsr a ;2 @18 integer divide 1024 (result 0-63)
|
||||||
|
|
||||||
|
cpx #$A7 ;2 @20 account for overflow of multiplying 24 from 43,000 ($A7F8) onward,
|
||||||
|
adc #1 ;2 @22 we can just round it to $A700, and the divide by 1024 is fine...
|
||||||
|
|
||||||
|
;at this point we have a number 1-65 that we have to times by 24,
|
||||||
|
;add to original sum, and Mod 1024 to get a remainder 0-999
|
||||||
|
|
||||||
|
|
||||||
|
sta temp ;3 @25
|
||||||
|
asl a ;2 @27
|
||||||
|
adc temp ;3 @30 x3
|
||||||
|
tay ;2 @32
|
||||||
|
lsr a ;2 @34
|
||||||
|
lsr a ;2 @36
|
||||||
|
lsr a ;2 @38
|
||||||
|
lsr a ;2 @40
|
||||||
|
lsr a ;2 @42
|
||||||
|
tax ;2 @44
|
||||||
|
tya ;2 @46
|
||||||
|
asl a ;2 @48
|
||||||
|
asl a ;2 @50
|
||||||
|
asl a ;2 @52
|
||||||
|
clc ;2 @54
|
||||||
|
adc hexLow ;3 @57
|
||||||
|
sta hexLow ;3 @60
|
||||||
|
txa ;2 @62
|
||||||
|
adc hexHigh ;3 @65
|
||||||
|
sta hexHigh ;3 @68
|
||||||
|
ror a ;2 @70
|
||||||
|
lsr a ;2 @72
|
||||||
|
tay ;2 @74 integer divide 1,000 (result 0-65)
|
||||||
|
|
||||||
|
lsr a ;2 @76 split the 1,000 and 10,000 digit
|
||||||
|
tax ;2 @78
|
||||||
|
lda ShiftedBcdTab,x ;4 @82
|
||||||
|
tax ;2 @84
|
||||||
|
rol a ;2 @86
|
||||||
|
and #$0F ;2 @88
|
||||||
|
ora #ASCII_0_OFFSET
|
||||||
|
sta decThousands ;3 @91
|
||||||
|
txa ;2 @93
|
||||||
|
lsr a ;2 @95
|
||||||
|
lsr a ;2 @97
|
||||||
|
lsr a ;2 @99
|
||||||
|
ora #ASCII_0_OFFSET
|
||||||
|
sta decTenThousands ;3 @102
|
||||||
|
|
||||||
|
lda hexLow ;3 @105
|
||||||
|
cpy temp ;3 @108
|
||||||
|
bmi _doSubtract ;2³ @110/111
|
||||||
|
beq _useZero ;2³ @112/113
|
||||||
|
adc #23 + 24 ;2 @114
|
||||||
|
_doSubtract
|
||||||
|
sbc #23 ;2 @116
|
||||||
|
sta hexLow ;3 @119
|
||||||
|
_useZero
|
||||||
|
lda hexHigh ;3 @122
|
||||||
|
sbc #0 ;2 @124
|
||||||
|
|
||||||
|
Start100s
|
||||||
|
and #$03 ;2 @126
|
||||||
|
tax ;2 @128 0,1,2,3
|
||||||
|
cmp #2 ;2 @130
|
||||||
|
rol a ;2 @132 0,2,5,7
|
||||||
|
ora #ASCII_0_OFFSET
|
||||||
|
tay ;2 @134 Y = Hundreds digit
|
||||||
|
|
||||||
|
lda hexLow ;3 @137
|
||||||
|
adc Mod100Tab,x ;4 @141 adding remainder of 256, 512, and 256+512 (all mod 100)
|
||||||
|
bcs hex_doSub200 ;2³ @143/144
|
||||||
|
|
||||||
|
hex_try200
|
||||||
|
cmp #200 ;2 @145
|
||||||
|
bcc hex_try100 ;2³ @147/148
|
||||||
|
hex_doSub200
|
||||||
|
iny ;2 @149
|
||||||
|
iny ;2 @151
|
||||||
|
sbc #200 ;2 @153
|
||||||
|
hex_try100
|
||||||
|
cmp #100 ;2 @155
|
||||||
|
bcc HexToDec99 ;2³ @157/158
|
||||||
|
iny ;2 @159
|
||||||
|
sbc #100 ;2 @161
|
||||||
|
|
||||||
|
HexToDec99; SUBROUTINE
|
||||||
|
lsr a ;2 @163
|
||||||
|
tax ;2 @165
|
||||||
|
lda ShiftedBcdTab,x ;4 @169
|
||||||
|
tax ;2 @171
|
||||||
|
rol a ;2 @173
|
||||||
|
and #$0F ;2 @175
|
||||||
|
ora #ASCII_0_OFFSET
|
||||||
|
sta decOnes ;3 @178
|
||||||
|
txa ;2 @180
|
||||||
|
lsr a ;2 @182
|
||||||
|
lsr a ;2 @184
|
||||||
|
lsr a ;2 @186
|
||||||
|
ora #ASCII_0_OFFSET
|
||||||
|
|
||||||
|
; irmen: load X with ones, and store Y and A too, for easy printing afterwards
|
||||||
|
sty decHundreds
|
||||||
|
sta decTens
|
||||||
|
ldx decOnes
|
||||||
|
rts ;6 @192 Y=hundreds, A = tens digit, X=ones digit
|
||||||
|
|
||||||
|
|
||||||
|
HexToDec999; SUBROUTINE
|
||||||
|
sty hexLow ;3 @9
|
||||||
|
jmp Start100s ;3 @12
|
||||||
|
|
||||||
|
Mod100Tab
|
||||||
|
.byte 0,56,12,56+12
|
||||||
|
|
||||||
|
ShiftedBcdTab
|
||||||
|
.byte $00,$01,$02,$03,$04,$08,$09,$0A,$0B,$0C
|
||||||
|
.byte $10,$11,$12,$13,$14,$18,$19,$1A,$1B,$1C
|
||||||
|
.byte $20,$21,$22,$23,$24,$28,$29,$2A,$2B,$2C
|
||||||
|
.byte $30,$31,$32,$33,$34,$38,$39,$3A,$3B,$3C
|
||||||
|
.byte $40,$41,$42,$43,$44,$48,$49,$4A,$4B,$4C
|
||||||
|
|
||||||
|
decTenThousands .byte 0
|
||||||
|
decThousands .byte 0
|
||||||
|
decHundreds .byte 0
|
||||||
|
decTens .byte 0
|
||||||
|
decOnes .byte 0
|
||||||
|
.byte 0 ; zero-terminate the decimal output string
|
||||||
|
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
; ----- utility functions ----
|
||||||
|
|
||||||
|
|
||||||
|
asmsub byte2decimal (byte value @ A) -> ubyte @ Y, ubyte @ A, ubyte @ X {
|
||||||
|
; ---- A (signed byte) to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
|
||||||
|
; note: if the number is negative, you have to deal with the '-' yourself!
|
||||||
|
%asm {{
|
||||||
|
cmp #0
|
||||||
|
bpl +
|
||||||
|
eor #255
|
||||||
|
clc
|
||||||
|
adc #1
|
||||||
|
+ jmp ubyte2decimal
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub ubyte2hex (ubyte value @ A) -> ubyte @ A, ubyte @ Y {
|
||||||
|
; ---- A to hex petscii string in AY (first hex char in A, second hex char in Y)
|
||||||
|
%asm {{
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
pha
|
||||||
|
and #$0f
|
||||||
|
tax
|
||||||
|
ldy _hex_digits,x
|
||||||
|
pla
|
||||||
|
lsr a
|
||||||
|
lsr a
|
||||||
|
lsr a
|
||||||
|
lsr a
|
||||||
|
tax
|
||||||
|
lda _hex_digits,x
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
|
||||||
|
_hex_digits .text "0123456789abcdef" ; can probably be reused for other stuff as well
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub uword2hex (uword value @ AY) clobbers(A,Y) {
|
||||||
|
; ---- convert 16 bit uword in A/Y into 4-character hexadecimal string 'uword2hex.output' (0-terminated)
|
||||||
|
%asm {{
|
||||||
|
sta P8ZP_SCRATCH_REG
|
||||||
|
tya
|
||||||
|
jsr ubyte2hex
|
||||||
|
sta output
|
||||||
|
sty output+1
|
||||||
|
lda P8ZP_SCRATCH_REG
|
||||||
|
jsr ubyte2hex
|
||||||
|
sta output+2
|
||||||
|
sty output+3
|
||||||
|
rts
|
||||||
|
output .text "0000", $00 ; 0-terminated output buffer (to make printing easier)
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
asmsub str2ubyte(str string @ AY) clobbers(Y) -> ubyte @A {
|
||||||
|
; -- returns the unsigned byte value of the string number argument in AY
|
||||||
|
; the number may NOT be preceded by a + sign and may NOT contain spaces
|
||||||
|
; (any non-digit character will terminate the number string that is parsed)
|
||||||
|
; TODO implement optimized custom version of this instead of simply reusing str2uword
|
||||||
|
%asm {{
|
||||||
|
jmp str2uword
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub str2byte(str string @ AY) clobbers(Y) -> ubyte @A {
|
||||||
|
; -- returns the signed byte value of the string number argument in AY
|
||||||
|
; the number may be preceded by a + or - sign but may NOT contain spaces
|
||||||
|
; (any non-digit character will terminate the number string that is parsed)
|
||||||
|
; TODO implement optimized custom version of this instead of simply reusing str2word
|
||||||
|
%asm {{
|
||||||
|
jmp str2word
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub str2uword(str string @ AY) -> uword @ AY {
|
||||||
|
; -- returns the unsigned word value of the string number argument in AY
|
||||||
|
; the number may NOT be preceded by a + sign and may NOT contain spaces
|
||||||
|
; (any non-digit character will terminate the number string that is parsed)
|
||||||
|
%asm {{
|
||||||
|
_result = P8ZP_SCRATCH_W2
|
||||||
|
sta _mod+1
|
||||||
|
sty _mod+2
|
||||||
|
ldy #0
|
||||||
|
sty _result
|
||||||
|
sty _result+1
|
||||||
|
_mod lda $ffff,y ; modified
|
||||||
|
sec
|
||||||
|
sbc #48
|
||||||
|
bpl +
|
||||||
|
_done ; return result
|
||||||
|
lda _result
|
||||||
|
ldy _result+1
|
||||||
|
rts
|
||||||
|
+ cmp #10
|
||||||
|
bcs _done
|
||||||
|
; add digit to result
|
||||||
|
pha
|
||||||
|
jsr _result_times_10
|
||||||
|
pla
|
||||||
|
clc
|
||||||
|
adc _result
|
||||||
|
sta _result
|
||||||
|
bcc +
|
||||||
|
inc _result+1
|
||||||
|
+ iny
|
||||||
|
bne _mod
|
||||||
|
; never reached
|
||||||
|
|
||||||
|
_result_times_10 ; (W*4 + W)*2
|
||||||
|
lda _result+1
|
||||||
|
sta P8ZP_SCRATCH_REG
|
||||||
|
lda _result
|
||||||
|
asl a
|
||||||
|
rol P8ZP_SCRATCH_REG
|
||||||
|
asl a
|
||||||
|
rol P8ZP_SCRATCH_REG
|
||||||
|
clc
|
||||||
|
adc _result
|
||||||
|
sta _result
|
||||||
|
lda P8ZP_SCRATCH_REG
|
||||||
|
adc _result+1
|
||||||
|
asl _result
|
||||||
|
rol a
|
||||||
|
sta _result+1
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub str2word(str string @ AY) -> word @ AY {
|
||||||
|
; -- returns the signed word value of the string number argument in AY
|
||||||
|
; the number may be preceded by a + or - sign but may NOT contain spaces
|
||||||
|
; (any non-digit character will terminate the number string that is parsed)
|
||||||
|
%asm {{
|
||||||
|
_result = P8ZP_SCRATCH_W2
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
ldy #0
|
||||||
|
sty _result
|
||||||
|
sty _result+1
|
||||||
|
sty _negative
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
cmp #'+'
|
||||||
|
bne +
|
||||||
|
iny
|
||||||
|
+ cmp #'-'
|
||||||
|
bne _parse
|
||||||
|
inc _negative
|
||||||
|
iny
|
||||||
|
_parse lda (P8ZP_SCRATCH_W1),y
|
||||||
|
sec
|
||||||
|
sbc #48
|
||||||
|
bpl _digit
|
||||||
|
_done ; return result
|
||||||
|
lda _negative
|
||||||
|
beq +
|
||||||
|
sec
|
||||||
|
lda #0
|
||||||
|
sbc _result
|
||||||
|
sta _result
|
||||||
|
lda #0
|
||||||
|
sbc _result+1
|
||||||
|
sta _result+1
|
||||||
|
+ lda _result
|
||||||
|
ldy _result+1
|
||||||
|
rts
|
||||||
|
_digit cmp #10
|
||||||
|
bcs _done
|
||||||
|
; add digit to result
|
||||||
|
pha
|
||||||
|
jsr str2uword._result_times_10
|
||||||
|
pla
|
||||||
|
clc
|
||||||
|
adc _result
|
||||||
|
sta _result
|
||||||
|
bcc +
|
||||||
|
inc _result+1
|
||||||
|
+ iny
|
||||||
|
bne _parse
|
||||||
|
; never reached
|
||||||
|
_negative .byte 0
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub hex2uword(str string @ AY) -> uword @AY {
|
||||||
|
; -- hexadecimal string with or without '$' to uword.
|
||||||
|
; string may be in petscii or c64-screencode encoding.
|
||||||
|
%asm {{
|
||||||
|
sta P8ZP_SCRATCH_W2
|
||||||
|
sty P8ZP_SCRATCH_W2+1
|
||||||
|
ldy #0
|
||||||
|
sty P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
_loop ldy #0
|
||||||
|
sty P8ZP_SCRATCH_B1
|
||||||
|
lda (P8ZP_SCRATCH_W2),y
|
||||||
|
beq _stop
|
||||||
|
cmp #'$'
|
||||||
|
beq _skip
|
||||||
|
cmp #7
|
||||||
|
bcc _add_nine
|
||||||
|
cmp #'9'
|
||||||
|
beq _calc
|
||||||
|
bcs _add_nine
|
||||||
|
_calc asl P8ZP_SCRATCH_W1
|
||||||
|
rol P8ZP_SCRATCH_W1+1
|
||||||
|
asl P8ZP_SCRATCH_W1
|
||||||
|
rol P8ZP_SCRATCH_W1+1
|
||||||
|
asl P8ZP_SCRATCH_W1
|
||||||
|
rol P8ZP_SCRATCH_W1+1
|
||||||
|
asl P8ZP_SCRATCH_W1
|
||||||
|
rol P8ZP_SCRATCH_W1+1
|
||||||
|
and #$0f
|
||||||
|
clc
|
||||||
|
adc P8ZP_SCRATCH_B1
|
||||||
|
ora P8ZP_SCRATCH_W1
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
_skip inc P8ZP_SCRATCH_W2
|
||||||
|
bne _loop
|
||||||
|
inc P8ZP_SCRATCH_W2+1
|
||||||
|
bne _loop
|
||||||
|
_stop lda P8ZP_SCRATCH_W1
|
||||||
|
ldy P8ZP_SCRATCH_W1+1
|
||||||
|
rts
|
||||||
|
_add_nine ldy #9
|
||||||
|
sty P8ZP_SCRATCH_B1
|
||||||
|
bne _calc
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub bin2uword(str string @ AY) -> uword @AY {
|
||||||
|
; -- binary string with or without '%' to uword.
|
||||||
|
%asm {{
|
||||||
|
sta P8ZP_SCRATCH_W2
|
||||||
|
sty P8ZP_SCRATCH_W2+1
|
||||||
|
ldy #0
|
||||||
|
sty P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
_loop lda (P8ZP_SCRATCH_W2),y
|
||||||
|
beq _stop
|
||||||
|
cmp #'%'
|
||||||
|
beq +
|
||||||
|
asl P8ZP_SCRATCH_W1
|
||||||
|
rol P8ZP_SCRATCH_W1+1
|
||||||
|
and #1
|
||||||
|
ora P8ZP_SCRATCH_W1
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
+ inc P8ZP_SCRATCH_W2
|
||||||
|
bne _loop
|
||||||
|
inc P8ZP_SCRATCH_W2+1
|
||||||
|
bne _loop
|
||||||
|
_stop lda P8ZP_SCRATCH_W1
|
||||||
|
ldy P8ZP_SCRATCH_W1+1
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
154
compiler/res/prog8lib/cx16/floats.p8
Normal file
154
compiler/res/prog8lib/cx16/floats.p8
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
; Prog8 definitions for floating point handling on the CommanderX16
|
||||||
|
;
|
||||||
|
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||||
|
;
|
||||||
|
; indent format: TABS, size=8
|
||||||
|
|
||||||
|
%target cx16
|
||||||
|
%option enable_floats
|
||||||
|
|
||||||
|
floats {
|
||||||
|
; ---- this block contains C-64 floating point related functions ----
|
||||||
|
|
||||||
|
const float PI = 3.141592653589793
|
||||||
|
const float TWOPI = 6.283185307179586
|
||||||
|
|
||||||
|
|
||||||
|
; ---- ROM float functions ----
|
||||||
|
|
||||||
|
; note: the fac1 and fac2 are working registers and take 6 bytes each,
|
||||||
|
; floats in memory (and rom) are stored in 5-byte MFLPT packed format.
|
||||||
|
|
||||||
|
; note: fac1/2 might get clobbered even if not mentioned in the function's name.
|
||||||
|
; note: for subtraction and division, the left operand is in fac2, the right operand in fac1.
|
||||||
|
|
||||||
|
romsub $fe00 = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 100-101 ($64-$65) MSB FIRST. (might throw ILLEGAL QUANTITY)
|
||||||
|
|
||||||
|
; GIVAYF: signed word in Y/A (note different lsb/msb order) -> float in fac1
|
||||||
|
; there is also floats.GIVUAYFAY - unsigned word in A/Y (lo/hi) to fac1
|
||||||
|
; (tip: use GIVAYFAY to use A/Y input; lo/hi switched to normal order)
|
||||||
|
romsub $fe03 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
|
||||||
|
|
||||||
|
; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
|
||||||
|
; (tip: use GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
|
||||||
|
romsub $fe06 = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
|
||||||
|
|
||||||
|
romsub $fe09 = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for rounding- call this before INT
|
||||||
|
romsub $fe0c = FSUB(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt from A/Y - fac1
|
||||||
|
romsub $fe0f = FSUBT() clobbers(A,X,Y) ; fac1 = fac2-fac1 mind the order of the operands
|
||||||
|
romsub $fe12 = FADD(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 += mflpt value from A/Y
|
||||||
|
romsub $fe15 = FADDT() clobbers(A,X,Y) ; fac1 += fac2
|
||||||
|
romsub $fe1b = ZEROFC() clobbers(A,X,Y) ; fac1 = 0
|
||||||
|
romsub $fe1e = NORMAL() clobbers(A,X,Y) ; normalize fac1 (?)
|
||||||
|
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 $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 $fe36 = MUL10() clobbers(A,X,Y) ; fac1 *= 10
|
||||||
|
romsub $fe3c = 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 $fe42 = 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 $fe4b = MOVMF(uword mflpt @ XY) clobbers(A,Y) ; store fac1 to memory X/Y as 5-byte mflpt
|
||||||
|
romsub $fe4e = MOVFA() clobbers(A,X) ; copy fac2 to fac1
|
||||||
|
romsub $fe51 = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded)
|
||||||
|
romsub $fe54 = 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 $fe5d = 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 $fe6c = ABS() ; 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 $fe78 = 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 $fe81 = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY
|
||||||
|
romsub $fe8a = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
|
||||||
|
romsub $fe8d = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
|
||||||
|
romsub $fe93 = NEGOP() clobbers(A) ; switch the sign of fac1
|
||||||
|
romsub $fe96 = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
|
||||||
|
romsub $fe9f = RND2(byte value @A) clobbers(A,X,Y) ; fac1 = RND(A) float random number generator
|
||||||
|
romsub $fea2 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
|
||||||
|
romsub $fea5 = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
|
||||||
|
romsub $fea8 = SIN() clobbers(A,X,Y) ; fac1 = SIN(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) {
|
||||||
|
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
sta P8ZP_SCRATCH_W2
|
||||||
|
sty P8ZP_SCRATCH_B1
|
||||||
|
tya
|
||||||
|
ldy P8ZP_SCRATCH_W2
|
||||||
|
jsr GIVAYF ; load it as signed... correct afterwards
|
||||||
|
lda P8ZP_SCRATCH_B1
|
||||||
|
bpl +
|
||||||
|
lda #<_flt65536
|
||||||
|
ldy #>_flt65536
|
||||||
|
jsr FADD
|
||||||
|
+ plx
|
||||||
|
rts
|
||||||
|
_flt65536 .byte 145,0,0,0,0 ; 65536.0
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
||||||
|
; ---- signed 16 bit word in A/Y (lo/hi) to float in fac1
|
||||||
|
%asm {{
|
||||||
|
sta P8ZP_SCRATCH_W2
|
||||||
|
tya
|
||||||
|
ldy P8ZP_SCRATCH_W2
|
||||||
|
jmp GIVAYF ; this uses the inverse order, Y/A
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub FTOSWRDAY () clobbers(X) -> uword @ AY {
|
||||||
|
; ---- fac1 to signed word in A/Y
|
||||||
|
%asm {{
|
||||||
|
jsr FTOSWORDYA ; note the inverse Y/A order
|
||||||
|
sta P8ZP_SCRATCH_REG
|
||||||
|
tya
|
||||||
|
ldy P8ZP_SCRATCH_REG
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub GETADRAY () clobbers(X) -> uword @ AY {
|
||||||
|
; ---- fac1 to unsigned word in A/Y
|
||||||
|
%asm {{
|
||||||
|
jsr GETADR ; this uses the inverse order, Y/A
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
tya
|
||||||
|
ldy P8ZP_SCRATCH_B1
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub print_f (float value) {
|
||||||
|
; ---- prints the floating point value (without a newline).
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
lda #<value
|
||||||
|
ldy #>value
|
||||||
|
jsr MOVFM ; load float into fac1
|
||||||
|
jsr FOUT ; fac1 to string in A/Y
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
ldy #0
|
||||||
|
- lda (P8ZP_SCRATCH_W1),y
|
||||||
|
beq +
|
||||||
|
jsr c64.CHROUT
|
||||||
|
iny
|
||||||
|
bne -
|
||||||
|
+ plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
%asminclude "library:c64/floats.asm", ""
|
||||||
|
%asminclude "library:c64/floats_funcs.asm", ""
|
||||||
|
|
||||||
|
}
|
156
compiler/res/prog8lib/cx16/graphics.p8
Normal file
156
compiler/res/prog8lib/cx16/graphics.p8
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
%target cx16
|
||||||
|
%import syslib
|
||||||
|
|
||||||
|
; bitmap pixel graphics module for the CommanderX16
|
||||||
|
; wraps the graphics functions that are in ROM.
|
||||||
|
; only black/white monchrome 320x200 for now.
|
||||||
|
|
||||||
|
graphics {
|
||||||
|
const uword WIDTH = 320
|
||||||
|
const ubyte HEIGHT = 200
|
||||||
|
|
||||||
|
sub enable_bitmap_mode() {
|
||||||
|
; enable bitmap screen, erase it and set colors to black/white.
|
||||||
|
void cx16.screen_set_mode($80)
|
||||||
|
cx16.r0 = 0
|
||||||
|
cx16.GRAPH_init()
|
||||||
|
clear_screen(1, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
sub clear_screen(ubyte pixelcolor, ubyte bgcolor) {
|
||||||
|
cx16.GRAPH_set_colors(pixelcolor, pixelcolor, bgcolor)
|
||||||
|
cx16.GRAPH_clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
|
||||||
|
cx16.r0 = x1
|
||||||
|
cx16.r1 = y1
|
||||||
|
cx16.r2 = x2
|
||||||
|
cx16.r3 = y2
|
||||||
|
cx16.GRAPH_draw_line()
|
||||||
|
}
|
||||||
|
|
||||||
|
sub circle(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||||
|
;cx16.r0 = xcenter - radius/2
|
||||||
|
;cx16.r1 = ycenter - radius/2
|
||||||
|
;cx16.r2 = radius*2
|
||||||
|
;cx16.r3 = radius*2
|
||||||
|
;cx16.GRAPH_draw_oval(false) ; TODO currently is not implemented on cx16, does a BRK
|
||||||
|
|
||||||
|
; Midpoint algorithm
|
||||||
|
ubyte @zp xx = radius
|
||||||
|
ubyte @zp yy = 0
|
||||||
|
byte @zp decisionOver2 = 1-xx as byte
|
||||||
|
|
||||||
|
while xx>=yy {
|
||||||
|
cx16.r0 = xcenter + xx
|
||||||
|
cx16.r1 = ycenter + yy
|
||||||
|
cx16.FB_cursor_position()
|
||||||
|
cx16.FB_set_pixel(1)
|
||||||
|
cx16.r0 = xcenter - xx
|
||||||
|
cx16.FB_cursor_position()
|
||||||
|
cx16.FB_set_pixel(1)
|
||||||
|
cx16.r0 = xcenter + xx
|
||||||
|
cx16.r1 = ycenter - yy
|
||||||
|
cx16.FB_cursor_position()
|
||||||
|
cx16.FB_set_pixel(1)
|
||||||
|
cx16.r0 = xcenter - xx
|
||||||
|
cx16.FB_cursor_position()
|
||||||
|
cx16.FB_set_pixel(1)
|
||||||
|
cx16.r0 = xcenter + yy
|
||||||
|
cx16.r1 = ycenter + xx
|
||||||
|
cx16.FB_cursor_position()
|
||||||
|
cx16.FB_set_pixel(1)
|
||||||
|
cx16.r0 = xcenter - yy
|
||||||
|
cx16.FB_cursor_position()
|
||||||
|
cx16.FB_set_pixel(1)
|
||||||
|
cx16.r0 = xcenter + yy
|
||||||
|
cx16.r1 = ycenter - xx
|
||||||
|
cx16.FB_cursor_position()
|
||||||
|
cx16.FB_set_pixel(1)
|
||||||
|
cx16.r0 = xcenter - yy
|
||||||
|
cx16.FB_cursor_position()
|
||||||
|
cx16.FB_set_pixel(1)
|
||||||
|
yy++
|
||||||
|
if decisionOver2<=0 {
|
||||||
|
decisionOver2 += 2*yy+1
|
||||||
|
} else {
|
||||||
|
xx--
|
||||||
|
decisionOver2 += 2*(yy-xx)+1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||||
|
; cx16.r0 = xcenter - radius/2
|
||||||
|
; cx16.r1 = ycenter - radius/2
|
||||||
|
; cx16.r2 = radius*2
|
||||||
|
; cx16.r3 = radius*2
|
||||||
|
; cx16.GRAPH_draw_oval(true) ; TODO currently is not implemented on cx16, does a BRK
|
||||||
|
|
||||||
|
ubyte xx = radius
|
||||||
|
ubyte yy = 0
|
||||||
|
byte decisionOver2 = 1-xx as byte
|
||||||
|
|
||||||
|
while xx>=yy {
|
||||||
|
ubyte ycenter_plus_yy = ycenter + yy
|
||||||
|
ubyte ycenter_min_yy = ycenter - yy
|
||||||
|
ubyte ycenter_plus_xx = ycenter + xx
|
||||||
|
ubyte ycenter_min_xx = ycenter - xx
|
||||||
|
uword @zp plotx
|
||||||
|
|
||||||
|
for plotx in xcenter to xcenter+xx {
|
||||||
|
cx16.r0 = plotx
|
||||||
|
cx16.r1 = ycenter_plus_yy
|
||||||
|
cx16.FB_cursor_position()
|
||||||
|
cx16.FB_set_pixel(1)
|
||||||
|
cx16.r1 = ycenter_min_yy
|
||||||
|
cx16.FB_cursor_position()
|
||||||
|
cx16.FB_set_pixel(1)
|
||||||
|
}
|
||||||
|
for plotx in xcenter-xx to xcenter-1 {
|
||||||
|
cx16.r0 = plotx
|
||||||
|
cx16.r1 = ycenter_plus_yy
|
||||||
|
cx16.FB_cursor_position()
|
||||||
|
cx16.FB_set_pixel(1)
|
||||||
|
cx16.r1 = ycenter_min_yy
|
||||||
|
cx16.FB_cursor_position()
|
||||||
|
cx16.FB_set_pixel(1)
|
||||||
|
}
|
||||||
|
for plotx in xcenter to xcenter+yy {
|
||||||
|
cx16.r0 = plotx
|
||||||
|
cx16.r1 = ycenter_plus_xx
|
||||||
|
cx16.FB_cursor_position()
|
||||||
|
cx16.FB_set_pixel(1)
|
||||||
|
cx16.r1 = ycenter_min_xx
|
||||||
|
cx16.FB_cursor_position()
|
||||||
|
cx16.FB_set_pixel(1)
|
||||||
|
}
|
||||||
|
for plotx in xcenter-yy to xcenter {
|
||||||
|
cx16.r0 = plotx
|
||||||
|
cx16.r1 = ycenter_plus_xx
|
||||||
|
cx16.FB_cursor_position()
|
||||||
|
cx16.FB_set_pixel(1)
|
||||||
|
cx16.r1 = ycenter_min_xx
|
||||||
|
cx16.FB_cursor_position()
|
||||||
|
cx16.FB_set_pixel(1)
|
||||||
|
}
|
||||||
|
yy++
|
||||||
|
if decisionOver2<=0
|
||||||
|
decisionOver2 += 2*yy+1
|
||||||
|
else {
|
||||||
|
xx--
|
||||||
|
decisionOver2 += 2*(yy-xx)+1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub plot(uword plotx, ubyte ploty) {
|
||||||
|
cx16.r0 = plotx
|
||||||
|
cx16.r1 = ploty
|
||||||
|
cx16.FB_cursor_position()
|
||||||
|
cx16.FB_set_pixel(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
291
compiler/res/prog8lib/cx16/syslib.p8
Normal file
291
compiler/res/prog8lib/cx16/syslib.p8
Normal file
@ -0,0 +1,291 @@
|
|||||||
|
; Prog8 definitions for the CommanderX16
|
||||||
|
; Including memory registers, I/O registers, Basic and Kernal subroutines.
|
||||||
|
;
|
||||||
|
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||||
|
;
|
||||||
|
; indent format: TABS, size=8
|
||||||
|
|
||||||
|
%target cx16
|
||||||
|
|
||||||
|
|
||||||
|
c64 {
|
||||||
|
|
||||||
|
; ---- kernal routines, these are the same as on the Commodore-64 (hence the same block name) ----
|
||||||
|
|
||||||
|
; STROUT --> use txt.print
|
||||||
|
; CLEARSCR -> use txt.clear_screen
|
||||||
|
; HOMECRSR -> use txt.plot
|
||||||
|
|
||||||
|
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
|
||||||
|
romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ)
|
||||||
|
romsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, tape buffer, screen
|
||||||
|
romsub $FF8A = RESTOR() clobbers(A,X,Y) ; restore default I/O vectors
|
||||||
|
romsub $FF8D = VECTOR(uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y) ; read/set I/O vector table
|
||||||
|
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 $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
|
||||||
|
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 $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
|
||||||
|
romsub $FFA5 = ACPTR() -> ubyte @ A ; (alias: IECIN) input byte from serial bus
|
||||||
|
romsub $FFA8 = CIOUT(ubyte databyte @ A) ; (alias: IECOUT) output byte to serial bus
|
||||||
|
romsub $FFAB = UNTLK() clobbers(A) ; command serial bus device to UNTALK
|
||||||
|
romsub $FFAE = UNLSN() clobbers(A) ; command serial bus device to UNLISTEN
|
||||||
|
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 $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 $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 $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
|
||||||
|
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> ubyte @Pc ; (via 798 ($31E)) define an input channel
|
||||||
|
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
|
||||||
|
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 $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 $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 $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock
|
||||||
|
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 $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
|
||||||
|
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
|
||||||
|
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of screen rows and columns
|
||||||
|
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
|
||||||
|
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
cx16 {
|
||||||
|
|
||||||
|
; 65c02 hardware vectors:
|
||||||
|
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
|
||||||
|
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
|
||||||
|
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
|
||||||
|
|
||||||
|
|
||||||
|
; the sixteen virtual 16-bit registers
|
||||||
|
&uword r0 = $0002
|
||||||
|
&uword r1 = $0004
|
||||||
|
&uword r2 = $0006
|
||||||
|
&uword r3 = $0008
|
||||||
|
&uword r4 = $000a
|
||||||
|
&uword r5 = $000c
|
||||||
|
&uword r6 = $000e
|
||||||
|
&uword r7 = $0010
|
||||||
|
&uword r8 = $0012
|
||||||
|
&uword r9 = $0014
|
||||||
|
&uword r10 = $0016
|
||||||
|
&uword r11 = $0018
|
||||||
|
&uword r12 = $001a
|
||||||
|
&uword r13 = $001c
|
||||||
|
&uword r14 = $001e
|
||||||
|
&uword r15 = $0020
|
||||||
|
|
||||||
|
; VERA registers
|
||||||
|
|
||||||
|
const uword VERA_BASE = $9F20
|
||||||
|
&ubyte VERA_ADDR_L = VERA_BASE + $0000
|
||||||
|
&ubyte VERA_ADDR_M = VERA_BASE + $0001
|
||||||
|
&ubyte VERA_ADDR_H = VERA_BASE + $0002
|
||||||
|
&ubyte VERA_DATA0 = VERA_BASE + $0003
|
||||||
|
&ubyte VERA_DATA1 = VERA_BASE + $0004
|
||||||
|
&ubyte VERA_CTRL = VERA_BASE + $0005
|
||||||
|
&ubyte VERA_IEN = VERA_BASE + $0006
|
||||||
|
&ubyte VERA_ISR = VERA_BASE + $0007
|
||||||
|
&ubyte VERA_IRQ_LINE_L = VERA_BASE + $0008
|
||||||
|
&ubyte VERA_DC_VIDEO = VERA_BASE + $0009
|
||||||
|
&ubyte VERA_DC_HSCALE = VERA_BASE + $000A
|
||||||
|
&ubyte VERA_DC_VSCALE = VERA_BASE + $000B
|
||||||
|
&ubyte VERA_DC_BORDER = VERA_BASE + $000C
|
||||||
|
&ubyte VERA_DC_HSTART = VERA_BASE + $0009
|
||||||
|
&ubyte VERA_DC_HSTOP = VERA_BASE + $000A
|
||||||
|
&ubyte VERA_DC_VSTART = VERA_BASE + $000B
|
||||||
|
&ubyte VERA_DC_VSTOP = VERA_BASE + $000C
|
||||||
|
&ubyte VERA_L0_CONFIG = VERA_BASE + $000D
|
||||||
|
&ubyte VERA_L0_MAPBASE = VERA_BASE + $000E
|
||||||
|
&ubyte VERA_L0_TILEBASE = VERA_BASE + $000F
|
||||||
|
&ubyte VERA_L0_HSCROLL_L = VERA_BASE + $0010
|
||||||
|
&ubyte VERA_L0_HSCROLL_H = VERA_BASE + $0011
|
||||||
|
&ubyte VERA_L0_VSCROLL_L = VERA_BASE + $0012
|
||||||
|
&ubyte VERA_L0_VSCROLL_H = VERA_BASE + $0013
|
||||||
|
&ubyte VERA_L1_CONFIG = VERA_BASE + $0014
|
||||||
|
&ubyte VERA_L1_MAPBASE = VERA_BASE + $0015
|
||||||
|
&ubyte VERA_L1_TILEBASE = VERA_BASE + $0016
|
||||||
|
&ubyte VERA_L1_HSCROLL_L = VERA_BASE + $0017
|
||||||
|
&ubyte VERA_L1_HSCROLL_H = VERA_BASE + $0018
|
||||||
|
&ubyte VERA_L1_VSCROLL_L = VERA_BASE + $0019
|
||||||
|
&ubyte VERA_L1_VSCROLL_H = VERA_BASE + $001A
|
||||||
|
&ubyte VERA_AUDIO_CTRL = VERA_BASE + $001B
|
||||||
|
&ubyte VERA_AUDIO_RATE = VERA_BASE + $001C
|
||||||
|
&ubyte VERA_AUDIO_DATA = VERA_BASE + $001D
|
||||||
|
&ubyte VERA_SPI_DATA = VERA_BASE + $001E
|
||||||
|
&ubyte VERA_SPI_CTRL = VERA_BASE + $001F
|
||||||
|
; VERA_PSG_BASE = $1F9C0
|
||||||
|
; VERA_PALETTE_BASE = $1FA00
|
||||||
|
; VERA_SPRITES_BASE = $1FC00
|
||||||
|
|
||||||
|
; I/O
|
||||||
|
|
||||||
|
const uword via1 = $9f60 ;VIA 6522 #1
|
||||||
|
&ubyte d1prb = via1+0
|
||||||
|
&ubyte d1pra = via1+1
|
||||||
|
&ubyte d1ddrb = via1+2
|
||||||
|
&ubyte d1ddra = via1+3
|
||||||
|
&ubyte d1t1l = via1+4
|
||||||
|
&ubyte d1t1h = via1+5
|
||||||
|
&ubyte d1t1ll = via1+6
|
||||||
|
&ubyte d1t1lh = via1+7
|
||||||
|
&ubyte d1t2l = via1+8
|
||||||
|
&ubyte d1t2h = via1+9
|
||||||
|
&ubyte d1sr = via1+10
|
||||||
|
&ubyte d1acr = via1+11
|
||||||
|
&ubyte d1pcr = via1+12
|
||||||
|
&ubyte d1ifr = via1+13
|
||||||
|
&ubyte d1ier = via1+14
|
||||||
|
&ubyte d1ora = via1+15
|
||||||
|
|
||||||
|
const uword via2 = $9f70 ;VIA 6522 #2
|
||||||
|
&ubyte d2prb =via2+0
|
||||||
|
&ubyte d2pra =via2+1
|
||||||
|
&ubyte d2ddrb =via2+2
|
||||||
|
&ubyte d2ddra =via2+3
|
||||||
|
&ubyte d2t1l =via2+4
|
||||||
|
&ubyte d2t1h =via2+5
|
||||||
|
&ubyte d2t1ll =via2+6
|
||||||
|
&ubyte d2t1lh =via2+7
|
||||||
|
&ubyte d2t2l =via2+8
|
||||||
|
&ubyte d2t2h =via2+9
|
||||||
|
&ubyte d2sr =via2+10
|
||||||
|
&ubyte d2acr =via2+11
|
||||||
|
&ubyte d2pcr =via2+12
|
||||||
|
&ubyte d2ifr =via2+13
|
||||||
|
&ubyte d2ier =via2+14
|
||||||
|
&ubyte d2ora =via2+15
|
||||||
|
|
||||||
|
|
||||||
|
; ---- Commander X-16 additions on top of C64 kernal routines ----
|
||||||
|
; spelling of the names is taken from the Commander X-16 rom sources
|
||||||
|
|
||||||
|
; supported C128 additions
|
||||||
|
romsub $ff4a = close_all(ubyte device @A) clobbers(A,X,Y)
|
||||||
|
romsub $ff59 = lkupla(ubyte la @A) clobbers(A,X,Y)
|
||||||
|
romsub $ff5c = lkupsa(ubyte sa @Y) clobbers(A,X,Y)
|
||||||
|
romsub $ff5f = screen_set_mode(ubyte mode @A) clobbers(A, X, Y) -> ubyte @Pc
|
||||||
|
romsub $ff62 = screen_set_charset(ubyte charset @A, uword charsetptr @XY) clobbers(A,X,Y) ; incompatible with C128 dlchr()
|
||||||
|
; not yet supported: romsub $ff65 = pfkey() clobbers(A,X,Y)
|
||||||
|
romsub $ff6e = jsrfar()
|
||||||
|
romsub $ff74 = fetch(ubyte bank @X, ubyte index @Y) clobbers(X) -> ubyte @A
|
||||||
|
romsub $ff77 = stash(ubyte data @A, ubyte bank @X, ubyte index @Y) clobbers(X)
|
||||||
|
romsub $ff7a = cmpare(ubyte data @A, ubyte bank @X, ubyte index @Y) clobbers(X)
|
||||||
|
romsub $ff7d = primm()
|
||||||
|
|
||||||
|
; X16 additions
|
||||||
|
romsub $ff44 = macptr() clobbers(A,X,Y)
|
||||||
|
romsub $ff47 = enter_basic(ubyte cold_or_warm @Pc) clobbers(A,X,Y)
|
||||||
|
romsub $ff68 = mouse_config(ubyte shape @A, ubyte scale @X) clobbers (A, X, Y)
|
||||||
|
romsub $ff6b = mouse_get(ubyte zpdataptr @X) clobbers(A)
|
||||||
|
romsub $ff71 = mouse_scan() clobbers(A, X, Y)
|
||||||
|
romsub $ff53 = joystick_scan() clobbers(A, X, Y)
|
||||||
|
romsub $ff56 = joystick_get(ubyte joynr @A) -> ubyte @A, ubyte @X, ubyte @Y
|
||||||
|
romsub $ff4d = clock_set_date_time() clobbers(A, X, Y) ; args: r0, r1, r2, r3L
|
||||||
|
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) ; outout args: r0, r1, r2, r3L
|
||||||
|
|
||||||
|
; TODO specify the correct clobbers for alle these functions below, we now assume all 3 regs are clobbered
|
||||||
|
|
||||||
|
; high level graphics & fonts
|
||||||
|
romsub $ff20 = GRAPH_init() clobbers(A,X,Y) ; uses vectors=r0
|
||||||
|
romsub $ff23 = GRAPH_clear() clobbers(A,X,Y)
|
||||||
|
romsub $ff26 = GRAPH_set_window() clobbers(A,X,Y) ; uses x=r0, y=r1, width=r2, height=r3
|
||||||
|
romsub $ff29 = GRAPH_set_colors(ubyte stroke @A, ubyte fill @X, ubyte background @Y) clobbers (A,X,Y)
|
||||||
|
romsub $ff2c = GRAPH_draw_line() clobbers(A,X,Y) ; uses x1=r0, y1=r1, x2=r2, y2=r3
|
||||||
|
romsub $ff2f = GRAPH_draw_rect(ubyte fill @Pc) clobbers(A,X,Y) ; uses x=r0, y=r1, width=r2, height=r3, cornerradius=r4
|
||||||
|
romsub $ff32 = GRAPH_move_rect() clobbers(A,X,Y) ; uses sx=r0, sy=r1, tx=r2, ty=r3, width=r4, height=r5
|
||||||
|
romsub $ff35 = GRAPH_draw_oval(ubyte fill @Pc) clobbers(A,X,Y) ; uses x=r0, y=r1, width=r2, height=r3
|
||||||
|
romsub $ff38 = GRAPH_draw_image() clobbers(A,X,Y) ; uses x=r0, y=r1, ptr=r2, width=r3, height=r4
|
||||||
|
romsub $ff3b = GRAPH_set_font() clobbers(A,X,Y) ; uses ptr=r0
|
||||||
|
romsub $ff3e = GRAPH_get_char_size(ubyte baseline @A, ubyte width @X, ubyte height_or_style @Y, ubyte is_control @Pc) clobbers(A,X,Y)
|
||||||
|
romsub $ff41 = GRAPH_put_char(ubyte char @A) clobbers(A,X,Y) ; uses x=r0, y=r1
|
||||||
|
|
||||||
|
; framebuffer
|
||||||
|
romsub $fef6 = FB_init() clobbers(A,X,Y)
|
||||||
|
romsub $fef9 = FB_get_info() clobbers(X,Y) -> byte @A ; also outputs width=r0, height=r1
|
||||||
|
romsub $fefc = FB_set_palette(ubyte index @A, ubyte bytecount @X) clobbers(A,X,Y) ; also uses pointer=r0
|
||||||
|
romsub $feff = FB_cursor_position() clobbers(A,X,Y) ; uses x=r0, y=r1
|
||||||
|
romsub $ff02 = FB_cursor_next_line() clobbers(A,X,Y) ; uses x=r0
|
||||||
|
romsub $ff05 = FB_get_pixel() clobbers(X,Y) -> ubyte @A
|
||||||
|
romsub $ff08 = FB_get_pixels() clobbers(A,X,Y) ; uses ptr=r0, count=r1
|
||||||
|
romsub $ff0b = FB_set_pixel(ubyte color @A) clobbers(A,X,Y)
|
||||||
|
romsub $ff0e = FB_set_pixels() clobbers(A,X,Y) ; uses ptr=r0, count=r1
|
||||||
|
romsub $ff11 = FB_set_8_pixels(ubyte pattern @A, ubyte color @X) clobbers(A,X,Y)
|
||||||
|
romsub $ff14 = FB_set_8_pixels_opaque(ubyte pattern @A, ubyte color1 @X, ubyte color2 @Y) clobbers(A,X,Y) ; also uses mask=r0L
|
||||||
|
romsub $ff17 = FB_fill_pixels(ubyte color @A) clobbers(A,X,Y) ; also uses count=r0, step=r1
|
||||||
|
romsub $ff1a = FB_filter_pixels() clobbers(A,X,Y) ; uses ptr=r0, count=r1
|
||||||
|
romsub $ff1d = FB_move_pixels() clobbers(A,X,Y) ; uses sx=r0, sy=r1, tx=r2, ty=r3, count=r4
|
||||||
|
|
||||||
|
; misc
|
||||||
|
romsub $fef0 = sprite_set_image(ubyte number @A, ubyte width @X, ubyte height @Y, ubyte apply_mask @Pc) clobbers(A,X,Y) -> ubyte @Pc ; also uses pixels=r0, mask=r1, bpp=r2L
|
||||||
|
romsub $fef3 = sprite_set_position(ubyte number @A) clobbers(A,X,Y) ; also uses x=r0 and y=r1
|
||||||
|
romsub $fee4 = memory_fill(ubyte value @A) clobbers(A,X,Y) ; uses address=r0, num_bytes=r1
|
||||||
|
romsub $fee7 = memory_copy() clobbers(A,X,Y) ; uses source=r0, target=r1, num_bytes=r2
|
||||||
|
romsub $feea = memory_crc() clobbers(A,X,Y) ; uses address=r0, num_bytes=r1 result->r2
|
||||||
|
romsub $feed = memory_decompress() clobbers(A,X,Y) ; uses input=r0, output=r1 result->r1
|
||||||
|
romsub $fedb = console_init() clobbers(A,X,Y) ; uses x=r0, y=r1, width=r2, height=r3
|
||||||
|
romsub $fede = console_put_char(ubyte char @A, ubyte wrapping @Pc) clobbers(A,X,Y)
|
||||||
|
romsub $fee1 = console_get_char() clobbers(X,Y) -> ubyte @A
|
||||||
|
romsub $fed8 = console_put_image() clobbers(A,X,Y) ; uses ptr=r0, width=r1, height=r2
|
||||||
|
romsub $fed5 = console_set_paging_message() clobbers(A,X,Y) ; uses messageptr=r0
|
||||||
|
romsub $fed2 = kbdbuf_put(ubyte key @A) clobbers(A,X,Y)
|
||||||
|
romsub $fecf = entropy_get() -> ubyte @A, ubyte @X, ubyte @Y
|
||||||
|
romsub $fecc = monitor() clobbers(A,X,Y)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
; ---- end of kernal routines ----
|
||||||
|
|
||||||
|
asmsub init_system() {
|
||||||
|
; Initializes the machine to a sane starting state.
|
||||||
|
; Called automatically by the loader program logic.
|
||||||
|
%asm {{
|
||||||
|
sei
|
||||||
|
cld
|
||||||
|
;stz $00
|
||||||
|
;stz $01
|
||||||
|
;stz d1prb ; select rom bank 0
|
||||||
|
lda #$80
|
||||||
|
sta VERA_CTRL
|
||||||
|
jsr c64.IOINIT
|
||||||
|
jsr c64.RESTOR
|
||||||
|
jsr c64.CINT
|
||||||
|
lda #$90 ; black
|
||||||
|
jsr c64.CHROUT
|
||||||
|
lda #1 ; swap fg/bg
|
||||||
|
jsr c64.CHROUT
|
||||||
|
lda #$9e ; yellow
|
||||||
|
jsr c64.CHROUT
|
||||||
|
lda #147 ; clear screen
|
||||||
|
jsr c64.CHROUT
|
||||||
|
lda #0
|
||||||
|
tax
|
||||||
|
tay
|
||||||
|
clc
|
||||||
|
clv
|
||||||
|
cli
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub reset_system() {
|
||||||
|
; Soft-reset the system back to Basic prompt.
|
||||||
|
%asm {{
|
||||||
|
sei
|
||||||
|
lda #14
|
||||||
|
sta $01
|
||||||
|
stz cx16.d1prb ; bank the kernal in
|
||||||
|
jmp (cx16.RESET_VEC)
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
699
compiler/res/prog8lib/cx16/textio.p8
Normal file
699
compiler/res/prog8lib/cx16/textio.p8
Normal file
@ -0,0 +1,699 @@
|
|||||||
|
; Prog8 definitions for the Text I/O and Screen routines for the CommanderX16
|
||||||
|
;
|
||||||
|
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||||
|
;
|
||||||
|
; indent format: TABS, size=8
|
||||||
|
|
||||||
|
%target cx16
|
||||||
|
%import syslib
|
||||||
|
%import conv
|
||||||
|
|
||||||
|
|
||||||
|
txt {
|
||||||
|
|
||||||
|
const ubyte DEFAULT_WIDTH = 80
|
||||||
|
const ubyte DEFAULT_HEIGHT = 60
|
||||||
|
|
||||||
|
|
||||||
|
sub clear_screen() {
|
||||||
|
clear_screenchars(' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
|
||||||
|
; ---- fill the character screen with the given fill character and character color.
|
||||||
|
%asm {{
|
||||||
|
sty _ly+1
|
||||||
|
phx
|
||||||
|
pha
|
||||||
|
jsr c64.SCREEN ; get dimensions in X/Y
|
||||||
|
txa
|
||||||
|
lsr a
|
||||||
|
lsr a
|
||||||
|
sta _lx+1
|
||||||
|
stz cx16.VERA_CTRL
|
||||||
|
lda #%00010000
|
||||||
|
sta cx16.VERA_ADDR_H ; enable auto increment by 1, bank 0.
|
||||||
|
stz cx16.VERA_ADDR_L ; start at (0,0)
|
||||||
|
stz cx16.VERA_ADDR_M
|
||||||
|
pla
|
||||||
|
_lx ldx #0 ; modified
|
||||||
|
phy
|
||||||
|
_ly ldy #1 ; modified
|
||||||
|
- sta cx16.VERA_DATA0
|
||||||
|
sty cx16.VERA_DATA0
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
sty cx16.VERA_DATA0
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
sty cx16.VERA_DATA0
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
sty cx16.VERA_DATA0
|
||||||
|
dex
|
||||||
|
bne -
|
||||||
|
ply
|
||||||
|
dey
|
||||||
|
beq +
|
||||||
|
stz cx16.VERA_ADDR_L
|
||||||
|
inc cx16.VERA_ADDR_M ; next line
|
||||||
|
bra _lx
|
||||||
|
+ plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
|
||||||
|
; ---- clear the character screen with the given fill character (leaves colors)
|
||||||
|
; (assumes screen matrix is at the default address)
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
pha
|
||||||
|
jsr c64.SCREEN ; get dimensions in X/Y
|
||||||
|
txa
|
||||||
|
lsr a
|
||||||
|
lsr a
|
||||||
|
sta _lx+1
|
||||||
|
stz cx16.VERA_CTRL
|
||||||
|
lda #%00100000
|
||||||
|
sta cx16.VERA_ADDR_H ; enable auto increment by 2, bank 0.
|
||||||
|
stz cx16.VERA_ADDR_L ; start at (0,0)
|
||||||
|
stz cx16.VERA_ADDR_M
|
||||||
|
pla
|
||||||
|
_lx ldx #0 ; modified
|
||||||
|
- sta cx16.VERA_DATA0
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
dex
|
||||||
|
bne -
|
||||||
|
dey
|
||||||
|
beq +
|
||||||
|
stz cx16.VERA_ADDR_L
|
||||||
|
inc cx16.VERA_ADDR_M ; next line
|
||||||
|
bra _lx
|
||||||
|
+ plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub clear_screencolors (ubyte color @ A) clobbers(Y) {
|
||||||
|
; ---- clear the character screen colors with the given color (leaves characters).
|
||||||
|
; (assumes color matrix is at the default address)
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
sta _la+1
|
||||||
|
jsr c64.SCREEN ; get dimensions in X/Y
|
||||||
|
txa
|
||||||
|
lsr a
|
||||||
|
lsr a
|
||||||
|
sta _lx+1
|
||||||
|
stz cx16.VERA_CTRL
|
||||||
|
lda #%00100000
|
||||||
|
sta cx16.VERA_ADDR_H ; enable auto increment by 2, bank 0.
|
||||||
|
lda #1
|
||||||
|
sta cx16.VERA_ADDR_L ; start at (1,0)
|
||||||
|
stz cx16.VERA_ADDR_M
|
||||||
|
_lx ldx #0 ; modified
|
||||||
|
_la lda #0 ; modified
|
||||||
|
- sta cx16.VERA_DATA0
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
dex
|
||||||
|
bne -
|
||||||
|
dey
|
||||||
|
beq +
|
||||||
|
lda #1
|
||||||
|
sta cx16.VERA_ADDR_L
|
||||||
|
inc cx16.VERA_ADDR_M ; next line
|
||||||
|
bra _lx
|
||||||
|
+ plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ubyte[16] color_to_charcode = [$90,$05,$1c,$9f,$9c,$1e,$1f,$9e,$81,$95,$96,$97,$98,$99,$9a,$9b]
|
||||||
|
|
||||||
|
sub color (ubyte txtcol) {
|
||||||
|
txtcol &= 15
|
||||||
|
c64.CHROUT(color_to_charcode[txtcol])
|
||||||
|
}
|
||||||
|
|
||||||
|
sub color2 (ubyte txtcol, ubyte bgcol) {
|
||||||
|
txtcol &= 15
|
||||||
|
bgcol &= 15
|
||||||
|
c64.CHROUT(color_to_charcode[bgcol])
|
||||||
|
c64.CHROUT(1) ; switch fg and bg colors
|
||||||
|
c64.CHROUT(color_to_charcode[txtcol])
|
||||||
|
}
|
||||||
|
|
||||||
|
sub lowercase() {
|
||||||
|
cx16.screen_set_charset(3, 0) ; lowercase charset
|
||||||
|
}
|
||||||
|
|
||||||
|
sub uppercase() {
|
||||||
|
cx16.screen_set_charset(2, 0) ; uppercase charset
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub scroll_left (ubyte dummy @ Pc) clobbers(A, Y) {
|
||||||
|
; ---- scroll the whole screen 1 character to the left
|
||||||
|
; contents of the rightmost column are unchanged, you should clear/refill this yourself
|
||||||
|
; Carry flag is a dummy on the cx16
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
jsr c64.SCREEN
|
||||||
|
dex
|
||||||
|
stx _lx+1
|
||||||
|
dey
|
||||||
|
sty P8ZP_SCRATCH_B1 ; number of rows to scroll
|
||||||
|
|
||||||
|
_nextline
|
||||||
|
stz cx16.VERA_CTRL ; data port 0: source column
|
||||||
|
lda #%00010000 ; auto increment 1
|
||||||
|
sta cx16.VERA_ADDR_H
|
||||||
|
lda #2
|
||||||
|
sta cx16.VERA_ADDR_L ; begin in column 1
|
||||||
|
ldy P8ZP_SCRATCH_B1
|
||||||
|
sty cx16.VERA_ADDR_M
|
||||||
|
lda #1
|
||||||
|
sta cx16.VERA_CTRL ; data port 1: destination column
|
||||||
|
lda #%00010000 ; auto increment 1
|
||||||
|
sta cx16.VERA_ADDR_H
|
||||||
|
stz cx16.VERA_ADDR_L
|
||||||
|
sty cx16.VERA_ADDR_M
|
||||||
|
|
||||||
|
_lx ldx #0 ; modified
|
||||||
|
- lda cx16.VERA_DATA0
|
||||||
|
sta cx16.VERA_DATA1 ; copy char
|
||||||
|
lda cx16.VERA_DATA0
|
||||||
|
sta cx16.VERA_DATA1 ; copy color
|
||||||
|
dex
|
||||||
|
bne -
|
||||||
|
dec P8ZP_SCRATCH_B1
|
||||||
|
bpl _nextline
|
||||||
|
|
||||||
|
lda #0
|
||||||
|
sta cx16.VERA_CTRL
|
||||||
|
plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub scroll_right (ubyte dummy @ Pc) clobbers(A) {
|
||||||
|
; ---- scroll the whole screen 1 character to the right
|
||||||
|
; contents of the leftmost column are unchanged, you should clear/refill this yourself
|
||||||
|
; Carry flag is a dummy on the cx16
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
jsr c64.SCREEN
|
||||||
|
dex
|
||||||
|
stx _lx+1
|
||||||
|
txa
|
||||||
|
asl a
|
||||||
|
dea
|
||||||
|
sta _rcol+1
|
||||||
|
ina
|
||||||
|
ina
|
||||||
|
sta _rcol2+1
|
||||||
|
dey
|
||||||
|
sty P8ZP_SCRATCH_B1 ; number of rows to scroll
|
||||||
|
|
||||||
|
_nextline
|
||||||
|
stz cx16.VERA_CTRL ; data port 0: source column
|
||||||
|
lda #%00011000 ; auto decrement 1
|
||||||
|
sta cx16.VERA_ADDR_H
|
||||||
|
_rcol lda #79*2-1 ; modified
|
||||||
|
sta cx16.VERA_ADDR_L ; begin in rightmost column minus one
|
||||||
|
ldy P8ZP_SCRATCH_B1
|
||||||
|
sty cx16.VERA_ADDR_M
|
||||||
|
lda #1
|
||||||
|
sta cx16.VERA_CTRL ; data port 1: destination column
|
||||||
|
lda #%00011000 ; auto decrement 1
|
||||||
|
sta cx16.VERA_ADDR_H
|
||||||
|
_rcol2 lda #79*2+1 ; modified
|
||||||
|
sta cx16.VERA_ADDR_L
|
||||||
|
sty cx16.VERA_ADDR_M
|
||||||
|
|
||||||
|
_lx ldx #0 ; modified
|
||||||
|
- lda cx16.VERA_DATA0
|
||||||
|
sta cx16.VERA_DATA1 ; copy char
|
||||||
|
lda cx16.VERA_DATA0
|
||||||
|
sta cx16.VERA_DATA1 ; copy color
|
||||||
|
dex
|
||||||
|
bne -
|
||||||
|
dec P8ZP_SCRATCH_B1
|
||||||
|
bpl _nextline
|
||||||
|
|
||||||
|
lda #0
|
||||||
|
sta cx16.VERA_CTRL
|
||||||
|
plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub scroll_up (ubyte dummy @ Pc) clobbers(A, Y) {
|
||||||
|
; ---- scroll the whole screen 1 character up
|
||||||
|
; contents of the bottom row are unchanged, you should refill/clear this yourself
|
||||||
|
; Carry flag is a dummy on the cx16
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
jsr c64.SCREEN
|
||||||
|
stx _nextline+1
|
||||||
|
dey
|
||||||
|
sty P8ZP_SCRATCH_B1
|
||||||
|
stz cx16.VERA_CTRL ; data port 0 is source
|
||||||
|
lda #1
|
||||||
|
sta cx16.VERA_ADDR_M ; start at second line
|
||||||
|
stz cx16.VERA_ADDR_L
|
||||||
|
lda #%00010000
|
||||||
|
sta cx16.VERA_ADDR_H ; enable auto increment by 1, bank 0.
|
||||||
|
|
||||||
|
lda #1
|
||||||
|
sta cx16.VERA_CTRL ; data port 1 is destination
|
||||||
|
stz cx16.VERA_ADDR_M ; start at top line
|
||||||
|
stz cx16.VERA_ADDR_L
|
||||||
|
lda #%00010000
|
||||||
|
sta cx16.VERA_ADDR_H ; enable auto increment by 1, bank 0.
|
||||||
|
|
||||||
|
_nextline
|
||||||
|
ldx #80 ; modified
|
||||||
|
- lda cx16.VERA_DATA0
|
||||||
|
sta cx16.VERA_DATA1 ; copy char
|
||||||
|
lda cx16.VERA_DATA0
|
||||||
|
sta cx16.VERA_DATA1 ; copy color
|
||||||
|
dex
|
||||||
|
bne -
|
||||||
|
dec P8ZP_SCRATCH_B1
|
||||||
|
beq +
|
||||||
|
stz cx16.VERA_CTRL ; data port 0
|
||||||
|
stz cx16.VERA_ADDR_L
|
||||||
|
inc cx16.VERA_ADDR_M
|
||||||
|
lda #1
|
||||||
|
sta cx16.VERA_CTRL ; data port 1
|
||||||
|
stz cx16.VERA_ADDR_L
|
||||||
|
inc cx16.VERA_ADDR_M
|
||||||
|
bra _nextline
|
||||||
|
|
||||||
|
+ lda #0
|
||||||
|
sta cx16.VERA_CTRL
|
||||||
|
plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub scroll_down (ubyte dummy @ Pc) clobbers(A, Y) {
|
||||||
|
; ---- scroll the whole screen 1 character down
|
||||||
|
; contents of the top row are unchanged, you should refill/clear this yourself
|
||||||
|
; Carry flag is a dummy on the cx16
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
jsr c64.SCREEN
|
||||||
|
stx _nextline+1
|
||||||
|
dey
|
||||||
|
sty P8ZP_SCRATCH_B1
|
||||||
|
stz cx16.VERA_CTRL ; data port 0 is source
|
||||||
|
dey
|
||||||
|
sty cx16.VERA_ADDR_M ; start at line before bottom line
|
||||||
|
stz cx16.VERA_ADDR_L
|
||||||
|
lda #%00010000
|
||||||
|
sta cx16.VERA_ADDR_H ; enable auto increment by 1, bank 0.
|
||||||
|
|
||||||
|
lda #1
|
||||||
|
sta cx16.VERA_CTRL ; data port 1 is destination
|
||||||
|
iny
|
||||||
|
sty cx16.VERA_ADDR_M ; start at bottom line
|
||||||
|
stz cx16.VERA_ADDR_L
|
||||||
|
lda #%00010000
|
||||||
|
sta cx16.VERA_ADDR_H ; enable auto increment by 1, bank 0.
|
||||||
|
|
||||||
|
_nextline
|
||||||
|
ldx #80 ; modified
|
||||||
|
- lda cx16.VERA_DATA0
|
||||||
|
sta cx16.VERA_DATA1 ; copy char
|
||||||
|
lda cx16.VERA_DATA0
|
||||||
|
sta cx16.VERA_DATA1 ; copy color
|
||||||
|
dex
|
||||||
|
bne -
|
||||||
|
dec P8ZP_SCRATCH_B1
|
||||||
|
beq +
|
||||||
|
stz cx16.VERA_CTRL ; data port 0
|
||||||
|
stz cx16.VERA_ADDR_L
|
||||||
|
dec cx16.VERA_ADDR_M
|
||||||
|
lda #1
|
||||||
|
sta cx16.VERA_CTRL ; data port 1
|
||||||
|
stz cx16.VERA_ADDR_L
|
||||||
|
dec cx16.VERA_ADDR_M
|
||||||
|
bra _nextline
|
||||||
|
|
||||||
|
+ lda #0
|
||||||
|
sta cx16.VERA_CTRL
|
||||||
|
plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
romsub $FFD2 = chrout(ubyte char @ A) ; for consistency. You can also use c64.CHROUT directly ofcourse.
|
||||||
|
|
||||||
|
asmsub print (str text @ AY) clobbers(A,Y) {
|
||||||
|
; ---- print null terminated string from A/Y
|
||||||
|
; note: the compiler contains an optimization that will replace
|
||||||
|
; a call to this subroutine with a string argument of just one char,
|
||||||
|
; by just one call to c64.CHROUT of that single char.
|
||||||
|
%asm {{
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
sty P8ZP_SCRATCH_REG
|
||||||
|
ldy #0
|
||||||
|
- lda (P8ZP_SCRATCH_B1),y
|
||||||
|
beq +
|
||||||
|
jsr c64.CHROUT
|
||||||
|
iny
|
||||||
|
bne -
|
||||||
|
+ rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_ub0 (ubyte value @ A) clobbers(A,Y) {
|
||||||
|
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
jsr conv.ubyte2decimal
|
||||||
|
pha
|
||||||
|
tya
|
||||||
|
jsr c64.CHROUT
|
||||||
|
pla
|
||||||
|
jsr c64.CHROUT
|
||||||
|
txa
|
||||||
|
jsr c64.CHROUT
|
||||||
|
plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_ub (ubyte value @ A) clobbers(A,Y) {
|
||||||
|
; ---- print the ubyte in A in decimal form, without left padding 0s
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
jsr conv.ubyte2decimal
|
||||||
|
_print_byte_digits
|
||||||
|
pha
|
||||||
|
cpy #'0'
|
||||||
|
beq +
|
||||||
|
tya
|
||||||
|
jsr c64.CHROUT
|
||||||
|
pla
|
||||||
|
jsr c64.CHROUT
|
||||||
|
jmp _ones
|
||||||
|
+ pla
|
||||||
|
cmp #'0'
|
||||||
|
beq _ones
|
||||||
|
jsr c64.CHROUT
|
||||||
|
_ones txa
|
||||||
|
jsr c64.CHROUT
|
||||||
|
plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_b (byte value @ A) clobbers(A,Y) {
|
||||||
|
; ---- print the byte in A in decimal form, without left padding 0s
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
pha
|
||||||
|
cmp #0
|
||||||
|
bpl +
|
||||||
|
lda #'-'
|
||||||
|
jsr c64.CHROUT
|
||||||
|
+ pla
|
||||||
|
jsr conv.byte2decimal
|
||||||
|
jmp print_ub._print_byte_digits
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_ubhex (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||||
|
; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well)
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
bcc +
|
||||||
|
pha
|
||||||
|
lda #'$'
|
||||||
|
jsr c64.CHROUT
|
||||||
|
pla
|
||||||
|
+ jsr conv.ubyte2hex
|
||||||
|
jsr c64.CHROUT
|
||||||
|
tya
|
||||||
|
jsr c64.CHROUT
|
||||||
|
plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_ubbin (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||||
|
; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well)
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
bcc +
|
||||||
|
lda #'%'
|
||||||
|
jsr c64.CHROUT
|
||||||
|
+ ldy #8
|
||||||
|
- lda #'0'
|
||||||
|
asl P8ZP_SCRATCH_B1
|
||||||
|
bcc +
|
||||||
|
lda #'1'
|
||||||
|
+ jsr c64.CHROUT
|
||||||
|
dey
|
||||||
|
bne -
|
||||||
|
plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_uwbin (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||||
|
; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
|
||||||
|
%asm {{
|
||||||
|
pha
|
||||||
|
tya
|
||||||
|
jsr print_ubbin
|
||||||
|
pla
|
||||||
|
clc
|
||||||
|
jmp print_ubbin
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_uwhex (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||||
|
; ---- print the uword in A/Y in hexadecimal form (4 digits)
|
||||||
|
; (if Carry is set, a radix prefix '$' is printed as well)
|
||||||
|
%asm {{
|
||||||
|
pha
|
||||||
|
tya
|
||||||
|
jsr print_ubhex
|
||||||
|
pla
|
||||||
|
clc
|
||||||
|
jmp print_ubhex
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_uw0 (uword value @ AY) clobbers(A,Y) {
|
||||||
|
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
jsr conv.uword2decimal
|
||||||
|
ldy #0
|
||||||
|
- lda conv.uword2decimal.decTenThousands,y
|
||||||
|
beq +
|
||||||
|
jsr c64.CHROUT
|
||||||
|
iny
|
||||||
|
bne -
|
||||||
|
+ plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_uw (uword value @ AY) clobbers(A,Y) {
|
||||||
|
; ---- print the uword in A/Y in decimal form, without left padding 0s
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
jsr conv.uword2decimal
|
||||||
|
plx
|
||||||
|
ldy #0
|
||||||
|
- lda conv.uword2decimal.decTenThousands,y
|
||||||
|
beq _allzero
|
||||||
|
cmp #'0'
|
||||||
|
bne _gotdigit
|
||||||
|
iny
|
||||||
|
bne -
|
||||||
|
|
||||||
|
_gotdigit
|
||||||
|
jsr c64.CHROUT
|
||||||
|
iny
|
||||||
|
lda conv.uword2decimal.decTenThousands,y
|
||||||
|
bne _gotdigit
|
||||||
|
rts
|
||||||
|
_allzero
|
||||||
|
lda #'0'
|
||||||
|
jmp c64.CHROUT
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub print_w (word value @ AY) clobbers(A,Y) {
|
||||||
|
; ---- print the (signed) word in A/Y in decimal form, without left padding 0's
|
||||||
|
%asm {{
|
||||||
|
cpy #0
|
||||||
|
bpl +
|
||||||
|
pha
|
||||||
|
lda #'-'
|
||||||
|
jsr c64.CHROUT
|
||||||
|
tya
|
||||||
|
eor #255
|
||||||
|
tay
|
||||||
|
pla
|
||||||
|
eor #255
|
||||||
|
clc
|
||||||
|
adc #1
|
||||||
|
bcc +
|
||||||
|
iny
|
||||||
|
+ jmp print_uw
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
|
||||||
|
; ---- Input a string (max. 80 chars) from the keyboard. Returns length in Y. (string is terminated with a 0 byte as well)
|
||||||
|
; It assumes the keyboard is selected as I/O channel!
|
||||||
|
|
||||||
|
%asm {{
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
ldy #0 ; char counter = 0
|
||||||
|
- jsr c64.CHRIN
|
||||||
|
cmp #$0d ; return (ascii 13) pressed?
|
||||||
|
beq + ; yes, end.
|
||||||
|
sta (P8ZP_SCRATCH_W1),y ; else store char in buffer
|
||||||
|
iny
|
||||||
|
bne -
|
||||||
|
+ lda #0
|
||||||
|
sta (P8ZP_SCRATCH_W1),y ; finish string with 0 byte
|
||||||
|
rts
|
||||||
|
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A) {
|
||||||
|
; ---- sets the character in the screen matrix at the given position
|
||||||
|
%asm {{
|
||||||
|
pha
|
||||||
|
txa
|
||||||
|
asl a
|
||||||
|
stz cx16.VERA_CTRL
|
||||||
|
stz cx16.VERA_ADDR_H
|
||||||
|
sta cx16.VERA_ADDR_L
|
||||||
|
sty cx16.VERA_ADDR_M
|
||||||
|
pla
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub getchr (ubyte col @A, ubyte row @Y) -> ubyte @ A {
|
||||||
|
; ---- get the character in the screen matrix at the given location
|
||||||
|
%asm {{
|
||||||
|
asl a
|
||||||
|
stz cx16.VERA_CTRL
|
||||||
|
stz cx16.VERA_ADDR_H
|
||||||
|
sta cx16.VERA_ADDR_L
|
||||||
|
sty cx16.VERA_ADDR_M
|
||||||
|
lda cx16.VERA_DATA0
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub setclr (ubyte col @X, ubyte row @Y, ubyte color @A) clobbers(A) {
|
||||||
|
; ---- set the color in A on the screen matrix at the given position
|
||||||
|
%asm {{
|
||||||
|
pha
|
||||||
|
txa
|
||||||
|
asl a
|
||||||
|
ina
|
||||||
|
stz cx16.VERA_CTRL
|
||||||
|
stz cx16.VERA_ADDR_H
|
||||||
|
sta cx16.VERA_ADDR_L
|
||||||
|
sty cx16.VERA_ADDR_M
|
||||||
|
pla
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub getclr (ubyte col @A, ubyte row @Y) -> ubyte @ A {
|
||||||
|
; ---- get the color in the screen color matrix at the given location
|
||||||
|
%asm {{
|
||||||
|
asl a
|
||||||
|
ina
|
||||||
|
stz cx16.VERA_CTRL
|
||||||
|
stz cx16.VERA_ADDR_H
|
||||||
|
sta cx16.VERA_ADDR_L
|
||||||
|
sty cx16.VERA_ADDR_M
|
||||||
|
lda cx16.VERA_DATA0
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
|
||||||
|
; ---- set char+color at the given position on the screen
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
lda column
|
||||||
|
asl a
|
||||||
|
tax
|
||||||
|
ldy row
|
||||||
|
lda charcolor
|
||||||
|
and #$0f
|
||||||
|
sta P8ZP_SCRATCH_B1
|
||||||
|
stz cx16.VERA_CTRL
|
||||||
|
stz cx16.VERA_ADDR_H
|
||||||
|
stx cx16.VERA_ADDR_L
|
||||||
|
sty cx16.VERA_ADDR_M
|
||||||
|
lda char
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
inx
|
||||||
|
stz cx16.VERA_ADDR_H
|
||||||
|
stx cx16.VERA_ADDR_L
|
||||||
|
sty cx16.VERA_ADDR_M
|
||||||
|
lda cx16.VERA_DATA0
|
||||||
|
and #$f0
|
||||||
|
ora P8ZP_SCRATCH_B1
|
||||||
|
sta cx16.VERA_DATA0
|
||||||
|
plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub plot (ubyte col @ Y, ubyte row @ A) clobbers(A) {
|
||||||
|
; ---- safe wrapper around PLOT kernel routine, to save the X register.
|
||||||
|
%asm {{
|
||||||
|
phx
|
||||||
|
tax
|
||||||
|
clc
|
||||||
|
jsr c64.PLOT
|
||||||
|
plx
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub width() clobbers(X,Y) -> ubyte @A {
|
||||||
|
; -- returns the text screen width (number of columns)
|
||||||
|
%asm {{
|
||||||
|
jsr c64.SCREEN
|
||||||
|
txa
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
asmsub height() clobbers(X, Y) -> ubyte @A {
|
||||||
|
; -- returns the text screen height (number of rows)
|
||||||
|
%asm {{
|
||||||
|
jsr c64.SCREEN
|
||||||
|
tya
|
||||||
|
rts
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
29
compiler/res/prog8lib/cx16logo.p8
Normal file
29
compiler/res/prog8lib/cx16logo.p8
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
%import textio
|
||||||
|
|
||||||
|
cx16logo {
|
||||||
|
sub logo_at(ubyte column, ubyte row) {
|
||||||
|
uword strptr
|
||||||
|
for strptr in logo_lines {
|
||||||
|
txt.plot(column, row)
|
||||||
|
txt.print(strptr)
|
||||||
|
row++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub logo() {
|
||||||
|
uword strptr
|
||||||
|
for strptr in logo_lines
|
||||||
|
txt.print(strptr)
|
||||||
|
txt.chrout('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
str[] logo_lines = [
|
||||||
|
"\uf10d\uf11a\uf139\uf11b \uf11a\uf13a\uf11b\n",
|
||||||
|
"\uf10b\uf11a▎\uf139\uf11b \uf11a\uf13a\uf130\uf11b\n",
|
||||||
|
"\uf10f\uf11a▌ \uf139\uf11b \uf11a\uf13a \uf11b▌\n",
|
||||||
|
"\uf102 \uf132\uf11a▖\uf11b \uf11a▗\uf11b\uf132\n",
|
||||||
|
"\uf10e ▂\uf11a▘\uf11b \uf11a▝\uf11b▂\n",
|
||||||
|
"\uf104 \uf11a \uf11b\uf13a\uf11b \uf139\uf11a \uf11b\n",
|
||||||
|
"\uf101\uf130\uf13a \uf139▎\uf100"
|
||||||
|
]
|
||||||
|
}
|
162
compiler/res/prog8lib/diskio.p8
Normal file
162
compiler/res/prog8lib/diskio.p8
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
%import textio
|
||||||
|
%import syslib
|
||||||
|
|
||||||
|
; Note: this code is compatible with C64 and CX16.
|
||||||
|
|
||||||
|
diskio {
|
||||||
|
|
||||||
|
|
||||||
|
sub directory(ubyte drivenumber) -> byte {
|
||||||
|
; -- Shows the directory contents of disk drive 8-11 (provide as argument).
|
||||||
|
|
||||||
|
c64.SETNAM(1, "$")
|
||||||
|
c64.SETLFS(1, drivenumber, 0)
|
||||||
|
void c64.OPEN() ; open 1,8,0,"$"
|
||||||
|
if_cs
|
||||||
|
goto io_error
|
||||||
|
void c64.CHKIN(1) ; use #1 as input channel
|
||||||
|
if_cs
|
||||||
|
goto io_error
|
||||||
|
|
||||||
|
repeat 4 {
|
||||||
|
void c64.CHRIN() ; skip the 4 prologue bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
; while not key pressed / EOF encountered, read data.
|
||||||
|
ubyte status = c64.READST()
|
||||||
|
while not status {
|
||||||
|
txt.print_uw(mkword(c64.CHRIN(), c64.CHRIN()))
|
||||||
|
txt.chrout(' ')
|
||||||
|
ubyte @zp char
|
||||||
|
do {
|
||||||
|
char = c64.CHRIN()
|
||||||
|
txt.chrout(char)
|
||||||
|
} until char==0
|
||||||
|
txt.chrout('\n')
|
||||||
|
void c64.CHRIN() ; skip 2 bytes
|
||||||
|
void c64.CHRIN()
|
||||||
|
status = c64.READST()
|
||||||
|
void c64.STOP()
|
||||||
|
if_nz
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
io_error:
|
||||||
|
status = c64.READST()
|
||||||
|
c64.CLRCHN() ; restore default i/o devices
|
||||||
|
c64.CLOSE(1)
|
||||||
|
|
||||||
|
if status and status != 64 { ; 64=end of file
|
||||||
|
txt.print("\ni/o error, status: ")
|
||||||
|
txt.print_ub(status)
|
||||||
|
txt.chrout('\n')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sub status(ubyte drivenumber) {
|
||||||
|
; -- display the disk drive's current status message
|
||||||
|
c64.SETNAM(0, $0000)
|
||||||
|
c64.SETLFS(15, drivenumber, 15)
|
||||||
|
void c64.OPEN() ; open 15,8,15
|
||||||
|
if_cs
|
||||||
|
goto io_error
|
||||||
|
void c64.CHKIN(15) ; use #15 as input channel
|
||||||
|
if_cs
|
||||||
|
goto io_error
|
||||||
|
|
||||||
|
while not c64.READST()
|
||||||
|
txt.chrout(c64.CHRIN())
|
||||||
|
|
||||||
|
io_error:
|
||||||
|
c64.CLRCHN() ; restore default i/o devices
|
||||||
|
c64.CLOSE(15)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sub save(ubyte drivenumber, uword filenameptr, uword address, uword size) -> byte {
|
||||||
|
c64.SETNAM(strlen(filenameptr), filenameptr)
|
||||||
|
c64.SETLFS(1, drivenumber, 0)
|
||||||
|
uword end_address = address + size
|
||||||
|
|
||||||
|
%asm {{
|
||||||
|
lda address
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
lda address+1
|
||||||
|
sta P8ZP_SCRATCH_W1+1
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
lda #<P8ZP_SCRATCH_W1
|
||||||
|
ldx end_address
|
||||||
|
ldy end_address+1
|
||||||
|
jsr c64.SAVE
|
||||||
|
php
|
||||||
|
ldx P8ZP_SCRATCH_REG
|
||||||
|
plp
|
||||||
|
}}
|
||||||
|
|
||||||
|
if_cc
|
||||||
|
return c64.READST()==0
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
sub load(ubyte drivenumber, uword filenameptr, uword address_override) -> uword {
|
||||||
|
c64.SETNAM(strlen(filenameptr), filenameptr)
|
||||||
|
ubyte secondary = 1
|
||||||
|
uword end_of_load = 0
|
||||||
|
if address_override
|
||||||
|
secondary = 0
|
||||||
|
c64.SETLFS(1, drivenumber, secondary)
|
||||||
|
%asm {{
|
||||||
|
stx P8ZP_SCRATCH_REG
|
||||||
|
lda #0
|
||||||
|
ldx address_override
|
||||||
|
ldy address_override+1
|
||||||
|
jsr c64.LOAD
|
||||||
|
bcs +
|
||||||
|
stx end_of_load
|
||||||
|
sty end_of_load+1
|
||||||
|
+ ldx P8ZP_SCRATCH_REG
|
||||||
|
}}
|
||||||
|
|
||||||
|
if end_of_load
|
||||||
|
return end_of_load - address_override
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
str filename = "0:??????????????????????????????????????"
|
||||||
|
|
||||||
|
sub delete(ubyte drivenumber, uword filenameptr) {
|
||||||
|
; -- delete a file on the drive
|
||||||
|
ubyte flen = strlen(filenameptr)
|
||||||
|
filename[0] = 's'
|
||||||
|
filename[1] = ':'
|
||||||
|
memcopy(filenameptr, &filename+2, flen+1)
|
||||||
|
c64.SETNAM(flen+2, filename)
|
||||||
|
c64.SETLFS(1, drivenumber, 15)
|
||||||
|
void c64.OPEN()
|
||||||
|
c64.CLRCHN()
|
||||||
|
c64.CLOSE(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
sub rename(ubyte drivenumber, uword oldfileptr, uword newfileptr) {
|
||||||
|
; -- rename a file on the drive
|
||||||
|
ubyte flen_old = strlen(oldfileptr)
|
||||||
|
ubyte flen_new = strlen(newfileptr)
|
||||||
|
filename[0] = 'r'
|
||||||
|
filename[1] = ':'
|
||||||
|
memcopy(newfileptr, &filename+2, flen_new)
|
||||||
|
filename[flen_new+2] = '='
|
||||||
|
memcopy(oldfileptr, &filename+3+flen_new, flen_old+1)
|
||||||
|
c64.SETNAM(3+flen_new+flen_old, filename)
|
||||||
|
c64.SETLFS(1, drivenumber, 15)
|
||||||
|
void c64.OPEN()
|
||||||
|
c64.CLRCHN()
|
||||||
|
c64.CLOSE(1)
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -4,8 +4,6 @@
|
|||||||
;
|
;
|
||||||
; indent format: TABS, size=8
|
; indent format: TABS, size=8
|
||||||
|
|
||||||
%import c64lib
|
|
||||||
|
|
||||||
math {
|
math {
|
||||||
%asminclude "library:math.asm", ""
|
%asminclude "library:math.asm", ""
|
||||||
}
|
}
|
||||||
|
1348
compiler/res/prog8lib/prog8_funcs.asm
Normal file
1348
compiler/res/prog8lib/prog8_funcs.asm
Normal file
File diff suppressed because it is too large
Load Diff
991
compiler/res/prog8lib/prog8_lib.asm
Normal file
991
compiler/res/prog8lib/prog8_lib.asm
Normal file
@ -0,0 +1,991 @@
|
|||||||
|
; Prog8 internal library routines - always included by the compiler
|
||||||
|
; Generic machine independent 6502 code.
|
||||||
|
;
|
||||||
|
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||||
|
;
|
||||||
|
; indent format: TABS, size=8
|
||||||
|
|
||||||
|
|
||||||
|
read_byte_from_address_on_stack .proc
|
||||||
|
; -- read the byte from the memory address on the top of the stack, return in A (stack remains unchanged)
|
||||||
|
lda P8ESTACK_LO+1,x
|
||||||
|
ldy P8ESTACK_HI+1,x
|
||||||
|
sta P8ZP_SCRATCH_W2
|
||||||
|
sty P8ZP_SCRATCH_W2+1
|
||||||
|
ldy #0
|
||||||
|
lda (P8ZP_SCRATCH_W2),y
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
write_byte_to_address_on_stack .proc
|
||||||
|
; -- write the byte in A to the memory address on the top of the stack (stack remains unchanged)
|
||||||
|
ldy P8ESTACK_LO+1,x
|
||||||
|
sty P8ZP_SCRATCH_W2
|
||||||
|
ldy P8ESTACK_HI+1,x
|
||||||
|
sty P8ZP_SCRATCH_W2+1
|
||||||
|
ldy #0
|
||||||
|
sta (P8ZP_SCRATCH_W2),y
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
neg_b .proc
|
||||||
|
lda #0
|
||||||
|
sec
|
||||||
|
sbc P8ESTACK_LO+1,x
|
||||||
|
sta P8ESTACK_LO+1,x
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
neg_w .proc
|
||||||
|
sec
|
||||||
|
lda #0
|
||||||
|
sbc P8ESTACK_LO+1,x
|
||||||
|
sta P8ESTACK_LO+1,x
|
||||||
|
lda #0
|
||||||
|
sbc P8ESTACK_HI+1,x
|
||||||
|
sta P8ESTACK_HI+1,x
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
inv_word .proc
|
||||||
|
lda P8ESTACK_LO+1,x
|
||||||
|
eor #255
|
||||||
|
sta P8ESTACK_LO+1,x
|
||||||
|
lda P8ESTACK_HI+1,x
|
||||||
|
eor #255
|
||||||
|
sta P8ESTACK_HI+1,x
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
not_byte .proc
|
||||||
|
lda P8ESTACK_LO+1,x
|
||||||
|
beq +
|
||||||
|
lda #1
|
||||||
|
+ eor #1
|
||||||
|
sta P8ESTACK_LO+1,x
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
not_word .proc
|
||||||
|
lda P8ESTACK_LO + 1,x
|
||||||
|
ora P8ESTACK_HI + 1,x
|
||||||
|
beq +
|
||||||
|
lda #1
|
||||||
|
+ eor #1
|
||||||
|
sta P8ESTACK_LO + 1,x
|
||||||
|
lsr a
|
||||||
|
sta P8ESTACK_HI + 1,x
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
bitand_b .proc
|
||||||
|
; -- bitwise and (of 2 bytes)
|
||||||
|
lda P8ESTACK_LO+2,x
|
||||||
|
and P8ESTACK_LO+1,x
|
||||||
|
inx
|
||||||
|
sta P8ESTACK_LO+1,x
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
bitor_b .proc
|
||||||
|
; -- bitwise or (of 2 bytes)
|
||||||
|
lda P8ESTACK_LO+2,x
|
||||||
|
ora P8ESTACK_LO+1,x
|
||||||
|
inx
|
||||||
|
sta P8ESTACK_LO+1,x
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
bitxor_b .proc
|
||||||
|
; -- bitwise xor (of 2 bytes)
|
||||||
|
lda P8ESTACK_LO+2,x
|
||||||
|
eor P8ESTACK_LO+1,x
|
||||||
|
inx
|
||||||
|
sta P8ESTACK_LO+1,x
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
bitand_w .proc
|
||||||
|
; -- bitwise and (of 2 words)
|
||||||
|
lda P8ESTACK_LO+2,x
|
||||||
|
and P8ESTACK_LO+1,x
|
||||||
|
sta P8ESTACK_LO+2,x
|
||||||
|
lda P8ESTACK_HI+2,x
|
||||||
|
and P8ESTACK_HI+1,x
|
||||||
|
sta P8ESTACK_HI+2,x
|
||||||
|
inx
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
bitor_w .proc
|
||||||
|
; -- bitwise or (of 2 words)
|
||||||
|
lda P8ESTACK_LO+2,x
|
||||||
|
ora P8ESTACK_LO+1,x
|
||||||
|
sta P8ESTACK_LO+2,x
|
||||||
|
lda P8ESTACK_HI+2,x
|
||||||
|
ora P8ESTACK_HI+1,x
|
||||||
|
sta P8ESTACK_HI+2,x
|
||||||
|
inx
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
bitxor_w .proc
|
||||||
|
; -- bitwise xor (of 2 bytes)
|
||||||
|
lda P8ESTACK_LO+2,x
|
||||||
|
eor P8ESTACK_LO+1,x
|
||||||
|
sta P8ESTACK_LO+2,x
|
||||||
|
lda P8ESTACK_HI+2,x
|
||||||
|
eor P8ESTACK_HI+1,x
|
||||||
|
sta P8ESTACK_HI+2,x
|
||||||
|
inx
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
and_b .proc
|
||||||
|
; -- logical and (of 2 bytes)
|
||||||
|
lda P8ESTACK_LO+2,x
|
||||||
|
beq +
|
||||||
|
lda #1
|
||||||
|
+ sta P8ZP_SCRATCH_B1
|
||||||
|
lda P8ESTACK_LO+1,x
|
||||||
|
beq +
|
||||||
|
lda #1
|
||||||
|
+ and P8ZP_SCRATCH_B1
|
||||||
|
inx
|
||||||
|
sta P8ESTACK_LO+1,x
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
or_b .proc
|
||||||
|
; -- logical or (of 2 bytes)
|
||||||
|
lda P8ESTACK_LO+2,x
|
||||||
|
ora P8ESTACK_LO+1,x
|
||||||
|
beq +
|
||||||
|
lda #1
|
||||||
|
+ inx
|
||||||
|
sta P8ESTACK_LO+1,x
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
xor_b .proc
|
||||||
|
; -- logical xor (of 2 bytes)
|
||||||
|
lda P8ESTACK_LO+2,x
|
||||||
|
beq +
|
||||||
|
lda #1
|
||||||
|
+ sta P8ZP_SCRATCH_B1
|
||||||
|
lda P8ESTACK_LO+1,x
|
||||||
|
beq +
|
||||||
|
lda #1
|
||||||
|
+ eor P8ZP_SCRATCH_B1
|
||||||
|
inx
|
||||||
|
sta P8ESTACK_LO+1,x
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
and_w .proc
|
||||||
|
; -- logical and (word and word -> byte)
|
||||||
|
lda P8ESTACK_LO+2,x
|
||||||
|
ora P8ESTACK_HI+2,x
|
||||||
|
beq +
|
||||||
|
lda #1
|
||||||
|
+ sta P8ZP_SCRATCH_B1
|
||||||
|
lda P8ESTACK_LO+1,x
|
||||||
|
ora P8ESTACK_HI+1,x
|
||||||
|
beq +
|
||||||
|
lda #1
|
||||||
|
+ and P8ZP_SCRATCH_B1
|
||||||
|
inx
|
||||||
|
sta P8ESTACK_LO+1,x
|
||||||
|
sta P8ESTACK_HI+1,x
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
or_w .proc
|
||||||
|
; -- logical or (word or word -> byte)
|
||||||
|
lda P8ESTACK_LO+2,x
|
||||||
|
ora P8ESTACK_LO+1,x
|
||||||
|
ora P8ESTACK_HI+2,x
|
||||||
|
ora P8ESTACK_HI+1,x
|
||||||
|
beq +
|
||||||
|
lda #1
|
||||||
|
+ inx
|
||||||
|
sta P8ESTACK_LO+1,x
|
||||||
|
sta P8ESTACK_HI+1,x
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
xor_w .proc
|
||||||
|
; -- logical xor (word xor word -> byte)
|
||||||
|
lda P8ESTACK_LO+2,x
|
||||||
|
ora P8ESTACK_HI+2,x
|
||||||
|
beq +
|
||||||
|
lda #1
|
||||||
|
+ sta P8ZP_SCRATCH_B1
|
||||||
|
lda P8ESTACK_LO+1,x
|
||||||
|
ora P8ESTACK_HI+1,x
|
||||||
|
beq +
|
||||||
|
lda #1
|
||||||
|
+ eor P8ZP_SCRATCH_B1
|
||||||
|
inx
|
||||||
|
sta P8ESTACK_LO+1,x
|
||||||
|
sta P8ESTACK_HI+1,x
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
add_w .proc
|
||||||
|
; -- push word+word / uword+uword
|
||||||
|
inx
|
||||||
|
clc
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
adc P8ESTACK_LO+1,x
|
||||||
|
sta P8ESTACK_LO+1,x
|
||||||
|
lda P8ESTACK_HI,x
|
||||||
|
adc P8ESTACK_HI+1,x
|
||||||
|
sta P8ESTACK_HI+1,x
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
sub_w .proc
|
||||||
|
; -- push word-word
|
||||||
|
inx
|
||||||
|
sec
|
||||||
|
lda P8ESTACK_LO+1,x
|
||||||
|
sbc P8ESTACK_LO,x
|
||||||
|
sta P8ESTACK_LO+1,x
|
||||||
|
lda P8ESTACK_HI+1,x
|
||||||
|
sbc P8ESTACK_HI,x
|
||||||
|
sta P8ESTACK_HI+1,x
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
mul_byte .proc
|
||||||
|
; -- b*b->b (signed and unsigned)
|
||||||
|
inx
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
ldy P8ESTACK_LO+1,x
|
||||||
|
jsr math.multiply_bytes
|
||||||
|
sta P8ESTACK_LO+1,x
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
mul_word .proc
|
||||||
|
inx
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
lda P8ESTACK_HI,x
|
||||||
|
sta P8ZP_SCRATCH_W1+1
|
||||||
|
lda P8ESTACK_LO+1,x
|
||||||
|
ldy P8ESTACK_HI+1,x
|
||||||
|
jsr math.multiply_words
|
||||||
|
lda math.multiply_words.result
|
||||||
|
sta P8ESTACK_LO+1,x
|
||||||
|
lda math.multiply_words.result+1
|
||||||
|
sta P8ESTACK_HI+1,x
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
idiv_b .proc
|
||||||
|
; signed division: use unsigned division and fix sign of result afterwards
|
||||||
|
inx
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
eor P8ESTACK_LO+1,x
|
||||||
|
php ; save sign of result
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
bpl +
|
||||||
|
eor #$ff
|
||||||
|
sec
|
||||||
|
adc #0 ; make num1 positive
|
||||||
|
+ tay
|
||||||
|
inx
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
bpl +
|
||||||
|
eor #$ff
|
||||||
|
sec
|
||||||
|
adc #0 ; make num2 positive
|
||||||
|
+ jsr math.divmod_ub_asm
|
||||||
|
sta _remainder
|
||||||
|
tya
|
||||||
|
plp ; get sign of result
|
||||||
|
bpl +
|
||||||
|
eor #$ff
|
||||||
|
sec
|
||||||
|
adc #0 ; negate result
|
||||||
|
+ sta P8ESTACK_LO,x
|
||||||
|
dex
|
||||||
|
rts
|
||||||
|
_remainder .byte 0
|
||||||
|
.pend
|
||||||
|
|
||||||
|
idiv_ub .proc
|
||||||
|
inx
|
||||||
|
ldy P8ESTACK_LO,x
|
||||||
|
lda P8ESTACK_LO+1,x
|
||||||
|
jsr math.divmod_ub_asm
|
||||||
|
tya
|
||||||
|
sta P8ESTACK_LO+1,x
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
idiv_w .proc
|
||||||
|
; signed division: use unsigned division and fix sign of result afterwards
|
||||||
|
lda P8ESTACK_HI+2,x
|
||||||
|
eor P8ESTACK_HI+1,x
|
||||||
|
php ; save sign of result
|
||||||
|
lda P8ESTACK_HI+1,x
|
||||||
|
bpl +
|
||||||
|
jsr neg_w ; make value positive
|
||||||
|
+ inx
|
||||||
|
lda P8ESTACK_HI+1,x
|
||||||
|
bpl +
|
||||||
|
jsr neg_w ; make value positive
|
||||||
|
+ lda P8ESTACK_LO+1,x
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
lda P8ESTACK_HI+1,x
|
||||||
|
sta P8ZP_SCRATCH_W1+1
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
ldy P8ESTACK_HI,x
|
||||||
|
jsr math.divmod_uw_asm
|
||||||
|
sta P8ESTACK_LO+1,x
|
||||||
|
tya
|
||||||
|
sta P8ESTACK_HI+1,x
|
||||||
|
plp
|
||||||
|
bpl +
|
||||||
|
jmp neg_w ; negate result
|
||||||
|
+ rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
idiv_uw .proc
|
||||||
|
inx
|
||||||
|
lda P8ESTACK_LO+1,x
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
lda P8ESTACK_HI+1,x
|
||||||
|
sta P8ZP_SCRATCH_W1+1
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
ldy P8ESTACK_HI,x
|
||||||
|
jsr math.divmod_uw_asm
|
||||||
|
sta P8ESTACK_LO+1,x
|
||||||
|
tya
|
||||||
|
sta P8ESTACK_HI+1,x
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
remainder_ub .proc
|
||||||
|
inx
|
||||||
|
ldy P8ESTACK_LO,x ; right operand
|
||||||
|
lda P8ESTACK_LO+1,x ; left operand
|
||||||
|
jsr math.divmod_ub_asm
|
||||||
|
sta P8ESTACK_LO+1,x
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
remainder_uw .proc
|
||||||
|
inx
|
||||||
|
lda P8ESTACK_LO+1,x
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
lda P8ESTACK_HI+1,x
|
||||||
|
sta P8ZP_SCRATCH_W1+1
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
ldy P8ESTACK_HI,x
|
||||||
|
jsr math.divmod_uw_asm
|
||||||
|
lda P8ZP_SCRATCH_W2
|
||||||
|
sta P8ESTACK_LO+1,x
|
||||||
|
lda P8ZP_SCRATCH_W2+1
|
||||||
|
sta P8ESTACK_HI+1,x
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
equal_w .proc
|
||||||
|
; -- are the two words on the stack identical?
|
||||||
|
lda P8ESTACK_LO+1,x
|
||||||
|
cmp P8ESTACK_LO+2,x
|
||||||
|
bne equal_b._equal_b_false
|
||||||
|
lda P8ESTACK_HI+1,x
|
||||||
|
cmp P8ESTACK_HI+2,x
|
||||||
|
bne equal_b._equal_b_false
|
||||||
|
beq equal_b._equal_b_true
|
||||||
|
.pend
|
||||||
|
|
||||||
|
notequal_b .proc
|
||||||
|
; -- are the two bytes on the stack different?
|
||||||
|
lda P8ESTACK_LO+1,x
|
||||||
|
cmp P8ESTACK_LO+2,x
|
||||||
|
beq equal_b._equal_b_false
|
||||||
|
bne equal_b._equal_b_true
|
||||||
|
.pend
|
||||||
|
|
||||||
|
notequal_w .proc
|
||||||
|
; -- are the two words on the stack different?
|
||||||
|
lda P8ESTACK_HI+1,x
|
||||||
|
cmp P8ESTACK_HI+2,x
|
||||||
|
beq notequal_b
|
||||||
|
bne equal_b._equal_b_true
|
||||||
|
.pend
|
||||||
|
|
||||||
|
less_ub .proc
|
||||||
|
lda P8ESTACK_LO+2,x
|
||||||
|
cmp P8ESTACK_LO+1,x
|
||||||
|
bcc equal_b._equal_b_true
|
||||||
|
bcs equal_b._equal_b_false
|
||||||
|
.pend
|
||||||
|
|
||||||
|
less_b .proc
|
||||||
|
; see http://www.6502.org/tutorials/compare_beyond.html
|
||||||
|
lda P8ESTACK_LO+2,x
|
||||||
|
sec
|
||||||
|
sbc P8ESTACK_LO+1,x
|
||||||
|
bvc +
|
||||||
|
eor #$80
|
||||||
|
+ bmi equal_b._equal_b_true
|
||||||
|
bpl equal_b._equal_b_false
|
||||||
|
.pend
|
||||||
|
|
||||||
|
less_uw .proc
|
||||||
|
lda P8ESTACK_HI+2,x
|
||||||
|
cmp P8ESTACK_HI+1,x
|
||||||
|
bcc equal_b._equal_b_true
|
||||||
|
bne equal_b._equal_b_false
|
||||||
|
lda P8ESTACK_LO+2,x
|
||||||
|
cmp P8ESTACK_LO+1,x
|
||||||
|
bcc equal_b._equal_b_true
|
||||||
|
bcs equal_b._equal_b_false
|
||||||
|
.pend
|
||||||
|
|
||||||
|
less_w .proc
|
||||||
|
lda P8ESTACK_LO+2,x
|
||||||
|
cmp P8ESTACK_LO+1,x
|
||||||
|
lda P8ESTACK_HI+2,x
|
||||||
|
sbc P8ESTACK_HI+1,x
|
||||||
|
bvc +
|
||||||
|
eor #$80
|
||||||
|
+ bmi equal_b._equal_b_true
|
||||||
|
bpl equal_b._equal_b_false
|
||||||
|
.pend
|
||||||
|
|
||||||
|
equal_b .proc
|
||||||
|
; -- are the two bytes on the stack identical?
|
||||||
|
lda P8ESTACK_LO+2,x
|
||||||
|
cmp P8ESTACK_LO+1,x
|
||||||
|
bne _equal_b_false
|
||||||
|
_equal_b_true lda #1
|
||||||
|
_equal_b_store inx
|
||||||
|
sta P8ESTACK_LO+1,x
|
||||||
|
rts
|
||||||
|
_equal_b_false lda #0
|
||||||
|
beq _equal_b_store
|
||||||
|
.pend
|
||||||
|
|
||||||
|
lesseq_ub .proc
|
||||||
|
lda P8ESTACK_LO+1,x
|
||||||
|
cmp P8ESTACK_LO+2,x
|
||||||
|
bcs equal_b._equal_b_true
|
||||||
|
bcc equal_b._equal_b_false
|
||||||
|
.pend
|
||||||
|
|
||||||
|
lesseq_b .proc
|
||||||
|
; see http://www.6502.org/tutorials/compare_beyond.html
|
||||||
|
lda P8ESTACK_LO+2,x
|
||||||
|
clc
|
||||||
|
sbc P8ESTACK_LO+1,x
|
||||||
|
bvc +
|
||||||
|
eor #$80
|
||||||
|
+ bmi equal_b._equal_b_true
|
||||||
|
bpl equal_b._equal_b_false
|
||||||
|
.pend
|
||||||
|
|
||||||
|
lesseq_uw .proc
|
||||||
|
lda P8ESTACK_HI+1,x
|
||||||
|
cmp P8ESTACK_HI+2,x
|
||||||
|
bcc equal_b._equal_b_false
|
||||||
|
bne equal_b._equal_b_true
|
||||||
|
lda P8ESTACK_LO+1,x
|
||||||
|
cmp P8ESTACK_LO+2,x
|
||||||
|
bcs equal_b._equal_b_true
|
||||||
|
bcc equal_b._equal_b_false
|
||||||
|
.pend
|
||||||
|
|
||||||
|
lesseq_w .proc
|
||||||
|
lda P8ESTACK_LO+1,x
|
||||||
|
cmp P8ESTACK_LO+2,x
|
||||||
|
lda P8ESTACK_HI+1,x
|
||||||
|
sbc P8ESTACK_HI+2,x
|
||||||
|
bvc +
|
||||||
|
eor #$80
|
||||||
|
+ bpl equal_b._equal_b_true
|
||||||
|
bmi equal_b._equal_b_false
|
||||||
|
.pend
|
||||||
|
|
||||||
|
greater_ub .proc
|
||||||
|
lda P8ESTACK_LO+2,x
|
||||||
|
cmp P8ESTACK_LO+1,x
|
||||||
|
beq equal_b._equal_b_false
|
||||||
|
bcs equal_b._equal_b_true
|
||||||
|
bcc equal_b._equal_b_false
|
||||||
|
.pend
|
||||||
|
|
||||||
|
greater_b .proc
|
||||||
|
; see http://www.6502.org/tutorials/compare_beyond.html
|
||||||
|
lda P8ESTACK_LO+2,x
|
||||||
|
clc
|
||||||
|
sbc P8ESTACK_LO+1,x
|
||||||
|
bvc +
|
||||||
|
eor #$80
|
||||||
|
+ bpl equal_b._equal_b_true
|
||||||
|
bmi equal_b._equal_b_false
|
||||||
|
.pend
|
||||||
|
|
||||||
|
greater_uw .proc
|
||||||
|
lda P8ESTACK_HI+1,x
|
||||||
|
cmp P8ESTACK_HI+2,x
|
||||||
|
bcc equal_b._equal_b_true
|
||||||
|
bne equal_b._equal_b_false
|
||||||
|
lda P8ESTACK_LO+1,x
|
||||||
|
cmp P8ESTACK_LO+2,x
|
||||||
|
bcc equal_b._equal_b_true
|
||||||
|
bcs equal_b._equal_b_false
|
||||||
|
.pend
|
||||||
|
|
||||||
|
greater_w .proc
|
||||||
|
lda P8ESTACK_LO+1,x
|
||||||
|
cmp P8ESTACK_LO+2,x
|
||||||
|
lda P8ESTACK_HI+1,x
|
||||||
|
sbc P8ESTACK_HI+2,x
|
||||||
|
bvc +
|
||||||
|
eor #$80
|
||||||
|
+ bmi equal_b._equal_b_true
|
||||||
|
bpl equal_b._equal_b_false
|
||||||
|
.pend
|
||||||
|
|
||||||
|
greatereq_ub .proc
|
||||||
|
lda P8ESTACK_LO+2,x
|
||||||
|
cmp P8ESTACK_LO+1,x
|
||||||
|
bcs equal_b._equal_b_true
|
||||||
|
bcc equal_b._equal_b_false
|
||||||
|
.pend
|
||||||
|
|
||||||
|
greatereq_b .proc
|
||||||
|
; see http://www.6502.org/tutorials/compare_beyond.html
|
||||||
|
lda P8ESTACK_LO+2,x
|
||||||
|
sec
|
||||||
|
sbc P8ESTACK_LO+1,x
|
||||||
|
bvc +
|
||||||
|
eor #$80
|
||||||
|
+ bpl equal_b._equal_b_true
|
||||||
|
bmi equal_b._equal_b_false
|
||||||
|
.pend
|
||||||
|
|
||||||
|
greatereq_uw .proc
|
||||||
|
lda P8ESTACK_HI+2,x
|
||||||
|
cmp P8ESTACK_HI+1,x
|
||||||
|
bcc equal_b._equal_b_false
|
||||||
|
bne equal_b._equal_b_true
|
||||||
|
lda P8ESTACK_LO+2,x
|
||||||
|
cmp P8ESTACK_LO+1,x
|
||||||
|
bcs equal_b._equal_b_true
|
||||||
|
bcc equal_b._equal_b_false
|
||||||
|
.pend
|
||||||
|
|
||||||
|
greatereq_w .proc
|
||||||
|
lda P8ESTACK_LO+2,x
|
||||||
|
cmp P8ESTACK_LO+1,x
|
||||||
|
lda P8ESTACK_HI+2,x
|
||||||
|
sbc P8ESTACK_HI+1,x
|
||||||
|
bvc +
|
||||||
|
eor #$80
|
||||||
|
+ bpl equal_b._equal_b_true
|
||||||
|
bmi equal_b._equal_b_false
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
shiftleft_b .proc
|
||||||
|
inx
|
||||||
|
ldy P8ESTACK_LO,x
|
||||||
|
bne +
|
||||||
|
rts
|
||||||
|
+ lda P8ESTACK_LO+1,x
|
||||||
|
- asl a
|
||||||
|
dey
|
||||||
|
bne -
|
||||||
|
sta P8ESTACK_LO+1,x
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
shiftright_b .proc
|
||||||
|
inx
|
||||||
|
ldy P8ESTACK_LO,x
|
||||||
|
bne +
|
||||||
|
rts
|
||||||
|
+ lda P8ESTACK_LO+1,x
|
||||||
|
- lsr a
|
||||||
|
dey
|
||||||
|
bne -
|
||||||
|
sta P8ESTACK_LO+1,x
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
orig_stackpointer .byte 0 ; stores the Stack pointer register at program start
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
memcopy16_up .proc
|
||||||
|
; -- copy memory UP from (P8ZP_SCRATCH_W1) to (P8ZP_SCRATCH_W2) of length X/Y (16-bit, X=lo, Y=hi)
|
||||||
|
; clobbers register A,X,Y
|
||||||
|
source = P8ZP_SCRATCH_W1
|
||||||
|
dest = P8ZP_SCRATCH_W2
|
||||||
|
length = P8ZP_SCRATCH_B1 ; (and SCRATCH_ZPREG)
|
||||||
|
|
||||||
|
stx length
|
||||||
|
sty length+1
|
||||||
|
|
||||||
|
ldx length ; move low byte of length into X
|
||||||
|
bne + ; jump to start if X > 0
|
||||||
|
dec length ; subtract 1 from length
|
||||||
|
+ ldy #0 ; set Y to 0
|
||||||
|
- lda (source),y ; set A to whatever (source) points to offset by Y
|
||||||
|
sta (dest),y ; move A to location pointed to by (dest) offset by Y
|
||||||
|
iny ; increment Y
|
||||||
|
bne + ; if Y<>0 then (rolled over) then still moving bytes
|
||||||
|
inc source+1 ; increment hi byte of source
|
||||||
|
inc dest+1 ; increment hi byte of dest
|
||||||
|
+ dex ; decrement X (lo byte counter)
|
||||||
|
bne - ; if X<>0 then move another byte
|
||||||
|
dec length ; we've moved 255 bytes, dec length
|
||||||
|
bpl - ; if length is still positive go back and move more
|
||||||
|
rts ; done
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
memset .proc
|
||||||
|
; -- fill memory from (P8ZP_SCRATCH_W1), length XY, with value in A.
|
||||||
|
; clobbers X, Y
|
||||||
|
stx P8ZP_SCRATCH_B1
|
||||||
|
sty _save_reg
|
||||||
|
ldy #0
|
||||||
|
ldx _save_reg
|
||||||
|
beq _lastpage
|
||||||
|
|
||||||
|
_fullpage sta (P8ZP_SCRATCH_W1),y
|
||||||
|
iny
|
||||||
|
bne _fullpage
|
||||||
|
inc P8ZP_SCRATCH_W1+1 ; next page
|
||||||
|
dex
|
||||||
|
bne _fullpage
|
||||||
|
|
||||||
|
_lastpage ldy P8ZP_SCRATCH_B1
|
||||||
|
beq +
|
||||||
|
- dey
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
bne -
|
||||||
|
|
||||||
|
+ rts
|
||||||
|
_save_reg .byte 0
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
memsetw .proc
|
||||||
|
; -- fill memory from (P8ZP_SCRATCH_W1) number of words in P8ZP_SCRATCH_W2, with word value in AY.
|
||||||
|
; clobbers A, X, Y
|
||||||
|
sta _mod1+1 ; self-modify
|
||||||
|
sty _mod1b+1 ; self-modify
|
||||||
|
sta _mod2+1 ; self-modify
|
||||||
|
sty _mod2b+1 ; self-modify
|
||||||
|
ldx P8ZP_SCRATCH_W1
|
||||||
|
stx P8ZP_SCRATCH_B1
|
||||||
|
ldx P8ZP_SCRATCH_W1+1
|
||||||
|
inx
|
||||||
|
stx P8ZP_SCRATCH_REG ; second page
|
||||||
|
|
||||||
|
ldy #0
|
||||||
|
ldx P8ZP_SCRATCH_W2+1
|
||||||
|
beq _lastpage
|
||||||
|
|
||||||
|
_fullpage
|
||||||
|
_mod1 lda #0 ; self-modified
|
||||||
|
sta (P8ZP_SCRATCH_W1),y ; first page
|
||||||
|
sta (P8ZP_SCRATCH_B1),y ; second page
|
||||||
|
iny
|
||||||
|
_mod1b lda #0 ; self-modified
|
||||||
|
sta (P8ZP_SCRATCH_W1),y ; first page
|
||||||
|
sta (P8ZP_SCRATCH_B1),y ; second page
|
||||||
|
iny
|
||||||
|
bne _fullpage
|
||||||
|
inc P8ZP_SCRATCH_W1+1 ; next page pair
|
||||||
|
inc P8ZP_SCRATCH_W1+1 ; next page pair
|
||||||
|
inc P8ZP_SCRATCH_B1+1 ; next page pair
|
||||||
|
inc P8ZP_SCRATCH_B1+1 ; next page pair
|
||||||
|
dex
|
||||||
|
bne _fullpage
|
||||||
|
|
||||||
|
_lastpage ldx P8ZP_SCRATCH_W2
|
||||||
|
beq _done
|
||||||
|
|
||||||
|
ldy #0
|
||||||
|
-
|
||||||
|
_mod2 lda #0 ; self-modified
|
||||||
|
sta (P8ZP_SCRATCH_W1), y
|
||||||
|
inc P8ZP_SCRATCH_W1
|
||||||
|
bne _mod2b
|
||||||
|
inc P8ZP_SCRATCH_W1+1
|
||||||
|
_mod2b lda #0 ; self-modified
|
||||||
|
sta (P8ZP_SCRATCH_W1), y
|
||||||
|
inc P8ZP_SCRATCH_W1
|
||||||
|
bne +
|
||||||
|
inc P8ZP_SCRATCH_W1+1
|
||||||
|
+ dex
|
||||||
|
bne -
|
||||||
|
_done rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ror2_mem_ub .proc
|
||||||
|
; -- in-place 8-bit ror of byte at memory location in AY
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
ldy #0
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
lsr a
|
||||||
|
bcc +
|
||||||
|
ora #$80
|
||||||
|
+ sta (P8ZP_SCRATCH_W1),y
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
rol2_mem_ub .proc
|
||||||
|
; -- in-place 8-bit rol of byte at memory location in AY
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
ldy #0
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
cmp #$80
|
||||||
|
rol a
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
rol_array_ub .proc
|
||||||
|
; -- rol a ubyte in an array
|
||||||
|
lda _arg_target
|
||||||
|
ldy _arg_target+1
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
ldy _arg_index
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
rol a
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
rts
|
||||||
|
_arg_target .word 0
|
||||||
|
_arg_index .byte 0
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
ror_array_ub .proc
|
||||||
|
; -- ror a ubyte in an array
|
||||||
|
lda _arg_target
|
||||||
|
ldy _arg_target+1
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
ldy _arg_index
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
ror a
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
rts
|
||||||
|
_arg_target .word 0
|
||||||
|
_arg_index .byte 0
|
||||||
|
.pend
|
||||||
|
|
||||||
|
ror2_array_ub .proc
|
||||||
|
; -- ror2 (8-bit ror) a ubyte in an array
|
||||||
|
lda _arg_target
|
||||||
|
ldy _arg_target+1
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
ldy _arg_index
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
lsr a
|
||||||
|
bcc +
|
||||||
|
ora #$80
|
||||||
|
+ sta (P8ZP_SCRATCH_W1),y
|
||||||
|
rts
|
||||||
|
_arg_target .word 0
|
||||||
|
_arg_index .byte 0
|
||||||
|
.pend
|
||||||
|
|
||||||
|
rol2_array_ub .proc
|
||||||
|
; -- rol2 (8-bit rol) a ubyte in an array
|
||||||
|
lda _arg_target
|
||||||
|
ldy _arg_target+1
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
ldy _arg_index
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
cmp #$80
|
||||||
|
rol a
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
rts
|
||||||
|
_arg_target .word 0
|
||||||
|
_arg_index .byte 0
|
||||||
|
.pend
|
||||||
|
|
||||||
|
ror_array_uw .proc
|
||||||
|
; -- ror a uword in an array
|
||||||
|
php
|
||||||
|
lda _arg_target
|
||||||
|
ldy _arg_target+1
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
lda _arg_index
|
||||||
|
asl a
|
||||||
|
tay
|
||||||
|
iny
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
plp
|
||||||
|
ror a
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
dey
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
ror a
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
rts
|
||||||
|
_arg_target .word 0
|
||||||
|
_arg_index .byte 0
|
||||||
|
.pend
|
||||||
|
|
||||||
|
rol_array_uw .proc
|
||||||
|
; -- rol a uword in an array
|
||||||
|
php
|
||||||
|
lda _arg_target
|
||||||
|
ldy _arg_target+1
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
lda _arg_index
|
||||||
|
asl a
|
||||||
|
tay
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
plp
|
||||||
|
rol a
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
iny
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
rol a
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
rts
|
||||||
|
_arg_target .word 0
|
||||||
|
_arg_index .byte 0
|
||||||
|
.pend
|
||||||
|
|
||||||
|
rol2_array_uw .proc
|
||||||
|
; -- rol2 (16-bit rol) a uword in an array
|
||||||
|
lda _arg_target
|
||||||
|
ldy _arg_target+1
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
lda _arg_index
|
||||||
|
asl a
|
||||||
|
tay
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
asl a
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
iny
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
rol a
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
bcc +
|
||||||
|
dey
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
adc #0
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
+ rts
|
||||||
|
_arg_target .word 0
|
||||||
|
_arg_index .byte 0
|
||||||
|
.pend
|
||||||
|
|
||||||
|
ror2_array_uw .proc
|
||||||
|
; -- ror2 (16-bit ror) a uword in an array
|
||||||
|
lda _arg_target
|
||||||
|
ldy _arg_target+1
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
lda _arg_index
|
||||||
|
asl a
|
||||||
|
tay
|
||||||
|
iny
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
lsr a
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
dey
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
ror a
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
bcc +
|
||||||
|
iny
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
ora #$80
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
+ rts
|
||||||
|
_arg_target .word 0
|
||||||
|
_arg_index .byte 0
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
strcpy .proc
|
||||||
|
; copy a string (must be 0-terminated) from A/Y to (P8ZP_SCRATCH_W1)
|
||||||
|
; it is assumed the target string is large enough.
|
||||||
|
sta P8ZP_SCRATCH_W2
|
||||||
|
sty P8ZP_SCRATCH_W2+1
|
||||||
|
ldy #$ff
|
||||||
|
- iny
|
||||||
|
lda (P8ZP_SCRATCH_W2),y
|
||||||
|
sta (P8ZP_SCRATCH_W1),y
|
||||||
|
bne -
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
strcmp_expression .proc
|
||||||
|
; -- compare strings, result in A
|
||||||
|
lda _arg_s2
|
||||||
|
ldy _arg_s2+1
|
||||||
|
sta P8ZP_SCRATCH_W2
|
||||||
|
sty P8ZP_SCRATCH_W2+1
|
||||||
|
lda _arg_s1
|
||||||
|
ldy _arg_s1+1
|
||||||
|
jmp strcmp_mem
|
||||||
|
_arg_s1 .word 0
|
||||||
|
_arg_s2 .word 0
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
strcmp_mem .proc
|
||||||
|
; -- compares strings in s1 (AY) and s2 (P8ZP_SCRATCH_W2).
|
||||||
|
; Returns -1,0,1 in A, depeding on the ordering. Clobbers Y.
|
||||||
|
sta P8ZP_SCRATCH_W1
|
||||||
|
sty P8ZP_SCRATCH_W1+1
|
||||||
|
_loop ldy #0
|
||||||
|
lda (P8ZP_SCRATCH_W1),y
|
||||||
|
bne +
|
||||||
|
lda (P8ZP_SCRATCH_W2),y
|
||||||
|
bne _return_minusone
|
||||||
|
beq _return
|
||||||
|
+ lda (P8ZP_SCRATCH_W2),y
|
||||||
|
sec
|
||||||
|
sbc (P8ZP_SCRATCH_W1),y
|
||||||
|
bmi _return_one
|
||||||
|
bne _return_minusone
|
||||||
|
inc P8ZP_SCRATCH_W1
|
||||||
|
bne +
|
||||||
|
inc P8ZP_SCRATCH_W1+1
|
||||||
|
+ inc P8ZP_SCRATCH_W2
|
||||||
|
bne _loop
|
||||||
|
inc P8ZP_SCRATCH_W2+1
|
||||||
|
bne _loop
|
||||||
|
_return_one
|
||||||
|
lda #1
|
||||||
|
_return rts
|
||||||
|
_return_minusone
|
||||||
|
lda #-1
|
||||||
|
rts
|
||||||
|
.pend
|
@ -4,8 +4,7 @@
|
|||||||
;
|
;
|
||||||
; indent format: TABS, size=8
|
; indent format: TABS, size=8
|
||||||
|
|
||||||
%import c64lib
|
|
||||||
|
|
||||||
prog8_lib {
|
prog8_lib {
|
||||||
%asminclude "library:prog8lib.asm", ""
|
%asminclude "library:prog8_lib.asm", ""
|
||||||
|
%asminclude "library:prog8_funcs.asm", ""
|
||||||
}
|
}
|
File diff suppressed because it is too large
Load Diff
@ -1,2 +1 @@
|
|||||||
1.60
|
5.0
|
||||||
|
|
||||||
|
@ -1,24 +1,23 @@
|
|||||||
package prog8
|
package prog8
|
||||||
|
|
||||||
|
import kotlinx.cli.*
|
||||||
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.Cx16Target
|
||||||
|
import prog8.compiler.target.CompilationTarget
|
||||||
import prog8.parser.ParsingFailedError
|
import prog8.parser.ParsingFailedError
|
||||||
import prog8.vm.astvm.AstVm
|
|
||||||
import java.nio.file.FileSystems
|
import java.nio.file.FileSystems
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.nio.file.Paths
|
|
||||||
import java.nio.file.StandardWatchEventKinds
|
import java.nio.file.StandardWatchEventKinds
|
||||||
import java.util.*
|
import java.time.LocalDateTime
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
|
|
||||||
printSoftwareHeader("compiler")
|
printSoftwareHeader("compiler")
|
||||||
|
|
||||||
if (args.isEmpty())
|
|
||||||
usage()
|
|
||||||
compileMain(args)
|
compileMain(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,51 +28,49 @@ internal fun printSoftwareHeader(what: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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>) {
|
||||||
var emulatorToStart = ""
|
val cli = CommandLineInterface("prog8compiler")
|
||||||
var moduleFile = ""
|
val startEmulator by cli.flagArgument("-emu", "auto-start emulator after successful compilation")
|
||||||
var writeAssembly = true
|
val outputDir by cli.flagValueArgument("-out", "directory", "directory for output files instead of current directory", ".")
|
||||||
var optimize = true
|
val dontWriteAssembly by cli.flagArgument("-noasm", "don't create assembly code")
|
||||||
var launchAstVm = false
|
val dontOptimize by cli.flagArgument("-noopt", "don't perform any optimizations")
|
||||||
var watchMode = false
|
val watchMode by cli.flagArgument("-watch", "continuous compilation mode (watches for file changes), greatly increases compilation speed")
|
||||||
for (arg in args) {
|
val slowCodegenWarnings by cli.flagArgument("-slowwarn", "show debug warnings about slow/problematic assembly code generation")
|
||||||
if(arg=="-emu")
|
val compilationTarget by cli.flagValueArgument("-target", "compilertarget",
|
||||||
emulatorToStart = "x64"
|
"target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available", C64Target.name)
|
||||||
else if(arg=="-emu2")
|
val moduleFiles by cli.positionalArgumentsList("modules", "main module file(s) to compile", minArgs = 1)
|
||||||
emulatorToStart = "x64sc"
|
|
||||||
else if(arg=="-noasm")
|
try {
|
||||||
writeAssembly = false
|
cli.parse(args)
|
||||||
else if(arg=="-noopt")
|
} catch (e: Exception) {
|
||||||
optimize = false
|
exitProcess(1)
|
||||||
else if(arg=="-avm")
|
|
||||||
launchAstVm = true
|
|
||||||
else if(arg=="-watch")
|
|
||||||
watchMode = true
|
|
||||||
else if(!arg.startsWith("-"))
|
|
||||||
moduleFile = arg
|
|
||||||
else
|
|
||||||
usage()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(watchMode) {
|
val outputPath = pathFrom(outputDir)
|
||||||
if(moduleFile.isBlank())
|
if(!outputPath.toFile().isDirectory) {
|
||||||
usage()
|
System.err.println("Output path doesn't exist")
|
||||||
|
exitProcess(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if(watchMode && moduleFiles.size<=1) {
|
||||||
val watchservice = FileSystems.getDefault().newWatchService()
|
val watchservice = FileSystems.getDefault().newWatchService()
|
||||||
|
|
||||||
while(true) {
|
while(true) {
|
||||||
val filepath = Paths.get(moduleFile).normalize()
|
val filepath = pathFrom(moduleFiles.single()).normalize()
|
||||||
println("Continuous watch mode active. Main module: $filepath")
|
println("Continuous watch mode active. Main module: $filepath")
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val compilationResult = compileProgram(filepath, optimize, writeAssembly)
|
val compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, slowCodegenWarnings, compilationTarget, outputPath)
|
||||||
println("Imported files (now watching:)")
|
println("Imported files (now watching:)")
|
||||||
for (importedFile in compilationResult.importedFiles) {
|
for (importedFile in compilationResult.importedFiles) {
|
||||||
print(" ")
|
print(" ")
|
||||||
println(importedFile)
|
println(importedFile)
|
||||||
importedFile.parent.register(watchservice, StandardWatchEventKinds.ENTRY_MODIFY)
|
importedFile.parent.register(watchservice, StandardWatchEventKinds.ENTRY_MODIFY)
|
||||||
}
|
}
|
||||||
println("${Date()}: Waiting for file changes.")
|
println("[${LocalDateTime.now().withNano(0)}] Waiting for file changes.")
|
||||||
val event = watchservice.take()
|
val event = watchservice.take()
|
||||||
for(changed in event.pollEvents()) {
|
for(changed in event.pollEvents()) {
|
||||||
val changedPath = changed.context() as Path
|
val changedPath = changed.context() as Path
|
||||||
@ -87,51 +84,26 @@ private fun compileMain(args: Array<String>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if(moduleFile.isBlank())
|
for(filepathRaw in moduleFiles) {
|
||||||
usage()
|
val filepath = pathFrom(filepathRaw).normalize()
|
||||||
|
val compilationResult: CompilationResult
|
||||||
val filepath = Paths.get(moduleFile).normalize()
|
try {
|
||||||
val compilationResult: CompilationResult
|
compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, slowCodegenWarnings, compilationTarget, outputPath)
|
||||||
|
if(!compilationResult.success)
|
||||||
try {
|
exitProcess(1)
|
||||||
compilationResult = compileProgram(filepath, optimize, writeAssembly)
|
} catch (x: ParsingFailedError) {
|
||||||
if(!compilationResult.success)
|
|
||||||
exitProcess(1)
|
exitProcess(1)
|
||||||
} catch (x: ParsingFailedError) {
|
} catch (x: AstException) {
|
||||||
exitProcess(1)
|
exitProcess(1)
|
||||||
} catch (x: AstException) {
|
}
|
||||||
exitProcess(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (launchAstVm) {
|
if (startEmulator) {
|
||||||
println("\nLaunching AST-based vm...")
|
if (compilationResult.programName.isEmpty())
|
||||||
val vm = AstVm(compilationResult.programAst)
|
println("\nCan't start emulator because no program was assembled.")
|
||||||
vm.run()
|
else if(startEmulator) {
|
||||||
}
|
CompilationTarget.instance.machine.launchEmulator(compilationResult.programName)
|
||||||
|
}
|
||||||
if (emulatorToStart.isNotEmpty()) {
|
|
||||||
if (compilationResult.programName.isEmpty())
|
|
||||||
println("\nCan't start emulator because no program was assembled.")
|
|
||||||
else {
|
|
||||||
println("\nStarting C-64 emulator $emulatorToStart...")
|
|
||||||
val cmdline = listOf(emulatorToStart, "-silent", "-moncommands", "${compilationResult.programName}.vice-mon-list",
|
|
||||||
"-autostartprgmode", "1", "-autostart-warp", "-autostart", compilationResult.programName + ".prg")
|
|
||||||
val process = ProcessBuilder(cmdline).inheritIO().start()
|
|
||||||
process.waitFor()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun usage() {
|
|
||||||
System.err.println("Missing argument(s):")
|
|
||||||
System.err.println(" [-noasm] don't create assembly code")
|
|
||||||
System.err.println(" [-noopt] don't perform any optimizations")
|
|
||||||
System.err.println(" [-emu] auto-start the 'x64' C-64 emulator after successful compilation")
|
|
||||||
System.err.println(" [-emu2] auto-start the 'x64sc' C-64 emulator after successful compilation")
|
|
||||||
System.err.println(" [-avm] launch the prog8 ast-based virtual machine after compilation")
|
|
||||||
System.err.println(" [-watch] continuous compilation mode (watches for file changes)")
|
|
||||||
System.err.println(" modulefile main module file to compile")
|
|
||||||
exitProcess(1)
|
|
||||||
}
|
|
||||||
|
@ -3,7 +3,6 @@ package prog8.ast
|
|||||||
import prog8.ast.antlr.escape
|
import prog8.ast.antlr.escape
|
||||||
import prog8.ast.base.DataType
|
import prog8.ast.base.DataType
|
||||||
import prog8.ast.base.NumericDatatypes
|
import prog8.ast.base.NumericDatatypes
|
||||||
import prog8.ast.base.StringDatatypes
|
|
||||||
import prog8.ast.base.VarDeclType
|
import prog8.ast.base.VarDeclType
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.processing.IAstVisitor
|
import prog8.ast.processing.IAstVisitor
|
||||||
@ -54,12 +53,14 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(expr: BinaryExpression) {
|
override fun visit(expr: BinaryExpression) {
|
||||||
|
output("(")
|
||||||
expr.left.accept(this)
|
expr.left.accept(this)
|
||||||
if(expr.operator.any { it.isLetter() })
|
if(expr.operator.any { it.isLetter() })
|
||||||
output(" ${expr.operator} ")
|
output(" ${expr.operator} ")
|
||||||
else
|
else
|
||||||
output(expr.operator)
|
output(expr.operator)
|
||||||
expr.right.accept(this)
|
expr.right.accept(this)
|
||||||
|
output(")")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(directive: Directive) {
|
override fun visit(directive: Directive) {
|
||||||
@ -79,14 +80,14 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
|||||||
private fun datatypeString(dt: DataType): String {
|
private fun datatypeString(dt: DataType): String {
|
||||||
return when(dt) {
|
return when(dt) {
|
||||||
in NumericDatatypes -> dt.toString().toLowerCase()
|
in NumericDatatypes -> dt.toString().toLowerCase()
|
||||||
in StringDatatypes -> dt.toString().toLowerCase()
|
DataType.STR -> dt.toString().toLowerCase()
|
||||||
DataType.ARRAY_UB -> "ubyte["
|
DataType.ARRAY_UB -> "ubyte["
|
||||||
DataType.ARRAY_B -> "byte["
|
DataType.ARRAY_B -> "byte["
|
||||||
DataType.ARRAY_UW -> "uword["
|
DataType.ARRAY_UW -> "uword["
|
||||||
DataType.ARRAY_W -> "word["
|
DataType.ARRAY_W -> "word["
|
||||||
DataType.ARRAY_F -> "float["
|
DataType.ARRAY_F -> "float["
|
||||||
DataType.STRUCT -> "" // the name of the struct is enough
|
DataType.STRUCT -> "" // the name of the struct is enough
|
||||||
else -> "?????2"
|
else -> "?????"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,15 +104,25 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(decl: VarDecl) {
|
override fun visit(decl: VarDecl) {
|
||||||
|
|
||||||
|
// if the vardecl is a parameter of a subroutine, don't output it again
|
||||||
|
val paramNames = (decl.definingScope() as? Subroutine)?.parameters?.map { it.name }
|
||||||
|
if(paramNames!=null && decl.name in paramNames)
|
||||||
|
return
|
||||||
|
|
||||||
when(decl.type) {
|
when(decl.type) {
|
||||||
VarDeclType.VAR -> {}
|
VarDeclType.VAR -> {}
|
||||||
VarDeclType.CONST -> output("const ")
|
VarDeclType.CONST -> output("const ")
|
||||||
VarDeclType.MEMORY -> output("&")
|
VarDeclType.MEMORY -> output("&")
|
||||||
}
|
}
|
||||||
output(decl.struct?.name ?: "")
|
|
||||||
|
if(decl.datatype==DataType.STRUCT && decl.struct!=null)
|
||||||
|
output(decl.struct!!.name)
|
||||||
|
|
||||||
output(datatypeString(decl.datatype))
|
output(datatypeString(decl.datatype))
|
||||||
if(decl.arraysize!=null) {
|
if(decl.arraysize!=null) {
|
||||||
decl.arraysize!!.index.accept(this)
|
decl.arraysize!!.indexNum?.accept(this)
|
||||||
|
decl.arraysize!!.indexVar?.accept(this)
|
||||||
}
|
}
|
||||||
if(decl.isArray)
|
if(decl.isArray)
|
||||||
output("]")
|
output("]")
|
||||||
@ -132,10 +143,9 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
|||||||
for(param in subroutine.parameters.zip(subroutine.asmParameterRegisters)) {
|
for(param in subroutine.parameters.zip(subroutine.asmParameterRegisters)) {
|
||||||
val reg =
|
val reg =
|
||||||
when {
|
when {
|
||||||
param.second.stack -> "stack"
|
|
||||||
param.second.registerOrPair!=null -> param.second.registerOrPair.toString()
|
param.second.registerOrPair!=null -> param.second.registerOrPair.toString()
|
||||||
param.second.statusflag!=null -> param.second.statusflag.toString()
|
param.second.statusflag!=null -> param.second.statusflag.toString()
|
||||||
else -> "?????1"
|
else -> "?????"
|
||||||
}
|
}
|
||||||
output("${datatypeString(param.first.type)} ${param.first.name} @$reg")
|
output("${datatypeString(param.first.type)} ${param.first.name} @$reg")
|
||||||
if(param.first!==subroutine.parameters.last())
|
if(param.first!==subroutine.parameters.last())
|
||||||
@ -178,8 +188,6 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
|||||||
|
|
||||||
private fun outputStatements(statements: List<Statement>) {
|
private fun outputStatements(statements: List<Statement>) {
|
||||||
for(stmt in statements) {
|
for(stmt in statements) {
|
||||||
if(stmt is VarDecl && stmt.autogeneratedDontRemove)
|
|
||||||
continue // skip autogenerated decls (to avoid generating a newline)
|
|
||||||
outputi("")
|
outputi("")
|
||||||
stmt.accept(this)
|
stmt.accept(this)
|
||||||
output("\n")
|
output("\n")
|
||||||
@ -197,9 +205,9 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
|||||||
private fun printout(call: IFunctionCall) {
|
private fun printout(call: IFunctionCall) {
|
||||||
call.target.accept(this)
|
call.target.accept(this)
|
||||||
output("(")
|
output("(")
|
||||||
for(arg in call.arglist) {
|
for(arg in call.args) {
|
||||||
arg.accept(this)
|
arg.accept(this)
|
||||||
if(arg!==call.arglist.last())
|
if(arg!==call.args.last())
|
||||||
output(", ")
|
output(", ")
|
||||||
}
|
}
|
||||||
output(")")
|
output(")")
|
||||||
@ -284,20 +292,19 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(assignment: Assignment) {
|
override fun visit(assignment: Assignment) {
|
||||||
if(assignment is VariableInitializationAssignment) {
|
val binExpr = assignment.value as? BinaryExpression
|
||||||
val targetVar = assignment.target.identifier?.targetVarDecl(program.namespace)
|
if(binExpr!=null && binExpr.left isSameAs assignment.target
|
||||||
if(targetVar?.struct != null) {
|
&& binExpr.operator !in setOf("and", "or", "xor")
|
||||||
// skip STRUCT init assignments
|
&& binExpr.operator !in comparisonOperators) {
|
||||||
return
|
// we only support the inplace assignments of the form A = A <operator> <value>
|
||||||
}
|
assignment.target.accept(this)
|
||||||
}
|
output(" ${binExpr.operator}= ")
|
||||||
|
binExpr.right.accept(this)
|
||||||
assignment.target.accept(this)
|
} else {
|
||||||
if (assignment.aug_op != null)
|
assignment.target.accept(this)
|
||||||
output(" ${assignment.aug_op} ")
|
|
||||||
else
|
|
||||||
output(" = ")
|
output(" = ")
|
||||||
assignment.value.accept(this)
|
assignment.value.accept(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(postIncrDecr: PostIncrDecr) {
|
override fun visit(postIncrDecr: PostIncrDecr) {
|
||||||
@ -305,20 +312,13 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
|||||||
output(postIncrDecr.operator)
|
output(postIncrDecr.operator)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(contStmt: Continue) {
|
|
||||||
output("continue")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(breakStmt: Break) {
|
override fun visit(breakStmt: Break) {
|
||||||
output("break")
|
output("break")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(forLoop: ForLoop) {
|
override fun visit(forLoop: ForLoop) {
|
||||||
output("for ")
|
output("for ")
|
||||||
if(forLoop.loopRegister!=null)
|
forLoop.loopVar.accept(this)
|
||||||
output(forLoop.loopRegister.toString())
|
|
||||||
else
|
|
||||||
forLoop.loopVar!!.accept(this)
|
|
||||||
output(" in ")
|
output(" in ")
|
||||||
forLoop.iterable.accept(this)
|
forLoop.iterable.accept(this)
|
||||||
output(" ")
|
output(" ")
|
||||||
@ -334,9 +334,16 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
|||||||
|
|
||||||
override fun visit(repeatLoop: RepeatLoop) {
|
override fun visit(repeatLoop: RepeatLoop) {
|
||||||
output("repeat ")
|
output("repeat ")
|
||||||
|
repeatLoop.iterations?.accept(this)
|
||||||
|
output(" ")
|
||||||
repeatLoop.body.accept(this)
|
repeatLoop.body.accept(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(untilLoop: UntilLoop) {
|
||||||
|
output("do ")
|
||||||
|
untilLoop.body.accept(this)
|
||||||
output(" until ")
|
output(" until ")
|
||||||
repeatLoop.untilCondition.accept(this)
|
untilLoop.condition.accept(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(returnStmt: Return) {
|
override fun visit(returnStmt: Return) {
|
||||||
@ -345,19 +352,16 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
|
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
|
||||||
arrayIndexedExpression.identifier.accept(this)
|
arrayIndexedExpression.arrayvar.accept(this)
|
||||||
output("[")
|
output("[")
|
||||||
arrayIndexedExpression.arrayspec.index.accept(this)
|
arrayIndexedExpression.indexer.indexNum?.accept(this)
|
||||||
|
arrayIndexedExpression.indexer.indexVar?.accept(this)
|
||||||
output("]")
|
output("]")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(assignTarget: AssignTarget) {
|
override fun visit(assignTarget: AssignTarget) {
|
||||||
if(assignTarget.register!=null)
|
assignTarget.memoryAddress?.accept(this)
|
||||||
output(assignTarget.register.toString())
|
assignTarget.identifier?.accept(this)
|
||||||
else {
|
|
||||||
assignTarget.memoryAddress?.accept(this)
|
|
||||||
assignTarget.identifier?.accept(this)
|
|
||||||
}
|
|
||||||
assignTarget.arrayindexed?.accept(this)
|
assignTarget.arrayindexed?.accept(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -398,10 +402,6 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
|||||||
outputlni("}}")
|
outputlni("}}")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(registerExpr: RegisterExpr) {
|
|
||||||
output(registerExpr.register.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder) {
|
override fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder) {
|
||||||
output(builtinFunctionStatementPlaceholder.name)
|
output(builtinFunctionStatementPlaceholder.name)
|
||||||
}
|
}
|
||||||
@ -435,12 +435,4 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
|||||||
whenChoice.statements.accept(this)
|
whenChoice.statements.accept(this)
|
||||||
outputln("")
|
outputln("")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(structLv: StructLiteralValue) {
|
|
||||||
outputListMembers(structLv.values.asSequence(), '{', '}')
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(nopStatement: NopStatement) {
|
|
||||||
output("; NOP @ ${nopStatement.position} $nopStatement")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,9 @@ package prog8.ast
|
|||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
import prog8.ast.expressions.Expression
|
import prog8.ast.expressions.Expression
|
||||||
import prog8.ast.expressions.IdentifierReference
|
import prog8.ast.expressions.IdentifierReference
|
||||||
|
import prog8.ast.processing.AstWalker
|
||||||
|
import prog8.ast.processing.IAstVisitor
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.compiler.HeapValues
|
|
||||||
import prog8.functions.BuiltinFunctions
|
import prog8.functions.BuiltinFunctions
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
@ -34,13 +35,28 @@ interface Node {
|
|||||||
return this
|
return this
|
||||||
throw FatalAstException("scope missing from $this")
|
throw FatalAstException("scope missing from $this")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun definingBlock(): Block {
|
||||||
|
if(this is Block)
|
||||||
|
return this
|
||||||
|
return findParentNode<Block>(this)!!
|
||||||
|
}
|
||||||
|
|
||||||
|
fun containingStatement(): Statement {
|
||||||
|
if(this is Statement)
|
||||||
|
return this
|
||||||
|
return findParentNode<Statement>(this)!!
|
||||||
|
}
|
||||||
|
|
||||||
|
fun replaceChildNode(node: Node, replacement: Node)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IFunctionCall {
|
interface IFunctionCall {
|
||||||
var target: IdentifierReference
|
var target: IdentifierReference
|
||||||
var arglist: MutableList<Expression>
|
var args: MutableList<Expression>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
interface INameScope {
|
interface INameScope {
|
||||||
val name: String
|
val name: String
|
||||||
val position: Position
|
val position: Position
|
||||||
@ -49,32 +65,31 @@ interface INameScope {
|
|||||||
|
|
||||||
fun linkParents(parent: Node)
|
fun linkParents(parent: Node)
|
||||||
|
|
||||||
fun subScopes(): Map<String, INameScope> {
|
fun subScope(name: String): INameScope? {
|
||||||
val subscopes = mutableMapOf<String, INameScope>()
|
|
||||||
for(stmt in statements) {
|
for(stmt in statements) {
|
||||||
when(stmt) {
|
when(stmt) {
|
||||||
// NOTE: if other nodes are introduced that are a scope, or contain subscopes, they must be added here!
|
// NOTE: if other nodes are introduced that are a scope, or contain subscopes, they must be added here!
|
||||||
is ForLoop -> subscopes[stmt.body.name] = stmt.body
|
is ForLoop -> if(stmt.body.name==name) return stmt.body
|
||||||
is RepeatLoop -> subscopes[stmt.body.name] = stmt.body
|
is UntilLoop -> if(stmt.body.name==name) return stmt.body
|
||||||
is WhileLoop -> subscopes[stmt.body.name] = stmt.body
|
is WhileLoop -> if(stmt.body.name==name) return stmt.body
|
||||||
is BranchStatement -> {
|
is BranchStatement -> {
|
||||||
subscopes[stmt.truepart.name] = stmt.truepart
|
if(stmt.truepart.name==name) return stmt.truepart
|
||||||
if(stmt.elsepart.containsCodeOrVars())
|
if(stmt.elsepart.containsCodeOrVars() && stmt.elsepart.name==name) return stmt.elsepart
|
||||||
subscopes[stmt.elsepart.name] = stmt.elsepart
|
|
||||||
}
|
}
|
||||||
is IfStatement -> {
|
is IfStatement -> {
|
||||||
subscopes[stmt.truepart.name] = stmt.truepart
|
if(stmt.truepart.name==name) return stmt.truepart
|
||||||
if(stmt.elsepart.containsCodeOrVars())
|
if(stmt.elsepart.containsCodeOrVars() && stmt.elsepart.name==name) return stmt.elsepart
|
||||||
subscopes[stmt.elsepart.name] = stmt.elsepart
|
|
||||||
}
|
}
|
||||||
is WhenStatement -> {
|
is WhenStatement -> {
|
||||||
stmt.choices.forEach { subscopes[it.statements.name] = it.statements }
|
val scope = stmt.choices.firstOrNull { it.statements.name==name }
|
||||||
|
if(scope!=null)
|
||||||
|
return scope.statements
|
||||||
}
|
}
|
||||||
is INameScope -> subscopes[stmt.name] = stmt
|
is INameScope -> if(stmt.name==name) return stmt
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return subscopes
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLabelOrVariable(name: String): Statement? {
|
fun getLabelOrVariable(name: String): Statement? {
|
||||||
@ -122,7 +137,7 @@ interface INameScope {
|
|||||||
for(module in localContext.definingModule().program.modules) {
|
for(module in localContext.definingModule().program.modules) {
|
||||||
var scope: INameScope? = module
|
var scope: INameScope? = module
|
||||||
for(name in scopedName.dropLast(1)) {
|
for(name in scopedName.dropLast(1)) {
|
||||||
scope = scope?.subScopes()?.get(name)
|
scope = scope?.subScope(name)
|
||||||
if(scope==null)
|
if(scope==null)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -130,19 +145,27 @@ interface INameScope {
|
|||||||
val result = scope.getLabelOrVariable(scopedName.last())
|
val result = scope.getLabelOrVariable(scopedName.last())
|
||||||
if(result!=null)
|
if(result!=null)
|
||||||
return result
|
return result
|
||||||
return scope.subScopes()[scopedName.last()] as Statement?
|
return scope.subScope(scopedName.last()) as Statement?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
} else {
|
} else {
|
||||||
// unqualified name, find the scope the localContext is in, look in that first
|
// unqualified name
|
||||||
|
// special case: the do....until statement can also look INSIDE the anonymous scope
|
||||||
|
if(localContext.parent.parent is UntilLoop) {
|
||||||
|
val symbolFromInnerScope = (localContext.parent.parent as UntilLoop).body.getLabelOrVariable(scopedName[0])
|
||||||
|
if(symbolFromInnerScope!=null)
|
||||||
|
return symbolFromInnerScope
|
||||||
|
}
|
||||||
|
|
||||||
|
// find the scope the localContext is in, look in that first
|
||||||
var statementScope = localContext
|
var statementScope = localContext
|
||||||
while(statementScope !is ParentSentinel) {
|
while(statementScope !is ParentSentinel) {
|
||||||
val localScope = statementScope.definingScope()
|
val localScope = statementScope.definingScope()
|
||||||
val result = localScope.getLabelOrVariable(scopedName[0])
|
val result = localScope.getLabelOrVariable(scopedName[0])
|
||||||
if (result != null)
|
if (result != null)
|
||||||
return result
|
return result
|
||||||
val subscope = localScope.subScopes()[scopedName[0]] as Statement?
|
val subscope = localScope.subScope(scopedName[0]) as Statement?
|
||||||
if (subscope != null)
|
if (subscope != null)
|
||||||
return subscope
|
return subscope
|
||||||
// not found in this scope, look one higher up
|
// not found in this scope, look one higher up
|
||||||
@ -159,14 +182,58 @@ interface INameScope {
|
|||||||
if(!statements.remove(stmt))
|
if(!statements.remove(stmt))
|
||||||
throw FatalAstException("stmt to remove wasn't found in scope")
|
throw FatalAstException("stmt to remove wasn't found in scope")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getAllLabels(label: String): List<Label> {
|
||||||
|
val result = mutableListOf<Label>()
|
||||||
|
|
||||||
|
fun find(scope: INameScope) {
|
||||||
|
scope.statements.forEach {
|
||||||
|
when(it) {
|
||||||
|
is Label -> result.add(it)
|
||||||
|
is INameScope -> find(it)
|
||||||
|
is IfStatement -> {
|
||||||
|
find(it.truepart)
|
||||||
|
find(it.elsepart)
|
||||||
|
}
|
||||||
|
is UntilLoop -> find(it.body)
|
||||||
|
is RepeatLoop -> find(it.body)
|
||||||
|
is WhileLoop -> find(it.body)
|
||||||
|
is WhenStatement -> it.choices.forEach { choice->find(choice.statements) }
|
||||||
|
else -> { /* do nothing */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
find(this)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun nextSibling(stmt: Statement): Statement? {
|
||||||
|
val nextIdx = statements.indexOfFirst { it===stmt } + 1
|
||||||
|
return if(nextIdx < statements.size)
|
||||||
|
statements[nextIdx]
|
||||||
|
else
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun indexOfChild(stmt: Statement): Int {
|
||||||
|
val idx = statements.indexOfFirst { it===stmt }
|
||||||
|
if(idx>=0)
|
||||||
|
return idx
|
||||||
|
else
|
||||||
|
throw FatalAstException("attempt to find a non-child")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IAssignable {
|
||||||
|
// just a tag for now
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*********** Everything starts from here, the Program; zero or more modules *************/
|
/*********** Everything starts from here, the Program; zero or more modules *************/
|
||||||
|
|
||||||
class Program(val name: String, val modules: MutableList<Module>) {
|
class Program(val name: String, val modules: MutableList<Module>): Node {
|
||||||
val namespace = GlobalNamespace(modules)
|
val namespace = GlobalNamespace(modules)
|
||||||
val heap = HeapValues()
|
|
||||||
|
|
||||||
val definedLoadAddress: Int
|
val definedLoadAddress: Int
|
||||||
get() = modules.first().loadAddress
|
get() = modules.first().loadAddress
|
||||||
@ -174,17 +241,35 @@ class Program(val name: String, val modules: MutableList<Module>) {
|
|||||||
var actualLoadAddress: Int = 0
|
var actualLoadAddress: Int = 0
|
||||||
|
|
||||||
fun entrypoint(): Subroutine? {
|
fun entrypoint(): Subroutine? {
|
||||||
val mainBlocks = modules.flatMap { it.statements }.filter { b -> b is Block && b.name=="main" }.map { it as Block }
|
val mainBlocks = allBlocks().filter { it.name=="main" }
|
||||||
if(mainBlocks.size > 1)
|
if(mainBlocks.size > 1)
|
||||||
throw FatalAstException("more than one 'main' block")
|
throw FatalAstException("more than one 'main' block")
|
||||||
return if(mainBlocks.isEmpty()) {
|
return if(mainBlocks.isEmpty()) {
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
mainBlocks[0].subScopes()["start"] as Subroutine?
|
mainBlocks[0].subScope("start") as Subroutine?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun allBlocks(): List<Block> = modules.flatMap { it.statements.filterIsInstance<Block>() }
|
fun allBlocks(): List<Block> = modules.flatMap { it.statements.filterIsInstance<Block>() }
|
||||||
|
|
||||||
|
override val position: Position = Position.DUMMY
|
||||||
|
override var parent: Node
|
||||||
|
get() = throw FatalAstException("program has no parent")
|
||||||
|
set(_) = throw FatalAstException("can't set parent of program")
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
modules.forEach {
|
||||||
|
it.linkParents(namespace)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
require(node is Module && replacement is Module)
|
||||||
|
val idx = modules.indexOfFirst { it===node }
|
||||||
|
modules[idx] = replacement
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Module(override val name: String,
|
class Module(override val name: String,
|
||||||
@ -192,12 +277,16 @@ class Module(override val name: String,
|
|||||||
override val position: Position,
|
override val position: Position,
|
||||||
val isLibraryModule: Boolean,
|
val isLibraryModule: Boolean,
|
||||||
val source: Path) : Node, INameScope {
|
val source: Path) : Node, INameScope {
|
||||||
|
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
lateinit var program: Program
|
lateinit var program: Program
|
||||||
val importedBy = mutableListOf<Module>()
|
val importedBy = mutableListOf<Module>()
|
||||||
val imports = mutableSetOf<Module>()
|
val imports = mutableSetOf<Module>()
|
||||||
|
|
||||||
var loadAddress: Int = 0 // can be set with the %address directive
|
val loadAddress: Int by lazy {
|
||||||
|
val address = (statements.singleOrNull { it is Directive && it.directive == "%address" } as? Directive)?.args?.single()?.int ?: 0
|
||||||
|
address
|
||||||
|
}
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
this.parent = parent
|
this.parent = parent
|
||||||
@ -205,20 +294,34 @@ class Module(override val name: String,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun definingScope(): INameScope = program.namespace
|
override fun definingScope(): INameScope = program.namespace
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
require(node is Statement && replacement is Statement)
|
||||||
|
val idx = statements.indexOfFirst { it===node }
|
||||||
|
statements[idx] = replacement
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
override fun toString() = "Module(name=$name, pos=$position, lib=$isLibraryModule)"
|
override fun toString() = "Module(name=$name, pos=$position, lib=$isLibraryModule)"
|
||||||
|
|
||||||
|
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class GlobalNamespace(val modules: List<Module>): Node, INameScope {
|
class GlobalNamespace(val modules: List<Module>): Node, INameScope {
|
||||||
override val name = "<<<global>>>"
|
override val name = "<<<global>>>"
|
||||||
override val position = Position("<<<global>>>", 0, 0, 0)
|
override val position = Position("<<<global>>>", 0, 0, 0)
|
||||||
override val statements = mutableListOf<Statement>()
|
override val statements = mutableListOf<Statement>() // not used
|
||||||
override var parent: Node = ParentSentinel
|
override var parent: Node = ParentSentinel
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
modules.forEach { it.linkParents(this) }
|
modules.forEach { it.linkParents(this) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
throw FatalAstException("cannot replace anything in the namespace")
|
||||||
|
}
|
||||||
|
|
||||||
override fun lookup(scopedName: List<String>, localContext: Node): Statement? {
|
override fun lookup(scopedName: List<String>, localContext: Node): Statement? {
|
||||||
if (scopedName.size == 1 && scopedName[0] in BuiltinFunctions) {
|
if (scopedName.size == 1 && scopedName[0] in BuiltinFunctions) {
|
||||||
// builtin functions always exist, return a dummy localContext for them
|
// builtin functions always exist, return a dummy localContext for them
|
||||||
@ -240,12 +343,19 @@ class GlobalNamespace(val modules: List<Module>): Node, INameScope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// special case: the do....until statement can also look INSIDE the anonymous scope
|
||||||
|
if(localContext.parent.parent is UntilLoop) {
|
||||||
|
val symbolFromInnerScope = (localContext.parent.parent as UntilLoop).body.lookup(scopedName, localContext)
|
||||||
|
if(symbolFromInnerScope!=null)
|
||||||
|
return symbolFromInnerScope
|
||||||
|
}
|
||||||
|
|
||||||
// lookup something from the module.
|
// lookup something from the module.
|
||||||
val stmt = localContext.definingModule().lookup(scopedName, localContext)
|
return when (val stmt = localContext.definingModule().lookup(scopedName, localContext)) {
|
||||||
return when (stmt) {
|
is Label, is VarDecl, is Block, is Subroutine, is StructDecl -> stmt
|
||||||
is Label, is VarDecl, is Block, is Subroutine -> stmt
|
|
||||||
null -> null
|
null -> null
|
||||||
else -> throw NameError("wrong identifier target: $stmt", stmt.position)
|
else -> throw SyntaxError("invalid identifier target type", stmt.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ import prog8.ast.Module
|
|||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.compiler.target.c64.Petscii
|
import prog8.compiler.target.CompilationTarget
|
||||||
import prog8.parser.CustomLexer
|
import prog8.parser.CustomLexer
|
||||||
import prog8.parser.prog8Parser
|
import prog8.parser.prog8Parser
|
||||||
import java.io.CharConversionException
|
import java.io.CharConversionException
|
||||||
@ -19,18 +19,18 @@ import java.nio.file.Path
|
|||||||
|
|
||||||
private data class NumericLiteral(val number: Number, val datatype: DataType)
|
private data class NumericLiteral(val number: Number, val datatype: DataType)
|
||||||
|
|
||||||
|
internal fun prog8Parser.ModuleContext.toAst(name: String, isLibrary: Boolean, source: Path) : Module {
|
||||||
fun prog8Parser.ModuleContext.toAst(name: String, isLibrary: Boolean, source: Path) : Module {
|
|
||||||
val nameWithoutSuffix = if(name.endsWith(".p8")) name.substringBeforeLast('.') else name
|
val nameWithoutSuffix = if(name.endsWith(".p8")) name.substringBeforeLast('.') else name
|
||||||
return Module(nameWithoutSuffix, modulestatement().asSequence().map { it.toAst(isLibrary) }.toMutableList(), toPosition(), isLibrary, source)
|
val directives = this.directive().map { it.toAst() }
|
||||||
|
val blocks = this.block().map { it.toAst(isLibrary) }
|
||||||
|
return Module(nameWithoutSuffix, (directives + blocks).toMutableList(), toPosition(), isLibrary, source)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun ParserRuleContext.toPosition() : Position {
|
private fun ParserRuleContext.toPosition() : Position {
|
||||||
val customTokensource = this.start.tokenSource as? CustomLexer
|
val customTokensource = this.start.tokenSource as? CustomLexer
|
||||||
val filename =
|
val filename =
|
||||||
when {
|
when {
|
||||||
customTokensource!=null -> customTokensource.modulePath.fileName.toString()
|
customTokensource!=null -> customTokensource.modulePath.toString()
|
||||||
start.tokenSource.sourceName == IntStream.UNKNOWN_SOURCE_NAME -> "@internal@"
|
start.tokenSource.sourceName == IntStream.UNKNOWN_SOURCE_NAME -> "@internal@"
|
||||||
else -> File(start.inputStream.sourceName).name
|
else -> File(start.inputStream.sourceName).name
|
||||||
}
|
}
|
||||||
@ -38,27 +38,23 @@ private fun ParserRuleContext.toPosition() : Position {
|
|||||||
return Position(filename, start.line, start.charPositionInLine, stop.charPositionInLine + stop.text.length)
|
return Position(filename, start.line, start.charPositionInLine, stop.charPositionInLine + stop.text.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun prog8Parser.BlockContext.toAst(isInLibrary: Boolean) : Statement {
|
||||||
private fun prog8Parser.ModulestatementContext.toAst(isInLibrary: Boolean) : Statement {
|
val blockstatements = block_statement().map {
|
||||||
val directive = directive()?.toAst()
|
when {
|
||||||
if(directive!=null) return directive
|
it.variabledeclaration()!=null -> it.variabledeclaration().toAst()
|
||||||
|
it.subroutinedeclaration()!=null -> it.subroutinedeclaration().toAst()
|
||||||
val block = block()?.toAst(isInLibrary)
|
it.directive()!=null -> it.directive().toAst()
|
||||||
if(block!=null) return block
|
it.inlineasm()!=null -> it.inlineasm().toAst()
|
||||||
|
else -> throw FatalAstException("weird block statement $it")
|
||||||
throw FatalAstException(text)
|
}
|
||||||
|
}
|
||||||
|
return Block(identifier().text, integerliteral()?.toAst()?.number?.toInt(), blockstatements.toMutableList(), isInLibrary, toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.BlockContext.toAst(isInLibrary: Boolean) : Statement =
|
|
||||||
Block(identifier().text, integerliteral()?.toAst()?.number?.toInt(), statement_block().toAst(), isInLibrary, toPosition())
|
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.Statement_blockContext.toAst(): MutableList<Statement> =
|
private fun prog8Parser.Statement_blockContext.toAst(): MutableList<Statement> =
|
||||||
statement().asSequence().map { it.toAst() }.toMutableList()
|
statement().asSequence().map { it.toAst() }.toMutableList()
|
||||||
|
|
||||||
|
private fun prog8Parser.VariabledeclarationContext.toAst() : Statement {
|
||||||
private fun prog8Parser.StatementContext.toAst() : Statement {
|
|
||||||
vardecl()?.let { return it.toAst() }
|
vardecl()?.let { return it.toAst() }
|
||||||
|
|
||||||
varinitializer()?.let {
|
varinitializer()?.let {
|
||||||
@ -66,7 +62,7 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
|
|||||||
return VarDecl(
|
return VarDecl(
|
||||||
VarDeclType.VAR,
|
VarDeclType.VAR,
|
||||||
vd.datatype()?.toAst() ?: DataType.STRUCT,
|
vd.datatype()?.toAst() ?: DataType.STRUCT,
|
||||||
if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
||||||
vd.arrayindex()?.toAst(),
|
vd.arrayindex()?.toAst(),
|
||||||
vd.varname.text,
|
vd.varname.text,
|
||||||
null,
|
null,
|
||||||
@ -114,7 +110,7 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
|
|||||||
return VarDecl(
|
return VarDecl(
|
||||||
VarDeclType.CONST,
|
VarDeclType.CONST,
|
||||||
vd.datatype()?.toAst() ?: DataType.STRUCT,
|
vd.datatype()?.toAst() ?: DataType.STRUCT,
|
||||||
if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
||||||
vd.arrayindex()?.toAst(),
|
vd.arrayindex()?.toAst(),
|
||||||
vd.varname.text,
|
vd.varname.text,
|
||||||
null,
|
null,
|
||||||
@ -131,7 +127,7 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
|
|||||||
return VarDecl(
|
return VarDecl(
|
||||||
VarDeclType.MEMORY,
|
VarDeclType.MEMORY,
|
||||||
vd.datatype()?.toAst() ?: DataType.STRUCT,
|
vd.datatype()?.toAst() ?: DataType.STRUCT,
|
||||||
if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
||||||
vd.arrayindex()?.toAst(),
|
vd.arrayindex()?.toAst(),
|
||||||
vd.varname.text,
|
vd.varname.text,
|
||||||
null,
|
null,
|
||||||
@ -142,15 +138,38 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
structdecl()?.let {
|
||||||
|
return StructDecl(it.identifier().text,
|
||||||
|
it.vardecl().map { vd->vd.toAst() }.toMutableList(),
|
||||||
|
toPosition())
|
||||||
|
}
|
||||||
|
|
||||||
|
throw FatalAstException("weird variable decl $this")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prog8Parser.SubroutinedeclarationContext.toAst() : Subroutine {
|
||||||
|
return when {
|
||||||
|
subroutine()!=null -> subroutine().toAst()
|
||||||
|
asmsubroutine()!=null -> asmsubroutine().toAst()
|
||||||
|
romsubroutine()!=null -> romsubroutine().toAst()
|
||||||
|
else -> throw FatalAstException("weird subroutine decl $this")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prog8Parser.StatementContext.toAst() : Statement {
|
||||||
|
val vardecl = variabledeclaration()?.toAst()
|
||||||
|
if(vardecl!=null) return vardecl
|
||||||
|
|
||||||
assignment()?.let {
|
assignment()?.let {
|
||||||
return Assignment(it.assign_target().toAst(), null, it.expression().toAst(), it.toPosition())
|
return Assignment(it.assign_target().toAst(), it.expression().toAst(), it.toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
augassignment()?.let {
|
augassignment()?.let {
|
||||||
return Assignment(it.assign_target().toAst(),
|
// replace A += X with A = A + X
|
||||||
it.operator.text,
|
val target = it.assign_target().toAst()
|
||||||
it.expression().toAst(),
|
val oper = it.operator.text.substringBefore('=')
|
||||||
it.toPosition())
|
val expression = BinaryExpression(target.toExpression(), oper, it.expression().toAst(), it.expression().toPosition())
|
||||||
|
return Assignment(it.assign_target().toAst(), expression, it.toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
postincrdecr()?.let {
|
postincrdecr()?.let {
|
||||||
@ -175,8 +194,8 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
|
|||||||
val returnstmt = returnstmt()?.toAst()
|
val returnstmt = returnstmt()?.toAst()
|
||||||
if(returnstmt!=null) return returnstmt
|
if(returnstmt!=null) return returnstmt
|
||||||
|
|
||||||
val sub = subroutine()?.toAst()
|
val subroutine = subroutinedeclaration()?.toAst()
|
||||||
if(sub!=null) return sub
|
if(subroutine!=null) return subroutine
|
||||||
|
|
||||||
val asm = inlineasm()?.toAst()
|
val asm = inlineasm()?.toAst()
|
||||||
if(asm!=null) return asm
|
if(asm!=null) return asm
|
||||||
@ -187,91 +206,115 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
|
|||||||
val forloop = forloop()?.toAst()
|
val forloop = forloop()?.toAst()
|
||||||
if(forloop!=null) return forloop
|
if(forloop!=null) return forloop
|
||||||
|
|
||||||
val repeatloop = repeatloop()?.toAst()
|
val untilloop = untilloop()?.toAst()
|
||||||
if(repeatloop!=null) return repeatloop
|
if(untilloop!=null) return untilloop
|
||||||
|
|
||||||
val whileloop = whileloop()?.toAst()
|
val whileloop = whileloop()?.toAst()
|
||||||
if(whileloop!=null) return whileloop
|
if(whileloop!=null) return whileloop
|
||||||
|
|
||||||
|
val repeatloop = repeatloop()?.toAst()
|
||||||
|
if(repeatloop!=null) return repeatloop
|
||||||
|
|
||||||
val breakstmt = breakstmt()?.toAst()
|
val breakstmt = breakstmt()?.toAst()
|
||||||
if(breakstmt!=null) return breakstmt
|
if(breakstmt!=null) return breakstmt
|
||||||
|
|
||||||
val continuestmt = continuestmt()?.toAst()
|
|
||||||
if(continuestmt!=null) return continuestmt
|
|
||||||
|
|
||||||
val asmsubstmt = asmsubroutine()?.toAst()
|
|
||||||
if(asmsubstmt!=null) return asmsubstmt
|
|
||||||
|
|
||||||
val whenstmt = whenstmt()?.toAst()
|
val whenstmt = whenstmt()?.toAst()
|
||||||
if(whenstmt!=null) return whenstmt
|
if(whenstmt!=null) return whenstmt
|
||||||
|
|
||||||
structdecl()?.let {
|
|
||||||
return StructDecl(it.identifier().text,
|
|
||||||
it.vardecl().map { vd->vd.toAst() }.toMutableList(),
|
|
||||||
toPosition())
|
|
||||||
}
|
|
||||||
|
|
||||||
throw FatalAstException("unprocessed source text (are we missing ast conversion rules for parser elements?): $text")
|
throw FatalAstException("unprocessed source text (are we missing ast conversion rules for parser elements?): $text")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun prog8Parser.AsmsubroutineContext.toAst(): Statement {
|
private fun prog8Parser.AsmsubroutineContext.toAst(): Subroutine {
|
||||||
|
val subdecl = asmsub_decl().toAst()
|
||||||
|
val statements = statement_block()?.toAst() ?: mutableListOf()
|
||||||
|
return Subroutine(subdecl.name, subdecl.parameters, subdecl.returntypes,
|
||||||
|
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters,
|
||||||
|
subdecl.asmClobbers, null, true, statements, toPosition())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prog8Parser.RomsubroutineContext.toAst(): Subroutine {
|
||||||
|
val subdecl = asmsub_decl().toAst()
|
||||||
|
val address = integerliteral().toAst().number.toInt()
|
||||||
|
return Subroutine(subdecl.name, subdecl.parameters, subdecl.returntypes,
|
||||||
|
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters,
|
||||||
|
subdecl.asmClobbers, address, true, mutableListOf(), toPosition())
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AsmsubDecl(val name: String,
|
||||||
|
val parameters: List<SubroutineParameter>,
|
||||||
|
val returntypes: List<DataType>,
|
||||||
|
val asmParameterRegisters: List<RegisterOrStatusflag>,
|
||||||
|
val asmReturnvaluesRegisters: List<RegisterOrStatusflag>,
|
||||||
|
val asmClobbers: Set<CpuRegister>)
|
||||||
|
|
||||||
|
private fun prog8Parser.Asmsub_declContext.toAst(): AsmsubDecl {
|
||||||
val name = identifier().text
|
val name = identifier().text
|
||||||
val address = asmsub_address()?.address?.toAst()?.number?.toInt()
|
|
||||||
val params = asmsub_params()?.toAst() ?: emptyList()
|
val params = asmsub_params()?.toAst() ?: emptyList()
|
||||||
val returns = asmsub_returns()?.toAst() ?: emptyList()
|
val returns = asmsub_returns()?.toAst() ?: emptyList()
|
||||||
val normalParameters = params.map { SubroutineParameter(it.name, it.type, it.position) }
|
|
||||||
val normalReturnvalues = returns.map { it.type }
|
|
||||||
val paramRegisters = params.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag, it.stack) }
|
|
||||||
val returnRegisters = returns.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag, it.stack) }
|
|
||||||
val clobbers = asmsub_clobbers()?.clobber()?.toAst() ?: emptySet()
|
val clobbers = asmsub_clobbers()?.clobber()?.toAst() ?: emptySet()
|
||||||
val statements = statement_block()?.toAst() ?: mutableListOf()
|
val normalParameters = params.map { SubroutineParameter(it.name, it.type, it.position) }
|
||||||
return Subroutine(name, normalParameters, normalReturnvalues,
|
val normalReturntypes = returns.map { it.type }
|
||||||
paramRegisters, returnRegisters, clobbers, address, true, statements, toPosition())
|
val paramRegisters = params.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag) }
|
||||||
|
val returnRegisters = returns.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag) }
|
||||||
|
return AsmsubDecl(name, normalParameters, normalReturntypes, paramRegisters, returnRegisters, clobbers)
|
||||||
}
|
}
|
||||||
|
|
||||||
private class AsmSubroutineParameter(name: String,
|
private class AsmSubroutineParameter(name: String,
|
||||||
type: DataType,
|
type: DataType,
|
||||||
val registerOrPair: RegisterOrPair?,
|
val registerOrPair: RegisterOrPair?,
|
||||||
val statusflag: Statusflag?,
|
val statusflag: Statusflag?,
|
||||||
val stack: Boolean,
|
|
||||||
position: Position) : SubroutineParameter(name, type, position)
|
position: Position) : SubroutineParameter(name, type, position)
|
||||||
|
|
||||||
private class AsmSubroutineReturn(val type: DataType,
|
private class AsmSubroutineReturn(val type: DataType,
|
||||||
val registerOrPair: RegisterOrPair?,
|
val registerOrPair: RegisterOrPair?,
|
||||||
val statusflag: Statusflag?,
|
val statusflag: Statusflag?,
|
||||||
val stack: Boolean,
|
|
||||||
val position: Position)
|
val position: Position)
|
||||||
|
|
||||||
private fun prog8Parser.ClobberContext.toAst(): Set<Register>
|
|
||||||
= this.register().asSequence().map { it.toAst() }.toSet()
|
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.Asmsub_returnsContext.toAst(): List<AsmSubroutineReturn>
|
private fun prog8Parser.Asmsub_returnsContext.toAst(): List<AsmSubroutineReturn>
|
||||||
= asmsub_return().map { AsmSubroutineReturn(it.datatype().toAst(), it.registerorpair()?.toAst(), it.statusregister()?.toAst(), !it.stack?.text.isNullOrEmpty(), toPosition()) }
|
= asmsub_return().map {
|
||||||
|
val register = it.identifier()?.toAst()
|
||||||
|
var registerorpair: RegisterOrPair? = null
|
||||||
|
var statusregister: Statusflag? = null
|
||||||
|
if(register!=null) {
|
||||||
|
when (val name = register.nameInSource.single()) {
|
||||||
|
in RegisterOrPair.names -> registerorpair = RegisterOrPair.valueOf(name)
|
||||||
|
in Statusflag.names -> statusregister = Statusflag.valueOf(name)
|
||||||
|
else -> throw FatalAstException("invalid register or status flag in $it")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AsmSubroutineReturn(
|
||||||
|
it.datatype().toAst(),
|
||||||
|
registerorpair,
|
||||||
|
statusregister,
|
||||||
|
toPosition())
|
||||||
|
}
|
||||||
|
|
||||||
private fun prog8Parser.Asmsub_paramsContext.toAst(): List<AsmSubroutineParameter>
|
private fun prog8Parser.Asmsub_paramsContext.toAst(): List<AsmSubroutineParameter>
|
||||||
= asmsub_param().map {
|
= asmsub_param().map {
|
||||||
val vardecl = it.vardecl()
|
val vardecl = it.vardecl()
|
||||||
val datatype = vardecl.datatype()?.toAst() ?: DataType.STRUCT
|
val datatype = vardecl.datatype()?.toAst() ?: DataType.STRUCT
|
||||||
AsmSubroutineParameter(vardecl.varname.text, datatype,
|
val register = it.identifier()?.toAst()
|
||||||
it.registerorpair()?.toAst(),
|
var registerorpair: RegisterOrPair? = null
|
||||||
it.statusregister()?.toAst(),
|
var statusregister: Statusflag? = null
|
||||||
!it.stack?.text.isNullOrEmpty(), toPosition())
|
if(register!=null) {
|
||||||
|
when (val name = register.nameInSource.single()) {
|
||||||
|
in RegisterOrPair.names -> registerorpair = RegisterOrPair.valueOf(name)
|
||||||
|
in Statusflag.names -> statusregister = Statusflag.valueOf(name)
|
||||||
|
else -> throw FatalAstException("invalid register or status flag '$name'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AsmSubroutineParameter(vardecl.varname.text, datatype, registerorpair, statusregister, toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.StatusregisterContext.toAst() = Statusflag.valueOf(text)
|
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.Functioncall_stmtContext.toAst(): Statement {
|
private fun prog8Parser.Functioncall_stmtContext.toAst(): Statement {
|
||||||
|
val void = this.VOID() != null
|
||||||
val location = scoped_identifier().toAst()
|
val location = scoped_identifier().toAst()
|
||||||
return if(expression_list() == null)
|
return if(expression_list() == null)
|
||||||
FunctionCallStatement(location, mutableListOf(), toPosition())
|
FunctionCallStatement(location, mutableListOf(), void, toPosition())
|
||||||
else
|
else
|
||||||
FunctionCallStatement(location, expression_list().toAst().toMutableList(), toPosition())
|
FunctionCallStatement(location, expression_list().toAst().toMutableList(), void, toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.FunctioncallContext.toAst(): FunctionCall {
|
private fun prog8Parser.FunctioncallContext.toAst(): FunctionCall {
|
||||||
val location = scoped_identifier().toAst()
|
val location = scoped_identifier().toAst()
|
||||||
return if(expression_list() == null)
|
return if(expression_list() == null)
|
||||||
@ -280,11 +323,9 @@ private fun prog8Parser.FunctioncallContext.toAst(): FunctionCall {
|
|||||||
FunctionCall(location, expression_list().toAst().toMutableList(), toPosition())
|
FunctionCall(location, expression_list().toAst().toMutableList(), toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.InlineasmContext.toAst() =
|
private fun prog8Parser.InlineasmContext.toAst() =
|
||||||
InlineAssembly(INLINEASMBLOCK().text, toPosition())
|
InlineAssembly(INLINEASMBLOCK().text, toPosition())
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.ReturnstmtContext.toAst() : Return {
|
private fun prog8Parser.ReturnstmtContext.toAst() : Return {
|
||||||
return Return(expression()?.toAst(), toPosition())
|
return Return(expression()?.toAst(), toPosition())
|
||||||
}
|
}
|
||||||
@ -295,20 +336,15 @@ private fun prog8Parser.UnconditionaljumpContext.toAst(): Jump {
|
|||||||
return Jump(address, identifier, null, toPosition())
|
return Jump(address, identifier, null, toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.LabeldefContext.toAst(): Statement =
|
private fun prog8Parser.LabeldefContext.toAst(): Statement =
|
||||||
Label(children[0].text, toPosition())
|
Label(children[0].text, toPosition())
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.SubroutineContext.toAst() : Subroutine {
|
private fun prog8Parser.SubroutineContext.toAst() : Subroutine {
|
||||||
|
// non-asm subroutine
|
||||||
|
val returntypes = sub_return_part()?.toAst() ?: emptyList()
|
||||||
return Subroutine(identifier().text,
|
return Subroutine(identifier().text,
|
||||||
sub_params()?.toAst() ?: emptyList(),
|
sub_params()?.toAst() ?: emptyList(),
|
||||||
sub_return_part()?.toAst() ?: emptyList(),
|
returntypes,
|
||||||
emptyList(),
|
|
||||||
emptyList(),
|
|
||||||
emptySet(),
|
|
||||||
null,
|
|
||||||
false,
|
|
||||||
statement_block()?.toAst() ?: mutableListOf(),
|
statement_block()?.toAst() ?: mutableListOf(),
|
||||||
toPosition())
|
toPosition())
|
||||||
}
|
}
|
||||||
@ -318,47 +354,45 @@ private fun prog8Parser.Sub_return_partContext.toAst(): List<DataType> {
|
|||||||
return returns.datatype().map { it.toAst() }
|
return returns.datatype().map { it.toAst() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.Sub_paramsContext.toAst(): List<SubroutineParameter> =
|
private fun prog8Parser.Sub_paramsContext.toAst(): List<SubroutineParameter> =
|
||||||
vardecl().map {
|
vardecl().map {
|
||||||
val datatype = it.datatype()?.toAst() ?: DataType.STRUCT
|
val datatype = it.datatype()?.toAst() ?: DataType.STRUCT
|
||||||
SubroutineParameter(it.varname.text, datatype, it.toPosition())
|
SubroutineParameter(it.varname.text, datatype, it.toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.Assign_targetContext.toAst() : AssignTarget {
|
private fun prog8Parser.Assign_targetContext.toAst() : AssignTarget {
|
||||||
val register = register()?.toAst()
|
|
||||||
val identifier = scoped_identifier()
|
val identifier = scoped_identifier()
|
||||||
return when {
|
return when {
|
||||||
register!=null -> AssignTarget(register, null, null, null, toPosition())
|
identifier!=null -> AssignTarget(identifier.toAst(), null, null, toPosition())
|
||||||
identifier!=null -> AssignTarget(null, identifier.toAst(), null, null, toPosition())
|
arrayindexed()!=null -> AssignTarget(null, arrayindexed().toAst(), null, toPosition())
|
||||||
arrayindexed()!=null -> AssignTarget(null, null, arrayindexed().toAst(), null, toPosition())
|
directmemory()!=null -> AssignTarget(null, null, DirectMemoryWrite(directmemory().expression().toAst(), toPosition()), toPosition())
|
||||||
directmemory()!=null -> AssignTarget(null, null, null, DirectMemoryWrite(directmemory().expression().toAst(), toPosition()), toPosition())
|
else -> AssignTarget(scoped_identifier()?.toAst(), null, null, toPosition())
|
||||||
else -> AssignTarget(null, scoped_identifier()?.toAst(), null, null, toPosition())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun prog8Parser.RegisterContext.toAst() = Register.valueOf(text.toUpperCase())
|
private fun prog8Parser.ClobberContext.toAst() : Set<CpuRegister> {
|
||||||
|
val names = this.identifier().map { it.toAst().nameInSource.single() }
|
||||||
|
return names.map { CpuRegister.valueOf(it) }.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
private fun prog8Parser.DatatypeContext.toAst() = DataType.valueOf(text.toUpperCase())
|
private fun prog8Parser.DatatypeContext.toAst() = DataType.valueOf(text.toUpperCase())
|
||||||
|
|
||||||
private fun prog8Parser.RegisterorpairContext.toAst() = RegisterOrPair.valueOf(text.toUpperCase())
|
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.ArrayindexContext.toAst() : ArrayIndex =
|
private fun prog8Parser.ArrayindexContext.toAst() : ArrayIndex =
|
||||||
ArrayIndex(expression().toAst(), toPosition())
|
ArrayIndex(expression().toAst(), toPosition())
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.DirectiveContext.toAst() : Directive =
|
private fun prog8Parser.DirectiveContext.toAst() : Directive =
|
||||||
Directive(directivename.text, directivearg().map { it.toAst() }, toPosition())
|
Directive(directivename.text, directivearg().map { it.toAst() }, toPosition())
|
||||||
|
|
||||||
|
private fun prog8Parser.DirectiveargContext.toAst() : DirectiveArg {
|
||||||
|
val str = stringliteral()
|
||||||
|
if(str?.ALT_STRING_ENCODING() != null)
|
||||||
|
throw AstException("${toPosition()} can't use alternate string encodings for directive arguments")
|
||||||
|
|
||||||
private fun prog8Parser.DirectiveargContext.toAst() : DirectiveArg =
|
return DirectiveArg(stringliteral()?.text, identifier()?.text, integerliteral()?.toAst()?.number?.toInt(), toPosition())
|
||||||
DirectiveArg(stringliteral()?.text, identifier()?.text, integerliteral()?.toAst()?.number?.toInt(), toPosition())
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral {
|
private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral {
|
||||||
fun makeLiteral(text: String, radix: Int, forceWord: Boolean): NumericLiteral {
|
fun makeLiteral(text: String, radix: Int): NumericLiteral {
|
||||||
val integer: Int
|
val integer: Int
|
||||||
var datatype = DataType.UBYTE
|
var datatype = DataType.UBYTE
|
||||||
when (radix) {
|
when (radix) {
|
||||||
@ -396,19 +430,18 @@ private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral {
|
|||||||
}
|
}
|
||||||
else -> throw FatalAstException("invalid radix")
|
else -> throw FatalAstException("invalid radix")
|
||||||
}
|
}
|
||||||
return NumericLiteral(integer, if (forceWord) DataType.UWORD else datatype)
|
return NumericLiteral(integer, datatype)
|
||||||
}
|
}
|
||||||
val terminal: TerminalNode = children[0] as TerminalNode
|
val terminal: TerminalNode = children[0] as TerminalNode
|
||||||
val integerPart = this.intpart.text
|
val integerPart = this.intpart.text
|
||||||
return when (terminal.symbol.type) {
|
return when (terminal.symbol.type) {
|
||||||
prog8Parser.DEC_INTEGER -> makeLiteral(integerPart, 10, wordsuffix()!=null)
|
prog8Parser.DEC_INTEGER -> makeLiteral(integerPart, 10)
|
||||||
prog8Parser.HEX_INTEGER -> makeLiteral(integerPart.substring(1), 16, wordsuffix()!=null)
|
prog8Parser.HEX_INTEGER -> makeLiteral(integerPart.substring(1), 16)
|
||||||
prog8Parser.BIN_INTEGER -> makeLiteral(integerPart.substring(1), 2, wordsuffix()!=null)
|
prog8Parser.BIN_INTEGER -> makeLiteral(integerPart.substring(1), 2)
|
||||||
else -> throw FatalAstException(terminal.text)
|
else -> throw FatalAstException(terminal.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.ExpressionContext.toAst() : Expression {
|
private fun prog8Parser.ExpressionContext.toAst() : Expression {
|
||||||
|
|
||||||
val litval = literalvalue()
|
val litval = literalvalue()
|
||||||
@ -429,10 +462,12 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
|
|||||||
else -> throw FatalAstException("invalid datatype for numeric literal")
|
else -> throw FatalAstException("invalid datatype for numeric literal")
|
||||||
}
|
}
|
||||||
litval.floatliteral()!=null -> NumericLiteralValue(DataType.FLOAT, litval.floatliteral().toAst(), litval.toPosition())
|
litval.floatliteral()!=null -> NumericLiteralValue(DataType.FLOAT, litval.floatliteral().toAst(), litval.toPosition())
|
||||||
litval.stringliteral()!=null -> StringLiteralValue(DataType.STR, unescape(litval.stringliteral().text, litval.toPosition()), position = litval.toPosition())
|
litval.stringliteral()!=null -> litval.stringliteral().toAst()
|
||||||
litval.charliteral()!=null -> {
|
litval.charliteral()!=null -> {
|
||||||
try {
|
try {
|
||||||
NumericLiteralValue(DataType.UBYTE, Petscii.encodePetscii(unescape(litval.charliteral().text, litval.toPosition()), true)[0], litval.toPosition())
|
NumericLiteralValue(DataType.UBYTE, CompilationTarget.instance.encodeString(
|
||||||
|
unescape(litval.charliteral().SINGLECHAR().text, litval.toPosition()),
|
||||||
|
litval.charliteral().ALT_STRING_ENCODING()!=null)[0], litval.toPosition())
|
||||||
} catch (ce: CharConversionException) {
|
} catch (ce: CharConversionException) {
|
||||||
throw SyntaxError(ce.message ?: ce.toString(), litval.toPosition())
|
throw SyntaxError(ce.message ?: ce.toString(), litval.toPosition())
|
||||||
}
|
}
|
||||||
@ -441,20 +476,13 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
|
|||||||
val array = litval.arrayliteral().toAst()
|
val array = litval.arrayliteral().toAst()
|
||||||
// the actual type of the arraysize can not yet be determined here (missing namespace & heap)
|
// the actual type of the arraysize can not yet be determined here (missing namespace & heap)
|
||||||
// the ConstantFold takes care of that and converts the type if needed.
|
// the ConstantFold takes care of that and converts the type if needed.
|
||||||
ArrayLiteralValue(DataType.ARRAY_UB, array, position = litval.toPosition())
|
ArrayLiteralValue(InferredTypes.InferredType.unknown(), array, position = litval.toPosition())
|
||||||
}
|
|
||||||
litval.structliteral()!=null -> {
|
|
||||||
val values = litval.structliteral().expression().map { it.toAst() }
|
|
||||||
StructLiteralValue(values, litval.toPosition())
|
|
||||||
}
|
}
|
||||||
else -> throw FatalAstException("invalid parsed literal")
|
else -> throw FatalAstException("invalid parsed literal")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(register()!=null)
|
|
||||||
return RegisterExpr(register().toAst(), register().toPosition())
|
|
||||||
|
|
||||||
if(scoped_identifier()!=null)
|
if(scoped_identifier()!=null)
|
||||||
return scoped_identifier().toAst()
|
return scoped_identifier().toAst()
|
||||||
|
|
||||||
@ -468,7 +496,8 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
|
|||||||
if(funcall!=null) return funcall
|
if(funcall!=null) return funcall
|
||||||
|
|
||||||
if (rangefrom!=null && rangeto!=null) {
|
if (rangefrom!=null && rangeto!=null) {
|
||||||
val step = rangestep?.toAst() ?: NumericLiteralValue(DataType.UBYTE, 1, toPosition())
|
val defaultstep = if(rto.text == "to") 1 else -1
|
||||||
|
val step = rangestep?.toAst() ?: NumericLiteralValue(DataType.UBYTE, defaultstep, toPosition())
|
||||||
return RangeExpr(rangefrom.toAst(), rangeto.toAst(), step, toPosition())
|
return RangeExpr(rangefrom.toAst(), rangeto.toAst(), step, toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -490,6 +519,8 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
|
|||||||
throw FatalAstException(text)
|
throw FatalAstException(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun prog8Parser.StringliteralContext.toAst(): StringLiteralValue =
|
||||||
|
StringLiteralValue(unescape(this.STRING().text, toPosition()), ALT_STRING_ENCODING()!=null, toPosition())
|
||||||
|
|
||||||
private fun prog8Parser.ArrayindexedContext.toAst(): ArrayIndexedExpression {
|
private fun prog8Parser.ArrayindexedContext.toAst(): ArrayIndexedExpression {
|
||||||
return ArrayIndexedExpression(scoped_identifier().toAst(),
|
return ArrayIndexedExpression(scoped_identifier().toAst(),
|
||||||
@ -497,28 +528,22 @@ private fun prog8Parser.ArrayindexedContext.toAst(): ArrayIndexedExpression {
|
|||||||
toPosition())
|
toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.Expression_listContext.toAst() = expression().map{ it.toAst() }
|
private fun prog8Parser.Expression_listContext.toAst() = expression().map{ it.toAst() }
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.IdentifierContext.toAst() : IdentifierReference =
|
private fun prog8Parser.IdentifierContext.toAst() : IdentifierReference =
|
||||||
IdentifierReference(listOf(text), toPosition())
|
IdentifierReference(listOf(text), toPosition())
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.Scoped_identifierContext.toAst() : IdentifierReference =
|
private fun prog8Parser.Scoped_identifierContext.toAst() : IdentifierReference =
|
||||||
IdentifierReference(NAME().map { it.text }, toPosition())
|
IdentifierReference(NAME().map { it.text }, toPosition())
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.FloatliteralContext.toAst() = text.toDouble()
|
private fun prog8Parser.FloatliteralContext.toAst() = text.toDouble()
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.BooleanliteralContext.toAst() = when(text) {
|
private fun prog8Parser.BooleanliteralContext.toAst() = when(text) {
|
||||||
"true" -> true
|
"true" -> true
|
||||||
"false" -> false
|
"false" -> false
|
||||||
else -> throw FatalAstException(text)
|
else -> throw FatalAstException(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.ArrayliteralContext.toAst() : Array<Expression> =
|
private fun prog8Parser.ArrayliteralContext.toAst() : Array<Expression> =
|
||||||
expression().map { it.toAst() }.toTypedArray()
|
expression().map { it.toAst() }.toTypedArray()
|
||||||
|
|
||||||
@ -536,7 +561,6 @@ private fun prog8Parser.Else_partContext.toAst(): MutableList<Statement> {
|
|||||||
return statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
return statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.Branch_stmtContext.toAst(): BranchStatement {
|
private fun prog8Parser.Branch_stmtContext.toAst(): BranchStatement {
|
||||||
val branchcondition = branchcondition().toAst()
|
val branchcondition = branchcondition().toAst()
|
||||||
val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||||
@ -549,25 +573,19 @@ private fun prog8Parser.Branch_stmtContext.toAst(): BranchStatement {
|
|||||||
|
|
||||||
private fun prog8Parser.BranchconditionContext.toAst() = BranchCondition.valueOf(text.substringAfter('_').toUpperCase())
|
private fun prog8Parser.BranchconditionContext.toAst() = BranchCondition.valueOf(text.substringAfter('_').toUpperCase())
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.ForloopContext.toAst(): ForLoop {
|
private fun prog8Parser.ForloopContext.toAst(): ForLoop {
|
||||||
val loopregister = register()?.toAst()
|
val loopvar = identifier().toAst()
|
||||||
val loopvar = identifier()?.toAst()
|
|
||||||
val iterable = expression()!!.toAst()
|
val iterable = expression()!!.toAst()
|
||||||
val scope =
|
val scope =
|
||||||
if(statement()!=null)
|
if(statement()!=null)
|
||||||
AnonymousScope(mutableListOf(statement().toAst()), statement().toPosition())
|
AnonymousScope(mutableListOf(statement().toAst()), statement().toPosition())
|
||||||
else
|
else
|
||||||
AnonymousScope(statement_block().toAst(), statement_block().toPosition())
|
AnonymousScope(statement_block().toAst(), statement_block().toPosition())
|
||||||
return ForLoop(loopregister, loopvar, iterable, scope, toPosition())
|
return ForLoop(loopvar, iterable, scope, toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.ContinuestmtContext.toAst() = Continue(toPosition())
|
|
||||||
|
|
||||||
private fun prog8Parser.BreakstmtContext.toAst() = Break(toPosition())
|
private fun prog8Parser.BreakstmtContext.toAst() = Break(toPosition())
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.WhileloopContext.toAst(): WhileLoop {
|
private fun prog8Parser.WhileloopContext.toAst(): WhileLoop {
|
||||||
val condition = expression().toAst()
|
val condition = expression().toAst()
|
||||||
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||||
@ -576,13 +594,20 @@ private fun prog8Parser.WhileloopContext.toAst(): WhileLoop {
|
|||||||
return WhileLoop(condition, scope, toPosition())
|
return WhileLoop(condition, scope, toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.RepeatloopContext.toAst(): RepeatLoop {
|
private fun prog8Parser.RepeatloopContext.toAst(): RepeatLoop {
|
||||||
|
val iterations = expression()?.toAst()
|
||||||
|
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||||
|
val scope = AnonymousScope(statements, statement_block()?.toPosition()
|
||||||
|
?: statement().toPosition())
|
||||||
|
return RepeatLoop(iterations, scope, toPosition())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prog8Parser.UntilloopContext.toAst(): UntilLoop {
|
||||||
val untilCondition = expression().toAst()
|
val untilCondition = expression().toAst()
|
||||||
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||||
val scope = AnonymousScope(statements, statement_block()?.toPosition()
|
val scope = AnonymousScope(statements, statement_block()?.toPosition()
|
||||||
?: statement().toPosition())
|
?: statement().toPosition())
|
||||||
return RepeatLoop(scope, untilCondition, toPosition())
|
return UntilLoop(scope, untilCondition, toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun prog8Parser.WhenstmtContext.toAst(): WhenStatement {
|
private fun prog8Parser.WhenstmtContext.toAst(): WhenStatement {
|
||||||
@ -616,7 +641,20 @@ private fun prog8Parser.VardeclContext.toAst(): VarDecl {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun escape(str: String) = str.replace("\t", "\\t").replace("\n", "\\n").replace("\r", "\\r")
|
internal fun escape(str: String): String {
|
||||||
|
val es = str.map {
|
||||||
|
when(it) {
|
||||||
|
'\t' -> "\\t"
|
||||||
|
'\n' -> "\\n"
|
||||||
|
'\r' -> "\\r"
|
||||||
|
'"' -> "\\\""
|
||||||
|
in '\u8000'..'\u80ff' -> "\\x" + (it.toInt() - 0x8000).toString(16).padStart(2, '0')
|
||||||
|
in '\u0000'..'\u00ff' -> it.toString()
|
||||||
|
else -> "\\u" + it.toInt().toString(16).padStart(4, '0')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return es.joinToString("")
|
||||||
|
}
|
||||||
|
|
||||||
internal fun unescape(str: String, position: Position): String {
|
internal fun unescape(str: String, position: Position): String {
|
||||||
val result = mutableListOf<Char>()
|
val result = mutableListOf<Char>()
|
||||||
@ -630,9 +668,15 @@ internal fun unescape(str: String, position: Position): String {
|
|||||||
'n' -> '\n'
|
'n' -> '\n'
|
||||||
'r' -> '\r'
|
'r' -> '\r'
|
||||||
'"' -> '"'
|
'"' -> '"'
|
||||||
|
'\'' -> '\''
|
||||||
'u' -> {
|
'u' -> {
|
||||||
"${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}".toInt(16).toChar()
|
"${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}".toInt(16).toChar()
|
||||||
}
|
}
|
||||||
|
'x' -> {
|
||||||
|
// special hack 0x8000..0x80ff will be outputted verbatim without encoding
|
||||||
|
val hex = ("" + iter.nextChar() + iter.nextChar()).toInt(16)
|
||||||
|
(0x8000 + hex).toChar()
|
||||||
|
}
|
||||||
else -> throw SyntaxError("invalid escape char in string: \\$ec", position)
|
else -> throw SyntaxError("invalid escape char in string: \\$ec", position)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@ -641,4 +685,3 @@ internal fun unescape(str: String, position: Position): String {
|
|||||||
}
|
}
|
||||||
return result.joinToString("")
|
return result.joinToString("")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
package prog8.ast.base
|
package prog8.ast.base
|
||||||
|
|
||||||
import prog8.ast.Node
|
import prog8.ast.Node
|
||||||
import prog8.compiler.target.c64.MachineDefinition
|
import prog8.compiler.target.CompilationTarget
|
||||||
|
|
||||||
|
|
||||||
/**************************** AST Data classes ****************************/
|
/**************************** AST Data classes ****************************/
|
||||||
|
|
||||||
@ -12,7 +13,6 @@ enum class DataType {
|
|||||||
WORD, // pass by value
|
WORD, // pass by value
|
||||||
FLOAT, // pass by value
|
FLOAT, // pass by value
|
||||||
STR, // pass by reference
|
STR, // pass by reference
|
||||||
STR_S, // pass by reference
|
|
||||||
ARRAY_UB, // pass by reference
|
ARRAY_UB, // pass by reference
|
||||||
ARRAY_B, // pass by reference
|
ARRAY_B, // pass by reference
|
||||||
ARRAY_UW, // pass by reference
|
ARRAY_UW, // pass by reference
|
||||||
@ -21,36 +21,39 @@ enum class DataType {
|
|||||||
STRUCT; // pass by reference
|
STRUCT; // pass by reference
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* is the type assignable to the given other type?
|
* is the type assignable to the given other type (perhaps via a typecast) without loss of precision?
|
||||||
*/
|
*/
|
||||||
infix fun isAssignableTo(targetType: DataType) =
|
infix fun isAssignableTo(targetType: DataType) =
|
||||||
// what types are assignable to others without loss of precision?
|
|
||||||
when(this) {
|
when(this) {
|
||||||
UBYTE -> targetType in setOf(UBYTE, WORD, UWORD, FLOAT)
|
UBYTE -> targetType in setOf(UBYTE, WORD, UWORD, FLOAT)
|
||||||
BYTE -> targetType in setOf(BYTE, WORD, FLOAT)
|
BYTE -> targetType in setOf(BYTE, WORD, FLOAT)
|
||||||
UWORD -> targetType in setOf(UWORD, FLOAT)
|
UWORD -> targetType in setOf(UWORD, FLOAT)
|
||||||
WORD -> targetType in setOf(WORD, FLOAT)
|
WORD -> targetType in setOf(WORD, FLOAT)
|
||||||
FLOAT -> targetType == FLOAT
|
FLOAT -> targetType == FLOAT
|
||||||
STR -> targetType == STR || targetType==STR_S
|
STR -> targetType == STR || targetType == UWORD
|
||||||
STR_S -> targetType == STR || targetType==STR_S
|
in ArrayDatatypes -> targetType == this || targetType == UWORD
|
||||||
in ArrayDatatypes -> targetType == this
|
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
infix fun isAssignableTo(targetTypes: Set<DataType>) = targetTypes.any { this isAssignableTo it }
|
infix fun isAssignableTo(targetTypes: Set<DataType>) = targetTypes.any { this isAssignableTo it }
|
||||||
|
infix fun isNotAssignableTo(targetType: DataType) = !this.isAssignableTo(targetType)
|
||||||
|
infix fun isNotAssignableTo(targetTypes: Set<DataType>) = !this.isAssignableTo(targetTypes)
|
||||||
|
|
||||||
infix fun largerThan(other: DataType) =
|
infix fun largerThan(other: DataType) =
|
||||||
when(this) {
|
when {
|
||||||
in ByteDatatypes -> false
|
this == other -> false
|
||||||
in WordDatatypes -> other in ByteDatatypes
|
this in ByteDatatypes -> false
|
||||||
|
this in WordDatatypes -> other in ByteDatatypes
|
||||||
|
this==STR && other==UWORD || this==UWORD && other==STR -> false
|
||||||
else -> true
|
else -> true
|
||||||
}
|
}
|
||||||
|
|
||||||
infix fun equalsSize(other: DataType) =
|
infix fun equalsSize(other: DataType) =
|
||||||
when(this) {
|
when {
|
||||||
in ByteDatatypes -> other in ByteDatatypes
|
this == other -> true
|
||||||
in WordDatatypes -> other in WordDatatypes
|
this in ByteDatatypes -> other in ByteDatatypes
|
||||||
|
this in WordDatatypes -> other in WordDatatypes
|
||||||
|
this==STR && other==UWORD || this==UWORD && other==STR -> true
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,14 +61,14 @@ enum class DataType {
|
|||||||
return when(this) {
|
return when(this) {
|
||||||
in ByteDatatypes -> 1
|
in ByteDatatypes -> 1
|
||||||
in WordDatatypes -> 2
|
in WordDatatypes -> 2
|
||||||
FLOAT -> MachineDefinition.Mflpt5.MemorySize
|
FLOAT -> CompilationTarget.instance.machine.FLOAT_MEM_SIZE
|
||||||
in PassByReferenceDatatypes -> 2
|
in PassByReferenceDatatypes -> CompilationTarget.instance.machine.POINTER_MEM_SIZE
|
||||||
else -> -9999999
|
else -> -9999999
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class Register {
|
enum class CpuRegister {
|
||||||
A,
|
A,
|
||||||
X,
|
X,
|
||||||
Y
|
Y
|
||||||
@ -77,14 +80,23 @@ enum class RegisterOrPair {
|
|||||||
Y,
|
Y,
|
||||||
AX,
|
AX,
|
||||||
AY,
|
AY,
|
||||||
XY
|
XY;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val names by lazy { values().map { it.toString()} }
|
||||||
|
}
|
||||||
|
|
||||||
} // only used in parameter and return value specs in asm subroutines
|
} // only used in parameter and return value specs in asm subroutines
|
||||||
|
|
||||||
enum class Statusflag {
|
enum class Statusflag {
|
||||||
Pc,
|
Pc,
|
||||||
Pz,
|
Pz,
|
||||||
Pv,
|
Pv,
|
||||||
Pn
|
Pn;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val names by lazy { values().map { it.toString()} }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class BranchCondition {
|
enum class BranchCondition {
|
||||||
@ -112,10 +124,9 @@ val ByteDatatypes = setOf(DataType.UBYTE, DataType.BYTE)
|
|||||||
val WordDatatypes = setOf(DataType.UWORD, DataType.WORD)
|
val WordDatatypes = setOf(DataType.UWORD, DataType.WORD)
|
||||||
val IntegerDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD)
|
val IntegerDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD)
|
||||||
val NumericDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT)
|
val NumericDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT)
|
||||||
val StringDatatypes = setOf(DataType.STR, DataType.STR_S)
|
|
||||||
val ArrayDatatypes = setOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F)
|
val ArrayDatatypes = setOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F)
|
||||||
val IterableDatatypes = setOf(
|
val IterableDatatypes = setOf(
|
||||||
DataType.STR, DataType.STR_S,
|
DataType.STR,
|
||||||
DataType.ARRAY_UB, DataType.ARRAY_B,
|
DataType.ARRAY_UB, DataType.ARRAY_B,
|
||||||
DataType.ARRAY_UW, DataType.ARRAY_W,
|
DataType.ARRAY_UW, DataType.ARRAY_W,
|
||||||
DataType.ARRAY_F)
|
DataType.ARRAY_F)
|
||||||
@ -123,12 +134,18 @@ val PassByValueDatatypes = NumericDatatypes
|
|||||||
val PassByReferenceDatatypes = IterableDatatypes.plus(DataType.STRUCT)
|
val PassByReferenceDatatypes = IterableDatatypes.plus(DataType.STRUCT)
|
||||||
val ArrayElementTypes = mapOf(
|
val ArrayElementTypes = mapOf(
|
||||||
DataType.STR to DataType.UBYTE,
|
DataType.STR to DataType.UBYTE,
|
||||||
DataType.STR_S to DataType.UBYTE,
|
|
||||||
DataType.ARRAY_B to DataType.BYTE,
|
DataType.ARRAY_B to DataType.BYTE,
|
||||||
DataType.ARRAY_UB to DataType.UBYTE,
|
DataType.ARRAY_UB to DataType.UBYTE,
|
||||||
DataType.ARRAY_W to DataType.WORD,
|
DataType.ARRAY_W to DataType.WORD,
|
||||||
DataType.ARRAY_UW to DataType.UWORD,
|
DataType.ARRAY_UW to DataType.UWORD,
|
||||||
DataType.ARRAY_F to DataType.FLOAT)
|
DataType.ARRAY_F to DataType.FLOAT)
|
||||||
|
val ElementArrayTypes = mapOf(
|
||||||
|
DataType.BYTE to DataType.ARRAY_B,
|
||||||
|
DataType.UBYTE to DataType.ARRAY_UB,
|
||||||
|
DataType.WORD to DataType.ARRAY_W,
|
||||||
|
DataType.UWORD to DataType.ARRAY_UW,
|
||||||
|
DataType.FLOAT to DataType.ARRAY_F
|
||||||
|
)
|
||||||
|
|
||||||
// find the parent node of a specific type or interface
|
// find the parent node of a specific type or interface
|
||||||
// (useful to figure out in what namespace/block something is defined, etc)
|
// (useful to figure out in what namespace/block something is defined, etc)
|
||||||
@ -146,8 +163,16 @@ object ParentSentinel : Node {
|
|||||||
override val position = Position("<<sentinel>>", 0, 0, 0)
|
override val position = Position("<<sentinel>>", 0, 0, 0)
|
||||||
override var parent: Node = this
|
override var parent: Node = this
|
||||||
override fun linkParents(parent: Node) {}
|
override fun linkParents(parent: Node) {}
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) {
|
data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) {
|
||||||
override fun toString(): String = "[$file: line $line col ${startCol+1}-${endCol+1}]"
|
override fun toString(): String = "[$file: line $line col ${startCol+1}-${endCol+1}]"
|
||||||
|
fun toClickableStr(): String = "$file:$line:$startCol:"
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val DUMMY = Position("<dummy>", 0, 0, 0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,35 +3,42 @@ package prog8.ast.base
|
|||||||
import prog8.parser.ParsingFailedError
|
import prog8.parser.ParsingFailedError
|
||||||
|
|
||||||
|
|
||||||
fun printErrors(errors: List<Any>, moduleName: String) {
|
class ErrorReporter {
|
||||||
val reportedMessages = mutableSetOf<String>()
|
private enum class MessageSeverity {
|
||||||
print("\u001b[91m") // bright red
|
WARNING,
|
||||||
errors.forEach {
|
ERROR
|
||||||
val msg = it.toString()
|
|
||||||
if(msg !in reportedMessages) {
|
|
||||||
System.err.println(msg)
|
|
||||||
reportedMessages.add(msg)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
print("\u001b[0m") // reset color
|
private class CompilerMessage(val severity: MessageSeverity, val message: String, val position: Position)
|
||||||
if(reportedMessages.isNotEmpty())
|
|
||||||
throw ParsingFailedError("There are ${reportedMessages.size} errors in module '$moduleName'.")
|
private val messages = mutableListOf<CompilerMessage>()
|
||||||
}
|
private val alreadyReportedMessages = mutableSetOf<String>()
|
||||||
|
|
||||||
|
fun err(msg: String, position: Position) = messages.add(CompilerMessage(MessageSeverity.ERROR, msg, position))
|
||||||
fun printWarning(msg: String, position: Position, detailInfo: String?=null) {
|
fun warn(msg: String, position: Position) = messages.add(CompilerMessage(MessageSeverity.WARNING, msg, position))
|
||||||
print("\u001b[93m") // bright yellow
|
|
||||||
print("$position Warning: $msg")
|
fun handle() {
|
||||||
if(detailInfo==null)
|
var numErrors = 0
|
||||||
print("\n")
|
var numWarnings = 0
|
||||||
else
|
messages.forEach {
|
||||||
println(": $detailInfo\n")
|
when(it.severity) {
|
||||||
print("\u001b[0m") // normal
|
MessageSeverity.ERROR -> System.err.print("\u001b[91m") // bright red
|
||||||
}
|
MessageSeverity.WARNING -> System.err.print("\u001b[93m") // bright yellow
|
||||||
|
}
|
||||||
|
val msg = "${it.position.toClickableStr()} ${it.severity} ${it.message}".trim()
|
||||||
fun printWarning(msg: String) {
|
if(msg !in alreadyReportedMessages) {
|
||||||
print("\u001b[93m") // bright yellow
|
System.err.println(msg)
|
||||||
print("Warning: $msg")
|
alreadyReportedMessages.add(msg)
|
||||||
print("\u001b[0m\n") // normal
|
when(it.severity) {
|
||||||
|
MessageSeverity.WARNING -> numWarnings++
|
||||||
|
MessageSeverity.ERROR -> numErrors++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.err.print("\u001b[0m") // reset color
|
||||||
|
}
|
||||||
|
messages.clear()
|
||||||
|
if(numErrors>0)
|
||||||
|
throw ParsingFailedError("There are $numErrors errors and $numWarnings warnings.")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isEmpty() = messages.isEmpty()
|
||||||
}
|
}
|
||||||
|
@ -2,21 +2,17 @@ package prog8.ast.base
|
|||||||
|
|
||||||
import prog8.ast.expressions.IdentifierReference
|
import prog8.ast.expressions.IdentifierReference
|
||||||
|
|
||||||
class FatalAstException (override var message: String) : Exception(message)
|
open class FatalAstException (override var message: String) : Exception(message)
|
||||||
|
|
||||||
open class AstException (override var message: String) : Exception(message)
|
open class AstException (override var message: String) : Exception(message)
|
||||||
|
|
||||||
class SyntaxError(override var message: String, val position: Position) : AstException(message) {
|
open class SyntaxError(override var message: String, val position: Position) : AstException(message) {
|
||||||
override fun toString() = "$position Syntax error: $message"
|
override fun toString() = "${position.toClickableStr()} Syntax error: $message"
|
||||||
}
|
|
||||||
|
|
||||||
open class NameError(override var message: String, val position: Position) : AstException(message) {
|
|
||||||
override fun toString() = "$position Name error: $message"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class ExpressionError(message: String, val position: Position) : AstException(message) {
|
class ExpressionError(message: String, val position: Position) : AstException(message) {
|
||||||
override fun toString() = "$position Error: $message"
|
override fun toString() = "${position.toClickableStr()} Error: $message"
|
||||||
}
|
}
|
||||||
|
|
||||||
class UndefinedSymbolError(symbol: IdentifierReference)
|
class UndefinedSymbolError(symbol: IdentifierReference)
|
||||||
: NameError("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position)
|
: SyntaxError("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position)
|
||||||
|
@ -3,68 +3,100 @@ package prog8.ast.base
|
|||||||
import prog8.ast.Module
|
import prog8.ast.Module
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.processing.*
|
import prog8.ast.processing.*
|
||||||
|
import prog8.ast.statements.Directive
|
||||||
import prog8.compiler.CompilationOptions
|
import prog8.compiler.CompilationOptions
|
||||||
import prog8.compiler.target.c64.codegen.AnonymousScopeVarsCleanup
|
import prog8.compiler.BeforeAsmGenerationAstChanger
|
||||||
import prog8.optimizer.FlattenAnonymousScopesAndRemoveNops
|
|
||||||
|
|
||||||
|
|
||||||
// the name of the subroutine that should be called for every block to initialize its variables
|
internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: ErrorReporter) {
|
||||||
internal const val initvarsSubName="prog8_init_vars"
|
val checker = AstChecker(this, compilerOptions, errors)
|
||||||
|
|
||||||
|
|
||||||
internal fun Program.removeNopsFlattenAnonScopes() {
|
|
||||||
val flattener = FlattenAnonymousScopesAndRemoveNops()
|
|
||||||
flattener.visit(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
internal fun Program.checkValid(compilerOptions: CompilationOptions) {
|
|
||||||
val checker = AstChecker(this, compilerOptions)
|
|
||||||
checker.visit(this)
|
|
||||||
printErrors(checker.result(), name)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
internal fun Program.anonscopeVarsCleanup() {
|
|
||||||
val mover = AnonymousScopeVarsCleanup(this)
|
|
||||||
mover.visit(this)
|
|
||||||
printErrors(mover.result(), name)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
internal fun Program.reorderStatements() {
|
|
||||||
val initvalueCreator = VarInitValueAndAddressOfCreator(this)
|
|
||||||
initvalueCreator.visit(this)
|
|
||||||
|
|
||||||
val checker = StatementReorderer(this)
|
|
||||||
checker.visit(this)
|
checker.visit(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun Program.addTypecasts() {
|
internal fun Program.processAstBeforeAsmGeneration(errors: ErrorReporter) {
|
||||||
val caster = TypecastsAdder(this)
|
val fixer = BeforeAsmGenerationAstChanger(this, errors)
|
||||||
|
fixer.visit(this)
|
||||||
|
fixer.applyModifications()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Program.reorderStatements(errors: ErrorReporter) {
|
||||||
|
val reorder = StatementReorderer(this, errors)
|
||||||
|
reorder.visit(this)
|
||||||
|
reorder.applyModifications()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Program.addTypecasts(errors: ErrorReporter) {
|
||||||
|
val caster = TypecastsAdder(this, errors)
|
||||||
caster.visit(this)
|
caster.visit(this)
|
||||||
|
caster.applyModifications()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Program.verifyFunctionArgTypes() {
|
||||||
|
val fixer = VerifyFunctionArgTypes(this)
|
||||||
|
fixer.visit(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun Module.checkImportedValid() {
|
internal fun Module.checkImportedValid() {
|
||||||
val checker = ImportedModuleDirectiveRemover()
|
val imr = ImportedModuleDirectiveRemover()
|
||||||
checker.visit(this)
|
imr.visit(this, this.parent)
|
||||||
printErrors(checker.result(), name)
|
imr.applyModifications()
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun Program.checkRecursion() {
|
internal fun Program.checkIdentifiers(errors: ErrorReporter) {
|
||||||
val checker = AstRecursionChecker(namespace)
|
|
||||||
checker.visit(this)
|
|
||||||
printErrors(checker.result(), name)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
val checker2 = AstIdentifiersChecker(this, errors)
|
||||||
|
checker2.visit(this)
|
||||||
|
|
||||||
internal fun Program.checkIdentifiers() {
|
if(errors.isEmpty()) {
|
||||||
val checker = AstIdentifiersChecker(this)
|
val transforms = AstVariousTransforms(this)
|
||||||
checker.visit(this)
|
transforms.visit(this)
|
||||||
|
transforms.applyModifications()
|
||||||
if(modules.map {it.name}.toSet().size != modules.size) {
|
val lit2decl = LiteralsToAutoVars(this)
|
||||||
throw FatalAstException("modules should all be unique")
|
lit2decl.visit(this)
|
||||||
|
lit2decl.applyModifications()
|
||||||
}
|
}
|
||||||
|
|
||||||
printErrors(checker.result(), name)
|
if (modules.map { it.name }.toSet().size != modules.size) {
|
||||||
|
throw FatalAstException("modules should all be unique")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Program.variousCleanups() {
|
||||||
|
val process = VariousCleanups()
|
||||||
|
process.visit(this)
|
||||||
|
process.applyModifications()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Program.moveMainAndStartToFirst() {
|
||||||
|
// the module containing the program entrypoint is moved to the first in the sequence.
|
||||||
|
// the "main" block containing the entrypoint is moved to the top in there,
|
||||||
|
// and finally the entrypoint subroutine "start" itself is moved to the top in that block.
|
||||||
|
|
||||||
|
val directives = modules[0].statements.filterIsInstance<Directive>()
|
||||||
|
val start = this.entrypoint()
|
||||||
|
if(start!=null) {
|
||||||
|
val mod = start.definingModule()
|
||||||
|
val block = start.definingBlock()
|
||||||
|
if(!modules.remove(mod))
|
||||||
|
throw FatalAstException("module wrong")
|
||||||
|
modules.add(0, mod)
|
||||||
|
mod.remove(block)
|
||||||
|
var afterDirective = mod.statements.indexOfFirst { it !is Directive }
|
||||||
|
if(afterDirective<0)
|
||||||
|
mod.statements.add(block)
|
||||||
|
else
|
||||||
|
mod.statements.add(afterDirective, block)
|
||||||
|
block.remove(start)
|
||||||
|
afterDirective = block.statements.indexOfFirst { it !is Directive }
|
||||||
|
if(afterDirective<0)
|
||||||
|
block.statements.add(start)
|
||||||
|
else
|
||||||
|
block.statements.add(afterDirective, start)
|
||||||
|
|
||||||
|
// overwrite the directives in the module containing the entrypoint
|
||||||
|
for(directive in directives) {
|
||||||
|
modules[0].statements.removeAll { it is Directive && it.directive == directive.directive }
|
||||||
|
modules[0].statements.add(0, directive)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,14 @@
|
|||||||
package prog8.ast.expressions
|
package prog8.ast.expressions
|
||||||
|
|
||||||
import prog8.ast.IFunctionCall
|
import prog8.ast.*
|
||||||
import prog8.ast.INameScope
|
|
||||||
import prog8.ast.Node
|
|
||||||
import prog8.ast.Program
|
|
||||||
import prog8.ast.antlr.escape
|
import prog8.ast.antlr.escape
|
||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
import prog8.ast.processing.IAstModifyingVisitor
|
import prog8.ast.processing.AstWalker
|
||||||
import prog8.ast.processing.IAstVisitor
|
import prog8.ast.processing.IAstVisitor
|
||||||
import prog8.ast.statements.ArrayIndex
|
import prog8.ast.statements.*
|
||||||
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
|
import prog8.compiler.target.CompilationTarget
|
||||||
import prog8.ast.statements.Subroutine
|
|
||||||
import prog8.ast.statements.VarDecl
|
|
||||||
import prog8.compiler.HeapValues
|
|
||||||
import prog8.compiler.IntegerOrAddressOf
|
|
||||||
import prog8.compiler.target.c64.Petscii
|
|
||||||
import prog8.functions.BuiltinFunctions
|
import prog8.functions.BuiltinFunctions
|
||||||
|
import prog8.functions.CannotEvaluateException
|
||||||
import prog8.functions.NotConstArgumentException
|
import prog8.functions.NotConstArgumentException
|
||||||
import prog8.functions.builtinFunctionReturnType
|
import prog8.functions.builtinFunctionReturnType
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@ -23,34 +16,52 @@ import kotlin.math.abs
|
|||||||
|
|
||||||
|
|
||||||
val associativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "==", "!=")
|
val associativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "==", "!=")
|
||||||
|
val comparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
|
||||||
|
val augmentAssignmentOperators = setOf("+", "-", "/", "*", "**", "&", "|", "^", "<<", ">>")
|
||||||
|
|
||||||
sealed class Expression: Node {
|
sealed class Expression: Node {
|
||||||
abstract fun constValue(program: Program): NumericLiteralValue?
|
abstract fun constValue(program: Program): NumericLiteralValue?
|
||||||
abstract fun accept(visitor: IAstModifyingVisitor): Expression
|
|
||||||
abstract fun accept(visitor: IAstVisitor)
|
abstract fun accept(visitor: IAstVisitor)
|
||||||
abstract fun referencesIdentifiers(vararg name: String): Boolean // todo: remove this and add identifier usage tracking into CallGraph instead
|
abstract fun accept(visitor: AstWalker, parent: Node)
|
||||||
|
abstract fun referencesIdentifier(vararg scopedName: String): Boolean
|
||||||
abstract fun inferType(program: Program): InferredTypes.InferredType
|
abstract fun inferType(program: Program): InferredTypes.InferredType
|
||||||
|
|
||||||
|
infix fun isSameAs(assigntarget: AssignTarget) = assigntarget.isSameAs(this)
|
||||||
|
|
||||||
infix fun isSameAs(other: Expression): Boolean {
|
infix fun isSameAs(other: Expression): Boolean {
|
||||||
if(this===other)
|
if(this===other)
|
||||||
return true
|
return true
|
||||||
when(this) {
|
return when(this) {
|
||||||
is RegisterExpr ->
|
|
||||||
return (other is RegisterExpr && other.register==register)
|
|
||||||
is IdentifierReference ->
|
is IdentifierReference ->
|
||||||
return (other is IdentifierReference && other.nameInSource==nameInSource)
|
(other is IdentifierReference && other.nameInSource==nameInSource)
|
||||||
is PrefixExpression ->
|
is PrefixExpression ->
|
||||||
return (other is PrefixExpression && other.operator==operator && other.expression isSameAs expression)
|
(other is PrefixExpression && other.operator==operator && other.expression isSameAs expression)
|
||||||
is BinaryExpression ->
|
is BinaryExpression ->
|
||||||
return (other is BinaryExpression && other.operator==operator
|
(other is BinaryExpression && other.operator==operator
|
||||||
&& other.left isSameAs left
|
&& other.left isSameAs left
|
||||||
&& other.right isSameAs right)
|
&& other.right isSameAs right)
|
||||||
is ArrayIndexedExpression -> {
|
is ArrayIndexedExpression -> {
|
||||||
return (other is ArrayIndexedExpression && other.identifier.nameInSource == identifier.nameInSource
|
(other is ArrayIndexedExpression && other.arrayvar.nameInSource == arrayvar.nameInSource
|
||||||
&& other.arrayspec.index isSameAs arrayspec.index)
|
&& other.indexer isSameAs indexer)
|
||||||
}
|
}
|
||||||
else -> return other==this
|
is DirectMemoryRead -> {
|
||||||
|
(other is DirectMemoryRead && other.addressExpression isSameAs addressExpression)
|
||||||
|
}
|
||||||
|
is TypecastExpression -> {
|
||||||
|
(other is TypecastExpression && other.implicit==implicit && other.type==type && other.expression isSameAs expression)
|
||||||
|
}
|
||||||
|
is AddressOf -> {
|
||||||
|
(other is AddressOf && other.identifier.nameInSource == identifier.nameInSource)
|
||||||
|
}
|
||||||
|
is RangeExpr -> {
|
||||||
|
(other is RangeExpr && other.from==from && other.to==to && other.step==step)
|
||||||
|
}
|
||||||
|
is FunctionCall -> {
|
||||||
|
(other is FunctionCall && other.target.nameInSource == target.nameInSource
|
||||||
|
&& other.args.size == args.size
|
||||||
|
&& other.args.zip(args).all { it.first isSameAs it.second } )
|
||||||
|
}
|
||||||
|
else -> other==this
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -64,11 +75,38 @@ class PrefixExpression(val operator: String, var expression: Expression, overrid
|
|||||||
expression.linkParents(this)
|
expression.linkParents(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
require(node === expression && replacement is Expression)
|
||||||
|
expression = replacement
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
override fun referencesIdentifiers(vararg name: String) = expression.referencesIdentifiers(*name)
|
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||||
override fun inferType(program: Program): InferredTypes.InferredType = expression.inferType(program)
|
|
||||||
|
override fun referencesIdentifier(vararg scopedName: String) = expression.referencesIdentifier(*scopedName)
|
||||||
|
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||||
|
val inferred = expression.inferType(program)
|
||||||
|
return when(operator) {
|
||||||
|
"+" -> inferred
|
||||||
|
"~", "not" -> {
|
||||||
|
when(inferred.typeOrElse(DataType.STRUCT)) {
|
||||||
|
in ByteDatatypes -> InferredTypes.knownFor(DataType.UBYTE)
|
||||||
|
in WordDatatypes -> InferredTypes.knownFor(DataType.UWORD)
|
||||||
|
else -> inferred
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"-" -> {
|
||||||
|
when(inferred.typeOrElse(DataType.STRUCT)) {
|
||||||
|
in ByteDatatypes -> InferredTypes.knownFor(DataType.BYTE)
|
||||||
|
in WordDatatypes -> InferredTypes.knownFor(DataType.WORD)
|
||||||
|
else -> inferred
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> throw FatalAstException("weird prefix expression operator")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "Prefix($operator $expression)"
|
return "Prefix($operator $expression)"
|
||||||
@ -84,6 +122,16 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
|
|||||||
right.linkParents(this)
|
right.linkParents(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
require(replacement is Expression)
|
||||||
|
when {
|
||||||
|
node===left -> left = replacement
|
||||||
|
node===right -> right = replacement
|
||||||
|
else -> throw FatalAstException("invalid replace, no child $node")
|
||||||
|
}
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "[$left $operator $right]"
|
return "[$left $operator $right]"
|
||||||
}
|
}
|
||||||
@ -91,9 +139,10 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
|
|||||||
// binary expression should actually have been optimized away into a single value, before const value was requested...
|
// binary expression should actually have been optimized away into a single value, before const value was requested...
|
||||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||||
|
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
override fun referencesIdentifiers(vararg name: String) = left.referencesIdentifiers(*name) || right.referencesIdentifiers(*name)
|
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||||
|
|
||||||
|
override fun referencesIdentifier(vararg scopedName: String) = left.referencesIdentifier(*scopedName) || right.referencesIdentifier(*scopedName)
|
||||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||||
val leftDt = left.inferType(program)
|
val leftDt = left.inferType(program)
|
||||||
val rightDt = right.inferType(program)
|
val rightDt = right.inferType(program)
|
||||||
@ -183,26 +232,35 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ArrayIndexedExpression(var identifier: IdentifierReference,
|
class ArrayIndexedExpression(var arrayvar: IdentifierReference,
|
||||||
val arrayspec: ArrayIndex,
|
val indexer: ArrayIndex,
|
||||||
override val position: Position) : Expression() {
|
override val position: Position) : Expression(), IAssignable {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
this.parent = parent
|
this.parent = parent
|
||||||
identifier.linkParents(this)
|
arrayvar.linkParents(this)
|
||||||
arrayspec.linkParents(this)
|
indexer.linkParents(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
when {
|
||||||
|
node===arrayvar -> arrayvar = replacement as IdentifierReference
|
||||||
|
else -> throw FatalAstException("invalid replace")
|
||||||
|
}
|
||||||
|
replacement.parent = this
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
override fun referencesIdentifiers(vararg name: String) = identifier.referencesIdentifiers(*name)
|
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||||
|
|
||||||
|
override fun referencesIdentifier(vararg scopedName: String) = arrayvar.referencesIdentifier(*scopedName)
|
||||||
|
|
||||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||||
val target = identifier.targetStatement(program.namespace)
|
val target = arrayvar.targetStatement(program.namespace)
|
||||||
if (target is VarDecl) {
|
if (target is VarDecl) {
|
||||||
return when (target.datatype) {
|
return when (target.datatype) {
|
||||||
in StringDatatypes -> InferredTypes.knownFor(DataType.UBYTE)
|
DataType.STR -> InferredTypes.knownFor(DataType.UBYTE)
|
||||||
in ArrayDatatypes -> InferredTypes.knownFor(ArrayElementTypes.getValue(target.datatype))
|
in ArrayDatatypes -> InferredTypes.knownFor(ArrayElementTypes.getValue(target.datatype))
|
||||||
else -> InferredTypes.unknown()
|
else -> InferredTypes.unknown()
|
||||||
}
|
}
|
||||||
@ -211,7 +269,7 @@ class ArrayIndexedExpression(var identifier: IdentifierReference,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "ArrayIndexed(ident=$identifier, arraysize=$arrayspec; pos=$position)"
|
return "ArrayIndexed(ident=$arrayvar, arraysize=$indexer; pos=$position)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,15 +281,24 @@ class TypecastExpression(var expression: Expression, var type: DataType, val imp
|
|||||||
expression.linkParents(this)
|
expression.linkParents(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
require(replacement is Expression && node===expression)
|
||||||
|
expression = replacement
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
override fun referencesIdentifiers(vararg name: String) = expression.referencesIdentifiers(*name)
|
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||||
|
|
||||||
|
override fun referencesIdentifier(vararg scopedName: String) = expression.referencesIdentifier(*scopedName)
|
||||||
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(type)
|
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(type)
|
||||||
override fun constValue(program: Program): NumericLiteralValue? {
|
override fun constValue(program: Program): NumericLiteralValue? {
|
||||||
val cv = expression.constValue(program) ?: return null
|
val cv = expression.constValue(program) ?: return null
|
||||||
return cv.cast(type)
|
val cast = cv.cast(type)
|
||||||
// val value = RuntimeValue(cv.type, cv.asNumericValue!!).cast(type)
|
return if(cast.isValid)
|
||||||
// return LiteralValue.fromNumber(value.numericValue(), value.type, position).cast(type)
|
cast.valueOrZero()
|
||||||
|
else
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
@ -247,14 +314,20 @@ data class AddressOf(var identifier: IdentifierReference, override val position:
|
|||||||
identifier.parent=this
|
identifier.parent=this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
require(replacement is IdentifierReference && node===identifier)
|
||||||
|
identifier = replacement
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||||
override fun referencesIdentifiers(vararg name: String) = false
|
override fun referencesIdentifier(vararg scopedName: String) = false
|
||||||
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UWORD)
|
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UWORD)
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
class DirectMemoryRead(var addressExpression: Expression, override val position: Position) : Expression() {
|
class DirectMemoryRead(var addressExpression: Expression, override val position: Position) : Expression(), IAssignable {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
@ -262,9 +335,16 @@ class DirectMemoryRead(var addressExpression: Expression, override val position:
|
|||||||
this.addressExpression.linkParents(this)
|
this.addressExpression.linkParents(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
require(replacement is Expression && node===addressExpression)
|
||||||
|
addressExpression = replacement
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
override fun referencesIdentifiers(vararg name: String) = false
|
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||||
|
|
||||||
|
override fun referencesIdentifier(vararg scopedName: String) = false
|
||||||
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UBYTE)
|
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UBYTE)
|
||||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||||
|
|
||||||
@ -307,17 +387,21 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val asBooleanValue: Boolean = number!=0
|
val asBooleanValue: Boolean = number.toDouble() != 0.0
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
this.parent = parent
|
this.parent = parent
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun referencesIdentifiers(vararg name: String) = false
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
throw FatalAstException("can't replace here")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun referencesIdentifier(vararg scopedName: String) = false
|
||||||
override fun constValue(program: Program) = this
|
override fun constValue(program: Program) = this
|
||||||
|
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||||
|
|
||||||
override fun toString(): String = "NumericLiteral(${type.name}:$number)"
|
override fun toString(): String = "NumericLiteral(${type.name}:$number)"
|
||||||
|
|
||||||
@ -333,138 +417,129 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
|
|||||||
|
|
||||||
operator fun compareTo(other: NumericLiteralValue): Int = number.toDouble().compareTo(other.number.toDouble())
|
operator fun compareTo(other: NumericLiteralValue): Int = number.toDouble().compareTo(other.number.toDouble())
|
||||||
|
|
||||||
fun cast(targettype: DataType): NumericLiteralValue {
|
class CastValue(val isValid: Boolean, private val value: NumericLiteralValue?) {
|
||||||
|
fun valueOrZero() = if(isValid) value!! else NumericLiteralValue(DataType.UBYTE, 0, Position.DUMMY)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cast(targettype: DataType): CastValue {
|
||||||
if(type==targettype)
|
if(type==targettype)
|
||||||
return this
|
return CastValue(true, this)
|
||||||
val numval = number.toDouble()
|
val numval = number.toDouble()
|
||||||
when(type) {
|
when(type) {
|
||||||
DataType.UBYTE -> {
|
DataType.UBYTE -> {
|
||||||
if(targettype== DataType.BYTE && numval <= 127)
|
if(targettype== DataType.BYTE && numval <= 127)
|
||||||
return NumericLiteralValue(targettype, number.toShort(), position)
|
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||||
if(targettype== DataType.WORD || targettype== DataType.UWORD)
|
if(targettype== DataType.WORD || targettype== DataType.UWORD)
|
||||||
return NumericLiteralValue(targettype, number.toInt(), position)
|
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
|
||||||
if(targettype== DataType.FLOAT)
|
if(targettype== DataType.FLOAT)
|
||||||
return NumericLiteralValue(targettype, number.toDouble(), position)
|
return CastValue(true, NumericLiteralValue(targettype, number.toDouble(), position))
|
||||||
}
|
}
|
||||||
DataType.BYTE -> {
|
DataType.BYTE -> {
|
||||||
if(targettype== DataType.UBYTE && numval >= 0)
|
if(targettype== DataType.UBYTE && numval >= 0)
|
||||||
return NumericLiteralValue(targettype, number.toShort(), position)
|
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||||
if(targettype== DataType.UWORD && numval >= 0)
|
if(targettype== DataType.UWORD && numval >= 0)
|
||||||
return NumericLiteralValue(targettype, number.toInt(), position)
|
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
|
||||||
if(targettype== DataType.WORD)
|
if(targettype== DataType.WORD)
|
||||||
return NumericLiteralValue(targettype, number.toInt(), position)
|
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
|
||||||
if(targettype== DataType.FLOAT)
|
if(targettype== DataType.FLOAT)
|
||||||
return NumericLiteralValue(targettype, number.toDouble(), position)
|
return CastValue(true, NumericLiteralValue(targettype, number.toDouble(), position))
|
||||||
}
|
}
|
||||||
DataType.UWORD -> {
|
DataType.UWORD -> {
|
||||||
if(targettype== DataType.BYTE && numval <= 127)
|
if(targettype== DataType.BYTE && numval <= 127)
|
||||||
return NumericLiteralValue(targettype, number.toShort(), position)
|
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||||
if(targettype== DataType.UBYTE && numval <= 255)
|
if(targettype== DataType.UBYTE && numval <= 255)
|
||||||
return NumericLiteralValue(targettype, number.toShort(), position)
|
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||||
if(targettype== DataType.WORD && numval <= 32767)
|
if(targettype== DataType.WORD && numval <= 32767)
|
||||||
return NumericLiteralValue(targettype, number.toInt(), position)
|
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
|
||||||
if(targettype== DataType.FLOAT)
|
if(targettype== DataType.FLOAT)
|
||||||
return NumericLiteralValue(targettype, number.toDouble(), position)
|
return CastValue(true, NumericLiteralValue(targettype, number.toDouble(), position))
|
||||||
}
|
}
|
||||||
DataType.WORD -> {
|
DataType.WORD -> {
|
||||||
if(targettype== DataType.BYTE && numval >= -128 && numval <=127)
|
if(targettype== DataType.BYTE && numval >= -128 && numval <=127)
|
||||||
return NumericLiteralValue(targettype, number.toShort(), position)
|
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||||
if(targettype== DataType.UBYTE && numval >= 0 && numval <= 255)
|
if(targettype== DataType.UBYTE && numval >= 0 && numval <= 255)
|
||||||
return NumericLiteralValue(targettype, number.toShort(), position)
|
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||||
if(targettype== DataType.UWORD && numval >=0)
|
if(targettype== DataType.UWORD && numval >=0)
|
||||||
return NumericLiteralValue(targettype, number.toInt(), position)
|
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
|
||||||
if(targettype== DataType.FLOAT)
|
if(targettype== DataType.FLOAT)
|
||||||
return NumericLiteralValue(targettype, number.toDouble(), position)
|
return CastValue(true, NumericLiteralValue(targettype, number.toDouble(), position))
|
||||||
}
|
}
|
||||||
DataType.FLOAT -> {
|
DataType.FLOAT -> {
|
||||||
if (targettype == DataType.BYTE && numval >= -128 && numval <=127)
|
if (targettype == DataType.BYTE && numval >= -128 && numval <=127)
|
||||||
return NumericLiteralValue(targettype, number.toShort(), position)
|
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||||
if (targettype == DataType.UBYTE && numval >=0 && numval <= 255)
|
if (targettype == DataType.UBYTE && numval >=0 && numval <= 255)
|
||||||
return NumericLiteralValue(targettype, number.toShort(), position)
|
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||||
if (targettype == DataType.WORD && numval >= -32768 && numval <= 32767)
|
if (targettype == DataType.WORD && numval >= -32768 && numval <= 32767)
|
||||||
return NumericLiteralValue(targettype, number.toInt(), position)
|
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
|
||||||
if (targettype == DataType.UWORD && numval >=0 && numval <= 65535)
|
if (targettype == DataType.UWORD && numval >=0 && numval <= 65535)
|
||||||
return NumericLiteralValue(targettype, number.toInt(), position)
|
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
|
||||||
}
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
throw ExpressionError("can't cast $type into $targettype", position)
|
return CastValue(false, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class StructLiteralValue(var values: List<Expression>,
|
private var heapIdSequence = 0 // unique ids for strings and arrays "on the heap"
|
||||||
override val position: Position): Expression() {
|
|
||||||
override lateinit var parent: Node
|
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
class StringLiteralValue(val value: String,
|
||||||
this.parent=parent
|
val altEncoding: Boolean, // such as: screencodes instead of Petscii for the C64
|
||||||
values.forEach { it.linkParents(this) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
|
||||||
override fun referencesIdentifiers(vararg name: String) = values.any { it.referencesIdentifiers(*name) }
|
|
||||||
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.STRUCT)
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
return "struct{ ${values.joinToString(", ")} }"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class StringLiteralValue(val type: DataType, // only string types
|
|
||||||
val value: String,
|
|
||||||
initHeapId: Int? =null,
|
|
||||||
override val position: Position) : Expression() {
|
override val position: Position) : Expression() {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
|
|
||||||
var heapId = initHeapId
|
val heapId = ++heapIdSequence
|
||||||
private set
|
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
this.parent = parent
|
this.parent = parent
|
||||||
}
|
}
|
||||||
override fun referencesIdentifiers(vararg name: String) = false
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
throw FatalAstException("can't replace here")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun referencesIdentifier(vararg scopedName: String) = false
|
||||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||||
|
|
||||||
override fun toString(): String = "'${escape(value)}'"
|
override fun toString(): String = "'${escape(value)}'"
|
||||||
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(type)
|
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.STR)
|
||||||
operator fun compareTo(other: StringLiteralValue): Int = value.compareTo(other.value)
|
operator fun compareTo(other: StringLiteralValue): Int = value.compareTo(other.value)
|
||||||
override fun hashCode(): Int = Objects.hash(value, type)
|
override fun hashCode(): Int = Objects.hash(value, altEncoding)
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if(other==null || other !is StringLiteralValue)
|
if(other==null || other !is StringLiteralValue)
|
||||||
return false
|
return false
|
||||||
return value==other.value && type==other.type
|
return value==other.value && altEncoding == other.altEncoding
|
||||||
}
|
|
||||||
|
|
||||||
fun addToHeap(heap: HeapValues) {
|
|
||||||
if (heapId != null)
|
|
||||||
return
|
|
||||||
else
|
|
||||||
heapId = heap.addString(type, value)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ArrayLiteralValue(val type: DataType, // only array types
|
class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred because not all array literals hava a known type yet
|
||||||
val value: Array<Expression>,
|
val value: Array<Expression>,
|
||||||
initHeapId: Int? =null,
|
|
||||||
override val position: Position) : Expression() {
|
override val position: Position) : Expression() {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
|
|
||||||
var heapId = initHeapId
|
val heapId = ++heapIdSequence
|
||||||
private set
|
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
this.parent = parent
|
this.parent = parent
|
||||||
value.forEach {it.linkParents(this)}
|
value.forEach {it.linkParents(this)}
|
||||||
}
|
}
|
||||||
override fun referencesIdentifiers(vararg name: String) = value.any { it.referencesIdentifiers(*name) }
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
require(replacement is Expression)
|
||||||
|
val idx = value.indexOfFirst { it===node }
|
||||||
|
value[idx] = replacement
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun referencesIdentifier(vararg scopedName: String) = value.any { it.referencesIdentifier(*scopedName) }
|
||||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||||
|
|
||||||
override fun toString(): String = "$value"
|
override fun toString(): String = "$value"
|
||||||
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(type)
|
override fun inferType(program: Program): InferredTypes.InferredType = if(type.isKnown) type else guessDatatype(program)
|
||||||
|
|
||||||
operator fun compareTo(other: ArrayLiteralValue): Int = throw ExpressionError("cannot order compare arrays", position)
|
operator fun compareTo(other: ArrayLiteralValue): Int = throw ExpressionError("cannot order compare arrays", position)
|
||||||
override fun hashCode(): Int = Objects.hash(value, type)
|
override fun hashCode(): Int = Objects.hash(value, type)
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
@ -473,58 +548,65 @@ class ArrayLiteralValue(val type: DataType, // only array types
|
|||||||
return type==other.type && value.contentEquals(other.value)
|
return type==other.type && value.contentEquals(other.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun guessDatatype(program: Program): InferredTypes.InferredType {
|
||||||
|
// Educated guess of the desired array literal's datatype.
|
||||||
|
// If it's inside a for loop, assume the data type of the loop variable is what we want.
|
||||||
|
val forloop = parent as? ForLoop
|
||||||
|
if(forloop != null) {
|
||||||
|
val loopvarDt = forloop.loopVarDt(program)
|
||||||
|
if(loopvarDt.isKnown) {
|
||||||
|
return if(loopvarDt.typeOrElse(DataType.STRUCT) !in ElementArrayTypes)
|
||||||
|
InferredTypes.InferredType.unknown()
|
||||||
|
else
|
||||||
|
InferredTypes.InferredType.known(ElementArrayTypes.getValue(loopvarDt.typeOrElse(DataType.STRUCT)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise, select the "biggegst" datatype based on the elements in the array.
|
||||||
|
val datatypesInArray = value.map { it.inferType(program) }
|
||||||
|
require(datatypesInArray.isNotEmpty() && datatypesInArray.all { it.isKnown }) { "can't determine type of empty array" }
|
||||||
|
val dts = datatypesInArray.map { it.typeOrElse(DataType.STRUCT) }
|
||||||
|
return when {
|
||||||
|
DataType.FLOAT in dts -> InferredTypes.InferredType.known(DataType.ARRAY_F)
|
||||||
|
DataType.STR in dts -> InferredTypes.InferredType.known(DataType.ARRAY_UW)
|
||||||
|
DataType.WORD in dts -> InferredTypes.InferredType.known(DataType.ARRAY_W)
|
||||||
|
DataType.UWORD in dts -> InferredTypes.InferredType.known(DataType.ARRAY_UW)
|
||||||
|
DataType.BYTE in dts -> InferredTypes.InferredType.known(DataType.ARRAY_B)
|
||||||
|
DataType.UBYTE in dts -> InferredTypes.InferredType.known(DataType.ARRAY_UB)
|
||||||
|
DataType.ARRAY_UW in dts ||
|
||||||
|
DataType.ARRAY_W in dts ||
|
||||||
|
DataType.ARRAY_UB in dts ||
|
||||||
|
DataType.ARRAY_B in dts ||
|
||||||
|
DataType.ARRAY_F in dts ||
|
||||||
|
DataType.STRUCT in dts -> InferredTypes.InferredType.known(DataType.ARRAY_UW)
|
||||||
|
else -> InferredTypes.InferredType.unknown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun cast(targettype: DataType): ArrayLiteralValue? {
|
fun cast(targettype: DataType): ArrayLiteralValue? {
|
||||||
if(type==targettype)
|
if(type.istype(targettype))
|
||||||
return this
|
return this
|
||||||
if(targettype in ArrayDatatypes) {
|
if(targettype in ArrayDatatypes) {
|
||||||
val elementType = ArrayElementTypes.getValue(targettype)
|
val elementType = ArrayElementTypes.getValue(targettype)
|
||||||
val castArray = value.map{
|
val castArray = value.map{
|
||||||
val num = it as? NumericLiteralValue
|
val num = it as? NumericLiteralValue
|
||||||
if(num==null) {
|
if(num==null) {
|
||||||
// an array of UWORDs could possibly also contain AddressOfs
|
// an array of UWORDs could possibly also contain AddressOfs, other stuff can't be casted
|
||||||
if (elementType != DataType.UWORD || it !is AddressOf)
|
if (elementType != DataType.UWORD || it !is AddressOf)
|
||||||
throw FatalAstException("weird array element $it")
|
return null // can't cast a value of the array, abort
|
||||||
it
|
it
|
||||||
} else {
|
} else {
|
||||||
try {
|
val cast = num.cast(elementType)
|
||||||
num.cast(elementType)
|
if(cast.isValid)
|
||||||
} catch(x: ExpressionError) {
|
cast.valueOrZero()
|
||||||
return null
|
else
|
||||||
}
|
return null // can't cast a value of the array, abort
|
||||||
}
|
}
|
||||||
}.toTypedArray()
|
}.toTypedArray()
|
||||||
return ArrayLiteralValue(targettype, castArray, position = position)
|
return ArrayLiteralValue(InferredTypes.InferredType.known(targettype), castArray, position = position)
|
||||||
}
|
}
|
||||||
return null // invalid type conversion from $this to $targettype
|
return null // invalid type conversion from $this to $targettype
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addToHeap(heap: HeapValues) {
|
|
||||||
if (heapId != null)
|
|
||||||
return
|
|
||||||
else {
|
|
||||||
if(value.any {it is AddressOf }) {
|
|
||||||
val intArrayWithAddressOfs = value.map {
|
|
||||||
when (it) {
|
|
||||||
is AddressOf -> IntegerOrAddressOf(null, it)
|
|
||||||
is NumericLiteralValue -> IntegerOrAddressOf(it.number.toInt(), null)
|
|
||||||
else -> throw FatalAstException("invalid datatype in array")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
heapId = heap.addIntegerArray(type, intArrayWithAddressOfs.toTypedArray())
|
|
||||||
} else {
|
|
||||||
val valuesInArray = value.map { (it as? NumericLiteralValue)?.number }
|
|
||||||
if(null !in valuesInArray) {
|
|
||||||
heapId = if (type == DataType.ARRAY_F) {
|
|
||||||
val doubleArray = valuesInArray.map { it!!.toDouble() }.toDoubleArray()
|
|
||||||
heap.addDoublesArray(doubleArray)
|
|
||||||
} else {
|
|
||||||
val integerArray = valuesInArray.map { it!!.toInt() }
|
|
||||||
heap.addIntegerArray(type, integerArray.map { IntegerOrAddressOf(it, null) }.toTypedArray())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class RangeExpr(var from: Expression,
|
class RangeExpr(var from: Expression,
|
||||||
@ -540,10 +622,22 @@ class RangeExpr(var from: Expression,
|
|||||||
step.linkParents(this)
|
step.linkParents(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
require(replacement is Expression)
|
||||||
|
when {
|
||||||
|
from===node -> from=replacement
|
||||||
|
to===node -> to=replacement
|
||||||
|
step===node -> step=replacement
|
||||||
|
else -> throw FatalAstException("invalid replacement")
|
||||||
|
}
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
override fun referencesIdentifiers(vararg name: String): Boolean = from.referencesIdentifiers(*name) || to.referencesIdentifiers(*name)
|
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||||
|
|
||||||
|
override fun referencesIdentifier(vararg scopedName: String): Boolean = from.referencesIdentifier(*scopedName) || to.referencesIdentifier(*scopedName)
|
||||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||||
val fromDt=from.inferType(program)
|
val fromDt=from.inferType(program)
|
||||||
val toDt=to.inferType(program)
|
val toDt=to.inferType(program)
|
||||||
@ -552,10 +646,16 @@ class RangeExpr(var from: Expression,
|
|||||||
fromDt istype DataType.UBYTE && toDt istype DataType.UBYTE -> InferredTypes.knownFor(DataType.ARRAY_UB)
|
fromDt istype DataType.UBYTE && toDt istype DataType.UBYTE -> InferredTypes.knownFor(DataType.ARRAY_UB)
|
||||||
fromDt istype DataType.UWORD && toDt istype DataType.UWORD -> InferredTypes.knownFor(DataType.ARRAY_UW)
|
fromDt istype DataType.UWORD && toDt istype DataType.UWORD -> InferredTypes.knownFor(DataType.ARRAY_UW)
|
||||||
fromDt istype DataType.STR && toDt istype DataType.STR -> InferredTypes.knownFor(DataType.STR)
|
fromDt istype DataType.STR && toDt istype DataType.STR -> InferredTypes.knownFor(DataType.STR)
|
||||||
fromDt istype DataType.STR_S && toDt istype DataType.STR_S -> InferredTypes.knownFor(DataType.STR_S)
|
|
||||||
fromDt istype DataType.WORD || toDt istype DataType.WORD -> InferredTypes.knownFor(DataType.ARRAY_W)
|
fromDt istype DataType.WORD || toDt istype DataType.WORD -> InferredTypes.knownFor(DataType.ARRAY_W)
|
||||||
fromDt istype DataType.BYTE || toDt istype DataType.BYTE -> InferredTypes.knownFor(DataType.ARRAY_B)
|
fromDt istype DataType.BYTE || toDt istype DataType.BYTE -> InferredTypes.knownFor(DataType.ARRAY_B)
|
||||||
else -> InferredTypes.knownFor(DataType.ARRAY_UB)
|
else -> {
|
||||||
|
val fdt = fromDt.typeOrElse(DataType.STRUCT)
|
||||||
|
val tdt = toDt.typeOrElse(DataType.STRUCT)
|
||||||
|
if(fdt largerThan tdt)
|
||||||
|
InferredTypes.knownFor(ElementArrayTypes.getValue(fdt))
|
||||||
|
else
|
||||||
|
InferredTypes.knownFor(ElementArrayTypes.getValue(tdt))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
@ -576,9 +676,9 @@ class RangeExpr(var from: Expression,
|
|||||||
val fromString = from as? StringLiteralValue
|
val fromString = from as? StringLiteralValue
|
||||||
val toString = to as? StringLiteralValue
|
val toString = to as? StringLiteralValue
|
||||||
if(fromString!=null && toString!=null ) {
|
if(fromString!=null && toString!=null ) {
|
||||||
// string range -> int range over petscii values
|
// string range -> int range over character values
|
||||||
fromVal = Petscii.encodePetscii(fromString.value, true)[0].toInt()
|
fromVal = CompilationTarget.instance.encodeString(fromString.value, fromString.altEncoding)[0].toInt()
|
||||||
toVal = Petscii.encodePetscii(toString.value, true)[0].toInt()
|
toVal = CompilationTarget.instance.encodeString(toString.value, fromString.altEncoding)[0].toInt()
|
||||||
} else {
|
} else {
|
||||||
val fromLv = from as? NumericLiteralValue
|
val fromLv = from as? NumericLiteralValue
|
||||||
val toLv = to as? NumericLiteralValue
|
val toLv = to as? NumericLiteralValue
|
||||||
@ -608,25 +708,7 @@ internal fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RegisterExpr(val register: Register, override val position: Position) : Expression() {
|
data class IdentifierReference(val nameInSource: List<String>, override val position: Position) : Expression(), IAssignable {
|
||||||
override lateinit var parent: Node
|
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
|
||||||
this.parent = parent
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
|
||||||
override fun referencesIdentifiers(vararg name: String): Boolean = register.name in name
|
|
||||||
override fun toString(): String {
|
|
||||||
return "RegisterExpr(register=$register, pos=$position)"
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UBYTE)
|
|
||||||
}
|
|
||||||
|
|
||||||
data class IdentifierReference(val nameInSource: List<String>, override val position: Position) : Expression() {
|
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
|
|
||||||
fun targetStatement(namespace: INameScope) =
|
fun targetStatement(namespace: INameScope) =
|
||||||
@ -638,10 +720,17 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
|
|||||||
fun targetVarDecl(namespace: INameScope): VarDecl? = targetStatement(namespace) as? VarDecl
|
fun targetVarDecl(namespace: INameScope): VarDecl? = targetStatement(namespace) as? VarDecl
|
||||||
fun targetSubroutine(namespace: INameScope): Subroutine? = targetStatement(namespace) as? Subroutine
|
fun targetSubroutine(namespace: INameScope): Subroutine? = targetStatement(namespace) as? Subroutine
|
||||||
|
|
||||||
|
override fun equals(other: Any?) = other is IdentifierReference && other.nameInSource==nameInSource
|
||||||
|
override fun hashCode() = nameInSource.hashCode()
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
this.parent = parent
|
this.parent = parent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
throw FatalAstException("can't replace here")
|
||||||
|
}
|
||||||
|
|
||||||
override fun constValue(program: Program): NumericLiteralValue? {
|
override fun constValue(program: Program): NumericLiteralValue? {
|
||||||
val node = program.namespace.lookup(nameInSource, this)
|
val node = program.namespace.lookup(nameInSource, this)
|
||||||
?: throw UndefinedSymbolError(this)
|
?: throw UndefinedSymbolError(this)
|
||||||
@ -658,16 +747,17 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
|
|||||||
return "IdentifierRef($nameInSource)"
|
return "IdentifierRef($nameInSource)"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
override fun referencesIdentifiers(vararg name: String): Boolean = nameInSource.last() in name
|
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||||
|
|
||||||
|
override fun referencesIdentifier(vararg scopedName: String): Boolean =
|
||||||
|
nameInSource.size==scopedName.size && nameInSource.toTypedArray().contentEquals(scopedName)
|
||||||
|
|
||||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||||
val targetStmt = targetStatement(program.namespace)
|
return when (val targetStmt = targetStatement(program.namespace)) {
|
||||||
if(targetStmt is VarDecl) {
|
is VarDecl -> InferredTypes.knownFor(targetStmt.datatype)
|
||||||
return InferredTypes.knownFor(targetStmt.datatype)
|
is StructDecl -> InferredTypes.knownFor(DataType.STRUCT)
|
||||||
} else {
|
else -> InferredTypes.InferredType.unknown()
|
||||||
throw FatalAstException("cannot get datatype from identifier reference ${this}, pos=$position")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -678,22 +768,47 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
|
|||||||
val value = (node as? VarDecl)?.value ?: throw FatalAstException("requires a reference value")
|
val value = (node as? VarDecl)?.value ?: throw FatalAstException("requires a reference value")
|
||||||
return when (value) {
|
return when (value) {
|
||||||
is IdentifierReference -> value.heapId(namespace)
|
is IdentifierReference -> value.heapId(namespace)
|
||||||
is StringLiteralValue -> value.heapId ?: throw FatalAstException("string is not on the heap: $value")
|
is StringLiteralValue -> value.heapId
|
||||||
is ArrayLiteralValue -> value.heapId ?: throw FatalAstException("array is not on the heap: $value")
|
is ArrayLiteralValue -> value.heapId
|
||||||
else -> throw FatalAstException("requires a reference value")
|
else -> throw FatalAstException("requires a reference value")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun firstStructVarName(namespace: INameScope): String? {
|
||||||
|
// take the name of the first struct member of the structvariable instead
|
||||||
|
// if it's just a regular variable, return null.
|
||||||
|
val struct = memberOfStruct(namespace) ?: return null
|
||||||
|
val decl = targetVarDecl(namespace)!!
|
||||||
|
if(decl.datatype!=DataType.STRUCT)
|
||||||
|
return null
|
||||||
|
|
||||||
|
val firstStructMember = struct.nameOfFirstMember()
|
||||||
|
// find the flattened var that belongs to this first struct member
|
||||||
|
val firstVarName = listOf(decl.name, firstStructMember)
|
||||||
|
val firstVar = definingScope().lookup(firstVarName, this) as VarDecl
|
||||||
|
return firstVar.name
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FunctionCall(override var target: IdentifierReference,
|
class FunctionCall(override var target: IdentifierReference,
|
||||||
override var arglist: MutableList<Expression>,
|
override var args: MutableList<Expression>,
|
||||||
override val position: Position) : Expression(), IFunctionCall {
|
override val position: Position) : Expression(), IFunctionCall {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
this.parent = parent
|
this.parent = parent
|
||||||
target.linkParents(this)
|
target.linkParents(this)
|
||||||
arglist.forEach { it.linkParents(this) }
|
args.forEach { it.linkParents(this) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
if(node===target)
|
||||||
|
target=replacement as IdentifierReference
|
||||||
|
else {
|
||||||
|
val idx = args.indexOfFirst { it===node }
|
||||||
|
args[idx] = replacement as Expression
|
||||||
|
}
|
||||||
|
replacement.parent = this
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun constValue(program: Program) = constValue(program, true)
|
override fun constValue(program: Program) = constValue(program, true)
|
||||||
@ -708,8 +823,8 @@ class FunctionCall(override var target: IdentifierReference,
|
|||||||
if(func!=null) {
|
if(func!=null) {
|
||||||
val exprfunc = func.constExpressionFunc
|
val exprfunc = func.constExpressionFunc
|
||||||
if(exprfunc!=null)
|
if(exprfunc!=null)
|
||||||
resultValue = exprfunc(arglist, position, program)
|
resultValue = exprfunc(args, position, program)
|
||||||
else if(func.returntype==null)
|
else if(func.known_returntype==null)
|
||||||
throw ExpressionError("builtin function ${target.nameInSource[0]} can't be used here because it doesn't return a value", position)
|
throw ExpressionError("builtin function ${target.nameInSource[0]} can't be used here because it doesn't return a value", position)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -726,15 +841,20 @@ class FunctionCall(override var target: IdentifierReference,
|
|||||||
// const-evaluating the builtin function call failed.
|
// const-evaluating the builtin function call failed.
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
catch(x: CannotEvaluateException) {
|
||||||
|
// const-evaluating the builtin function call failed.
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "FunctionCall(target=$target, pos=$position)"
|
return "FunctionCall(target=$target, pos=$position)"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
override fun referencesIdentifiers(vararg name: String): Boolean = target.referencesIdentifiers(*name) || arglist.any{it.referencesIdentifiers(*name)}
|
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||||
|
|
||||||
|
override fun referencesIdentifier(vararg scopedName: String): Boolean = target.referencesIdentifier(*scopedName) || args.any{it.referencesIdentifier(*scopedName)}
|
||||||
|
|
||||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||||
val constVal = constValue(program ,false)
|
val constVal = constValue(program ,false)
|
||||||
@ -747,13 +867,19 @@ class FunctionCall(override var target: IdentifierReference,
|
|||||||
target.nameInSource[0] == "clear_carry" || target.nameInSource[0]=="clear_irqd") {
|
target.nameInSource[0] == "clear_carry" || target.nameInSource[0]=="clear_irqd") {
|
||||||
return InferredTypes.void() // these have no return value
|
return InferredTypes.void() // these have no return value
|
||||||
}
|
}
|
||||||
return builtinFunctionReturnType(target.nameInSource[0], this.arglist, program)
|
return builtinFunctionReturnType(target.nameInSource[0], this.args, program)
|
||||||
}
|
}
|
||||||
is Subroutine -> {
|
is Subroutine -> {
|
||||||
if(stmt.returntypes.isEmpty())
|
if(stmt.returntypes.isEmpty())
|
||||||
return InferredTypes.void() // no return value
|
return InferredTypes.void() // no return value
|
||||||
if(stmt.returntypes.size==1)
|
if(stmt.returntypes.size==1)
|
||||||
return InferredTypes.knownFor(stmt.returntypes[0])
|
return InferredTypes.knownFor(stmt.returntypes[0])
|
||||||
|
|
||||||
|
// multiple return values. Can occur for asmsub routines. If there is exactly one register return value, take that.
|
||||||
|
val numRegisterReturns = stmt.asmReturnvaluesRegisters.count { it.registerOrPair!=null }
|
||||||
|
if(numRegisterReturns==1)
|
||||||
|
return InferredTypes.InferredType.known(DataType.UBYTE)
|
||||||
|
|
||||||
return InferredTypes.unknown() // has multiple return types... so not a single resulting datatype possible
|
return InferredTypes.unknown() // has multiple return types... so not a single resulting datatype possible
|
||||||
}
|
}
|
||||||
else -> return InferredTypes.unknown()
|
else -> return InferredTypes.unknown()
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
package prog8.ast.expressions
|
package prog8.ast.expressions
|
||||||
|
|
||||||
import prog8.ast.base.DataType
|
import prog8.ast.base.DataType
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
object InferredTypes {
|
object InferredTypes {
|
||||||
class InferredType private constructor(val isUnknown: Boolean, val isVoid: Boolean, private var datatype: DataType?) {
|
class InferredType private constructor(val isUnknown: Boolean, val isVoid: Boolean, private var datatype: DataType?) {
|
||||||
init {
|
init {
|
||||||
if(datatype!=null && (isUnknown || isVoid))
|
require(!(datatype!=null && (isUnknown || isVoid))) { "invalid combination of args" }
|
||||||
throw IllegalArgumentException("invalid combination of args")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val isKnown = datatype!=null
|
val isKnown = datatype!=null
|
||||||
@ -29,9 +30,18 @@ object InferredTypes {
|
|||||||
return when {
|
return when {
|
||||||
datatype!=null -> datatype.toString()
|
datatype!=null -> datatype.toString()
|
||||||
isVoid -> "<void>"
|
isVoid -> "<void>"
|
||||||
else -> "<unkonwn>"
|
else -> "<unknown>"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int = Objects.hash(isVoid, datatype)
|
||||||
|
|
||||||
|
infix fun isAssignableTo(targetDt: InferredType): Boolean =
|
||||||
|
isKnown && targetDt.isKnown && (datatype!! isAssignableTo targetDt.datatype!!)
|
||||||
|
infix fun isAssignableTo(targetDt: DataType): Boolean =
|
||||||
|
isKnown && (datatype!! isAssignableTo targetDt)
|
||||||
|
infix fun isNotAssignableTo(targetDt: InferredType): Boolean = !this.isAssignableTo(targetDt)
|
||||||
|
infix fun isNotAssignableTo(targetDt: DataType): Boolean = !this.isAssignableTo(targetDt)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val unknownInstance = InferredType.unknown()
|
private val unknownInstance = InferredType.unknown()
|
||||||
@ -43,7 +53,6 @@ object InferredTypes {
|
|||||||
DataType.WORD to InferredType.known(DataType.WORD),
|
DataType.WORD to InferredType.known(DataType.WORD),
|
||||||
DataType.FLOAT to InferredType.known(DataType.FLOAT),
|
DataType.FLOAT to InferredType.known(DataType.FLOAT),
|
||||||
DataType.STR to InferredType.known(DataType.STR),
|
DataType.STR to InferredType.known(DataType.STR),
|
||||||
DataType.STR_S to InferredType.known(DataType.STR_S),
|
|
||||||
DataType.ARRAY_UB to InferredType.known(DataType.ARRAY_UB),
|
DataType.ARRAY_UB to InferredType.known(DataType.ARRAY_UB),
|
||||||
DataType.ARRAY_B to InferredType.known(DataType.ARRAY_B),
|
DataType.ARRAY_B to InferredType.known(DataType.ARRAY_B),
|
||||||
DataType.ARRAY_UW to InferredType.known(DataType.ARRAY_UW),
|
DataType.ARRAY_UW to InferredType.known(DataType.ARRAY_UW),
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,113 +1,99 @@
|
|||||||
package prog8.ast.processing
|
package prog8.ast.processing
|
||||||
|
|
||||||
import prog8.ast.INameScope
|
|
||||||
import prog8.ast.Module
|
import prog8.ast.Module
|
||||||
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.*
|
||||||
import prog8.compiler.target.c64.AssemblyProgram
|
import prog8.compiler.target.CompilationTarget
|
||||||
import prog8.functions.BuiltinFunctions
|
import prog8.functions.BuiltinFunctions
|
||||||
|
|
||||||
|
internal class AstIdentifiersChecker(private val program: Program, private val errors: ErrorReporter) : IAstVisitor {
|
||||||
internal class AstIdentifiersChecker(private val program: Program) : IAstModifyingVisitor {
|
|
||||||
|
|
||||||
private val checkResult: MutableList<AstException> = mutableListOf()
|
|
||||||
|
|
||||||
private var blocks = mutableMapOf<String, Block>()
|
private var blocks = mutableMapOf<String, Block>()
|
||||||
private val vardeclsToAdd = mutableMapOf<INameScope, MutableList<VarDecl>>()
|
|
||||||
|
|
||||||
internal fun result(): List<AstException> {
|
|
||||||
return checkResult
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun nameError(name: String, position: Position, existing: Statement) {
|
private fun nameError(name: String, position: Position, existing: Statement) {
|
||||||
checkResult.add(NameError("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) {
|
override fun visit(module: Module) {
|
||||||
vardeclsToAdd.clear()
|
|
||||||
blocks.clear() // blocks may be redefined within a different module
|
blocks.clear() // blocks may be redefined within a different module
|
||||||
|
|
||||||
super.visit(module)
|
super.visit(module)
|
||||||
// add any new vardecls to the various scopes
|
|
||||||
for((where, decls) in vardeclsToAdd) {
|
|
||||||
where.statements.addAll(0, decls)
|
|
||||||
decls.forEach { it.linkParents(where as Node) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(block: Block): Statement {
|
override fun visit(block: Block) {
|
||||||
|
if(block.name in CompilationTarget.instance.machine.opcodeNames)
|
||||||
|
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)
|
nameError(block.name, block.position, existing)
|
||||||
else
|
else
|
||||||
blocks[block.name] = block
|
blocks[block.name] = block
|
||||||
|
|
||||||
return super.visit(block)
|
super.visit(block)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(functionCall: FunctionCall): Expression {
|
override fun visit(directive: Directive) {
|
||||||
if(functionCall.target.nameInSource.size==1 && functionCall.target.nameInSource[0]=="lsb") {
|
if(directive.directive=="%target") {
|
||||||
// lsb(...) is just an alias for type cast to ubyte, so replace with "... as ubyte"
|
val compatibleTarget = directive.args.single().name
|
||||||
val typecast = TypecastExpression(functionCall.arglist.single(), DataType.UBYTE, false, functionCall.position)
|
if (compatibleTarget != CompilationTarget.instance.name)
|
||||||
typecast.linkParents(functionCall.parent)
|
errors.err("module's compilation target ($compatibleTarget) differs from active target (${CompilationTarget.instance.name})", directive.position)
|
||||||
return super.visit(typecast)
|
|
||||||
}
|
}
|
||||||
return super.visit(functionCall)
|
|
||||||
|
super.visit(directive)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(decl: VarDecl): Statement {
|
override fun visit(decl: VarDecl) {
|
||||||
// first, check if there are datatype errors on the vardecl
|
decl.datatypeErrors.forEach { errors.err(it.message, it.position) }
|
||||||
decl.datatypeErrors.forEach { checkResult.add(it) }
|
|
||||||
|
|
||||||
// now check the identifier
|
|
||||||
if(decl.name in BuiltinFunctions)
|
if(decl.name in BuiltinFunctions)
|
||||||
// the builtin functions can't be redefined
|
errors.err("builtin function cannot be redefined", decl.position)
|
||||||
checkResult.add(NameError("builtin function cannot be redefined", decl.position))
|
|
||||||
|
|
||||||
if(decl.name in AssemblyProgram.opcodeNames)
|
if(decl.name in CompilationTarget.instance.machine.opcodeNames)
|
||||||
checkResult.add(NameError("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)
|
||||||
|
|
||||||
// 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) {
|
if(decl.datatype==DataType.STRUCT) {
|
||||||
if(decl.structHasBeenFlattened)
|
if (decl.structHasBeenFlattened)
|
||||||
return super.visit(decl) // don't do this multiple times
|
return super.visit(decl) // don't do this multiple times
|
||||||
|
|
||||||
if(decl.struct==null) {
|
if (decl.struct == null) {
|
||||||
checkResult.add(NameError("undefined struct type", decl.position))
|
errors.err("undefined struct type", decl.position)
|
||||||
return super.visit(decl)
|
return super.visit(decl)
|
||||||
}
|
}
|
||||||
|
|
||||||
if(decl.struct!!.statements.any { (it as VarDecl).datatype !in NumericDatatypes})
|
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
|
return super.visit(decl) // a non-numeric member, not supported. proper error is given by AstChecker later
|
||||||
|
|
||||||
if(decl.value is NumericLiteralValue) {
|
if (decl.value is NumericLiteralValue) {
|
||||||
checkResult.add(ExpressionError("you cannot initialize a struct using a single value", decl.position))
|
errors.err("you cannot initialize a struct using a single value", decl.position)
|
||||||
return super.visit(decl)
|
return super.visit(decl)
|
||||||
}
|
}
|
||||||
|
|
||||||
val decls = decl.flattenStructMembers()
|
if (decl.value != null && decl.value !is ArrayLiteralValue) {
|
||||||
decls.add(decl)
|
errors.err("initializing a struct requires array literal value", decl.value?.position ?: decl.position)
|
||||||
val result = AnonymousScope(decls, decl.position)
|
return super.visit(decl)
|
||||||
result.linkParents(decl.parent)
|
}
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
return super.visit(decl)
|
if(decl.definingBlock().name==decl.name)
|
||||||
|
nameError(decl.name, decl.position, decl.definingBlock())
|
||||||
|
if(decl.definingSubroutine()?.name==decl.name)
|
||||||
|
nameError(decl.name, decl.position, decl.definingSubroutine()!!)
|
||||||
|
|
||||||
|
super.visit(decl)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(subroutine: Subroutine): Statement {
|
override fun visit(subroutine: Subroutine) {
|
||||||
if(subroutine.name in AssemblyProgram.opcodeNames) {
|
if(subroutine.name in CompilationTarget.instance.machine.opcodeNames) {
|
||||||
checkResult.add(NameError("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position))
|
errors.err("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position)
|
||||||
} else if(subroutine.name in BuiltinFunctions) {
|
} else if(subroutine.name in BuiltinFunctions) {
|
||||||
// the builtin functions can't be redefined
|
// the builtin functions can't be redefined
|
||||||
checkResult.add(NameError("builtin function cannot be redefined", subroutine.position))
|
errors.err("builtin function cannot be redefined", subroutine.position)
|
||||||
} else {
|
} else {
|
||||||
// already reported elsewhere:
|
// already reported elsewhere:
|
||||||
// if (subroutine.parameters.any { it.name in BuiltinFunctions })
|
// if (subroutine.parameters.any { it.name in BuiltinFunctions })
|
||||||
@ -117,14 +103,6 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
|
|||||||
if (existing != null && existing !== subroutine)
|
if (existing != null && existing !== subroutine)
|
||||||
nameError(subroutine.name, subroutine.position, existing)
|
nameError(subroutine.name, subroutine.position, existing)
|
||||||
|
|
||||||
// does the parameter redefine a variable declared elsewhere?
|
|
||||||
for(param in subroutine.parameters) {
|
|
||||||
val existingVar = subroutine.lookup(listOf(param.name), subroutine)
|
|
||||||
if (existingVar != null && existingVar.parent !== subroutine) {
|
|
||||||
nameError(param.name, param.position, existingVar)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
||||||
val symbolsInSub = subroutine.allDefinedSymbols()
|
val symbolsInSub = subroutine.allDefinedSymbols()
|
||||||
val namesInSub = symbolsInSub.map{ it.first }.toSet()
|
val namesInSub = symbolsInSub.map{ it.first }.toSet()
|
||||||
@ -139,296 +117,50 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
|
|||||||
nameError(name, sub.position, subroutine)
|
nameError(name, sub.position, subroutine)
|
||||||
}
|
}
|
||||||
|
|
||||||
// inject subroutine params as local variables (if they're not there yet) (for non-kernel subroutines and non-asm parameters)
|
|
||||||
// NOTE:
|
|
||||||
// - numeric types BYTE and WORD and FLOAT are passed by value;
|
|
||||||
// - strings, arrays, matrices are passed by reference (their 16-bit address is passed as an uword parameter)
|
|
||||||
if(subroutine.asmAddress==null) {
|
|
||||||
if(subroutine.asmParameterRegisters.isEmpty()) {
|
|
||||||
subroutine.parameters
|
|
||||||
.filter { it.name !in namesInSub }
|
|
||||||
.forEach {
|
|
||||||
val vardecl = VarDecl(VarDeclType.VAR, it.type, ZeropageWish.NOT_IN_ZEROPAGE, null, it.name, null, null,
|
|
||||||
isArray = false, autogeneratedDontRemove = true, position = subroutine.position)
|
|
||||||
vardecl.linkParents(subroutine)
|
|
||||||
subroutine.statements.add(0, vardecl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) {
|
if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) {
|
||||||
checkResult.add(SyntaxError("asmsub can only contain inline assembly (%asm)", subroutine.position))
|
errors.err("asmsub can only contain inline assembly (%asm)", subroutine.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return super.visit(subroutine)
|
|
||||||
|
super.visit(subroutine)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(label: Label): Statement {
|
override fun visit(label: Label) {
|
||||||
if(label.name in AssemblyProgram.opcodeNames)
|
if(label.name in CompilationTarget.instance.machine.opcodeNames)
|
||||||
checkResult.add(NameError("can't use a cpu opcode name as a symbol: '${label.name}'", label.position))
|
errors.err("can't use a cpu opcode name as a symbol: '${label.name}'", label.position)
|
||||||
|
|
||||||
if(label.name in BuiltinFunctions) {
|
if(label.name in BuiltinFunctions) {
|
||||||
// the builtin functions can't be redefined
|
// the builtin functions can't be redefined
|
||||||
checkResult.add(NameError("builtin function cannot be redefined", label.position))
|
errors.err("builtin function cannot be redefined", label.position)
|
||||||
} else {
|
} else {
|
||||||
val existing = program.namespace.lookup(listOf(label.name), label)
|
val existing = label.definingSubroutine()?.getAllLabels(label.name) ?: emptyList()
|
||||||
if (existing != null && existing !== label)
|
for(el in existing) {
|
||||||
nameError(label.name, label.position, existing)
|
if(el === label || el.name != label.name)
|
||||||
}
|
continue
|
||||||
return super.visit(label)
|
else {
|
||||||
}
|
nameError(label.name, label.position, el)
|
||||||
|
break
|
||||||
override fun visit(forLoop: ForLoop): Statement {
|
|
||||||
// If the for loop has a decltype, it means to declare the loopvar inside the loop body
|
|
||||||
// rather than reusing an already declared loopvar from an outer scope.
|
|
||||||
// For loops that loop over an interable variable (instead of a range of numbers) get an
|
|
||||||
// additional interation count variable in their scope.
|
|
||||||
if(forLoop.loopRegister!=null) {
|
|
||||||
if(forLoop.loopRegister == Register.X)
|
|
||||||
printWarning("writing to the X register is dangerous, because it's used as an internal pointer", forLoop.position)
|
|
||||||
} else {
|
|
||||||
val loopVar = forLoop.loopVar
|
|
||||||
if (loopVar != null) {
|
|
||||||
val validName = forLoop.body.name.replace("<", "").replace(">", "").replace("-", "")
|
|
||||||
val loopvarName = "prog8_loopvar_$validName"
|
|
||||||
if (forLoop.iterable !is RangeExpr) {
|
|
||||||
val existing = if (forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(listOf(loopvarName), forLoop.body.statements.first())
|
|
||||||
if (existing == null) {
|
|
||||||
// create loop iteration counter variable (without value, to avoid an assignment)
|
|
||||||
val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.PREFER_ZEROPAGE, null, loopvarName, null, null,
|
|
||||||
isArray = false, autogeneratedDontRemove = true, position = loopVar.position)
|
|
||||||
vardecl.linkParents(forLoop.body)
|
|
||||||
forLoop.body.statements.add(0, vardecl)
|
|
||||||
loopVar.parent = forLoop.body // loopvar 'is defined in the body'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return super.visit(forLoop)
|
|
||||||
|
super.visit(label)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(assignTarget: AssignTarget): AssignTarget {
|
override fun visit(string: StringLiteralValue) {
|
||||||
if(assignTarget.register== Register.X)
|
if (string.value.length > 255)
|
||||||
printWarning("writing to the X register is dangerous, because it's used as an internal pointer", assignTarget.position)
|
errors.err("string literal length max is 255", string.position)
|
||||||
return super.visit(assignTarget)
|
|
||||||
|
super.visit(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(returnStmt: Return): Statement {
|
override fun visit(structDecl: StructDecl) {
|
||||||
if(returnStmt.value!=null) {
|
|
||||||
// possibly adjust any literal values returned, into the desired returning data type
|
|
||||||
val subroutine = returnStmt.definingSubroutine()!!
|
|
||||||
if(subroutine.returntypes.size!=1)
|
|
||||||
return returnStmt // mismatch in number of return values, error will be printed later.
|
|
||||||
val newValue: Expression
|
|
||||||
val lval = returnStmt.value as? NumericLiteralValue
|
|
||||||
if(lval!=null) {
|
|
||||||
newValue = lval.cast(subroutine.returntypes.single())
|
|
||||||
} else {
|
|
||||||
newValue = returnStmt.value!!
|
|
||||||
}
|
|
||||||
|
|
||||||
returnStmt.value = newValue
|
|
||||||
}
|
|
||||||
return super.visit(returnStmt)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(arrayLiteral: ArrayLiteralValue): Expression {
|
|
||||||
val array = super.visit(arrayLiteral)
|
|
||||||
if(array is ArrayLiteralValue) {
|
|
||||||
val vardecl = array.parent as? VarDecl
|
|
||||||
return if (vardecl!=null) {
|
|
||||||
fixupArrayDatatype(array, vardecl, program)
|
|
||||||
} else if(array.heapId!=null) {
|
|
||||||
// fix the datatype of the array (also on the heap) to the 'biggest' datatype in the array
|
|
||||||
// (we don't know the desired datatype here exactly so we guess)
|
|
||||||
val datatype = determineArrayDt(array.value)
|
|
||||||
val litval2 = array.cast(datatype)!!
|
|
||||||
litval2.parent = array.parent
|
|
||||||
// finally, replace the literal array by a identifier reference.
|
|
||||||
makeIdentifierFromRefLv(litval2)
|
|
||||||
} else
|
|
||||||
array
|
|
||||||
}
|
|
||||||
return array
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(stringLiteral: StringLiteralValue): Expression {
|
|
||||||
val string = super.visit(stringLiteral)
|
|
||||||
if(string is StringLiteralValue) {
|
|
||||||
val vardecl = string.parent as? VarDecl
|
|
||||||
// intern the string; move it into the heap
|
|
||||||
if (string.value.length !in 1..255)
|
|
||||||
checkResult.add(ExpressionError("string literal length must be between 1 and 255", string.position))
|
|
||||||
else {
|
|
||||||
string.addToHeap(program.heap)
|
|
||||||
}
|
|
||||||
return if (vardecl != null)
|
|
||||||
string
|
|
||||||
else
|
|
||||||
makeIdentifierFromRefLv(string) // replace the literal string by a identifier reference.
|
|
||||||
}
|
|
||||||
return string
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun determineArrayDt(array: Array<Expression>): DataType {
|
|
||||||
val datatypesInArray = array.map { it.inferType(program) }
|
|
||||||
if(datatypesInArray.isEmpty() || datatypesInArray.any { !it.isKnown })
|
|
||||||
throw IllegalArgumentException("can't determine type of empty array")
|
|
||||||
val dts = datatypesInArray.map { it.typeOrElse(DataType.STRUCT) }
|
|
||||||
return when {
|
|
||||||
DataType.FLOAT in dts -> DataType.ARRAY_F
|
|
||||||
DataType.WORD in dts -> DataType.ARRAY_W
|
|
||||||
DataType.UWORD in dts -> DataType.ARRAY_UW
|
|
||||||
DataType.BYTE in dts -> DataType.ARRAY_B
|
|
||||||
DataType.UBYTE in dts -> DataType.ARRAY_UB
|
|
||||||
else -> throw IllegalArgumentException("can't determine type of array")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun makeIdentifierFromRefLv(array: ArrayLiteralValue): IdentifierReference {
|
|
||||||
// a referencetype literal value that's not declared as a variable
|
|
||||||
// we need to introduce an auto-generated variable for this to be able to refer to the value
|
|
||||||
// note: if the var references the same literal value, it is not yet de-duplicated here.
|
|
||||||
array.addToHeap(program.heap)
|
|
||||||
val scope = array.definingScope()
|
|
||||||
val variable = VarDecl.createAuto(array)
|
|
||||||
return replaceWithIdentifier(variable, scope, array.parent)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun makeIdentifierFromRefLv(string: StringLiteralValue): IdentifierReference {
|
|
||||||
// a referencetype literal value that's not declared as a variable
|
|
||||||
// we need to introduce an auto-generated variable for this to be able to refer to the value
|
|
||||||
// note: if the var references the same literal value, it is not yet de-duplicated here.
|
|
||||||
string.addToHeap(program.heap)
|
|
||||||
val scope = string.definingScope()
|
|
||||||
val variable = VarDecl.createAuto(string)
|
|
||||||
return replaceWithIdentifier(variable, scope, string.parent)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun replaceWithIdentifier(variable: VarDecl, scope: INameScope, parent: Node): IdentifierReference {
|
|
||||||
val variable1 = addVarDecl(scope, variable)
|
|
||||||
// replace the reference literal by a identifier reference
|
|
||||||
val identifier = IdentifierReference(listOf(variable1.name), variable1.position)
|
|
||||||
identifier.parent = parent
|
|
||||||
return identifier
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(structDecl: StructDecl): Statement {
|
|
||||||
for(member in structDecl.statements){
|
for(member in structDecl.statements){
|
||||||
val decl = member as? VarDecl
|
val decl = member as? VarDecl
|
||||||
if(decl!=null && decl.datatype !in NumericDatatypes)
|
if(decl!=null && decl.datatype !in NumericDatatypes)
|
||||||
checkResult.add(SyntaxError("structs can only contain numerical types", decl.position))
|
errors.err("structs can only contain numerical types", decl.position)
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.visit(structDecl)
|
super.visit(structDecl)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(expr: BinaryExpression): Expression {
|
|
||||||
return when {
|
|
||||||
expr.left is StringLiteralValue ->
|
|
||||||
processBinaryExprWithString(expr.left as StringLiteralValue, expr.right, expr)
|
|
||||||
expr.right is StringLiteralValue ->
|
|
||||||
processBinaryExprWithString(expr.right as StringLiteralValue, expr.left, expr)
|
|
||||||
else -> super.visit(expr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun processBinaryExprWithString(string: StringLiteralValue, operand: Expression, expr: BinaryExpression): Expression {
|
|
||||||
val constvalue = operand.constValue(program)
|
|
||||||
if(constvalue!=null) {
|
|
||||||
if (expr.operator == "*") {
|
|
||||||
// repeat a string a number of times
|
|
||||||
val idt = string.inferType(program)
|
|
||||||
return StringLiteralValue(idt.typeOrElse(DataType.STR),
|
|
||||||
string.value.repeat(constvalue.number.toInt()), null, expr.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(expr.operator == "+" && operand is StringLiteralValue) {
|
|
||||||
// concatenate two strings
|
|
||||||
val idt = string.inferType(program)
|
|
||||||
return StringLiteralValue(idt.typeOrElse(DataType.STR),
|
|
||||||
"${string.value}${operand.value}", null, expr.position)
|
|
||||||
}
|
|
||||||
return expr
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addVarDecl(scope: INameScope, variable: VarDecl): VarDecl {
|
|
||||||
if(scope !in vardeclsToAdd)
|
|
||||||
vardeclsToAdd[scope] = mutableListOf()
|
|
||||||
val declList = vardeclsToAdd.getValue(scope)
|
|
||||||
val existing = declList.singleOrNull { it.name==variable.name }
|
|
||||||
return if(existing!=null) {
|
|
||||||
existing
|
|
||||||
} else {
|
|
||||||
declList.add(variable)
|
|
||||||
variable
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun fixupArrayDatatype(array: ArrayLiteralValue, program: Program): ArrayLiteralValue {
|
|
||||||
val dts = array.value.map {it.inferType(program).typeOrElse(DataType.STRUCT)}.toSet()
|
|
||||||
if(dts.any { it !in NumericDatatypes }) {
|
|
||||||
return array
|
|
||||||
}
|
|
||||||
val dt = when {
|
|
||||||
DataType.FLOAT in dts -> DataType.ARRAY_F
|
|
||||||
DataType.WORD in dts -> DataType.ARRAY_W
|
|
||||||
DataType.UWORD in dts -> DataType.ARRAY_UW
|
|
||||||
DataType.BYTE in dts -> DataType.ARRAY_B
|
|
||||||
else -> DataType.ARRAY_UB
|
|
||||||
}
|
|
||||||
if(dt==array.type)
|
|
||||||
return array
|
|
||||||
|
|
||||||
// convert values and array type
|
|
||||||
val elementType = ArrayElementTypes.getValue(dt)
|
|
||||||
val values = array.value.map { (it as NumericLiteralValue).cast(elementType) as Expression}.toTypedArray()
|
|
||||||
val array2 = ArrayLiteralValue(dt, values, array.heapId, array.position)
|
|
||||||
array2.linkParents(array.parent)
|
|
||||||
return array2
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun fixupArrayDatatype(array: ArrayLiteralValue, vardecl: VarDecl, program: Program): ArrayLiteralValue {
|
|
||||||
if(array.heapId!=null) {
|
|
||||||
val arrayDt = array.type
|
|
||||||
if(arrayDt!=vardecl.datatype) {
|
|
||||||
// fix the datatype of the array (also on the heap) to match the vardecl
|
|
||||||
val litval2 =
|
|
||||||
try {
|
|
||||||
val result = array.cast(vardecl.datatype)
|
|
||||||
if(result==null) {
|
|
||||||
val constElements = array.value.mapNotNull { it.constValue(program) }
|
|
||||||
val elementDts = constElements.map { it.type }
|
|
||||||
if(DataType.FLOAT in elementDts) {
|
|
||||||
array.cast(DataType.ARRAY_F) ?: ArrayLiteralValue(DataType.ARRAY_F, array.value, array.heapId, array.position)
|
|
||||||
} else {
|
|
||||||
val numbers = constElements.map { it.number.toInt() }
|
|
||||||
val minValue = numbers.min()!!
|
|
||||||
val maxValue = numbers.max()!!
|
|
||||||
if (minValue >= 0) {
|
|
||||||
// only positive values, so uword or ubyte
|
|
||||||
val dt = if(maxValue<256) DataType.ARRAY_UB else DataType.ARRAY_UW
|
|
||||||
array.cast(dt) ?: ArrayLiteralValue(dt, array.value, array.heapId, array.position)
|
|
||||||
} else {
|
|
||||||
// negative value present, so word or byte
|
|
||||||
val dt = if(minValue >= -128 && maxValue<=127) DataType.ARRAY_B else DataType.ARRAY_W
|
|
||||||
array.cast(dt) ?: ArrayLiteralValue(dt, array.value, array.heapId, array.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else result
|
|
||||||
} catch(x: ExpressionError) {
|
|
||||||
// couldn't cast permanently.
|
|
||||||
// instead, simply adjust the array type and trust the AstChecker to report the exact error
|
|
||||||
ArrayLiteralValue(vardecl.datatype, array.value, array.heapId, array.position)
|
|
||||||
}
|
|
||||||
vardecl.value = litval2
|
|
||||||
litval2.linkParents(vardecl)
|
|
||||||
litval2.addToHeap(program.heap)
|
|
||||||
return litval2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return array
|
|
||||||
}
|
}
|
||||||
|
@ -1,117 +0,0 @@
|
|||||||
package prog8.ast.processing
|
|
||||||
|
|
||||||
import prog8.ast.INameScope
|
|
||||||
import prog8.ast.base.AstException
|
|
||||||
import prog8.ast.expressions.FunctionCall
|
|
||||||
import prog8.ast.statements.FunctionCallStatement
|
|
||||||
import prog8.ast.statements.Subroutine
|
|
||||||
|
|
||||||
|
|
||||||
internal class AstRecursionChecker(private val namespace: INameScope) : IAstVisitor {
|
|
||||||
private val callGraph = DirectedGraph<INameScope>()
|
|
||||||
|
|
||||||
internal fun result(): List<AstException> {
|
|
||||||
val cycle = callGraph.checkForCycle()
|
|
||||||
if(cycle.isEmpty())
|
|
||||||
return emptyList()
|
|
||||||
val chain = cycle.joinToString(" <-- ") { "${it.name} at ${it.position}" }
|
|
||||||
return listOf(AstException("Program contains recursive subroutine calls, this is not supported. Recursive chain:\n (a subroutine call in) $chain"))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(functionCallStatement: FunctionCallStatement) {
|
|
||||||
val scope = functionCallStatement.definingScope()
|
|
||||||
val targetStatement = functionCallStatement.target.targetStatement(namespace)
|
|
||||||
if(targetStatement!=null) {
|
|
||||||
val targetScope = when (targetStatement) {
|
|
||||||
is Subroutine -> targetStatement
|
|
||||||
else -> targetStatement.definingScope()
|
|
||||||
}
|
|
||||||
callGraph.add(scope, targetScope)
|
|
||||||
}
|
|
||||||
super.visit(functionCallStatement)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(functionCall: FunctionCall) {
|
|
||||||
val scope = functionCall.definingScope()
|
|
||||||
val targetStatement = functionCall.target.targetStatement(namespace)
|
|
||||||
if(targetStatement!=null) {
|
|
||||||
val targetScope = when (targetStatement) {
|
|
||||||
is Subroutine -> targetStatement
|
|
||||||
else -> targetStatement.definingScope()
|
|
||||||
}
|
|
||||||
callGraph.add(scope, targetScope)
|
|
||||||
}
|
|
||||||
super.visit(functionCall)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private class DirectedGraph<VT> {
|
|
||||||
private val graph = mutableMapOf<VT, MutableSet<VT>>()
|
|
||||||
private var uniqueVertices = mutableSetOf<VT>()
|
|
||||||
val numVertices : Int
|
|
||||||
get() = uniqueVertices.size
|
|
||||||
|
|
||||||
fun add(from: VT, to: VT) {
|
|
||||||
var targets = graph[from]
|
|
||||||
if(targets==null) {
|
|
||||||
targets = mutableSetOf()
|
|
||||||
graph[from] = targets
|
|
||||||
}
|
|
||||||
targets.add(to)
|
|
||||||
uniqueVertices.add(from)
|
|
||||||
uniqueVertices.add(to)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun print() {
|
|
||||||
println("#vertices: $numVertices")
|
|
||||||
graph.forEach { (from, to) ->
|
|
||||||
println("$from CALLS:")
|
|
||||||
to.forEach { println(" $it") }
|
|
||||||
}
|
|
||||||
val cycle = checkForCycle()
|
|
||||||
if(cycle.isNotEmpty()) {
|
|
||||||
println("CYCLIC! $cycle")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun checkForCycle(): MutableList<VT> {
|
|
||||||
val visited = uniqueVertices.associateWith { false }.toMutableMap()
|
|
||||||
val recStack = uniqueVertices.associateWith { false }.toMutableMap()
|
|
||||||
val cycle = mutableListOf<VT>()
|
|
||||||
for(node in uniqueVertices) {
|
|
||||||
if(isCyclicUntil(node, visited, recStack, cycle))
|
|
||||||
return cycle
|
|
||||||
}
|
|
||||||
return mutableListOf()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isCyclicUntil(node: VT,
|
|
||||||
visited: MutableMap<VT, Boolean>,
|
|
||||||
recStack: MutableMap<VT, Boolean>,
|
|
||||||
cycleNodes: MutableList<VT>): Boolean {
|
|
||||||
|
|
||||||
if(recStack[node]==true) return true
|
|
||||||
if(visited[node]==true) return false
|
|
||||||
|
|
||||||
// mark current node as visited and add to recursion stack
|
|
||||||
visited[node] = true
|
|
||||||
recStack[node] = true
|
|
||||||
|
|
||||||
// recurse for all neighbours
|
|
||||||
val neighbors = graph[node]
|
|
||||||
if(neighbors!=null) {
|
|
||||||
for (neighbour in neighbors) {
|
|
||||||
if (isCyclicUntil(neighbour, visited, recStack, cycleNodes)) {
|
|
||||||
cycleNodes.add(node)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// pop node from recursion stack
|
|
||||||
recStack[node] = false
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
105
compiler/src/prog8/ast/processing/AstVariousTransforms.kt
Normal file
105
compiler/src/prog8/ast/processing/AstVariousTransforms.kt
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
package prog8.ast.processing
|
||||||
|
|
||||||
|
import prog8.ast.Node
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.*
|
||||||
|
import prog8.ast.expressions.*
|
||||||
|
import prog8.ast.statements.*
|
||||||
|
|
||||||
|
|
||||||
|
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> {
|
||||||
|
// For non-kernel subroutines and non-asm parameters:
|
||||||
|
// inject subroutine params as local variables (if they're not there yet).
|
||||||
|
val symbolsInSub = subroutine.allDefinedSymbols()
|
||||||
|
val namesInSub = symbolsInSub.map{ it.first }.toSet()
|
||||||
|
if(subroutine.asmAddress==null) {
|
||||||
|
if(subroutine.asmParameterRegisters.isEmpty() && subroutine.parameters.isNotEmpty()) {
|
||||||
|
val vars = subroutine.statements.filterIsInstance<VarDecl>().map { it.name }.toSet()
|
||||||
|
if(!vars.containsAll(subroutine.parameters.map{it.name})) {
|
||||||
|
return subroutine.parameters
|
||||||
|
.filter { it.name !in namesInSub }
|
||||||
|
.map {
|
||||||
|
val vardecl = ParameterVarDecl(it.name, it.type, subroutine.position)
|
||||||
|
IAstModification.InsertFirst(vardecl, subroutine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||||
|
val leftStr = expr.left as? StringLiteralValue
|
||||||
|
val rightStr = expr.right as? StringLiteralValue
|
||||||
|
if(expr.operator == "+") {
|
||||||
|
val concatenatedString = concatString(expr)
|
||||||
|
if(concatenatedString!=null)
|
||||||
|
return listOf(IAstModification.ReplaceNode(expr, concatenatedString, parent))
|
||||||
|
}
|
||||||
|
else if(expr.operator == "*") {
|
||||||
|
if (leftStr!=null) {
|
||||||
|
val amount = expr.right.constValue(program)
|
||||||
|
if(amount!=null) {
|
||||||
|
val string = leftStr.value.repeat(amount.number.toInt())
|
||||||
|
val strval = StringLiteralValue(string, leftStr.altEncoding, expr.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(expr, strval, parent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (rightStr!=null) {
|
||||||
|
val amount = expr.right.constValue(program)
|
||||||
|
if(amount!=null) {
|
||||||
|
val string = rightStr.value.repeat(amount.number.toInt())
|
||||||
|
val strval = StringLiteralValue(string, rightStr.altEncoding, expr.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(expr, strval, parent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun concatString(expr: BinaryExpression): StringLiteralValue? {
|
||||||
|
val rightStrval = expr.right as? StringLiteralValue
|
||||||
|
val leftStrval = expr.left as? StringLiteralValue
|
||||||
|
return when {
|
||||||
|
expr.operator!="+" -> null
|
||||||
|
expr.left is BinaryExpression && rightStrval!=null -> {
|
||||||
|
val subStrVal = concatString(expr.left as BinaryExpression)
|
||||||
|
if(subStrVal==null)
|
||||||
|
null
|
||||||
|
else
|
||||||
|
StringLiteralValue("${subStrVal.value}${rightStrval.value}", subStrVal.altEncoding, rightStrval.position)
|
||||||
|
}
|
||||||
|
expr.right is BinaryExpression && leftStrval!=null -> {
|
||||||
|
val subStrVal = concatString(expr.right as BinaryExpression)
|
||||||
|
if(subStrVal==null)
|
||||||
|
null
|
||||||
|
else
|
||||||
|
StringLiteralValue("${leftStrval.value}${subStrVal.value}", subStrVal.altEncoding, leftStrval.position)
|
||||||
|
}
|
||||||
|
leftStrval!=null && rightStrval!=null -> {
|
||||||
|
StringLiteralValue("${leftStrval.value}${rightStrval.value}", leftStrval.altEncoding, leftStrval.position)
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
424
compiler/src/prog8/ast/processing/AstWalker.kt
Normal file
424
compiler/src/prog8/ast/processing/AstWalker.kt
Normal file
@ -0,0 +1,424 @@
|
|||||||
|
package prog8.ast.processing
|
||||||
|
|
||||||
|
import prog8.ast.*
|
||||||
|
import prog8.ast.base.FatalAstException
|
||||||
|
import prog8.ast.expressions.*
|
||||||
|
import prog8.ast.statements.*
|
||||||
|
|
||||||
|
|
||||||
|
interface IAstModification {
|
||||||
|
fun perform()
|
||||||
|
|
||||||
|
class Remove(val node: Node, val parent: INameScope) : IAstModification {
|
||||||
|
override fun perform() {
|
||||||
|
if (!parent.statements.remove(node) && parent !is GlobalNamespace)
|
||||||
|
throw FatalAstException("attempt to remove non-existing node $node")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SetExpression(private val setter: (newExpr: Expression) -> Unit, private val newExpr: Expression, private val parent: Node) : IAstModification {
|
||||||
|
override fun perform() {
|
||||||
|
setter(newExpr)
|
||||||
|
newExpr.linkParents(parent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InsertFirst(private val stmt: Statement, private val parent: INameScope) : IAstModification {
|
||||||
|
override fun perform() {
|
||||||
|
parent.statements.add(0, stmt)
|
||||||
|
stmt.linkParents(parent as Node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InsertLast(private val stmt: Statement, private val parent: INameScope) : IAstModification {
|
||||||
|
override fun perform() {
|
||||||
|
parent.statements.add(stmt)
|
||||||
|
stmt.linkParents(parent as Node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InsertAfter(private val after: Statement, private val stmt: Statement, private val parent: INameScope) : IAstModification {
|
||||||
|
override fun perform() {
|
||||||
|
val idx = parent.statements.indexOfFirst { it===after } + 1
|
||||||
|
parent.statements.add(idx, stmt)
|
||||||
|
stmt.linkParents(parent as Node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InsertBefore(private val before: Statement, private val stmt: Statement, private val parent: INameScope) : IAstModification {
|
||||||
|
override fun perform() {
|
||||||
|
val idx = parent.statements.indexOfFirst { it===before }
|
||||||
|
parent.statements.add(idx, stmt)
|
||||||
|
stmt.linkParents(parent as Node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReplaceNode(private val node: Node, private val replacement: Node, private val parent: Node) : IAstModification {
|
||||||
|
override fun perform() {
|
||||||
|
parent.replaceChildNode(node, replacement)
|
||||||
|
replacement.linkParents(parent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SwapOperands(private val expr: BinaryExpression): IAstModification {
|
||||||
|
override fun perform() {
|
||||||
|
require(expr.operator in associativeOperators)
|
||||||
|
val tmp = expr.left
|
||||||
|
expr.left = expr.right
|
||||||
|
expr.right = tmp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
abstract class AstWalker {
|
||||||
|
open fun before(addressOf: AddressOf, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(assignTarget: AssignTarget, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(block: Block, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(branchStatement: BranchStatement, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(directive: Directive, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(expr: PrefixExpression, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(forLoop: ForLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(inlineAssembly: InlineAssembly, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(jump: Jump, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(label: Label, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(module: Module, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(numLiteral: NumericLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(postIncrDecr: PostIncrDecr, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(program: Program, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(range: RangeExpr, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(string: StringLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(structDecl: StructDecl, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
|
||||||
|
open fun after(addressOf: AddressOf, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(assignTarget: AssignTarget, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(block: Block, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(branchStatement: BranchStatement, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(breakStmt: Break, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(directive: Directive, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(inlineAssembly: InlineAssembly, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(jump: Jump, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(label: Label, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(module: Module, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(numLiteral: NumericLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(postIncrDecr: PostIncrDecr, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(program: Program, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(range: RangeExpr, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(structDecl: StructDecl, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun after(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
|
||||||
|
private val modifications = mutableListOf<Triple<IAstModification, Node, Node>>()
|
||||||
|
|
||||||
|
private fun track(mods: Iterable<IAstModification>, node: Node, parent: Node) {
|
||||||
|
for (it in mods) modifications += Triple(it, node, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun applyModifications(): Int {
|
||||||
|
modifications.forEach {
|
||||||
|
it.first.perform()
|
||||||
|
}
|
||||||
|
val amount = modifications.size
|
||||||
|
modifications.clear()
|
||||||
|
return amount
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(program: Program) {
|
||||||
|
track(before(program, program), program, program)
|
||||||
|
program.modules.forEach { it.accept(this, program) }
|
||||||
|
track(after(program, program), program, program)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(module: Module, parent: Node) {
|
||||||
|
track(before(module, parent), module, parent)
|
||||||
|
module.statements.forEach{ it.accept(this, module) }
|
||||||
|
track(after(module, parent), module, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(expr: PrefixExpression, parent: Node) {
|
||||||
|
track(before(expr, parent), expr, parent)
|
||||||
|
expr.expression.accept(this, expr)
|
||||||
|
track(after(expr, parent), expr, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(expr: BinaryExpression, parent: Node) {
|
||||||
|
track(before(expr, parent), expr, parent)
|
||||||
|
expr.left.accept(this, expr)
|
||||||
|
expr.right.accept(this, expr)
|
||||||
|
track(after(expr, parent), expr, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(directive: Directive, parent: Node) {
|
||||||
|
track(before(directive, parent), directive, parent)
|
||||||
|
track(after(directive, parent), directive, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(block: Block, parent: Node) {
|
||||||
|
track(before(block, parent), block, parent)
|
||||||
|
block.statements.forEach { it.accept(this, block) }
|
||||||
|
track(after(block, parent), block, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(decl: VarDecl, parent: Node) {
|
||||||
|
track(before(decl, parent), decl, parent)
|
||||||
|
decl.value?.accept(this, decl)
|
||||||
|
decl.arraysize?.accept(this, decl)
|
||||||
|
decl.struct?.accept(this, decl)
|
||||||
|
track(after(decl, parent), decl, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(subroutine: Subroutine, parent: Node) {
|
||||||
|
track(before(subroutine, parent), subroutine, parent)
|
||||||
|
subroutine.statements.forEach { it.accept(this, subroutine) }
|
||||||
|
track(after(subroutine, parent), subroutine, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(functionCall: FunctionCall, parent: Node) {
|
||||||
|
track(before(functionCall, parent), functionCall, parent)
|
||||||
|
functionCall.target.accept(this, functionCall)
|
||||||
|
functionCall.args.forEach { it.accept(this, functionCall) }
|
||||||
|
track(after(functionCall, parent), functionCall, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(functionCallStatement: FunctionCallStatement, parent: Node) {
|
||||||
|
track(before(functionCallStatement, parent), functionCallStatement, parent)
|
||||||
|
functionCallStatement.target.accept(this, functionCallStatement)
|
||||||
|
functionCallStatement.args.forEach { it.accept(this, functionCallStatement) }
|
||||||
|
track(after(functionCallStatement, parent), functionCallStatement, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(identifier: IdentifierReference, parent: Node) {
|
||||||
|
track(before(identifier, parent), identifier, parent)
|
||||||
|
track(after(identifier, parent), identifier, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(jump: Jump, parent: Node) {
|
||||||
|
track(before(jump, parent), jump, parent)
|
||||||
|
jump.identifier?.accept(this, jump)
|
||||||
|
track(after(jump, parent), jump, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(ifStatement: IfStatement, parent: Node) {
|
||||||
|
track(before(ifStatement, parent), ifStatement, parent)
|
||||||
|
ifStatement.condition.accept(this, ifStatement)
|
||||||
|
ifStatement.truepart.accept(this, ifStatement)
|
||||||
|
ifStatement.elsepart.accept(this, ifStatement)
|
||||||
|
track(after(ifStatement, parent), ifStatement, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(branchStatement: BranchStatement, parent: Node) {
|
||||||
|
track(before(branchStatement, parent), branchStatement, parent)
|
||||||
|
branchStatement.truepart.accept(this, branchStatement)
|
||||||
|
branchStatement.elsepart.accept(this, branchStatement)
|
||||||
|
track(after(branchStatement, parent), branchStatement, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(range: RangeExpr, parent: Node) {
|
||||||
|
track(before(range, parent), range, parent)
|
||||||
|
range.from.accept(this, range)
|
||||||
|
range.to.accept(this, range)
|
||||||
|
range.step.accept(this, range)
|
||||||
|
track(after(range, parent), range, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(label: Label, parent: Node) {
|
||||||
|
track(before(label, parent), label, parent)
|
||||||
|
track(after(label, parent), label, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(numLiteral: NumericLiteralValue, parent: Node) {
|
||||||
|
track(before(numLiteral, parent), numLiteral, parent)
|
||||||
|
track(after(numLiteral, parent), numLiteral, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(string: StringLiteralValue, parent: Node) {
|
||||||
|
track(before(string, parent), string, parent)
|
||||||
|
track(after(string, parent), string, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(array: ArrayLiteralValue, parent: Node) {
|
||||||
|
track(before(array, parent), array, parent)
|
||||||
|
array.value.forEach { v->v.accept(this, array) }
|
||||||
|
track(after(array, parent), array, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(assignment: Assignment, parent: Node) {
|
||||||
|
track(before(assignment, parent), assignment, parent)
|
||||||
|
assignment.target.accept(this, assignment)
|
||||||
|
assignment.value.accept(this, assignment)
|
||||||
|
track(after(assignment, parent), assignment, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(postIncrDecr: PostIncrDecr, parent: Node) {
|
||||||
|
track(before(postIncrDecr, parent), postIncrDecr, parent)
|
||||||
|
postIncrDecr.target.accept(this, postIncrDecr)
|
||||||
|
track(after(postIncrDecr, parent), postIncrDecr, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(breakStmt: Break, parent: Node) {
|
||||||
|
track(before(breakStmt, parent), breakStmt, parent)
|
||||||
|
track(after(breakStmt, parent), breakStmt, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(forLoop: ForLoop, parent: Node) {
|
||||||
|
track(before(forLoop, parent), forLoop, parent)
|
||||||
|
forLoop.loopVar.accept(this, forLoop)
|
||||||
|
forLoop.iterable.accept(this, forLoop)
|
||||||
|
forLoop.body.accept(this, forLoop)
|
||||||
|
track(after(forLoop, parent), forLoop, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(whileLoop: WhileLoop, parent: Node) {
|
||||||
|
track(before(whileLoop, parent), whileLoop, parent)
|
||||||
|
whileLoop.condition.accept(this, whileLoop)
|
||||||
|
whileLoop.body.accept(this, whileLoop)
|
||||||
|
track(after(whileLoop, parent), whileLoop, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(repeatLoop: RepeatLoop, parent: Node) {
|
||||||
|
track(before(repeatLoop, parent), repeatLoop, parent)
|
||||||
|
repeatLoop.iterations?.accept(this, repeatLoop)
|
||||||
|
repeatLoop.body.accept(this, repeatLoop)
|
||||||
|
track(after(repeatLoop, parent), repeatLoop, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(untilLoop: UntilLoop, parent: Node) {
|
||||||
|
track(before(untilLoop, parent), untilLoop, parent)
|
||||||
|
untilLoop.condition.accept(this, untilLoop)
|
||||||
|
untilLoop.body.accept(this, untilLoop)
|
||||||
|
track(after(untilLoop, parent), untilLoop, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(returnStmt: Return, parent: Node) {
|
||||||
|
track(before(returnStmt, parent), returnStmt, parent)
|
||||||
|
returnStmt.value?.accept(this, returnStmt)
|
||||||
|
track(after(returnStmt, parent), returnStmt, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(arrayIndexedExpression: ArrayIndexedExpression, parent: Node) {
|
||||||
|
track(before(arrayIndexedExpression, parent), arrayIndexedExpression, parent)
|
||||||
|
arrayIndexedExpression.arrayvar.accept(this, arrayIndexedExpression)
|
||||||
|
arrayIndexedExpression.indexer.accept(this, arrayIndexedExpression)
|
||||||
|
track(after(arrayIndexedExpression, parent), arrayIndexedExpression, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(assignTarget: AssignTarget, parent: Node) {
|
||||||
|
track(before(assignTarget, parent), assignTarget, parent)
|
||||||
|
assignTarget.arrayindexed?.accept(this, assignTarget)
|
||||||
|
assignTarget.identifier?.accept(this, assignTarget)
|
||||||
|
assignTarget.memoryAddress?.accept(this, assignTarget)
|
||||||
|
track(after(assignTarget, parent), assignTarget, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(scope: AnonymousScope, parent: Node) {
|
||||||
|
track(before(scope, parent), scope, parent)
|
||||||
|
scope.statements.forEach { it.accept(this, scope) }
|
||||||
|
track(after(scope, parent), scope, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(typecast: TypecastExpression, parent: Node) {
|
||||||
|
track(before(typecast, parent), typecast, parent)
|
||||||
|
typecast.expression.accept(this, typecast)
|
||||||
|
track(after(typecast, parent), typecast, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(memread: DirectMemoryRead, parent: Node) {
|
||||||
|
track(before(memread, parent), memread, parent)
|
||||||
|
memread.addressExpression.accept(this, memread)
|
||||||
|
track(after(memread, parent), memread, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(memwrite: DirectMemoryWrite, parent: Node) {
|
||||||
|
track(before(memwrite, parent), memwrite, parent)
|
||||||
|
memwrite.addressExpression.accept(this, memwrite)
|
||||||
|
track(after(memwrite, parent), memwrite, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(addressOf: AddressOf, parent: Node) {
|
||||||
|
track(before(addressOf, parent), addressOf, parent)
|
||||||
|
addressOf.identifier.accept(this, addressOf)
|
||||||
|
track(after(addressOf, parent), addressOf, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(inlineAssembly: InlineAssembly, parent: Node) {
|
||||||
|
track(before(inlineAssembly, parent), inlineAssembly, parent)
|
||||||
|
track(after(inlineAssembly, parent), inlineAssembly, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder, parent: Node) {
|
||||||
|
track(before(builtinFunctionStatementPlaceholder, parent), builtinFunctionStatementPlaceholder, parent)
|
||||||
|
track(after(builtinFunctionStatementPlaceholder, parent), builtinFunctionStatementPlaceholder, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(nopStatement: NopStatement, parent: Node) {
|
||||||
|
track(before(nopStatement, parent), nopStatement, parent)
|
||||||
|
track(after(nopStatement, parent), nopStatement, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(whenStatement: WhenStatement, parent: Node) {
|
||||||
|
track(before(whenStatement, parent), whenStatement, parent)
|
||||||
|
whenStatement.condition.accept(this, whenStatement)
|
||||||
|
whenStatement.choices.forEach { it.accept(this, whenStatement) }
|
||||||
|
track(after(whenStatement, parent), whenStatement, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(whenChoice: WhenChoice, parent: Node) {
|
||||||
|
track(before(whenChoice, parent), whenChoice, parent)
|
||||||
|
whenChoice.values?.forEach { it.accept(this, whenChoice) }
|
||||||
|
whenChoice.statements.accept(this, whenChoice)
|
||||||
|
track(after(whenChoice, parent), whenChoice, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(structDecl: StructDecl, parent: Node) {
|
||||||
|
track(before(structDecl, parent), structDecl, parent)
|
||||||
|
structDecl.statements.forEach { it.accept(this, structDecl) }
|
||||||
|
track(after(structDecl, parent), structDecl, parent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,263 +0,0 @@
|
|||||||
package prog8.ast.processing
|
|
||||||
|
|
||||||
import prog8.ast.Module
|
|
||||||
import prog8.ast.Program
|
|
||||||
import prog8.ast.base.FatalAstException
|
|
||||||
import prog8.ast.expressions.*
|
|
||||||
import prog8.ast.statements.*
|
|
||||||
|
|
||||||
interface IAstModifyingVisitor {
|
|
||||||
fun visit(program: Program) {
|
|
||||||
program.modules.forEach { visit(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(module: Module) {
|
|
||||||
module.statements = module.statements.map { it.accept(this) }.toMutableList()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(expr: PrefixExpression): Expression {
|
|
||||||
expr.expression = expr.expression.accept(this)
|
|
||||||
return expr
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(expr: BinaryExpression): Expression {
|
|
||||||
expr.left = expr.left.accept(this)
|
|
||||||
expr.right = expr.right.accept(this)
|
|
||||||
return expr
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(directive: Directive): Statement {
|
|
||||||
return directive
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(block: Block): Statement {
|
|
||||||
block.statements = block.statements.map { it.accept(this) }.toMutableList()
|
|
||||||
return block
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(decl: VarDecl): Statement {
|
|
||||||
decl.value = decl.value?.accept(this)
|
|
||||||
decl.arraysize?.accept(this)
|
|
||||||
return decl
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(subroutine: Subroutine): Statement {
|
|
||||||
subroutine.statements = subroutine.statements.map { it.accept(this) }.toMutableList()
|
|
||||||
return subroutine
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(functionCall: FunctionCall): Expression {
|
|
||||||
val newtarget = functionCall.target.accept(this)
|
|
||||||
if(newtarget is IdentifierReference)
|
|
||||||
functionCall.target = newtarget
|
|
||||||
else
|
|
||||||
throw FatalAstException("cannot change class of function call target")
|
|
||||||
functionCall.arglist = functionCall.arglist.map { it.accept(this) }.toMutableList()
|
|
||||||
return functionCall
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(functionCallStatement: FunctionCallStatement): Statement {
|
|
||||||
val newtarget = functionCallStatement.target.accept(this)
|
|
||||||
if(newtarget is IdentifierReference)
|
|
||||||
functionCallStatement.target = newtarget
|
|
||||||
else
|
|
||||||
throw FatalAstException("cannot change class of function call target")
|
|
||||||
functionCallStatement.arglist = functionCallStatement.arglist.map { it.accept(this) }.toMutableList()
|
|
||||||
return functionCallStatement
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(identifier: IdentifierReference): Expression {
|
|
||||||
// note: this is an identifier that is used in an expression.
|
|
||||||
// other identifiers are simply part of the other statements (such as jumps, subroutine defs etc)
|
|
||||||
return identifier
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(jump: Jump): Statement {
|
|
||||||
if(jump.identifier!=null) {
|
|
||||||
val ident = jump.identifier.accept(this)
|
|
||||||
if(ident is IdentifierReference && ident!==jump.identifier) {
|
|
||||||
return Jump(null, ident, null, jump.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return jump
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(ifStatement: IfStatement): Statement {
|
|
||||||
ifStatement.condition = ifStatement.condition.accept(this)
|
|
||||||
ifStatement.truepart = ifStatement.truepart.accept(this) as AnonymousScope
|
|
||||||
ifStatement.elsepart = ifStatement.elsepart.accept(this) as AnonymousScope
|
|
||||||
return ifStatement
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(branchStatement: BranchStatement): Statement {
|
|
||||||
branchStatement.truepart = branchStatement.truepart.accept(this) as AnonymousScope
|
|
||||||
branchStatement.elsepart = branchStatement.elsepart.accept(this) as AnonymousScope
|
|
||||||
return branchStatement
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(range: RangeExpr): Expression {
|
|
||||||
range.from = range.from.accept(this)
|
|
||||||
range.to = range.to.accept(this)
|
|
||||||
range.step = range.step.accept(this)
|
|
||||||
return range
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(label: Label): Statement {
|
|
||||||
return label
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(literalValue: NumericLiteralValue): NumericLiteralValue {
|
|
||||||
return literalValue
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(stringLiteral: StringLiteralValue): Expression {
|
|
||||||
return stringLiteral
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(arrayLiteral: ArrayLiteralValue): Expression {
|
|
||||||
for(av in arrayLiteral.value.withIndex()) {
|
|
||||||
val newvalue = av.value.accept(this)
|
|
||||||
arrayLiteral.value[av.index] = newvalue
|
|
||||||
}
|
|
||||||
return arrayLiteral
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(assignment: Assignment): Statement {
|
|
||||||
assignment.target = assignment.target.accept(this)
|
|
||||||
assignment.value = assignment.value.accept(this)
|
|
||||||
return assignment
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(postIncrDecr: PostIncrDecr): Statement {
|
|
||||||
postIncrDecr.target = postIncrDecr.target.accept(this)
|
|
||||||
return postIncrDecr
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(contStmt: Continue): Statement {
|
|
||||||
return contStmt
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(breakStmt: Break): Statement {
|
|
||||||
return breakStmt
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(forLoop: ForLoop): Statement {
|
|
||||||
val newloopvar = forLoop.loopVar?.accept(this)
|
|
||||||
when(newloopvar) {
|
|
||||||
is IdentifierReference -> forLoop.loopVar = newloopvar
|
|
||||||
null -> forLoop.loopVar = null
|
|
||||||
else -> throw FatalAstException("can't change class of loopvar")
|
|
||||||
}
|
|
||||||
forLoop.iterable = forLoop.iterable.accept(this)
|
|
||||||
forLoop.body = forLoop.body.accept(this) as AnonymousScope
|
|
||||||
return forLoop
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(whileLoop: WhileLoop): Statement {
|
|
||||||
whileLoop.condition = whileLoop.condition.accept(this)
|
|
||||||
whileLoop.body = whileLoop.body.accept(this) as AnonymousScope
|
|
||||||
return whileLoop
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(repeatLoop: RepeatLoop): Statement {
|
|
||||||
repeatLoop.untilCondition = repeatLoop.untilCondition.accept(this)
|
|
||||||
repeatLoop.body = repeatLoop.body.accept(this) as AnonymousScope
|
|
||||||
return repeatLoop
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(returnStmt: Return): Statement {
|
|
||||||
returnStmt.value = returnStmt.value?.accept(this)
|
|
||||||
return returnStmt
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(arrayIndexedExpression: ArrayIndexedExpression): ArrayIndexedExpression {
|
|
||||||
val ident = arrayIndexedExpression.identifier.accept(this)
|
|
||||||
if(ident is IdentifierReference)
|
|
||||||
arrayIndexedExpression.identifier = ident
|
|
||||||
arrayIndexedExpression.arrayspec.accept(this)
|
|
||||||
return arrayIndexedExpression
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(assignTarget: AssignTarget): AssignTarget {
|
|
||||||
val ident = assignTarget.identifier?.accept(this)
|
|
||||||
when (ident) {
|
|
||||||
is IdentifierReference -> assignTarget.identifier = ident
|
|
||||||
null -> assignTarget.identifier = null
|
|
||||||
else -> throw FatalAstException("can't change class of assign target identifier")
|
|
||||||
}
|
|
||||||
assignTarget.arrayindexed = assignTarget.arrayindexed?.accept(this)
|
|
||||||
assignTarget.memoryAddress?.let { visit(it) }
|
|
||||||
return assignTarget
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(scope: AnonymousScope): Statement {
|
|
||||||
scope.statements = scope.statements.map { it.accept(this) }.toMutableList()
|
|
||||||
return scope
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(typecast: TypecastExpression): Expression {
|
|
||||||
typecast.expression = typecast.expression.accept(this)
|
|
||||||
return typecast
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(memread: DirectMemoryRead): Expression {
|
|
||||||
memread.addressExpression = memread.addressExpression.accept(this)
|
|
||||||
return memread
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(memwrite: DirectMemoryWrite) {
|
|
||||||
memwrite.addressExpression = memwrite.addressExpression.accept(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(addressOf: AddressOf): Expression {
|
|
||||||
val ident = addressOf.identifier.accept(this)
|
|
||||||
if(ident is IdentifierReference)
|
|
||||||
addressOf.identifier = ident
|
|
||||||
else
|
|
||||||
throw FatalAstException("can't change class of addressof identifier")
|
|
||||||
return addressOf
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(inlineAssembly: InlineAssembly): Statement {
|
|
||||||
return inlineAssembly
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(registerExpr: RegisterExpr): Expression {
|
|
||||||
return registerExpr
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder): Statement {
|
|
||||||
return builtinFunctionStatementPlaceholder
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(nopStatement: NopStatement): Statement {
|
|
||||||
return nopStatement
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(whenStatement: WhenStatement): Statement {
|
|
||||||
whenStatement.condition = whenStatement.condition.accept(this)
|
|
||||||
whenStatement.choices.forEach { it.accept(this) }
|
|
||||||
return whenStatement
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(whenChoice: WhenChoice) {
|
|
||||||
whenChoice.values = whenChoice.values?.map { it.accept(this) }
|
|
||||||
val stmt = whenChoice.statements.accept(this)
|
|
||||||
if(stmt is AnonymousScope)
|
|
||||||
whenChoice.statements = stmt
|
|
||||||
else {
|
|
||||||
whenChoice.statements = AnonymousScope(mutableListOf(stmt), stmt.position)
|
|
||||||
whenChoice.statements.linkParents(whenChoice)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(structDecl: StructDecl): Statement {
|
|
||||||
structDecl.statements = structDecl.statements.map{ it.accept(this) }.toMutableList()
|
|
||||||
return structDecl
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(structLv: StructLiteralValue): Expression {
|
|
||||||
structLv.values = structLv.values.map { it.accept(this) }
|
|
||||||
return structLv
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,7 +7,7 @@ import prog8.ast.statements.*
|
|||||||
|
|
||||||
interface IAstVisitor {
|
interface IAstVisitor {
|
||||||
fun visit(program: Program) {
|
fun visit(program: Program) {
|
||||||
program.modules.forEach { visit(it) }
|
program.modules.forEach { it.accept(this) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(module: Module) {
|
fun visit(module: Module) {
|
||||||
@ -33,6 +33,7 @@ interface IAstVisitor {
|
|||||||
fun visit(decl: VarDecl) {
|
fun visit(decl: VarDecl) {
|
||||||
decl.value?.accept(this)
|
decl.value?.accept(this)
|
||||||
decl.arraysize?.accept(this)
|
decl.arraysize?.accept(this)
|
||||||
|
decl.struct?.accept(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(subroutine: Subroutine) {
|
fun visit(subroutine: Subroutine) {
|
||||||
@ -41,12 +42,12 @@ interface IAstVisitor {
|
|||||||
|
|
||||||
fun visit(functionCall: FunctionCall) {
|
fun visit(functionCall: FunctionCall) {
|
||||||
functionCall.target.accept(this)
|
functionCall.target.accept(this)
|
||||||
functionCall.arglist.forEach { it.accept(this) }
|
functionCall.args.forEach { it.accept(this) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(functionCallStatement: FunctionCallStatement) {
|
fun visit(functionCallStatement: FunctionCallStatement) {
|
||||||
functionCallStatement.target.accept(this)
|
functionCallStatement.target.accept(this)
|
||||||
functionCallStatement.arglist.forEach { it.accept(this) }
|
functionCallStatement.args.forEach { it.accept(this) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(identifier: IdentifierReference) {
|
fun visit(identifier: IdentifierReference) {
|
||||||
@ -95,14 +96,11 @@ interface IAstVisitor {
|
|||||||
postIncrDecr.target.accept(this)
|
postIncrDecr.target.accept(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(contStmt: Continue) {
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(breakStmt: Break) {
|
fun visit(breakStmt: Break) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(forLoop: ForLoop) {
|
fun visit(forLoop: ForLoop) {
|
||||||
forLoop.loopVar?.accept(this)
|
forLoop.loopVar.accept(this)
|
||||||
forLoop.iterable.accept(this)
|
forLoop.iterable.accept(this)
|
||||||
forLoop.body.accept(this)
|
forLoop.body.accept(this)
|
||||||
}
|
}
|
||||||
@ -113,17 +111,22 @@ interface IAstVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun visit(repeatLoop: RepeatLoop) {
|
fun visit(repeatLoop: RepeatLoop) {
|
||||||
repeatLoop.untilCondition.accept(this)
|
repeatLoop.iterations?.accept(this)
|
||||||
repeatLoop.body.accept(this)
|
repeatLoop.body.accept(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun visit(untilLoop: UntilLoop) {
|
||||||
|
untilLoop.condition.accept(this)
|
||||||
|
untilLoop.body.accept(this)
|
||||||
|
}
|
||||||
|
|
||||||
fun visit(returnStmt: Return) {
|
fun visit(returnStmt: Return) {
|
||||||
returnStmt.value?.accept(this)
|
returnStmt.value?.accept(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
|
fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
|
||||||
arrayIndexedExpression.identifier.accept(this)
|
arrayIndexedExpression.arrayvar.accept(this)
|
||||||
arrayIndexedExpression.arrayspec.accept(this)
|
arrayIndexedExpression.indexer.accept(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(assignTarget: AssignTarget) {
|
fun visit(assignTarget: AssignTarget) {
|
||||||
@ -155,9 +158,6 @@ interface IAstVisitor {
|
|||||||
fun visit(inlineAssembly: InlineAssembly) {
|
fun visit(inlineAssembly: InlineAssembly) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(registerExpr: RegisterExpr) {
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder) {
|
fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,8 +177,4 @@ interface IAstVisitor {
|
|||||||
fun visit(structDecl: StructDecl) {
|
fun visit(structDecl: StructDecl) {
|
||||||
structDecl.statements.forEach { it.accept(this) }
|
structDecl.statements.forEach { it.accept(this) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(structLv: StructLiteralValue) {
|
|
||||||
structLv.values.forEach { it.accept(this) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,36 +1,22 @@
|
|||||||
package prog8.ast.processing
|
package prog8.ast.processing
|
||||||
|
|
||||||
import prog8.ast.Module
|
import prog8.ast.INameScope
|
||||||
import prog8.ast.base.SyntaxError
|
import prog8.ast.Node
|
||||||
import prog8.ast.base.printWarning
|
|
||||||
import prog8.ast.statements.Directive
|
import prog8.ast.statements.Directive
|
||||||
import prog8.ast.statements.Statement
|
|
||||||
|
|
||||||
internal class ImportedModuleDirectiveRemover : IAstModifyingVisitor {
|
|
||||||
private val checkResult: MutableList<SyntaxError> = mutableListOf()
|
|
||||||
|
|
||||||
internal fun result(): List<SyntaxError> {
|
|
||||||
return checkResult
|
|
||||||
}
|
|
||||||
|
|
||||||
|
internal class ImportedModuleDirectiveRemover: AstWalker() {
|
||||||
/**
|
/**
|
||||||
* Most global directives don't apply for imported modules, so remove them
|
* Most global directives don't apply for imported modules, so remove them
|
||||||
*/
|
*/
|
||||||
override fun visit(module: Module) {
|
|
||||||
super.visit(module)
|
|
||||||
val newStatements : MutableList<Statement> = mutableListOf()
|
|
||||||
|
|
||||||
val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address")
|
private val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address")
|
||||||
for (sourceStmt in module.statements) {
|
private val noModifications = emptyList<IAstModification>()
|
||||||
val stmt = sourceStmt.accept(this)
|
|
||||||
if(stmt is Directive && stmt.parent is Module) {
|
override fun before(directive: Directive, parent: Node): Iterable<IAstModification> {
|
||||||
if(stmt.directive in moduleLevelDirectives) {
|
if(directive.directive in moduleLevelDirectives) {
|
||||||
printWarning("ignoring module directive because it was imported", stmt.position, stmt.directive)
|
return listOf(IAstModification.Remove(directive, parent as INameScope))
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newStatements.add(stmt)
|
|
||||||
}
|
}
|
||||||
module.statements = newStatements
|
return noModifications
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
53
compiler/src/prog8/ast/processing/LiteralsToAutoVars.kt
Normal file
53
compiler/src/prog8/ast/processing/LiteralsToAutoVars.kt
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package prog8.ast.processing
|
||||||
|
|
||||||
|
import prog8.ast.Node
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.*
|
||||||
|
import prog8.ast.expressions.*
|
||||||
|
import prog8.ast.statements.*
|
||||||
|
|
||||||
|
|
||||||
|
internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
|
||||||
|
private val noModifications = emptyList<IAstModification>()
|
||||||
|
|
||||||
|
override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||||
|
if(string.parent !is VarDecl && string.parent !is WhenChoice) {
|
||||||
|
// replace the literal string by a identifier reference to a new local vardecl
|
||||||
|
val vardecl = VarDecl.createAuto(string)
|
||||||
|
val identifier = IdentifierReference(listOf(vardecl.name), vardecl.position)
|
||||||
|
return listOf(
|
||||||
|
IAstModification.ReplaceNode(string, identifier, parent),
|
||||||
|
IAstModification.InsertFirst(vardecl, string.definingScope())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||||
|
val vardecl = array.parent as? VarDecl
|
||||||
|
if(vardecl!=null) {
|
||||||
|
// adjust the datatype of the array (to an educated guess)
|
||||||
|
val arrayDt = array.type
|
||||||
|
if(!arrayDt.istype(vardecl.datatype)) {
|
||||||
|
val cast = array.cast(vardecl.datatype)
|
||||||
|
if (cast != null && cast !== array)
|
||||||
|
return listOf(IAstModification.ReplaceNode(vardecl.value!!, cast, vardecl))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val arrayDt = array.guessDatatype(program)
|
||||||
|
if(arrayDt.isKnown) {
|
||||||
|
// this array literal is part of an expression, turn it into an identifier reference
|
||||||
|
val litval2 = array.cast(arrayDt.typeOrElse(DataType.STRUCT))
|
||||||
|
if(litval2!=null) {
|
||||||
|
val vardecl2 = VarDecl.createAuto(litval2)
|
||||||
|
val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position)
|
||||||
|
return listOf(
|
||||||
|
IAstModification.ReplaceNode(array, identifier, parent),
|
||||||
|
IAstModification.InsertFirst(vardecl2, array.definingScope())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
}
|
71
compiler/src/prog8/ast/processing/ReflectionAstWalker.kt
Normal file
71
compiler/src/prog8/ast/processing/ReflectionAstWalker.kt
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package prog8.ast.processing
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
This is here for reference only, reflection based ast walking is very slow
|
||||||
|
when compared to the more verbose visitor pattern interfaces.
|
||||||
|
Too bad, because the code is very small
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
//import prog8.ast.NoAstWalk
|
||||||
|
//import prog8.ast.Node
|
||||||
|
//import prog8.ast.Program
|
||||||
|
//import prog8.ast.base.Position
|
||||||
|
//import prog8.ast.expressions.BinaryExpression
|
||||||
|
//import prog8.ast.expressions.NumericLiteralValue
|
||||||
|
//import kotlin.reflect.KClass
|
||||||
|
//import kotlin.reflect.KVisibility
|
||||||
|
//import kotlin.reflect.full.declaredMemberProperties
|
||||||
|
//import kotlin.reflect.full.isSubtypeOf
|
||||||
|
//import kotlin.reflect.full.starProjectedType
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//class ReflectionAstWalker {
|
||||||
|
// private val nodeType = Node::class.starProjectedType
|
||||||
|
// private val collectionType = Collection::class.starProjectedType
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// fun walk(node: Node, nesting: Int) {
|
||||||
|
// val nodetype: KClass<out Node> = node::class
|
||||||
|
// val indent = " ".repeat(nesting)
|
||||||
|
// //println("$indent VISITING ${nodetype.simpleName}")
|
||||||
|
// val visibleAstMembers = nodetype.declaredMemberProperties.filter {
|
||||||
|
// it.visibility!=KVisibility.PRIVATE && !it.isLateinit &&
|
||||||
|
// !(it.annotations.any{a->a is NoAstWalk})
|
||||||
|
// }
|
||||||
|
// for(prop in visibleAstMembers) {
|
||||||
|
// if(prop.returnType.isSubtypeOf(nodeType)) {
|
||||||
|
// // println("$indent +PROP: ${prop.name}")
|
||||||
|
// walk(prop.call(node) as Node, nesting + 1)
|
||||||
|
// }
|
||||||
|
// else if(prop.returnType.isSubtypeOf(collectionType)) {
|
||||||
|
// val elementType = prop.returnType.arguments.single().type
|
||||||
|
// if(elementType!=null && elementType.isSubtypeOf(nodeType)) {
|
||||||
|
// val nodes = prop.call(node) as Collection<Node>
|
||||||
|
// nodes.forEach { walk(it, nesting+1) }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// fun walk(program: Program) {
|
||||||
|
// for(module in program.modules) {
|
||||||
|
// println("---MODULE $module---")
|
||||||
|
// walk(module, 0)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//fun main() {
|
||||||
|
// val ast = BinaryExpression(
|
||||||
|
// NumericLiteralValue.optimalInteger(100, Position.DUMMY),
|
||||||
|
// "+",
|
||||||
|
// NumericLiteralValue.optimalInteger(200, Position.DUMMY),
|
||||||
|
// Position.DUMMY
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// val walker = ReflectionAstWalker()
|
||||||
|
// walker.walk(ast,0)
|
||||||
|
//
|
||||||
|
//}
|
@ -1,259 +1,370 @@
|
|||||||
package prog8.ast.processing
|
package prog8.ast.processing
|
||||||
|
|
||||||
import prog8.ast.*
|
import prog8.ast.*
|
||||||
import prog8.ast.base.DataType
|
import prog8.ast.base.*
|
||||||
import prog8.ast.base.FatalAstException
|
|
||||||
import prog8.ast.base.initvarsSubName
|
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.mangledStructMemberName
|
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
|
|
||||||
|
|
||||||
private fun flattenStructAssignmentFromIdentifier(structAssignment: Assignment, program: Program): List<Assignment> {
|
internal class StatementReorderer(val program: Program, val errors: ErrorReporter) : AstWalker() {
|
||||||
val identifier = structAssignment.target.identifier!!
|
|
||||||
val identifierName = identifier.nameInSource.single()
|
|
||||||
val targetVar = identifier.targetVarDecl(program.namespace)!!
|
|
||||||
val struct = targetVar.struct!!
|
|
||||||
when {
|
|
||||||
structAssignment.value is IdentifierReference -> {
|
|
||||||
val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program.namespace)!!
|
|
||||||
if (sourceVar.struct == null)
|
|
||||||
throw FatalAstException("can only assign arrays or structs to structs")
|
|
||||||
// struct memberwise copy
|
|
||||||
val sourceStruct = sourceVar.struct!!
|
|
||||||
if(sourceStruct!==targetVar.struct) {
|
|
||||||
// structs are not the same in assignment
|
|
||||||
return listOf() // error will be printed elsewhere
|
|
||||||
}
|
|
||||||
return struct.statements.zip(sourceStruct.statements).map { member ->
|
|
||||||
val targetDecl = member.first as VarDecl
|
|
||||||
val sourceDecl = member.second as VarDecl
|
|
||||||
if(targetDecl.name != sourceDecl.name)
|
|
||||||
throw FatalAstException("struct member mismatch")
|
|
||||||
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
|
|
||||||
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
|
|
||||||
val sourcemangled = mangledStructMemberName(sourceVar.name, sourceDecl.name)
|
|
||||||
val sourceIdref = IdentifierReference(listOf(sourcemangled), structAssignment.position)
|
|
||||||
val assign = Assignment(AssignTarget(null, idref, null, null, structAssignment.position),
|
|
||||||
null, sourceIdref, member.second.position)
|
|
||||||
assign.linkParents(structAssignment)
|
|
||||||
assign
|
|
||||||
}
|
|
||||||
}
|
|
||||||
structAssignment.value is StructLiteralValue -> {
|
|
||||||
throw IllegalArgumentException("not going to flatten a structLv assignment here")
|
|
||||||
}
|
|
||||||
else -> throw FatalAstException("strange struct value")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
internal class StatementReorderer(private val program: Program): IAstModifyingVisitor {
|
|
||||||
// Reorders the statements in a way the compiler needs.
|
// Reorders the statements in a way the compiler needs.
|
||||||
// - 'main' block must be the very first statement UNLESS it has an address set.
|
// - 'main' block must be the very first statement UNLESS it has an address set.
|
||||||
// - blocks are ordered by address, where blocks without address are put at the end.
|
// - library blocks are put last.
|
||||||
// - in every scope:
|
// - blocks are ordered by address, where blocks without address are placed last.
|
||||||
// -- the directives '%output', '%launcher', '%zeropage', '%zpreserved', '%address' and '%option' will come first.
|
// - in every block and module, most directives and vardecls are moved to the top. (not in subroutines!)
|
||||||
// -- all vardecls then follow.
|
// - the 'start' subroutine is moved to the top.
|
||||||
// -- the remaining statements then follow in their original order.
|
// - (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.
|
||||||
// - the 'start' subroutine in the 'main' block will be moved to the top immediately following the directives.
|
// - in-place assignments are reordered a bit so that they are mostly of the form A = A <operator> <rest>
|
||||||
// - all other subroutines will be moved to the end of their block.
|
|
||||||
// - 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).
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
private val addReturns = mutableListOf<Pair<INameScope, Int>>()
|
override fun after(module: Module, parent: Node): Iterable<IAstModification> {
|
||||||
|
|
||||||
override fun visit(module: Module) {
|
|
||||||
addReturns.clear()
|
|
||||||
super.visit(module)
|
|
||||||
|
|
||||||
val (blocks, other) = module.statements.partition { it is Block }
|
val (blocks, other) = module.statements.partition { it is Block }
|
||||||
module.statements = other.asSequence().plus(blocks.sortedBy { (it as Block).address ?: Int.MAX_VALUE }).toMutableList()
|
module.statements = other.asSequence().plus(blocks.sortedBy { (it as Block).address ?: Int.MAX_VALUE }).toMutableList()
|
||||||
|
|
||||||
// make sure user-defined blocks come BEFORE library blocks, and move the "main" block to the top of everything
|
val mainBlock = module.statements.filterIsInstance<Block>().firstOrNull { it.name=="main" }
|
||||||
val nonLibraryBlocks = module.statements.withIndex()
|
if(mainBlock!=null && mainBlock.address==null) {
|
||||||
.filter { it.value is Block && !(it.value as Block).isInLibrary }
|
module.statements.remove(mainBlock)
|
||||||
.map { it.index to it.value }
|
|
||||||
.reversed()
|
|
||||||
for(nonLibBlock in nonLibraryBlocks)
|
|
||||||
module.statements.removeAt(nonLibBlock.first)
|
|
||||||
for(nonLibBlock in nonLibraryBlocks)
|
|
||||||
module.statements.add(0, nonLibBlock.second)
|
|
||||||
val mainBlock = module.statements.singleOrNull { it is Block && it.name=="main" }
|
|
||||||
if(mainBlock!=null && (mainBlock as Block).address==null) {
|
|
||||||
module.remove(mainBlock)
|
|
||||||
module.statements.add(0, mainBlock)
|
module.statements.add(0, mainBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
val varDecls = module.statements.filterIsInstance<VarDecl>()
|
reorderVardeclsAndDirectives(module.statements)
|
||||||
module.statements.removeAll(varDecls)
|
return noModifications
|
||||||
module.statements.addAll(0, varDecls)
|
}
|
||||||
|
|
||||||
val directives = module.statements.filter {it is Directive && it.directive in directivesToMove}
|
private fun reorderVardeclsAndDirectives(statements: MutableList<Statement>) {
|
||||||
module.statements.removeAll(directives)
|
val varDecls = statements.filterIsInstance<VarDecl>()
|
||||||
module.statements.addAll(0, directives)
|
statements.removeAll(varDecls)
|
||||||
|
statements.addAll(0, varDecls)
|
||||||
|
|
||||||
for(pos in addReturns) {
|
val directives = statements.filterIsInstance<Directive>().filter {it.directive in directivesToMove}
|
||||||
println(pos)
|
statements.removeAll(directives)
|
||||||
val returnStmt = Return(null, pos.first.position)
|
statements.addAll(0, directives)
|
||||||
returnStmt.linkParents(pos.first as Node)
|
}
|
||||||
pos.first.statements.add(pos.second, returnStmt)
|
|
||||||
|
override fun before(block: Block, parent: Node): Iterable<IAstModification> {
|
||||||
|
parent as Module
|
||||||
|
if(block.isInLibrary) {
|
||||||
|
return listOf(
|
||||||
|
IAstModification.Remove(block, parent),
|
||||||
|
IAstModification.InsertLast(block, parent)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
reorderVardeclsAndDirectives(block.statements)
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||||
|
if(subroutine.name=="start" && parent is Block) {
|
||||||
|
if(parent.statements.filterIsInstance<Subroutine>().first().name!="start") {
|
||||||
|
return listOf(
|
||||||
|
IAstModification.Remove(subroutine, parent),
|
||||||
|
IAstModification.InsertFirst(subroutine, parent)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||||
|
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 visit(block: Block): Statement {
|
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 indexerVarName: String
|
||||||
|
val repo = subroutine.asmGenInfo.usedAutoArrayIndexerForStatements
|
||||||
|
|
||||||
val subroutines = block.statements.filterIsInstance<Subroutine>()
|
val freeVar = repo.filter { statement !in it.value }.map { it.key }.firstOrNull()
|
||||||
var numSubroutinesAtEnd = 0
|
if(freeVar==null) {
|
||||||
// move all subroutines to the end of the block
|
// add another loop index var to be used for this expression
|
||||||
for (subroutine in subroutines) {
|
val statementId = expr.hashCode()
|
||||||
if(subroutine.name!="start" || block.name!="main") {
|
indexerVarName = "$indexerVarPrefix$statementId"
|
||||||
block.remove(subroutine)
|
// create the indexer var at block level scope
|
||||||
block.statements.add(subroutine)
|
val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.PREFER_ZEROPAGE,
|
||||||
}
|
null, indexerVarName, null, null, isArray = false, autogeneratedDontRemove = true, position = expr.position)
|
||||||
numSubroutinesAtEnd++
|
modifications.add(IAstModification.InsertFirst(vardecl, subroutine))
|
||||||
}
|
var statements = repo[indexerVarName]
|
||||||
// move the "start" subroutine to the top
|
if(statements==null) {
|
||||||
if(block.name=="main") {
|
statements = mutableSetOf()
|
||||||
block.statements.singleOrNull { it is Subroutine && it.name == "start" } ?.let {
|
repo[indexerVarName] = statements
|
||||||
block.remove(it)
|
|
||||||
block.statements.add(0, it)
|
|
||||||
numSubroutinesAtEnd--
|
|
||||||
}
|
}
|
||||||
|
statements.add(statement)
|
||||||
|
} else {
|
||||||
|
// reuse an already created indexer autovar
|
||||||
|
repo.getValue(freeVar).add(statement)
|
||||||
|
indexerVarName = freeVar
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure there is a 'return' in front of the first subroutine
|
// assign the indexing expression to the helper variable, and replace the indexer with just the variable
|
||||||
// (if it isn't the first statement in the block itself, and isn't the program's entrypoint)
|
val indexerExpression = expr.indexer.origExpression!!
|
||||||
if(numSubroutinesAtEnd>0 && block.statements.size > (numSubroutinesAtEnd+1)) {
|
val target = AssignTarget(IdentifierReference(listOf(indexerVarName), indexerExpression.position), null, null, indexerExpression.position)
|
||||||
val firstSub = block.statements[block.statements.size - numSubroutinesAtEnd] as Subroutine
|
val assign = Assignment(target, indexerExpression, indexerExpression.position)
|
||||||
if(firstSub.name != "start" && block.name != "main") {
|
val beforeWhat = expr.containingStatement()
|
||||||
val stmtBeforeFirstSub = block.statements[block.statements.size - numSubroutinesAtEnd - 1]
|
modifications.add(IAstModification.InsertBefore(beforeWhat, assign, beforeWhat.definingScope()))
|
||||||
if (stmtBeforeFirstSub !is Return
|
modifications.add(IAstModification.SetExpression( {
|
||||||
&& stmtBeforeFirstSub !is Jump
|
expr.indexer.indexVar = it as IdentifierReference
|
||||||
&& stmtBeforeFirstSub !is Subroutine
|
expr.indexer.indexNum = null
|
||||||
&& stmtBeforeFirstSub !is BuiltinFunctionStatementPlaceholder) {
|
expr.indexer.origExpression = null
|
||||||
val ret = Return(null, stmtBeforeFirstSub.position)
|
}, target.identifier!!.copy(), expr.indexer))
|
||||||
ret.linkParents(block)
|
|
||||||
block.statements.add(block.statements.size - numSubroutinesAtEnd, ret)
|
return modifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
|
||||||
|
val choices = whenStatement.choiceValues(program).sortedBy {
|
||||||
|
it.first?.first() ?: Int.MAX_VALUE
|
||||||
|
}
|
||||||
|
whenStatement.choices.clear()
|
||||||
|
choices.mapTo(whenStatement.choices) { it.second }
|
||||||
|
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
|
||||||
val varDecls = block.statements.filterIsInstance<VarDecl>()
|
|
||||||
block.statements.removeAll(varDecls)
|
|
||||||
block.statements.addAll(0, varDecls)
|
|
||||||
val directives = block.statements.filter {it is Directive && it.directive in directivesToMove}
|
|
||||||
block.statements.removeAll(directives)
|
|
||||||
block.statements.addAll(0, directives)
|
|
||||||
block.linkParents(block.parent)
|
|
||||||
|
|
||||||
// create subroutine that initializes the block's variables (if any)
|
|
||||||
val varInits = block.statements.withIndex().filter { it.value is VariableInitializationAssignment }
|
|
||||||
if(varInits.isNotEmpty()) {
|
|
||||||
val statements = varInits.map{it.value}.toMutableList()
|
|
||||||
val varInitSub = Subroutine(initvarsSubName, emptyList(), emptyList(), emptyList(), emptyList(),
|
|
||||||
emptySet(), null, false, statements, block.position)
|
|
||||||
varInitSub.keepAlways = true
|
|
||||||
varInitSub.linkParents(block)
|
|
||||||
block.statements.add(varInitSub)
|
|
||||||
|
|
||||||
// remove the varinits from the block's statements
|
|
||||||
for(index in varInits.map{it.index}.reversed())
|
|
||||||
block.statements.removeAt(index)
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.visit(block)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(subroutine: Subroutine): Statement {
|
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||||
super.visit(subroutine)
|
val valueType = assignment.value.inferType(program)
|
||||||
|
val targetType = assignment.target.inferType(program)
|
||||||
|
var assignments = emptyList<Assignment>()
|
||||||
|
|
||||||
val scope = subroutine.definingScope()
|
if(targetType.istype(DataType.STRUCT) && (valueType.istype(DataType.STRUCT) || valueType.typeOrElse(DataType.STRUCT) in ArrayDatatypes )) {
|
||||||
if(scope is Subroutine) {
|
assignments = if (assignment.value is ArrayLiteralValue) {
|
||||||
for(stmt in scope.statements.withIndex()) {
|
flattenStructAssignmentFromStructLiteral(assignment) // 'structvar = [ ..... ] '
|
||||||
if(stmt.index>0 && stmt.value===subroutine) {
|
} else {
|
||||||
val precedingStmt = scope.statements[stmt.index-1]
|
flattenStructAssignmentFromIdentifier(assignment) // 'structvar1 = structvar2'
|
||||||
if(precedingStmt !is Jump && precedingStmt !is Subroutine) {
|
}
|
||||||
// insert a return statement before a nested subroutine, to avoid falling trough inside the subroutine
|
}
|
||||||
addReturns.add(Pair(scope, stmt.index))
|
|
||||||
|
if(targetType.typeOrElse(DataType.STRUCT) in ArrayDatatypes && valueType.typeOrElse(DataType.STRUCT) in ArrayDatatypes ) {
|
||||||
|
assignments = if (assignment.value is ArrayLiteralValue) {
|
||||||
|
flattenArrayAssignmentFromArrayLiteral(assignment) // 'arrayvar = [ ..... ] '
|
||||||
|
} else {
|
||||||
|
flattenArrayAssignmentFromIdentifier(assignment) // 'arrayvar1 = arrayvar2'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(assignments.isNotEmpty()) {
|
||||||
|
val modifications = mutableListOf<IAstModification>()
|
||||||
|
val scope = assignment.definingScope()
|
||||||
|
assignments.reversed().mapTo(modifications) { IAstModification.InsertAfter(assignment, it, scope) }
|
||||||
|
modifications.add(IAstModification.Remove(assignment, scope))
|
||||||
|
return modifications
|
||||||
|
}
|
||||||
|
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||||
|
// rewrite in-place assignment expressions a bit so that the assignment target usually is the leftmost operand
|
||||||
|
val binExpr = assignment.value as? BinaryExpression
|
||||||
|
if(binExpr!=null) {
|
||||||
|
if(binExpr.left isSameAs assignment.target) {
|
||||||
|
// A = A <operator> 5, unchanged
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
if(binExpr.operator in associativeOperators) {
|
||||||
|
if (binExpr.right isSameAs assignment.target) {
|
||||||
|
// A = v <associative-operator> A ==> A = A <associative-operator> v
|
||||||
|
return listOf(IAstModification.SwapOperands(binExpr))
|
||||||
|
}
|
||||||
|
|
||||||
|
val leftBinExpr = binExpr.left as? BinaryExpression
|
||||||
|
if(leftBinExpr?.operator == binExpr.operator) {
|
||||||
|
return if(leftBinExpr.left isSameAs assignment.target) {
|
||||||
|
// A = (A <associative-operator> x) <same-operator> y ==> A = A <associative-operator> (x <same-operator> y)
|
||||||
|
val newRight = BinaryExpression(leftBinExpr.right, binExpr.operator, binExpr.right, binExpr.position)
|
||||||
|
val newValue = BinaryExpression(leftBinExpr.left, binExpr.operator, newRight, binExpr.position)
|
||||||
|
listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment))
|
||||||
|
} else {
|
||||||
|
// A = (x <associative-operator> A) <same-operator> y ==> A = A <associative-operator> (x <same-operator> y)
|
||||||
|
val newRight = BinaryExpression(leftBinExpr.left, binExpr.operator, binExpr.right, binExpr.position)
|
||||||
|
val newValue = BinaryExpression(leftBinExpr.right, binExpr.operator, newRight, binExpr.position)
|
||||||
|
listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val rightBinExpr = binExpr.right as? BinaryExpression
|
||||||
|
if(rightBinExpr?.operator == binExpr.operator) {
|
||||||
|
return if(rightBinExpr.left isSameAs assignment.target) {
|
||||||
|
// A = x <associative-operator> (A <same-operator> y) ==> A = A <associative-operator> (x <same-operator> y)
|
||||||
|
val newRight = BinaryExpression(binExpr.left, binExpr.operator, rightBinExpr.right, binExpr.position)
|
||||||
|
val newValue = BinaryExpression(rightBinExpr.left, binExpr.operator, newRight, binExpr.position)
|
||||||
|
listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment))
|
||||||
|
} else {
|
||||||
|
// A = x <associative-operator> (y <same-operator> A) ==> A = A <associative-operator> (x <same-operator> y)
|
||||||
|
val newRight = BinaryExpression(binExpr.left, binExpr.operator, rightBinExpr.left, binExpr.position)
|
||||||
|
val newValue = BinaryExpression(rightBinExpr.right, binExpr.operator, newRight, binExpr.position)
|
||||||
|
listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val varDecls = subroutine.statements.filterIsInstance<VarDecl>()
|
return noModifications
|
||||||
subroutine.statements.removeAll(varDecls)
|
|
||||||
subroutine.statements.addAll(0, varDecls)
|
|
||||||
val directives = subroutine.statements.filter {it is Directive && it.directive in directivesToMove}
|
|
||||||
subroutine.statements.removeAll(directives)
|
|
||||||
subroutine.statements.addAll(0, directives)
|
|
||||||
|
|
||||||
if(subroutine.returntypes.isEmpty()) {
|
|
||||||
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernel routine.
|
|
||||||
// and if an assembly block doesn't contain a rts/rti
|
|
||||||
if(subroutine.asmAddress==null && subroutine.amountOfRtsInAsm()==0) {
|
|
||||||
if (subroutine.statements.lastOrNull {it !is VarDecl } !is Return) {
|
|
||||||
val returnStmt = Return(null, subroutine.position)
|
|
||||||
returnStmt.linkParents(subroutine)
|
|
||||||
subroutine.statements.add(returnStmt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return subroutine
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(assignment: Assignment): Statement {
|
private fun flattenArrayAssignmentFromArrayLiteral(assign: Assignment): List<Assignment> {
|
||||||
val assg = super.visit(assignment)
|
val identifier = assign.target.identifier!!
|
||||||
if(assg !is Assignment)
|
val targetVar = identifier.targetVarDecl(program.namespace)!!
|
||||||
return assg
|
val alv = assign.value as? ArrayLiteralValue
|
||||||
|
return flattenArrayAssign(targetVar, alv, identifier, assign.position)
|
||||||
|
}
|
||||||
|
|
||||||
// see if a typecast is needed to convert the value's type into the proper target type
|
private fun flattenArrayAssignmentFromIdentifier(assign: Assignment): List<Assignment> {
|
||||||
val valueItype = assg.value.inferType(program)
|
val identifier = assign.target.identifier!!
|
||||||
val targetItype = assg.target.inferType(program, assg)
|
val targetVar = identifier.targetVarDecl(program.namespace)!!
|
||||||
|
val sourceIdent = assign.value as IdentifierReference
|
||||||
|
val sourceVar = sourceIdent.targetVarDecl(program.namespace)!!
|
||||||
|
if(!sourceVar.isArray) {
|
||||||
|
errors.err("value must be an array", sourceIdent.position)
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
val alv = sourceVar.value as? ArrayLiteralValue
|
||||||
|
return flattenArrayAssign(targetVar, alv, identifier, assign.position)
|
||||||
|
}
|
||||||
|
|
||||||
if(targetItype.isKnown && valueItype.isKnown) {
|
private fun flattenArrayAssign(targetVar: VarDecl, alv: ArrayLiteralValue?, identifier: IdentifierReference, position: Position): List<Assignment> {
|
||||||
val targettype = targetItype.typeOrElse(DataType.STRUCT)
|
if(targetVar.arraysize==null) {
|
||||||
val valuetype = valueItype.typeOrElse(DataType.STRUCT)
|
errors.err("array has no defined size", identifier.position)
|
||||||
|
return emptyList()
|
||||||
// struct assignments will be flattened (if it's not a struct literal)
|
|
||||||
if (valuetype == DataType.STRUCT && targettype == DataType.STRUCT) {
|
|
||||||
if (assg.value is StructLiteralValue)
|
|
||||||
return assg // do NOT flatten it at this point!! (the compiler will take care if it, later, if needed)
|
|
||||||
|
|
||||||
val assignments = flattenStructAssignmentFromIdentifier(assg, program) // 'structvar1 = structvar2'
|
|
||||||
return if (assignments.isEmpty()) {
|
|
||||||
// something went wrong (probably incompatible struct types)
|
|
||||||
// we'll get an error later from the AstChecker
|
|
||||||
assg
|
|
||||||
} else {
|
|
||||||
val scope = AnonymousScope(assignments.toMutableList(), assg.position)
|
|
||||||
scope.linkParents(assg.parent)
|
|
||||||
scope
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(assg.aug_op!=null) {
|
if(alv==null || alv.value.size != targetVar.arraysize!!.constIndex()) {
|
||||||
// transform augmented assg into normal assg so we have one case less to deal with later
|
errors.err("element count mismatch", position)
|
||||||
val newTarget: Expression =
|
return emptyList()
|
||||||
when {
|
}
|
||||||
assg.target.register != null -> RegisterExpr(assg.target.register!!, assg.target.position)
|
|
||||||
assg.target.identifier != null -> assg.target.identifier!!
|
// TODO use a pointer loop instead of individual assignments
|
||||||
assg.target.arrayindexed != null -> assg.target.arrayindexed!!
|
return alv.value.mapIndexed { index, value ->
|
||||||
assg.target.memoryAddress != null -> DirectMemoryRead(assg.target.memoryAddress!!.addressExpression, assg.value.position)
|
val idx = ArrayIndexedExpression(identifier, ArrayIndex(NumericLiteralValue(DataType.UBYTE, index, position), position), position)
|
||||||
else -> throw FatalAstException("strange assg")
|
Assignment(AssignTarget(null, idx, null, position), value, value.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun flattenStructAssignmentFromStructLiteral(structAssignment: Assignment): List<Assignment> {
|
||||||
|
val identifier = structAssignment.target.identifier!!
|
||||||
|
val identifierName = identifier.nameInSource.single()
|
||||||
|
val targetVar = identifier.targetVarDecl(program.namespace)!!
|
||||||
|
val struct = targetVar.struct!!
|
||||||
|
|
||||||
|
val slv = structAssignment.value as? ArrayLiteralValue
|
||||||
|
if(slv==null || slv.value.size != struct.numberOfElements) {
|
||||||
|
errors.err("element count mismatch", structAssignment.position)
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
return struct.statements.zip(slv.value).map { (targetDecl, sourceValue) ->
|
||||||
|
targetDecl as VarDecl
|
||||||
|
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
|
||||||
|
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
|
||||||
|
val assign = Assignment(AssignTarget(idref, null, null, structAssignment.position),
|
||||||
|
sourceValue, sourceValue.position)
|
||||||
|
assign.linkParents(structAssignment)
|
||||||
|
assign
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun flattenStructAssignmentFromIdentifier(structAssignment: Assignment): List<Assignment> {
|
||||||
|
// TODO use memcopy beyond a certain number of elements
|
||||||
|
val identifier = structAssignment.target.identifier!!
|
||||||
|
val identifierName = identifier.nameInSource.single()
|
||||||
|
val targetVar = identifier.targetVarDecl(program.namespace)!!
|
||||||
|
val struct = targetVar.struct!!
|
||||||
|
when (structAssignment.value) {
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program.namespace)!!
|
||||||
|
when {
|
||||||
|
sourceVar.struct!=null -> {
|
||||||
|
// struct memberwise copy
|
||||||
|
val sourceStruct = sourceVar.struct!!
|
||||||
|
if(sourceStruct!==targetVar.struct) {
|
||||||
|
// structs are not the same in assignment
|
||||||
|
return listOf() // error will be printed elsewhere
|
||||||
|
}
|
||||||
|
if(struct.statements.size!=sourceStruct.statements.size)
|
||||||
|
return listOf() // error will be printed elsewhere
|
||||||
|
return struct.statements.zip(sourceStruct.statements).map { member ->
|
||||||
|
val targetDecl = member.first as VarDecl
|
||||||
|
val sourceDecl = member.second as VarDecl
|
||||||
|
if(targetDecl.name != sourceDecl.name)
|
||||||
|
throw FatalAstException("struct member mismatch")
|
||||||
|
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
|
||||||
|
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
|
||||||
|
val sourcemangled = mangledStructMemberName(sourceVar.name, sourceDecl.name)
|
||||||
|
val sourceIdref = IdentifierReference(listOf(sourcemangled), structAssignment.position)
|
||||||
|
val assign = Assignment(AssignTarget(idref, null, null, structAssignment.position), sourceIdref, member.second.position)
|
||||||
|
assign.linkParents(structAssignment)
|
||||||
|
assign
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
sourceVar.isArray -> {
|
||||||
val expression = BinaryExpression(newTarget, assg.aug_op.substringBeforeLast('='), assg.value, assg.position)
|
val array = (sourceVar.value as ArrayLiteralValue).value
|
||||||
expression.linkParents(assg.parent)
|
if(struct.statements.size!=array.size)
|
||||||
val convertedAssignment = Assignment(assg.target, null, expression, assg.position)
|
return listOf() // error will be printed elsewhere
|
||||||
convertedAssignment.linkParents(assg.parent)
|
return struct.statements.zip(array).map {
|
||||||
return super.visit(convertedAssignment)
|
val decl = it.first as VarDecl
|
||||||
|
val mangled = mangledStructMemberName(identifierName, decl.name)
|
||||||
|
val targetName = IdentifierReference(listOf(mangled), structAssignment.position)
|
||||||
|
val target = AssignTarget(targetName, null, null, structAssignment.position)
|
||||||
|
val assign = Assignment(target, it.second, structAssignment.position)
|
||||||
|
assign.linkParents(structAssignment)
|
||||||
|
assign
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
throw FatalAstException("can only assign arrays or structs to structs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is ArrayLiteralValue -> {
|
||||||
|
throw IllegalArgumentException("not going to flatten a structLv assignment here")
|
||||||
|
}
|
||||||
|
else -> throw FatalAstException("strange struct value")
|
||||||
}
|
}
|
||||||
|
|
||||||
return assg
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,113 +2,167 @@ package prog8.ast.processing
|
|||||||
|
|
||||||
import prog8.ast.IFunctionCall
|
import prog8.ast.IFunctionCall
|
||||||
import prog8.ast.INameScope
|
import prog8.ast.INameScope
|
||||||
|
import prog8.ast.Node
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.DataType
|
import prog8.ast.base.*
|
||||||
import prog8.ast.base.FatalAstException
|
|
||||||
import prog8.ast.base.printWarning
|
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.functions.BuiltinFunctions
|
import prog8.functions.BuiltinFunctions
|
||||||
|
|
||||||
|
|
||||||
internal class TypecastsAdder(private val program: Program): IAstModifyingVisitor {
|
class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalker() {
|
||||||
// Make sure any value assignments get the proper type casts if needed to cast them into the target variable's type.
|
/*
|
||||||
// (this includes function call arguments)
|
* Make sure any value assignments get the proper type casts if needed to cast them into the target variable's type.
|
||||||
|
* (this includes function call arguments)
|
||||||
|
*/
|
||||||
|
|
||||||
override fun visit(expr: BinaryExpression): Expression {
|
private val noModifications = emptyList<IAstModification>()
|
||||||
val expr2 = super.visit(expr)
|
|
||||||
if(expr2 !is BinaryExpression)
|
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||||
return expr2
|
val declValue = decl.value
|
||||||
val leftDt = expr2.left.inferType(program)
|
if(decl.type==VarDeclType.VAR && declValue!=null && decl.struct==null) {
|
||||||
val rightDt = expr2.right.inferType(program)
|
val valueDt = declValue.inferType(program)
|
||||||
|
if(!valueDt.istype(decl.datatype)) {
|
||||||
|
|
||||||
|
// don't add a typecast on an array initializer value
|
||||||
|
if(valueDt.typeOrElse(DataType.STRUCT) in IntegerDatatypes && decl.datatype in ArrayDatatypes)
|
||||||
|
return noModifications
|
||||||
|
|
||||||
|
return listOf(IAstModification.ReplaceNode(
|
||||||
|
declValue,
|
||||||
|
TypecastExpression(declValue, decl.datatype, true, declValue.position),
|
||||||
|
decl
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||||
|
val leftDt = expr.left.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), expr2.left, expr2.right)
|
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.typeOrElse(DataType.STRUCT), rightDt.typeOrElse(DataType.STRUCT), expr.left, expr.right)
|
||||||
if(toFix!=null) {
|
if(toFix!=null) {
|
||||||
when {
|
return when {
|
||||||
toFix===expr2.left -> {
|
toFix===expr.left -> listOf(IAstModification.ReplaceNode(
|
||||||
expr2.left = TypecastExpression(expr2.left, commonDt, true, expr2.left.position)
|
expr.left, TypecastExpression(expr.left, commonDt, true, expr.left.position), expr))
|
||||||
expr2.left.linkParents(expr2)
|
toFix===expr.right -> listOf(IAstModification.ReplaceNode(
|
||||||
}
|
expr.right, TypecastExpression(expr.right, commonDt, true, expr.right.position), expr))
|
||||||
toFix===expr2.right -> {
|
|
||||||
expr2.right = TypecastExpression(expr2.right, commonDt, true, expr2.right.position)
|
|
||||||
expr2.right.linkParents(expr2)
|
|
||||||
}
|
|
||||||
else -> throw FatalAstException("confused binary expression side")
|
else -> throw FatalAstException("confused binary expression side")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return expr2
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(assignment: Assignment): Statement {
|
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||||
val assg = super.visit(assignment)
|
|
||||||
if(assg !is Assignment)
|
|
||||||
return assg
|
|
||||||
|
|
||||||
// see if a typecast is needed to convert the value's type into the proper target type
|
// see if a typecast is needed to convert the value's type into the proper target type
|
||||||
val valueItype = assg.value.inferType(program)
|
val valueItype = assignment.value.inferType(program)
|
||||||
val targetItype = assg.target.inferType(program, assg)
|
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.typeOrElse(DataType.STRUCT)
|
||||||
val valuetype = valueItype.typeOrElse(DataType.STRUCT)
|
val valuetype = valueItype.typeOrElse(DataType.STRUCT)
|
||||||
if (valuetype != targettype) {
|
if (valuetype != targettype) {
|
||||||
if (valuetype isAssignableTo targettype) {
|
if (valuetype isAssignableTo targettype) {
|
||||||
assg.value = TypecastExpression(assg.value, targettype, true, assg.value.position)
|
if(valuetype in IterableDatatypes && targettype==DataType.UWORD)
|
||||||
assg.value.linkParents(assg)
|
// special case, don't typecast STR/arrays to UWORD, we support those assignments "directly"
|
||||||
|
return noModifications
|
||||||
|
return listOf(IAstModification.ReplaceNode(
|
||||||
|
assignment.value,
|
||||||
|
TypecastExpression(assignment.value, targettype, true, assignment.value.position),
|
||||||
|
assignment))
|
||||||
|
} else {
|
||||||
|
fun castLiteral(cvalue: NumericLiteralValue): List<IAstModification.ReplaceNode> {
|
||||||
|
val cast = cvalue.cast(targettype)
|
||||||
|
return if(cast.isValid)
|
||||||
|
listOf(IAstModification.ReplaceNode(cvalue, cast.valueOrZero(), cvalue.parent))
|
||||||
|
else
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
val cvalue = assignment.value.constValue(program)
|
||||||
|
if(cvalue!=null) {
|
||||||
|
val number = cvalue.number.toDouble()
|
||||||
|
// more complex comparisons if the type is different, but the constant value is compatible
|
||||||
|
if (valuetype == DataType.BYTE && targettype == DataType.UBYTE) {
|
||||||
|
if(number>0)
|
||||||
|
return castLiteral(cvalue)
|
||||||
|
} else if (valuetype == DataType.WORD && targettype == DataType.UWORD) {
|
||||||
|
if(number>0)
|
||||||
|
return castLiteral(cvalue)
|
||||||
|
} else if (valuetype == DataType.UBYTE && targettype == DataType.BYTE) {
|
||||||
|
if(number<0x80)
|
||||||
|
return castLiteral(cvalue)
|
||||||
|
} else if (valuetype == DataType.UWORD && targettype == DataType.WORD) {
|
||||||
|
if(number<0x8000)
|
||||||
|
return castLiteral(cvalue)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// if they're not assignable, we'll get a proper error later from the AstChecker
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return assg
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
|
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||||
checkFunctionCallArguments(functionCallStatement, functionCallStatement.definingScope())
|
return afterFunctionCallArgs(functionCallStatement, functionCallStatement.definingScope())
|
||||||
return super.visit(functionCallStatement)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(functionCall: FunctionCall): Expression {
|
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||||
checkFunctionCallArguments(functionCall, functionCall.definingScope())
|
return afterFunctionCallArgs(functionCall, functionCall.definingScope())
|
||||||
return super.visit(functionCall)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkFunctionCallArguments(call: IFunctionCall, scope: INameScope) {
|
private fun afterFunctionCallArgs(call: IFunctionCall, scope: INameScope): Iterable<IAstModification> {
|
||||||
// see if a typecast is needed to convert the arguments into the required parameter's type
|
// see if a typecast is needed to convert the arguments into the required parameter's type
|
||||||
|
val modifications = mutableListOf<IAstModification>()
|
||||||
|
|
||||||
when(val sub = call.target.targetStatement(scope)) {
|
when(val sub = call.target.targetStatement(scope)) {
|
||||||
is Subroutine -> {
|
is Subroutine -> {
|
||||||
for(arg in sub.parameters.zip(call.arglist.withIndex())) {
|
sub.parameters.zip(call.args).forEachIndexed { index, pair ->
|
||||||
val argItype = arg.second.value.inferType(program)
|
val argItype = pair.second.inferType(program)
|
||||||
if(argItype.isKnown) {
|
if(argItype.isKnown) {
|
||||||
val argtype = argItype.typeOrElse(DataType.STRUCT)
|
val argtype = argItype.typeOrElse(DataType.STRUCT)
|
||||||
val requiredType = arg.first.type
|
val requiredType = pair.first.type
|
||||||
if (requiredType != argtype) {
|
if (requiredType != argtype) {
|
||||||
if (argtype isAssignableTo requiredType) {
|
if (argtype isAssignableTo requiredType) {
|
||||||
val typecasted = TypecastExpression(arg.second.value, requiredType, true, arg.second.value.position)
|
modifications += IAstModification.ReplaceNode(
|
||||||
typecasted.linkParents(arg.second.value.parent)
|
call.args[index],
|
||||||
call.arglist[arg.second.index] = typecasted
|
TypecastExpression(pair.second, requiredType, true, pair.second.position),
|
||||||
|
call as Node)
|
||||||
|
} else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) {
|
||||||
|
// we allow STR/ARRAY values in place of UWORD parameters. Take their address instead.
|
||||||
|
if(pair.second is IdentifierReference) {
|
||||||
|
modifications += IAstModification.ReplaceNode(
|
||||||
|
call.args[index],
|
||||||
|
AddressOf(pair.second as IdentifierReference, pair.second.position),
|
||||||
|
call as Node)
|
||||||
|
}
|
||||||
|
} else if(pair.second is NumericLiteralValue) {
|
||||||
|
val cast = (pair.second as NumericLiteralValue).cast(requiredType)
|
||||||
|
if(cast.isValid)
|
||||||
|
modifications += IAstModification.ReplaceNode(
|
||||||
|
call.args[index],
|
||||||
|
cast.valueOrZero(),
|
||||||
|
call as Node)
|
||||||
}
|
}
|
||||||
// if they're not assignable, we'll get a proper error later from the AstChecker
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is BuiltinFunctionStatementPlaceholder -> {
|
is BuiltinFunctionStatementPlaceholder -> {
|
||||||
val func = BuiltinFunctions.getValue(sub.name)
|
val func = BuiltinFunctions.getValue(sub.name)
|
||||||
if(func.pure) {
|
func.parameters.zip(call.args).forEachIndexed { index, pair ->
|
||||||
// non-pure functions don't get automatic typecasts because sometimes they act directly on their parameters
|
val argItype = pair.second.inferType(program)
|
||||||
for (arg in func.parameters.zip(call.arglist.withIndex())) {
|
if (argItype.isKnown) {
|
||||||
val argItype = arg.second.value.inferType(program)
|
val argtype = argItype.typeOrElse(DataType.STRUCT)
|
||||||
if (argItype.isKnown) {
|
if (pair.first.possibleDatatypes.all { argtype != it }) {
|
||||||
val argtype = argItype.typeOrElse(DataType.STRUCT)
|
for (possibleType in pair.first.possibleDatatypes) {
|
||||||
if (arg.first.possibleDatatypes.any { argtype == it })
|
|
||||||
continue
|
|
||||||
for (possibleType in arg.first.possibleDatatypes) {
|
|
||||||
if (argtype isAssignableTo possibleType) {
|
if (argtype isAssignableTo possibleType) {
|
||||||
val typecasted = TypecastExpression(arg.second.value, possibleType, true, arg.second.value.position)
|
modifications += IAstModification.ReplaceNode(
|
||||||
typecasted.linkParents(arg.second.value.parent)
|
call.args[index],
|
||||||
call.arglist[arg.second.index] = typecasted
|
TypecastExpression(pair.second, possibleType, true, pair.second.position),
|
||||||
|
call as Node)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -116,83 +170,63 @@ internal class TypecastsAdder(private val program: Program): IAstModifyingVisito
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
null -> {}
|
else -> { }
|
||||||
else -> throw FatalAstException("call to something weird $sub ${call.target}")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return modifications
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(typecast: TypecastExpression): Expression {
|
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 in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
|
||||||
printWarning("byte or word value implicitly converted to float. Suggestion: use explicit cast as float, a float number, 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 super.visit(typecast)
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(memread: DirectMemoryRead): Expression {
|
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.typeOrElse(DataType.UWORD)!=DataType.UWORD) {
|
||||||
val literaladdr = memread.addressExpression as? NumericLiteralValue
|
val typecast = (memread.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)?.valueOrZero()
|
||||||
if(literaladdr!=null) {
|
?: TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position)
|
||||||
memread.addressExpression = literaladdr.cast(DataType.UWORD)
|
return listOf(IAstModification.ReplaceNode(memread.addressExpression, typecast, memread))
|
||||||
} else {
|
|
||||||
memread.addressExpression = TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position)
|
|
||||||
memread.addressExpression.parent = memread
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return super.visit(memread)
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(memwrite: DirectMemoryWrite) {
|
override fun after(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> {
|
||||||
|
// 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.typeOrElse(DataType.UWORD)!=DataType.UWORD) {
|
||||||
val literaladdr = memwrite.addressExpression as? NumericLiteralValue
|
val typecast = (memwrite.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)?.valueOrZero()
|
||||||
if(literaladdr!=null) {
|
?: TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position)
|
||||||
memwrite.addressExpression = literaladdr.cast(DataType.UWORD)
|
return listOf(IAstModification.ReplaceNode(memwrite.addressExpression, typecast, memwrite))
|
||||||
} else {
|
|
||||||
memwrite.addressExpression = TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position)
|
|
||||||
memwrite.addressExpression.parent = memwrite
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
super.visit(memwrite)
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(structLv: StructLiteralValue): Expression {
|
override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> {
|
||||||
val litval = super.visit(structLv)
|
// add a typecast to the return type if it doesn't match the subroutine's signature
|
||||||
if(litval !is StructLiteralValue)
|
val returnValue = returnStmt.value
|
||||||
return litval
|
if(returnValue!=null) {
|
||||||
|
val subroutine = returnStmt.definingSubroutine()!!
|
||||||
val decl = litval.parent as? VarDecl
|
if(subroutine.returntypes.size==1) {
|
||||||
if(decl != null) {
|
val subReturnType = subroutine.returntypes.first()
|
||||||
val struct = decl.struct
|
if (returnValue.inferType(program).istype(subReturnType))
|
||||||
if(struct != null) {
|
return noModifications
|
||||||
addTypecastsIfNeeded(litval, struct)
|
if (returnValue is NumericLiteralValue) {
|
||||||
}
|
val cast = returnValue.cast(subroutine.returntypes.single())
|
||||||
} else {
|
if(cast.isValid)
|
||||||
val assign = litval.parent as? Assignment
|
returnStmt.value = cast.valueOrZero()
|
||||||
if (assign != null) {
|
} else {
|
||||||
val decl2 = assign.target.identifier?.targetVarDecl(program.namespace)
|
return listOf(IAstModification.ReplaceNode(
|
||||||
if(decl2 != null) {
|
returnValue,
|
||||||
val struct = decl2.struct
|
TypecastExpression(returnValue, subReturnType, true, returnValue.position),
|
||||||
if(struct != null) {
|
returnStmt))
|
||||||
addTypecastsIfNeeded(litval, struct)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return noModifications
|
||||||
return litval
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addTypecastsIfNeeded(structLv: StructLiteralValue, struct: StructDecl) {
|
|
||||||
structLv.values = struct.statements.zip(structLv.values).map {
|
|
||||||
val memberDt = (it.first as VarDecl).datatype
|
|
||||||
val valueDt = it.second.inferType(program)
|
|
||||||
if (valueDt.typeOrElse(memberDt) != memberDt)
|
|
||||||
TypecastExpression(it.second, memberDt, true, it.second.position)
|
|
||||||
else
|
|
||||||
it.second
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,157 +0,0 @@
|
|||||||
package prog8.ast.processing
|
|
||||||
|
|
||||||
import prog8.ast.INameScope
|
|
||||||
import prog8.ast.Module
|
|
||||||
import prog8.ast.Node
|
|
||||||
import prog8.ast.Program
|
|
||||||
import prog8.ast.base.*
|
|
||||||
import prog8.ast.expressions.*
|
|
||||||
import prog8.ast.statements.*
|
|
||||||
import prog8.compiler.CompilerException
|
|
||||||
import prog8.functions.BuiltinFunctions
|
|
||||||
import prog8.functions.FunctionSignature
|
|
||||||
|
|
||||||
|
|
||||||
internal class VarInitValueAndAddressOfCreator(private val program: Program): IAstModifyingVisitor {
|
|
||||||
// For VarDecls that declare an initialization value:
|
|
||||||
// Replace the vardecl with an assignment (to set the initial value),
|
|
||||||
// and add a new vardecl with the default constant value of that type (usually zero) to the scope.
|
|
||||||
// This makes sure the variables get reset to the intended value on a next run of the program.
|
|
||||||
// Variable decls without a value don't get this treatment, which means they retain the last
|
|
||||||
// value they had when restarting the program.
|
|
||||||
// This is done in a separate step because it interferes with the namespace lookup of symbols
|
|
||||||
// in other ast processors.
|
|
||||||
|
|
||||||
// Also takes care to insert AddressOf (&) expression where required (string params to a UWORD function param etc).
|
|
||||||
|
|
||||||
private val vardeclsToAdd = mutableMapOf<INameScope, MutableList<VarDecl>>()
|
|
||||||
|
|
||||||
override fun visit(module: Module) {
|
|
||||||
vardeclsToAdd.clear()
|
|
||||||
super.visit(module)
|
|
||||||
// add any new vardecls to the various scopes
|
|
||||||
for((where, decls) in vardeclsToAdd) {
|
|
||||||
where.statements.addAll(0, decls)
|
|
||||||
decls.forEach { it.linkParents(where as Node) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(decl: VarDecl): Statement {
|
|
||||||
super.visit(decl)
|
|
||||||
|
|
||||||
if(decl.isArray && decl.value==null) {
|
|
||||||
// array datatype without initialization value, add list of zeros
|
|
||||||
val arraysize = decl.arraysize!!.size()!!
|
|
||||||
val array = ArrayLiteralValue(decl.datatype,
|
|
||||||
Array(arraysize) { NumericLiteralValue.optimalInteger(0, decl.position) },
|
|
||||||
null, decl.position)
|
|
||||||
array.addToHeap(program.heap)
|
|
||||||
decl.value = array
|
|
||||||
}
|
|
||||||
|
|
||||||
if(decl.type!= VarDeclType.VAR || decl.value==null)
|
|
||||||
return decl
|
|
||||||
|
|
||||||
if(decl.datatype in NumericDatatypes) {
|
|
||||||
val scope = decl.definingScope()
|
|
||||||
addVarDecl(scope, decl.asDefaultValueDecl(null))
|
|
||||||
val declvalue = decl.value!!
|
|
||||||
val value =
|
|
||||||
if(declvalue is NumericLiteralValue)
|
|
||||||
declvalue.cast(decl.datatype)
|
|
||||||
else
|
|
||||||
declvalue
|
|
||||||
val identifierName = listOf(decl.name) // this was: (scoped name) decl.scopedname.split(".")
|
|
||||||
return VariableInitializationAssignment(
|
|
||||||
AssignTarget(null, IdentifierReference(identifierName, decl.position), null, null, decl.position),
|
|
||||||
null,
|
|
||||||
value,
|
|
||||||
decl.position
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return decl
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(functionCall: FunctionCall): Expression {
|
|
||||||
var parentStatement: Node = functionCall
|
|
||||||
while(parentStatement !is Statement)
|
|
||||||
parentStatement = parentStatement.parent
|
|
||||||
val targetStatement = functionCall.target.targetSubroutine(program.namespace)
|
|
||||||
if(targetStatement!=null) {
|
|
||||||
addAddressOfExprIfNeeded(targetStatement, functionCall.arglist, parentStatement)
|
|
||||||
} else {
|
|
||||||
val builtinFunc = BuiltinFunctions[functionCall.target.nameInSource.joinToString (".")]
|
|
||||||
if(builtinFunc!=null)
|
|
||||||
addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCall.arglist, parentStatement)
|
|
||||||
}
|
|
||||||
return functionCall
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
|
|
||||||
val targetStatement = functionCallStatement.target.targetSubroutine(program.namespace)
|
|
||||||
if(targetStatement!=null) {
|
|
||||||
addAddressOfExprIfNeeded(targetStatement, functionCallStatement.arglist, functionCallStatement)
|
|
||||||
} else {
|
|
||||||
val builtinFunc = BuiltinFunctions[functionCallStatement.target.nameInSource.joinToString (".")]
|
|
||||||
if(builtinFunc!=null)
|
|
||||||
addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCallStatement.arglist, functionCallStatement)
|
|
||||||
}
|
|
||||||
return functionCallStatement
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addAddressOfExprIfNeeded(subroutine: Subroutine, arglist: MutableList<Expression>, parent: Statement) {
|
|
||||||
// functions that accept UWORD and are given an array type, or string, will receive the AddressOf (memory location) of that value instead.
|
|
||||||
for(argparam in subroutine.parameters.withIndex().zip(arglist)) {
|
|
||||||
if(argparam.first.value.type==DataType.UWORD || argparam.first.value.type in StringDatatypes) {
|
|
||||||
if(argparam.second is AddressOf)
|
|
||||||
continue
|
|
||||||
val idref = argparam.second as? IdentifierReference
|
|
||||||
val strvalue = argparam.second as? StringLiteralValue
|
|
||||||
if(idref!=null) {
|
|
||||||
val variable = idref.targetVarDecl(program.namespace)
|
|
||||||
if(variable!=null && (variable.datatype in StringDatatypes || variable.datatype in ArrayDatatypes)) {
|
|
||||||
val pointerExpr = AddressOf(idref, idref.position)
|
|
||||||
pointerExpr.linkParents(arglist[argparam.first.index].parent)
|
|
||||||
arglist[argparam.first.index] = pointerExpr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(strvalue!=null) {
|
|
||||||
// add a vardecl so that the autovar can be resolved in later lookups
|
|
||||||
val variable = VarDecl.createAuto(strvalue)
|
|
||||||
addVarDecl(strvalue.definingScope(), variable)
|
|
||||||
// replace the argument with &autovar
|
|
||||||
val autoHeapvarRef = IdentifierReference(listOf(variable.name), strvalue.position)
|
|
||||||
val pointerExpr = AddressOf(autoHeapvarRef, strvalue.position)
|
|
||||||
pointerExpr.linkParents(arglist[argparam.first.index].parent)
|
|
||||||
arglist[argparam.first.index] = pointerExpr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addAddressOfExprIfNeededForBuiltinFuncs(signature: FunctionSignature, args: MutableList<Expression>, parent: Statement) {
|
|
||||||
// val paramTypesForAddressOf = PassByReferenceDatatypes + DataType.UWORD
|
|
||||||
for(arg in args.withIndex().zip(signature.parameters)) {
|
|
||||||
val argvalue = arg.first.value
|
|
||||||
val argDt = argvalue.inferType(program)
|
|
||||||
if(argDt.typeOrElse(DataType.UBYTE) in PassByReferenceDatatypes && DataType.UWORD in arg.second.possibleDatatypes) {
|
|
||||||
if(argvalue !is IdentifierReference)
|
|
||||||
throw CompilerException("pass-by-reference parameter isn't an identifier? $argvalue")
|
|
||||||
val addrOf = AddressOf(argvalue, argvalue.position)
|
|
||||||
args[arg.first.index] = addrOf
|
|
||||||
addrOf.linkParents(parent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private fun addVarDecl(scope: INameScope, variable: VarDecl) {
|
|
||||||
if(scope !in vardeclsToAdd)
|
|
||||||
vardeclsToAdd[scope] = mutableListOf()
|
|
||||||
val declList = vardeclsToAdd.getValue(scope)
|
|
||||||
if(declList.all{it.name!=variable.name})
|
|
||||||
declList.add(variable)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
44
compiler/src/prog8/ast/processing/VariousCleanups.kt
Normal file
44
compiler/src/prog8/ast/processing/VariousCleanups.kt
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package prog8.ast.processing
|
||||||
|
|
||||||
|
import prog8.ast.INameScope
|
||||||
|
import prog8.ast.Node
|
||||||
|
import prog8.ast.expressions.NumericLiteralValue
|
||||||
|
import prog8.ast.expressions.TypecastExpression
|
||||||
|
import prog8.ast.statements.AnonymousScope
|
||||||
|
import prog8.ast.statements.NopStatement
|
||||||
|
|
||||||
|
|
||||||
|
internal class VariousCleanups: AstWalker() {
|
||||||
|
private val noModifications = emptyList<IAstModification>()
|
||||||
|
|
||||||
|
override fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> {
|
||||||
|
return listOf(IAstModification.Remove(nopStatement, parent as INameScope))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||||
|
return if(parent is INameScope)
|
||||||
|
listOf(ScopeFlatten(scope, parent as INameScope))
|
||||||
|
else
|
||||||
|
noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScopeFlatten(val scope: AnonymousScope, val into: INameScope) : IAstModification {
|
||||||
|
override fun perform() {
|
||||||
|
val idx = into.statements.indexOf(scope)
|
||||||
|
if(idx>=0) {
|
||||||
|
into.statements.addAll(idx+1, scope.statements)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
87
compiler/src/prog8/ast/processing/VerifyFunctionArgTypes.kt
Normal file
87
compiler/src/prog8/ast/processing/VerifyFunctionArgTypes.kt
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
package prog8.ast.processing
|
||||||
|
|
||||||
|
import prog8.ast.IFunctionCall
|
||||||
|
import prog8.ast.INameScope
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.DataType
|
||||||
|
import prog8.ast.base.FatalAstException
|
||||||
|
import prog8.ast.expressions.Expression
|
||||||
|
import prog8.ast.expressions.FunctionCall
|
||||||
|
import prog8.ast.statements.*
|
||||||
|
import prog8.compiler.CompilerException
|
||||||
|
import prog8.functions.BuiltinFunctions
|
||||||
|
|
||||||
|
class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
|
||||||
|
|
||||||
|
override fun visit(functionCall: FunctionCall) {
|
||||||
|
val error = checkTypes(functionCall as IFunctionCall, functionCall.definingScope(), program)
|
||||||
|
if(error!=null)
|
||||||
|
throw CompilerException(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(functionCallStatement: FunctionCallStatement) {
|
||||||
|
val error = checkTypes(functionCallStatement as IFunctionCall, functionCallStatement.definingScope(), program)
|
||||||
|
if (error!=null)
|
||||||
|
throw CompilerException(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private fun argTypeCompatible(argDt: DataType, paramDt: DataType): Boolean {
|
||||||
|
if(argDt==paramDt)
|
||||||
|
return true
|
||||||
|
|
||||||
|
// there are some exceptions that are considered compatible, such as STR <> UWORD
|
||||||
|
if(argDt==DataType.STR && paramDt==DataType.UWORD ||
|
||||||
|
argDt==DataType.UWORD && paramDt==DataType.STR)
|
||||||
|
return true
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkTypes(call: IFunctionCall, scope: INameScope, program: Program): String? {
|
||||||
|
val argITypes = call.args.map { it.inferType(program) }
|
||||||
|
if(argITypes.any { !it.isKnown })
|
||||||
|
throw FatalAstException("unknown dt")
|
||||||
|
val argtypes = argITypes.map { it.typeOrElse(DataType.STRUCT) }
|
||||||
|
val target = call.target.targetStatement(scope)
|
||||||
|
if (target is Subroutine) {
|
||||||
|
if(call.args.size != target.parameters.size)
|
||||||
|
return "invalid number of arguments"
|
||||||
|
val paramtypes = target.parameters.map { it.type }
|
||||||
|
val mismatch = argtypes.zip(paramtypes).indexOfFirst { !argTypeCompatible(it.first, it.second) }
|
||||||
|
if(mismatch>=0) {
|
||||||
|
val actual = argtypes[mismatch].toString()
|
||||||
|
val expected = paramtypes[mismatch].toString()
|
||||||
|
return "argument ${mismatch + 1} type mismatch, was: $actual expected: $expected"
|
||||||
|
}
|
||||||
|
if(target.isAsmSubroutine) {
|
||||||
|
if(target.asmReturnvaluesRegisters.size>1) {
|
||||||
|
// multiple return values will NOT work inside an expression.
|
||||||
|
// they MIGHT work in a regular assignment or just a function call statement.
|
||||||
|
val parent = if(call is Statement) call.parent else if(call is Expression) call.parent else null
|
||||||
|
if(call !is FunctionCallStatement && parent !is Assignment && parent !is VarDecl) {
|
||||||
|
return "can't use subroutine call that returns multiple return values here (try moving it into a separate assignment)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (target is BuiltinFunctionStatementPlaceholder) {
|
||||||
|
val func = BuiltinFunctions.getValue(target.name)
|
||||||
|
if(call.args.size != func.parameters.size)
|
||||||
|
return "invalid number of arguments"
|
||||||
|
val paramtypes = func.parameters.map { it.possibleDatatypes }
|
||||||
|
argtypes.zip(paramtypes).forEachIndexed { index, pair ->
|
||||||
|
val anyCompatible = pair.second.any { argTypeCompatible(pair.first, it) }
|
||||||
|
if (!anyCompatible) {
|
||||||
|
val actual = pair.first.toString()
|
||||||
|
val expected = pair.second.toString()
|
||||||
|
return "argument ${index + 1} type mismatch, was: $actual expected: $expected"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
3
compiler/src/prog8/compiler/AssemblyError.kt
Normal file
3
compiler/src/prog8/compiler/AssemblyError.kt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
package prog8.compiler
|
||||||
|
|
||||||
|
internal class AssemblyError(msg: String) : RuntimeException(msg)
|
211
compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt
Normal file
211
compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
package prog8.compiler
|
||||||
|
|
||||||
|
import prog8.ast.IFunctionCall
|
||||||
|
import prog8.ast.Node
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.*
|
||||||
|
import prog8.ast.expressions.*
|
||||||
|
import prog8.ast.processing.AstWalker
|
||||||
|
import prog8.ast.processing.IAstModification
|
||||||
|
import prog8.ast.statements.*
|
||||||
|
|
||||||
|
|
||||||
|
internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: ErrorReporter) : AstWalker() {
|
||||||
|
|
||||||
|
private val noModifications = emptyList<IAstModification>()
|
||||||
|
|
||||||
|
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||||
|
subroutineVariables.add(decl.name to decl)
|
||||||
|
if (decl.value == null && !decl.autogeneratedDontRemove && decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
|
||||||
|
// a numeric vardecl without an initial value is initialized with zero,
|
||||||
|
// unless there's already an assignment below, that initializes the value
|
||||||
|
if(decl.allowInitializeWithZero)
|
||||||
|
{
|
||||||
|
val nextAssign = decl.definingScope().nextSibling(decl) as? Assignment
|
||||||
|
if (nextAssign != null && nextAssign.target.isSameAs(IdentifierReference(listOf(decl.name), Position.DUMMY)))
|
||||||
|
decl.value = null
|
||||||
|
else
|
||||||
|
decl.value = decl.zeroElementValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||||
|
// Try to replace A = B <operator> Something by A= B, A = A <operator> Something
|
||||||
|
// this triggers the more efficent augmented assignment code generation more often.
|
||||||
|
// But it can only be done if the target variable IS NOT OCCURRING AS AN OPERAND ITSELF.
|
||||||
|
if(!assignment.isAugmentable
|
||||||
|
&& assignment.target.identifier != null
|
||||||
|
&& assignment.target.isInRegularRAM(program.namespace)) {
|
||||||
|
val binExpr = assignment.value as? BinaryExpression
|
||||||
|
if (binExpr != null && binExpr.operator !in comparisonOperators) {
|
||||||
|
if (binExpr.left !is BinaryExpression) {
|
||||||
|
if (binExpr.right.referencesIdentifier(*assignment.target.identifier!!.nameInSource.toTypedArray())) {
|
||||||
|
// the right part of the expression contains the target variable itself.
|
||||||
|
// we can't 'split' it trivially because the variable will be changed halfway through.
|
||||||
|
if(binExpr.operator in associativeOperators) {
|
||||||
|
// A = <something-without-A> <associativeoperator> <otherthing-with-A>
|
||||||
|
// use the other part of the expression to split.
|
||||||
|
val assignRight = Assignment(assignment.target, binExpr.right, assignment.position)
|
||||||
|
return listOf(
|
||||||
|
IAstModification.InsertBefore(assignment, assignRight, assignment.definingScope()),
|
||||||
|
IAstModification.ReplaceNode(binExpr.right, binExpr.left, binExpr),
|
||||||
|
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val assignLeft = Assignment(assignment.target, binExpr.left, assignment.position)
|
||||||
|
return listOf(
|
||||||
|
IAstModification.InsertBefore(assignment, assignLeft, assignment.definingScope()),
|
||||||
|
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
private val subroutineVariables = mutableListOf<Pair<String, VarDecl>>()
|
||||||
|
|
||||||
|
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||||
|
subroutineVariables.clear()
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||||
|
val decls = scope.statements.filterIsInstance<VarDecl>()
|
||||||
|
subroutineVariables.addAll(decls.map { it.name to it })
|
||||||
|
|
||||||
|
val sub = scope.definingSubroutine()
|
||||||
|
if (sub != null) {
|
||||||
|
// move 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 replaceVardecls =numericVarsWithValue.map {
|
||||||
|
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)
|
||||||
|
val target = AssignTarget(IdentifierReference(listOf(it.name), it.position), null, null, it.position)
|
||||||
|
val assign = Assignment(target, initValue, it.position)
|
||||||
|
initValue.parent = assign
|
||||||
|
IAstModification.ReplaceNode(it, assign, scope)
|
||||||
|
}
|
||||||
|
val moveVardeclsUp = decls.map { IAstModification.InsertFirst(it, sub) }
|
||||||
|
return replaceVardecls + moveVardeclsUp
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||||
|
val firstDeclarations = mutableMapOf<String, VarDecl>()
|
||||||
|
for(decl in subroutineVariables) {
|
||||||
|
val existing = firstDeclarations[decl.first]
|
||||||
|
if(existing!=null && existing !== decl.second) {
|
||||||
|
errors.err("variable ${decl.first} already defined in subroutine ${subroutine.name} at ${existing.position}", decl.second.position)
|
||||||
|
} else {
|
||||||
|
firstDeclarations[decl.first] = decl.second
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernel routine.
|
||||||
|
// and if an assembly block doesn't contain a rts/rti, and some other situations.
|
||||||
|
val mods = mutableListOf<IAstModification>()
|
||||||
|
val returnStmt = Return(null, subroutine.position)
|
||||||
|
if (subroutine.asmAddress == null
|
||||||
|
&& subroutine.statements.isNotEmpty()
|
||||||
|
&& subroutine.amountOfRtsInAsm() == 0
|
||||||
|
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return
|
||||||
|
&& subroutine.statements.last() !is Subroutine) {
|
||||||
|
mods += IAstModification.InsertLast(returnStmt, subroutine)
|
||||||
|
}
|
||||||
|
|
||||||
|
// precede a subroutine with a return to avoid falling through into the subroutine from code above it
|
||||||
|
val outerScope = subroutine.definingScope()
|
||||||
|
val outerStatements = outerScope.statements
|
||||||
|
val subroutineStmtIdx = outerStatements.indexOf(subroutine)
|
||||||
|
if (subroutineStmtIdx > 0
|
||||||
|
&& outerStatements[subroutineStmtIdx - 1] !is Jump
|
||||||
|
&& outerStatements[subroutineStmtIdx - 1] !is Subroutine
|
||||||
|
&& outerStatements[subroutineStmtIdx - 1] !is Return
|
||||||
|
&& outerScope !is Block) {
|
||||||
|
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope)
|
||||||
|
}
|
||||||
|
return mods
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||||
|
// see if we can remove superfluous typecasts (outside of expressions)
|
||||||
|
// such as casting byte<->ubyte, word<->uword
|
||||||
|
// Also the special typecast of a reference type (str, array) to an UWORD will be changed into address-of.
|
||||||
|
val sourceDt = typecast.expression.inferType(program).typeOrElse(DataType.STRUCT)
|
||||||
|
if (typecast.type in ByteDatatypes && sourceDt in ByteDatatypes
|
||||||
|
|| typecast.type in WordDatatypes && sourceDt in WordDatatypes) {
|
||||||
|
if(typecast.parent !is Expression) {
|
||||||
|
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Note: for various reasons (most importantly, code simplicity), the code generator assumes/requires
|
||||||
|
// that the types of assignment values and their target are the same,
|
||||||
|
// and that the types of both operands of a binaryexpression node are the same.
|
||||||
|
// So, it is not easily possible to remove the typecasts that are there to make these conditions true.
|
||||||
|
// The only place for now where we can do this is for:
|
||||||
|
// asmsub register pair parameter.
|
||||||
|
|
||||||
|
if(typecast.type in WordDatatypes) {
|
||||||
|
val fcall = typecast.parent as? IFunctionCall
|
||||||
|
if (fcall != null) {
|
||||||
|
val sub = fcall.target.targetStatement(program.namespace) as? Subroutine
|
||||||
|
if (sub != null && sub.isAsmSubroutine) {
|
||||||
|
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(sourceDt in PassByReferenceDatatypes) {
|
||||||
|
if(typecast.type==DataType.UWORD) {
|
||||||
|
if(typecast.expression is IdentifierReference) {
|
||||||
|
return listOf(IAstModification.ReplaceNode(
|
||||||
|
typecast,
|
||||||
|
AddressOf(typecast.expression as IdentifierReference, typecast.position),
|
||||||
|
parent
|
||||||
|
))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errors.err("cannot cast pass-by-reference value to type ${typecast.type} (only to UWORD)", typecast.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
|
||||||
|
val binExpr = ifStatement.condition as? BinaryExpression
|
||||||
|
if(binExpr==null || binExpr.operator !in comparisonOperators) {
|
||||||
|
// if x -> if x!=0, if x+5 -> if x+5 != 0
|
||||||
|
val booleanExpr = BinaryExpression(ifStatement.condition, "!=", NumericLiteralValue.optimalInteger(0, ifStatement.condition.position), ifStatement.condition.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(ifStatement.condition, booleanExpr, ifStatement))
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
|
||||||
|
val binExpr = untilLoop.condition as? BinaryExpression
|
||||||
|
if(binExpr==null || binExpr.operator !in comparisonOperators) {
|
||||||
|
// until x -> until x!=0, until x+5 -> until x+5 != 0
|
||||||
|
val booleanExpr = BinaryExpression(untilLoop.condition, "!=", NumericLiteralValue.optimalInteger(0, untilLoop.condition.position), untilLoop.condition.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(untilLoop.condition, booleanExpr, untilLoop))
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
|
||||||
|
val binExpr = whileLoop.condition as? BinaryExpression
|
||||||
|
if(binExpr==null || binExpr.operator !in comparisonOperators) {
|
||||||
|
// while x -> while x!=0, while x+5 -> while x+5 != 0
|
||||||
|
val booleanExpr = BinaryExpression(whileLoop.condition, "!=", NumericLiteralValue.optimalInteger(0, whileLoop.condition.position), whileLoop.condition.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(whileLoop.condition, booleanExpr, whileLoop))
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,8 @@
|
|||||||
package prog8.compiler
|
package prog8.compiler
|
||||||
|
|
||||||
import prog8.ast.base.ArrayDatatypes
|
|
||||||
import prog8.ast.base.DataType
|
|
||||||
import prog8.ast.base.StringDatatypes
|
|
||||||
import prog8.ast.expressions.AddressOf
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.util.*
|
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
enum class OutputType {
|
enum class OutputType {
|
||||||
@ -28,13 +23,14 @@ enum class ZeropageType {
|
|||||||
DONTUSE
|
DONTUSE
|
||||||
}
|
}
|
||||||
|
|
||||||
data class IntegerOrAddressOf(val integer: Int?, val addressOf: AddressOf?)
|
|
||||||
|
|
||||||
data class CompilationOptions(val output: OutputType,
|
data class CompilationOptions(val output: OutputType,
|
||||||
val launcher: LauncherType,
|
val launcher: LauncherType,
|
||||||
val zeropage: ZeropageType,
|
val zeropage: ZeropageType,
|
||||||
val zpReserved: List<IntRange>,
|
val zpReserved: List<IntRange>,
|
||||||
val floats: Boolean)
|
val floats: Boolean,
|
||||||
|
val noSysInit: Boolean) {
|
||||||
|
var slowCodegenWarnings = false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class CompilerException(message: String?) : Exception(message)
|
class CompilerException(message: String?) : Exception(message)
|
||||||
@ -73,77 +69,3 @@ fun loadAsmIncludeFile(filename: String, source: Path): String {
|
|||||||
internal fun tryGetEmbeddedResource(name: String): InputStream? {
|
internal fun tryGetEmbeddedResource(name: String): InputStream? {
|
||||||
return object{}.javaClass.getResourceAsStream("/prog8lib/$name")
|
return object{}.javaClass.getResourceAsStream("/prog8lib/$name")
|
||||||
}
|
}
|
||||||
|
|
||||||
class HeapValues {
|
|
||||||
data class HeapValue(val type: DataType, val str: String?, val array: Array<IntegerOrAddressOf>?, val doubleArray: DoubleArray?) {
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
other as HeapValue
|
|
||||||
return type==other.type && str==other.str && Arrays.equals(array, other.array) && Arrays.equals(doubleArray, other.doubleArray)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int = Objects.hash(str, array, doubleArray)
|
|
||||||
|
|
||||||
val arraysize: Int = array?.size ?: doubleArray?.size ?: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
private val heap = mutableMapOf<Int, HeapValue>()
|
|
||||||
private var heapId = 1
|
|
||||||
|
|
||||||
fun size(): Int = heap.size
|
|
||||||
|
|
||||||
fun addString(type: DataType, str: String): Int {
|
|
||||||
if (str.length > 255)
|
|
||||||
throw IllegalArgumentException("string length must be 0-255")
|
|
||||||
|
|
||||||
// strings are 'interned' and shared if they're the isSameAs
|
|
||||||
val value = HeapValue(type, str, null, null)
|
|
||||||
|
|
||||||
val existing = heap.filter { it.value==value }.map { it.key }.firstOrNull()
|
|
||||||
if(existing!=null)
|
|
||||||
return existing
|
|
||||||
val newId = heapId++
|
|
||||||
heap[newId] = value
|
|
||||||
return newId
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addIntegerArray(type: DataType, array: Array<IntegerOrAddressOf>): Int {
|
|
||||||
// arrays are never shared, don't check for existing
|
|
||||||
if(type !in ArrayDatatypes)
|
|
||||||
throw CompilerException("wrong array type")
|
|
||||||
val newId = heapId++
|
|
||||||
heap[newId] = HeapValue(type, null, array, null)
|
|
||||||
return newId
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addDoublesArray(darray: DoubleArray): Int {
|
|
||||||
// arrays are never shared, don't check for existing
|
|
||||||
val newId = heapId++
|
|
||||||
heap[newId] = HeapValue(DataType.ARRAY_F, null, null, darray)
|
|
||||||
return newId
|
|
||||||
}
|
|
||||||
|
|
||||||
fun update(heapId: Int, str: String) {
|
|
||||||
val oldVal = heap[heapId] ?: throw IllegalArgumentException("heapId not found in heap")
|
|
||||||
if(oldVal.type in StringDatatypes) {
|
|
||||||
if (oldVal.str!!.length != str.length)
|
|
||||||
throw IllegalArgumentException("heap string length mismatch")
|
|
||||||
heap[heapId] = oldVal.copy(str = str)
|
|
||||||
}
|
|
||||||
else throw IllegalArgumentException("heap data type mismatch")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun update(heapId: Int, heapval: HeapValue) {
|
|
||||||
if(heapId !in heap)
|
|
||||||
throw IllegalArgumentException("heapId not found in heap")
|
|
||||||
heap[heapId] = heapval
|
|
||||||
}
|
|
||||||
|
|
||||||
fun get(heapId: Int): HeapValue {
|
|
||||||
return heap[heapId] ?:
|
|
||||||
throw IllegalArgumentException("heapId $heapId not found in heap")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun allEntries() = heap.entries
|
|
||||||
}
|
|
||||||
|
@ -4,16 +4,19 @@ import prog8.ast.AstToSourceCode
|
|||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
import prog8.ast.statements.Directive
|
import prog8.ast.statements.Directive
|
||||||
import prog8.compiler.target.c64.MachineDefinition
|
import prog8.compiler.target.C64Target
|
||||||
import prog8.compiler.target.c64.codegen.AsmGen
|
import prog8.compiler.target.CompilationTarget
|
||||||
|
import prog8.compiler.target.Cx16Target
|
||||||
|
import prog8.optimizer.*
|
||||||
|
import prog8.optimizer.UnusedCodeRemover
|
||||||
import prog8.optimizer.constantFold
|
import prog8.optimizer.constantFold
|
||||||
import prog8.optimizer.optimizeStatements
|
import prog8.optimizer.optimizeStatements
|
||||||
import prog8.optimizer.simplifyExpressions
|
import prog8.optimizer.simplifyExpressions
|
||||||
|
import prog8.parser.ModuleImporter
|
||||||
import prog8.parser.ParsingFailedError
|
import prog8.parser.ParsingFailedError
|
||||||
import prog8.parser.importLibraryModule
|
|
||||||
import prog8.parser.importModule
|
|
||||||
import prog8.parser.moduleName
|
import prog8.parser.moduleName
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
import kotlin.system.exitProcess
|
||||||
import kotlin.system.measureTimeMillis
|
import kotlin.system.measureTimeMillis
|
||||||
|
|
||||||
|
|
||||||
@ -25,90 +28,45 @@ class CompilationResult(val success: Boolean,
|
|||||||
|
|
||||||
fun compileProgram(filepath: Path,
|
fun compileProgram(filepath: Path,
|
||||||
optimize: Boolean,
|
optimize: Boolean,
|
||||||
writeAssembly: Boolean): CompilationResult {
|
writeAssembly: Boolean,
|
||||||
|
slowCodegenWarnings: Boolean,
|
||||||
|
compilationTarget: String,
|
||||||
|
outputDir: Path): CompilationResult {
|
||||||
|
var programName = ""
|
||||||
lateinit var programAst: Program
|
lateinit var programAst: Program
|
||||||
var programName: String? = null
|
lateinit var importedFiles: List<Path>
|
||||||
|
val errors = ErrorReporter()
|
||||||
|
|
||||||
var importedFiles: List<Path> = emptyList()
|
when(compilationTarget) {
|
||||||
var success=false
|
C64Target.name -> CompilationTarget.instance = C64Target
|
||||||
|
Cx16Target.name -> CompilationTarget.instance = Cx16Target
|
||||||
|
else -> {
|
||||||
|
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
|
||||||
println("Parsing...")
|
val (ast, compilationOptions, imported) = parseImports(filepath, errors)
|
||||||
programAst = Program(moduleName(filepath.fileName), mutableListOf())
|
compilationOptions.slowCodegenWarnings = slowCodegenWarnings
|
||||||
importModule(programAst, filepath)
|
programAst = ast
|
||||||
|
importedFiles = imported
|
||||||
importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map{ it.source }
|
processAst(programAst, errors, compilationOptions)
|
||||||
|
if (optimize)
|
||||||
val compilerOptions = determineCompilationOptions(programAst)
|
optimizeAst(programAst, errors)
|
||||||
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
|
postprocessAst(programAst, errors, compilationOptions)
|
||||||
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
|
|
||||||
|
|
||||||
// if we're producing a PRG or BASIC program, include the c64utils and c64lib libraries
|
|
||||||
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG) {
|
|
||||||
importLibraryModule(programAst, "c64lib")
|
|
||||||
importLibraryModule(programAst, "c64utils")
|
|
||||||
}
|
|
||||||
|
|
||||||
// always import prog8lib and math
|
|
||||||
importLibraryModule(programAst, "math")
|
|
||||||
importLibraryModule(programAst, "prog8lib")
|
|
||||||
|
|
||||||
|
|
||||||
// perform initial syntax checks and constant folding
|
|
||||||
println("Syntax check...")
|
|
||||||
val time1 = measureTimeMillis {
|
|
||||||
programAst.checkIdentifiers()
|
|
||||||
}
|
|
||||||
|
|
||||||
//println(" time1: $time1")
|
|
||||||
val time2 = measureTimeMillis {
|
|
||||||
programAst.constantFold()
|
|
||||||
}
|
|
||||||
//println(" time2: $time2")
|
|
||||||
val time3 = measureTimeMillis {
|
|
||||||
programAst.removeNopsFlattenAnonScopes()
|
|
||||||
programAst.reorderStatements()
|
|
||||||
programAst.addTypecasts()
|
|
||||||
}
|
|
||||||
//println(" time3: $time3")
|
|
||||||
val time4 = measureTimeMillis {
|
|
||||||
programAst.checkValid(compilerOptions) // check if tree is valid
|
|
||||||
}
|
|
||||||
//println(" time4: $time4")
|
|
||||||
|
|
||||||
programAst.checkIdentifiers()
|
|
||||||
if (optimize) {
|
|
||||||
// optimize the parse tree
|
|
||||||
println("Optimizing...")
|
|
||||||
while (true) {
|
|
||||||
// keep optimizing expressions and statements until no more steps remain
|
|
||||||
val optsDone1 = programAst.simplifyExpressions()
|
|
||||||
val optsDone2 = programAst.optimizeStatements()
|
|
||||||
if (optsDone1 + optsDone2 == 0)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
programAst.addTypecasts()
|
|
||||||
programAst.removeNopsFlattenAnonScopes()
|
|
||||||
programAst.checkValid(compilerOptions) // check if final tree is valid
|
|
||||||
programAst.checkRecursion() // check if there are recursive subroutine calls
|
|
||||||
|
|
||||||
// printAst(programAst)
|
// printAst(programAst)
|
||||||
|
|
||||||
if(writeAssembly) {
|
if(writeAssembly)
|
||||||
// asm generation directly from the Ast, no need for intermediate code
|
programName = writeAssembly(programAst, errors, outputDir, optimize, compilationOptions)
|
||||||
val zeropage = MachineDefinition.C64Zeropage(compilerOptions)
|
|
||||||
programAst.anonscopeVarsCleanup()
|
|
||||||
val assembly = AsmGen(programAst, compilerOptions, zeropage).compileToAssembly(optimize)
|
|
||||||
assembly.assemble(compilerOptions)
|
|
||||||
programName = assembly.name
|
|
||||||
}
|
|
||||||
success = true
|
|
||||||
}
|
}
|
||||||
|
System.out.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, importedFiles)
|
||||||
|
|
||||||
} catch (px: ParsingFailedError) {
|
} catch (px: ParsingFailedError) {
|
||||||
System.err.print("\u001b[91m") // bright red
|
System.err.print("\u001b[91m") // bright red
|
||||||
@ -131,16 +89,32 @@ fun compileProgram(filepath: Path,
|
|||||||
System.out.flush()
|
System.out.flush()
|
||||||
throw x
|
throw x
|
||||||
}
|
}
|
||||||
return CompilationResult(success, programAst, programName ?: "", importedFiles)
|
|
||||||
|
return CompilationResult(false, Program("failed", mutableListOf()), programName, emptyList())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun printAst(programAst: Program) {
|
private fun parseImports(filepath: Path, errors: ErrorReporter): Triple<Program, CompilationOptions, List<Path>> {
|
||||||
println()
|
println("Compiler target: ${CompilationTarget.instance.name}. Parsing...")
|
||||||
val printer = AstToSourceCode(::print, programAst)
|
val importer = ModuleImporter()
|
||||||
printer.visit(programAst)
|
val programAst = Program(moduleName(filepath.fileName), mutableListOf())
|
||||||
println()
|
importer.importModule(programAst, filepath)
|
||||||
}
|
errors.handle()
|
||||||
|
|
||||||
|
val importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map { it.source }
|
||||||
|
|
||||||
|
val compilerOptions = determineCompilationOptions(programAst)
|
||||||
|
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
|
||||||
|
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
|
||||||
|
|
||||||
|
// depending on the machine and compiler options we may have to include some libraries
|
||||||
|
CompilationTarget.instance.machine.importLibs(compilerOptions, importer, programAst)
|
||||||
|
|
||||||
|
// always import prog8_lib and math
|
||||||
|
importer.importLibraryModule(programAst, "math")
|
||||||
|
importer.importLibraryModule(programAst, "prog8_lib")
|
||||||
|
errors.handle()
|
||||||
|
return Triple(programAst, compilerOptions, importedFiles)
|
||||||
|
}
|
||||||
|
|
||||||
private fun determineCompilationOptions(program: Program): CompilationOptions {
|
private fun determineCompilationOptions(program: Program): CompilationOptions {
|
||||||
val mainModule = program.modules.first()
|
val mainModule = program.modules.first()
|
||||||
@ -148,13 +122,12 @@ private fun determineCompilationOptions(program: Program): CompilationOptions {
|
|||||||
as? Directive)?.args?.single()?.name?.toUpperCase()
|
as? Directive)?.args?.single()?.name?.toUpperCase()
|
||||||
val launcherType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" }
|
val launcherType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" }
|
||||||
as? Directive)?.args?.single()?.name?.toUpperCase()
|
as? Directive)?.args?.single()?.name?.toUpperCase()
|
||||||
mainModule.loadAddress = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%address" }
|
|
||||||
as? Directive)?.args?.single()?.int ?: 0
|
|
||||||
val zpoption: String? = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%zeropage" }
|
val zpoption: String? = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%zeropage" }
|
||||||
as? Directive)?.args?.single()?.name?.toUpperCase()
|
as? Directive)?.args?.single()?.name?.toUpperCase()
|
||||||
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 zpType: ZeropageType =
|
val noSysInit = allOptions.any { it.name == "no_sysinit" }
|
||||||
|
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
|
||||||
@ -164,6 +137,12 @@ private fun determineCompilationOptions(program: Program): CompilationOptions {
|
|||||||
ZeropageType.KERNALSAFE
|
ZeropageType.KERNALSAFE
|
||||||
// error will be printed by the astchecker
|
// error will be printed by the astchecker
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (zpType==ZeropageType.FLOATSAFE && CompilationTarget.instance.name == Cx16Target.name) {
|
||||||
|
System.err.println("Warning: Cx16 target must use zp option basicsafe instead of floatsafe")
|
||||||
|
zpType = ZeropageType.BASICSAFE
|
||||||
|
}
|
||||||
|
|
||||||
val zpReserved = mainModule.statements
|
val zpReserved = mainModule.statements
|
||||||
.asSequence()
|
.asSequence()
|
||||||
.filter { it is Directive && it.directive == "%zpreserved" }
|
.filter { it is Directive && it.directive == "%zpreserved" }
|
||||||
@ -171,9 +150,97 @@ private fun determineCompilationOptions(program: Program): CompilationOptions {
|
|||||||
.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}) {
|
||||||
|
System.err.println("invalid output type $outputType")
|
||||||
|
exitProcess(1)
|
||||||
|
}
|
||||||
|
if(launcherType!=null && !LauncherType.values().any {it.name==launcherType}) {
|
||||||
|
System.err.println("invalid launcher type $launcherType")
|
||||||
|
exitProcess(1)
|
||||||
|
}
|
||||||
|
|
||||||
return CompilationOptions(
|
return CompilationOptions(
|
||||||
if (outputType == null) OutputType.PRG else OutputType.valueOf(outputType),
|
if (outputType == null) OutputType.PRG else OutputType.valueOf(outputType),
|
||||||
if (launcherType == null) LauncherType.BASIC else LauncherType.valueOf(launcherType),
|
if (launcherType == null) LauncherType.BASIC else LauncherType.valueOf(launcherType),
|
||||||
zpType, zpReserved, floatsEnabled
|
zpType, zpReserved, floatsEnabled, noSysInit
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun processAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions) {
|
||||||
|
// perform initial syntax checks and processings
|
||||||
|
println("Processing for target ${CompilationTarget.instance.name}...")
|
||||||
|
programAst.checkIdentifiers(errors)
|
||||||
|
errors.handle()
|
||||||
|
programAst.constantFold(errors)
|
||||||
|
errors.handle()
|
||||||
|
programAst.reorderStatements(errors)
|
||||||
|
errors.handle()
|
||||||
|
programAst.addTypecasts(errors)
|
||||||
|
errors.handle()
|
||||||
|
programAst.variousCleanups()
|
||||||
|
programAst.checkValid(compilerOptions, errors)
|
||||||
|
errors.handle()
|
||||||
|
programAst.checkIdentifiers(errors)
|
||||||
|
errors.handle()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun optimizeAst(programAst: Program, errors: ErrorReporter) {
|
||||||
|
// optimize the parse tree
|
||||||
|
println("Optimizing...")
|
||||||
|
while (true) {
|
||||||
|
// keep optimizing expressions and statements until no more steps remain
|
||||||
|
val optsDone1 = programAst.simplifyExpressions()
|
||||||
|
val optsDone2 = programAst.splitBinaryExpressions()
|
||||||
|
val optsDone3 = programAst.optimizeStatements(errors)
|
||||||
|
programAst.constantFold(errors) // because simplified statements and expressions can result in more constants that can be folded away
|
||||||
|
errors.handle()
|
||||||
|
if (optsDone1 + optsDone2 + optsDone3 == 0)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
val remover = UnusedCodeRemover(programAst, errors)
|
||||||
|
remover.visit(programAst)
|
||||||
|
remover.applyModifications()
|
||||||
|
errors.handle()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun postprocessAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions) {
|
||||||
|
programAst.addTypecasts(errors)
|
||||||
|
errors.handle()
|
||||||
|
programAst.variousCleanups()
|
||||||
|
programAst.checkValid(compilerOptions, errors) // check if final tree is still valid
|
||||||
|
errors.handle()
|
||||||
|
val callGraph = CallGraph(programAst)
|
||||||
|
callGraph.checkRecursiveCalls(errors)
|
||||||
|
errors.handle()
|
||||||
|
programAst.verifyFunctionArgTypes()
|
||||||
|
programAst.moveMainAndStartToFirst()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun writeAssembly(programAst: Program, errors: ErrorReporter, outputDir: Path,
|
||||||
|
optimize: Boolean, compilerOptions: CompilationOptions): String {
|
||||||
|
// asm generation directly from the Ast,
|
||||||
|
programAst.processAstBeforeAsmGeneration(errors)
|
||||||
|
errors.handle()
|
||||||
|
|
||||||
|
// printAst(programAst)
|
||||||
|
|
||||||
|
CompilationTarget.instance.machine.initializeZeropage(compilerOptions)
|
||||||
|
val assembly = CompilationTarget.instance.asmGenerator(
|
||||||
|
programAst,
|
||||||
|
errors,
|
||||||
|
CompilationTarget.instance.machine.zeropage,
|
||||||
|
compilerOptions,
|
||||||
|
outputDir).compileToAssembly(optimize)
|
||||||
|
assembly.assemble(compilerOptions)
|
||||||
|
errors.handle()
|
||||||
|
return assembly.name
|
||||||
|
}
|
||||||
|
|
||||||
|
fun printAst(programAst: Program) {
|
||||||
|
println()
|
||||||
|
val printer = AstToSourceCode(::print, programAst)
|
||||||
|
printer.visit(programAst)
|
||||||
|
println()
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,12 @@ class ZeropageDepletedError(message: String) : Exception(message)
|
|||||||
|
|
||||||
abstract class Zeropage(protected val options: CompilationOptions) {
|
abstract class Zeropage(protected val options: CompilationOptions) {
|
||||||
|
|
||||||
|
abstract val SCRATCH_B1 : Int // temp storage for a single byte
|
||||||
|
abstract val SCRATCH_REG : Int // temp storage for a register
|
||||||
|
abstract val SCRATCH_W1 : Int // temp storage 1 for a word $fb+$fc
|
||||||
|
abstract val SCRATCH_W2 : Int // temp storage 2 for a word $fb+$fc
|
||||||
|
|
||||||
|
|
||||||
private val allocations = mutableMapOf<Int, Pair<String, DataType>>()
|
private val allocations = mutableMapOf<Int, Pair<String, DataType>>()
|
||||||
val free = mutableListOf<Int>() // subclasses must set this to the appropriate free locations.
|
val free = mutableListOf<Int>() // subclasses must set this to the appropriate free locations.
|
||||||
|
|
||||||
@ -15,8 +21,8 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
|||||||
|
|
||||||
fun available() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size
|
fun available() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size
|
||||||
|
|
||||||
fun allocate(scopedname: String, datatype: DataType, position: Position?): Int {
|
fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: ErrorReporter): Int {
|
||||||
assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"isSameAs scopedname can't be allocated twice"}
|
assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"scopedname can't be allocated twice"}
|
||||||
|
|
||||||
if(options.zeropage==ZeropageType.DONTUSE)
|
if(options.zeropage==ZeropageType.DONTUSE)
|
||||||
throw CompilerException("zero page usage has been disabled")
|
throw CompilerException("zero page usage has been disabled")
|
||||||
@ -28,9 +34,9 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
|||||||
DataType.FLOAT -> {
|
DataType.FLOAT -> {
|
||||||
if (options.floats) {
|
if (options.floats) {
|
||||||
if(position!=null)
|
if(position!=null)
|
||||||
printWarning("allocated a large value (float) in zeropage", position)
|
errors.warn("allocated a large value (float) in zeropage", position)
|
||||||
else
|
else
|
||||||
printWarning("$scopedname: allocated a large value (float) in zeropage")
|
errors.warn("$scopedname: allocated a large value (float) in zeropage", position ?: Position.DUMMY)
|
||||||
5
|
5
|
||||||
} else throw CompilerException("floating point option not enabled")
|
} else throw CompilerException("floating point option not enabled")
|
||||||
}
|
}
|
||||||
@ -39,13 +45,13 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
|||||||
|
|
||||||
if(free.size > 0) {
|
if(free.size > 0) {
|
||||||
if(size==1) {
|
if(size==1) {
|
||||||
for(candidate in free.min()!! .. free.max()!!+1) {
|
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1) {
|
||||||
if(loneByte(candidate))
|
if(loneByte(candidate))
|
||||||
return makeAllocation(candidate, 1, datatype, scopedname)
|
return makeAllocation(candidate, 1, datatype, scopedname)
|
||||||
}
|
}
|
||||||
return makeAllocation(free[0], 1, datatype, scopedname)
|
return makeAllocation(free[0], 1, datatype, scopedname)
|
||||||
}
|
}
|
||||||
for(candidate in free.min()!! .. free.max()!!+1) {
|
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1) {
|
||||||
if (sequentialFree(candidate, size))
|
if (sequentialFree(candidate, size))
|
||||||
return makeAllocation(candidate, size, datatype, scopedname)
|
return makeAllocation(candidate, size, datatype, scopedname)
|
||||||
}
|
}
|
||||||
@ -58,18 +64,10 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
|||||||
|
|
||||||
private fun makeAllocation(address: Int, size: Int, datatype: DataType, name: String?): Int {
|
private fun makeAllocation(address: Int, size: Int, datatype: DataType, name: String?): Int {
|
||||||
free.removeAll(address until address+size)
|
free.removeAll(address until address+size)
|
||||||
allocations[address] = Pair(name ?: "<unnamed>", datatype)
|
allocations[address] = (name ?: "<unnamed>") to datatype
|
||||||
return address
|
return address
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loneByte(address: Int) = address in free && address-1 !in free && address+1 !in free
|
private fun loneByte(address: Int) = address in free && address-1 !in free && address+1 !in free
|
||||||
private fun sequentialFree(address: Int, size: Int) = free.containsAll((address until address+size).toList())
|
private fun sequentialFree(address: Int, size: Int) = free.containsAll((address until address+size).toList())
|
||||||
|
|
||||||
enum class ExitProgramStrategy {
|
|
||||||
CLEAN_EXIT,
|
|
||||||
SYSTEM_RESET
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract val exitProgramStrategy: ExitProgramStrategy
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
47
compiler/src/prog8/compiler/target/CompilationTarget.kt
Normal file
47
compiler/src/prog8/compiler/target/CompilationTarget.kt
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package prog8.compiler.target
|
||||||
|
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.ErrorReporter
|
||||||
|
import prog8.compiler.CompilationOptions
|
||||||
|
import prog8.compiler.Zeropage
|
||||||
|
import prog8.compiler.target.c64.C64MachineDefinition
|
||||||
|
import prog8.compiler.target.c64.Petscii
|
||||||
|
import prog8.compiler.target.c64.codegen.AsmGen
|
||||||
|
import prog8.compiler.target.cx16.CX16MachineDefinition
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
|
||||||
|
internal interface CompilationTarget {
|
||||||
|
val name: String
|
||||||
|
val machine: IMachineDefinition
|
||||||
|
fun encodeString(str: String, altEncoding: Boolean): List<Short>
|
||||||
|
fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
|
||||||
|
fun asmGenerator(program: Program, errors: ErrorReporter, zp: Zeropage, options: CompilationOptions, path: Path): IAssemblyGenerator
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
lateinit var instance: CompilationTarget
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal object C64Target: CompilationTarget {
|
||||||
|
override val name = "c64"
|
||||||
|
override val machine = C64MachineDefinition
|
||||||
|
override fun encodeString(str: String, altEncoding: Boolean) =
|
||||||
|
if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||||
|
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
|
||||||
|
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||||
|
override fun asmGenerator(program: Program, errors: ErrorReporter, zp: Zeropage, options: CompilationOptions, path: Path) =
|
||||||
|
AsmGen(program, errors, zp, options, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal object Cx16Target: CompilationTarget {
|
||||||
|
override val name = "cx16"
|
||||||
|
override val machine = CX16MachineDefinition
|
||||||
|
override fun encodeString(str: String, altEncoding: Boolean) =
|
||||||
|
if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||||
|
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
|
||||||
|
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||||
|
override fun asmGenerator(program: Program, errors: ErrorReporter, zp: Zeropage, options: CompilationOptions, path: Path) =
|
||||||
|
AsmGen(program, errors, zp, options, path)
|
||||||
|
}
|
14
compiler/src/prog8/compiler/target/IAssemblyGenerator.kt
Normal file
14
compiler/src/prog8/compiler/target/IAssemblyGenerator.kt
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package prog8.compiler.target
|
||||||
|
|
||||||
|
import prog8.compiler.CompilationOptions
|
||||||
|
|
||||||
|
internal interface IAssemblyGenerator {
|
||||||
|
fun compileToAssembly(optimize: Boolean): IAssemblyProgram
|
||||||
|
}
|
||||||
|
|
||||||
|
internal const val generatedLabelPrefix = "_prog8_label_"
|
||||||
|
|
||||||
|
internal interface IAssemblyProgram {
|
||||||
|
val name: String
|
||||||
|
fun assemble(options: CompilationOptions)
|
||||||
|
}
|
39
compiler/src/prog8/compiler/target/IMachineDefinition.kt
Normal file
39
compiler/src/prog8/compiler/target/IMachineDefinition.kt
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package prog8.compiler.target
|
||||||
|
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.compiler.CompilationOptions
|
||||||
|
import prog8.compiler.Zeropage
|
||||||
|
import prog8.parser.ModuleImporter
|
||||||
|
|
||||||
|
|
||||||
|
internal interface IMachineFloat {
|
||||||
|
fun toDouble(): Double
|
||||||
|
fun makeFloatFillAsm(): String
|
||||||
|
}
|
||||||
|
|
||||||
|
internal enum class CpuType {
|
||||||
|
CPU6502,
|
||||||
|
CPU65c02
|
||||||
|
}
|
||||||
|
|
||||||
|
internal interface IMachineDefinition {
|
||||||
|
val FLOAT_MAX_NEGATIVE: Double
|
||||||
|
val FLOAT_MAX_POSITIVE: Double
|
||||||
|
val FLOAT_MEM_SIZE: Int
|
||||||
|
val POINTER_MEM_SIZE: Int
|
||||||
|
val ESTACK_LO: Int
|
||||||
|
val ESTACK_HI: Int
|
||||||
|
val BASIC_LOAD_ADDRESS : Int
|
||||||
|
val RAW_LOAD_ADDRESS : Int
|
||||||
|
|
||||||
|
val opcodeNames: Set<String>
|
||||||
|
var zeropage: Zeropage
|
||||||
|
val cpu: CpuType
|
||||||
|
|
||||||
|
fun initializeZeropage(compilerOptions: CompilationOptions)
|
||||||
|
fun getFloat(num: Number): IMachineFloat
|
||||||
|
fun getFloatRomConst(number: Double): String?
|
||||||
|
fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program)
|
||||||
|
fun launchEmulator(programName: String)
|
||||||
|
fun isRegularRAMaddress(address: Int): Boolean
|
||||||
|
}
|
@ -2,70 +2,73 @@ package prog8.compiler.target.c64
|
|||||||
|
|
||||||
import prog8.compiler.CompilationOptions
|
import prog8.compiler.CompilationOptions
|
||||||
import prog8.compiler.OutputType
|
import prog8.compiler.OutputType
|
||||||
import java.io.File
|
import prog8.compiler.target.CompilationTarget
|
||||||
|
import prog8.compiler.target.IAssemblyProgram
|
||||||
|
import prog8.compiler.target.generatedLabelPrefix
|
||||||
|
import java.nio.file.Path
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
class AssemblyProgram(val name: String) {
|
class AssemblyProgram(override val name: String, outputDir: Path) : IAssemblyProgram {
|
||||||
private val assemblyFile = "$name.asm"
|
private val assemblyFile = outputDir.resolve("$name.asm")
|
||||||
private val viceMonListFile = "$name.vice-mon-list"
|
private val prgFile = outputDir.resolve("$name.prg")
|
||||||
|
private val binFile = outputDir.resolve("$name.bin")
|
||||||
|
private val viceMonListFile = outputDir.resolve("$name.vice-mon-list")
|
||||||
|
|
||||||
companion object {
|
override fun assemble(options: CompilationOptions) {
|
||||||
// 6502 opcodes (including aliases and illegal opcodes), these cannot be used as variable or label names
|
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps (default = do this silently)
|
||||||
val opcodeNames = setOf("adc", "ahx", "alr", "anc", "and", "ane", "arr", "asl", "asr", "axs", "bcc", "bcs",
|
|
||||||
"beq", "bge", "bit", "blt", "bmi", "bne", "bpl", "brk", "bvc", "bvs", "clc",
|
|
||||||
"cld", "cli", "clv", "cmp", "cpx", "cpy", "dcm", "dcp", "dec", "dex", "dey",
|
|
||||||
"eor", "gcc", "gcs", "geq", "gge", "glt", "gmi", "gne", "gpl", "gvc", "gvs",
|
|
||||||
"inc", "ins", "inx", "iny", "isb", "isc", "jam", "jmp", "jsr", "lae", "las",
|
|
||||||
"lax", "lda", "lds", "ldx", "ldy", "lsr", "lxa", "nop", "ora", "pha", "php",
|
|
||||||
"pla", "plp", "rla", "rol", "ror", "rra", "rti", "rts", "sax", "sbc", "sbx",
|
|
||||||
"sec", "sed", "sei", "sha", "shl", "shr", "shs", "shx", "shy", "slo", "sre",
|
|
||||||
"sta", "stx", "sty", "tas", "tax", "tay", "tsx", "txa", "txs", "tya", "xaa")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun assemble(options: CompilationOptions) {
|
|
||||||
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps
|
|
||||||
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", "-Wno-error=long-branch",
|
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
|
||||||
"--dump-labels", "--vice-labels", "-l", viceMonListFile, "--no-monitor")
|
"--dump-labels", "--vice-labels", "-l", viceMonListFile.toString(), "--no-monitor")
|
||||||
|
|
||||||
val outFile = when(options.output) {
|
val outFile = when (options.output) {
|
||||||
OutputType.PRG -> {
|
OutputType.PRG -> {
|
||||||
command.add("--cbm-prg")
|
command.add("--cbm-prg")
|
||||||
println("\nCreating C-64 prg.")
|
println("\nCreating prg for target ${CompilationTarget.instance.name}.")
|
||||||
"$name.prg"
|
prgFile
|
||||||
}
|
}
|
||||||
OutputType.RAW -> {
|
OutputType.RAW -> {
|
||||||
command.add("--nostart")
|
command.add("--nostart")
|
||||||
println("\nCreating raw binary.")
|
println("\nCreating raw binary for target ${CompilationTarget.instance.name}.")
|
||||||
"$name.bin"
|
binFile
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
command.addAll(listOf("--output", outFile, assemblyFile))
|
command.addAll(listOf("--output", outFile.toString(), assemblyFile.toString()))
|
||||||
|
|
||||||
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")
|
System.err.println("assembler failed with returncode $result")
|
||||||
exitProcess(result)
|
exitProcess(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removeGeneratedLabelsFromMonlist()
|
||||||
generateBreakpointList()
|
generateBreakpointList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun removeGeneratedLabelsFromMonlist() {
|
||||||
|
val pattern = Regex("""al (\w+) \S+${generatedLabelPrefix}.+?""")
|
||||||
|
val lines = viceMonListFile.toFile().readLines()
|
||||||
|
viceMonListFile.toFile().outputStream().bufferedWriter().use {
|
||||||
|
for (line in lines) {
|
||||||
|
if(pattern.matchEntire(line)==null)
|
||||||
|
it.write(line+"\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun generateBreakpointList() {
|
private fun generateBreakpointList() {
|
||||||
// builds list of breakpoints, appends to monitor list file
|
// builds list of breakpoints, appends to monitor list file
|
||||||
val breakpoints = mutableListOf<String>()
|
val breakpoints = mutableListOf<String>()
|
||||||
val pattern = Regex("""al (\w+) \S+_prog8_breakpoint_\d+.?""") // gather breakpoints by the source label that"s generated for them
|
val pattern = Regex("""al (\w+) \S+_prog8_breakpoint_\d+.?""") // gather breakpoints by the source label that's generated for them
|
||||||
for(line in File(viceMonListFile).readLines()) {
|
for (line in viceMonListFile.toFile().readLines()) {
|
||||||
val match = pattern.matchEntire(line)
|
val match = pattern.matchEntire(line)
|
||||||
if(match!=null)
|
if (match != null)
|
||||||
breakpoints.add("break \$" + match.groupValues[1])
|
breakpoints.add("break \$" + match.groupValues[1])
|
||||||
}
|
}
|
||||||
val num = breakpoints.size
|
val num = breakpoints.size
|
||||||
breakpoints.add(0, "; vice monitor breakpoint list now follows")
|
breakpoints.add(0, "; vice monitor breakpoint list now follows")
|
||||||
breakpoints.add(1, "; $num breakpoints have been defined")
|
breakpoints.add(1, "; $num breakpoints have been defined")
|
||||||
breakpoints.add(2, "del")
|
breakpoints.add(2, "del")
|
||||||
File(viceMonListFile).appendText(breakpoints.joinToString("\n")+"\n")
|
viceMonListFile.toFile().appendText(breakpoints.joinToString("\n") + "\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
243
compiler/src/prog8/compiler/target/c64/C64MachineDefinition.kt
Normal file
243
compiler/src/prog8/compiler/target/c64/C64MachineDefinition.kt
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
package prog8.compiler.target.c64
|
||||||
|
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.compiler.*
|
||||||
|
import prog8.compiler.target.CpuType
|
||||||
|
import prog8.compiler.target.IMachineDefinition
|
||||||
|
import prog8.compiler.target.IMachineFloat
|
||||||
|
import prog8.parser.ModuleImporter
|
||||||
|
import java.io.IOException
|
||||||
|
import java.math.RoundingMode
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
|
import kotlin.math.pow
|
||||||
|
|
||||||
|
internal object C64MachineDefinition: IMachineDefinition {
|
||||||
|
|
||||||
|
override val cpu = CpuType.CPU6502
|
||||||
|
|
||||||
|
// 5-byte cbm MFLPT format limitations:
|
||||||
|
override val FLOAT_MAX_POSITIVE = 1.7014118345e+38 // bytes: 255,127,255,255,255
|
||||||
|
override val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255
|
||||||
|
override val FLOAT_MEM_SIZE = 5
|
||||||
|
override val POINTER_MEM_SIZE = 2
|
||||||
|
override val BASIC_LOAD_ADDRESS = 0x0801
|
||||||
|
override val RAW_LOAD_ADDRESS = 0xc000
|
||||||
|
|
||||||
|
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
||||||
|
// and some heavily used string constants derived from the two values above
|
||||||
|
override val ESTACK_LO = 0xce00 // $ce00-$ceff inclusive
|
||||||
|
override val ESTACK_HI = 0xcf00 // $ce00-$ceff inclusive
|
||||||
|
|
||||||
|
override lateinit var zeropage: Zeropage
|
||||||
|
|
||||||
|
override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
|
||||||
|
|
||||||
|
override fun getFloatRomConst(number: Double): String? {
|
||||||
|
// try to match the ROM float constants to save memory
|
||||||
|
val mflpt5 = Mflpt5.fromNumber(number)
|
||||||
|
val floatbytes = shortArrayOf(mflpt5.b0, mflpt5.b1, mflpt5.b2, mflpt5.b3, mflpt5.b4)
|
||||||
|
when {
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x00, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_ZERO_const" // not a ROM const
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x81, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_ONE_const" // not a ROM const
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x82, 0x49, 0x0f, 0xda, 0xa1)) -> return "floats.FL_PIVAL"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x90, 0x80, 0x00, 0x00, 0x00)) -> return "floats.FL_N32768"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x81, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_FONE"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x80, 0x35, 0x04, 0xf3, 0x34)) -> return "floats.FL_SQRHLF"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x81, 0x35, 0x04, 0xf3, 0x34)) -> return "floats.FL_SQRTWO"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x80, 0x80, 0x00, 0x00, 0x00)) -> return "floats.FL_NEGHLF"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x80, 0x31, 0x72, 0x17, 0xf8)) -> return "floats.FL_LOG2"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x84, 0x20, 0x00, 0x00, 0x00)) -> return "floats.FL_TENC"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x9e, 0x6e, 0x6b, 0x28, 0x00)) -> return "floats.FL_NZMIL"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x80, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_FHALF"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x81, 0x38, 0xaa, 0x3b, 0x29)) -> return "floats.FL_LOGEB2"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x81, 0x49, 0x0f, 0xda, 0xa2)) -> return "floats.FL_PIHALF"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x83, 0x49, 0x0f, 0xda, 0xa2)) -> return "floats.FL_TWOPI"
|
||||||
|
floatbytes.contentEquals(shortArrayOf(0x7f, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_FR4"
|
||||||
|
else -> {
|
||||||
|
// attempt to correct for a few rounding issues
|
||||||
|
when (number.toBigDecimal().setScale(10, RoundingMode.HALF_DOWN).toDouble()) {
|
||||||
|
3.1415926536 -> return "floats.FL_PIVAL"
|
||||||
|
1.4142135624 -> return "floats.FL_SQRTWO"
|
||||||
|
0.7071067812 -> return "floats.FL_SQRHLF"
|
||||||
|
0.6931471806 -> return "floats.FL_LOG2"
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program) {
|
||||||
|
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
||||||
|
importer.importLibraryModule(program, "syslib")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun launchEmulator(programName: String) {
|
||||||
|
for(emulator in listOf("x64sc", "x64")) {
|
||||||
|
println("\nStarting C-64 emulator $emulator...")
|
||||||
|
val cmdline = listOf(emulator, "-silent", "-moncommands", "$programName.vice-mon-list",
|
||||||
|
"-autostartprgmode", "1", "-autostart-warp", "-autostart", programName + ".prg")
|
||||||
|
val processb = ProcessBuilder(cmdline).inheritIO()
|
||||||
|
val process: Process
|
||||||
|
try {
|
||||||
|
process=processb.start()
|
||||||
|
} catch(x: IOException) {
|
||||||
|
continue // try the next emulator executable
|
||||||
|
}
|
||||||
|
process.waitFor()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isRegularRAMaddress(address: Int): Boolean = address<0xa000 || address in 0xc000..0xcfff
|
||||||
|
|
||||||
|
override fun initializeZeropage(compilerOptions: CompilationOptions) {
|
||||||
|
zeropage = C64Zeropage(compilerOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6502 opcodes (including aliases and illegal opcodes), these cannot be used as variable or label names
|
||||||
|
override val opcodeNames = setOf("adc", "ahx", "alr", "anc", "and", "ane", "arr", "asl", "asr", "axs", "bcc", "bcs",
|
||||||
|
"beq", "bge", "bit", "blt", "bmi", "bne", "bpl", "brk", "bvc", "bvs", "clc",
|
||||||
|
"cld", "cli", "clv", "cmp", "cpx", "cpy", "dcm", "dcp", "dec", "dex", "dey",
|
||||||
|
"eor", "gcc", "gcs", "geq", "gge", "glt", "gmi", "gne", "gpl", "gvc", "gvs",
|
||||||
|
"inc", "ins", "inx", "iny", "isb", "isc", "jam", "jmp", "jsr", "lae", "las",
|
||||||
|
"lax", "lda", "lds", "ldx", "ldy", "lsr", "lxa", "nop", "ora", "pha", "php",
|
||||||
|
"pla", "plp", "rla", "rol", "ror", "rra", "rti", "rts", "sax", "sbc", "sbx",
|
||||||
|
"sec", "sed", "sei", "sha", "shl", "shr", "shs", "shx", "shy", "slo", "sre",
|
||||||
|
"sta", "stx", "sty", "tas", "tax", "tay", "tsx", "txa", "txs", "tya", "xaa")
|
||||||
|
|
||||||
|
|
||||||
|
internal class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||||
|
|
||||||
|
override val SCRATCH_B1 = 0x02 // temp storage for a single byte
|
||||||
|
override val SCRATCH_REG = 0x03 // temp storage for a register, must be B1+1
|
||||||
|
override val SCRATCH_W1 = 0xfb // temp storage 1 for a word $fb+$fc
|
||||||
|
override val SCRATCH_W2 = 0xfd // temp storage 2 for a word $fb+$fc
|
||||||
|
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (options.floats && options.zeropage !in setOf(ZeropageType.FLOATSAFE, ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
|
||||||
|
throw CompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
|
||||||
|
|
||||||
|
if (options.zeropage == ZeropageType.FULL) {
|
||||||
|
free.addAll(0x04..0xf9)
|
||||||
|
free.add(0xff)
|
||||||
|
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
|
||||||
|
free.removeAll(listOf(0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6)) // these are updated by IRQ
|
||||||
|
} else {
|
||||||
|
if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
|
||||||
|
free.addAll(listOf(
|
||||||
|
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
|
||||||
|
0x16, 0x17, 0x18, 0x19, 0x1a,
|
||||||
|
0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
|
||||||
|
0x22, 0x23, 0x24, 0x25,
|
||||||
|
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
|
||||||
|
0x47, 0x48, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x51, 0x52, 0x53,
|
||||||
|
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
|
||||||
|
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
||||||
|
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
|
||||||
|
0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c,
|
||||||
|
0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
|
||||||
|
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
|
||||||
|
// 0x90-0xfa is 'kernel work storage area'
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.zeropage == ZeropageType.FLOATSAFE) {
|
||||||
|
// remove the zero page locations used for floating point operations from the free list
|
||||||
|
free.removeAll(listOf(
|
||||||
|
0x10, 0x11, 0x12, 0x26, 0x27, 0x28, 0x29, 0x2a,
|
||||||
|
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
|
||||||
|
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
||||||
|
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
|
||||||
|
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
if(options.zeropage!=ZeropageType.DONTUSE) {
|
||||||
|
// add the free Zp addresses
|
||||||
|
// these are valid for the C-64 but allow BASIC to keep running fully *as long as you don't use tape I/O*
|
||||||
|
free.addAll(listOf(0x04, 0x05, 0x06, 0x0a, 0x0e,
|
||||||
|
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa5, 0xa6,
|
||||||
|
0xb0, 0xb1, 0xbe, 0xbf, 0xf9))
|
||||||
|
} else {
|
||||||
|
// don't use the zeropage at all
|
||||||
|
free.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require(SCRATCH_B1 !in free)
|
||||||
|
require(SCRATCH_REG !in free)
|
||||||
|
require(SCRATCH_W1 !in free)
|
||||||
|
require(SCRATCH_W2 !in free)
|
||||||
|
|
||||||
|
for (reserved in options.zpReserved)
|
||||||
|
reserve(reserved)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short): IMachineFloat {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val zero = Mflpt5(0, 0, 0, 0, 0)
|
||||||
|
fun fromNumber(num: Number): Mflpt5 {
|
||||||
|
// see https://en.wikipedia.org/wiki/Microsoft_Binary_Format
|
||||||
|
// and https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a
|
||||||
|
// and https://en.wikipedia.org/wiki/IEEE_754-1985
|
||||||
|
|
||||||
|
val flt = num.toDouble()
|
||||||
|
if (flt < FLOAT_MAX_NEGATIVE || flt > FLOAT_MAX_POSITIVE)
|
||||||
|
throw CompilerException("floating point number out of 5-byte mflpt range: $this")
|
||||||
|
if (flt == 0.0)
|
||||||
|
return zero
|
||||||
|
|
||||||
|
val sign = if (flt < 0.0) 0x80L else 0x00L
|
||||||
|
var exponent = 128 + 32 // 128 is cbm's bias, 32 is this algo's bias
|
||||||
|
var mantissa = flt.absoluteValue
|
||||||
|
|
||||||
|
// if mantissa is too large, shift right and adjust exponent
|
||||||
|
while (mantissa >= 0x100000000) {
|
||||||
|
mantissa /= 2.0
|
||||||
|
exponent++
|
||||||
|
}
|
||||||
|
// if mantissa is too small, shift left and adjust exponent
|
||||||
|
while (mantissa < 0x80000000) {
|
||||||
|
mantissa *= 2.0
|
||||||
|
exponent--
|
||||||
|
}
|
||||||
|
|
||||||
|
return when {
|
||||||
|
exponent < 0 -> zero // underflow, use zero instead
|
||||||
|
exponent > 255 -> throw CompilerException("floating point overflow: $this")
|
||||||
|
exponent == 0 -> zero
|
||||||
|
else -> {
|
||||||
|
val mantLong = mantissa.toLong()
|
||||||
|
Mflpt5(
|
||||||
|
exponent.toShort(),
|
||||||
|
(mantLong.and(0x7f000000L) ushr 24).or(sign).toShort(),
|
||||||
|
(mantLong.and(0x00ff0000L) ushr 16).toShort(),
|
||||||
|
(mantLong.and(0x0000ff00L) ushr 8).toShort(),
|
||||||
|
(mantLong.and(0x000000ffL)).toShort())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toDouble(): Double {
|
||||||
|
if (this == zero) return 0.0
|
||||||
|
val exp = b0 - 128
|
||||||
|
val sign = (b1.toInt() and 0x80) > 0
|
||||||
|
val number = 0x80000000L.or(b1.toLong() shl 24).or(b2.toLong() shl 16).or(b3.toLong() shl 8).or(b4.toLong())
|
||||||
|
val result = number.toDouble() * (2.0).pow(exp) / 0x100000000
|
||||||
|
return if (sign) -result else result
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun makeFloatFillAsm(): String {
|
||||||
|
val b0 = "$" + b0.toString(16).padStart(2, '0')
|
||||||
|
val b1 = "$" + b1.toString(16).padStart(2, '0')
|
||||||
|
val b2 = "$" + b2.toString(16).padStart(2, '0')
|
||||||
|
val b3 = "$" + b3.toString(16).padStart(2, '0')
|
||||||
|
val b4 = "$" + b4.toString(16).padStart(2, '0')
|
||||||
|
return "$b0, $b1, $b2, $b3, $b4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,255 +0,0 @@
|
|||||||
package prog8.compiler.target.c64
|
|
||||||
|
|
||||||
import prog8.compiler.CompilationOptions
|
|
||||||
import prog8.compiler.CompilerException
|
|
||||||
import prog8.compiler.Zeropage
|
|
||||||
import prog8.compiler.ZeropageType
|
|
||||||
import java.awt.Color
|
|
||||||
import java.awt.image.BufferedImage
|
|
||||||
import javax.imageio.ImageIO
|
|
||||||
import kotlin.math.absoluteValue
|
|
||||||
import kotlin.math.pow
|
|
||||||
|
|
||||||
object MachineDefinition {
|
|
||||||
|
|
||||||
// 5-byte cbm MFLPT format limitations:
|
|
||||||
const val FLOAT_MAX_POSITIVE = 1.7014118345e+38 // bytes: 255,127,255,255,255
|
|
||||||
const val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255
|
|
||||||
|
|
||||||
const val BASIC_LOAD_ADDRESS = 0x0801
|
|
||||||
const val RAW_LOAD_ADDRESS = 0xc000
|
|
||||||
|
|
||||||
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
|
||||||
// and some heavily used string constants derived from the two values above
|
|
||||||
const val ESTACK_LO_VALUE = 0xce00 // $ce00-$ceff inclusive
|
|
||||||
const val ESTACK_HI_VALUE = 0xcf00 // $cf00-$cfff inclusive
|
|
||||||
const val ESTACK_LO_HEX = "\$ce00"
|
|
||||||
const val ESTACK_LO_PLUS1_HEX = "\$ce01"
|
|
||||||
const val ESTACK_LO_PLUS2_HEX = "\$ce02"
|
|
||||||
const val ESTACK_HI_HEX = "\$cf00"
|
|
||||||
const val ESTACK_HI_PLUS1_HEX = "\$cf01"
|
|
||||||
const val ESTACK_HI_PLUS2_HEX = "\$cf02"
|
|
||||||
|
|
||||||
|
|
||||||
class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val SCRATCH_B1 = 0x02
|
|
||||||
const val SCRATCH_REG = 0x03 // temp storage for a register
|
|
||||||
const val SCRATCH_REG_X = 0xfa // temp storage for register X (the evaluation stack pointer)
|
|
||||||
const val SCRATCH_W1 = 0xfb // $fb+$fc
|
|
||||||
const val SCRATCH_W2 = 0xfd // $fd+$fe
|
|
||||||
}
|
|
||||||
|
|
||||||
override val exitProgramStrategy: ExitProgramStrategy = when (options.zeropage) {
|
|
||||||
ZeropageType.BASICSAFE, ZeropageType.DONTUSE -> ExitProgramStrategy.CLEAN_EXIT
|
|
||||||
ZeropageType.FLOATSAFE, ZeropageType.KERNALSAFE, ZeropageType.FULL -> ExitProgramStrategy.SYSTEM_RESET
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
init {
|
|
||||||
if (options.floats && options.zeropage !in setOf(ZeropageType.FLOATSAFE, ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
|
|
||||||
throw CompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
|
|
||||||
|
|
||||||
if (options.zeropage == ZeropageType.FULL) {
|
|
||||||
free.addAll(0x04..0xf9)
|
|
||||||
free.add(0xff)
|
|
||||||
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_REG_X, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
|
|
||||||
free.removeAll(listOf(0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6)) // these are updated by IRQ
|
|
||||||
} else {
|
|
||||||
if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
|
|
||||||
free.addAll(listOf(0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
|
|
||||||
0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
|
|
||||||
0x22, 0x23, 0x24, 0x25,
|
|
||||||
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
|
|
||||||
0x47, 0x48, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x51, 0x52, 0x53,
|
|
||||||
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
|
|
||||||
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
|
||||||
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
|
|
||||||
0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c,
|
|
||||||
0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
|
|
||||||
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
|
|
||||||
// 0x90-0xfa is 'kernel work storage area'
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.zeropage == ZeropageType.FLOATSAFE) {
|
|
||||||
// remove the zero page locations used for floating point operations from the free list
|
|
||||||
free.removeAll(listOf(
|
|
||||||
0x12, 0x26, 0x27, 0x28, 0x29, 0x2a,
|
|
||||||
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
|
|
||||||
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
|
||||||
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
|
|
||||||
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xf
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
if(options.zeropage!=ZeropageType.DONTUSE) {
|
|
||||||
// add the other free Zp addresses,
|
|
||||||
// these are valid for the C-64 (when no RS232 I/O is performed) but to keep BASIC running fully:
|
|
||||||
free.addAll(listOf(0x04, 0x05, 0x06, 0x0a, 0x0e,
|
|
||||||
0x94, 0x95, 0xa7, 0xa8, 0xa9, 0xaa,
|
|
||||||
0xb5, 0xb6, 0xf7, 0xf8, 0xf9))
|
|
||||||
} else {
|
|
||||||
// don't use the zeropage at all
|
|
||||||
free.clear()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert(SCRATCH_B1 !in free)
|
|
||||||
assert(SCRATCH_REG !in free)
|
|
||||||
assert(SCRATCH_REG_X !in free)
|
|
||||||
assert(SCRATCH_W1 !in free)
|
|
||||||
assert(SCRATCH_W2 !in free)
|
|
||||||
|
|
||||||
for (reserved in options.zpReserved)
|
|
||||||
reserve(reserved)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short) {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val MemorySize = 5
|
|
||||||
|
|
||||||
val zero = Mflpt5(0, 0, 0, 0, 0)
|
|
||||||
fun fromNumber(num: Number): Mflpt5 {
|
|
||||||
// see https://en.wikipedia.org/wiki/Microsoft_Binary_Format
|
|
||||||
// and https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a
|
|
||||||
// and https://en.wikipedia.org/wiki/IEEE_754-1985
|
|
||||||
|
|
||||||
val flt = num.toDouble()
|
|
||||||
if (flt < FLOAT_MAX_NEGATIVE || flt > FLOAT_MAX_POSITIVE)
|
|
||||||
throw CompilerException("floating point number out of 5-byte mflpt range: $this")
|
|
||||||
if (flt == 0.0)
|
|
||||||
return zero
|
|
||||||
|
|
||||||
val sign = if (flt < 0.0) 0x80L else 0x00L
|
|
||||||
var exponent = 128 + 32 // 128 is cbm's bias, 32 is this algo's bias
|
|
||||||
var mantissa = flt.absoluteValue
|
|
||||||
|
|
||||||
// if mantissa is too large, shift right and adjust exponent
|
|
||||||
while (mantissa >= 0x100000000) {
|
|
||||||
mantissa /= 2.0
|
|
||||||
exponent++
|
|
||||||
}
|
|
||||||
// if mantissa is too small, shift left and adjust exponent
|
|
||||||
while (mantissa < 0x80000000) {
|
|
||||||
mantissa *= 2.0
|
|
||||||
exponent--
|
|
||||||
}
|
|
||||||
|
|
||||||
return when {
|
|
||||||
exponent < 0 -> zero // underflow, use zero instead
|
|
||||||
exponent > 255 -> throw CompilerException("floating point overflow: $this")
|
|
||||||
exponent == 0 -> zero
|
|
||||||
else -> {
|
|
||||||
val mantLong = mantissa.toLong()
|
|
||||||
Mflpt5(
|
|
||||||
exponent.toShort(),
|
|
||||||
(mantLong.and(0x7f000000L) ushr 24).or(sign).toShort(),
|
|
||||||
(mantLong.and(0x00ff0000L) ushr 16).toShort(),
|
|
||||||
(mantLong.and(0x0000ff00L) ushr 8).toShort(),
|
|
||||||
(mantLong.and(0x000000ffL)).toShort())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toDouble(): Double {
|
|
||||||
if (this == zero) return 0.0
|
|
||||||
val exp = b0 - 128
|
|
||||||
val sign = (b1.toInt() and 0x80) > 0
|
|
||||||
val number = 0x80000000L.or(b1.toLong() shl 24).or(b2.toLong() shl 16).or(b3.toLong() shl 8).or(b4.toLong())
|
|
||||||
val result = number.toDouble() * (2.0).pow(exp) / 0x100000000
|
|
||||||
return if (sign) -result else result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object Charset {
|
|
||||||
private val normalImg = ImageIO.read(javaClass.getResource("/charset/c64/charset-normal.png"))
|
|
||||||
private val shiftedImg = ImageIO.read(javaClass.getResource("/charset/c64/charset-shifted.png"))
|
|
||||||
|
|
||||||
private fun scanChars(img: BufferedImage): Array<BufferedImage> {
|
|
||||||
|
|
||||||
val transparent = BufferedImage(img.width, img.height, BufferedImage.TYPE_INT_ARGB)
|
|
||||||
transparent.createGraphics().drawImage(img, 0, 0, null)
|
|
||||||
|
|
||||||
val black = Color(0, 0, 0).rgb
|
|
||||||
val nopixel = Color(0, 0, 0, 0).rgb
|
|
||||||
for (y in 0 until transparent.height) {
|
|
||||||
for (x in 0 until transparent.width) {
|
|
||||||
val col = transparent.getRGB(x, y)
|
|
||||||
if (col == black)
|
|
||||||
transparent.setRGB(x, y, nopixel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val numColumns = transparent.width / 8
|
|
||||||
val charImages = (0..255).map {
|
|
||||||
val charX = it % numColumns
|
|
||||||
val charY = it / numColumns
|
|
||||||
transparent.getSubimage(charX * 8, charY * 8, 8, 8)
|
|
||||||
}
|
|
||||||
return charImages.toTypedArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
val normalChars = scanChars(normalImg)
|
|
||||||
val shiftedChars = scanChars(shiftedImg)
|
|
||||||
|
|
||||||
private val coloredNormalChars = mutableMapOf<Short, Array<BufferedImage>>()
|
|
||||||
|
|
||||||
fun getColoredChar(screenCode: Short, color: Short): BufferedImage {
|
|
||||||
val colorIdx = (color % colorPalette.size).toShort()
|
|
||||||
val chars = coloredNormalChars[colorIdx]
|
|
||||||
if (chars != null)
|
|
||||||
return chars[screenCode.toInt()]
|
|
||||||
|
|
||||||
val coloredChars = mutableListOf<BufferedImage>()
|
|
||||||
val transparent = Color(0, 0, 0, 0).rgb
|
|
||||||
val rgb = colorPalette[colorIdx.toInt()].rgb
|
|
||||||
for (c in normalChars) {
|
|
||||||
val colored = c.copy()
|
|
||||||
for (y in 0 until colored.height)
|
|
||||||
for (x in 0 until colored.width) {
|
|
||||||
if (colored.getRGB(x, y) != transparent) {
|
|
||||||
colored.setRGB(x, y, rgb)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
coloredChars.add(colored)
|
|
||||||
}
|
|
||||||
coloredNormalChars[colorIdx] = coloredChars.toTypedArray()
|
|
||||||
return coloredNormalChars.getValue(colorIdx)[screenCode.toInt()]
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun BufferedImage.copy(): BufferedImage {
|
|
||||||
val bcopy = BufferedImage(this.width, this.height, this.type)
|
|
||||||
val g = bcopy.graphics
|
|
||||||
g.drawImage(this, 0, 0, null)
|
|
||||||
g.dispose()
|
|
||||||
return bcopy
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
val colorPalette = listOf( // this is Pepto's Commodore-64 palette http://www.pepto.de/projects/colorvic/
|
|
||||||
Color(0x000000), // 0 = black
|
|
||||||
Color(0xFFFFFF), // 1 = white
|
|
||||||
Color(0x813338), // 2 = red
|
|
||||||
Color(0x75cec8), // 3 = cyan
|
|
||||||
Color(0x8e3c97), // 4 = purple
|
|
||||||
Color(0x56ac4d), // 5 = green
|
|
||||||
Color(0x2e2c9b), // 6 = blue
|
|
||||||
Color(0xedf171), // 7 = yellow
|
|
||||||
Color(0x8e5029), // 8 = orange
|
|
||||||
Color(0x553800), // 9 = brown
|
|
||||||
Color(0xc46c71), // 10 = light red
|
|
||||||
Color(0x4a4a4a), // 11 = dark grey
|
|
||||||
Color(0x7b7b7b), // 12 = medium grey
|
|
||||||
Color(0xa9ff9f), // 13 = light green
|
|
||||||
Color(0x706deb), // 14 = light blue
|
|
||||||
Color(0xb2b2b2) // 15 = light grey
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
@ -1054,11 +1054,16 @@ object Petscii {
|
|||||||
val lookup = if(lowercase) encodingPetsciiLowercase else encodingPetsciiUppercase
|
val lookup = if(lowercase) encodingPetsciiLowercase else encodingPetsciiUppercase
|
||||||
return text.map {
|
return text.map {
|
||||||
val petscii = lookup[it]
|
val petscii = lookup[it]
|
||||||
petscii?.toShort() ?: if(it=='\u0000')
|
petscii?.toShort() ?: when (it) {
|
||||||
0.toShort()
|
'\u0000' -> 0.toShort()
|
||||||
else {
|
in '\u8000'..'\u80ff' -> {
|
||||||
val case = if (lowercase) "lower" else "upper"
|
// special case: take the lower 8 bit hex value directly
|
||||||
throw CharConversionException("no ${case}case Petscii character for '$it'")
|
(it.toInt() - 0x8000).toShort()
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
val case = if (lowercase) "lower" else "upper"
|
||||||
|
throw CharConversionException("no ${case}case Petscii character for '$it' (${it.toShort()})")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1072,11 +1077,16 @@ object Petscii {
|
|||||||
val lookup = if(lowercase) encodingScreencodeLowercase else encodingScreencodeUppercase
|
val lookup = if(lowercase) encodingScreencodeLowercase else encodingScreencodeUppercase
|
||||||
return text.map{
|
return text.map{
|
||||||
val screencode = lookup[it]
|
val screencode = lookup[it]
|
||||||
screencode?.toShort() ?: if(it=='\u0000')
|
screencode?.toShort() ?: when (it) {
|
||||||
0.toShort()
|
'\u0000' -> 0.toShort()
|
||||||
else {
|
in '\u8000'..'\u80ff' -> {
|
||||||
val case = if (lowercase) "lower" else "upper"
|
// special case: take the lower 8 bit hex value directly
|
||||||
throw CharConversionException("no ${case}Screencode character for '$it'")
|
(it.toInt() - 0x8000).toShort()
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
val case = if (lowercase) "lower" else "upper"
|
||||||
|
throw CharConversionException("no ${case}Screencode character for '$it' (${it.toShort()})")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,48 +0,0 @@
|
|||||||
package prog8.compiler.target.c64.codegen
|
|
||||||
|
|
||||||
import prog8.ast.Program
|
|
||||||
import prog8.ast.base.AstException
|
|
||||||
import prog8.ast.base.NameError
|
|
||||||
import prog8.ast.processing.IAstModifyingVisitor
|
|
||||||
import prog8.ast.statements.AnonymousScope
|
|
||||||
import prog8.ast.statements.Statement
|
|
||||||
import prog8.ast.statements.VarDecl
|
|
||||||
|
|
||||||
class AnonymousScopeVarsCleanup(val program: Program): IAstModifyingVisitor {
|
|
||||||
private val checkResult: MutableList<AstException> = mutableListOf()
|
|
||||||
private val varsToMove: MutableMap<AnonymousScope, List<VarDecl>> = mutableMapOf()
|
|
||||||
|
|
||||||
fun result(): List<AstException> {
|
|
||||||
return checkResult
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(program: Program) {
|
|
||||||
varsToMove.clear()
|
|
||||||
super.visit(program)
|
|
||||||
for((scope, decls) in varsToMove) {
|
|
||||||
val sub = scope.definingSubroutine()!!
|
|
||||||
val existingVariables = sub.statements.filterIsInstance<VarDecl>().associateBy { it.name }
|
|
||||||
var conflicts = false
|
|
||||||
decls.forEach {
|
|
||||||
val existing = existingVariables[it.name]
|
|
||||||
if (existing!=null) {
|
|
||||||
checkResult.add(NameError("variable ${it.name} already defined in subroutine ${sub.name} at ${existing.position}", it.position))
|
|
||||||
conflicts = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!conflicts) {
|
|
||||||
decls.forEach { scope.remove(it) }
|
|
||||||
sub.statements.addAll(0, decls)
|
|
||||||
decls.forEach { it.parent = sub }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(scope: AnonymousScope): Statement {
|
|
||||||
val scope2 = super.visit(scope) as AnonymousScope
|
|
||||||
val vardecls = scope2.statements.filterIsInstance<VarDecl>()
|
|
||||||
varsToMove[scope2] = vardecls
|
|
||||||
return scope2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
@ -1,8 +1,5 @@
|
|||||||
package prog8.compiler.target.c64.codegen
|
package prog8.compiler.target.c64.codegen
|
||||||
|
|
||||||
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_HEX
|
|
||||||
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS1_HEX
|
|
||||||
|
|
||||||
|
|
||||||
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
|
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
|
||||||
|
|
||||||
@ -13,43 +10,45 @@ fun optimizeAssembly(lines: MutableList<String>): Int {
|
|||||||
|
|
||||||
var linesByFour = getLinesBy(lines, 4)
|
var linesByFour = getLinesBy(lines, 4)
|
||||||
|
|
||||||
var removeLines = optimizeUselessStackByteWrites(linesByFour)
|
var mods = optimizeUselessStackByteWrites(linesByFour)
|
||||||
if(removeLines.isNotEmpty()) {
|
if(mods.isNotEmpty()) {
|
||||||
for (i in removeLines.reversed())
|
apply(mods, lines)
|
||||||
lines.removeAt(i)
|
|
||||||
linesByFour = getLinesBy(lines, 4)
|
linesByFour = getLinesBy(lines, 4)
|
||||||
numberOfOptimizations++
|
numberOfOptimizations++
|
||||||
}
|
}
|
||||||
|
|
||||||
removeLines = optimizeIncDec(linesByFour)
|
mods = optimizeIncDec(linesByFour)
|
||||||
if(removeLines.isNotEmpty()) {
|
if(mods.isNotEmpty()) {
|
||||||
for (i in removeLines.reversed())
|
apply(mods, lines)
|
||||||
lines.removeAt(i)
|
|
||||||
linesByFour = getLinesBy(lines, 4)
|
linesByFour = getLinesBy(lines, 4)
|
||||||
numberOfOptimizations++
|
numberOfOptimizations++
|
||||||
}
|
}
|
||||||
|
|
||||||
removeLines = optimizeCmpSequence(linesByFour)
|
mods = optimizeCmpSequence(linesByFour)
|
||||||
if(removeLines.isNotEmpty()) {
|
if(mods.isNotEmpty()) {
|
||||||
for (i in removeLines.reversed())
|
apply(mods, lines)
|
||||||
lines.removeAt(i)
|
|
||||||
linesByFour = getLinesBy(lines, 4)
|
linesByFour = getLinesBy(lines, 4)
|
||||||
numberOfOptimizations++
|
numberOfOptimizations++
|
||||||
}
|
}
|
||||||
|
|
||||||
removeLines = optimizeStoreLoadSame(linesByFour)
|
mods = optimizeStoreLoadSame(linesByFour)
|
||||||
if(removeLines.isNotEmpty()) {
|
if(mods.isNotEmpty()) {
|
||||||
for (i in removeLines.reversed())
|
apply(mods, lines)
|
||||||
lines.removeAt(i)
|
linesByFour = getLinesBy(lines, 4)
|
||||||
|
numberOfOptimizations++
|
||||||
|
}
|
||||||
|
|
||||||
|
mods= optimizeJsrRts(linesByFour)
|
||||||
|
if(mods.isNotEmpty()) {
|
||||||
|
apply(mods, lines)
|
||||||
linesByFour = getLinesBy(lines, 4)
|
linesByFour = getLinesBy(lines, 4)
|
||||||
numberOfOptimizations++
|
numberOfOptimizations++
|
||||||
}
|
}
|
||||||
|
|
||||||
var linesByFourteen = getLinesBy(lines, 14)
|
var linesByFourteen = getLinesBy(lines, 14)
|
||||||
removeLines = optimizeSameAssignments(linesByFourteen)
|
mods = optimizeSameAssignments(linesByFourteen)
|
||||||
if(removeLines.isNotEmpty()) {
|
if(mods.isNotEmpty()) {
|
||||||
for (i in removeLines.reversed())
|
apply(mods, lines)
|
||||||
lines.removeAt(i)
|
|
||||||
linesByFourteen = getLinesBy(lines, 14)
|
linesByFourteen = getLinesBy(lines, 14)
|
||||||
numberOfOptimizations++
|
numberOfOptimizations++
|
||||||
}
|
}
|
||||||
@ -59,7 +58,22 @@ fun optimizeAssembly(lines: MutableList<String>): Int {
|
|||||||
return numberOfOptimizations
|
return numberOfOptimizations
|
||||||
}
|
}
|
||||||
|
|
||||||
fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>): List<Int> {
|
private class Modification(val lineIndex: Int, val remove: Boolean, val replacement: String?)
|
||||||
|
|
||||||
|
private fun apply(modifications: List<Modification>, lines: MutableList<String>) {
|
||||||
|
for (modification in modifications.sortedBy { it.lineIndex }.reversed()) {
|
||||||
|
if(modification.remove)
|
||||||
|
lines.removeAt(modification.lineIndex)
|
||||||
|
else
|
||||||
|
lines[modification.lineIndex] = modification.replacement!!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getLinesBy(lines: MutableList<String>, windowSize: Int) =
|
||||||
|
// all lines (that aren't empty or comments) in sliding windows of certain size
|
||||||
|
lines.withIndex().filter { it.value.isNotBlank() && !it.value.trimStart().startsWith(';') }.windowed(windowSize, partialWindows = false)
|
||||||
|
|
||||||
|
private fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||||
// the when statement (on bytes) generates a sequence of:
|
// the when statement (on bytes) generates a sequence of:
|
||||||
// lda $ce01,x
|
// lda $ce01,x
|
||||||
// cmp #$20
|
// cmp #$20
|
||||||
@ -68,42 +82,42 @@ fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>): List<Int
|
|||||||
// cmp #$21
|
// cmp #$21
|
||||||
// beq check_prog8_s73choice_33
|
// beq check_prog8_s73choice_33
|
||||||
// the repeated lda can be removed
|
// the repeated lda can be removed
|
||||||
val removeLines = mutableListOf<Int>()
|
val mods = mutableListOf<Modification>()
|
||||||
for(lines in linesByFour) {
|
for(lines in linesByFour) {
|
||||||
if(lines[0].value.trim()=="lda $ESTACK_LO_PLUS1_HEX,x" &&
|
if(lines[0].value.trim()=="lda P8ESTACK_LO+1,x" &&
|
||||||
lines[1].value.trim().startsWith("cmp ") &&
|
lines[1].value.trim().startsWith("cmp ") &&
|
||||||
lines[2].value.trim().startsWith("beq ") &&
|
lines[2].value.trim().startsWith("beq ") &&
|
||||||
lines[3].value.trim()=="lda $ESTACK_LO_PLUS1_HEX,x") {
|
lines[3].value.trim()=="lda P8ESTACK_LO+1,x") {
|
||||||
removeLines.add(lines[3].index) // remove the second lda
|
mods.add(Modification(lines[3].index, true, null)) // remove the second lda
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return removeLines
|
return mods
|
||||||
}
|
}
|
||||||
|
|
||||||
fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<String>>>): List<Int> {
|
private fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||||
// sta on stack, dex, inx, lda from stack -> eliminate this useless stack byte write
|
// sta on stack, dex, inx, lda from stack -> eliminate this useless stack byte write
|
||||||
// this is a lot harder for word values because the instruction sequence varies.
|
// this is a lot harder for word values because the instruction sequence varies.
|
||||||
val removeLines = mutableListOf<Int>()
|
val mods = mutableListOf<Modification>()
|
||||||
for(lines in linesByFour) {
|
for(lines in linesByFour) {
|
||||||
if(lines[0].value.trim()=="sta $ESTACK_LO_HEX,x" &&
|
if(lines[0].value.trim()=="sta P8ESTACK_LO,x" &&
|
||||||
lines[1].value.trim()=="dex" &&
|
lines[1].value.trim()=="dex" &&
|
||||||
lines[2].value.trim()=="inx" &&
|
lines[2].value.trim()=="inx" &&
|
||||||
lines[3].value.trim()=="lda $ESTACK_LO_HEX,x") {
|
lines[3].value.trim()=="lda P8ESTACK_LO,x") {
|
||||||
removeLines.add(lines[1].index)
|
mods.add(Modification(lines[1].index, true, null))
|
||||||
removeLines.add(lines[2].index)
|
mods.add(Modification(lines[2].index, true, null))
|
||||||
removeLines.add(lines[3].index)
|
mods.add(Modification(lines[3].index, true, null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return removeLines
|
return mods
|
||||||
}
|
}
|
||||||
|
|
||||||
fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>): List<Int> {
|
private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>): List<Modification> {
|
||||||
|
|
||||||
// optimize sequential assignments of the isSameAs value to various targets (bytes, words, floats)
|
// optimize sequential assignments of the isSameAs value to various targets (bytes, words, floats)
|
||||||
// the float one is the one that requires 2*7=14 lines of code to check...
|
// the float one is the one that requires 2*7=14 lines of code to check...
|
||||||
// @todo a better place to do this is in the Compiler instead and transform the Ast, and never even create the inefficient asm in the first place...
|
// @todo a better place to do this is in the Compiler instead and transform the Ast, or the AsmGen, and never even create the inefficient asm in the first place...
|
||||||
|
|
||||||
val removeLines = mutableListOf<Int>()
|
val mods = mutableListOf<Modification>()
|
||||||
for (pair in linesByFourteen) {
|
for (pair in linesByFourteen) {
|
||||||
val first = pair[0].value.trimStart()
|
val first = pair[0].value.trimStart()
|
||||||
val second = pair[1].value.trimStart()
|
val second = pair[1].value.trimStart()
|
||||||
@ -122,8 +136,8 @@ fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>):
|
|||||||
val fourthvalue = sixth.substring(4)
|
val fourthvalue = sixth.substring(4)
|
||||||
if(firstvalue==thirdvalue && secondvalue==fourthvalue) {
|
if(firstvalue==thirdvalue && secondvalue==fourthvalue) {
|
||||||
// lda/ldy sta/sty twice the isSameAs word --> remove second lda/ldy pair (fifth and sixth lines)
|
// lda/ldy sta/sty twice the isSameAs word --> remove second lda/ldy pair (fifth and sixth lines)
|
||||||
removeLines.add(pair[4].index)
|
mods.add(Modification(pair[4].index, true, null))
|
||||||
removeLines.add(pair[5].index)
|
mods.add(Modification(pair[5].index, true, null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,12 +146,13 @@ fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>):
|
|||||||
val secondvalue = third.substring(4)
|
val secondvalue = third.substring(4)
|
||||||
if(firstvalue==secondvalue) {
|
if(firstvalue==secondvalue) {
|
||||||
// lda value / sta ? / lda isSameAs-value / sta ? -> remove second lda (third line)
|
// lda value / sta ? / lda isSameAs-value / sta ? -> remove second lda (third line)
|
||||||
removeLines.add(pair[2].index)
|
mods.add(Modification(pair[2].index, true, null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(first.startsWith("lda") && second.startsWith("ldy") && third.startsWith("sta") && fourth.startsWith("sty") &&
|
if(first.startsWith("lda") && second.startsWith("ldy") && third.startsWith("sta") && fourth.startsWith("sty") &&
|
||||||
fifth.startsWith("lda") && sixth.startsWith("ldy") && seventh.startsWith("jsr c64flt.copy_float")) {
|
fifth.startsWith("lda") && sixth.startsWith("ldy") &&
|
||||||
|
(seventh.startsWith("jsr floats.copy_float") || seventh.startsWith("jsr cx16flt.copy_float"))) {
|
||||||
|
|
||||||
val nineth = pair[8].value.trimStart()
|
val nineth = pair[8].value.trimStart()
|
||||||
val tenth = pair[9].value.trimStart()
|
val tenth = pair[9].value.trimStart()
|
||||||
@ -147,28 +162,26 @@ fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>):
|
|||||||
val fourteenth = pair[13].value.trimStart()
|
val fourteenth = pair[13].value.trimStart()
|
||||||
|
|
||||||
if(eighth.startsWith("lda") && nineth.startsWith("ldy") && tenth.startsWith("sta") && eleventh.startsWith("sty") &&
|
if(eighth.startsWith("lda") && nineth.startsWith("ldy") && tenth.startsWith("sta") && eleventh.startsWith("sty") &&
|
||||||
twelveth.startsWith("lda") && thirteenth.startsWith("ldy") && fourteenth.startsWith("jsr c64flt.copy_float")) {
|
twelveth.startsWith("lda") && thirteenth.startsWith("ldy") &&
|
||||||
|
(fourteenth.startsWith("jsr floats.copy_float") || fourteenth.startsWith("jsr cx16flt.copy_float"))) {
|
||||||
|
|
||||||
if(first.substring(4) == eighth.substring(4) && second.substring(4)==nineth.substring(4)) {
|
if(first.substring(4) == eighth.substring(4) && second.substring(4)==nineth.substring(4)) {
|
||||||
// identical float init
|
// identical float init
|
||||||
removeLines.add(pair[7].index)
|
mods.add(Modification(pair[7].index, true, null))
|
||||||
removeLines.add(pair[8].index)
|
mods.add(Modification(pair[8].index, true, null))
|
||||||
removeLines.add(pair[9].index)
|
mods.add(Modification(pair[9].index, true, null))
|
||||||
removeLines.add(pair[10].index)
|
mods.add(Modification(pair[10].index, true, null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return removeLines
|
return mods
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getLinesBy(lines: MutableList<String>, windowSize: Int) =
|
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||||
// all lines (that aren't empty or comments) in sliding windows of certain size
|
// TODO not sure if this is correct in all situations....:
|
||||||
lines.withIndex().filter { it.value.isNotBlank() && !it.value.trimStart().startsWith(';') }.windowed(windowSize, partialWindows = false)
|
|
||||||
|
|
||||||
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Int> {
|
|
||||||
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can be eliminated
|
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can be eliminated
|
||||||
val removeLines = mutableListOf<Int>()
|
val mods = mutableListOf<Modification>()
|
||||||
for (pair in linesByFour) {
|
for (pair in linesByFour) {
|
||||||
val first = pair[0].value.trimStart()
|
val first = pair[0].value.trimStart()
|
||||||
val second = pair[1].value.trimStart()
|
val second = pair[1].value.trimStart()
|
||||||
@ -183,29 +196,43 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>)
|
|||||||
(first.startsWith("sty ") && second.startsWith("ldy ")) ||
|
(first.startsWith("sty ") && second.startsWith("ldy ")) ||
|
||||||
(first.startsWith("stx ") && second.startsWith("ldx "))
|
(first.startsWith("stx ") && second.startsWith("ldx "))
|
||||||
) {
|
) {
|
||||||
val firstLoc = first.substring(4)
|
val firstLoc = first.substring(4).trimStart()
|
||||||
val secondLoc = second.substring(4)
|
val secondLoc = second.substring(4).trimStart()
|
||||||
if (firstLoc == secondLoc) {
|
if (firstLoc == secondLoc) {
|
||||||
removeLines.add(pair[1].index)
|
mods.add(Modification(pair[1].index, true, null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return removeLines
|
return mods
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun optimizeIncDec(linesByTwo: List<List<IndexedValue<String>>>): List<Int> {
|
private fun optimizeIncDec(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||||
// sometimes, iny+dey / inx+dex / dey+iny / dex+inx sequences are generated, these can be eliminated.
|
// sometimes, iny+dey / inx+dex / dey+iny / dex+inx sequences are generated, these can be eliminated.
|
||||||
val removeLines = mutableListOf<Int>()
|
val mods = mutableListOf<Modification>()
|
||||||
for (pair in linesByTwo) {
|
for (pair in linesByFour) {
|
||||||
val first = pair[0].value
|
val first = pair[0].value
|
||||||
val second = pair[1].value
|
val second = pair[1].value
|
||||||
if ((" iny" in first || "\tiny" in first) && (" dey" in second || "\tdey" in second)
|
if ((" iny" in first || "\tiny" in first) && (" dey" in second || "\tdey" in second)
|
||||||
|| (" inx" in first || "\tinx" in first) && (" dex" in second || "\tdex" in second)
|
|| (" inx" in first || "\tinx" in first) && (" dex" in second || "\tdex" in second)
|
||||||
|| (" dey" in first || "\tdey" in first) && (" iny" in second || "\tiny" in second)
|
|| (" dey" in first || "\tdey" in first) && (" iny" in second || "\tiny" in second)
|
||||||
|| (" dex" in first || "\tdex" in first) && (" inx" in second || "\tinx" in second)) {
|
|| (" dex" in first || "\tdex" in first) && (" inx" in second || "\tinx" in second)) {
|
||||||
removeLines.add(pair[0].index)
|
mods.add(Modification(pair[0].index, true, null))
|
||||||
removeLines.add(pair[1].index)
|
mods.add(Modification(pair[1].index, true, null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return removeLines
|
return mods
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun optimizeJsrRts(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||||
|
// jsr Sub + rts -> jmp Sub
|
||||||
|
val mods = mutableListOf<Modification>()
|
||||||
|
for (pair in linesByFour) {
|
||||||
|
val first = pair[0].value
|
||||||
|
val second = pair[1].value
|
||||||
|
if ((" jsr" in first || "\tjsr" in first ) && (" rts" in second || "\trts" in second)) {
|
||||||
|
mods += Modification(pair[0].index, false, pair[0].value.replace("jsr", "jmp"))
|
||||||
|
mods += Modification(pair[1].index, true, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mods
|
||||||
}
|
}
|
||||||
|
@ -1,745 +0,0 @@
|
|||||||
package prog8.compiler.target.c64.codegen
|
|
||||||
|
|
||||||
import prog8.ast.Program
|
|
||||||
import prog8.ast.base.*
|
|
||||||
import prog8.ast.expressions.*
|
|
||||||
import prog8.ast.statements.AssignTarget
|
|
||||||
import prog8.ast.statements.Assignment
|
|
||||||
import prog8.ast.statements.DirectMemoryWrite
|
|
||||||
import prog8.ast.statements.VarDecl
|
|
||||||
import prog8.compiler.target.c64.MachineDefinition
|
|
||||||
import prog8.compiler.toHex
|
|
||||||
|
|
||||||
internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
|
||||||
|
|
||||||
internal fun translate(assign: Assignment) {
|
|
||||||
if(assign.aug_op!=null)
|
|
||||||
throw AssemblyError("aug-op assignments should have been transformed to normal ones")
|
|
||||||
|
|
||||||
when(assign.value) {
|
|
||||||
is NumericLiteralValue -> {
|
|
||||||
val numVal = assign.value as NumericLiteralValue
|
|
||||||
when(numVal.type) {
|
|
||||||
DataType.UBYTE, DataType.BYTE -> assignFromByteConstant(assign.target, numVal.number.toShort())
|
|
||||||
DataType.UWORD, DataType.WORD -> assignFromWordConstant(assign.target, numVal.number.toInt())
|
|
||||||
DataType.FLOAT -> assignFromFloatConstant(assign.target, numVal.number.toDouble())
|
|
||||||
else -> throw AssemblyError("weird numval type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is RegisterExpr -> {
|
|
||||||
assignFromRegister(assign.target, (assign.value as RegisterExpr).register)
|
|
||||||
}
|
|
||||||
is IdentifierReference -> {
|
|
||||||
val type = assign.target.inferType(program, assign).typeOrElse(DataType.STRUCT)
|
|
||||||
when(type) {
|
|
||||||
DataType.UBYTE, DataType.BYTE -> assignFromByteVariable(assign.target, assign.value as IdentifierReference)
|
|
||||||
DataType.UWORD, DataType.WORD -> assignFromWordVariable(assign.target, assign.value as IdentifierReference)
|
|
||||||
DataType.FLOAT -> assignFromFloatVariable(assign.target, assign.value as IdentifierReference)
|
|
||||||
else -> throw AssemblyError("unsupported assignment target type $type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is AddressOf -> {
|
|
||||||
val identifier = (assign.value as AddressOf).identifier
|
|
||||||
assignFromAddressOf(assign.target, identifier)
|
|
||||||
}
|
|
||||||
is DirectMemoryRead -> {
|
|
||||||
val read = (assign.value as DirectMemoryRead)
|
|
||||||
when(read.addressExpression) {
|
|
||||||
is NumericLiteralValue -> {
|
|
||||||
val address = (read.addressExpression as NumericLiteralValue).number.toInt()
|
|
||||||
assignFromMemoryByte(assign.target, address, null)
|
|
||||||
}
|
|
||||||
is IdentifierReference -> {
|
|
||||||
assignFromMemoryByte(assign.target, null, read.addressExpression as IdentifierReference)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
asmgen.translateExpression(read.addressExpression)
|
|
||||||
TODO("read memory byte from result and put that in ${assign.target}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is PrefixExpression -> {
|
|
||||||
// TODO optimize common cases
|
|
||||||
asmgen.translateExpression(assign.value as PrefixExpression)
|
|
||||||
assignFromEvalResult(assign.target)
|
|
||||||
}
|
|
||||||
is BinaryExpression -> {
|
|
||||||
// TODO optimize common cases
|
|
||||||
asmgen.translateExpression(assign.value as BinaryExpression)
|
|
||||||
assignFromEvalResult(assign.target)
|
|
||||||
}
|
|
||||||
is ArrayIndexedExpression -> {
|
|
||||||
// TODO optimize common cases
|
|
||||||
val arrayExpr = assign.value as ArrayIndexedExpression
|
|
||||||
val arrayDt = arrayExpr.identifier.targetVarDecl(program.namespace)!!.datatype
|
|
||||||
val index = arrayExpr.arrayspec.index
|
|
||||||
if(index is NumericLiteralValue) {
|
|
||||||
// constant array index value
|
|
||||||
val arrayVarName = asmgen.asmIdentifierName(arrayExpr.identifier)
|
|
||||||
val indexValue = index.number.toInt() * ArrayElementTypes.getValue(arrayDt).memorySize()
|
|
||||||
when (arrayDt) {
|
|
||||||
DataType.STR, DataType.STR_S, DataType.ARRAY_UB, DataType.ARRAY_B ->
|
|
||||||
asmgen.out(" lda $arrayVarName+$indexValue | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
|
|
||||||
DataType.ARRAY_UW, DataType.ARRAY_W ->
|
|
||||||
asmgen.out(" lda $arrayVarName+$indexValue | sta ${MachineDefinition.ESTACK_LO_HEX},x | lda $arrayVarName+$indexValue+1 | sta ${MachineDefinition.ESTACK_HI_HEX},x | dex")
|
|
||||||
DataType.ARRAY_F ->
|
|
||||||
asmgen.out(" lda #<$arrayVarName+$indexValue | ldy #>$arrayVarName+$indexValue | jsr c64flt.push_float")
|
|
||||||
else ->
|
|
||||||
throw AssemblyError("weird array type")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
asmgen.translateArrayIndexIntoA(arrayExpr)
|
|
||||||
asmgen.readAndPushArrayvalueWithIndexA(arrayDt, arrayExpr.identifier)
|
|
||||||
}
|
|
||||||
assignFromEvalResult(assign.target)
|
|
||||||
}
|
|
||||||
is TypecastExpression -> {
|
|
||||||
val cast = assign.value as TypecastExpression
|
|
||||||
val sourceType = cast.expression.inferType(program)
|
|
||||||
val targetType = assign.target.inferType(program, assign)
|
|
||||||
if(sourceType.isKnown && targetType.isKnown &&
|
|
||||||
(sourceType.typeOrElse(DataType.STRUCT) in ByteDatatypes && targetType.typeOrElse(DataType.STRUCT) in ByteDatatypes) ||
|
|
||||||
(sourceType.typeOrElse(DataType.STRUCT) in WordDatatypes && targetType.typeOrElse(DataType.STRUCT) in WordDatatypes)) {
|
|
||||||
// no need for a type cast
|
|
||||||
assign.value = cast.expression
|
|
||||||
translate(assign)
|
|
||||||
} else {
|
|
||||||
asmgen.translateExpression(assign.value as TypecastExpression)
|
|
||||||
assignFromEvalResult(assign.target)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is FunctionCall -> {
|
|
||||||
asmgen.translateExpression(assign.value as FunctionCall)
|
|
||||||
assignFromEvalResult(assign.target)
|
|
||||||
}
|
|
||||||
is ArrayLiteralValue, is StringLiteralValue -> TODO("string/array/struct assignment?")
|
|
||||||
is StructLiteralValue -> throw AssemblyError("struct literal value assignment should have been flattened")
|
|
||||||
is RangeExpr -> throw AssemblyError("range expression should have been changed into array values")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun assignFromEvalResult(target: AssignTarget) {
|
|
||||||
val targetIdent = target.identifier
|
|
||||||
when {
|
|
||||||
target.register!=null -> {
|
|
||||||
if(target.register== Register.X)
|
|
||||||
throw AssemblyError("can't pop into X register - use variable instead")
|
|
||||||
asmgen.out(" inx | ld${target.register.name.toLowerCase()} ${MachineDefinition.ESTACK_LO_HEX},x ")
|
|
||||||
}
|
|
||||||
targetIdent!=null -> {
|
|
||||||
val targetName = asmgen.asmIdentifierName(targetIdent)
|
|
||||||
val targetDt = targetIdent.inferType(program).typeOrElse(DataType.STRUCT)
|
|
||||||
when(targetDt) {
|
|
||||||
DataType.UBYTE, DataType.BYTE -> {
|
|
||||||
asmgen.out(" inx | lda ${MachineDefinition.ESTACK_LO_HEX},x | sta $targetName")
|
|
||||||
}
|
|
||||||
DataType.UWORD, DataType.WORD -> {
|
|
||||||
asmgen.out("""
|
|
||||||
inx
|
|
||||||
lda ${MachineDefinition.ESTACK_LO_HEX},x
|
|
||||||
sta $targetName
|
|
||||||
lda ${MachineDefinition.ESTACK_HI_HEX},x
|
|
||||||
sta $targetName+1
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
DataType.FLOAT -> {
|
|
||||||
asmgen.out("""
|
|
||||||
lda #<$targetName
|
|
||||||
ldy #>$targetName
|
|
||||||
jsr c64flt.pop_float
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird target variable type $targetDt")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
target.memoryAddress!=null -> {
|
|
||||||
asmgen.out(" inx | ldy ${MachineDefinition.ESTACK_LO_HEX},x")
|
|
||||||
storeRegisterInMemoryAddress(Register.Y, target.memoryAddress)
|
|
||||||
}
|
|
||||||
target.arrayindexed!=null -> {
|
|
||||||
val arrayDt = target.arrayindexed!!.identifier.targetVarDecl(program.namespace)!!.datatype
|
|
||||||
val arrayVarName = asmgen.asmIdentifierName(target.arrayindexed!!.identifier)
|
|
||||||
asmgen.translateExpression(target.arrayindexed!!.arrayspec.index)
|
|
||||||
asmgen.out(" inx | lda ${MachineDefinition.ESTACK_LO_HEX},x")
|
|
||||||
popAndWriteArrayvalueWithIndexA(arrayDt, arrayVarName)
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird assignment target $target")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun assignFromAddressOf(target: AssignTarget, name: IdentifierReference) {
|
|
||||||
val targetIdent = target.identifier
|
|
||||||
val targetArrayIdx = target.arrayindexed
|
|
||||||
val struct = name.memberOfStruct(program.namespace)
|
|
||||||
val sourceName = if(struct!=null) {
|
|
||||||
// take the address of the first struct member instead
|
|
||||||
val decl = name.targetVarDecl(program.namespace)!!
|
|
||||||
val firstStructMember = struct.nameOfFirstMember()
|
|
||||||
// find the flattened var that belongs to this first struct member
|
|
||||||
val firstVarName = listOf(decl.name, firstStructMember)
|
|
||||||
val firstVar = name.definingScope().lookup(firstVarName, name) as VarDecl
|
|
||||||
firstVar.name
|
|
||||||
} else {
|
|
||||||
asmgen.fixNameSymbols(name.nameInSource.joinToString ("."))
|
|
||||||
}
|
|
||||||
|
|
||||||
when {
|
|
||||||
targetIdent!=null -> {
|
|
||||||
val targetName = asmgen.asmIdentifierName(targetIdent)
|
|
||||||
asmgen.out("""
|
|
||||||
lda #<$sourceName
|
|
||||||
ldy #>$sourceName
|
|
||||||
sta $targetName
|
|
||||||
sty $targetName+1
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
target.memoryAddress!=null -> {
|
|
||||||
TODO("assign address $sourceName to memory word $target")
|
|
||||||
}
|
|
||||||
targetArrayIdx!=null -> {
|
|
||||||
val index = targetArrayIdx.arrayspec.index
|
|
||||||
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
|
||||||
TODO("assign address $sourceName to array $targetName [ $index ]")
|
|
||||||
}
|
|
||||||
else -> TODO("assign address $sourceName to $target")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun assignFromWordVariable(target: AssignTarget, variable: IdentifierReference) {
|
|
||||||
val sourceName = asmgen.asmIdentifierName(variable)
|
|
||||||
val targetIdent = target.identifier
|
|
||||||
val targetArrayIdx = target.arrayindexed
|
|
||||||
when {
|
|
||||||
targetIdent!=null -> {
|
|
||||||
val targetName = asmgen.asmIdentifierName(targetIdent)
|
|
||||||
asmgen.out("""
|
|
||||||
lda $sourceName
|
|
||||||
ldy $sourceName+1
|
|
||||||
sta $targetName
|
|
||||||
sty $targetName+1
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
target.memoryAddress!=null -> {
|
|
||||||
TODO("assign wordvar $sourceName to memory ${target.memoryAddress}")
|
|
||||||
}
|
|
||||||
targetArrayIdx!=null -> {
|
|
||||||
val index = targetArrayIdx.arrayspec.index
|
|
||||||
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
|
||||||
asmgen.out(" lda $sourceName | sta ${MachineDefinition.ESTACK_LO_HEX},x | lda $sourceName+1 | sta ${MachineDefinition.ESTACK_HI_HEX},x | dex")
|
|
||||||
asmgen.translateExpression(index)
|
|
||||||
asmgen.out(" inx | lda ${MachineDefinition.ESTACK_LO_HEX},x")
|
|
||||||
val arrayDt = targetArrayIdx.identifier.inferType(program).typeOrElse(DataType.STRUCT)
|
|
||||||
popAndWriteArrayvalueWithIndexA(arrayDt, targetName)
|
|
||||||
}
|
|
||||||
else -> TODO("assign wordvar to $target")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun assignFromFloatVariable(target: AssignTarget, variable: IdentifierReference) {
|
|
||||||
val sourceName = asmgen.asmIdentifierName(variable)
|
|
||||||
val targetIdent = target.identifier
|
|
||||||
val targetArrayIdx = target.arrayindexed
|
|
||||||
when {
|
|
||||||
targetIdent!=null -> {
|
|
||||||
val targetName = asmgen.asmIdentifierName(targetIdent)
|
|
||||||
asmgen.out("""
|
|
||||||
lda $sourceName
|
|
||||||
sta $targetName
|
|
||||||
lda $sourceName+1
|
|
||||||
sta $targetName+1
|
|
||||||
lda $sourceName+2
|
|
||||||
sta $targetName+2
|
|
||||||
lda $sourceName+3
|
|
||||||
sta $targetName+3
|
|
||||||
lda $sourceName+4
|
|
||||||
sta $targetName+4
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
targetArrayIdx!=null -> {
|
|
||||||
val index = targetArrayIdx.arrayspec.index
|
|
||||||
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
|
||||||
asmgen.out(" lda #<$sourceName | ldy #>$sourceName | jsr c64flt.push_float")
|
|
||||||
asmgen.translateExpression(index)
|
|
||||||
asmgen.out(" lda #<$targetName | ldy #>$targetName | jsr c64flt.pop_float_to_indexed_var")
|
|
||||||
}
|
|
||||||
else -> TODO("assign floatvar to $target")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun assignFromByteVariable(target: AssignTarget, variable: IdentifierReference) {
|
|
||||||
val sourceName = asmgen.asmIdentifierName(variable)
|
|
||||||
val targetIdent = target.identifier
|
|
||||||
val targetArrayIdx = target.arrayindexed
|
|
||||||
when {
|
|
||||||
target.register!=null -> {
|
|
||||||
asmgen.out(" ld${target.register.name.toLowerCase()} $sourceName")
|
|
||||||
}
|
|
||||||
targetIdent!=null -> {
|
|
||||||
val targetName = asmgen.asmIdentifierName(targetIdent)
|
|
||||||
asmgen.out("""
|
|
||||||
lda $sourceName
|
|
||||||
sta $targetName
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
targetArrayIdx!=null -> {
|
|
||||||
val index = targetArrayIdx.arrayspec.index
|
|
||||||
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
|
||||||
val arrayDt = targetArrayIdx.identifier.inferType(program).typeOrElse(DataType.STRUCT)
|
|
||||||
asmgen.out(" lda $sourceName | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
|
|
||||||
asmgen.translateExpression(index)
|
|
||||||
asmgen.out(" inx | lda ${MachineDefinition.ESTACK_LO_HEX},x")
|
|
||||||
popAndWriteArrayvalueWithIndexA(arrayDt, targetName)
|
|
||||||
}
|
|
||||||
target.memoryAddress != null -> {
|
|
||||||
val addressExpr = target.memoryAddress.addressExpression
|
|
||||||
val addressLv = addressExpr as? NumericLiteralValue
|
|
||||||
when {
|
|
||||||
addressLv != null -> asmgen.out(" lda $sourceName | sta ${addressLv.number.toHex()}")
|
|
||||||
addressExpr is IdentifierReference -> {
|
|
||||||
val targetName = asmgen.asmIdentifierName(addressExpr)
|
|
||||||
asmgen.out(" lda $sourceName | sta $targetName")
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
asmgen.translateExpression(addressExpr)
|
|
||||||
asmgen.out("""
|
|
||||||
inx
|
|
||||||
lda ${MachineDefinition.ESTACK_LO_HEX},x
|
|
||||||
ldy ${MachineDefinition.ESTACK_HI_HEX},x
|
|
||||||
sta (+) +1
|
|
||||||
sty (+) +2
|
|
||||||
lda $sourceName
|
|
||||||
+ sta ${65535.toHex()} ; modified
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> TODO("assign bytevar to $target")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun assignFromRegister(target: AssignTarget, register: Register) {
|
|
||||||
val targetIdent = target.identifier
|
|
||||||
val targetArrayIdx = target.arrayindexed
|
|
||||||
when {
|
|
||||||
targetIdent!=null -> {
|
|
||||||
val targetName = asmgen.asmIdentifierName(targetIdent)
|
|
||||||
asmgen.out(" st${register.name.toLowerCase()} $targetName")
|
|
||||||
}
|
|
||||||
target.register!=null -> {
|
|
||||||
when(register) {
|
|
||||||
Register.A -> when(target.register) {
|
|
||||||
Register.A -> {}
|
|
||||||
Register.X -> asmgen.out(" tax")
|
|
||||||
Register.Y -> asmgen.out(" tay")
|
|
||||||
}
|
|
||||||
Register.X -> when(target.register) {
|
|
||||||
Register.A -> asmgen.out(" txa")
|
|
||||||
Register.X -> {}
|
|
||||||
Register.Y -> asmgen.out(" txy")
|
|
||||||
}
|
|
||||||
Register.Y -> when(target.register) {
|
|
||||||
Register.A -> asmgen.out(" tya")
|
|
||||||
Register.X -> asmgen.out(" tyx")
|
|
||||||
Register.Y -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
target.memoryAddress!=null -> {
|
|
||||||
storeRegisterInMemoryAddress(register, target.memoryAddress)
|
|
||||||
}
|
|
||||||
targetArrayIdx!=null -> {
|
|
||||||
val index = targetArrayIdx.arrayspec.index
|
|
||||||
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
|
||||||
when (index) {
|
|
||||||
is NumericLiteralValue -> {
|
|
||||||
val memindex = index.number.toInt()
|
|
||||||
when(register) {
|
|
||||||
Register.A -> asmgen.out(" sta $targetName+$memindex")
|
|
||||||
Register.X -> asmgen.out(" stx $targetName+$memindex")
|
|
||||||
Register.Y -> asmgen.out(" sty $targetName+$memindex")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is RegisterExpr -> {
|
|
||||||
when(register) {
|
|
||||||
Register.A -> asmgen.out(" sta ${MachineDefinition.C64Zeropage.SCRATCH_B1}")
|
|
||||||
Register.X -> asmgen.out(" stx ${MachineDefinition.C64Zeropage.SCRATCH_B1}")
|
|
||||||
Register.Y -> asmgen.out(" sty ${MachineDefinition.C64Zeropage.SCRATCH_B1}")
|
|
||||||
}
|
|
||||||
when(index.register) {
|
|
||||||
Register.A -> {}
|
|
||||||
Register.X -> asmgen.out(" txa")
|
|
||||||
Register.Y -> asmgen.out(" tya")
|
|
||||||
}
|
|
||||||
asmgen.out("""
|
|
||||||
tay
|
|
||||||
lda ${MachineDefinition.C64Zeropage.SCRATCH_B1}
|
|
||||||
sta $targetName,y
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
is IdentifierReference -> {
|
|
||||||
when(register) {
|
|
||||||
Register.A -> asmgen.out(" sta ${MachineDefinition.C64Zeropage.SCRATCH_B1}")
|
|
||||||
Register.X -> asmgen.out(" stx ${MachineDefinition.C64Zeropage.SCRATCH_B1}")
|
|
||||||
Register.Y -> asmgen.out(" sty ${MachineDefinition.C64Zeropage.SCRATCH_B1}")
|
|
||||||
}
|
|
||||||
asmgen.out("""
|
|
||||||
lda ${asmgen.asmIdentifierName(index)}
|
|
||||||
tay
|
|
||||||
lda ${MachineDefinition.C64Zeropage.SCRATCH_B1}
|
|
||||||
sta $targetName,y
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
asmgen.saveRegister(register)
|
|
||||||
asmgen.translateExpression(index)
|
|
||||||
asmgen.restoreRegister(register)
|
|
||||||
when(register) {
|
|
||||||
Register.A -> asmgen.out(" sta ${MachineDefinition.C64Zeropage.SCRATCH_B1}")
|
|
||||||
Register.X -> asmgen.out(" stx ${MachineDefinition.C64Zeropage.SCRATCH_B1}")
|
|
||||||
Register.Y -> asmgen.out(" sty ${MachineDefinition.C64Zeropage.SCRATCH_B1}")
|
|
||||||
}
|
|
||||||
asmgen.out("""
|
|
||||||
inx
|
|
||||||
lda ${MachineDefinition.ESTACK_LO_HEX},x
|
|
||||||
tay
|
|
||||||
lda ${MachineDefinition.C64Zeropage.SCRATCH_B1}
|
|
||||||
sta $targetName,y
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> TODO("assign register $register to $target")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun storeRegisterInMemoryAddress(register: Register, memoryAddress: DirectMemoryWrite) {
|
|
||||||
val addressExpr = memoryAddress.addressExpression
|
|
||||||
val addressLv = addressExpr as? NumericLiteralValue
|
|
||||||
val registerName = register.name.toLowerCase()
|
|
||||||
when {
|
|
||||||
addressLv != null -> asmgen.out(" st$registerName ${addressLv.number.toHex()}")
|
|
||||||
addressExpr is IdentifierReference -> {
|
|
||||||
val targetName = asmgen.asmIdentifierName(addressExpr)
|
|
||||||
when(register) {
|
|
||||||
Register.A -> asmgen.out("""
|
|
||||||
ldy $targetName
|
|
||||||
sty ${MachineDefinition.C64Zeropage.SCRATCH_W1}
|
|
||||||
ldy $targetName+1
|
|
||||||
sty ${MachineDefinition.C64Zeropage.SCRATCH_W1+1}
|
|
||||||
ldy #0
|
|
||||||
sta (${MachineDefinition.C64Zeropage.SCRATCH_W1}),y
|
|
||||||
""")
|
|
||||||
Register.X -> asmgen.out("""
|
|
||||||
txa
|
|
||||||
ldy $targetName
|
|
||||||
sty ${MachineDefinition.C64Zeropage.SCRATCH_W1}
|
|
||||||
ldy $targetName+1
|
|
||||||
sty ${MachineDefinition.C64Zeropage.SCRATCH_W1+1}
|
|
||||||
ldy #0
|
|
||||||
sta (${MachineDefinition.C64Zeropage.SCRATCH_W1}),y
|
|
||||||
""")
|
|
||||||
Register.Y -> asmgen.out("""
|
|
||||||
tya
|
|
||||||
ldy $targetName
|
|
||||||
sty ${MachineDefinition.C64Zeropage.SCRATCH_W1}
|
|
||||||
ldy $targetName+1
|
|
||||||
sty ${MachineDefinition.C64Zeropage.SCRATCH_W1+1}
|
|
||||||
ldy #0
|
|
||||||
sta (${MachineDefinition.C64Zeropage.SCRATCH_W1}),y
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
asmgen.saveRegister(register)
|
|
||||||
asmgen.translateExpression(addressExpr)
|
|
||||||
asmgen.restoreRegister(register)
|
|
||||||
when (register) {
|
|
||||||
Register.A -> asmgen.out(" tay")
|
|
||||||
Register.X -> throw AssemblyError("can't use X register here")
|
|
||||||
Register.Y -> {}
|
|
||||||
}
|
|
||||||
asmgen.out("""
|
|
||||||
inx
|
|
||||||
lda ${MachineDefinition.ESTACK_LO_HEX},x
|
|
||||||
sta (+) +1
|
|
||||||
lda ${MachineDefinition.ESTACK_HI_HEX},x
|
|
||||||
sta (+) +2
|
|
||||||
+ sty ${65535.toHex()} ; modified
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun assignFromWordConstant(target: AssignTarget, word: Int) {
|
|
||||||
val targetIdent = target.identifier
|
|
||||||
val targetArrayIdx = target.arrayindexed
|
|
||||||
when {
|
|
||||||
targetIdent!=null -> {
|
|
||||||
val targetName = asmgen.asmIdentifierName(targetIdent)
|
|
||||||
if(word ushr 8 == word and 255) {
|
|
||||||
// lsb=msb
|
|
||||||
asmgen.out("""
|
|
||||||
lda #${(word and 255).toHex()}
|
|
||||||
sta $targetName
|
|
||||||
sta $targetName+1
|
|
||||||
""")
|
|
||||||
} else {
|
|
||||||
asmgen.out("""
|
|
||||||
lda #<${word.toHex()}
|
|
||||||
ldy #>${word.toHex()}
|
|
||||||
sta $targetName
|
|
||||||
sty $targetName+1
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
target.memoryAddress!=null -> {
|
|
||||||
TODO("assign word $word to memory ${target.memoryAddress}")
|
|
||||||
}
|
|
||||||
targetArrayIdx!=null -> {
|
|
||||||
val index = targetArrayIdx.arrayspec.index
|
|
||||||
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
|
||||||
// TODO optimize common cases
|
|
||||||
asmgen.translateExpression(index)
|
|
||||||
asmgen.out("""
|
|
||||||
inx
|
|
||||||
lda ${MachineDefinition.ESTACK_LO_HEX},x
|
|
||||||
asl a
|
|
||||||
tay
|
|
||||||
lda #<${word.toHex()}
|
|
||||||
sta $targetName,y
|
|
||||||
lda #>${word.toHex()}
|
|
||||||
sta $targetName+1,y
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
else -> TODO("assign word $word to $target")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun assignFromByteConstant(target: AssignTarget, byte: Short) {
|
|
||||||
val targetIdent = target.identifier
|
|
||||||
val targetArrayIdx = target.arrayindexed
|
|
||||||
when {
|
|
||||||
target.register!=null -> {
|
|
||||||
asmgen.out(" ld${target.register.name.toLowerCase()} #${byte.toHex()}")
|
|
||||||
}
|
|
||||||
targetIdent!=null -> {
|
|
||||||
val targetName = asmgen.asmIdentifierName(targetIdent)
|
|
||||||
asmgen.out(" lda #${byte.toHex()} | sta $targetName ")
|
|
||||||
}
|
|
||||||
target.memoryAddress!=null -> {
|
|
||||||
asmgen.out(" ldy #${byte.toHex()}")
|
|
||||||
storeRegisterInMemoryAddress(Register.Y, target.memoryAddress)
|
|
||||||
}
|
|
||||||
targetArrayIdx!=null -> {
|
|
||||||
val index = targetArrayIdx.arrayspec.index
|
|
||||||
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
|
||||||
// TODO optimize common cases
|
|
||||||
asmgen.translateExpression(index)
|
|
||||||
asmgen.out("""
|
|
||||||
inx
|
|
||||||
ldy ${MachineDefinition.ESTACK_LO_HEX},x
|
|
||||||
lda #${byte.toHex()}
|
|
||||||
sta $targetName,y
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
else -> TODO("assign byte $byte to $target")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun assignFromFloatConstant(target: AssignTarget, float: Double) {
|
|
||||||
val targetIdent = target.identifier
|
|
||||||
val targetArrayIdx = target.arrayindexed
|
|
||||||
if(float==0.0) {
|
|
||||||
// optimized case for float zero
|
|
||||||
when {
|
|
||||||
targetIdent != null -> {
|
|
||||||
val targetName = asmgen.asmIdentifierName(targetIdent)
|
|
||||||
asmgen.out("""
|
|
||||||
lda #0
|
|
||||||
sta $targetName
|
|
||||||
sta $targetName+1
|
|
||||||
sta $targetName+2
|
|
||||||
sta $targetName+3
|
|
||||||
sta $targetName+4
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
targetArrayIdx!=null -> {
|
|
||||||
val index = targetArrayIdx.arrayspec.index
|
|
||||||
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
|
||||||
if(index is NumericLiteralValue) {
|
|
||||||
val indexValue = index.number.toInt() * MachineDefinition.Mflpt5.MemorySize
|
|
||||||
asmgen.out("""
|
|
||||||
lda #0
|
|
||||||
sta $targetName+$indexValue
|
|
||||||
sta $targetName+$indexValue+1
|
|
||||||
sta $targetName+$indexValue+2
|
|
||||||
sta $targetName+$indexValue+3
|
|
||||||
sta $targetName+$indexValue+4
|
|
||||||
""")
|
|
||||||
} else {
|
|
||||||
asmgen.translateExpression(index)
|
|
||||||
asmgen.out("""
|
|
||||||
inx
|
|
||||||
lda ${MachineDefinition.ESTACK_LO_HEX},x
|
|
||||||
asl a
|
|
||||||
asl a
|
|
||||||
clc
|
|
||||||
adc ${MachineDefinition.ESTACK_LO_HEX},x
|
|
||||||
tay
|
|
||||||
lda #0
|
|
||||||
sta $targetName,y
|
|
||||||
sta $targetName+1,y
|
|
||||||
sta $targetName+2,y
|
|
||||||
sta $targetName+3,y
|
|
||||||
sta $targetName+4,y
|
|
||||||
""") // TODO use a subroutine for this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> TODO("assign float 0.0 to $target")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// non-zero value
|
|
||||||
val constFloat = asmgen.getFloatConst(float)
|
|
||||||
when {
|
|
||||||
targetIdent != null -> {
|
|
||||||
val targetName = asmgen.asmIdentifierName(targetIdent)
|
|
||||||
asmgen.out("""
|
|
||||||
lda $constFloat
|
|
||||||
sta $targetName
|
|
||||||
lda $constFloat+1
|
|
||||||
sta $targetName+1
|
|
||||||
lda $constFloat+2
|
|
||||||
sta $targetName+2
|
|
||||||
lda $constFloat+3
|
|
||||||
sta $targetName+3
|
|
||||||
lda $constFloat+4
|
|
||||||
sta $targetName+4
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
targetArrayIdx!=null -> {
|
|
||||||
val index = targetArrayIdx.arrayspec.index
|
|
||||||
val arrayVarName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
|
||||||
if(index is NumericLiteralValue) {
|
|
||||||
val indexValue = index.number.toInt() * MachineDefinition.Mflpt5.MemorySize
|
|
||||||
asmgen.out("""
|
|
||||||
lda $constFloat
|
|
||||||
sta $arrayVarName+$indexValue
|
|
||||||
lda $constFloat+1
|
|
||||||
sta $arrayVarName+$indexValue+1
|
|
||||||
lda $constFloat+2
|
|
||||||
sta $arrayVarName+$indexValue+2
|
|
||||||
lda $constFloat+3
|
|
||||||
sta $arrayVarName+$indexValue+3
|
|
||||||
lda $constFloat+4
|
|
||||||
sta $arrayVarName+$indexValue+4
|
|
||||||
""")
|
|
||||||
} else {
|
|
||||||
asmgen.translateArrayIndexIntoA(targetArrayIdx)
|
|
||||||
asmgen.out("""
|
|
||||||
sta ${MachineDefinition.C64Zeropage.SCRATCH_REG}
|
|
||||||
asl a
|
|
||||||
asl a
|
|
||||||
clc
|
|
||||||
adc ${MachineDefinition.C64Zeropage.SCRATCH_REG}
|
|
||||||
tay
|
|
||||||
lda $constFloat
|
|
||||||
sta $arrayVarName,y
|
|
||||||
lda $constFloat+1
|
|
||||||
sta $arrayVarName+1,y
|
|
||||||
lda $constFloat+2
|
|
||||||
sta $arrayVarName+2,y
|
|
||||||
lda $constFloat+3
|
|
||||||
sta $arrayVarName+3,y
|
|
||||||
lda $constFloat+4
|
|
||||||
sta $arrayVarName+4,y
|
|
||||||
""") // TODO use a subroutine for this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> TODO("assign float $float to $target")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun assignFromMemoryByte(target: AssignTarget, address: Int?, identifier: IdentifierReference?) {
|
|
||||||
val targetIdent = target.identifier
|
|
||||||
val targetArrayIdx = target.arrayindexed
|
|
||||||
if(address!=null) {
|
|
||||||
when {
|
|
||||||
target.register!=null -> {
|
|
||||||
asmgen.out(" ld${target.register.name.toLowerCase()} ${address.toHex()}")
|
|
||||||
}
|
|
||||||
targetIdent!=null -> {
|
|
||||||
val targetName = asmgen.asmIdentifierName(targetIdent)
|
|
||||||
asmgen.out("""
|
|
||||||
lda ${address.toHex()}
|
|
||||||
sta $targetName
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
target.memoryAddress!=null -> {
|
|
||||||
asmgen.out(" ldy ${address.toHex()}")
|
|
||||||
storeRegisterInMemoryAddress(Register.Y, target.memoryAddress)
|
|
||||||
}
|
|
||||||
targetArrayIdx!=null -> {
|
|
||||||
val index = targetArrayIdx.arrayspec.index
|
|
||||||
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
|
||||||
TODO("assign memory byte at $address to array $targetName [ $index ]")
|
|
||||||
}
|
|
||||||
else -> TODO("assign memory byte $target")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(identifier!=null) {
|
|
||||||
val sourceName = asmgen.asmIdentifierName(identifier)
|
|
||||||
when {
|
|
||||||
target.register!=null -> {
|
|
||||||
asmgen.out("""
|
|
||||||
ldy #0
|
|
||||||
lda ($sourceName),y
|
|
||||||
""")
|
|
||||||
when(target.register){
|
|
||||||
Register.A -> {}
|
|
||||||
Register.X -> asmgen.out(" tax")
|
|
||||||
Register.Y -> asmgen.out(" tay")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
targetIdent!=null -> {
|
|
||||||
val targetName = asmgen.asmIdentifierName(targetIdent)
|
|
||||||
asmgen.out("""
|
|
||||||
ldy #0
|
|
||||||
lda ($sourceName),y
|
|
||||||
sta $targetName
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
target.memoryAddress!=null -> {
|
|
||||||
asmgen.out(" ldy $sourceName")
|
|
||||||
storeRegisterInMemoryAddress(Register.Y, target.memoryAddress)
|
|
||||||
}
|
|
||||||
targetArrayIdx!=null -> {
|
|
||||||
val index = targetArrayIdx.arrayspec.index
|
|
||||||
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
|
||||||
TODO("assign memory byte $sourceName to array $targetName [ $index ]")
|
|
||||||
}
|
|
||||||
else -> TODO("assign memory byte $target")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun popAndWriteArrayvalueWithIndexA(arrayDt: DataType, variablename: String) {
|
|
||||||
when (arrayDt) {
|
|
||||||
DataType.STR, DataType.STR_S, DataType.ARRAY_UB, DataType.ARRAY_B ->
|
|
||||||
asmgen.out(" tay | inx | lda ${MachineDefinition.ESTACK_LO_HEX},x | sta $variablename,y")
|
|
||||||
DataType.ARRAY_UW, DataType.ARRAY_W ->
|
|
||||||
asmgen.out(" asl a | tay | inx | lda ${MachineDefinition.ESTACK_LO_HEX},x | sta $variablename,y | lda ${MachineDefinition.ESTACK_HI_HEX},x | sta $variablename+1,y")
|
|
||||||
DataType.ARRAY_F ->
|
|
||||||
// index * 5 is done in the subroutine that's called
|
|
||||||
asmgen.out("""
|
|
||||||
sta ${MachineDefinition.ESTACK_LO_HEX},x
|
|
||||||
dex
|
|
||||||
lda #<$variablename
|
|
||||||
ldy #>$variablename
|
|
||||||
jsr c64flt.pop_float_to_indexed_var
|
|
||||||
""")
|
|
||||||
else ->
|
|
||||||
throw AssemblyError("weird array type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,233 +1,277 @@
|
|||||||
package prog8.compiler.target.c64.codegen
|
package prog8.compiler.target.c64.codegen
|
||||||
|
|
||||||
import prog8.ast.IFunctionCall
|
import prog8.ast.IFunctionCall
|
||||||
|
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.AssignTarget
|
import prog8.ast.statements.RegisterOrStatusflag
|
||||||
import prog8.ast.statements.Subroutine
|
import prog8.ast.statements.Subroutine
|
||||||
import prog8.ast.statements.SubroutineParameter
|
import prog8.ast.statements.SubroutineParameter
|
||||||
import prog8.compiler.target.c64.MachineDefinition
|
import prog8.compiler.AssemblyError
|
||||||
import prog8.compiler.toHex
|
import prog8.compiler.target.c64.codegen.assignment.*
|
||||||
|
|
||||||
|
|
||||||
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||||
|
|
||||||
internal fun translateFunctionCall(stmt: IFunctionCall) {
|
internal fun translateFunctionCallStatement(stmt: IFunctionCall) {
|
||||||
|
val sub = stmt.target.targetSubroutine(program.namespace)!!
|
||||||
|
val preserveStatusRegisterAfterCall = sub.asmReturnvaluesRegisters.any {it.statusflag!=null}
|
||||||
|
translateFunctionCall(stmt, preserveStatusRegisterAfterCall)
|
||||||
|
// functioncalls no longer return results on the stack, so simply ignore the results in the registers
|
||||||
|
if(preserveStatusRegisterAfterCall)
|
||||||
|
asmgen.out(" plp\t; restore status flags from call")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal fun translateFunctionCall(stmt: IFunctionCall, preserveStatusRegisterAfterCall: Boolean) {
|
||||||
// output the code to setup the parameters and perform the actual call
|
// output the code to setup the parameters and perform the actual call
|
||||||
// does NOT output the code to deal with the result values!
|
// does NOT output the code to deal with the result values!
|
||||||
val sub = stmt.target.targetSubroutine(program.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
|
val sub = stmt.target.targetSubroutine(program.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
|
||||||
if(Register.X in sub.asmClobbers)
|
val saveX = CpuRegister.X in sub.asmClobbers || sub.regXasResult() || sub.regXasParam()
|
||||||
asmgen.out(" stx c64.SCRATCH_ZPREGX") // we only save X for now (required! is the eval stack pointer), screw A and Y...
|
if(saveX)
|
||||||
|
asmgen.saveRegister(CpuRegister.X, preserveStatusRegisterAfterCall, (stmt as Node).definingSubroutine()!!)
|
||||||
|
|
||||||
val subName = asmgen.asmIdentifierName(stmt.target)
|
val subName = asmgen.asmSymbolName(stmt.target)
|
||||||
if(stmt.arglist.isNotEmpty()) {
|
if(stmt.args.isNotEmpty()) {
|
||||||
for(arg in sub.parameters.withIndex().zip(stmt.arglist)) {
|
if(sub.asmParameterRegisters.isEmpty()) {
|
||||||
translateFuncArguments(arg.first, arg.second, sub)
|
// via variables
|
||||||
|
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
|
||||||
|
argumentViaVariable(sub, arg.first, arg.second)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// via registers
|
||||||
|
if(sub.parameters.size==1) {
|
||||||
|
// just a single parameter, no risk of clobbering registers
|
||||||
|
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single()), stmt.args[0])
|
||||||
|
} else {
|
||||||
|
// multiple register arguments, risk of register clobbering.
|
||||||
|
// evaluate arguments onto the stack, and load the registers from the evaluated values on the stack.
|
||||||
|
when {
|
||||||
|
stmt.args.all {it is AddressOf ||
|
||||||
|
it is NumericLiteralValue ||
|
||||||
|
it is StringLiteralValue ||
|
||||||
|
it is ArrayLiteralValue ||
|
||||||
|
it is IdentifierReference} -> {
|
||||||
|
// no risk of clobbering for these simple argument types. Optimize the register loading.
|
||||||
|
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
|
||||||
|
argumentViaRegister(sub, arg.first, arg.second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// Risk of clobbering due to complex expression args. Work via the stack.
|
||||||
|
registerArgsViaStackEvaluation(stmt, sub)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
asmgen.out(" jsr $subName")
|
asmgen.out(" jsr $subName")
|
||||||
|
|
||||||
if(Register.X in sub.asmClobbers)
|
if(preserveStatusRegisterAfterCall) {
|
||||||
asmgen.out(" ldx c64.SCRATCH_ZPREGX") // restore X again
|
asmgen.out(" php\t; save status flags from call")
|
||||||
|
// note: the containing statement (such as the FunctionCallStatement or the Assignment or the Expression)
|
||||||
|
// must take care of popping this value again at the end!
|
||||||
|
}
|
||||||
|
|
||||||
|
if(saveX)
|
||||||
|
asmgen.restoreRegister(CpuRegister.X, preserveStatusRegisterAfterCall)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translateFuncArguments(parameter: IndexedValue<SubroutineParameter>, value: Expression, sub: Subroutine) {
|
private fun registerArgsViaStackEvaluation(stmt: IFunctionCall, sub: Subroutine) {
|
||||||
val sourceIDt = value.inferType(program)
|
// this is called when one or more of the arguments are 'complex' and
|
||||||
if(!sourceIDt.isKnown)
|
// cannot be assigned to a register easily or risk clobbering other registers.
|
||||||
throw AssemblyError("arg type unknown")
|
|
||||||
val sourceDt = sourceIDt.typeOrElse(DataType.STRUCT)
|
if(sub.parameters.isEmpty())
|
||||||
if(!argumentTypeCompatible(sourceDt, parameter.value.type))
|
return
|
||||||
|
|
||||||
|
// 1. load all arguments reversed onto the stack: first arg goes last (is on top).
|
||||||
|
for (arg in stmt.args.reversed())
|
||||||
|
asmgen.translateExpression(arg)
|
||||||
|
|
||||||
|
var argForCarry: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
|
||||||
|
var argForXregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
|
||||||
|
var argForAregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
|
||||||
|
|
||||||
|
asmgen.out(" inx") // align estack pointer
|
||||||
|
|
||||||
|
for(argi in stmt.args.zip(sub.asmParameterRegisters).withIndex()) {
|
||||||
|
when {
|
||||||
|
argi.value.second.statusflag == Statusflag.Pc -> {
|
||||||
|
require(argForCarry == null)
|
||||||
|
argForCarry = argi
|
||||||
|
}
|
||||||
|
argi.value.second.statusflag != null -> throw AssemblyError("can only use Carry as status flag parameter")
|
||||||
|
argi.value.second.registerOrPair in setOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) -> {
|
||||||
|
require(argForXregister==null)
|
||||||
|
argForXregister = argi
|
||||||
|
}
|
||||||
|
argi.value.second.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.AY) -> {
|
||||||
|
require(argForAregister == null)
|
||||||
|
argForAregister = argi
|
||||||
|
}
|
||||||
|
argi.value.second.registerOrPair == RegisterOrPair.Y -> {
|
||||||
|
asmgen.out(" ldy P8ESTACK_LO+${argi.index},x")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird argument")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(argForCarry!=null) {
|
||||||
|
asmgen.out("""
|
||||||
|
lda P8ESTACK_LO+${argForCarry.index},x
|
||||||
|
beq +
|
||||||
|
sec
|
||||||
|
bcs ++
|
||||||
|
+ clc
|
||||||
|
+ php""") // push the status flags
|
||||||
|
}
|
||||||
|
|
||||||
|
if(argForAregister!=null) {
|
||||||
|
when(argForAregister.value.second.registerOrPair) {
|
||||||
|
RegisterOrPair.A -> asmgen.out(" lda P8ESTACK_LO+${argForAregister.index},x")
|
||||||
|
RegisterOrPair.AY -> asmgen.out(" lda P8ESTACK_LO+${argForAregister.index},x | ldy P8ESTACK_HI+${argForAregister.index},x")
|
||||||
|
else -> throw AssemblyError("weird arg")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(argForXregister!=null) {
|
||||||
|
|
||||||
|
if(argForAregister!=null)
|
||||||
|
asmgen.out(" pha")
|
||||||
|
when(argForXregister.value.second.registerOrPair) {
|
||||||
|
RegisterOrPair.X -> asmgen.out(" lda P8ESTACK_LO+${argForXregister.index},x | tax")
|
||||||
|
RegisterOrPair.AX -> asmgen.out(" ldy P8ESTACK_LO+${argForXregister.index},x | lda P8ESTACK_HI+${argForXregister.index},x | tax | tya")
|
||||||
|
RegisterOrPair.XY -> asmgen.out(" ldy P8ESTACK_HI+${argForXregister.index},x | lda P8ESTACK_LO+${argForXregister.index},x | tax")
|
||||||
|
else -> throw AssemblyError("weird arg")
|
||||||
|
}
|
||||||
|
if(argForAregister!=null)
|
||||||
|
asmgen.out(" pla")
|
||||||
|
} else {
|
||||||
|
repeat(sub.parameters.size - 1) { asmgen.out(" inx") } // unwind stack
|
||||||
|
}
|
||||||
|
|
||||||
|
if(argForCarry!=null)
|
||||||
|
asmgen.out(" plp") // set the carry flag back to correct value
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun argumentViaVariable(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
|
||||||
|
// pass parameter via a regular variable (not via registers)
|
||||||
|
val valueIDt = value.inferType(program)
|
||||||
|
if(!valueIDt.isKnown)
|
||||||
|
throw AssemblyError("unknown dt")
|
||||||
|
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
|
||||||
|
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
|
||||||
throw AssemblyError("argument type incompatible")
|
throw AssemblyError("argument type incompatible")
|
||||||
if(sub.asmParameterRegisters.isEmpty()) {
|
|
||||||
// pass parameter via a variable
|
val varName = asmgen.asmVariableName(sub.scopedname+"."+parameter.value.name)
|
||||||
val paramVar = parameter.value
|
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, parameter.value.type, sub, variableAsmName = varName)
|
||||||
val scopedParamVar = (sub.scopedname+"."+paramVar.name).split(".")
|
val source = AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(tgt)
|
||||||
val target = AssignTarget(null, IdentifierReference(scopedParamVar, sub.position), null, null, sub.position)
|
val asgn = AsmAssignment(source, tgt, false, Position.DUMMY)
|
||||||
target.linkParents(value.parent)
|
asmgen.translateNormalAssignment(asgn)
|
||||||
when (value) {
|
}
|
||||||
is NumericLiteralValue -> {
|
|
||||||
// optimize when the argument is a constant literal
|
private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
|
||||||
when(parameter.value.type) {
|
// pass argument via a register parameter
|
||||||
in ByteDatatypes -> asmgen.assignFromByteConstant(target, value.number.toShort())
|
val valueIDt = value.inferType(program)
|
||||||
in WordDatatypes -> asmgen.assignFromWordConstant(target, value.number.toInt())
|
if(!valueIDt.isKnown)
|
||||||
DataType.FLOAT -> asmgen.assignFromFloatConstant(target, value.number.toDouble())
|
throw AssemblyError("unknown dt")
|
||||||
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as arguments?")
|
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
|
||||||
else -> throw AssemblyError("weird parameter datatype")
|
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
|
||||||
}
|
throw AssemblyError("argument type incompatible")
|
||||||
}
|
|
||||||
is IdentifierReference -> {
|
val paramRegister = sub.asmParameterRegisters[parameter.index]
|
||||||
// optimize when the argument is a variable
|
val statusflag = paramRegister.statusflag
|
||||||
when (parameter.value.type) {
|
val register = paramRegister.registerOrPair
|
||||||
in ByteDatatypes -> asmgen.assignFromByteVariable(target, value)
|
val requiredDt = parameter.value.type
|
||||||
in WordDatatypes -> asmgen.assignFromWordVariable(target, value)
|
if(requiredDt!=valueDt) {
|
||||||
DataType.FLOAT -> asmgen.assignFromFloatVariable(target, value)
|
if(valueDt largerThan requiredDt)
|
||||||
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as arguments?")
|
throw AssemblyError("can only convert byte values to word param types")
|
||||||
else -> throw AssemblyError("weird parameter datatype")
|
}
|
||||||
}
|
when {
|
||||||
}
|
statusflag!=null -> {
|
||||||
is RegisterExpr -> {
|
if(requiredDt!=valueDt)
|
||||||
asmgen.assignFromRegister(target, value.register)
|
throw AssemblyError("for statusflag, byte value is required")
|
||||||
}
|
if (statusflag == Statusflag.Pc) {
|
||||||
is DirectMemoryRead -> {
|
// this param needs to be set last, right before the jsr
|
||||||
when(value.addressExpression) {
|
// for now, this is already enforced on the subroutine definition by the Ast Checker
|
||||||
|
when(value) {
|
||||||
is NumericLiteralValue -> {
|
is NumericLiteralValue -> {
|
||||||
val address = (value.addressExpression as NumericLiteralValue).number.toInt()
|
val carrySet = value.number.toInt() != 0
|
||||||
asmgen.assignFromMemoryByte(target, address, null)
|
asmgen.out(if(carrySet) " sec" else " clc")
|
||||||
}
|
}
|
||||||
is IdentifierReference -> {
|
is IdentifierReference -> {
|
||||||
asmgen.assignFromMemoryByte(target, null, value.addressExpression as IdentifierReference)
|
val sourceName = asmgen.asmVariableName(value)
|
||||||
}
|
asmgen.out("""
|
||||||
else -> {
|
pha
|
||||||
asmgen.translateExpression(value.addressExpression)
|
|
||||||
asmgen.out(" jsr prog8_lib.read_byte_from_address | inx")
|
|
||||||
asmgen.assignFromRegister(target, Register.A)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
asmgen.translateExpression(value)
|
|
||||||
asmgen.assignFromEvalResult(target)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// pass parameter via a register parameter
|
|
||||||
val paramRegister = sub.asmParameterRegisters[parameter.index]
|
|
||||||
val statusflag = paramRegister.statusflag
|
|
||||||
val register = paramRegister.registerOrPair
|
|
||||||
val stack = paramRegister.stack
|
|
||||||
when {
|
|
||||||
stack -> {
|
|
||||||
// push arg onto the stack
|
|
||||||
// note: argument order is reversed (first argument will be deepest on the stack)
|
|
||||||
asmgen.translateExpression(value)
|
|
||||||
}
|
|
||||||
statusflag!=null -> {
|
|
||||||
if (statusflag == Statusflag.Pc) {
|
|
||||||
// this param needs to be set last, right before the jsr
|
|
||||||
// for now, this is already enforced on the subroutine definition by the Ast Checker
|
|
||||||
when(value) {
|
|
||||||
is NumericLiteralValue -> {
|
|
||||||
val carrySet = value.number.toInt() != 0
|
|
||||||
asmgen.out(if(carrySet) " sec" else " clc")
|
|
||||||
}
|
|
||||||
is IdentifierReference -> {
|
|
||||||
val sourceName = asmgen.asmIdentifierName(value)
|
|
||||||
asmgen.out("""
|
|
||||||
lda $sourceName
|
lda $sourceName
|
||||||
beq +
|
beq +
|
||||||
sec
|
sec
|
||||||
bcs ++
|
bcs ++
|
||||||
+ clc
|
+ clc
|
||||||
+
|
+ pla
|
||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
is RegisterExpr -> {
|
else -> {
|
||||||
when(value.register) {
|
asmgen.translateExpression(value) // todo directly into A
|
||||||
Register.A -> asmgen.out(" cmp #0")
|
asmgen.out("""
|
||||||
Register.X -> asmgen.out(" txa")
|
inx
|
||||||
Register.Y -> asmgen.out(" tya")
|
pha
|
||||||
}
|
lda P8ESTACK_LO,x
|
||||||
asmgen.out("""
|
|
||||||
beq +
|
|
||||||
sec
|
|
||||||
bcs ++
|
|
||||||
+ clc
|
|
||||||
+
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
asmgen.translateExpression(value)
|
|
||||||
asmgen.out("""
|
|
||||||
inx
|
|
||||||
lda ${MachineDefinition.ESTACK_LO_HEX},x
|
|
||||||
beq +
|
beq +
|
||||||
sec
|
sec
|
||||||
bcs ++
|
bcs ++
|
||||||
+ clc
|
+ clc
|
||||||
+
|
+ pla
|
||||||
""")
|
""")
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else throw AssemblyError("can only use Carry as status flag parameter")
|
|
||||||
}
|
|
||||||
register!=null && register.name.length==1 -> {
|
|
||||||
when (value) {
|
|
||||||
is NumericLiteralValue -> {
|
|
||||||
val target = AssignTarget(Register.valueOf(register.name), null, null, null, sub.position)
|
|
||||||
target.linkParents(value.parent)
|
|
||||||
asmgen.assignFromByteConstant(target, value.number.toShort())
|
|
||||||
}
|
|
||||||
is IdentifierReference -> {
|
|
||||||
val target = AssignTarget(Register.valueOf(register.name), null, null, null, sub.position)
|
|
||||||
target.linkParents(value.parent)
|
|
||||||
asmgen.assignFromByteVariable(target, value)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
asmgen.translateExpression(value)
|
|
||||||
when(register) {
|
|
||||||
RegisterOrPair.A -> asmgen.out(" inx | lda ${MachineDefinition.ESTACK_LO_HEX},x")
|
|
||||||
RegisterOrPair.X -> throw AssemblyError("can't pop into X register - use a variable instead")
|
|
||||||
RegisterOrPair.Y -> asmgen.out(" inx | ldy ${MachineDefinition.ESTACK_LO_HEX},x")
|
|
||||||
else -> throw AssemblyError("cannot assign to register pair")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
register!=null && register.name.length==2 -> {
|
else throw AssemblyError("can only use Carry as status flag parameter")
|
||||||
// register pair as a 16-bit value (only possible for subroutine parameters)
|
}
|
||||||
when (value) {
|
else -> {
|
||||||
is NumericLiteralValue -> {
|
// via register or register pair
|
||||||
// optimize when the argument is a constant literal
|
val target = AsmAssignTarget.fromRegisters(register!!, sub, program, asmgen)
|
||||||
val hex = value.number.toHex()
|
if(requiredDt largerThan valueDt) {
|
||||||
when (register) {
|
// we need to sign extend the source, do this via temporary word variable
|
||||||
RegisterOrPair.AX -> asmgen.out(" lda #<$hex | ldx #>$hex")
|
val scratchVar = asmgen.asmVariableName("P8ZP_SCRATCH_W1")
|
||||||
RegisterOrPair.AY -> asmgen.out(" lda #<$hex | ldy #>$hex")
|
val scratchTarget = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UBYTE, sub, scratchVar)
|
||||||
RegisterOrPair.XY -> asmgen.out(" ldx #<$hex | ldy #>$hex")
|
val source = AsmAssignSource.fromAstSource(value, program, asmgen)
|
||||||
else -> {}
|
asmgen.translateNormalAssignment(AsmAssignment(source, scratchTarget, false, value.position))
|
||||||
}
|
asmgen.signExtendVariableLsb(scratchVar, valueDt)
|
||||||
}
|
val src = AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, DataType.UWORD, scratchVar)
|
||||||
is AddressOf -> {
|
asmgen.translateNormalAssignment(AsmAssignment(src, target, false, Position.DUMMY))
|
||||||
// optimize when the argument is an address of something
|
}
|
||||||
val sourceName = asmgen.asmIdentifierName(value.identifier)
|
else {
|
||||||
when (register) {
|
val src = if(valueDt in PassByReferenceDatatypes) {
|
||||||
RegisterOrPair.AX -> asmgen.out(" lda #<$sourceName | ldx #>$sourceName")
|
if(value is IdentifierReference) {
|
||||||
RegisterOrPair.AY -> asmgen.out(" lda #<$sourceName | ldy #>$sourceName")
|
val addr = AddressOf(value, Position.DUMMY)
|
||||||
RegisterOrPair.XY -> asmgen.out(" ldx #<$sourceName | ldy #>$sourceName")
|
AsmAssignSource.fromAstSource(addr, program, asmgen).adjustSignedUnsigned(target)
|
||||||
else -> {}
|
} else {
|
||||||
}
|
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
|
||||||
}
|
|
||||||
is IdentifierReference -> {
|
|
||||||
val sourceName = asmgen.asmIdentifierName(value)
|
|
||||||
when (register) {
|
|
||||||
RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx $sourceName+1")
|
|
||||||
RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy $sourceName+1")
|
|
||||||
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName | ldy $sourceName+1")
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
asmgen.translateExpression(value)
|
|
||||||
if (register == RegisterOrPair.AX || register == RegisterOrPair.XY)
|
|
||||||
throw AssemblyError("can't use X register here - use a variable")
|
|
||||||
else if (register == RegisterOrPair.AY)
|
|
||||||
asmgen.out(" inx | lda ${MachineDefinition.ESTACK_LO_HEX},x | ldy ${MachineDefinition.ESTACK_HI_HEX},x")
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
|
||||||
}
|
}
|
||||||
|
asmgen.translateNormalAssignment(AsmAssignment(src, target, false, Position.DUMMY))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun argumentTypeCompatible(argType: DataType, paramType: DataType): Boolean {
|
private fun isArgumentTypeCompatible(argType: DataType, paramType: DataType): Boolean {
|
||||||
if(argType isAssignableTo paramType)
|
if(argType isAssignableTo paramType)
|
||||||
return true
|
return true
|
||||||
|
if(argType in ByteDatatypes && paramType in ByteDatatypes)
|
||||||
|
return true
|
||||||
|
if(argType in WordDatatypes && paramType in WordDatatypes)
|
||||||
|
return true
|
||||||
|
|
||||||
// we have a special rule for some types.
|
// we have a special rule for some types.
|
||||||
// strings are assignable to UWORD, for example, and vice versa
|
// strings are assignable to UWORD, for example, and vice versa
|
||||||
if(argType in StringDatatypes && paramType==DataType.UWORD)
|
if(argType==DataType.STR && paramType==DataType.UWORD)
|
||||||
return true
|
return true
|
||||||
if(argType==DataType.UWORD && paramType in StringDatatypes)
|
if(argType==DataType.UWORD && paramType == DataType.STR)
|
||||||
return true
|
return true
|
||||||
|
|
||||||
return false
|
return false
|
||||||
|
@ -4,39 +4,22 @@ import prog8.ast.Program
|
|||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
import prog8.ast.expressions.IdentifierReference
|
import prog8.ast.expressions.IdentifierReference
|
||||||
import prog8.ast.expressions.NumericLiteralValue
|
import prog8.ast.expressions.NumericLiteralValue
|
||||||
import prog8.ast.expressions.RegisterExpr
|
|
||||||
import prog8.ast.statements.PostIncrDecr
|
import prog8.ast.statements.PostIncrDecr
|
||||||
import prog8.compiler.target.c64.MachineDefinition
|
import prog8.compiler.AssemblyError
|
||||||
import prog8.compiler.toHex
|
import prog8.compiler.toHex
|
||||||
|
|
||||||
|
|
||||||
internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||||
internal fun translate(stmt: PostIncrDecr) {
|
internal fun translate(stmt: PostIncrDecr) {
|
||||||
val incr = stmt.operator=="++"
|
val incr = stmt.operator=="++"
|
||||||
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 targetRegister = stmt.target.register
|
val scope = stmt.definingSubroutine()
|
||||||
when {
|
when {
|
||||||
targetRegister!=null -> {
|
|
||||||
when(targetRegister) {
|
|
||||||
Register.A -> {
|
|
||||||
if(incr)
|
|
||||||
asmgen.out(" clc | adc #1 ")
|
|
||||||
else
|
|
||||||
asmgen.out(" sec | sbc #1 ")
|
|
||||||
}
|
|
||||||
Register.X -> {
|
|
||||||
if(incr) asmgen.out(" inx") else asmgen.out(" dex")
|
|
||||||
}
|
|
||||||
Register.Y -> {
|
|
||||||
if(incr) asmgen.out(" iny") else asmgen.out(" dey")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
targetIdent!=null -> {
|
targetIdent!=null -> {
|
||||||
val what = asmgen.asmIdentifierName(targetIdent)
|
val what = asmgen.asmVariableName(targetIdent)
|
||||||
val dt = stmt.target.inferType(program, stmt).typeOrElse(DataType.STRUCT)
|
when (stmt.target.inferType(program).typeOrElse(DataType.STRUCT)) {
|
||||||
when (dt) {
|
|
||||||
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)
|
||||||
@ -51,99 +34,100 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
|||||||
}
|
}
|
||||||
DataType.FLOAT -> {
|
DataType.FLOAT -> {
|
||||||
asmgen.out(" lda #<$what | ldy #>$what")
|
asmgen.out(" lda #<$what | ldy #>$what")
|
||||||
asmgen.out(if(incr) " jsr c64flt.inc_var_f" else " jsr c64flt.dec_var_f")
|
asmgen.out(if(incr) " jsr floats.inc_var_f" else " jsr floats.dec_var_f")
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("need numeric type")
|
else -> throw AssemblyError("need numeric type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
targetMemory!=null -> {
|
targetMemory!=null -> {
|
||||||
val addressExpr = targetMemory.addressExpression
|
when (val addressExpr = targetMemory.addressExpression) {
|
||||||
when (addressExpr) {
|
|
||||||
is NumericLiteralValue -> {
|
is NumericLiteralValue -> {
|
||||||
val what = addressExpr.number.toHex()
|
val what = addressExpr.number.toHex()
|
||||||
asmgen.out(if(incr) " inc $what" else " dec $what")
|
asmgen.out(if(incr) " inc $what" else " dec $what")
|
||||||
}
|
}
|
||||||
is IdentifierReference -> {
|
is IdentifierReference -> {
|
||||||
val what = asmgen.asmIdentifierName(addressExpr)
|
val what = asmgen.asmVariableName(addressExpr)
|
||||||
asmgen.out(if(incr) " inc $what" else " dec $what")
|
asmgen.out(" lda $what | sta (+) +1 | lda $what+1 | sta (+) +2")
|
||||||
|
if(incr)
|
||||||
|
asmgen.out("+\tinc ${'$'}ffff\t; modified")
|
||||||
|
else
|
||||||
|
asmgen.out("+\tdec ${'$'}ffff\t; modified")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
asmgen.translateExpression(addressExpr) // todo directly into AY?
|
||||||
|
asmgen.out("""
|
||||||
|
inx
|
||||||
|
lda P8ESTACK_LO,x
|
||||||
|
sta (+) + 1
|
||||||
|
lda P8ESTACK_HI,x
|
||||||
|
sta (+) + 2
|
||||||
|
""")
|
||||||
|
if(incr)
|
||||||
|
asmgen.out("+\tinc ${'$'}ffff\t; modified")
|
||||||
|
else
|
||||||
|
asmgen.out("+\tdec ${'$'}ffff\t; modified")
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("weird target type $targetMemory")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
targetArrayIdx!=null -> {
|
targetArrayIdx!=null -> {
|
||||||
val index = targetArrayIdx.arrayspec.index
|
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.arrayvar)
|
||||||
val what = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
val elementDt = targetArrayIdx.inferType(program).typeOrElse(DataType.STRUCT)
|
||||||
val arrayDt = targetArrayIdx.identifier.inferType(program).typeOrElse(DataType.STRUCT)
|
if(targetArrayIdx.indexer.indexNum!=null) {
|
||||||
val elementDt = ArrayElementTypes.getValue(arrayDt)
|
val indexValue = targetArrayIdx.indexer.constIndex()!! * elementDt.memorySize()
|
||||||
when(index) {
|
when(elementDt) {
|
||||||
is NumericLiteralValue -> {
|
in ByteDatatypes -> asmgen.out(if (incr) " inc $asmArrayvarname+$indexValue" else " dec $asmArrayvarname+$indexValue")
|
||||||
val indexValue = index.number.toInt() * elementDt.memorySize()
|
in WordDatatypes -> {
|
||||||
when(elementDt) {
|
if(incr)
|
||||||
in ByteDatatypes -> asmgen.out(if (incr) " inc $what+$indexValue" else " dec $what+$indexValue")
|
asmgen.out(" inc $asmArrayvarname+$indexValue | bne + | inc $asmArrayvarname+$indexValue+1 |+")
|
||||||
in WordDatatypes -> {
|
else
|
||||||
if(incr)
|
asmgen.out("""
|
||||||
asmgen.out(" inc $what+$indexValue | bne + | inc $what+$indexValue+1 |+")
|
lda $asmArrayvarname+$indexValue
|
||||||
else
|
bne +
|
||||||
asmgen.out("""
|
dec $asmArrayvarname+$indexValue+1
|
||||||
lda $what+$indexValue
|
+ dec $asmArrayvarname+$indexValue
|
||||||
bne +
|
|
||||||
dec $what+$indexValue+1
|
|
||||||
+ dec $what+$indexValue
|
|
||||||
""")
|
""")
|
||||||
}
|
|
||||||
DataType.FLOAT -> {
|
|
||||||
asmgen.out(" lda #<$what+$indexValue | ldy #>$what+$indexValue")
|
|
||||||
asmgen.out(if(incr) " jsr c64flt.inc_var_f" else " jsr c64flt.dec_var_f")
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("need numeric type")
|
|
||||||
}
|
}
|
||||||
}
|
DataType.FLOAT -> {
|
||||||
is RegisterExpr -> {
|
asmgen.out(" lda #<$asmArrayvarname+$indexValue | ldy #>$asmArrayvarname+$indexValue")
|
||||||
// TODO optimize common cases
|
asmgen.out(if(incr) " jsr floats.inc_var_f" else " jsr floats.dec_var_f")
|
||||||
asmgen.translateArrayIndexIntoA(targetArrayIdx)
|
}
|
||||||
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
|
else -> throw AssemblyError("need numeric type")
|
||||||
}
|
|
||||||
is IdentifierReference -> {
|
|
||||||
// TODO optimize common cases
|
|
||||||
asmgen.translateArrayIndexIntoA(targetArrayIdx)
|
|
||||||
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
// TODO optimize common cases
|
|
||||||
asmgen.translateArrayIndexIntoA(targetArrayIdx)
|
|
||||||
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird target type ${stmt.target}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun incrDecrArrayvalueWithIndexA(incr: Boolean, arrayDt: DataType, arrayVarName: String) {
|
|
||||||
asmgen.out(" stx ${MachineDefinition.C64Zeropage.SCRATCH_REG_X} | tax")
|
|
||||||
when(arrayDt) {
|
|
||||||
DataType.STR, DataType.STR_S,
|
|
||||||
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
|
||||||
asmgen.out(if(incr) " inc $arrayVarName,x" else " dec $arrayVarName,x")
|
|
||||||
}
|
|
||||||
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
|
||||||
if(incr)
|
|
||||||
asmgen.out(" inc $arrayVarName,x | bne + | inc $arrayVarName+1,x |+")
|
|
||||||
else
|
else
|
||||||
asmgen.out("""
|
{
|
||||||
lda $arrayVarName,x
|
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, elementDt, CpuRegister.A)
|
||||||
bne +
|
asmgen.saveRegister(CpuRegister.X, false, scope!!)
|
||||||
dec $arrayVarName+1,x
|
asmgen.out(" tax")
|
||||||
+ dec $arrayVarName
|
when(elementDt) {
|
||||||
|
in ByteDatatypes -> {
|
||||||
|
asmgen.out(if(incr) " inc $asmArrayvarname,x" else " dec $asmArrayvarname,x")
|
||||||
|
}
|
||||||
|
in WordDatatypes -> {
|
||||||
|
if(incr)
|
||||||
|
asmgen.out(" inc $asmArrayvarname,x | bne + | inc $asmArrayvarname+1,x |+")
|
||||||
|
else
|
||||||
|
asmgen.out("""
|
||||||
|
lda $asmArrayvarname,x
|
||||||
|
bne +
|
||||||
|
dec $asmArrayvarname+1,x
|
||||||
|
+ dec $asmArrayvarname
|
||||||
""")
|
""")
|
||||||
|
}
|
||||||
|
DataType.FLOAT -> {
|
||||||
|
asmgen.out("""
|
||||||
|
ldy #>$asmArrayvarname
|
||||||
|
clc
|
||||||
|
adc #<$asmArrayvarname
|
||||||
|
bcc +
|
||||||
|
iny
|
||||||
|
+ jsr floats.inc_var_f""")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird array elt dt")
|
||||||
|
}
|
||||||
|
asmgen.restoreRegister(CpuRegister.X, false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
DataType.ARRAY_F -> {
|
|
||||||
asmgen.out(" lda #<$arrayVarName | ldy #>$arrayVarName")
|
|
||||||
asmgen.out(if(incr) " jsr c64flt.inc_indexed_var_f" else " jsr c64flt.dec_indexed_var_f")
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird array dt")
|
|
||||||
}
|
}
|
||||||
asmgen.out(" ldx ${MachineDefinition.C64Zeropage.SCRATCH_REG_X}")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user